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的一些特性:

  1. 快速:基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。
  2. 支持中间件:传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。
  3. Crash 处理:Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!
  4. JSON 验证:Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。
  5. 路由组:更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。
  6. 错误管理:Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。
  7. 内置渲染:Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。
  8. 可扩展性:新建一个中间件非常简单。

1.2.2 Gin的使用

其他文档1:Golang gin框架

2 Gin面试问题

2.1 Gin里的路由转发都用到什么功能了

参考1:Gin路由

Gin中的路由系统的用于接收请求,并将请求转发给注册的中间件或请求处理器来处理。核心功能如下:

  1. 路由系统可根据请求方法,请求路径,和路径参数来识别转发。
  2. 可设置一个或多个中间件用于在请求处理器前后,处理特殊的事件。
  3. 可以分组设置,将一个或多个中间件作用在一组多个路由上。

2.2 鉴权的中间件是在Gin里写的还是怎么处理的

可以使用 gin-jwt:gin框架学习-JWT认证

2.3 Gin里面的中间件

参考1:Gin框架—中间件
参考2:gin中间件

Gin中的中间件实际上还是一个Gin中的 gin.HandlerFunc。中间都是需要注册后才能启用的。
中间件分类
全局中间件:全局中间件设置之后对全局的路由都起作用。
路由组中间件:路由组中间件仅对该路由组下面的路由起作用。
单个路由中间件:单个路由中间件仅对一个路由起作用。

2.4 Gin怎么对某些接口做日志记录

2.1 Gin里的路由转发都用到什么功能了?
参考1:Gin路由

核心功能:

  • 路由系统可根据请求方法,请求路径,和路径参数来识别转发。
  • 可设置一个或多个中间件用于在请求处理器前后,处理特殊的事件。
  • 可以分组设置,将一个或多个中间件作用在一组多个路由上。

可以使用Gin路由的中间件功能,把要记录的接口封装到一个接口组中,然后注册中间件【方法】来处理。

2.5 为什么选择使用Gin框架

参考1:Gin框架介绍与基本使用

  1. Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点;
  2. 对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错;
  3. 借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范;
  4. 由于使用了http router,速度提高了近40倍。 如果你是性能和高效的追求者, 你会爱上Gin;

2.6 Gin都有哪些包,这些包分别能处理哪些内容

参考1:golang入门笔记——Gin
参考2:浅谈Gin框架中bind
参考3:浅谈go语言renderer包代码分析

  1. binding包
    Gin框架中的binding包,有bind函数可以非常方便的将url的查询参数query parameter、http的Header,body中提交上来的数据格式,如form,json,xml等,绑定到go中的结构体中去。
  2. 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框架实现请求地址映射到方法的过程

  1. 创建Gin引擎
router := gin.Default()
  1. 定义路由和处理程序方法:在Gin中,路由定义使用HTTP请求方法(GET、POST、PUT、DELETE等)和URL模式来匹配请求的URL。每个路由都映射到一个处理程序方法(通常是一个闭包函数)。
  2. 启动Gin服务。
  3. 请求映射到处理程序方法:当收到HTTP请求时,Gin会根据请求的HTTP方法和URL路径来查找匹配的路由定义,并将请求映射到相应的处理程序方法。处理程序方法接收一个*gin.Context参数,该参数包含有关请求和响应的信息,可以用来读取请求参数、设置响应头、返回JSON数据等。

2.10 Gin是如何解决跨域的

可以在gin.Context.Writer.Header()配置。

  1. Access-Control-Allow-Origin:必填,它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
  2. Access-Control-Allow-Methods:必填,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
  3. Access-Control-Allow-Headers:如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
  4. Access-Control-Expose-Headers:可选,CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
  5. Access-Control-Allow-Credentials:可选,它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
  6. 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()
}

文件的上传地址:直接复制WINDOWSLIUNX下的文件路径即可。
示例:

  1. WINDOWS系统路径F:\project\files,代码中的路径F:\\project\\files\\,注意在最后也要加\\
  2. 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/gormgorm.io/gorm。这两个有什么区别呢?
省流解释:

  1. gorm.io/gorm:是 GORM 2.0版本库的地址;
  2. github.com/jinzhu/gormGORM 1.0版本库的地址;
  3. 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标签(红圈中),但此时我们仍然没有讲解两者的区别。
Gorm官网
上图中,可以看到除了红圈,我还标注了V2 RELEASE NOTE(黄圈),它是 Gorm 2.0 版本的文档,点进去查看GORM 2.0 发布说明(语言切换成中文)。
GORM 2.0 发布说明
在如何升级这一部分终于看到两者的区别了:

  1. gorm.io/gorm:是 GORM 2.0版本库的地址;
  2. github.com/jinzhu/gormGORM 1.0版本库的地址;
  3. 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”的问题

参考1:gorm的mysql表名带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踩过的坑

  1. 使用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
  1. 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和原生的相比有什么优势

  1. 支持多种数据库Gorm支持多种关系型数据库,包括MySQLPostgreSQLSQLiteMicrosoft SQL Server等,这使得它成为一个非常灵活的ORM库。
  2. 提供强大的查询功能Gorm提供了许多强大的查询功能,如条件查询、排序、分组、连接查询和原始SQL查询等。这些功能使得开发人员可以轻松地执行复杂的数据库查询操作。
  3. 支持事务处理Gorm支持事务处理,这意味着开发人员可以将一系列数据库操作放在同一个事务中,并且在任何一个操作失败时,事务会自动回滚,保证数据的完整性。
  4. 支持模型关系映射Gorm支持模型之间的关系映射,包括一对一、一对多和多对多关系。这使得开发人员可以轻松地在不同的数据表之间建立关联。
  5. 可扩展性强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)
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值