- Gin官网中文文档:Gin Web Framework(偶尔需要翻墙)
- Gin中文文档:Gin中文文档 · 看云(不用翻墙,但部分内容老旧)
准备工作
- 创建工程参考之前的Go 学习笔记 01-Go 项目的创建与运行文章
- 创建完成后,导入
gin
包:github.com/gin-gonic/gin
- 运行下面这段代码
package main
import "github.com/gin-gonic/gin"
func main() {
// 使用默认中间件创建一个gin路由器
// logger and recovery (crash-free) 中间件
router := gin.Default()
//接收Get请求
router.GET("/test", func(c *gin.Context) {
//返回响应信息
c.JSON(200, gin.H{
"message": "hello gin",
})
})
// 默认启动的是8080端口,也可以自己定义启动端口
router.Run()
}
在浏览器中访问127.0.0.1:8080/test
,可以看到浏览器显示hello gin
即准备工作完成
四种请求的使用
func main() {
// 使用默认中间件创建一个gin路由器
// logger and recovery (crash-free) 中间件
router := gin.Default()
//接收Get请求
// :id是占位符,可以用来取url中的参数
// 匹配的url格式: /test/123?user=wyatt&pwd=123456
router.GET("/test/:id", func(c *gin.Context) {
//获取uri参数
id := c.Param("id")
//获取query参数
user := c.DefaultQuery("user", "nil") //默认值设置为nil
pwd := c.Query("pwd")
//返回响应信息
c.JSON(200, gin.H{
"success": true,
"id": id,
"user": user,
"pwd": pwd,
})
})
router.POST("/test", func(c *gin.Context) {
//获取表单中数据
user := c.DefaultPostForm("user", "nil")
pwd := c.PostForm("pwd")
//返回响应信息
c.JSON(200, gin.H{
"success": true,
"user": user,
"pwd": pwd,
})
})
// 匹配的url格式: /test/123
router.DELETE("/test/:id", func(c *gin.Context) {
id := c.Param("id")
//返回响应信息
c.JSON(200, gin.H{
"success": true,
"id": id,
})
})
router.PUT("/test", func(c *gin.Context) {
//获取表单中数据
user := c.DefaultPostForm("user", "nil")
pwd := c.PostForm("pwd")
//返回响应信息
c.JSON(200, gin.H{
"success": true,
"user": user,
"pwd": pwd,
})
})
// 默认启动的是8080端口,也可以自己定义启动端口
router.Run()
}
模型绑定和验证
- 先创建一个结构体,然后把客户端传来的参数通过绑定的形式直接映射到结构体实例上
- 模型绑定和验证 | Gin Web Framework
- 可以给字段指定特定规则的修饰符,如果一个字段用
binding:"required"
修饰,并且在绑定时该字段的值为空,那么将返回一个错误。
type PostParams struct {
Name string `json:"name"`
Age int `json:"age"`
Sex bool `json:"sex"`
}
func main() {
router := gin.Default()
router.POST("/test", func(c *gin.Context) {
var p PostParams
err := c.ShouldBindJSON(&p)
if err != nil {
c.JSON(200, gin.H{
"msg": "失败",
"data": gin.H{},
})
} else {
c.JSON(200, gin.H{
"msg": "成功",
"data": p,
})
}
})
router.Run(":8080")
}
请求响应结果:

文件上传和返回
- 注意:前端上传文件时需要在请求头
Headers
设置Content-Type
为multipart/from-data
,Body
选择from-data
单文件
上传文件到服务器,服务器将接收到的文件先保存到本地然后直接返回接收到的文件信息(不是文件本身):
func main() {
router := gin.Default()
router.POST("/test", func(c *gin.Context) {
//接收到文件file
file, _ := c.FormFile("file")
//获取附加的表单信息
name:=c.PostForm("uploader")
//保存到本地
c.SaveUploadedFile(file, "./"+file.Filename)
//返回响应信息
c.JSON(200, gin.H{
"msg": file,
"uploader": name,
})
})
router.Run(":8080")
}
请求响应结果:

上传文件到服务器,服务器将接收到的文件先保存到本地再将保存到本地的文件返回:
func main() {
router := gin.Default()
router.POST("/test", func(c *gin.Context) {
//接收到文件file
file, _ := c.FormFile("file")
//保存到本地
c.SaveUploadedFile(file, "./"+file.Filename)
//返回保存到本地的文件
c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment;filename=%s", file.Filename))
c.File("./" + file.Filename)
})
router.Run(":8080")
}
请求响应结果:

多文件
func main() {
router := gin.Default()
router.POST("/test", func(c *gin.Context) {
//多文件接收
form, _ := c.MultipartForm()
files := form.File["file"]
//打印接收到的所有文件名
for _, file := range files {
log.Println(file.Filename)
}
})
router.Run(":8080")
}
路由分组和中间件
- 路由分组:同一组的路由前缀相同
func main() {
router := gin.Default()
// 简单的路由组: v1
v1 := router.Group("/v1")
// url: /v1/test
v1.GET("/test", func(context *gin.Context) {
fmt.Println("我在分组方法内部")
context.JSON(200, gin.H{
"success": true,
})
})
router.Run(":8080")
}
- 路由分组+中间件:洋葱模型
func middle1() gin.HandlerFunc {
return func(context *gin.Context) {
fmt.Println("我在方法前,我是中间件1")
context.Next() //继续往下走
fmt.Println("我在方法后,我是中间件1")
}
}
func middle2() gin.HandlerFunc {
return func(context *gin.Context) {
fmt.Println("我在方法前,我是中间件2")
context.Next() //继续往下走
fmt.Println("我在方法后,我是中间件2")
}
}
func main() {
router := gin.Default()
// 简单的路由组: v1
v1 := router.Group("/v1").Use(middle1()).Use(middle2())
v1.GET("/test", func(context *gin.Context) {
fmt.Println("我在分组方法内部")
context.JSON(200, gin.H{
"success": true,
})
})
router.Run(":8080")
}
访问/v1/test
后,控制台打印:
日志和日志格式
- Gin自带日志:
- 写日志文件 · Gin中文文档
- 自定义日志格式 · Gin中文文档
- 缺点:灵活度不够,自定义比较麻烦
- 第三方日志工具
- go-logging
- logrus
初识GROM
对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。ORM框架是连接数据库的桥梁,只要提供了持久化类与表的映射关系,ORM框架在运行时就能参照映射文件的信息,把对象持久化到数据库中。
连接数据库
- 导入驱动程序和gorm包
- 打开
Navicat
或mysql
,新建一个数据库 - 填入数据库名、用户名和密码等信息后连接数据库
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "root:123@tcp(127.0.0.1:3306)/goclass?charset=utf8mb4&parseTime=True&loc=Local"
_, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
}
自动化创建数据库表
- 创建结构体
- 使用
AutoMigrate
方法,若表不存在则自动创建
type Person struct {
//gorm的底层model,是一些规范
//包括主键 新增时间 删除时间 更新时间
gorm.Model
Name string
Sex bool
Age int
}
func main() {
dsn := "root:123@tcp(127.0.0.1:3306)/goclass?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Person{})
}
基本的增删改查
db.Create(&Person{
Model: gorm.Model{},
Name: "wyatt",
Sex: true,
Age: 21,
})
- 查
//查询符合条件的一条记录
var person1 Person
db.First(&person1, "name = ?", "wyatt")
fmt.Println(person1)
//查询符合条件的所有记录
var persons []Person
db.Where("sex=?", 1).Find(&persons)
fmt.Println(persons)
//查询符合多个条件的所有记录
db.Where("sex = ? AND age < ?", 1, 22).Find(&persons)
- 改
//修改单个属性
db.Where("id=?", 1).First(&Person{}).Update("name", "wang")
//修改多个属性
db.Where("id=?", 1).First(&Person{}).Updates(Person{
Name: "wyatt",
Age: 20,
})
//批量修改多个记录
db.Where("id in (?)", []int{1, 2}).Find(&[]Person{}).Updates(Person{
Name: "wyatt",
Age: 20,
})
- 删
//软删除
db.Delete(&Person{}, "id=?", 1)
db.Where("id in (?)", []int{1, 2}).Delete(&Person{})
//硬删除
db.Where("id in (?)", []int{1, 2}).Unscoped().Delete(&Person{})
gorm结构体创建技巧和结合gin使用
gorm结构体创建技巧
tag设置
工作中常用的:

type User struct {
//包含第一个主键 id
gorm.Model
//定义第二个主键 Name
//列名重定义为user_name
//指定数据类型为varchar 长度为100
Name string `gorm:"primarykey;column:user_name;type:varchar(100)"`
}
自定义表名
//这里实际是实现了gorm中一个接口的方法
func (u User) TableName() string {
//可以用if语句作分支控制,这里不演示
return "my_users"
}
关联
type Class struct {
gorm.Model
ClassName string
Students []Student //1.一对多
}
type Student struct {
gorm.Model
StudentName string
ClassID uint // 1.一对多
IDCard IDcard // 2.一对一
// 3.多对多
//`student_teachers` 是连接表
Teachers []Teacher `gorm:"many2many:student_teachers;"`
}
type IDcard struct {
gorm.Model
StudentID uint // 2.一对一
Num int
}
type Teacher struct {
gorm.Model
TeacherName string
// 3.多对多
Students []Student `gorm:"many2many:student_teachers;"`
}
func main() {
dsn := "root:123@tcp(127.0.0.1:3306)/goclass?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Teacher{}, &Class{}, &Student{}, &IDcard{})
}
运行结果:
填充数据:
//填充数据
i := IDcard{
Num: 123456,
}
t := Teacher{
TeacherName: "老师傅",
}
s := Student{
StudentName: "wyatt",
IDCard: i,
Teachers: []Teacher{t},
}
c := Class{
ClassName: "wyatt's class",
Students: []Student{s},
}
// classes -> students -> id_cards -> teachers -> student_teachers
// 创建班级就能通过表之间关系创建其他表数据
//不然通过teacher反向创建学生没班级gorm v2版本会报外键约束错误
_ = db.Create(&c).Error
结合gin使用
从前端获取数据后写入数据库中:
func main() {
dsn := "root:123@tcp(127.0.0.1:3306)/goclass?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Teacher{}, &Class{}, &Student{}, &IDcard{})
router := gin.Default()
router.POST("/create_class", func(context *gin.Context) {
var class Class
_ = context.BindJSON(&class)
db.Create(&class)
})
router.Run(":8888")
}
前端构造的json
数据:
{
"ClassName":"class1",
"Students":[
{
"StudentName":"学生1",
"IDCard":{
"Num":123
},
"Teachers":[{
"TeacherName":"melody"
}]
},
{
"StudentName":"学生2",
"IDCard":{
"Num":234
},
"Teachers":[{
"TeacherName":"melody"
}]
}]
}
从前端发送GET请求查询数据:
- 涉及一对多、多对多时,要使用
preload()
预加载 - 预加载可以嵌套
func main() {
dsn := "root:123@tcp(127.0.0.1:3306)/goclass?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Teacher{}, &Class{}, &Student{}, &IDcard{})
router := gin.Default()
router.POST("/create_class", func(context *gin.Context) {
var class Class
_ = context.BindJSON(&class)
db.Create(&class)
})
router.GET("/student/:ID", func(context *gin.Context) {
id := context.Param("ID")
var student Student
_ = context.BindJSON(&student)
db.Preload("Teachers").Preload("IDCard").First(&student, "id=?", id)
//预加载可以嵌套
//db.Preload("Teachers").Preload("Teachers.Students").Preload("IDCard").First(&student, "id=?", id)
context.JSON(200, gin.H{
"s": student,
})
})
router.Run(":8888")
}
查询到的结果:

gorm更多内容:bilibili
JWT-GO
创建JWT
type MyClaims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
func main() {
//密钥
mySigningKey := []byte("thisisakey")
// Create the claims
claims := MyClaims{
"wyatt",
jwt.RegisteredClaims{
//生效时间
NotBefore: jwt.NewNumericDate(time.Now()),
//过期时间
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
//签发人
Issuer: "wyatt",
//IssuedAt: jwt.NewNumericDate(time.Now()),
//Subject: "somebody",
//ID: "1",
//Audience: []string{"somebody_else"},
},
}
//包含结构的创建一个JWT
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
//加密token
ss, err := token.SignedString(mySigningKey)
if err != nil {
fmt.Printf("%v", err)
}
fmt.Printf("%v", ss)
}
解析JWT
//解析JWT
token, err := jwt.ParseWithClaims(ss, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return mySigningKey, nil
})
if err != nil {
fmt.Println(err)
}
//这里用到了断言
fmt.Println(token.Claims.(*MyClaims).Username)
Casbin模型
Casbin基础模型
PERM元模型
- Policy策略、Effect影响、Request请求、Matchers匹配规则
- 定义一个策略
P
和匹配规则M
,通过请求R
过来的参数,与策略P
通过规则M
进行匹配,获取一个影响E
,拿到影响E
的结果进到影响E
的表达式,返回一个布尔值
Policy策略
p={sub,obj,act,eft}
- sub(subject,访问实体)、obj(obj,访问资源)、act(action,访问方法)、eft(策略结果,一般为空,默认指定allow,还可以定义为deny)
- 策略一般存储到数据库,因为数量很多
[policy_definition]
p=sub,obj,act
Request请求
r={sub,obj,act}
Matchers匹配规则
m = r.sub == p.sub && r.act == p.act && r.obj == p.obj
Matchers
是Request
和Policy
的匹配规则- 会把
r
和p
按照上述描述进行匹配,从而返回匹配结果(eft
),如果不定义,会返回allow
,如果定义过了,会返回我们定义过的那个结果
Effect影响
e = some(where(p.eft == allow))
- 决定是否可以放行
- 这里的规则是定死的,不是自定义的

role_definition角色域
role_definition角色域:用来存储用户的角色
g = 用户,角色
- 表示以角色为基础
- 用户是那个角色
g = 用户,角色,域
- 表示以域为基础(多商户模式)
- 用户是那个角色属于那个商户(域)
Casbin使用
本地文件模式
main.go
:
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
)
func main() {
//要先手动在当前目录下创建两个文件
e, err := casbin.NewEnforcer("./src/model.conf", "./src/policy.csv")
if err != nil {
fmt.Println(err)
}
sub := "zhangsan" // 想要访问资源的用户。
obj := "data1" // 将被访问的资源。
act := "read" // 用户对资源执行的操作。
ok, err := e.Enforce(sub, obj, act)
if err != nil {
// 处理err
//fmt.Printf("%s", err)
}
if ok == true {
// 允许alice读取data1
fmt.Println("通过")
} else {
// 拒绝请求,抛出异常
fmt.Println("未通过")
}
}
model.conf
:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
policy.csv
:
p,zhangsan,data1,read
使用数据库存储policy
main.go
:
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// Your driver and data source.
a, _ := gormadapter.NewAdapter("mysql", "root:123@tcp(127.0.0.1:3306)/casbin", true)
e, _ := casbin.NewEnforcer("./src/model.conf", a)
sub := "alice" // 想要访问资源的用户。
obj := "data1" // 将被访问的资源。
act := "read" // 用户对资源执行的操作。
//通过API向数据库添加policy
added, err := e.AddPolicy("alice", "data1", "read")
fmt.Println(added)
if err != nil {
fmt.Println(err)
}
ok, err := e.Enforce(sub, obj, act)
if err != nil {
// 处理err
//fmt.Printf("%s", err)
}
if ok == true {
// 允许alice读取data1
fmt.Println("通过")
} else {
// 拒绝请求,抛出异常
fmt.Println("未通过")
}
}
//运行结果:
//true
//通过
model.conf
同上
提前建一个数据库casbin
,casbin会自动建一个表casbin_rule
对policy增删改查
//查看v0是alice的policy
filteredPolicy := e.GetFilteredPolicy(0, "alice")
fmt.Println(filteredPolicy)
//运行结果:
//[[alice data1 read]]
增:
added, err := e.AddGroupingPolicy("alice", "data2_admin")
model.conf
中添加role_definition
,修改role_definition
:
[role_definition]
g = _,_
[matchers]
m = g(r.sub,p.sub) && r.obj == p.obj && r.act == p.act
删:
改:
自定义比较函数
main.go
:
func main() {
// Your driver and data source.
a, _ := gormadapter.NewAdapter("mysql", "root:123@tcp(127.0.0.1:3306)/casbin", true)
e, _ := casbin.NewEnforcer("./src/model.conf", a)
// 注册自定义匹配原则 KeyMatchFunc
e.AddFunction("my_func", KeyMatchFunc)
sub := "alice" // 想要访问资源的用户。
obj := "data1" // 将被访问的资源。
act := "read" // 用户对资源执行的操作。
ok, err := e.Enforce(sub, obj, act)
if err != nil {
// 处理err
fmt.Printf("%s", err)
}
if ok == true {
// 允许alice读取data1
fmt.Println("通过")
} else {
// 拒绝请求,抛出异常
fmt.Println("未通过")
}
}
// KeyMatch 第一个匹配原则
func KeyMatch(key1 string, key2 string) bool {
return key1 == key2
}
// KeyMatchFunc 包装一下 KeyMatch
func KeyMatchFunc(args ...interface{}) (interface{}, error) {
name1 := args[0].(string)
name2 := args[1].(string)
return (bool)(KeyMatch(name1, name2)), nil
}
model.conf
:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _,_
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub,p.sub) && my_func(r.obj,p.obj) && r.act == p.act