关于go:Gin-GORM-入门到实战

6次阅读

共计 10567 个字符,预计需要花费 27 分钟才能阅读完成。

本文参加了思否技术征文,欢送正在浏览的你也退出。

Hello,大家好,我是海军,最近始终在学习 Go,目前在做我的项目相熟 Go 阶段。本文来分享一下 Gin + GORM 的一些 开发体验,有喜爱 Go 方向的敌人,欢送一起交流学习呀!
后续会更新实战我的项目,目前在实现一个 技术论坛我的项目,结尾有效果图,前端局部实现了,当初在欠缺后端接口和前端联调的过程,没多久就会公布了。后续,会再写一篇我的项目文章。

干就完了 🤔

导读目录

Gin

  • Gin 学习路线
  • Gin 入门装置
  • Gin 中间件应用
  • Gin 获取申请参数
  • Gin 获取 JSON 数据
  • Gin Cookie
  • Gin Session
  • Gin 上传文件

GORM

  • 什么是 GORM
  • 如何建 Model

Gin

学习路线

入门装置

装置 Gin

  1. 下载依赖
$ go get -u github.com/gin-gonic/gin
  1. 将 gin 引入到代码中:
import "github.com/gin-gonic/gin"
  1. (可选)如果应用诸如 http.StatusOK 之类的常量,则须要引入 net/http 包:
import "net/http"

demo

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func main() {fmt.Println("Gin 入门学习")

    // 创立一个默认的路由引擎
    r := gin.Default()
    // GET:申请形式;/hello:申请的门路
    // 当客户端以 GET 办法申请 /hello 门路时,会执行前面的匿名函数
    r.GET("/hello", func(c *gin.Context) {
        // c.JSON:返回 JSON 格局的数据
        c.JSON(200, gin.H{"message": "Hello world!",})
    })
    // 启动 HTTP 服务,默认在 0.0.0.0:8080 启动服务
    r.Run(":8098")
}

Gin 中间件应用

目录

  • 什么是 中间件?
  • 中间件作用域
  • 中间件数据共享
  • 中间件留神

什么是中间件?

中间件是在当客户端拜访服务端接口之前和之后会做一些事件。
作用 :登陆认证,权限校验,记录日志,耗时统计 …..

中间件作用域

全局作用域

main 入口文件中,通过 创立的默认路由引擎. use(中间件 1, 两头 2,….) 即可注册应用全局中间件了。

import (
   "Gin/middleware"
   "Gin/router"
   "fmt"
   "github.com/gin-gonic/gin"
   "net/http"
)

func mian() {

// 创立一个默认的路由引擎
var r = gin.Default()  

// 全局应用 中间价, 能够应用一个 / 多个中间件
r.Use(middleware.GetHttpHost, middleware.GetHttpMethod)
}

创立两个中间件,别离获取 Host,Method

package middleware

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func GetHttpHost(r *gin.Context) {fmt.Printf("ℹ️ : 申请的地址为 ------%s", r.Request.Host)
    r.Set("token", "SDASD12312rtywe")
    fmt.Println()
    r.Next()}

func GetHttpMethod(r *gin.Context) {fmt.Printf("ℹ️ : 申请办法为 ------%s", r.Request.Method)
    r.Next()}

部分作用域

通过在,路由分组参数门路前面,增加中间件即可,反对增加多个中间件

package router

import "github.com/gin-gonic/gin"
import "Gin/controller/BookStore"
import "Gin/middleware"


func BookRouter(r *gin.Engine) {bookRouter := r.Group("/book", middleware.GetLogInfo)  // 增加部分中间件
    {bookRouter.GET("/", BookStore.BookController{}.Index)

        bookRouter.GET("/:bookName", BookStore.BookController{}.SearchBookName)

        bookRouter.POST("/add", BookStore.BookController{}.Add)

    }
}

中间件数据共享

  • c.set(name, value) c*gin.Context
  • c.get(name)
package middleware

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func GetHttpHost(r *gin.Context) {fmt.Printf("ℹ️ : 申请的地址为 ------%s", r.Request.Host)
    r.Set("token", "SDASD12312rtywe")
    fmt.Println()
    r.Next()}
package middleware

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "time"
)

func GetLogInfo(c *gin.Context) {receiveToken, _ := c.Get("token")
    fmt.Println("进入中间件 ------")
    fmt.Printf("测试获取中间件中共享的数据 ---token:  %s", receiveToken)
    fmt.Println()
    c.Next()}

获取申请参数

获取 querystring 参数

querystring 指的是 URL 中? 前面携带的参数,例如:/user/search?username= 小王子 &address= 沙河。获取申请的 querystring 参数的办法如下:

func getLoginInfo() {r.GET("/login", func(c *gin.Context) {username := c.Query("username")
        password := c.Query("password")
        // c.JSON:返回 JSON 格局的数据
        c.JSON(http.StatusOK, gin.H{
            "info":     "==========",
            "username": username,
            "password": password,
        })
    })
}

⚠️ 留神

  • 能够指定默认的 query 值, 通过 c.DefaultQuery
username := c.DefaultQuery("username", "admin")
  • 指定接管的 query key,c.Query(“username”)
username := c.Query("username")

下面拜访 “http://127.0.0.1:8098/login?username=admin&password=1123123” 即可返回指定的 json 值

{
    "info": "==========",
    "password": "1123123",
    "username": "admin"
}

获取 form 参数

申请的数据通过 form 表单来提交的,通过 PostForm()接管

func getFormParams() {r.POST("/saveBookInfo", func(c *gin.Context) {bookName := c.PostForm("bookName")
        author := c.PostForm("author")
        // c.JSON:返回 JSON 格局的数据
        c.JSON(http.StatusOK, gin.H{
            "bookName": bookName,
            "author":   author,
        })
        fmt.Println("测试 Post")
        fmt.Printf("拜访的接口为 --%s, 参数为:bookName--- %s, author----%s", "/saveBookInfo", bookName, author)
    })
}

获取 path 参数

获取 URl 的 path,能够通过 c.Param()接管

func getRouterPathParams() {r.GET("/bookStore/:bookName/:author", func(c *gin.Context) {bookName := c.Param("bookName")
        author := c.Param("author")
        // c.JSON:返回 JSON 格局的数据
        c.JSON(http.StatusOK, gin.H{
            "bookName": bookName,
            "author":   author,
        })
        fmt.Println("测试 Post")
        fmt.Printf("拜访的接口为 --%s, 参数为:bookName--- %s, author----%s", "/saveBookInfo", bookName, author)
    })
}

拜访链接🔗 http://127.0.0.1:8098/bookStore/Go/ 海军,返回

{
    "author": "海军",
    "bookName": "Go"
}

为了可能更不便的获取申请相干参数,进步开发效率,咱们能够基于申请的 Content-Type 辨认申请数据类型并利用反射机制主动提取申请中 QueryString、form 表单、JSON、XML 等参数到构造体中。上面的示例代码演示了.ShouldBind() 弱小的性能,它可能基于申请主动提取 JSON、form 表单和 QueryString 类型的数据,并把值绑定到指定的构造体对象。

Cookie

Cookie 是存储在客户端的浏览器环境中的,它能够 在拜访同一个域下的网站 数据共享。它解决了 HTTP 无状态数据不共享问题。

Cookie 性能

:::info

  • 保留用户登陆状态
  • 保留用户浏览器记录
  • 猜你喜爱,智能举荐等
  • 购物车等
    :::

设置 Cookie

SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool)
name base
key cookie – key
value cookie – value
maxAge cookie 过期工夫
path cookie 门路
domain cookie 门路的作用域,本地 localhost,线上为域名
secure 当 secure 为 true 时,cookie 在 HTTP 是有效的,HTTPS 才无效
httpOnly 微软对 cookie 的扩大,如果设置了 httpOnly,则无奈获取 cookie 信息,避免了 XSS 攻打
c.SetCookie("loginStatus", "登陆胜利状态", 5600, "/", "localhost", false, true)

获取 cookie

c.Cookie(name)

Session

session 基本原理

http 协定是无状态的,就是说你在申请服务器同时,服务器不晓得哪个是你拜访的,怎么让服务器晓得哪个是你拜访的,那么 session 就进去了。
session 和 cookie 不分家,每次说 session 其实就是说 cookie。

服务端 和 客户端 有状态通信原理:
第一次登录时,服务器给客户端颁发一个惟一的 sessionId, 并通过 http 的响应头返回。客户端(浏览器)发现返回的数据中有 cookie 数据就把这个 cookie 数据寄存到内存。下次再发送 http 申请时,把内存中的 cookie 数据再塞到 http 申请头中,一并发给服务器,服务器在解析申请时,发现申请头中有 cookie,就开始辨认 cookie 中的 sessionId,拿到 sessionId,咱们就晓得这个申请时由哪个客户端发送来的了。

Gin 中应用 session

Gin 自身是没有提供 session 的,须要应用第三方依赖。

  1. 装置依赖
go get github.com/gin-contrib/sessions
  1. 导入依赖 应用
import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)

func main(){

// 创立一个默认的路由引擎
var r = gin.Default()

// 1. 创立基于 cookie 的存储引擎,haijun 参数是用于加密的密钥,能够轻易填写
store := cookie.NewStore([]byte("haijun"))

// 2. 设置 session 中间件,参数 mysession,指的是 session 的名字,也是 cookie 的名字
// store 是后面创立的存储引擎
r.Use(sessions.Sessions("mysession", store))


r.GET("/test", func(c *gin.Context) {
        // 初始化 session 对象
        session := sessions.Default(c)
                
        // session 是键值对格局数据,因而须要通过 key 查问数据
        session.Set("token", "haijun23123")
        session.Save()

                
        c.JSON(200, gin.H{"token": session.Get("token")})
    })

}


Session 操作

设置 session

session.Set("kay", value)

获取 session

session.Get("key")

删除 session

session.Delete("key")  // 删除单个 session

session.Clear() // 删除全副 session

保留 session

ssession.Save()

⚠️留神

  1. session 仓库其实就是一个 map[interface]interface 对象,所有 session 能够存储任意数据
  2. session 应用的编解码器是自带的 gob,所以存储相似:struct、map 这些对象时须要先注册对象,不然会报错 gob: type not registered for…
  3. session 存储引擎反对:cookie、内存、mongodb、redis、postgres、memstore、memcached 以及 gorm 反对的各类数据库(mysql、sqlite)
  4. session 在创立时有一个配置项,能够配置 session 过期工夫、cookie、domain、secure、path 等参数
  5. 调用 session 办法:Set()、Delete()、Clear()、办法后,必须调用一次 Save() 办法。否则 session 数据不会更新

上传文件

上传单文件

func (bk BookController) UploadOne(c *gin.Context) {
    // 获取文件
    //c.FormFile("文件参数名")
    file, err := c.FormFile("file")
    // 文件门路
    dst := path.Join("./static/images", file.Filename)

    // err == nil 代表上传胜利
    if err == nil {c.SaveUploadedFile(file, dst)
        c.JSON(200, gin.H{"msg": "上传文件胜利",})

    } else {
        c.JSON(400, gin.H{"msg": "上传文件失败 ----- 🙅",})
    }
}

上传多文件

// 上传多个文件
func (bk BookController) Upload(c *gin.Context) {// 通过 c.MultipartForm() 获取多个文件 
    form, _ := c.MultipartForm()
    filesArr := form.File["file[]"]
    fmt.Println(filesArr)
    //for range 遍历保留文件
    for _, file := range filesArr {dst := path.Join("./static/images", file.Filename)
        c.SaveUploadedFile(file, dst)
    }

    c.JSON(200, gin.H{
        "msg":      "上传文件胜利",
        "fileList": form.File["file[]"],
    })

}

实战

// 测验上传文件的格局
func (bk BookController) CheckFileFormat(c *gin.Context) {
    //1. 获取文件
    file, status := c.FormFile("companyInfo")
    if status == nil {
        //2. 获取文件格式
        fileFormat := path.Ext(file.Filename)

        allowFormat := map[string]bool{
            ".jpg": true,
            ".png": true,
        }
        _, ok := allowFormat[fileFormat]
        fmt.Println(fileFormat)
        fmt.Println(ok)
        if ok == true {
            //3。合乎接管的格局 创立保留目录
            mkDirStatus := os.MkdirAll("./static/fileDir", 0777)

            if mkDirStatus != nil {c.String(400, "创立目录失败")
                return
            }

            //4。生成文件名门路
            newFilePath := path.Join("./static/fileDir", file.Filename)

            //5. 保留文件
            c.SaveUploadedFile(file, newFilePath)

            c.String(200, "上传文件胜利😊")
        } else {c.String(400, "上传文件失败")
        }
    } else {c.String(400, "上传文件失败")
    }
}

小结

  • 上传文件时,类型必须为 multipart/form-data
  • c.FormFile(“file”) 获取单个文件
  • c.MultipartForm() 获取多个文件,多个文件的参数名必须是对立的,
  • path.Join(‘ 门路 ’,文件名) 拼接文件门路
  • c.SaveUploadedFile(file, dst) 保留文件,第一个参数为 file,第二个参数为文件门路
  • path.Ext(file.Filename) 获取到文件后缀名
  • os.MkdirAll(“ 创立文件的门路 ”,权限 code) 创立文件

GORM

什么是 ORM

什么是 ORM?O:Object 对象 Relationall: 关系 Mapping:映射

​相当于定义了一张  数据库表 type Person struct {Id      int Name    string Age     int​}​​构造体实例  相当于 数据行 student:= Person{1,” 小明 ”,28}

ORM 优缺点

长处:

  • 进步开发效率

毛病:

  • 就义执行性能
  • 就义灵活性
  • 弱化 SQL 能力,手写 SQL 能升高

如何建 Model

GoRM 默认模型

GORM 内置了一个 gorm.Model 构造体。gorm.Model 是一个蕴含了 ID, CreatedAt, UpdatedAt, DeletedAt 四个字段的 Golang 构造体。

// gorm.Model 定义
type Model struct {
  ID        uint `gorm:"primary_key"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt *time.Time
}

也能够继承到本人的构造体中

// 将 `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` 字段注入到 `Student` 模型中
type Student struct {
  gorm.Model
  Name string
  Age int
}

Model 定义

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:"-"` // 疏忽本字段
}

User 构造体的属性 Birthday 和 MemberNumber,应用指针,是有什么含意么?

默认所有字段的零值, 比方 0, ”, false 或者其它 零值,都不会保留到数据库内,应用指针能够防止这种状况。

构造体标记 (tags)

构造体标记 (tags)

应用构造体申明模型时,标记(tags)是可选项。
打标记的作用:

是对数据表的字段做润饰,例如(自增,主键,大小,类型,索引 ……..)

构造体标记

构造体标记(Tag) 形容
Column 指定列名
Type 指定列数据类型
Size 指定列大小, 默认值 255
PRIMARY_KEY 将列指定为主键
UNIQUE 将列指定为惟一
DEFAULT 指定列默认值
PRECISION 指定列精度
NOT NULL 将列指定为非 NULL
AUTO_INCREMENT 指定列是否为自增类型
INDEX 创立具备或不带名称的索引, 如果多个索引同名则创立复合索引
UNIQUE_INDEX 和 INDEX 相似,只不过创立的是惟一索引
EMBEDDED 将构造设置为嵌入
EMBEDDED_PREFIX 设置嵌入构造的前缀
疏忽此字段

关联相干标记

构造体标记(Tag) 形容
MANY2MANY 指定连贯表
FOREIGNKEY 设置外键
ASSOCIATION_FOREIGNKEY 设置关联外键
POLYMORPHIC 指定多态类型
POLYMORPHIC_VALUE 指定多态值
JOINTABLE_FOREIGNKEY 指定连贯表的外键
ASSOCIATION_JOINTABLE_FOREIGNKEY 指定连贯表的关联外键
SAVE_ASSOCIATIONS 是否主动实现 save 的相干操作
ASSOCIATION_AUTOUPDATE 是否主动实现 update 的相干操作
ASSOCIATION_AUTOCREATE 是否主动实现 create 的相干操作
ASSOCIATION_SAVE_REFERENCE 是否主动实现援用的 save 的相干操作
PRELOAD 是否主动实现预加载的相干操作

主键. 表名. 列名约定

主键

GORM 默认会应用名为 ID 的字段作为表的主键。

type User struct {
  ID   string // 名为 `ID` 的字段会默认作为表的主键
  Name string
}

// 应用 `StudentlID` 作为主键
type Student struct {
  StudentID int64 `gorm:"primary_key"`
  Name     string
  Age      int64
}

表名

  1. 表名默认是构造体名称的复数, 也能够勾销默认复数表名
type User struct {} // 默认表名是 `users`

// 将 User 的表名设置为 `user`
func (User) TableName() string {return "user"}



// 禁用默认表名的复数模式,如果置为 true,则 `User` 的默认表名是 `user`
db.SingularTable(true)
  1. 也能够通过 Table() 指定表名:
// 应用 User 构造体创立名为 `student` 的表
db.Table("student").CreateTable(&User{})
  1. GORM 还反对更改默认表名称规定:
gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string  {return "prefix_" + defaultTableName;}

"prefix_" 可任意名称 

列名

列名由字段名称进行下划线宰割来生成 , 构造体字段为驼峰命名时,第二个单词会在后面以下划线显示,例如:
AddressInfo —> address_info

type User struct {
  ID        uint      // column name is `id`
  Name      string    // column name is `name`
  Birthday  time.Time // column name is `birthday`
  CreatedAt time.Time // column name is `created_at`
}

能够应用构造体 Tag 指定列名

type Animal struct {
  AnimalId    int64     `gorm:"column:beast_id"`         // set column name to `beast_id`
  Birthday    time.Time `gorm:"column:day_of_the_beast"` // set column name to `day_of_the_beast`
  Age         int64     `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast`
}

工夫戳跟踪

CreatedAt

如果模型有 CreatedAt 字段,该字段的值将会是首次创立记录的工夫。

db.Create(&user) // `CreatedAt` 将会是以后工夫 
// 能够应用 `Update` 办法来扭转 `CreateAt` 的值 
db.Model(&user).Update("CreatedAt", time.Now()) 

UpdatedAt

如果模型有 UpdatedAt 字段,该字段的值将会是每次更新记录的工夫。

db.Save(&user) // `UpdatedAt` 将会是以后工夫 
db.Model(&user).Update("name", "jinzhu") 
// `UpdatedAt` 将会是以后工夫 

DeletedAt

如果模型有 DeletedAt 字段,调用 Delete 删除该记录时,将会设置 DeletedAt 字段为以后工夫,而不是间接将记录从数据库中删除。

我的项目开发中

正文完
 0