Refactor WeChat integration to support multiple mini programs, removing hardcoded WeChat credentials and updating user model and authentication flow accordingly.
This commit is contained in:
@@ -9,9 +9,5 @@ DB_USER=root
|
|||||||
DB_PASSWORD=your_password
|
DB_PASSWORD=your_password
|
||||||
DB_NAME=wx_service
|
DB_NAME=wx_service
|
||||||
|
|
||||||
# 微信小程序配置
|
|
||||||
WECHAT_APP_ID=your_app_id
|
|
||||||
WECHAT_APP_SECRET=your_app_secret
|
|
||||||
|
|
||||||
# JWT配置
|
# JWT配置
|
||||||
JWT_SECRET=your-secret-key-change-in-production
|
JWT_SECRET=your-secret-key-change-in-production
|
||||||
|
|||||||
+3
-11
@@ -19,23 +19,15 @@ func main() {
|
|||||||
if err := database.InitDB(); err != nil {
|
if err := database.InitDB(); err != nil {
|
||||||
log.Fatalf("init database failed: %v", err)
|
log.Fatalf("init database failed: %v", err)
|
||||||
}
|
}
|
||||||
if err := database.AutoMigrate(&model.User{}); err != nil {
|
if err := database.AutoMigrate(&model.MiniProgram{}, &model.User{}); err != nil {
|
||||||
log.Fatalf("auto migrate failed: %v", err)
|
log.Fatalf("auto migrate failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AppConfig.WeChat.AppID == "" || config.AppConfig.WeChat.AppSecret == "" {
|
|
||||||
log.Fatal("wechat app id/secret are not configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
gin.SetMode(config.AppConfig.Server.Mode)
|
gin.SetMode(config.AppConfig.Server.Mode)
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
wechatClient := service.NewWeChatClient(
|
miniProgramService := service.NewMiniProgramService(database.DB)
|
||||||
config.AppConfig.WeChat.AppID,
|
authService := service.NewAuthService(database.DB, miniProgramService)
|
||||||
config.AppConfig.WeChat.AppSecret,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
authService := service.NewAuthService(database.DB, wechatClient)
|
|
||||||
authHandler := handler.NewAuthHandler(authService)
|
authHandler := handler.NewAuthHandler(authService)
|
||||||
|
|
||||||
api := router.Group("/api/v1")
|
api := router.Group("/api/v1")
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
Server ServerConfig
|
Server ServerConfig
|
||||||
Database DatabaseConfig
|
Database DatabaseConfig
|
||||||
WeChat WeChatConfig
|
|
||||||
JWT JWTConfig
|
JWT JWTConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,11 +26,6 @@ type DatabaseConfig struct {
|
|||||||
DBName string
|
DBName string
|
||||||
}
|
}
|
||||||
|
|
||||||
type WeChatConfig struct {
|
|
||||||
AppID string
|
|
||||||
AppSecret string
|
|
||||||
}
|
|
||||||
|
|
||||||
type JWTConfig struct {
|
type JWTConfig struct {
|
||||||
Secret string
|
Secret string
|
||||||
Expire int
|
Expire int
|
||||||
@@ -57,10 +51,6 @@ func LoadConfig() {
|
|||||||
Password: getEnv("DB_PASSWORD", ""),
|
Password: getEnv("DB_PASSWORD", ""),
|
||||||
DBName: getEnv("DB_NAME", "wx_service"),
|
DBName: getEnv("DB_NAME", "wx_service"),
|
||||||
},
|
},
|
||||||
WeChat: WeChatConfig{
|
|
||||||
AppID: getEnv("WECHAT_APP_ID", ""),
|
|
||||||
AppSecret: getEnv("WECHAT_APP_SECRET", ""),
|
|
||||||
},
|
|
||||||
JWT: JWTConfig{
|
JWT: JWTConfig{
|
||||||
Secret: getEnv("JWT_SECRET", "your-secret-key"),
|
Secret: getEnv("JWT_SECRET", "your-secret-key"),
|
||||||
Expire: 86400, // 24小时
|
Expire: 86400, // 24小时
|
||||||
|
|||||||
+33
-5
@@ -8,8 +8,8 @@
|
|||||||
2. 按实际环境填写以下变量:
|
2. 按实际环境填写以下变量:
|
||||||
- `SERVER_PORT`:HTTP 服务端口,例如 `8080`。
|
- `SERVER_PORT`:HTTP 服务端口,例如 `8080`。
|
||||||
- `DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME`:MySQL 连接信息。
|
- `DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME`:MySQL 连接信息。
|
||||||
- `WECHAT_APP_ID`、`WECHAT_APP_SECRET`:小程序后台 `appId` 与 `secret`(例如用户提供的 `wx67444119b166caa0` / `eb57cd73ff48a10b14df484e1d20facf`)。
|
|
||||||
3. 如果需要,替换 `GIN_MODE`、`JWT_SECRET` 等其他变量。
|
3. 如果需要,替换 `GIN_MODE`、`JWT_SECRET` 等其他变量。
|
||||||
|
4. 通过 `docs/sql/users.sql` 初始化 `mini_programs` 与 `users` 表,并插入每个小程序的 `name/app_id/app_secret`。
|
||||||
|
|
||||||
## 启动
|
## 启动
|
||||||
|
|
||||||
@@ -23,12 +23,26 @@ go run ./cmd/api
|
|||||||
3. 自动迁移 `internal/model/user.go` 中的 `users` 表。
|
3. 自动迁移 `internal/model/user.go` 中的 `users` 表。
|
||||||
4. 注册路由并启动 Gin HTTP 服务。
|
4. 注册路由并启动 Gin HTTP 服务。
|
||||||
|
|
||||||
## 数据表 `users`
|
## 数据表
|
||||||
|
|
||||||
|
### `mini_programs`
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `id` | bigint unsigned | 小程序 ID,登录接口需传 |
|
||||||
|
| `name` | varchar(100) | 业务名称或备注 |
|
||||||
|
| `app_id` | varchar(100), unique | 微信小程序 `appId` |
|
||||||
|
| `app_secret` | varchar(200) | 微信小程序 `appSecret`(明文存储,注意权限) |
|
||||||
|
| `description` | varchar(255) | 可选描述 |
|
||||||
|
| `created_at/updated_at/deleted_at` | timestamp | GORM 默认时间戳 |
|
||||||
|
|
||||||
|
### `users`
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
| 字段 | 类型 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `id` | bigint unsigned (auto increment) | 主键 |
|
| `id` | bigint unsigned (auto increment) | 主键 |
|
||||||
| `open_id` | varchar(100), unique | 微信 `openid`,登录唯一标识 |
|
| `mini_program_id` | bigint unsigned | 外键,关联 `mini_programs.id` |
|
||||||
|
| `open_id` | varchar(100) | 与 `mini_program_id` 组合成唯一键 |
|
||||||
| `union_id` | varchar(100), nullable | 微信 `unionid`(若有) |
|
| `union_id` | varchar(100), nullable | 微信 `unionid`(若有) |
|
||||||
| `nick_name` | varchar(100) | 用户昵称 |
|
| `nick_name` | varchar(100) | 用户昵称 |
|
||||||
| `avatar_url` | varchar(500) | 头像地址 |
|
| `avatar_url` | varchar(500) | 头像地址 |
|
||||||
@@ -46,6 +60,7 @@ go run ./cmd/api
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"mini_program_id": 1,
|
||||||
"code": "wx.login返回的code",
|
"code": "wx.login返回的code",
|
||||||
"nickname": "可选",
|
"nickname": "可选",
|
||||||
"avatar_url": "可选",
|
"avatar_url": "可选",
|
||||||
@@ -63,6 +78,7 @@ go run ./cmd/api
|
|||||||
"data": {
|
"data": {
|
||||||
"user": {
|
"user": {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
|
"mini_program_id": 1,
|
||||||
"open_id": "oXXX",
|
"open_id": "oXXX",
|
||||||
"union_id": "可选",
|
"union_id": "可选",
|
||||||
"nickname": "昵称",
|
"nickname": "昵称",
|
||||||
@@ -70,16 +86,28 @@ go run ./cmd/api
|
|||||||
"gender": 1,
|
"gender": 1,
|
||||||
"phone": "110"
|
"phone": "110"
|
||||||
},
|
},
|
||||||
"session_key": "wx-session-key"
|
"session_key": "wx-session-key",
|
||||||
|
"mini_program": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "商城小程序",
|
||||||
|
"app_id": "wx67444119b166caa0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **错误**
|
- **错误**
|
||||||
- `400`:`code` 缺失或请求体非法。
|
- `400`:`code` 或 `mini_program_id` 缺失、请求体非法、小程序不存在。
|
||||||
- `502`:微信 API 返回错误。
|
- `502`:微信 API 返回错误。
|
||||||
- `500`:数据库或其他内部异常。
|
- `500`:数据库或其他内部异常。
|
||||||
|
|
||||||
## 健康检查
|
## 健康检查
|
||||||
|
|
||||||
`GET /healthz` 返回 `{"status": "ok"}`,用于部署探活。
|
`GET /healthz` 返回 `{"status": "ok"}`,用于部署探活。
|
||||||
|
|
||||||
|
## 多小程序共用后台设计
|
||||||
|
|
||||||
|
- **凭证管理表**:`mini_programs` 持久化 `name/app_id/app_secret`,可通过后台页面或 SQL 插入,避免把密钥写进环境变量。
|
||||||
|
- **接口约定**:小程序端调用登录接口时传入 `mini_program_id`。服务端通过该 ID 读取凭证,动态拼装 `jscode2session` 请求。
|
||||||
|
- **数据隔离**:`users` 以 `mini_program_id + open_id` 建立唯一索引,同一 `openid` 在不同小程序下互不影响。
|
||||||
|
- **扩展性**:后续如需在用户层面区分策略(如积分、营销),可直接以 `mini_program_id` 作为维度做统计或挂载其他表。
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
-- mini_programs 表存储小程序凭证
|
||||||
|
CREATE TABLE IF NOT EXISTS `mini_programs` (
|
||||||
|
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`created_at` datetime(3) NULL DEFAULT NULL,
|
||||||
|
`updated_at` datetime(3) NULL DEFAULT NULL,
|
||||||
|
`deleted_at` datetime(3) NULL DEFAULT NULL,
|
||||||
|
`name` varchar(100) NOT NULL,
|
||||||
|
`app_id` varchar(100) NOT NULL,
|
||||||
|
`app_secret` varchar(200) NOT NULL,
|
||||||
|
`description` varchar(255) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `idx_mini_programs_app_id` (`app_id`),
|
||||||
|
KEY `idx_mini_programs_deleted_at` (`deleted_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- users 表结构,与 internal/model/user.go 对应
|
||||||
|
CREATE TABLE IF NOT EXISTS `users` (
|
||||||
|
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`created_at` datetime(3) NULL DEFAULT NULL,
|
||||||
|
`updated_at` datetime(3) NULL DEFAULT NULL,
|
||||||
|
`deleted_at` datetime(3) NULL DEFAULT NULL,
|
||||||
|
`mini_program_id` bigint unsigned NOT NULL,
|
||||||
|
`open_id` varchar(100) NOT NULL,
|
||||||
|
`union_id` varchar(100) DEFAULT NULL,
|
||||||
|
`nick_name` varchar(100) DEFAULT NULL,
|
||||||
|
`avatar_url` varchar(500) DEFAULT NULL,
|
||||||
|
`gender` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
|
`phone` varchar(20) DEFAULT NULL,
|
||||||
|
`session_key` varchar(100) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `idx_mini_open` (`mini_program_id`,`open_id`),
|
||||||
|
KEY `idx_users_deleted_at` (`deleted_at`),
|
||||||
|
CONSTRAINT `fk_users_mini_program` FOREIGN KEY (`mini_program_id`) REFERENCES `mini_programs`(`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
@@ -21,11 +21,12 @@ func NewAuthHandler(authService *service.AuthService) *AuthHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type weChatLoginRequest struct {
|
type weChatLoginRequest struct {
|
||||||
Code string `json:"code" binding:"required"`
|
MiniProgramID uint `json:"mini_program_id" binding:"required"`
|
||||||
NickName string `json:"nickname"`
|
Code string `json:"code" binding:"required"`
|
||||||
AvatarURL string `json:"avatar_url"`
|
NickName string `json:"nickname"`
|
||||||
Gender *int `json:"gender"`
|
AvatarURL string `json:"avatar_url"`
|
||||||
Phone string `json:"phone"`
|
Gender *int `json:"gender"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AuthHandler) LoginWithWeChat(c *gin.Context) {
|
func (h *AuthHandler) LoginWithWeChat(c *gin.Context) {
|
||||||
@@ -36,16 +37,21 @@ func (h *AuthHandler) LoginWithWeChat(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result, err := h.authService.LoginWithCode(c.Request.Context(), service.LoginRequest{
|
result, err := h.authService.LoginWithCode(c.Request.Context(), service.LoginRequest{
|
||||||
Code: req.Code,
|
MiniProgramID: req.MiniProgramID,
|
||||||
NickName: req.NickName,
|
Code: req.Code,
|
||||||
AvatarURL: req.AvatarURL,
|
NickName: req.NickName,
|
||||||
Gender: req.Gender,
|
AvatarURL: req.AvatarURL,
|
||||||
Phone: req.Phone,
|
Gender: req.Gender,
|
||||||
|
Phone: req.Phone,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, service.ErrCodeRequired):
|
case errors.Is(err, service.ErrCodeRequired):
|
||||||
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "code is required"))
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "code is required"))
|
||||||
|
case errors.Is(err, service.ErrMiniProgramRequired):
|
||||||
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "mini_program_id is required"))
|
||||||
|
case errors.Is(err, service.ErrMiniProgramNotFound):
|
||||||
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "mini program not found"))
|
||||||
default:
|
default:
|
||||||
var apiErr *service.WeChatError
|
var apiErr *service.WeChatError
|
||||||
if errors.As(err, &apiErr) {
|
if errors.As(err, &apiErr) {
|
||||||
@@ -58,19 +64,30 @@ func (h *AuthHandler) LoginWithWeChat(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userPayload := gin.H{
|
userPayload := gin.H{
|
||||||
"id": result.User.ID,
|
"id": result.User.ID,
|
||||||
"open_id": result.User.OpenID,
|
"mini_program_id": result.User.MiniProgramID,
|
||||||
"nickname": result.User.NickName,
|
"open_id": result.User.OpenID,
|
||||||
"avatar_url": result.User.AvatarURL,
|
"nickname": result.User.NickName,
|
||||||
"gender": result.User.Gender,
|
"avatar_url": result.User.AvatarURL,
|
||||||
"phone": result.User.Phone,
|
"gender": result.User.Gender,
|
||||||
|
"phone": result.User.Phone,
|
||||||
}
|
}
|
||||||
if result.User.UnionID != "" {
|
if result.User.UnionID != "" {
|
||||||
userPayload["union_id"] = result.User.UnionID
|
userPayload["union_id"] = result.User.UnionID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
miniProgramPayload := gin.H{
|
||||||
|
"id": result.MiniProgram.ID,
|
||||||
|
"name": result.MiniProgram.Name,
|
||||||
|
"app_id": result.MiniProgram.AppID,
|
||||||
|
}
|
||||||
|
if result.MiniProgram.Description != "" {
|
||||||
|
miniProgramPayload["description"] = result.MiniProgram.Description
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, model.Success(gin.H{
|
c.JSON(http.StatusOK, model.Success(gin.H{
|
||||||
"user": userPayload,
|
"user": userPayload,
|
||||||
"session_key": result.SessionKey,
|
"session_key": result.SessionKey,
|
||||||
|
"mini_program": miniProgramPayload,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MiniProgram struct {
|
||||||
|
ID uint `gorm:"primarykey" json:"id"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
|
Name string `gorm:"size:100;not null" json:"name"`
|
||||||
|
AppID string `gorm:"size:100;uniqueIndex" json:"app_id"`
|
||||||
|
AppSecret string `gorm:"size:200;not null" json:"-"`
|
||||||
|
Description string `gorm:"size:255" json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MiniProgram) TableName() string {
|
||||||
|
return "mini_programs"
|
||||||
|
}
|
||||||
@@ -12,12 +12,14 @@ type User struct {
|
|||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
OpenID string `gorm:"uniqueIndex;size:100" json:"open_id"`
|
MiniProgramID uint `gorm:"index:idx_mini_open,priority:1" json:"mini_program_id"`
|
||||||
UnionID string `gorm:"size:100" json:"union_id,omitempty"`
|
MiniProgram MiniProgram `gorm:"foreignKey:MiniProgramID" json:"mini_program,omitempty"`
|
||||||
NickName string `gorm:"size:100" json:"nickname"`
|
OpenID string `gorm:"size:100;index:idx_mini_open,priority:2" json:"open_id"`
|
||||||
AvatarURL string `gorm:"size:500" json:"avatar_url"`
|
UnionID string `gorm:"size:100" json:"union_id,omitempty"`
|
||||||
Gender int `gorm:"default:0" json:"gender"`
|
NickName string `gorm:"size:100" json:"nickname"`
|
||||||
Phone string `gorm:"size:20" json:"phone,omitempty"`
|
AvatarURL string `gorm:"size:500" json:"avatar_url"`
|
||||||
|
Gender int `gorm:"default:0" json:"gender"`
|
||||||
|
Phone string `gorm:"size:20" json:"phone,omitempty"`
|
||||||
|
|
||||||
SessionKey string `gorm:"size:100" json:"-"`
|
SessionKey string `gorm:"size:100" json:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,46 +4,67 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"sync"
|
||||||
|
|
||||||
"wx_service/internal/model"
|
"wx_service/internal/model"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrCodeRequired = errors.New("code is required")
|
var (
|
||||||
|
ErrCodeRequired = errors.New("code is required")
|
||||||
|
ErrMiniProgramRequired = errors.New("mini program id is required")
|
||||||
|
ErrMiniProgramNotFound = errors.New("mini program not found")
|
||||||
|
)
|
||||||
|
|
||||||
type AuthService struct {
|
type AuthService struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
wechat *WeChatClient
|
miniProgramSvc *MiniProgramService
|
||||||
|
wechatClientCache map[uint]*WeChatClient
|
||||||
|
cacheMu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
Code string
|
MiniProgramID uint
|
||||||
NickName string
|
Code string
|
||||||
AvatarURL string
|
NickName string
|
||||||
Gender *int
|
AvatarURL string
|
||||||
Phone string
|
Gender *int
|
||||||
|
Phone string
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginResult struct {
|
type LoginResult struct {
|
||||||
User *model.User
|
User *model.User
|
||||||
SessionKey string
|
SessionKey string
|
||||||
|
MiniProgram *model.MiniProgram
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthService(db *gorm.DB, wechat *WeChatClient) *AuthService {
|
func NewAuthService(db *gorm.DB, miniProgramSvc *MiniProgramService) *AuthService {
|
||||||
return &AuthService{
|
return &AuthService{
|
||||||
db: db,
|
db: db,
|
||||||
wechat: wechat,
|
miniProgramSvc: miniProgramSvc,
|
||||||
|
wechatClientCache: make(map[uint]*WeChatClient),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthService) LoginWithCode(ctx context.Context, req LoginRequest) (*LoginResult, error) {
|
func (s *AuthService) LoginWithCode(ctx context.Context, req LoginRequest) (*LoginResult, error) {
|
||||||
if strings.TrimSpace(req.Code) == "" {
|
if req.MiniProgramID == 0 {
|
||||||
|
return nil, ErrMiniProgramRequired
|
||||||
|
}
|
||||||
|
if req.Code == "" {
|
||||||
return nil, ErrCodeRequired
|
return nil, ErrCodeRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := s.wechat.Code2Session(ctx, req.Code)
|
miniProgram, err := s.miniProgramSvc.GetByID(ctx, req.MiniProgramID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, ErrMiniProgramNotFound
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("load mini program: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := s.getWeChatClient(miniProgram)
|
||||||
|
session, err := client.Code2Session(ctx, req.Code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -53,15 +74,16 @@ func (s *AuthService) LoginWithCode(ctx context.Context, req LoginRequest) (*Log
|
|||||||
|
|
||||||
tx := s.db.WithContext(ctx)
|
tx := s.db.WithContext(ctx)
|
||||||
var user model.User
|
var user model.User
|
||||||
err = tx.Where("open_id = ?", session.OpenID).First(&user).Error
|
err = tx.Where("mini_program_id = ? AND open_id = ?", miniProgram.ID, session.OpenID).First(&user).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
user = model.User{
|
user = model.User{
|
||||||
OpenID: session.OpenID,
|
MiniProgramID: miniProgram.ID,
|
||||||
UnionID: session.UnionID,
|
OpenID: session.OpenID,
|
||||||
NickName: req.NickName,
|
UnionID: session.UnionID,
|
||||||
AvatarURL: req.AvatarURL,
|
NickName: req.NickName,
|
||||||
Phone: req.Phone,
|
AvatarURL: req.AvatarURL,
|
||||||
SessionKey: session.SessionKey,
|
Phone: req.Phone,
|
||||||
|
SessionKey: session.SessionKey,
|
||||||
}
|
}
|
||||||
if req.Gender != nil {
|
if req.Gender != nil {
|
||||||
user.Gender = *req.Gender
|
user.Gender = *req.Gender
|
||||||
@@ -104,8 +126,24 @@ func (s *AuthService) LoginWithCode(ctx context.Context, req LoginRequest) (*Log
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := &LoginResult{
|
result := &LoginResult{
|
||||||
User: &user,
|
User: &user,
|
||||||
SessionKey: session.SessionKey,
|
SessionKey: session.SessionKey,
|
||||||
|
MiniProgram: miniProgram,
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AuthService) getWeChatClient(mp *model.MiniProgram) *WeChatClient {
|
||||||
|
s.cacheMu.RLock()
|
||||||
|
client, ok := s.wechatClientCache[mp.ID]
|
||||||
|
s.cacheMu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
newClient := NewWeChatClient(mp.AppID, mp.AppSecret, nil)
|
||||||
|
s.cacheMu.Lock()
|
||||||
|
s.wechatClientCache[mp.ID] = newClient
|
||||||
|
s.cacheMu.Unlock()
|
||||||
|
return newClient
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"wx_service/internal/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MiniProgramService struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMiniProgramService(db *gorm.DB) *MiniProgramService {
|
||||||
|
return &MiniProgramService{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiniProgramService) GetByID(ctx context.Context, id uint) (*model.MiniProgram, error) {
|
||||||
|
var mp model.MiniProgram
|
||||||
|
if err := s.db.WithContext(ctx).Where("id = ?", id).First(&mp).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mp, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user