From 1c0a128855189662a1a7936f95def17b4ac4843e Mon Sep 17 00:00:00 2001 From: coward Date: Thu, 7 Mar 2024 11:03:46 +0800 Subject: [PATCH] =?UTF-8?q?:new:=E6=8E=88=E6=9D=83=E7=99=BB=E9=99=86?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compoment/captcha_store.go | 6 +- compoment/jwt.go | 59 ++++++++++++ constant/admin.go | 20 +++++ go.mod | 11 +-- go.sum | 24 +++-- http/api/user.go | 67 +++++++++++++- http/param/user.go | 6 +- middleware/authorization.go | 49 ++++++++++ model/entity/base.go | 175 ++++++------------------------------ model/entity/user.go | 13 +-- model/vo/user.go | 20 +++++ repository/user.go | 34 +++++++ route/captcha.go | 2 +- route/user.go | 10 ++- script/db_migrate.go | 1 + utils/gin_response.go | 14 +++ 16 files changed, 336 insertions(+), 175 deletions(-) create mode 100644 compoment/jwt.go create mode 100644 middleware/authorization.go create mode 100644 model/vo/user.go create mode 100644 repository/user.go diff --git a/compoment/captcha_store.go b/compoment/captcha_store.go index 85c9f02..caffe4b 100644 --- a/compoment/captcha_store.go +++ b/compoment/captcha_store.go @@ -3,6 +3,7 @@ package compoment import ( "context" "fmt" + "os" "strings" "time" "wireguard-dashboard/client" @@ -18,7 +19,7 @@ type CaptchaStore struct{} // @param value // @return error func (CaptchaStore) Set(id string, value string) error { - return client.Redis.Set(context.Background(), fmt.Sprintf("%s:%s", constant.Captcha, id), value, time.Minute).Err() + return client.Redis.Set(context.Background(), fmt.Sprintf("%s:%s", constant.Captcha, id), value, 2*time.Minute).Err() } // Get @@ -49,6 +50,9 @@ func (CaptchaStore) Get(id string, clear bool) string { // @param clear // @return bool func (c CaptchaStore) Verify(id, answer string, clear bool) bool { + if os.Getenv("GIN_MODE") != "release" { + return true + } storeAnswer := c.Get(id, clear) return strings.ToUpper(answer) == strings.ToUpper(storeAnswer) } diff --git a/compoment/jwt.go b/compoment/jwt.go new file mode 100644 index 0000000..62da684 --- /dev/null +++ b/compoment/jwt.go @@ -0,0 +1,59 @@ +package compoment + +import ( + "github.com/golang-jwt/jwt/v5" + "strings" + "time" +) + +const Secret = "IK8MSs76Pb2VJxleTDadf1Wzu3h9QROLv0XtmnCUErYgBG5wAyjk4cioqFZHNpZG" + +type JwtClaims struct { + ID string `json:"id"` + Name string `json:"name"` + jwt.RegisteredClaims `json:"-"` +} + +func JWT() JwtClaims { + return JwtClaims{} +} + +// GenerateToken +// @description: 生成token +// @receiver Jwt +// @return token +// @return err +func (j JwtClaims) GenerateToken(userId string) (token string, err error) { + claims := JwtClaims{ + ID: userId, + RegisteredClaims: jwt.RegisteredClaims{ + Subject: "wireguard-dashboard", + ExpiresAt: jwt.NewNumericDate(time.Now().Local().Add(7 * time.Hour)), + NotBefore: jwt.NewNumericDate(time.Now().Local()), + IssuedAt: jwt.NewNumericDate(time.Now().Local()), + }, + } + + t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + token, err = t.SignedString([]byte(Secret)) + return +} + +// ParseToken +// @description: 解析token +// @receiver Jwt +// @return Jwt +// @return error +func (JwtClaims) ParseToken(token string) (*JwtClaims, error) { + tokenStr := strings.Split(token, "Bearer ")[1] + + t, err := jwt.ParseWithClaims(tokenStr, &JwtClaims{}, func(token *jwt.Token) (any, error) { + return []byte(Secret), nil + }) + + if claims, ok := t.Claims.(*JwtClaims); ok && t.Valid { + return claims, nil + } else { + return nil, err + } +} diff --git a/constant/admin.go b/constant/admin.go index 0005236..ce91724 100644 --- a/constant/admin.go +++ b/constant/admin.go @@ -19,3 +19,23 @@ func (u UserType) String() string { return "未知类型" } + +type UserStatus int + +const ( + Disabled UserStatus = iota + Normal +) + +var UserStatusMap = map[UserStatus]string{ + Disabled: "禁用", + Normal: "正常", +} + +func (u UserStatus) String() string { + if v, ok := UserStatusMap[u]; ok { + return v + } + + return "未知类型" +} diff --git a/go.mod b/go.mod index ac0e7c0..8c664fc 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,15 @@ module wireguard-dashboard go 1.21 require ( - github.com/cowardmrx/go_aliyun_oss v1.0.6 + github.com/cowardmrx/go_aliyun_oss v1.0.7 github.com/gin-gonic/gin v1.9.1 github.com/go-resty/resty/v2 v2.11.0 + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/mojocn/base64Captcha v1.3.6 github.com/redis/go-redis/v9 v9.5.1 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.21.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.5.4 @@ -42,6 +43,7 @@ require ( github.com/josharian/native v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect @@ -52,15 +54,14 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/satori/go.uuid v1.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/image v0.15.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect diff --git a/go.sum b/go.sum index d05221e..13df7ce 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,9 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/cowardmrx/go_aliyun_oss v1.0.6 h1:KVCCUJe4n/hGzNH1RBuu4qDGa9PfTYJOLsAt3GDZx3s= -github.com/cowardmrx/go_aliyun_oss v1.0.6/go.mod h1:hisPCZT3TaXex2fCTXRl/rozLw/dpifyIJtihBlRnkk= +github.com/cowardmrx/go_aliyun_oss v1.0.7 h1:MCSKUWi4RZnHhwe4fd7VAsgeRXL0kT9z56TTde+1lME= +github.com/cowardmrx/go_aliyun_oss v1.0.7/go.mod h1:xz6B8H840TX7yPcgSLUbK7q6nnEsxFutaltR08Aetdg= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -40,6 +41,8 @@ github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -67,8 +70,8 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -96,14 +99,14 @@ github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew0 github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -131,8 +134,9 @@ golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= @@ -143,8 +147,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v 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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= @@ -158,8 +163,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc 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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= diff --git a/http/api/user.go b/http/api/user.go index 5733cc0..c3df52a 100644 --- a/http/api/user.go +++ b/http/api/user.go @@ -2,7 +2,12 @@ package api import ( "github.com/gin-gonic/gin" + "wireguard-dashboard/compoment" + "wireguard-dashboard/constant" "wireguard-dashboard/http/param" + "wireguard-dashboard/model/entity" + "wireguard-dashboard/model/vo" + "wireguard-dashboard/repository" "wireguard-dashboard/utils" ) @@ -22,5 +27,65 @@ func (u user) Login(c *gin.Context) { utils.GinResponse(c).FailedWithErr("参数错误", err) return } - utils.GinResponse(c).OKWithData("不准") + + // 校验验证码 + pass := compoment.CaptchaStore{}.Verify(p.CaptchaId, p.CaptchaAnswer, true) + if !pass { + utils.GinResponse(c).FailedWithMsg("验证码错误") + return + } + + // 校验用户是否存在 + user, err := repository.User().GetUserByAccount(p.Account) + if err != nil { + utils.GinResponse(c).FailedWithMsg("账户不存在") + return + } + + if user.Status != constant.Normal { + utils.GinResponse(c).FailedWithMsg("账户状态异常") + return + } + + // 校验密码 + if !utils.Password().ComparePassword(user.Password, p.Password) { + utils.GinResponse(c).FailedWithMsg("密码错误") + return + } + + // 生成token + token, err := compoment.JWT().GenerateToken(user.Id) + if err != nil { + utils.GinResponse(c).FailedWithMsg("登陆失败") + return + } + + utils.GinResponse(c).OKWithData(map[string]any{ + "token": token, + "type": "Bearer", + }) +} + +// GetUser +// @description: 获取登陆用户信息 +// @receiver u +// @param c +func (u user) GetUser(c *gin.Context) { + user, ok := c.Get("user") + if !ok { + utils.GinResponse(c).FailedWithMsg("获取信息失败") + return + } + data := &vo.User{ + Id: user.(*entity.User).Id, + Name: user.(*entity.User).Name, + Avatar: user.(*entity.User).Avatar, + Account: user.(*entity.User).Account, + Email: user.(*entity.User).Email, + IsAdmin: user.(*entity.User).IsAdmin, + Status: user.(*entity.User).Status, + CreatedAt: user.(*entity.User).CreatedAt, + UpdatedAt: user.(*entity.User).UpdatedAt, + } + utils.GinResponse(c).OKWithData(data) } diff --git a/http/param/user.go b/http/param/user.go index 3f4261a..3791716 100644 --- a/http/param/user.go +++ b/http/param/user.go @@ -1,6 +1,8 @@ package param type Login struct { - Account string `json:"account" form:"account" binding:"required"` - Password string `json:"password" form:"password" binding:"required"` + Account string `json:"account" form:"account" binding:"required"` + Password string `json:"password" form:"password" binding:"required"` + CaptchaId string `json:"captchaId" form:"captchaId" binding:"required"` + CaptchaAnswer string `json:"captchaAnswer" form:"captchaAnswer" binding:"required"` } diff --git a/middleware/authorization.go b/middleware/authorization.go new file mode 100644 index 0000000..2e4e7f3 --- /dev/null +++ b/middleware/authorization.go @@ -0,0 +1,49 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "strings" + "wireguard-dashboard/compoment" + "wireguard-dashboard/constant" + "wireguard-dashboard/repository" + "wireguard-dashboard/utils" +) + +// Authorization +// @description: 授权中间件 +// @return gin.HandlerFunc +func Authorization() gin.HandlerFunc { + return func(c *gin.Context) { + token := c.GetHeader("Authorization") + if token == "" || !strings.HasPrefix(token, "Bearer ") { + utils.GinResponse(c).AuthorizationFailed() + c.Abort() + return + } + + userClaims, err := compoment.JWT().ParseToken(token) + if err != nil { + utils.GinResponse(c).AuthorizationFailed() + c.Abort() + return + } + + // 查询用户 + user, err := repository.User().GetUserById(userClaims.ID) + if err != nil { + utils.GinResponse(c).FailedWithMsg("用户不存在") + c.Abort() + return + } + + if user.Status != constant.Normal { + utils.GinResponse(c).FailedWithMsg("用户状态异常,请联系管理员处理!") + c.Abort() + return + } + + // 将用户信息放入上下文 + c.Set("user", user) + c.Next() + } +} diff --git a/model/entity/base.go b/model/entity/base.go index a17e1a9..153e63d 100644 --- a/model/entity/base.go +++ b/model/entity/base.go @@ -10,171 +10,48 @@ import ( ) type Base struct { - Id string `json:"id" gorm:"primaryKey;type:varchar(36);not null;comment:'主键'"` - CreatedAt DateTime - UpdatedAt DateTime + Id string `json:"id" gorm:"primaryKey;type:varchar(36);not null;comment:'主键'"` + Timestamp } func (b *Base) BeforeCreate(*gorm.DB) (err error) { if b.Id == "" { - b.Id = uuid.NewString() + b.Id = strings.ReplaceAll(uuid.NewString(), "-", "") } return } -// 默认时间格式 -const dateTimeFormat = "2006-01-02 15:04:05.000" - -// DateTime 自定义时间类型 -type DateTime time.Time - -// 可能包含的时间格式 -var formatMap = map[string]string{ - "yyyy-mm-dd hh:mm:ss": "2006-01-02 15:04:05", - "yyyy-mm-dd hh:mm": "2006-01-02 15:04", - "yyyy-mm-dd hh": "2006-01-02 15:04", - "yyyy-mm-dd": "2006-01-02", - "yyyy-mm": "2006-01", - "mm-dd": "01-02", - "dd-mm-yy hh:mm:ss": "02-01-06 15:04:05", - "yyyy/mm/dd hh:mm:ss": "2006/01/02 15:04:05", - "yyyy/mm/dd hh:mm": "2006/01/02 15:04", - "yyyy/mm/dd hh": "2006/01/02 15", - "yyyy/mm/dd": "2006/01/02", - "yyyy/mm": "2006/01", - "mm/dd": "01/02", - "dd/mm/yy hh:mm:ss": "02/01/06 15:04:05", - "yyyy": "2006", - "mm": "01", - "hh:mm:ss": "15:04:05", - "mm:ss": "04:05", +type JsonTime struct { + time.Time } -// Scan implements the Scanner interface. -func (dt *DateTime) Scan(value interface{}) error { - // mysql 内部日期的格式可能是 2006-01-02 15:04:05 +0800 CST 格式,所以检出的时候还需要进行一次格式化 - tTime, _ := time.Parse("2006-01-02 15:04:05 +0800 CST", value.(time.Time).String()) - *dt = DateTime(tTime) - return nil +type Timestamp struct { + CreatedAt JsonTime + UpdatedAt JsonTime } -// Value implements the driver Valuer interface. -func (dt DateTime) Value() (dv driver.Value, err error) { - // 0001-01-01 00:00:00 属于空值,遇到空值解析成 null 即可 - if dt.String() == "0001-01-01 00:00:00.000" { - return nil, nil +func (jt JsonTime) MarshalJSON() ([]byte, error) { + if jt.IsZero() { + return []byte(`""`), nil } - dv, err = []byte(dt.Format(dateTimeFormat)), nil - return -} - -// 用于 fmt.Println 和后续验证场景 -func (dt DateTime) String() string { - return dt.Format(dateTimeFormat) -} - -// Format 格式化 -func (dt *DateTime) Format(fm string) string { - return time.Time(*dt).Format(fm) -} - -// AutoParse 假装是个自动解析时间的函数 -func (dt DateTime) AutoParse(timeStr string) (t time.Time, err error) { - // 循环匹配预设的时间格式 - for _, v := range formatMap { - // 尝试解析,没报错就是解析成功了 - t, err = time.ParseInLocation(v, timeStr, time.Local) - if err == nil { - // 错误为空,表示匹配上了 - return - } - } - return -} - -// After 时间比较 -func (dt *DateTime) After(now time.Time) bool { - return time.Time(*dt).After(now) -} - -// Before 时间比较 -func (dt *DateTime) Before(now time.Time) bool { - return time.Time(*dt).Before(now) -} - -// IBefore 时间比较 -func (dt *DateTime) IBefore(now DateTime) bool { - return dt.Before(time.Time(now)) -} - -// SubTime 对比 -func (dt DateTime) SubTime(t time.Time) time.Duration { - return dt.ToTime().Sub(t) -} - -// Sub 对比 -func (dt DateTime) Sub(t DateTime) time.Duration { - return dt.ToTime().Sub(t.ToTime()) -} - -// ToTime 转换为golang的时间类型 -func (dt DateTime) ToTime() time.Time { - return time.Time(dt) -} - -// IsNil 是否为空值 -func (dt DateTime) IsNil() bool { - return dt.Format(dateTimeFormat) == "0001-01-01 00:00:00.000" -} - -// Unix 实现Unix函数 -func (dt DateTime) Unix() int64 { - return dt.ToTime().Unix() -} - -// EndOfCentury 获取本世纪最后时间 -func (dt DateTime) EndOfCentury() DateTime { - yearEnd := time.Now().Local().Year()/100*100 + 99 - return DateTime(time.Date(yearEnd, 12, 31, 23, 59, 59, 999999999, time.Local)) -} - -// Add -// @description: 添加时间 -// @receiver dt -// @param t -func (dt DateTime) Add(d time.Duration) DateTime { - return DateTime(dt.ToTime().Add(d)) -} - -// ======== 序列化 JSON ======== - -// MarshalJSON 时间到字符串 -func (dt DateTime) MarshalJSON() ([]byte, error) { - // 过滤掉空数据 - if dt.IsNil() { - return []byte("\"\""), nil - } - output := fmt.Sprintf(`"%s"`, dt.Format("2006-01-02 15:04:05")) + output := fmt.Sprintf("\"%s\"", jt.Format("2006-01-02 15:04:05")) return []byte(output), nil } -// UnmarshalJSON 字符串到时间 -func (dt *DateTime) UnmarshalJSON(b []byte) (err error) { - if len(b) == 2 { - *dt = DateTime{} - return +func (jt JsonTime) Value() (driver.Value, error) { + var zeroTime time.Time + if jt.Time.UnixNano() == zeroTime.UnixNano() { + return nil, nil } - // 解析指定的格式 - var now time.Time - if strings.HasPrefix(string(b), "\"") { - now, err = dt.AutoParse(string(b)[1 : len(b)-1]) - } else { - now, err = dt.AutoParse(string(b)) - } - if err != nil { - return - } - *dt = DateTime(now) - return + return jt.Time.Format("2006-01-02 15:04:05"), nil +} + +func (jt *JsonTime) Scan(v interface{}) error { + value, ok := v.(time.Time) + if ok { + *jt = JsonTime{Time: value} + return nil + } + return fmt.Errorf("can not convert %v to timestamp", v) } diff --git a/model/entity/user.go b/model/entity/user.go index 7f38c77..336f37f 100644 --- a/model/entity/user.go +++ b/model/entity/user.go @@ -6,12 +6,13 @@ import "wireguard-dashboard/constant" // @description: 用户信息 type User struct { Base - Avatar string `json:"avatar" gorm:"type:varchar(255);not null;comment:'头像'"` - Name string `json:"name" gorm:"type:varchar(50);not null;comment:'用户名'"` - Account string `json:"account" gorm:"type:varchar(50);not null;comment:'账号'"` - Email string `json:"email" gorm:"type:varchar(255);default null;comment:'联系邮箱'"` - Password string `json:"password" gorm:"type:varchar(255);not null;comment:'密码'"` - IsAdmin constant.UserType `json:"isAdmin" gorm:"type:int(1);not null;comment:'是否为管理员'"` + Avatar string `json:"avatar" gorm:"type:varchar(255);not null;comment:'头像'"` + Name string `json:"name" gorm:"type:varchar(50);not null;comment:'用户名'"` + Account string `json:"account" gorm:"type:varchar(50);not null;comment:'账号'"` + Email string `json:"email" gorm:"type:varchar(255);default null;comment:'联系邮箱'"` + Password string `json:"password" gorm:"type:varchar(255);not null;comment:'密码'"` + IsAdmin constant.UserType `json:"isAdmin" gorm:"type:int(1);not null;comment:'是否为管理员'"` + Status constant.UserStatus `json:"status" gorm:"type:int(1);not null;comment:'用户状态(0 - 禁用 | 1 - 正常)'"` } func (*User) TableName() string { diff --git a/model/vo/user.go b/model/vo/user.go new file mode 100644 index 0000000..9690ab0 --- /dev/null +++ b/model/vo/user.go @@ -0,0 +1,20 @@ +package vo + +import ( + "wireguard-dashboard/constant" + "wireguard-dashboard/model/entity" +) + +// User +// @description: 用户信息 +type User struct { + Id string `json:"id"` // id + Name string `json:"name"` // 用户名 + Avatar string `json:"avatar"` // 头像 + Account string `json:"account"` // 账户 + Email string `json:"email"` // 联系邮箱 + IsAdmin constant.UserType `json:"isAdmin"` // 管理员 + Status constant.UserStatus `json:"status"` // 状态 + CreatedAt entity.JsonTime `json:"createdAt"` // 创建时间 + UpdatedAt entity.JsonTime `json:"updatedAt"` // 更新时间 +} diff --git a/repository/user.go b/repository/user.go new file mode 100644 index 0000000..10812d8 --- /dev/null +++ b/repository/user.go @@ -0,0 +1,34 @@ +package repository + +import ( + "wireguard-dashboard/client" + "wireguard-dashboard/model/entity" +) + +type user struct{} + +func User() user { + return user{} +} + +// GetUserById +// @description: 根据id获取用户信息 +// @receiver r +// @param id +// @return *entity.User +// @return error +func (r user) GetUserById(id string) (data *entity.User, err error) { + err = client.DB.Where("id = ?", id).First(&data).Error + return +} + +// GetUserByAccount +// @description: 通过账户号获取用户信息 +// @receiver r +// @param account +// @return data +// @return err +func (r user) GetUserByAccount(account string) (data *entity.User, err error) { + err = client.DB.Where("account = ?", account).First(&data).Error + return +} diff --git a/route/captcha.go b/route/captcha.go index 49e95c8..b74495b 100644 --- a/route/captcha.go +++ b/route/captcha.go @@ -11,6 +11,6 @@ import ( func Captcha(r *gin.Engine) { captcha := r.Group("captcha") { - captcha.GET("captcha", api.Captcha().GenerateCaptcha) // 生成验证码 + captcha.GET("", api.Captcha().GenerateCaptcha) // 生成验证码 } } diff --git a/route/user.go b/route/user.go index f53a738..b872e31 100644 --- a/route/user.go +++ b/route/user.go @@ -3,11 +3,19 @@ package route import ( "github.com/gin-gonic/gin" "wireguard-dashboard/http/api" + "wireguard-dashboard/middleware" ) func UserApi(r *gin.Engine) { + // 登陆相关API + login := r.Group("/login") + { + login.POST("", api.UserApi().Login) + } + + // 用户登陆后相关的API userApi := r.Group("user") { - userApi.POST("/login", api.UserApi().Login) // 登陆 + userApi.GET("", middleware.Authorization(), api.UserApi().GetUser) // 获取登陆用户信息 } } diff --git a/script/db_migrate.go b/script/db_migrate.go index 646cd88..d2dd89f 100644 --- a/script/db_migrate.go +++ b/script/db_migrate.go @@ -65,6 +65,7 @@ func (s Script) CreateSuperAdmin() error { Email: "", Password: utils.Password().GenerateHashPassword("admin123"), IsAdmin: constant.SuperAdmin, + Status: constant.Normal, }).Error; err != nil { zap.S().Errorf("创建管理员失败: %v", err.Error()) } diff --git a/utils/gin_response.go b/utils/gin_response.go index 155db52..5f31acd 100644 --- a/utils/gin_response.go +++ b/utils/gin_response.go @@ -35,6 +35,20 @@ func (r ginResponse) FailedWithErr(msg string, err error) { }) } +func (r ginResponse) FailedWithMsg(msg string) { + r.c.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": msg, + }) +} + +func (r ginResponse) AuthorizationFailed() { + r.c.JSON(http.StatusUnauthorized, gin.H{ + "code": http.StatusUnauthorized, + "message": "请先登陆", + }) +} + func (r ginResponse) OKWithData(data any) { r.c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK,