经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
快速搭建一个go语言web后端服务脚手架
来源:cnblogs  作者:秋玻  时间:2023/4/12 13:38:14  对本文有异议

快速搭建一个go语言web后端服务脚手架
源码:https://github.com/weloe/go-web-demo

web框架使用gin,数据操作使用gorm,访问控制使用casbin

首先添加一下自定义的middleware

recover_control.go ,统一处理panic error返回的信息

  1. package middleware
  2. import (
  3. "fmt"
  4. "github.com/gin-gonic/gin"
  5. "go-web-demo/component"
  6. "log"
  7. "net/http"
  8. )
  9. func Recover(c *gin.Context) {
  10. defer func() {
  11. if r := recover(); r != nil {
  12. // print err msg
  13. log.Printf("panic: %v\n", r)
  14. // debug.PrintStack()
  15. // response same struct
  16. c.JSON(http.StatusBadRequest, component.RestResponse{Code: -1, Message: fmt.Sprintf("%v", r)})
  17. }
  18. }()
  19. c.Next()
  20. }

access_control.go 使用casbin进行访问控制的中间件

  1. package middleware
  2. import (
  3. "fmt"
  4. "github.com/casbin/casbin/v2"
  5. gormadapter "github.com/casbin/gorm-adapter/v3"
  6. "github.com/gin-gonic/gin"
  7. _ "github.com/go-sql-driver/mysql"
  8. "go-web-demo/component"
  9. "log"
  10. "net/http"
  11. )
  12. // DefaultAuthorize determines if current subject has been authorized to take an action on an object.
  13. func DefaultAuthorize(obj string, act string) gin.HandlerFunc {
  14. return func(c *gin.Context) {
  15. // Get current user/subject
  16. token := c.Request.Header.Get("token")
  17. if token == "" {
  18. c.AbortWithStatusJSON(http.StatusUnauthorized, component.RestResponse{Message: "token is nil"})
  19. return
  20. }
  21. username, err := component.GlobalCache.Get(token)
  22. if err != nil || string(username) == "" {
  23. log.Println(err)
  24. c.AbortWithStatusJSON(http.StatusUnauthorized, component.RestResponse{Message: "user hasn't logged in yet"})
  25. return
  26. }
  27. // Casbin enforces policy
  28. ok, err := enforce(string(username), obj, act, component.Enforcer)
  29. if err != nil {
  30. log.Println(err)
  31. c.AbortWithStatusJSON(http.StatusInternalServerError, component.RestResponse{Message: "error occurred when authorizing user"})
  32. return
  33. }
  34. if !ok {
  35. c.AbortWithStatusJSON(http.StatusForbidden, component.RestResponse{Message: "forbidden"})
  36. return
  37. }
  38. c.Next()
  39. }
  40. }
  41. func enforce(sub string, obj string, act string, enforcer *casbin.Enforcer) (bool, error) {
  42. // Load policies from DB dynamically
  43. err := enforcer.LoadPolicy()
  44. if err != nil {
  45. return false, fmt.Errorf("failed to load policy from DB: %w", err)
  46. }
  47. // Verify
  48. ok, err := enforcer.Enforce(sub, obj, act)
  49. return ok, err
  50. }
  51. func AuthorizeAdapterAndModel(obj string, act string, adapter *gormadapter.Adapter, model string) gin.HandlerFunc {
  52. return func(c *gin.Context) {
  53. // Get current user/subject
  54. token := c.Request.Header.Get("token")
  55. if token == "" {
  56. c.AbortWithStatusJSON(401, component.RestResponse{Message: "token is nil"})
  57. return
  58. }
  59. username, err := component.GlobalCache.Get(token)
  60. if err != nil || string(username) == "" {
  61. log.Println(err)
  62. c.AbortWithStatusJSON(401, component.RestResponse{Message: "user hasn't logged in yet"})
  63. return
  64. }
  65. // Load model configuration file and policy store adapter
  66. enforcer, err := casbin.NewEnforcer(model, adapter)
  67. // Casbin enforces policy
  68. ok, err := enforce(string(username), obj, act, enforcer)
  69. if err != nil {
  70. log.Println(err)
  71. c.AbortWithStatusJSON(500, component.RestResponse{Message: "error occurred when authorizing user"})
  72. return
  73. }
  74. if !ok {
  75. c.AbortWithStatusJSON(403, component.RestResponse{Message: "forbidden"})
  76. return
  77. }
  78. c.Next()
  79. }
  80. }

reader.go 读取yaml配置文件的根据类,使用了viter

  1. package config
  2. import (
  3. "fmt"
  4. "github.com/spf13/viper"
  5. "log"
  6. "sync"
  7. "time"
  8. )
  9. type Config struct {
  10. Server *Server
  11. Mysql *DB
  12. LocalCache *LocalCache
  13. Casbin *Casbin
  14. }
  15. type Server struct {
  16. Port int64
  17. }
  18. type DB struct {
  19. Username string
  20. Password string
  21. Host string
  22. Port int64
  23. Dbname string
  24. TimeOut string
  25. }
  26. type LocalCache struct {
  27. ExpireTime time.Duration
  28. }
  29. type Casbin struct {
  30. Model string
  31. }
  32. var (
  33. once sync.Once
  34. Reader = new(Config)
  35. )
  36. func (config *Config) ReadConfig() *Config {
  37. once.Do(func() {
  38. viper.SetConfigName("config") // filename
  39. viper.SetConfigType("yaml") // filename extension : yaml | json |
  40. viper.AddConfigPath("./config") // workspace dir : ./
  41. var err error
  42. err = viper.ReadInConfig() // read config
  43. if err != nil { // handler err
  44. log.Fatalf(fmt.Sprintf("Fatal error config file: %s \n", err))
  45. }
  46. err = viper.Unmarshal(config)
  47. if err != nil {
  48. log.Fatalf(fmt.Sprintf("Fatal error viper unmarshal config: %s \n", err))
  49. }
  50. })
  51. return Reader
  52. }

配置文件

  1. server:
  2. port: 8080
  3. mysql:
  4. username: root
  5. password: pwd
  6. host: 127.0.0.1
  7. port: 3306
  8. dbname: casbin_demo
  9. timeout: 10s
  10. localCache:
  11. expireTime: 60
  12. casbin:
  13. model: config/rbac_model.conf

persistence.go, gorm,bigcache, casbin 初始化,这里用的casbin是从数据库读取policy

  1. package component
  2. import (
  3. "fmt"
  4. "github.com/allegro/bigcache"
  5. "github.com/casbin/casbin/v2"
  6. gormadapter "github.com/casbin/gorm-adapter/v3"
  7. _ "github.com/go-sql-driver/mysql"
  8. "go-web-demo/config"
  9. "gorm.io/driver/mysql"
  10. "gorm.io/gorm"
  11. "log"
  12. "time"
  13. )
  14. var (
  15. DB *gorm.DB
  16. GlobalCache *bigcache.BigCache
  17. Enforcer *casbin.Enforcer
  18. )
  19. // CreateByConfig create components
  20. func CreateByConfig() {
  21. ConnectDB()
  22. CreateLocalCache()
  23. CreateCasbinEnforcer()
  24. }
  25. func ConnectDB() {
  26. // connect to DB
  27. var err error
  28. dbConfig := config.Reader.ReadConfig().Mysql
  29. if dbConfig == nil {
  30. log.Fatalf(fmt.Sprintf("db config is nil"))
  31. }
  32. // config
  33. username := dbConfig.Username
  34. password := dbConfig.Password
  35. host := dbConfig.Host
  36. port := dbConfig.Port
  37. Dbname := dbConfig.Dbname
  38. timeout := dbConfig.TimeOut
  39. dbUrl := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)
  40. log.Println("connect db url: " + dbUrl)
  41. DB, err = gorm.Open(mysql.Open(dbUrl), &gorm.Config{})
  42. if err != nil {
  43. log.Fatalf(fmt.Sprintf("failed to connect to DB: %v", err))
  44. }
  45. }
  46. func CreateLocalCache() {
  47. var err error
  48. cacheConfig := config.Reader.ReadConfig().LocalCache
  49. if cacheConfig == nil {
  50. log.Fatalf(fmt.Sprintf("cache config is nil"))
  51. }
  52. // Initialize cache to store current user in cache.
  53. GlobalCache, err = bigcache.NewBigCache(bigcache.DefaultConfig(cacheConfig.ExpireTime * time.Second)) // Set expire time to 30 s
  54. if err != nil {
  55. log.Fatalf(fmt.Sprintf("failed to initialize cahce: %v", err))
  56. }
  57. }
  58. func CreateCasbinEnforcer() {
  59. var err error
  60. // casbin model
  61. config := config.Reader.ReadConfig().Casbin
  62. if config == nil {
  63. log.Fatalf(fmt.Sprintf("casbin config is nil"))
  64. }
  65. model := config.Model
  66. //Initialize casbin adapter
  67. adapter, _ := gormadapter.NewAdapterByDB(DB)
  68. // Load model configuration file and policy store adapter
  69. Enforcer, err = casbin.NewEnforcer(model, adapter)
  70. if err != nil {
  71. log.Fatalf(fmt.Sprintf("failed to create casbin enforcer: %v", err))
  72. }
  73. }

到这里准备工作基本完成,我们来写一个通用的 登录,注册,退出 业务吧

user_handler.go

  1. package handler
  2. import (
  3. "fmt"
  4. "github.com/gin-gonic/gin"
  5. "github.com/gin-gonic/gin/binding"
  6. "go-web-demo/component"
  7. "go-web-demo/handler/request"
  8. "go-web-demo/service"
  9. "net/http"
  10. )
  11. func Login(c *gin.Context) {
  12. loginRequest := &request.Login{}
  13. err := c.ShouldBindBodyWith(loginRequest, binding.JSON)
  14. if err != nil {
  15. panic(fmt.Errorf("request body bind error: %v", err))
  16. }
  17. token := service.Login(loginRequest)
  18. c.JSON(http.StatusOK, component.RestResponse{Code: 1, Data: token, Message: loginRequest.Username + " logged in successfully"})
  19. }
  20. func Logout(c *gin.Context) {
  21. token := c.Request.Header.Get("token")
  22. if token == "" {
  23. panic(fmt.Errorf("token error: token is nil"))
  24. }
  25. bytes, err := component.GlobalCache.Get(token)
  26. if err != nil {
  27. panic(fmt.Errorf("token error: failed to get username: %v", err))
  28. }
  29. username := string(bytes)
  30. // Authentication
  31. // Delete store current subject in cache
  32. err = component.GlobalCache.Delete(token)
  33. if err != nil {
  34. panic(fmt.Errorf("failed to delete current subject in cache: %w", err))
  35. }
  36. c.JSON(http.StatusOK, component.RestResponse{Code: 1, Data: token, Message: username + " logout in successfully"})
  37. }
  38. func Register(c *gin.Context) {
  39. register := &request.Register{}
  40. err := c.ShouldBindBodyWith(register, binding.JSON)
  41. if err != nil {
  42. c.JSON(400, component.RestResponse{Code: -1, Message: " bind error"})
  43. return
  44. }
  45. service.Register(register)
  46. c.JSON(http.StatusOK, component.RestResponse{Code: 1, Data: nil, Message: "register successfully"})
  47. }

service.user.go

这里要注意 注册的时候我们做了两个操作,注册到user表,把policy写入到casbin_rule表,要保证他们要同时成功,所以要用事务

  1. func Login(loginRequest *request.Login) string {
  2. password := loginRequest.Password
  3. username := loginRequest.Username
  4. // Authentication
  5. user := dao.GetByUsername(username)
  6. if password != user.Password {
  7. panic(fmt.Errorf(username + " logged error : password error"))
  8. }
  9. // Generate random uuid token
  10. u, err := uuid.NewRandom()
  11. if err != nil {
  12. panic(fmt.Errorf("failed to generate UUID: %w", err))
  13. }
  14. // Sprintf token
  15. token := fmt.Sprintf("%s-%s", u.String(), "token")
  16. // Store current subject in cache
  17. err = component.GlobalCache.Set(token, []byte(username))
  18. if err != nil {
  19. panic(fmt.Errorf("failed to store current subject in cache: %w", err))
  20. }
  21. // Send cache key back to client cookie
  22. //c.SetCookie("current_subject", token, 30*60, "/resource", "", false, true)
  23. return token
  24. }
  25. func Register(register *request.Register) {
  26. var err error
  27. e := component.Enforcer
  28. err = e.GetAdapter().(*gormadapter.Adapter).Transaction(e, func(copyEnforcer casbin.IEnforcer) error {
  29. // Insert to table
  30. db := copyEnforcer.GetAdapter().(*gormadapter.Adapter).GetDb()
  31. res := db.Exec("insert into user (username,password) values(?,?)", register.Username, register.Password)
  32. //User has Username and Password
  33. //res := db.Table("user").Create(&User{
  34. // Username: register.Username,
  35. // Password: register.Password,
  36. //})
  37. if err != nil || res.RowsAffected < 1 {
  38. return fmt.Errorf("insert error: %w", err)
  39. }
  40. _, err = copyEnforcer.AddRoleForUser(register.Username, "role::user")
  41. if err != nil {
  42. return fmt.Errorf("add plocy error: %w", err)
  43. }
  44. return nil
  45. })
  46. if err != nil {
  47. panic(err)
  48. }
  49. }

dao.user.go 对数据库的操作

  1. package dao
  2. import "go-web-demo/component"
  3. type User struct {
  4. Id int64 `gorm:"primaryKey"`
  5. Username string
  6. Password string
  7. Email string
  8. Phone string
  9. }
  10. func (u *User) TableName() string {
  11. return "user"
  12. }
  13. func GetByUsername(username string) *User {
  14. res := new(User)
  15. component.DB.Model(&User{}).Where("username = ?", username).First(res)
  16. return res
  17. }
  18. func Insert(username string, password string) (int64, error, int64) {
  19. user := &User{Username: username, Password: password}
  20. res := component.DB.Create(&user)
  21. return user.Id, res.Error, res.RowsAffected
  22. }

最后一步,启动web服务,配置路由

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gin-contrib/cors"
  5. "github.com/gin-gonic/gin"
  6. "go-web-demo/component"
  7. "go-web-demo/config"
  8. "go-web-demo/handler"
  9. "go-web-demo/middleware"
  10. "log"
  11. )
  12. var (
  13. router *gin.Engine
  14. )
  15. func init() {
  16. //Initialize components from config yaml: mysql locaCache casbin
  17. component.CreateByConfig()
  18. // Initialize gin engine
  19. router = gin.Default()
  20. // Initialize gin middleware
  21. corsConfig := cors.DefaultConfig()
  22. corsConfig.AllowAllOrigins = true
  23. corsConfig.AllowCredentials = true
  24. router.Use(cors.New(corsConfig))
  25. router.Use(middleware.Recover)
  26. // Initialize gin router
  27. user := router.Group("/user")
  28. {
  29. user.POST("/login", handler.Login)
  30. user.POST("/logout", handler.Logout)
  31. user.POST("/register", handler.Register)
  32. }
  33. resource := router.Group("/api")
  34. {
  35. resource.Use(middleware.DefaultAuthorize("user::resource", "read-write"))
  36. resource.GET("/resource", handler.ReadResource)
  37. resource.POST("/resource", handler.WriteResource)
  38. }
  39. }
  40. func main() {
  41. // Start
  42. port := config.Reader.Server.Port
  43. err := router.Run(":" + port)
  44. if err != nil {
  45. panic(fmt.Sprintf("failed to start gin engine: %v", err))
  46. }
  47. log.Println("application is now running...")
  48. }

表结构和相关测试数据

  1. CREATE DATABASE /*!32312 IF NOT EXISTS*/`casbin_demo` /*!40100 DEFAULT CHARACTER SET utf8 */;
  2. USE `casbin_demo`;
  3. /*Table structure for table `casbin_rule` */
  4. DROP TABLE IF EXISTS `casbin_rule`;
  5. CREATE TABLE `casbin_rule` (
  6. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  7. `ptype` varchar(100) NOT NULL,
  8. `v0` varchar(100) DEFAULT NULL,
  9. `v1` varchar(100) DEFAULT NULL,
  10. `v2` varchar(100) DEFAULT NULL,
  11. `v3` varchar(100) DEFAULT NULL,
  12. `v4` varchar(100) DEFAULT NULL,
  13. `v5` varchar(100) DEFAULT NULL,
  14. PRIMARY KEY (`id`),
  15. UNIQUE KEY `idx_casbin_rule` (`v0`,`v1`,`v2`,`v3`,`v4`,`v5`)
  16. ) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8;
  17. /*Data for the table `casbin_rule` */
  18. insert into `casbin_rule`(`id`,`ptype`,`v0`,`v1`,`v2`,`v3`,`v4`,`v5`) values
  19. (3,'p','role::admin','admin::resource','read-write','','',''),
  20. (5,'p','role::user','user::resource','read-write','','',''),
  21. (57,'g','test1','role::user','','','',''),
  22. (59,'g','role::admin','role::user','','','',''),
  23. (63,'g','test2','role::admin',NULL,NULL,NULL,NULL);
  24. /*Table structure for table `user` */
  25. DROP TABLE IF EXISTS `user`;
  26. CREATE TABLE `user` (
  27. `id` int(11) NOT NULL AUTO_INCREMENT,
  28. `username` varchar(50) DEFAULT NULL,
  29. `password` varchar(50) DEFAULT NULL,
  30. `email` varchar(50) DEFAULT NULL,
  31. `phone` varchar(50) DEFAULT NULL,
  32. PRIMARY KEY (`id`)
  33. ) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8;
  34. /*Data for the table `user` */
  35. insert into `user`(`id`,`username`,`password`,`email`,`phone`) values
  36. (36,'test1','123',NULL,NULL),
  37. (38,'test2','123',NULL,NULL);

原文链接:https://www.cnblogs.com/weloe/p/17309521.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号