This commit is contained in:
2026-02-12 13:27:08 +08:00
parent a38c58c357
commit f400052f95
6 changed files with 770 additions and 7 deletions

View File

@@ -2,6 +2,8 @@
package handlers
import (
"encoding/json"
"fmt"
"strconv"
"github.com/gin-gonic/gin"
@@ -11,6 +13,8 @@ import (
"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"
)
@@ -22,6 +26,7 @@ type UserHandler struct {
validator interfaces.RequestValidator
logger *zap.Logger
jwtAuth *middleware.JWTAuthMiddleware
config *config.Config
}
// NewUserHandler 创建用户处理器
@@ -31,6 +36,7 @@ func NewUserHandler(
validator interfaces.RequestValidator,
logger *zap.Logger,
jwtAuth *middleware.JWTAuthMiddleware,
cfg *config.Config,
) *UserHandler {
return &UserHandler{
appService: appService,
@@ -38,16 +44,26 @@ func NewUserHandler(
validator: validator,
logger: logger,
jwtAuth: jwtAuth,
config: cfg,
}
}
// 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 向指定手机号发送验证码,支持注册、登录、修改密码等场景
// @Description 向指定手机号发送验证码,支持注册、登录、修改密码等场景。需要提供有效的签名验证。只接收编码后的data字段使用自定义编码方案
// @Tags 用户认证
// @Accept json
// @Produce json
// @Param request body commands.SendCodeCommand true "发送验证码请求"
// @Param request body commands.SendCodeCommand true "发送验证码请求只包含data字段"
// @Success 200 {object} map[string]interface{} "验证码发送成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 429 {object} map[string]interface{} "请求频率限制"
@@ -55,14 +71,61 @@ func NewUserHandler(
// @Router /api/v1/users/send-code [post]
func (h *UserHandler) SendCode(c *gin.Context) {
var cmd commands.SendCodeCommand
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
// 绑定请求只包含data字段
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
}
// 如果启用了签名验证,进行签名校验
if h.config.SMS.SignatureEnabled {
if err := h.verifyDecodedSignature(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))
h.response.BadRequest(c, "签名验证失败,请求无效")
return
}
}
// 构建SendCodeCommand用于调用应用服务
serviceCmd := &commands.SendCodeCommand{
Phone: decodedData.Phone,
Scene: decodedData.Scene,
Timestamp: decodedData.Timestamp,
Nonce: decodedData.Nonce,
Signature: decodedData.Signature,
}
clientIP := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
if err := h.appService.SendCode(c.Request.Context(), &cmd, clientIP, userAgent); err != nil {
if err := h.appService.SendCode(c.Request.Context(), serviceCmd, clientIP, userAgent); err != nil {
h.response.BadRequest(c, err.Error())
return
}
@@ -70,6 +133,42 @@ func (h *UserHandler) SendCode(c *gin.Context) {
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 验证解码后的签名
func (h *UserHandler) verifyDecodedSignature(data *decodedSendCodeData) error {
// 构建参数map包含signature字段VerifySignature会自动排除它
params := map[string]string{
"phone": data.Phone,
"scene": data.Scene,
"signature": data.Signature,
}
// 验证签名
return crypto.VerifySignature(
params,
h.config.SMS.SignatureSecret,
data.Timestamp,
data.Nonce,
)
}
// Register 用户注册
// @Summary 用户注册
// @Description 使用手机号、密码和验证码进行用户注册,需要确认密码