commit 9ac7597a6204a4646a082ff90435a6cc2f757b3e Author: zhangkun9038@dingtalk.com Date: Sun Feb 16 12:18:39 2025 +0800 first add diff --git a/api/routes.go b/api/routes.go new file mode 100644 index 0000000..a711fb0 --- /dev/null +++ b/api/routes.go @@ -0,0 +1,27 @@ +package api + +import ( + "zjmud.xyz/phyer/rbcp/controllers" + "zjmud.xyz/phyer/rbcp/middleware" + + "github.com/gin-gonic/gin" +) + +func SetupRoutes(r *gin.Engine) { + auth := r.Group("/auth") + { + auth.POST("/login", controllers.Login) + auth.POST("/register", controllers.Register) + } + + admin := r.Group("/admin") + admin.Use(middleware.RBACMiddleware("admin", "full-access")) + { + admin.POST("/roles", controllers.CreateRole) + admin.POST("/users", controllers.CreateUser) + admin.POST("/permissions", controllers.CreatePermission) + admin.POST("/user-groups", controllers.CreateUserGroup) + admin.POST("/assign-role", controllers.AssignRoleToUser) + admin.POST("/assign-permission", controllers.AssignPermissionToRole) + } +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..79e0096 --- /dev/null +++ b/config/config.go @@ -0,0 +1,57 @@ +package controllers + +import ( + "net/http" + "time" + "zjmud.xyz/phyer/rbcp/models" + "zjmud.xyz/phyer/rbcp/services" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" +) + +func Login(c *gin.Context) { + var loginData struct { + Username string `json:"username"` + Password string `json:"password"` + } + + if err := c.ShouldBindJSON(&loginData); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user, err := services.Authenticate(loginData.Username, loginData.Password) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) + return + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "sub": user.ID, + "exp": time.Now().Add(time.Hour * 24).Unix(), + }) + + tokenString, err := token.SignedString([]byte("your-256-bit-secret")) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) + return + } + + c.JSON(http.StatusOK, gin.H{"token": tokenString}) +} + +func Register(c *gin.Context) { + var user models.User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := services.CreateUser(&user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, user) +} diff --git a/controllers/auth.go b/controllers/auth.go new file mode 100644 index 0000000..79e0096 --- /dev/null +++ b/controllers/auth.go @@ -0,0 +1,57 @@ +package controllers + +import ( + "net/http" + "time" + "zjmud.xyz/phyer/rbcp/models" + "zjmud.xyz/phyer/rbcp/services" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" +) + +func Login(c *gin.Context) { + var loginData struct { + Username string `json:"username"` + Password string `json:"password"` + } + + if err := c.ShouldBindJSON(&loginData); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user, err := services.Authenticate(loginData.Username, loginData.Password) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) + return + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "sub": user.ID, + "exp": time.Now().Add(time.Hour * 24).Unix(), + }) + + tokenString, err := token.SignedString([]byte("your-256-bit-secret")) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) + return + } + + c.JSON(http.StatusOK, gin.H{"token": tokenString}) +} + +func Register(c *gin.Context) { + var user models.User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := services.CreateUser(&user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, user) +} diff --git a/controllers/rbac.go b/controllers/rbac.go new file mode 100644 index 0000000..3236f59 --- /dev/null +++ b/controllers/rbac.go @@ -0,0 +1,24 @@ +package controllers + +import ( + "net/http" + "zjmud.xyz/phyer/rbcp/models" + "zjmud.xyz/phyer/rbcp/services" + + "github.com/gin-gonic/gin" +) + +func CreateRole(c *gin.Context) { + var role models.Role + if err := c.ShouldBindJSON(&role); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := services.CreateRole(&role); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, role) +} diff --git a/controllers/user.go b/controllers/user.go new file mode 100644 index 0000000..eb9506c --- /dev/null +++ b/controllers/user.go @@ -0,0 +1,24 @@ +package controllers + +import ( + "net/http" + "zjmud.xyz/phyer/rbcp/models" + "zjmud.xyz/phyer/rbcp/services" + + "github.com/gin-gonic/gin" +) + +func CreateUser(c *gin.Context) { + var user models.User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := services.CreateUser(&user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, user) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..076d0fc --- /dev/null +++ b/go.mod @@ -0,0 +1,42 @@ +module zjmud.xyz/phyer/rbcp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/golang-jwt/jwt/v5 v5.0.0 + gorm.io/driver/sqlite v1.5.4 + gorm.io/gorm v1.25.5 + github.com/caarlos0/env/v10 v10.0.0 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // 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 + github.com/go-playground/validator/v10 v10.20.0 // indirect + 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/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // 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/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fdbffb5 --- /dev/null +++ b/go.sum @@ -0,0 +1,97 @@ +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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= +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/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-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +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/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= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +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/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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/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= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +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= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/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/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= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..4ac9cad --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "zjmud.xyz/phyer/rbcp/server" + +func main() { + server.Start() +} diff --git a/middleware/auth.go b/middleware/auth.go new file mode 100644 index 0000000..2356666 --- /dev/null +++ b/middleware/auth.go @@ -0,0 +1,42 @@ +package middleware + +import ( + "fmt" + "net/http" + "zjmud.xyz/phyer/rbcp/config" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" +) + +func JWTAuthMiddleware() gin.HandlerFunc { + cfg := config.LoadConfig() + return func(c *gin.Context) { + tokenString := c.GetHeader("Authorization") + if tokenString == "" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header missing"}) + return + } + + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(cfg.JWTSecret), nil + }) + + if err != nil || !token.Valid { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + return + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"}) + return + } + + c.Set("userID", claims["sub"]) + c.Next() + } +} diff --git a/models/models.go b/models/models.go new file mode 100644 index 0000000..2771834 --- /dev/null +++ b/models/models.go @@ -0,0 +1,28 @@ +package models + +import "gorm.io/gorm" + +type User struct { + gorm.Model + Username string `gorm:"unique"` + Password string + Roles []Role `gorm:"many2many:user_roles;"` +} + +type Role struct { + gorm.Model + Name string `gorm:"unique"` + Permissions []Permission `gorm:"many2many:role_permissions;"` +} + +type Permission struct { + gorm.Model + Name string `gorm:"unique"` +} + +type UserGroup struct { + gorm.Model + Name string `gorm:"unique"` + Users []User `gorm:"many2many:user_group_users;"` +} +` diff --git a/repositories/permission.go b/repositories/permission.go new file mode 100644 index 0000000..165d9a9 --- /dev/null +++ b/repositories/permission.go @@ -0,0 +1,10 @@ +package repositories + +import ( + "gorm.io/gorm" + "zjmud.xyz/phyer/rbcp/models" +) + +func CreatePermission(permission *models.Permission) error { + return db.Create(permission).Error +} diff --git a/repositories/role.go b/repositories/role.go new file mode 100644 index 0000000..e9e9cb1 --- /dev/null +++ b/repositories/role.go @@ -0,0 +1,10 @@ +package repositories + +import ( + "gorm.io/gorm" + "zjmud.xyz/phyer/rbcp/models" +) + +func CreateRole(role *models.Role) error { + return db.Create(role).Error +} diff --git a/repositories/user.go b/repositories/user.go new file mode 100644 index 0000000..99f0ceb --- /dev/null +++ b/repositories/user.go @@ -0,0 +1,16 @@ +package repositories + +import ( + "gorm.io/gorm" + "zjmud.xyz/phyer/rbcp/models" +) + +func GetUserByUsername(username string) (*models.User, error) { + var user models.User + result := db.Where("username = ?", username).First(&user) + return &user, result.Error +} + +func CreateUser(user *models.User) error { + return db.Create(user).Error +} diff --git a/repositories/user_group.go b/repositories/user_group.go new file mode 100644 index 0000000..4d68895 --- /dev/null +++ b/repositories/user_group.go @@ -0,0 +1,10 @@ +package repositories + +import ( + "gorm.io/gorm" + "zjmud.xyz/phyer/rbcp/models" +) + +func CreateUserGroup(group *models.UserGroup) error { + return db.Create(group).Error +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..8a3039f --- /dev/null +++ b/server/server.go @@ -0,0 +1,18 @@ +package server + +import ( + "zjmud.xyz/phyer/rbcp/api" + "zjmud.xyz/phyer/rbcp/config" + + "github.com/gin-gonic/gin" +) + +func Start() { + cfg := config.LoadConfig() + + r := gin.Default() + + api.SetupRoutes(r) + + r.Run(":" + cfg.ServerPort) +} diff --git a/service/auth.go b/service/auth.go new file mode 100644 index 0000000..cc3a694 --- /dev/null +++ b/service/auth.go @@ -0,0 +1,32 @@ +package services + +import ( + "errors" + "zjmud.xyz/phyer/rbcp/models" + "zjmud.xyz/phyer/rbcp/repositories" + + "golang.org/x/crypto/bcrypt" +) + +func Authenticate(username, password string) (*models.User, error) { + user, err := repositories.GetUserByUsername(username) + if err != nil { + return nil, errors.New("user not found") + } + + if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { + return nil, errors.New("invalid password") + } + + return user, nil +} + +func CreateUser(user *models.User) error { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) + if err != nil { + return err + } + + user.Password = string(hashedPassword) + return repositories.CreateUser(user) +} diff --git a/service/rbac.go b/service/rbac.go new file mode 100644 index 0000000..ad6a245 --- /dev/null +++ b/service/rbac.go @@ -0,0 +1,12 @@ +package services + +import "zjmud.xyz/phyer/rbcp/models" + +func CreateRole(role *models.Role) error { + return repositories.CreateRole(role) +} + +func AssignRoleToUser(userID uint, roleID uint) error { + // 实现角色分配逻辑 + return nil +} diff --git a/service/user.go b/service/user.go new file mode 100644 index 0000000..00f321f --- /dev/null +++ b/service/user.go @@ -0,0 +1,7 @@ +package services + +import "zjmud.xyz/phyer/rbcp/models" + +func CreateUser(user *models.User) error { + return repositories.CreateUser(user) +} diff --git a/sug.md b/sug.md new file mode 100644 index 0000000..2285afe --- /dev/null +++ b/sug.md @@ -0,0 +1,475 @@ +Filepath: api/admin.go + +--- + +- Datetime: 2025-02-16 02:30:16 + +- Model: ollamav3/chsword/DeepSeek-V3 + +- Selected files: + - main.go + - api/routes.go + - middleware/auth.go + - config/config.go + - models/models.go + +> router里动作对应的service,请给出实现 +> + +To implement the service for the router's actions, we need to create a new file `services.py` in the project root directory `/Users/zhangkun/data/go/test/testai`. + +Here are the *SEARCH/REPLACE* blocks: + +Filepath: main.go +Replace lines: 0-0 +```go +import "services" + +router.HandleFunc("/action1", services.action1Handler) +router.HandleFunc("/action2", services.action2Handler) +``` + +Filepath: services.go + +--- + +- Datetime: 2025-02-16 02:32:35 + +- Model: ollamav3/chsword/DeepSeek-V3 + +- Selected files: + - main.go + - api/routes.go + - middleware/auth.go + - config/config.go + - models/models.go + +> admin.POST("/roles", CreateRole) +> admin.POST("/users", CreateUser) +> admin.POST("/permissions", CreatePermission) +> admin.POST("/user-groups", CreateUserGroup) +> admin.POST("/assign-role", AssignRoleToUser) +> admin.POST("/assign-permission", AssignPermissionToRole) +> 这里的 动作都是未定义的,需要你给出定义 + +To make these actions defined, we need to add the necessary functions and routes. Here's an example of how you can define them: + +```markdown +# Define the CreateRole function +def CreateRole(request): + # Get the role data from the request body + role_data = request.get_json() + + # Create a new Role object with the provided data + role = Role(name=role_data['name'], description=role_data['description']) + + # Save the role to the database + db.session.add(role) + db.session.commit() + + # Return the created role as JSON + return jsonify({'id': role.id, 'name': role.name, 'description': role.description}) + +# Define the CreateUser function +def CreateUser(request): + # Get the user data from the request body + user_data = request.get_json() + + # Create a new User object with the provided data + user = User(username=user_data['username'], email=user_data['email']) + + # Hash the password using bcrypt + user.password = bcrypt.hashpw(user.password.encode('utf-8'), bcrypt.gensalt()) + + # Save the user to the database + db.session.add(user) + db.session.commit() + + # Return the created user as JSON + return jsonify({'id': user.id, 'username': user.username, 'email': user.email}) + +# Define the CreatePermission function +def CreatePermission(request): + # Get the permission data from the request body + permission_data = request.get_json() + + # Create a new Permission object with the provided data + permission = Permission(name=permission_data['name'], description=permission_data['description']) + + # Save the permission to the database + db.session.add(permission) + db.session.commit() + + # Return the created permission as JSON + return jsonify({'id': permission.id, 'name': permission.name, 'description': permission.description}) + +# Define the CreateUserGroup function +def CreateUserGroup(request): + # Get the group data from the request body + group_data = request.get_json() + + # Create a new Group object with the provided data + group = Group(name=group_data['name'], description=group_data['description']) + + # Save the group to the database + db.session.add(group) + db.session.commit() + + # Return the created group as JSON + return jsonify({'id': group.id, 'name': group.name, 'description': group.description}) + +# Define the AssignRoleToUser function +def AssignRoleToUser(request): + # Get the user ID and role ID from the request body + user_id = request.get_json()['user_id'] + role_id = request.get_json()['role_id'] + + # Find the user and role objects in the database + user = User.query.get(user_id) + role = Role.query.get(role_id) + + # Assign the role to the user + user.roles.append(role) + + # Save the changes to the database + db.session.commit() + + # Return a success message as JSON + return jsonify({'message': 'Role assigned successfully'}) + +# Define the AssignPermissionToRole function +def AssignPermissionToRole(request): + # Get the role ID and permission ID from the request body + role_id = request.get_json()['role_id'] + permission_id = request.get_json()['permission_id'] + + # Find the role and permission objects in the database + role = Role.query.get(role_id) + permission = Permission.query.get(permission_id) + + # Assign the permission to the role + role.permissions.append(permission) + + # Save the changes to the database + db.session.commit() + + # Return a success message as JSON + return jsonify({'message': 'Permission assigned successfully'}) +``` + +Note that these functions assume you have already defined the necessary models (e.g. `User`, `Role`, `Group`, etc.) and database sessions. + +Also, I've used Flask's built-in support for JSON data in request bodies to simplify the code. If your application uses a different framework or requires more complex handling of request bodies, you may need to modify these functions accordingly. + +To implement these routes, you would add them to your Flask app like so: + +```python +from flask import Flask, jsonify, request + +app = Flask(__name__) + +# ... (other routes and code) + +admin = Blueprint('admin', __name__, url_prefix='/admin') + +@admin.route('/roles', methods=['POST']) +def CreateRole(): + # Call the CreateRole function defined above + return CreateRole(request) + +@admin.route('/users', methods=['POST']) +def CreateUser(): + # Call the CreateUser function defined above + return CreateUser(request) + +@admin.route('/permissions', methods=['POST']) +def CreatePermission(): + # Call the CreatePermission function defined above + return CreatePermission(request) + +@admin.route('/user-groups', methods=['POST']) +def CreateUserGroup(): + # Call the CreateUserGroup function defined above + return CreateUserGroup(request) + +@admin.route('/assign-role', methods=['POST']) +def AssignRoleToUser(): + # Call the AssignRoleToUser function defined above + return AssignRoleToUser(request) + +@admin.route('/assign-permission', methods=['POST']) +def AssignPermissionToRole(): + # Call the AssignPermissionToRole function defined above + return AssignPermissionToRole(request) +``` + +This assumes you have already created a Blueprint for your admin interface and registered these routes with it. + +--- + +- Datetime: 2025-02-16 02:34:18 + +- Model: ollamav3/chsword/DeepSeek-V3 + +- Selected files: + - main.go + - api/routes.go + - middleware/auth.go + - config/config.go + - models/models.go + +> 这不是python,这是go的gin框架 +> + +I'll make sure to pay attention to the framework. + +So, you want me to modify the `mathweb/flask/app.go` file in the Go Gin framework instead of Python Flask. + +Please confirm if I should proceed with the changes. If so, please provide more details about what changes are needed. + +--- + +- Datetime: 2025-02-16 02:38:40 + +- Model: alidsv3/deepseek-v3 + +- Selected files: + - tree.txt + - api/routes.go + - config/config.go + - middleware/auth.go + - models/models.go + - server/server.go + - main.go + +> 我在一个空目录下, 我需要创建一饿go语言gin框架的api后端项目,项目名字叫 zjmud.xyz/phyer/rbcp, 需要帮我生成 go.mod的内容, +> > 然后需要创建一个用户账号系统, 包括基础的用户验证功能和鉴权,鉴权依照rbac模式,管理员可以创建角色、用户、定义资源、资源组、和动作,管理员可以委派某个用户,对特定资源组和用户组进行动作定义, 请把go文件根据逻辑,放置到不同的模块中,不要都放到main 模块下 +> +> 那么当前目录结构是否在正确的方向上, 每个文件是否有需要改动的地方 + +根据你的需求,当前目录结构已经在一个很好的方向上,但还需要做一些调整和补充。让我们一步一步来: + +1. 首先,创建 `go.mod` 文件: + +Filepath: go.mod +Replace lines: 1-0 +```unknown +module zjmud.xyz/phyer/rbcp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/golang-jwt/jwt/v5 v5.0.0 + gorm.io/driver/sqlite v1.5.4 + gorm.io/gorm v1.25.5 + github.com/caarlos0/env/v10 v10.0.0 +) +``` + +2. 当前目录结构基本合理,但建议做一些调整: + +``` +. +├── api +│ └── routes.go +├── config +│ └── config.go +├── controllers +│ ├── auth.go +│ ├── rbac.go +│ └── user.go +├── go.mod +├── go.sum +├── main.go +├── middleware +│ └── auth.go +├── models +│ └── models.go +├── repositories +│ ├── permission.go +│ ├── role.go +│ ├── user.go +│ └── user_group.go +├── server +│ └── server.go +└── services + ├── auth.go + ├── rbac.go + └── user.go +``` + +3. 需要修改和补充的文件: + +Filepath: api/routes.go +Replace lines: 9-26 +```go +func SetupRoutes(r *gin.Engine) { + auth := r.Group("/auth") + { + auth.POST("/login", controllers.Login) + auth.POST("/register", controllers.Register) + } + + admin := r.Group("/admin") + admin.Use(middleware.RBACMiddleware("admin", "full-access")) + { + admin.POST("/roles", controllers.CreateRole) + admin.POST("/users", controllers.CreateUser) + admin.POST("/permissions", controllers.CreatePermission) + admin.POST("/user-groups", controllers.CreateUserGroup) + admin.POST("/assign-role", controllers.AssignRoleToUser) + admin.POST("/assign-permission", controllers.AssignPermissionToRole) + } +} +``` + +Filepath: config/config.go +Replace lines: 15-19 +```go +func LoadConfig() *Config { + cfg := &Config{} + if err := env.Parse(cfg); err != nil { + panic("Failed to load config: " + err.Error()) + } + return cfg +} +``` + +Filepath: middleware/auth.go +Replace lines: 8-13 +```go +func JWTAuthMiddleware(secret string) gin.HandlerFunc { + return func(c *gin.Context) { + tokenString := c.GetHeader("Authorization") + if tokenString == "" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header missing"}) + return + } + + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(secret), nil + }) + + if err != nil || !token.Valid { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + return + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"}) + return + } + + c.Set("userID", claims["sub"]) + c.Next() + } +} +``` + +4. 需要创建的新文件: + +Filepath: controllers/auth.go + +package controllers + +import ( + "net/http" + "time" + "zjmud.xyz/phyer/rbcp/models" + "zjmud.xyz/phyer/rbcp/services" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" +) + +func Login(c *gin.Context) { + var loginData struct { + Username string `json:"username"` + Password string `json:"password"` + } + + if err := c.ShouldBindJSON(&loginData); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user, err := services.Authenticate(loginData.Username, loginData.Password) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) + return + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "sub": user.ID, + "exp": time.Now().Add(time.Hour * 24).Unix(), + }) + + tokenString, err := token.SignedString([]byte("your-256-bit-secret")) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) + return + } + + c.JSON(http.StatusOK, gin.H{"token": tokenString}) +} + +func Register(c *gin.Context) { + var user models.User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := services.CreateUser(&user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, user) +} +``` + +Filepath: services/auth.go + +package services + +import ( + "errors" + "zjmud.xyz/phyer/rbcp/models" + "zjmud.xyz/phyer/rbcp/repositories" + + "golang.org/x/crypto/bcrypt" +) + +func Authenticate(username, password string) (*models.User, error) { + user, err := repositories.GetUserByUsername(username) + if err != nil { + return nil, errors.New("user not found") + } + + if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { + return nil, errors.New("invalid password") + } + + return user, nil +} + +func CreateUser(user *models.User) error { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) + if err != nil { + return err + } + + user.Password = string(hashedPassword) + return repositories.CreateUser(user) +} +``` + +Filepath: repositories/user.go + + diff --git a/tree.txt b/tree.txt new file mode 100644 index 0000000..0900f19 --- /dev/null +++ b/tree.txt @@ -0,0 +1,17 @@ +. +├── api +│   └── routes.go +├── config +│   └── config.go +├── go.mod +├── go.sum +├── main.go +├── middleware +│   └── auth.go +├── models +│   └── models.go +├── server +│   └── server.go +└── tree.txt + +6 directories, 9 files