共计 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
- 下载依赖
$ go get -u github.com/gin-gonic/gin
- 将 gin 引入到代码中:
import "github.com/gin-gonic/gin"
- (可选)如果应用诸如 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 的,须要应用第三方依赖。
- 装置依赖
go get github.com/gin-contrib/sessions
- 导入依赖 应用
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()
⚠️留神
- session 仓库其实就是一个 map[interface]interface 对象,所有 session 能够存储任意数据
- session 应用的编解码器是自带的 gob,所以存储相似:struct、map 这些对象时须要先注册对象,不然会报错 gob: type not registered for…
- session 存储引擎反对:cookie、内存、mongodb、redis、postgres、memstore、memcached 以及 gorm 反对的各类数据库(mysql、sqlite)
- session 在创立时有一个配置项,能够配置 session 过期工夫、cookie、domain、secure、path 等参数
- 调用 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
}
表名
- 表名默认是构造体名称的复数, 也能够勾销默认复数表名
type User struct {} // 默认表名是 `users`
// 将 User 的表名设置为 `user`
func (User) TableName() string {return "user"}
// 禁用默认表名的复数模式,如果置为 true,则 `User` 的默认表名是 `user`
db.SingularTable(true)
- 也能够通过 Table() 指定表名:
// 应用 User 构造体创立名为 `student` 的表
db.Table("student").CreateTable(&User{})
- 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 字段为以后工夫,而不是间接将记录从数据库中删除。
我的项目开发中