Files
devstar-create-from-template/models/wechat/wechat.go

193 lines
6.6 KiB
Go
Raw Normal View History

2025-08-25 15:46:12 +08:00
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
}