文章目录
1. 为什么要使用图形验证码
图形验证码(CAPTCHA)是一种区分用户是计算机还是人的全自动程序,主要用于防止恶意行为和提高网站安全性。以下是图形验证码的主要用途:
1.1 保障投票的公正性
投票系统:在线投票系统中使用图形验证码可以避免机器刷票,确保投票结果的真实性和公平性
1.2 防止自动化攻击
机器人程序:恶意攻击者可以使用机器人程序(bots)自动执行大量操作,如注册账户、登录账户、发布垃圾信息、刷票等。图形验证码通过要求用户执行特定的人类才能完成的任务(如识别扭曲的文字或图像),有效阻止机器人程序进行自动化攻击
1.3 防止垃圾信息和恶意行为
垃圾邮件和评论:在没有验证码的情况下,恶意用户可以使用机器人程序在论坛、评论区、联系表单等地方发布大量垃圾信息。图形验证码通过验证用户是人类,有效防止垃圾信息的发布,保护平台的内容质量和用户体验
1.4 保护用户账户安全
暴力破解和账户盗用:恶意用户可能尝试通过暴力破解密码或利用漏洞进行账户盗用。图形验证码通过增加额外的验证步骤,使攻击者更难自动化地进行这些恶意操作,从而保护用户的个人信息和财产安全
1.5 防止刷单、刷票等行为
刷票和刷单:在电子商务平台、票务网站等场景中,恶意用户可能使用机器人程序进行刷单、刷票等操作,影响正常用户的体验和平台的公平性。图形验证码通过增加操作难度,防止机器批量操作,保护系统的稳定性和公平性
1.6 减轻服务器负担
自动化请求:机器人程序可以发送大量请求给服务器,导致服务器负载过高,影响正常服务。图形验证码通过区分人类用户和机器人,减少自动化请求,减轻服务器的负担,提高系统的稳定性和响应速度
2. 图形验证码的实现方式
- 文本验证码:用户需要识别并输入扭曲的字符或数字
- 拖动验证码:用户需要根据提示拖动某些元素到正确的位置
- 语音验证码:用户听取语音并输入听到的字符,主要用于视障用户
- 行为验证码:用户需要完成某些操作,如点击按钮或移动鼠标
- 图案选择(文字选择)验证码:用户从提供的图片中选择符合要求的图案(文字)
图形验证码和 Token 通常一起使用,以增强网站或应用的安全性,防止自动化攻击。以下是它们如何共同工作的简化流程:
-
生成图形验证码:
- 当用户请求一个包含图形验证码的页面时,服务器端会生成一个唯一的图形验证码图片和对应的正确答案(通常是随机字符串或数字)
- 这个正确答案会被临时存储在服务器端,与会话或者特定用户的上下文关联起来
-
创建Token:
- 与此同时,服务器还会生成一个安全Token,这个Token是独一无二且有时效性的,用来标识这次验证码验证过程
- Token可以是一个随机字符串,也可以是加密后的信息,比如包含了时间戳、用户ID等信息的数据包
-
发送到客户端:
- 图形验证码和Token都会被发送给客户端(浏览器或移动应用)。图形验证码以图像形式展示给用户,而Token则可能通过隐藏字段、HTTP头部或者URL参数等方式传递
-
用户提交表单:
- 用户根据看到的图形验证码输入他们认为正确的答案,并连同表单中的其他数据以及之前接收到的Token一起提交回服务器
-
服务器验证:
- 服务器接收到来自用户的请求后,首先检查Token的有效性和时效性,确保它没有被篡改并且还在有效期内
- 接着,服务器将用户提供的验证码答案与自己存储的答案进行比对,如果匹配成功,则表示验证通过;否则,拒绝该请求
-
处理结果:
- 如果验证码验证通过,服务器将继续处理用户的原始请求(例如登录、注册、评论发表等)
- 如果验证失败,服务器将返回错误信息,并提示用户重新尝试
-
安全性考虑:
- Token应该设置有效期,过期后就不再有效,这有助于防止重放攻击
- 使用HTTPS协议来传输所有数据,包括图形验证码和Token,以保护数据在传输过程中不被窃听或篡改
- 对于Token,最好采用一次性的原则,即每个Token只能使用一次,一旦使用过后即失效
这种机制结合了图形验证码的人类可读性和Token的安全性,共同为在线服务提供了一层额外的防护
3. tianai-captcha
3.1 简介
基于 JAVA 实现的行为验证码
3.2 官网&在线文档
Gitee 官网:dromara/tianai-captcha
https://gitee.com/dromara/tianai-captcha
在线文档:TIANAI-CAPTCHA(不得不吐槽一下,这个文档是我用过的所有开源项目中写得最差的。。。)
http://doc.captcha.tianai.cloud/
3.3 在线体验
在线体验地址:行为验证码(TAC)在线体验
https://captcha.tianai.cloud/
3.4 支持的图形验证码类型
虽然 tianai-captcha 的在线体验有很多种图形验证码,但是开源版只支持以下四种:
- 滑块验证码
- 旋转验证码
- 滑动还原验证码
- 文字点选验证码
4. 在SpringBoot项目中使用tianai-captcha
4.1 引入依赖
<dependency>
<groupId>cloud.tianai.captcha</groupId>
<artifactId>tianai-captcha-springboot-starter</artifactId>
<version>1.5.1</version>
</dependency>
4.2 编写配置文件
如果项目中使用到了 Redis,滑块验证码会自动把验证码数据存到 Redis 中
application.yaml
# 滑块验证码配置, 详细请看 cloud.tianai.captcha.autoconfiguration.ImageCaptchaProperties 类
captcha:
# 如果项目中使用到了redis,滑块验证码会自动把验证码数据存到redis中, 这里配置redis的key的前缀,默认是captcha:slider
prefix: captcha
# 验证码过期时间,默认是 2 分钟,单位毫秒, 可以根据自身业务进行调整
expire:
# 默认缓存时间 2分钟
default: 10000
# 针对 点选验证码 过期时间设置为 2分钟, 因为点选验证码验证比较慢,把过期时间调整大一些
WORD_IMAGE_CLICK: 20000
# 使用加载系统自带的资源, 系统的默认资源包含滑动验证码模板和旋转验证码模板
init-default-resource: true
# 缓存控制, 默认为false不开启
local-cache-enabled: true
# 验证码会提前缓存一些生成好的验证数据, 默认是20
local-cache-size: 20
# 缓存拉取失败后等待时间 默认是 5秒钟
local-cache-wait-time: 5000
# 缓存检查间隔 默认是2秒钟
local-cache-period: 2000
# 配置字体库,文字点选验证码的字体库,可以配置多个
font-path:
- classpath:META-INF/fonts/SIMSUN.TTC
secondary:
# 二次验证, 默认false 不开启
enabled: false
# 二次验证过期时间, 默认 2 分钟
expire: 120000
# 二次验证缓存key前缀,默认是 captcha:secondary
keyPrefix: "captcha:secondary"
4.3 编写验证码校验接口
4.3.1 CaptchaController.java
import cloud.tianai.captcha.application.ImageCaptchaApplication;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import cn.edu.scau.dto.CaptchaTrackDto;
import cn.edu.scau.dto.GenerateCaptchaDto;
import cn.edu.scau.enumeration.CaptchaType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/captcha")
@Slf4j
public class CaptchaController {
private final ImageCaptchaApplication imageCaptchaApplication;
public CaptchaController(ImageCaptchaApplication imageCaptchaApplication) {
this.imageCaptchaApplication = imageCaptchaApplication;
}
@PostMapping("/generate")
public CaptchaResponse<?> generate(@RequestBody GenerateCaptchaDto generateCaptchaDto) {
String captchaType = CaptchaType.getCaptchaTypeByTypeCode(generateCaptchaDto.getType());
// 根据验证码类型生成对应的验证码
CaptchaResponse<ImageCaptchaVO> captchaResponse = imageCaptchaApplication.generateCaptcha(captchaType);
log.info("验证码唯一标识: {}", captchaResponse.getId());
return captchaResponse;
}
@PostMapping("/check")
public ApiResponse<?> check(@RequestBody CaptchaTrackDto captchaTrackDto) {
log.info("请求参数: {}", captchaTrackDto);
ImageCaptchaTrack imageCaptchaTrack = new ImageCaptchaTrack();
BeanUtils.copyProperties(captchaTrackDto.getData(), imageCaptchaTrack);
return imageCaptchaApplication.matching(captchaTrackDto.getId(), imageCaptchaTrack);
}
}
4.3.2 CaptchaTrackDto.java
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
public class CaptchaTrackDto {
private String id;
private ImageCaptchaTrack data;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public ImageCaptchaTrack getData() {
return data;
}
public void setData(ImageCaptchaTrack data) {
this.data = data;
}
@Override
public String toString() {
return "CaptchaTrackDto{" +
"id='" + id + '\'' +
", data=" + data +
'}';
}
}
4.3.3 GenerateCaptchaDto.java
public class GenerateCaptchaDto {
private Integer type;
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
@Override
public String toString() {
return "GenerateCaptchaDto{" +
"type=" + type +
'}';
}
}
4.4 添加背景图片资源
背景图片存放在 resource/background
目录下,而且自定义背景图片的尺寸必须为 600 * 360
自定义一个配置类,用于读取图片背景图片资源,并将配置类交给 Spring 管理
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Configuration
public class CaptchaResourceConfiguration {
private final ResourceStore resourceStore;
public CaptchaResourceConfiguration(ResourceStore resourceStore) {
this.resourceStore = resourceStore;
}
@PostConstruct
public void init() throws Exception {
// 背景图片存放在 resource/background 目录下
String backgroundDirectory = "background";
List<String> backgroundList = new ArrayList<>();
String pattern = "classpath*:" + backgroundDirectory + "/**";
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
org.springframework.core.io.Resource[] resources = resourcePatternResolver.getResources(pattern);
for (org.springframework.core.io.Resource resource : resources) {
String path = resource.getURL().getPath();
if (!path.endsWith("/")) {
String background = path.substring(path.lastIndexOf(backgroundDirectory));
log.info("Found background image: {}", background);
backgroundList.add(background);
}
}
backgroundList.forEach((String background) -> {
// 添加自定义背景图片, Resource 接收三个参数
// 第一个参数为资源类型, 支持 classpath file url 三种类型
// 第二个参数为资源路径
// 第三个参数为资源的标签, 默认为 default
Resource resource = new Resource("classpath", background);
resourceStore.addResource(CaptchaTypeConstant.CONCAT, resource);
resourceStore.addResource(CaptchaTypeConstant.SLIDER, resource);
resourceStore.addResource(CaptchaTypeConstant.ROTATE, resource);
resourceStore.addResource(CaptchaTypeConstant.WORD_IMAGE_CLICK, resource);
// 使用日志记录每个添加的资源
log.info("Added background resource: {}", background);
});
// 打印最终的背景列表
backgroundList.forEach(background -> log.info("Background image: {}", background));
}
}
5. 在Vue3项目中使用tianai-captcha
5.1 安装
可以通过源码打包,也可以直接使用作者打包好的文件
5.1.1 通过源码打包
前端源码仓库:天爱有情/tianai-captcha-web-sdk
https://gitee.com/tianai/tianai-captcha-web-sdk
5.1.2 使用作者打包好的文件
作者打包好的文件:tianai-captcha-web-sdk-v1.2
5.1.3 将tac目录放到public目录下
将打包后的 tac 目录放到 public 目录下
5.2 引入初始化函数
load.js的下载地址:load.js ,可以将 load.js下载到本地
https://minio.tianai.cloud/public/static/captcha/js/load.min.js
下载完 load.js 文件后,将 load.js 放在与 main.js 同级的目录下,并在 public/index.html
中引入该文件
<script src="/src/load.min.js"></script>
5.3 创建一个div块用于渲染验证码
创建一个 div 块用于渲染验证码(整个 div 块的尺寸为 319.2px * 318 px)
<div id="captcha-box"></div>
5.4 在需要调用验证码的时候执行加载验证码相关的方法
加载验证码时可能需要用到代理,可以参考我的另一篇文章:Vue3项目(由Vite构建)中通过代理解决跨域问题
如果是在生产环境(也就是打包后),代理将不再生效,需要使用 Nginx 进行反向代理
<template>
<div id="captcha-box"></div>
</template>
<script setup>
import {onMounted} from 'vue'
onMounted(() => {
// 第一个参数是tac文件的目录地址, 目录里包含tac的js和css等文件
// 第一个参数是tac验证码的相关配置
// 第一个参数是tac窗口的一些样式
window.initTAC('./tac', config, style).then((tac) => {
tac.init() // 调用init显示验证码
}).catch(error => {
console.log('initTAC fail:', error)
})
})
const baseUrl = '/api'
const getTypeCode = (captchaType) => {
if (!captchaType) {
return Math.floor(Math.random() * 4) + 1
}
if (captchaType.toLowerCase() === 'slider') {
return 1
}
if (captchaType.toLowerCase() === 'rotate') {
return 2
}
if (captchaType.toLowerCase() === 'concat') {
return 3
}
if (captchaType.toLowerCase() === 'word_image_click') {
return 4
}
return Math.floor(Math.random() * 4) + 1
}
// 验证码的一些配置和验证的回调
const config = {
// 生成接口, 必选项, 必须配置
requestCaptchaDataUrl: `${baseUrl}/captcha/generate`,
// 验证接口, 必选项, 必须配置
validCaptchaUrl: `${baseUrl}/captcha/check`,
// 验证码绑定的 div 块, 必选项, 必须配置
bindEl: '#captcha-box',
// 验证码的类型
data: {type: getTypeCode()},
// 验证成功回调函数, 必选项, 必须配置
validSuccess: (response, configuration, tac) => {
console.log('验证成功, 后端返回的数据为', response)
// 销毁验证码服务
tac.destroyWindow()
// 验证成功后调用后端接口
},
// 验证失败的回调函数, 可忽略, 如果不自定义 validFail 方法时,会使用默认的验证失败的函数
validFail: (response, configuration, tac) => {
// 验证失败后重新拉取验证码
config.data.type = getTypeCode()
tac.reloadCaptcha()
},
// 刷新按钮回调事件
btnRefreshFun: (el, tac) => {
config.data.type = getTypeCode()
tac.reloadCaptcha()
},
// 关闭按钮回调事件
btnCloseFun: (el, tac) => {
tac.destroyWindow()
}
}
const style = {
// 按钮样式
btnUrl: 'https://minio.tianai.cloud/public/captcha-btn/btn3.png',
// 背景样式
// bgUrl: 'https://minio.tianai.cloud/public/captcha-btn/btn3-bg.jpg',
bgUrl: 'src/background/yellow.png',
// logo地址
logoUrl: 'https://minio.tianai.cloud/public/static/captcha/images/logo.png',
// 滑动边框样式
moveTrackMaskBgColor: '#f7b645',
moveTrackMaskBorderColor: '#ef9c0d'
}
</script>
<style>
</style>
代理的参考配置(在 vite.config.js 文件中编写)
import {fileURLToPath, URL} from 'node:url'
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools()
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
server: {
proxy: {
'/api': {
target: 'http://localhost:5202',
changeOrigin: true,
rewrite: (path) => path.replace('/api', '')
}
}
}
})
5.5 自定义滑块按钮和背景的样式
const style = {
// 按钮样式
btnUrl: "https://minio.tianai.cloud/public/captcha-btn/btn3.png",
// 背景样式
// bgUrl: "https://minio.tianai.cloud/public/captcha-btn/btn3-bg.jpg",
bgUrl: "https://img1.baidu.com/it/u=1669573393,120316417&fm=253&fmt=auto&app=120&f=JPEG",
// logo地址
logoUrl: "https://minio.tianai.cloud/public/static/captcha/images/logo.png",
// 滑动边框样式
moveTrackMaskBgColor: "#f7b645",
moveTrackMaskBorderColor: "#ef9c0d"
}
6. 实现效果
6.1 滑块验证码
6.2 旋转验证码
6.3 滑动还原验证码
6.4 文字点选验证码
7. 使用tianai-captcha时可能遇到的问题
7.1 没有添加背景图片资源
7.1.1 问题呈现
当没有添加背景图片资源时,程序启动会报以下错误
2025-01-17 22:45:21.810 37840 [slider-captcha-queue-1] ERROR c.t.c.g.i.CacheImageCaptchaGenerator - 缓存队列扫描时出错, ex
java.lang.IllegalStateException: 随机获取资源错误,store中资源为空, type:SLIDER,tag:null
7.1.2 解决方法
添加尺寸为 600 * 360 的背景图片资源,参考本文的 添加背景图片资源 章节
7.2 没有添加模板资源
7.2.1 问题呈现
2025-01-17 22:48:01.705 38028 [slider-captcha-queue-1] ERROR c.t.c.g.i.CacheImageCaptchaGenerator - 缓存队列扫描时出错, ex
java.lang.IllegalStateException: 随机获取模板错误,store中模板为空, type:SLIDER,tag:null
7.2.2 解决方法
将 application.yaml 文件中的 init-default-resource
属性改为 true,也就是使用系统自带的模版资源(滑动验证码模板和旋转验证码模板)
7.3 背景图片资源的比例和大小都不符合要求
7.3.1 问题呈现
2025-01-18 00:35:53.938 17764 [http-nio-5202-exec-5] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke “java.awt.image.BufferedImage.getWidth()” because “background” is null] with root cause
java.lang.NullPointerException: Cannot invoke “java.awt.image.BufferedImage.getWidth()” because “background” is null
7.3.2 解决方法
使用尺寸为 600 * 360 的背景图片
7.4 背景图片资源的比例符合要求但是大小不符合要求
7.4.1 问题呈现
当背景图片的比例是 600 : 360(也就是 5 : 3)时,有可能会出现以下问题
2025-01-18 11:28:42.599 17420 [slider-captcha-queue-1] ERROR c.t.c.g.i.CacheImageCaptchaGenerator - 缓存队列扫描时出错, ex
java.lang.ArrayIndexOutOfBoundsException: Coordinate out of bounds!
at java.desktop/sun.awt.image.ByteInterleavedRaster.getDataElements(ByteInterleavedRaster.java:314)
at java.desktop/java.awt.image.BufferedImage.getRGB(BufferedImage.java:918)
at cloud.tianai.captcha.generator.common.util.CaptchaImageUtils.cutImage(CaptchaImageUtils.java:165)
at cloud.tianai.captcha.generator.impl.StandardRotateImageCaptchaGenerator.doGenerateCaptchaImage(StandardRotateImageCaptchaGenerator.java:70)
7.4.2 解决方法
使用尺寸为 600 * 360 的背景图片
8. 注意事项
8.1 自定义背景图片的尺寸要求
自定义背景图片的尺寸必须为 600 * 360
8.2 自定义模版资源的尺寸要求
如果没有特殊需求,使用系统自带的模版资源就可以了
- 滑块验证码
- 滑块大小为 110*110,文件格式为 png
- 凹槽大小为 110*110,文件格式为 png
- 旋转验证码
- 滑块大小为 200*200,文件格式为 png
- 凹槽大小为 200*200,文件格式为 png
8.3 前端访问验证接口时携带的参数
前端在访问验证接口时,发送的是 POST 请求,而且会携带一系列的 JSON 数据(包含验证码在后台中对应的唯一标识和校验验证码是否正确所需要的数据)
可以在浏览器控制台的网络一栏中查看 JSON 数据的具体内容,也可以在后端查看 JSON 数据的具体内容,最后可以根据 JSON 数据的具体内容封装成一个实体类(实体类可参考本文的 CaptchaTrackDto)
后台查看 JSON 数据具体内容的方法:在形参上添加一个 Map,并在 Map 前加上 @RequestBody 注解,用于封装 JSON 数据
public ApiResponse<?> check(@RequestBody Map<String, Object> data){
}
9. 前端源代码优化
9.1 前端如何指定生成图片验证码的类型
在访问后端的生成验证码接口时,会向后端的接口发送一个 POST 请求,如果想在 POST 请求中添加一些自定义的参数(例如指定生成图片验证码的类型),需要修改源代码
9.1.1 下载前端的源代码
前端源码仓库:天爱有情/tianai-captcha-web-sdk
https://gitee.com/tianai/tianai-captcha-web-sdk
9.1.2 修改config.js文件
args 对应的就是 config 对象
需要添加两行代码
this.data = args.data;
requestParam.data = this.data;
9.1.3 重新打包
将源代码重新打包,然后将生成的 tac 目录放到 public 目录下(可以点击 package.json
文件前面的三角形进行打包)
9.1.4 添加自定义参数
const getTypeCode = (captchaType) => {
if (!captchaType) {
return Math.floor(Math.random() * 4) + 1
}
if (captchaType.toLowerCase() === 'slider') {
return 1
}
if (captchaType.toLowerCase() === 'rotate') {
return 2
}
if (captchaType.toLowerCase() === 'concat') {
return 3
}
if (captchaType.toLowerCase() === 'word_image_click') {
return 4
}
return Math.floor(Math.random() * 4) + 1
}
9.2 优化鼠标拖拽的监听事件
9.2.1 问题呈现
如果直接使用前端源代码打包后的 tac 目录,控制台中会报以下警告
tac.min.js:1 [Violation]Added non-passive event listener to a scroll-blocking ‘touchstart’ event. Consider marking event handler as ‘passive’ to make the page more responsive. See https://www.chromestatus.com/feature/5745543795965952
on | @ | tac.min.js:1 | |
---|---|---|---|
touchstart | @ | tac.min.js:1 | |
init | @ | tac.min.js:1 | |
(匿名) | @ | tac.min.js:1 |
9.2.2 问题产生的原因
这条警告信息来自Chrome浏览器的开发者工具,它是在告诉你,在tac.min.js
文件的第1行,有一个事件监听器被添加到了一个可能会阻塞页面滚动的touchstart
事件上,并且这个事件监听器没有标记为passive
以下是详细解释:
[Violation]
: 这是一个违反了浏览器性能最佳实践的行为,可能会影响页面的性能Added non-passive event listener to a scroll-blocking 'touchstart' event.
: 这意味着在touchstart
事件上添加了一个非被动(non-passive)的事件监听器。touchstart
事件通常与滚动页面相关,如果事件监听器内部有代码可能会调用event.preventDefault()
来阻止默认的滚动行为,这可能会导致滚动事件的处理变得不那么流畅Consider marking event handler as 'passive' to make the page more responsive.
: 浏览器建议你将事件监听器标记为passive
。当事件监听器被标记为passive
时,它向浏览器表明事件处理程序不会调用preventDefault()
。这样,浏览器就可以在事件处理期间继续执行默认行为(如滚动),而不必担心会被阻止,从而提高页面的响应性
为什么会有这样的警告:
- 性能优化:浏览器可以优化其内部事件处理机制,如果它知道事件监听器不会阻止默认行为,那么浏览器可以更高效地处理事件
- 避免滚动卡顿:在移动设备上,非被动的事件监听器可能会导致滚动性能问题,因为浏览器需要等待事件监听器执行完毕后才能确定是否继续执行默认滚动行为
9.2.3 解决方法
要解决这个问题,我们需要修改前端的源代码,将事件监听器选项对象中的passive
属性设置为true
找到前端源代码中的 common.js
文件的第 385 行,添加 passive 属性,并将 passive 属性设置为 true
this.getTarget().addEventListener(eventType, fun, {passive: true});
最后重新将前端源代码打包,替换掉 public 目录中的 tac 目录,警告就会消失了
10. 自定义滑动按钮
滑动按钮图片的大小要求为 140 * 100
除了使用图片 URL 之外,也可以使用本地图片
引入本地图片时需要注意不能使用 @ 符号,因为设置滑动按钮是通过 js 代码手动添加 css 属性来实现的(在源代码中的 slider.js 的 loadStyle 方法中可以找到),因为源代码并不是 Vue 工程,所以不能使用 @ 符号来引入图片资源
正确引入图片的方式:btnUrl: 'src/captcha/button.png'
btnUrl: 'src/captcha/button.png'
拓展:之所以在 Vue 工程中能使用 @ 符号,是因为配置文件中为 @ 符号设置了别名(vite.config.js)
11. 完整的示例代码
后端示例代码已上传到 Gitee 上:captcha-backend
前端示例代码已上传到 Gitee 上:captcha-frontend