当前位置: 首页 > article >正文

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

说到用编程语言实现一个游戏,这恐怕就是儿时最大的梦想了,记得还在用翻盖手机的时候,那时手机里有一个游戏程序就叫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接口和静态资源地址的指定。

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等等类型,使用方式首先引入依赖:

go get -u github.com/spf13/viper

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

server:
  port: 8081
  tmp-path: /tmp
  domain: http://localhost:8081

其中:

  • server.port:后端HTTP服务绑定的端口

  • server.tmp-path:生成二维码的临时目录

  • server.domain:后端HTTP服务的Domain

然后使用config.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"),
    }
}

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

func main() {
    InitConfig()

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

    runHttp()
}

启动项目后的效果:

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

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

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等不同的配置生成不同类型的二维码,那么我们就先定义一个结构体,它的属性就是生成二维码所需要的属性:

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

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

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函数:

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方法,是生成二维码的主要方法:

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项目中生成各种样式的二维码。


http://www.kler.cn/news/368185.html

相关文章:

  • 安卓在windows连不上fastboot问题记录
  • 【学术论文投稿】Windows11开发指南:打造卓越应用的必备攻略
  • 多eSIM配置文件(MEP)
  • Mysql 数据库架构
  • python实现数据库两个表之间的更新操作(模糊匹配)示例
  • 【数据分享】中国汽车市场年鉴(2013-2023)
  • 10.23Python_Matplotlib_‘backend_interagg‘ has no attribute
  • smartctl硬盘检查工具
  • docker入门(三)自定义部署docker镜
  • Vuetify3响应式布局
  • c#时间对象(时间间隔)相关计算
  • 【已解决】edge浏览器会保存历史验证码?新版Edge如何关闭自动填充表单功能?
  • JMeter实战之——模拟登录
  • js实现数据版购物车(实现购物车所有功能)
  • CSS基础—网页布局(重点!)
  • JavaWeb合集18-接口管理Swager
  • YOLOv5/v8/v10/v11详细介绍:网络结构,创新点
  • 升级Unity后产生的Objects内存泄露现象
  • 面试总结(2024/10/16)
  • ubuntu 硬盘扩容
  • python对文件的读写操作
  • 快速入门HTML
  • 影刀RPA实战:验证码识别功能指令
  • 华为手机卡住了怎么办?,快来试试布局性能优化,让你的手机发挥极致性能!!!
  • Linux TCP CC状态机
  • 渗透测试-SQL注入基础知识