快速搭建一个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...")
}

表结构和相关测试数据

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后端服务脚手架的更多相关文章

  1. jquery+flask+keras+nsfw快速搭建一个简易鉴黄工具

    1. demo 地址:http://www.huchengchun.com:8127/porn_classification 接口说明: 1. http://www.huchengchun.com:8 ...

  2. 快速搭建一个Quartz定时任务【转载,好文 ,值得收藏,亲身试用 效果不错】

    Quartz.NET 入门 概述 Quartz.NET是一个开源的作业调度框架,非常适合在平时的工作中,定时轮询数据库同步,定时邮件通知,定时处理数据等. Quartz.NET允许开发人员根据时间间隔 ...

  3. 一个小时搭建一个全栈 Web 应用框架

    把想法变为现实的能力是空想家与实干家的区别.不管你是在一家跨国公司工作,还是正在为自己的创业公司而努力,那些有能力将创意转化为真正产品的人,都具有宝贵的技能并拥有明显的实力.如果你能在不到一个小时的时 ...

  4. 如何搭建一个简易的 Web Terminal(一)

    前言 在介绍本篇文章的时候,先说一下本篇文章的一些背景.笔者是基于公司的基础建设哆啦 A 梦(Doraemon)一些功能背景写的这篇文章,不了解.有兴趣的同学可以去 袋鼠云 的 github 下面了解 ...

  5. NodeJS 最快速搭建一个HttpServer

    最快速搭建一个HttpServer 在目录里放一个index.html cd D:\Web\InternalWeb start http-server -i -p 8081

  6. [原创] zabbix学习之旅五:如何快速搭建一个报警系统

    通过之前的文章,我们已搭建好zabbix server.agent和mail客户端,现在万事俱备,只差在server的界面中进行相应配置,即可快速搭建一个报警系统.总的来说,快速搭建一个报警系统的顺序 ...

  7. 快速搭建一个本地的FTP服务器

    快速搭建一个本地的FTP服务器   如果需要开发FTP文件上传下载功能,那么需要在本机上搭建一个本地FTP服务器,方便调试. 第一步:配置IIS Web服务器 1.1 控制面板中找到"程序& ...

  8. 快速搭建一个Spring Boot + MyBatis的开发框架

    前言:Spring Boot的自动化配置确实非常强大,为了方便大家把项目迁移到Spring Boot,特意总结了一下如何快速搭建一个Spring Boot + MyBatis的简易文档,下面是简单的步 ...

  9. 快速搭建一个直播Demo

    缘由 最近帮朋友看一个直播网站的源码,发现这份直播源码借助 阿里云 .腾讯云这些大公司提供的SDK 可以非常方便的搭建一个直播网站.下面我们来给大家讲解下如何借助 腾讯云 我们搭建一个简易的 直播示例 ...

  10. 快速搭建一个“微视”类短视频 App

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯云视频发表于云+社区专栏 关注公众号"腾讯云视频",一键获取 技术干货 | 优惠活动 | 视频方案 " ...

随机推荐

  1. VOIP(SIP)呼叫环境及流程试验

    宿主机:win11  IP: .1         PHONE: 102 虚拟机: v11     IP: .129     SIP SERVER 虚拟机: v10     IP: .128      ...

  2. K8S中Pod概念

    一.资源限制 Pod 是 kubernetes 中最小的资源管理组件,Pod 也是最小化运行容器化应用的资源对象.一个 Pod 代表着集群中运行的一个进程.kubernetes 中其他大多数组件都是围 ...

  3. 问题积累 - IAR - ErrorTa97:Cannot callintrinsic functionnounwwind _DSBfrom Thumb mode in this architecture

    IAR编译工程时报错: ErrorTa97:Cannot callintrinsic functionnounwwind _DSBfrom Thumb mode in this architectur ...

  4. 1007.Django模型基础02

    一.常用的查询 常用的查询方法(注:User为app项目): 获取所有的记录: rs = User.objects.all() 获取第一条数据:rs = User.objects.first() 获取 ...

  5. DBCC大全集之(适用版本MS SQLServer 2008 R2)----DBCC SHRINKDATABASE收缩指定数据库中的数据文件和日志文件的大小

    收缩指定数据库中的数据文件和日志文件的大小.  Transact-SQL 语法约定 语法 DBCC SHRINKDATABASE ( database_name | database_id | 0 [ ...

  6. Matlab %伍

    第五章:初级绘图进阶 Special Plots  loglog semilogx semilogy plotyy hist bar pie polar Logarithm Plots  x = lo ...

  7. 前端复习之JavaScript(ECMAScript5)

    啦啦啦啦啦啦啦啦绿绿绿绿绿绿 1 1.JavaScript: 2 前段三大语言:HTML CSS js 3 HTML:专门编写网页内容的语言 4 CSS:专门编写网页样式的语言 5 js:专门编写网页 ...

  8. thread互斥测试

    thread互斥测试 实践代码 #include <stdio.h> #include <stdlib.h> #include <pthread.h> //linu ...

  9. Spring 常见问题 - 2

    1. @Component, @Controller, @Repository, @Service 有何区别? @Component:这将 java 类标记为 bean.它是任何 Spring 管理组 ...

  10. c# 数组 集合 属性访问 设置

    当只修改数组或者集合的某一个特定值时不会经过CLR属性封装器