后端: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 文件:

2.3 使用验证码组件
在需要使用验证码的页面中:

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”

更多推荐