扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • 宝典简介

  • gin系列目录

    • Golang介绍与环境安装
    • Gin搭建Blog API’s (一)
    • Gin实践 连载三 搭建Blog API’s(二)
    • Gin实践 连载四 搭建Blog API’s(三)
    • Gin实践 连载五 使用JWT进行身份校验
    • Gin实践 连载六 编写一个简单的文件日志
    • Gin实践 连载七 Golang优雅重启HTTP服务
    • Gin实践 连载八 为它加上Swagger
    • Gin实践 连载九 将Golang应用部署到Docker
    • Gin实践 连载十 定制 GORM Callbacks
    • Gin实践 连载十一 Cron定时任务
    • Gin实践 连载十二 优化配置结构及实现图片上传
    • Gin实践 连载十三 优化你的应用结构和实现Redis缓存
    • Gin实践 连载十四 实现导出、导入 Excel
    • Gin实践 连载十五 生成二维码、合并海报
    • Gin实践 连载十六 在图片上绘制文字
    • Gin实践 连载十七 用 Nginx 部署 Go 应用
    • Gin实践 番外 Golang交叉编译
    • Gin实践 番外 请入门 Makefile
  • 杂谈

  • 爬虫系列目录

  • gRPC系列目录

扫码订阅《 》或入驻星球,即可阅读文章!

Gin实践 连载十五 生成二维码、合并海报


煎鱼

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

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

# 前言

在本章节,将实现如下功能细项:

1、生成二维码

2、合并海报(背景图 + 二维码)

# 实现

首先,你需要在 App 配置项中增加二维码及其海报的存储路径,我们约定配置项名称为 QrCodeSavePath,值为 qrcode/

经过多节连载的你应该能够完成,若有不懂可参照 go-gin-example (opens new window)

# 生成二维码

# 安装

$ go get -u github.com/boombuler/barcode
1

# 工具包

考虑生成二维码这一动作贴合工具包的定义,且有公用的可能性,新建 pkg/qrcode/qrcode.go 文件,写入内容:

package qrcode
import (
    "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    string
    Width  int
    Height int
    Ext    string
    Level  qr.ErrorCorrectionLevel
    Mode   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 + name
    if 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

这里主要聚焦 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
2
3
4
5
6
7

# 路由方法

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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 验证

通过 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_service
import (
    "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
    *Article
    Qr *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 string
    X0   int
    Y0   int
    X1   int
    Y1   int
}
type Pt struct {
    X int
    Y 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

这里重点留意 func (a *ArticlePosterBg) Generate() 方法,做了如下事情:

  • 获取二维码存储路径
  • 生成二维码图像
  • 检查合并后图像(指的是存放合并后的海报)是否存在
  • 若不存在,则生成待合并的图像 mergedF
  • 打开事先存放的背景图 bgF
  • 打开生成的二维码图像 qrF
  • 解码 bgF 和 qrF 返回 image.Image
  • 创建一个新的 RGBA 图像
  • 在 RGBA 图像上绘制 背景图(bgF)
  • 在已绘制背景图的 RGBA 图像上,在指定 Point 上绘制二维码图像(qrF)
  • 将绘制好的 RGBA 图像以 JPEG 4:2:0 基线格式写入合并后的图像文件(mergedF)

# 错误码

新增 错误码 (opens new window),错误提示 (opens new window)

# 路由方法

打开 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,
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

这块涉及到大量知识,强烈建议阅读下,如下:

  • image.Rect (opens new window)
  • image.Pt (opens new window)
  • image.NewRGBA (opens new window)
  • jpeg.Encode (opens new window)
  • jpeg.Decode (opens new window)
  • draw.Op (opens new window)
  • draw.Draw (opens new window)
  • go-imagedraw-package (opens new window)

其所涉及、关联的库都建议研究一下

# StaticFS

在 routers/router.go 文件,增加如下代码:

r.StaticFS("/qrcode", http.Dir(qrcode.GetQrCodeFullPath()))
1

# 验证

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

# 总结

在本章节实现了两个很常见的业务功能,分别是生成二维码和合并海报。希望你能够仔细阅读我给出的链接,这块的知识量不少,想要用好图像处理的功能,必须理解对应的思路,举一反三

最后希望对你有所帮助 ?

# 参考

# 本系列示例代码

  • go-gin-example (opens new window)
  • 前言
  • 实现
  • 生成二维码
  • 安装
  • 工具包
  • 路由方法
  • 验证
  • 合并海报
  • 背景图
  • service 方法
  • 错误码
  • 路由方法
  • StaticFS
  • 验证
  • 总结
  • 参考
  • 本系列示例代码