mirror of
https://github.com/patdelphi/suanming.git
synced 2026-02-28 05:33:11 +08:00
refactor: comprehensive code review improvements
- Enhanced input validation and error handling across all services - Added comprehensive JSDoc documentation for all major functions - Improved database operations with transaction management and better error handling - Added PropTypes validation and error boundaries for React components - Unified error handling patterns across backend services - Reduced code duplication in API routes with shared validation functions - Enhanced security with stricter input validation and SQL injection prevention - Improved user experience with better error messages and retry functionality
This commit is contained in:
@@ -1,202 +0,0 @@
|
|||||||
const { getDB } = require('./server/database/index.cjs');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理重复记录的脚本
|
|
||||||
* 保留每组重复记录中最新的一条,删除其他重复记录
|
|
||||||
* 重新关联AI解读记录到保留的记录
|
|
||||||
*/
|
|
||||||
function cleanupDuplicateRecords() {
|
|
||||||
try {
|
|
||||||
// 初始化数据库连接
|
|
||||||
const { dbManager } = require('./server/database/index.cjs');
|
|
||||||
dbManager.init();
|
|
||||||
const db = getDB();
|
|
||||||
|
|
||||||
console.log('=== 开始清理重复记录 ===\n');
|
|
||||||
|
|
||||||
// 开始事务
|
|
||||||
db.exec('BEGIN TRANSACTION');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 找出所有重复记录组
|
|
||||||
console.log('1. 识别重复记录组...');
|
|
||||||
const duplicateGroups = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
name,
|
|
||||||
reading_type,
|
|
||||||
user_id,
|
|
||||||
COUNT(*) as count,
|
|
||||||
GROUP_CONCAT(id ORDER BY created_at DESC) as ids,
|
|
||||||
MAX(id) as keep_id
|
|
||||||
FROM numerology_readings
|
|
||||||
GROUP BY name, reading_type, user_id
|
|
||||||
HAVING COUNT(*) > 1
|
|
||||||
ORDER BY count DESC
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
console.log(`找到 ${duplicateGroups.length} 个重复记录组`);
|
|
||||||
|
|
||||||
let totalDuplicates = 0;
|
|
||||||
let totalDeleted = 0;
|
|
||||||
let aiRecordsUpdated = 0;
|
|
||||||
|
|
||||||
// 2. 处理每个重复组
|
|
||||||
for (const group of duplicateGroups) {
|
|
||||||
const ids = group.ids.split(',').map(id => parseInt(id));
|
|
||||||
const keepId = group.keep_id; // 保留最新的记录
|
|
||||||
const deleteIds = ids.filter(id => id !== keepId);
|
|
||||||
|
|
||||||
totalDuplicates += (group.count - 1);
|
|
||||||
|
|
||||||
console.log(`\n处理组: ${group.name} (${group.reading_type})`);
|
|
||||||
console.log(` 总记录: ${group.count}条`);
|
|
||||||
console.log(` 保留记录: ID ${keepId}`);
|
|
||||||
console.log(` 删除记录: ${deleteIds.join(', ')}`);
|
|
||||||
|
|
||||||
// 3. 更新AI解读记录的关联
|
|
||||||
for (const deleteId of deleteIds) {
|
|
||||||
const aiRecords = db.prepare(`
|
|
||||||
SELECT id FROM ai_interpretations
|
|
||||||
WHERE analysis_id = ? AND user_id = ?
|
|
||||||
`).all(deleteId.toString(), group.user_id);
|
|
||||||
|
|
||||||
if (aiRecords.length > 0) {
|
|
||||||
console.log(` 发现 ${aiRecords.length} 条AI解读记录需要重新关联`);
|
|
||||||
|
|
||||||
// 检查目标记录是否已有AI解读
|
|
||||||
const existingAI = db.prepare(`
|
|
||||||
SELECT id FROM ai_interpretations
|
|
||||||
WHERE analysis_id = ? AND user_id = ?
|
|
||||||
`).get(keepId.toString(), group.user_id);
|
|
||||||
|
|
||||||
if (existingAI) {
|
|
||||||
console.log(` 目标记录已有AI解读,删除重复的AI解读记录`);
|
|
||||||
// 删除重复的AI解读记录
|
|
||||||
const deleteAI = db.prepare(`
|
|
||||||
DELETE FROM ai_interpretations
|
|
||||||
WHERE analysis_id = ? AND user_id = ?
|
|
||||||
`);
|
|
||||||
deleteAI.run(deleteId.toString(), group.user_id);
|
|
||||||
} else {
|
|
||||||
console.log(` 重新关联AI解读记录到保留的记录`);
|
|
||||||
// 更新AI解读记录的analysis_id
|
|
||||||
const updateAI = db.prepare(`
|
|
||||||
UPDATE ai_interpretations
|
|
||||||
SET analysis_id = ?
|
|
||||||
WHERE analysis_id = ? AND user_id = ?
|
|
||||||
`);
|
|
||||||
const result = updateAI.run(keepId.toString(), deleteId.toString(), group.user_id);
|
|
||||||
aiRecordsUpdated += result.changes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 删除重复的历史记录
|
|
||||||
if (deleteIds.length > 0) {
|
|
||||||
const deleteStmt = db.prepare(`
|
|
||||||
DELETE FROM numerology_readings
|
|
||||||
WHERE id IN (${deleteIds.map(() => '?').join(',')})
|
|
||||||
`);
|
|
||||||
const result = deleteStmt.run(...deleteIds);
|
|
||||||
totalDeleted += result.changes;
|
|
||||||
console.log(` 删除了 ${result.changes} 条重复记录`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 清理无法关联的AI解读记录
|
|
||||||
console.log('\n5. 清理无法关联的AI解读记录...');
|
|
||||||
const orphanedAI = db.prepare(`
|
|
||||||
SELECT ai.id, ai.analysis_id, ai.analysis_type
|
|
||||||
FROM ai_interpretations ai
|
|
||||||
LEFT JOIN numerology_readings nr ON ai.analysis_id = nr.id AND ai.user_id = nr.user_id
|
|
||||||
WHERE nr.id IS NULL
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
if (orphanedAI.length > 0) {
|
|
||||||
console.log(`发现 ${orphanedAI.length} 条无法关联的AI解读记录`);
|
|
||||||
orphanedAI.forEach(record => {
|
|
||||||
console.log(` AI记录 ${record.id}: analysis_id=${record.analysis_id}, type=${record.analysis_type}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 删除无法关联的AI解读记录
|
|
||||||
const deleteOrphanedAI = db.prepare(`
|
|
||||||
DELETE FROM ai_interpretations
|
|
||||||
WHERE id IN (${orphanedAI.map(() => '?').join(',')})
|
|
||||||
`);
|
|
||||||
const result = deleteOrphanedAI.run(...orphanedAI.map(r => r.id));
|
|
||||||
console.log(`删除了 ${result.changes} 条无法关联的AI解读记录`);
|
|
||||||
} else {
|
|
||||||
console.log('没有发现无法关联的AI解读记录');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交事务
|
|
||||||
db.exec('COMMIT');
|
|
||||||
|
|
||||||
// 6. 输出清理结果
|
|
||||||
console.log('\n=== 清理完成 ===');
|
|
||||||
console.log(`处理的重复组: ${duplicateGroups.length}`);
|
|
||||||
console.log(`总重复记录: ${totalDuplicates}`);
|
|
||||||
console.log(`删除的记录: ${totalDeleted}`);
|
|
||||||
console.log(`更新的AI解读: ${aiRecordsUpdated}`);
|
|
||||||
console.log(`删除的孤立AI解读: ${orphanedAI.length}`);
|
|
||||||
|
|
||||||
// 7. 验证清理结果
|
|
||||||
console.log('\n=== 验证清理结果 ===');
|
|
||||||
const remainingDuplicates = db.prepare(`
|
|
||||||
SELECT COUNT(*) as count
|
|
||||||
FROM (
|
|
||||||
SELECT name, reading_type, user_id, COUNT(*) as cnt
|
|
||||||
FROM numerology_readings
|
|
||||||
GROUP BY name, reading_type, user_id
|
|
||||||
HAVING COUNT(*) > 1
|
|
||||||
)
|
|
||||||
`).get();
|
|
||||||
|
|
||||||
console.log(`剩余重复记录组: ${remainingDuplicates.count}`);
|
|
||||||
|
|
||||||
const totalRecords = db.prepare(`
|
|
||||||
SELECT COUNT(*) as count FROM numerology_readings
|
|
||||||
`).get();
|
|
||||||
|
|
||||||
console.log(`当前历史记录总数: ${totalRecords.count}`);
|
|
||||||
|
|
||||||
const totalAI = db.prepare(`
|
|
||||||
SELECT COUNT(*) as count FROM ai_interpretations
|
|
||||||
`).get();
|
|
||||||
|
|
||||||
console.log(`当前AI解读记录总数: ${totalAI.count}`);
|
|
||||||
|
|
||||||
const matchedAI = db.prepare(`
|
|
||||||
SELECT COUNT(*) as count
|
|
||||||
FROM ai_interpretations ai
|
|
||||||
JOIN numerology_readings nr ON ai.analysis_id = nr.id AND ai.user_id = nr.user_id
|
|
||||||
`).get();
|
|
||||||
|
|
||||||
console.log(`成功关联的AI解读: ${matchedAI.count}`);
|
|
||||||
console.log(`AI解读关联率: ${((matchedAI.count / totalAI.count) * 100).toFixed(1)}%`);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
// 回滚事务
|
|
||||||
db.exec('ROLLBACK');
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('清理过程中发生错误:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果直接运行此脚本
|
|
||||||
if (require.main === module) {
|
|
||||||
try {
|
|
||||||
cleanupDuplicateRecords();
|
|
||||||
console.log('\n✅ 数据清理成功完成');
|
|
||||||
process.exit(0);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('\n❌ 数据清理失败:', error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { cleanupDuplicateRecords };
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
/**
|
|
||||||
* 调试AI解读按钮状态的脚本
|
|
||||||
* 检查AI配置和recordId传递情况
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 模拟前端AI配置检查
|
|
||||||
function checkAIConfig() {
|
|
||||||
console.log('=== AI配置检查 ===');
|
|
||||||
|
|
||||||
// 模拟默认配置
|
|
||||||
const defaultConfig = {
|
|
||||||
apiKey: 'dee444451bdf4232920a88ef430ce753.Z4SAbECrSnf5JMq7',
|
|
||||||
apiUrl: 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
|
|
||||||
modelName: 'GLM-4.5',
|
|
||||||
maxTokens: 50000,
|
|
||||||
temperature: 0.6,
|
|
||||||
timeout: 120000,
|
|
||||||
stream: true
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('默认AI配置:');
|
|
||||||
console.log(JSON.stringify(defaultConfig, null, 2));
|
|
||||||
|
|
||||||
// 验证配置
|
|
||||||
const isValid = !!(defaultConfig.apiKey && defaultConfig.apiUrl && defaultConfig.modelName);
|
|
||||||
console.log(`AI配置是否有效: ${isValid}`);
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查数据库中的记录和AI解读状态
|
|
||||||
function checkRecordStatus() {
|
|
||||||
try {
|
|
||||||
const { getDB } = require('./server/database/index.cjs');
|
|
||||||
const { dbManager } = require('./server/database/index.cjs');
|
|
||||||
|
|
||||||
dbManager.init();
|
|
||||||
const db = getDB();
|
|
||||||
|
|
||||||
console.log('\n=== 数据库记录状态检查 ===');
|
|
||||||
|
|
||||||
// 检查最近的历史记录
|
|
||||||
const recentRecords = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
reading_type,
|
|
||||||
created_at,
|
|
||||||
datetime(created_at, 'localtime') as local_time
|
|
||||||
FROM numerology_readings
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 10
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
console.log('\n最近的历史记录:');
|
|
||||||
recentRecords.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. ID: ${record.id}, name: ${record.name}, type: ${record.reading_type}, created: ${record.local_time}`);
|
|
||||||
|
|
||||||
// 检查是否有对应的AI解读
|
|
||||||
const aiRecord = db.prepare(`
|
|
||||||
SELECT id, success FROM ai_interpretations
|
|
||||||
WHERE analysis_id = ? AND user_id = (
|
|
||||||
SELECT user_id FROM numerology_readings WHERE id = ?
|
|
||||||
)
|
|
||||||
`).get(record.id.toString(), record.id);
|
|
||||||
|
|
||||||
if (aiRecord) {
|
|
||||||
console.log(` → 有AI解读记录 (ID: ${aiRecord.id}, success: ${aiRecord.success})`);
|
|
||||||
} else {
|
|
||||||
console.log(` → 无AI解读记录`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 检查AI解读记录
|
|
||||||
const aiRecords = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
analysis_id,
|
|
||||||
analysis_type,
|
|
||||||
success,
|
|
||||||
created_at,
|
|
||||||
datetime(created_at, 'localtime') as local_time
|
|
||||||
FROM ai_interpretations
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 5
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
console.log('\n最近的AI解读记录:');
|
|
||||||
aiRecords.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. ID: ${record.id}, analysis_id: ${record.analysis_id}, type: ${record.analysis_type}, success: ${record.success}, created: ${record.local_time}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return { recentRecords, aiRecords };
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('检查数据库记录时发生错误:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟AI解读按钮状态检查
|
|
||||||
function simulateButtonStatus(recordId, hasAIConfig) {
|
|
||||||
console.log('\n=== AI解读按钮状态模拟 ===');
|
|
||||||
|
|
||||||
const hasValidId = !!recordId;
|
|
||||||
const isConfigValid = hasAIConfig;
|
|
||||||
|
|
||||||
console.log(`recordId: ${recordId}`);
|
|
||||||
console.log(`hasValidId: ${hasValidId}`);
|
|
||||||
console.log(`isConfigValid: ${isConfigValid}`);
|
|
||||||
|
|
||||||
// 模拟按钮禁用逻辑
|
|
||||||
const isDisabled = !isConfigValid || !hasValidId;
|
|
||||||
|
|
||||||
console.log(`按钮是否禁用: ${isDisabled}`);
|
|
||||||
|
|
||||||
if (isDisabled) {
|
|
||||||
console.log('禁用原因:');
|
|
||||||
if (!isConfigValid) {
|
|
||||||
console.log(' - AI配置无效');
|
|
||||||
}
|
|
||||||
if (!hasValidId) {
|
|
||||||
console.log(' - 缺少有效的recordId');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('按钮应该可以点击');
|
|
||||||
}
|
|
||||||
|
|
||||||
return !isDisabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 主函数
|
|
||||||
function debugAIButtonStatus() {
|
|
||||||
console.log('=== AI解读按钮状态调试 ===\n');
|
|
||||||
|
|
||||||
// 1. 检查AI配置
|
|
||||||
const isAIConfigValid = checkAIConfig();
|
|
||||||
|
|
||||||
// 2. 检查数据库记录
|
|
||||||
const dbStatus = checkRecordStatus();
|
|
||||||
|
|
||||||
if (dbStatus && dbStatus.recentRecords.length > 0) {
|
|
||||||
// 3. 模拟最新记录的按钮状态
|
|
||||||
const latestRecord = dbStatus.recentRecords[0];
|
|
||||||
console.log(`\n=== 模拟最新记录 (ID: ${latestRecord.id}) 的按钮状态 ===`);
|
|
||||||
|
|
||||||
const canClick = simulateButtonStatus(latestRecord.id, isAIConfigValid);
|
|
||||||
|
|
||||||
console.log('\n=== 总结 ===');
|
|
||||||
console.log(`最新记录: ${latestRecord.name} (${latestRecord.reading_type})`);
|
|
||||||
console.log(`记录ID: ${latestRecord.id}`);
|
|
||||||
console.log(`AI配置有效: ${isAIConfigValid}`);
|
|
||||||
console.log(`AI解读按钮可点击: ${canClick}`);
|
|
||||||
|
|
||||||
if (!canClick) {
|
|
||||||
console.log('\n🔧 解决建议:');
|
|
||||||
if (!isAIConfigValid) {
|
|
||||||
console.log('1. 检查AI配置是否正确设置');
|
|
||||||
console.log('2. 确认API密钥、URL和模型名称都已配置');
|
|
||||||
}
|
|
||||||
if (!latestRecord.id) {
|
|
||||||
console.log('1. 检查分析结果保存逻辑');
|
|
||||||
console.log('2. 确认recordId正确传递给AI解读按钮');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('\n❌ 没有找到历史记录,请先进行分析');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果直接运行此脚本
|
|
||||||
if (require.main === module) {
|
|
||||||
try {
|
|
||||||
debugAIButtonStatus();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('调试过程中发生错误:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { debugAIButtonStatus };
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
const { getDB } = require('./server/database/index.cjs');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调试AI解读记录匹配问题
|
|
||||||
* 检查历史记录与AI解读记录的ID匹配情况
|
|
||||||
*/
|
|
||||||
function debugAIInterpretationMatching() {
|
|
||||||
try {
|
|
||||||
// 初始化数据库连接
|
|
||||||
const { dbManager } = require('./server/database/index.cjs');
|
|
||||||
dbManager.init();
|
|
||||||
const db = getDB();
|
|
||||||
|
|
||||||
console.log('=== AI解读记录匹配调试 ===\n');
|
|
||||||
|
|
||||||
// 1. 获取最近的历史记录
|
|
||||||
console.log('1. 最近的历史记录:');
|
|
||||||
const recentRecords = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
reading_type,
|
|
||||||
created_at,
|
|
||||||
datetime(created_at, 'localtime') as local_time
|
|
||||||
FROM numerology_readings
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 10
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
recentRecords.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. ID: ${record.id} (${typeof record.id}), name: ${record.name}, type: ${record.reading_type}, created: ${record.local_time}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. 获取所有AI解读记录
|
|
||||||
console.log('\n2. 所有AI解读记录:');
|
|
||||||
const aiRecords = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
analysis_id,
|
|
||||||
analysis_type,
|
|
||||||
success,
|
|
||||||
created_at,
|
|
||||||
datetime(created_at, 'localtime') as local_time
|
|
||||||
FROM ai_interpretations
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
aiRecords.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. AI_ID: ${record.id}, analysis_id: ${record.analysis_id} (${typeof record.analysis_id}), type: ${record.analysis_type}, success: ${record.success}, created: ${record.local_time}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 检查每个历史记录是否有对应的AI解读
|
|
||||||
console.log('\n3. 历史记录与AI解读匹配检查:');
|
|
||||||
recentRecords.forEach((record, index) => {
|
|
||||||
const recordIdStr = record.id.toString();
|
|
||||||
const recordIdNum = parseInt(record.id);
|
|
||||||
|
|
||||||
// 尝试字符串匹配
|
|
||||||
const aiByString = db.prepare(`
|
|
||||||
SELECT id, analysis_id, success FROM ai_interpretations
|
|
||||||
WHERE analysis_id = ?
|
|
||||||
`).get(recordIdStr);
|
|
||||||
|
|
||||||
// 尝试数字匹配
|
|
||||||
const aiByNumber = db.prepare(`
|
|
||||||
SELECT id, analysis_id, success FROM ai_interpretations
|
|
||||||
WHERE analysis_id = ?
|
|
||||||
`).get(recordIdNum);
|
|
||||||
|
|
||||||
console.log(` ${index + 1}. 历史记录 ${record.id}:`);
|
|
||||||
console.log(` - 字符串匹配 '${recordIdStr}': ${aiByString ? `找到 AI_ID=${aiByString.id}` : '未找到'}`);
|
|
||||||
console.log(` - 数字匹配 ${recordIdNum}: ${aiByNumber ? `找到 AI_ID=${aiByNumber.id}` : '未找到'}`);
|
|
||||||
|
|
||||||
if (aiByString || aiByNumber) {
|
|
||||||
const ai = aiByString || aiByNumber;
|
|
||||||
console.log(` → 有AI解读: analysis_id=${ai.analysis_id}, success=${ai.success}`);
|
|
||||||
} else {
|
|
||||||
console.log(` → 无AI解读记录`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. 检查AI解读记录是否有对应的历史记录
|
|
||||||
console.log('\n4. AI解读记录与历史记录匹配检查:');
|
|
||||||
aiRecords.forEach((aiRecord, index) => {
|
|
||||||
const analysisIdStr = aiRecord.analysis_id.toString();
|
|
||||||
const analysisIdNum = parseInt(aiRecord.analysis_id);
|
|
||||||
|
|
||||||
// 尝试字符串匹配
|
|
||||||
const historyByString = db.prepare(`
|
|
||||||
SELECT id, name, reading_type FROM numerology_readings
|
|
||||||
WHERE id = ?
|
|
||||||
`).get(analysisIdStr);
|
|
||||||
|
|
||||||
// 尝试数字匹配
|
|
||||||
const historyByNumber = db.prepare(`
|
|
||||||
SELECT id, name, reading_type FROM numerology_readings
|
|
||||||
WHERE id = ?
|
|
||||||
`).get(analysisIdNum);
|
|
||||||
|
|
||||||
console.log(` ${index + 1}. AI解读 analysis_id=${aiRecord.analysis_id}:`);
|
|
||||||
console.log(` - 字符串匹配 '${analysisIdStr}': ${historyByString ? `找到 ${historyByString.name} (${historyByString.reading_type})` : '未找到'}`);
|
|
||||||
console.log(` - 数字匹配 ${analysisIdNum}: ${historyByNumber ? `找到 ${historyByNumber.name} (${historyByNumber.reading_type})` : '未找到'}`);
|
|
||||||
|
|
||||||
if (!historyByString && !historyByNumber) {
|
|
||||||
console.log(` → 孤立的AI解读记录,无对应历史记录`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 5. 模拟前端API调用
|
|
||||||
console.log('\n5. 模拟前端API调用:');
|
|
||||||
const testIds = recentRecords.slice(0, 3).map(r => r.id);
|
|
||||||
|
|
||||||
testIds.forEach(id => {
|
|
||||||
const idStr = id.toString();
|
|
||||||
console.log(`\n 测试ID: ${id} (string: '${idStr}')`);
|
|
||||||
|
|
||||||
// 模拟API调用逻辑
|
|
||||||
const aiRecord = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
ai.id,
|
|
||||||
ai.analysis_id,
|
|
||||||
ai.success,
|
|
||||||
ai.content,
|
|
||||||
ai.created_at,
|
|
||||||
ai.model,
|
|
||||||
ai.tokens_used,
|
|
||||||
ai.error_message,
|
|
||||||
nr.name,
|
|
||||||
nr.reading_type
|
|
||||||
FROM ai_interpretations ai
|
|
||||||
LEFT JOIN numerology_readings nr ON ai.analysis_id = nr.id
|
|
||||||
WHERE ai.analysis_id = ? AND ai.user_id = (
|
|
||||||
SELECT user_id FROM numerology_readings WHERE id = ?
|
|
||||||
)
|
|
||||||
`).get(idStr, id);
|
|
||||||
|
|
||||||
if (aiRecord) {
|
|
||||||
console.log(` ✅ API会返回: success=${aiRecord.success}, content长度=${aiRecord.content?.length || 0}`);
|
|
||||||
console.log(` 关联记录: ${aiRecord.name} (${aiRecord.reading_type})`);
|
|
||||||
} else {
|
|
||||||
console.log(` ❌ API会返回: 404 Not Found`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 6. 总结
|
|
||||||
console.log('\n=== 总结 ===');
|
|
||||||
const totalHistory = recentRecords.length;
|
|
||||||
const totalAI = aiRecords.length;
|
|
||||||
const matchedCount = recentRecords.filter(record => {
|
|
||||||
const recordIdStr = record.id.toString();
|
|
||||||
const recordIdNum = parseInt(record.id);
|
|
||||||
const aiByString = db.prepare(`SELECT id FROM ai_interpretations WHERE analysis_id = ?`).get(recordIdStr);
|
|
||||||
const aiByNumber = db.prepare(`SELECT id FROM ai_interpretations WHERE analysis_id = ?`).get(recordIdNum);
|
|
||||||
return aiByString || aiByNumber;
|
|
||||||
}).length;
|
|
||||||
|
|
||||||
console.log(`历史记录总数: ${totalHistory}`);
|
|
||||||
console.log(`AI解读记录总数: ${totalAI}`);
|
|
||||||
console.log(`匹配成功数: ${matchedCount}`);
|
|
||||||
console.log(`匹配率: ${((matchedCount / totalHistory) * 100).toFixed(1)}%`);
|
|
||||||
|
|
||||||
if (matchedCount === 0) {
|
|
||||||
console.log('\n⚠️ 没有任何匹配的记录,可能的原因:');
|
|
||||||
console.log('1. AI解读记录的analysis_id与历史记录的id不匹配');
|
|
||||||
console.log('2. 数据类型不一致(字符串 vs 数字)');
|
|
||||||
console.log('3. AI解读记录被意外删除或损坏');
|
|
||||||
} else if (matchedCount < totalHistory) {
|
|
||||||
console.log(`\n⚠️ 部分记录不匹配,${totalHistory - matchedCount}条历史记录没有AI解读`);
|
|
||||||
} else {
|
|
||||||
console.log('\n✅ 所有记录都有对应的AI解读');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('调试过程中发生错误:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果直接运行此脚本
|
|
||||||
if (require.main === module) {
|
|
||||||
try {
|
|
||||||
debugAIInterpretationMatching();
|
|
||||||
console.log('\n✅ 调试完成');
|
|
||||||
process.exit(0);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('\n❌ 调试失败:', error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { debugAIInterpretationMatching };
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
const { getDB } = require('./server/database/index.cjs');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调试AI解读记录与历史记录匹配问题的脚本
|
|
||||||
*/
|
|
||||||
function debugAIInterpretations() {
|
|
||||||
try {
|
|
||||||
// 初始化数据库连接
|
|
||||||
const { dbManager } = require('./server/database/index.cjs');
|
|
||||||
dbManager.init();
|
|
||||||
const db = getDB();
|
|
||||||
|
|
||||||
console.log('=== AI解读记录与历史记录匹配分析 ===\n');
|
|
||||||
|
|
||||||
// 1. 查看所有AI解读记录
|
|
||||||
console.log('1. 所有AI解读记录:');
|
|
||||||
const aiInterpretations = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
analysis_id,
|
|
||||||
analysis_type,
|
|
||||||
created_at,
|
|
||||||
success
|
|
||||||
FROM ai_interpretations
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
console.log(`总共有 ${aiInterpretations.length} 条AI解读记录`);
|
|
||||||
aiInterpretations.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. ID: ${record.id}, analysis_id: ${record.analysis_id}, type: ${record.analysis_type}, created: ${record.created_at}, success: ${record.success}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n2. 所有历史记录:');
|
|
||||||
const historyRecords = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
reading_type,
|
|
||||||
created_at
|
|
||||||
FROM numerology_readings
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
console.log(`总共有 ${historyRecords.length} 条历史记录`);
|
|
||||||
historyRecords.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. ID: ${record.id}, name: ${record.name}, type: ${record.reading_type}, created: ${record.created_at}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n3. 匹配分析:');
|
|
||||||
|
|
||||||
// 3.1 数字ID匹配分析
|
|
||||||
console.log('\n3.1 数字ID匹配分析:');
|
|
||||||
const numericMatches = [];
|
|
||||||
const numericMismatches = [];
|
|
||||||
|
|
||||||
aiInterpretations.forEach(ai => {
|
|
||||||
const isNumeric = !isNaN(parseInt(ai.analysis_id)) && isFinite(ai.analysis_id);
|
|
||||||
if (isNumeric) {
|
|
||||||
const historyRecord = historyRecords.find(h => h.id.toString() === ai.analysis_id.toString());
|
|
||||||
if (historyRecord) {
|
|
||||||
numericMatches.push({ ai, history: historyRecord });
|
|
||||||
} else {
|
|
||||||
numericMismatches.push(ai);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`数字ID匹配成功: ${numericMatches.length} 条`);
|
|
||||||
numericMatches.forEach((match, index) => {
|
|
||||||
console.log(` ${index + 1}. AI记录ID: ${match.ai.id}, analysis_id: ${match.ai.analysis_id} → 历史记录: ${match.history.name} (${match.history.reading_type})`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`\n数字ID匹配失败: ${numericMismatches.length} 条`);
|
|
||||||
numericMismatches.forEach((mismatch, index) => {
|
|
||||||
console.log(` ${index + 1}. AI记录ID: ${mismatch.id}, analysis_id: ${mismatch.analysis_id} (找不到对应的历史记录)`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3.2 字符串ID分析
|
|
||||||
console.log('\n3.2 字符串ID分析:');
|
|
||||||
const stringIds = aiInterpretations.filter(ai => {
|
|
||||||
const isNumeric = !isNaN(parseInt(ai.analysis_id)) && isFinite(ai.analysis_id);
|
|
||||||
return !isNumeric;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`字符串ID记录: ${stringIds.length} 条`);
|
|
||||||
stringIds.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. AI记录ID: ${record.id}, analysis_id: ${record.analysis_id}, type: ${record.analysis_type}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3.3 没有AI解读的历史记录
|
|
||||||
console.log('\n3.3 没有AI解读的历史记录:');
|
|
||||||
const recordsWithoutAI = [];
|
|
||||||
|
|
||||||
historyRecords.forEach(history => {
|
|
||||||
const hasAI = aiInterpretations.some(ai => {
|
|
||||||
const isNumeric = !isNaN(parseInt(ai.analysis_id)) && isFinite(ai.analysis_id);
|
|
||||||
if (isNumeric) {
|
|
||||||
return ai.analysis_id.toString() === history.id.toString();
|
|
||||||
}
|
|
||||||
return false; // 字符串ID暂时不匹配
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!hasAI) {
|
|
||||||
recordsWithoutAI.push(history);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`没有AI解读的历史记录: ${recordsWithoutAI.length} 条`);
|
|
||||||
recordsWithoutAI.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. 历史记录ID: ${record.id}, name: ${record.name}, type: ${record.reading_type}, created: ${record.created_at}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. 问题总结
|
|
||||||
console.log('\n=== 问题总结 ===');
|
|
||||||
console.log(`1. 总AI解读记录: ${aiInterpretations.length}`);
|
|
||||||
console.log(`2. 总历史记录: ${historyRecords.length}`);
|
|
||||||
console.log(`3. 数字ID匹配成功: ${numericMatches.length}`);
|
|
||||||
console.log(`4. 数字ID匹配失败: ${numericMismatches.length}`);
|
|
||||||
console.log(`5. 字符串ID记录: ${stringIds.length}`);
|
|
||||||
console.log(`6. 没有AI解读的历史记录: ${recordsWithoutAI.length}`);
|
|
||||||
|
|
||||||
// 5. 可能的问题原因
|
|
||||||
console.log('\n=== 可能的问题原因 ===');
|
|
||||||
if (numericMismatches.length > 0) {
|
|
||||||
console.log('1. 数字ID不匹配问题:');
|
|
||||||
console.log(' - AI解读记录的analysis_id指向不存在的历史记录');
|
|
||||||
console.log(' - 可能是历史记录被删除,但AI解读记录仍然存在');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stringIds.length > 0) {
|
|
||||||
console.log('2. 字符串ID问题:');
|
|
||||||
console.log(' - 这些AI解读使用了生成的字符串ID(如"yijing-1234567890")');
|
|
||||||
console.log(' - 前端查询时无法找到对应的历史记录');
|
|
||||||
console.log(' - 需要改进匹配逻辑或数据关联方式');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recordsWithoutAI.length > 0) {
|
|
||||||
console.log('3. 历史记录缺少AI解读:');
|
|
||||||
console.log(' - 这些记录可能是在AI解读功能实现之前创建的');
|
|
||||||
console.log(' - 或者AI解读生成失败但历史记录保存成功');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n=== 建议的解决方案 ===');
|
|
||||||
console.log('1. 对于数字ID不匹配: 清理无效的AI解读记录');
|
|
||||||
console.log('2. 对于字符串ID: 改进前端匹配逻辑,支持字符串ID查询');
|
|
||||||
console.log('3. 对于缺少AI解读的历史记录: 可以提供重新生成AI解读的功能');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('调试过程中发生错误:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果直接运行此脚本
|
|
||||||
if (require.main === module) {
|
|
||||||
debugAIInterpretations();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { debugAIInterpretations };
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
const { getDB } = require('./server/database/index.cjs');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调试重复记录问题的脚本
|
|
||||||
* 检查是否存在AI解读后创建新历史记录的情况
|
|
||||||
*/
|
|
||||||
function debugDuplicateRecords() {
|
|
||||||
try {
|
|
||||||
// 初始化数据库连接
|
|
||||||
const { dbManager } = require('./server/database/index.cjs');
|
|
||||||
dbManager.init();
|
|
||||||
const db = getDB();
|
|
||||||
|
|
||||||
console.log('=== 调试重复记录问题 ===\n');
|
|
||||||
|
|
||||||
// 1. 检查最近的历史记录
|
|
||||||
console.log('1. 最近的历史记录 (按创建时间排序):');
|
|
||||||
const recentRecords = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
reading_type,
|
|
||||||
created_at,
|
|
||||||
datetime(created_at, 'localtime') as local_time
|
|
||||||
FROM numerology_readings
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 20
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
recentRecords.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. ID: ${record.id}, name: ${record.name}, type: ${record.reading_type}, created: ${record.local_time}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. 检查AI解读记录
|
|
||||||
console.log('\n2. AI解读记录:');
|
|
||||||
const aiRecords = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
analysis_id,
|
|
||||||
analysis_type,
|
|
||||||
created_at,
|
|
||||||
datetime(created_at, 'localtime') as local_time
|
|
||||||
FROM ai_interpretations
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
aiRecords.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. ID: ${record.id}, analysis_id: ${record.analysis_id}, type: ${record.analysis_type}, created: ${record.local_time}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 分析可能的重复模式
|
|
||||||
console.log('\n3. 分析重复模式:');
|
|
||||||
|
|
||||||
// 按名称和类型分组,查找可能的重复记录
|
|
||||||
const duplicateGroups = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
name,
|
|
||||||
reading_type,
|
|
||||||
COUNT(*) as count,
|
|
||||||
GROUP_CONCAT(id) as ids,
|
|
||||||
GROUP_CONCAT(datetime(created_at, 'localtime')) as times
|
|
||||||
FROM numerology_readings
|
|
||||||
GROUP BY name, reading_type
|
|
||||||
HAVING COUNT(*) > 1
|
|
||||||
ORDER BY count DESC, MAX(created_at) DESC
|
|
||||||
LIMIT 10
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
if (duplicateGroups.length > 0) {
|
|
||||||
console.log(' 发现可能的重复记录组:');
|
|
||||||
duplicateGroups.forEach((group, index) => {
|
|
||||||
console.log(` ${index + 1}. ${group.name} (${group.reading_type}): ${group.count}条记录`);
|
|
||||||
console.log(` IDs: ${group.ids}`);
|
|
||||||
console.log(` 创建时间: ${group.times}`);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(' 未发现明显的重复记录组');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 检查时间相近的记录
|
|
||||||
console.log('\n4. 检查时间相近的记录 (5分钟内):');
|
|
||||||
const closeTimeRecords = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
r1.id as id1,
|
|
||||||
r1.name as name1,
|
|
||||||
r1.reading_type as type1,
|
|
||||||
r1.created_at as time1,
|
|
||||||
r2.id as id2,
|
|
||||||
r2.name as name2,
|
|
||||||
r2.reading_type as type2,
|
|
||||||
r2.created_at as time2,
|
|
||||||
ABS(strftime('%s', r1.created_at) - strftime('%s', r2.created_at)) as time_diff_seconds
|
|
||||||
FROM numerology_readings r1
|
|
||||||
JOIN numerology_readings r2 ON r1.id < r2.id
|
|
||||||
WHERE r1.name = r2.name
|
|
||||||
AND r1.reading_type = r2.reading_type
|
|
||||||
AND ABS(strftime('%s', r1.created_at) - strftime('%s', r2.created_at)) <= 300
|
|
||||||
ORDER BY time_diff_seconds ASC
|
|
||||||
LIMIT 10
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
if (closeTimeRecords.length > 0) {
|
|
||||||
console.log(' 发现时间相近的相似记录:');
|
|
||||||
closeTimeRecords.forEach((pair, index) => {
|
|
||||||
console.log(` ${index + 1}. ${pair.name1} (${pair.type1}):`);
|
|
||||||
console.log(` 记录1: ID ${pair.id1}, 时间: ${pair.time1}`);
|
|
||||||
console.log(` 记录2: ID ${pair.id2}, 时间: ${pair.time2}`);
|
|
||||||
console.log(` 时间差: ${pair.time_diff_seconds}秒`);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(' 未发现时间相近的相似记录');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 检查AI解读与历史记录的关联情况
|
|
||||||
console.log('\n5. AI解读与历史记录关联分析:');
|
|
||||||
|
|
||||||
// 数字ID关联
|
|
||||||
const numericMatches = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
ai.id as ai_id,
|
|
||||||
ai.analysis_id,
|
|
||||||
ai.analysis_type,
|
|
||||||
nr.id as record_id,
|
|
||||||
nr.name,
|
|
||||||
nr.reading_type,
|
|
||||||
datetime(ai.created_at, 'localtime') as ai_time,
|
|
||||||
datetime(nr.created_at, 'localtime') as record_time
|
|
||||||
FROM ai_interpretations ai
|
|
||||||
JOIN numerology_readings nr ON ai.analysis_id = nr.id AND ai.user_id = nr.user_id
|
|
||||||
ORDER BY ai.created_at DESC
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
console.log(` 数字ID成功关联: ${numericMatches.length}条`);
|
|
||||||
numericMatches.forEach((match, index) => {
|
|
||||||
console.log(` ${index + 1}. AI记录${match.ai_id} → 历史记录${match.record_id} (${match.name}, ${match.reading_type})`);
|
|
||||||
console.log(` AI创建: ${match.ai_time}, 记录创建: ${match.record_time}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 字符串ID(无法关联)
|
|
||||||
const stringIdRecords = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
ai.id as ai_id,
|
|
||||||
ai.analysis_id,
|
|
||||||
ai.analysis_type,
|
|
||||||
datetime(ai.created_at, 'localtime') as ai_time
|
|
||||||
FROM ai_interpretations ai
|
|
||||||
LEFT JOIN numerology_readings nr ON ai.analysis_id = nr.id AND ai.user_id = nr.user_id
|
|
||||||
WHERE nr.id IS NULL
|
|
||||||
ORDER BY ai.created_at DESC
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
console.log(`\n 字符串ID无法关联: ${stringIdRecords.length}条`);
|
|
||||||
stringIdRecords.forEach((record, index) => {
|
|
||||||
console.log(` ${index + 1}. AI记录${record.ai_id}, analysis_id: ${record.analysis_id}, type: ${record.analysis_type}`);
|
|
||||||
console.log(` 创建时间: ${record.ai_time}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 6. 总结分析
|
|
||||||
console.log('\n=== 问题分析总结 ===');
|
|
||||||
|
|
||||||
const totalRecords = recentRecords.length;
|
|
||||||
const totalAI = aiRecords.length;
|
|
||||||
const duplicateCount = duplicateGroups.reduce((sum, group) => sum + (group.count - 1), 0);
|
|
||||||
|
|
||||||
console.log(`历史记录总数: ${totalRecords}`);
|
|
||||||
console.log(`AI解读记录总数: ${totalAI}`);
|
|
||||||
console.log(`可能的重复记录: ${duplicateCount}条`);
|
|
||||||
console.log(`成功关联的AI解读: ${numericMatches.length}条`);
|
|
||||||
console.log(`无法关联的AI解读: ${stringIdRecords.length}条`);
|
|
||||||
|
|
||||||
if (stringIdRecords.length > 0) {
|
|
||||||
console.log('\n⚠️ 发现问题:');
|
|
||||||
console.log(' - 存在无法关联到历史记录的AI解读');
|
|
||||||
console.log(' - 这些AI解读使用了字符串ID,不对应任何历史记录');
|
|
||||||
console.log(' - 可能的原因:AI解读时没有正确的recordId');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duplicateGroups.length > 0) {
|
|
||||||
console.log('\n⚠️ 发现重复记录:');
|
|
||||||
console.log(' - 存在相同名称和类型的多条历史记录');
|
|
||||||
console.log(' - 可能的原因:用户多次提交相同的分析请求');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('调试过程中发生错误:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果直接运行此脚本
|
|
||||||
if (require.main === module) {
|
|
||||||
debugDuplicateRecords();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { debugDuplicateRecords };
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
const { getDB } = require('./server/database/index.cjs');
|
|
||||||
const { dbManager } = require('./server/database/index.cjs');
|
|
||||||
|
|
||||||
try {
|
|
||||||
dbManager.init();
|
|
||||||
const db = getDB();
|
|
||||||
|
|
||||||
console.log('=== 最近的分析记录 ===');
|
|
||||||
const recent = db.prepare('SELECT id, name, reading_type, created_at FROM numerology_readings ORDER BY created_at DESC LIMIT 5').all();
|
|
||||||
console.log(`总共 ${recent.length} 条分析记录`);
|
|
||||||
recent.forEach((r, i) => {
|
|
||||||
console.log(`${i+1}. ID: ${r.id}, 名称: ${r.name}, 类型: ${r.reading_type}, 创建时间: ${r.created_at}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n=== AI解读记录 ===');
|
|
||||||
const ai = db.prepare('SELECT id, reading_id, created_at FROM ai_interpretations ORDER BY created_at DESC LIMIT 5').all();
|
|
||||||
console.log(`总共 ${ai.length} 条AI解读记录`);
|
|
||||||
ai.forEach((r, i) => {
|
|
||||||
console.log(`${i+1}. AI_ID: ${r.id}, reading_id: ${r.reading_id}, 创建时间: ${r.created_at}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n=== 检查最新记录的AI解读状态 ===');
|
|
||||||
if (recent.length > 0) {
|
|
||||||
const latestRecord = recent[0];
|
|
||||||
const hasAI = db.prepare('SELECT COUNT(*) as count FROM ai_interpretations WHERE reading_id = ?').get(latestRecord.id);
|
|
||||||
console.log(`最新记录 ID: ${latestRecord.id} (${latestRecord.reading_type}) 是否有AI解读: ${hasAI.count > 0 ? '是' : '否'}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('调试失败:', error);
|
|
||||||
} finally {
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
@@ -80,8 +80,12 @@ class DatabaseManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 迁移ai_interpretations表结构
|
/**
|
||||||
|
* 迁移ai_interpretations表结构
|
||||||
|
* 将旧的analysis_id字段迁移为reading_id字段,建立正确的外键关系
|
||||||
|
*/
|
||||||
migrateAiInterpretationsTable() {
|
migrateAiInterpretationsTable() {
|
||||||
|
let transaction;
|
||||||
try {
|
try {
|
||||||
// 检查ai_interpretations表是否存在且使用旧的analysis_id字段
|
// 检查ai_interpretations表是否存在且使用旧的analysis_id字段
|
||||||
const tableInfo = this.db.prepare(`
|
const tableInfo = this.db.prepare(`
|
||||||
@@ -89,60 +93,100 @@ class DatabaseManager {
|
|||||||
WHERE type='table' AND name='ai_interpretations'
|
WHERE type='table' AND name='ai_interpretations'
|
||||||
`).get();
|
`).get();
|
||||||
|
|
||||||
if (tableInfo) {
|
if (!tableInfo) {
|
||||||
// 检查是否有analysis_id字段(旧结构)
|
console.log('ai_interpretations表不存在,跳过迁移');
|
||||||
const columnInfo = this.db.prepare(`
|
return;
|
||||||
PRAGMA table_info(ai_interpretations)
|
|
||||||
`).all();
|
|
||||||
|
|
||||||
const hasAnalysisId = columnInfo.some(col => col.name === 'analysis_id');
|
|
||||||
const hasReadingId = columnInfo.some(col => col.name === 'reading_id');
|
|
||||||
|
|
||||||
if (hasAnalysisId && !hasReadingId) {
|
|
||||||
console.log('检测到旧的ai_interpretations表结构,开始迁移...');
|
|
||||||
|
|
||||||
// 创建新表结构
|
|
||||||
this.db.exec(`
|
|
||||||
CREATE TABLE ai_interpretations_new (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
reading_id INTEGER NOT NULL,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
model TEXT,
|
|
||||||
tokens_used INTEGER,
|
|
||||||
success BOOLEAN DEFAULT 1,
|
|
||||||
error_message TEXT,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (reading_id) REFERENCES numerology_readings(id) ON DELETE CASCADE,
|
|
||||||
UNIQUE(reading_id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 迁移数据(只迁移数字ID的记录)
|
|
||||||
this.db.exec(`
|
|
||||||
INSERT INTO ai_interpretations_new
|
|
||||||
(user_id, reading_id, content, model, tokens_used, success, error_message, created_at, updated_at)
|
|
||||||
SELECT user_id, CAST(analysis_id AS INTEGER), content, model, tokens_used, success, error_message, created_at, updated_at
|
|
||||||
FROM ai_interpretations
|
|
||||||
WHERE analysis_id GLOB '[0-9]*'
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 删除旧表,重命名新表
|
|
||||||
this.db.exec('DROP TABLE ai_interpretations');
|
|
||||||
this.db.exec('ALTER TABLE ai_interpretations_new RENAME TO ai_interpretations');
|
|
||||||
|
|
||||||
// 重新创建索引
|
|
||||||
this.db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_user_id ON ai_interpretations(user_id)');
|
|
||||||
this.db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_reading_id ON ai_interpretations(reading_id)');
|
|
||||||
this.db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_created_at ON ai_interpretations(created_at DESC)');
|
|
||||||
|
|
||||||
console.log('ai_interpretations表迁移完成');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否有analysis_id字段(旧结构)
|
||||||
|
const columnInfo = this.db.prepare(`
|
||||||
|
PRAGMA table_info(ai_interpretations)
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
const hasAnalysisId = columnInfo.some(col => col.name === 'analysis_id');
|
||||||
|
const hasReadingId = columnInfo.some(col => col.name === 'reading_id');
|
||||||
|
|
||||||
|
if (!hasAnalysisId || hasReadingId) {
|
||||||
|
console.log('ai_interpretations表结构已是最新,跳过迁移');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('检测到旧的ai_interpretations表结构,开始迁移...');
|
||||||
|
|
||||||
|
// 开始事务
|
||||||
|
transaction = this.db.transaction(() => {
|
||||||
|
// 创建新表结构
|
||||||
|
this.db.exec(`
|
||||||
|
CREATE TABLE ai_interpretations_new (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
reading_id INTEGER NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
model TEXT,
|
||||||
|
tokens_used INTEGER,
|
||||||
|
success BOOLEAN DEFAULT 1,
|
||||||
|
error_message TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (reading_id) REFERENCES numerology_readings(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(reading_id)
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 检查有多少条记录需要迁移
|
||||||
|
const countStmt = this.db.prepare(`
|
||||||
|
SELECT COUNT(*) as count FROM ai_interpretations
|
||||||
|
WHERE analysis_id GLOB '[0-9]*'
|
||||||
|
`);
|
||||||
|
const { count } = countStmt.get();
|
||||||
|
console.log(`准备迁移 ${count} 条有效记录`);
|
||||||
|
|
||||||
|
// 迁移数据(只迁移数字ID的记录)
|
||||||
|
const migrateStmt = this.db.prepare(`
|
||||||
|
INSERT INTO ai_interpretations_new
|
||||||
|
(user_id, reading_id, content, model, tokens_used, success, error_message, created_at, updated_at)
|
||||||
|
SELECT user_id, CAST(analysis_id AS INTEGER), content, model, tokens_used, success, error_message, created_at, updated_at
|
||||||
|
FROM ai_interpretations
|
||||||
|
WHERE analysis_id GLOB '[0-9]*'
|
||||||
|
`);
|
||||||
|
const migrateResult = migrateStmt.run();
|
||||||
|
console.log(`成功迁移 ${migrateResult.changes} 条记录`);
|
||||||
|
|
||||||
|
// 删除旧表,重命名新表
|
||||||
|
this.db.exec('DROP TABLE ai_interpretations');
|
||||||
|
this.db.exec('ALTER TABLE ai_interpretations_new RENAME TO ai_interpretations');
|
||||||
|
|
||||||
|
// 重新创建索引
|
||||||
|
this.db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_user_id ON ai_interpretations(user_id)');
|
||||||
|
this.db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_reading_id ON ai_interpretations(reading_id)');
|
||||||
|
this.db.exec('CREATE INDEX IF NOT EXISTS idx_ai_interpretations_created_at ON ai_interpretations(created_at DESC)');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 执行事务
|
||||||
|
transaction();
|
||||||
|
console.log('ai_interpretations表迁移完成');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('ai_interpretations表迁移失败:', error);
|
console.error('ai_interpretations表迁移失败:', error);
|
||||||
|
console.error('错误详情:', error.message);
|
||||||
|
|
||||||
|
// 如果事务失败,尝试回滚(SQLite会自动回滚失败的事务)
|
||||||
|
try {
|
||||||
|
// 检查是否存在临时表,如果存在则清理
|
||||||
|
const tempTableCheck = this.db.prepare(`
|
||||||
|
SELECT name FROM sqlite_master
|
||||||
|
WHERE type='table' AND name='ai_interpretations_new'
|
||||||
|
`).get();
|
||||||
|
|
||||||
|
if (tempTableCheck) {
|
||||||
|
this.db.exec('DROP TABLE ai_interpretations_new');
|
||||||
|
console.log('已清理临时表');
|
||||||
|
}
|
||||||
|
} catch (cleanupError) {
|
||||||
|
console.error('清理临时表失败:', cleanupError);
|
||||||
|
}
|
||||||
|
|
||||||
// 迁移失败不应该阻止应用启动,只记录错误
|
// 迁移失败不应该阻止应用启动,只记录错误
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,26 @@ const AIEnhancedAnalysis = require('../services/common/AIEnhancedAnalysis.cjs');
|
|||||||
// 初始化AI增强分析服务
|
// 初始化AI增强分析服务
|
||||||
const aiEnhancedAnalysis = new AIEnhancedAnalysis();
|
const aiEnhancedAnalysis = new AIEnhancedAnalysis();
|
||||||
|
|
||||||
// 八字分析接口
|
/**
|
||||||
router.post('/bazi', authenticate, asyncHandler(async (req, res) => {
|
* 通用输入验证函数
|
||||||
const { birth_data } = req.body;
|
* @param {Object} birth_data - 出生数据
|
||||||
|
* @throws {AppError} 验证失败时抛出错误
|
||||||
|
*/
|
||||||
|
function validateBirthData(birth_data) {
|
||||||
|
if (!birth_data || typeof birth_data !== 'object') {
|
||||||
|
throw new AppError('缺少必要参数:出生数据', 400, 'MISSING_BIRTH_DATA');
|
||||||
|
}
|
||||||
|
|
||||||
// 输入验证
|
if (!birth_data.name || typeof birth_data.name !== 'string' || birth_data.name.trim().length === 0) {
|
||||||
if (!birth_data || !birth_data.name || !birth_data.birth_date) {
|
throw new AppError('缺少必要参数:姓名不能为空', 400, 'MISSING_NAME');
|
||||||
throw new AppError('缺少必要参数:姓名和出生日期', 400, 'MISSING_BIRTH_DATA');
|
}
|
||||||
|
|
||||||
|
if (birth_data.name.length > 50) {
|
||||||
|
throw new AppError('姓名长度不能超过50个字符', 400, 'NAME_TOO_LONG');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!birth_data.birth_date || typeof birth_data.birth_date !== 'string') {
|
||||||
|
throw new AppError('缺少必要参数:出生日期', 400, 'MISSING_BIRTH_DATE');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证出生日期格式
|
// 验证出生日期格式
|
||||||
@@ -36,55 +49,127 @@ router.post('/bazi', authenticate, asyncHandler(async (req, res) => {
|
|||||||
throw new AppError('出生日期格式应为 YYYY-MM-DD', 400, 'INVALID_DATE_FORMAT');
|
throw new AppError('出生日期格式应为 YYYY-MM-DD', 400, 'INVALID_DATE_FORMAT');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证日期有效性
|
||||||
|
const birthDate = new Date(birth_data.birth_date);
|
||||||
|
if (isNaN(birthDate.getTime())) {
|
||||||
|
throw new AppError('出生日期无效', 400, 'INVALID_DATE');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证日期范围
|
||||||
|
const currentDate = new Date();
|
||||||
|
const minDate = new Date('1900-01-01');
|
||||||
|
if (birthDate < minDate || birthDate > currentDate) {
|
||||||
|
throw new AppError('出生日期必须在1900年至今之间', 400, 'DATE_OUT_OF_RANGE');
|
||||||
|
}
|
||||||
|
|
||||||
// 验证出生时间格式(如果提供)
|
// 验证出生时间格式(如果提供)
|
||||||
if (birth_data.birth_time) {
|
if (birth_data.birth_time) {
|
||||||
|
if (typeof birth_data.birth_time !== 'string') {
|
||||||
|
throw new AppError('出生时间格式无效', 400, 'INVALID_TIME_TYPE');
|
||||||
|
}
|
||||||
|
|
||||||
const timeRegex = /^\d{2}:\d{2}$/;
|
const timeRegex = /^\d{2}:\d{2}$/;
|
||||||
if (!timeRegex.test(birth_data.birth_time)) {
|
if (!timeRegex.test(birth_data.birth_time)) {
|
||||||
throw new AppError('出生时间格式应为 HH:MM', 400, 'INVALID_TIME_FORMAT');
|
throw new AppError('出生时间格式应为 HH:MM', 400, 'INVALID_TIME_FORMAT');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [hours, minutes] = birth_data.birth_time.split(':').map(Number);
|
||||||
|
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
||||||
|
throw new AppError('出生时间无效', 400, 'INVALID_TIME');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证性别(如果提供)
|
||||||
|
if (birth_data.gender) {
|
||||||
|
const validGenders = ['male', 'female', '男', '女', '男性', '女性'];
|
||||||
|
if (!validGenders.includes(birth_data.gender)) {
|
||||||
|
throw new AppError('性别必须是 male/female 或 男/女', 400, 'INVALID_GENDER');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证出生地点长度(如果提供)
|
||||||
|
if (birth_data.birth_place && birth_data.birth_place.length > 100) {
|
||||||
|
throw new AppError('出生地点长度不能超过100个字符', 400, 'BIRTH_PLACE_TOO_LONG');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用分析响应处理函数
|
||||||
|
* @param {Object} analysisResult - 分析结果
|
||||||
|
* @param {Object} res - Express响应对象
|
||||||
|
*/
|
||||||
|
function sendAnalysisResponse(analysisResult, res) {
|
||||||
|
res.json({
|
||||||
|
data: {
|
||||||
|
analysis: analysisResult
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 八字分析接口
|
||||||
|
* 执行八字命理分析,不存储历史记录
|
||||||
|
*/
|
||||||
|
router.post('/bazi', authenticate, asyncHandler(async (req, res) => {
|
||||||
|
const { birth_data } = req.body;
|
||||||
|
|
||||||
|
// 使用通用验证函数
|
||||||
|
validateBirthData(birth_data);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 执行八字分析(纯分析,不存储历史记录)
|
// 执行八字分析(纯分析,不存储历史记录)
|
||||||
const analysisResult = await baziAnalyzer.performFullBaziAnalysis(birth_data);
|
const analysisResult = await baziAnalyzer.performFullBaziAnalysis(birth_data);
|
||||||
|
|
||||||
// 只返回分析结果,不存储历史记录
|
// 使用通用响应函数
|
||||||
res.json({
|
sendAnalysisResponse(analysisResult, res);
|
||||||
data: {
|
|
||||||
analysis: analysisResult
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('八字分析错误:', error);
|
console.error('八字分析错误:', error);
|
||||||
throw new AppError('八字分析过程中发生错误', 500, 'BAZI_ANALYSIS_ERROR');
|
throw new AppError(`八字分析过程中发生错误: ${error.message}`, 500, 'BAZI_ANALYSIS_ERROR');
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 易经分析接口
|
/**
|
||||||
|
* 易经分析接口
|
||||||
|
* 执行易经占卜分析,不存储历史记录
|
||||||
|
*/
|
||||||
router.post('/yijing', authenticate, asyncHandler(async (req, res) => {
|
router.post('/yijing', authenticate, asyncHandler(async (req, res) => {
|
||||||
const { question, user_id, divination_method, user_timezone, local_time } = req.body;
|
const { question, user_id, divination_method, user_timezone, local_time } = req.body;
|
||||||
|
|
||||||
// 输入验证
|
// 输入验证
|
||||||
if (!question) {
|
if (!question || typeof question !== 'string' || question.trim().length === 0) {
|
||||||
throw new AppError('缺少必要参数:占卜问题', 400, 'MISSING_QUESTION');
|
throw new AppError('缺少必要参数:占卜问题不能为空', 400, 'MISSING_QUESTION');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (question.length > 200) {
|
||||||
|
throw new AppError('占卜问题长度不能超过200个字符', 400, 'QUESTION_TOO_LONG');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证起卦方法
|
||||||
|
if (divination_method) {
|
||||||
|
const validMethods = ['time', 'plum_blossom', 'coin', 'number'];
|
||||||
|
if (!validMethods.includes(divination_method)) {
|
||||||
|
throw new AppError(`不支持的起卦方法: ${divination_method}`, 400, 'INVALID_DIVINATION_METHOD');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证用户ID
|
||||||
|
const targetUserId = user_id || req.user.id;
|
||||||
|
if (!Number.isInteger(targetUserId) || targetUserId <= 0) {
|
||||||
|
throw new AppError('用户ID无效', 400, 'INVALID_USER_ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 执行易经分析(纯分析,不存储历史记录)
|
// 执行易经分析(纯分析,不存储历史记录)
|
||||||
const analysisResult = yijingAnalyzer.performYijingAnalysis({
|
const analysisResult = yijingAnalyzer.performYijingAnalysis({
|
||||||
question: question,
|
question: question.trim(),
|
||||||
user_id: user_id || req.user.id,
|
user_id: targetUserId,
|
||||||
divination_method: divination_method || 'time',
|
divination_method: divination_method || 'time',
|
||||||
user_timezone: user_timezone,
|
user_timezone: user_timezone,
|
||||||
local_time: local_time
|
local_time: local_time
|
||||||
});
|
});
|
||||||
|
|
||||||
// 只返回分析结果,不存储历史记录
|
// 使用通用响应函数
|
||||||
res.json({
|
sendAnalysisResponse(analysisResult, res);
|
||||||
data: {
|
|
||||||
analysis: analysisResult
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('易经分析详细错误:', error);
|
console.error('易经分析详细错误:', error);
|
||||||
console.error('错误堆栈:', error.stack);
|
console.error('错误堆栈:', error.stack);
|
||||||
@@ -92,42 +177,30 @@ router.post('/yijing', authenticate, asyncHandler(async (req, res) => {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 紫微斗数分析接口
|
/**
|
||||||
|
* 紫微斗数分析接口
|
||||||
|
* 执行紫微斗数分析,不存储历史记录
|
||||||
|
*/
|
||||||
router.post('/ziwei', authenticate, asyncHandler(async (req, res) => {
|
router.post('/ziwei', authenticate, asyncHandler(async (req, res) => {
|
||||||
const { birth_data } = req.body;
|
const { birth_data } = req.body;
|
||||||
|
|
||||||
// 输入验证
|
// 使用通用验证函数
|
||||||
if (!birth_data || !birth_data.name || !birth_data.birth_date) {
|
validateBirthData(birth_data);
|
||||||
throw new AppError('缺少必要参数:姓名和出生日期', 400, 'MISSING_BIRTH_DATA');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证出生日期格式
|
// 紫微斗数需要性别信息
|
||||||
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
if (!birth_data.gender) {
|
||||||
if (!dateRegex.test(birth_data.birth_date)) {
|
throw new AppError('紫微斗数分析需要性别信息', 400, 'MISSING_GENDER');
|
||||||
throw new AppError('出生日期格式应为 YYYY-MM-DD', 400, 'INVALID_DATE_FORMAT');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证出生时间格式(如果提供)
|
|
||||||
if (birth_data.birth_time) {
|
|
||||||
const timeRegex = /^\d{2}:\d{2}$/;
|
|
||||||
if (!timeRegex.test(birth_data.birth_time)) {
|
|
||||||
throw new AppError('出生时间格式应为 HH:MM', 400, 'INVALID_TIME_FORMAT');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 执行紫微斗数分析(纯分析,不存储历史记录)
|
// 执行紫微斗数分析(纯分析,不存储历史记录)
|
||||||
const analysisResult = ziweiAnalyzer.performRealZiweiAnalysis(birth_data);
|
const analysisResult = ziweiAnalyzer.performRealZiweiAnalysis(birth_data);
|
||||||
|
|
||||||
// 只返回分析结果,不存储历史记录
|
// 使用通用响应函数
|
||||||
res.json({
|
sendAnalysisResponse(analysisResult, res);
|
||||||
data: {
|
|
||||||
analysis: analysisResult
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('紫微斗数分析错误:', error);
|
console.error('紫微斗数分析错误:', error);
|
||||||
throw new AppError('紫微斗数分析过程中发生错误', 500, 'ZIWEI_ANALYSIS_ERROR');
|
throw new AppError(`紫微斗数分析过程中发生错误: ${error.message}`, 500, 'ZIWEI_ANALYSIS_ERROR');
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -52,17 +52,86 @@ class BaziAnalyzer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 完全个性化的八字分析主函数 - 基于真实用户数据
|
/**
|
||||||
|
* 完全个性化的八字分析主函数 - 基于真实用户数据
|
||||||
|
* @param {Object} birth_data - 出生数据
|
||||||
|
* @param {string} birth_data.birth_date - 出生日期 (YYYY-MM-DD)
|
||||||
|
* @param {string} birth_data.birth_time - 出生时间 (HH:MM)
|
||||||
|
* @param {string} birth_data.gender - 性别 ('male'/'female' 或 '男'/'女')
|
||||||
|
* @param {string} birth_data.birth_place - 出生地点(可选)
|
||||||
|
* @param {string} birth_data.name - 姓名(可选)
|
||||||
|
* @returns {Promise<Object>} 八字分析结果
|
||||||
|
*/
|
||||||
async performFullBaziAnalysis(birth_data) {
|
async performFullBaziAnalysis(birth_data) {
|
||||||
try {
|
try {
|
||||||
|
// 输入参数验证
|
||||||
|
if (!birth_data || typeof birth_data !== 'object') {
|
||||||
|
throw new Error('输入数据无效:必须提供有效的出生数据对象');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { birth_date, birth_time, gender, birth_place, name } = birth_data;
|
||||||
|
|
||||||
|
// 验证出生日期
|
||||||
|
if (!birth_date || typeof birth_date !== 'string') {
|
||||||
|
throw new Error('输入数据无效:出生日期不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||||
|
if (!dateRegex.test(birth_date)) {
|
||||||
|
throw new Error('输入数据无效:出生日期格式必须为 YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
|
||||||
|
const birthDateObj = new Date(birth_date);
|
||||||
|
if (isNaN(birthDateObj.getTime())) {
|
||||||
|
throw new Error('输入数据无效:出生日期无效');
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDate = new Date();
|
||||||
|
const minDate = new Date('1900-01-01');
|
||||||
|
if (birthDateObj < minDate || birthDateObj > currentDate) {
|
||||||
|
throw new Error('输入数据无效:出生日期必须在1900年至今之间');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证出生时间
|
||||||
|
if (birth_time && typeof birth_time === 'string') {
|
||||||
|
const timeRegex = /^\d{2}:\d{2}$/;
|
||||||
|
if (!timeRegex.test(birth_time)) {
|
||||||
|
throw new Error('输入数据无效:出生时间格式必须为 HH:MM');
|
||||||
|
}
|
||||||
|
|
||||||
|
const [hours, minutes] = birth_time.split(':').map(Number);
|
||||||
|
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
||||||
|
throw new Error('输入数据无效:出生时间无效');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证性别
|
||||||
|
if (!gender || typeof gender !== 'string') {
|
||||||
|
throw new Error('输入数据无效:性别不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
const validGenders = ['male', 'female', '男', '女', '男性', '女性'];
|
||||||
|
if (!validGenders.includes(gender)) {
|
||||||
|
throw new Error('输入数据无效:性别必须是 male/female 或 男/女');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证姓名长度
|
||||||
|
if (name && (typeof name !== 'string' || name.length > 50)) {
|
||||||
|
throw new Error('输入数据无效:姓名长度不能超过50个字符');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证出生地点长度
|
||||||
|
if (birth_place && (typeof birth_place !== 'string' || birth_place.length > 100)) {
|
||||||
|
throw new Error('输入数据无效:出生地点长度不能超过100个字符');
|
||||||
|
}
|
||||||
|
|
||||||
// 检查缓存
|
// 检查缓存
|
||||||
const cachedResult = this.cache.get('bazi', birth_data);
|
const cachedResult = this.cache.get('bazi', birth_data);
|
||||||
if (cachedResult) {
|
if (cachedResult) {
|
||||||
return cachedResult;
|
return cachedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { birth_date, birth_time, gender, birth_place, name } = birth_data;
|
const personalizedName = (name && name.trim()) || '您';
|
||||||
const personalizedName = name || '您';
|
|
||||||
|
|
||||||
// 1. 精确计算八字四柱(基础计算,必须先完成)
|
// 1. 精确计算八字四柱(基础计算,必须先完成)
|
||||||
const baziChart = this.calculatePreciseBazi(birth_date, birth_time);
|
const baziChart = this.calculatePreciseBazi(birth_date, birth_time);
|
||||||
|
|||||||
@@ -48,16 +48,49 @@ class YijingAnalyzer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 专业易经分析主函数
|
/**
|
||||||
|
* 专业易经分析主函数
|
||||||
|
* @param {Object} inputData - 输入数据
|
||||||
|
* @param {string} inputData.question - 占卜问题
|
||||||
|
* @param {number} inputData.user_id - 用户ID
|
||||||
|
* @param {Object} inputData.birth_data - 出生数据(可选)
|
||||||
|
* @param {string} inputData.divination_method - 起卦方法
|
||||||
|
* @returns {Object} 易经分析结果
|
||||||
|
*/
|
||||||
performYijingAnalysis(inputData) {
|
performYijingAnalysis(inputData) {
|
||||||
try {
|
try {
|
||||||
|
// 输入参数验证
|
||||||
|
if (!inputData || typeof inputData !== 'object') {
|
||||||
|
throw new Error('输入数据无效:必须提供有效的输入对象');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { question, user_id, birth_data, divination_method = 'time' } = inputData;
|
||||||
|
|
||||||
|
// 验证必要参数
|
||||||
|
if (!question || typeof question !== 'string' || question.trim().length === 0) {
|
||||||
|
throw new Error('输入数据无效:问题不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (question.length > 200) {
|
||||||
|
throw new Error('输入数据无效:问题长度不能超过200个字符');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user_id !== undefined && (!Number.isInteger(user_id) || user_id <= 0)) {
|
||||||
|
throw new Error('输入数据无效:用户ID必须是正整数');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证起卦方法
|
||||||
|
const validMethods = ['time', 'plum_blossom', 'coin', 'number'];
|
||||||
|
if (!validMethods.includes(divination_method)) {
|
||||||
|
throw new Error(`输入数据无效:不支持的起卦方法 ${divination_method}`);
|
||||||
|
}
|
||||||
|
|
||||||
// 检查缓存(易经分析时效性较短,缓存时间较短)
|
// 检查缓存(易经分析时效性较短,缓存时间较短)
|
||||||
const cachedResult = this.cache.get('yijing', inputData);
|
const cachedResult = this.cache.get('yijing', inputData);
|
||||||
if (cachedResult) {
|
if (cachedResult) {
|
||||||
return cachedResult;
|
return cachedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { question, user_id, birth_data, divination_method = 'time' } = inputData;
|
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
|
|
||||||
// 根据不同方法起卦
|
// 根据不同方法起卦
|
||||||
@@ -74,12 +107,12 @@ class YijingAnalyzer {
|
|||||||
// 象数分析
|
// 象数分析
|
||||||
const numerologyAnalysis = this.performNumerologyAnalysis(hexagramData, currentTime, question);
|
const numerologyAnalysis = this.performNumerologyAnalysis(hexagramData, currentTime, question);
|
||||||
|
|
||||||
return {
|
const result = {
|
||||||
analysis_type: 'yijing',
|
analysis_type: 'yijing',
|
||||||
analysis_date: currentTime.toISOString(),
|
analysis_date: currentTime.toISOString(),
|
||||||
basic_info: {
|
basic_info: {
|
||||||
divination_data: {
|
divination_data: {
|
||||||
question: question,
|
question: question.trim(),
|
||||||
divination_time: currentTime.toISOString(),
|
divination_time: currentTime.toISOString(),
|
||||||
method: this.getMethodName(divination_method),
|
method: this.getMethodName(divination_method),
|
||||||
lunar_info: this.calculateLunarInfo(currentTime)
|
lunar_info: this.calculateLunarInfo(currentTime)
|
||||||
|
|||||||
@@ -310,11 +310,75 @@ class ZiweiAnalyzer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 专业紫微斗数分析主函数
|
/**
|
||||||
|
* 专业紫微斗数分析主函数
|
||||||
|
* @param {Object} birth_data - 出生数据
|
||||||
|
* @param {string} birth_data.birth_date - 出生日期 (YYYY-MM-DD)
|
||||||
|
* @param {string} birth_data.birth_time - 出生时间 (HH:MM)
|
||||||
|
* @param {string} birth_data.gender - 性别 ('male'/'female' 或 '男'/'女')
|
||||||
|
* @param {string} birth_data.name - 姓名(可选)
|
||||||
|
* @returns {Object} 紫微斗数分析结果
|
||||||
|
*/
|
||||||
performRealZiweiAnalysis(birth_data) {
|
performRealZiweiAnalysis(birth_data) {
|
||||||
const { name, birth_date, birth_time, gender } = birth_data;
|
try {
|
||||||
const personName = name || '您';
|
// 输入参数验证
|
||||||
const personGender = gender === 'male' || gender === '男' ? '男性' : '女性';
|
if (!birth_data || typeof birth_data !== 'object') {
|
||||||
|
throw new Error('输入数据无效:必须提供有效的出生数据对象');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, birth_date, birth_time, gender } = birth_data;
|
||||||
|
|
||||||
|
// 验证出生日期
|
||||||
|
if (!birth_date || typeof birth_date !== 'string') {
|
||||||
|
throw new Error('输入数据无效:出生日期不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||||
|
if (!dateRegex.test(birth_date)) {
|
||||||
|
throw new Error('输入数据无效:出生日期格式必须为 YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
|
||||||
|
const birthDateObj = new Date(birth_date);
|
||||||
|
if (isNaN(birthDateObj.getTime())) {
|
||||||
|
throw new Error('输入数据无效:出生日期无效');
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDate = new Date();
|
||||||
|
const minDate = new Date('1900-01-01');
|
||||||
|
if (birthDateObj < minDate || birthDateObj > currentDate) {
|
||||||
|
throw new Error('输入数据无效:出生日期必须在1900年至今之间');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证出生时间
|
||||||
|
if (birth_time && typeof birth_time === 'string') {
|
||||||
|
const timeRegex = /^\d{2}:\d{2}$/;
|
||||||
|
if (!timeRegex.test(birth_time)) {
|
||||||
|
throw new Error('输入数据无效:出生时间格式必须为 HH:MM');
|
||||||
|
}
|
||||||
|
|
||||||
|
const [hours, minutes] = birth_time.split(':').map(Number);
|
||||||
|
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
||||||
|
throw new Error('输入数据无效:出生时间无效');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证性别
|
||||||
|
if (!gender || typeof gender !== 'string') {
|
||||||
|
throw new Error('输入数据无效:性别不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
const validGenders = ['male', 'female', '男', '女', '男性', '女性'];
|
||||||
|
if (!validGenders.includes(gender)) {
|
||||||
|
throw new Error('输入数据无效:性别必须是 male/female 或 男/女');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证姓名长度
|
||||||
|
if (name && (typeof name !== 'string' || name.length > 50)) {
|
||||||
|
throw new Error('输入数据无效:姓名长度不能超过50个字符');
|
||||||
|
}
|
||||||
|
|
||||||
|
const personName = (name && name.trim()) || '您';
|
||||||
|
const personGender = gender === 'male' || gender === '男' ? '男性' : '女性';
|
||||||
|
|
||||||
// 计算精确的八字信息
|
// 计算精确的八字信息
|
||||||
const baziInfo = this.calculatePreciseBazi(birth_date, birth_time);
|
const baziInfo = this.calculatePreciseBazi(birth_date, birth_time);
|
||||||
@@ -359,6 +423,11 @@ class ZiweiAnalyzer {
|
|||||||
},
|
},
|
||||||
detailed_analysis: analysis
|
detailed_analysis: analysis
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('紫微斗数分析详细错误:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算五行局(紫微斗数核心算法)- 基于纳音五行
|
// 计算五行局(紫微斗数核心算法)- 基于纳音五行
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, ErrorInfo } from 'react';
|
||||||
import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer } from 'recharts';
|
import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer } from 'recharts';
|
||||||
import { Calendar, Star, BookOpen, Sparkles, User, BarChart3, Zap, TrendingUp, Loader2, Clock, Target, Heart, DollarSign, Activity } from 'lucide-react';
|
import { Calendar, Star, BookOpen, Sparkles, User, BarChart3, Zap, TrendingUp, Loader2, Clock, Target, Heart, DollarSign, Activity, AlertTriangle } from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from './ui/Card';
|
import { Card, CardContent, CardHeader, CardTitle } from './ui/Card';
|
||||||
import { BackToTop } from './ui/BackToTop';
|
import { BackToTop } from './ui/BackToTop';
|
||||||
import DownloadButton from './ui/DownloadButton';
|
import DownloadButton from './ui/DownloadButton';
|
||||||
@@ -8,22 +8,121 @@ import AIInterpretationButton from './ui/AIInterpretationButton';
|
|||||||
import AIConfigModal from './ui/AIConfigModal';
|
import AIConfigModal from './ui/AIConfigModal';
|
||||||
import { localApi } from '../lib/localApi';
|
import { localApi } from '../lib/localApi';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 八字分析组件的Props接口
|
||||||
|
*/
|
||||||
interface CompleteBaziAnalysisProps {
|
interface CompleteBaziAnalysisProps {
|
||||||
|
/** 出生日期信息 */
|
||||||
birthDate: {
|
birthDate: {
|
||||||
|
/** 出生日期 (YYYY-MM-DD) */
|
||||||
date: string;
|
date: string;
|
||||||
|
/** 出生时间 (HH:MM) */
|
||||||
time: string;
|
time: string;
|
||||||
|
/** 姓名(可选) */
|
||||||
name?: string;
|
name?: string;
|
||||||
|
/** 性别(可选) */
|
||||||
gender?: string;
|
gender?: string;
|
||||||
};
|
};
|
||||||
analysisData?: any; // 可选的预先分析的数据
|
/** 可选的预先分析的数据 */
|
||||||
recordId?: number; // 历史记录ID,用于AI解读
|
analysisData?: any;
|
||||||
|
/** 历史记录ID,用于AI解读 */
|
||||||
|
recordId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证出生日期数据的有效性
|
||||||
|
* @param birthDate 出生日期数据
|
||||||
|
* @returns 验证结果和错误信息
|
||||||
|
*/
|
||||||
|
const validateBirthDate = (birthDate: CompleteBaziAnalysisProps['birthDate']): { isValid: boolean; error?: string } => {
|
||||||
|
if (!birthDate) {
|
||||||
|
return { isValid: false, error: '出生日期数据不能为空' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!birthDate.date || typeof birthDate.date !== 'string') {
|
||||||
|
return { isValid: false, error: '出生日期不能为空' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证日期格式
|
||||||
|
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||||
|
if (!dateRegex.test(birthDate.date)) {
|
||||||
|
return { isValid: false, error: '出生日期格式必须为 YYYY-MM-DD' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证日期有效性
|
||||||
|
const date = new Date(birthDate.date);
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
return { isValid: false, error: '出生日期无效' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!birthDate.time || typeof birthDate.time !== 'string') {
|
||||||
|
return { isValid: false, error: '出生时间不能为空' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证时间格式
|
||||||
|
const timeRegex = /^\d{2}:\d{2}$/;
|
||||||
|
if (!timeRegex.test(birthDate.time)) {
|
||||||
|
return { isValid: false, error: '出生时间格式必须为 HH:MM' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证时间有效性
|
||||||
|
const [hours, minutes] = birthDate.time.split(':').map(Number);
|
||||||
|
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
||||||
|
return { isValid: false, error: '出生时间无效' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证姓名长度
|
||||||
|
if (birthDate.name && birthDate.name.length > 50) {
|
||||||
|
return { isValid: false, error: '姓名长度不能超过50个字符' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证性别
|
||||||
|
if (birthDate.gender) {
|
||||||
|
const validGenders = ['male', 'female', '男', '女'];
|
||||||
|
if (!validGenders.includes(birthDate.gender)) {
|
||||||
|
return { isValid: false, error: '性别必须是 male/female 或 男/女' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isValid: true };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误显示组件
|
||||||
|
*/
|
||||||
|
const ErrorDisplay: React.FC<{ error: string; onRetry?: () => void }> = ({ error, onRetry }) => (
|
||||||
|
<Card className="border-red-200 bg-red-50">
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<AlertTriangle className="h-6 w-6 text-red-600 flex-shrink-0" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-lg font-semibold text-red-800 mb-2">分析出错</h3>
|
||||||
|
<p className="text-red-700 mb-4">{error}</p>
|
||||||
|
{onRetry && (
|
||||||
|
<button
|
||||||
|
onClick={onRetry}
|
||||||
|
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
|
||||||
|
>
|
||||||
|
重新分析
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate, analysisData: propAnalysisData, recordId }) => {
|
const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate, analysisData: propAnalysisData, recordId }) => {
|
||||||
const [isLoading, setIsLoading] = useState(!propAnalysisData);
|
const [isLoading, setIsLoading] = useState(!propAnalysisData);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [analysisData, setAnalysisData] = useState<any>(propAnalysisData || null);
|
const [analysisData, setAnalysisData] = useState<any>(propAnalysisData || null);
|
||||||
const [showAIConfig, setShowAIConfig] = useState(false);
|
const [showAIConfig, setShowAIConfig] = useState(false);
|
||||||
|
|
||||||
|
// 输入验证
|
||||||
|
const validation = validateBirthDate(birthDate);
|
||||||
|
if (!validation.isValid) {
|
||||||
|
return <ErrorDisplay error={validation.error!} />;
|
||||||
|
}
|
||||||
|
|
||||||
// 五行颜色配置
|
// 五行颜色配置
|
||||||
const elementColors: { [key: string]: string } = {
|
const elementColors: { [key: string]: string } = {
|
||||||
@@ -58,6 +157,40 @@ const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate,
|
|||||||
'日主': 'bg-amber-100 text-amber-800 border-amber-300'
|
'日主': 'bg-amber-100 text-amber-800 border-amber-300'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 分析数据获取函数
|
||||||
|
const fetchAnalysisData = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const birthData = {
|
||||||
|
name: birthDate.name || '用户',
|
||||||
|
birth_date: birthDate.date,
|
||||||
|
birth_time: birthDate.time,
|
||||||
|
gender: birthDate.gender || 'male'
|
||||||
|
};
|
||||||
|
|
||||||
|
const baziResponse = await localApi.analysis.bazi(birthData);
|
||||||
|
|
||||||
|
if (baziResponse.error) {
|
||||||
|
throw new Error(baziResponse.error.message || '八字分析失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const analysisResult = baziResponse.data?.analysis;
|
||||||
|
if (!analysisResult) {
|
||||||
|
throw new Error('分析结果为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
setAnalysisData(analysisResult);
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : '分析数据获取失败,请稍后重试';
|
||||||
|
console.error('八字分析错误:', err);
|
||||||
|
setError(errorMessage);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 如果已经有分析数据,直接使用
|
// 如果已经有分析数据,直接使用
|
||||||
if (propAnalysisData) {
|
if (propAnalysisData) {
|
||||||
@@ -66,37 +199,6 @@ const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchAnalysisData = async () => {
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
const birthData = {
|
|
||||||
name: birthDate.name || '用户',
|
|
||||||
birth_date: birthDate.date,
|
|
||||||
birth_time: birthDate.time,
|
|
||||||
gender: birthDate.gender || 'male'
|
|
||||||
};
|
|
||||||
|
|
||||||
const baziResponse = await localApi.analysis.bazi(birthData);
|
|
||||||
|
|
||||||
if (baziResponse.error) {
|
|
||||||
throw new Error(baziResponse.error.message || '八字分析失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
const analysisResult = baziResponse.data?.analysis;
|
|
||||||
if (!analysisResult) {
|
|
||||||
throw new Error('分析结果为空');
|
|
||||||
}
|
|
||||||
|
|
||||||
setAnalysisData(analysisResult);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : '分析数据获取失败,请稍后重试');
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (birthDate?.date && !propAnalysisData) {
|
if (birthDate?.date && !propAnalysisData) {
|
||||||
fetchAnalysisData();
|
fetchAnalysisData();
|
||||||
}
|
}
|
||||||
@@ -119,6 +221,11 @@ const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate,
|
|||||||
|
|
||||||
// 渲染错误状态
|
// 渲染错误状态
|
||||||
if (error) {
|
if (error) {
|
||||||
|
return <ErrorDisplay error={error} onRetry={fetchAnalysisData} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查分析数据的完整性
|
||||||
|
if (!analysisData) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-red-50 to-yellow-50">
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-red-50 to-yellow-50">
|
||||||
<Card className="chinese-card-decoration border-2 border-red-400 p-8">
|
<Card className="chinese-card-decoration border-2 border-red-400 p-8">
|
||||||
|
|||||||
Reference in New Issue
Block a user