143 lines
4.3 KiB
Go
143 lines
4.3 KiB
Go
package logic
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"strconv"
|
||
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/os/gtime"
|
||
|
||
v1 "tool-api/api/admin/v1"
|
||
"tool-api/internal/consts"
|
||
)
|
||
|
||
// auditId 审计对象主键统一转字符串(避免与 note.go 的 itoa(int) 冲突)
|
||
func auditId(v int64) string { return strconv.FormatInt(v, 10) }
|
||
|
||
// ============================================================================
|
||
// 后台操作审计(T17)
|
||
//
|
||
// WriteAudit 只负责「落一条记录」,调用点负责提供前后值;写入失败仅告警,
|
||
// 绝不因审计异常影响主流程(审计是旁路,不是业务前置条件)。
|
||
// ============================================================================
|
||
|
||
// AuditEntry 一条待写入的操作审计
|
||
type AuditEntry struct {
|
||
Action string // 动作,如 user.set_level / event.status / admin.create
|
||
TargetType string // 对象类型:user/event/table/admin/export...
|
||
TargetId string // 对象主键(字符串,兼容复合键)
|
||
Before interface{} // 操作前值(可为 nil / map / struct)
|
||
After interface{} // 操作后值
|
||
Result int // consts.AuditResultSuccess / Fail
|
||
Remark string
|
||
}
|
||
|
||
// auditJSON 把前后值序列化成 JSON 字符串;nil 存 NULL。
|
||
func auditJSON(v interface{}) interface{} {
|
||
if v == nil {
|
||
return nil
|
||
}
|
||
b, err := json.Marshal(v)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return string(b)
|
||
}
|
||
|
||
func auditClientIp(ctx context.Context) string {
|
||
req := g.RequestFromCtx(ctx)
|
||
if req == nil {
|
||
return ""
|
||
}
|
||
return req.GetClientIp()
|
||
}
|
||
|
||
// WriteAudit 写入一条操作审计(操作人取自上下文,由 AdminPerm 中间件注入)。
|
||
func WriteAudit(ctx context.Context, e AuditEntry) {
|
||
data := g.Map{
|
||
"admin_id": CtxAdminId(ctx),
|
||
"admin_account": CtxAdminAccount(ctx),
|
||
"admin_name": CtxAdminName(ctx),
|
||
"role_value": CtxAdminRole(ctx),
|
||
"action": e.Action,
|
||
"target_type": e.TargetType,
|
||
"target_id": e.TargetId,
|
||
"before_json": auditJSON(e.Before),
|
||
"after_json": auditJSON(e.After),
|
||
"result": e.Result,
|
||
"remark": e.Remark,
|
||
"ip": auditClientIp(ctx),
|
||
"created_at": gtime.Now(),
|
||
}
|
||
if _, err := g.Model(consts.TableAdminAuditLog).Data(data).Insert(); err != nil {
|
||
g.Log().Warningf(ctx, "[audit] 写入失败 action=%s err=%v", e.Action, err)
|
||
}
|
||
}
|
||
|
||
// AdminAuditList 审计列表(多条件筛选 + 分页)。
|
||
func AdminAuditList(ctx context.Context, req *v1.AuditListReq) (*v1.AuditListRes, error) {
|
||
if req.Page <= 0 {
|
||
req.Page = 1
|
||
}
|
||
if req.PageSize <= 0 {
|
||
req.PageSize = 20
|
||
}
|
||
m := g.Model(consts.TableAdminAuditLog)
|
||
if req.AdminKeyword != "" {
|
||
m = m.WhereLike("admin_account", "%"+req.AdminKeyword+"%")
|
||
}
|
||
if req.Action != "" {
|
||
m = m.WhereLike("action", "%"+req.Action+"%")
|
||
}
|
||
if req.Result != nil {
|
||
m = m.Where("result", *req.Result)
|
||
}
|
||
if req.DateFrom != "" {
|
||
m = m.WhereGTE("created_at", req.DateFrom+" 00:00:00")
|
||
}
|
||
if req.DateTo != "" {
|
||
m = m.WhereLTE("created_at", req.DateTo+" 23:59:59")
|
||
}
|
||
total, err := m.Count()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
records, err := m.Page(req.Page, req.PageSize).OrderDesc("id").All()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
list := make([]v1.AuditItem, 0, len(records))
|
||
for _, r := range records {
|
||
list = append(list, v1.AuditItem{
|
||
Id: r["id"].Int64(),
|
||
AdminId: r["admin_id"].Int64(),
|
||
AdminAccount: r["admin_account"].String(),
|
||
AdminName: r["admin_name"].String(),
|
||
RoleValue: r["role_value"].String(),
|
||
Action: r["action"].String(),
|
||
TargetType: r["target_type"].String(),
|
||
TargetId: r["target_id"].String(),
|
||
BeforeJson: r["before_json"].String(),
|
||
AfterJson: r["after_json"].String(),
|
||
Result: r["result"].Int(),
|
||
Remark: r["remark"].String(),
|
||
Ip: r["ip"].String(),
|
||
CreatedAt: r["created_at"].String(),
|
||
})
|
||
}
|
||
return &v1.AuditListRes{List: list, Total: total}, nil
|
||
}
|
||
|
||
// AdminAuditRecord 前端主动上报一条审计(如「导出」为纯前端动作,服务端无法感知)。
|
||
func AdminAuditRecord(ctx context.Context, req *v1.AuditRecordReq) (*v1.AuditRecordRes, error) {
|
||
WriteAudit(ctx, AuditEntry{
|
||
Action: req.Action,
|
||
TargetType: req.TargetType,
|
||
TargetId: req.TargetId,
|
||
Result: consts.AuditResultSuccess,
|
||
Remark: req.Remark,
|
||
})
|
||
return &v1.AuditRecordRes{}, nil
|
||
}
|