package model
import (
// "encoding/pem"
// "goskeleton/app/global/consts"
"goskeleton/app/global/variable"
"goskeleton/app/service/users/token_cache_redis"
"goskeleton/app/utils/md5_encrypt"
rsa "goskeleton/app/utils/rsa"
"time"
"go.uber.org/zap"
"fmt"
// "github.com/google/uuid" // 导入uuid库
)
// 操作数据库喜欢使用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 string ) bool {
// userID:=uuid.New()
sql := "INSERT INTO tb_users(user_name,pass) SELECT ?,? FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM tb_users WHERE user_name=?)"
result := u . Exec ( sql , userName , pass , userName )
if result . RowsAffected > 0 {
return true
} else {
variable . ZapLog . Error ( "注册失败:" , zap . Error ( result . Error ) )
return false
}
}
// 用户登录,
func ( u * UsersModel ) Login ( userName string , pass string ) * UsersModel {
sql := "select pass,id 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 ) bool {
sql := `
INSERT INTO tb_oauth_access_tokens ( fr_user_id , action_name , token , expires_at )
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 ) , 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()<DATE_ADD(expires_at,INTERVAL ? SECOND)"
if u . Raw ( sql , userId , oldToken , variable . ConfigYml . GetInt64 ( "Token.JwtTokenRefreshAllowSec" ) ) . First ( & oldTokenIsExists ) . Error == nil && oldTokenIsExists == 1 {
return true
}
return false
}
//用户刷新token
func ( u * UsersModel ) OauthRefreshToken ( userId , expiresAt int64 , oldToken , newToken string ) bool {
sql := "UPDATE tb_oauth_access_tokens SET token=? ,expires_at=?,updated_at=NOW(),action_name='refresh' WHERE fr_user_id=? AND token=?"
if u . Exec ( sql , newToken , time . Unix ( expiresAt , 0 ) . Format ( variable . DateFormat ) , userId , oldToken ) . Error == nil {
// 异步缓存用户有效的token到redis
if variable . ConfigYml . GetInt ( "Token.IsCacheToRedis" ) == 1 {
go u . ValidTokenCacheToRedis ( userId )
}
// go u.UpdateUserloginInfo(clientIp, userId)
return true
}
return false
}
// 更新用户登陆次数、最近一次登录ip、最近一次登录时间
// func (u *UsersModel) UpdateUserloginInfo(userId int64) {
// sql := "UPDATE tb_users SET login_times=IFNULL(login_times,0)+1,last_login_time=? WHERE id=? "
// _ = u.Exec(sql, time.Now().Format(variable.DateFormat), userId)
// }
//当用户更改密码后, 所有的token都失效, 必须重新登录
func ( u * UsersModel ) OauthResetToken ( userId int , newPass string ) bool {
//如果用户新旧密码一致, 直接返回true, 不需要处理
userItem , err := u . ShowOneItem ( userId )
if userItem != nil && err == nil && userItem . Pass == newPass {
return true
} else if userItem != nil {
// 如果用户密码被修改, 那么redis中的token值也清除
if variable . ConfigYml . GetInt ( "Token.IsCacheToRedis" ) == 1 {
go u . DelTokenCacheFromRedis ( int64 ( userId ) )
}
sql := "UPDATE tb_oauth_access_tokens SET revoked=1,updated_at=NOW(),action_name='ResetPass' WHERE fr_user_id=? "
if u . Exec ( sql , userId ) . Error == nil {
return true
}
}
return false
}
//当tb_users 删除数据, 相关的token同步删除
func ( u * UsersModel ) OauthDestroyToken ( userId int ) bool {
//如果用户新旧密码一致, 直接返回true, 不需要处理
sql := "DELETE FROM tb_oauth_access_tokens WHERE fr_user_id=? "
//判断>=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` 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
// }
// 查询(根据关键词模糊查询)
type baseInfo struct {
Id int64 ` gorm:"primaryKey" json:"id" `
UserName string ` gorm:"column:user_name" json:"user_name" `
}
func ( u * UsersModel ) Info ( userName string ) ( temp baseInfo ) {
sql := "SELECT `id`, `user_name` FROM `tb_users` WHERE user_name= ?"
if res := u . Raw ( sql , userName ) . Scan ( & temp ) ; res . RowsAffected > 0 {
return temp
}
return
}
//新增
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=?)"
return u . Exec ( sql , userName , pass , realName , phone , remark , userName ) . RowsAffected > 0
// 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 ) NameUpdate ( id int , userName string ) bool {
sql := "update tb_users set user_name=? WHERE status=1 AND id=?"
return u . Exec ( sql , userName , id ) . RowsAffected >= 0
}
func ( u * UsersModel ) UpdatePassword ( id int , userName , oldpass , newpass string ) * UsersModel {
sql := "select pass from tb_users where id=? limit 1"
result := u . Raw ( sql , id ) . First ( u )
if result . Error == nil {
// 账号密码验证成功
if len ( u . Pass ) > 0 && ( u . Pass == md5_encrypt . Base64Md5 ( oldpass ) ) {
if u . OauthResetToken ( id , md5_encrypt . Base64Md5 ( newpass ) ) {
variable . ZapLog . Info ( "密码更改后, token重已置" )
} else {
variable . ZapLog . Error ( "密码更改后, token重置失败" )
}
// 更新密码
sql = "update tb_users set pass=? WHERE status=1 AND id=?"
if u . Exec ( sql , newpass , id ) . RowsAffected >= 0 {
return u
}
}
} else {
variable . ZapLog . Error ( "根据账号查询单条记录出错:" , zap . Error ( result . Error ) )
}
return nil
}
//删除用户以及关联的token记录
func ( u * UsersModel ) Destroy ( id int , userName , pass string ) bool {
// 删除用户时, 清除用户缓存在redis的全部token
if variable . ConfigYml . GetInt ( "Token.IsCacheToRedis" ) == 1 {
go u . DelTokenCacheFromRedis ( int64 ( id ) )
}
// 检查密码是否正确
sql := "select pass from tb_users where id=? limit 1"
result := u . Raw ( sql , id ) . First ( u )
if result . Error == nil {
// 账号密码验证成功
if len ( u . Pass ) > 0 && ( u . Pass == md5_encrypt . Base64Md5 ( pass ) ) {
// 删除用户密钥
err := u . deleteKeyFromDB ( userName )
if err != nil {
variable . ZapLog . Error ( err . Error ( ) )
}
// 删除用户
if u . Delete ( u , id ) . Error == nil {
// 删除token
if u . OauthDestroyToken ( id ) {
return true
} else {
variable . ZapLog . Error ( "删除用户时token删除失败" )
}
} else {
variable . ZapLog . Error ( "删除用户失败" )
}
}
}
return false
}
func ( u * UsersModel ) Logout ( id int , userName string ) bool {
// 登出用户时, 清除用户缓存在redis的全部token
if variable . ConfigYml . GetInt ( "Token.IsCacheToRedis" ) == 1 {
go u . DelTokenCacheFromRedis ( int64 ( id ) )
}
// 删除token
if ! u . OauthDestroyToken ( id ) {
// return true
variable . ZapLog . Error ( "登出时删除token失败" )
}
// 删除用户密钥
if err := u . deleteKeyFromDB ( userName ) ; err != nil {
variable . ZapLog . Error ( "登出时删除密钥失败" )
}
return true
}
// 返回用户密码加密所需的公钥
func ( u * UsersModel ) PublicKey ( userName string ) [ ] byte {
// sql :="SELECT public_key FROM `tb_rsa_keypair` WHERE user_name = ?"
// var publicKey string
// if res := u.Raw(sql,userName).Scan(&publicKey); res.RowsAffected > 0 {
// return []byte(publicKey)
// }
// // 不存在密钥,重新生成密钥
// return u.GenerateKeyPair(userName)
publicKey , err := u . getKeyFromDB ( userName , "public_key" )
if err == nil {
return [ ] byte ( publicKey )
}
// 不存在密钥,重新生成密钥
pubPEM , priPEM , err := rsa . GenerateRSAKeyPair ( )
if err != nil {
variable . ZapLog . Error ( "不存在密钥且密钥生成失败" )
return nil
}
if err := u . storeKeyPair ( userName , pubPEM , priPEM ) ; err != nil {
variable . ZapLog . Error ( "密钥存储失败" )
return nil
}
return pubPEM
}
// 返回服务器解密所需的私钥
func ( u * UsersModel ) PrivateKey ( userName string ) [ ] byte {
// sql :="SELECT private_key FROM `tb_rsa_keypair` WHERE user_name = ?"
// var privateKey string
// if res := u.Raw(sql,userName).Scan(&privateKey); res.RowsAffected > 0 {
// priPEM:=pem.EncodeToMemory(&pem.Block{
// Type: "PRIVATE KEY",
// Bytes: []byte(privateKey),
// })
// return priPEM
// }
// return nil
privateKey , err := u . getKeyFromDB ( userName , "private_key" )
if err == nil {
// priPEM := pem.EncodeToMemory(&pem.Block{
// Type: "PRIVATE KEY",
// Bytes: []byte(privateKey),
// })
// return priPEM
return [ ] byte ( privateKey )
}
variable . ZapLog . Error ( "返回私钥失败" )
return nil
}
// func (u *UsersModel)GenerateKeyPair(userName string)[]byte{
// priKey,err:=rsa.GenerateKey(rand.Reader,variable.ConfigYml.GetInt("RSA.keySize"))
// if err!=nil{
// return nil
// }
// pubKey:=&priKey.PublicKey
// // convert to byte slice
// priASN1:=x509.MarshalPKCS1PrivateKey(priKey)
// priPEM:=pem.EncodeToMemory(&pem.Block{
// Type: "PRIVATE KEY",
// Bytes: priASN1,
// })
// pubASN1,err:=x509.MarshalPKIXPublicKey(pubKey)
// if err!=nil{
// return nil
// }
// pubPEM := pem.EncodeToMemory(&pem.Block{
// Type: "PUBLIC KEY",
// Bytes: pubASN1,
// })
// // store key_pair
// sql:="INSERT INTO tb_rsa_keypair(user_name,public_key,private_key) VALUES (?,?,?)"
// if u.Exec(sql, userName, pubPEM, priPEM).RowsAffected > 0 {
// return pubPEM
// }
// return nil
// }
// 从数据库获取密钥(公钥/私钥)
func ( u * UsersModel ) getKeyFromDB ( userName , keyType string ) ( string , error ) {
sql := fmt . Sprintf ( "SELECT %s FROM `tb_rsa_keypair` WHERE user_name = ?" , keyType )
var key string
if res := u . Raw ( sql , userName ) . Scan ( & key ) ; res . RowsAffected > 0 {
return key , nil
}
return "" , fmt . Errorf ( "key not found" )
}
// 从数据库删除用户的所有密钥
func ( u * UsersModel ) deleteKeyFromDB ( userName string ) error {
sql := "DELETE FROM `tb_rsa_keypair` WHERE user_name = ?"
if u . Exec ( sql , userName ) . RowsAffected > 0 {
return nil
}
return fmt . Errorf ( "failed to delete key pair" )
}
// 存储公私钥对到数据库
func ( u * UsersModel ) storeKeyPair ( userName string , pubPEM , priPEM [ ] byte ) error {
sql := "INSERT INTO tb_rsa_keypair(user_name, public_key, private_key) VALUES (?, ?, ?)"
if u . Exec ( sql , userName , pubPEM , priPEM ) . RowsAffected > 0 {
return nil
}
return fmt . Errorf ( "failed to store key pair" )
}
// 后续两个函数专门处理用户 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 ( )
}