本文作者:陈进坚
个人博客:https://jian1098.github.io
CSDN博客:https://blog.csdn.net/c_jian
简书:https://www.jianshu.com/u/8ba9ac5706b6
联系方式:jian1098@qq.com
下载使用
1 | $ go get -u github.com/gin-gonic/gin |
1 | import "github.com/gin-gonic/gin" |
HTTP服务
1 | func main() { |
路由绑定
router.GET("/", index)
表示用GET
方式接收路由,如果路由是根目录,那么直接执行index
控制器方法,index
控制器必须含有gin.Context
参数,也可以向上面一样将index
控制器的内容写成匿名函数。
1 | router.GET("/", index) |
路由分离
为了更好的管理路由,最好将路由和控制器分开不同的文件
在根目录下新建router.go
1 | package gin |
在main
方法中进行初始化
1 | func main() { |
此时目录结构如下
1 | --src |
路由组
一些情况下,我们会有统一前缀的 url 的需求,典型的如 Api 接口版本号 /v1/something。Gin 可以使用 Group 方法统一归类到路由组中
1 | func main() { |
异步处理
goroutine
机制可以方便地实现异步处理
1 | func main() { |
接收参数
接收GET参数
访问链接: http://localhost:8080/user?firstname=jian&lastname=chen
路由
1 | router.GET("/user", hello) |
控制器 (这里用的接收方法是Query
)
1 | func hello(c *gin.Context) { |
或者
访问链接:http://localhost:8080/user/jian/eat
路由 (参数名用:
号标记)
1 | router.GET("/user/:name/:action", user) |
或者
1 | router.GET("/user/:name/*action", user) |
上面这个写法将会匹配/user/:name/
开头的所有路由
控制器 (这里的接收方法是Param
)
1 | func user(c *gin.Context) { |
接收POST参数
访问链接:http://localhost:8080/post
路由
1 | router.POST("/post", post) |
控制器
1 | func post(c *gin.Context) { |
文件上传
单文件上传
路由
1 | router.POST("/upload", upload) |
控制器
1 | func upload(c *gin.Context) { |
多文件上传
注意多文件上传表单<form>
标签需要注明属性enctype="multipart/form-data" method="post"
,<input>
标签的name
属性值必须相同,例如全部为name="file"
路由
1 | router.POST("/multiupload", multiupload) |
控制器
1 | func multiupload(c *gin.Context) { |
视图模板
目录结构
在根目录下新建
templates
文件夹用于存放html
页面,为了便于管理在templates
目录下再创建一个index
文件夹存放与index
控制器相关的页面,在index
目录下新建index.html
文件
1 |
|
此时目录结构如下
1 | - src |
加载视图
在初始化路由的位置加载所有视图模板,其中**
表示各个控制器或路由组对应的视图目录,*
表示该目录下所有文件
1 | router := gin.Default() |
控制器绑定视图
1 | func index(c *gin.Context) { |
静态文件
网页开发离不开css
和图片等静态资源文件,我们必须设置好路径才能正确访问,例如我的目录结构为(缩进表示二级目录)
1 | --src |
如果index.html
文件需要引入index.css
文件,则在路由申请的地方声明
1 | //加载模板 |
然后index.css
文件中这样调用就可以了,其他静态资源用法类似
1 | <link rel="stylesheet" href="/static/css/index.css"> |
参数传递
在控制器传递参数
1 | c.HTML(http.StatusOK, "index.html", gin.H{ |
在视图渲染参数
1 | <h1> |
重定向
1 | r.GET("/redirect", func(c *gin.Context) { |
中间件
使用中间件
1 | router := gin.Default() |
中间件实现
1 | package middleware |
数据绑定和验证
使用 c.ShouldBind
方法,可以将参数自动绑定到 struct
,该方法是会检查 Url 查询字符串和 POST 的数据,而且会根据 content-type
类型,优先匹配JSON
或者 XML
,之后才是 Form
。数据绑定可以用来做数据验证,例如
路由
1 | router.POST("/binding", binding) |
控制器
1 | //数据结构体,username为表单字段,required表示必须参数,可选的话binding留空即可 |
当没有接收到参数时返回
1
2
3{
"error": "Key: 'Login.Username' Error:Field validation for 'Username' failed on the 'required' tag"
}当参数错误时返回
1
2
3{
"msg": "username or password error"
}当接收到参数且正确时放回
1
2
3{
"msg": "Login successfully"
}不用在接收参数时用
if
逐个进行验证,除了binding:"required"
属性外还有更多的校验规则,可以参考 https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags
日志
自带日志
Gin
日志默认只在控制台显示,如果要写入文件需要在main
方法中声明
1 | gin.DisableConsoleColor() //关掉控制台颜色,可省略 |
Logrus 日志库
Gin自带的日志系统只支持简单的功能,需要更强大的功能还需要用到第三方日志库,这里选择github
上面star
最多的Logrus
。
下载
1 | go get github.com/sirupsen/logrus |
使用
1 | package main |
数据库
Gin
框架没有自带的数据库封装,导入的数据库驱动由开发使用的数据库类型决定,例如开发使用mysql
就直接import _ "github.com/go-sql-driver/mysql"
;但是访问数据库都是直接用写 sql,取出结果然后自己拼成对象,使用上面不是很方便,可读性也不好。这里使用目前github
上star
数量最多的https://github.com/jinzhu/gorm
gorm
的详细教程参考http://gorm.book.jasperxu.com/models.html#md
下载
1 | go get -u github.com/jinzhu/gorm |
连接mysql
1 | package gin |
数据表
1 | import ( |
添加记录
1 | //添加记录 |
查询数据
无条件查询
1 | // 获取第一条记录,按主键排序 |
Where查询条件
1 | user := User{} |
Not条件查询
1 | db.Not("name", "jinzhu").First(&user) |
Or条件查询
1 | db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users) |
指定字段和表
1 | db.Select("name, age").Find(&users) |
Order条件查询
1 | db.Order("age desc, name").Find(&users) |
Limit条件查询
1 | db.Limit(3).Find(&users) |
Offset条件查询
指定在开始返回记录之前要跳过的记录数
1 | db.Offset(3).Find(&users) |
Count条件查询
1 | db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count) |
多条件查询
1 | db.Where("role = ?", "admin").Or("role = ?", "super_admin").Not("name = ?", "jinzhu").Find(&users) |
更新数据
1 | // 使用组合条件更新单个属性 |
删除数据
1 | db.Where("email LIKE ?", "%jinzhu%").Delete(Email{}) |
执行原生SQL语句
Scan()是将结果扫描到另一个结构中。
1 | db.Exec("DROP TABLE users;") |
连接池
1 | db.DB().SetMaxIdleConns(10) |
锁行
注意:加行锁的表必须是InnoDB并且要加索引,否则无效;语句必须在事务里面,必须提交或回滚
1 | // 为Select语句添加扩展SQL选项 |
锁表
1 | db.Exec("LOCK TABLES real_table WRITE, insert_table WRITE;") //锁定real_table和insert_table表 |
日志
1 | // 启用Logger,显示详细日志 |
事务
1 | // 开始事务 |