From ab51ba91bce48be30fa882550497f5c54c16216b Mon Sep 17 00:00:00 2001 From: Blizzard Date: Thu, 8 May 2025 23:03:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20client=20=E5=A2=9E=E5=88=A0=E6=94=B9?= =?UTF-8?q?=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/v1/system/auth.go | 76 +++++++ api/v1/system/enter.go | 10 +- api/v1/system/login.go | 15 -- api/v1/system/sys_client.go | 100 +++++++++ api/v1/system/sys_user.go | 29 +++ config-dev.yaml | 9 + config-prod.yaml | 7 + config/config.go | 1 + config/system.go | 7 +- global/global.go | 12 +- global/model.go | 8 +- go.mod | 4 + go.sum | 69 +++++++ initialize/gorm.go | 2 + initialize/router.go | 8 +- middleware/auth.go | 38 ++++ model/commom/request/common.go | 48 +++++ model/commom/response/common.go | 8 + model/commom/response/response.go | 60 ++++++ model/system/request/captcha.go | 15 ++ model/system/request/sys_client.go | 9 + model/system/request/sys_user.go | 16 ++ model/system/response/sys_captcha.go | 6 + model/system/response/sys_user.go | 13 ++ model/system/sys_client.go | 12 ++ model/system/sys_operation_record.go | 21 ++ model/system/sys_user.go | 20 +- router/system/auth_router.go | 17 ++ router/system/client_router.go | 17 ++ router/system/enter.go | 8 +- router/system/login_router.go | 17 -- router/system/user_router.go | 4 + service/enter.go | 9 + service/system/enter.go | 6 + service/system/sys_client.go | 53 +++++ service/system/sys_user.go | 44 ++++ utils/captcha/redis.go | 5 + utils/claims.go | 13 ++ utils/hash_test.go | 10 +- utils/human_duration.go | 30 +++ utils/validator.go | 294 +++++++++++++++++++++++++++ 41 files changed, 1093 insertions(+), 57 deletions(-) create mode 100644 api/v1/system/auth.go delete mode 100644 api/v1/system/login.go create mode 100644 api/v1/system/sys_client.go create mode 100644 middleware/auth.go create mode 100644 model/commom/request/common.go create mode 100644 model/commom/response/common.go create mode 100644 model/commom/response/response.go create mode 100644 model/system/request/captcha.go create mode 100644 model/system/request/sys_client.go create mode 100644 model/system/request/sys_user.go create mode 100644 model/system/response/sys_captcha.go create mode 100644 model/system/response/sys_user.go create mode 100644 model/system/sys_client.go create mode 100644 model/system/sys_operation_record.go create mode 100644 router/system/auth_router.go create mode 100644 router/system/client_router.go delete mode 100644 router/system/login_router.go create mode 100644 service/enter.go create mode 100644 service/system/enter.go create mode 100644 service/system/sys_client.go create mode 100644 service/system/sys_user.go create mode 100644 utils/captcha/redis.go create mode 100644 utils/human_duration.go create mode 100644 utils/validator.go diff --git a/api/v1/system/auth.go b/api/v1/system/auth.go new file mode 100644 index 0000000..3c063e5 --- /dev/null +++ b/api/v1/system/auth.go @@ -0,0 +1,76 @@ +package system + +import ( + "github.com/gin-gonic/gin" + "github.com/mojocn/base64Captcha" + "go.uber.org/zap" + "sundynix-go/global" + "sundynix-go/model/commom/response" + "sundynix-go/model/system" + systemReq "sundynix-go/model/system/request" + systemRes "sundynix-go/model/system/response" + "sundynix-go/utils" +) + +var store = base64Captcha.DefaultMemStore + +type AuthApi struct{} + +// Login api +func (a *AuthApi) Login(c *gin.Context) { + var l systemReq.Login + err := c.ShouldBindJSON(&l) + if err != nil { + response.FailWithMsg(err.Error(), c) + return + } + if l.CaptchaId != "" && l.Captcha != "" && store.Verify(l.CaptchaId, l.Captcha, true) { + u := &system.User{Account: l.Account, Password: l.Password} + user, err := UserService.Login(u) + if err != nil { + global.Logger.Error("登录失败! 用户名不存在或者密码错误!", zap.Error(err)) + response.FailWithMsg("用户名不存在或者密码错误", c) + return + } + a.GetToken(c, *user) + return + } + response.FailWithMsg("验证码错误", c) +} + +// Captcha api 生成验证码 +func (u *AuthApi) Captcha(c *gin.Context) { + var driver = base64Captcha.DriverString{ + Height: 80, + Width: 240, + NoiseCount: 2, + ShowLineOptions: 4, + Length: 6, + Source: "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM", + } + + cp := base64Captcha.NewCaptcha(&driver, store) + id, b64s, _, err := cp.Generate() + if err != nil { + global.Logger.Error("GenerateCaptcha err", zap.Error(err)) + response.FailWithMsg("GenerateCaptcha err", c) + return + } + response.OkWithData(systemRes.CaptchaRes{ + CaptchaId: id, + Captcha: b64s, + }, c) +} + +func (a *AuthApi) GetToken(c *gin.Context, user system.User) { + token, claims, err := utils.GetLoginToken(&user) + if err != nil { + global.Logger.Error("GetToken err", zap.Error(err)) + response.FailWithMsg("GetToken err", c) + } + response.OkWithData(systemRes.LoginResponse{ + ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000, + Token: token, + User: user, + }, c) +} diff --git a/api/v1/system/enter.go b/api/v1/system/enter.go index 218e958..50dd9d3 100644 --- a/api/v1/system/enter.go +++ b/api/v1/system/enter.go @@ -1,6 +1,14 @@ package system +import "sundynix-go/service" + type ApiGroup struct { - LoginApi + AuthApi UserApi + ClientApi } + +var ( + UserService = service.ServiceGroupApp.SystemServiceGroup.UserService + ClientService = service.ServiceGroupApp.SystemServiceGroup.ClientService +) diff --git a/api/v1/system/login.go b/api/v1/system/login.go deleted file mode 100644 index ae52732..0000000 --- a/api/v1/system/login.go +++ /dev/null @@ -1,15 +0,0 @@ -package system - -import "github.com/gin-gonic/gin" - -type LoginApi struct{} - -// Login -func (u LoginApi) Login(c *gin.Context) { - c.String(200, "Hello Admin") -} - -// Captcha -func (u LoginApi) Captcha(c *gin.Context) { - c.String(200, "Hello Admin") -} diff --git a/api/v1/system/sys_client.go b/api/v1/system/sys_client.go new file mode 100644 index 0000000..03e8d66 --- /dev/null +++ b/api/v1/system/sys_client.go @@ -0,0 +1,100 @@ +package system + +import ( + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "sundynix-go/global" + "sundynix-go/model/commom/request" + "sundynix-go/model/commom/response" + "sundynix-go/model/system" + systemReq "sundynix-go/model/system/request" +) + +type ClientApi struct { +} + +func (s *ClientApi) SaveClient(c *gin.Context) { + var client system.Client + err := c.ShouldBindJSON(&client) + if err != nil { + response.FailWithMsg(err.Error(), c) + return + } + err = ClientService.SaveClient(client) + if err != nil { + global.Logger.Error("保存客户端失败!", zap.Error(err)) + response.FailWithMsg(err.Error(), c) + return + } + response.OkWithMsg("保存客户端成功!", c) +} + +func (s *ClientApi) UpdateClient(c *gin.Context) { + var client system.Client + err := c.ShouldBindJSON(&client) + if err != nil { + response.FailWithMsg(err.Error(), c) + return + } + err = ClientService.UpdateClient(client) + if err != nil { + global.Logger.Error("更新客户端失败!", zap.Error(err)) + response.FailWithMsg(err.Error(), c) + return + } + response.OkWithMsg("更新客户端成功!", c) +} + +func (s *ClientApi) GetClientList(c *gin.Context) { + var pageInfo systemReq.GetClientList + err := c.ShouldBindJSON(&pageInfo) + if err != nil { + response.FailWithMsg(err.Error(), c) + return + } + list, total, err := ClientService.GetClientList(pageInfo) + if err != nil { + global.Logger.Error("获取客户端列表失败!", zap.Error(err)) + response.FailWithMsg(err.Error(), c) + return + } + response.OkWithData(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Current, + PageSize: pageInfo.PageSize, + }, c) +} + +func (s *ClientApi) Delete(c *gin.Context) { + var ids request.IdsReq + err := c.ShouldBindJSON(&ids) + if err != nil { + response.FailWithMsg(err.Error(), c) + return + } + err = ClientService.DeleteClientByIds(ids) + if err != nil { + global.Logger.Error("删除客户端失败!", zap.Error(err)) + response.FailWithMsg(err.Error(), c) + return + } + response.OkWithMsg("删除客户端成功!", c) +} + +func (s *ClientApi) Detail(c *gin.Context) { + var idInfo request.GetById + err := c.ShouldBindJSON(&idInfo) + if err != nil { + response.FailWithMsg(err.Error(), c) + return + } + client, err := ClientService.GetClientById(idInfo.ID) + if err != nil { + global.Logger.Error("获取客户端详情失败!", zap.Error(err)) + response.FailWithMsg(err.Error(), c) + return + } + response.OkWithData(client, c) + +} diff --git a/api/v1/system/sys_user.go b/api/v1/system/sys_user.go index 8e05938..ef4b8f0 100644 --- a/api/v1/system/sys_user.go +++ b/api/v1/system/sys_user.go @@ -1,4 +1,33 @@ package system +import ( + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "sundynix-go/global" + "sundynix-go/model/commom/response" + systemReq "sundynix-go/model/system/request" +) + type UserApi struct { } + +func (u *UserApi) GetUserList(c *gin.Context) { + var pageInfo systemReq.GetUserList + err := c.ShouldBindJSON(&pageInfo) + if err != nil { + response.FailWithMsg(err.Error(), c) + return + } + list, total, err := UserService.GetUserList(pageInfo) + if err != nil { + global.Logger.Error("获取用户列表失败!", zap.Error(err)) + response.FailWithMsg(err.Error(), c) + return + } + response.OkWithData(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Current, + PageSize: pageInfo.PageSize, + }, c) +} diff --git a/config-dev.yaml b/config-dev.yaml index 452b32f..770f5c4 100644 --- a/config-dev.yaml +++ b/config-dev.yaml @@ -2,6 +2,15 @@ system: addr: 8888 db-type: mysql router-prefix: "api" + enable-captcha: 0 + +jwt: + buffer-time: 1d + expires-time: 7d + issuer: sunfynix + signing-key: 9149f2eb-d517-4a50-a03a-231dbcf0d872 + + mysql: config: charset=utf8mb4&parseTime=True&loc=Local diff --git a/config-prod.yaml b/config-prod.yaml index 2c4f0fc..d13b040 100644 --- a/config-prod.yaml +++ b/config-prod.yaml @@ -2,6 +2,13 @@ system: addr: 8888 db-type: mysql +captcha: + img-height: 80 + img-width: 240 + key-long: 6 + open-captcha: 0 + open-captcha-timeout: 3600 + mysql: config: charset=utf8mb4&parseTime=True&loc=Local db-name: sundynix_go diff --git a/config/config.go b/config/config.go index 759b882..1937d72 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,7 @@ package config type Config struct { + JWT JWT `mapstructure:"jwt" json:"jwt" yaml:"jwt"` System System `mapstructure:"system" json:"system" yaml:"system"` Mysql Mysql `mapstructure:"mysql" json:"mysql" yaml:"mysql"` Pgsql Pgsql `mapstructure:"pgsql" json:"pgsql" yaml:"pgsql"` diff --git a/config/system.go b/config/system.go index 581d052..6123e71 100644 --- a/config/system.go +++ b/config/system.go @@ -1,7 +1,8 @@ package config type System struct { - Addr int `mapstructure:"addr" json:"addr" yaml:"addr"` - DbType string `mapstructure:"db-type" json:"db-type" yaml:"db-type"` - RouterPrefix string `mapstructure:"router-prefix" json:"router-prefix" yaml:"router-prefix"` + Addr int `mapstructure:"addr" json:"addr" yaml:"addr"` + DbType string `mapstructure:"db-type" json:"db-type" yaml:"db-type"` + RouterPrefix string `mapstructure:"router-prefix" json:"router-prefix" yaml:"router-prefix"` + EnableCaptcha int `mapstructure:"enable-captcha" json:"enable-captcha" yaml:"enable-captcha"` } diff --git a/global/global.go b/global/global.go index 1770b0c..944ab32 100644 --- a/global/global.go +++ b/global/global.go @@ -4,15 +4,17 @@ import ( "github.com/redis/go-redis/v9" "github.com/spf13/viper" "go.uber.org/zap" + "golang.org/x/sync/singleflight" "gorm.io/gorm" "sundynix-go/config" ) // 全局变量 加载在内存中 var ( - Viper *viper.Viper - Logger *zap.Logger - Config *config.Config - DB *gorm.DB - Redis redis.UniversalClient + Viper *viper.Viper + Logger *zap.Logger + Config *config.Config + DB *gorm.DB + Redis redis.UniversalClient + ConcurrencyControl = &singleflight.Group{} ) diff --git a/global/model.go b/global/model.go index df78a4d..cb3da4f 100644 --- a/global/model.go +++ b/global/model.go @@ -6,10 +6,10 @@ import ( ) type BaseModel struct { - ID uint `gorm:"primarykey" json:"ID"` // 主键ID - CreatedAt time.Time // 创建时间 - UpdatedAt time.Time // 更新时间 - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // 删除时间 + Id uint `gorm:"primarykey" json:"id"` // 主键ID + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"` // 删除时间 } // BeforeCreate 定义一个钩子,在创建之前执行自动插入字段 diff --git a/go.mod b/go.mod index 1e65b59..8f634d6 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,8 @@ require ( github.com/go-sql-driver/mysql v1.9.2 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/goccy/go-json v0.10.5 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -44,6 +46,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mojocn/base64Captcha v1.3.8 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/redis/go-redis/v9 v9.7.3 // indirect @@ -60,6 +63,7 @@ require ( golang.org/x/arch v0.16.0 // indirect golang.org/x/crypto v0.37.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/image v0.26.0 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect diff --git a/go.sum b/go.sum index 043a7c7..d13e006 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,10 @@ github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIx github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -83,6 +87,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mojocn/base64Captcha v1.3.8 h1:rrN9BhCwXKS8ht1e21kvR3iTaMgf4qPC9sRoV52bqEg= +github.com/mojocn/base64Captcha v1.3.8/go.mod h1:QFZy927L8HVP3+VV5z2b1EAEiv1KxVJKZbAucVgLUy4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= @@ -125,6 +131,7 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -133,23 +140,85 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= +golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= +golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/initialize/gorm.go b/initialize/gorm.go index 848c31e..9ff4df3 100644 --- a/initialize/gorm.go +++ b/initialize/gorm.go @@ -31,6 +31,8 @@ func MigrateTable() { db := global.DB err := db.AutoMigrate( system.User{}, + system.Client{}, + system.SysOperationRecord{}, ) if err != nil { global.Logger.Error("Migrate table failed,err:", zap.Error(err)) diff --git a/initialize/router.go b/initialize/router.go index 6660f85..489444b 100644 --- a/initialize/router.go +++ b/initialize/router.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/gin" "go.uber.org/zap" "sundynix-go/global" + "sundynix-go/middleware" "sundynix-go/router" ) @@ -24,15 +25,16 @@ func Routers() { PublicGroup := Router.Group(global.Config.System.RouterPrefix) //鉴权中间件 - //NeedAuthGroup.Use(middleware.JWTAuthMiddleware()) + NeedAuthGroup.Use(middleware.AuthMiddleware()) { //无须鉴权的路由 - systemRouter.InitLoginRouter(PublicGroup) //登录和验证码不需要鉴权 + systemRouter.InitAuthRouter(PublicGroup) //登录和验证码不需要鉴权 } { //需要鉴权的路由 - systemRouter.InitUserRouter(NeedAuthGroup) + systemRouter.InitUserRouter(NeedAuthGroup) //用户相关 + systemRouter.InitClientRouter(NeedAuthGroup) //客户端相关 } address := fmt.Sprintf(":%d", global.Config.System.Addr) diff --git a/middleware/auth.go b/middleware/auth.go new file mode 100644 index 0000000..793bc5a --- /dev/null +++ b/middleware/auth.go @@ -0,0 +1,38 @@ +package middleware + +import ( + "errors" + "github.com/gin-gonic/gin" + "sundynix-go/model/commom/response" + "sundynix-go/utils" +) + +// AuthMiddleware 验证token有效性 +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + token := utils.GetToken(c) + if token == "" { + response.NoAuth("未登录或非法访问", c) + c.Abort() + return + } + //todo 黑名单处理 + + j := utils.NewJWT() + // 解析token信息 + claims, err := j.ParseToken(token) + if err != nil { + if errors.Is(err, utils.TokenExpired) { + response.NoAuth("登录过期", c) + utils.ClearToken(c) + c.Abort() + return + } + response.NoAuth(err.Error(), c) + utils.ClearToken(c) + c.Abort() + return + } + c.Set("claims", claims) + } +} diff --git a/model/commom/request/common.go b/model/commom/request/common.go new file mode 100644 index 0000000..af78fed --- /dev/null +++ b/model/commom/request/common.go @@ -0,0 +1,48 @@ +package request + +import ( + "gorm.io/gorm" +) + +// PageInfo Paging common input parameter structure +type PageInfo struct { + Current int `json:"current" form:"current"` // 页码 + PageSize int `json:"pageSize" form:"pageSize"` // 每页大小 + Keyword string `json:"keyword" form:"keyword"` // 关键字 +} + +func (r *PageInfo) Paginate() func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if r.Current <= 0 { + r.Current = 1 + } + switch { + case r.PageSize > 100: + r.PageSize = 100 + case r.PageSize <= 0: + r.PageSize = 10 + } + offset := (r.Current - 1) * r.PageSize + return db.Offset(offset).Limit(r.PageSize) + } +} + +// GetById Find by id structure +type GetById struct { + ID int `json:"id" form:"id"` // 主键ID +} + +func (r *GetById) Uint() uint { + return uint(r.ID) +} + +type IdsReq struct { + Ids []int `json:"ids" form:"ids"` +} + +// GetAuthorityId Get role by id structure +type GetAuthorityId struct { + AuthorityId uint `json:"authorityId" form:"authorityId"` // 角色ID +} + +type Empty struct{} diff --git a/model/commom/response/common.go b/model/commom/response/common.go new file mode 100644 index 0000000..7461096 --- /dev/null +++ b/model/commom/response/common.go @@ -0,0 +1,8 @@ +package response + +type PageResult struct { + List interface{} `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` +} diff --git a/model/commom/response/response.go b/model/commom/response/response.go new file mode 100644 index 0000000..503168b --- /dev/null +++ b/model/commom/response/response.go @@ -0,0 +1,60 @@ +package response + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +type Response struct { + Code int `json:"code"` + Data interface{} `json:"data"` + Msg string `json:"msg"` +} + +const ( + SUCCESS = 200 + ERROR = 7 +) + +// Result 返回结果 +func Result(code int, data interface{}, msg string, c *gin.Context) { + c.JSON(http.StatusOK, Response{ + code, + data, + msg, + }) +} + +// Ok 成功返回 +func Ok(c *gin.Context) { + Result(SUCCESS, map[string]interface{}{}, "操作成功", c) +} + +// OkWithData 带数据成功返回 +func OkWithData(data interface{}, c *gin.Context) { + Result(SUCCESS, data, "操作成功", c) +} + +// OkWithMsg 带信息成功返回 +func OkWithMsg(msg string, c *gin.Context) { + Result(SUCCESS, map[string]interface{}{}, msg, c) +} + +// Fail 失败返回 +func Fail(code int, msg string, c *gin.Context) { + Result(code, map[string]interface{}{}, "操作失败", c) +} + +// FailWithMsg 带信息失败返回 +func FailWithMsg(msg string, c *gin.Context) { + Result(ERROR, map[string]interface{}{}, msg, c) +} + +// NoAuth 未授权返回 +func NoAuth(message string, c *gin.Context) { + c.JSON(http.StatusUnauthorized, Response{ + 7, + nil, + message, + }) +} diff --git a/model/system/request/captcha.go b/model/system/request/captcha.go new file mode 100644 index 0000000..3da0d01 --- /dev/null +++ b/model/system/request/captcha.go @@ -0,0 +1,15 @@ +package request + +import "github.com/mojocn/base64Captcha" + +// configJsonBody json request body. +type CaptchaReqBody struct { + Id string + CaptchaType string + VerifyValue string + DriverAudio *base64Captcha.DriverAudio + DriverString *base64Captcha.DriverString + DriverChinese *base64Captcha.DriverChinese + DriverMath *base64Captcha.DriverMath + DriverDigit *base64Captcha.DriverDigit +} diff --git a/model/system/request/sys_client.go b/model/system/request/sys_client.go new file mode 100644 index 0000000..2f6d4b9 --- /dev/null +++ b/model/system/request/sys_client.go @@ -0,0 +1,9 @@ +package request + +import common "sundynix-go/model/commom/request" + +type GetClientList struct { + common.PageInfo + ClientId string `json:"clientId" form:"clientId"` + Name string `json:"name" form:"name"` +} diff --git a/model/system/request/sys_user.go b/model/system/request/sys_user.go new file mode 100644 index 0000000..781c682 --- /dev/null +++ b/model/system/request/sys_user.go @@ -0,0 +1,16 @@ +package request + +import common "sundynix-go/model/commom/request" + +type Login struct { + Account string `json:"account"` + Password string `json:"password"` + Captcha string `json:"captcha"` + CaptchaId string `json:"captchaId"` +} + +type GetUserList struct { + common.PageInfo + Account string `json:"account" form:"account"` + Phone string `json:"phone" form:"phone"` +} diff --git a/model/system/response/sys_captcha.go b/model/system/response/sys_captcha.go new file mode 100644 index 0000000..40a1842 --- /dev/null +++ b/model/system/response/sys_captcha.go @@ -0,0 +1,6 @@ +package response + +type CaptchaRes struct { + CaptchaId string `json:"captchaId"` + Captcha string `json:"captcha"` +} diff --git a/model/system/response/sys_user.go b/model/system/response/sys_user.go new file mode 100644 index 0000000..78ba53c --- /dev/null +++ b/model/system/response/sys_user.go @@ -0,0 +1,13 @@ +package response + +import "sundynix-go/model/system" + +type SysUserResponse struct { + User system.User `json:"user"` +} + +type LoginResponse struct { + User system.User `json:"user"` + Token string `json:"token"` + ExpiresAt int64 `json:"expiresAt"` +} diff --git a/model/system/sys_client.go b/model/system/sys_client.go new file mode 100644 index 0000000..1aa7bda --- /dev/null +++ b/model/system/sys_client.go @@ -0,0 +1,12 @@ +package system + +import "sundynix-go/global" + +type Client struct { + global.BaseModel + ClientId string `gorm:"size:20;" json:"clientId"` + Name string `gorm:"size:50;" json:"name"` + GrantType string `gorm:"size:50;" json:"grantType"` + AdditionalInfo string `gorm:"type:text" json:"additionalInfo"` + ActiveTimeout uint `json:"activeTimeout"` +} diff --git a/model/system/sys_operation_record.go b/model/system/sys_operation_record.go new file mode 100644 index 0000000..240f255 --- /dev/null +++ b/model/system/sys_operation_record.go @@ -0,0 +1,21 @@ +package system + +import ( + "sundynix-go/global" + "time" +) + +type SysOperationRecord struct { + global.BaseModel + Ip string `json:"ip" form:"ip" gorm:"column:ip;comment:请求ip"` // 请求ip + Method string `json:"method" form:"method" gorm:"column:method;comment:请求方法"` // 请求方法 + Path string `json:"path" form:"path" gorm:"column:path;comment:请求路径"` // 请求路径 + Status int `json:"status" form:"status" gorm:"column:status;comment:请求状态"` // 请求状态 + Latency time.Duration `json:"latency" form:"latency" gorm:"column:latency;comment:延迟" swaggertype:"string"` // 延迟 + Agent string `json:"agent" form:"agent" gorm:"type:text;column:agent;comment:代理"` // 代理 + ErrorMessage string `json:"error_message" form:"error_message" gorm:"column:error_message;comment:错误信息"` // 错误信息 + Body string `json:"body" form:"body" gorm:"type:text;column:body;comment:请求Body"` // 请求Body + Resp string `json:"resp" form:"resp" gorm:"type:text;column:resp;comment:响应Body"` // 响应Body + UserID int `json:"user_id" form:"user_id" gorm:"column:user_id;comment:用户id"` // 用户id + User User `json:"user"` +} diff --git a/model/system/sys_user.go b/model/system/sys_user.go index 6d76841..5a418b6 100644 --- a/model/system/sys_user.go +++ b/model/system/sys_user.go @@ -1,7 +1,14 @@ package system -import "sundynix-go/global" +import ( + "sundynix-go/global" +) +type Login interface { + GetAccount() string + GetUserId() uint + GetUserInfo() any +} type User struct { global.BaseModel ClientId string `gorm:"size:20;" json:"clientId"` @@ -9,3 +16,14 @@ type User struct { Password string `gorm:"size:100;" json:"-" form:"password"` Phone string `gorm:"size:11;" json:"phone" form:"phone"` } + +func (u *User) GetAccount() string { + return u.Account +} +func (u *User) GetUserId() uint { + return u.Id +} + +func (u *User) GetUserInfo() any { + return *u +} diff --git a/router/system/auth_router.go b/router/system/auth_router.go new file mode 100644 index 0000000..2da4f61 --- /dev/null +++ b/router/system/auth_router.go @@ -0,0 +1,17 @@ +package system + +import ( + "github.com/gin-gonic/gin" +) + +type AuthRouter struct { +} + +// InitAuthRouter 初始化登录路由 +func (s *AuthRouter) InitAuthRouter(Router *gin.RouterGroup) { + loginRouter := Router.Group("auth") + { + loginRouter.POST("login", authApi.Login) + loginRouter.GET("captcha", authApi.Captcha) + } +} diff --git a/router/system/client_router.go b/router/system/client_router.go new file mode 100644 index 0000000..2b03fde --- /dev/null +++ b/router/system/client_router.go @@ -0,0 +1,17 @@ +package system + +import "github.com/gin-gonic/gin" + +type ClientRouter struct { +} + +func (s *ClientRouter) InitClientRouter(Router *gin.RouterGroup) { + clientRouter := Router.Group("client") + { + clientRouter.POST("save", clientApi.SaveClient) + clientRouter.POST("update", clientApi.UpdateClient) + clientRouter.POST("getClientList", clientApi.GetClientList) + clientRouter.GET("delete", clientApi.Delete) + clientRouter.GET("detail", clientApi.Detail) + } +} diff --git a/router/system/enter.go b/router/system/enter.go index 91ef5a6..bfa2f57 100644 --- a/router/system/enter.go +++ b/router/system/enter.go @@ -3,12 +3,14 @@ package system import v1 "sundynix-go/api/v1" type RouterGroup struct { - LoginRouter + AuthRouter UserRouter + ClientRouter } // 初始化路由 var ( - loginApi = v1.ApiGroupApp.SystemApiGroup.LoginApi - userApi = v1.ApiGroupApp.SystemApiGroup.UserApi + authApi = v1.ApiGroupApp.SystemApiGroup.AuthApi + userApi = v1.ApiGroupApp.SystemApiGroup.UserApi + clientApi = v1.ApiGroupApp.SystemApiGroup.ClientApi ) diff --git a/router/system/login_router.go b/router/system/login_router.go deleted file mode 100644 index 30ae271..0000000 --- a/router/system/login_router.go +++ /dev/null @@ -1,17 +0,0 @@ -package system - -import ( - "github.com/gin-gonic/gin" -) - -type LoginRouter struct { -} - -// InitLoginRouter 初始化登录路由 -func (s *LoginRouter) InitLoginRouter(Router *gin.RouterGroup) { - loginRouter := Router.Group("login") - { - loginRouter.POST("login", loginApi.Login) - loginRouter.POST("captcha", loginApi.Captcha) - } -} diff --git a/router/system/user_router.go b/router/system/user_router.go index 02fb859..9767f04 100644 --- a/router/system/user_router.go +++ b/router/system/user_router.go @@ -8,4 +8,8 @@ type UserRouter struct { } func (s *UserRouter) InitUserRouter(Router *gin.RouterGroup) { + userRouter := Router.Group("user") + { + userRouter.POST("getUserList", userApi.GetUserList) + } } diff --git a/service/enter.go b/service/enter.go new file mode 100644 index 0000000..d7cd944 --- /dev/null +++ b/service/enter.go @@ -0,0 +1,9 @@ +package service + +import "sundynix-go/service/system" + +var ServiceGroupApp = new(ServiceGroup) + +type ServiceGroup struct { + SystemServiceGroup system.ServiceGroup +} diff --git a/service/system/enter.go b/service/system/enter.go new file mode 100644 index 0000000..7f0bff6 --- /dev/null +++ b/service/system/enter.go @@ -0,0 +1,6 @@ +package system + +type ServiceGroup struct { + UserService + ClientService +} diff --git a/service/system/sys_client.go b/service/system/sys_client.go new file mode 100644 index 0000000..108d9f4 --- /dev/null +++ b/service/system/sys_client.go @@ -0,0 +1,53 @@ +package system + +import ( + "errors" + "gorm.io/gorm" + "sundynix-go/global" + common "sundynix-go/model/commom/request" + "sundynix-go/model/system" + systemReq "sundynix-go/model/system/request" +) + +type ClientService struct{} + +var ClientServiceApp = new(ClientService) + +func (s *ClientService) SaveClient(client system.Client) error { + if !errors.Is(global.DB.Where("client_id = ?", client.ClientId).First(&system.Client{}).Error, gorm.ErrRecordNotFound) { + return errors.New("存在重复clientId,请修改clientId") + } + return global.DB.Create(&client).Error +} + +func (s *ClientService) UpdateClient(client system.Client) error { + return global.DB.Model(&client).Where("id = ?", client.Id).Updates(&client).Error +} + +func (s *ClientService) GetClientList(info systemReq.GetClientList) (list interface{}, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Current - 1) + db := global.DB.Model(&system.Client{}) + var clientList []system.Client + if info.ClientId != "" { + db = db.Where("client_id = ?", info.ClientId) + } + if info.Name != "" { + db = db.Where("name LIKE ?", "%"+info.Name+"%") + } + err = db.Count(&total).Error + if err != nil { + return + } + err = db.Limit(limit).Offset(offset).Find(&clientList).Error + return clientList, total, err +} + +func (s *ClientService) DeleteClientByIds(ids common.IdsReq) (err error) { + return global.DB.Delete(&system.Client{}, "id IN ?", ids.Ids).Error +} + +func (s *ClientService) GetClientById(id int) (client system.Client, err error) { + err = global.DB.Where("id = ?", id).First(&client).Error + return client, err +} diff --git a/service/system/sys_user.go b/service/system/sys_user.go new file mode 100644 index 0000000..342dba0 --- /dev/null +++ b/service/system/sys_user.go @@ -0,0 +1,44 @@ +package system + +import ( + "errors" + "sundynix-go/global" + "sundynix-go/model/system" + systemReq "sundynix-go/model/system/request" + "sundynix-go/utils" +) + +type UserService struct{} + +var UserServiceApp = new(UserService) + +func (userService *UserService) Login(u *system.User) (userInfo *system.User, err error) { + var user system.User + err = global.DB.Where("account = ?", u.Account).First(&user).Error + if err == nil { + if ok := utils.BcryptCheck(u.Password, user.Password); !ok { + return nil, errors.New("密码错误") + } + } + return &user, err +} + +func (userService *UserService) GetUserList(info systemReq.GetUserList) (list interface{}, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Current - 1) + db := global.DB.Model(&system.User{}) + var userList []system.User + + if info.Account != "" { + db = db.Where("account LIKE ?", "%"+info.Account+"%") + } + if info.Phone != "" { + db = db.Where("phone LIKE ?", "%"+info.Phone+"%") + } + err = db.Count(&total).Error + if err != nil { + return + } + err = db.Limit(limit).Offset(offset).Find(&userList).Error + return userList, total, err +} diff --git a/utils/captcha/redis.go b/utils/captcha/redis.go new file mode 100644 index 0000000..6e09bc6 --- /dev/null +++ b/utils/captcha/redis.go @@ -0,0 +1,5 @@ +package captcha + +import "github.com/mojocn/base64Captcha" + +var CaptchaStore = base64Captcha.DefaultMemStore diff --git a/utils/claims.go b/utils/claims.go index e9ea23a..6e6a6bb 100644 --- a/utils/claims.go +++ b/utils/claims.go @@ -78,6 +78,19 @@ func GetToken(c *gin.Context) string { return token } +// ClearToken 清除Cookie中的token +func ClearToken(c *gin.Context) { + host, _, err := net.SplitHostPort(c.Request.Host) + if err != nil { + host = c.Request.Host + } + if net.ParseIP(host) != nil { + c.SetCookie("x-token", "", -1, "/", "", false, false) + } else { + c.SetCookie("x-token", "", -1, "/", host, false, false) + } +} + // GetUserInfo 从 gin.Context 中获取用户信息,并返回 CustomClaims 类型的指针。 // 该函数首先尝试从上下文中获取已存在的 claims,如果不存在,则调用 GetClaims 函数获取 claims。 // 如果获取 claims 失败,则返回 nil。 diff --git a/utils/hash_test.go b/utils/hash_test.go index 26c29a7..fc4c221 100644 --- a/utils/hash_test.go +++ b/utils/hash_test.go @@ -1,14 +1,14 @@ package utils import ( - "fmt" "testing" ) func TestHashPwd(t *testing.T) { - hash := BcryptHash("admin") - fmt.Println(hash) // $2a$10$QC/zkQ/ohPmvjF/goDyicu7cHgAEj8gHg6OTDHWhbYQMHHn4dwxX2 + //hash := BcryptHash("admin") + //fmt.Println(hash) // $2a$10$QC/zkQ/ohPmvjF/goDyicu7cHgAEj8gHg6OTDHWhbYQMHHn4dwxX2 + // + //check := BcryptCheck("admin", "$2a$10$QC/zkQ/ohPmvjF/goDyicu7cHgAEj8gHg6OTDHWhbYQMHHn4dwxX2") + //fmt.Println(check) - check := BcryptCheck("admin", "$2a$10$QC/zkQ/ohPmvjF/goDyicu7cHgAEj8gHg6OTDHWhbYQMHHn4dwxX2") - fmt.Println(check) } diff --git a/utils/human_duration.go b/utils/human_duration.go new file mode 100644 index 0000000..abdb3b1 --- /dev/null +++ b/utils/human_duration.go @@ -0,0 +1,30 @@ +package utils + +import ( + "strconv" + "strings" + "time" +) + +// ParseDuration 解析时间 +func ParseDuration(d string) (time.Duration, error) { + d = strings.TrimSpace(d) + dr, err := time.ParseDuration(d) + if err == nil { + return dr, nil + } + if strings.Contains(d, "d") { + index := strings.Index(d, "d") + + hour, _ := strconv.Atoi(d[:index]) + dr = time.Hour * 24 * time.Duration(hour) + ndr, err := time.ParseDuration(d[index+1:]) + if err != nil { + return dr, nil + } + return dr + ndr, nil + } + + dv, err := strconv.ParseInt(d, 10, 64) + return time.Duration(dv), err +} diff --git a/utils/validator.go b/utils/validator.go new file mode 100644 index 0000000..a56dac0 --- /dev/null +++ b/utils/validator.go @@ -0,0 +1,294 @@ +package utils + +import ( + "errors" + "reflect" + "regexp" + "strconv" + "strings" +) + +type Rules map[string][]string + +type RulesMap map[string]Rules + +var CustomizeMap = make(map[string]Rules) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: RegisterRule +//@description: 注册自定义规则方案建议在路由初始化层即注册 +//@param: key string, rule Rules +//@return: err error + +func RegisterRule(key string, rule Rules) (err error) { + if CustomizeMap[key] != nil { + return errors.New(key + "已注册,无法重复注册") + } else { + CustomizeMap[key] = rule + return nil + } +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: NotEmpty +//@description: 非空 不能为其对应类型的0值 +//@return: string + +func NotEmpty() string { + return "notEmpty" +} + +// @author: [zooqkl](https://github.com/zooqkl) +// @function: RegexpMatch +// @description: 正则校验 校验输入项是否满足正则表达式 +// @param: rule string +// @return: string + +func RegexpMatch(rule string) string { + return "regexp=" + rule +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Lt +//@description: 小于入参(<) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Lt(mark string) string { + return "lt=" + mark +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Le +//@description: 小于等于入参(<=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Le(mark string) string { + return "le=" + mark +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Eq +//@description: 等于入参(==) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Eq(mark string) string { + return "eq=" + mark +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Ne +//@description: 不等于入参(!=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Ne(mark string) string { + return "ne=" + mark +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Ge +//@description: 大于等于入参(>=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Ge(mark string) string { + return "ge=" + mark +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Gt +//@description: 大于入参(>) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Gt(mark string) string { + return "gt=" + mark +} + +// +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Verify +//@description: 校验方法 +//@param: st interface{}, roleMap Rules(入参实例,规则map) +//@return: err error + +func Verify(st interface{}, roleMap Rules) (err error) { + compareMap := map[string]bool{ + "lt": true, + "le": true, + "eq": true, + "ne": true, + "ge": true, + "gt": true, + } + + typ := reflect.TypeOf(st) + val := reflect.ValueOf(st) // 获取reflect.Type类型 + + kd := val.Kind() // 获取到st对应的类别 + if kd != reflect.Struct { + return errors.New("expect struct") + } + num := val.NumField() + // 遍历结构体的所有字段 + for i := 0; i < num; i++ { + tagVal := typ.Field(i) + val := val.Field(i) + if tagVal.Type.Kind() == reflect.Struct { + if err = Verify(val.Interface(), roleMap); err != nil { + return err + } + } + if len(roleMap[tagVal.Name]) > 0 { + for _, v := range roleMap[tagVal.Name] { + switch { + case v == "notEmpty": + if isBlank(val) { + return errors.New(tagVal.Name + "值不能为空") + } + case strings.Split(v, "=")[0] == "regexp": + if !regexpMatch(strings.Split(v, "=")[1], val.String()) { + return errors.New(tagVal.Name + "格式校验不通过") + } + case compareMap[strings.Split(v, "=")[0]]: + if !compareVerify(val, v) { + return errors.New(tagVal.Name + "长度或值不在合法范围," + v) + } + } + } + } + } + return nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: compareVerify +//@description: 长度和数字的校验方法 根据类型自动校验 +//@param: value reflect.Value, VerifyStr string +//@return: bool + +func compareVerify(value reflect.Value, VerifyStr string) bool { + switch value.Kind() { + case reflect.String: + return compare(len([]rune(value.String())), VerifyStr) + case reflect.Slice, reflect.Array: + return compare(value.Len(), VerifyStr) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return compare(value.Uint(), VerifyStr) + case reflect.Float32, reflect.Float64: + return compare(value.Float(), VerifyStr) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return compare(value.Int(), VerifyStr) + default: + return false + } +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: isBlank +//@description: 非空校验 +//@param: value reflect.Value +//@return: bool + +func isBlank(value reflect.Value) bool { + switch value.Kind() { + case reflect.String, reflect.Slice: + return value.Len() == 0 + case reflect.Bool: + return !value.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return value.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return value.Uint() == 0 + case reflect.Float32, reflect.Float64: + return value.Float() == 0 + case reflect.Interface, reflect.Ptr: + return value.IsNil() + } + return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface()) +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: compare +//@description: 比较函数 +//@param: value interface{}, VerifyStr string +//@return: bool + +func compare(value interface{}, VerifyStr string) bool { + VerifyStrArr := strings.Split(VerifyStr, "=") + val := reflect.ValueOf(value) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + VInt, VErr := strconv.ParseInt(VerifyStrArr[1], 10, 64) + if VErr != nil { + return false + } + switch { + case VerifyStrArr[0] == "lt": + return val.Int() < VInt + case VerifyStrArr[0] == "le": + return val.Int() <= VInt + case VerifyStrArr[0] == "eq": + return val.Int() == VInt + case VerifyStrArr[0] == "ne": + return val.Int() != VInt + case VerifyStrArr[0] == "ge": + return val.Int() >= VInt + case VerifyStrArr[0] == "gt": + return val.Int() > VInt + default: + return false + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + VInt, VErr := strconv.Atoi(VerifyStrArr[1]) + if VErr != nil { + return false + } + switch { + case VerifyStrArr[0] == "lt": + return val.Uint() < uint64(VInt) + case VerifyStrArr[0] == "le": + return val.Uint() <= uint64(VInt) + case VerifyStrArr[0] == "eq": + return val.Uint() == uint64(VInt) + case VerifyStrArr[0] == "ne": + return val.Uint() != uint64(VInt) + case VerifyStrArr[0] == "ge": + return val.Uint() >= uint64(VInt) + case VerifyStrArr[0] == "gt": + return val.Uint() > uint64(VInt) + default: + return false + } + case reflect.Float32, reflect.Float64: + VFloat, VErr := strconv.ParseFloat(VerifyStrArr[1], 64) + if VErr != nil { + return false + } + switch { + case VerifyStrArr[0] == "lt": + return val.Float() < VFloat + case VerifyStrArr[0] == "le": + return val.Float() <= VFloat + case VerifyStrArr[0] == "eq": + return val.Float() == VFloat + case VerifyStrArr[0] == "ne": + return val.Float() != VFloat + case VerifyStrArr[0] == "ge": + return val.Float() >= VFloat + case VerifyStrArr[0] == "gt": + return val.Float() > VFloat + default: + return false + } + default: + return false + } +} + +func regexpMatch(rule, matchStr string) bool { + return regexp.MustCompile(rule).MatchString(matchStr) +}