2
头图

本文参与了思否技术征文,欢迎正在阅读的你也加入。

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)
namebase
keycookie - key
valuecookie - value
maxAgecookie 过期时间
pathcookie 路径
domaincookie 路径的作用域, 本地 localhost ,线上为域名
secure当 secure 为 true 时,cookie 在 HTTP 是无效的, HTTPS 才有效
httpOnly微软对cookie 的扩展,如果设置了 httpOnly, 则无法获取cookie信息,防止了XSS 攻击
c.SetCookie("loginStatus", "登陆成功状态", 5600, "/", "localhost", false, true)

获取 cookie

c.Cookie(name)

Session

wallhaven-1keo6g.jpeg

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: 映射

 title=

 title=

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

ORM 优缺点

优点:

  • 提高开发效率

缺点:

  • 牺牲执行性能
  • 牺牲灵活性
  • 弱化SQL 能力, 手写SQL 能降低

如何建Model

wallhaven-1k6ljv.jpeg

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字段为当前时间,而不是直接将记录从数据库中删除。

项目开发中

image.png

image.png

image.png

image.png


程序员海军
942 声望7k 粉丝