前言
在现代 Web 应用中,验证码是防止机器人攻击和恶意请求的重要手段。相比传统的图形验证码,滑动行为验证码具有更好的用户体验。本文将介绍如何使用 go-captcha 库在 Go 后端和 Vue 前端实现滑动验证码功能。
技术栈
- 后端:Go + Gin 框架
- 前端:Vue 3 + Element Plus
- 验证码库:go-captcha(后端)+ go-captcha-vue(前端)
- 缓存:Redis(用于存储验证码数据)
一、后端实现
1.1 安装依赖
- go get github.com/wenlng/go-captcha/v2
- go get github.com/wenlng/go-captcha-assets
- go get github.com/gin-gonic/gin
- go get github.com/redis/go-redis/v9
复制代码 1.2 初始化验证码模块
创建 captcha/init.go 文件:- package captcha
- import (
- "errors"
- )
- var (
- ErrGenData = errors.New("generate data error")
- )
- const (
- Deviation = 10 // 验证偏差值,允许用户滑动有一定误差
- )
- func Init() error {
- return initSlide()
- }
复制代码 1.3 实现滑动验证码核心逻辑
创建 captcha/slide.go 文件:- package captcha
- import (
- images "github.com/wenlng/go-captcha-assets/resources/imagesv2"
- "github.com/wenlng/go-captcha-assets/resources/tiles"
- "github.com/wenlng/go-captcha/v2/base/option"
- "github.com/wenlng/go-captcha/v2/slide"
- )
- var slideCapt slide.Captcha
- // initSlide 初始化滑动验证码
- func initSlide() error {
- builder := slide.NewBuilder(
- slide.WithGenGraphNumber(1),
- slide.WithEnableGraphVerticalRandom(true),
- slide.WithImageSize(option.Size{Width: 300, Height: 220}),
- )
- // 加载背景图片资源
- imgs, err := images.GetImages()
- if err != nil {
- return err
- }
- // 加载滑块图形资源
- graphs, err := tiles.GetTiles()
- if err != nil {
- return err
- }
- var newGraphs = make([]*slide.GraphImage, 0, len(graphs))
- for i := range graphs {
- graph := graphs[i]
- newGraphs = append(newGraphs, &slide.GraphImage{
- OverlayImage: graph.OverlayImage,
- MaskImage: graph.MaskImage,
- ShadowImage: graph.ShadowImage,
- })
- }
- // 设置资源
- builder.SetResources(
- slide.WithGraphImages(newGraphs),
- slide.WithBackgrounds(imgs),
- )
- slideCapt = builder.Make()
- return nil
- }
- // Slide 验证码数据结构
- type Slide struct {
- // 滑块初始显示坐标
- SliderX int
- SliderY int
- SliderWidth int
- SliderHeight int
- // 整体大小
- MainWidth int
- MainHeight int
- // 答案坐标(服务端保存,不返回给前端)
- X int
- Y int
- // 主图与滑块的图片,base64编码
- MainImage string
- SliderImage string
- }
- // NewSlide 生成新的滑动验证码
- func NewSlide() (*Slide, error) {
- m := &Slide{}
- captData, err := slideCapt.Generate()
- if err != nil {
- return nil, err
- }
- dotData := captData.GetData()
- if dotData == nil {
- return nil, ErrGenData
- }
-
- // 答案坐标(正确的滑块位置)
- m.X = dotData.X
- m.Y = dotData.Y
-
- // 滑块初始显示坐标
- m.SliderWidth = dotData.Width
- m.SliderHeight = dotData.Height
- m.SliderX = dotData.DX
- m.SliderY = dotData.DY
-
- // 图片大小
- m.MainWidth = 300
- m.MainHeight = 220
- // 转换为 base64 编码
- m.MainImage, err = captData.GetMasterImage().ToBase64()
- if err != nil {
- return nil, err
- }
- m.SliderImage, err = captData.GetTileImage().ToBase64()
- if err != nil {
- return nil, err
- }
- return m, nil
- }
- // VerifySlide 验证滑动位置是否正确
- func VerifySlide(userX, userY, slideX, slideY int) bool {
- return slide.Validate(userX, userY, slideX, slideY, Deviation)
- }
复制代码 1.4 定义响应结构体
创建 vo/captcha.go 文件:- package vo
- type GetCaptchaRes struct {
- ID string `json:"id"` // 验证码唯一标识
- SliderX int `json:"sliderX"` // 滑块初始X坐标
- SliderY int `json:"sliderY"` // 滑块初始Y坐标
- SliderWidth int `json:"sliderWidth"` // 滑块宽度
- SliderHeight int `json:"sliderHeight"` // 滑块高度
- SliderImage string `json:"sliderImage"` // 滑块图片(base64)
- MainWidth int `json:"mainWidth"` // 主图宽度
- MainHeight int `json:"mainHeight"` // 主图高度
- MainImage string `json:"mainImage"` // 主图(base64)
- }
复制代码 1.5 实现 HTTP 处理器
创建 handler/captcha_handler.go 文件:- package handler
- import (
- "encoding/json"
- "time"
- "github.com/gin-gonic/gin"
- "github.com/redis/go-redis/v9"
- "github.com/rs/xid"
-
- "your-project/captcha"
- "your-project/vo"
- )
- type CaptchaHandler struct {
- redis *redis.Client
- }
- func NewCaptchaHandler(redis *redis.Client) *CaptchaHandler {
- return &CaptchaHandler{
- redis: redis,
- }
- }
- // GetCaptcha 获取验证码
- func (h *CaptchaHandler) GetCaptcha(c *gin.Context) {
- // 生成验证码
- m, err := captcha.NewSlide()
- if err != nil {
- c.JSON(500, gin.H{"error": "Failed to create captcha"})
- return
- }
- // 序列化验证码数据
- value, err := json.Marshal(m)
- if err != nil {
- c.JSON(500, gin.H{"error": "Failed to marshal captcha"})
- return
- }
- // 生成唯一ID并存储到 Redis,有效期1分钟
- uuid := xid.New().String()
- err = h.redis.Set(c, uuid, value, time.Minute).Err()
- if err != nil {
- c.JSON(500, gin.H{"error": "Failed to store captcha"})
- return
- }
- // 返回给前端的数据(不包含答案坐标)
- res := &vo.GetCaptchaRes{
- ID: uuid,
- SliderX: m.SliderX,
- SliderY: m.SliderY,
- SliderWidth: m.SliderWidth,
- SliderHeight: m.SliderHeight,
- SliderImage: m.SliderImage,
- MainWidth: m.MainWidth,
- MainHeight: m.MainHeight,
- MainImage: m.MainImage,
- }
- c.JSON(200, gin.H{"code": 0, "data": res})
- }
- // VerifyCaptcha 验证滑动验证码
- func (h *CaptchaHandler) VerifyCaptcha(c *gin.Context) {
- var data struct {
- ID string `json:"id"` // 验证码标识
- X int `json:"x"` // 用户滑动的X坐标
- Y int `json:"y"` // 用户滑动的Y坐标
- }
-
- if err := c.ShouldBindJSON(&data); err != nil {
- c.JSON(400, gin.H{"error": "Invalid arguments"})
- return
- }
- // 从 Redis 获取验证码数据
- value, err := h.redis.Get(c, data.ID).Result()
- if err != nil {
- c.JSON(400, gin.H{"error": "验证码已过期或不存在"})
- return
- }
- // 反序列化验证码数据
- slide := captcha.Slide{}
- err = json.Unmarshal([]byte(value), &slide)
- if err != nil {
- c.JSON(500, gin.H{"error": "验证码数据错误"})
- return
- }
- // 验证滑动位置
- if !captcha.VerifySlide(data.X, data.Y, slide.X, slide.Y) {
- c.JSON(400, gin.H{"error": "验证码验证失败"})
- return
- }
- // 验证成功后删除 Redis 中的数据(防止重复使用)
- h.redis.Del(c, data.ID)
- c.JSON(200, gin.H{"code": 0, "message": "验证成功"})
- }
复制代码 1.6 注册路由
在 main.go 中注册路由:- package main
- import (
- "github.com/gin-gonic/gin"
- "github.com/redis/go-redis/v9"
-
- "your-project/captcha"
- "your-project/handler"
- )
- func main() {
- // 初始化验证码模块
- if err := captcha.Init(); err != nil {
- panic(err)
- }
- // 初始化 Redis 客户端
- rdb := redis.NewClient(&redis.Options{
- Addr: "localhost:6379",
- })
- // 创建 Gin 路由
- r := gin.Default()
- // 创建处理器
- captchaHandler := handler.NewCaptchaHandler(rdb)
- // 注册路由
- api := r.Group("/api")
- {
- api.POST("/captcha", captchaHandler.GetCaptcha)
- api.POST("/captcha/verify", captchaHandler.VerifyCaptcha)
- }
- r.Run(":8080")
- }
复制代码 二、前端实现
2.1 安装依赖
- npm install go-captcha-vue
- npm install element-plus
复制代码 2.2 创建验证码组件
创建 components/SlideCaptcha.vue 文件:- <template>
-
- <el-button
- :disabled="!canSend"
- @click="handleClick"
-
- >
- {{ btnText }}
- </el-button>
-
- <el-dialog
- v-model="showDialog"
- width="326px"
- :close-on-click-modal="false"
- :show-close="false"
- :append-to-body="true"
- >
- <GoCaptchaSlide
- v-if="captchaData"
- :config="captchaConfig"
- :data="captchaData"
- :events="captchaEvents"
- />
- </el-dialog>
-
- </template>
复制代码 2.3 使用验证码组件
在需要使用验证码的页面中:- <template>
-
- <el-form>
- <el-form-item label="手机号">
- <el-input v-model="mobile" placeholder="请输入手机号" />
- </el-form-item>
-
- <el-form-item label="验证码">
- <el-input v-model="code" placeholder="请输入验证码">
- <template #append>
- <SlideCaptcha
- @success="handleCaptchaSuccess"
- btn-text="获取验证码"
- />
- </template>
- </el-input>
- </el-form-item>
-
- <el-form-item>
- <el-button type="primary" @click="handleLogin">登录</el-button>
- </el-form-item>
- </el-form>
-
- </template>
复制代码 2.4 HTTP 工具函数
创建 utils/http.js 文件:- import axios from 'axios'
- const http = axios.create({
- baseURL: 'http://localhost:8080',
- timeout: 10000
- })
- // 请求拦截器
- http.interceptors.request.use(
- config => {
- // 可以在这里添加 token
- return config
- },
- error => {
- return Promise.reject(error)
- }
- )
- // 响应拦截器
- http.interceptors.response.use(
- response => {
- const res = response.data
- if (res.code !== 0) {
- return Promise.reject(new Error(res.error || 'Error'))
- }
- return res
- },
- error => {
- return Promise.reject(error)
- }
- )
- export const httpPost = (url, data) => {
- return http.post(url, data)
- }
- export const httpGet = (url, params) => {
- return http.get(url, { params })
- }
复制代码 三、核心流程说明
3.1 验证码生成流程
- 前端点击"获取验证码"按钮
- 前端调用 /api/captcha 接口
- 后端生成验证码图片和答案坐标
- 后端将完整数据(包含答案)存储到 Redis,有效期1分钟
- 后端返回验证码ID和图片数据(不包含答案)给前端
- 前端展示滑动验证码弹窗
3.2 验证码验证流程
- 用户拖动滑块到目标位置
- 前端获取滑块坐标,调用 /api/captcha/verify 接口
- 后端从 Redis 获取验证码答案数据
- 后端比对用户滑动坐标与答案坐标(允许一定偏差)
- 验证成功后删除 Redis 数据,返回成功响应
- 前端收到成功响应后执行后续业务逻辑
3.3 安全性说明
- 答案不暴露:验证码答案坐标只存储在服务端 Redis 中,不返回给前端
- 一次性使用:验证成功后立即删除 Redis 数据,防止重复使用
- 时效性:验证码有效期1分钟,过期自动失效
- 偏差容忍:允许用户滑动有一定误差(默认10像素),提升用户体验
四、常见问题
4.1 验证码图片不显示
检查 base64 编码是否正确,确保前端正确解析 data:image/png;base64, 前缀。
4.2 验证总是失败
检查偏差值设置是否合理,可以适当增大 Deviation 常量的值。
4.3 Redis 连接失败
确保 Redis 服务已启动,检查连接地址和端口是否正确。
4.4 跨域问题
在 Gin 中添加 CORS 中间件:- import "github.com/gin-contrib/cors"
- r.Use(cors.Default())
复制代码 五、总结
本文介绍了如何使用 go-captcha 库在 Go + Vue 项目中实现滑动验证码功能。核心要点:
- 后端使用 go-captcha 生成验证码图片和答案
- 使用 Redis 存储验证码数据,保证安全性和时效性
- 前端使用 go-captcha-vue 组件展示验证码
- 验证流程简单清晰,用户体验良好
完整代码可以直接应用到新项目中,只需根据实际情况调整路由、响应格式等细节即可。
参考资源
- go-captcha GitHub
- go-captcha-vue GitHub
- Gin 框架文档
- Vue 3 官方文档
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |