Gin和Gorm常见面试题及解答_gorm面试题,2024年最新Golang基础入门

224 阅读8分钟

参考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的学习,推荐看官网 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官网: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)
}



![img](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/fcad19bea500413d8fdf66d78dcedb39~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1771262128&x-signature=sqV0NhdD5iPPdRUpfD0Xa1xJRd8%3D)
![img](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/6268a0f24c8c4f1097acc4ee0f749077~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1771262128&x-signature=joE4DYkTVlXQd7XGIXy3KIavvb4%3D)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://gitee.com/vip204888)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**