Files
jurong_Intermediate/routes/captcha.js
2025-09-25 11:01:00 +08:00

158 lines
4.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const crypto = require('crypto');
const router = express.Router();
/**
* 生成随机验证码字符串
* @param {number} length 验证码长度
* @returns {string} 验证码字符串
*/
function generateCaptchaText(length = 4) {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
/**
* 生成SVG验证码图片
* @param {string} text 验证码文本
* @returns {string} SVG字符串
*/
function generateCaptchaSVG(text) {
const width = 120;
const height = 40;
const fontSize = 18;
// 生成随机颜色
const getRandomColor = () => {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8'];
return colors[Math.floor(Math.random() * colors.length)];
};
// 生成干扰线
const generateNoise = () => {
let noise = '';
for (let i = 0; i < 3; i++) {
const x1 = Math.random() * width;
const y1 = Math.random() * height;
const x2 = Math.random() * width;
const y2 = Math.random() * height;
noise += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${getRandomColor()}" stroke-width="1" opacity="0.3"/>`;
}
return noise;
};
// 生成干扰点
const generateDots = () => {
let dots = '';
for (let i = 0; i < 20; i++) {
const x = Math.random() * width;
const y = Math.random() * height;
const r = Math.random() * 2 + 1;
dots += `<circle cx="${x}" cy="${y}" r="${r}" fill="${getRandomColor()}" opacity="0.4"/>`;
}
return dots;
};
// 生成文字
let textElements = '';
const charWidth = width / text.length;
for (let i = 0; i < text.length; i++) {
const char = text[i];
const x = charWidth * i + charWidth / 2;
const y = height / 2 + fontSize / 3;
const rotation = (Math.random() - 0.5) * 30; // 随机旋转角度
const color = getRandomColor();
textElements += `
<text x="${x}" y="${y}"
font-family="Arial, sans-serif"
font-size="${fontSize}"
font-weight="bold"
fill="${color}"
text-anchor="middle"
transform="rotate(${rotation} ${x} ${y})">
${char}
</text>`;
}
const svg = `
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#f8f9fa;stop-opacity:1" />
<stop offset="100%" style="stop-color:#e9ecef;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="${width}" height="${height}" fill="url(#bg)" stroke="#dee2e6" stroke-width="1"/>
${generateNoise()}
${generateDots()}
${textElements}
</svg>`;
return svg;
}
router.get('/generate', (req, res) => {
try {
// 生成验证码文本
const captchaText = generateCaptchaText();
// 生成唯一ID
const captchaId = crypto.randomUUID();
// 存储验证码5分钟过期
global.captchaStore.set(captchaId, {
text: captchaText.toLowerCase(), // 存储小写用于比较
expires: Date.now() + 5 * 60 * 1000 // 5分钟过期
});
// 生成SVG图片
const svgImage = generateCaptchaSVG(captchaText);
res.json({
success: true,
data: {
captchaId,
image: `data:image/svg+xml;base64,${Buffer.from(svgImage).toString('base64')}`
}
});
} catch (error) {
console.error('生成验证码失败:', error);
res.status(500).json({
success: false,
message: '生成验证码失败'
});
}
});
// 清理过期验证码的定时任务
setInterval(() => {
const now = Date.now();
for (const [id, captcha] of global.captchaStore.entries()) {
if (now > captcha.expires) {
global.captchaStore.delete(id);
}
}
}, 60 * 1000); // 每分钟清理一次
// 导出验证函数供其他模块使用
module.exports = router;
module.exports.verifyCaptcha = (captchaId, captchaText) => {
const captcha = global.captchaStore.get(captchaId);
if (!captcha) {
return false; // 验证码不存在或已过期
}
if (captcha.text.toLowerCase() !== captchaText.toLowerCase()) {
return false; // 验证码错误
}
// 验证成功后删除验证码(一次性使用)
global.captchaStore.delete(captchaId);
return true;
};