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

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

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

617 lines
16 KiB
JavaScript

// 奇门遁甲API路由
// 提供奇门遁甲相关的RESTful接口
const express = require('express');
const QimenAnalyzer = require('../services/qimenAnalyzer.cjs');
const inputValidator = require('../utils/inputValidator.cjs');
const logger = require('../middleware/logger.cjs');
const router = express.Router();
const qimenAnalyzer = new QimenAnalyzer();
const { authenticate } = require('../middleware/auth.cjs');
const { getDB } = require('../database/index.cjs');
const { AppError, asyncHandler } = require('../middleware/errorHandler.cjs');
/**
* @route POST /api/qimen/analyze
* @desc 奇门遁甲完整分析
* @access Private
*/
router.post('/analyze', asyncHandler(async (req, res) => {
try {
const { question, birth_date, birth_time, user_timezone, local_time, user_id } = req.body;
const userId = user_id || 1; // 测试用户ID
// 输入验证
if (!question || typeof question !== 'string' || question.trim().length === 0) {
throw new AppError('缺少必要参数:占卜问题不能为空', 400, 'MISSING_QUESTION');
}
if (!birth_date || typeof birth_date !== 'string') {
throw new AppError('缺少必要参数:出生日期', 400, 'MISSING_BIRTH_DATE');
}
if (!birth_time || typeof birth_time !== 'string') {
throw new AppError('缺少必要参数:出生时间', 400, 'MISSING_BIRTH_TIME');
}
// 构建分析数据
const analysisData = {
question: question.trim(),
birth_date,
birth_time,
user_timezone: user_timezone || 'Asia/Shanghai',
local_time: local_time || new Date().toISOString()
};
// 执行奇门遁甲分析
const analysisResult = qimenAnalyzer.performQimenAnalysis(analysisData);
// 保存到历史记录
const db = getDB();
const insertReading = db.prepare(`
INSERT INTO numerology_readings (
user_id, reading_type, input_data, analysis, created_at
) VALUES (?, ?, ?, ?, datetime('now'))
`);
const result = insertReading.run(
userId,
'qimen',
JSON.stringify(analysisData),
JSON.stringify(analysisResult)
);
res.json({
data: {
record_id: result.lastInsertRowid,
analysis: analysisResult
}
});
} catch (error) {
console.error('奇门遁甲分析错误:', error);
throw new AppError(`奇门遁甲分析过程中发生错误: ${error.message}`, 500, 'QIMEN_ANALYSIS_ERROR');
}
}));
/**
* @route POST /api/qimen/calculate
* @desc 奇门遁甲起局计算
* @access Public
*/
router.post('/calculate', async (req, res) => {
try {
const { timeInfo, options = {} } = req.body;
// 输入验证
if (!timeInfo || !timeInfo.datetime) {
return res.status(400).json({
success: false,
error: {
code: 'INVALID_INPUT',
message: '缺少必要的时间信息',
details: 'datetime字段是必需的'
}
});
}
// 验证时间格式
const datetime = new Date(timeInfo.datetime);
if (isNaN(datetime.getTime())) {
return res.status(400).json({
success: false,
error: {
code: 'INVALID_TIME',
message: '时间格式不正确',
details: '请使用ISO 8601格式的时间字符串'
}
});
}
// 计算奇门盘
const qimenPan = qimenAnalyzer.calculator.calculateQimenPan(datetime);
// 构建响应数据
const response = {
success: true,
data: {
timeInfo: {
datetime: timeInfo.datetime,
timezone: timeInfo.timezone || 'Asia/Shanghai',
ganzhi: {
year: `${qimenPan.timeInfo.year.gan}${qimenPan.timeInfo.year.zhi}`,
month: `${qimenPan.timeInfo.month.gan}${qimenPan.timeInfo.month.zhi}`,
day: `${qimenPan.timeInfo.day.gan}${qimenPan.timeInfo.day.zhi}`,
hour: `${qimenPan.timeInfo.hour.gan}${qimenPan.timeInfo.hour.zhi}`
},
jieqi: qimenPan.timeInfo.jieqi,
yuan: qimenPan.timeInfo.yuan,
jushu: qimenPan.timeInfo.jushu,
yindun: qimenPan.timeInfo.yindun ? '阴遁' : '阳遁'
},
qimenPan: {
dipan: qimenPan.dipan.map((item, index) => ({
palace: index + 1,
palaceName: qimenAnalyzer.getPalaceName(index),
direction: qimenAnalyzer.getDirection(index),
ganzhi: item.ganzhi,
star: item.star,
door: item.door,
god: item.god
})),
tianpan: qimenPan.tianpan.map((item, index) => ({
palace: index + 1,
palaceName: qimenAnalyzer.getPalaceName(index),
direction: qimenAnalyzer.getDirection(index),
ganzhi: item.ganzhi,
star: item.star,
door: item.door,
god: item.god
})),
zhifu: qimenPan.zhifu,
zhishi: qimenPan.zhishi
},
method: options.method || '时家奇门'
}
};
logger.info('奇门起局成功', {
datetime: timeInfo.datetime,
jushu: qimenPan.timeInfo.jushu,
yindun: qimenPan.timeInfo.yindun
});
res.json(response);
} catch (error) {
logger.error('奇门起局失败', error);
res.status(500).json({
success: false,
error: {
code: 'CALCULATION_ERROR',
message: '奇门盘计算失败',
details: error.message
}
});
}
});
/**
* @route POST /api/qimen/analyze
* @desc 奇门遁甲格局分析
* @access Public
*/
router.post('/analyze', async (req, res) => {
try {
const { qimenPan, analysisOptions = {} } = req.body;
// 输入验证
if (!qimenPan || !qimenPan.dipan || !qimenPan.tianpan) {
return res.status(400).json({
success: false,
error: {
code: 'INVALID_INPUT',
message: '缺少奇门盘数据',
details: '需要提供完整的奇门盘信息'
}
});
}
// 格局分析
const patterns = qimenAnalyzer.patternAnalyzer.analyzePatterns(qimenPan);
// 构建响应数据
const response = {
success: true,
data: {
patterns: patterns.map(pattern => ({
name: pattern.name,
type: pattern.type,
level: pattern.level,
score: pattern.score,
palace: pattern.palace,
description: pattern.description
})),
summary: {
totalPatterns: patterns.length,
favorablePatterns: patterns.filter(p => ['吉', '大吉'].includes(p.level)).length,
unfavorablePatterns: patterns.filter(p => ['凶', '大凶'].includes(p.level)).length,
neutralPatterns: patterns.filter(p => ['中', '平'].includes(p.level)).length
},
analysisOptions: {
includePatterns: analysisOptions.includePatterns !== false,
includeYongshen: analysisOptions.includeYongshen !== false,
detailLevel: analysisOptions.detailLevel || 'standard'
}
}
};
logger.info('奇门格局分析成功', {
patternsCount: patterns.length,
detailLevel: analysisOptions.detailLevel
});
res.json(response);
} catch (error) {
logger.error('奇门格局分析失败', error);
res.status(500).json({
success: false,
error: {
code: 'ANALYSIS_ERROR',
message: '格局分析失败',
details: error.message
}
});
}
});
/**
* @route POST /api/qimen/predict
* @desc 奇门遁甲预测生成
* @access Public
*/
router.post('/predict', async (req, res) => {
try {
const { qimenPan, question, querent, options = {} } = req.body;
// 输入验证
if (!qimenPan || !question) {
return res.status(400).json({
success: false,
error: {
code: 'INVALID_INPUT',
message: '缺少必要参数',
details: '需要提供奇门盘数据和问题描述'
}
});
}
// 验证问题描述
if (!question.description || question.description.trim().length === 0) {
return res.status(400).json({
success: false,
error: {
code: 'INVALID_QUESTION',
message: '问题描述不能为空',
details: '请提供具体的问题描述'
}
});
}
if (question.description.length > 200) {
return res.status(400).json({
success: false,
error: {
code: 'QUESTION_TOO_LONG',
message: '问题描述过长',
details: '问题描述不能超过200个字符'
}
});
}
// 选择用神
const yongshen = qimenAnalyzer.yongShenAnalyzer.selectYongShen(
question.description,
querent,
qimenPan
);
// 分析用神
const yongShenAnalysis = qimenAnalyzer.yongShenAnalyzer.analyzeYongShen(
yongshen,
qimenPan
);
// 格局分析
const patterns = qimenAnalyzer.patternAnalyzer.analyzePatterns(qimenPan);
// 生成预测结果
const prediction = qimenAnalyzer.predictionGenerator.generatePrediction(
qimenPan,
yongShenAnalysis,
question.description,
patterns
);
// 构建响应数据
const response = {
success: true,
data: {
question: {
type: question.type || '其他',
description: question.description,
timeframe: question.timeframe || '近期'
},
yongshen: {
primary: yongshen,
analysis: yongShenAnalysis
},
prediction: {
overall: prediction.overall,
probability: prediction.probability,
details: prediction.details,
suggestions: prediction.suggestions,
timing: prediction.timing
},
patterns: patterns.slice(0, 5), // 只返回前5个重要格局
querent: querent ? {
birthDate: querent.birthDate,
gender: querent.gender
} : null
}
};
logger.info('奇门预测生成成功', {
questionType: question.type,
probability: prediction.probability,
patternsCount: patterns.length
});
res.json(response);
} catch (error) {
logger.error('奇门预测生成失败', error);
res.status(500).json({
success: false,
error: {
code: 'PREDICTION_ERROR',
message: '预测生成失败',
details: error.message
}
});
}
});
/**
* @route GET /api/qimen/solar-terms
* @desc 获取节气信息
* @access Public
*/
router.get('/solar-terms', async (req, res) => {
try {
const { year = new Date().getFullYear() } = req.query;
// 验证年份
const yearNum = parseInt(year);
if (isNaN(yearNum) || yearNum < 1900 || yearNum > 2100) {
return res.status(400).json({
success: false,
error: {
code: 'INVALID_YEAR',
message: '年份参数无效',
details: '年份必须在1900-2100之间'
}
});
}
// 计算节气
const solarTerms = qimenAnalyzer.solarTerms.calculateYearSolarTerms(yearNum);
const response = {
success: true,
data: solarTerms.map(term => ({
name: term.name,
date: term.date.toISOString(),
yindun: qimenAnalyzer.solarTerms.isYindunSeason(term.name),
season: qimenAnalyzer.solarTerms.getSeasonByTerm(term.name),
info: qimenAnalyzer.solarTerms.getSolarTermInfo(term.name)
}))
};
res.json(response);
} catch (error) {
logger.error('节气查询失败', error);
res.status(500).json({
success: false,
error: {
code: 'SOLAR_TERMS_ERROR',
message: '节气查询失败',
details: error.message
}
});
}
});
/**
* @route GET /api/qimen/yongshen
* @desc 获取用神配置
* @access Public
*/
router.get('/yongshen', async (req, res) => {
try {
const { type, gender } = req.query;
if (!type) {
return res.status(400).json({
success: false,
error: {
code: 'MISSING_TYPE',
message: '缺少问题类型参数',
details: '请提供type参数'
}
});
}
// 模拟用神配置(实际应该从分析器获取)
const yongShenConfig = {
'婚姻': {
primary: {
self: gender === '男' ? '庚' : '乙',
spouse: gender === '男' ? '乙' : '庚',
matchmaker: '六合'
},
secondary: {
marriage_palace: '兑宫',
relationship_door: '休门'
}
},
'求财': {
primary: {
wealth: '生门',
capital: '戊',
opportunity: '开门'
},
secondary: {
wealth_palace: '艮宫',
profit_star: '天任'
}
},
'疾病': {
primary: {
illness: '天芮',
doctor: '天心',
medicine: '乙'
},
secondary: {
health_palace: '坤宫',
recovery_door: '生门'
}
}
};
const config = yongShenConfig[type] || {
primary: {
matter: '时干',
result: '值使'
},
secondary: {}
};
res.json({
success: true,
data: config
});
} catch (error) {
logger.error('用神查询失败', error);
res.status(500).json({
success: false,
error: {
code: 'YONGSHEN_ERROR',
message: '用神查询失败',
details: error.message
}
});
}
});
/**
* @route POST /api/qimen/batch-calculate
* @desc 批量奇门起局计算
* @access Public
*/
router.post('/batch-calculate', async (req, res) => {
try {
const { timeList, options = {} } = req.body;
// 输入验证
if (!Array.isArray(timeList) || timeList.length === 0) {
return res.status(400).json({
success: false,
error: {
code: 'INVALID_INPUT',
message: '时间列表不能为空',
details: 'timeList必须是非空数组'
}
});
}
if (timeList.length > 10) {
return res.status(400).json({
success: false,
error: {
code: 'TOO_MANY_REQUESTS',
message: '批量请求数量过多',
details: '单次批量请求不能超过10个时间点'
}
});
}
const results = [];
const errors = [];
for (let i = 0; i < timeList.length; i++) {
try {
const datetime = new Date(timeList[i]);
if (isNaN(datetime.getTime())) {
errors.push({
index: i,
datetime: timeList[i],
error: '时间格式不正确'
});
continue;
}
const qimenPan = qimenAnalyzer.calculator.calculateQimenPan(datetime);
results.push({
index: i,
datetime: timeList[i],
qimenPan: {
timeInfo: qimenPan.timeInfo,
jushu: qimenPan.timeInfo.jushu,
yindun: qimenPan.timeInfo.yindun ? '阴遁' : '阳遁',
zhifu: qimenPan.zhifu,
zhishi: qimenPan.zhishi
}
});
} catch (error) {
errors.push({
index: i,
datetime: timeList[i],
error: error.message
});
}
}
res.json({
success: true,
data: {
results,
errors,
summary: {
total: timeList.length,
successful: results.length,
failed: errors.length
}
}
});
} catch (error) {
logger.error('批量计算失败', error);
res.status(500).json({
success: false,
error: {
code: 'BATCH_CALCULATION_ERROR',
message: '批量计算失败',
details: error.message
}
});
}
});
// 错误处理中间件
router.use((error, req, res, next) => {
console.error('奇门API错误:', error);
res.status(500).json({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: '服务器内部错误',
details: process.env.NODE_ENV === 'development' ? error.message : '请联系管理员'
}
});
});
module.exports = router;