package model import ( "context" "fmt" "github.com/QuantumNous/new-api/common" "github.com/gin-gonic/gin" ) // OperationLog 记录用户对数据的增删改操作,用于审计。 type OperationLog struct { Id int `json:"id" gorm:"index:idx_oplog_created_at_id,priority:1"` UserId int `json:"user_id" gorm:"index;index:idx_oplog_user_id_id,priority:1"` Username string `json:"username" gorm:"index;default:''"` CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_oplog_created_at_id,priority:2;index:idx_oplog_created_at_action"` Action string `json:"action" gorm:"type:varchar(32);index;index:idx_oplog_created_at_action"` // create / update / delete TargetType string `json:"target_type" gorm:"type:varchar(64);index;default:''"` // 资源类型: channel, token, user, redemption, model, setting … TargetId int `json:"target_id" gorm:"index;default:0"` // 资源 ID TargetName string `json:"target_name" gorm:"type:varchar(255);default:''"` // 资源名称(便于检索) Content string `json:"content"` // 操作描述 Ip string `json:"ip" gorm:"index;default:''"` // 操作者 IP RequestBody string `json:"request_body" gorm:"type:text"` // 请求体快照(脱敏后) UserRole int `json:"user_role" gorm:"default:0"` // 操作者角色 } // 操作类型常量 const ( OperationActionCreate = "create" OperationActionUpdate = "update" OperationActionDelete = "delete" ) // RecordOperationLog 记录一条操作日志。 func RecordOperationLog(c *gin.Context, userId int, action string, targetType string, targetId int, targetName string, content string, requestBody string) { username := c.GetString("username") userRole := c.GetInt("role") ip := c.ClientIP() log := &OperationLog{ UserId: userId, Username: username, CreatedAt: common.GetTimestamp(), Action: action, TargetType: targetType, TargetId: targetId, TargetName: targetName, Content: content, Ip: ip, RequestBody: requestBody, UserRole: userRole, } err := DB.Create(log).Error if err != nil { common.SysError("failed to record operation log: " + err.Error()) } } // GetOperationLogs 管理员分页查询操作日志。 func GetOperationLogs(action string, targetType string, targetId int, username string, startTimestamp int64, endTimestamp int64, startIdx int, num int) (logs []*OperationLog, total int64, err error) { tx := DB.Model(&OperationLog{}) if action != "" { tx = tx.Where("action = ?", action) } if targetType != "" { tx = tx.Where("target_type = ?", targetType) } if targetId != 0 { tx = tx.Where("target_id = ?", targetId) } if username != "" { tx = tx.Where("username = ?", username) } if startTimestamp != 0 { tx = tx.Where("created_at >= ?", startTimestamp) } if endTimestamp != 0 { tx = tx.Where("created_at <= ?", endTimestamp) } err = tx.Count(&total).Error if err != nil { return nil, 0, err } err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&logs).Error return logs, total, err } // GetUserOperationLogs 普通用户查询自己的操作日志。 func GetUserOperationLogs(userId int, action string, targetType string, startTimestamp int64, endTimestamp int64, startIdx int, num int) (logs []*OperationLog, total int64, err error) { tx := DB.Model(&OperationLog{}).Where("user_id = ?", userId) if action != "" { tx = tx.Where("action = ?", action) } if targetType != "" { tx = tx.Where("target_type = ?", targetType) } if startTimestamp != 0 { tx = tx.Where("created_at >= ?", startTimestamp) } if endTimestamp != 0 { tx = tx.Where("created_at <= ?", endTimestamp) } err = tx.Count(&total).Error if err != nil { return nil, 0, err } err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&logs).Error return logs, total, err } // DeleteOldOperationLogs 清理指定时间之前的操作日志。 func DeleteOldOperationLogs(ctx context.Context, targetTimestamp int64, limit int) (int64, error) { var total int64 = 0 for { if ctx.Err() != nil { return total, ctx.Err() } result := DB.Where("created_at < ?", targetTimestamp).Limit(limit).Delete(&OperationLog{}) if result.Error != nil { return total, result.Error } total += result.RowsAffected if result.RowsAffected < int64(limit) { break } } return total, nil } // GetOperationLogStats 按操作类型统计数量。 func GetOperationLogStats(startTimestamp int64, endTimestamp int64) (map[string]int64, error) { type result struct { Action string Count int64 } var results []result tx := DB.Model(&OperationLog{}).Select("action, count(*) as count") if startTimestamp != 0 { tx = tx.Where("created_at >= ?", startTimestamp) } if endTimestamp != 0 { tx = tx.Where("created_at <= ?", endTimestamp) } tx = tx.Group("action") if err := tx.Scan(&results).Error; err != nil { return nil, fmt.Errorf("查询操作日志统计失败: %w", err) } stats := make(map[string]int64, len(results)) for _, r := range results { stats[r.Action] = r.Count } return stats, nil } // SanitizeRequestBody 脱敏请求体中的敏感字段。 func SanitizeRequestBody(body string) string { if body == "" { return "" } var m map[string]interface{} if err := common.Unmarshal([]byte(body), &m); err != nil { // 无法解析为 JSON,直接返回截断后的原始内容 if len(body) > 2000 { return body[:2000] + "...(truncated)" } return body } // 脱敏敏感字段 sensitiveKeys := []string{"password", "key", "secret", "token", "access_token", "refresh_token", "api_key", "credential"} for _, k := range sensitiveKeys { if _, ok := m[k]; ok { m[k] = "******" } } sanitized, err := common.Marshal(m) if err != nil { return body } result := string(sanitized) if len(result) > 4000 { result = result[:4000] + "...(truncated)" } return result }