From e830896d471edfe1b1a8543152dcadd7ec33a919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E5=BC=BA?= <357099073@qq.com> Date: Wed, 1 Apr 2026 15:51:29 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E6=90=9E=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controllers/platform_domain.go | 615 ++++++++++++++++++++++++++ controllers/platform_operation_log.go | 351 +++++++++++++++ middleware/operationLog.go | 343 +++++++------- models/init.go | 3 + models/permission_check.go | 48 ++ models/system_domain_pool.go | 17 + models/system_operation_log.go | 28 ++ models/system_tenant_domain.go | 20 + routers/platform/platform.go | 21 + routers/router.go | 6 + 10 files changed, 1293 insertions(+), 159 deletions(-) create mode 100644 controllers/platform_domain.go create mode 100644 controllers/platform_operation_log.go create mode 100644 models/permission_check.go create mode 100644 models/system_domain_pool.go create mode 100644 models/system_operation_log.go create mode 100644 models/system_tenant_domain.go diff --git a/controllers/platform_domain.go b/controllers/platform_domain.go new file mode 100644 index 0000000..a5bcf72 --- /dev/null +++ b/controllers/platform_domain.go @@ -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 diff --git a/controllers/platform_operation_log.go b/controllers/platform_operation_log.go new file mode 100644 index 0000000..cec78a9 --- /dev/null +++ b/controllers/platform_operation_log.go @@ -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") +} diff --git a/middleware/operationLog.go b/middleware/operationLog.go index dc238e2..7736dc9 100644 --- a/middleware/operationLog.go +++ b/middleware/operationLog.go @@ -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" + module := parseModule(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 } - // 读取请求体(对于有请求体的方法) - 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)) - } + 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 } - startTime := time.Now() - ipAddress := ctx.Input.IP() - userAgent := ctx.Input.Header("User-Agent") - queryString := ctx.Request.URL.RawQuery + var respData *string + if code := ctx.ResponseWriter.Status; code >= 400 { + msg := "HTTP " + strconv.Itoa(code) + respData = &msg + } - // 使用延迟函数来记录操作 - defer func() { - duration := time.Since(startTime) - - // 解析操作相关信息 - operation := parseOperationType(method, url) - module := parseModule(url) - resourceType := parseResourceType(url) - resourceId := parseResourceId(url) - - // 为所有接口都记录日志 - log := &models.OperationLog{ - TenantId: tenantId, - UserId: userId, - Username: username, - Module: module, - ResourceType: resourceType, - Operation: operation, - IpAddress: ipAddress, - UserAgent: userAgent, - RequestMethod: method, - RequestUrl: url, - Status: 1, // 默认成功 - Duration: int(duration.Milliseconds()), - CreateTime: time.Now(), + var errMsg *string + if status == 0 { + msg := "请求失败" + if respData != nil { + msg = *respData } + errMsg = &msg + } - // 设置资源ID - if resourceId > 0 { - log.ResourceId = &resourceId - } - - // 记录请求信息到Description - var description strings.Builder - if requestBody != "" { - description.WriteString("Request: " + truncateString(requestBody, 500)) - } - if queryString != "" { - if description.Len() > 0 { - description.WriteString(" | ") - } - 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) - } - }() + logRow := &models.SystemOperationLog{ + Tid: tid, + UserID: uid, + Module: module, + Action: action, + Method: method, + URL: truncateString(url, 255), + IP: truncateString(ip, 50), + UserAgent: userAgent, + RequestData: reqData, + ResponseData: respData, + Status: status, + ErrorMessage: errMsg, + ExecutionTime: execSec, + } + _, _ = models.Orm.Insert(logRow) } -// parseOperationType 根据HTTP方法解析操作类型 -func parseOperationType(method, url string) string { +func parseAction(method, url string) string { + u := strings.ToLower(url) + if strings.Contains(u, "login") { + return "登录" + } + if strings.Contains(u, "logout") { + return "退出" + } + if strings.Contains(u, "upload") { + return "上传" + } 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 + } + } +} diff --git a/models/init.go b/models/init.go index 74da700..9264ebc 100644 --- a/models/init.go +++ b/models/init.go @@ -43,6 +43,9 @@ func Init(_ string) { new(SystemEmail), new(SystemSMS), new(SystemSMSTask), + new(SystemOperationLog), + new(SystemDomainPool), + new(SystemTenantDomain), ) // 创建全局 Ormer diff --git a/models/permission_check.go b/models/permission_check.go new file mode 100644 index 0000000..f927f91 --- /dev/null +++ b/models/permission_check.go @@ -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 +} diff --git a/models/system_domain_pool.go b/models/system_domain_pool.go new file mode 100644 index 0000000..6ddaf33 --- /dev/null +++ b/models/system_domain_pool.go @@ -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" +} diff --git a/models/system_operation_log.go b/models/system_operation_log.go new file mode 100644 index 0000000..df2216d --- /dev/null +++ b/models/system_operation_log.go @@ -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" +} diff --git a/models/system_tenant_domain.go b/models/system_tenant_domain.go new file mode 100644 index 0000000..99211cf --- /dev/null +++ b/models/system_tenant_domain.go @@ -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" +} diff --git a/routers/platform/platform.go b/routers/platform/platform.go index a7670cd..cdf44fe 100644 --- a/routers/platform/platform.go +++ b/routers/platform/platform.go @@ -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") diff --git a/routers/router.go b/routers/router.go index d215e63..15d93ae 100644 --- a/routers/router.go +++ b/routers/router.go @@ -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")