From 00ab1f459e39db98c557429bb87a705665d31db1 Mon Sep 17 00:00:00 2001 From: joefalmko Date: Sat, 26 Oct 2024 21:44:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E5=87=8F=E5=8E=9F=E7=89=88=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=B3=A8=E5=86=8C=E7=AD=89=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=99=BB=E5=87=BA=E3=80=81=E5=AF=86=E7=A0=81?= =?UTF-8?q?RSA=E5=8A=A0=E5=AF=86=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GinSkeleton/app/global/consts/consts.go | 7 +- GinSkeleton/app/global/variable/variable.go | 2 +- .../http/controller/web/users_controller.go | 254 ++++++++++++---- .../app/http/middleware/authorization/auth.go | 2 +- .../web_register_validator.go | 18 +- .../app/http/validator/web/users/data_type.go | 4 +- .../app/http/validator/web/users/destroy.go | 2 + .../validator/web/users/{show.go => info.go} | 14 +- .../app/http/validator/web/users/logout.go | 49 ++++ .../app/http/validator/web/users/publickey.go | 55 ++++ .../app/http/validator/web/users/register.go | 3 - .../app/http/validator/web/users/store.go | 62 ++-- .../app/http/validator/web/users/update.go | 72 ++++- GinSkeleton/app/model/users.go | 271 ++++++++++++++---- .../app/service/users/curd/users_curd.go | 18 +- GinSkeleton/app/service/users/token/token.go | 14 +- GinSkeleton/app/utils/rsa/rsa.go | 86 ++++++ GinSkeleton/cmd/web/main.go | 8 +- GinSkeleton/config/config.yml | 2 + GinSkeleton/config/gorm_v2.yml | 12 +- GinSkeleton/database/db_demo_mysql.sql | 32 ++- GinSkeleton/main.go | 15 + GinSkeleton/routers/web.go | 191 ++++++++---- 23 files changed, 926 insertions(+), 267 deletions(-) rename GinSkeleton/app/http/validator/web/users/{show.go => info.go} (76%) create mode 100644 GinSkeleton/app/http/validator/web/users/logout.go create mode 100644 GinSkeleton/app/http/validator/web/users/publickey.go create mode 100644 GinSkeleton/app/utils/rsa/rsa.go create mode 100644 GinSkeleton/main.go diff --git a/GinSkeleton/app/global/consts/consts.go b/GinSkeleton/app/global/consts/consts.go index 6193a15..e49ccac 100644 --- a/GinSkeleton/app/global/consts/consts.go +++ b/GinSkeleton/app/global/consts/consts.go @@ -38,6 +38,8 @@ const ( CurdCreatFailMsg string = "新增失败" CurdUpdateFailCode int = -400201 CurdUpdateFailMsg string = "更新失败" + CurdUpdatePassFailCode int = -400207 + CurdUpdatePassFailMsg string = "更新密码失败" CurdDeleteFailCode int = -400202 CurdDeleteFailMsg string = "删除失败" CurdSelectFailCode int = -400203 @@ -48,7 +50,10 @@ const ( CurdLoginFailMsg string = "登录失败" CurdRefreshTokenFailCode int = -400206 CurdRefreshTokenFailMsg string = "刷新Token失败" - + CurdLogoutFailCode int = -400208 + CurdLogoutFailMsg string = "登出失败" + CurdPublicKeyFailCode int = -400209 + CurdPublicKeyFailMsg string = "密钥获取失败" //文件上传 FilesUploadFailCode int = -400250 FilesUploadFailMsg string = "文件上传失败, 获取上传文件发生错误!" diff --git a/GinSkeleton/app/global/variable/variable.go b/GinSkeleton/app/global/variable/variable.go index 5a38828..149037c 100644 --- a/GinSkeleton/app/global/variable/variable.go +++ b/GinSkeleton/app/global/variable/variable.go @@ -44,7 +44,7 @@ var ( Enforcer *casbin.SyncedEnforcer // 用户自行定义其他全局变量 ↓ - + ) func init() { diff --git a/GinSkeleton/app/http/controller/web/users_controller.go b/GinSkeleton/app/http/controller/web/users_controller.go index 96b15e2..aa8428b 100644 --- a/GinSkeleton/app/http/controller/web/users_controller.go +++ b/GinSkeleton/app/http/controller/web/users_controller.go @@ -1,14 +1,16 @@ package web import ( - "github.com/gin-gonic/gin" "goskeleton/app/global/consts" "goskeleton/app/global/variable" "goskeleton/app/model" "goskeleton/app/service/users/curd" userstoken "goskeleton/app/service/users/token" "goskeleton/app/utils/response" + rsa "goskeleton/app/utils/rsa" "time" + + "github.com/gin-gonic/gin" ) type Users struct { @@ -21,8 +23,9 @@ func (u *Users) Register(context *gin.Context) { // 当然也可以通过gin框架的上下文原始方法获取,例如: context.PostForm("user_name") 获取,这样获取的数据格式为文本,需要自己继续转换 userName := context.GetString(consts.ValidatorPrefix + "user_name") pass := context.GetString(consts.ValidatorPrefix + "pass") - userIp := context.ClientIP() - if curd.CreateUserCurdFactory().Register(userName, pass, userIp) { + // userIp := context.ClientIP() + // if curd.CreateUserCurdFactory().Register(userName, pass, userIp) { + if curd.CreateUserCurdFactory().Register(userName, pass) { response.Success(context, consts.CurdStatusOkMsg, "") } else { response.Fail(context, consts.CurdRegisterFailCode, consts.CurdRegisterFailMsg, "") @@ -30,38 +33,41 @@ func (u *Users) Register(context *gin.Context) { } // 2.用户登录 -func (u *Users) Login(context *gin.Context) { - userName := context.GetString(consts.ValidatorPrefix + "user_name") - pass := context.GetString(consts.ValidatorPrefix + "pass") - phone := context.GetString(consts.ValidatorPrefix + "phone") +func (u *Users) Login(c *gin.Context) { + userName := c.GetString(consts.ValidatorPrefix + "user_name") + pass := c.GetString(consts.ValidatorPrefix + "pass") + // 密码解密 + pass = string(u.DecryptPassword(userName,pass)) + + // phone := context.GetString(consts.ValidatorPrefix + "phone") userModelFact := model.CreateUserFactory("") userModel := userModelFact.Login(userName, pass) if userModel != nil { userTokenFactory := userstoken.CreateUserFactory() - if userToken, err := userTokenFactory.GenerateToken(userModel.Id, userModel.UserName, userModel.Phone, variable.ConfigYml.GetInt64("Token.JwtTokenCreatedExpireAt")); err == nil { - if userTokenFactory.RecordLoginToken(userToken, context.ClientIP()) { + if userToken, err := userTokenFactory.GenerateToken( userModel.Id,userName, variable.ConfigYml.GetInt64("Token.JwtTokenCreatedExpireAt")); err == nil { + if userTokenFactory.RecordLoginToken(userModel.Id,userToken) { // 记录用户登录记录,不必要,但会将token存入Redis,懒得改了 data := gin.H{ - "userId": userModel.Id, - "user_name": userName, - "realName": userModel.RealName, - "phone": phone, + // "userId": userModel.Id, + // "user_name": userName, + // "realName": userModel.RealName, + // "phone": phone, "token": userToken, "updated_at": time.Now().Format(variable.DateFormat), } - response.Success(context, consts.CurdStatusOkMsg, data) - go userModel.UpdateUserloginInfo(context.ClientIP(), userModel.Id) + response.Success(c, consts.CurdStatusOkMsg, data) + // go userModel.UpdateUserloginInfo( userModel.Id) return } } } - response.Fail(context, consts.CurdLoginFailCode, consts.CurdLoginFailMsg, "") + response.Fail(c, consts.CurdLoginFailCode, consts.CurdLoginFailMsg, "") } // 刷新用户token func (u *Users) RefreshToken(context *gin.Context) { 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{ "token": newToken, } @@ -77,58 +83,89 @@ func (u *Users) RefreshToken(context *gin.Context) { // 您也可以参考 Admin 项目地址:https://gitee.com/daitougege/gin-skeleton-admin-backend/ 中, app/model/ 提供的示例语法 //3.用户查询(show) -func (u *Users) Show(context *gin.Context) { - userName := context.GetString(consts.ValidatorPrefix + "user_name") - page := context.GetFloat64(consts.ValidatorPrefix + "page") - limit := context.GetFloat64(consts.ValidatorPrefix + "limit") - limitStart := (page - 1) * limit - counts, showlist := model.CreateUserFactory("").Show(userName, int(limitStart), int(limit)) - if counts > 0 && showlist != nil { - response.Success(context, consts.CurdStatusOkMsg, gin.H{"counts": counts, "list": showlist}) +func (u *Users) Info(c *gin.Context) { + userName := c.GetString(consts.ValidatorPrefix + "user_name") + // page := context.GetFloat64(consts.ValidatorPrefix + "page") + // limit := context.GetFloat64(consts.ValidatorPrefix + "limit") + // limitStart := (page - 1) * limit + res := model.CreateUserFactory("").Info(userName) + if res.UserName != ""{ + response.Success(c, consts.CurdStatusOkMsg, gin.H{"id":res.Id,"user_name":res.UserName}) } else { - response.Fail(context, consts.CurdSelectFailCode, consts.CurdSelectFailMsg, "") + response.Fail(c, consts.CurdSelectFailCode, consts.CurdSelectFailMsg, "") } } //4.用户新增(store) -func (u *Users) Store(context *gin.Context) { - userName := context.GetString(consts.ValidatorPrefix + "user_name") - pass := context.GetString(consts.ValidatorPrefix + "pass") - realName := context.GetString(consts.ValidatorPrefix + "real_name") - phone := context.GetString(consts.ValidatorPrefix + "phone") - remark := context.GetString(consts.ValidatorPrefix + "remark") +// func (u *Users) Store(context *gin.Context) { +// userName := context.GetString(consts.ValidatorPrefix + "user_name") +// pass := context.GetString(consts.ValidatorPrefix + "pass") +// realName := context.GetString(consts.ValidatorPrefix + "real_name") +// phone := context.GetString(consts.ValidatorPrefix + "phone") +// remark := context.GetString(consts.ValidatorPrefix + "remark") - if curd.CreateUserCurdFactory().Store(userName, pass, realName, phone, remark) { - response.Success(context, consts.CurdStatusOkMsg, "") - } else { - response.Fail(context, consts.CurdCreatFailCode, consts.CurdCreatFailMsg, "") - } -} +// if curd.CreateUserCurdFactory().Store(userName, pass, realName, phone, remark) { +// response.Success(context, consts.CurdStatusOkMsg, "") +// } else { +// response.Fail(context, consts.CurdCreatFailCode, consts.CurdCreatFailMsg, "") +// } +// } //5.用户更新(update) -func (u *Users) Update(context *gin.Context) { +func (u *Users) NameUpdate(c *gin.Context) { //表单参数验证中的int、int16、int32 、int64、float32、float64等数字键(字段),请统一使用 GetFloat64() 获取,其他函数无效 - userId := context.GetFloat64(consts.ValidatorPrefix + "id") - userName := context.GetString(consts.ValidatorPrefix + "user_name") - pass := context.GetString(consts.ValidatorPrefix + "pass") - realName := context.GetString(consts.ValidatorPrefix + "real_name") - phone := context.GetString(consts.ValidatorPrefix + "phone") - remark := context.GetString(consts.ValidatorPrefix + "remark") - userIp := context.ClientIP() + userId := c.GetFloat64(consts.ValidatorPrefix + "id") + userName := c.GetString(consts.ValidatorPrefix + "user_name") + // pass := context.GetString(consts.ValidatorPrefix + "pass") + // realName := context.GetString(consts.ValidatorPrefix + "real_name") + // phone := context.GetString(consts.ValidatorPrefix + "phone") + // remark := context.GetString(consts.ValidatorPrefix + "remark") + // userIp := context.ClientIP() // 检查正在修改的用户名是否被其他人使用 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 } //注意:这里没有实现更加精细的权限控制逻辑,例如:超级管理管理员可以更新全部用户数据,普通用户只能修改自己的数据。目前只是验证了token有效、合法之后就可以进行后续操作 // 实际使用请根据真是业务实现权限控制逻辑、再进行数据库操作 - if curd.CreateUserCurdFactory().Update(int(userId), userName, pass, realName, phone, remark, userIp) { - response.Success(context, consts.CurdStatusOkMsg, "") + // if len(pass)>0{ + // 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 { - 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, "") + } @@ -136,9 +173,124 @@ func (u *Users) Update(context *gin.Context) { func (u *Users) Destroy(context *gin.Context) { //表单参数验证中的int、int16、int32 、int64、float32、float64等数字键(字段),请统一使用 GetFloat64() 获取,其他函数无效 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, "") } else { 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 +} \ No newline at end of file diff --git a/GinSkeleton/app/http/middleware/authorization/auth.go b/GinSkeleton/app/http/middleware/authorization/auth.go index c8a46a0..48cc64d 100644 --- a/GinSkeleton/app/http/middleware/authorization/auth.go +++ b/GinSkeleton/app/http/middleware/authorization/auth.go @@ -73,7 +73,7 @@ func CheckTokenAuthWithRefresh() gin.HandlerFunc { // 判断token是否满足刷新条件 if userstoken.CreateUserFactory().TokenIsMeetRefreshCondition(token[1]) { // 刷新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 { key := variable.ConfigYml.GetString("Token.BindContextKeyName") // token刷新成功,同时绑定在请求上下文 diff --git a/GinSkeleton/app/http/validator/common/register_validator/web_register_validator.go b/GinSkeleton/app/http/validator/common/register_validator/web_register_validator.go index 278c34a..d08d28f 100644 --- a/GinSkeleton/app/http/validator/common/register_validator/web_register_validator.go +++ b/GinSkeleton/app/http/validator/common/register_validator/web_register_validator.go @@ -24,14 +24,20 @@ func WebRegisterValidator() { containers.Set(key, users.RefreshToken{}) // Users基本操作(CURD) - key = consts.ValidatorPrefix + "UsersShow" - containers.Set(key, users.Show{}) - key = consts.ValidatorPrefix + "UsersStore" - containers.Set(key, users.Store{}) - key = consts.ValidatorPrefix + "UsersUpdate" - containers.Set(key, users.Update{}) + key = consts.ValidatorPrefix + "UsersInfo" + containers.Set(key, users.Info{}) + // key = consts.ValidatorPrefix + "UsersStore" + // containers.Set(key, users.Store{}) + key = consts.ValidatorPrefix + "UsersNameUpdate" + containers.Set(key, users.NameUpdate{}) + key = consts.ValidatorPrefix + "UsersPasswordUpdate" + containers.Set(key, users.PasswordUpdate{}) key = consts.ValidatorPrefix + "UsersDestroy" 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" diff --git a/GinSkeleton/app/http/validator/web/users/data_type.go b/GinSkeleton/app/http/validator/web/users/data_type.go index 9c4fd48..f7f20eb 100644 --- a/GinSkeleton/app/http/validator/web/users/data_type.go +++ b/GinSkeleton/app/http/validator/web/users/data_type.go @@ -1,8 +1,8 @@ package users type BaseField struct { - UserName string `form:"user_name" json:"user_name" binding:"required,min=1"` // 必填、对于文本,表示它的长度>=1 - Pass string `form:"pass" json:"pass" binding:"required,min=6,max=20"` // 密码为 必填,长度>=6 + UserName string `form:"user_name" json:"user_name" binding:"required,min=3"` // 必填、对于文本,表示它的长度>=1 + Pass string `form:"pass" json:"pass" binding:"required,min=6"` // 密码为 必填,长度>=6 } type Id struct { diff --git a/GinSkeleton/app/http/validator/web/users/destroy.go b/GinSkeleton/app/http/validator/web/users/destroy.go index d712088..ca63fb9 100644 --- a/GinSkeleton/app/http/validator/web/users/destroy.go +++ b/GinSkeleton/app/http/validator/web/users/destroy.go @@ -11,6 +11,8 @@ import ( type Destroy struct { // 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合 Id + BaseField + // Pass string `form:"pass" json:"pass" binding:"required,min=6"` } // 验证器语法,参见 Register.go文件,有详细说明 diff --git a/GinSkeleton/app/http/validator/web/users/show.go b/GinSkeleton/app/http/validator/web/users/info.go similarity index 76% rename from GinSkeleton/app/http/validator/web/users/show.go rename to GinSkeleton/app/http/validator/web/users/info.go index 7552271..c4fd05b 100644 --- a/GinSkeleton/app/http/validator/web/users/show.go +++ b/GinSkeleton/app/http/validator/web/users/info.go @@ -4,19 +4,19 @@ import ( "github.com/gin-gonic/gin" "goskeleton/app/global/consts" "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/utils/response" ) -type Show struct { +type Info struct { // 表单参数验证结构体支持匿名结构体嵌套 - UserName string `form:"user_name" json:"user_name" binding:"required,min=1"` // 必填、对于文本,表示它的长度>=1 - common_data_type.Page + UserName string `form:"user_name" json:"user_name" binding:"required,min=3"` // 必填、对于文本,表示它的长度>=1 + // common_data_type.Page } // 验证器语法,参见 Register.go文件,有详细说明 -func (s Show) CheckParams(context *gin.Context) { +func (s Info) CheckParams(context *gin.Context) { //1.基本的验证规则没有通过 if err := context.ShouldBind(&s); err != nil { // 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可 @@ -27,9 +27,9 @@ func (s Show) CheckParams(context *gin.Context) { // 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值 extraAddBindDataContext := data_transfer.DataAddContext(s, consts.ValidatorPrefix, context) if extraAddBindDataContext == nil { - response.ErrorSystem(context, "UserShow表单验证器json化失败", "") + response.ErrorSystem(context, "UserInfo表单验证器json化失败", "") } else { // 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性 - (&web.Users{}).Show(extraAddBindDataContext) + (&web.Users{}).Info(extraAddBindDataContext) } } diff --git a/GinSkeleton/app/http/validator/web/users/logout.go b/GinSkeleton/app/http/validator/web/users/logout.go new file mode 100644 index 0000000..ced8cc9 --- /dev/null +++ b/GinSkeleton/app/http/validator/web/users/logout.go @@ -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) + */ + } +} diff --git a/GinSkeleton/app/http/validator/web/users/publickey.go b/GinSkeleton/app/http/validator/web/users/publickey.go new file mode 100644 index 0000000..9c4ef7b --- /dev/null +++ b/GinSkeleton/app/http/validator/web/users/publickey.go @@ -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) + } + +} diff --git a/GinSkeleton/app/http/validator/web/users/register.go b/GinSkeleton/app/http/validator/web/users/register.go index 6523150..d1fabbf 100644 --- a/GinSkeleton/app/http/validator/web/users/register.go +++ b/GinSkeleton/app/http/validator/web/users/register.go @@ -23,9 +23,6 @@ import ( type Register struct { BaseField - // 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合 - Phone string `form:"phone" json:"phone"` // 手机号, 非必填 - CardNo string `form:"card_no" json:"card_no"` //身份证号码,非必填 } // 特别注意: 表单参数验证器结构体的函数,绝对不能绑定在指针上 diff --git a/GinSkeleton/app/http/validator/web/users/store.go b/GinSkeleton/app/http/validator/web/users/store.go index 070e9e1..5f79f5e 100644 --- a/GinSkeleton/app/http/validator/web/users/store.go +++ b/GinSkeleton/app/http/validator/web/users/store.go @@ -1,37 +1,37 @@ 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" -) +// 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 Store struct { - BaseField - // 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合 - 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" ` -} +// type Store struct { +// BaseField +// // 表单参数验证结构体支持匿名结构体嵌套、以及匿名结构体与普通字段组合 +// 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文件,有详细说明 +// // 验证器语法,参见 Register.go文件,有详细说明 -func (s Store) CheckParams(context *gin.Context) { - //1.基本的验证规则没有通过 - if err := context.ShouldBind(&s); err != nil { - // 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可 - response.ValidatorError(context, err) - return - } +// func (s Store) CheckParams(context *gin.Context) { +// //1.基本的验证规则没有通过 +// if err := context.ShouldBind(&s); err != nil { +// // 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可 +// response.ValidatorError(context, err) +// return +// } - // 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值 - extraAddBindDataContext := data_transfer.DataAddContext(s, consts.ValidatorPrefix, context) - if extraAddBindDataContext == nil { - response.ErrorSystem(context, "UserStore表单验证器json化失败", "") - } else { - // 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性 - (&web.Users{}).Store(extraAddBindDataContext) - } -} +// // 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值 +// extraAddBindDataContext := data_transfer.DataAddContext(s, consts.ValidatorPrefix, context) +// if extraAddBindDataContext == nil { +// response.ErrorSystem(context, "UserStore表单验证器json化失败", "") +// } else { +// // 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性 +// (&web.Users{}).Store(extraAddBindDataContext) +// } +// } diff --git a/GinSkeleton/app/http/validator/web/users/update.go b/GinSkeleton/app/http/validator/web/users/update.go index ac92a0d..0779d4e 100644 --- a/GinSkeleton/app/http/validator/web/users/update.go +++ b/GinSkeleton/app/http/validator/web/users/update.go @@ -8,31 +8,77 @@ import ( "goskeleton/app/utils/response" ) -type Update struct { - BaseField - 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"` -} +// type Update struct { +// // 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文件,有详细说明 -// 验证器语法,参见 Register.go文件,有详细说明 +// func (u Update) CheckParams(context *gin.Context) { +// //1.基本的验证规则没有通过 +// if err := context.ShouldBind(&u); err != nil { +// // 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可 +// response.ValidatorError(context, err) +// return +// } -func (u Update) CheckParams(context *gin.Context) { +// // 该函数主要是将本结构体的字段(成员)按照 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 +} +type PasswordUpdate struct{ + OldPass string `form:"oldpass" json:"oldpass" binding:"required,min=6"` // 密码为 长度>=6 + NewPass string `form:"newpass" json:"newpass" binding:"required,min=6"` + NameUpdate +} +func (n NameUpdate) CheckParams(context *gin.Context) { //1.基本的验证规则没有通过 - if err := context.ShouldBind(&u); err != nil { + if err := context.ShouldBind(&n); err != nil { // 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可 response.ValidatorError(context, err) return } // 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值 - extraAddBindDataContext := data_transfer.DataAddContext(u, consts.ValidatorPrefix, context) + extraAddBindDataContext := data_transfer.DataAddContext(n, consts.ValidatorPrefix, context) if extraAddBindDataContext == nil { response.ErrorSystem(context, "UserUpdate表单验证器json化失败", "") } else { // 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性 - (&web.Users{}).Update(extraAddBindDataContext) + (&web.Users{}).NameUpdate(extraAddBindDataContext) } } +func (p PasswordUpdate) CheckParams(context *gin.Context) { + //1.基本的验证规则没有通过 + if err := context.ShouldBind(&p); err != nil { + // 将表单参数验证器出现的错误直接交给错误翻译器统一处理即可 + response.ValidatorError(context, err) + return + } + + // 该函数主要是将本结构体的字段(成员)按照 consts.ValidatorPrefix+ json标签对应的 键 => 值 形式绑定在上下文,便于下一步(控制器)可以直接通过 context.Get(键) 获取相关值 + extraAddBindDataContext := data_transfer.DataAddContext(p, consts.ValidatorPrefix, context) + if extraAddBindDataContext == nil { + response.ErrorSystem(context, "UserUpdate表单验证器json化失败", "") + } else { + // 验证完成,调用控制器,并将验证器成员(字段)递给控制器,保持上下文数据一致性 + (&web.Users{}).PasswordUpdate(extraAddBindDataContext) + } +} \ No newline at end of file diff --git a/GinSkeleton/app/model/users.go b/GinSkeleton/app/model/users.go index c4962fe..966de93 100644 --- a/GinSkeleton/app/model/users.go +++ b/GinSkeleton/app/model/users.go @@ -1,11 +1,18 @@ package model import ( - "go.uber.org/zap" + + "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 系统相关代码 @@ -23,11 +30,11 @@ 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"` + // 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"` + // 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 { - sql := "INSERT INTO tb_users(user_name,pass,last_login_ip) SELECT ?,?,? FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM tb_users WHERE user_name=?)" - result := u.Exec(sql, userName, pass, userIp, userName) +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 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) if result.Error == nil { // 账号密码验证成功 @@ -62,14 +71,14 @@ func (u *UsersModel) Login(userName string, pass string) *UsersModel { } //记录用户登陆(login)生成的token,每次登陆记录一次token -func (u *UsersModel) OauthLoginToken(userId int64, token string, expiresAt int64, clientIp string) bool { +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,client_ip) SELECT ?,'login',? ,?,? FROM DUAL WHERE NOT EXISTS(SELECT 1 FROM tb_oauth_access_tokens a WHERE a.fr_user_id=? AND a.action_name='login' AND a.token=? ) ` //注意:token的精确度为秒,如果在一秒之内,一个账号多次调用接口生成的token其实是相同的,这样写入数据库,第二次的影响行数为0,知己实际上操作仍然是有效的。 //所以这里只判断无错误即可,判断影响行数的话,>=0 都是ok的 - if u.Exec(sql, userId, token, time.Unix(expiresAt, 0).Format(variable.DateFormat), clientIp, userId, token).Error == nil { + if 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) @@ -91,27 +100,27 @@ func (u *UsersModel) OauthRefreshConditionCheck(userId int64, oldToken string) b } //用户刷新token -func (u *UsersModel) OauthRefreshToken(userId, expiresAt int64, oldToken, newToken, clientIp 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=?" - if u.Exec(sql, newToken, time.Unix(expiresAt, 0).Format(variable.DateFormat), clientIp, userId, oldToken).Error == nil { +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) + // go u.UpdateUserloginInfo(clientIp, userId) return true } return false } // 更新用户登陆次数、最近一次登录ip、最近一次登录时间 -func (u *UsersModel) UpdateUserloginInfo(last_login_ip string, userId int64) { - sql := "UPDATE tb_users SET login_times=IFNULL(login_times,0)+1,last_login_ip=?,last_login_time=? WHERE id=? " - _ = u.Exec(sql, last_login_ip, time.Now().Format(variable.DateFormat), userId) -} +// 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, clientIp string) bool { +func (u *UsersModel) OauthResetToken(userId int, newPass string) bool { //如果用户新旧密码一致,直接返回true,不需要处理 userItem, err := u.ShowOneItem(userId) 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)) } - sql := "UPDATE tb_oauth_access_tokens SET revoked=1,updated_at=NOW(),action_name='ResetPass',client_ip=? WHERE fr_user_id=? " - if u.Exec(sql, clientIp, userId).Error == nil { + 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 } } @@ -167,19 +176,19 @@ func (u *UsersModel) OauthCheckTokenIsOk(userId int64, token string) bool { // 禁用一个用户的: 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 -} +// func (u *UsersModel) SetTokenInvalid(userId int) bool { +// sql := "delete from `tb_oauth_access_tokens` where `fr_user_id`=? " +// if u.Exec(sql, userId).Error == nil { +// if u.Exec("update tb_users set status=0 where id=?", userId).Error == nil { +// return true +// } +// } +// return false +// } //根据用户ID查询一条信息 func (u *UsersModel) ShowOneItem(userId int) (*UsersModel, error) { - sql := "SELECT `id`, `user_name`,`pass`, `real_name`, `phone`, `status` FROM `tb_users` WHERE `status`=1 and id=? LIMIT 1" + 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 @@ -189,32 +198,32 @@ func (u *UsersModel) ShowOneItem(userId int) (*UsersModel, error) { } // 查询数据之前统计条数 -func (u *UsersModel) counts(userName string) (counts int64) { - sql := "SELECT count(*) as counts FROM tb_users WHERE status=1 and user_name like ?" - if res := u.Raw(sql, "%"+userName+"%").First(&counts); res.Error != nil { - variable.ZapLog.Error("UsersModel - counts 查询数据条数出错", zap.Error(res.Error)) - } - return counts -} +// func (u *UsersModel) counts(userName string) (counts int64) { +// sql := "SELECT count(*) as counts FROM tb_users WHERE status=1 and user_name like ?" +// if res := u.Raw(sql, "%"+userName+"%").First(&counts); res.Error != nil { +// variable.ZapLog.Error("UsersModel - counts 查询数据条数出错", zap.Error(res.Error)) +// } +// return counts +// } // 查询(根据关键词模糊查询) -func (u *UsersModel) Show(userName string, limitStart, limitItems int) (counts int64, temp []UsersModel) { - if counts = u.counts(userName); counts > 0 { - sql := "SELECT `id`, `user_name`, `real_name`, `phone`,last_login_ip, `status`,created_at,updated_at FROM `tb_users` WHERE `status`=1 and user_name like ? LIMIT ?,?" - if res := u.Raw(sql, "%"+userName+"%", limitStart, limitItems).Find(&temp); res.RowsAffected > 0 { - return counts, temp - } +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 0, nil + 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=?)" - if u.Exec(sql, userName, pass, realName, phone, remark, userName).RowsAffected > 0 { - return true - } - return false + return u.Exec(sql, userName, pass, realName, phone, remark, userName).RowsAffected > 0 + // return false } //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 { - sql := "update tb_users set user_name=?,pass=?,real_name=?,phone=?,remark=? WHERE status=1 AND id=?" - if u.Exec(sql, userName, pass, realName, phone, remark, id).RowsAffected >= 0 { - if u.OauthResetToken(id, pass, clientIp) { - return true +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 false + return nil } //删除用户以及关联的token记录 -func (u *UsersModel) Destroy(id int) bool { +func (u *UsersModel) Destroy(id int,userName,pass string) bool { // 删除用户时,清除用户缓存在redis的全部token if variable.ConfigYml.GetInt("Token.IsCacheToRedis") == 1 { go u.DelTokenCacheFromRedis(int64(id)) } - if u.Delete(u, id).Error == nil { - if u.OauthDestroyToken(id) { - return true + + // 检查密码是否正确 + 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 { + // 删除token + if u.OauthDestroyToken(id) { + return true + } + } } } + 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 逻辑 func (u *UsersModel) ValidTokenCacheToRedis(userId int64) { diff --git a/GinSkeleton/app/service/users/curd/users_curd.go b/GinSkeleton/app/service/users/curd/users_curd.go index 26b096e..3afd9ce 100644 --- a/GinSkeleton/app/service/users/curd/users_curd.go +++ b/GinSkeleton/app/service/users/curd/users_curd.go @@ -13,9 +13,10 @@ type UsersCurd struct { 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) // 预先处理密码加密,然后存储在数据库 - 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 { @@ -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) } -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) // 预先处理密码加密,然后存储在数据库 - return u.userModel.Update(id, name, pass, realName, phone, remark, clientIp) + // pass = md5_encrypt.Base64Md5(pass) // 预先处理密码加密,然后存储在数据库 + 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 +} \ No newline at end of file diff --git a/GinSkeleton/app/service/users/token/token.go b/GinSkeleton/app/service/users/token/token.go index d6638ab..9b0dea3 100644 --- a/GinSkeleton/app/service/users/token/token.go +++ b/GinSkeleton/app/service/users/token/token.go @@ -24,13 +24,13 @@ type userToken struct { } // 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 customClaims := my_jwt.CustomClaims{ UserId: userid, Name: username, - Phone: phone, + // Phone: phone, // 特别注意,针对前文的匿名结构体,初始化的时候必须指定键名,并且不带 jwt. 否则报错:Mixture of field: value and value initializers StandardClaims: jwt.StandardClaims{ NotBefore: time.Now().Unix() - 10, // 生效开始时间 @@ -41,11 +41,11 @@ func (u *userToken) GenerateToken(userid int64, username string, phone string, e } // 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 { - userId := customClaims.UserId + // userId := customClaims.UserId expiresAt := customClaims.ExpiresAt - return model.CreateUserFactory("").OauthLoginToken(userId, userToken, expiresAt, clientIp) + return model.CreateUserFactory("").OauthLoginToken(id, userToken, expiresAt) } else { return false } @@ -66,14 +66,14 @@ func (u *userToken) TokenIsMeetRefreshCondition(token string) bool { } // 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 //如果token是有效的、或者在过期时间内,那么执行更新,换取新token if newToken, err = u.userJwt.RefreshToken(oldToken, variable.ConfigYml.GetInt64("Token.JwtTokenRefreshExpireAt")); err == nil { if customClaims, err := u.userJwt.ParseToken(newToken); err == nil { userId := customClaims.UserId expiresAt := customClaims.ExpiresAt - if model.CreateUserFactory("").OauthRefreshToken(userId, expiresAt, oldToken, newToken, clientIp) { + if model.CreateUserFactory("").OauthRefreshToken(userId, expiresAt, oldToken, newToken) { return newToken, true } } diff --git a/GinSkeleton/app/utils/rsa/rsa.go b/GinSkeleton/app/utils/rsa/rsa.go new file mode 100644 index 0000000..7461d85 --- /dev/null +++ b/GinSkeleton/app/utils/rsa/rsa.go @@ -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) +} \ No newline at end of file diff --git a/GinSkeleton/cmd/web/main.go b/GinSkeleton/cmd/web/main.go index d778669..bea21ba 100644 --- a/GinSkeleton/cmd/web/main.go +++ b/GinSkeleton/cmd/web/main.go @@ -1,13 +1,15 @@ package main import ( - "goskeleton/app/global/variable" + // "goskeleton/app/global/variable" _ "goskeleton/bootstrap" "goskeleton/routers" ) // 这里可以存放后端路由(例如后台管理系统) func main() { - router := routers.InitWebRouter() - _ = router.Run(variable.ConfigYml.GetString("HttpServer.Web.Port")) + // router := routers.InitWebRouter() + // _ = router.Run(variable.ConfigYml.GetString("HttpServer.Web.Port")) + r:=routers.InitWebRouter_Co() + r.Run("localhost:14514") } diff --git a/GinSkeleton/config/config.yml b/GinSkeleton/config/config.yml index 3234b53..4238184 100644 --- a/GinSkeleton/config/config.yml +++ b/GinSkeleton/config/config.yml @@ -11,6 +11,8 @@ HttpServer: ProxyServerList: - "192.168.10.1" # nginx 代理服务器ip地址 - "192.168.10.2" +RSA: + KeySize: 2048 Token: JwtTokenSignKey: "goskeleton" #设置token生成时加密的签名 diff --git a/GinSkeleton/config/gorm_v2.yml b/GinSkeleton/config/gorm_v2.yml index 9ed4228..65f3436 100644 --- a/GinSkeleton/config/gorm_v2.yml +++ b/GinSkeleton/config/gorm_v2.yml @@ -1,16 +1,16 @@ Gormv2: # 只针对 gorm 操作数据库有效 UseDbType: "mysql" # 备选项 mysql 、sqlserver、 postgresql - SqlDebug: false # 请根据个人习惯设置,true 表示执行的sql全部会输出在终端(一般来说开发环境可能会方便调试) , false 表示默认不会在终端输出sql(生产环境建议设置为 false), + SqlDebug: true # 请根据个人习惯设置,true 表示执行的sql全部会输出在终端(一般来说开发环境可能会方便调试) , false 表示默认不会在终端输出sql(生产环境建议设置为 false), Mysql: - IsInitGlobalGormMysql: 0 # 随项目启动为gorm db初始化一个全局 variable.GormDbMysql(完全等于*gorm.Db),正确配置数据库,该值必须设置为: 1 + IsInitGlobalGormMysql: 1 # 随项目启动为gorm db初始化一个全局 variable.GormDbMysql(完全等于*gorm.Db),正确配置数据库,该值必须设置为: 1 SlowThreshold: 30 # 慢 SQL 阈值(sql执行时间超过此时间单位(秒),就会触发系统日志记录) Write: Host: "127.0.0.1" - DataBase: "db_goskeleton" + DataBase: "User" Port: 3306 Prefix: "tb_" # 目前没有用到该配置项 User: "root" - Pass: "DRsXT5ZJ6Oi55LPQ" + Pass: "Gm33894239" Charset: "utf8" SetMaxIdleConns: 10 SetMaxOpenConns: 128 @@ -20,11 +20,11 @@ Gormv2: # 只针对 gorm 操作数据库有效 IsOpenReadDb: 0 # 是否开启读写分离配置(1=开启、0=关闭),IsOpenReadDb=1,Read 部分参数有效,否则Read部分参数直接忽略 Read: Host: "127.0.0.1" - DataBase: "db_goskeleton" + DataBase: "User" Port: 3308 #注意,非3306,请自行调整 Prefix: "tb_" User: "root" - Pass: "yourPassword" + Pass: "Gm33894239" Charset: "utf8" SetMaxIdleConns: 10 SetMaxOpenConns: 128 diff --git a/GinSkeleton/database/db_demo_mysql.sql b/GinSkeleton/database/db_demo_mysql.sql index b10cdef..ec0e507 100644 --- a/GinSkeleton/database/db_demo_mysql.sql +++ b/GinSkeleton/database/db_demo_mysql.sql @@ -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` */ @@ -11,15 +13,15 @@ CREATE TABLE `tb_users` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `user_name` VARCHAR(30) DEFAULT '' COMMENT '账号', `pass` VARCHAR(128) DEFAULT '' COMMENT '密码', - `real_name` VARCHAR(30) DEFAULT '' COMMENT '姓名', - `phone` CHAR(11) DEFAULT '' COMMENT '手机', - `status` TINYINT(4) DEFAULT 1 COMMENT '状态', - `remark` VARCHAR(255) DEFAULT '' COMMENT '备注', - `last_login_time` DATETIME DEFAULT CURRENT_TIMESTAMP, - `last_login_ip` CHAR(30) DEFAULT '' COMMENT '最近一次登录ip', - `login_times` INT(11) DEFAULT 0 COMMENT '累计登录次数', - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, - `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP, + /* `real_name` VARCHAR(30) DEFAULT '' COMMENT '姓名', */ + /* `phone` CHAR(11) DEFAULT '' COMMENT '手机', */ + /* `status` TINYINT(4) DEFAULT 1 COMMENT '状态', */ + /* `remark` VARCHAR(255) DEFAULT '' COMMENT '备注', */ + /* `last_login_time` DATETIME DEFAULT CURRENT_TIMESTAMP, */ + /* `last_login_ip` CHAR(30) DEFAULT '' COMMENT '最近一次登录ip', */ + /* `login_times` INT(11) DEFAULT 0 COMMENT '累计登录次数', */ + /* `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, */ + /* `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP, */ PRIMARY KEY (`id`) ) 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`) ) 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; + diff --git a/GinSkeleton/main.go b/GinSkeleton/main.go new file mode 100644 index 0000000..bea21ba --- /dev/null +++ b/GinSkeleton/main.go @@ -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") +} diff --git a/GinSkeleton/routers/web.go b/GinSkeleton/routers/web.go index 22efb67..abeffdd 100644 --- a/GinSkeleton/routers/web.go +++ b/GinSkeleton/routers/web.go @@ -3,66 +3,138 @@ package routers import ( "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" - "go.uber.org/zap" + // "go.uber.org/zap" "goskeleton/app/global/consts" "goskeleton/app/global/variable" "goskeleton/app/http/controller/captcha" "goskeleton/app/http/middleware/authorization" "goskeleton/app/http/middleware/cors" validatorFactory "goskeleton/app/http/validator/core/factory" - "goskeleton/app/utils/gin_release" - "net/http" + // "goskeleton/app/utils/gin_release" + // "net/http" ) // 该路由主要设置 后台管理系统等后端应用路由 -func InitWebRouter() *gin.Engine { - var router *gin.Engine - // 非调试模式(生产模式) 日志写到日志文件 - if variable.ConfigYml.GetBool("AppDebug") == false { - - //1.gin自行记录接口访问日志,不需要nginx,如果开启以下3行,那么请屏蔽第 34 行代码 - //gin.DisableConsoleColor() - //f, _ := os.Create(variable.BasePath + variable.ConfigYml.GetString("Logs.GinLogName")) - //gin.DefaultWriter = io.MultiWriter(f) - - //【生产模式】 - // 根据 gin 官方的说明:[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - // 如果部署到生产环境,请使用以下模式: - // 1.生产模式(release) 和开发模式的变化主要是禁用 gin 记录接口访问日志, - // 2.go服务就必须使用nginx作为前置代理服务,这样也方便实现负载均衡 - // 3.如果程序发生 panic 等异常使用自定义的 panic 恢复中间件拦截、记录到日志 - router = gin_release.ReleaseRouter() - - } else { - // 调试模式,开启 pprof 包,便于开发阶段分析程序性能 - 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) - } +// func InitWebRouter() *gin.Engine { +// var router *gin.Engine +// // 非调试模式(生产模式) 日志写到日志文件 +// if variable.ConfigYml.GetBool("AppDebug") == false { + +// //1.gin自行记录接口访问日志,不需要nginx,如果开启以下3行,那么请屏蔽第 34 行代码 +// //gin.DisableConsoleColor() +// //f, _ := os.Create(variable.BasePath + variable.ConfigYml.GetString("Logs.GinLogName")) +// //gin.DefaultWriter = io.MultiWriter(f) + +// //【生产模式】 +// // 根据 gin 官方的说明:[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. +// // 如果部署到生产环境,请使用以下模式: +// // 1.生产模式(release) 和开发模式的变化主要是禁用 gin 记录接口访问日志, +// // 2.go服务就必须使用nginx作为前置代理服务,这样也方便实现负载均衡 +// // 3.如果程序发生 panic 等异常使用自定义的 panic 恢复中间件拦截、记录到日志 +// router = gin_release.ReleaseRouter() + +// } else { +// // 调试模式,开启 pprof 包,便于开发阶段分析程序性能 +// 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) //根据配置进行设置跨域 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") { @@ -74,8 +146,6 @@ func InitWebRouter() *gin.Engine { // 创建一个后端接口路由组 backend := router.Group("/admin/") { - // 创建一个websocket,如果ws需要账号密码登录才能使用,就写在需要鉴权的分组,这里暂定是开放式的,不需要严格鉴权,我们简单验证一下token值 - backend.GET("ws", validatorFactory.Create(consts.ValidatorPrefix+"WebsocketConnect")) // 【不需要token】中间件验证的路由 用户注册、登录 noAuth := backend.Group("users/") @@ -84,13 +154,15 @@ func InitWebRouter() *gin.Engine { // 1.编写一个表单参数验证器结构体,参见代码: app/http/validator/web/users/register.go // 2.将以上表单参数验证器注册,遵守 键 =》值 格式注册即可 ,app/http/validator/common/register_validator/web_register_validator.go 20行就是注册时候的键 consts.ValidatorPrefix+"UsersRegister" // 3.按照注册时的键,直接从容器调用即可 :validatorFactory.Create(consts.ValidatorPrefix+"UsersRegister") - noAuth.POST("register", validatorFactory.Create(consts.ValidatorPrefix+"UsersRegister")) + noAuth.POST("register", validatorFactory.Create(consts.ValidatorPrefix+"UsersRegister")) // ok // 不需要验证码即可登陆 - noAuth.POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin")) + noAuth.POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin")) // ok // 如果加载了验证码中间件,那么就需要提交验证码才可以登陆(本质上就是给登陆接口增加了2个参数:验证码id提交时的键:captcha_id 和 验证码值提交时的键 captcha_value,具体参见配置文件) //noAuth.Use(authorization.CheckCaptchaAuth()).POST("login", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogin")) - + + // 获取公钥,用于密码等敏感信息的加密 + noAuth.POST("publickey",validatorFactory.Create(consts.ValidatorPrefix+"PublicKey")) } // 刷新token @@ -107,20 +179,19 @@ func InitWebRouter() *gin.Engine { users := backend.Group("users/") { // 查询 ,这里的验证器直接从容器获取,是因为程序启动时,将验证器注册在了容器,具体代码位置: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("edit", validatorFactory.Create(consts.ValidatorPrefix+"UsersUpdate")) - // 删除 + // users.POST("create", validatorFactory.Create(consts.ValidatorPrefix+"UsersStore")) // not need + // 更新用户名或密码 + users.POST("username", validatorFactory.Create(consts.ValidatorPrefix+"UsersNameUpdate")) + users.POST("password",validatorFactory.Create(consts.ValidatorPrefix+"UsersPasswordUpdate")) + // 注销用户 users.POST("delete", validatorFactory.Create(consts.ValidatorPrefix+"UsersDestroy")) - } - //文件上传公共路由 - uploadFiles := backend.Group("upload/") - { - uploadFiles.POST("files", validatorFactory.Create(consts.ValidatorPrefix+"UploadFiles")) + // 退出登录 + users.POST("logout", validatorFactory.Create(consts.ValidatorPrefix+"UsersLogout")) } } } return router -} +} \ No newline at end of file