From 825a3c88ce6c33f30485c865e52b264f433dc634 Mon Sep 17 00:00:00 2001 From: Havoc412 <2993167370@qq.com> Date: Thu, 17 Oct 2024 19:28:26 +0800 Subject: [PATCH] update WeChat Login --- app/http/controller/web/users_controller.go | 35 +++++++++------- app/model/users.go | 45 ++++++++++++++------- app/service/weixin/code2Session.go | 7 ++++ config/config.yml | 2 +- routers/web.go | 4 +- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/app/http/controller/web/users_controller.go b/app/http/controller/web/users_controller.go index bae05f7..33e4865 100644 --- a/app/http/controller/web/users_controller.go +++ b/app/http/controller/web/users_controller.go @@ -2,6 +2,7 @@ package web import ( "catface/app/global/consts" + "catface/app/global/errcode" "catface/app/global/variable" "catface/app/model" "catface/app/service/users/curd" @@ -158,27 +159,31 @@ func (u *Users) WeixinLogin(context *gin.Context) { weixinRes, err := weixin.Code2Session(code) if err != nil { // 解析微信登录成功,返回用户信息 - response.Fail(context, consts.CurdLoginFailCode, consts.CurdLoginFailMsg, err) + response.Fail(context, errcode.ErrWeixinApi, errcode.ErrMsg[errcode.ErrWeixinApi], err) return } // 2. 执行 CURD - user, err := model.CreateUserFactory("mysql").WeixinLogin(weixinRes.OpenId, weixinRes.SessionKey, userName, userAvatar, userIp) - if err == nil && user != nil { - if user.Id > 0 { + userModel, err := model.CreateUserFactory("").WeixinLogin(weixinRes.OpenId, weixinRes.SessionKey, userName, userAvatar, userIp) + if err == nil && userModel != nil { + if userModel.Id > 0 { // 3. 生成 token - token, err := userstoken.CreateUserFactory().GenerateToken(user.Id, userName, "", 0) - if err != nil { - response.Fail(context, consts.CurdLoginFailCode, consts.CurdLoginFailMsg, err) - return + userTokenFactory := userstoken.CreateUserFactory() + if userToken, err := userTokenFactory.GenerateToken(userModel.Id, userModel.UserName, userModel.SessionKey, variable.ConfigYml.GetInt64("Token.JwtTokenCreatedExpireAt")); err == nil { + if userTokenFactory.RecordLoginToken(userToken, context.ClientIP()) { + data := gin.H{ + "userId": userModel.Id, + "user_name": userName, + "permission": userModel.Permission, + "token": userToken, + "updated_at": time.Now().Format(variable.DateFormat), + } + response.Success(context, consts.CurdStatusOkMsg, data) + go model.CreateUserFactory("").UpdateUserloginInfo(context.ClientIP(), userModel.Id) // TODO 暂时的解决方案就是直接重新一个实例 + return + } } - // 4. 返回 token - res := gin.H{ - "userId": user.Id, - "permission": user.Permission, - "token": token, - } - response.Success(context, consts.CurdStatusOkMsg, res) + // TODO 这里不写错误处理? } } else { response.Fail(context, consts.CurdLoginFailCode, consts.CurdLoginFailMsg, "") diff --git a/app/model/users.go b/app/model/users.go index a192723..0061032 100644 --- a/app/model/users.go +++ b/app/model/users.go @@ -4,6 +4,7 @@ import ( "catface/app/global/variable" "catface/app/service/users/token_cache_redis" "catface/app/utils/md5_encrypt" + "fmt" "time" "go.uber.org/zap" @@ -30,6 +31,7 @@ type UsersModel struct { // TAG 状态管理 Status uint8 `json:"status"` // QUESTION Token string `json:"token"` + LoginTimes uint64 `json:"login_times"` LastLoginIp string `gorm:"column:last_login_ip" json:"last_login_ip"` // TAG MySELF UserAvatar string `gorm:"column:user_avatar;size:255" json:"user_avatar"` // TODO 暂时存储 url,之后考虑需要把文件上传到 Nginx @@ -41,7 +43,7 @@ type UsersModel struct { // 表名 func (u *UsersModel) TableName() string { // TIP GORM 也会自动调用这个函数。 - return "users" + return "tb_users" } // 用户注册(写一个最简单的使用账号、密码注册即可) @@ -73,17 +75,24 @@ func (u *UsersModel) Login(userName string, pass string) *UsersModel { // 记录用户登陆(login)生成的token,每次登陆记录一次token func (u *UsersModel) OauthLoginToken(userId int64, token string, expiresAt int64, clientIp string) bool { sql := ` - INSERT INTO tb_oauth_access_tokens(fr_user_id,action_name,token,expires_at,client_ip) - SELECT ?,'login',? ,?,? FROM DUAL WHERE NOT EXISTS(SELECT 1 FROM tb_oauth_access_tokens a WHERE a.fr_user_id=? AND a.action_name='login' AND a.token=? ) + INSERT INTO tb_oauth_access_tokens(fr_user_id, action_name, token, expires_at, client_ip) + SELECT ?, 'login', ? , ?, ? FROM DUAL + WHERE NOT EXISTS( + SELECT 1 + FROM tb_oauth_access_tokens a + WHERE a.fr_user_id=? AND a.action_name='login' AND a.token=? + ) ` //注意:token的精确度为秒,如果在一秒之内,一个账号多次调用接口生成的token其实是相同的,这样写入数据库,第二次的影响行数为0,知己实际上操作仍然是有效的。 //所以这里只判断无错误即可,判断影响行数的话,>=0 都是ok的 - if u.Exec(sql, userId, token, time.Unix(expiresAt, 0).Format(variable.DateFormat), clientIp, userId, token).Error == nil { + if err := u.Exec(sql, userId, token, time.Unix(expiresAt, 0).Format(variable.DateFormat), clientIp, userId, token).Error; err == nil { // 异步缓存用户有效的token到redis if variable.ConfigYml.GetInt("Token.IsCacheToRedis") == 1 { go u.ValidTokenCacheToRedis(userId) } return true + } else { + fmt.Println(err) } return false } @@ -115,8 +124,10 @@ func (u *UsersModel) OauthRefreshToken(userId, expiresAt int64, oldToken, newTok // 更新用户登陆次数、最近一次登录ip、最近一次登录时间 func (u *UsersModel) UpdateUserloginInfo(last_login_ip string, userId int64) { - sql := "UPDATE tb_users SET login_times=IFNULL(login_times,0)+1,last_login_ip=?,last_login_time=? WHERE id=? " - _ = u.Exec(sql, last_login_ip, time.Now().Format(variable.DateFormat), userId) + // sql := "UPDATE tb_users SET login_times=IFNULL(login_times,0)+1,last_login_ip=?,last_login_time=? WHERE id=? " + // _ = u.Exec(sql, last_login_ip, time.Now().Format(variable.DateFormat), userId) + sql := "UPDATE tb_users SET login_times=IFNULL(login_times,0)+1,last_login_ip=? WHERE id=? " + _ = u.Exec(sql, last_login_ip, userId) } // 当用户更改密码后,所有的token都失效,必须重新登录 @@ -260,7 +271,6 @@ func (u *UsersModel) Destroy(id int) bool { } // 后续两个函数专门处理用户 token 缓存到 redis 逻辑 - func (u *UsersModel) ValidTokenCacheToRedis(userId int64) { tokenCacheRedisFact := token_cache_redis.CreateUsersTokenCacheFactory(userId) if tokenCacheRedisFact == nil { @@ -313,21 +323,28 @@ func (u *UsersModel) DelTokenCacheFromRedis(userId int64) { * @description * @return {*} */ -func (u *UsersModel) WeixinLogin(openId, sessionKey, name, avatar, ip string) (temp *UsersModel, err error) { +func (u *UsersModel) WeixinLogin(openId, sessionKey, name, avatar, ip string) (temp *UsersModel, errTemp error) { db := u.DB var user UsersModel - if result := db.Where("open_id = ?", openId).First(&user); result.Error != nil { + result := db.Where("open_id = ?", openId).First(&user) + + if result.Error == nil && result.RowsAffected > 0 { temp = &user - } else if result.Error == gorm.ErrRecordNotFound { - newUser := UsersModel{OpenId: openId, UserName: name, UserAvatar: avatar, LastLoginIp: ip, SessionKey: sessionKey} + } else if result.Error == gorm.ErrRecordNotFound || result.RowsAffected == 0 { // BUG 始终不会返回 ErrRecordNotFound 的报错 + newUser := UsersModel{ + OpenId: openId, + UserName: name, + UserAvatar: avatar, + LastLoginIp: ip, + SessionKey: sessionKey, + } if err := db.Create(&newUser).Error; err != nil { return nil, err } - temp = &newUser // INFO 这里应该就是 GORM 插入后得到的对象。 + temp = &newUser // 这里是 GORM 插入后得到的对象。 } else { return nil, result.Error } - - return temp, nil + return } diff --git a/app/service/weixin/code2Session.go b/app/service/weixin/code2Session.go index 071265d..53583c8 100644 --- a/app/service/weixin/code2Session.go +++ b/app/service/weixin/code2Session.go @@ -16,6 +16,13 @@ type Response struct { } func Code2Session(js_code string) (*Response, error) { + + return &Response{ // TEST + OpenId: "open_id", + SessionKey: "session_key", + Errcode: 0, + Errmsg: "ok", + }, nil appid := variable.ConfigYml.GetString("Weixin.AppId") appSecret := variable.ConfigYml.GetString("Weixin.AppSecret") grantType := variable.ConfigYml.GetString("Weixin.Code2Session.GrantType") diff --git a/config/config.yml b/config/config.yml index f1247a4..2acc897 100644 --- a/config/config.yml +++ b/config/config.yml @@ -22,7 +22,7 @@ Token: IsCacheToRedis: 0 #用户token是否缓存到redis, 如果已经正确配置了redis,建议设置为1, 开启redis缓存token,(1=用户token缓存到redis; 0=token只存在于mysql) Redis: - Host: "127.0.0.1" # 服务器本地调用。 开发时也在本地启动一个。 + Host: "113.44.68.213" # 服务器本地调用。 开发时也在本地启动一个。 Port: 6379 Auth: "" MaxIdle: 10 diff --git a/routers/web.go b/routers/web.go index e554c9b..4094066 100644 --- a/routers/web.go +++ b/routers/web.go @@ -84,9 +84,9 @@ func InitWebRouter() *gin.Engine { // 1.编写一个表单参数验证器结构体,参见代码: app/http/validator/web/users/register.go // 2.将以上表单参数验证器注册,遵守 键 =》值 格式注册即可 ,app/http/validator/common/register_validator/web_register_validator.go 20行就是注册时候的键 consts.ValidatorPrefix+"UsersRegister" // 3.按照注册时的键,直接从容器调用即可 :validatorFactory.Create(consts.ValidatorPrefix+"UsersRegister") - noAuth.POST("register", validatorFactory.Create(consts.ValidatorPrefix+"UsersRegister")) + // noAuth.POST("register", validatorFactory.Create(consts.ValidatorPrefix+"UsersRegister")) // 不需要验证码即可登陆 - noAuth.POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin")) + // noAuth.POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin")) // 如果加载了验证码中间件,那么就需要提交验证码才可以登陆(本质上就是给登陆接口增加了2个参数:验证码id提交时的键:captcha_id 和 验证码值提交时的键 captcha_value,具体参见配置文件) //noAuth.Use(authorization.CheckCaptchaAuth()).POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin")) }