Golang Gin實踐 連載十五 生成二維碼、合併海報

原文地址:Golang Gin實踐 連載十五 生成二維碼、合併海報
項目地址:https://github.com/EDDYCJY/go...html

若是對你有所幫助,歡迎點個 Star 👍git

前言

在本章節,將實現以下功能細項:github

一、生成二維碼golang

二、合併海報(背景圖 + 二維碼)segmentfault

實現

首先,你須要在 App 配置項中增長二維碼及其海報的存儲路徑,咱們約定配置項名稱爲 QrCodeSavePath,值爲 qrcode/api

通過多節連載的你應該可以完成,如有不懂可參照 go-gin-example緩存

生成二維碼

安裝

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

工具包

考慮生成二維碼這一動做貼合工具包的定義,且有公用的可能性,新建 pkg/qrcode/qrcode.go 文件,寫入內容:app

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
}

這裏主要聚焦 func (q *QrCode) Encode 方法,作了以下事情:工具

  • 獲取二維碼生成路徑
  • 建立二維碼
  • 縮放二維碼到指定大小
  • 新建存放二維碼圖片的文件
  • 將圖像(二維碼)以 JPEG 4:2:0 基線格式寫入文件

另外在 jpeg.Encode(f, code, nil) 中,第三個參數可設置其圖像質量,默認值爲 75post

// 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
}

路由方法

一、第一步

在 routers/api/v1/article.go 新增 GenerateArticlePoster 方法用於接口開發

二、第二步

在 routers/router.go 的 apiv1 中新增 apiv1.POST("/articles/poster/generate", v1.GenerateArticlePoster) 路由

三、第三步

修改 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)

image

經過檢查兩個點肯定功能是否正常,以下:

一、訪問結果是否 200

二、本地目錄是否成功生成二維碼圖片

image

合併海報

在這一節,將實現二維碼圖片與背景圖合併成新的一張圖,可用於常見的宣傳海報等業務場景

背景圖

image

將背景圖另存爲 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
}

這裏重點留意 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,
    })
}

這塊涉及到大量知識,強烈建議閱讀下,以下:

其所涉及、關聯的庫都建議研究一下

StaticFS

在 routers/router.go 文件,增長以下代碼:

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

驗證

image

訪問完整的 URL 路徑,返回合成後的海報並掃除二維碼成功則正確 🤓

image

總結

在本章節實現了兩個很常見的業務功能,分別是生成二維碼和合並海報。但願你可以仔細閱讀我給出的連接,這塊的知識量很多,想要用好圖像處理的功能,必須理解對應的思路,觸類旁通

最後但願對你有所幫助 👌

參考

本系列示例代碼

本系列目錄

相關文章
相關標籤/搜索