package model import ( "go.uber.org/zap" "goskeleton/app/global/variable" "goskeleton/app/service/users/token_cache_redis" "goskeleton/app/utils/md5_encrypt" "time" ) // 操作数据库喜欢使用gorm自带语法的开发者可以参考 GinSkeleton-Admin 系统相关代码 // Admin 项目地址:https://gitee.com/daitougege/gin-skeleton-admin-backend/ // gorm_v2 提供的语法+ ginskeleton 实践 : http://gitee.com/daitougege/gin-skeleton-admin-backend/blob/master/app/model/button_cn_en.go // 创建 userFactory // 参数说明: 传递空值,默认使用 配置文件选项:UseDbType(mysql) func CreateUserFactory(sqlType string) *UsersModel { return &UsersModel{BaseModel: BaseModel{DB: UseDbConn(sqlType)}} } type UsersModel struct { BaseModel UserName string `gorm:"column:user_name" json:"user_name"` Pass string `json:"-"` Phone string `json:"phone"` RealName string `gorm:"column:real_name" json:"real_name"` Status int `json:"status"` Token string `json:"token"` LastLoginIp string `gorm:"column:last_login_ip" json:"last_login_ip"` } // 表名 func (u *UsersModel) TableName() string { return "tb_users" } // 用户注册(写一个最简单的使用账号、密码注册即可) func (u *UsersModel) Register(userName, pass, userIp string) bool { sql := "INSERT INTO tb_users(user_name,pass,last_login_ip) SELECT ?,?,? FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM tb_users WHERE user_name=?)" result := u.Exec(sql, userName, pass, userIp, userName) if result.RowsAffected > 0 { return true } else { return false } } // 用户登录, func (u *UsersModel) Login(userName string, pass string) *UsersModel { sql := "select id, user_name,real_name,pass,phone from tb_users where user_name=? limit 1" result := u.Raw(sql, userName).First(u) if result.Error == nil { // 账号密码验证成功 if len(u.Pass) > 0 && (u.Pass == md5_encrypt.Base64Md5(pass)) { return u } } else { variable.ZapLog.Error("根据账号查询单条记录出错:", zap.Error(result.Error)) } return nil } //记录用户登陆(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=? ) ` //注意:token的精确度为秒,如果在一秒之内,一个账号多次调用接口生成的token其实是相同的,这样写入数据库,第二次的影响行数为0,知己实际上操作仍然是有效的。 //所以这里只判断无错误即可,判断影响行数的话,>=0 都是ok的 if u.Exec(sql, userId, token, time.Unix(expiresAt, 0).Format(variable.DateFormat), clientIp, userId, token).Error == nil { // 异步缓存用户有效的token到redis if variable.ConfigYml.GetInt("Token.IsCacheToRedis") == 1 { go u.ValidTokenCacheToRedis(userId) } return true } return false } //用户刷新token,条件检查: 相关token在过期的时间之内,就符合刷新条件 func (u *UsersModel) OauthRefreshConditionCheck(userId int64, oldToken string) bool { // 首先判断旧token在本系统自带的数据库已经存在,才允许继续执行刷新逻辑 var oldTokenIsExists int sql := "SELECT count(*) as counts FROM tb_oauth_access_tokens WHERE fr_user_id =? and token=? and NOW()=0, 有些没有登录过的用户没有相关token,此语句执行影响行数为0,但是仍然是执行成功 if u.Exec(sql, userId).Error == nil { return true } return false } // 判断用户token是否在数据库存在+状态OK func (u *UsersModel) OauthCheckTokenIsOk(userId int64, token string) bool { sql := "SELECT token FROM `tb_oauth_access_tokens` WHERE fr_user_id=? AND revoked=0 AND expires_at>NOW() ORDER BY expires_at DESC , updated_at DESC LIMIT ?" maxOnlineUsers := variable.ConfigYml.GetInt("Token.JwtTokenOnlineUsers") rows, err := u.Raw(sql, userId, maxOnlineUsers).Rows() defer func() { // 凡是查询类记得释放记录集 _ = rows.Close() }() if err == nil && rows != nil { for rows.Next() { var tempToken string err := rows.Scan(&tempToken) if err == nil { if tempToken == token { return true } } } } return false } // 禁用一个用户的: 1.tb_users表的 status 设置为 0,tb_oauth_access_tokens 表的所有token删除 // 禁用一个用户的token请求(本质上就是把tb_users表的 status 字段设置为 0 即可) func (u *UsersModel) SetTokenInvalid(userId int) bool { sql := "delete from `tb_oauth_access_tokens` where `fr_user_id`=? " if u.Exec(sql, userId).Error == nil { if u.Exec("update tb_users set status=0 where id=?", userId).Error == nil { return true } } return false } //根据用户ID查询一条信息 func (u *UsersModel) ShowOneItem(userId int) (*UsersModel, error) { sql := "SELECT `id`, `user_name`,`pass`, `real_name`, `phone`, `status` FROM `tb_users` WHERE `status`=1 and id=? LIMIT 1" result := u.Raw(sql, userId).First(u) if result.Error == nil { return u, nil } else { return nil, result.Error } } // 查询数据之前统计条数 func (u *UsersModel) counts(userName string) (counts int64) { sql := "SELECT count(*) as counts FROM tb_users WHERE status=1 and user_name like ?" if res := u.Raw(sql, "%"+userName+"%").First(&counts); res.Error != nil { variable.ZapLog.Error("UsersModel - counts 查询数据条数出错", zap.Error(res.Error)) } return counts } // 查询(根据关键词模糊查询) func (u *UsersModel) Show(userName string, limitStart, limitItems int) (counts int64, temp []UsersModel) { if counts = u.counts(userName); counts > 0 { sql := "SELECT `id`, `user_name`, `real_name`, `phone`,last_login_ip, `status`,created_at,updated_at FROM `tb_users` WHERE `status`=1 and user_name like ? LIMIT ?,?" if res := u.Raw(sql, "%"+userName+"%", limitStart, limitItems).Find(&temp); res.RowsAffected > 0 { return counts, temp } } return 0, nil } //新增 func (u *UsersModel) Store(userName string, pass string, realName string, phone string, remark string) bool { sql := "INSERT INTO tb_users(user_name,pass,real_name,phone,remark) SELECT ?,?,?,?,? FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM tb_users WHERE user_name=?)" if u.Exec(sql, userName, pass, realName, phone, remark, userName).RowsAffected > 0 { return true } return false } //UpdateDataCheckUserNameIsUsed 更新前检查新的用户名是否已经存在(避免和别的账号重名) func (u *UsersModel) UpdateDataCheckUserNameIsUsed(userId int, userName string) (exists int64) { sql := "select count(*) as counts from tb_users where id!=? AND user_name=?" _ = u.Raw(sql, userId, userName).First(&exists) return exists } //更新 func (u *UsersModel) Update(id int, userName string, pass string, realName string, phone string, remark string, clientIp string) bool { sql := "update tb_users set user_name=?,pass=?,real_name=?,phone=?,remark=? WHERE status=1 AND id=?" if u.Exec(sql, userName, pass, realName, phone, remark, id).RowsAffected >= 0 { if u.OauthResetToken(id, pass, clientIp) { return true } } return false } //删除用户以及关联的token记录 func (u *UsersModel) Destroy(id int) bool { // 删除用户时,清除用户缓存在redis的全部token if variable.ConfigYml.GetInt("Token.IsCacheToRedis") == 1 { go u.DelTokenCacheFromRedis(int64(id)) } if u.Delete(u, id).Error == nil { if u.OauthDestroyToken(id) { return true } } return false } // 后续两个函数专门处理用户 token 缓存到 redis 逻辑 func (u *UsersModel) ValidTokenCacheToRedis(userId int64) { tokenCacheRedisFact := token_cache_redis.CreateUsersTokenCacheFactory(userId) if tokenCacheRedisFact == nil { variable.ZapLog.Error("redis连接失败,请检查配置") return } defer tokenCacheRedisFact.ReleaseRedisConn() sql := "SELECT token,expires_at FROM `tb_oauth_access_tokens` WHERE fr_user_id=? AND revoked=0 AND expires_at>NOW() ORDER BY expires_at DESC , updated_at DESC LIMIT ?" maxOnlineUsers := variable.ConfigYml.GetInt("Token.JwtTokenOnlineUsers") rows, err := u.Raw(sql, userId, maxOnlineUsers).Rows() defer func() { // 凡是获取原生结果集的查询,记得释放记录集 _ = rows.Close() }() var tempToken, expires string if err == nil && rows != nil { for i := 1; rows.Next(); i++ { err = rows.Scan(&tempToken, &expires) if err == nil { if ts, err := time.ParseInLocation(variable.DateFormat, expires, time.Local); err == nil { tokenCacheRedisFact.SetTokenCache(ts.Unix(), tempToken) // 因为每个用户的token是按照过期时间倒叙排列的,第一个是有效期最长的,将该用户的总键设置一个最大过期时间,到期则自动清理,避免不必要的数据残留 if i == 1 { tokenCacheRedisFact.SetUserTokenExpire(ts.Unix()) } } else { variable.ZapLog.Error("expires_at 转换位时间戳出错", zap.Error(err)) } } } } // 缓存结束之后删除超过系统设置最大在线数量的token tokenCacheRedisFact.DelOverMaxOnlineCache() } // DelTokenCacheFromRedis 用户密码修改后,删除redis所有的token func (u *UsersModel) DelTokenCacheFromRedis(userId int64) { tokenCacheRedisFact := token_cache_redis.CreateUsersTokenCacheFactory(userId) if tokenCacheRedisFact == nil { variable.ZapLog.Error("redis连接失败,请检查配置") return } tokenCacheRedisFact.ClearUserToken() tokenCacheRedisFact.ReleaseRedisConn() }