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
|
|||
|
}
|