Files
frontend/src/app/verify-code-test/page.tsx
Mikuisnotavailable 957e7d9a15 我忘了改啥了
2025-10-26 00:19:23 +08:00

471 lines
15 KiB
TypeScript
Raw 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.

'use client';
import React, { useState, useEffect } from 'react';
// 验证码测试工具 - 专注于验证功能的测试
// 注意尽管控制台显示NextAuth错误但这不影响本工具的核心功能
export default function VerifyCodeTest() {
// 状态管理
const [email, setEmail] = useState('');
const [isSending, setIsSending] = useState(false);
const [message, setMessage] = useState('');
const [messageType, setMessageType] = useState<'success' | 'error' | 'info'>('info');
const [logs, setLogs] = useState<string[]>([]);
const [testMode, setTestMode] = useState<'real' | 'simulation'>('simulation');
// 添加CSS样式
useEffect(() => {
// 避免水合错误的样式注入方式
const styleId = 'verify-code-test-styles';
if (!document.getElementById(styleId)) {
const style = document.createElement('style');
style.id = styleId;
style.textContent = `
.verify-code-container {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
padding: 2rem;
display: flex;
flex-direction: column;
align-items: center;
background: linear-gradient(to bottom, #f0f4f8, #e2e8f0);
}
.verify-code-header {
text-align: center;
margin-bottom: 2rem;
}
.verify-code-header h1 {
color: #3730a3;
font-size: 2rem;
margin-bottom: 0.5rem;
}
.verify-code-header p {
color: #6b7280;
font-size: 1rem;
}
.verify-code-card {
background: white;
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
width: 100%;
max-width: 450px;
margin-bottom: 1.5rem;
}
.verify-code-form-group {
margin-bottom: 1rem;
}
.verify-code-form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #374151;
}
.verify-code-form-group input[type="email"] {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
font-size: 1rem;
}
.verify-code-form-group input[type="email"]:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.verify-code-btn {
width: 100%;
padding: 0.75rem;
border: none;
border-radius: 0.375rem;
font-weight: 500;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}
.verify-code-btn-primary {
background-color: #3b82f6;
color: white;
}
.verify-code-btn-primary:hover:not(:disabled) {
background-color: #2563eb;
}
.verify-code-btn-primary:disabled {
background-color: #d1d5db;
color: #9ca3af;
cursor: not-allowed;
}
.verify-code-message {
margin-top: 1rem;
padding: 0.75rem;
border-radius: 0.375rem;
border: 1px solid;
}
.verify-code-message-success {
background-color: #dcfce7;
color: #15803d;
border-color: #bbf7d0;
}
.verify-code-message-error {
background-color: #fee2e2;
color: #b91c1c;
border-color: #fecaca;
}
.verify-code-info-box {
margin-top: 1rem;
padding: 1rem;
background-color: #eff6ff;
border: 1px solid #dbeafe;
border-radius: 0.375rem;
font-size: 0.9rem;
}
.verify-code-info-box h3 {
margin-top: 0;
color: #1e40af;
font-size: 1rem;
}
.verify-code-info-box p {
margin-bottom: 0.5rem;
color: #374151;
}
.verify-code-info-box ul {
margin-top: 0.5rem;
margin-bottom: 0;
padding-left: 1.5rem;
}
.verify-code-test-mode {
margin-top: 1rem;
}
.verify-code-test-mode-options {
display: flex;
gap: 1rem;
margin-bottom: 0.5rem;
}
.verify-code-test-mode-options label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.verify-code-test-mode-description {
font-size: 0.8rem;
color: #6b7280;
}
.verify-code-logs-container {
width: 100%;
max-width: 600px;
background: white;
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
}
.verify-code-logs-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.verify-code-logs-header h3 {
margin: 0;
color: #1f2937;
font-size: 1.1rem;
}
.verify-code-clear-logs-btn {
background: none;
border: none;
color: #3b82f6;
cursor: pointer;
font-size: 0.85rem;
}
.verify-code-clear-logs-btn:hover {
text-decoration: underline;
}
.verify-code-logs {
background-color: #f9fafb;
padding: 1rem;
border-radius: 0.375rem;
max-height: 250px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 0.8rem;
line-height: 1.4;
}
.verify-code-logs-empty {
color: #9ca3af;
font-style: italic;
}
`;
document.head.appendChild(style);
}
return () => {
const style = document.getElementById(styleId);
if (style) {
style.remove();
}
};
}, []);
// 添加日志
const addLog = (text: string) => {
setLogs(prev => [...prev, `[${new Date().toLocaleTimeString()}] ${text}`]);
};
// 清除日志
const clearLogs = () => {
setLogs([]);
};
// 发送验证码
const sendVerificationCode = async () => {
// 重置状态
setMessage('');
setLogs([]);
setIsSending(true);
addLog('开始发送验证码...');
addLog(`测试模式: ${testMode === 'real' ? '真实API调用' : '本地模拟'}`);
// 验证邮箱格式
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
setMessageType('error');
setMessage('请输入有效的邮箱地址');
setIsSending(false);
return;
}
// 模拟模式
if (testMode === 'simulation') {
try {
addLog(`模拟发送验证码到: ${email}`);
// 模拟后端处理流程
addLog('模拟验证邮箱格式...');
await new Promise(resolve => setTimeout(resolve, 200));
addLog('模拟生成6位数字验证码...');
await new Promise(resolve => setTimeout(resolve, 200));
// 生成测试验证码
const testCode = Math.floor(100000 + Math.random() * 900000).toString();
addLog(`模拟验证码已生成: ${testCode}`);
addLog('模拟存储验证码到Redis (10分钟有效期)...');
await new Promise(resolve => setTimeout(resolve, 200));
addLog('模拟发送HTML格式邮件...');
await new Promise(resolve => setTimeout(resolve, 200));
addLog('模拟返回成功响应');
setMessageType('success');
setMessage(`模拟发送成功!测试验证码: ${testCode}`);
} catch (error) {
addLog(`模拟异常: ${error instanceof Error ? error.message : String(error)}`);
setMessageType('error');
setMessage(`模拟失败: ${error instanceof Error ? error.message : '未知错误'}`);
} finally {
setIsSending(false);
}
return;
}
try {
// 根据后端代码分析使用正确的API接口格式
const API_ENDPOINTS = [
{
name: 'API v1版本',
url: `${process.env.NEXT_PUBLIC_API_URL}/api/v1/auth/send-code`
}
];
// 测试多个可能的端点
for (const endpoint of API_ENDPOINTS) {
try {
// 根据后端代码分析需要包含type参数
const requestBody = {
email,
type: 'register' // 默认使用register类型可以根据需要修改为reset_password或change_email
};
addLog(`\n=== 测试 ${endpoint.name} ===`);
addLog(`请求URL: ${endpoint.url}`);
addLog(`请求参数: ${JSON.stringify(requestBody)}`);
// 发送请求
const startTime = Date.now();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 8000); // 8秒超时
const response = await fetch(endpoint.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
signal: controller.signal,
credentials: 'include'
});
clearTimeout(timeoutId);
const endTime = Date.now();
addLog(`响应状态码: ${response.status}`);
addLog(`响应时间: ${endTime - startTime}ms`);
// 解析响应
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
const data = await response.json();
addLog(`响应数据: ${JSON.stringify(data)}`);
if (response.ok) {
setMessageType('success');
setMessage(`验证码发送成功,请检查您的邮箱 (使用 ${endpoint.name})`);
setIsSending(false);
return;
} else {
addLog(`请求失败: ${data.message || response.status}`);
}
} else {
const text = await response.text();
addLog(`非JSON响应: ${text.substring(0, 150)}${text.length > 150 ? '...' : ''}`);
}
} catch (fetchError) {
if (fetchError instanceof Error && fetchError.name === 'AbortError') {
addLog('请求超时 (8秒)');
} else {
addLog(`请求错误: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`);
}
}
}
// 诊断信息
addLog('\n=== 诊断信息 ===');
addLog('1. 后端API网关可能未启动或网络不可达');
addLog('2. 请检查API端点地址是否正确');
addLog('3. 根据架构文档端点应位于auth_handler.go中');
addLog('4. 验证码服务使用Redis存储(10分钟有效期)');
addLog('5. 建议使用模拟模式测试前端功能');
setMessageType('error');
setMessage('所有API端点连接失败请检查后端服务状态');
} catch (error) {
addLog(`系统异常: ${error instanceof Error ? error.message : String(error)}`);
setMessageType('error');
setMessage(`系统错误: ${error instanceof Error ? error.message : '未知错误'}`);
} finally {
setIsSending(false);
}
};
return (
<div className="verify-code-container">
{/* 页面标题 */}
<div className="verify-code-header">
<h1></h1>
<p></p>
</div>
{/* 测试表单 */}
<div className="verify-code-card">
<h2></h2>
{/* 邮箱输入 */}
<div className="verify-code-form-group">
<label htmlFor="email"></label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="请输入邮箱地址"
disabled={isSending}
/>
</div>
{/* 发送按钮 */}
<button
onClick={sendVerificationCode}
disabled={isSending || !email}
className="verify-code-btn verify-code-btn-primary"
>
{isSending ? '发送中...' : '发送验证码'}
</button>
{/* 消息显示 */}
{message && (
<div className={`verify-code-message verify-code-message-${messageType}`}>
{message}
</div>
)}
{/* 后端架构信息 */}
<div className="verify-code-info-box">
<h3>API接口信息</h3>
<ul>
<li>API端点: <code>POST /api/v1/auth/send-code</code></li>
<li>: <code>{'{"email": "user@example.com", "type": "register|reset_password|change_email"}'}</code></li>
<li>验证码规则: 6位数字Redis存储(10)</li>
<li>限制: 每分钟只能发送1次</li>
<li>实现文件: internal/handler/auth_handler.go internal/service/verification_service.go</li>
</ul>
</div>
{/* 测试模式选择 */}
<div className="verify-code-test-mode">
<label></label>
<div className="verify-code-test-mode-options">
<label>
<input
type="radio"
name="testMode"
value="real"
checked={testMode === 'real'}
onChange={() => setTestMode('real')}
/>
API调用
</label>
<label>
<input
type="radio"
name="testMode"
value="simulation"
checked={testMode === 'simulation'}
onChange={() => setTestMode('simulation')}
/>
</label>
</div>
<p className="verify-code-test-mode-description">
API调用: 真实调用后端API接口<br/>
本地模拟: 在前端模拟验证码发送流程
</p>
</div>
</div>
{/* 测试日志 */}
<div className="verify-code-logs-container">
<div className="verify-code-logs-header">
<h3></h3>
{logs.length > 0 && (
<button
onClick={clearLogs}
className="verify-code-clear-logs-btn"
>
</button>
)}
</div>
<div className="verify-code-logs">
{logs.length === 0 ? (
<p className="verify-code-logs-empty"></p>
) : (
logs.map((log, index) => (
<div key={index}>{log}</div>
))
)}
</div>
</div>
</div>
);
}