Gin和Gorm常见面试题及解答
1 Gin
1.1 Gin文档
Gin官网地址:https://gin-gonic.com/zh-cn/docs
其他文档1:gin中文文档
其他文档2:golang入门笔记——Gin
其他文档3:Golang gin框架
其他文档4:Gin 快速入门知识点总结
1.2 Gin基本概念
1.2.1 Gin特点
参考1:Go gin框架入门教程
Gin的一些特性:
- 快速:基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。
- 支持中间件:传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。
- Crash 处理:Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!
- JSON 验证:Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。
- 路由组:更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。
- 错误管理:Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。
- 内置渲染:Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。
- 可扩展性:新建一个中间件非常简单。
1.2.2 Gin的使用
其他文档1:Golang gin框架
2 Gin面试问题
2.1 Gin里的路由转发都用到什么功能了
参考1:Gin路由
Gin中的路由系统的用于接收请求,并将请求转发给注册的中间件或请求处理器来处理。核心功能如下:
- 路由系统可根据请求方法,请求路径,和路径参数来识别转发。
- 可设置一个或多个中间件用于在请求处理器前后,处理特殊的事件。
- 可以分组设置,将一个或多个中间件作用在一组多个路由上。
2.2 鉴权的中间件是在Gin里写的还是怎么处理的
可以使用 gin-jwt:gin框架学习-JWT认证
2.3 Gin里面的中间件
Gin中的中间件实际上还是一个Gin中的 gin.HandlerFunc。中间都是需要注册后才能启用的。
中间件分类
:
全局中间件:全局中间件设置之后对全局的路由都起作用。
路由组中间件:路由组中间件仅对该路由组下面的路由起作用。
单个路由中间件:单个路由中间件仅对一个路由起作用。
2.4 Gin怎么对某些接口做日志记录
同 2.1 Gin里的路由转发都用到什么功能了?
参考1:Gin路由
核心功能:
- 路由系统可根据请求方法,请求路径,和路径参数来识别转发。
- 可设置一个或多个中间件用于在请求处理器前后,处理特殊的事件。
- 可以分组设置,将一个或多个中间件作用在一组多个路由上。
可以使用Gin路由的中间件功能,把要记录的接口封装到一个接口组中,然后注册中间件【方法】来处理。
2.5 为什么选择使用Gin框架
参考1:Gin框架介绍与基本使用
- Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点;
- 对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错;
- 借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范;
- 由于使用了http router,速度提高了近40倍。 如果你是性能和高效的追求者, 你会爱上Gin;
2.6 Gin都有哪些包,这些包分别能处理哪些内容
参考1:golang入门笔记——Gin
参考2:浅谈Gin框架中bind
参考3:浅谈go语言renderer包代码分析
- binding包
Gin框架中的binding包,有bind函数可以非常方便的将url的查询参数query parameter、http的Header,body中提交上来的数据格式,如form,json,xml等,绑定到go中的结构体中去。 - render包
renderer是Go语言的一个简单的、轻量的、快速响应的呈现包,它可以支持JSON、JSONP、XML、HYAML、HTML、File等类型的响应。在开发web应用或RESTFul API的时候,这个包是非常方便的工具包。
2.7 实现了Gin里的哪些方法,往Gin里注册的是路由还是回调
注册的是路由。
2.8 Gin里对goroutine的处理模式有了解吗,或者说goroutine的处理是在Gin还是在外面的程序里?
Gin里,因为一个请求过来后,是可以再开一个协程来处理请求的,这个需要在Gin里才能启用协程。
2.9 Gin框架实现请求地址映射到方法的过程
- 创建Gin引擎
router := gin.Default()
- 定义路由和处理程序方法:在Gin中,路由定义使用HTTP请求方法(GET、POST、PUT、DELETE等)和URL模式来匹配请求的URL。每个路由都映射到一个处理程序方法(通常是一个闭包函数)。
- 启动Gin服务。
- 请求映射到处理程序方法:当收到HTTP请求时,Gin会根据请求的HTTP方法和URL路径来查找匹配的路由定义,并将请求映射到相应的处理程序方法。处理程序方法接收一个*gin.Context参数,该参数包含有关请求和响应的信息,可以用来读取请求参数、设置响应头、返回JSON数据等。
2.10 Gin是如何解决跨域的
可以在gin.Context.Writer.Header()
配置。
- Access-Control-Allow-Origin:必填,它的值要么是请求时
Origin
字段的值,要么是一个*
,表示接受任意域名的请求。 - Access-Control-Allow-Methods:必填,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
- Access-Control-Allow-Headers:如果浏览器请求包括
Access-Control-Request-Headers
字段,则Access-Control-Allow-Headers
字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。 - Access-Control-Expose-Headers:可选,
CORS
请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定。 - Access-Control-Allow-Credentials:可选,它的值是一个布尔值,表示是否允许发送
Cookie
。默认情况下,Cookie
不包括在CORS
请求之中。设为true
,即表示服务器明确许可,Cookie
可以包含在请求中,一起发给服务器。这个值也只能设为true
,如果服务器不要浏览器发送Cookie
,删除该字段即可。 - Access-Control-Max-Age:可选,用来指定本次预检请求的有效期,单位为秒,在此期间,不用发出另一条预检请求。
func CORS() gin.HandlerFunc {
return func(context *gin.Context) {
// 允许 Origin 字段中的域发送请求
context.Writer.Header().Add("Access-Control-Allow-Origin", *)
// 设置预验请求有效期为 86400 秒
context.Writer.Header().Set("Access-Control-Max-Age", "86400")
// 设置允许请求的方法
context.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE, PATCH")
// 设置允许请求的 Header
context.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length,Apitoken")
// 设置拿到除基本字段外的其他字段,如上面的Apitoken, 这里通过引用Access-Control-Expose-Headers,进行配置,效果是一样的。
context.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Headers")
// 配置是否可以带认证信息
context.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
// OPTIONS请求返回200
if context.Request.Method == "OPTIONS" {
fmt.Println(context.Request.Header)
context.AbortWithStatus(200)
} else {
context.Next()
}
}
}
3 个人笔记
3.1 加入网站图标ICON
下载依赖:github.com/thinkerou/favicon
图标位置:templates/favicon.ico
代码配置:
router := gin.Default()
router.Use(favicon.New("templates/favicon.ico"))
3.2 文件上传功能
多文件上传:
put_file.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
请选择上传文件:<input type="file" name="files" multiple><br>
<input type="submit" value="上传">
</form>
</body>
</html>
后端代码:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/satori/go.uuid"
"net/http"
"path"
"strings"
)
func Upload(c *gin.Context) {
单文件
//file, _ := c.FormFile("file")
//log.Println(file.Filename)
//
上传文件到项目根目录,使用原文件名
//c.SaveUploadedFile(file, file.Filename)
//
//c.String(http.StatusOK, fmt.Sprintf("'%s' upload!", file.Filename))
//多个文件
form, err := c.MultipartForm()
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
}
// 获取所有图片
files := form.File["files"]
// 遍历所有图片
for _, file := range files {
//获取文件名称带后缀
fileNameWithSuffix := path.Base(file.Filename)
//获取文件的后缀(文件类型)
fileType := path.Ext(fileNameWithSuffix)
//获取文件名称(不带后缀)
fileNameOnly := strings.TrimSuffix(fileNameWithSuffix, fileType)
fmt.Printf("fileNameWithSuffix==%s\n fileType==%s;\n fileNameOnly==%s;",
fileNameWithSuffix, fileType, fileNameOnly)
//生成UUID防止文件被覆盖
fileUUID := uuid.NewV4().String()
uuidName := strings.Replace(fileUUID, "-", "", -1)
dst := "F:\\project\\files\\" + uuidName + fileType
// 逐个存
if err := c.SaveUploadedFile(file, dst); err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
return
}
}
c.String(http.StatusOK, fmt.Sprintf("upload ok %d files", len(files)))
}
func GoUpload(c *gin.Context) {
c.HTML(200, "put_file.html", nil)
}
func main() {
r := gin.Default()
r.MaxMultipartMemory = 1024 << 20 //设置上传文件的最大内存1024MB
r.LoadHTMLGlob("templates/*")
r.GET("/upload", GoUpload)
r.POST("/upload", Upload)
r.Run()
}
文件的上传地址:直接复制WINDOWS
或LIUNX
下的文件路径即可。
示例:
- WINDOWS系统路径
F:\project\files
,代码中的路径F:\\project\\files\\
,注意在最后也要加\\
。 - LINUX系统路径:待操作。
3.3 文件下载
注意:在测试下载功能时,浏览器需要清缓存,否则会异常!!!!
视频、图片、音频,这三种既可以实现在浏览器端直接播放(预览),也可以改为直接下载。其他的文件类型都只能下载。
文件的本地路径和 1.5.2
一致。
代码:
package main
import (
"github.com/gin-gonic/gin"
)
//注意:在测试下载功能时,浏览器需要清缓存,否则会异常!!!!
func main() {
router := gin.Default()
//1、视频在线查看及下载
router.GET("/video", func(c *gin.Context) {
//1、如果是下载,则需要在Header中设置这两个参数
//c.Header("Content-Type", "application/octet-stream")
//c.Header("Content-Disposition", "attachment; filename=Gin框架一小时上手.mp4")
c.File("F:\\project\\files\\Gin框架一小时上手.mp4")
})
//2、图片在线查看及下载
router.GET("/image", func(c *gin.Context) {
//1、如果是下载,则需要在Header中设置这两个参数
//c.Header("Content-Type", "application/octet-stream")
//c.Header("Content-Disposition", "attachment; filename=IMG_20211004_113834.jpg")
c.File("F:\\project\\files\\IMG_20211004_113834.jpg")
})
//3、音频在线查看及下载
router.GET("/mp3", func(c *gin.Context) {
//1、如果是下载,则需要在Header中设置这两个参数
//c.Header("Content-Type", "application/octet-stream")
//c.Header("Content-Disposition", "attachment; filename=吴景军 - 午后 (纯音乐) [mqms2].mp3")
c.File("F:\\project\\files\\吴景军 - 午后 (纯音乐) [mqms2].mp3")
})
//4、压缩包只能下载
router.GET("/zip", func(c *gin.Context) {
//1、如果是下载,则需要在Header中设置这两个参数
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename=nginx-1.19.2.zip")
c.File("F:\\project\\files\\nginx-1.19.2.zip")
})
//5、office文档下载(xlsx)
router.GET("/xlsx", func(c *gin.Context) {
//1、如果是下载,则需要在Header中设置这两个参数
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename=更新记录.xlsx")
c.File("F:\\project\\files\\更新记录.xlsx")
})
router.Run(":8080")
}
4 Gorm
4.1 Gorm文档
对于Gorm的学习,推荐看官网 https://gorm.io 文档。
4.2 Gorm版本区别
目前网上有两个地址在提供Gorm,分别是 github.com/jinzhu/gorm
和 gorm.io/gorm
。这两个有什么区别呢?
省流解释:
gorm.io/gorm
:是GORM 2.0
版本库的地址;github.com/jinzhu/gorm
:GORM 1.0
版本库的地址;gorm.io/gorm
使用的数据库驱动被拆分为独立的项目,例如:github.com/go-gorm/mysql
,且它的 import 路径也变更为gorm.io/driver/mysql
。
具体解释:
访问 Gorm官网:https://gorm.io(语言可以更换成中文),可以看到官方推荐的拉取Gorm的命令是:go get -u gorm.io/gorm
,同时注意到在下放的推介中出现了JINZHU
标签(红圈中),但此时我们仍然没有讲解两者的区别。
上图中,可以看到除了红圈,我还标注了V2 RELEASE NOTE
(黄圈),它是 Gorm 2.0
版本的文档,点进去查看GORM 2.0 发布说明
(语言切换成中文)。
在如何升级这一部分终于看到两者的区别了:
gorm.io/gorm
:是GORM 2.0
版本库的地址;github.com/jinzhu/gorm
:GORM 1.0
版本库的地址;gorm.io/gorm
使用的数据库驱动被拆分为独立的项目,例如:github.com/go-gorm/mysql
,且它的 import 路径也变更为gorm.io/driver/mysql
。
4.3 Gorm使用示例
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"time"
)
type USocial struct {
ID string
SocialName string
SocialUrl string
IconUrl string
CreateUser string
CreateTime time.Time
UpdateUser string
UpdateTime time.Time
DelFlag string
}
func main() {
db, err := gorm.Open(mysql.Open("root:root@tcp(localhost:3306)/blog?parseTime=true&loc=Asia%2FShanghai"), &gorm.Config{
//解决Gorm结构体和数据表映射时加 ”s”的问题
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
if err != nil {
panic("failed to connect database")
}
sqlDb, _ := db.DB()
sqlDb.SetMaxOpenConns(5)
sqlDb.SetMaxIdleConns(2)
sqlDb.SetConnMaxIdleTime(time.Minute)
// 迁移 schema
db.AutoMigrate(&USocial{})
// Read
var user USocial
db.First(&user, "id = ?", "77a1daa8e26b49329c02c12bd3502edd") // 查找 social_name 字段值为 微博 的记录
fmt.Println(user)
}
4.3.1 Gorm结构体和数据表映射时出现复数 “s”的问题
db, err := gorm.Open(mysql.Open("root:root@tcp(localhost:3306)/blog?parseTime=true&loc=Asia%2FShanghai"), &gorm.Config{
//解决Gorm结构体和数据表映射时加 ”s”的问题
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
5 Gorm面试问题
5.1 查询一条数据,如果没查到会怎样
描述:使用Gorm里如果只想查一条数据,比如通过用户ID去查询用户表,一般只会查到一条,如果没查到会怎么样?
参考1:GORM查询数据
当 First、Last、Take 方法找不到记录时,GORM 会返回 ErrRecordNotFound 错误。
在实际开发中查询不到数据,我们不一定会当成错误处理, gorm库通过下面办法检测Error是不是查询不到数据。
err := db.Take(&food).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
fmt.Println("查询不到数据")
} else if err != nil {
//如果err不等于record not found错误,又不等于nil,那说明sql执行失败了。
fmt.Println("查询失败", err)
}
5.2 网络连接失败也会报错,如何与5.1区分开
待查。
5.3 gorm遇到过的坑
参考1:如何解决Go gorm踩过的坑
- 使用gorm.Model后无法查询数据
Scan error on column index 1, name “created_at”
//提示:
Scan error on column index 1, name “created_at”: unsupported Scan, storing driver.Value type []uint8
//解决办法:打开数据库的时候加上parseTime=true
root:123456@tcp(127.0.0.1:3306)/mapdb?charset=utf8&parseTime=true
- Gorm结构体和数据表映射时出现复数 “s”的问题:看5.1
5.4 Gorm更新数据为零值问题
参考1:Gorm 更新零值问题
通过结构体变量更新字段值, gorm库会忽略零值字段。就是字段值等于0, nil, “”, false这些值会被忽略掉,不会更新。如果想更新零值,可以使用map类型替代结构体。
5.5 Gorm的自动建表有使用过吗
参考1:(十三)GORM 自动建表(Migration特性)
GORM支持Migration特性,支持根据Go Struct结构自动生成对应的表结构。如果表已经存在不会重复创建。
注意:GORM 的AutoMigrate函数,仅支持建表,不支持修改字段和删除字段,避免意外导致丢失数据。
5.6 Gorm和原生的相比有什么优势
- 支持多种数据库:
Gorm
支持多种关系型数据库,包括MySQL
、PostgreSQL
、SQLite
和Microsoft SQL Server
等,这使得它成为一个非常灵活的ORM
库。 - 提供强大的查询功能:
Gorm
提供了许多强大的查询功能,如条件查询、排序、分组、连接查询和原始SQL
查询等。这些功能使得开发人员可以轻松地执行复杂的数据库查询操作。 - 支持事务处理:
Gorm
支持事务处理,这意味着开发人员可以将一系列数据库操作放在同一个事务中,并且在任何一个操作失败时,事务会自动回滚,保证数据的完整性。 - 支持模型关系映射:
Gorm
支持模型之间的关系映射,包括一对一、一对多和多对多关系。这使得开发人员可以轻松地在不同的数据表之间建立关联。 - 可扩展性强:
Gorm
的可扩展性非常强,可以通过插件机制来扩展其功能。例如,可以通过插件来添加缓存、日志记录和自定义数据类型等功能。
6 Gorm面试的SQL语句
6.1 SQL查询1
场景:有一张学生表,有ID、name、age、创建时间、更新时间,这5个字段,查询年龄大于20,并且更新时间为今天的数据,Gorm里该怎么写?
参考1:gorm实现数据库连接以及创建,查询,更新,删除的测试
查看Where查询
部分
db.Where("age > ? AND 更新时间 BETWEEN ? AND ?",age,今天开始时间,当前查询时间).Find(&users)