chore: add remaining test docs, scripts and temp files
- Add Git commit preparation checklist - Add Phase testing guides and issue tracking - Add utility scripts (env setup, test data initialization) - Add temp migration SQL files (for reference) - Update startup scripts and README - Remove obsolete scripts
This commit is contained in:
155
backend/temp-migration/005-validate-simple.sql
Normal file
155
backend/temp-migration/005-validate-simple.sql
Normal file
@@ -0,0 +1,155 @@
|
||||
-- ========================================
|
||||
-- 简化版验证脚本
|
||||
-- ========================================
|
||||
|
||||
-- 验证10个Schema是否创建
|
||||
DO $$
|
||||
DECLARE
|
||||
schema_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO schema_count
|
||||
FROM information_schema.schemata
|
||||
WHERE schema_name IN (
|
||||
'platform_schema', 'aia_schema', 'pkb_schema',
|
||||
'asl_schema', 'common_schema', 'dc_schema',
|
||||
'rvw_schema', 'admin_schema', 'ssa_schema', 'st_schema'
|
||||
);
|
||||
|
||||
RAISE NOTICE '========================================';
|
||||
IF schema_count = 10 THEN
|
||||
RAISE NOTICE '✅ 10个Schema全部创建成功';
|
||||
ELSE
|
||||
RAISE WARNING '⚠️ Schema数量异常: 预期10个,实际%个', schema_count;
|
||||
END IF;
|
||||
RAISE NOTICE '========================================';
|
||||
END $$;
|
||||
|
||||
-- 验证表数量
|
||||
DO $$
|
||||
DECLARE
|
||||
platform_tables INTEGER;
|
||||
aia_tables INTEGER;
|
||||
pkb_tables INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO platform_tables FROM pg_tables WHERE schemaname = 'platform_schema';
|
||||
SELECT COUNT(*) INTO aia_tables FROM pg_tables WHERE schemaname = 'aia_schema';
|
||||
SELECT COUNT(*) INTO pkb_tables FROM pg_tables WHERE schemaname = 'pkb_schema';
|
||||
|
||||
RAISE NOTICE '表数量统计:';
|
||||
RAISE NOTICE 'platform_schema: % 个表 (预期1个)', platform_tables;
|
||||
RAISE NOTICE 'aia_schema: % 个表 (预期5个)', aia_tables;
|
||||
RAISE NOTICE 'pkb_schema: % 个表 (预期5个)', pkb_tables;
|
||||
|
||||
IF platform_tables = 1 AND aia_tables = 5 AND pkb_tables = 5 THEN
|
||||
RAISE NOTICE '✅ 所有表创建成功';
|
||||
ELSE
|
||||
RAISE WARNING '⚠️ 表数量存在异常';
|
||||
END IF;
|
||||
RAISE NOTICE '========================================';
|
||||
END $$;
|
||||
|
||||
-- 验证数据量
|
||||
DO $$
|
||||
DECLARE
|
||||
public_users INTEGER;
|
||||
platform_users INTEGER;
|
||||
public_projects INTEGER;
|
||||
aia_projects INTEGER;
|
||||
public_kb INTEGER;
|
||||
pkb_kb INTEGER;
|
||||
BEGIN
|
||||
-- 统计users
|
||||
SELECT COUNT(*) INTO public_users FROM public.users;
|
||||
SELECT COUNT(*) INTO platform_users FROM platform_schema.users;
|
||||
|
||||
-- 统计projects
|
||||
SELECT COUNT(*) INTO public_projects FROM public.projects;
|
||||
SELECT COUNT(*) INTO aia_projects FROM aia_schema.projects;
|
||||
|
||||
-- 统计knowledge_bases
|
||||
SELECT COUNT(*) INTO public_kb FROM public.knowledge_bases;
|
||||
SELECT COUNT(*) INTO pkb_kb FROM pkb_schema.knowledge_bases;
|
||||
|
||||
RAISE NOTICE '数据量对比:';
|
||||
RAISE NOTICE 'users: public.% -> platform_schema.%', public_users, platform_users;
|
||||
RAISE NOTICE 'projects: public.% -> aia_schema.%', public_projects, aia_projects;
|
||||
RAISE NOTICE 'knowledge_bases: public.% -> pkb_schema.%', public_kb, pkb_kb;
|
||||
|
||||
IF public_users = platform_users AND
|
||||
public_projects = aia_projects AND
|
||||
public_kb = pkb_kb THEN
|
||||
RAISE NOTICE '✅ 数据迁移完整';
|
||||
ELSE
|
||||
RAISE WARNING '⚠️ 数据量存在差异';
|
||||
END IF;
|
||||
RAISE NOTICE '========================================';
|
||||
END $$;
|
||||
|
||||
-- 验证外键约束
|
||||
DO $$
|
||||
DECLARE
|
||||
fk_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO fk_count
|
||||
FROM information_schema.table_constraints
|
||||
WHERE constraint_type = 'FOREIGN KEY'
|
||||
AND table_schema IN ('platform_schema', 'aia_schema', 'pkb_schema');
|
||||
|
||||
RAISE NOTICE '外键约束统计:';
|
||||
RAISE NOTICE '外键约束总数:%', fk_count;
|
||||
RAISE NOTICE '✅ 外键约束已建立';
|
||||
RAISE NOTICE '========================================';
|
||||
END $$;
|
||||
|
||||
-- 验证跨Schema引用
|
||||
DO $$
|
||||
DECLARE
|
||||
invalid_aia_projects INTEGER;
|
||||
invalid_pkb_kb INTEGER;
|
||||
BEGIN
|
||||
-- 验证aia_schema.projects
|
||||
SELECT COUNT(*) INTO invalid_aia_projects
|
||||
FROM aia_schema.projects p
|
||||
LEFT JOIN platform_schema.users u ON p.user_id = u.id
|
||||
WHERE u.id IS NULL;
|
||||
|
||||
-- 验证pkb_schema.knowledge_bases
|
||||
SELECT COUNT(*) INTO invalid_pkb_kb
|
||||
FROM pkb_schema.knowledge_bases kb
|
||||
LEFT JOIN platform_schema.users u ON kb.user_id = u.id
|
||||
WHERE u.id IS NULL;
|
||||
|
||||
RAISE NOTICE '跨Schema引用验证:';
|
||||
IF invalid_aia_projects = 0 THEN
|
||||
RAISE NOTICE '✅ aia_schema.projects外键全部有效';
|
||||
ELSE
|
||||
RAISE WARNING '⚠️ aia_schema.projects有%条无效user_id', invalid_aia_projects;
|
||||
END IF;
|
||||
|
||||
IF invalid_pkb_kb = 0 THEN
|
||||
RAISE NOTICE '✅ pkb_schema.knowledge_bases外键全部有效';
|
||||
ELSE
|
||||
RAISE WARNING '⚠️ pkb_schema.knowledge_bases有%条无效user_id', invalid_pkb_kb;
|
||||
END IF;
|
||||
RAISE NOTICE '========================================';
|
||||
END $$;
|
||||
|
||||
-- 最终总结
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '';
|
||||
RAISE NOTICE '🎉🎉🎉 Schema迁移验证完成 🎉🎉🎉';
|
||||
RAISE NOTICE '';
|
||||
RAISE NOTICE '下一步:';
|
||||
RAISE NOTICE '1. 更新Prisma配置(schema.prisma)';
|
||||
RAISE NOTICE '2. 生成Prisma Client';
|
||||
RAISE NOTICE '3. 更新代码以使用新Schema';
|
||||
RAISE NOTICE '4. 测试现有功能';
|
||||
RAISE NOTICE '';
|
||||
RAISE NOTICE '========================================';
|
||||
END $$;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
17
backend/temp-migration/quick-check.sql
Normal file
17
backend/temp-migration/quick-check.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- 快速检查Schema和数据
|
||||
SELECT
|
||||
'Schema检查' as check_type,
|
||||
schema_name,
|
||||
(SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = s.schema_name) as table_count
|
||||
FROM information_schema.schemata s
|
||||
WHERE schema_name IN (
|
||||
'platform_schema', 'aia_schema', 'pkb_schema',
|
||||
'asl_schema', 'common_schema', 'dc_schema',
|
||||
'rvw_schema', 'admin_schema', 'ssa_schema', 'st_schema'
|
||||
)
|
||||
ORDER BY schema_name;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
252
backend/test-batch-api.js
Normal file
252
backend/test-batch-api.js
Normal file
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* Phase 3: 批处理API测试脚本
|
||||
*
|
||||
* 测试所有批处理API端点
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
const BASE_URL = 'http://localhost:3001/api/v1';
|
||||
|
||||
// 等待服务器启动
|
||||
async function waitForServer(maxAttempts = 10) {
|
||||
console.log('⏳ 等待服务器启动...');
|
||||
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
try {
|
||||
await axios.get(`${BASE_URL}`);
|
||||
console.log('✅ 服务器已就绪\n');
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (i < maxAttempts - 1) {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.error('❌ 服务器启动超时');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 测试1: 获取所有预设模板
|
||||
async function testGetTemplates() {
|
||||
console.log('📋 测试1: 获取所有预设模板');
|
||||
console.log('GET /api/v1/batch/templates\n');
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${BASE_URL}/batch/templates`);
|
||||
|
||||
console.log('✅ 成功');
|
||||
console.log('返回数据:', JSON.stringify(response.data, null, 2));
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
console.error('❌ 失败:', error.response?.data || error.message);
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试2: 执行批处理任务(需要真实的知识库和文档ID)
|
||||
async function testExecuteBatch(kbId, documentIds) {
|
||||
console.log('📦 测试2: 执行批处理任务');
|
||||
console.log('POST /api/v1/batch/execute\n');
|
||||
|
||||
const requestData = {
|
||||
kb_id: kbId,
|
||||
document_ids: documentIds,
|
||||
template_type: 'preset',
|
||||
template_id: 'clinical_research',
|
||||
model_type: 'deepseek-v3',
|
||||
task_name: '批处理测试任务',
|
||||
};
|
||||
|
||||
console.log('请求数据:', JSON.stringify(requestData, null, 2));
|
||||
|
||||
try {
|
||||
const response = await axios.post(`${BASE_URL}/batch/execute`, requestData);
|
||||
|
||||
console.log('\n✅ 成功');
|
||||
console.log('返回数据:', JSON.stringify(response.data, null, 2));
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('❌ 失败:', error.response?.data || error.message);
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试3: 获取任务状态
|
||||
async function testGetTask(taskId) {
|
||||
console.log('📊 测试3: 获取任务状态');
|
||||
console.log(`GET /api/v1/batch/tasks/${taskId}\n`);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${BASE_URL}/batch/tasks/${taskId}`);
|
||||
|
||||
console.log('✅ 成功');
|
||||
console.log('返回数据:', JSON.stringify(response.data, null, 2));
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
console.error('❌ 失败:', error.response?.data || error.message);
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试4: 获取任务结果
|
||||
async function testGetTaskResults(taskId) {
|
||||
console.log('📄 测试4: 获取任务结果');
|
||||
console.log(`GET /api/v1/batch/tasks/${taskId}/results\n`);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${BASE_URL}/batch/tasks/${taskId}/results`);
|
||||
|
||||
console.log('✅ 成功');
|
||||
console.log('任务信息:', JSON.stringify(response.data.data.task, null, 2));
|
||||
console.log(`\n结果数量: ${response.data.data.results.length}`);
|
||||
|
||||
// 显示前3个结果的摘要
|
||||
console.log('\n前3个结果摘要:');
|
||||
response.data.data.results.slice(0, 3).forEach((r, i) => {
|
||||
console.log(`\n结果 ${i + 1}:`);
|
||||
console.log(` - 文档: ${r.document_name}`);
|
||||
console.log(` - 状态: ${r.status}`);
|
||||
console.log(` - 处理时间: ${r.processing_time_ms}ms`);
|
||||
if (r.status === 'success' && r.data) {
|
||||
console.log(` - 数据字段:`, Object.keys(r.data).join(', '));
|
||||
}
|
||||
if (r.error_message) {
|
||||
console.log(` - 错误: ${r.error_message}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
console.error('❌ 失败:', error.response?.data || error.message);
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试5: 重试失败的文档
|
||||
async function testRetryFailed(taskId) {
|
||||
console.log('🔄 测试5: 重试失败的文档');
|
||||
console.log(`POST /api/v1/batch/tasks/${taskId}/retry-failed\n`);
|
||||
|
||||
try {
|
||||
const response = await axios.post(`${BASE_URL}/batch/tasks/${taskId}/retry-failed`);
|
||||
|
||||
console.log('✅ 成功');
|
||||
console.log('返回数据:', JSON.stringify(response.data, null, 2));
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('❌ 失败:', error.response?.data || error.message);
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试流程
|
||||
async function main() {
|
||||
console.log('🧪 Phase 3 批处理API测试\n');
|
||||
console.log('='.repeat(60));
|
||||
console.log('测试服务器: ' + BASE_URL);
|
||||
console.log('='.repeat(60) + '\n');
|
||||
|
||||
// 等待服务器启动
|
||||
const serverReady = await waitForServer();
|
||||
if (!serverReady) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 测试1: 获取模板
|
||||
const templates = await testGetTemplates();
|
||||
|
||||
// 检查是否有测试数据
|
||||
console.log('⚠️ 测试2-5需要真实的知识库和文档数据\n');
|
||||
console.log('请手动提供以下信息来继续测试:\n');
|
||||
console.log('示例命令:');
|
||||
console.log('node test-batch-api.js <知识库ID> <文档ID1> <文档ID2> <文档ID3>\n');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 4) {
|
||||
console.log('📝 跳过测试2-5(需要提供知识库ID和至少3个文档ID)\n');
|
||||
console.log('✅ 测试1完成!');
|
||||
return;
|
||||
}
|
||||
|
||||
const kbId = args[0];
|
||||
const documentIds = args.slice(1);
|
||||
|
||||
console.log(`\n📌 使用测试数据:`);
|
||||
console.log(` 知识库ID: ${kbId}`);
|
||||
console.log(` 文档数量: ${documentIds.length}`);
|
||||
console.log(` 文档IDs: ${documentIds.slice(0, 3).join(', ')}${documentIds.length > 3 ? '...' : ''}\n`);
|
||||
console.log('='.repeat(60) + '\n');
|
||||
|
||||
// 测试2: 执行批处理
|
||||
const executeResult = await testExecuteBatch(kbId, documentIds);
|
||||
|
||||
if (!executeResult || !executeResult.data || !executeResult.data.task_id) {
|
||||
console.log('❌ 批处理任务创建失败或未返回task_id,停止测试');
|
||||
return;
|
||||
}
|
||||
|
||||
const taskId = executeResult.data.task_id;
|
||||
console.log(`✅ 获取到任务ID: ${taskId}\n`);
|
||||
|
||||
// 等待一段时间让任务开始执行
|
||||
console.log('⏳ 等待5秒让任务开始执行...\n');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
// 测试3: 获取任务状态
|
||||
const taskStatus = await testGetTask(taskId);
|
||||
|
||||
// 等待任务完成(最多等待2分钟)
|
||||
if (taskStatus && taskStatus.status === 'processing') {
|
||||
console.log('⏳ 任务仍在处理中,等待完成(最多2分钟)...\n');
|
||||
|
||||
for (let i = 0; i < 24; i++) { // 24次 * 5秒 = 2分钟
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
const currentStatus = await testGetTask(taskId);
|
||||
if (currentStatus && currentStatus.status !== 'processing') {
|
||||
console.log(`✅ 任务已完成,状态: ${currentStatus.status}\n`);
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(`⏳ 仍在处理中... (${currentStatus.completed_count}/${currentStatus.total_documents})\n`);
|
||||
}
|
||||
}
|
||||
|
||||
// 测试4: 获取任务结果
|
||||
const taskResults = await testGetTaskResults(taskId);
|
||||
|
||||
// 测试5: 重试失败的文档(如果有失败的)
|
||||
if (taskResults && taskResults.task.failed_count > 0) {
|
||||
console.log(`\n⚠️ 发现 ${taskResults.task.failed_count} 个失败的文档,测试重试功能\n`);
|
||||
await testRetryFailed(taskId);
|
||||
} else {
|
||||
console.log('\n✅ 所有文档处理成功,跳过重试测试\n');
|
||||
}
|
||||
|
||||
console.log('🎉 所有API测试完成!');
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
main().catch(error => {
|
||||
console.error('💥 测试执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
409
backend/test-review-api.js
Normal file
409
backend/test-review-api.js
Normal file
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* 稿件审查API测试脚本
|
||||
* 测试5个API端点的功能
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import FormData from 'form-data';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const API_BASE_URL = 'http://localhost:3001/api/v1';
|
||||
const TEST_FILE_PATH = path.join(__dirname, '../docs/稿约规范性评估标准.txt'); // 使用现有文本文件作为测试
|
||||
|
||||
// 颜色输出
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
green: '\x1b[32m',
|
||||
red: '\x1b[31m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
cyan: '\x1b[36m',
|
||||
};
|
||||
|
||||
function log(message, color = 'reset') {
|
||||
console.log(`${colors[color]}${message}${colors.reset}`);
|
||||
}
|
||||
|
||||
function separator() {
|
||||
console.log('\n' + '='.repeat(80) + '\n');
|
||||
}
|
||||
|
||||
// 延迟函数
|
||||
function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// ==================== 测试函数 ====================
|
||||
|
||||
/**
|
||||
* 测试1: 上传稿件
|
||||
*/
|
||||
async function testUploadManuscript() {
|
||||
log('📤 测试1: 上传稿件 (POST /review/upload)', 'cyan');
|
||||
|
||||
try {
|
||||
// 检查测试文件是否存在
|
||||
if (!fs.existsSync(TEST_FILE_PATH)) {
|
||||
log(`❌ 测试文件不存在: ${TEST_FILE_PATH}`, 'red');
|
||||
log('💡 提示:请准备一个Word文档(.doc或.docx)作为测试文件', 'yellow');
|
||||
return null;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', fs.createReadStream(TEST_FILE_PATH));
|
||||
formData.append('modelType', 'deepseek-v3');
|
||||
|
||||
log(`📄 测试文件: ${path.basename(TEST_FILE_PATH)}`, 'blue');
|
||||
log(`🤖 使用模型: deepseek-v3`, 'blue');
|
||||
|
||||
const response = await axios.post(
|
||||
`${API_BASE_URL}/review/upload`,
|
||||
formData,
|
||||
{
|
||||
headers: formData.getHeaders(),
|
||||
timeout: 30000,
|
||||
}
|
||||
);
|
||||
|
||||
if (response.data.success) {
|
||||
log('✅ 上传成功!', 'green');
|
||||
console.log('返回数据:', JSON.stringify(response.data, null, 2));
|
||||
return response.data.data.taskId;
|
||||
} else {
|
||||
log('❌ 上传失败', 'red');
|
||||
console.log('错误信息:', response.data.message);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
log('❌ 请求失败', 'red');
|
||||
if (error.response) {
|
||||
console.log('状态码:', error.response.status);
|
||||
console.log('错误信息:', error.response.data);
|
||||
} else {
|
||||
console.log('错误:', error.message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试2: 查询任务状态
|
||||
*/
|
||||
async function testGetTaskStatus(taskId) {
|
||||
log('🔍 测试2: 查询任务状态 (GET /review/tasks/:taskId)', 'cyan');
|
||||
|
||||
try {
|
||||
log(`📋 任务ID: ${taskId}`, 'blue');
|
||||
|
||||
const response = await axios.get(`${API_BASE_URL}/review/tasks/${taskId}`, {
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
log('✅ 查询成功!', 'green');
|
||||
console.log('任务状态:', JSON.stringify(response.data.data, null, 2));
|
||||
return response.data.data;
|
||||
} else {
|
||||
log('❌ 查询失败', 'red');
|
||||
console.log('错误信息:', response.data.message);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
log('❌ 请求失败', 'red');
|
||||
if (error.response) {
|
||||
console.log('状态码:', error.response.status);
|
||||
console.log('错误信息:', error.response.data);
|
||||
} else {
|
||||
console.log('错误:', error.message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试3: 轮询任务直到完成
|
||||
*/
|
||||
async function pollTaskUntilComplete(taskId, maxAttempts = 60) {
|
||||
log('⏳ 测试3: 轮询任务状态直到完成', 'cyan');
|
||||
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
const task = await testGetTaskStatus(taskId);
|
||||
|
||||
if (!task) {
|
||||
log('❌ 查询任务失败,停止轮询', 'red');
|
||||
return null;
|
||||
}
|
||||
|
||||
log(`📊 当前状态: ${task.status}`, 'yellow');
|
||||
|
||||
if (task.status === 'completed') {
|
||||
log('✅ 任务已完成!', 'green');
|
||||
return task;
|
||||
}
|
||||
|
||||
if (task.status === 'failed') {
|
||||
log('❌ 任务失败', 'red');
|
||||
console.log('错误信息:', task.errorMessage);
|
||||
return null;
|
||||
}
|
||||
|
||||
log(`⏱️ 等待5秒后重试... (${i + 1}/${maxAttempts})`, 'blue');
|
||||
await delay(5000);
|
||||
}
|
||||
|
||||
log('⚠️ 超过最大等待时间,任务仍未完成', 'yellow');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试4: 获取审查报告
|
||||
*/
|
||||
async function testGetReport(taskId) {
|
||||
log('📊 测试4: 获取审查报告 (GET /review/tasks/:taskId/report)', 'cyan');
|
||||
|
||||
try {
|
||||
log(`📋 任务ID: ${taskId}`, 'blue');
|
||||
|
||||
const response = await axios.get(`${API_BASE_URL}/review/tasks/${taskId}/report`, {
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
log('✅ 获取报告成功!', 'green');
|
||||
console.log('\n📄 完整报告:');
|
||||
console.log(JSON.stringify(response.data.data, null, 2));
|
||||
|
||||
// 显示关键指标
|
||||
const report = response.data.data;
|
||||
separator();
|
||||
log('📈 评估结果摘要:', 'cyan');
|
||||
log(`总分: ${report.overallScore?.toFixed(1) || 'N/A'}`, 'green');
|
||||
log(`稿约规范性评分: ${report.editorialReview?.overall_score || 'N/A'}`, 'blue');
|
||||
log(`方法学评分: ${report.methodologyReview?.overall_score || 'N/A'}`, 'blue');
|
||||
log(`字数: ${report.wordCount || 'N/A'}`, 'blue');
|
||||
log(`耗时: ${report.durationSeconds || 'N/A'}秒`, 'blue');
|
||||
separator();
|
||||
|
||||
return response.data.data;
|
||||
} else {
|
||||
log('❌ 获取报告失败', 'red');
|
||||
console.log('错误信息:', response.data.message);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
log('❌ 请求失败', 'red');
|
||||
if (error.response) {
|
||||
console.log('状态码:', error.response.status);
|
||||
console.log('错误信息:', error.response.data);
|
||||
} else {
|
||||
console.log('错误:', error.message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试5: 获取任务列表
|
||||
*/
|
||||
async function testGetTaskList() {
|
||||
log('📋 测试5: 获取任务列表 (GET /review/tasks)', 'cyan');
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/review/tasks`, {
|
||||
params: { page: 1, limit: 10 },
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
log('✅ 获取列表成功!', 'green');
|
||||
console.log(`找到 ${response.data.data.length} 个任务`);
|
||||
console.log('任务列表:', JSON.stringify(response.data.data, null, 2));
|
||||
console.log('分页信息:', JSON.stringify(response.data.pagination, null, 2));
|
||||
return response.data.data;
|
||||
} else {
|
||||
log('❌ 获取列表失败', 'red');
|
||||
console.log('错误信息:', response.data.message);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
log('❌ 请求失败', 'red');
|
||||
if (error.response) {
|
||||
console.log('状态码:', error.response.status);
|
||||
console.log('错误信息:', error.response.data);
|
||||
} else {
|
||||
console.log('错误:', error.message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试6: 删除任务(可选)
|
||||
*/
|
||||
async function testDeleteTask(taskId) {
|
||||
log('🗑️ 测试6: 删除任务 (DELETE /review/tasks/:taskId)', 'cyan');
|
||||
|
||||
try {
|
||||
log(`📋 任务ID: ${taskId}`, 'blue');
|
||||
|
||||
const response = await axios.delete(`${API_BASE_URL}/review/tasks/${taskId}`, {
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
log('✅ 删除成功!', 'green');
|
||||
console.log('返回数据:', JSON.stringify(response.data, null, 2));
|
||||
return true;
|
||||
} else {
|
||||
log('❌ 删除失败', 'red');
|
||||
console.log('错误信息:', response.data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
log('❌ 请求失败', 'red');
|
||||
if (error.response) {
|
||||
console.log('状态码:', error.response.status);
|
||||
console.log('错误信息:', error.response.data);
|
||||
} else {
|
||||
console.log('错误:', error.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 主测试流程 ====================
|
||||
|
||||
async function runAllTests() {
|
||||
log('🚀 开始稿件审查API测试', 'green');
|
||||
separator();
|
||||
|
||||
// 测试1: 上传稿件
|
||||
const taskId = await testUploadManuscript();
|
||||
if (!taskId) {
|
||||
log('❌ 上传失败,终止测试', 'red');
|
||||
return;
|
||||
}
|
||||
separator();
|
||||
|
||||
// 等待2秒
|
||||
await delay(2000);
|
||||
|
||||
// 测试2: 查询任务状态
|
||||
await testGetTaskStatus(taskId);
|
||||
separator();
|
||||
|
||||
// 测试3: 轮询直到完成
|
||||
const completedTask = await pollTaskUntilComplete(taskId);
|
||||
if (!completedTask) {
|
||||
log('❌ 任务未能完成,跳过报告测试', 'red');
|
||||
separator();
|
||||
|
||||
// 但仍然测试任务列表
|
||||
await testGetTaskList();
|
||||
separator();
|
||||
return;
|
||||
}
|
||||
separator();
|
||||
|
||||
// 测试4: 获取报告
|
||||
await testGetReport(taskId);
|
||||
separator();
|
||||
|
||||
// 测试5: 获取任务列表
|
||||
await testGetTaskList();
|
||||
separator();
|
||||
|
||||
// 测试6: 删除任务(可选,取消注释以启用)
|
||||
// log('⚠️ 是否删除测试任务?(取消注释testDeleteTask以启用)', 'yellow');
|
||||
// await testDeleteTask(taskId);
|
||||
// separator();
|
||||
|
||||
log('✅ 所有测试完成!', 'green');
|
||||
}
|
||||
|
||||
// ==================== 健康检查 ====================
|
||||
|
||||
async function checkHealth() {
|
||||
log('🔍 检查后端服务健康状态...', 'cyan');
|
||||
|
||||
try {
|
||||
const response = await axios.get('http://localhost:3001/health', {
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
log('✅ 后端服务正常', 'green');
|
||||
console.log('健康状态:', response.data);
|
||||
return true;
|
||||
} catch (error) {
|
||||
log('❌ 后端服务不可用', 'red');
|
||||
console.log('错误:', error.message);
|
||||
log('💡 请先启动后端服务: npm run dev 或 启动后端.bat', 'yellow');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 入口 ====================
|
||||
|
||||
async function main() {
|
||||
console.log('\n');
|
||||
log('═══════════════════════════════════════════════════════════════════════════════', 'cyan');
|
||||
log(' 稿件审查API自动化测试脚本 ', 'cyan');
|
||||
log('═══════════════════════════════════════════════════════════════════════════════', 'cyan');
|
||||
console.log('\n');
|
||||
|
||||
// 健康检查
|
||||
const healthy = await checkHealth();
|
||||
if (!healthy) {
|
||||
process.exit(1);
|
||||
}
|
||||
separator();
|
||||
|
||||
// 运行所有测试
|
||||
await runAllTests();
|
||||
|
||||
console.log('\n');
|
||||
log('═══════════════════════════════════════════════════════════════════════════════', 'cyan');
|
||||
log(' 测试完成 ', 'cyan');
|
||||
log('═══════════════════════════════════════════════════════════════════════════════', 'cyan');
|
||||
console.log('\n');
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error('测试脚本执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
79
backend/update-env-closeai.ps1
Normal file
79
backend/update-env-closeai.ps1
Normal file
@@ -0,0 +1,79 @@
|
||||
# PowerShell脚本:自动添加CloseAI配置到.env文件
|
||||
# 使用方法:在 backend 目录下运行 .\update-env-closeai.ps1
|
||||
|
||||
$envFile = ".env"
|
||||
|
||||
# 检查.env文件是否存在
|
||||
if (-not (Test-Path $envFile)) {
|
||||
Write-Host "错误:.env文件不存在!" -ForegroundColor Red
|
||||
Write-Host "请先创建.env文件" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 读取现有配置
|
||||
$envContent = Get-Content $envFile -Raw
|
||||
|
||||
# 检查是否已经包含CloseAI配置
|
||||
if ($envContent -match "CLOSEAI_API_KEY") {
|
||||
Write-Host "检测到.env文件中已包含CloseAI配置" -ForegroundColor Yellow
|
||||
$overwrite = Read-Host "是否要覆盖现有配置?(y/n)"
|
||||
|
||||
if ($overwrite -ne "y") {
|
||||
Write-Host "取消更新" -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 删除旧的CloseAI配置
|
||||
$envContent = $envContent -replace "(?ms)# ={32}.*?# CloseAI.*?# ={32}.*?(?=\r?\n# ={32}|\r?\n\r?\n|\Z)", ""
|
||||
}
|
||||
|
||||
# CloseAI配置内容
|
||||
$closeaiConfig = @"
|
||||
|
||||
# ================================
|
||||
# CloseAI配置(代理OpenAI和Claude)⭐
|
||||
# ================================
|
||||
# CloseAI是一个API代理平台,提供稳定的OpenAI和Claude访问
|
||||
# 官网:https://platform.openai-proxy.org
|
||||
|
||||
# 统一API Key(同时用于OpenAI和Claude)
|
||||
CLOSEAI_API_KEY=sk-cu0iepbXYGGx2jc7BqP6ogtSWmP6fk918qV3RUdtGC3Edlpo
|
||||
|
||||
# OpenAI端点
|
||||
CLOSEAI_OPENAI_BASE_URL=https://api.openai-proxy.org/v1
|
||||
|
||||
# Claude端点
|
||||
CLOSEAI_CLAUDE_BASE_URL=https://api.openai-proxy.org/anthropic
|
||||
|
||||
# 支持的模型:
|
||||
# - OpenAI: gpt-5-pro (最新), gpt-4-turbo-preview, gpt-3.5-turbo
|
||||
# - Claude: claude-sonnet-4-5-20250929 (最新), claude-3-5-sonnet-20241022
|
||||
"@
|
||||
|
||||
# 添加到文件末尾
|
||||
$envContent = $envContent.TrimEnd() + "`r`n" + $closeaiConfig
|
||||
|
||||
# 写回文件
|
||||
Set-Content -Path $envFile -Value $envContent -NoNewline
|
||||
|
||||
Write-Host "✅ CloseAI配置已成功添加到.env文件!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "已添加的配置:" -ForegroundColor Cyan
|
||||
Write-Host "- CLOSEAI_API_KEY: sk-cu0iep...(真实API Key)" -ForegroundColor White
|
||||
Write-Host "- CLOSEAI_OPENAI_BASE_URL: https://api.openai-proxy.org/v1" -ForegroundColor White
|
||||
Write-Host "- CLOSEAI_CLAUDE_BASE_URL: https://api.openai-proxy.org/anthropic" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "可用模型:" -ForegroundColor Cyan
|
||||
Write-Host "- GPT-5-Pro: gpt-5-pro" -ForegroundColor White
|
||||
Write-Host "- Claude-4.5-Sonnet: claude-sonnet-4-5-20250929" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "下一步:重启后端服务以应用新配置" -ForegroundColor Yellow
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
63
backend/初始化测试用户.bat
Normal file
63
backend/初始化测试用户.bat
Normal file
@@ -0,0 +1,63 @@
|
||||
@echo off
|
||||
chcp 65001 > nul
|
||||
echo.
|
||||
echo ========================================
|
||||
echo 初始化测试用户
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
echo [1/1] 正在初始化测试用户数据...
|
||||
call npm run prisma:seed
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo 初始化完成!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 测试账号信息:
|
||||
echo 邮箱: test@example.com
|
||||
echo 密码: password123
|
||||
echo 用户ID: user-mock-001
|
||||
echo.
|
||||
echo 管理员账号信息:
|
||||
echo 邮箱: admin@example.com
|
||||
echo 密码: password123
|
||||
echo 用户ID: user-admin-001
|
||||
echo.
|
||||
echo 现在可以创建知识库了!
|
||||
echo.
|
||||
|
||||
pause
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
96
backend/测试用户说明.md
Normal file
96
backend/测试用户说明.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# 测试用户初始化说明
|
||||
|
||||
## 问题
|
||||
创建知识库时报错 "User not found",因为数据库中没有用户记录。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方法1:运行批处理脚本(推荐)⭐
|
||||
|
||||
双击运行:**`初始化测试用户.bat`**
|
||||
|
||||
这个脚本会自动创建两个测试用户:
|
||||
- 普通用户(user-mock-001)
|
||||
- 管理员用户(user-admin-001)
|
||||
|
||||
### 方法2:手动运行命令
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm run prisma:seed
|
||||
```
|
||||
|
||||
## 测试账号信息
|
||||
|
||||
### 普通用户
|
||||
- **用户ID**: `user-mock-001`
|
||||
- **邮箱**: `test@example.com`
|
||||
- **密码**: `password123`
|
||||
- **知识库配额**: 3个
|
||||
- **试用期**: 30天
|
||||
|
||||
### 管理员用户
|
||||
- **用户ID**: `user-admin-001`
|
||||
- **邮箱**: `admin@example.com`
|
||||
- **密码**: `password123`
|
||||
- **知识库配额**: 10个
|
||||
|
||||
## 说明
|
||||
|
||||
1. **当前系统使用硬编码的用户ID**
|
||||
- 后端控制器使用 `MOCK_USER_ID = 'user-mock-001'`
|
||||
- 所有API请求都使用这个固定ID
|
||||
|
||||
2. **用户认证系统未实现**
|
||||
- 当前处于开发阶段
|
||||
- 用户登录/注册功能在里程碑3实现
|
||||
|
||||
3. **重新初始化数据库后需要重新运行seed**
|
||||
- 如果执行了 `prisma migrate reset`
|
||||
- 或手动清空了数据库
|
||||
- 需要重新运行 `初始化测试用户.bat`
|
||||
|
||||
## 验证
|
||||
|
||||
运行seed脚本后,可以通过Prisma Studio验证:
|
||||
|
||||
```bash
|
||||
npm run prisma:studio
|
||||
```
|
||||
|
||||
在浏览器中打开,查看 `users` 表,应该能看到两个用户记录。
|
||||
|
||||
## 下一步
|
||||
|
||||
初始化完成后,就可以正常创建知识库了!🎉
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user