Golang之gin框架
1. Gin框架
1.1 初识
读取文件可以使用content , _=ioutil.ReadFile(“xxx.txt”)方式,比os中的read简单
例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:
请求方法 URL 含义 GET /book 查询书籍信息 POST /create_book 创建书籍记录 POST /update_book 更新书籍信息 POST /delete_book 删除书籍信息 同样的需求我们按照RESTful API设计如下:
请求方法 URL 含义 GET /book 查询书籍信息 POST /book 创建书籍记录 PUT /book 更新书籍信息 DELETE /book 删除书籍信息 通过不同的方法代表不同的动作,这样就不用建立多个文件,维护代码方便
1.2 template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21创建一个main.go文件,在其中写下HTTP server端代码如下:
// main.go
//原生的模板渲染
func sayHello(w http.ResponseWriter, r *http.Request) {
// 解析指定文件生成模板对象,即解析模板
tmpl, err := template.ParseFiles("./hello.tmpl")//tmp1为生成的模板对象
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
// 利用给定数据渲染模板,并将最终渲染成形的html写入w,由w来返回给客户端
tmpl.Execute(w, "沙河小王子")
}
func main() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Println("HTTP server failed,err:", err)
return
}
}条件判断
1
2
3
4
5
6
7
8
9
10
11
12{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
eg:
{{if lt .ma.age 18}}
好好学习
{{else}}
好好工作
{{end}} //记住判断符号在前面range遍历
1
2
3{{range $index,$value := .hobby}}//hobby为预定义切片,其他类型也可以
<p> {{$index}} - {{$hobby}}</p> //执行动作
{{end}}index取值//针对数组,切片或者字典
1
{{index .hobby 2}}//取出hobby切片中下标为2的值
模板添加添加自定义函数
1
2
3
4
5
6
7
8
9
10
11
12//当前目录下有一个f.tmpl的模板文件
k:= func(name string)(string,error){
return name+"年轻又帅气!",nil
}//自定义函数
t := template.New("f.tmpl")//创建一个名字为f.tmpl的模板对象,名字一定要和模板名字能对应上,此时可以理解为创建了一个模板对象,其名字为f.tmpl,但是内容啥的都为空
t.Funcs(template.FuncMap{
"kua99":k
})//告诉模板引擎,我现在给t添加了一个自定义函数,通过kua99来用
//解析模板
_,err := t.ParseFiles("./f.tmpl")//解析模板,可以理解为给t添加了模板内容,现在t这个模板对象既有名字又有可用自定义函数,又有内容
name := "小王子"
t.Execute(w,name)//渲染模板并发送给w其中f.tmpl内容如下:
1
2...html
<body>{{kua99 .}}</body>//在模板文件中运用自定义函数当一个模板1嵌套了另一个模板2,在解析模板时参数顺序要写对,先是1后是2
当一个模板1继承了另一个模板2,在解析模板时参数顺序要写对,先是2后是1
当模板解析时,解析了不止一个模板,则在渲染模板时要用ExcuteTemplate(),并在参数中指定渲染其中哪一个
记着在模板继承的时候根模板中块代码那块有个. 以及在子模版中继承时也有个.
注意:
1
2
3
4
5
6t,err := template.ParseFiles("./template/base.tmpl","./template/xiaoming.tmpl")
if err != nil{
fmt.Println("err= ",err)
}
name := "xiaoming"
t.ExecuteTemplate(w,"xiaoming.tmpl",name)//第二个参数直接写文件名即该模板define的名字,不要写./template/xiaoming.tmpl路径推荐新建一个项目时打开一个新的goland窗口
以上为用net/http包进行模板渲染的东西,以下为利用gin框架进行模板渲染
1.3 gin框架渲染
gin中已经定义好了一个map方便用户使用,该map为gin.H{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18func main() {//gin框架下的模板渲染 1.定义模板 2.解析模板 3.渲染模板
r := gin.Default()
r.LoadHTMLFiles("templates/posts/index.html", "templates/users/index.html")//解析模板
//r.LoadHTMLGlob("templates/**/*"),其中**表示所有目录,*表示所有文件
r.GET("/posts/index", func(c *gin.Context) {//该匿名函数为渲染模板,将c就当作那条请求链接
c.HTML(http.StatusOK, "posts/index.html", gin.H{
"title": "posts/index",
})
})
r.GET("users/index", func(c *gin.Context) {//该匿名函数为渲染模板
c.HTML(http.StatusOK, "users/index.html", gin.H{
"title": "users/index",
})
})
r.Run(":8080")//服务跑起来
}静态文件:html页面上用到的样式文件.css js文件 图片
添加预定义函数必须在解析模板之前
1
r.Static("/xxx", "./static")//第二个目录为静态文件的目录,第一个参数可以理解为给./static取了个别名,在html文件中出现/xxx就用./static替换
如果没有给一个模板起名字,那么默认就是文件名
小米网站搭建项目xiaomi
json渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24func main() {
r := gin.Default()
// gin.H 是map[string]interface{}的缩写
r.GET("/someJSON", func(c *gin.Context) {
// 方式一:自己拼接JSON
c.JSON(http.StatusOK, gin.H{"message": "Hello world!"})
})
r.GET("/moreJSON", func(c *gin.Context) {
// 方法二:使用结构体
type msg struct {
Name string `json:"name"`
Message string
Age int
}
var msg1 := msg{
Name:"xiaoming",
Message:"abc",
Age:18
}
c.JSON(http.StatusOK, msg1)
})
r.Run(":8080")
}参数绑定:前端传过来的参数在后端自动生成一个结构体存储相应参数
gin框架有两种字符串拼接方式
1
2
3
4//第一种方式,eg:
dst := fmt.Sprintf("./%s",f.filename)
//第二种方式,eg:
dst := path.Join("./%s",f.filename)goland中unhandle error指的是没有处理返回的err
1 | package main //当用户访问home目录时将static文件夹下的index.html返回 |
1.4 gin路由
- 一个路径加一个请求方法对应一个处理函数就叫一个路由
- r.Any通常配合switch一起用
1.5 中间件
中间件又叫中间件函数,它本质是一个函数,该函数必须是handlerfunc函数类型,换句话说handlerfunc函数类型的函数就可以充当中间件
1
2
3
4
5
6//handlerfunc函数类型
type handlerFunc func(*context)
//eg:
func m1 (c *gin.context){
fmt.println("m1 in....")
}//这就是一个中间件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47//定义一个耗时统计中间件
package main
import
func indexHandler(c *gin.context){
fmt.println("index in...")
c.JSON(http.StatusOK,gin.H{
"msg":"index"
})
}
//中间件写法1
func m1(c *gin.context){
fmt.println("m1 in...")
start = time.Now()
c.Next()
cost := time.Since(start)
fmt.printf("耗时:%v\n",cost )
}
//中间件写法2,常用,利用闭包
func StatCost(docheck) gin.HandlerFunc {//可以用docheck作为一个中间件开关
//数据库连接
//数据库查询
return func(c *gin.Context) {
if docheck{
start := time.Now()
c.Next()
// 不调用该请求的剩余处理程序
// c.Abort()
// 计算耗时
cost := time.Since(start)
log.Println(cost)
}
else{
c.Next()
}
}
func main(){
r:=gin.Default()
r.use(m1,StatCost(ture))//全局注册中间件函数m1
//GET(relativePath string,handlers ...HandlerFunc) IRoutes
r.GET("/index",indexHandler)//indexHandler为页面处理函数不是中间件
r.GET("/shop",func(c *gin.Context)){//这里为了方便写成了匿名函数形式
c.JSON(http.StatusOK,gin.H{
"msg":"shop",
})
}
r.Run()
}中间件可以定义的不止一个
中间件有三种1.全局中间件2.局部中间件:为路由组设置中间件3.单一中间件:为某个路由设置中间件
跨中间件取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14r.use(m1,m2)//有两个中间件
func m1(c *gin.context){
//认证成功
c.Set("name":"xiaoming")//可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
}
func m2(c *gin.context){
name,ok := c.Get("name")//后续处理函数通过c。
if !ok{
name = "匿名用户"
}
c.JSON(200,gin.H{
"name":name,
})
}
1.6 gorm
gorm利用其接口可以帮助我们快速的操作mysql数据库,而省去了自己编写sql语句的步骤
一个结构体对应一个表,表名默认就是结构体名称的复数
例如创建一个user表,只需要创建对应结构体并automigrate就行
1
2
3
4
5
6
7
8
9
10
11
12type User struct {
gorm.Model
Name string
Age sql.NullInt64
Birthday *time.Time
Email string `gorm:"type:varchar(100);unique_index"`
Role string `gorm:"size:255"` // 设置字段大小为255
MemberNumber *string `gorm:"unique;not null"` // 设置会员号(member number)唯一并且不为空
Num int `gorm:"AUTO_INCREMENT"` // 设置 num 为自增类型
Address string `gorm:"index:addr"` // 给address字段创建名为addr的索引
IgnoreMe int `gorm:"-"` // 忽略本字段
}数据库中就会出现如下表
GORM 默认会使用名为ID的字段作为表的主键,也可设置不加s,或者自定义表明,下方链接有详细步骤
当表名和字段名是由两个单词组成时,创建表时会默认给两个单词中间加_,可修改
注意所有代码中的表操作可以通过mysql命令:show create table 表名; 来查看该表的配置信息
注意:通过tag定义字段的默认值,在创建记录时候生成的 SQL 语句会排除没有值或值为 零值 的字段。 在将记录插入到数据库后,Gorm会从数据库加载那些字段的默认值。
举个例子:
1
2var user = User{Name: "", Age: 99}
db.Create(&user)上面代码实际执行的SQL语句是
INSERT INTO users("age") values('99');
,排除了零值字段Name
,而在数据库中这一条数据会使用设置的默认值小王子
作为Name字段的值。- 所有字段的零值, 比如
0
,""
,false
或者其它零值
,都不会保存到数据库内,而是会会使用他们的默认值。 如果你想避免这种情况,可以考虑使用指针或实现Scanner/Valuer
接口,比如:
使用指针方式实现零值存入数据库
1
2
3
4
5
6
7
8// 使用指针
type User struct {
ID int64
Name *string `gorm:"default:'小王子'"`
Age int64
}
user := User{Name: new(string), Age: 18))}
db.Create(&user) // 此时数据库中该条记录name字段的值就是''使用Scanner/Valuer接口方式实现零值存入数据库
1
2
3
4
5
6
7
8// 使用 Scanner/Valuer
type User struct {
ID int64
Name sql.NullString `gorm:"default:'小王子'"` // sql.NullString 实现了Scanner/Valuer接口,是一个结构体
Age int64
}
user := User{Name: sql.NullString{"", true}, Age:18}//设置为ture时,数据库就知道存入前面的string字段"",如果设置为false就用默认值
db.Create(&user) // 此时数据库中该条记录name字段的值就是'' 有的结构体用到指针类型就是因为这个原因,该字段可能传入零值为了防止被存为默认值就用指针或者方法2
- 所有字段的零值, 比如
1
2
3
4
5type Model struct { //gorm.model
ID uint `gorm:"primary_key"`
CreatedAt time.Time //该记录的创建时间
UpdatedAt time.Time //该记录的更新时间
DeletedAt *time.Time `sql:"index"` //该记录的删除时间,某条记录的该字段值不为null时表明被删除<-软删除当要用where的时候有两种写法
1
2
3
4//写法一
db.Where("name = ?", "jinzhu").Find(&users)//users为User结构体切片
//写法二,内联条件
db.Find(&user, "name = ?", "jinzhu")使用 struct 更新时,只会更新非零值字段,所以建议使用map
代码里出现User{},其代表的就是数据库中的user表
1
2
3
4db.Model(&user).函数//给查询出来的一条数据应用后面的函数
db.Model(&users).函数//给查询出来的全部数据应用后面的函数
db.Model(&user{}).函数//给user表应用后面的函数在查询出来的结果中指定条件更新
1
db.Model(&users).Where("age > 10").UpdateColumn("age", gorm.Expr("age - ?", 1))
更新多个字段用updates,更新一个字段用update
当结构体嵌入gorm.Modle时gorm的删除都是软删除,即给delet字段加时间,如果要真删除,调用Unscoped()
当然结构体没有嵌入gorm.Modle时gorm的删除都是真删除
1
2// 物理删除
db.Debug().Unscoped().Where("name=?", "aaa").Delete(User{})Gorm CURD
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79package main //CRUD基本操作
import (
//"database/sql"
"fmt"
"github.com/jinzhu/gorm"
//"time"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
//type User struct {
// gorm.Model
// Name string
// Age sql.NullInt64
// Birthday *time.Time
// Email string `gorm:"type:varchar(100);unique_index"`
// Role string `gorm:"size:255"` // 设置字段大小为255
// MemberNumber *string `gorm:"unique;not null"` // 设置会员号(member number)唯一并且不为空
// Num int `gorm:"AUTO_INCREMENT"` // 设置 num 为自增类型
// Address string `gorm:"index:addr"` // 给address字段创建名为addr的索引
// IgnoreMe int `gorm:"-"` // 忽略本字段
//}
// 1.定义模型
type User struct {
gorm.Model
Name string `gorm:"default:'xupt_w'"`//默认值为xupt_w
Age int64
Active bool
}
func main() {
//链接mysql数据库
db, err := gorm.Open("mysql", "root:root1234@(192.168.99.100:13306)/db1?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil{
fmt.Println("err is ",err)
return
}
defer db.Close()
//2.把模型与数据库中的表对应起来,即创建一个对应user表
db.AutoMigrate(&User{})
//3.创建
u := User{Name:"wjk",Age:21}
db.Debug().Create(&u)//如果想要看这句执行了什么sql语句可以加debug()函数
fmt.Println(db.NewRecord(&u))//判断是否该条记录存在
u1 := User{Name:"aaa",Age:19}
u2 := User{Name:"bbb",Age:20}
db.Create(&u1)
db.Create(&u2)
u3 := User{Name:"ccc",Age:21}
db.Create(&u3)
//4. 查询
var user User
db.First(&user)//返回值为结构体存储在user变量
fmt.Println(user)
//如果查询多条记录,则要先定义一个结构体切片
var users []User
db.Find(&users)
fmt.Println(users)
//其他的查询操作见https://www.liwenzhou.com/posts/Go/gorm_crud/#autoid-1-3-3
//5. 更新
db.Model(&user).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
//注意在执行update函数时会执行几个gorm的钩子(hook)函数,该函数的作用是同时更新updated_at字段,如果不想更新updated_at字段即禁用hook函数可以使用UpdateColumn函数
//修改多个字段,利用map,如果想更新或忽略某些字段,可以使用 Select,Omit
db.Model(&user).Updates(map[string]interface{}{"name": "john", "age": 18, "active": false})
//其他的查询操作见https://www.liwenzhou.com/posts/Go/gorm_crud/#autoid-1-3-3
//6. 删除
db.Delete(&user)
//GORM 会通过主键去删除记录,所以也可以通过
var s=User{}
s.ID=2
db.Delete(&s)//删除id为2的数据,DELETE from users where id=2;如果主键为空,GORM 会删除该表的所有记录
//以上的删除方法太麻烦,还要传入主键才能删除,以下为简便方法删除name='wjk'这条的方法
db.Where("name=?", "wjk").Delete(User{})
//或者,同样的效果
db.Delete(User{}, "name=?", "wjk")
}
源码地址: