找回密码
 立即注册
首页 业界区 业界 Go + Vue 接入行为验证码完整指南

Go + Vue 接入行为验证码完整指南

斜素欣 昨天 19:40
前言

在现代 Web 应用中,验证码是防止机器人攻击和恶意请求的重要手段。相比传统的图形验证码,滑动行为验证码具有更好的用户体验。本文将介绍如何使用 go-captcha 库在 Go 后端和 Vue 前端实现滑动验证码功能。
技术栈


  • 后端:Go + Gin 框架
  • 前端:Vue 3 + Element Plus
  • 验证码库:go-captcha(后端)+ go-captcha-vue(前端)
  • 缓存:Redis(用于存储验证码数据)
一、后端实现

1.1 安装依赖
  1. go get github.com/wenlng/go-captcha/v2
  2. go get github.com/wenlng/go-captcha-assets
  3. go get github.com/gin-gonic/gin
  4. go get github.com/redis/go-redis/v9
复制代码
1.2 初始化验证码模块

创建 captcha/init.go 文件:
  1. package captcha
  2. import (
  3.         "errors"
  4. )
  5. var (
  6.         ErrGenData = errors.New("generate data error")
  7. )
  8. const (
  9.         Deviation = 10  // 验证偏差值,允许用户滑动有一定误差
  10. )
  11. func Init() error {
  12.         return initSlide()
  13. }
复制代码
1.3 实现滑动验证码核心逻辑

创建 captcha/slide.go 文件:
  1. package captcha
  2. import (
  3.         images "github.com/wenlng/go-captcha-assets/resources/imagesv2"
  4.         "github.com/wenlng/go-captcha-assets/resources/tiles"
  5.         "github.com/wenlng/go-captcha/v2/base/option"
  6.         "github.com/wenlng/go-captcha/v2/slide"
  7. )
  8. var slideCapt slide.Captcha
  9. // initSlide 初始化滑动验证码
  10. func initSlide() error {
  11.         builder := slide.NewBuilder(
  12.                 slide.WithGenGraphNumber(1),
  13.                 slide.WithEnableGraphVerticalRandom(true),
  14.                 slide.WithImageSize(option.Size{Width: 300, Height: 220}),
  15.         )
  16.         // 加载背景图片资源
  17.         imgs, err := images.GetImages()
  18.         if err != nil {
  19.                 return err
  20.         }
  21.         // 加载滑块图形资源
  22.         graphs, err := tiles.GetTiles()
  23.         if err != nil {
  24.                 return err
  25.         }
  26.         var newGraphs = make([]*slide.GraphImage, 0, len(graphs))
  27.         for i := range graphs {
  28.                 graph := graphs[i]
  29.                 newGraphs = append(newGraphs, &slide.GraphImage{
  30.                         OverlayImage: graph.OverlayImage,
  31.                         MaskImage:    graph.MaskImage,
  32.                         ShadowImage:  graph.ShadowImage,
  33.                 })
  34.         }
  35.         // 设置资源
  36.         builder.SetResources(
  37.                 slide.WithGraphImages(newGraphs),
  38.                 slide.WithBackgrounds(imgs),
  39.         )
  40.         slideCapt = builder.Make()
  41.         return nil
  42. }
  43. // Slide 验证码数据结构
  44. type Slide struct {
  45.         // 滑块初始显示坐标
  46.         SliderX      int
  47.         SliderY      int
  48.         SliderWidth  int
  49.         SliderHeight int
  50.         // 整体大小
  51.         MainWidth  int
  52.         MainHeight int
  53.         // 答案坐标(服务端保存,不返回给前端)
  54.         X int
  55.         Y int
  56.         // 主图与滑块的图片,base64编码
  57.         MainImage   string
  58.         SliderImage string
  59. }
  60. // NewSlide 生成新的滑动验证码
  61. func NewSlide() (*Slide, error) {
  62.         m := &Slide{}
  63.         captData, err := slideCapt.Generate()
  64.         if err != nil {
  65.                 return nil, err
  66.         }
  67.         dotData := captData.GetData()
  68.         if dotData == nil {
  69.                 return nil, ErrGenData
  70.         }
  71.        
  72.         // 答案坐标(正确的滑块位置)
  73.         m.X = dotData.X
  74.         m.Y = dotData.Y
  75.        
  76.         // 滑块初始显示坐标
  77.         m.SliderWidth = dotData.Width
  78.         m.SliderHeight = dotData.Height
  79.         m.SliderX = dotData.DX
  80.         m.SliderY = dotData.DY
  81.        
  82.         // 图片大小
  83.         m.MainWidth = 300
  84.         m.MainHeight = 220
  85.         // 转换为 base64 编码
  86.         m.MainImage, err = captData.GetMasterImage().ToBase64()
  87.         if err != nil {
  88.                 return nil, err
  89.         }
  90.         m.SliderImage, err = captData.GetTileImage().ToBase64()
  91.         if err != nil {
  92.                 return nil, err
  93.         }
  94.         return m, nil
  95. }
  96. // VerifySlide 验证滑动位置是否正确
  97. func VerifySlide(userX, userY, slideX, slideY int) bool {
  98.         return slide.Validate(userX, userY, slideX, slideY, Deviation)
  99. }
复制代码
1.4 定义响应结构体

创建 vo/captcha.go 文件:
  1. package vo
  2. type GetCaptchaRes struct {
  3.         ID           string `json:"id"`           // 验证码唯一标识
  4.         SliderX      int    `json:"sliderX"`      // 滑块初始X坐标
  5.         SliderY      int    `json:"sliderY"`      // 滑块初始Y坐标
  6.         SliderWidth  int    `json:"sliderWidth"`  // 滑块宽度
  7.         SliderHeight int    `json:"sliderHeight"` // 滑块高度
  8.         SliderImage  string `json:"sliderImage"`  // 滑块图片(base64)
  9.         MainWidth    int    `json:"mainWidth"`    // 主图宽度
  10.         MainHeight   int    `json:"mainHeight"`   // 主图高度
  11.         MainImage    string `json:"mainImage"`    // 主图(base64)
  12. }
复制代码
1.5 实现 HTTP 处理器

创建 handler/captcha_handler.go 文件:
  1. package handler
  2. import (
  3.         "encoding/json"
  4.         "time"
  5.         "github.com/gin-gonic/gin"
  6.         "github.com/redis/go-redis/v9"
  7.         "github.com/rs/xid"
  8.        
  9.         "your-project/captcha"
  10.         "your-project/vo"
  11. )
  12. type CaptchaHandler struct {
  13.         redis *redis.Client
  14. }
  15. func NewCaptchaHandler(redis *redis.Client) *CaptchaHandler {
  16.         return &CaptchaHandler{
  17.                 redis: redis,
  18.         }
  19. }
  20. // GetCaptcha 获取验证码
  21. func (h *CaptchaHandler) GetCaptcha(c *gin.Context) {
  22.         // 生成验证码
  23.         m, err := captcha.NewSlide()
  24.         if err != nil {
  25.                 c.JSON(500, gin.H{"error": "Failed to create captcha"})
  26.                 return
  27.         }
  28.         // 序列化验证码数据
  29.         value, err := json.Marshal(m)
  30.         if err != nil {
  31.                 c.JSON(500, gin.H{"error": "Failed to marshal captcha"})
  32.                 return
  33.         }
  34.         // 生成唯一ID并存储到 Redis,有效期1分钟
  35.         uuid := xid.New().String()
  36.         err = h.redis.Set(c, uuid, value, time.Minute).Err()
  37.         if err != nil {
  38.                 c.JSON(500, gin.H{"error": "Failed to store captcha"})
  39.                 return
  40.         }
  41.         // 返回给前端的数据(不包含答案坐标)
  42.         res := &vo.GetCaptchaRes{
  43.                 ID:           uuid,
  44.                 SliderX:      m.SliderX,
  45.                 SliderY:      m.SliderY,
  46.                 SliderWidth:  m.SliderWidth,
  47.                 SliderHeight: m.SliderHeight,
  48.                 SliderImage:  m.SliderImage,
  49.                 MainWidth:    m.MainWidth,
  50.                 MainHeight:   m.MainHeight,
  51.                 MainImage:    m.MainImage,
  52.         }
  53.         c.JSON(200, gin.H{"code": 0, "data": res})
  54. }
  55. // VerifyCaptcha 验证滑动验证码
  56. func (h *CaptchaHandler) VerifyCaptcha(c *gin.Context) {
  57.         var data struct {
  58.                 ID string `json:"id"` // 验证码标识
  59.                 X  int    `json:"x"`  // 用户滑动的X坐标
  60.                 Y  int    `json:"y"`  // 用户滑动的Y坐标
  61.         }
  62.        
  63.         if err := c.ShouldBindJSON(&data); err != nil {
  64.                 c.JSON(400, gin.H{"error": "Invalid arguments"})
  65.                 return
  66.         }
  67.         // 从 Redis 获取验证码数据
  68.         value, err := h.redis.Get(c, data.ID).Result()
  69.         if err != nil {
  70.                 c.JSON(400, gin.H{"error": "验证码已过期或不存在"})
  71.                 return
  72.         }
  73.         // 反序列化验证码数据
  74.         slide := captcha.Slide{}
  75.         err = json.Unmarshal([]byte(value), &slide)
  76.         if err != nil {
  77.                 c.JSON(500, gin.H{"error": "验证码数据错误"})
  78.                 return
  79.         }
  80.         // 验证滑动位置
  81.         if !captcha.VerifySlide(data.X, data.Y, slide.X, slide.Y) {
  82.                 c.JSON(400, gin.H{"error": "验证码验证失败"})
  83.                 return
  84.         }
  85.         // 验证成功后删除 Redis 中的数据(防止重复使用)
  86.         h.redis.Del(c, data.ID)
  87.         c.JSON(200, gin.H{"code": 0, "message": "验证成功"})
  88. }
复制代码
1.6 注册路由

在 main.go 中注册路由:
  1. package main
  2. import (
  3.         "github.com/gin-gonic/gin"
  4.         "github.com/redis/go-redis/v9"
  5.        
  6.         "your-project/captcha"
  7.         "your-project/handler"
  8. )
  9. func main() {
  10.         // 初始化验证码模块
  11.         if err := captcha.Init(); err != nil {
  12.                 panic(err)
  13.         }
  14.         // 初始化 Redis 客户端
  15.         rdb := redis.NewClient(&redis.Options{
  16.                 Addr: "localhost:6379",
  17.         })
  18.         // 创建 Gin 路由
  19.         r := gin.Default()
  20.         // 创建处理器
  21.         captchaHandler := handler.NewCaptchaHandler(rdb)
  22.         // 注册路由
  23.         api := r.Group("/api")
  24.         {
  25.                 api.POST("/captcha", captchaHandler.GetCaptcha)
  26.                 api.POST("/captcha/verify", captchaHandler.VerifyCaptcha)
  27.         }
  28.         r.Run(":8080")
  29. }
复制代码
二、前端实现

2.1 安装依赖
  1. npm install go-captcha-vue
  2. npm install element-plus
复制代码
2.2 创建验证码组件

创建 components/SlideCaptcha.vue 文件:
  1. <template>
  2.   
  3.     <el-button
  4.       :disabled="!canSend"
  5.       @click="handleClick"
  6.       
  7.     >
  8.       {{ btnText }}
  9.     </el-button>
  10.    
  11.     <el-dialog
  12.       v-model="showDialog"
  13.       width="326px"
  14.       :close-on-click-modal="false"
  15.       :show-close="false"
  16.       :append-to-body="true"
  17.     >
  18.       <GoCaptchaSlide
  19.         v-if="captchaData"
  20.         :config="captchaConfig"
  21.         :data="captchaData"
  22.         :events="captchaEvents"
  23.       />
  24.     </el-dialog>
  25.   
  26. </template>
复制代码
2.3 使用验证码组件

在需要使用验证码的页面中:
  1. <template>
  2.   
  3.     <el-form>
  4.       <el-form-item label="手机号">
  5.         <el-input v-model="mobile" placeholder="请输入手机号" />
  6.       </el-form-item>
  7.       
  8.       <el-form-item label="验证码">
  9.         <el-input v-model="code" placeholder="请输入验证码">
  10.           <template #append>
  11.             <SlideCaptcha
  12.               @success="handleCaptchaSuccess"
  13.               btn-text="获取验证码"
  14.             />
  15.           </template>
  16.         </el-input>
  17.       </el-form-item>
  18.       
  19.       <el-form-item>
  20.         <el-button type="primary" @click="handleLogin">登录</el-button>
  21.       </el-form-item>
  22.     </el-form>
  23.   
  24. </template>
复制代码
2.4 HTTP 工具函数

创建 utils/http.js 文件:
  1. import axios from 'axios'
  2. const http = axios.create({
  3.   baseURL: 'http://localhost:8080',
  4.   timeout: 10000
  5. })
  6. // 请求拦截器
  7. http.interceptors.request.use(
  8.   config => {
  9.     // 可以在这里添加 token
  10.     return config
  11.   },
  12.   error => {
  13.     return Promise.reject(error)
  14.   }
  15. )
  16. // 响应拦截器
  17. http.interceptors.response.use(
  18.   response => {
  19.     const res = response.data
  20.     if (res.code !== 0) {
  21.       return Promise.reject(new Error(res.error || 'Error'))
  22.     }
  23.     return res
  24.   },
  25.   error => {
  26.     return Promise.reject(error)
  27.   }
  28. )
  29. export const httpPost = (url, data) => {
  30.   return http.post(url, data)
  31. }
  32. export const httpGet = (url, params) => {
  33.   return http.get(url, { params })
  34. }
复制代码
三、核心流程说明

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 中间件:
  1. import "github.com/gin-contrib/cors"
  2. r.Use(cors.Default())
复制代码
五、总结

本文介绍了如何使用 go-captcha 库在 Go + Vue 项目中实现滑动验证码功能。核心要点:

  • 后端使用 go-captcha 生成验证码图片和答案
  • 使用 Redis 存储验证码数据,保证安全性和时效性
  • 前端使用 go-captcha-vue 组件展示验证码
  • 验证流程简单清晰,用户体验良好
完整代码可以直接应用到新项目中,只需根据实际情况调整路由、响应格式等细节即可。
参考资源


  • go-captcha GitHub
  • go-captcha-vue GitHub
  • Gin 框架文档
  • Vue 3 官方文档

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册