203 lines
5.9 KiB
Go
203 lines
5.9 KiB
Go
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
|
||
}
|