193 lines
6.6 KiB
Go
193 lines
6.6 KiB
Go
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
|
||
}
|