Files

203 lines
5.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}