gin 怎么通过 post 发送结构体_Go Web 框架 Gin 实践12—优化配置结构及实现图片上传…

  • Post author:
  • Post category:其他


Go语言中文网,致力于每日分享编码、开源等知识,欢迎关注我,会有意想不到的收获!

9d1a5981b70a0a4ba6c5317cd9ab9990.png

项目地址:https://github.com/EDDYCJY/go-gin-example

如果对你有所帮助,欢迎点个 Star

前言

一天,产品经理突然跟你说文章列表,没有封面图,不够美观,!)&¥*!&)#&¥*!加一个吧,几分钟的事

你打开你的程序,分析了一波写了个清单:

  • 优化配置结构(因为配置项越来越多)
  • 抽离 原 logging 的 File 便于公用(logging、upload 各保有一份并不合适)
  • 实现上传图片接口(需限制文件格式、大小)
  • 修改文章接口(需支持封面地址参数)
  • 增加 blog_article (文章)的数据库字段
  • 实现 http.FileServer

嗯,你发现要较优的话,需要调整部分的应用程序结构,因为功能越来越多,原本的设计也要跟上节奏

也就是在适当的时候,及时优化

优化配置结构


一、讲解

在先前章节中,采用了直接读取 KEY 的方式去存储配置项,而本次需求中,需要增加图片的配置项,总体就有些冗余了

我们采用以下解决方法:

  • 映射结构体:使用 MapTo 来设置配置参数
  • 配置统管:所有的配置项统管到 setting 中


映射结构体(示例)

在 go-ini 中可以采用 MapTo 的方式来映射结构体,例如:

5b66a4cdd24706331fe6b49006657101.png

在这段代码中,可以注意 ServerSetting 取了地址,为什么 MapTo 必须地址入参呢?

30c6efd6fd37bf8de33e068d1982b35c.png

在 MapTo 中 typ.Kind() == reflect.Ptr 约束了必须使用指针,否则会返回 cannot map to non-pointer struct 的错误。这个是表面原因

更往内探究,可以认为是 field.Set 的原因,当执行 val := reflect.ValueOf(v) ,函数通过传递 v 拷贝创建了 val,但是 val 的改变并不能更改原始的 v,要想 val 的更改能作用到 v,则必须传递 v 的地址

显然 go-ini 里也是包含修改原始值这一项功能的,你觉得是什么原因呢?


配置统管

在先前的版本中,models 和 file 的配置是在自己的文件中解析的,而其他在 setting.go 中,因此我们需要将其在 setting 中统一接管

你可能会想,直接把两者的配置项复制粘贴到 setting.go 的 init 中,一下子就完事了,搞那么麻烦?

但你在想想,先前的代码中存在多个 init 函数,执行顺序存在问题,无法达到我们的要求,你可以试试

(此处是一个基础知识点)

在 Go 中,当存在多个 init 函数时,执行顺序为:

  • 相同包下的 init 函数:按照源文件编译顺序决定执行顺序(默认按文件名排序)
  • 不同包下的 init 函数:按照包导入的依赖关系决定先后顺序

所以要避免多 init 的情况,尽量由程序把控初始化的先后顺序

二、落实


修改配置文件

打开 conf/app.ini 将配置文件修改为大驼峰命名,另外我们增加了 5 个配置项用于上传图片的功能,4 个文件日志方面的配置项

[app]PageSize = 10JwtSecret = 233RuntimeRootPath = runtime/ImagePrefixUrl = http://127.0.0.1:8000ImageSavePath = upload/images/# MBImageMaxSize = 5ImageAllowExts = .jpg,.jpeg,.pngLogSavePath = logs/LogSaveName = logLogFileExt = logTimeFormat = 20060102[server]#debug or releaseRunMode = debugHttpPort = 8000ReadTimeout = 60WriteTimeout = 60[database]Type = mysqlUser = rootPassword = rootrootHost = 127.0.0.1:3306Name = blogTablePrefix = blog_

优化配置读取及设置初始化顺序


第一步

将散落在其他文件里的配置都删掉,统一在 setting 中处理以及修改 init 函数为 Setup 方法

打开 pkg/setting/setting.go 文件,修改如下:

d4a7590aba8162c277cbe69873443afc.png

在这里,我们做了如下几件事:

  • 编写与配置项保持一致的结构体(App、Server、Database)
  • 使用 MapTo 将配置项映射到结构体上
  • 对一些需特殊设置的配置项进行再赋值

需要你去做的事:

  • 将 models.go、setting.go、pkg/logging/log.go 的 init 函数修改为 Setup 方法
  • 将 models/models.go 独立读取的 DB 配置项删除,改为统一读取 setting
  • 将 pkg/logging/file 独立的 LOG 配置项删除,改为统一读取 setting

这几项比较基础,并没有贴出来,我希望你可以自己动手,有问题的话可右拐 项目地址 https://github.com/EDDYCJY/go-gin-example


第二步

在这一步我们要设置初始化的流程,打开 main.go 文件,修改内容:

0a69ca1714f4213ccea6ace2eec03ff3.png

修改完毕后,就成功将多模块的初始化函数放到启动流程中了(先后顺序也可以控制)

验证

在这里为止,针对本需求的配置优化就完毕了,你需要执行 go run main.go 验证一下你的功能是否正常哦

顺带留个基础问题,大家可以思考下

ServerSetting.ReadTimeout = ServerSetting.ReadTimeout * time.SecondServerSetting.WriteTimeout = ServerSetting.ReadTimeout * time.Second

若将 setting.go 文件中的这两行删除,会出现什么问题,为什么呢?

抽离 File

在先前版本中,在 logging/file.go 中使用到了 os 的一些方法,我们通过前期规划发现,这部分在上传图片功能中可以复用


第一步

在 pkg 目录下新建 file/file.go ,写入文件内容如下:

6f2b57543ba657a8f305fc8dacb6f3b3.png

在这里我们一共封装了 7个 方法

  • GetSize:获取文件大小
  • GetExt:获取文件后缀
  • CheckExist:检查文件是否存在
  • CheckPermission:检查文件权限
  • IsNotExistMkDir:如果不存在则新建文件夹
  • MkDir:新建文件夹
  • Open:打开文件

在这里我们用到了 mime/multipart 包,它主要实现了 MIME 的 multipart 解析,主要适用于 HTTP 和常见浏览器生成的 multipart 主体

multipart 又是什么,rfc2388 的 multipart/form-data 了解一下 https://tools.ietf.org/html/rfc2388


第二步

我们在第一步已经将 file 重新封装了一层,在这一步我们将原先 logging 包的方法都修改掉

1、打开 pkg/logging/file.go 文件,修改文件内容:

2d6e123f5ad8f74842605c56d85ddc3d.png

我们将引用都改为了 file/file.go 包里的方法

2、打开 pkg/logging/log.go 文件,修改文件内容:

f590913a9e17ba38f44da0a571f03736.png

由于原方法形参改变了,因此 openLogFile 也需要调整

实现上传图片接口

这一小节,我们开始实现上次图片相关的一些方法和功能

首先需要在 blog_article 中增加字段 cover_image_url,格式为 varchar(255) DEFAULT ” COMMENT ‘封面图片地址’


第零步

一般不会直接将上传的图片名暴露出来,因此我们对图片名进行 MD5 来达到这个效果

在 util 目录下新建 md5.go,写入文件内容:

6cbe41dbd967baa7491320dc062786b3.png


第一步

在先前我们已经把底层方法给封装好了,实质这一步为封装 image 的处理逻辑

在 pkg 目录下新建 upload/image.go 文件,写入文件内容:

6c4cad687baf67430bb8cc589e3c7f88.png

在这里我们实现了 7 个方法,如下:

  • GetImageFullUrl:获取图片完整访问URL
  • GetImageName:获取图片名称
  • GetImagePath:获取图片路径
  • GetImageFullPath:获取图片完整路径
  • CheckImageExt:检查图片后缀
  • CheckImageSize:检查图片大小
  • CheckImage:检查图片

这里基本是对底层代码的二次封装,为了更灵活的处理一些图片特有的逻辑,并且方便修改,不直接对外暴露下层


第二步

这一步将编写上传图片的业务逻辑,在 routers/api 目录下 新建 upload.go 文件,写入文件内容:

ea8961b2c147a0178fe2e05c4dbe9e5a.png

所涉及的错误码(需在 pkg/e/code.go、msg.go 添加):

// 保存图片失败ERROR_UPLOAD_SAVE_IMAGE_FAIL = 30001// 检查图片失败ERROR_UPLOAD_CHECK_IMAGE_FAIL = 30002// 校验图片错误,图片格式或大小有问题ERROR_UPLOAD_CHECK_IMAGE_FORMAT = 30003

在这一大段的业务逻辑中,我们做了如下事情:

  • c.Request.FormFile:获取上传的图片(返回提供的表单键的第一个文件)
  • CheckImageExt、CheckImageSize检查图片大小,检查图片后缀
  • CheckImage:检查上传图片所需(权限、文件夹)
  • SaveUploadedFile:保存图片

总的来说,就是 入参 -> 检查 -》 保存 的应用流程


第三步

打开 routers/router.go 文件,增加路由 r.POST(“/upload