前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >带你用Go实现二维码小游戏(上)

带你用Go实现二维码小游戏(上)

原创
作者头像
闫同学
发布2024-10-27 13:22:41
623
发布2024-10-27 13:22:41

说到用编程语言实现一个游戏,这恐怕就是儿时最大的梦想了,记得还在用翻盖手机的时候,那时手机里有一个游戏程序就叫Java,但当时哪懂Java是什么,只知道这个游戏和手机里其他的自带游戏不一样,需要手机开流量,再后来就到了智能机,游戏可以作为单独的APP放在手机里,互联网逐渐普及后,各种PC端的网页游戏、大型网络游戏等就开始如潮水般涌出,玩游戏的心也越来也重,直到有一天,我最喜欢的一个网页游戏发布了停服通知,那应该是我童年时期最喜欢的游戏之一,而在收到这个停服通知的时候我还没有上大学,更没有接触编程,但让这个游戏能回来是我一只的梦想。

后来上了大学,对编程有了越来越多的认识,才知道做一个游戏是多么复杂,要牵扯到的东西会很多很多,而技术只是最基本的要求,正因为如此,在做游戏的时候可以用到和学到很多知识,比如我们这篇文章要讲的《拼图二维码》小游戏,就牵扯到了Go语言的很多技术以及前端技术,下面我们话不多说,开始分享这个游戏的制作过程。

1 技术点

编程语言:Go、HTML+CSS、JavaScript

二维码生成:github.com/yeqown/go-qrcode

配置文件读取:github.com/spf13/viper

后续还可能在Docker镜像打包服务监控等方向扩展

2 游戏介绍

先看整个业务的流程图,游戏的核心就是拼图+二维码生成,拼图由前端实现,二维码则由后端实现:

体验地址(目前仅支持PC端):

http://yankaka.chat:8081/static/

游戏流程

1)开始输入你的用户名,并选择你要拼图的图片,然后点击开始:

2)选择图片完成后会出现游戏页面,进行拼图游戏:

3)拼图完成后会进行提示,点击确定页面跳转到二维码:

4)扫码,获取证书

3 代码实现

在代码实现层面,主要是采用HTTP接口进行前后端的交互,后端主要提供了两个HTTP接口,分别是:

/qrcode/gen:根据图片生成二维码,返回二维码地址。

/success:根据请求的用户名、拼图时间和二维码文件生成证书。

除此之外,后端进行了静态资源地址的配置,下面我们就从HTTP接口、配置文件读取、二维码生成、证书生成这几个流程进行分别的讲解。

3.1 HTTP接口定义

Go实现HTTP服务相对简单,只需要启动一个tcp的Listener,绑定端口,与HTTP Server关联,然后启动HTTP服务,如下代码,是我们提供的两个HTTP接口和静态资源地址的指定。

代码语言:go
复制
func runHttp() {
    listen, err := net.Listen("tcp", ":8081")
    if err != nil {
        panic(err)
    }
    mux := http.NewServeMux()
    mux.HandleFunc("/qrcode/gen", uploadFileHandler)
    mux.HandleFunc("/success", success)
    mux.Handle("/static/", http.StripPrefix("/", http.FileServer(http.Dir("."))))

    _ = http.Serve(listen, mux)
}

func main() {
    runHttp()
}
3.2 使用Viper进行配置文件读取

Viper应该算是Go项目中最常用的插件之一,主要是提供Go对不同类型配置文件的读取,可以是ini、conf、yaml等等类型,使用方式首先引入依赖:

代码语言:shell
复制
go get -u github.com/spf13/viper

然后在项目中增加config.yaml文件,进行配置的定义:

代码语言:yaml
复制
server:
  port: 8081
  tmp-path: /tmp
  domain: http://localhost:8081

其中:

  • server.port:后端HTTP服务绑定的端口
  • server.tmp-path:生成二维码的临时目录
  • server.domain:后端HTTP服务的Domain

然后使用config.go进行对配置文件的读取:

代码语言:go
复制
package main

import (
    "github.com/spf13/viper"
)

var globalConfig *GlobalConfig

type GlobalConfig struct {
    Port    int
    TmpPath string
    Domain  string
}

func GetGlobalConfig() *GlobalConfig {
    return globalConfig
}

func InitConfig() {
    viper.SetConfigFile("config.yaml")
    if err := viper.ReadInConfig(); err != nil {
        panic(err)
    }
    globalConfig = &GlobalConfig{
        Port:    viper.GetInt("server.port"),
        TmpPath: viper.GetString("server.tmp-path"),
        Domain:  viper.GetString("server.domain"),
    }
}

接下来我们在启动项目的时候就可以先读取配置文件,例如:

代码语言:go
复制
func main() {
    InitConfig()

    log.Infof("starting server config: %+v", GetGlobalConfig())

    runHttp()
}

启动项目后的效果:

代码语言:shell
复制
INFO[0000] starting server config: &{Port:8081 TmpPath:/tmp Domain:http://localhost:8081} 
3.3 生成二维码逻辑
3.3.1 提前了解插件使用方式

这一步应该是整个二维码游戏的核心功能之一,需要我们先引入两个三方包:

代码语言:shell
复制
go get -u github.com/yeqown/go-qrcode/v2

go get -u github.com/disintegration/imaging

如果是初次了解,可以先看下它的官方说明,先知道大概是怎么用的,比如:

https://github.com/yeqown/go-qrcode/blob/main/README.md

https://github.com/yeqown/go-qrcode/blob/main/example/simple/main.go

这两个文档中我们可以知道,使用该包生成二维码的大概流程就是三步:

1)实例化一个对象,并赋值二维码内容、编码格式等属性

2)指定要生成的二维码的文件名称

3)执行生成二维码的方法

3.3.2 定义生成二维码的结构体

阅读一下后续的文档,我们还可以了解到可以根据halftone、logo等不同的配置生成不同类型的二维码,那么我们就先定义一个结构体,它的属性就是生成二维码所需要的属性:

代码语言:go
复制
type QrCodeGen struct {
    Name            string // 文件名称
    Content         string // 二维码内容
    LogoFile        string // logo文件
    LogoWidth       LogoWidth // logo大小
    HalftoneSrcFile string // Halftone源文件
    Width           OutputSize // 二维码大小
    OutputFileType  FileType  // 输出文件格式
}

此外,为了有相对的规范,我们将logo大小、二维码大小和输出文件格式都定义了枚举:

代码语言:go
复制
type LogoWidth int

type FileType string

type OutputSize int

const (
    MINI   LogoWidth = 4
    MEDIUM LogoWidth = 3
    BIG    LogoWidth = 2

    NOT FileType = ""
    JPG FileType = "jpg"
    PNG FileType = "png"

    OutputMini   OutputSize = 200
    OutputMedium OutputSize = 500
    OutputBig    OutputSize = 1000
)
3.3.3 巧用Optional模式

什么是Optional模式?它有什么好处?大家可以阅读下这篇文章,在这里就不做额外的讲解:

https://time.geekbang.org/column/article/330212

使用Optional模式实现一个NewQuCodeGen函数:

代码语言:go
复制
type Option func(*QrCodeGen)

const (
    DefaultLogoWidth  = MEDIUM
    DefaultOutputSize = OutputMedium
    DefaultFileType   = JPG
)

type QrCodeGen struct {
    Name            string
    Content         string
    LogoFile        string
    LogoWidth       LogoWidth
    HalftoneSrcFile string
    Width           OutputSize
    OutputFileType  FileType
    Path            string
}

func NewQuCodeGen(content string, opts ...Option) *QrCodeGen {
    gen := &QrCodeGen{
        Content:        content,
        Width:          DefaultOutputSize,
        OutputFileType: DefaultFileType,
        LogoWidth:      DefaultLogoWidth,
    }
    for _, opt := range opts {
        opt(gen)
    }
    return gen
}

func WithLogoFile(fileName string) Option {
    return func(c *QrCodeGen) {
        c.LogoFile = fileName
    }
}

func WithLogoWidth(width LogoWidth) Option {
    return func(c *QrCodeGen) {
        c.LogoWidth = width
    }
}

func WithHalftoneSrcFile(fileName string) Option {
    return func(c *QrCodeGen) {
        c.HalftoneSrcFile = fileName
    }
}

func WithName(name string) Option {
    return func(c *QrCodeGen) {
        c.Name = name
    }
}

func WithOutputFileType(fileType FileType) Option {
    return func(c *QrCodeGen) {
        c.OutputFileType = fileType
    }
}

func WithOutputFileSize(size OutputSize) Option {
    return func(c *QrCodeGen) {
        c.Width = size
    }
}

func WithPath(path string) Option {
    return func(c *QrCodeGen) {
        c.Path = path
    }
}
3.3.4 生成二维码的核心方法

最重点的就是GenQrCode方法,是生成二维码的主要方法:

代码语言:go
复制
func (g *QrCodeGen) GenQrCode() (string, error) {
    // 确认文件名称
    qrFileName := fmt.Sprintf("%d.%s", time.Now().UnixMilli(), g.OutputFileType)
    if g.Name != "" {
        qrFileName = fmt.Sprintf("%s.%s", g.Name, g.OutputFileType)
    }

    // 内容
    qrc, err := qrcode.NewWith(g.Content,
        qrcode.WithEncodingMode(qrcode.EncModeByte),
        qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionMedium),
    )
    if err != nil {
        log.Errorf("qrcode.NewWith error: %v", err)
        return "", err
    }

    // 基本内容
    imageOptions := make([]standard.ImageOption, 0)
    imageOptions = append(imageOptions, standard.WithQRWidth(uint8(g.Width/10)))

    if g.LogoFile != "" {
        var resizeImg *image.NRGBA
        logoSrc, err := imaging.Open(g.LogoFile)
        if err != nil {
            log.Errorf("imaging.Open error: %v", err)
            return "", err
        }
        logoWidth, logoHeight := logoSrc.Bounds().Dx(), logoSrc.Bounds().Dy()

        log.Infof("logofile size width: %d ,height: %d", logoWidth, logoHeight)

        if g.LogoWidth > 0 {
            switch g.LogoWidth {
            case MINI:
                resizeImg = imaging.Resize(logoSrc, int(g.Width)/int(MINI), int(g.Width)/int(MINI), imaging.Lanczos)
            case MEDIUM:
                resizeImg = imaging.Resize(logoSrc, int(g.Width)/int(MEDIUM), int(g.Width)/int(MEDIUM), imaging.Lanczos)
            case BIG:
                resizeImg = imaging.Resize(logoSrc, int(g.Width)/int(BIG), int(g.Width)/int(BIG), imaging.Lanczos)
            }
        } else {
            resizeImg = imaging.Resize(logoSrc, int(g.Width)/int(MEDIUM), int(g.Width)/int(MEDIUM), imaging.Lanczos)
        }

        g.LogoFile = fmt.Sprintf("%s_tmp.%s", GetFileName(g.LogoFile), JPG)
        if err = imaging.Save(resizeImg, g.LogoFile); err != nil {
            log.Errorf("imaging.Save: %v", err)
            return "", err
        }
        imageOptions = append(imageOptions, standard.WithLogoImageFileJPEG(g.LogoFile))
        imageOptions = append(imageOptions, standard.WithLogoSizeMultiplier(int(g.LogoWidth)))
    }

    // Halftone
    if g.HalftoneSrcFile != "" {
        imageOptions = append(imageOptions, []standard.ImageOption{
            standard.WithHalftone(g.HalftoneSrcFile),
            standard.WithBgTransparent(),
        }...)
    }

    w, err := standard.New(fmt.Sprintf("%s/%s", "./static", qrFileName), imageOptions...)
    if err != nil {
        log.Errorf("qrcode.NewWith error: %v", err)
        return "", err
    }
    if err = qrc.Save(w); err != nil {
        log.Errorf("qrc.Save: %v", err)
        return "", err
    }
    return qrFileName, nil
}

在这个方法里主要用到了两个Go的三方依赖:

  • github.com/disintegration/imaging
  • github.com/yeqown/go-qrcode

其中github.com/disintegration/imaging是一个功能强大的Go语言图像处理库,它提供了丰富的图像处理函数和方法,可以满足大多数图像处理需求。通过简单的API调用,开发者可以轻松地在Go项目中实现图像的打开、缩放、裁剪、旋转、滤镜效果添加以及保存等操作。

github.com/yeqown/go-qrcode是一个功能强大且易于使用的Go语言二维码生成库。它提供了丰富的自定义选项和高效的性能,支持广泛的应用场景。通过简单的API调用,开发者可以轻松地在Go项目中生成各种样式的二维码。

源码获取方式,关注公众号【扯编程的淡】后回复【qrcode】

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 技术点
  • 2 游戏介绍
  • 3 代码实现
    • 3.1 HTTP接口定义
      • 3.2 使用Viper进行配置文件读取
        • 3.3 生成二维码逻辑
          • 3.3.1 提前了解插件使用方式
          • 3.3.2 定义生成二维码的结构体
          • 3.3.3 巧用Optional模式
          • 3.3.4 生成二维码的核心方法
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档