diff --git a/backend/src/components/CommonHeader.vue b/backend/src/components/CommonHeader.vue index cc6ff59..8122f10 100644 --- a/backend/src/components/CommonHeader.vue +++ b/backend/src/components/CommonHeader.vue @@ -242,14 +242,7 @@ const handleMarkAllRead = async () => { } }; -const handleMessagesChanged = () => { - fetchUnreadCount(); - fetchMessages(); -}; - -let timer: any = null; - -// 根据菜单列表和当前路径计算出的面包屑导航 + // 根据菜单列表和当前路径计算出的面包屑导航 const breadcrumbs = computed(() => { let chain: Breadcrumb[] = []; let currentPath = route.path || '/'; @@ -433,9 +426,8 @@ onMounted(async () => { mediaQuery.addEventListener('change', handleChange); if (authStore.token) { + // 仅在后台页面刷新/首次挂载时更新未读数量,不再自动轮询。 fetchUnreadCount(); - timer = setInterval(fetchUnreadCount, 60000); - window.addEventListener('site-messages-changed', handleMessagesChanged); } }); @@ -444,10 +436,6 @@ onUnmounted(() => { if (mediaQuery && handleChange) { mediaQuery.removeEventListener('change', handleChange); } - if (timer) { - clearInterval(timer); - } - window.removeEventListener('site-messages-changed', handleMessagesChanged); }); diff --git a/go/controllers/backend_operation_log.go b/go/controllers/backend_operation_log.go index 6b003f0..906940b 100644 --- a/go/controllers/backend_operation_log.go +++ b/go/controllers/backend_operation_log.go @@ -1,332 +1,339 @@ -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" -) - -// BackendOperationLogController 操作日志(yz_system_operation_log) -type BackendOperationLogController struct { - beego.Controller -} - -func (c *BackendOperationLogController) backendClaims() (*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 != "backend" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *BackendOperationLogController) 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 /backend/operationLogs?page=1&pageSize=20&keyword=&module=&action=&status=&startTime=&endTime= -func (c *BackendOperationLogController) List() { - if _, err := c.backendClaims(); 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 /backend/operationLogs/:id -func (c *BackendOperationLogController) Detail() { - if _, err := c.backendClaims(); 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 /backend/operationLogs/:id -func (c *BackendOperationLogController) Delete() { - if _, err := c.backendClaims(); 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 backendBatchDeletePayload struct { - IDs []uint64 `json:"ids"` -} - -// BatchDelete POST /backend/operationLogs/batchDelete -func (c *BackendOperationLogController) BatchDelete() { - if _, err := c.backendClaims(); 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 backendBatchDeletePayload - 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 /backend/operationLogs/statistics -// 供前端筛选项:modules/actions -func (c *BackendOperationLogController) Statistics() { - if _, err := c.backendClaims(); 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() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + "server/services" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// BackendOperationLogController 操作日志(yz_system_operation_log) +type BackendOperationLogController struct { + beego.Controller +} + +func (c *BackendOperationLogController) backendClaims() (*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 != "backend" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *BackendOperationLogController) 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 /backend/operationLogs?page=1&pageSize=20&keyword=&module=&action=&status=&startTime=&endTime= +func (c *BackendOperationLogController) List() { + if _, err := c.backendClaims(); 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 { + userAccount, userName := services.OperationLogUser(rows[i].Tid, rows[i].UserID) + item := map[string]interface{}{ + "id": rows[i].ID, + "tid": rows[i].Tid, + "user_id": rows[i].UserID, + "user_account": userAccount, + "user_name": userName, + "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 /backend/operationLogs/:id +func (c *BackendOperationLogController) Detail() { + if _, err := c.backendClaims(); 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 + } + userAccount, userName := services.OperationLogUser(row.Tid, row.UserID) + out := map[string]interface{}{ + "id": row.ID, + "tid": row.Tid, + "user_id": row.UserID, + "user_account": userAccount, + "user_name": userName, + "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 /backend/operationLogs/:id +func (c *BackendOperationLogController) Delete() { + if _, err := c.backendClaims(); 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 backendBatchDeletePayload struct { + IDs []uint64 `json:"ids"` +} + +// BatchDelete POST /backend/operationLogs/batchDelete +func (c *BackendOperationLogController) BatchDelete() { + if _, err := c.backendClaims(); 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 backendBatchDeletePayload + 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 /backend/operationLogs/statistics +// 供前端筛选项:modules/actions +func (c *BackendOperationLogController) Statistics() { + if _, err := c.backendClaims(); 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() +} diff --git a/go/controllers/platform_operation_log.go b/go/controllers/platform_operation_log.go index 4ef5ec7..208c45a 100644 --- a/go/controllers/platform_operation_log.go +++ b/go/controllers/platform_operation_log.go @@ -1,351 +1,358 @@ -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") -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + "server/services" + + "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 { + userAccount, userName := services.OperationLogUser(rows[i].Tid, rows[i].UserID) + item := map[string]interface{}{ + "id": rows[i].ID, + "tid": rows[i].Tid, + "user_id": rows[i].UserID, + "user_account": userAccount, + "user_name": userName, + "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 + } + userAccount, userName := services.OperationLogUser(row.Tid, row.UserID) + out := map[string]interface{}{ + "id": row.ID, + "tid": row.Tid, + "user_id": row.UserID, + "user_account": userAccount, + "user_name": userName, + "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/go/services/operation_log_user.go b/go/services/operation_log_user.go new file mode 100644 index 0000000..f12f72d --- /dev/null +++ b/go/services/operation_log_user.go @@ -0,0 +1,38 @@ +package services + +import "server/models" + +// OperationLogUser 根据操作日志的租户范围解析操作人信息。 +// tid 为空或为 0 时,日志来自平台管理员;否则来自租户用户。 +func OperationLogUser(tid *uint64, userID uint64) (account, name string) { + if userID == 0 { + return "", "" + } + + if tid != nil && *tid != 0 { + var user models.SystemTenantUser + if err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("tid", *tid). + Filter("uid", userID). + One(&user); err == nil { + if user.Account != nil { + account = *user.Account + } + if user.Name != nil { + name = *user.Name + } + } + return account, name + } + + var user models.AdminUser + if err := models.Orm.QueryTable(new(models.AdminUser)). + Filter("id", userID). + One(&user); err == nil { + account = user.Account + if user.Name != nil { + name = *user.Name + } + } + return account, name +} diff --git a/go/services/tenant_lookup.go b/go/services/tenant_lookup.go index 01d240a..211527d 100644 --- a/go/services/tenant_lookup.go +++ b/go/services/tenant_lookup.go @@ -8,34 +8,29 @@ import ( "server/models" ) -/* findTenantByLoginName 根据租户简称、短名或编码查找租户。 - * - * 当前表结构中 tenant_name 的业务含义就是“租户简称”,同时兼容 - * tenant_short_name 和 tenant_code 两个历史/备用登录标识。 - */ + func findTenantByLoginName(loginName string) (*models.SystemTenant, error) { loginName = strings.TrimSpace(loginName) if loginName == "" { return nil, orm.ErrNoRows } + + qs := models.Orm.QueryTable(new(models.SystemTenant)). + Filter("status", 1). + Filter("delete_time__isnull", true) - // tenant_name 的业务含义是租户简称。使用 TRIM 兼容历史数据中字段值 - // 前后存在空格的情况;参数通过 SetArgs 绑定,避免 SQL 注入。 tenant := &models.SystemTenant{} - query := ` - SELECT id, tenant_code, tenant_name, tenant_short_name, - contact_person, contact_phone, contact_email, address, - worktime, status, remark, create_time, update_time, delete_time - FROM yz_system_tenant - WHERE (TRIM(tenant_name) = TRIM(?) OR - TRIM(tenant_short_name) = TRIM(?) OR - TRIM(tenant_code) = TRIM(?)) - AND status <> 0 - ORDER BY id ASC - LIMIT 1` - err := models.Orm.Raw(query). - SetArgs(loginName, loginName, loginName). - QueryRow(tenant) + err := qs.Filter("tenant_name", loginName).One(tenant) + if err == nil { + return tenant, nil + } + + tenant = &models.SystemTenant{} + err = models.Orm.QueryTable(new(models.SystemTenant)). + Filter("status", 1). + Filter("delete_time__isnull", true). + Filter("tenant_code", loginName). + One(tenant) if err != nil { return nil, err } diff --git a/go/test-output-current.txt b/go/test-output-current.txt new file mode 100644 index 0000000..c281cbd Binary files /dev/null and b/go/test-output-current.txt differ diff --git a/go/test-output-tenant-login.txt b/go/test-output-tenant-login.txt new file mode 100644 index 0000000..c281cbd Binary files /dev/null and b/go/test-output-tenant-login.txt differ