实现面向个人客户的 7 个认证接口(A1-A7),覆盖资产验证、 微信公众号/小程序登录、手机号绑定/换绑、退出登录完整流程。 主要变更: - 新增 PersonalCustomerOpenID 模型,支持多 AppID 多 OpenID 管理 - 实现有状态 JWT(JWT + Redis 双重校验),支持服务端主动失效 - 扩展微信 SDK:小程序 Code2Session + 3 个 DB 动态工厂函数 - 实现 A1 资产验证 IP 限流(30/min)和 A4 三层验证码限流 - 新增 7 个错误码(1180-1186)和 6 个 Redis Key 函数 - 注册 /api/c/v1/auth/* 下 7 个端点并更新 OpenAPI 文档 - 数据库迁移 000083:新建 tb_personal_customer_openid 表
166 lines
4.8 KiB
Go
166 lines
4.8 KiB
Go
package app
|
|
|
|
import (
|
|
"github.com/break/junhong_cmp_fiber/internal/middleware"
|
|
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
|
clientAuthSvc "github.com/break/junhong_cmp_fiber/internal/service/client_auth"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
"github.com/break/junhong_cmp_fiber/pkg/logger"
|
|
"github.com/break/junhong_cmp_fiber/pkg/response"
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/gofiber/fiber/v2"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var clientAuthValidator = validator.New()
|
|
|
|
// ClientAuthHandler C 端认证处理器
|
|
type ClientAuthHandler struct {
|
|
service *clientAuthSvc.Service
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewClientAuthHandler 创建 C 端认证处理器
|
|
func NewClientAuthHandler(service *clientAuthSvc.Service, logger *zap.Logger) *ClientAuthHandler {
|
|
return &ClientAuthHandler{service: service, logger: logger}
|
|
}
|
|
|
|
// VerifyAsset A1 资产验证
|
|
// POST /api/c/v1/auth/verify-asset
|
|
func (h *ClientAuthHandler) VerifyAsset(c *fiber.Ctx) error {
|
|
var req dto.VerifyAssetRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
if err := clientAuthValidator.Struct(&req); err != nil {
|
|
logger.GetAppLogger().Warn("资产验证参数校验失败", zap.Error(err))
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
|
|
resp, err := h.service.VerifyAsset(c.UserContext(), &req, c.IP())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, resp)
|
|
}
|
|
|
|
// WechatLogin A2 公众号登录
|
|
// POST /api/c/v1/auth/wechat-login
|
|
func (h *ClientAuthHandler) WechatLogin(c *fiber.Ctx) error {
|
|
var req dto.WechatLoginRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
if err := clientAuthValidator.Struct(&req); err != nil {
|
|
logger.GetAppLogger().Warn("公众号登录参数校验失败", zap.Error(err))
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
|
|
resp, err := h.service.WechatLogin(c.UserContext(), &req, c.IP())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, resp)
|
|
}
|
|
|
|
// MiniappLogin A3 小程序登录
|
|
// POST /api/c/v1/auth/miniapp-login
|
|
func (h *ClientAuthHandler) MiniappLogin(c *fiber.Ctx) error {
|
|
var req dto.MiniappLoginRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
if err := clientAuthValidator.Struct(&req); err != nil {
|
|
logger.GetAppLogger().Warn("小程序登录参数校验失败", zap.Error(err))
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
|
|
resp, err := h.service.MiniappLogin(c.UserContext(), &req, c.IP())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, resp)
|
|
}
|
|
|
|
// SendCode A4 发送验证码
|
|
// POST /api/c/v1/auth/send-code
|
|
func (h *ClientAuthHandler) SendCode(c *fiber.Ctx) error {
|
|
var req dto.ClientSendCodeRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
if err := clientAuthValidator.Struct(&req); err != nil {
|
|
logger.GetAppLogger().Warn("发送验证码参数校验失败", zap.Error(err))
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
|
|
resp, err := h.service.SendCode(c.UserContext(), &req, c.IP())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, resp)
|
|
}
|
|
|
|
// BindPhone A5 绑定手机号
|
|
// POST /api/c/v1/auth/bind-phone
|
|
func (h *ClientAuthHandler) BindPhone(c *fiber.Ctx) error {
|
|
customerID, ok := middleware.GetCustomerID(c)
|
|
if !ok || customerID == 0 {
|
|
return errors.New(errors.CodeUnauthorized)
|
|
}
|
|
|
|
var req dto.BindPhoneRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
if err := clientAuthValidator.Struct(&req); err != nil {
|
|
logger.GetAppLogger().Warn("绑定手机号参数校验失败", zap.Error(err))
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
|
|
resp, err := h.service.BindPhone(c.UserContext(), customerID, &req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, resp)
|
|
}
|
|
|
|
// ChangePhone A6 更换手机号
|
|
// POST /api/c/v1/auth/change-phone
|
|
func (h *ClientAuthHandler) ChangePhone(c *fiber.Ctx) error {
|
|
customerID, ok := middleware.GetCustomerID(c)
|
|
if !ok || customerID == 0 {
|
|
return errors.New(errors.CodeUnauthorized)
|
|
}
|
|
|
|
var req dto.ChangePhoneRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
if err := clientAuthValidator.Struct(&req); err != nil {
|
|
logger.GetAppLogger().Warn("更换手机号参数校验失败", zap.Error(err))
|
|
return errors.New(errors.CodeInvalidParam)
|
|
}
|
|
|
|
resp, err := h.service.ChangePhone(c.UserContext(), customerID, &req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, resp)
|
|
}
|
|
|
|
// Logout A7 退出登录
|
|
// POST /api/c/v1/auth/logout
|
|
func (h *ClientAuthHandler) Logout(c *fiber.Ctx) error {
|
|
customerID, ok := middleware.GetCustomerID(c)
|
|
if !ok || customerID == 0 {
|
|
return errors.New(errors.CodeUnauthorized)
|
|
}
|
|
|
|
resp, err := h.service.Logout(c.UserContext(), customerID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return response.Success(c, resp)
|
|
}
|