//nolint:unused package handlers import ( "context" "encoding/json" "fmt" "strconv" "strings" "github.com/gin-gonic/gin" "go.uber.org/zap" "tyapi-server/internal/application/user" "tyapi-server/internal/application/user/dto/commands" "tyapi-server/internal/application/user/dto/queries" _ "tyapi-server/internal/application/user/dto/responses" "tyapi-server/internal/config" "tyapi-server/internal/shared/crypto" "tyapi-server/internal/shared/interfaces" "tyapi-server/internal/shared/middleware" ) // UserHandler 用户HTTP处理器 type UserHandler struct { appService user.UserApplicationService response interfaces.ResponseBuilder validator interfaces.RequestValidator logger *zap.Logger jwtAuth *middleware.JWTAuthMiddleware config *config.Config cache interfaces.CacheService } // NewUserHandler 创建用户处理器 func NewUserHandler( appService user.UserApplicationService, response interfaces.ResponseBuilder, validator interfaces.RequestValidator, logger *zap.Logger, jwtAuth *middleware.JWTAuthMiddleware, cfg *config.Config, cache interfaces.CacheService, ) *UserHandler { return &UserHandler{ appService: appService, response: response, validator: validator, logger: logger, jwtAuth: jwtAuth, config: cfg, cache: cache, } } // decodedSendCodeData 解码后的请求数据结构 type decodedSendCodeData struct { Phone string `json:"phone"` Scene string `json:"scene"` Timestamp int64 `json:"timestamp"` Nonce string `json:"nonce"` Signature string `json:"signature"` } // SendCode 发送验证码 // @Summary 发送短信验证码 // @Description 向指定手机号发送验证码,支持注册、登录、修改密码等场景。需要提供有效的签名验证。只接收编码后的data字段(使用自定义编码方案) // @Tags 用户认证 // @Accept json // @Produce json // @Param request body commands.SendCodeCommand true "发送验证码请求(包含data字段和可选的captchaVerifyParam字段)" // @Success 200 {object} map[string]interface{} "验证码发送成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 429 {object} map[string]interface{} "请求频率限制" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/users/send-code [post] func (h *UserHandler) SendCode(c *gin.Context) { var cmd commands.SendCodeCommand // 绑定请求(包含data字段和可选的captchaVerifyParam字段) if err := c.ShouldBindJSON(&cmd); err != nil { h.response.BadRequest(c, "请求参数格式错误,必须提供data字段") return } // 验证data字段不为空 if cmd.Data == "" { h.response.BadRequest(c, "data字段不能为空") return } // 解码自定义编码的数据 decodedData, err := h.decodeRequestData(cmd.Data) if err != nil { h.logger.Warn("解码请求数据失败", zap.String("client_ip", c.ClientIP()), zap.Error(err)) h.response.BadRequest(c, "请求数据解码失败") return } // 验证必要字段 if decodedData.Phone == "" || decodedData.Scene == "" { h.response.BadRequest(c, "手机号和场景不能为空") return } // 如果启用了签名验证,进行签名校验(包含nonce唯一性检查,防止重放攻击) if h.config.SMS.SignatureEnabled { if err := h.verifyDecodedSignature(c.Request.Context(), decodedData); err != nil { h.logger.Warn("短信发送签名验证失败", zap.String("phone", decodedData.Phone), zap.String("scene", decodedData.Scene), zap.String("client_ip", c.ClientIP()), zap.Error(err)) // 根据错误类型返回不同的用户友好消息(不暴露技术细节) userMessage := h.getSignatureErrorMessage(err) h.response.BadRequest(c, userMessage) return } } // 构建SendCodeCommand用于调用应用服务 serviceCmd := &commands.SendCodeCommand{ Phone: decodedData.Phone, Scene: decodedData.Scene, Timestamp: decodedData.Timestamp, Nonce: decodedData.Nonce, Signature: decodedData.Signature, CaptchaVerifyParam: cmd.CaptchaVerifyParam, } clientIP := c.ClientIP() userAgent := c.GetHeader("User-Agent") if err := h.appService.SendCode(c.Request.Context(), serviceCmd, clientIP, userAgent); err != nil { h.response.BadRequest(c, err.Error()) return } h.response.Success(c, nil, "验证码发送成功") } // decodeRequestData 解码自定义编码的请求数据 func (h *UserHandler) decodeRequestData(encodedData string) (*decodedSendCodeData, error) { // 使用自定义编码方案解码 decodedData, err := crypto.DecodeRequest(encodedData) if err != nil { return nil, fmt.Errorf("自定义编码解码失败: %w", err) } // 解析JSON var decoded decodedSendCodeData if err := json.Unmarshal([]byte(decodedData), &decoded); err != nil { return nil, fmt.Errorf("JSON解析失败: %w", err) } return &decoded, nil } // verifyDecodedSignature 验证解码后的签名(包含nonce唯一性检查,防止重放攻击) func (h *UserHandler) verifyDecodedSignature(ctx context.Context, data *decodedSendCodeData) error { // 构建参数map(包含signature字段,VerifySignature会自动排除它) params := map[string]string{ "phone": data.Phone, "scene": data.Scene, "signature": data.Signature, } // 验证签名并检查nonce唯一性(防止重放攻击) return crypto.VerifySignatureWithNonceCheck( ctx, params, h.config.SMS.SignatureSecret, data.Timestamp, data.Nonce, h.cache, "sms:signature", // 缓存键前缀 ) } // getSignatureErrorMessage 根据错误类型返回用户友好的错误消息(不暴露技术细节) func (h *UserHandler) getSignatureErrorMessage(err error) string { errMsg := err.Error() // 根据错误消息内容判断错误类型,返回通用的用户友好消息 if strings.Contains(errMsg, "请求已被使用") || strings.Contains(errMsg, "重复提交") { // 重放攻击:返回通用消息,不暴露具体原因 return "请求无效,请重新操作" } if strings.Contains(errMsg, "时间戳") || strings.Contains(errMsg, "过期") { // 时间戳过期:返回通用消息 return "请求已过期,请重新操作" } if strings.Contains(errMsg, "签名") { // 签名错误:返回通用消息 return "请求验证失败,请重新操作" } // 其他错误:返回通用消息 return "请求验证失败,请重新操作" } // Register 用户注册 // @Summary 用户注册 // @Description 使用手机号、密码和验证码进行用户注册,需要确认密码 // @Tags 用户认证 // @Accept json // @Produce json // @Param request body commands.RegisterUserCommand true "用户注册请求" // @Success 201 {object} responses.RegisterUserResponse "注册成功" // @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效" // @Failure 409 {object} map[string]interface{} "手机号已存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/users/register [post] func (h *UserHandler) Register(c *gin.Context) { var cmd commands.RegisterUserCommand if err := h.validator.BindAndValidate(c, &cmd); err != nil { return } resp, err := h.appService.Register(c.Request.Context(), &cmd) if err != nil { h.logger.Error("注册用户失败", zap.Error(err)) h.response.BadRequest(c, err.Error()) return } h.response.Created(c, resp, "用户注册成功") } // LoginWithPassword 密码登录 // @Summary 用户密码登录 // @Description 使用手机号和密码进行用户登录,返回JWT令牌 // @Tags 用户认证 // @Accept json // @Produce json // @Param request body commands.LoginWithPasswordCommand true "密码登录请求" // @Success 200 {object} responses.LoginUserResponse "登录成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 401 {object} map[string]interface{} "用户名或密码错误" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/users/login-password [post] func (h *UserHandler) LoginWithPassword(c *gin.Context) { var cmd commands.LoginWithPasswordCommand if err := h.validator.BindAndValidate(c, &cmd); err != nil { return } resp, err := h.appService.LoginWithPassword(c.Request.Context(), &cmd) if err != nil { h.logger.Error("密码登录失败", zap.Error(err)) h.response.Unauthorized(c, "用户名或密码错误") return } h.response.Success(c, resp, "登录成功") } // LoginWithSMS 短信验证码登录 // @Summary 用户短信验证码登录 // @Description 使用手机号和短信验证码进行用户登录,返回JWT令牌 // @Tags 用户认证 // @Accept json // @Produce json // @Param request body commands.LoginWithSMSCommand true "短信登录请求" // @Success 200 {object} responses.LoginUserResponse "登录成功" // @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效" // @Failure 401 {object} map[string]interface{} "认证失败" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/users/login-sms [post] func (h *UserHandler) LoginWithSMS(c *gin.Context) { var cmd commands.LoginWithSMSCommand if err := h.validator.BindAndValidate(c, &cmd); err != nil { return } resp, err := h.appService.LoginWithSMS(c.Request.Context(), &cmd) if err != nil { h.logger.Error("短信登录失败", zap.Error(err)) h.response.Unauthorized(c, err.Error()) return } h.response.Success(c, resp, "登录成功") } // GetProfile 获取当前用户信息 // @Summary 获取当前用户信息 // @Description 根据JWT令牌获取当前登录用户的详细信息 // @Tags 用户管理 // @Accept json // @Produce json // @Security Bearer // @Success 200 {object} responses.UserProfileResponse "用户信息" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 404 {object} map[string]interface{} "用户不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/users/me [get] func (h *UserHandler) GetProfile(c *gin.Context) { userID := h.getCurrentUserID(c) if userID == "" { h.response.Unauthorized(c, "用户未登录") return } resp, err := h.appService.GetUserProfile(c.Request.Context(), userID) if err != nil { h.logger.Error("获取用户资料失败", zap.Error(err)) h.response.NotFound(c, "用户不存在") return } h.response.Success(c, resp, "获取用户资料成功") } // ChangePassword 修改密码 // @Summary 修改密码 // @Description 使用旧密码、新密码确认和验证码修改当前用户的密码 // @Tags 用户管理 // @Accept json // @Produce json // @Security Bearer // @Param request body commands.ChangePasswordCommand true "修改密码请求" // @Success 200 {object} map[string]interface{} "密码修改成功" // @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/users/me/password [put] func (h *UserHandler) ChangePassword(c *gin.Context) { userID := h.getCurrentUserID(c) if userID == "" { h.response.Unauthorized(c, "用户未登录") return } var cmd commands.ChangePasswordCommand if err := h.validator.BindAndValidate(c, &cmd); err != nil { return } cmd.UserID = userID if err := h.appService.ChangePassword(c.Request.Context(), &cmd); err != nil { h.logger.Error("修改密码失败", zap.Error(err)) h.response.BadRequest(c, err.Error()) return } h.response.Success(c, nil, "密码修改成功") } // ResetPassword 重置密码 // @Summary 重置密码 // @Description 使用手机号、验证码和新密码重置用户密码(忘记密码时使用) // @Tags 用户认证 // @Accept json // @Produce json // @Param request body commands.ResetPasswordCommand true "重置密码请求" // @Success 200 {object} map[string]interface{} "密码重置成功" // @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效" // @Failure 404 {object} map[string]interface{} "用户不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/users/reset-password [post] func (h *UserHandler) ResetPassword(c *gin.Context) { var cmd commands.ResetPasswordCommand if err := h.validator.BindAndValidate(c, &cmd); err != nil { return } if err := h.appService.ResetPassword(c.Request.Context(), &cmd); err != nil { h.logger.Error("重置密码失败", zap.Error(err)) h.response.BadRequest(c, err.Error()) return } h.response.Success(c, nil, "密码重置成功") } // ListUsers 管理员查看用户列表 // @Summary 管理员查看用户列表 // @Description 管理员查看用户列表,支持分页和筛选 // @Tags 用户管理 // @Accept json // @Produce json // @Security Bearer // @Param page query int false "页码" default(1) // @Param page_size query int false "每页数量" default(10) // @Param phone query string false "手机号筛选" // @Param user_type query string false "用户类型筛选" Enums(user,admin) // @Param is_active query bool false "是否激活筛选" // @Param is_certified query bool false "是否已认证筛选" // @Param company_name query string false "企业名称筛选" // @Param start_date query string false "开始日期" format(date) // @Param end_date query string false "结束日期" format(date) // @Success 200 {object} responses.UserListResponse "用户列表" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 403 {object} map[string]interface{} "权限不足" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/users/admin/list [get] func (h *UserHandler) ListUsers(c *gin.Context) { // 检查管理员权限 userID := h.getCurrentUserID(c) if userID == "" { h.response.Unauthorized(c, "用户未登录") return } // 构建查询参数 query := &queries.ListUsersQuery{ Page: 1, PageSize: 10, } // 从查询参数中获取筛选条件 if page := c.Query("page"); page != "" { if pageNum, err := strconv.Atoi(page); err == nil && pageNum > 0 { query.Page = pageNum } } if pageSize := c.Query("page_size"); pageSize != "" { if size, err := strconv.Atoi(pageSize); err == nil && size > 0 && size <= 1000 { query.PageSize = size } } query.Phone = c.Query("phone") query.UserType = c.Query("user_type") query.CompanyName = c.Query("company_name") query.StartDate = c.Query("start_date") query.EndDate = c.Query("end_date") // 处理布尔值参数 if isActive := c.Query("is_active"); isActive != "" { if active, err := strconv.ParseBool(isActive); err == nil { query.IsActive = &active } } if isCertified := c.Query("is_certified"); isCertified != "" { if certified, err := strconv.ParseBool(isCertified); err == nil { query.IsCertified = &certified } } // 调用应用服务 resp, err := h.appService.ListUsers(c.Request.Context(), query) if err != nil { h.logger.Error("获取用户列表失败", zap.Error(err)) h.response.BadRequest(c, "获取用户列表失败") return } h.response.Success(c, resp, "获取用户列表成功") } // GetUserDetail 管理员获取用户详情 // @Summary 管理员获取用户详情 // @Description 管理员获取指定用户的详细信息 // @Tags 用户管理 // @Accept json // @Produce json // @Security Bearer // @Param user_id path string true "用户ID" // @Success 200 {object} responses.UserDetailResponse "用户详情" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 403 {object} map[string]interface{} "权限不足" // @Failure 404 {object} map[string]interface{} "用户不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/users/admin/{user_id} [get] func (h *UserHandler) GetUserDetail(c *gin.Context) { // 检查管理员权限 userID := h.getCurrentUserID(c) if userID == "" { h.response.Unauthorized(c, "用户未登录") return } // 获取路径参数中的用户ID targetUserID := c.Param("user_id") if targetUserID == "" { h.response.BadRequest(c, "用户ID不能为空") return } // 调用应用服务 resp, err := h.appService.GetUserDetail(c.Request.Context(), targetUserID) if err != nil { h.logger.Error("获取用户详情失败", zap.Error(err), zap.String("target_user_id", targetUserID)) h.response.BadRequest(c, "获取用户详情失败") return } h.response.Success(c, resp, "获取用户详情成功") } // GetUserStats 获取用户统计信息 // @Summary 获取用户统计信息 // @Description 管理员获取用户相关的统计信息 // @Tags 用户管理 // @Accept json // @Produce json // @Security Bearer // @Success 200 {object} responses.UserStatsResponse "用户统计信息" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 403 {object} map[string]interface{} "权限不足" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/users/admin/stats [get] func (h *UserHandler) GetUserStats(c *gin.Context) { // 调用应用服务 resp, err := h.appService.GetUserStats(c.Request.Context()) if err != nil { h.logger.Error("获取用户统计信息失败", zap.Error(err)) h.response.BadRequest(c, "获取用户统计信息失败") return } h.response.Success(c, resp, "获取用户统计信息成功") } // getCurrentUserID 获取当前用户ID func (h *UserHandler) getCurrentUserID(c *gin.Context) string { if userID, exists := c.Get("user_id"); exists { if id, ok := userID.(string); ok { return id } } return "" }