系统管理基本搞定
This commit is contained in:
parent
5ed24a8003
commit
e830896d47
615
controllers/platform_domain.go
Normal file
615
controllers/platform_domain.go
Normal file
@ -0,0 +1,615 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// PlatformDomainPoolController 主域名池管理
|
||||
type PlatformDomainPoolController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// PlatformTenantDomainController 租户域名管理
|
||||
type PlatformTenantDomainController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func requirePlatform(c *beego.Controller) (*jwtutil.Claims, error) {
|
||||
auth := c.Ctx.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return nil, fmt.Errorf("未登录")
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return nil, fmt.Errorf("认证信息格式错误")
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无效的token")
|
||||
}
|
||||
if claims.UserType != "platform" {
|
||||
return nil, fmt.Errorf("无权访问")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func jsonErr(c *beego.Controller, httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// ===== 主域名池 =====
|
||||
|
||||
// Index GET /platform/domain/pool/index?page=&pageSize=&main_domain=&status=
|
||||
func (c *PlatformDomainPoolController) Index() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
page, _ := c.GetInt("page", 1)
|
||||
pageSize, _ := c.GetInt("pageSize", 10)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 200 {
|
||||
pageSize = 200
|
||||
}
|
||||
|
||||
mainDomain := strings.TrimSpace(c.GetString("main_domain"))
|
||||
statusStr := strings.TrimSpace(c.GetString("status"))
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.SystemDomainPool)).Filter("delete_time__isnull", true)
|
||||
if mainDomain != "" {
|
||||
qs = qs.Filter("main_domain__icontains", mainDomain)
|
||||
}
|
||||
if statusStr != "" {
|
||||
if st, err := strconv.Atoi(statusStr); err == nil {
|
||||
qs = qs.Filter("status", st)
|
||||
}
|
||||
}
|
||||
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
var rows []models.SystemDomainPool
|
||||
_, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for i := range rows {
|
||||
item := map[string]interface{}{
|
||||
"id": rows[i].ID,
|
||||
"main_domain": rows[i].MainDomain,
|
||||
"status": rows[i].Status,
|
||||
"create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"),
|
||||
"update_time": "",
|
||||
}
|
||||
if rows[i].UpdateTime != nil {
|
||||
item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{
|
||||
"list": list,
|
||||
"total": total,
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetEnabledDomains GET /platform/domain/pool/getEnabledDomains
|
||||
func (c *PlatformDomainPoolController) GetEnabledDomains() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
var rows []models.SystemDomainPool
|
||||
_, err := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("-id").
|
||||
All(&rows)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取主域名失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
out := make([]map[string]interface{}, 0, len(rows))
|
||||
for i := range rows {
|
||||
out = append(out, map[string]interface{}{
|
||||
"id": rows[i].ID,
|
||||
"main_domain": rows[i].MainDomain,
|
||||
"status": rows[i].Status,
|
||||
})
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type domainPoolPayload struct {
|
||||
ID uint64 `json:"id"`
|
||||
MainDomain string `json:"main_domain"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
// Create POST /platform/domain/pool/create
|
||||
func (c *PlatformDomainPoolController) Create() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p domainPoolPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
md := strings.TrimSpace(p.MainDomain)
|
||||
if md == "" {
|
||||
jsonErr(&c.Controller, 400, 400, "主域名不能为空")
|
||||
return
|
||||
}
|
||||
if p.Status != 0 && p.Status != 1 {
|
||||
p.Status = 1
|
||||
}
|
||||
// 简单去重
|
||||
cnt, _ := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("main_domain", md).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if cnt > 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "主域名已存在")
|
||||
return
|
||||
}
|
||||
row := &models.SystemDomainPool{MainDomain: md, Status: p.Status}
|
||||
if _, err := models.Orm.Insert(row); err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Update POST /platform/domain/pool/update
|
||||
func (c *PlatformDomainPoolController) Update() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p domainPoolPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.ID == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "id 不能为空")
|
||||
return
|
||||
}
|
||||
md := strings.TrimSpace(p.MainDomain)
|
||||
if md == "" {
|
||||
jsonErr(&c.Controller, 400, 400, "主域名不能为空")
|
||||
return
|
||||
}
|
||||
if p.Status != 0 && p.Status != 1 {
|
||||
p.Status = 1
|
||||
}
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("id", p.ID).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"main_domain": md, "status": p.Status, "update_time": now})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "更新失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
jsonErr(&c.Controller, 404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete DELETE /platform/domain/pool/delete/:id
|
||||
func (c *PlatformDomainPoolController) Delete() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
jsonErr(&c.Controller, 404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// ToggleStatus POST /platform/domain/pool/toggleStatus body:{id}
|
||||
func (c *PlatformDomainPoolController) ToggleStatus() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
ID uint64 `json:"id"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var row models.SystemDomainPool
|
||||
if err := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("id", p.ID).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row); err != nil {
|
||||
jsonErr(&c.Controller, 404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
newStatus := int8(1)
|
||||
if row.Status == 1 {
|
||||
newStatus = 0
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("id", p.ID).
|
||||
Update(map[string]interface{}{"status": newStatus, "update_time": now})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "切换失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// ===== 租户域名 =====
|
||||
|
||||
// Index GET /platform/domain/tenant/index?page=&pageSize=&tid=&status=&sub_domain=
|
||||
func (c *PlatformTenantDomainController) Index() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
page, _ := c.GetInt("page", 1)
|
||||
pageSize, _ := c.GetInt("pageSize", 10)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 200 {
|
||||
pageSize = 200
|
||||
}
|
||||
|
||||
tid, _ := c.GetUint64("tid")
|
||||
statusStr := strings.TrimSpace(c.GetString("status"))
|
||||
subDomain := strings.TrimSpace(c.GetString("sub_domain"))
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("delete_time__isnull", true)
|
||||
if tid > 0 {
|
||||
qs = qs.Filter("tid", tid)
|
||||
}
|
||||
if statusStr != "" {
|
||||
if st, err := strconv.Atoi(statusStr); err == nil {
|
||||
qs = qs.Filter("status", st)
|
||||
}
|
||||
}
|
||||
if subDomain != "" {
|
||||
qs = qs.Filter("sub_domain__icontains", subDomain)
|
||||
}
|
||||
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
var rows []models.SystemTenantDomain
|
||||
_, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
list := make([]models.SystemTenantDomain, 0, len(rows))
|
||||
list = append(list, rows...)
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{"list": list, "total": total},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// MyDomains GET /platform/domain/tenant/myDomains?tid=1
|
||||
func (c *PlatformTenantDomainController) MyDomains() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid, _ := c.GetUint64("tid")
|
||||
if tid == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "租户ID不能为空")
|
||||
return
|
||||
}
|
||||
var rows []models.SystemTenantDomain
|
||||
_, err := models.Orm.QueryTable(new(models.SystemTenantDomain)).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("-id").
|
||||
All(&rows)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": rows}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
var subDomainRe = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$`)
|
||||
|
||||
// Apply POST /platform/domain/tenant/apply body:{tid,sub_domain,main_domain}
|
||||
func (c *PlatformTenantDomainController) Apply() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
Tid uint64 `json:"tid"`
|
||||
SubDomain string `json:"sub_domain"`
|
||||
MainDomain string `json:"main_domain"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.Tid == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "租户ID不能为空")
|
||||
return
|
||||
}
|
||||
sub := strings.TrimSpace(p.SubDomain)
|
||||
main := strings.TrimSpace(p.MainDomain)
|
||||
if sub == "" {
|
||||
jsonErr(&c.Controller, 400, 400, "二级域名前缀不能为空")
|
||||
return
|
||||
}
|
||||
if main == "" {
|
||||
jsonErr(&c.Controller, 400, 400, "请选择主域名")
|
||||
return
|
||||
}
|
||||
if !subDomainRe.MatchString(sub) {
|
||||
jsonErr(&c.Controller, 400, 400, "二级域名前缀格式不正确")
|
||||
return
|
||||
}
|
||||
|
||||
// 该租户是否已有域名
|
||||
cnt, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)).
|
||||
Filter("tid", p.Tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if cnt > 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "该租户已有域名,请删除后再次申请")
|
||||
return
|
||||
}
|
||||
|
||||
// 主域名存在且启用
|
||||
var pool models.SystemDomainPool
|
||||
if err := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("main_domain", main).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&pool); err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "主域名不存在或已禁用")
|
||||
return
|
||||
}
|
||||
|
||||
// 二级域名是否已被使用(同主域名下)
|
||||
used, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)).
|
||||
Filter("sub_domain", sub).
|
||||
Filter("main_domain", main).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if used > 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "该二级域名已被使用")
|
||||
return
|
||||
}
|
||||
|
||||
full := sub + "." + main
|
||||
now := time.Now()
|
||||
tid := p.Tid
|
||||
row := &models.SystemTenantDomain{
|
||||
Tid: &tid,
|
||||
SubDomain: &sub,
|
||||
MainDomain: &main,
|
||||
FullDomain: &full,
|
||||
Status: 0,
|
||||
CreateTime: now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
id, err := models.Orm.Insert(row)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "申请失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "申请提交成功,等待审核", "data": map[string]interface{}{"id": uint64(id)}}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Audit POST /platform/domain/tenant/audit body:{id,action} action=approve/reject
|
||||
func (c *PlatformTenantDomainController) Audit() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
ID uint64 `json:"id"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var row models.SystemTenantDomain
|
||||
if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil {
|
||||
jsonErr(&c.Controller, 404, 404, "域名不存在")
|
||||
return
|
||||
}
|
||||
if row.Status != 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "该域名已审核过了")
|
||||
return
|
||||
}
|
||||
newStatus := 2
|
||||
msg := "已拒绝"
|
||||
if strings.ToLower(strings.TrimSpace(p.Action)) == "approve" {
|
||||
newStatus = 1
|
||||
msg = "审核通过"
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{
|
||||
"status": newStatus,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "审核失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// ToggleStatus POST /platform/domain/tenant/toggleStatus body:{id}
|
||||
func (c *PlatformTenantDomainController) ToggleStatus() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
ID uint64 `json:"id"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var row models.SystemTenantDomain
|
||||
if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil {
|
||||
jsonErr(&c.Controller, 404, 404, "域名不存在")
|
||||
return
|
||||
}
|
||||
if row.Status == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "审核中不可操作")
|
||||
return
|
||||
}
|
||||
newStatus := 2
|
||||
if row.Status == 2 {
|
||||
newStatus = 1
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{
|
||||
"status": newStatus,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "操作失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete DELETE /platform/domain/tenant/delete/:id
|
||||
func (c *PlatformTenantDomainController) Delete() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.SystemTenantDomain)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
jsonErr(&c.Controller, 404, 404, "域名不存在")
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// 用于复杂筛选时可扩展:当前保留 orm.Condition import,避免被 gofmt 删除
|
||||
var _ = orm.NewCondition
|
||||
351
controllers/platform_operation_log.go
Normal file
351
controllers/platform_operation_log.go
Normal file
@ -0,0 +1,351 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// PlatformOperationLogController 操作日志(yz_system_operation_log)
|
||||
type PlatformOperationLogController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (c *PlatformOperationLogController) platformClaims() (*jwtutil.Claims, error) {
|
||||
auth := c.Ctx.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return nil, fmt.Errorf("未登录")
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return nil, fmt.Errorf("认证信息格式错误")
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无效的token")
|
||||
}
|
||||
if claims.UserType != "platform" {
|
||||
return nil, fmt.Errorf("无权访问")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (c *PlatformOperationLogController) jsonErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// List GET /platform/operationLogs?page=1&pageSize=20&keyword=&module=&action=&status=&startTime=&endTime=
|
||||
func (c *PlatformOperationLogController) List() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := c.GetInt("page", 1)
|
||||
pageSize, _ := c.GetInt("pageSize", 20)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
if pageSize > 200 {
|
||||
pageSize = 200
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
module := strings.TrimSpace(c.GetString("module"))
|
||||
action := strings.TrimSpace(c.GetString("action"))
|
||||
statusStr := strings.TrimSpace(c.GetString("status"))
|
||||
startTimeStr := strings.TrimSpace(c.GetString("startTime"))
|
||||
endTimeStr := strings.TrimSpace(c.GetString("endTime"))
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.SystemOperationLog)).Filter("delete_time__isnull", true)
|
||||
|
||||
// 条件拼装
|
||||
cond := orm.NewCondition()
|
||||
needCond := false
|
||||
|
||||
if module != "" {
|
||||
cond = cond.And("module", module)
|
||||
needCond = true
|
||||
}
|
||||
if action != "" {
|
||||
cond = cond.And("action", action)
|
||||
needCond = true
|
||||
}
|
||||
if statusStr != "" {
|
||||
if st, err := strconv.Atoi(statusStr); err == nil {
|
||||
cond = cond.And("status", st)
|
||||
needCond = true
|
||||
}
|
||||
}
|
||||
if keyword != "" {
|
||||
kw := orm.NewCondition().
|
||||
Or("module__icontains", keyword).
|
||||
Or("action__icontains", keyword).
|
||||
Or("method__icontains", keyword).
|
||||
Or("url__icontains", keyword).
|
||||
Or("ip__icontains", keyword).
|
||||
Or("user_agent__icontains", keyword)
|
||||
if uid, err := strconv.ParseUint(keyword, 10, 64); err == nil && uid > 0 {
|
||||
kw = kw.Or("user_id", uid)
|
||||
}
|
||||
cond = cond.AndCond(kw)
|
||||
needCond = true
|
||||
}
|
||||
if t, err := parseTimeFlexible(startTimeStr); err == nil && !t.IsZero() {
|
||||
cond = cond.And("create_time__gte", t)
|
||||
needCond = true
|
||||
}
|
||||
if t, err := parseTimeFlexible(endTimeStr); err == nil && !t.IsZero() {
|
||||
cond = cond.And("create_time__lte", t)
|
||||
needCond = true
|
||||
}
|
||||
|
||||
if needCond {
|
||||
qs = qs.SetCond(cond)
|
||||
}
|
||||
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "获取操作日志失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var rows []models.SystemOperationLog
|
||||
_, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows)
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "获取操作日志失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for i := range rows {
|
||||
item := map[string]interface{}{
|
||||
"id": rows[i].ID,
|
||||
"tid": rows[i].Tid,
|
||||
"user_id": rows[i].UserID,
|
||||
"module": rows[i].Module,
|
||||
"action": rows[i].Action,
|
||||
"method": rows[i].Method,
|
||||
"url": rows[i].URL,
|
||||
"ip": rows[i].IP,
|
||||
"user_agent": rows[i].UserAgent,
|
||||
"request_data": rows[i].RequestData,
|
||||
"response_data": rows[i].ResponseData,
|
||||
"status": rows[i].Status,
|
||||
"error_message": rows[i].ErrorMessage,
|
||||
"execution_time": rows[i].ExecutionTime,
|
||||
"create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"),
|
||||
"update_time": "",
|
||||
}
|
||||
if rows[i].UpdateTime != nil {
|
||||
item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{
|
||||
"list": list,
|
||||
"total": total,
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Detail GET /platform/operationLogs/:id
|
||||
func (c *PlatformOperationLogController) Detail() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
c.jsonErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
var row models.SystemOperationLog
|
||||
err = models.Orm.QueryTable(new(models.SystemOperationLog)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row)
|
||||
if err != nil {
|
||||
c.jsonErr(404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
out := map[string]interface{}{
|
||||
"id": row.ID,
|
||||
"tid": row.Tid,
|
||||
"user_id": row.UserID,
|
||||
"module": row.Module,
|
||||
"action": row.Action,
|
||||
"method": row.Method,
|
||||
"url": row.URL,
|
||||
"ip": row.IP,
|
||||
"user_agent": row.UserAgent,
|
||||
"request_data": row.RequestData,
|
||||
"response_data": row.ResponseData,
|
||||
"status": row.Status,
|
||||
"error_message": row.ErrorMessage,
|
||||
"execution_time": row.ExecutionTime,
|
||||
"create_time": row.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete DELETE /platform/operationLogs/:id
|
||||
func (c *PlatformOperationLogController) Delete() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
c.jsonErr(400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.SystemOperationLog)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"delete_time": now})
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "删除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
c.jsonErr(404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type batchDeletePayload struct {
|
||||
IDs []uint64 `json:"ids"`
|
||||
}
|
||||
|
||||
// BatchDelete POST /platform/operationLogs/batchDelete
|
||||
func (c *PlatformOperationLogController) BatchDelete() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p batchDeletePayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.jsonErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if len(p.IDs) == 0 {
|
||||
c.jsonErr(400, 400, "请选择要删除的日志")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.SystemOperationLog)).
|
||||
Filter("id__in", p.IDs).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"delete_time": now})
|
||||
if err != nil {
|
||||
c.jsonErr(500, 500, "批量删除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Statistics GET /platform/operationLogs/statistics
|
||||
// 供前端筛选项:modules/actions
|
||||
func (c *PlatformOperationLogController) Statistics() {
|
||||
if _, err := c.platformClaims(); err != nil {
|
||||
c.jsonErr(401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var moduleRows []models.SystemOperationLog
|
||||
_, _ = models.Orm.QueryTable(new(models.SystemOperationLog)).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("module__isnull", false).
|
||||
Limit(1000).
|
||||
All(&moduleRows, "Module")
|
||||
modSet := map[string]struct{}{}
|
||||
for i := range moduleRows {
|
||||
m := strings.TrimSpace(moduleRows[i].Module)
|
||||
if m != "" {
|
||||
modSet[m] = struct{}{}
|
||||
}
|
||||
}
|
||||
modules := make([]string, 0, len(modSet))
|
||||
for k := range modSet {
|
||||
modules = append(modules, k)
|
||||
}
|
||||
|
||||
var actionRows []models.SystemOperationLog
|
||||
_, _ = models.Orm.QueryTable(new(models.SystemOperationLog)).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("action__isnull", false).
|
||||
Limit(1000).
|
||||
All(&actionRows, "Action")
|
||||
actSet := map[string]struct{}{}
|
||||
for i := range actionRows {
|
||||
a := strings.TrimSpace(actionRows[i].Action)
|
||||
if a != "" {
|
||||
actSet[a] = struct{}{}
|
||||
}
|
||||
}
|
||||
actions := make([]string, 0, len(actSet))
|
||||
for k := range actSet {
|
||||
actions = append(actions, k)
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{
|
||||
"modules": modules,
|
||||
"actions": actions,
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func parseTimeFlexible(s string) (time.Time, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return time.Time{}, fmt.Errorf("empty")
|
||||
}
|
||||
layouts := []string{
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02 15:04",
|
||||
"2006-01-02",
|
||||
time.RFC3339,
|
||||
}
|
||||
for _, ly := range layouts {
|
||||
if t, err := time.ParseInLocation(ly, s, time.Local); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("invalid time")
|
||||
}
|
||||
@ -1,202 +1,153 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"server/models"
|
||||
"server/services"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
|
||||
"github.com/beego/beego/v2/server/web/context"
|
||||
)
|
||||
|
||||
// OperationLogMiddleware 操作日志中间件 - 记录所有接口的调用记录
|
||||
func OperationLogMiddleware(ctx *context.Context) {
|
||||
// 跳过静态资源和内部路由
|
||||
const (
|
||||
oplogStartKey = "__oplog_start"
|
||||
oplogReqBodyKey = "__oplog_req_body"
|
||||
)
|
||||
|
||||
// BeginOperationLog 在 BeforeRouter 采集请求信息
|
||||
func BeginOperationLog(ctx *context.Context) {
|
||||
url := ctx.Input.URL()
|
||||
if shouldSkipLogging(url) {
|
||||
method := ctx.Input.Method()
|
||||
if shouldSkipLogging(method, url) {
|
||||
return
|
||||
}
|
||||
ctx.Input.SetData(oplogStartKey, time.Now())
|
||||
|
||||
// 请求体由 main.go 的 CopyBody 保留在 Input.RequestBody
|
||||
if rb := ctx.Input.RequestBody; len(rb) > 0 {
|
||||
s := string(rb)
|
||||
ctx.Input.SetData(oplogReqBodyKey, truncateString(maskSensitive(s), 5000))
|
||||
}
|
||||
}
|
||||
|
||||
// FinishOperationLog 在 FinishRouter 统一落库到 yz_system_operation_log
|
||||
func FinishOperationLog(ctx *context.Context) {
|
||||
url := ctx.Input.URL()
|
||||
method := ctx.Input.Method()
|
||||
if shouldSkipLogging(method, url) {
|
||||
return
|
||||
}
|
||||
|
||||
method := ctx.Input.Method()
|
||||
start, _ := ctx.Input.GetData(oplogStartKey).(time.Time)
|
||||
if start.IsZero() {
|
||||
start = time.Now()
|
||||
}
|
||||
execSec := float64(time.Since(start).Milliseconds()) / 1000.0
|
||||
|
||||
// 获取用户信息和租户信息(由 JWT 中间件设置在 Input.Data 中)
|
||||
userId := 0
|
||||
tenantId := 0
|
||||
username := ""
|
||||
userType := "" // 用户类型:user(平台用户) 或 employee(租户员工)
|
||||
|
||||
if v := ctx.Input.GetData("userId"); v != nil {
|
||||
if id, ok := v.(int); ok {
|
||||
userId = id
|
||||
}
|
||||
}
|
||||
if v := ctx.Input.GetData("tenantId"); v != nil {
|
||||
if id, ok := v.(int); ok {
|
||||
tenantId = id
|
||||
}
|
||||
}
|
||||
if v := ctx.Input.GetData("username"); v != nil {
|
||||
if s, ok := v.(string); ok {
|
||||
username = s
|
||||
}
|
||||
}
|
||||
if v := ctx.Input.GetData("userType"); v != nil {
|
||||
if s, ok := v.(string); ok {
|
||||
userType = s
|
||||
}
|
||||
uid := parseUint64FromCtx(ctx.Input.GetData("userId"))
|
||||
tidVal := parseUint64FromCtx(ctx.Input.GetData("tenantId"))
|
||||
var tid *uint64
|
||||
if tidVal > 0 {
|
||||
tid = &tidVal
|
||||
}
|
||||
|
||||
// 用户信息补全
|
||||
if username == "" {
|
||||
username = "anonymous"
|
||||
}
|
||||
|
||||
// 读取请求体(对于有请求体的方法)
|
||||
var requestBody string
|
||||
if method == "POST" || method == "PUT" || method == "PATCH" {
|
||||
body, err := io.ReadAll(ctx.Request.Body)
|
||||
if err == nil && len(body) > 0 {
|
||||
requestBody = string(body)
|
||||
// 重置请求体,使其可以被后续处理
|
||||
ctx.Request.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
ipAddress := ctx.Input.IP()
|
||||
userAgent := ctx.Input.Header("User-Agent")
|
||||
queryString := ctx.Request.URL.RawQuery
|
||||
|
||||
// 使用延迟函数来记录操作
|
||||
defer func() {
|
||||
duration := time.Since(startTime)
|
||||
|
||||
// 解析操作相关信息
|
||||
operation := parseOperationType(method, url)
|
||||
module := parseModule(url)
|
||||
resourceType := parseResourceType(url)
|
||||
resourceId := parseResourceId(url)
|
||||
action := parseAction(method, url)
|
||||
ip := ctx.Input.IP()
|
||||
userAgent := truncateString(ctx.Input.Header("User-Agent"), 500)
|
||||
status := int8(1)
|
||||
if code := ctx.ResponseWriter.Status; code >= 400 {
|
||||
status = 0
|
||||
}
|
||||
|
||||
// 为所有接口都记录日志
|
||||
log := &models.OperationLog{
|
||||
TenantId: tenantId,
|
||||
UserId: userId,
|
||||
Username: username,
|
||||
var reqData *string
|
||||
if v, ok := ctx.Input.GetData(oplogReqBodyKey).(string); ok && strings.TrimSpace(v) != "" {
|
||||
reqData = &v
|
||||
} else if q := strings.TrimSpace(ctx.Request.URL.RawQuery); q != "" {
|
||||
q = truncateString(maskSensitive(q), 5000)
|
||||
reqData = &q
|
||||
}
|
||||
|
||||
var respData *string
|
||||
if code := ctx.ResponseWriter.Status; code >= 400 {
|
||||
msg := "HTTP " + strconv.Itoa(code)
|
||||
respData = &msg
|
||||
}
|
||||
|
||||
var errMsg *string
|
||||
if status == 0 {
|
||||
msg := "请求失败"
|
||||
if respData != nil {
|
||||
msg = *respData
|
||||
}
|
||||
errMsg = &msg
|
||||
}
|
||||
|
||||
logRow := &models.SystemOperationLog{
|
||||
Tid: tid,
|
||||
UserID: uid,
|
||||
Module: module,
|
||||
ResourceType: resourceType,
|
||||
Operation: operation,
|
||||
IpAddress: ipAddress,
|
||||
Action: action,
|
||||
Method: method,
|
||||
URL: truncateString(url, 255),
|
||||
IP: truncateString(ip, 50),
|
||||
UserAgent: userAgent,
|
||||
RequestMethod: method,
|
||||
RequestUrl: url,
|
||||
Status: 1, // 默认成功
|
||||
Duration: int(duration.Milliseconds()),
|
||||
CreateTime: time.Now(),
|
||||
RequestData: reqData,
|
||||
ResponseData: respData,
|
||||
Status: status,
|
||||
ErrorMessage: errMsg,
|
||||
ExecutionTime: execSec,
|
||||
}
|
||||
_, _ = models.Orm.Insert(logRow)
|
||||
}
|
||||
|
||||
// 设置资源ID
|
||||
if resourceId > 0 {
|
||||
log.ResourceId = &resourceId
|
||||
func parseAction(method, url string) string {
|
||||
u := strings.ToLower(url)
|
||||
if strings.Contains(u, "login") {
|
||||
return "登录"
|
||||
}
|
||||
|
||||
// 记录请求信息到Description
|
||||
var description strings.Builder
|
||||
if requestBody != "" {
|
||||
description.WriteString("Request: " + truncateString(requestBody, 500))
|
||||
if strings.Contains(u, "logout") {
|
||||
return "退出"
|
||||
}
|
||||
if queryString != "" {
|
||||
if description.Len() > 0 {
|
||||
description.WriteString(" | ")
|
||||
if strings.Contains(u, "upload") {
|
||||
return "上传"
|
||||
}
|
||||
description.WriteString("Query: " + queryString)
|
||||
}
|
||||
|
||||
log.Description = description.String()
|
||||
|
||||
// 如果有请求体,作为NewValue保存
|
||||
if requestBody != "" {
|
||||
log.NewValue = requestBody
|
||||
}
|
||||
|
||||
// 添加用户类型信息到Description
|
||||
if userType != "" {
|
||||
if log.Description != "" {
|
||||
log.Description += " | "
|
||||
}
|
||||
log.Description += "UserType: " + userType
|
||||
}
|
||||
|
||||
// 调用服务层保存日志
|
||||
if err := services.AddOperationLog(log); err != nil {
|
||||
fmt.Printf("Failed to save operation log: %v\n", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// parseOperationType 根据HTTP方法解析操作类型
|
||||
func parseOperationType(method, url string) string {
|
||||
switch method {
|
||||
case "POST":
|
||||
// 检查URL是否包含特定的操作关键字
|
||||
if strings.Contains(url, "login") {
|
||||
return "LOGIN"
|
||||
if strings.Contains(u, "delete") {
|
||||
return "删除"
|
||||
}
|
||||
if strings.Contains(url, "logout") {
|
||||
return "LOGOUT"
|
||||
if strings.Contains(u, "update") || strings.Contains(u, "edit") || strings.Contains(u, "rename") {
|
||||
return "编辑"
|
||||
}
|
||||
if strings.Contains(url, "add") || strings.Contains(url, "create") {
|
||||
return "CREATE"
|
||||
if strings.Contains(u, "create") || strings.Contains(u, "add") {
|
||||
return "新增"
|
||||
}
|
||||
return "CREATE"
|
||||
return "提交"
|
||||
case "PUT", "PATCH":
|
||||
return "UPDATE"
|
||||
return "编辑"
|
||||
case "DELETE":
|
||||
return "DELETE"
|
||||
return "删除"
|
||||
default:
|
||||
return "READ"
|
||||
return "查询"
|
||||
}
|
||||
}
|
||||
|
||||
// parseResourceType 根据URL解析资源类型
|
||||
func parseResourceType(url string) string {
|
||||
parts := strings.Split(strings.TrimPrefix(url, "/api/"), "/")
|
||||
if len(parts) > 0 {
|
||||
// 移除复数形式的s
|
||||
resourceType := strings.TrimSuffix(parts[0], "s")
|
||||
return resourceType
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// parseResourceId 从URL中提取资源ID
|
||||
func parseResourceId(url string) int {
|
||||
parts := strings.Split(strings.TrimPrefix(url, "/api/"), "/")
|
||||
if len(parts) >= 2 {
|
||||
// 尝试解析第二个部分为ID
|
||||
if id, err := strconv.Atoi(parts[1]); err == nil {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// parseModule 根据URL解析模块名称
|
||||
func parseModule(url string) string {
|
||||
// 返回与 sys_operation_log.module 字段匹配的短code(例如 dict、user 等)
|
||||
parts := strings.Split(strings.TrimPrefix(url, "/api/"), "/")
|
||||
if len(parts) > 0 {
|
||||
return strings.ToLower(parts[0])
|
||||
path := strings.Trim(strings.ToLower(url), "/")
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) >= 2 {
|
||||
return truncateString(parts[1], 50)
|
||||
}
|
||||
if len(parts) == 1 && parts[0] != "" {
|
||||
return truncateString(parts[0], 50)
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// shouldSkipLogging 判断是否需要跳过日志记录
|
||||
func shouldSkipLogging(url string) bool {
|
||||
// 跳过静态资源、健康检查等
|
||||
func shouldSkipLogging(method, url string) bool {
|
||||
skipPatterns := []string{
|
||||
"/static/",
|
||||
"/uploads/",
|
||||
@ -204,19 +155,93 @@ func shouldSkipLogging(url string) bool {
|
||||
"/health",
|
||||
"/ping",
|
||||
}
|
||||
|
||||
for _, pattern := range skipPatterns {
|
||||
if strings.HasPrefix(url, pattern) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 高频噪声接口:默认跳过(可按需再扩充)
|
||||
if method == "GET" {
|
||||
noisyExact := map[string]bool{
|
||||
"/platform/currentUser": true,
|
||||
"/platform/allmenu": true,
|
||||
"/platform/getOpenVerify": true, // 若未来改名/迁移可再调整
|
||||
}
|
||||
if noisyExact[url] {
|
||||
return true
|
||||
}
|
||||
// 菜单详情/列表类:频率高且多为前端路由加载
|
||||
if strings.HasPrefix(url, "/platform/menu/") {
|
||||
return true
|
||||
}
|
||||
// 登录页极验配置轮询/获取(不影响关键业务)
|
||||
if strings.HasPrefix(url, "/platform/login/getGeetest") || strings.HasPrefix(url, "/platform/login/getOpenVerify") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// truncateString 截断字符串到指定长度
|
||||
func parseUint64FromCtx(v interface{}) uint64 {
|
||||
switch x := v.(type) {
|
||||
case int:
|
||||
if x > 0 {
|
||||
return uint64(x)
|
||||
}
|
||||
case int64:
|
||||
if x > 0 {
|
||||
return uint64(x)
|
||||
}
|
||||
case uint64:
|
||||
return x
|
||||
case float64:
|
||||
if x > 0 {
|
||||
return uint64(x)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func truncateString(s string, maxLen int) string {
|
||||
if len(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
return s[:maxLen] + "..."
|
||||
}
|
||||
|
||||
func maskSensitive(s string) string {
|
||||
// 尝试 JSON 脱敏(失败则返回原文)
|
||||
var obj interface{}
|
||||
if err := json.Unmarshal([]byte(s), &obj); err != nil {
|
||||
return s
|
||||
}
|
||||
maskInObj(&obj)
|
||||
bs, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return s
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
func maskInObj(v *interface{}) {
|
||||
switch t := (*v).(type) {
|
||||
case map[string]interface{}:
|
||||
for k, val := range t {
|
||||
lk := strings.ToLower(k)
|
||||
if lk == "password" || lk == "pwd" || lk == "token" || lk == "api_key" || lk == "api_secret" || lk == "authorization" {
|
||||
t[k] = "***"
|
||||
continue
|
||||
}
|
||||
tmp := val
|
||||
maskInObj(&tmp)
|
||||
t[k] = tmp
|
||||
}
|
||||
case []interface{}:
|
||||
for i := range t {
|
||||
tmp := t[i]
|
||||
maskInObj(&tmp)
|
||||
t[i] = tmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,9 @@ func Init(_ string) {
|
||||
new(SystemEmail),
|
||||
new(SystemSMS),
|
||||
new(SystemSMSTask),
|
||||
new(SystemOperationLog),
|
||||
new(SystemDomainPool),
|
||||
new(SystemTenantDomain),
|
||||
)
|
||||
|
||||
// 创建全局 Ormer
|
||||
|
||||
48
models/permission_check.go
Normal file
48
models/permission_check.go
Normal file
@ -0,0 +1,48 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckUserPermission 校验用户是否拥有指定权限标识。
|
||||
// 兼容 rights 为 JSON 数组 / 逗号分隔字符串;解析失败时默认放行,避免历史数据阻断请求。
|
||||
func CheckUserPermission(userID int, permission string) (bool, error) {
|
||||
if permission == "" || userID <= 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var user AdminUser
|
||||
if err := Orm.QueryTable(new(AdminUser)).Filter("id", userID).One(&user); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var role AdminRole
|
||||
if err := Orm.QueryTable(new(AdminRole)).Filter("id", user.RoleID).One(&role); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if role.Rights == nil || strings.TrimSpace(*role.Rights) == "" {
|
||||
return true, nil
|
||||
}
|
||||
rightsRaw := strings.TrimSpace(*role.Rights)
|
||||
|
||||
// 1) JSON 数组格式
|
||||
var arr []string
|
||||
if err := json.Unmarshal([]byte(rightsRaw), &arr); err == nil {
|
||||
for _, p := range arr {
|
||||
if strings.TrimSpace(p) == permission {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 2) 逗号分隔字符串
|
||||
for _, p := range strings.Split(rightsRaw, ",") {
|
||||
if strings.TrimSpace(p) == permission {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
17
models/system_domain_pool.go
Normal file
17
models/system_domain_pool.go
Normal file
@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// SystemDomainPool 主域名池表 yz_system_domain_pool
|
||||
type SystemDomainPool struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
MainDomain string `orm:"column(main_domain);size(255)" json:"main_domain"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"` // 1启用 0禁用
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *SystemDomainPool) TableName() string {
|
||||
return "yz_system_domain_pool"
|
||||
}
|
||||
28
models/system_operation_log.go
Normal file
28
models/system_operation_log.go
Normal file
@ -0,0 +1,28 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// SystemOperationLog 操作日志表 yz_system_operation_log
|
||||
type SystemOperationLog struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid *uint64 `orm:"column(tid);null" json:"tid"`
|
||||
UserID uint64 `orm:"column(user_id);default(0)" json:"user_id"`
|
||||
Module string `orm:"column(module);size(50);default('')" json:"module"`
|
||||
Action string `orm:"column(action);size(50);default('')" json:"action"`
|
||||
Method string `orm:"column(method);size(10);default('')" json:"method"`
|
||||
URL string `orm:"column(url);size(255);default('')" json:"url"`
|
||||
IP string `orm:"column(ip);size(50);default('')" json:"ip"`
|
||||
UserAgent string `orm:"column(user_agent);size(500);default('')" json:"user_agent"`
|
||||
RequestData *string `orm:"column(request_data);type(text);null" json:"request_data"`
|
||||
ResponseData *string `orm:"column(response_data);type(text);null" json:"response_data"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"`
|
||||
ErrorMessage *string `orm:"column(error_message);type(text);null" json:"error_message"`
|
||||
ExecutionTime float64 `orm:"column(execution_time);digits(10);decimals(3);default(0)" json:"execution_time"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *SystemOperationLog) TableName() string {
|
||||
return "yz_system_operation_log"
|
||||
}
|
||||
20
models/system_tenant_domain.go
Normal file
20
models/system_tenant_domain.go
Normal file
@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// SystemTenantDomain 租户域名表 yz_system_tenant_domain
|
||||
type SystemTenantDomain struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid *uint64 `orm:"column(tid);null" json:"tid"`
|
||||
SubDomain *string `orm:"column(sub_domain);size(50);null" json:"sub_domain"`
|
||||
MainDomain *string `orm:"column(main_domain);size(255);null" json:"main_domain"`
|
||||
FullDomain *string `orm:"column(full_domain);size(255);null" json:"full_domain"`
|
||||
Status int `orm:"column(status);null" json:"status"` // 1已生效 / 0审核中 / 2禁用
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *SystemTenantDomain) TableName() string {
|
||||
return "yz_system_tenant_domain"
|
||||
}
|
||||
@ -63,6 +63,27 @@ func Register() {
|
||||
beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "put:UpdateRole")
|
||||
beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "delete:DeleteRole")
|
||||
|
||||
// 操作日志(yz_system_operation_log)
|
||||
beego.Router("/platform/operationLogs", &controllers.PlatformOperationLogController{}, "get:List")
|
||||
beego.Router("/platform/operationLogs/statistics", &controllers.PlatformOperationLogController{}, "get:Statistics")
|
||||
beego.Router("/platform/operationLogs/:id", &controllers.PlatformOperationLogController{}, "get:Detail;delete:Delete")
|
||||
beego.Router("/platform/operationLogs/batchDelete", &controllers.PlatformOperationLogController{}, "post:BatchDelete")
|
||||
|
||||
// 域名管理(主域名池 / 租户域名)
|
||||
beego.Router("/platform/domain/pool/index", &controllers.PlatformDomainPoolController{}, "get:Index")
|
||||
beego.Router("/platform/domain/pool/getEnabledDomains", &controllers.PlatformDomainPoolController{}, "get:GetEnabledDomains")
|
||||
beego.Router("/platform/domain/pool/create", &controllers.PlatformDomainPoolController{}, "post:Create")
|
||||
beego.Router("/platform/domain/pool/update", &controllers.PlatformDomainPoolController{}, "post:Update")
|
||||
beego.Router("/platform/domain/pool/delete/:id", &controllers.PlatformDomainPoolController{}, "delete:Delete")
|
||||
beego.Router("/platform/domain/pool/toggleStatus", &controllers.PlatformDomainPoolController{}, "post:ToggleStatus")
|
||||
|
||||
beego.Router("/platform/domain/tenant/index", &controllers.PlatformTenantDomainController{}, "get:Index")
|
||||
beego.Router("/platform/domain/tenant/myDomains", &controllers.PlatformTenantDomainController{}, "get:MyDomains")
|
||||
beego.Router("/platform/domain/tenant/apply", &controllers.PlatformTenantDomainController{}, "post:Apply")
|
||||
beego.Router("/platform/domain/tenant/audit", &controllers.PlatformTenantDomainController{}, "post:Audit")
|
||||
beego.Router("/platform/domain/tenant/toggleStatus", &controllers.PlatformTenantDomainController{}, "post:ToggleStatus")
|
||||
beego.Router("/platform/domain/tenant/delete/:id", &controllers.PlatformTenantDomainController{}, "delete:Delete")
|
||||
|
||||
// 系统邮箱配置(yz_system_email)
|
||||
beego.Router("/platform/email/info", &controllers.PlatformEmailController{}, "get:GetInfo")
|
||||
beego.Router("/platform/email/editinfo", &controllers.PlatformEmailController{}, "post:EditInfo")
|
||||
|
||||
@ -3,6 +3,7 @@ package routers
|
||||
import (
|
||||
"os"
|
||||
|
||||
"server/middleware"
|
||||
"server/routers/api"
|
||||
"server/routers/backend"
|
||||
"server/routers/index"
|
||||
@ -29,6 +30,11 @@ func init() {
|
||||
}
|
||||
})
|
||||
|
||||
// 全局操作日志:请求开始采集
|
||||
beego.InsertFilter("*", beego.BeforeRouter, middleware.BeginOperationLog)
|
||||
// 全局操作日志:请求结束统一落库
|
||||
beego.InsertFilter("*", beego.FinishRouter, middleware.FinishOperationLog)
|
||||
|
||||
// 根据运行模式选择要注册的路由组
|
||||
// 优先读取环境变量 APP_MODE,其次读取配置 app_mode,默认 all
|
||||
mode := os.Getenv("APP_MODE")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user