Files
devstar-create-from-template/models/wechat/wechat.go
2025-08-25 15:46:12 +08:00

193 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package wechat
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// ErrFailedToOperateWechatOfficialAccountUserDB 错误类型:打开数据库失败
type ErrFailedToOperateWechatOfficialAccountUserDB struct {
Action string
Message string
}
func (err ErrFailedToOperateWechatOfficialAccountUserDB) Error() string {
return fmt.Sprintf("Failed to %v in WeChat Official Account DB: %v", err.Action, err.Message)
}
// ErrWechatOfficialAccountUserNotExist 错误类型:找不到对应的微信公众号用户
type ErrWechatOfficialAccountUserNotExist struct {
AppID string
OpenID string
}
func (err ErrWechatOfficialAccountUserNotExist) Error() string {
return fmt.Sprintf("WeChat Official Account User not found: AppID = %v, OpenID = %v", err.AppID, err.OpenID)
}
// UserWechatOpenid 用户ID与微信公众号openid一对一关联表
// 数据库指定schema下 必须存在 `user_wechat_openid`,否则报错 ERROR 1146 (42S02): Table does not exist
//
// 遵循gonic规则映射数据库表 `user_wechat_openid`,各字段注解见 https://xorm.io/docs/chapter-02/4.columns/
type UserWechatOpenid struct {
Id int64 `xorm:"BIGINT pk NOT NULL autoincr 'id' comment('主键')"`
Uid int64 `xorm:"BIGINT UNIQUE NOT NULL 'uid' comment('user表主键')"`
WechatAppid string `xorm:"VARCHAR(50) charset=utf8mb4 collate=utf8mb4_bin NOT NULL 'official_account_appid' comment('微信公众号关联的AppID便于区分不同公众号用户、便于后期公众号迁移')"`
Openid string `xorm:"VARCHAR(50) charset=utf8mb4 collate=utf8mb4_bin UNIQUE NOT NULL 'openid' comment('微信公众号粉丝OpenID')"`
}
// Use `charset=utf8mb4 collate=utf8mb4_bin` to fix log.Error: There are 2 table columns(`official_account_appid`, `openid`) using inconsistent collation, they should use "utf8mb4_bin". Please go to admin panel Self Check page
func init() {
db.RegisterModel(new(UserWechatOpenid))
}
// QueryUserByOpenid 根据微信公众号用户openid查询 `user` 表用户
func QueryUserByOpenid(ctx context.Context, openid string) (*user_model.User, error) {
// 1. 根据公众号 AppID 和 OpenID 查询数据库中 user表 主键ID
wechatUser := &UserWechatOpenid{
WechatAppid: setting.Wechat.UserConfig.AppID,
Openid: openid,
}
var user *user_model.User
// 2. 开启数据库事务,查询用户信息
err := db.WithTx(ctx, func(ctx context.Context) error {
// 2.1 根据 `user_wechat_openid` 表 查询用户 openid
exist, err := db.GetEngine(ctx).Get(wechatUser) // 注: 断点必须打在操作数据库之后,否则超时导致报错: context canceled
if err != nil {
return ErrFailedToOperateWechatOfficialAccountUserDB{
Action: fmt.Sprintf("Find WeChat openid '%s'", openid),
Message: err.Error(),
}
}
if !exist {
return ErrWechatOfficialAccountUserNotExist{
AppID: wechatUser.WechatAppid,
OpenID: wechatUser.Openid,
}
}
// 2.2 根据 `user` 表 主键ID查询用户信息
user, err = user_model.GetUserByID(ctx, wechatUser.Uid)
if err != nil {
return err
}
// 数据库事务完成返回nil自动进行Commit
return nil
})
if err != nil {
return nil, err
}
// 3. 检查用户是否被禁用,参考校验逻辑 services/auth/signin.go#UserSignIn.
if user.ProhibitLogin {
return nil, user_model.ErrUserProhibitLogin{UID: user.ID, Name: user.Name}
}
return user, err
}
// UpdateOrCreateWechatUser 更新/新建用户微信
func UpdateOrCreateWechatUser(ctx context.Context, user *user_model.User, openid string) error {
// 1. 构造查询条件DevStar 用户ID即 UID
wechatUser := &UserWechatOpenid{
Uid: user.ID,
}
WechatAppid := setting.Wechat.UserConfig.AppID
// 2. 开启数据库事务,保证数据库表 `user_wechat_openid` 原子操作(出错时候自动进行 transaction Rollback
err := db.WithTx(ctx, func(ctx context.Context) error {
// 2.1 根据 UID 查询数据库
has, err := db.GetEngine(ctx).Get(wechatUser)
if err != nil {
return ErrFailedToOperateWechatOfficialAccountUserDB{
Action: fmt.Sprintf("Find UserID %v", user.ID),
Message: err.Error(),
}
}
// 2.2 数据库更新或插入新记录
if has && wechatUser.WechatAppid == WechatAppid && wechatUser.Openid == openid {
// 有用户记录,且未发生变化,直接返回,结束事务
return nil
} else if has && (wechatUser.WechatAppid != WechatAppid || wechatUser.Openid != openid) {
// 有用户记录,但公众号主体 AppId 或者 关联的用户号 OpenID 发生变化
wechatUser.WechatAppid = WechatAppid
wechatUser.Openid = openid
_, err = db.GetEngine(ctx).ID(wechatUser.Id).Update(wechatUser)
if err != nil {
return ErrFailedToOperateWechatOfficialAccountUserDB{
Action: fmt.Sprintf("Update existing UID %v", user.ID),
Message: err.Error(),
}
}
} else {
// 没有用户记录,需要插入新纪录
wechatUser.WechatAppid = WechatAppid
wechatUser.Openid = openid
_, err = db.GetEngine(ctx).Insert(wechatUser)
if err != nil {
return ErrFailedToOperateWechatOfficialAccountUserDB{
Action: fmt.Sprintf("Insert new user UID = %v", user.ID),
Message: err.Error(),
}
}
}
return nil
})
if err != nil {
return ErrFailedToOperateWechatOfficialAccountUserDB{
Action: "Start an DB transaction",
Message: err.Error(),
}
}
log.Info("微信公众号绑定成功: (UID, User.Name, OpenID) = (%v, %v, %v)", user.ID, user.Name, openid)
return nil
}
// DeleteWechatUser 清空当前用户的微信公众号绑定信息
func DeleteWechatUser(ctx context.Context, user *user_model.User) error {
// 0. 检查参数
if user == nil || user.ID <= 0 {
return ErrFailedToOperateWechatOfficialAccountUserDB{
Action: "Find User",
Message: "User and its ID cannot be nil",
}
}
// 1. 构造查询条件DevStar 用户ID即 UID
wechatUser := &UserWechatOpenid{
Uid: user.ID,
}
// 2. 开启数据库事务,删除数据库表 `user_wechat_openid` 中 user.ID 对应条目
err := db.WithTx(ctx, func(ctx context.Context) error {
// 2.1 根据 UID 查询数据库
_, err := db.GetEngine(ctx).Delete(wechatUser)
if err != nil {
return ErrFailedToOperateWechatOfficialAccountUserDB{
Action: fmt.Sprintf("Delete WeChat Official Account Binding for User '%v'(ID = %v)", user.Name, user.ID),
Message: err.Error(),
}
}
return nil
})
return err
}