package admin import ( "context" "strings" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" v1 "github.com/qianfree/team-api/api/admin/v1" "github.com/qianfree/team-api/internal/dao" "github.com/qianfree/team-api/internal/logic/common" do "github.com/qianfree/team-api/internal/model/do" ) // ErrorLogList returns a paginated list of system error logs. func (s *sAdmin) ErrorLogList(ctx context.Context, req *v1.ErrorLogListReq) (*v1.ErrorLogListRes, error) { if err := common.ValidateDateParam(req.StartDate, "开始日期"); err != nil { return nil, err } if err := common.ValidateDateParam(req.EndDate, "结束日期"); err != nil { return nil, err } var conditions []string var args []any if req.Source != "" { conditions = append(conditions, "source = ?") args = append(args, req.Source) } if req.ErrorCode > 0 { conditions = append(conditions, "error_code = ?") args = append(args, req.ErrorCode) } if req.Resolved == "true" { conditions = append(conditions, "resolved = true") } else if req.Resolved == "false" { conditions = append(conditions, "resolved = false") } if req.StartDate != "" { conditions = append(conditions, "created_at >= ?") args = append(args, req.StartDate) } if req.EndDate != "" { conditions = append(conditions, "created_at <= ?") args = append(args, req.EndDate+" 23:59:59") } if req.Keyword != "" { conditions = append(conditions, "(error_message ILIKE ? OR request_path ILIKE ?)") kw := "%" + req.Keyword + "%" args = append(args, kw, kw) } where := strings.Join(conditions, " AND ") page, pageSize := common.NormalizePagination(req.Page, req.PageSize) items, total, err := queryPage(ctx, "sys_error_logs", "*", where, "id DESC", page, pageSize, args...) if err != nil { return nil, err } if items == nil { items = []map[string]any{} } return &v1.ErrorLogListRes{ List: items, Total: total, Page: page, PageSize: pageSize, }, nil } // ErrorLogDetail returns the detail of a single error log. func (s *sAdmin) ErrorLogDetail(ctx context.Context, req *v1.ErrorLogDetailReq) (*v1.ErrorLogDetailRes, error) { result, err := g.DB().Ctx(ctx).Query(ctx, "SELECT * FROM sys_error_logs WHERE id = ?", req.Id) if err != nil { return nil, err } if len(result) == 0 { return nil, common.NewNotFoundError("错误日志") } row := result[0] data := make(map[string]any, len(row)) for k, v := range row { data[k] = v.Val() } return &v1.ErrorLogDetailRes{Data: data}, nil } // ErrorLogResolve marks an error log as resolved. func (s *sAdmin) ErrorLogResolve(ctx context.Context, req *v1.ErrorLogResolveReq) (*v1.ErrorLogResolveRes, error) { count, err := dao.SysErrorLogs.Ctx(ctx).Where("id", req.Id).Count() if err != nil { return nil, err } if count == 0 { return nil, common.NewNotFoundError("错误日志") } userID := common.GetCtxUserID(ctx) _, err = dao.SysErrorLogs.Ctx(ctx). Where("id", req.Id). Data(do.SysErrorLogs{ Resolved: true, ResolvedBy: userID, ResolvedAt: gtime.Now(), }).Update() if err != nil { return nil, err } return &v1.ErrorLogResolveRes{}, nil } // ErrorLogBatchResolve marks multiple error logs as resolved. func (s *sAdmin) ErrorLogBatchResolve(ctx context.Context, req *v1.ErrorLogBatchResolveReq) (*v1.ErrorLogBatchResolveRes, error) { userID := common.GetCtxUserID(ctx) _, err := dao.SysErrorLogs.Ctx(ctx). WhereIn("id", req.Ids). Data(do.SysErrorLogs{ Resolved: true, ResolvedBy: userID, ResolvedAt: gtime.Now(), }).Update() if err != nil { return nil, err } return &v1.ErrorLogBatchResolveRes{}, nil } // ErrorLogClear 硬删除全部错误日志。 // 系统报错大量堆积时用于快速释放数据库空间:用 TRUNCATE 而非逐行 DELETE, // 秒级清空并立即归还磁盘空间(DELETE 需等 VACUUM 回收)。TRUNCATE 不返回行数, // 故先取删除前条数作为反馈。表无数据库级外键,TRUNCATE 安全。 func (s *sAdmin) ErrorLogClear(ctx context.Context, _ *v1.ErrorLogClearReq) (*v1.ErrorLogClearRes, error) { count, err := dao.SysErrorLogs.Ctx(ctx).Count() if err != nil { return nil, err } if _, err := g.DB().Ctx(ctx).Exec(ctx, "TRUNCATE TABLE sys_error_logs RESTART IDENTITY"); err != nil { return nil, err } return &v1.ErrorLogClearRes{Deleted: int64(count)}, nil } // ErrorLogStats returns error log statistics. func (s *sAdmin) ErrorLogStats(ctx context.Context, _ *v1.ErrorLogStatsReq) (*v1.ErrorLogStatsRes, error) { // Total unresolved unresolved, err := dao.SysErrorLogs.Ctx(ctx).Where("resolved", false).Count() if err != nil { return nil, err } // Total today today := gtime.Now().Format("Y-m-d") todayCount, err := dao.SysErrorLogs.Ctx(ctx). Where("created_at >= ?", today). Count() if err != nil { return nil, err } // By source (last 7 days) bySourceResult, err := g.DB().Ctx(ctx).Query(ctx, "SELECT source, COUNT(*) as count FROM sys_error_logs WHERE created_at >= NOW() - INTERVAL '7 days' GROUP BY source") if err != nil { return nil, err } bySource := make([]map[string]any, 0, len(bySourceResult)) for _, row := range bySourceResult { m := make(map[string]any, len(row)) for k, v := range row { m[k] = v.Val() } bySource = append(bySource, m) } // By error code (last 7 days, top 10) byCodeResult, err := g.DB().Ctx(ctx).Query(ctx, "SELECT error_code, COUNT(*) as count FROM sys_error_logs WHERE created_at >= NOW() - INTERVAL '7 days' GROUP BY error_code ORDER BY count DESC LIMIT 10") if err != nil { return nil, err } byCode := make([]map[string]any, 0, len(byCodeResult)) for _, row := range byCodeResult { m := make(map[string]any, len(row)) for k, v := range row { m[k] = v.Val() } byCode = append(byCode, m) } return &v1.ErrorLogStatsRes{ Data: map[string]any{ "unresolved": unresolved, "today_count": todayCount, "by_source": bySource, "by_error_code": byCode, }, }, nil }