Write the Code. Change the World.

9月 07

从 http 请求到自定义路由,再对应的返回已经跑通了。但是,还没具体的实现业务。那就先从图片验证码开始。从以下几点开始。

  • 先去找一个比较成熟的生成图片验证码的库
  • 写生成逻辑,并加入缓存功能。比如缓存 5 分钟。
  • 添加配置。图片的默认宽高,复杂度等。以及上边说的缓存 5 分钟这些,最好有一个统一配置。
  • 再就是核验验证码(该步骤留到需要使用验证码的地方使用)

redis 配置

在做这些之前,我们先把 redis 配置和使用搞好。就是准备使用 redis 来对图片验证码的值进行缓存。

goframe 官方配置介绍: https://goframe.org/pages/viewpage.action?pageId=1114194

redis 官方介绍: https://goframe.org/display/gf/NoSQL+Redis

配置文件的文件类型就使用 yaml,配置文件就放在 *manifest/config下。

好吧。那么就在 *manifest/config/config.yaml 下补充。

# Redis 配置
redis:
  default:
    address: 192.168.56.56:6379
    db:      1

  cache:
    address:     192.168.56.56:6379
    db:          2

配置好了,那就开始使用测试下看看。

先安装 gredis ,并在 main.go 中引入

go get -u github.com/gogf/gf/contrib/nosql/redis/v2

import (
    _ "github.com/gogf/gf/contrib/nosql/redis/v2"

    // other imported packages.
)

只是测试一下,在 cmd.go 中加入一段测试代码。

func test(ctx context.Context) {
    _, err := g.Redis().Set(ctx, "want", "wind")
    if err != nil {
        fmt.Println("error1")
    }
    value, err := g.Redis().Get(ctx, "want")
    if err != nil {
        fmt.Println("error2")
    }
    fmt.Println("want", value)
}

并调用起来。然后运行 go run main.go 看看输出。

Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
            test(ctx)

            s := g.Server()
            …

# 输出
❯ go run main.go
want wind
2023-09-07 13:16:57.437 [INFO] pid[66978]: http server started listening on [:9885]

发现 redis 已经正常 set get 了哈。 再去 redis 管理工具中看看,对应 db 中是不是对应的数据。

数据有了哈。goframe 中的 redis 配置项,没有前缀的配置,这个有点难受,还得主动搞。

验证码抽象

不是所有轮子都自己造,有好用的轮子,当然会省去很多事。这里选择一个优秀的图片验证码开源库。

https://github.com/mojocn/base64Captcha

图片验证码,后边用的地方可能会比较多。这里就独立抽象出来哈。开始操作。

go get github.com/mojocn/base64Captcha

base64Captcha 内置了存储功能,也提供自定义存储功能。我想用 redis,所以就自定义吧。实现其三个接口就好。

Set
Get
Verify

先建立 manifest/config/captcha.yaml 配置文件。填充以下内容。

captcha:
  width: 120       # 验证码图片的宽度
  height: 50       # 验证码图片的高度
  length: 4        # 验证码的长度
  maxskew: 0.7     # 验证码的最大倾斜角度
  dotcount: 80     # 图片背景里的混淆点数量
  expire_time: 5   # 过期时间,单位是分钟

新建 internal/pkg/captcha/store.go,作为 base64Captcha 的存储件。

package captcha

import (
    "context"

    "github.com/gogf/gf/v2/frame/g"
)

type RedisStore struct {
    Ctx       context.Context
    KeyPrefix string
}

func (s *RedisStore) Set(key string, value string) error {

    expireTime := g.Cfg("captcha").MustGet(s.Ctx, "captcha.expire_time").Int64()

    error := g.Redis("cache").SetEX(s.Ctx, s.KeyPrefix+key, value, expireTime*60)

    return error
}

func (s *RedisStore) Get(key string, clear bool) string {
    key = s.KeyPrefix + key

    val, err := g.Redis("cache").Get(s.Ctx, key)

    if err != nil {
        return ""
    }

    if clear {
        g.Redis("cache").Del(s.Ctx, key)
    }
    return val.String()
}

func (s *RedisStore) Verify(key, answer string, clear bool) bool {
    v := s.Get(key, clear)
    return v == answer
}

再来新建 internal/pkg/captcha/captche.go,作为图片验证码的对外的功能件。

package captcha

import (
    "context"
    "goSimpleAdmin/internal/consts"
    "sync"

    "github.com/gogf/gf/v2/errors/gerror"
    "github.com/gogf/gf/v2/frame/g"
    "github.com/mojocn/base64Captcha"
)

type Captcha struct {
    Base64Captcha *base64Captcha.Captcha
}

var once sync.Once

var captcha *Captcha

var captchaStore *RedisStore

type CaptchaConfig struct {
    Width      int     `json:"width"`
    Height     int     `json:"Height"`
    Length     int     `json:"length"`
    Maxskew    float64 `json:"maxskew"`
    Dotcount   int     `json:"dotcount"`
    ExpireTime float32 `json:"expire_time"`
}

func NewCaptcha(ctx context.Context) *Captcha {
    once.Do(func() {
        captcha = &Captcha{}
        captchaStore = &RedisStore{
            KeyPrefix: consts.REDIS_PREFIX + ":captcha:",
        }

        // 配置文件
        captchaConfig := &CaptchaConfig{}
        err := g.Cfg("captcha").MustGet(ctx, "captcha").Scan(captchaConfig)
        if err != nil {
            gerror.Wrap(err, "captcha 配置错误")
            return
        }

        driver := base64Captcha.NewDriverDigit(
            captchaConfig.Height,
            captchaConfig.Width,
            captchaConfig.Length,
            captchaConfig.Maxskew,
            captchaConfig.Dotcount,
        )

        captcha.Base64Captcha = base64Captcha.NewCaptcha(driver, captchaStore)
    })
    captchaStore.Ctx = ctx

    return captcha
}

// 生成图片验证码
func (c *Captcha) GenerateCaptcha() (id string, b64s string, err error) {
    return c.Base64Captcha.Generate()
}

// 验证验证码
func (c *Captcha) VerifyCaptcha(id string, answer string) (match bool) {
    return c.Base64Captcha.Verify(id, answer, false)
}

好了,就这么多代码了。刚开始学 go,刚开始用 goframe。还是费了一些时间。有以下几个地方需要注意一下。

  • context.Context 这个上下文, goframe 框架的组件都需要。到哪都得带上。
  • 配置文件的使用。对于多个 map[string]interface{} 对象,我们可以建立一个 struct, 使用 config 的 scan 方法。将数据填进去。再使用就很爽了。要不转数据类型转到头晕晕。

测试测试

先在 cmd.go 中写个小 demo 测试测试。

# 先打印出 id,再把 base64 拷贝到浏览器看看具体的数字。再在 rdm 中看看 redis 对应的值和图片上的值对不对。
id, b64s, err := captcha.NewCaptcha(ctx).GenerateCaptcha()

fmt.Println("dddd", id, b64s, err)

# 上边的都搞检验完了。就调用 VerifyCaptcha 的试试,看是否是期望的结果。
result := captcha.NewCaptcha(ctx).VerifyCaptcha("xxxx", "xx")
fmt.Println("result", result)

上边的测试代码是在 cmd.go 中的哈,分两次操作。第一次先生成,然后注释生成的代码。再调用检测代码。就可以看到结果了。

通过测试,的确达到预期的效果了。只是有一点需要注意哈。 验证码信息还在 redis 中,并没有清理掉。可以等着时间到了清理掉。

如果 Base64Captcha.Verify 的第三个参数传 true ,就会马上清理掉。这样有个不好的地方就是,用户没有尝试的机会,一次不对只能再生再试。看个人喜好吧。

将生成图片验证码逻辑接入到接口实现逻辑中

api,ctrl,model,service 这些都会按照 https://blog.vini123.com/1031 里的方式进行重构一遍。最后看看 ctrl 中的逻辑。

package user

import (
    "context"

    "goSimpleAdmin/api/user/admin"
    model "goSimpleAdmin/internal/model/admin"
    "goSimpleAdmin/internal/service"
)

func (c *ControllerAdmin) Captcha(ctx context.Context, req *admin.CaptchaReq) (res *admin.CaptchaRes, err error) {
    data, err := service.User().Captcha(ctx, model.CaptchaReq{})

    if err != nil {
        return
    }

    res = new(admin.CaptchaRes)
    res.CaptchaRes = data
    return
}

使用 postman 调用一下,就会收到结果了。

{
    "code": 0,
    "message": "",
    "data": {
        "id": "j2SUdau9u8XNazKTHoYn",
        "base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFgAAAAgCAMAAAChOk+qAAAAe1BMVEUAAAB5b0qonnmYjmmxp4KTiWRDORSKgFuSiGNqYDs+NA9FOxY9Mw6EelVIPhk2LAc0KgV3bUiRh2JsYj19c06KgFtdUy5QRiGWjGeimHNiWDNYTimSiGNiWDNaUCumnHekmnVSSCN9c05qYDuVi2aSiGNaUCtSSCOAdlFnFDvAAAAAAXRSTlMAQObYZgAAAbdJREFUeJyklt2OgyAQhWdaSzSpxqgX/sRqjL7/K24EBwYXWOieCyLa+TgMAxSMUAo8WnwfInRSb+hCc5f/kBWecwtDjov+BMAIxniR6OrzCZFVuhORROadAqBlXCQusqESNGluUbQtI9cn6VpIOUSa/WmaXI43rOvaJFl5/9Ix17ZtWINgZWcn5aZX/HAbIggh9ORNSlzcVzxZAgRLq8wxeNApXCpjVFtCOu7YpxTUvTOcD9myLFdNdF3nDX9HsZWpYRgAsixb1AdEDHDfQTJQbfWyN5zPGb115iCPcqzRfd+zPl/AGznP87+AHI3Ya3c3m9h6HCtVcXglq5SsPf9LVeUjV5qq2br4n2eztdL/EY4PjWgSQdznU+50yT18ZOt8dI/IcqwmoByrtwFuMFcXusSd0PJFAxF7zs8d6KEsy30n2wdi0zTf3icwXvvtIsNOoOM4yd9iYRxHcrxeLXk87MP4ke5YaV1Xal2H2eNhyI0L5J/bylq+Zt3dcdPIK0hwasJSUGU4Tk55aQohItxamhncOLbIluNYt/M8A8QHJSRhvgV6I4P/UWN0ntAuqufnPwEAAP//PacP4Gqbpg8AAAAASUVORK5CYII="
    }
}

真心不喜欢包裹一层。 code 和 message 这些真想干掉。

到此,整个项目的文件构成是这样子的。

.
├── Makefile
├── README.MD
├── api
│   └── user
│       ├── admin
│       │   └── user.go
│       └── user.go
├── go.mod
├── go.sum
├── hack
│   ├── config.yaml
│   ├── hack-cli.mk
│   └── hack.mk
├── internal
│   ├── cmd
│   │   └── cmd.go
│   ├── consts
│   │   ├── consts.go
│   │   └── redis.go
│   ├── controller
│   │   └── user
│   │       ├── user.go
│   │       ├── user_admin_captcha.go
│   │       ├── user_admin_sign_in.go
│   │       ├── user_admin_sign_up.go
│   │       └── user_new.go
│   ├── dao
│   │   ├── internal
│   │   │   └── user.go
│   │   └── user.go
│   ├── logic
│   │   ├── logic.go
│   │   └── user
│   │       └── user.go
│   ├── model
│   │   ├── admin
│   │   │   └── user.go
│   │   ├── do
│   │   │   └── user.go
│   │   └── entity
│   │       └── user.go
│   ├── packed
│   │   └── packed.go
│   ├── pkg
│   │   └── captcha
│   │       ├── captche.go
│   │       └── store.go
│   └── service
│       └── user.go
├── main
├── main.go
├── manifest
│   ├── config
│   │   ├── captcha.yaml
│   │   └── config.yaml
│   ├── deploy
│   │   └── kustomize
│   │       ├── base
│   │       │   ├── deployment.yaml
│   │       │   ├── kustomization.yaml
│   │       │   └── service.yaml
│   │       └── overlays
│   │           └── develop
│   │               ├── configmap.yaml
│   │               ├── deployment.yaml
│   │               └── kustomization.yaml
│   ├── docker
│   │   ├── Dockerfile
│   │   └── docker.sh
│   └── protobuf
├── resource
│   ├── i18n
│   ├── public
│   │   ├── html
│   │   ├── plugin
│   │   └── resource
│   │       ├── css
│   │       ├── image
│   │       └── js
│   └── template
└── utility

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注