diff --git a/.gitignore b/.gitignore index 75a1f53..1371b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea *.log +.env diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..294dab2 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,3 @@ +{ + "project_name": "project" +} \ No newline at end of file diff --git a/src/configs/db.go b/src/configs/db.go deleted file mode 100644 index 53dbea7..0000000 --- a/src/configs/db.go +++ /dev/null @@ -1,29 +0,0 @@ -package configs - -import ( - "github.com/OVINC-CN/DevTemplateGo/src/utils" - "strconv" - "time" -) - -type dbConfigModel struct { - Host string - Port string - User string - Password string - Name string - MaxConnections int - ConnectionTimeOut time.Duration - SlowThreshold time.Duration -} - -var DBConfig = dbConfigModel{ - Host: utils.GetEnv("DB_HOST", "127.0.0.1"), - Port: utils.GetEnv("DB_PORT", "3306"), - User: utils.GetEnv("DB_USER", ""), - Password: utils.GetEnv("DB_PASSWORD", ""), - Name: utils.GetEnv("DB_NAME", ""), - MaxConnections: utils.StrToInt(utils.GetEnv("DB_MAX_CONNECTIONS", "10")), - ConnectionTimeOut: time.Duration(utils.StrToInt(utils.GetEnv("DB_CONNECTION_TIMEOUT", strconv.Itoa(60*60)))) * time.Second, - SlowThreshold: time.Duration(utils.StrToInt(utils.GetEnv("DB_SLOW_THRESHOLD", strconv.Itoa(100)))) * time.Millisecond, -} diff --git a/src/configs/default.go b/src/configs/default.go deleted file mode 100644 index df05e3b..0000000 --- a/src/configs/default.go +++ /dev/null @@ -1,30 +0,0 @@ -package configs - -import ( - "github.com/OVINC-CN/DevTemplateGo/src/utils" - "github.com/sirupsen/logrus" - "os" - "time" -) - -type configModel struct { - AppCode string - AppSecret string - Debug bool - LogLevel logrus.Level - Port string - RequestTimeout time.Duration - TLSCert string - TLSKey string -} - -var Config = configModel{ - AppCode: os.Getenv("APP_CODE"), - AppSecret: os.Getenv("APP_SECRET"), - Debug: utils.StrToBool(utils.GetEnv("DEBUG", "false")), - LogLevel: logrus.InfoLevel, - Port: utils.GetEnv("PORT", ":8000"), - RequestTimeout: time.Duration(utils.StrToInt(utils.GetEnv("REQUEST_TIMEOUT", "10"))) * time.Second, - TLSCert: utils.GetEnv("TLS_CERT", ""), - TLSKey: utils.GetEnv("TLS_KEY", ""), -} diff --git a/src/configs/redis.go b/src/configs/redis.go deleted file mode 100644 index f8d26a8..0000000 --- a/src/configs/redis.go +++ /dev/null @@ -1,21 +0,0 @@ -package configs - -import "github.com/OVINC-CN/DevTemplateGo/src/utils" - -type redisConfigModel struct { - Host string - Port string - Password string - DB int - Prefix string - MaxConnections int -} - -var RedisConfig = redisConfigModel{ - Host: utils.GetEnv("REDIS_HOST", "127.0.0.1"), - Port: utils.GetEnv("REDIS_PORT", "6379"), - Password: utils.GetEnv("REDIS_PASSWORD", ""), - DB: utils.StrToInt(utils.GetEnv("REDIS_DB", "0")), - Prefix: utils.GetEnv("REDIS_PREFIX", ""), - MaxConnections: utils.StrToInt(utils.GetEnv("REDIS_MAX_CONNECTIONS", "10")), -} diff --git a/src/configs/session.go b/src/configs/session.go deleted file mode 100644 index d557e61..0000000 --- a/src/configs/session.go +++ /dev/null @@ -1,37 +0,0 @@ -package configs - -import ( - "fmt" - "github.com/OVINC-CN/DevTemplateGo/src/utils" - "os" - "strconv" -) - -type sessionConfigModel struct { - SessionCookieName string - SessionCookieAge int - SessionCookiePath string - SessionCookieDomain string - SessionCookieSecure bool - SessionCookieHttpOnly bool -} - -var SessionConfig = sessionConfigModel{ - SessionCookieName: buildSessionCookieName(), - SessionCookieAge: utils.StrToInt(utils.GetEnv("SESSION_COOKIE_AGE", strconv.Itoa(60*60*24*7))), - SessionCookiePath: utils.GetEnv("SESSION_COOKIE_PATH", "/"), - SessionCookieDomain: os.Getenv("SESSION_COOKIE_DOMAIN"), - SessionCookieSecure: utils.StrToBool(utils.GetEnv("SESSION_COOKIE_SECURE", "false")), - SessionCookieHttpOnly: utils.StrToBool(utils.GetEnv("SESSION_COOKIE_HTTP_ONLY", "false")), -} - -func buildSessionCookieName() (cookieName string) { - var devFlag string - if Config.Debug { - devFlag = "-dev" - } else { - devFlag = "" - } - cookieName = fmt.Sprintf("%s-session-id%s", Config.AppCode, devFlag) - return -} diff --git a/src/db/redis.go b/src/db/redis.go deleted file mode 100644 index 1c10982..0000000 --- a/src/db/redis.go +++ /dev/null @@ -1,21 +0,0 @@ -package db - -import ( - "context" - "fmt" - "github.com/OVINC-CN/DevTemplateGo/src/utils" - "github.com/redis/go-redis/v9" -) - -var Redis *redis.Client - -func InitRedisConnection(host, port, password string, db, maxConnections int) { - Redis = redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:%s", host, port), - Password: password, - DB: db, - MaxActiveConns: maxConnections, - }) - status := Redis.Ping(context.Background()) - utils.Logger.Infof("[InitRedisConnectionSuccess] %T %s", Redis, status) -} diff --git a/src/middlewares/request.go b/src/middlewares/request.go deleted file mode 100644 index b052bff..0000000 --- a/src/middlewares/request.go +++ /dev/null @@ -1,26 +0,0 @@ -package middlewares - -import ( - "github.com/OVINC-CN/DevTemplateGo/src/services/account" - "github.com/OVINC-CN/DevTemplateGo/src/utils" - "github.com/gin-gonic/gin" - "time" -) - -func RequestLogger() gin.HandlerFunc { - return func(c *gin.Context) { - // 初始化请求时间 - t := time.Now() - // 执行 - c.Next() - // 记录请求耗时 - duration := time.Since(t).Milliseconds() - // 记录用户 - username := account.GetContextUser(c).Username - if username == "" { - username = "-" - } - // 记录请求日志 - utils.ContextInfof(c, "[RequestLog] %s %s %s %d %d", username, c.Request.Method, c.Request.URL, duration, c.Writer.Status()) - } -} diff --git a/src/middlewares/timout.go b/src/middlewares/timout.go deleted file mode 100644 index 0e85eb6..0000000 --- a/src/middlewares/timout.go +++ /dev/null @@ -1,20 +0,0 @@ -package middlewares - -import ( - "github.com/OVINC-CN/DevTemplateGo/src/configs" - "github.com/gin-contrib/timeout" - "github.com/gin-gonic/gin" - "net/http" -) - -func Timeout() gin.HandlerFunc { - return timeout.New( - timeout.WithTimeout(configs.Config.RequestTimeout), - timeout.WithHandler(func(c *gin.Context) { - c.Next() - }), - timeout.WithResponse(func(c *gin.Context) { - c.AbortWithStatusJSON(http.StatusGatewayTimeout, gin.H{"error": "timeout"}) - }), - ) -} diff --git a/src/services/account/controls.go b/src/services/account/controls.go deleted file mode 100644 index 3b9f048..0000000 --- a/src/services/account/controls.go +++ /dev/null @@ -1,98 +0,0 @@ -package account - -import ( - "context" - "github.com/OVINC-CN/DevTemplateGo/src/configs" - "github.com/OVINC-CN/DevTemplateGo/src/db" - "github.com/gin-gonic/gin" - "net/http" -) - -func Login(c *gin.Context) { - // 验证请求 - var form loginForm - if err := c.ShouldBind(&form); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - // 获取用户 - user := User{Username: form.Username} - result := db.DB.First(&user) - if result.RowsAffected == 0 { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "user not exists"}) - return - } - // 校验密码 - passResult := user.CheckPassword(form.Password) - // 不通过,报错 - if !passResult { - c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "username or password invalid"}) - return - } - // 通过,发放令牌 - sessionID := user.CreateSessionID() - // 响应 - c.SetCookie( - configs.SessionConfig.SessionCookieName, - sessionID, - configs.SessionConfig.SessionCookieAge, - configs.SessionConfig.SessionCookiePath, - configs.SessionConfig.SessionCookieDomain, - configs.SessionConfig.SessionCookieSecure, - configs.SessionConfig.SessionCookieHttpOnly, - ) - c.JSON(http.StatusOK, gin.H{"data": gin.H{}}) -} - -func SignUp(c *gin.Context) { - // 验证请求 - var form signUpForm - if err := c.ShouldBind(&form); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - // 创建用户 - user := &User{Username: form.Username, NickName: form.Nickname, Enabled: true} - err := user.SetPassword(form.Password) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - createResult := db.DB.Create(user) - if createResult.Error != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": createResult.Error.Error()}) - return - } - // 发放令牌 - sessionID := user.CreateSessionID() - // 响应 - c.SetCookie( - configs.SessionConfig.SessionCookieName, - sessionID, - configs.SessionConfig.SessionCookieAge, - configs.SessionConfig.SessionCookiePath, - configs.SessionConfig.SessionCookieDomain, - configs.SessionConfig.SessionCookieSecure, - configs.SessionConfig.SessionCookieHttpOnly, - ) - c.JSON(http.StatusOK, gin.H{"data": gin.H{}}) -} - -func SignOut(c *gin.Context) { - sessionID, err := c.Cookie(configs.SessionConfig.SessionCookieName) - if err != nil { - c.AbortWithStatusJSON(http.StatusOK, gin.H{"error": err.Error()}) - return - } - db.Redis.Del(context.Background(), sessionID) - c.SetCookie( - configs.SessionConfig.SessionCookieName, - sessionID, - -1, - configs.SessionConfig.SessionCookiePath, - configs.SessionConfig.SessionCookieDomain, - configs.SessionConfig.SessionCookieSecure, - configs.SessionConfig.SessionCookieHttpOnly, - ) - c.JSON(http.StatusOK, gin.H{"data": gin.H{}}) -} diff --git a/src/services/account/forms.go b/src/services/account/forms.go deleted file mode 100644 index fbf0f44..0000000 --- a/src/services/account/forms.go +++ /dev/null @@ -1,12 +0,0 @@ -package account - -type loginForm struct { - Username string `form:"user" binding:"required"` - Password string `form:"password" binding:"required"` -} - -type signUpForm struct { - Username string `form:"user" binding:"required,min=4,max=16,username"` - Nickname string `form:"nickname" binding:"required,min=4,max=16"` - Password string `form:"password" binding:"required,min=6,max=16"` -} diff --git a/src/services/account/utils.go b/src/services/account/utils.go deleted file mode 100644 index 5c721c6..0000000 --- a/src/services/account/utils.go +++ /dev/null @@ -1,14 +0,0 @@ -package account - -import ( - "github.com/gin-gonic/gin" -) - -func GetContextUser(c *gin.Context) *User { - if val, ok := c.Get("User"); ok { - if user, ok := val.(*User); ok { - return user - } - } - return &User{} -} diff --git a/src/services/home/controls.go b/src/services/home/controls.go deleted file mode 100644 index 2c3a07d..0000000 --- a/src/services/home/controls.go +++ /dev/null @@ -1,20 +0,0 @@ -package home - -import ( - "github.com/OVINC-CN/DevTemplateGo/src/services/account" - "github.com/gin-gonic/gin" - "net/http" -) - -func Home(c *gin.Context) { - user := account.GetContextUser(c) - c.JSON( - http.StatusOK, - gin.H{ - "data": gin.H{ - "username": user.Username, - "nickname": user.NickName, - }, - }, - ) -} diff --git a/go.mod b/{{cookiecutter.project_name}}/go.mod similarity index 90% rename from go.mod rename to {{cookiecutter.project_name}}/go.mod index 89a2222..3e52011 100644 --- a/go.mod +++ b/{{cookiecutter.project_name}}/go.mod @@ -21,6 +21,8 @@ require ( github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/cors v1.5.0 // indirect + github.com/gin-contrib/i18n v1.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -28,12 +30,14 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/joho/godotenv v1.5.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/leodido/go-urn v1.4.0 // indirect 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/nicksnyder/go-i18n/v2 v2.2.1 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect diff --git a/go.sum b/{{cookiecutter.project_name}}/go.sum similarity index 75% rename from go.sum rename to {{cookiecutter.project_name}}/go.sum index 4a0455e..7da39fb 100644 --- a/go.sum +++ b/{{cookiecutter.project_name}}/go.sum @@ -1,3 +1,4 @@ +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= @@ -20,6 +21,10 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= +github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= +github.com/gin-contrib/i18n v1.1.0 h1:kmNinScsMKAsgGg0lvxCm6ecifzmwvxZIu0ImLHVfEA= +github.com/gin-contrib/i18n v1.1.0/go.mod h1:n0OOIxqHLXT445Vv8CIybqwKVGUekjHP/vMl1rI62GI= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/timeout v0.0.6 h1:hRx+DnzQvHAsM7T3SGgIPOOMmyM/Z+szE5IDqf91Mog= @@ -46,6 +51,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -61,6 +68,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/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA= +github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -83,25 +92,51 @@ 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= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +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/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-20220715151400-c0bba94af5f8/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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.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/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/{{cookiecutter.project_name}}/src/configs/configs.go b/{{cookiecutter.project_name}}/src/configs/configs.go new file mode 100644 index 0000000..f7381b3 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/configs/configs.go @@ -0,0 +1,76 @@ +package configs + +import ( + "fmt" + "github.com/OVINC-CN/DevTemplateGo/src/utils" + "github.com/sirupsen/logrus" + "os" + "strconv" + "strings" + "time" +) + +var Config configModel + +func InitConfig() { + loadEnv() + Config = configModel{ + AppCode: os.Getenv("APP_CODE"), + AppSecret: os.Getenv("APP_SECRET"), + Debug: utils.StrToBool(utils.GetEnv("DEBUG", "false")), + LogLevel: logrus.InfoLevel, + serverConfigModel: serverConfigModel{ + Addr: utils.GetEnv("SERVER_ADDR", ":8000"), + RequestTimeout: time.Duration(utils.StrToInt(utils.GetEnv("REQUEST_TIMEOUT", "10"))) * time.Second, + TLSCert: utils.GetEnv("TLS_CERT", ""), + TLSKey: utils.GetEnv("TLS_KEY", ""), + }, + sessionConfigModel: sessionConfigModel{ + SessionCookieName: buildSessionCookieName(), + SessionCookieAge: utils.StrToInt(utils.GetEnv("SESSION_COOKIE_AGE", strconv.Itoa(60*60*24*7))), + SessionCookiePath: utils.GetEnv("SESSION_COOKIE_PATH", "/"), + SessionCookieDomain: os.Getenv("SESSION_COOKIE_DOMAIN"), + SessionCookieSecure: utils.StrToBool(utils.GetEnv("SESSION_COOKIE_SECURE", "false")), + SessionCookieHttpOnly: utils.StrToBool(utils.GetEnv("SESSION_COOKIE_HTTP_ONLY", "false")), + }, + corsConfigModel: corsConfigModel{ + AllowOrigins: strings.Split(utils.GetEnv("CORS_ALLOW_ORIGINS", ""), ";"), + AllowMethods: strings.Split(utils.GetEnv("CORS_ALLOW_METHODS", "*"), ";"), + AllowHeaders: strings.Split(utils.GetEnv("CORS_ALLOW_HEADERS", "Content-Type"), ";"), + ExposeHeaders: strings.Split(utils.GetEnv("CORS_EXPOSE_HEADERS", "Content-Length"), ";"), + }, + dbConfigModel: dbConfigModel{ + DBHost: utils.GetEnv("DB_HOST", "127.0.0.1"), + DBPort: utils.GetEnv("DB_PORT", "3306"), + DBUser: utils.GetEnv("DB_USER", ""), + DBPassword: utils.GetEnv("DB_PASSWORD", ""), + DBName: utils.GetEnv("DB_NAME", ""), + DBMaxConnections: utils.StrToInt(utils.GetEnv("DB_MAX_CONNECTIONS", "10")), + DBConnectionTimeOut: time.Duration(utils.StrToInt(utils.GetEnv("DB_CONNECTION_TIMEOUT", strconv.Itoa(60*60)))) * time.Second, + DBSlowThreshold: time.Duration(utils.StrToInt(utils.GetEnv("DB_SLOW_THRESHOLD", strconv.Itoa(100)))) * time.Millisecond, + }, + redisConfigModel: redisConfigModel{ + RedisHost: utils.GetEnv("REDIS_HOST", "127.0.0.1"), + RedisPort: utils.GetEnv("REDIS_PORT", "6379"), + RedisPassword: utils.GetEnv("REDIS_PASSWORD", ""), + RedisDB: utils.StrToInt(utils.GetEnv("REDIS_DB", "0")), + RedisPrefix: utils.GetEnv("REDIS_PREFIX", ""), + RedisMaxConnections: utils.StrToInt(utils.GetEnv("REDIS_MAX_CONNECTIONS", "10")), + }, + traceConfigModel: traceConfigModel{ + RUMID: utils.GetEnv("RUM_ID", ""), + RUMHost: utils.GetEnv("RUM_HOST", "https://rumt-zh.com"), + }, + } +} + +func buildSessionCookieName() (cookieName string) { + var devFlag string + if Config.Debug { + devFlag = "-dev" + } else { + devFlag = "" + } + cookieName = fmt.Sprintf("%s-session-id%s", Config.AppCode, devFlag) + return +} diff --git a/{{cookiecutter.project_name}}/src/configs/env.go b/{{cookiecutter.project_name}}/src/configs/env.go new file mode 100644 index 0000000..8ff1773 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/configs/env.go @@ -0,0 +1,20 @@ +package configs + +import ( + "fmt" + "github.com/joho/godotenv" + "os" + "path/filepath" + "runtime" +) + +func loadEnv() { + _, currentPath, _, ok := runtime.Caller(0) + if !ok { + panic("[SystemLoadFailed] LoadEnv") + } + envFilePath := fmt.Sprintf("%s/.env", filepath.Dir(filepath.Dir(filepath.Dir(currentPath)))) + if _, err := os.Stat(envFilePath); err == nil { + _ = godotenv.Load(envFilePath) + } +} diff --git a/{{cookiecutter.project_name}}/src/configs/models.go b/{{cookiecutter.project_name}}/src/configs/models.go new file mode 100644 index 0000000..ebc2bf5 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/configs/models.go @@ -0,0 +1,67 @@ +package configs + +import ( + "github.com/sirupsen/logrus" + "time" +) + +type configModel struct { + AppCode string + AppSecret string + Debug bool + LogLevel logrus.Level + serverConfigModel + traceConfigModel + corsConfigModel + dbConfigModel + sessionConfigModel + redisConfigModel +} + +type serverConfigModel struct { + Addr string + RequestTimeout time.Duration + TLSCert string + TLSKey string +} + +type corsConfigModel struct { + AllowOrigins []string + AllowMethods []string + AllowHeaders []string + ExposeHeaders []string +} + +type dbConfigModel struct { + DBHost string + DBPort string + DBUser string + DBPassword string + DBName string + DBMaxConnections int + DBConnectionTimeOut time.Duration + DBSlowThreshold time.Duration +} + +type sessionConfigModel struct { + SessionCookieName string + SessionCookieAge int + SessionCookiePath string + SessionCookieDomain string + SessionCookieSecure bool + SessionCookieHttpOnly bool +} + +type redisConfigModel struct { + RedisHost string + RedisPort string + RedisPassword string + RedisDB int + RedisPrefix string + RedisMaxConnections int +} + +type traceConfigModel struct { + RUMID string + RUMHost string +} diff --git a/{{cookiecutter.project_name}}/src/core/errors.go b/{{cookiecutter.project_name}}/src/core/errors.go new file mode 100644 index 0000000..fbfd8ae --- /dev/null +++ b/{{cookiecutter.project_name}}/src/core/errors.go @@ -0,0 +1,27 @@ +package core + +import ( + "net/http" +) + +type APIError struct { + Status int + Message string + Detail *map[string]any +} + +func (err *APIError) Error() string { + return err.Message +} + +func NewError(status int, message string, detail *map[string]any) *APIError { + return &APIError{ + Status: status, + Message: message, + Detail: detail, + } +} + +var ( + LoginRequired = NewError(http.StatusUnauthorized, "login required", nil) +) diff --git a/src/db/mysql.go b/{{cookiecutter.project_name}}/src/db/mysql.go similarity index 100% rename from src/db/mysql.go rename to {{cookiecutter.project_name}}/src/db/mysql.go diff --git a/{{cookiecutter.project_name}}/src/db/redis.go b/{{cookiecutter.project_name}}/src/db/redis.go new file mode 100644 index 0000000..5850830 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/db/redis.go @@ -0,0 +1,57 @@ +package db + +import ( + "context" + "fmt" + "github.com/OVINC-CN/DevTemplateGo/src/configs" + "github.com/OVINC-CN/DevTemplateGo/src/utils" + "github.com/redis/go-redis/v9" + "time" +) + +var Redis redisClient + +type redisClient struct { + backend *redis.Client +} + +func (client *redisClient) buildCacheKey(keyType, key string) string { + return fmt.Sprintf("%s:%s:%s", configs.Config.RedisPrefix, keyType, key) +} + +func (client *redisClient) Ping(ctx context.Context) *redis.StatusCmd { + return client.backend.Ping(ctx) +} + +func (client *redisClient) Set(ctx context.Context, keyType, key string, value interface{}, expiration time.Duration) *redis.StatusCmd { + cacheKey := client.buildCacheKey(keyType, key) + return client.backend.Set(ctx, cacheKey, value, expiration) +} + +func (client *redisClient) Get(ctx context.Context, keyType, key string) *redis.StringCmd { + cacheKey := client.buildCacheKey(keyType, key) + return client.backend.Get(ctx, cacheKey) +} + +func (client *redisClient) Del(ctx context.Context, keyType string, keys ...string) *redis.IntCmd { + var cacheKeys []string + for _, key := range keys { + cacheKeys = append(cacheKeys, client.buildCacheKey(keyType, key)) + } + return client.backend.Del(ctx, cacheKeys...) +} + +func InitRedisConnection(host, port, password string, db, maxConnections int) { + Redis = redisClient{ + backend: redis.NewClient( + &redis.Options{ + Addr: fmt.Sprintf("%s:%s", host, port), + Password: password, + DB: db, + MaxActiveConns: maxConnections, + }, + ), + } + status := Redis.Ping(context.Background()) + utils.Logger.Infof("[InitRedisConnectionSuccess] %T %s", Redis, status) +} diff --git a/{{cookiecutter.project_name}}/src/locale/en.json b/{{cookiecutter.project_name}}/src/locale/en.json new file mode 100644 index 0000000..1844d53 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/locale/en.json @@ -0,0 +1,9 @@ +{ + "timeout": "Timeout", + "login required": "Login required", + "user not exists": "User invalid", + "username or password invalid": "Username or password invalid", + "sign up failed": "Sign up failed", + "session id not exist": "SessionID invalid", + "token invalid": "Token Invalid" +} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/src/locale/zh.json b/{{cookiecutter.project_name}}/src/locale/zh.json new file mode 100644 index 0000000..1c6ed05 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/locale/zh.json @@ -0,0 +1,9 @@ +{ + "timeout": "请求超时", + "login required": "未登录", + "user not exists": "用户不存在", + "username or password invalid": "用户名或密码错误", + "sign up failed": "注册失败", + "session id not exist": "SessionID 不存在", + "token invalid": "Token 不合法" +} \ No newline at end of file diff --git a/src/middlewares/auth.go b/{{cookiecutter.project_name}}/src/middlewares/auth.go similarity index 67% rename from src/middlewares/auth.go rename to {{cookiecutter.project_name}}/src/middlewares/auth.go index 6a97294..63fa954 100644 --- a/src/middlewares/auth.go +++ b/{{cookiecutter.project_name}}/src/middlewares/auth.go @@ -2,18 +2,17 @@ package middlewares import ( "github.com/OVINC-CN/DevTemplateGo/src/configs" + "github.com/OVINC-CN/DevTemplateGo/src/core" "github.com/OVINC-CN/DevTemplateGo/src/services/account" "github.com/gin-gonic/gin" - "net/http" ) func Authenticate() gin.HandlerFunc { return func(c *gin.Context) { // 获取用户身份 - sessionID, err := c.Cookie(configs.SessionConfig.SessionCookieName) + sessionID, err := c.Cookie(configs.Config.SessionCookieName) if err == nil { - user := account.User{} - user.LoadUserBySessionID(sessionID) + user := account.LoadUserBySessionID(sessionID) if user.Enabled { c.Set("User", &user) } @@ -26,8 +25,7 @@ func LoginRequired() gin.HandlerFunc { return func(c *gin.Context) { user := account.GetContextUser(c) if user.Username == "" { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "login required"}) - return + panic(core.LoginRequired) } c.Next() } diff --git a/{{cookiecutter.project_name}}/src/middlewares/cors.go b/{{cookiecutter.project_name}}/src/middlewares/cors.go new file mode 100644 index 0000000..d94a833 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/middlewares/cors.go @@ -0,0 +1,17 @@ +package middlewares + +import ( + "github.com/OVINC-CN/DevTemplateGo/src/configs" + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +func CORS() gin.HandlerFunc { + return cors.New(cors.Config{ + AllowOrigins: configs.Config.AllowOrigins, + AllowMethods: configs.Config.AllowMethods, + AllowHeaders: configs.Config.AllowHeaders, + ExposeHeaders: configs.Config.ExposeHeaders, + AllowCredentials: true, + }) +} diff --git a/{{cookiecutter.project_name}}/src/middlewares/locale.go b/{{cookiecutter.project_name}}/src/middlewares/locale.go new file mode 100644 index 0000000..46f1d5c --- /dev/null +++ b/{{cookiecutter.project_name}}/src/middlewares/locale.go @@ -0,0 +1,29 @@ +package middlewares + +import ( + "encoding/json" + "fmt" + "github.com/OVINC-CN/DevTemplateGo/src/utils" + ginI18n "github.com/gin-contrib/i18n" + "github.com/gin-gonic/gin" + "golang.org/x/text/language" + "path/filepath" + "runtime" +) + +func Locale() gin.HandlerFunc { + _, currentPath, _, ok := runtime.Caller(0) + if !ok { + utils.Logger.Warningf("[LoadLocaleFileFailed] %T", ok) + panic("LoadLocaleFileFailed") + } + return ginI18n.Localize( + ginI18n.WithBundle(&ginI18n.BundleCfg{ + RootPath: fmt.Sprintf("%s/locale", filepath.Dir(filepath.Dir(currentPath))), + AcceptLanguage: []language.Tag{language.Chinese, language.English}, + DefaultLanguage: language.Chinese, + UnmarshalFunc: json.Unmarshal, + FormatBundleFile: "json", + }), + ) +} diff --git a/src/middlewares/logger.go b/{{cookiecutter.project_name}}/src/middlewares/logger.go similarity index 91% rename from src/middlewares/logger.go rename to {{cookiecutter.project_name}}/src/middlewares/logger.go index 104e283..a2343a0 100644 --- a/src/middlewares/logger.go +++ b/{{cookiecutter.project_name}}/src/middlewares/logger.go @@ -10,6 +10,7 @@ func InitLogger() gin.HandlerFunc { return func(c *gin.Context) { // 初始化请求ID requestID := utils.GenerateUniqID() + c.Header("Request-ID", requestID) // 初始化Logger logEntry := utils.Logger.WithFields(logrus.Fields{"request_id": requestID}) c.Set("logEntry", logEntry) diff --git a/{{cookiecutter.project_name}}/src/middlewares/recovery.go b/{{cookiecutter.project_name}}/src/middlewares/recovery.go new file mode 100644 index 0000000..d95156d --- /dev/null +++ b/{{cookiecutter.project_name}}/src/middlewares/recovery.go @@ -0,0 +1,55 @@ +package middlewares + +import ( + "github.com/OVINC-CN/DevTemplateGo/src/core" + "github.com/OVINC-CN/DevTemplateGo/src/utils" + ginI18n "github.com/gin-contrib/i18n" + "github.com/gin-gonic/gin" + "net/http" + "runtime/debug" +) + +func Recovery() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if p := recover(); p != nil { + switch err := p.(type) { + case *core.APIError: + utils.ContextInfof(c, "[RequestError] %s %v", err.Error(), err.Detail) + message, translateError := ginI18n.GetMessage(c, err.Error()) + if translateError != nil { + message = err.Error() + } + c.AbortWithStatusJSON( + err.Status, + gin.H{ + "error": gin.H{ + "message": message, + "detail": err.Detail, + }, + }, + ) + case error: + utils.ContextErrorf(c, "[ServerError] %s\n%s", p, debug.Stack()) + message, translateError := ginI18n.GetMessage(c, err.Error()) + if translateError != nil { + message = err.Error() + } + c.AbortWithStatusJSON( + http.StatusInternalServerError, + gin.H{ + "error": gin.H{ + "message": message, + "detail": &map[string]any{}, + }, + }, + ) + default: + utils.ContextErrorf(c, "[ServerError] %s\n%s", p, debug.Stack()) + c.AbortWithStatus(http.StatusInternalServerError) + } + } + }() + c.Next() + } +} diff --git a/{{cookiecutter.project_name}}/src/middlewares/request.go b/{{cookiecutter.project_name}}/src/middlewares/request.go new file mode 100644 index 0000000..9114d11 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/middlewares/request.go @@ -0,0 +1,45 @@ +package middlewares + +import ( + "github.com/OVINC-CN/DevTemplateGo/src/core" + "github.com/OVINC-CN/DevTemplateGo/src/services/account" + "github.com/OVINC-CN/DevTemplateGo/src/utils" + "github.com/gin-gonic/gin" + "net/http" + "time" +) + +func RequestLogger() gin.HandlerFunc { + return func(c *gin.Context) { + // 初始化请求时间 + t := time.Now() + // 记录日志 + defer func() { + statusCode := c.Writer.Status() + p := recover() + if p != nil { + switch err := p.(type) { + case *core.APIError: + statusCode = err.Status + case error: + statusCode = http.StatusInternalServerError + } + } + // 记录请求耗时 + duration := time.Since(t).Milliseconds() + // 记录用户 + username := account.GetContextUser(c).Username + if username == "" { + username = "-" + } + // 记录请求日志 + utils.ContextInfof(c, "[RequestLog] %s %s %s %d %d", username, c.Request.Method, c.Request.URL, duration, statusCode) + // 如有错误继续抛出 + if p != nil { + panic(p) + } + }() + // 执行 + c.Next() + } +} diff --git a/src/server/engine.go b/{{cookiecutter.project_name}}/src/server/engine.go similarity index 62% rename from src/server/engine.go rename to {{cookiecutter.project_name}}/src/server/engine.go index 7c42abd..8adcde1 100644 --- a/src/server/engine.go +++ b/{{cookiecutter.project_name}}/src/server/engine.go @@ -17,22 +17,34 @@ func setupRouter() (engine *gin.Engine) { } engine = gin.New() engine.RedirectTrailingSlash = false - engine.Use(middlewares.InitLogger(), middlewares.RequestLogger(), middlewares.Timeout(), middlewares.Authenticate()) + engine.Use( + middlewares.Recovery(), + middlewares.CORS(), + middlewares.Locale(), + middlewares.InitLogger(), + middlewares.RequestLogger(), + middlewares.Authenticate(), + ) + // 注册校验器 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { _ = v.RegisterValidation("username", account.UsernameValidator) } + // Home homeGroup := engine.Group("/") { homeGroup.GET("", home.Home) + homeGroup.GET("/rum_config/", home.RumConfig) } + // Account accountGroup := engine.Group("/account/") { - accountGroup.POST("/signin/", account.Login) - accountGroup.POST("/signup/", account.SignUp) - accountGroup.POST("/signout/", middlewares.LoginRequired(), account.SignOut) + accountGroup.POST("/sign_in/", account.SignIn) + accountGroup.POST("/sign_up/", account.SignUp) + accountGroup.POST("/sign_out/", middlewares.LoginRequired(), account.SignOut) + accountGroup.GET("/user_info/", middlewares.LoginRequired(), account.UserInfo) } return } diff --git a/src/server/main.go b/{{cookiecutter.project_name}}/src/server/main.go similarity index 100% rename from src/server/main.go rename to {{cookiecutter.project_name}}/src/server/main.go diff --git a/src/server/server.go b/{{cookiecutter.project_name}}/src/server/server.go similarity index 68% rename from src/server/server.go rename to {{cookiecutter.project_name}}/src/server/server.go index 3209bdb..a035be8 100644 --- a/src/server/server.go +++ b/{{cookiecutter.project_name}}/src/server/server.go @@ -10,6 +10,9 @@ import ( ) func startServer() { + var err error + // init config + configs.InitConfig() // init log utils.Logger = utils.InitLogger( configs.Config.Debug, @@ -18,17 +21,17 @@ func startServer() { utils.DbLogger = utils.InitDBLogger( configs.Config.Debug, configs.Config.LogLevel, - configs.DBConfig.SlowThreshold, + configs.Config.DBSlowThreshold, ) // init db db.InitDBConnection( - configs.DBConfig.Host, - configs.DBConfig.Port, - configs.DBConfig.User, - configs.DBConfig.Password, - configs.DBConfig.Name, - configs.DBConfig.MaxConnections, - configs.DBConfig.ConnectionTimeOut, + configs.Config.DBHost, + configs.Config.DBPort, + configs.Config.DBUser, + configs.Config.DBPassword, + configs.Config.DBName, + configs.Config.DBMaxConnections, + configs.Config.DBConnectionTimeOut, &gorm.Config{ Logger: utils.DbLogger, }, @@ -36,11 +39,11 @@ func startServer() { migrate() // init redis db.InitRedisConnection( - configs.RedisConfig.Host, - configs.RedisConfig.Port, - configs.RedisConfig.Password, - configs.RedisConfig.DB, - configs.RedisConfig.MaxConnections, + configs.Config.RedisHost, + configs.Config.RedisPort, + configs.Config.RedisPassword, + configs.Config.RedisDB, + configs.Config.RedisMaxConnections, ) // init cpu threads := runtime.NumCPU() @@ -48,11 +51,10 @@ func startServer() { utils.Logger.Infof("[InitCPUSuccess] Runs on %d CPUs", threads) // init gin engine := setupRouter() - var err error if configs.Config.TLSCert != "" { - err = engine.RunTLS(configs.Config.Port, configs.Config.TLSCert, configs.Config.TLSKey) + err = engine.RunTLS(configs.Config.Addr, configs.Config.TLSCert, configs.Config.TLSKey) } else { - err = engine.Run(configs.Config.Port) + err = engine.Run(configs.Config.Addr) } if err != nil { utils.Logger.Infof("[ServerStartFailed] %s", err) diff --git a/{{cookiecutter.project_name}}/src/services/account/controls.go b/{{cookiecutter.project_name}}/src/services/account/controls.go new file mode 100644 index 0000000..31dd07c --- /dev/null +++ b/{{cookiecutter.project_name}}/src/services/account/controls.go @@ -0,0 +1,101 @@ +package account + +import ( + "context" + "github.com/OVINC-CN/DevTemplateGo/src/configs" + "github.com/OVINC-CN/DevTemplateGo/src/core" + "github.com/OVINC-CN/DevTemplateGo/src/db" + "github.com/gin-gonic/gin" + "net/http" +) + +func SignIn(c *gin.Context) { + // 验证请求 + var form loginForm + if err := c.ShouldBind(&form); err != nil { + panic(core.NewError(http.StatusBadRequest, err.Error(), nil)) + } + // 获取用户 + user := User{Username: form.Username} + result := db.DB.First(&user) + if result.RowsAffected == 0 { + panic(UserNotExist) + } + // 校验密码 + passResult := user.CheckPassword(form.Password) + // 不通过,报错 + if !passResult { + panic(SignInFailed) + } + // 通过,发放令牌 + sessionID := user.CreateSessionID() + // 响应 + c.SetCookie( + configs.Config.SessionCookieName, + sessionID, + configs.Config.SessionCookieAge, + configs.Config.SessionCookiePath, + configs.Config.SessionCookieDomain, + configs.Config.SessionCookieSecure, + configs.Config.SessionCookieHttpOnly, + ) + c.JSON(http.StatusOK, gin.H{"data": gin.H{}}) +} + +func SignUp(c *gin.Context) { + // 验证请求 + var form signUpForm + if err := c.ShouldBind(&form); err != nil { + panic(core.NewError(http.StatusBadRequest, err.Error(), nil)) + } + // 创建用户 + user := &User{Username: form.Username, NickName: form.Nickname, Enabled: true} + err := user.SetPassword(form.Password) + if err != nil { + panic(SignUpFailed) + } + createResult := db.DB.Create(user) + if createResult.Error != nil { + panic(SignUpFailed) + } + // 发放令牌 + sessionID := user.CreateSessionID() + // 响应 + c.SetCookie( + configs.Config.SessionCookieName, + sessionID, + configs.Config.SessionCookieAge, + configs.Config.SessionCookiePath, + configs.Config.SessionCookieDomain, + configs.Config.SessionCookieSecure, + configs.Config.SessionCookieHttpOnly, + ) + c.JSON(http.StatusOK, gin.H{"data": gin.H{}}) +} + +func SignOut(c *gin.Context) { + sessionID, err := c.Cookie(configs.Config.SessionCookieName) + if err != nil { + panic(SessionIDNotExists) + + } + db.Redis.Del(context.Background(), "sessionID", sessionID) + c.SetCookie( + configs.Config.SessionCookieName, + sessionID, + -1, + configs.Config.SessionCookiePath, + configs.Config.SessionCookieDomain, + configs.Config.SessionCookieSecure, + configs.Config.SessionCookieHttpOnly, + ) + c.JSON(http.StatusOK, gin.H{"data": gin.H{}}) +} + +func UserInfo(c *gin.Context) { + user := GetContextUser(c) + c.JSON( + http.StatusOK, + gin.H{"data": gin.H{"username": user.Username, "nick_name": user.NickName}}, + ) +} diff --git a/{{cookiecutter.project_name}}/src/services/account/errors.go b/{{cookiecutter.project_name}}/src/services/account/errors.go new file mode 100644 index 0000000..231a9ca --- /dev/null +++ b/{{cookiecutter.project_name}}/src/services/account/errors.go @@ -0,0 +1,13 @@ +package account + +import ( + "github.com/OVINC-CN/DevTemplateGo/src/core" + "net/http" +) + +var ( + SignInFailed = core.NewError(http.StatusUnauthorized, "username or password invalid", nil) + SignUpFailed = core.NewError(http.StatusBadRequest, "sign up failed", nil) + SessionIDNotExists = core.NewError(http.StatusBadRequest, "session id not exist", nil) + UserNotExist = core.NewError(http.StatusNotFound, "user not exist", nil) +) diff --git a/{{cookiecutter.project_name}}/src/services/account/forms.go b/{{cookiecutter.project_name}}/src/services/account/forms.go new file mode 100644 index 0000000..13f7eb2 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/services/account/forms.go @@ -0,0 +1,12 @@ +package account + +type loginForm struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} + +type signUpForm struct { + Username string `json:"username" binding:"required,min=4,max=16,username"` + Nickname string `json:"nickname" binding:"required,min=4,max=16"` + Password string `json:"password" binding:"required,min=6,max=64"` +} diff --git a/src/services/account/models.go b/{{cookiecutter.project_name}}/src/services/account/models.go similarity index 66% rename from src/services/account/models.go rename to {{cookiecutter.project_name}}/src/services/account/models.go index a1c6d5d..8e91ed6 100644 --- a/src/services/account/models.go +++ b/{{cookiecutter.project_name}}/src/services/account/models.go @@ -10,11 +10,16 @@ import ( ) type User struct { - Username string `json:"username" gorm:"primaryKey"` - NickName string `json:"nick_name"` - Password string `json:"password"` - JoinAt int64 `json:"join_at" gorm:"autoCreateTime:milli"` - Enabled bool `json:"enabled"` + Username string `json:"username" gorm:"primaryKey"` + NickName string `json:"nick_name"` + Password string `json:"password"` + PhoneNumber string `json:"phone_number"` + Email string `json:"email"` + WeChatOpenID string `json:"wechat_open_id"` + WeChatUnionID string `json:"wechat_union_id"` + WeChatAvatar string `json:"wechat_avatar"` + JoinAt int64 `json:"join_at" gorm:"autoCreateTime:milli"` + Enabled bool `json:"enabled"` } func (user *User) SetPassword(password string) (err error) { @@ -37,23 +42,18 @@ func (user *User) CreateSessionID() (sessionID string) { db.DB.Create(&UserSession{ Username: user.Username, SessionID: sessionID, - ExpiredAt: time.Now().Add(time.Duration(configs.SessionConfig.SessionCookieAge) * time.Second).UnixMilli(), + ExpiredAt: time.Now().Add(time.Duration(configs.Config.SessionCookieAge) * time.Second).UnixMilli(), }) db.Redis.Set( context.Background(), + "sessionID", sessionID, user.Username, - time.Duration(configs.SessionConfig.SessionCookieAge)*time.Second, + time.Duration(configs.Config.SessionCookieAge)*time.Second, ) return } -func (user *User) LoadUserBySessionID(sessionID string) { - result := db.Redis.Get(context.Background(), sessionID) - user.Username = result.Val() - db.DB.First(user) -} - type UserSession struct { ID uint `json:"id" gorm:"primaryKey"` Username string `json:"username" gorm:"index"` diff --git a/{{cookiecutter.project_name}}/src/services/account/utils.go b/{{cookiecutter.project_name}}/src/services/account/utils.go new file mode 100644 index 0000000..a04e959 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/services/account/utils.go @@ -0,0 +1,26 @@ +package account + +import ( + "context" + "github.com/OVINC-CN/DevTemplateGo/src/db" + "github.com/gin-gonic/gin" +) + +func GetContextUser(c *gin.Context) *User { + if val, ok := c.Get("User"); ok { + if user, ok := val.(*User); ok { + return user + } + } + return &User{} +} + +func LoadUserBySessionID(sessionID string) *User { + result := db.Redis.Get(context.Background(), "sessionID", sessionID) + user := &User{Username: result.Val()} + dbResult := db.DB.First(user) + if dbResult.Error == nil { + return user + } + return &User{} +} diff --git a/src/services/account/validators.go b/{{cookiecutter.project_name}}/src/services/account/validators.go similarity index 100% rename from src/services/account/validators.go rename to {{cookiecutter.project_name}}/src/services/account/validators.go diff --git a/{{cookiecutter.project_name}}/src/services/home/controls.go b/{{cookiecutter.project_name}}/src/services/home/controls.go new file mode 100644 index 0000000..9d0bba2 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/services/home/controls.go @@ -0,0 +1,36 @@ +package home + +import ( + "github.com/OVINC-CN/DevTemplateGo/src/configs" + "github.com/OVINC-CN/DevTemplateGo/src/services/account" + "github.com/gin-gonic/gin" + "net/http" +) + +func Home(c *gin.Context) { + user := account.GetContextUser(c) + c.JSON( + http.StatusOK, + gin.H{ + "data": gin.H{ + "username": user.Username, + "nickname": user.NickName, + }, + }, + ) +} + +func RumConfig(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "data": gin.H{ + "id": configs.Config.RUMID, + "reportApiSpeed": true, + "reportAssetSpeed": true, + "spa": true, + "hostUrl": configs.Config.RUMHost, + }, + }, + ) +} diff --git a/src/utils/log.go b/{{cookiecutter.project_name}}/src/utils/log.go similarity index 100% rename from src/utils/log.go rename to {{cookiecutter.project_name}}/src/utils/log.go diff --git a/src/utils/tools.go b/{{cookiecutter.project_name}}/src/utils/tools.go similarity index 100% rename from src/utils/tools.go rename to {{cookiecutter.project_name}}/src/utils/tools.go