删减原版登录注册等逻辑,增加登出、密码RSA加密等

master
joefalmko 1 month ago
parent 6e30eb041d
commit 00ab1f459e

@ -38,6 +38,8 @@ const (
CurdCreatFailMsg string = "新增失败" CurdCreatFailMsg string = "新增失败"
CurdUpdateFailCode int = -400201 CurdUpdateFailCode int = -400201
CurdUpdateFailMsg string = "更新失败" CurdUpdateFailMsg string = "更新失败"
CurdUpdatePassFailCode int = -400207
CurdUpdatePassFailMsg string = "更新密码失败"
CurdDeleteFailCode int = -400202 CurdDeleteFailCode int = -400202
CurdDeleteFailMsg string = "删除失败" CurdDeleteFailMsg string = "删除失败"
CurdSelectFailCode int = -400203 CurdSelectFailCode int = -400203
@ -48,7 +50,10 @@ const (
CurdLoginFailMsg string = "登录失败" CurdLoginFailMsg string = "登录失败"
CurdRefreshTokenFailCode int = -400206 CurdRefreshTokenFailCode int = -400206
CurdRefreshTokenFailMsg string = "刷新Token失败" CurdRefreshTokenFailMsg string = "刷新Token失败"
CurdLogoutFailCode int = -400208
CurdLogoutFailMsg string = "登出失败"
CurdPublicKeyFailCode int = -400209
CurdPublicKeyFailMsg string = "密钥获取失败"
//文件上传 //文件上传
FilesUploadFailCode int = -400250 FilesUploadFailCode int = -400250
FilesUploadFailMsg string = "文件上传失败, 获取上传文件发生错误!" FilesUploadFailMsg string = "文件上传失败, 获取上传文件发生错误!"

@ -1,14 +1,16 @@
package web package web
import ( import (
"github.com/gin-gonic/gin"
"goskeleton/app/global/consts" "goskeleton/app/global/consts"
"goskeleton/app/global/variable" "goskeleton/app/global/variable"
"goskeleton/app/model" "goskeleton/app/model"
"goskeleton/app/service/users/curd" "goskeleton/app/service/users/curd"
userstoken "goskeleton/app/service/users/token" userstoken "goskeleton/app/service/users/token"
"goskeleton/app/utils/response" "goskeleton/app/utils/response"
rsa "goskeleton/app/utils/rsa"
"time" "time"
"github.com/gin-gonic/gin"
) )
type Users struct { type Users struct {
@ -21,8 +23,9 @@ func (u *Users) Register(context *gin.Context) {
// 当然也可以通过gin框架的上下文原始方法获取例如 context.PostForm("user_name") 获取,这样获取的数据格式为文本,需要自己继续转换 // 当然也可以通过gin框架的上下文原始方法获取例如 context.PostForm("user_name") 获取,这样获取的数据格式为文本,需要自己继续转换
userName := context.GetString(consts.ValidatorPrefix + "user_name") userName := context.GetString(consts.ValidatorPrefix + "user_name")
pass := context.GetString(consts.ValidatorPrefix + "pass") pass := context.GetString(consts.ValidatorPrefix + "pass")
userIp := context.ClientIP() // userIp := context.ClientIP()
if curd.CreateUserCurdFactory().Register(userName, pass, userIp) { // if curd.CreateUserCurdFactory().Register(userName, pass, userIp) {
if curd.CreateUserCurdFactory().Register(userName, pass) {
response.Success(context, consts.CurdStatusOkMsg, "") response.Success(context, consts.CurdStatusOkMsg, "")
} else { } else {
response.Fail(context, consts.CurdRegisterFailCode, consts.CurdRegisterFailMsg, "") response.Fail(context, consts.CurdRegisterFailCode, consts.CurdRegisterFailMsg, "")
@ -30,38 +33,41 @@ func (u *Users) Register(context *gin.Context) {
} }
// 2.用户登录 // 2.用户登录
func (u *Users) Login(context *gin.Context) { func (u *Users) Login(c *gin.Context) {
userName := context.GetString(consts.ValidatorPrefix + "user_name") userName := c.GetString(consts.ValidatorPrefix + "user_name")
pass := context.GetString(consts.ValidatorPrefix + "pass") pass := c.GetString(consts.ValidatorPrefix + "pass")
phone := context.GetString(consts.ValidatorPrefix + "phone") // 密码解密
pass = string(u.DecryptPassword(userName,pass))
// phone := context.GetString(consts.ValidatorPrefix + "phone")
userModelFact := model.CreateUserFactory("") userModelFact := model.CreateUserFactory("")
userModel := userModelFact.Login(userName, pass) userModel := userModelFact.Login(userName, pass)
if userModel != nil { if userModel != nil {
userTokenFactory := userstoken.CreateUserFactory() userTokenFactory := userstoken.CreateUserFactory()
if userToken, err := userTokenFactory.GenerateToken(userModel.Id, userModel.UserName, userModel.Phone, variable.ConfigYml.GetInt64("Token.JwtTokenCreatedExpireAt")); err == nil { if userToken, err := userTokenFactory.GenerateToken( userModel.Id,userName, variable.ConfigYml.GetInt64("Token.JwtTokenCreatedExpireAt")); err == nil {
if userTokenFactory.RecordLoginToken(userToken, context.ClientIP()) { if userTokenFactory.RecordLoginToken(userModel.Id,userToken) { // 记录用户登录记录不必要但会将token存入Redis懒得改了
data := gin.H{ data := gin.H{
"userId": userModel.Id, // "userId": userModel.Id,
"user_name": userName, // "user_name": userName,
"realName": userModel.RealName, // "realName": userModel.RealName,
"phone": phone, // "phone": phone,
"token": userToken, "token": userToken,
"updated_at": time.Now().Format(variable.DateFormat), "updated_at": time.Now().Format(variable.DateFormat),
} }
response.Success(context, consts.CurdStatusOkMsg, data) response.Success(c, consts.CurdStatusOkMsg, data)
go userModel.UpdateUserloginInfo(context.ClientIP(), userModel.Id) // go userModel.UpdateUserloginInfo( userModel.Id)
return return
} }
} }
} }
response.Fail(context, consts.CurdLoginFailCode, consts.CurdLoginFailMsg, "") response.Fail(c, consts.CurdLoginFailCode, consts.CurdLoginFailMsg, "")
} }
// 刷新用户token // 刷新用户token
func (u *Users) RefreshToken(context *gin.Context) { func (u *Users) RefreshToken(context *gin.Context) {
oldToken := context.GetString(consts.ValidatorPrefix + "token") oldToken := context.GetString(consts.ValidatorPrefix + "token")
if newToken, ok := userstoken.CreateUserFactory().RefreshToken(oldToken, context.ClientIP()); ok { if newToken, ok := userstoken.CreateUserFactory().RefreshToken(oldToken); ok {
res := gin.H{ res := gin.H{
"token": newToken, "token": newToken,
} }
@ -77,68 +83,214 @@ func (u *Users) RefreshToken(context *gin.Context) {
// 您也可以参考 Admin 项目地址https://gitee.com/daitougege/gin-skeleton-admin-backend/ 中, app/model/ 提供的示例语法 // 您也可以参考 Admin 项目地址https://gitee.com/daitougege/gin-skeleton-admin-backend/ 中, app/model/ 提供的示例语法
//3.用户查询show //3.用户查询show
func (u *Users) Show(context *gin.Context) { func (u *Users) Info(c *gin.Context) {
userName := context.GetString(consts.ValidatorPrefix + "user_name") userName := c.GetString(consts.ValidatorPrefix + "user_name")
page := context.GetFloat64(consts.ValidatorPrefix + "page") // page := context.GetFloat64(consts.ValidatorPrefix + "page")
limit := context.GetFloat64(consts.ValidatorPrefix + "limit") // limit := context.GetFloat64(consts.ValidatorPrefix + "limit")
limitStart := (page - 1) * limit // limitStart := (page - 1) * limit
counts, showlist := model.CreateUserFactory("").Show(userName, int(limitStart), int(limit)) res := model.CreateUserFactory("").Info(userName)
if counts > 0 && showlist != nil { if res.UserName != ""{
response.Success(context, consts.CurdStatusOkMsg, gin.H{"counts": counts, "list": showlist}) response.Success(c, consts.CurdStatusOkMsg, gin.H{"id":res.Id,"user_name":res.UserName})
} else { } else {
response.Fail(context, consts.CurdSelectFailCode, consts.CurdSelectFailMsg, "") response.Fail(c, consts.CurdSelectFailCode, consts.CurdSelectFailMsg, "")
} }
} }
//4.用户新增(store) //4.用户新增(store)
func (u *Users) Store(context *gin.Context) { // func (u *Users) Store(context *gin.Context) {
userName := context.GetString(consts.ValidatorPrefix + "user_name") // userName := context.GetString(consts.ValidatorPrefix + "user_name")
pass := context.GetString(consts.ValidatorPrefix + "pass") // pass := context.GetString(consts.ValidatorPrefix + "pass")
realName := context.GetString(consts.ValidatorPrefix + "real_name") // realName := context.GetString(consts.ValidatorPrefix + "real_name")
phone := context.GetString(consts.ValidatorPrefix + "phone") // phone := context.GetString(consts.ValidatorPrefix + "phone")
remark := context.GetString(consts.ValidatorPrefix + "remark") // remark := context.GetString(consts.ValidatorPrefix + "remark")
if curd.CreateUserCurdFactory().Store(userName, pass, realName, phone, remark) { // if curd.CreateUserCurdFactory().Store(userName, pass, realName, phone, remark) {
response.Success(context, consts.CurdStatusOkMsg, "") // response.Success(context, consts.CurdStatusOkMsg, "")
} else { // } else {
response.Fail(context, consts.CurdCreatFailCode, consts.CurdCreatFailMsg, "") // response.Fail(context, consts.CurdCreatFailCode, consts.CurdCreatFailMsg, "")
} // }
} // }
//5.用户更新(update) //5.用户更新(update)
func (u *Users) Update(context *gin.Context) { func (u *Users) NameUpdate(c *gin.Context) {
//表单参数验证中的int、int16、int32 、int64、float32、float64等数字键字段请统一使用 GetFloat64() 获取,其他函数无效 //表单参数验证中的int、int16、int32 、int64、float32、float64等数字键字段请统一使用 GetFloat64() 获取,其他函数无效
userId := context.GetFloat64(consts.ValidatorPrefix + "id") userId := c.GetFloat64(consts.ValidatorPrefix + "id")
userName := context.GetString(consts.ValidatorPrefix + "user_name") userName := c.GetString(consts.ValidatorPrefix + "user_name")
pass := context.GetString(consts.ValidatorPrefix + "pass") // pass := context.GetString(consts.ValidatorPrefix + "pass")
realName := context.GetString(consts.ValidatorPrefix + "real_name") // realName := context.GetString(consts.ValidatorPrefix + "real_name")
phone := context.GetString(consts.ValidatorPrefix + "phone") // phone := context.GetString(consts.ValidatorPrefix + "phone")
remark := context.GetString(consts.ValidatorPrefix + "remark") // remark := context.GetString(consts.ValidatorPrefix + "remark")
userIp := context.ClientIP() // userIp := context.ClientIP()
// 检查正在修改的用户名是否被其他人使用 // 检查正在修改的用户名是否被其他人使用
if model.CreateUserFactory("").UpdateDataCheckUserNameIsUsed(int(userId), userName) > 0 { if model.CreateUserFactory("").UpdateDataCheckUserNameIsUsed(int(userId), userName) > 0 {
response.Fail(context, consts.CurdUpdateFailCode, consts.CurdUpdateFailMsg+", "+userName+" 已经被其他人使用", "") response.Fail(c, consts.CurdUpdateFailCode, consts.CurdUpdateFailMsg+", "+userName+" 已经被其他人使用", "")
return return
} }
//注意这里没有实现更加精细的权限控制逻辑例如超级管理管理员可以更新全部用户数据普通用户只能修改自己的数据。目前只是验证了token有效、合法之后就可以进行后续操作 //注意这里没有实现更加精细的权限控制逻辑例如超级管理管理员可以更新全部用户数据普通用户只能修改自己的数据。目前只是验证了token有效、合法之后就可以进行后续操作
// 实际使用请根据真是业务实现权限控制逻辑、再进行数据库操作 // 实际使用请根据真是业务实现权限控制逻辑、再进行数据库操作
if curd.CreateUserCurdFactory().Update(int(userId), userName, pass, realName, phone, remark, userIp) { // if len(pass)>0{
response.Success(context, consts.CurdStatusOkMsg, "") // if !curd.CreateUserCurdFactory().UpdatePassword(int(userId),pass) {
// response.Fail(context, consts.CurdUpdatePassFailCode, consts.CurdUpdatePassFailMsg, "")
// }
// }
if curd.CreateUserCurdFactory().NameUpdate(int(userId), userName) {
response.Success(c, consts.CurdStatusOkMsg, "")
} else { } else {
response.Fail(context, consts.CurdUpdateFailCode, consts.CurdUpdateFailMsg, "") response.Fail(c, consts.CurdUpdateFailCode, consts.CurdUpdateFailMsg, "")
}
}
func (u *Users) PasswordUpdate(c *gin.Context) {
//表单参数验证中的int、int16、int32 、int64、float32、float64等数字键字段请统一使用 GetFloat64() 获取,其他函数无效
userId := c.GetFloat64(consts.ValidatorPrefix + "id")
userName :=c.GetString(consts.ValidatorPrefix + "user_name")
// userName := context.GetString(consts.ValidatorPrefix + "user_name")
oldpass := c.GetString(consts.ValidatorPrefix + "oldpass")
newpass := c.GetString(consts.ValidatorPrefix + "newpass")
// 密码解密
oldpass = string(u.DecryptPassword(userName, oldpass))
newpass = string(u.DecryptPassword(userName, newpass))
//注意这里没有实现更加精细的权限控制逻辑例如超级管理管理员可以更新全部用户数据普通用户只能修改自己的数据。目前只是验证了token有效、合法之后就可以进行后续操作
// 实际使用请根据真是业务实现权限控制逻辑、再进行数据库操作
userModelFact := model.CreateUserFactory("")
userModel := userModelFact.UpdatePassword(int(userId),userName, oldpass,newpass)
if userModel != nil {
response.Success(c, consts.CurdStatusOkMsg, "")
// go userModel.UpdateUserloginInfo( userModel.Id)
return
} }
response.Fail(c, consts.CurdUpdateFailCode, consts.CurdUpdateFailMsg, "")
} }
//6.删除记录 //6.删除记录
func (u *Users) Destroy(context *gin.Context) { func (u *Users) Destroy(context *gin.Context) {
//表单参数验证中的int、int16、int32 、int64、float32、float64等数字键字段请统一使用 GetFloat64() 获取,其他函数无效 //表单参数验证中的int、int16、int32 、int64、float32、float64等数字键字段请统一使用 GetFloat64() 获取,其他函数无效
userId := context.GetFloat64(consts.ValidatorPrefix + "id") userId := context.GetFloat64(consts.ValidatorPrefix + "id")
if model.CreateUserFactory("").Destroy(int(userId)) { pass :=context.GetString(consts.ValidatorPrefix + "pass")
userName:=context.GetString(consts.ValidatorPrefix + "user_name")
// 密码解密
pass = string(u.DecryptPassword(userName, pass))
if model.CreateUserFactory("").Destroy(int(userId),userName,pass) {
response.Success(context, consts.CurdStatusOkMsg, "") response.Success(context, consts.CurdStatusOkMsg, "")
} else { } else {
response.Fail(context, consts.CurdDeleteFailCode, consts.CurdDeleteFailMsg, "") response.Fail(context, consts.CurdDeleteFailCode, consts.CurdDeleteFailMsg, "")
} }
} }
func (u *Users) Logout(c *gin.Context) {
//表单参数验证中的int、int16、int32 、int64、float32、float64等数字键字段请统一使用 GetFloat64() 获取,其他函数无效
id := c.GetFloat64(consts.ValidatorPrefix + "id")
userName:=c.GetString(consts.ValidatorPrefix + "user_name")
if model.CreateUserFactory("").Logout(int(id),userName) {
response.Success(c, consts.CurdStatusOkMsg, "")
} else {
response.Fail(c, consts.CurdLogoutFailCode, consts.CurdDeleteFailMsg, "")
}
}
// 返回user_name对应的公钥如果不存在会重新创建并存储后返回新的公钥
func (u *Users)PublicKey(c *gin.Context){
userName:=c.GetString(consts.ValidatorPrefix+"user_name")
key := model.CreateUserFactory("").PublicKey(userName)
if key!=nil{
response.Success(c,consts.CurdStatusOkMsg,gin.H{"PublicKey":key})
}else{
response.Fail(c,consts.CurdPublicKeyFailCode,consts.CurdPublicKeyFailMsg,"")
}
}
// // 解密密码
// func DecryptPassword(userName,pass string)[]byte{
// if pass==""{
// return nil
// }
// key:=model.CreateUserFactory("").PrivateKey(userName)
// if key!=nil{
// privateKey,err:=parsePrivateKeyFromPEM(key)
// if err!=nil{
// return nil
// }
// // Base64 decode the encrypted password
// encryptedPassword, err := base64.StdEncoding.DecodeString(pass)
// if err != nil {
// return nil
// }
// // Decrypt the password using the private key
// decryptedBytes, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedPassword)
// if err != nil {
// return nil
// }
// return decryptedBytes
// }
// return nil
// }
// // 解析PEM格式的私钥
// func parsePrivateKeyFromPEM(pemKey []byte)(*rsa.PrivateKey,error){
// block, _ := pem.Decode(pemKey)
// if block == nil {
// return nil, fmt.Errorf("failed to parse PEM block")
// }
// // 解析私钥
// privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
// if err != nil {
// // 如果不是PKCS#1格式尝试使用PKCS#8格式解析
// privateKeyInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes)
// if err != nil {
// return nil, fmt.Errorf("failed to parse PKCS8 private key: %v", err)
// }
// privateKey, ok := privateKeyInterface.(*rsa.PrivateKey)
// if !ok {
// return nil, fmt.Errorf("parsed key is not an RSA private key")
// }
// return privateKey,nil
// }
// return privateKey, nil
// }
// 密码解密
func (u *Users)DecryptPassword(userName, pass string) []byte {
if pass == "" {
return nil
}
// 获取私钥
key := model.CreateUserFactory("").PrivateKey(userName)
if key == nil {
variable.ZapLog.Error("私钥获取失败")
return nil
}
// 解析私钥
privateKey, err := rsa.ParsePrivateKeyFromPEM(key)
if err != nil {
variable.ZapLog.Error("私钥解析失败")
return nil
}
// Base64 解码密码
encryptedPassword, err := rsa.DecodeBase64(pass)
if err != nil {
variable.ZapLog.Error("密码Base64解码失败")
return nil
}
// 使用私钥解密密码
decryptedBytes, err := rsa.DecryptWithPrivateKey(privateKey, encryptedPassword)
if err != nil {
variable.ZapLog.Error("密码使用私钥解密失败")
return nil
}
return decryptedBytes
}

@ -73,7 +73,7 @@ func CheckTokenAuthWithRefresh() gin.HandlerFunc {
// 判断token是否满足刷新条件 // 判断token是否满足刷新条件
if userstoken.CreateUserFactory().TokenIsMeetRefreshCondition(token[1]) { if userstoken.CreateUserFactory().TokenIsMeetRefreshCondition(token[1]) {
// 刷新token // 刷新token
if newToken, ok := userstoken.CreateUserFactory().RefreshToken(token[1], context.ClientIP()); ok { if newToken, ok := userstoken.CreateUserFactory().RefreshToken(token[1]); ok {
if customToken, err := userstoken.CreateUserFactory().ParseToken(newToken); err == nil { if customToken, err := userstoken.CreateUserFactory().ParseToken(newToken); err == nil {
key := variable.ConfigYml.GetString("Token.BindContextKeyName") key := variable.ConfigYml.GetString("Token.BindContextKeyName")
// token刷新成功同时绑定在请求上下文 // token刷新成功同时绑定在请求上下文

@ -24,14 +24,20 @@ func WebRegisterValidator() {
containers.Set(key, users.RefreshToken{}) containers.Set(key, users.RefreshToken{})
// Users基本操作CURD // Users基本操作CURD
key = consts.ValidatorPrefix + "UsersShow" key = consts.ValidatorPrefix + "UsersInfo"
containers.Set(key, users.Show{}) containers.Set(key, users.Info{})
key = consts.ValidatorPrefix + "UsersStore" // key = consts.ValidatorPrefix + "UsersStore"
containers.Set(key, users.Store{}) // containers.Set(key, users.Store{})
key = consts.ValidatorPrefix + "UsersUpdate" key = consts.ValidatorPrefix + "UsersNameUpdate"
containers.Set(key, users.Update{}) containers.Set(key, users.NameUpdate{})
key = consts.ValidatorPrefix + "UsersPasswordUpdate"
containers.Set(key, users.PasswordUpdate{})
key = consts.ValidatorPrefix + "UsersDestroy" key = consts.ValidatorPrefix + "UsersDestroy"
containers.Set(key, users.Destroy{}) containers.Set(key, users.Destroy{})
key = consts.ValidatorPrefix + "UsersLogout"
containers.Set(key, users.Logout{})
key = consts.ValidatorPrefix + "PublicKey"
containers.Set(key, users.PublicKey{})
// 文件上传 // 文件上传
key = consts.ValidatorPrefix + "UploadFiles" key = consts.ValidatorPrefix + "UploadFiles"

@ -1,8 +1,8 @@
package users package users
type BaseField struct { type BaseField struct {
UserName string `form:"user_name" json:"user_name" binding:"required,min=1"` // 必填、对于文本,表示它的长度>=1 UserName string `form:"user_name" json:"user_name" binding:"required,min=3"` // 必填、对于文本,表示它的长度>=1
Pass string `form:"pass" json:"pass" binding:"required,min=6,max=20"` // 密码为 必填,长度>=6 Pass string `form:"pass" json:"pass" binding:"required,min=6"` // 密码为 必填,长度>=6
} }
type Id struct { type Id struct {

@ -11,6 +11,8 @@ import (
type Destroy struct { type Destroy struct {
// 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合 // 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合
Id Id
BaseField
// Pass string `form:"pass" json:"pass" binding:"required,min=6"`
} }
// 验证器语法,参见 Register.go文件有详细说明 // 验证器语法,参见 Register.go文件有详细说明

@ -4,19 +4,19 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"goskeleton/app/global/consts" "goskeleton/app/global/consts"
"goskeleton/app/http/controller/web" "goskeleton/app/http/controller/web"
common_data_type "goskeleton/app/http/validator/common/data_type" // common_data_type "goskeleton/app/http/validator/common/data_type"
"goskeleton/app/http/validator/core/data_transfer" "goskeleton/app/http/validator/core/data_transfer"
"goskeleton/app/utils/response" "goskeleton/app/utils/response"
) )
type Show struct { type Info struct {
// 表单参数验证结构体支持匿名结构体嵌套 // 表单参数验证结构体支持匿名结构体嵌套
UserName string `form:"user_name" json:"user_name" binding:"required,min=1"` // 必填、对于文本,表示它的长度>=1 UserName string `form:"user_name" json:"user_name" binding:"required,min=3"` // 必填、对于文本,表示它的长度>=1
common_data_type.Page // common_data_type.Page
} }
// 验证器语法,参见 Register.go文件有详细说明 // 验证器语法,参见 Register.go文件有详细说明
func (s Show) CheckParams(context *gin.Context) { func (s Info) CheckParams(context *gin.Context) {
//1.基本的验证规则没有通过 //1.基本的验证规则没有通过
if err := context.ShouldBind(&s); err != nil { if err := context.ShouldBind(&s); err != nil {
// 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可 // 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可
@ -27,9 +27,9 @@ func (s Show) CheckParams(context *gin.Context) {
// 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值 // 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值
extraAddBindDataContext := data_transfer.DataAddContext(s, consts.ValidatorPrefix, context) extraAddBindDataContext := data_transfer.DataAddContext(s, consts.ValidatorPrefix, context)
if extraAddBindDataContext == nil { if extraAddBindDataContext == nil {
response.ErrorSystem(context, "UserShow表单验证器json化失败", "") response.ErrorSystem(context, "UserInfo表单验证器json化失败", "")
} else { } else {
// 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性 // 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性
(&web.Users{}).Show(extraAddBindDataContext) (&web.Users{}).Info(extraAddBindDataContext)
} }
} }

@ -0,0 +1,49 @@
package users
import (
"github.com/gin-gonic/gin"
"goskeleton/app/global/consts"
"goskeleton/app/http/controller/web"
"goskeleton/app/http/validator/core/data_transfer"
"goskeleton/app/utils/response"
)
type Logout struct {
// 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合
// UserName string `form:"user_name" json:"user_name" binding:"required,min=3"`
Id
}
// 验证器语法,参见 Register.go文件有详细说明
func (l Logout) CheckParams(context *gin.Context) {
if err := context.ShouldBind(&l); err != nil {
// 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可
response.ValidatorError(context, err)
return
}
// 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值
extraAddBindDataContext := data_transfer.DataAddContext(l, consts.ValidatorPrefix, context)
if extraAddBindDataContext == nil {
response.ErrorSystem(context, "UserShow表单参数验证器json化失败", "")
return
} else {
// 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性
(&web.Users{}).Logout(extraAddBindDataContext)
// 以下代码为模拟 前置、后置函数的回调代码
/*
func(before_callback_fn func(context *gin.Context) bool, after_callback_fn func(context *gin.Context)) {
if before_callback_fn(extraAddBindDataContext) {
defer after_callback_fn(extraAddBindDataContext)
(&Web.Users{}).Destroy(extraAddBindDataContext)
} else {
// 这里编写前置函数验证不通过的相关返回提示逻辑...
}
}((&Users.DestroyBefore{}).Before, (&Users.DestroyAfter{}).After)
*/
}
}

@ -0,0 +1,55 @@
package users
import (
"github.com/gin-gonic/gin"
"goskeleton/app/global/consts"
"goskeleton/app/http/controller/web"
"goskeleton/app/http/validator/core/data_transfer"
"goskeleton/app/utils/response"
)
// 验证器是本项目骨架的先锋队,必须发挥它的极致优势,具体参考地址:
//https://godoc.org/github.com/go-playground/validator ,该验证器非常强大,强烈建议重点发挥,
//请求正式进入控制器等后面的业务逻辑层之前,参数的校验必须在验证器层完成,后面的控制器等就只管获取各种参数,代码一把梭
// 给出一些最常用的验证规则:
//required 必填;
//len=11 长度=11
//min=3 如果是数字验证的是数据范围最小值为3如果是文本验证的是最小长度为3
//max=6 如果是数字验证的是数字最大值为6如果是文本验证的是最大长度为6
// mail 验证邮箱
//gt=3 对于文本就是长度>=3
//lt=6 对于文本就是长度<=6
type PublicKey struct {
UserName string `form:"user_name" json:"user_name" binding:"required,min=3"`
}
// 特别注意: 表单参数验证器结构体的函数,绝对不能绑定在指针上
// 我们这部分代码项目启动后会加载到容器,如果绑定在指针,一次请求之后,会造成容器中的代码段被污染
func (p PublicKey) CheckParams(context *gin.Context) {
//1.先按照验证器提供的基本语法基本可以校验90%以上的不合格参数
if err := context.ShouldBind(&p); err != nil {
response.ValidatorError(context, err)
return
}
//2.继续验证具有中国特色的参数,例如 身份证号码等基本语法校验了长度18位然后可以自行编写正则表达式等更进一步验证每一部分组成
// r.CardNo 获取身份证号码继续校验,可能需要开发者编写正则表达式,稍微复杂,这里忽略
// r.Phone 获取手机号码,可以根据手机号码开头等等自定义验证,例如 如果不是以138 开头的手机号码,则报错
//if !strings.HasPrefix(r.CardNo, "138") {
// response.ErrorParam(context, gin.H{"tips": "手机号码字段card_no 必须以138开头"})
// return
//}
// 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值
extraAddBindDataContext := data_transfer.DataAddContext(p, consts.ValidatorPrefix, context)
if extraAddBindDataContext == nil {
response.ErrorSystem(context, "UserRegister表单验证器json化失败", "")
} else {
// 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性
(&web.Users{}).PublicKey(extraAddBindDataContext)
}
}

@ -23,9 +23,6 @@ import (
type Register struct { type Register struct {
BaseField BaseField
// 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合
Phone string `form:"phone" json:"phone"` // 手机号, 非必填
CardNo string `form:"card_no" json:"card_no"` //身份证号码,非必填
} }
// 特别注意: 表单参数验证器结构体的函数,绝对不能绑定在指针上 // 特别注意: 表单参数验证器结构体的函数,绝对不能绑定在指针上

@ -1,37 +1,37 @@
package users package users
import ( // import (
"github.com/gin-gonic/gin" // "github.com/gin-gonic/gin"
"goskeleton/app/global/consts" // "goskeleton/app/global/consts"
"goskeleton/app/http/controller/web" // "goskeleton/app/http/controller/web"
"goskeleton/app/http/validator/core/data_transfer" // "goskeleton/app/http/validator/core/data_transfer"
"goskeleton/app/utils/response" // "goskeleton/app/utils/response"
) // )
type Store struct { // type Store struct {
BaseField // BaseField
// 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合 // // 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合
RealName string `form:"real_name" json:"real_name" binding:"required,min=2"` // RealName string `form:"real_name" json:"real_name" binding:"required,min=2"`
Phone string `form:"phone" json:"phone" binding:"required,len=11"` // Phone string `form:"phone" json:"phone" binding:"required,len=11"`
Remark string `form:"remark" json:"remark" ` // Remark string `form:"remark" json:"remark" `
} // }
// 验证器语法,参见 Register.go文件有详细说明 // // 验证器语法,参见 Register.go文件有详细说明
func (s Store) CheckParams(context *gin.Context) { // func (s Store) CheckParams(context *gin.Context) {
//1.基本的验证规则没有通过 // //1.基本的验证规则没有通过
if err := context.ShouldBind(&s); err != nil { // if err := context.ShouldBind(&s); err != nil {
// 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可 // // 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可
response.ValidatorError(context, err) // response.ValidatorError(context, err)
return // return
} // }
// 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值 // // 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值
extraAddBindDataContext := data_transfer.DataAddContext(s, consts.ValidatorPrefix, context) // extraAddBindDataContext := data_transfer.DataAddContext(s, consts.ValidatorPrefix, context)
if extraAddBindDataContext == nil { // if extraAddBindDataContext == nil {
response.ErrorSystem(context, "UserStore表单验证器json化失败", "") // response.ErrorSystem(context, "UserStore表单验证器json化失败", "")
} else { // } else {
// 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性 // // 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性
(&web.Users{}).Store(extraAddBindDataContext) // (&web.Users{}).Store(extraAddBindDataContext)
} // }
} // }

@ -8,31 +8,77 @@ import (
"goskeleton/app/utils/response" "goskeleton/app/utils/response"
) )
type Update struct { // type Update struct {
BaseField // // BaseField
// UserName string `form:"user_name" json:"user_name" binding:"required,min=3"` // 必填、对于文本,表示它的长度>=1
// Pass string `form:"pass" json:"pass" binding:"min=6"` // 密码为 长度>=6
// Id
// // 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合
// // RealName string `form:"real_name" json:"real_name" binding:"required,min=2"`
// // Phone string `form:"phone" json:"phone" binding:"required,len=11"`
// // Remark string `form:"remark" json:"remark"`
// }
// // 验证器语法,参见 Register.go文件有详细说明
// func (u Update) CheckParams(context *gin.Context) {
// //1.基本的验证规则没有通过
// if err := context.ShouldBind(&u); err != nil {
// // 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可
// response.ValidatorError(context, err)
// return
// }
// // 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值
// extraAddBindDataContext := data_transfer.DataAddContext(u, consts.ValidatorPrefix, context)
// if extraAddBindDataContext == nil {
// response.ErrorSystem(context, "UserUpdate表单验证器json化失败", "")
// } else {
// // 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性
// (&web.Users{}).Update(extraAddBindDataContext)
// }
// }
type NameUpdate struct{
UserName string `form:"user_name" json:"user_name" binding:"required,min=3"` // 必填、对于文本,表示它的长度>=1
Id Id
// 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合 }
RealName string `form:"real_name" json:"real_name" binding:"required,min=2"` type PasswordUpdate struct{
Phone string `form:"phone" json:"phone" binding:"required,len=11"` OldPass string `form:"oldpass" json:"oldpass" binding:"required,min=6"` // 密码为 长度>=6
Remark string `form:"remark" json:"remark"` NewPass string `form:"newpass" json:"newpass" binding:"required,min=6"`
NameUpdate
}
func (n NameUpdate) CheckParams(context *gin.Context) {
//1.基本的验证规则没有通过
if err := context.ShouldBind(&n); err != nil {
// 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可
response.ValidatorError(context, err)
return
} }
// 验证器语法,参见 Register.go文件有详细说明 // 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值
extraAddBindDataContext := data_transfer.DataAddContext(n, consts.ValidatorPrefix, context)
func (u Update) CheckParams(context *gin.Context) { if extraAddBindDataContext == nil {
response.ErrorSystem(context, "UserUpdate表单验证器json化失败", "")
} else {
// 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性
(&web.Users{}).NameUpdate(extraAddBindDataContext)
}
}
func (p PasswordUpdate) CheckParams(context *gin.Context) {
//1.基本的验证规则没有通过 //1.基本的验证规则没有通过
if err := context.ShouldBind(&u); err != nil { if err := context.ShouldBind(&p); err != nil {
// 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可 // 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可
response.ValidatorError(context, err) response.ValidatorError(context, err)
return return
} }
// 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值 // 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值
extraAddBindDataContext := data_transfer.DataAddContext(u, consts.ValidatorPrefix, context) extraAddBindDataContext := data_transfer.DataAddContext(p, consts.ValidatorPrefix, context)
if extraAddBindDataContext == nil { if extraAddBindDataContext == nil {
response.ErrorSystem(context, "UserUpdate表单验证器json化失败", "") response.ErrorSystem(context, "UserUpdate表单验证器json化失败", "")
} else { } else {
// 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性 // 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性
(&web.Users{}).Update(extraAddBindDataContext) (&web.Users{}).PasswordUpdate(extraAddBindDataContext)
} }
} }

@ -1,11 +1,18 @@
package model package model
import ( import (
"go.uber.org/zap"
"encoding/pem"
// "goskeleton/app/global/consts"
"goskeleton/app/global/variable" "goskeleton/app/global/variable"
"goskeleton/app/service/users/token_cache_redis" "goskeleton/app/service/users/token_cache_redis"
"goskeleton/app/utils/md5_encrypt" "goskeleton/app/utils/md5_encrypt"
rsa "goskeleton/app/utils/rsa"
"time" "time"
"go.uber.org/zap"
"fmt"
// "github.com/google/uuid" // 导入uuid库
) )
// 操作数据库喜欢使用gorm自带语法的开发者可以参考 GinSkeleton-Admin 系统相关代码 // 操作数据库喜欢使用gorm自带语法的开发者可以参考 GinSkeleton-Admin 系统相关代码
@ -23,11 +30,11 @@ type UsersModel struct {
BaseModel BaseModel
UserName string `gorm:"column:user_name" json:"user_name"` UserName string `gorm:"column:user_name" json:"user_name"`
Pass string `json:"-"` Pass string `json:"-"`
Phone string `json:"phone"` // Phone string `json:"phone"`
RealName string `gorm:"column:real_name" json:"real_name"` // RealName string `gorm:"column:real_name" json:"real_name"`
Status int `json:"status"` Status int `json:"status"`
Token string `json:"token"` Token string `json:"token"`
LastLoginIp string `gorm:"column:last_login_ip" json:"last_login_ip"` // LastLoginIp string `gorm:"column:last_login_ip" json:"last_login_ip"`
} }
// 表名 // 表名
@ -36,19 +43,21 @@ func (u *UsersModel) TableName() string {
} }
// 用户注册(写一个最简单的使用账号、密码注册即可) // 用户注册(写一个最简单的使用账号、密码注册即可)
func (u *UsersModel) Register(userName, pass, userIp string) bool { func (u *UsersModel) Register(userName, pass 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=?)" // userID:=uuid.New()
result := u.Exec(sql, userName, pass, userIp, userName) 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 { if result.RowsAffected > 0 {
return true return true
} else { } else {
variable.ZapLog.Error("注册失败:", zap.Error(result.Error))
return false return false
} }
} }
// 用户登录, // 用户登录,
func (u *UsersModel) Login(userName string, pass string) *UsersModel { 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" sql := "select pass,id from tb_users where user_name=? limit 1"
result := u.Raw(sql, userName).First(u) result := u.Raw(sql, userName).First(u)
if result.Error == nil { if result.Error == nil {
// 账号密码验证成功 // 账号密码验证成功
@ -62,14 +71,14 @@ func (u *UsersModel) Login(userName string, pass string) *UsersModel {
} }
//记录用户登陆login生成的token每次登陆记录一次token //记录用户登陆login生成的token每次登陆记录一次token
func (u *UsersModel) OauthLoginToken(userId int64, token string, expiresAt int64, clientIp string) bool { func (u *UsersModel) OauthLoginToken(userId int64, token string, expiresAt int64) bool {
sql := ` sql := `
INSERT INTO tb_oauth_access_tokens(fr_user_id,action_name,token,expires_at,client_ip) 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=? ) 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知己实际上操作仍然是有效的。 //注意token的精确度为秒如果在一秒之内一个账号多次调用接口生成的token其实是相同的这样写入数据库第二次的影响行数为0知己实际上操作仍然是有效的。
//所以这里只判断无错误即可,判断影响行数的话,>=0 都是ok的 //所以这里只判断无错误即可,判断影响行数的话,>=0 都是ok的
if u.Exec(sql, userId, token, time.Unix(expiresAt, 0).Format(variable.DateFormat), clientIp, userId, token).Error == nil { if u.Exec(sql, userId, token, time.Unix(expiresAt, 0).Format(variable.DateFormat), userId, token).Error == nil {
// 异步缓存用户有效的token到redis // 异步缓存用户有效的token到redis
if variable.ConfigYml.GetInt("Token.IsCacheToRedis") == 1 { if variable.ConfigYml.GetInt("Token.IsCacheToRedis") == 1 {
go u.ValidTokenCacheToRedis(userId) go u.ValidTokenCacheToRedis(userId)
@ -91,27 +100,27 @@ func (u *UsersModel) OauthRefreshConditionCheck(userId int64, oldToken string) b
} }
//用户刷新token //用户刷新token
func (u *UsersModel) OauthRefreshToken(userId, expiresAt int64, oldToken, newToken, clientIp string) bool { func (u *UsersModel) OauthRefreshToken(userId, expiresAt int64, oldToken, newToken string) bool {
sql := "UPDATE tb_oauth_access_tokens SET token=? ,expires_at=?,client_ip=?,updated_at=NOW(),action_name='refresh' WHERE fr_user_id=? AND token=?" 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), clientIp, userId, oldToken).Error == nil { if u.Exec(sql, newToken, time.Unix(expiresAt, 0).Format(variable.DateFormat), userId, oldToken).Error == nil {
// 异步缓存用户有效的token到redis // 异步缓存用户有效的token到redis
if variable.ConfigYml.GetInt("Token.IsCacheToRedis") == 1 { if variable.ConfigYml.GetInt("Token.IsCacheToRedis") == 1 {
go u.ValidTokenCacheToRedis(userId) go u.ValidTokenCacheToRedis(userId)
} }
go u.UpdateUserloginInfo(clientIp, userId) // go u.UpdateUserloginInfo(clientIp, userId)
return true return true
} }
return false return false
} }
// 更新用户登陆次数、最近一次登录ip、最近一次登录时间 // 更新用户登陆次数、最近一次登录ip、最近一次登录时间
func (u *UsersModel) UpdateUserloginInfo(last_login_ip string, userId int64) { // func (u *UsersModel) UpdateUserloginInfo(userId int64) {
sql := "UPDATE tb_users SET login_times=IFNULL(login_times,0)+1,last_login_ip=?,last_login_time=? WHERE id=? " // sql := "UPDATE tb_users SET login_times=IFNULL(login_times,0)+1,last_login_time=? WHERE id=? "
_ = u.Exec(sql, last_login_ip, time.Now().Format(variable.DateFormat), userId) // _ = u.Exec(sql, time.Now().Format(variable.DateFormat), userId)
} // }
//当用户更改密码后所有的token都失效必须重新登录 //当用户更改密码后所有的token都失效必须重新登录
func (u *UsersModel) OauthResetToken(userId int, newPass, clientIp string) bool { func (u *UsersModel) OauthResetToken(userId int, newPass string) bool {
//如果用户新旧密码一致直接返回true不需要处理 //如果用户新旧密码一致直接返回true不需要处理
userItem, err := u.ShowOneItem(userId) userItem, err := u.ShowOneItem(userId)
if userItem != nil && err == nil && userItem.Pass == newPass { if userItem != nil && err == nil && userItem.Pass == newPass {
@ -123,8 +132,8 @@ func (u *UsersModel) OauthResetToken(userId int, newPass, clientIp string) bool
go u.DelTokenCacheFromRedis(int64(userId)) go u.DelTokenCacheFromRedis(int64(userId))
} }
sql := "UPDATE tb_oauth_access_tokens SET revoked=1,updated_at=NOW(),action_name='ResetPass',client_ip=? WHERE fr_user_id=? " sql := "UPDATE tb_oauth_access_tokens SET revoked=1,updated_at=NOW(),action_name='ResetPass' WHERE fr_user_id=? "
if u.Exec(sql, clientIp, userId).Error == nil { if u.Exec(sql, userId).Error == nil {
return true return true
} }
} }
@ -167,19 +176,19 @@ func (u *UsersModel) OauthCheckTokenIsOk(userId int64, token string) bool {
// 禁用一个用户的: 1.tb_users表的 status 设置为 0tb_oauth_access_tokens 表的所有token删除 // 禁用一个用户的: 1.tb_users表的 status 设置为 0tb_oauth_access_tokens 表的所有token删除
// 禁用一个用户的token请求本质上就是把tb_users表的 status 字段设置为 0 即可) // 禁用一个用户的token请求本质上就是把tb_users表的 status 字段设置为 0 即可)
func (u *UsersModel) SetTokenInvalid(userId int) bool { // func (u *UsersModel) SetTokenInvalid(userId int) bool {
sql := "delete from `tb_oauth_access_tokens` where `fr_user_id`=? " // sql := "delete from `tb_oauth_access_tokens` where `fr_user_id`=? "
if u.Exec(sql, userId).Error == nil { // if u.Exec(sql, userId).Error == nil {
if u.Exec("update tb_users set status=0 where id=?", userId).Error == nil { // if u.Exec("update tb_users set status=0 where id=?", userId).Error == nil {
return true // return true
} // }
} // }
return false // return false
} // }
//根据用户ID查询一条信息 //根据用户ID查询一条信息
func (u *UsersModel) ShowOneItem(userId int) (*UsersModel, error) { 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" sql := "SELECT `id`, `user_name`,`pass` FROM `tb_users` WHERE `status`=1 and id=? LIMIT 1"
result := u.Raw(sql, userId).First(u) result := u.Raw(sql, userId).First(u)
if result.Error == nil { if result.Error == nil {
return u, nil return u, nil
@ -189,32 +198,32 @@ func (u *UsersModel) ShowOneItem(userId int) (*UsersModel, error) {
} }
// 查询数据之前统计条数 // 查询数据之前统计条数
func (u *UsersModel) counts(userName string) (counts int64) { // func (u *UsersModel) counts(userName string) (counts int64) {
sql := "SELECT count(*) as counts FROM tb_users WHERE status=1 and user_name like ?" // 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 { // if res := u.Raw(sql, "%"+userName+"%").First(&counts); res.Error != nil {
variable.ZapLog.Error("UsersModel - counts 查询数据条数出错", zap.Error(res.Error)) // variable.ZapLog.Error("UsersModel - counts 查询数据条数出错", zap.Error(res.Error))
} // }
return counts // return counts
} // }
// 查询(根据关键词模糊查询) // 查询(根据关键词模糊查询)
func (u *UsersModel) Show(userName string, limitStart, limitItems int) (counts int64, temp []UsersModel) { type baseInfo struct{
if counts = u.counts(userName); counts > 0 { Id int64 `gorm:"primaryKey" json:"id"`
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 ?,?" UserName string `gorm:"column:user_name" json:"user_name"`
if res := u.Raw(sql, "%"+userName+"%", limitStart, limitItems).Find(&temp); res.RowsAffected > 0 {
return counts, temp
} }
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 0, nil return
} }
//新增 //新增
func (u *UsersModel) Store(userName string, pass string, realName string, phone string, remark string) bool { 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=?)" 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 u.Exec(sql, userName, pass, realName, phone, remark, userName).RowsAffected > 0
return true // return false
}
return false
} }
//UpdateDataCheckUserNameIsUsed 更新前检查新的用户名是否已经存在(避免和别的账号重名) //UpdateDataCheckUserNameIsUsed 更新前检查新的用户名是否已经存在(避免和别的账号重名)
@ -225,31 +234,175 @@ func (u *UsersModel) UpdateDataCheckUserNameIsUsed(userId int, userName string)
} }
//更新 //更新
func (u *UsersModel) Update(id int, userName string, pass string, realName string, phone string, remark string, clientIp string) bool { func (u *UsersModel) NameUpdate(id int, userName string) bool {
sql := "update tb_users set user_name=?,pass=?,real_name=?,phone=?,remark=? WHERE status=1 AND id=?" sql := "update tb_users set user_name=? WHERE status=1 AND id=?"
if u.Exec(sql, userName, pass, realName, phone, remark, id).RowsAffected >= 0 { return u.Exec(sql, userName, id).RowsAffected >= 0
if u.OauthResetToken(id, pass, clientIp) {
return true
} }
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重置失败")
} }
return false
// 更新密码
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记录 //删除用户以及关联的token记录
func (u *UsersModel) Destroy(id int) bool { func (u *UsersModel) Destroy(id int,userName,pass string) bool {
// 删除用户时清除用户缓存在redis的全部token // 删除用户时清除用户缓存在redis的全部token
if variable.ConfigYml.GetInt("Token.IsCacheToRedis") == 1 { if variable.ConfigYml.GetInt("Token.IsCacheToRedis") == 1 {
go u.DelTokenCacheFromRedis(int64(id)) 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)) {
// 删除用户密钥
u.deleteKeyFromDB(userName)
// 删除用户
if u.Delete(u, id).Error == nil { if u.Delete(u, id).Error == nil {
// 删除token
if u.OauthDestroyToken(id) { if u.OauthDestroyToken(id) {
return true return true
} }
} }
}
}
return false 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失败")
}
// 删除用户密钥
u.deleteKeyFromDB(userName)
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 {
return nil
}
if err := u.storeKeyPair(userName, pubPEM, priPEM); err != nil {
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 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 逻辑 // 后续两个函数专门处理用户 token 缓存到 redis 逻辑
func (u *UsersModel) ValidTokenCacheToRedis(userId int64) { func (u *UsersModel) ValidTokenCacheToRedis(userId int64) {

@ -13,9 +13,10 @@ type UsersCurd struct {
userModel *model.UsersModel userModel *model.UsersModel
} }
func (u *UsersCurd) Register(userName, pass, userIp string) bool { // func (u *UsersCurd) Register(userName, pass, userIp string) bool {
func (u *UsersCurd) Register(userName, pass string) bool {
pass = md5_encrypt.Base64Md5(pass) // 预先处理密码加密,然后存储在数据库 pass = md5_encrypt.Base64Md5(pass) // 预先处理密码加密,然后存储在数据库
return u.userModel.Register(userName, pass, userIp) return u.userModel.Register(userName, pass)
} }
func (u *UsersCurd) Store(name string, pass string, realName string, phone string, remark string) bool { func (u *UsersCurd) Store(name string, pass string, realName string, phone string, remark string) bool {
@ -24,8 +25,15 @@ func (u *UsersCurd) Store(name string, pass string, realName string, phone strin
return u.userModel.Store(name, pass, realName, phone, remark) return u.userModel.Store(name, pass, realName, phone, remark)
} }
func (u *UsersCurd) Update(id int, name string, pass string, realName string, phone string, remark string, clientIp string) bool { func (u *UsersCurd) NameUpdate(id int, name string) bool {
//预先处理密码加密等操作,然后进行更新 //预先处理密码加密等操作,然后进行更新
pass = md5_encrypt.Base64Md5(pass) // 预先处理密码加密,然后存储在数据库 // pass = md5_encrypt.Base64Md5(pass) // 预先处理密码加密,然后存储在数据库
return u.userModel.Update(id, name, pass, realName, phone, remark, clientIp) return u.userModel.NameUpdate(id, name)
}
func (u *UsersCurd) UpdatePassword(id int,userName,oldpass,newpass string) bool {
//预先处理密码加密等操作,然后进行更新
oldpass = md5_encrypt.Base64Md5(oldpass)
newpass = md5_encrypt.Base64Md5(newpass) // 预先处理密码加密,然后存储在数据库
// return u.userModel.UpdatePassword(id,oldpass,newpass)
return u.userModel.UpdatePassword(id,userName,oldpass,newpass)!=nil
} }

@ -24,13 +24,13 @@ type userToken struct {
} }
// GenerateToken 生成token // GenerateToken 生成token
func (u *userToken) GenerateToken(userid int64, username string, phone string, expireAt int64) (tokens string, err error) { func (u *userToken) GenerateToken(userid int64, username string, expireAt int64) (tokens string, err error) {
// 根据实际业务自定义token需要包含的参数生成token注意用户密码请勿包含在token // 根据实际业务自定义token需要包含的参数生成token注意用户密码请勿包含在token
customClaims := my_jwt.CustomClaims{ customClaims := my_jwt.CustomClaims{
UserId: userid, UserId: userid,
Name: username, Name: username,
Phone: phone, // Phone: phone,
// 特别注意,针对前文的匿名结构体,初始化的时候必须指定键名,并且不带 jwt. 否则报错Mixture of field: value and value initializers // 特别注意,针对前文的匿名结构体,初始化的时候必须指定键名,并且不带 jwt. 否则报错Mixture of field: value and value initializers
StandardClaims: jwt.StandardClaims{ StandardClaims: jwt.StandardClaims{
NotBefore: time.Now().Unix() - 10, // 生效开始时间 NotBefore: time.Now().Unix() - 10, // 生效开始时间
@ -41,11 +41,11 @@ func (u *userToken) GenerateToken(userid int64, username string, phone string, e
} }
// RecordLoginToken 用户login成功记录用户token // RecordLoginToken 用户login成功记录用户token
func (u *userToken) RecordLoginToken(userToken, clientIp string) bool { func (u *userToken) RecordLoginToken(id int64,userToken string) bool {
if customClaims, err := u.userJwt.ParseToken(userToken); err == nil { if customClaims, err := u.userJwt.ParseToken(userToken); err == nil {
userId := customClaims.UserId // userId := customClaims.UserId
expiresAt := customClaims.ExpiresAt expiresAt := customClaims.ExpiresAt
return model.CreateUserFactory("").OauthLoginToken(userId, userToken, expiresAt, clientIp) return model.CreateUserFactory("").OauthLoginToken(id, userToken, expiresAt)
} else { } else {
return false return false
} }
@ -66,14 +66,14 @@ func (u *userToken) TokenIsMeetRefreshCondition(token string) bool {
} }
// RefreshToken 刷新token的有效期默认+3600秒参见常量配置项 // RefreshToken 刷新token的有效期默认+3600秒参见常量配置项
func (u *userToken) RefreshToken(oldToken, clientIp string) (newToken string, res bool) { func (u *userToken) RefreshToken(oldToken string) (newToken string, res bool) {
var err error var err error
//如果token是有效的、或者在过期时间内那么执行更新换取新token //如果token是有效的、或者在过期时间内那么执行更新换取新token
if newToken, err = u.userJwt.RefreshToken(oldToken, variable.ConfigYml.GetInt64("Token.JwtTokenRefreshExpireAt")); err == nil { if newToken, err = u.userJwt.RefreshToken(oldToken, variable.ConfigYml.GetInt64("Token.JwtTokenRefreshExpireAt")); err == nil {
if customClaims, err := u.userJwt.ParseToken(newToken); err == nil { if customClaims, err := u.userJwt.ParseToken(newToken); err == nil {
userId := customClaims.UserId userId := customClaims.UserId
expiresAt := customClaims.ExpiresAt expiresAt := customClaims.ExpiresAt
if model.CreateUserFactory("").OauthRefreshToken(userId, expiresAt, oldToken, newToken, clientIp) { if model.CreateUserFactory("").OauthRefreshToken(userId, expiresAt, oldToken, newToken) {
return newToken, true return newToken, true
} }
} }

@ -0,0 +1,86 @@
package rsa
import(
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"encoding/base64"
"goskeleton/app/global/variable"
"fmt"
)
func GenerateRSAKeyPair() ([]byte, []byte, error) {
priKey, err := rsa.GenerateKey(rand.Reader, variable.ConfigYml.GetInt("RSA.keySize"))
if err != nil {
return nil, nil, err
}
pubKey := &priKey.PublicKey
// 转换为字节切片
priASN1 := x509.MarshalPKCS1PrivateKey(priKey)
priPEM := pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: priASN1,
})
pubASN1, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return nil, nil, err
}
pubPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pubASN1,
})
return pubPEM, priPEM, nil
}
func DecryptWithPrivateKey(privateKey *rsa.PrivateKey, encryptedPassword []byte) ([]byte, error) {
decryptedBytes, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedPassword)
if err != nil {
return nil, fmt.Errorf("failed to decrypt password: %v", err)
}
return decryptedBytes, nil
}
func DecodeBase64(encodedString string) ([]byte, error) {
decodedBytes, err := base64.StdEncoding.DecodeString(encodedString)
if err != nil {
return nil, fmt.Errorf("failed to decode base64 string: %v", err)
}
return decodedBytes, nil
}
func parsePKCS1PrivateKey(block *pem.Block) (*rsa.PrivateKey, error) {
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse PKCS1 private key: %v", err)
}
return privateKey, nil
}
func parsePKCS8PrivateKey(block *pem.Block) (*rsa.PrivateKey, error) {
privateKeyInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse PKCS8 private key: %v", err)
}
privateKey, ok := privateKeyInterface.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("parsed key is not an RSA private key")
}
return privateKey, nil
}
func ParsePrivateKeyFromPEM(pemKey []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(pemKey)
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block")
}
// 尝试解析 PKCS#1 格式
privateKey, err := parsePKCS1PrivateKey(block)
if err == nil {
return privateKey, nil
}
// 如果不是 PKCS#1 格式,尝试解析 PKCS#8 格式
return parsePKCS8PrivateKey(block)
}

@ -1,13 +1,15 @@
package main package main
import ( import (
"goskeleton/app/global/variable" // "goskeleton/app/global/variable"
_ "goskeleton/bootstrap" _ "goskeleton/bootstrap"
"goskeleton/routers" "goskeleton/routers"
) )
// 这里可以存放后端路由(例如后台管理系统) // 这里可以存放后端路由(例如后台管理系统)
func main() { func main() {
router := routers.InitWebRouter() // router := routers.InitWebRouter()
_ = router.Run(variable.ConfigYml.GetString("HttpServer.Web.Port")) // _ = router.Run(variable.ConfigYml.GetString("HttpServer.Web.Port"))
r:=routers.InitWebRouter_Co()
r.Run("localhost:14514")
} }

@ -11,6 +11,8 @@ HttpServer:
ProxyServerList: ProxyServerList:
- "192.168.10.1" # nginx 代理服务器ip地址 - "192.168.10.1" # nginx 代理服务器ip地址
- "192.168.10.2" - "192.168.10.2"
RSA:
KeySize: 2048
Token: Token:
JwtTokenSignKey: "goskeleton" #设置token生成时加密的签名 JwtTokenSignKey: "goskeleton" #设置token生成时加密的签名

@ -1,16 +1,16 @@
Gormv2: # 只针对 gorm 操作数据库有效 Gormv2: # 只针对 gorm 操作数据库有效
UseDbType: "mysql" # 备选项 mysql 、sqlserver、 postgresql UseDbType: "mysql" # 备选项 mysql 、sqlserver、 postgresql
SqlDebug: false # 请根据个人习惯设置true 表示执行的sql全部会输出在终端(一般来说开发环境可能会方便调试) false 表示默认不会在终端输出sql(生产环境建议设置为 false), SqlDebug: true # 请根据个人习惯设置true 表示执行的sql全部会输出在终端(一般来说开发环境可能会方便调试) false 表示默认不会在终端输出sql(生产环境建议设置为 false),
Mysql: Mysql:
IsInitGlobalGormMysql: 0 # 随项目启动为gorm db初始化一个全局 variable.GormDbMysql完全等于*gorm.Db,正确配置数据库,该值必须设置为: 1 IsInitGlobalGormMysql: 1 # 随项目启动为gorm db初始化一个全局 variable.GormDbMysql完全等于*gorm.Db,正确配置数据库,该值必须设置为: 1
SlowThreshold: 30 # 慢 SQL 阈值(sql执行时间超过此时间单位就会触发系统日志记录) SlowThreshold: 30 # 慢 SQL 阈值(sql执行时间超过此时间单位就会触发系统日志记录)
Write: Write:
Host: "127.0.0.1" Host: "127.0.0.1"
DataBase: "db_goskeleton" DataBase: "User"
Port: 3306 Port: 3306
Prefix: "tb_" # 目前没有用到该配置项 Prefix: "tb_" # 目前没有用到该配置项
User: "root" User: "root"
Pass: "DRsXT5ZJ6Oi55LPQ" Pass: "Gm33894239"
Charset: "utf8" Charset: "utf8"
SetMaxIdleConns: 10 SetMaxIdleConns: 10
SetMaxOpenConns: 128 SetMaxOpenConns: 128
@ -20,11 +20,11 @@ Gormv2: # 只针对 gorm 操作数据库有效
IsOpenReadDb: 0 # 是否开启读写分离配置1=开启、0=关闭IsOpenReadDb=1,Read 部分参数有效否则Read部分参数直接忽略 IsOpenReadDb: 0 # 是否开启读写分离配置1=开启、0=关闭IsOpenReadDb=1,Read 部分参数有效否则Read部分参数直接忽略
Read: Read:
Host: "127.0.0.1" Host: "127.0.0.1"
DataBase: "db_goskeleton" DataBase: "User"
Port: 3308 #注意非3306请自行调整 Port: 3308 #注意非3306请自行调整
Prefix: "tb_" Prefix: "tb_"
User: "root" User: "root"
Pass: "yourPassword" Pass: "Gm33894239"
Charset: "utf8" Charset: "utf8"
SetMaxIdleConns: 10 SetMaxIdleConns: 10
SetMaxOpenConns: 128 SetMaxOpenConns: 128

@ -1,7 +1,9 @@
CREATE DATABASE /*!32312 IF NOT EXISTS*/`db_goskeleton` /*!40100 DEFAULT CHARACTER SET utf8 */; /* CREATE DATABASE /*!32312 IF NOT EXISTS*/`db_goskeleton` /*!40100 DEFAULT CHARACTER SET utf8 */; */
CREATE DATABASE IF NOT EXISTS `User`;
USE `db_goskeleton`; /* USE `db_goskeleton`; */
USE `User`;
/*Table structure for table `tb_users` */ /*Table structure for table `tb_users` */
@ -11,15 +13,15 @@ CREATE TABLE `tb_users` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR(30) DEFAULT '' COMMENT '账号', `user_name` VARCHAR(30) DEFAULT '' COMMENT '账号',
`pass` VARCHAR(128) DEFAULT '' COMMENT '密码', `pass` VARCHAR(128) DEFAULT '' COMMENT '密码',
`real_name` VARCHAR(30) DEFAULT '' COMMENT '姓名', /* `real_name` VARCHAR(30) DEFAULT '' COMMENT '姓名', */
`phone` CHAR(11) DEFAULT '' COMMENT '手机', /* `phone` CHAR(11) DEFAULT '' COMMENT '手机', */
`status` TINYINT(4) DEFAULT 1 COMMENT '状态', /* `status` TINYINT(4) DEFAULT 1 COMMENT '状态', */
`remark` VARCHAR(255) DEFAULT '' COMMENT '备注', /* `remark` VARCHAR(255) DEFAULT '' COMMENT '备注', */
`last_login_time` DATETIME DEFAULT CURRENT_TIMESTAMP, /* `last_login_time` DATETIME DEFAULT CURRENT_TIMESTAMP, */
`last_login_ip` CHAR(30) DEFAULT '' COMMENT '最近一次登录ip', /* `last_login_ip` CHAR(30) DEFAULT '' COMMENT '最近一次登录ip', */
`login_times` INT(11) DEFAULT 0 COMMENT '累计登录次数', /* `login_times` INT(11) DEFAULT 0 COMMENT '累计登录次数', */
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, /* `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, */
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP, /* `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP, */
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
@ -57,4 +59,12 @@ PRIMARY KEY (`id`),
UNIQUE KEY `unique_index` (`ptype`,`v0`,`v1`,`v2`,`v3`,`v4`,`v5`) UNIQUE KEY `unique_index` (`ptype`,`v0`,`v1`,`v2`,`v3`,`v4`,`v5`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
CREATE TABLE `tb_rsa_keypair` (
`user_name` VARCHAR(30) DEFAULT '' COMMENT '账号',
`public_key` VARCHAR(400) DEFAULT '' COMMENT '公钥',
`private_key` VARCHAR(400) DEFAULT '' COMMENT '私钥',
/* `pass` VARCHAR(128) DEFAULT '' COMMENT '密码', */
PRIMARY KEY (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

@ -0,0 +1,15 @@
package main
import (
// "goskeleton/app/global/variable"
_ "goskeleton/bootstrap"
"goskeleton/routers"
)
// 这里可以存放后端路由(例如后台管理系统)
func main() {
// router := routers.InitWebRouter()
// _ = router.Run(variable.ConfigYml.GetString("HttpServer.Web.Port"))
r:=routers.InitWebRouter_Co()
r.Run("localhost:14514")
}

@ -3,66 +3,138 @@ package routers
import ( import (
"github.com/gin-contrib/pprof" "github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"go.uber.org/zap" // "go.uber.org/zap"
"goskeleton/app/global/consts" "goskeleton/app/global/consts"
"goskeleton/app/global/variable" "goskeleton/app/global/variable"
"goskeleton/app/http/controller/captcha" "goskeleton/app/http/controller/captcha"
"goskeleton/app/http/middleware/authorization" "goskeleton/app/http/middleware/authorization"
"goskeleton/app/http/middleware/cors" "goskeleton/app/http/middleware/cors"
validatorFactory "goskeleton/app/http/validator/core/factory" validatorFactory "goskeleton/app/http/validator/core/factory"
"goskeleton/app/utils/gin_release" // "goskeleton/app/utils/gin_release"
"net/http" // "net/http"
) )
// 该路由主要设置 后台管理系统等后端应用路由 // 该路由主要设置 后台管理系统等后端应用路由
func InitWebRouter() *gin.Engine { // func InitWebRouter() *gin.Engine {
var router *gin.Engine // var router *gin.Engine
// 非调试模式(生产模式) 日志写到日志文件 // // 非调试模式(生产模式) 日志写到日志文件
if variable.ConfigYml.GetBool("AppDebug") == false { // if variable.ConfigYml.GetBool("AppDebug") == false {
//1.gin自行记录接口访问日志不需要nginx如果开启以下3行那么请屏蔽第 34 行代码 // //1.gin自行记录接口访问日志不需要nginx如果开启以下3行那么请屏蔽第 34 行代码
//gin.DisableConsoleColor() // //gin.DisableConsoleColor()
//f, _ := os.Create(variable.BasePath + variable.ConfigYml.GetString("Logs.GinLogName")) // //f, _ := os.Create(variable.BasePath + variable.ConfigYml.GetString("Logs.GinLogName"))
//gin.DefaultWriter = io.MultiWriter(f) // //gin.DefaultWriter = io.MultiWriter(f)
//【生产模式】 // //【生产模式】
// 根据 gin 官方的说明:[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. // // 根据 gin 官方的说明:[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
// 如果部署到生产环境,请使用以下模式: // // 如果部署到生产环境,请使用以下模式:
// 1.生产模式(release) 和开发模式的变化主要是禁用 gin 记录接口访问日志, // // 1.生产模式(release) 和开发模式的变化主要是禁用 gin 记录接口访问日志,
// 2.go服务就必须使用nginx作为前置代理服务这样也方便实现负载均衡 // // 2.go服务就必须使用nginx作为前置代理服务这样也方便实现负载均衡
// 3.如果程序发生 panic 等异常使用自定义的 panic 恢复中间件拦截、记录到日志 // // 3.如果程序发生 panic 等异常使用自定义的 panic 恢复中间件拦截、记录到日志
router = gin_release.ReleaseRouter() // router = gin_release.ReleaseRouter()
} else { // } else {
// 调试模式,开启 pprof 包,便于开发阶段分析程序性能 // // 调试模式,开启 pprof 包,便于开发阶段分析程序性能
router = gin.Default() // router = gin.Default()
// pprof.Register(router)
// }
// // 设置可信任的代理服务器列表,gin (2021-11-24发布的v1.7.7版本之后出的新功能)
// if variable.ConfigYml.GetInt("HttpServer.TrustProxies.IsOpen") == 1 {
// if err := router.SetTrustedProxies(variable.ConfigYml.GetStringSlice("HttpServer.TrustProxies.ProxyServerList")); err != nil {
// variable.ZapLog.Error(consts.GinSetTrustProxyError, zap.Error(err))
// }
// } else {
// _ = router.SetTrustedProxies(nil)
// }
// //根据配置进行设置跨域
// if variable.ConfigYml.GetBool("HttpServer.AllowCrossDomain") {
// router.Use(cors.Next())
// }
// router.GET("/", func(context *gin.Context) {
// context.String(http.StatusOK, "HelloWorld,这是后端模块")
// })
// //处理静态资源不建议gin框架处理静态资源参见 public/readme.md 说明
// router.Static("/public", "./public") // 定义静态资源路由与实际目录映射关系
// router.StaticFS("/dir", http.Dir("./public")) // 将public目录内的文件列举展示
// router.StaticFile("/abcd", "./public/readme.md") // 可以根据文件名绑定需要返回的文件名
// // 创建一个验证码路由
// verifyCode := router.Group("captcha")
// {
// // 验证码业务,该业务无需专门校验参数,所以可以直接调用控制器
// verifyCode.GET("/", (&captcha.Captcha{}).GenerateId) // 获取验证码ID
// verifyCode.GET("/:captcha_id", (&captcha.Captcha{}).GetImg) // 获取图像地址
// verifyCode.GET("/:captcha_id/:captcha_value", (&captcha.Captcha{}).CheckCode) // 校验验证码
// }
// // 创建一个后端接口路由组
// backend := router.Group("/admin/")
// {
// // 创建一个websocket,如果ws需要账号密码登录才能使用就写在需要鉴权的分组这里暂定是开放式的不需要严格鉴权我们简单验证一下token值
// backend.GET("ws", validatorFactory.Create(consts.ValidatorPrefix+"WebsocketConnect"))
// // 【不需要token】中间件验证的路由 用户注册、登录
// noAuth := backend.Group("users/")
// {
// // 关于路由的第二个参数用法说明
// // 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("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin"))
// // 如果加载了验证码中间件那么就需要提交验证码才可以登陆本质上就是给登陆接口增加了2个参数验证码id提交时的键captcha_id 和 验证码值提交时的键 captcha_value具体参见配置文件
// //noAuth.Use(authorization.CheckCaptchaAuth()).POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin"))
// }
// // 刷新token
// refreshToken := backend.Group("users/")
// {
// // 刷新token当过期的token在允许失效的延长时间范围内用旧token换取新token
// refreshToken.Use(authorization.RefreshTokenConditionCheck()).POST("refreshtoken", validatorFactory.Create(consts.ValidatorPrefix+"RefreshToken"))
// }
// // 【需要token】中间件验证的路由
// backend.Use(authorization.CheckTokenAuth())
// {
// // 用户组路由
// users := backend.Group("users/")
// {
// // 查询 这里的验证器直接从容器获取是因为程序启动时将验证器注册在了容器具体代码位置App\Http\Validator\Web\Users\xxx
// users.GET("index", validatorFactory.Create(consts.ValidatorPrefix+"UsersShow"))
// // 新增
// users.POST("create", validatorFactory.Create(consts.ValidatorPrefix+"UsersStore"))
// // 更新
// users.POST("edit", validatorFactory.Create(consts.ValidatorPrefix+"UsersUpdate"))
// // 删除
// users.POST("delete", validatorFactory.Create(consts.ValidatorPrefix+"UsersDestroy"))
// }
// //文件上传公共路由
// uploadFiles := backend.Group("upload/")
// {
// uploadFiles.POST("files", validatorFactory.Create(consts.ValidatorPrefix+"UploadFiles"))
// }
// }
// }
// return router
// }
func InitWebRouter_Co() *gin.Engine {
var router *gin.Engine = gin.Default()
// 日志显示在控制台
pprof.Register(router) pprof.Register(router)
}
// 设置可信任的代理服务器列表,gin (2021-11-24发布的v1.7.7版本之后出的新功能)
if variable.ConfigYml.GetInt("HttpServer.TrustProxies.IsOpen") == 1 {
if err := router.SetTrustedProxies(variable.ConfigYml.GetStringSlice("HttpServer.TrustProxies.ProxyServerList")); err != nil {
variable.ZapLog.Error(consts.GinSetTrustProxyError, zap.Error(err))
}
} else {
_ = router.SetTrustedProxies(nil)
}
//根据配置进行设置跨域 //根据配置进行设置跨域
if variable.ConfigYml.GetBool("HttpServer.AllowCrossDomain") { if variable.ConfigYml.GetBool("HttpServer.AllowCrossDomain") {
router.Use(cors.Next()) router.Use(cors.Next())
} }
router.GET("/", func(context *gin.Context) {
context.String(http.StatusOK, "HelloWorld,这是后端模块")
})
//处理静态资源不建议gin框架处理静态资源参见 public/readme.md 说明
router.Static("/public", "./public") // 定义静态资源路由与实际目录映射关系
router.StaticFS("/dir", http.Dir("./public")) // 将public目录内的文件列举展示
router.StaticFile("/abcd", "./public/readme.md") // 可以根据文件名绑定需要返回的文件名
// 创建一个验证码路由 // 创建一个验证码路由
verifyCode := router.Group("captcha") verifyCode := router.Group("captcha")
{ {
@ -74,8 +146,6 @@ func InitWebRouter() *gin.Engine {
// 创建一个后端接口路由组 // 创建一个后端接口路由组
backend := router.Group("/admin/") backend := router.Group("/admin/")
{ {
// 创建一个websocket,如果ws需要账号密码登录才能使用就写在需要鉴权的分组这里暂定是开放式的不需要严格鉴权我们简单验证一下token值
backend.GET("ws", validatorFactory.Create(consts.ValidatorPrefix+"WebsocketConnect"))
// 【不需要token】中间件验证的路由 用户注册、登录 // 【不需要token】中间件验证的路由 用户注册、登录
noAuth := backend.Group("users/") noAuth := backend.Group("users/")
@ -84,13 +154,15 @@ func InitWebRouter() *gin.Engine {
// 1.编写一个表单参数验证器结构体,参见代码: app/http/validator/web/users/register.go // 1.编写一个表单参数验证器结构体,参见代码: app/http/validator/web/users/register.go
// 2.将以上表单参数验证器注册,遵守 键 =》值 格式注册即可 app/http/validator/common/register_validator/web_register_validator.go 20行就是注册时候的键 consts.ValidatorPrefix+"UsersRegister" // 2.将以上表单参数验证器注册,遵守 键 =》值 格式注册即可 app/http/validator/common/register_validator/web_register_validator.go 20行就是注册时候的键 consts.ValidatorPrefix+"UsersRegister"
// 3.按照注册时的键,直接从容器调用即可 validatorFactory.Create(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")) // ok
// 不需要验证码即可登陆 // 不需要验证码即可登陆
noAuth.POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin")) noAuth.POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin")) // ok
// 如果加载了验证码中间件那么就需要提交验证码才可以登陆本质上就是给登陆接口增加了2个参数验证码id提交时的键captcha_id 和 验证码值提交时的键 captcha_value具体参见配置文件 // 如果加载了验证码中间件那么就需要提交验证码才可以登陆本质上就是给登陆接口增加了2个参数验证码id提交时的键captcha_id 和 验证码值提交时的键 captcha_value具体参见配置文件
//noAuth.Use(authorization.CheckCaptchaAuth()).POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin")) //noAuth.Use(authorization.CheckCaptchaAuth()).POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin"))
// 获取公钥,用于密码等敏感信息的加密
noAuth.POST("publickey",validatorFactory.Create(consts.ValidatorPrefix+"PublicKey"))
} }
// 刷新token // 刷新token
@ -107,18 +179,17 @@ func InitWebRouter() *gin.Engine {
users := backend.Group("users/") users := backend.Group("users/")
{ {
// 查询 这里的验证器直接从容器获取是因为程序启动时将验证器注册在了容器具体代码位置App\Http\Validator\Web\Users\xxx // 查询 这里的验证器直接从容器获取是因为程序启动时将验证器注册在了容器具体代码位置App\Http\Validator\Web\Users\xxx
users.GET("index", validatorFactory.Create(consts.ValidatorPrefix+"UsersShow")) // 展示用户详细信息-用户名、ID
users.GET("info", validatorFactory.Create(consts.ValidatorPrefix+"UsersInfo"))
// 新增 // 新增
users.POST("create", validatorFactory.Create(consts.ValidatorPrefix+"UsersStore")) // users.POST("create", validatorFactory.Create(consts.ValidatorPrefix+"UsersStore")) // not need
// 更新 // 更新用户名或密码
users.POST("edit", validatorFactory.Create(consts.ValidatorPrefix+"UsersUpdate")) users.POST("username", validatorFactory.Create(consts.ValidatorPrefix+"UsersNameUpdate"))
// 删除 users.POST("password",validatorFactory.Create(consts.ValidatorPrefix+"UsersPasswordUpdate"))
// 注销用户
users.POST("delete", validatorFactory.Create(consts.ValidatorPrefix+"UsersDestroy")) users.POST("delete", validatorFactory.Create(consts.ValidatorPrefix+"UsersDestroy"))
} // 退出登录
//文件上传公共路由 users.POST("logout", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogout"))
uploadFiles := backend.Group("upload/")
{
uploadFiles.POST("files", validatorFactory.Create(consts.ValidatorPrefix+"UploadFiles"))
} }
} }
} }

Loading…
Cancel
Save