实现个人客户微信认证和短信验证功能

- 添加个人客户微信登录和手机验证码登录接口
- 实现个人客户设备、ICCID、手机号关联管理
- 添加短信发送服务(HTTP 客户端)
- 添加微信认证服务(含 mock 实现)
- 添加 JWT Token 生成和验证工具
- 创建数据库迁移脚本(personal_customer 关联表)
- 修复测试文件中的路由注册参数错误
- 重构 scripts 目录结构(分离独立脚本到子目录)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-10 11:42:38 +08:00
parent 1b9080e3ab
commit 9c6d4a3bd4
53 changed files with 4258 additions and 97 deletions

72
pkg/auth/jwt.go Normal file
View File

@@ -0,0 +1,72 @@
package auth
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
// PersonalCustomerClaims 个人客户 JWT Claims
type PersonalCustomerClaims struct {
CustomerID uint `json:"customer_id"` // 个人客户 ID
Phone string `json:"phone"` // 手机号
jwt.RegisteredClaims
}
// JWTManager JWT 管理器
type JWTManager struct {
secretKey string
tokenDuration time.Duration
}
// NewJWTManager 创建 JWT 管理器
func NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager {
return &JWTManager{
secretKey: secretKey,
tokenDuration: tokenDuration,
}
}
// GeneratePersonalCustomerToken 生成个人客户 Token
func (m *JWTManager) GeneratePersonalCustomerToken(customerID uint, phone string) (string, error) {
claims := &PersonalCustomerClaims{
CustomerID: customerID,
Phone: phone,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.tokenDuration)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(m.secretKey))
if err != nil {
return "", fmt.Errorf("生成 Token 失败: %w", err)
}
return tokenString, nil
}
// VerifyPersonalCustomerToken 验证个人客户 Token
func (m *JWTManager) VerifyPersonalCustomerToken(tokenString string) (*PersonalCustomerClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &PersonalCustomerClaims{}, func(token *jwt.Token) (interface{}, error) {
// 验证签名算法
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(m.secretKey), nil
})
if err != nil {
return nil, fmt.Errorf("Token 解析失败: %w", err)
}
claims, ok := token.Claims.(*PersonalCustomerClaims)
if !ok || !token.Valid {
return nil, fmt.Errorf("Token 无效")
}
return claims, nil
}