Files
suanming/server/routes/analysis.cjs
patdelphi 0f3e1f406f feat: 完整实现奇门遁甲功能并优化显示效果
主要功能实现:
- 新增奇门遁甲分析完整功能模块
- 实现奇门盘可视化展示
- 添加用神分析、格局识别、预测结果等核心功能
- 集成AI解读和PDF导出功能
- 扩展历史记录支持奇门遁甲类型

显示优化:
- 修复时机评估[object Object]显示问题
- 优化时机评估显示为简洁格式
- 完善英文字段中文化映射
- 移除重复的成功概率显示
- 统一数值显示格式(小数转整数)

技术改进:
- 扩展类型定义支持奇门遁甲
- 完善API接口和路由
- 优化错误处理和用户体验
- 统一前后端字段映射机制
2025-08-25 21:56:31 +08:00

703 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const express = require('express');
const { getDB } = require('../database/index.cjs');
const { authenticate } = require('../middleware/auth.cjs');
const { AppError, asyncHandler } = require('../middleware/errorHandler.cjs');
// 导入分析服务
const BaziAnalyzer = require('../services/baziAnalyzer.cjs');
const YijingAnalyzer = require('../services/yijingAnalyzer.cjs');
const ZiweiAnalyzer = require('../services/ziweiAnalyzer.cjs');
const router = express.Router();
// 初始化分析器
const baziAnalyzer = new BaziAnalyzer();
const yijingAnalyzer = new YijingAnalyzer();
const ziweiAnalyzer = new ZiweiAnalyzer();
// 导入AI增强分析服务
const AIEnhancedAnalysis = require('../services/common/AIEnhancedAnalysis.cjs');
// 初始化AI增强分析服务
const aiEnhancedAnalysis = new AIEnhancedAnalysis();
/**
* 通用输入验证函数
* @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) {
throw new AppError('缺少必要参数:姓名不能为空', 400, 'MISSING_NAME');
}
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');
}
// 验证出生日期格式
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(birth_data.birth_date)) {
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 (typeof birth_data.birth_time !== 'string') {
throw new AppError('出生时间格式无效', 400, 'INVALID_TIME_TYPE');
}
const timeRegex = /^\d{2}:\d{2}$/;
if (!timeRegex.test(birth_data.birth_time)) {
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 {
// 执行八字分析(纯分析,不存储历史记录)
const analysisResult = await baziAnalyzer.performFullBaziAnalysis(birth_data);
// 使用通用响应函数
sendAnalysisResponse(analysisResult, res);
} catch (error) {
console.error('八字分析错误:', error);
throw new AppError(`八字分析过程中发生错误: ${error.message}`, 500, 'BAZI_ANALYSIS_ERROR');
}
}));
/**
* 易经分析接口
* 执行易经占卜分析,不存储历史记录
*/
router.post('/yijing', authenticate, asyncHandler(async (req, res) => {
const { question, user_id, divination_method, user_timezone, local_time } = req.body;
// 输入验证
if (!question || typeof question !== 'string' || question.trim().length === 0) {
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;
const numericUserId = typeof targetUserId === 'string' ? parseInt(targetUserId, 10) : targetUserId;
if (!numericUserId || isNaN(numericUserId) || numericUserId <= 0) {
throw new AppError('用户ID无效', 400, 'INVALID_USER_ID');
}
try {
// 执行易经分析(纯分析,不存储历史记录)
const analysisResult = yijingAnalyzer.performYijingAnalysis({
question: question.trim(),
user_id: numericUserId,
divination_method: divination_method || 'time',
user_timezone: user_timezone,
local_time: local_time
});
// 使用通用响应函数
sendAnalysisResponse(analysisResult, res);
} catch (error) {
console.error('易经分析详细错误:', error);
console.error('错误堆栈:', error.stack);
throw new AppError(`易经分析过程中发生错误: ${error.message}`, 500, 'YIJING_ANALYSIS_ERROR');
}
}));
/**
* 紫微斗数分析接口
* 执行紫微斗数分析,不存储历史记录
*/
router.post('/ziwei', authenticate, asyncHandler(async (req, res) => {
const { birth_data } = req.body;
// 使用通用验证函数
validateBirthData(birth_data);
// 紫微斗数需要性别信息
if (!birth_data.gender) {
throw new AppError('紫微斗数分析需要性别信息', 400, 'MISSING_GENDER');
}
try {
// 执行紫微斗数分析(纯分析,不存储历史记录)
const analysisResult = ziweiAnalyzer.performRealZiweiAnalysis(birth_data);
// 使用通用响应函数
sendAnalysisResponse(analysisResult, res);
} catch (error) {
console.error('紫微斗数分析错误:', error);
throw new AppError(`紫微斗数分析过程中发生错误: ${error.message}`, 500, 'ZIWEI_ANALYSIS_ERROR');
}
}));
// 历史记录存储接口
router.post('/save-history', authenticate, asyncHandler(async (req, res) => {
const { analysis_type, analysis_data, input_data } = req.body;
// 输入验证
if (!analysis_type || !analysis_data) {
throw new AppError('缺少必要参数:分析类型和分析数据', 400, 'MISSING_REQUIRED_DATA');
}
// 验证分析类型
const validTypes = ['bazi', 'ziwei', 'yijing', 'qimen'];
if (!validTypes.includes(analysis_type)) {
throw new AppError('无效的分析类型', 400, 'INVALID_ANALYSIS_TYPE');
}
try {
const db = getDB();
// 根据分析类型准备不同的数据
let name, birth_date, birth_time, birth_place, gender;
if (analysis_type === 'yijing') {
// 易经占卜:获取用户档案信息
const getUserProfile = db.prepare('SELECT full_name FROM user_profiles WHERE user_id = ?');
const userProfile = getUserProfile.get(req.user.id);
name = userProfile?.full_name || '易经占卜用户';
birth_date = null;
birth_time = null;
birth_place = null;
gender = null;
} else if (analysis_type === 'qimen') {
// 奇门遁甲:获取用户档案信息
const getUserProfile = db.prepare('SELECT full_name FROM user_profiles WHERE user_id = ?');
const userProfile = getUserProfile.get(req.user.id);
name = userProfile?.full_name || '奇门遁甲用户';
birth_date = input_data?.birth_date || null;
birth_time = input_data?.birth_time || null;
birth_place = null;
gender = null;
} else {
// 八字和紫微:从输入数据中获取
name = input_data?.name || '用户';
birth_date = input_data?.birth_date || null;
birth_time = input_data?.birth_time || null;
birth_place = input_data?.birth_place || null;
gender = input_data?.gender || null;
}
// 插入历史记录
const insertReading = db.prepare(`
INSERT INTO numerology_readings (
user_id, reading_type, name, birth_date, birth_time, birth_place, gender,
input_data, analysis, status, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const result = insertReading.run(
req.user.id,
analysis_type,
name,
birth_date,
birth_time,
birth_place,
gender,
JSON.stringify(input_data || {}),
JSON.stringify(analysis_data),
'completed',
new Date().toISOString()
);
res.json({
data: {
record_id: result.lastInsertRowid,
message: '历史记录保存成功'
}
});
} catch (error) {
console.error('保存历史记录错误:', error);
throw new AppError('保存历史记录失败', 500, 'SAVE_HISTORY_ERROR');
}
}));
// 综合分析接口(可选)
router.post('/comprehensive', authenticate, asyncHandler(async (req, res) => {
const { birth_data, include_types } = req.body;
// 输入验证
if (!birth_data || !birth_data.name || !birth_data.birth_date) {
throw new AppError('缺少必要参数:姓名和出生日期', 400, 'MISSING_BIRTH_DATA');
}
const analysisTypes = include_types || ['bazi', 'ziwei', 'yijing'];
const results = {};
try {
// 根据请求的类型执行相应分析
if (analysisTypes.includes('bazi')) {
results.bazi = await baziAnalyzer.performFullBaziAnalysis(birth_data);
}
if (analysisTypes.includes('ziwei')) {
results.ziwei = ziweiAnalyzer.performRealZiweiAnalysis(birth_data);
}
if (analysisTypes.includes('yijing')) {
results.yijing = yijingAnalyzer.performYijingAnalysis({
question: '人生运势综合占卜',
user_id: req.user.id,
birth_data: birth_data
});
}
// 保存综合分析结果
const db = getDB();
const insertReading = db.prepare(`
INSERT INTO numerology_readings (
user_id, reading_type, name, birth_date, birth_time, birth_place, gender,
input_data, analysis, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const comprehensiveResult = {
analysis_type: 'comprehensive',
analysis_date: new Date().toISOString().split('T')[0],
included_types: analysisTypes,
results: results
};
const result = insertReading.run(
req.user.id,
'comprehensive',
birth_data.name,
birth_data.birth_date,
birth_data.birth_time || null,
birth_data.birth_place || null,
birth_data.gender || null,
JSON.stringify({ birth_data, include_types: analysisTypes }),
JSON.stringify(comprehensiveResult),
'completed'
);
res.json({
data: {
record_id: result.lastInsertRowid,
analysis: comprehensiveResult
}
});
} catch (error) {
console.error('综合分析错误:', error);
throw new AppError('综合分析过程中发生错误', 500, 'COMPREHENSIVE_ANALYSIS_ERROR');
}
}));
// 获取分析类型列表
router.get('/types', (req, res) => {
res.json({
data: {
available_types: [
{
type: 'bazi',
name: '八字命理',
description: '基于出生年月日时的传统命理分析',
required_fields: ['name', 'birth_date'],
optional_fields: ['birth_time', 'gender', 'birth_place']
},
{
type: 'ziwei',
name: '紫微斗数',
description: '紫微斗数排盘和命理分析',
required_fields: ['name', 'birth_date'],
optional_fields: ['birth_time', 'gender', 'birth_place']
},
{
type: 'yijing',
name: '易经占卜',
description: '基于易经的占卜和指导',
required_fields: ['name'],
optional_fields: ['question', 'birth_date', 'birth_time', 'gender']
},
{
type: 'comprehensive',
name: '综合分析',
description: '包含多种分析方法的综合报告',
required_fields: ['name', 'birth_date'],
optional_fields: ['birth_time', 'gender', 'birth_place', 'include_types']
}
]
}
});
});
// 八字详细分析接口
router.post('/bazi-details', authenticate, asyncHandler(async (req, res) => {
const { birthDate, birthTime } = req.body;
// 输入验证
if (!birthDate) {
throw new AppError('缺少必要参数:出生日期', 400, 'MISSING_BIRTH_DATE');
}
try {
// 构造birth_data对象
const birthData = {
name: '详细分析',
birth_date: birthDate,
birth_time: birthTime || '12:00',
gender: 'male'
};
// 执行八字分析
const analysisResult = await baziAnalyzer.performFullBaziAnalysis(birthData);
res.json({
data: {
data: analysisResult
}
});
} catch (error) {
console.error('八字详细分析错误:', error);
throw new AppError('八字详细分析过程中发生错误', 500, 'BAZI_DETAILS_ERROR');
}
}));
// 八字五行分析接口
router.post('/bazi-wuxing', authenticate, asyncHandler(async (req, res) => {
const { birthDate, birthTime } = req.body;
// 输入验证
if (!birthDate) {
throw new AppError('缺少必要参数:出生日期', 400, 'MISSING_BIRTH_DATE');
}
try {
// 构造birth_data对象
const birthData = {
name: '五行分析',
birth_date: birthDate,
birth_time: birthTime || '12:00',
gender: 'male'
};
// 执行八字分析,提取五行部分
const analysisResult = await baziAnalyzer.performFullBaziAnalysis(birthData);
// 只返回五行相关的分析结果
const wuxingResult = {
wuxing_analysis: analysisResult.wuxing_analysis,
basic_info: analysisResult.basic_info
};
res.json({
data: {
data: wuxingResult
}
});
} catch (error) {
console.error('八字五行分析错误:', error);
throw new AppError('八字五行分析过程中发生错误', 500, 'BAZI_WUXING_ERROR');
}
}));
// 验证分析数据格式
router.post('/validate', (req, res) => {
const { birth_data, analysis_type } = req.body;
const errors = [];
if (!birth_data) {
errors.push('缺少birth_data参数');
} else {
// 验证姓名
if (!birth_data.name || birth_data.name.trim().length === 0) {
errors.push('姓名不能为空');
}
// 验证出生日期(除易经外都需要)
if (analysis_type !== 'yijing') {
if (!birth_data.birth_date) {
errors.push('出生日期不能为空');
} else {
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(birth_data.birth_date)) {
errors.push('出生日期格式应为 YYYY-MM-DD');
} else {
const date = new Date(birth_data.birth_date);
if (isNaN(date.getTime())) {
errors.push('无效的出生日期');
}
}
}
}
// 验证出生时间格式(如果提供)
if (birth_data.birth_time) {
const timeRegex = /^\d{2}:\d{2}$/;
if (!timeRegex.test(birth_data.birth_time)) {
errors.push('出生时间格式应为 HH:MM');
}
}
// 验证性别(如果提供)
if (birth_data.gender && !['male', 'female', '男', '女'].includes(birth_data.gender)) {
errors.push('性别字段只能是 male、female、男 或 女');
}
}
res.json({
data: {
valid: errors.length === 0,
errors: errors
}
});
});
// AI增强个性化推荐接口
router.post('/ai-recommendations', authenticate, asyncHandler(async (req, res) => {
const { analysis_result, user_context } = req.body;
if (!analysis_result) {
throw new AppError('缺少分析结果数据', 400, 'MISSING_ANALYSIS_RESULT');
}
const { getDB } = require('../database/index.cjs');
const db = getDB();
// 获取用户历史分析数据
const analysisHistory = db.prepare(`
SELECT reading_type, analysis, created_at
FROM readings
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 20
`).all(req.user.id);
// 获取用户交互数据(简化实现)
const interactionData = {
averageSessionDuration: 180,
averagePagesPerSession: 3,
returnVisits: analysisHistory.length,
featureUsage: { charts: 5, text: 8, comparisons: 2 },
averageScrollDepth: 0.7,
feedback: []
};
// 分析用户行为模式
const behaviorProfile = aiEnhancedAnalysis.analyzeUserBehavior(
req.user.id,
analysisHistory,
interactionData
);
// 生成个性化推荐
const recommendations = aiEnhancedAnalysis.generatePersonalizedRecommendations(
req.user.id,
analysis_result,
behaviorProfile
);
res.json({
data: {
recommendations: recommendations,
behavior_profile: behaviorProfile,
personalization_level: 'high'
}
});
}));
// AI分析准确度优化接口
router.post('/ai-optimize-accuracy', authenticate, asyncHandler(async (req, res) => {
const { analysis_result, user_context, feedback_data } = req.body;
if (!analysis_result) {
throw new AppError('缺少分析结果数据', 400, 'MISSING_ANALYSIS_RESULT');
}
const { getDB } = require('../database/index.cjs');
const db = getDB();
// 获取历史反馈数据
const historicalFeedback = db.prepare(`
SELECT analysis, created_at
FROM readings
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 10
`).all(req.user.id);
// 构建用户上下文
const enhancedUserContext = {
userId: req.user.id,
currentSituation: user_context?.current_situation || 'general',
dataQuality: user_context?.data_quality || 0.8,
...user_context
};
// 优化分析准确度
const accuracyOptimization = aiEnhancedAnalysis.optimizeAnalysisAccuracy(
analysis_result,
enhancedUserContext,
historicalFeedback
);
res.json({
data: accuracyOptimization
});
}));
// 用户行为预测接口
router.get('/ai-predict-behavior', authenticate, asyncHandler(async (req, res) => {
const { context } = req.query;
const currentContext = {
timestamp: new Date().toISOString(),
context: context || 'general'
};
// 预测用户行为
const behaviorPrediction = aiEnhancedAnalysis.predictUserBehavior(
req.user.id,
currentContext
);
res.json({
data: behaviorPrediction
});
}));
// AI模型训练接口管理员专用
router.post('/ai-train-model', authenticate, asyncHandler(async (req, res) => {
// 简化的权限检查
if (req.user.role !== 'admin') {
throw new AppError('权限不足', 403, 'INSUFFICIENT_PERMISSIONS');
}
const { training_data } = req.body;
if (!training_data || !Array.isArray(training_data)) {
throw new AppError('无效的训练数据格式', 400, 'INVALID_TRAINING_DATA');
}
// 训练模型
const trainingResult = aiEnhancedAnalysis.trainModel(training_data);
res.json({
data: trainingResult
});
}));
// 获取AI分析统计信息
router.get('/ai-stats', authenticate, asyncHandler(async (req, res) => {
const { getDB } = require('../database/index.cjs');
const db = getDB();
// 获取用户分析统计
const userStats = db.prepare(`
SELECT
reading_type,
COUNT(*) as count,
AVG(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as success_rate
FROM readings
WHERE user_id = ?
GROUP BY reading_type
`).all(req.user.id);
// 获取用户行为模式
const analysisHistory = db.prepare(`
SELECT reading_type, created_at
FROM readings
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 50
`).all(req.user.id);
const interactionData = {
averageSessionDuration: 200,
returnVisits: analysisHistory.length,
featureUsage: { charts: 3, text: 7, comparisons: 1 }
};
const behaviorProfile = aiEnhancedAnalysis.analyzeUserBehavior(
req.user.id,
analysisHistory,
interactionData
);
res.json({
data: {
user_stats: userStats,
behavior_profile: behaviorProfile,
ai_model_version: '1.0',
personalization_enabled: true
}
});
}));
module.exports = router;