Golang Gin實踐 連載十五 產生二維碼、合并海報
原文地址:Golang Gin實踐 連載十五 產生二維碼、合并海報
項目地址:https://github.com/EDDYCJY/go...
如果對你有所協助,歡迎點個 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 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}
這裡主要聚焦 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 *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}
這裡重點留意 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 路徑,返回合成後的海報並掃除二維碼成功則正確
總結
在本章節實現了兩個很常見的業務功能,分別是產生二維碼和合并海報。希望你能夠仔細閱讀我給出的連結,這塊的知識量不少,想要用好影像處理的功能,必須理解對應的思路,舉一反三
最後希望對你有所協助
參考
本系列範例程式碼