从 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