node + Redis + svg-captcha 实现验证码
目录
前提说明
Redis链接与封装
svg-captcha使用步骤
封装中间件验证
前端接收
扩展【svg API】
svgCaptcha.create(options)
svgCaptcha.createMathExpr(options)
svgCaptcha.loadFont(url)
svgCaptcha.options
svgCaptcha.randomText([size|options])
svgCaptcha(text, options)
uuid
如何在 Node JS 中生成唯一 ID
前提说明
本文章采用了svg-captcha方法去实现验证码功能,后端是 koa 前端是vue。
实现方式:
- 通过后端生成验证码与uuid, 把验证码与uuid绑定的值存到Redis中;
- 再把验证码发到前端,前端去接收并把验证码在界面中显示出来;
- 前端输入验证码后提交到后端,后端和Redis中保存的验证码做对比,看是否一致。
Redis链接与封装
Redis 教程 看作者 Redis 文章 前端全栈 === 快速入 门 Redis-CSDN博客
这里我们封装成单例模式方便全局使用
const { createClient } = require("redis");
/**
* Redis客户端工具类
* 提供Redis连接管理和常用操作方法
*/
class RedisClient {
static instance = null;
client = null;
/**
* 私有构造函数,防止直接实例化
*/
constructor() {
// 单例模式 防止重复创建
if (RedisClient.instance) {
return RedisClient.instance;
}
RedisClient.instance = this;
}
/**
* 获取Redis客户端实例(单例模式)
* @returns {RedisClient}
*/
static getInstance() {
if (!RedisClient.instance) {
RedisClient.instance = new RedisClient();
}
return RedisClient.instance;
}
/**
* 初始化Redis连接
* @param {Object} config Redis配置项
* @returns {Promise<void>}
*/
async connect(config = {}) {
try {
if (this.client) return;
this.client = createClient({
url: config.url || "redis://localhost:6379",
password: config.password || "",
// Redis中的database用于数据隔离,默认使用0号数据库
// 可以通过SELECT命令切换,范围0-15,不同库数据相互独立
database: config.database || 0,
...config,
});
this.client.on("error", (err) => {
console.error("Redis连接错误:", err);
});
this.client.on("connect", () => {
console.log("Redis连接成功");
});
await this.client.connect();
} catch (error) {
console.error("Redis初始化失败:", error);
throw error;
}
}
/**
* 设置键值对
* @param {string} key 键
* @param {any} value 值
* @param {number} ttl 过期时间(秒)
* @returns {Promise<void>}
*/
async set(key, value, ttl = null) {
try {
if (!this.client) await this.connect();
const stringValue =
typeof value === "string" ? value : JSON.stringify(value);
if (ttl) {
await this.client.setEx(key, ttl, stringValue);
} else {
await this.client.set(key, stringValue);
}
} catch (error) {
console.error("Redis设置键值对失败:", error);
throw error;
}
}
/**
* 获取键值
* @param {string} key 键
* @returns {Promise<any>}
*/
async get(key) {
try {
if (!this.client) await this.connect();
const value = await this.client.get(key);
if (!value) return null;
try {
return JSON.parse(value);
} catch {
return value; // 如果不是JSON格式,返回原始值
}
} catch (error) {
console.error("Redis获取键值失败:", error);
throw error;
}
}
/**
* 删除键
* @param {string} key 键
* @returns {Promise<boolean>}
*/
async del(key) {
try {
if (!this.client) await this.connect();
return await this.client.del(key);
} catch (error) {
console.error("Redis删除键失败:", error);
throw error;
}
}
/**
* 设置键的过期时间
* @param {string} key 键
* @param {number} seconds 过期时间(秒)
* @returns {Promise<boolean>}
*/
async expire(key, seconds) {
try {
if (!this.client) await this.connect();
return await this.client.expire(key, seconds);
} catch (error) {
console.error("Redis设置过期时间失败:", error);
throw error;
}
}
/**
* 检查键是否存在
* @param {string} key 键
* @returns {Promise<boolean>}
*/
async exists(key) {
try {
if (!this.client) await this.connect();
return await this.client.exists(key);
} catch (error) {
console.error("Redis检查键存在失败:", error);
throw error;
}
}
/**
* 关闭Redis连接
* @returns {Promise<void>}
*/
async quit() {
try {
if (this.client) {
await this.client.quit();
this.client = null;
}
} catch (error) {
console.error("Redis关闭连接失败:", error);
throw error;
}
}
}
// 导出Redis客户端单例
module.exports = RedisClient.getInstance();
svg-captcha使用步骤
- 安装svg-captcha
npm i svg-captcha -S
- 后端生成验证码
1. 生成验证码
2. 将验证码存入redis 生成uuid与之绑定
3. 保存到 redis 设置过期时间10分钟
4. 返回结果 绑定的key 与生成的结果
const { v4: uuidv4 } = require("uuid");
const Redis = require("../utils/redis");
async getCaptcha(ctx, next) {
// 1. 生成验证码
let captcha = svgCaptcha.create({
size: 4, // 验证码长度
ignoreChars: "0o1i", // 验证码字符中排除 0o1i
noise: 2, // 干扰线条数
color: true, // 验证码字体颜色
background: "#fff", // 验证码图片背景颜色
width: 150, // 验证码图片宽度
height: 38, // 验证码图片高度
});
// 2. 将验证码存入redis
const captchaKey = `captcha:${uuidv4()}`;
// 3. 保存到 redis 设置过期时间10分钟
await Redis.set(captchaKey, captcha.text.toLowerCase(), 60 * 10);
// 4. 返回结果
ctx.body = {
code: 0,
message: "生成验证码成功",
result: {
captcha: captcha.data,
captchaKey,
},
};
}
封装中间件验证
1,验证签到参数
2.验证是否在有效期内 没有则为失效
3 验证码是否正确
// 验证验证码
const verifyCaptcha = async (ctx, next) => {
const { captcha, captchaKey } = ctx.request.body;
// 验证码是否存在
if (!captcha || !captchaKey) {
ctx.app.emit("error", captchaIsRequired, ctx);
return;
}
// 验证码是否存在
const redisCaptcha = await Redis.get(captchaKey);
if (!redisCaptcha) {
ctx.app.emit("error", redisCaptchaNotFount, ctx);
return;
}
// 验证码是否正确
if (captcha.toLowerCase() !== redisCaptcha.toLowerCase()) {
ctx.app.emit("error", invalidCaptcha, ctx);
return;
}
await next();
};
注意 ⚠️ 使用完毕后要清除Redis避免重复使用
//删除验证码 防止重复使用
await Redis.del(captchaKey);
前端接收
- 获取验证码
- 展示验证码
- 给后端携带captchaKey&& 用户输入的验证码
- 失效记得自动刷新验证码
<!-- html部分 -->
<el-input
v-model="formModel.captcha"
:prefix-icon="Picture"
placeholder="请输入验证码"
class="captcha-input"
>
</el-input>
<!-- script部分 -->
// 页面加载时获取验证码
onMounted(() => {
refreshCaptcha();
});
//获取
const refreshCaptcha = async () => {
try {
const { result } = await getCaptcha();
captchaImg.value = result.captcha;
captchaKey.value = result.captchaKey;
} catch (error) {
console.error("获取验证码失败:", error);
}
};
//注册
const register = async () => {
try {
// 注册成功之前先进行校验,校验成功 ==》请求 校验失败 ==》 自动提示
await form.value.validate();
console.log("开始注册请求");
await userRegisterService({
...formModel.value,
captchaKey: captchaKey.value,
});
ElMessage.success("注册成功");
refreshCaptcha();
isRegister.value = false;
} catch (error) {
console.log(error);
// 10110 验证码过期
if (error.code == "10110") {
refreshCaptcha();
}
}
};
扩展【svg API】
svgCaptcha.create(options)
如果未传递任何选项,则将获得一个随机字符串,该字符串包含四个字符和相应的svg。
size
:4 //随机字符串的大小ignoreChars
:’0o1i’//过滤掉一些字符,例如0o1inoise
:1 //噪声线数color
:true //字符将具有不同的颜色而不是灰色,如果设置了背景选项,则为truebackground
:’#cc9966’// SVG图片的背景颜色
此函数返回具有以下属性的对象:
data
:字符串// svg路径数据text
:字符串//验证码文本
svgCaptcha.createMathExpr(options)
与创建api类似,您可以使用上述选项以及3个其他选项:
mathMin
:1 //数学表达式可以为的最小值mathMax
:9 //数学表达式可以为的最大值
mathOperator:+ //要使用+,-或的运算符+-(对于random +或-)
此函数返回具有以下属性的对象:
data
:string //数学表达式的svgtext
:string //数学表达式的答案
svgCaptcha.loadFont(url)
加载您自己的字体并覆盖默认字体。
ps: 这个api真心没弄懂
url
:string // 字体的路径此api是opentype.js的loadFont api的包装。
您可能需要围绕各种选项进行实验,以使自己的字体可访问。
svgCaptcha.options
访问全局设置对象。它用于create和createMathExpr api作为默认选项。
除了大小,噪点,颜色和背景之外,您还可以设置以下属性:
width
:数字//验证码的宽度height
:数字//验证码的高度fontSize
:数字//验证码文字大小charPreset
:字符串//随机字符预设
svgCaptcha.randomText([size|options])
返回一个随机字符串。
svgCaptcha(text, options)
根据提供的文本返回svg验证码。
在1.1.0之前的版本中,您必须调用这两个函数,
现在您可以调用create()保存一些按键;)。
uuid
在 Microsoft 版本中也称为全局唯一标识符,通用唯一标识符是用于创建唯一 ID 的 128 位
标签。这些 ID
通常用于识别数据库中的实体,以唯一地记录个人身份。在这种情况下使用时,这些 ID
通常被称为数据库键。
如何在 Node JS 中生成唯一 ID
世界上任何人生成两个相似的 UUID
的可能性几乎为零。这就是为什么它们被称为普遍独特的原因。
互联网工程任务组提出了一份名为 RFC 的出版物,该出版物定义了一组标准,用于创建生成这些 UUID
以保持其在全球范围内的唯一性的算法。
有几个 Node js 模块用于在 Node JS 中创建 UUID。但是,最常用的包是 UUID 包。
我们可以使用下面的 npm 包安装程序命令安装 UUID 包。
npm install uuid
这个包可以与 Node js 8、10、12 和 14
一起使用,但它也是跨平台的,这意味着我们可以使用 ES6 syntax
或 CommonJS
来安全地生成加密的大量随机数。
现在,由于 UUID 包支持 RFC4122 版本 1、3、4 和 5 UUID
,这意味着我们可以根据需要生成不同类型的 UUID。
版本 1 是基于时间的 UUID,它使用随机数、日期时间
值和设备的 MAC 地址的组合来生成通用唯一 ID。
第 4 版 UUID 可能是最简单的 ID,因为它们是从字符 a 到 z 和 0 到 9
生成的。
另一方面,版本 3 和 5 UUID 使用预先存在的命名空间日期和名称数据来生成随机的字母数字 UUID。
使用 CommonJS 语法,我们可以生成 UUID 的版本 4 和版本 1,如下所示。
const {v4: uuidv4} = require('uuid');
const {v1: uuidv1} = require('uuid');
console.log(uuidv4());
console.log(uuidv1());
输出:
ddd3b16f-2b6c-4fef-9f21-aaa52d1f983c
69b42bc0-7a15-11ec-a689-5191cd2179b0
或者,我们可以使用 ES6 模块语法而不是 CommonJS,并且仍然生成任何版本的 UUID,如下所示。
请记住在 package.json
中设置 type
: module
以使用此语法。
import {v1 as uuidv1, v4 as uuidv4} from 'uuid';
console.log(uuidv4());
console.log(uuidv1());
输出:
9cae2569-010e-4a69-867c-65601234e2a5
0b2567c0-7a17-11ec-9b81-19257147bda6
我们还可以使用从 Node js 的 4.17.0 版
添加的加密模块生成 UUID。
在 Node js 的早期版本中,除了使用诸如 UUID 包之类的外部包之外,没有办法在本地生成 UUID。
使用这个模块,我们可以使用 randomUUID options
方法生成多达 128 个 RFC4122 版本 4 UUIDs
,只需在我们的程序顶部简单地要求 crypto
模块。
默认情况下,Node js 将缓存足够的数据以使用此方法生成多达 128 个版本 4 UUID
。但是,我们也可以通过将禁用 EntropyCache
的对象设置为 true 来生成不缓存的 UUID。
const crypto = require('crypto');
console.log(crypto.randomUUID());
输出:
7a2e09e9-4059-438d-89cf-c2badb76ed3c