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()=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() }