快速搭建一个go语言web后端服务脚手架

这篇具有很好参考价值的文章主要介绍了快速搭建一个go语言web后端服务脚手架。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

首先添加一下自定义的middleware

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

package middleware

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"go-web-demo/component"
	"log"
	"net/http"
)

func Recover(c *gin.Context) {
	defer func() {
		if r := recover(); r != nil {
			// print err msg
			log.Printf("panic: %v\n", r)
			// debug.PrintStack()
			// response same struct
			c.JSON(http.StatusBadRequest, component.RestResponse{Code: -1, Message: fmt.Sprintf("%v", r)})
		}
	}()

	c.Next()
}

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

package middleware

import (
	"fmt"
	"github.com/casbin/casbin/v2"
	gormadapter "github.com/casbin/gorm-adapter/v3"
	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
	"go-web-demo/component"
	"log"
	"net/http"
)

// DefaultAuthorize determines if current subject has been authorized to take an action on an object.
func DefaultAuthorize(obj string, act string) gin.HandlerFunc {
	return func(c *gin.Context) {

		// Get current user/subject
		token := c.Request.Header.Get("token")
		if token == "" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, component.RestResponse{Message: "token is nil"})
			return
		}
		username, err := component.GlobalCache.Get(token)
		if err != nil || string(username) == "" {
			log.Println(err)
			c.AbortWithStatusJSON(http.StatusUnauthorized, component.RestResponse{Message: "user hasn't logged in yet"})
			return
		}

		// Casbin enforces policy
		ok, err := enforce(string(username), obj, act, component.Enforcer)
		if err != nil {
			log.Println(err)
			c.AbortWithStatusJSON(http.StatusInternalServerError, component.RestResponse{Message: "error occurred when authorizing user"})
			return
		}
		if !ok {
			c.AbortWithStatusJSON(http.StatusForbidden, component.RestResponse{Message: "forbidden"})
			return
		}

		c.Next()
	}
}

func enforce(sub string, obj string, act string, enforcer *casbin.Enforcer) (bool, error) {
	// Load policies from DB dynamically
	err := enforcer.LoadPolicy()
	if err != nil {
		return false, fmt.Errorf("failed to load policy from DB: %w", err)
	}
	// Verify
	ok, err := enforcer.Enforce(sub, obj, act)
	return ok, err
}

func AuthorizeAdapterAndModel(obj string, act string, adapter *gormadapter.Adapter, model string) gin.HandlerFunc {
	return func(c *gin.Context) {

		// Get current user/subject
		token := c.Request.Header.Get("token")
		if token == "" {
			c.AbortWithStatusJSON(401, component.RestResponse{Message: "token is nil"})
			return
		}
		username, err := component.GlobalCache.Get(token)
		if err != nil || string(username) == "" {
			log.Println(err)
			c.AbortWithStatusJSON(401, component.RestResponse{Message: "user hasn't logged in yet"})
			return
		}

		// Load model configuration file and policy store adapter
		enforcer, err := casbin.NewEnforcer(model, adapter)
		// Casbin enforces policy
		ok, err := enforce(string(username), obj, act, enforcer)

		if err != nil {
			log.Println(err)
			c.AbortWithStatusJSON(500, component.RestResponse{Message: "error occurred when authorizing user"})
			return
		}
		if !ok {
			c.AbortWithStatusJSON(403, component.RestResponse{Message: "forbidden"})
			return
		}

		c.Next()
	}
}

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

package config

import (
	"fmt"
	"github.com/spf13/viper"
	"log"
	"sync"
	"time"
)

type Config struct {
	Server     *Server
	Mysql      *DB
	LocalCache *LocalCache
	Casbin     *Casbin
}

type Server struct {
	Port int64
}

type DB struct {
	Username string
	Password string
	Host     string
	Port     int64
	Dbname   string
	TimeOut  string
}

type LocalCache struct {
	ExpireTime time.Duration
}

type Casbin struct {
	Model string
}

var (
	once   sync.Once
	Reader = new(Config)
)

func (config *Config) ReadConfig() *Config {
	once.Do(func() {
		viper.SetConfigName("config")   // filename
		viper.SetConfigType("yaml")     // filename extension : yaml | json |
		viper.AddConfigPath("./config") // workspace dir : ./
		var err error
		err = viper.ReadInConfig() // read config
		if err != nil {            // handler err
			log.Fatalf(fmt.Sprintf("Fatal error config file: %s \n", err))
		}
		err = viper.Unmarshal(config)
		if err != nil {
			log.Fatalf(fmt.Sprintf("Fatal error viper unmarshal config: %s \n", err))
		}
	})
	return Reader
}

配置文件

server:
  port: 8080

mysql:
  username: root
  password: pwd
  host: 127.0.0.1
  port: 3306
  dbname: casbin_demo
  timeout: 10s

localCache:
  expireTime: 60

casbin:
  model: config/rbac_model.conf

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

package component

import (
	"fmt"
	"github.com/allegro/bigcache"
	"github.com/casbin/casbin/v2"
	gormadapter "github.com/casbin/gorm-adapter/v3"
	_ "github.com/go-sql-driver/mysql"
	"go-web-demo/config"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
	"time"
)

var (
	DB          *gorm.DB
	GlobalCache *bigcache.BigCache
	Enforcer    *casbin.Enforcer
)

// CreateByConfig create components
func CreateByConfig() {

	ConnectDB()

	CreateLocalCache()

	CreateCasbinEnforcer()
}

func ConnectDB() {
	// connect to DB
	var err error
	dbConfig := config.Reader.ReadConfig().Mysql
	if dbConfig == nil {
		log.Fatalf(fmt.Sprintf("db config is nil"))
	}
	// config
	username := dbConfig.Username
	password := dbConfig.Password
	host := dbConfig.Host
	port := dbConfig.Port
	Dbname := dbConfig.Dbname
	timeout := dbConfig.TimeOut

	dbUrl := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)
	log.Println("connect db url: " + dbUrl)
	DB, err = gorm.Open(mysql.Open(dbUrl), &gorm.Config{})

	if err != nil {
		log.Fatalf(fmt.Sprintf("failed to connect to DB: %v", err))
	}
}

func CreateLocalCache() {
	var err error
	cacheConfig := config.Reader.ReadConfig().LocalCache
	if cacheConfig == nil {
		log.Fatalf(fmt.Sprintf("cache config is nil"))
	}
	// Initialize cache to store current user in cache.
	GlobalCache, err = bigcache.NewBigCache(bigcache.DefaultConfig(cacheConfig.ExpireTime * time.Second)) // Set expire time to 30 s
	if err != nil {
		log.Fatalf(fmt.Sprintf("failed to initialize cahce: %v", err))
	}
}

func CreateCasbinEnforcer() {
	var err error

	// casbin model
	config := config.Reader.ReadConfig().Casbin
	if config == nil {
		log.Fatalf(fmt.Sprintf("casbin config is nil"))
	}
	model := config.Model
	//Initialize casbin adapter
	adapter, _ := gormadapter.NewAdapterByDB(DB)

	// Load model configuration file and policy store adapter
	Enforcer, err = casbin.NewEnforcer(model, adapter)
	if err != nil {
		log.Fatalf(fmt.Sprintf("failed to create casbin enforcer: %v", err))
	}
    
}

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

user_handler.go

package handler

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"go-web-demo/component"
	"go-web-demo/handler/request"
	"go-web-demo/service"
	"net/http"
)

func Login(c *gin.Context) {
	loginRequest := &request.Login{}
	err := c.ShouldBindBodyWith(loginRequest, binding.JSON)
	if err != nil {
		panic(fmt.Errorf("request body bind error: %v", err))
	}
	token := service.Login(loginRequest)

	c.JSON(http.StatusOK, component.RestResponse{Code: 1, Data: token, Message: loginRequest.Username + " logged in successfully"})

}

func Logout(c *gin.Context) {
	token := c.Request.Header.Get("token")

	if token == "" {
		panic(fmt.Errorf("token error: token is nil"))
	}

	bytes, err := component.GlobalCache.Get(token)

	if err != nil {
		panic(fmt.Errorf("token error: failed to get username: %v", err))
	}

	username := string(bytes)
	// Authentication

	// Delete store current subject in cache
	err = component.GlobalCache.Delete(token)
	if err != nil {
		panic(fmt.Errorf("failed to delete current subject in cache: %w", err))
	}

	c.JSON(http.StatusOK, component.RestResponse{Code: 1, Data: token, Message: username + " logout in successfully"})
}

func Register(c *gin.Context) {
	register := &request.Register{}
	err := c.ShouldBindBodyWith(register, binding.JSON)
	if err != nil {
		c.JSON(400, component.RestResponse{Code: -1, Message: " bind error"})
		return
	}

	service.Register(register)

	c.JSON(http.StatusOK, component.RestResponse{Code: 1, Data: nil, Message: "register successfully"})
}

service.user.go

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

func Login(loginRequest *request.Login) string {
	password := loginRequest.Password
	username := loginRequest.Username

	// Authentication
	user := dao.GetByUsername(username)
	if password != user.Password {
		panic(fmt.Errorf(username + " logged error : password error"))
	}

	// Generate random uuid token
	u, err := uuid.NewRandom()
	if err != nil {
		panic(fmt.Errorf("failed to generate UUID: %w", err))
	}
	// Sprintf token
	token := fmt.Sprintf("%s-%s", u.String(), "token")
	// Store current subject in cache
	err = component.GlobalCache.Set(token, []byte(username))
	if err != nil {
		panic(fmt.Errorf("failed to store current subject in cache: %w", err))
	}
	// Send cache key back to client cookie
	//c.SetCookie("current_subject", token, 30*60, "/resource", "", false, true)
	return token
}

func Register(register *request.Register) {
	var err error
	e := component.Enforcer
	err = e.GetAdapter().(*gormadapter.Adapter).Transaction(e, func(copyEnforcer casbin.IEnforcer) error {
		// Insert to table
		db := copyEnforcer.GetAdapter().(*gormadapter.Adapter).GetDb()
		res := db.Exec("insert into user (username,password) values(?,?)", register.Username, register.Password)

		//User has Username and Password
		//res := db.Table("user").Create(&User{
		//	Username: register.Username,
		//	Password: register.Password,
		//})

		if err != nil || res.RowsAffected < 1 {
			return fmt.Errorf("insert error: %w", err)
		}

		_, err = copyEnforcer.AddRoleForUser(register.Username, "role::user")
		if err != nil {
			return fmt.Errorf("add plocy error: %w", err)
		}
		return nil
	})

	if err != nil {
		panic(err)
	}

}

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

package dao

import "go-web-demo/component"

type User struct {
	Id       int64 `gorm:"primaryKey"`
	Username string
	Password string
	Email    string
	Phone    string
}

func (u *User) TableName() string {
	return "user"
}

func GetByUsername(username string) *User {
	res := new(User)
	component.DB.Model(&User{}).Where("username = ?", username).First(res)
	return res
}

func Insert(username string, password string) (int64, error, int64) {
	user := &User{Username: username, Password: password}
	res := component.DB.Create(&user)

	return user.Id, res.Error, res.RowsAffected
}

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

package main

import (
	"fmt"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"go-web-demo/component"
	"go-web-demo/config"
	"go-web-demo/handler"
	"go-web-demo/middleware"
	"log"
)

var (
	router *gin.Engine
)

func init() {
	//Initialize components from config yaml: mysql locaCache casbin
	component.CreateByConfig()

	// Initialize gin engine
	router = gin.Default()

	// Initialize gin middleware
	corsConfig := cors.DefaultConfig()
	corsConfig.AllowAllOrigins = true
	corsConfig.AllowCredentials = true
	router.Use(cors.New(corsConfig))
	router.Use(middleware.Recover)

	// Initialize gin router
	user := router.Group("/user")
	{
		user.POST("/login", handler.Login)
		user.POST("/logout", handler.Logout)
		user.POST("/register", handler.Register)
	}

	resource := router.Group("/api")
	{
		resource.Use(middleware.DefaultAuthorize("user::resource", "read-write"))
		resource.GET("/resource", handler.ReadResource)
		resource.POST("/resource", handler.WriteResource)
	}

}

func main() {
	// Start
	port := config.Reader.Server.Port
	err := router.Run(":" + port)
	if err != nil {
		panic(fmt.Sprintf("failed to start gin engine: %v", err))
	}
	log.Println("application is now running...")
}

表结构和相关测试数据文章来源地址https://www.toymoban.com/news/detail-411372.html

CREATE DATABASE /*!32312 IF NOT EXISTS*/`casbin_demo` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `casbin_demo`;

/*Table structure for table `casbin_rule` */

DROP TABLE IF EXISTS `casbin_rule`;

CREATE TABLE `casbin_rule` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `ptype` varchar(100) NOT NULL,
  `v0` varchar(100) DEFAULT NULL,
  `v1` varchar(100) DEFAULT NULL,
  `v2` varchar(100) DEFAULT NULL,
  `v3` varchar(100) DEFAULT NULL,
  `v4` varchar(100) DEFAULT NULL,
  `v5` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_casbin_rule` (`v0`,`v1`,`v2`,`v3`,`v4`,`v5`)
) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8;

/*Data for the table `casbin_rule` */

insert  into `casbin_rule`(`id`,`ptype`,`v0`,`v1`,`v2`,`v3`,`v4`,`v5`) values 

(3,'p','role::admin','admin::resource','read-write','','',''),

(5,'p','role::user','user::resource','read-write','','',''),

(57,'g','test1','role::user','','','',''),

(59,'g','role::admin','role::user','','','',''),

(63,'g','test2','role::admin',NULL,NULL,NULL,NULL);

/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  `email` varchar(50) DEFAULT NULL,
  `phone` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8;

/*Data for the table `user` */

insert  into `user`(`id`,`username`,`password`,`email`,`phone`) values 

(36,'test1','123',NULL,NULL),

(38,'test2','123',NULL,NULL);

到了这里,关于快速搭建一个go语言web后端服务脚手架的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Go For Web:一篇文章带你用 Go 搭建一个最简单的 Web 服务、了解 Golang 运行 web 的原理

    本文作为解决如何通过 Golang 来编写 Web 应用这个问题的前瞻,对 Golang 中的 Web 基础部分进行一个简单的介绍。目前 Go 拥有成熟的 Http 处理包,所以我们去编写一个做任何事情的动态 Web 程序应该是很轻松的,接下来我们就去学习了解一些关于 Web 的相关基础,了解一些概念,

    2023年04月14日
    浏览(41)
  • 使用go语言、Python脚本搭建一个简单的chatgpt服务网站。

    前言 研0在暑假想提升一下自己,自学了go语言编程和机器学习相关学习,但是一味学习理论,终究是枯燥的,于是自己弄点小项目做。 在这之前,建议您需要掌握以下两个技巧,我在这里不赘述了 一个openAI账号,并申请了KEY(b站有教程) 魔法的method(自己摸索哈~网上应该

    2024年02月11日
    浏览(44)
  • 微信小程序入门04-后端脚手架搭建

    我们上一篇已经介绍了权限系统的库表搭建,光有表还是不够的,我们还需要有一个后台系统和数据库进行交互。搭建后台的时候既需要选择使用什么语言,也需要选择框架。 框架分为前端框架和后端框架。在第一篇微信开发者工具搭建的时候我们其实前端框架已经选择好了

    2024年02月05日
    浏览(30)
  • 从 0 到 1 搭建自己的脚手架(java 后端)

    脚手架是一种基础设施工具,用于快速生成项目的框架代码和文件结构。它是一种标准化的开发工具,使开发人员能够在项目的早期阶段快速搭建出一个具备基本功能和结构的系统。 主流的微服务架构体系下很多公司会将原有的单体架构或者繁重的微服务进行拆分。这个时候

    2024年02月08日
    浏览(32)
  • springcloud-alibaba五大核心组件-后端开发工程(脚手架)搭建

    Gitee仓库地址 点我 服务注册与发现: nacos 配置中心: nacos 服务远程调用: openfeign 微服务网关: gateway 服务限流降级熔断等: sentinel 实现的功能demo openfeign服务远程调用 sentinel限流测试 gateway网关调用2个微服务 nacos的服务注册与发现 软件架构(环境) jdk: 1.8 maven: 3.5.2 nacos: 注册中心

    2024年02月05日
    浏览(35)
  • JAVA-服务器搭建-创建web后端项目

       

    2024年04月22日
    浏览(57)
  • 【NodeJs】使用Express框架快速搭建一个web网站

    如果电脑有安装使用Nodejs,用得次数少的话,忘了怎么弄,可以看看这个文章,按照步骤,能快速搭建一个web网站服务器, 首先,你需要保证电脑系统有安装了Node.js,然后可以用VsCode开发工具,新建一个项目文件夹, 如果你是新手,应该知道,创建nodejs项目会依赖npm, 在项

    2024年02月03日
    浏览(44)
  • Rust Vs Go:从头构建一个web服务

    Go 和 Rust 之间的许多比较都强调它们在语法和初始学习曲线上的差异。然而,最终的决定性因素是重要项目的易用性。 Rust vs Go 是一个不断出现的话题,并且已经有很多关于它的文章。部分原因是开发人员正在寻找信息来帮助他们决定下一个 Web 项目使用哪种语言,而这两种

    2024年02月22日
    浏览(39)
  • 使用vue脚手架搭建前端工程(附:搭配ElementUI来快速开发)

    目录 一、搭建过程 1. 全局安装webpack(打包工具) 2. 全局安装vue脚手架 3. 初始化vue项目 4. vue项目目录的简单介绍 二、执行流程分析 三、自己造一个组件案例 四、ElementUI的使用 1. 环境的引入 2. 一个简单使用 3. 使用它来快速搭建后台管理系统 五、总结 npm install webpack -g np

    2024年02月10日
    浏览(51)
  • 【Go Web 篇】从零开始:构建最简单的 Go 语言 Web 服务器

    随着互联网的迅速发展,Web 服务器成为了连接世界的关键组件之一。而在现代编程语言中,Go 语言因其卓越的性能和并发能力而备受青睐。本篇博客将带你从零开始,一步步构建最简单的 Go 语言 Web 服务器,让你对 Go 语言的 Web 开发能力有一个初步的了解。 在选择一门语言

    2024年02月11日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包