ListUserHistory 实现
This commit is contained in:
@@ -56,6 +56,7 @@ func NewAPI(cfg config.ServerConfig,
|
||||
userRepo repository.UserRepository,
|
||||
deviceRepository repository.DeviceRepository,
|
||||
planRepository repository.PlanRepository,
|
||||
userActionLogRepository repository.UserActionLogRepository,
|
||||
tokenService token.TokenService,
|
||||
auditService audit.Service, // 注入审计服务
|
||||
listenHandler transport.ListenHandler,
|
||||
@@ -82,7 +83,7 @@ func NewAPI(cfg config.ServerConfig,
|
||||
config: cfg,
|
||||
listenHandler: listenHandler,
|
||||
// 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员
|
||||
userController: user.NewController(userRepo, logger, tokenService),
|
||||
userController: user.NewController(userRepo, userActionLogRepository, logger, tokenService),
|
||||
// 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员
|
||||
deviceController: device.NewController(deviceRepository, logger),
|
||||
// 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员
|
||||
@@ -140,6 +141,13 @@ func (a *API) setupRoutes() {
|
||||
authGroup.Use(middleware.AuthMiddleware(a.tokenService, a.userRepo)) // 1. 身份认证
|
||||
authGroup.Use(middleware.AuditLogMiddleware(a.auditService)) // 2. 审计日志
|
||||
{
|
||||
// 用户相关路由组
|
||||
userGroup := authGroup.Group("/users")
|
||||
{
|
||||
userGroup.GET("/:id/history", a.userController.ListUserHistory)
|
||||
}
|
||||
a.logger.Info("用户相关接口注册成功 (需要认证和审计)")
|
||||
|
||||
// 设备相关路由组
|
||||
deviceGroup := authGroup.Group("/devices")
|
||||
{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service/token"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
@@ -13,19 +16,23 @@ import (
|
||||
// Controller 用户控制器
|
||||
type Controller struct {
|
||||
userRepo repository.UserRepository
|
||||
auditRepo repository.UserActionLogRepository
|
||||
logger *logs.Logger
|
||||
tokenService token.TokenService // 注入 token 服务
|
||||
}
|
||||
|
||||
// NewController 创建用户控制器实例
|
||||
func NewController(userRepo repository.UserRepository, logger *logs.Logger, tokenService token.TokenService) *Controller {
|
||||
func NewController(userRepo repository.UserRepository, auditRepo repository.UserActionLogRepository, logger *logs.Logger, tokenService token.TokenService) *Controller {
|
||||
return &Controller{
|
||||
userRepo: userRepo,
|
||||
auditRepo: auditRepo,
|
||||
logger: logger,
|
||||
tokenService: tokenService,
|
||||
}
|
||||
}
|
||||
|
||||
// --- DTOs ---
|
||||
|
||||
// CreateUserRequest 定义创建用户请求的结构体
|
||||
type CreateUserRequest struct {
|
||||
Username string `json:"username" binding:"required" example:"newuser"`
|
||||
@@ -52,6 +59,24 @@ type LoginResponse struct {
|
||||
Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
|
||||
}
|
||||
|
||||
// HistoryResponse 定义单条操作历史的响应结构体
|
||||
type HistoryResponse struct {
|
||||
UserID uint `json:"user_id" example:"101"`
|
||||
Username string `json:"username" example:"testuser"`
|
||||
ActionType string `json:"action_type" example:"更新设备"`
|
||||
Description string `json:"description" example:"设备更新成功"`
|
||||
TargetResource interface{} `json:"target_resource"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
// ListHistoryResponse 定义操作历史列表的响应结构体
|
||||
type ListHistoryResponse struct {
|
||||
History []HistoryResponse `json:"history"`
|
||||
Total int64 `json:"total" example:"100"`
|
||||
}
|
||||
|
||||
// --- Controller Methods ---
|
||||
|
||||
// CreateUser godoc
|
||||
// @Summary 创建新用户
|
||||
// @Description 根据用户名和密码创建一个新的系统用户。
|
||||
@@ -143,3 +168,76 @@ func (c *Controller) Login(ctx *gin.Context) {
|
||||
Token: tokenString,
|
||||
})
|
||||
}
|
||||
|
||||
// ListUserHistory godoc
|
||||
// @Summary 获取指定用户的操作历史
|
||||
// @Description 根据用户ID,分页获取该用户的操作审计日志。
|
||||
// @Tags 用户管理
|
||||
// @Produce json
|
||||
// @Param id path int true "用户ID"
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页大小" default(10)
|
||||
// @Param action_type query string false "按操作类型过滤"
|
||||
// @Success 200 {object} controller.Response{data=user.ListHistoryResponse} "业务码为200代表成功获取"
|
||||
// @Router /api/v1/users/{id}/history [get]
|
||||
func (c *Controller) ListUserHistory(ctx *gin.Context) {
|
||||
const actionType = "获取用户操作历史"
|
||||
|
||||
// 1. 解析路径中的用户ID
|
||||
userIDStr := ctx.Param("id")
|
||||
userID, err := strconv.ParseUint(userIDStr, 10, 64)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 无效的用户ID格式: %v, ID: %s", actionType, err, userIDStr)
|
||||
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的用户ID格式", actionType, "无效的用户ID格式", userIDStr)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 解析分页和过滤参数
|
||||
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(ctx.DefaultQuery("page_size", "10"))
|
||||
actionTypeFilter := ctx.Query("action_type")
|
||||
|
||||
// 确保分页参数有效
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
// 3. 调用审计仓库层获取历史数据
|
||||
id := uint(userID)
|
||||
findOptions := repository.FindAuditLogOptions{
|
||||
UserID: &id,
|
||||
ActionType: actionTypeFilter,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}
|
||||
logs, total, err := c.auditRepo.List(findOptions)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 查询历史记录失败: %v, Options: %+v", actionType, err, findOptions)
|
||||
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "查询历史记录失败", actionType, "查询历史记录失败", findOptions)
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 将数据库模型转换为响应 DTO
|
||||
historyResponses := make([]HistoryResponse, 0, len(logs))
|
||||
for _, log := range logs {
|
||||
historyResponses = append(historyResponses, HistoryResponse{
|
||||
UserID: log.UserID,
|
||||
Username: log.Username,
|
||||
ActionType: log.ActionType,
|
||||
Description: log.Description,
|
||||
TargetResource: log.TargetResource,
|
||||
Time: log.Time.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
// 5. 发送成功响应
|
||||
resp := ListHistoryResponse{
|
||||
History: historyResponses,
|
||||
Total: total,
|
||||
}
|
||||
c.logger.Infof("%s: 成功获取用户 %d 的操作历史, 数量: %d", actionType, userID, len(historyResponses))
|
||||
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作历史成功", resp, actionType, "获取用户操作历史成功", resp)
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ func NewApplication(configPath string) (*Application, error) {
|
||||
userRepo,
|
||||
deviceRepo,
|
||||
planRepo,
|
||||
userActionLogRepo,
|
||||
tokenService,
|
||||
auditService,
|
||||
listenHandler,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Package repository 提供了数据访问的仓库实现
|
||||
package repository
|
||||
|
||||
import (
|
||||
@@ -6,9 +5,18 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// FindAuditLogOptions 定义了查询审计日志的选项
|
||||
type FindAuditLogOptions struct {
|
||||
UserID *uint // 根据用户ID过滤
|
||||
ActionType string // 根据操作类型过滤
|
||||
Page int // 页码
|
||||
PageSize int // 每页大小
|
||||
}
|
||||
|
||||
// UserActionLogRepository 定义了与用户操作日志相关的数据库操作接口
|
||||
type UserActionLogRepository interface {
|
||||
Create(log *models.UserActionLog) error
|
||||
List(options FindAuditLogOptions) ([]*models.UserActionLog, int64, error)
|
||||
}
|
||||
|
||||
// gormUserActionLogRepository 是 UserActionLogRepository 的 GORM 实现
|
||||
@@ -25,3 +33,38 @@ func NewGormUserActionLogRepository(db *gorm.DB) UserActionLogRepository {
|
||||
func (r *gormUserActionLogRepository) Create(log *models.UserActionLog) error {
|
||||
return r.db.Create(log).Error
|
||||
}
|
||||
|
||||
// List 根据选项查询用户操作日志,并返回总数
|
||||
func (r *gormUserActionLogRepository) List(options FindAuditLogOptions) ([]*models.UserActionLog, int64, error) {
|
||||
var logs []*models.UserActionLog
|
||||
var total int64
|
||||
|
||||
query := r.db.Model(&models.UserActionLog{})
|
||||
|
||||
if options.UserID != nil {
|
||||
query = query.Where("user_id = ?", *options.UserID)
|
||||
}
|
||||
if options.ActionType != "" {
|
||||
query = query.Where("action_type = ?", options.ActionType)
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
if options.Page > 0 && options.PageSize > 0 {
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
query = query.Offset(offset).Limit(options.PageSize)
|
||||
}
|
||||
|
||||
// 默认按创建时间倒序
|
||||
query = query.Order("created_at DESC")
|
||||
|
||||
if err := query.Find(&logs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return logs, total, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user