Files
pig-farm-controller/internal/app/controller/user/user_controller.go
2025-10-13 14:15:38 +08:00

202 lines
7.1 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 (
"strconv"
"time"
"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/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
auditRepo repository.UserActionLogRepository
logger *logs.Logger
tokenService token.TokenService // 注入 token 服务
}
// NewController 创建用户控制器实例
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,
}
}
// --- 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 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=dto.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([]dto.HistoryResponse, 0, len(logs))
for _, log := range logs {
historyResponses = append(historyResponses, dto.HistoryResponse{
UserID: log.UserID,
Username: log.Username,
ActionType: log.ActionType,
Description: log.Description,
TargetResource: log.TargetResource,
Time: log.Time.Format(time.RFC3339),
})
}
// 5. 发送成功响应
resp := dto.ListHistoryResponse{
History: historyResponses,
Total: total,
}
c.logger.Infof("%s: 成功获取用户 %d 的操作历史, 数量: %d", actionType, userID, len(historyResponses))
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作历史成功", resp, actionType, "获取用户操作历史成功", resp)
}