Files
pig-farm-controller/internal/app/controller/user/user_controller.go
2025-10-19 20:36:10 +08:00

195 lines
7.3 KiB
Go
Raw 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 user
import (
"errors"
"strconv"
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// Controller 用户控制器
type Controller struct {
userRepo repository.UserRepository
monitorService service.MonitorService
tokenService token.TokenService // 注入 token 服务
logger *logs.Logger
}
// NewController 创建用户控制器实例
func NewController(userRepo repository.UserRepository, monitorService service.MonitorService, logger *logs.Logger, tokenService token.TokenService) *Controller {
return &Controller{
userRepo: userRepo,
monitorService: monitorService,
tokenService: tokenService,
logger: logger,
}
}
// --- Controller Methods ---
// CreateUser godoc
// @Summary 创建新用户
// @Description 根据用户名和密码创建一个新的系统用户。
// @Tags 用户管理
// @Accept json
// @Produce json
// @Param user body dto.CreateUserRequest true "用户信息"
// @Success 200 {object} controller.Response{data=dto.CreateUserResponse} "业务码为201代表创建成功"
// @Router /api/v1/users [post]
func (c *Controller) CreateUser(ctx *gin.Context) {
var req dto.CreateUserRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
c.logger.Errorf("创建用户: 参数绑定失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error())
return
}
user := &models.User{
Username: req.Username,
Password: req.Password, // 密码会在 BeforeSave 钩子中哈希
}
if err := c.userRepo.Create(user); err != nil {
c.logger.Errorf("创建用户: 创建用户失败: %v", err)
// 尝试查询用户,以判断是否是用户名重复导致的错误
_, findErr := c.userRepo.FindByUsername(req.Username)
if findErr == nil { // 如果能找到用户,说明是用户名重复
controller.SendErrorResponse(ctx, controller.CodeConflict, "用户名已存在")
return
}
// 其他创建失败的情况
controller.SendErrorResponse(ctx, controller.CodeInternalError, "创建用户失败")
return
}
controller.SendResponse(ctx, controller.CodeCreated, "用户创建成功", dto.CreateUserResponse{
Username: user.Username,
ID: user.ID,
})
}
// Login godoc
// @Summary 用户登录
// @Description 用户可以使用用户名、邮箱、手机号、微信号或飞书账号进行登录,成功后返回 JWT 令牌。
// @Tags 用户管理
// @Accept json
// @Produce json
// @Param credentials body dto.LoginRequest true "登录凭证"
// @Success 200 {object} controller.Response{data=dto.LoginResponse} "业务码为200代表登录成功"
// @Router /api/v1/users/login [post]
func (c *Controller) Login(ctx *gin.Context) {
var req dto.LoginRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
c.logger.Errorf("登录: 参数绑定失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error())
return
}
// 使用新的方法,通过唯一标识符(用户名、邮箱等)查找用户
user, err := c.userRepo.FindUserForLogin(req.Identifier)
if err != nil {
if err == gorm.ErrRecordNotFound {
controller.SendErrorResponse(ctx, controller.CodeUnauthorized, "登录凭证不正确")
return
}
c.logger.Errorf("登录: 查询用户失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "登录失败")
return
}
if !user.CheckPassword(req.Password) {
controller.SendErrorResponse(ctx, controller.CodeUnauthorized, "登录凭证不正确")
return
}
// 登录成功,生成 JWT token
tokenString, err := c.tokenService.GenerateToken(user.ID)
if err != nil {
c.logger.Errorf("登录: 生成令牌失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "登录失败,无法生成认证信息")
return
}
controller.SendResponse(ctx, controller.CodeSuccess, "登录成功", dto.LoginResponse{
Username: user.Username,
ID: user.ID,
Token: tokenString,
})
}
// ListUserHistory godoc
// @Summary 获取指定用户的操作历史
// @Description 根据用户ID分页获取该用户的操作审计日志。支持与通用日志查询接口相同的过滤和排序参数。
// @Tags 用户管理
// @Security BearerAuth
// @Produce json
// @Param id path int true "用户ID"
// @Param query query dto.ListUserActionLogRequest false "查询参数 (除了 user_id它被路径中的ID覆盖)"
// @Success 200 {object} controller.Response{data=dto.ListUserActionLogResponse} "业务码为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. 绑定通用的查询请求 DTO
var req dto.ListUserActionLogRequest
if err := ctx.ShouldBindQuery(&req); err != nil {
c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
return
}
// 3. 准备 Service 调用参数,并强制使用路径中的 UserID
uid := uint(userID)
req.UserID = &uid // 强制覆盖
opts := repository.UserActionLogListOptions{
UserID: req.UserID,
Username: req.Username,
ActionType: req.ActionType,
OrderBy: req.OrderBy,
StartTime: req.StartTime,
EndTime: req.EndTime,
}
if req.Status != nil {
status := models.AuditStatus(*req.Status)
opts.Status = &status
}
// 4. 调用 monitorService复用其业务逻辑
data, total, err := c.monitorService.ListUserActionLogs(opts, req.Page, req.PageSize)
if err != nil {
if errors.Is(err, repository.ErrInvalidPagination) {
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", opts)
return
}
c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取用户历史记录失败", actionType, "服务层查询失败", opts)
return
}
// 5. 使用复用的 DTO 构建并发送成功响应
resp := dto.NewListUserActionLogResponse(data, total, req.Page, req.PageSize)
c.logger.Infof("%s: 成功获取用户 %d 的操作历史, 数量: %d", actionType, userID, len(data))
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作历史成功", resp, actionType, "获取用户操作历史成功", opts)
}