add
This commit is contained in:
92
internal/infrastructure/http/handlers/captcha_handler.go
Normal file
92
internal/infrastructure/http/handlers/captcha_handler.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/infrastructure/external/captcha"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// CaptchaHandler 验证码(滑块)HTTP 处理器
|
||||
type CaptchaHandler struct {
|
||||
captchaService *captcha.CaptchaService
|
||||
response interfaces.ResponseBuilder
|
||||
config *config.Config
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCaptchaHandler 创建验证码处理器
|
||||
func NewCaptchaHandler(
|
||||
captchaService *captcha.CaptchaService,
|
||||
response interfaces.ResponseBuilder,
|
||||
cfg *config.Config,
|
||||
logger *zap.Logger,
|
||||
) *CaptchaHandler {
|
||||
return &CaptchaHandler{
|
||||
captchaService: captchaService,
|
||||
response: response,
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// EncryptedSceneIdReq 获取加密场景 ID 的请求(可选参数)
|
||||
type EncryptedSceneIdReq struct {
|
||||
ExpireSeconds *int `form:"expire_seconds" json:"expire_seconds"` // 有效期秒数,1~86400,默认 3600
|
||||
}
|
||||
|
||||
// GetEncryptedSceneId 获取加密场景 ID,供前端加密模式初始化阿里云验证码
|
||||
// @Summary 获取验证码加密场景ID
|
||||
// @Description 用于加密模式下发 EncryptedSceneId,前端用此初始化滑块验证码
|
||||
// @Tags 验证码
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body EncryptedSceneIdReq false "可选:expire_seconds 有效期(1-86400),默认3600"
|
||||
// @Success 200 {object} map[string]interface{} "encryptedSceneId"
|
||||
// @Failure 400 {object} map[string]interface{} "配置未启用或参数错误"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/captcha/encryptedSceneId [post]
|
||||
func (h *CaptchaHandler) GetEncryptedSceneId(c *gin.Context) {
|
||||
expireSec := 3600
|
||||
if c.Request.ContentLength > 0 {
|
||||
var req EncryptedSceneIdReq
|
||||
if err := c.ShouldBindJSON(&req); err == nil && req.ExpireSeconds != nil {
|
||||
expireSec = *req.ExpireSeconds
|
||||
}
|
||||
}
|
||||
if expireSec <= 0 || expireSec > 86400 {
|
||||
h.response.BadRequest(c, "expire_seconds 必须在 1~86400 之间")
|
||||
return
|
||||
}
|
||||
|
||||
encrypted, err := h.captchaService.GetEncryptedSceneId(expireSec)
|
||||
if err != nil {
|
||||
if err == captcha.ErrCaptchaEncryptMissing || err == captcha.ErrCaptchaConfig {
|
||||
h.logger.Warn("验证码加密场景ID生成失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "验证码加密模式未配置或配置错误")
|
||||
return
|
||||
}
|
||||
h.logger.Error("验证码加密场景ID生成失败", zap.Error(err))
|
||||
h.response.InternalError(c, "生成失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, map[string]string{"encryptedSceneId": encrypted}, "ok")
|
||||
}
|
||||
|
||||
// GetConfig 获取验证码前端配置(是否启用、场景ID等),便于前端决定是否展示滑块
|
||||
// @Summary 获取验证码配置
|
||||
// @Description 返回是否启用滑块、场景ID(非加密模式用)
|
||||
// @Tags 验证码
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{} "captchaEnabled, sceneId"
|
||||
// @Router /api/v1/captcha/config [get]
|
||||
func (h *CaptchaHandler) GetConfig(c *gin.Context) {
|
||||
data := map[string]interface{}{
|
||||
"captchaEnabled": h.config.SMS.CaptchaEnabled,
|
||||
"sceneId": h.config.SMS.SceneID,
|
||||
}
|
||||
h.response.Success(c, data, "ok")
|
||||
}
|
||||
@@ -68,7 +68,7 @@ type decodedSendCodeData struct {
|
||||
// @Tags 用户认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.SendCodeCommand true "发送验证码请求(只包含data字段)"
|
||||
// @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{} "请求频率限制"
|
||||
@@ -77,7 +77,7 @@ type decodedSendCodeData struct {
|
||||
func (h *UserHandler) SendCode(c *gin.Context) {
|
||||
var cmd commands.SendCodeCommand
|
||||
|
||||
// 绑定请求(只包含data字段)
|
||||
// 绑定请求(包含data字段和可选的captchaVerifyParam字段)
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.response.BadRequest(c, "请求参数格式错误,必须提供data字段")
|
||||
return
|
||||
@@ -123,11 +123,12 @@ func (h *UserHandler) SendCode(c *gin.Context) {
|
||||
|
||||
// 构建SendCodeCommand用于调用应用服务
|
||||
serviceCmd := &commands.SendCodeCommand{
|
||||
Phone: decodedData.Phone,
|
||||
Scene: decodedData.Scene,
|
||||
Timestamp: decodedData.Timestamp,
|
||||
Nonce: decodedData.Nonce,
|
||||
Signature: decodedData.Signature,
|
||||
Phone: decodedData.Phone,
|
||||
Scene: decodedData.Scene,
|
||||
Timestamp: decodedData.Timestamp,
|
||||
Nonce: decodedData.Nonce,
|
||||
Signature: decodedData.Signature,
|
||||
CaptchaVerifyParam: cmd.CaptchaVerifyParam,
|
||||
}
|
||||
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
33
internal/infrastructure/http/routes/captcha_routes.go
Normal file
33
internal/infrastructure/http/routes/captcha_routes.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"tyapi-server/internal/infrastructure/http/handlers"
|
||||
sharedhttp "tyapi-server/internal/shared/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// CaptchaRoutes 验证码路由
|
||||
type CaptchaRoutes struct {
|
||||
handler *handlers.CaptchaHandler
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCaptchaRoutes 创建验证码路由
|
||||
func NewCaptchaRoutes(handler *handlers.CaptchaHandler, logger *zap.Logger) *CaptchaRoutes {
|
||||
return &CaptchaRoutes{
|
||||
handler: handler,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册验证码相关路由
|
||||
func (r *CaptchaRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
engine := router.GetEngine()
|
||||
captchaGroup := engine.Group("/api/v1/captcha")
|
||||
{
|
||||
captchaGroup.POST("/encryptedSceneId", r.handler.GetEncryptedSceneId)
|
||||
captchaGroup.GET("/config", r.handler.GetConfig)
|
||||
}
|
||||
r.logger.Info("验证码路由注册完成")
|
||||
}
|
||||
Reference in New Issue
Block a user