1. Gin框架

1.1 初识

  1. 读取文件可以使用content , _=ioutil.ReadFile(“xxx.txt”)方式,比os中的read简单

  2. 例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们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. 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
    }
    }
  2. 条件判断

    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}} //记住判断符号在前面
  3. range遍历

    1
    2
    3
    {{range $index,$value := .hobby}}//hobby为预定义切片,其他类型也可以
    <p> {{$index}} - {{$hobby}}</p> //执行动作
    {{end}}
  4. index取值//针对数组,切片或者字典

    1
    {{index .hobby 2}}//取出hobby切片中下标为2的值
  5. 模板添加添加自定义函数

    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>//在模板文件中运用自定义函数
  6. 当一个模板1嵌套了另一个模板2,在解析模板时参数顺序要写对,先是1后是2

    当一个模板1继承了另一个模板2,在解析模板时参数顺序要写对,先是2后是1

  7. 当模板解析时,解析了不止一个模板,则在渲染模板时要用ExcuteTemplate(),并在参数中指定渲染其中哪一个

  8. 记着在模板继承的时候根模板中块代码那块有个. 以及在子模版中继承时也有个.

  9. 注意:

    1
    2
    3
    4
    5
    6
    t,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路径
  10. 推荐新建一个项目时打开一个新的goland窗口

    以上为用net/http包进行模板渲染的东西,以下为利用gin框架进行模板渲染

https://www.liwenzhou.com/posts/Go/go_template/


1.3 gin框架渲染

  1. gin中已经定义好了一个map方便用户使用,该map为gin.H{}

  2. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    func 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")//服务跑起来
    }
  3. 静态文件:html页面上用到的样式文件.css js文件 图片

  4. 添加预定义函数必须在解析模板之前

  5. 1
    r.Static("/xxx", "./static")//第二个目录为静态文件的目录,第一个参数可以理解为给./static取了个别名,在html文件中出现/xxx就用./static替换
  6. 如果没有给一个模板起名字,那么默认就是文件名

  7. 小米网站搭建项目xiaomi

  8. json渲染

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    func 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")
    }
  9. 参数绑定:前端传过来的参数在后端自动生成一个结构体存储相应参数

  10. gin框架有两种字符串拼接方式

    1
    2
    3
    4
    //第一种方式,eg:
    dst := fmt.Sprintf("./%s",f.filename)
    //第二种方式,eg:
    dst := path.Join("./%s",f.filename)
  11. goland中unhandle error指的是没有处理返回的err

https://www.liwenzhou.com/posts/Go/Gin_framework/

1
2
3
4
5
6
7
8
9
10
11
12
13
package main //当用户访问home目录时将static文件夹下的index.html返回

import "github.com/gin-gonic/gin"

func main(){
r := gin.Default()
r.LoadHTMLGlob("./templates/*")//解析模板
r.Static("/xxx","./static")//加载静态文件,将index.html中的所有./static替换为/xxx
r.GET("/home", func(c *gin.Context) {
c.HTML(200,"index.html",nil)
})
r.Run(":9090")
}

1.4 gin路由

  1. 一个路径加一个请求方法对应一个处理函数就叫一个路由
  2. r.Any通常配合switch一起用

https://www.liwenzhou.com/posts/Go/Gin_framework/

1.5 中间件

  1. 中间件又叫中间件函数,它本质是一个函数,该函数必须是handlerfunc函数类型,换句话说handlerfunc函数类型的函数就可以充当中间件

    1
    2
    3
    4
    5
    6
    //handlerfunc函数类型
    type handlerFunc func(*context)
    //eg:
    func m1 (c *gin.context){
    fmt.println("m1 in....")
    }//这就是一个中间件
  2. 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()
    }
  3. 中间件可以定义的不止一个

  4. 中间件有三种1.全局中间件2.局部中间件:为路由组设置中间件3.单一中间件:为某个路由设置中间件

  5. 跨中间件取值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    r.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,
    })
    }

https://www.liwenzhou.com/posts/Go/Gin_framework/

1.6 gorm

  1. gorm利用其接口可以帮助我们快速的操作mysql数据库,而省去了自己编写sql语句的步骤

  2. 一个结构体对应一个表,表名默认就是结构体名称的复数

    例如创建一个user表,只需要创建对应结构体并automigrate就行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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:"-"` // 忽略本字段
    }

    数据库中就会出现如下表

  3. GORM 默认会使用名为ID的字段作为表的主键,也可设置不加s,或者自定义表明,下方链接有详细步骤

  4. 当表名和字段名是由两个单词组成时,创建表时会默认给两个单词中间加_,可修改

  5. 注意所有代码中的表操作可以通过mysql命令:show create table 表名; 来查看该表的配置信息

  6. 注意:通过tag定义字段的默认值,在创建记录时候生成的 SQL 语句会排除没有值或值为 零值 的字段。 在将记录插入到数据库后,Gorm会从数据库加载那些字段的默认值。

    举个例子:

    1
    2
    var 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

  7. 1
    2
    3
    4
    5
    type Model struct {   //gorm.model
    ID uint `gorm:"primary_key"`
    CreatedAt time.Time //该记录的创建时间
    UpdatedAt time.Time //该记录的更新时间
    DeletedAt *time.Time `sql:"index"` //该记录的删除时间,某条记录的该字段值不为null时表明被删除<-软删除
  8. 当要用where的时候有两种写法

    1
    2
    3
    4
    //写法一
    db.Where("name = ?", "jinzhu").Find(&users)//users为User结构体切片
    //写法二,内联条件
    db.Find(&user, "name = ?", "jinzhu")
  9. 使用 struct 更新时,只会更新非零值字段,所以建议使用map

  10. 代码里出现User{},其代表的就是数据库中的user表

  11. 1
    2
    3
    4
    db.Model(&user).函数//给查询出来的一条数据应用后面的函数
    db.Model(&users).函数//给查询出来的全部数据应用后面的函数
    db.Model(&user{}).函数//给user表应用后面的函数

  12. 在查询出来的结果中指定条件更新

    1
    db.Model(&users).Where("age > 10").UpdateColumn("age", gorm.Expr("age - ?", 1))
  13. 更新多个字段用updates,更新一个字段用update

  14. 当结构体嵌入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
    79
    package 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")
    }

https://www.liwenzhou.com/posts/Go/gorm/

https://www.liwenzhou.com/posts/Go/gorm_crud/#autoid-1-3-3

源码地址:

https://github.com/Q1mi/go_web