- Golang Gin实践 连载十五 生成二维码、合并海报
- 前言
- 实现
- 生成二维码
- 安装
- 工具包
- 路由方法
- 验证
- 合并海报
- 背景图
- service 方法
- 错误码
- 路由方法
- StaticFS
- 验证
- 总结
- 参考
- 本系列示例代码
- 本系列示例代码
Golang Gin实践 连载十五 生成二维码、合并海报
项目地址:https://github.com/EDDYCJY/go-gin-example
如果对你有所帮助,欢迎点个 Star ?
前言
在本章节,将实现如下功能细项:
1、生成二维码
2、合并海报(背景图 + 二维码)
实现
首先,你需要在 App 配置项中增加二维码及其海报的存储路径,我们约定配置项名称为 QrCodeSavePath,值为 qrcode/
经过多节连载的你应该能够完成,若有不懂可参照 go-gin-example
生成二维码
安装
$ go get -u github.com/boombuler/barcode
工具包
考虑生成二维码这一动作贴合工具包的定义,且有公用的可能性,新建 pkg/qrcode/qrcode.go 文件,写入内容:
package qrcodeimport ("image/jpeg""github.com/boombuler/barcode""github.com/boombuler/barcode/qr""github.com/EDDYCJY/go-gin-example/pkg/file""github.com/EDDYCJY/go-gin-example/pkg/setting""github.com/EDDYCJY/go-gin-example/pkg/util")type QrCode struct {URL stringWidth intHeight intExt stringLevel qr.ErrorCorrectionLevelMode qr.Encoding}const (EXT_JPG = ".jpg")func NewQrCode(url string, width, height int, level qr.ErrorCorrectionLevel, mode qr.Encoding) *QrCode {return &QrCode{URL: url,Width: width,Height: height,Level: level,Mode: mode,Ext: EXT_JPG,}}func GetQrCodePath() string {return setting.AppSetting.QrCodeSavePath}func GetQrCodeFullPath() string {return setting.AppSetting.RuntimeRootPath + setting.AppSetting.QrCodeSavePath}func GetQrCodeFullUrl(name string) string {return setting.AppSetting.PrefixUrl + "/" + GetQrCodePath() + name}func GetQrCodeFileName(value string) string {return util.EncodeMD5(value)}func (q *QrCode) GetQrCodeExt() string {return q.Ext}func (q *QrCode) CheckEncode(path string) bool {src := path + GetQrCodeFileName(q.URL) + q.GetQrCodeExt()if file.CheckNotExist(src) == true {return false}return true}func (q *QrCode) Encode(path string) (string, string, error) {name := GetQrCodeFileName(q.URL) + q.GetQrCodeExt()src := path + nameif file.CheckNotExist(src) == true {code, err := qr.Encode(q.URL, q.Level, q.Mode)if err != nil {return "", "", err}code, err = barcode.Scale(code, q.Width, q.Height)if err != nil {return "", "", err}f, err := file.MustOpen(name, path)if err != nil {return "", "", err}defer f.Close()err = jpeg.Encode(f, code, nil)if err != nil {return "", "", err}}return name, path, nil}
这里主要聚焦 func (q *QrCode) Encode 方法,做了如下事情:
- 获取二维码生成路径
- 创建二维码
- 缩放二维码到指定大小
- 新建存放二维码图片的文件
- 将图像(二维码)以 JPEG 4:2:0 基线格式写入文件
另外在 jpeg.Encode(f, code, nil) 中,第三个参数可设置其图像质量,默认值为 75
// DefaultQuality is the default quality encoding parameter.const DefaultQuality = 75// Options are the encoding parameters.// Quality ranges from 1 to 100 inclusive, higher is better.type Options struct {Quality int}
路由方法
1、第一步
在 routers/api/v1/article.go 新增 GenerateArticlePoster 方法用于接口开发
2、第二步
在 routers/router.go 的 apiv1 中新增 apiv1.POST("/articles/poster/generate", v1.GenerateArticlePoster) 路由
3、第三步
修改 GenerateArticlePoster 方法,编写对应的生成逻辑,如下:
const (QRCODE_URL = "https://github.com/EDDYCJY/blog#gin%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95")func GenerateArticlePoster(c *gin.Context) {appG := app.Gin{c}qrc := qrcode.NewQrCode(QRCODE_URL, 300, 300, qr.M, qr.Auto)path := qrcode.GetQrCodeFullPath()_, _, err := qrc.Encode(path)if err != nil {appG.Response(http.StatusOK, e.ERROR, nil)return}appG.Response(http.StatusOK, e.SUCCESS, nil)}
验证
通过 POST 方法访问 http://127.0.0.1:8000/api/v1/articles/poster/generate?token=$token(注意 $token)

通过检查两个点确定功能是否正常,如下:
1、访问结果是否 200
2、本地目录是否成功生成二维码图片

合并海报
在这一节,将实现二维码图片与背景图合并成新的一张图,可用于常见的宣传海报等业务场景
背景图

将背景图另存为 runtime/qrcode/bg.jpg(实际应用,可存在 OSS 或其他地方)
service 方法
打开 service/article_service 目录,新建 article_poster.go 文件,写入内容:
package article_serviceimport ("image""image/draw""image/jpeg""os""github.com/EDDYCJY/go-gin-example/pkg/file""github.com/EDDYCJY/go-gin-example/pkg/qrcode")type ArticlePoster struct {PosterName string*ArticleQr *qrcode.QrCode}func NewArticlePoster(posterName string, article *Article, qr *qrcode.QrCode) *ArticlePoster {return &ArticlePoster{PosterName: posterName,Article: article,Qr: qr,}}func GetPosterFlag() string {return "poster"}func (a *ArticlePoster) CheckMergedImage(path string) bool {if file.CheckNotExist(path+a.PosterName) == true {return false}return true}func (a *ArticlePoster) OpenMergedImage(path string) (*os.File, error) {f, err := file.MustOpen(a.PosterName, path)if err != nil {return nil, err}return f, nil}type ArticlePosterBg struct {Name string*ArticlePoster*Rect*Pt}type Rect struct {Name stringX0 intY0 intX1 intY1 int}type Pt struct {X intY int}func NewArticlePosterBg(name string, ap *ArticlePoster, rect *Rect, pt *Pt) *ArticlePosterBg {return &ArticlePosterBg{Name: name,ArticlePoster: ap,Rect: rect,Pt: pt,}}func (a *ArticlePosterBg) Generate() (string, string, error) {fullPath := qrcode.GetQrCodeFullPath()fileName, path, err := a.Qr.Encode(fullPath)if err != nil {return "", "", err}if !a.CheckMergedImage(path) {mergedF, err := a.OpenMergedImage(path)if err != nil {return "", "", err}defer mergedF.Close()bgF, err := file.MustOpen(a.Name, path)if err != nil {return "", "", err}defer bgF.Close()qrF, err := file.MustOpen(fileName, path)if err != nil {return "", "", err}defer qrF.Close()bgImage, err := jpeg.Decode(bgF)if err != nil {return "", "", err}qrImage, err := jpeg.Decode(qrF)if err != nil {return "", "", err}jpg := image.NewRGBA(image.Rect(a.Rect.X0, a.Rect.Y0, a.Rect.X1, a.Rect.Y1))draw.Draw(jpg, jpg.Bounds(), bgImage, bgImage.Bounds().Min, draw.Over)draw.Draw(jpg, jpg.Bounds(), qrImage, qrImage.Bounds().Min.Sub(image.Pt(a.Pt.X, a.Pt.Y)), draw.Over)jpeg.Encode(mergedF, jpg, nil)}return fileName, path, nil}
这里重点留意 func (a *ArticlePosterBg) Generate() 方法,做了如下事情:
- 获取二维码存储路径
- 生成二维码图像
- 检查合并后图像(指的是存放合并后的海报)是否存在
- 若不存在,则生成待合并的图像 mergedF
- 打开事先存放的背景图 bgF
- 打开生成的二维码图像 qrF
- 解码 bgF 和 qrF 返回 image.Image
- 创建一个新的 RGBA 图像
- 在 RGBA 图像上绘制 背景图(bgF)
- 在已绘制背景图的 RGBA 图像上,在指定 Point 上绘制二维码图像(qrF)
- 将绘制好的 RGBA 图像以 JPEG 4:2:0 基线格式写入合并后的图像文件(mergedF)
错误码
新增 错误码,错误提示
路由方法
打开 routers/api/v1/article.go 文件,修改 GenerateArticlePoster 方法,编写最终的业务逻辑(含生成二维码及合并海报),如下:
const (QRCODE_URL = "https://github.com/EDDYCJY/blog#gin%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95")func GenerateArticlePoster(c *gin.Context) {appG := app.Gin{c}article := &article_service.Article{}qr := qrcode.NewQrCode(QRCODE_URL, 300, 300, qr.M, qr.Auto) // 目前写死 gin 系列路径,可自行增加业务逻辑posterName := article_service.GetPosterFlag() + "-" + qrcode.GetQrCodeFileName(qr.URL) + qr.GetQrCodeExt()articlePoster := article_service.NewArticlePoster(posterName, article, qr)articlePosterBgService := article_service.NewArticlePosterBg("bg.jpg",articlePoster,&article_service.Rect{X0: 0,Y0: 0,X1: 550,Y1: 700,},&article_service.Pt{X: 125,Y: 298,},)_, filePath, err := articlePosterBgService.Generate()if err != nil {appG.Response(http.StatusOK, e.ERROR_GEN_ARTICLE_POSTER_FAIL, nil)return}appG.Response(http.StatusOK, e.SUCCESS, map[string]string{"poster_url": qrcode.GetQrCodeFullUrl(posterName),"poster_save_url": filePath + posterName,})}
这块涉及到大量知识,强烈建议阅读下,如下:
- image.Rect
- image.Pt
- image.NewRGBA
- jpeg.Encode
- jpeg.Decode
- draw.Op
- draw.Draw
- go-imagedraw-package
其所涉及、关联的库都建议研究一下
StaticFS
在 routers/router.go 文件,增加如下代码:
r.StaticFS("/qrcode", http.Dir(qrcode.GetQrCodeFullPath()))
验证

访问完整的 URL 路径,返回合成后的海报并扫除二维码成功则正确 ?

总结
在本章节实现了两个很常见的业务功能,分别是生成二维码和合并海报。希望你能够仔细阅读我给出的链接,这块的知识量不少,想要用好图像处理的功能,必须理解对应的思路,举一反三
最后希望对你有所帮助 ?
参考
本系列示例代码
- go-gin-example
