修复角色中心组件中的类型不匹配错误,改进skinId和capeId的类型处理逻辑
This commit is contained in:
477
src/app/verify-code-test/page.tsx
Normal file
477
src/app/verify-code-test/page.tsx
Normal file
@@ -0,0 +1,477 @@
|
||||
'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;
|
||||
}
|
||||
|
||||
// 真实API调用模式
|
||||
try {
|
||||
// 根据后端架构文档中的API网关信息
|
||||
const API_ENDPOINTS = [
|
||||
{
|
||||
name: 'API网关v1版本',
|
||||
url: 'https://code.littlelan.cn/CarrotSkin/APIgateway/api/v1/auth/send-code'
|
||||
},
|
||||
{
|
||||
name: 'API网关v2版本',
|
||||
url: 'https://code.littlelan.cn/CarrotSkin/APIgateway/api/v2/auth/send-code'
|
||||
},
|
||||
{
|
||||
name: '直接API',
|
||||
url: 'https://code.littlelan.cn/CarrotSkin/api/auth/send-code'
|
||||
}
|
||||
];
|
||||
|
||||
// 测试多个可能的端点
|
||||
for (const endpoint of API_ENDPOINTS) {
|
||||
addLog(`\n=== 测试 ${endpoint.name} ===`);
|
||||
addLog(`请求URL: ${endpoint.url}`);
|
||||
addLog(`请求参数: {"email": "${email}"}`);
|
||||
|
||||
try {
|
||||
// 发送请求
|
||||
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({ email }),
|
||||
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.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>后端架构信息</h3>
|
||||
<p>基于提供的后端架构文档:</p>
|
||||
<ul>
|
||||
<li>认证模块: internal/handler/auth_handler.go</li>
|
||||
<li>邮箱验证码模块: internal/service/verification_service.go</li>
|
||||
<li>验证码: 6位数字,Redis存储(10分钟有效期)</li>
|
||||
<li>发送频率: 限制1分钟</li>
|
||||
<li>邮件格式: HTML格式</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">
|
||||
{testMode === 'real'
|
||||
? '将实际调用后端API发送验证码'
|
||||
: '模拟验证码发送过程,生成测试验证码'}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user