diff --git a/server/index.cjs b/server/index.cjs index 4b37f20..5c3374b 100644 --- a/server/index.cjs +++ b/server/index.cjs @@ -83,7 +83,7 @@ app.use(cors({ const allowedOrigins = [ 'http://localhost:5173', 'http://localhost:4173', - process.env.CORS_ORIGIN + ...(process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : []) ].filter(Boolean); // 允许所有.koyeb.app域名 diff --git a/server/index.cjs.bak b/server/index.cjs.bak new file mode 100644 index 0000000..4b37f20 --- /dev/null +++ b/server/index.cjs.bak @@ -0,0 +1,232 @@ +const express = require('express'); +const cors = require('cors'); +const helmet = require('helmet'); +const path = require('path'); +const { dbManager } = require('./database/index.cjs'); + +// 导入路由 +const authRoutes = require('./routes/auth.cjs'); +const analysisRoutes = require('./routes/analysis.cjs'); +const historyRoutes = require('./routes/history.cjs'); +const profileRoutes = require('./routes/profile.cjs'); +const downloadRoutes = require('./routes/download.cjs'); +const aiInterpretationRoutes = require('./routes/aiInterpretation.cjs'); +const qimenRoutes = require('./routes/qimen.cjs'); + +// 导入中间件 +const { errorHandler } = require('./middleware/errorHandler.cjs'); +const { requestLogger } = require('./middleware/logger.cjs'); + +const app = express(); +const PORT = process.env.PORT || 3001; + +// 初始化数据库 +try { + dbManager.init(); + console.log('数据库连接成功'); + + // 在生产环境中,确保管理员用户存在 + if (process.env.NODE_ENV === 'production') { + const db = dbManager.getDatabase(); + const adminExists = db.prepare('SELECT id FROM users WHERE email = ?').get('admin@localhost'); + + if (!adminExists) { + const bcrypt = require('bcryptjs'); + const adminPassword = bcrypt.hashSync('admin123', 12); + + // 创建管理员用户 + const insertAdmin = db.prepare( + 'INSERT INTO users (email, password_hash) VALUES (?, ?)' + ); + const adminResult = insertAdmin.run('admin@localhost', adminPassword); + + // 创建管理员档案 + const insertAdminProfile = db.prepare( + 'INSERT INTO user_profiles (user_id, full_name, username) VALUES (?, ?, ?)' + ); + insertAdminProfile.run(adminResult.lastInsertRowid, '系统管理员', 'admin'); + + console.log('✅ 管理员用户创建成功'); + } + } +} catch (error) { + console.error('数据库连接失败:', error); + process.exit(1); +} + +// 安全中间件 +app.use(helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + scriptSrc: ["'self'"], + imgSrc: ["'self'", "data:", "https:"], + connectSrc: [ + "'self'", + "https://api.openai.com", + "https://open.bigmodel.cn", + "https://*.openai.azure.com", + "https://api.anthropic.com", + "https://generativelanguage.googleapis.com" + ], + }, + }, + crossOriginEmbedderPolicy: false +})); + +// CORS配置 +app.use(cors({ + origin: process.env.NODE_ENV === 'production' + ? (origin, callback) => { + // 生产环境的严格检查 + const allowedOrigins = [ + 'http://localhost:5173', + 'http://localhost:4173', + process.env.CORS_ORIGIN + ].filter(Boolean); + + // 允许所有.koyeb.app域名 + if (!origin || origin.endsWith('.koyeb.app') || allowedOrigins.includes(origin)) { + callback(null, true); + } else { + callback(new Error('Not allowed by CORS')); + } + } + : true, // 开发环境允许所有域名 + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'] +})); + +// 基础中间件 +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true, limit: '10mb' })); +app.use(requestLogger); + +// 健康检查端点 +app.get('/health', (req, res) => { + res.json({ + status: 'ok', + timestamp: new Date().toISOString(), + database: 'connected' + }); +}); + +// API健康检查端点(用于Koyeb监控) +app.get('/api/health', (req, res) => { + res.status(200).json({ + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + database: 'connected' + }); +}); + +// API路由 +app.use('/api/auth', authRoutes); +app.use('/api/analysis', analysisRoutes); +app.use('/api/history', historyRoutes); +app.use('/api/profile', profileRoutes); +app.use('/api/download', downloadRoutes); +app.use('/api/ai-interpretation', aiInterpretationRoutes); +app.use('/api/qimen', qimenRoutes); + +// 静态文件服务 (用于生产环境) +// 强制在 Koyeb 部署时启用静态文件服务 +const isProduction = process.env.NODE_ENV === 'production' || process.env.PORT === '8000'; +console.log('当前环境:', process.env.NODE_ENV); +console.log('端口:', process.env.PORT); +console.log('是否为生产环境:', isProduction); + +if (isProduction) { + const distPath = path.join(__dirname, '../dist'); + const indexPath = path.join(distPath, 'index.html'); + + console.log('静态文件目录:', distPath); + console.log('index.html路径:', indexPath); + + // 检查文件是否存在 + const fs = require('fs'); + console.log('dist目录存在:', fs.existsSync(distPath)); + console.log('index.html存在:', fs.existsSync(indexPath)); + + if (fs.existsSync(distPath)) { + console.log('dist目录内容:', fs.readdirSync(distPath)); + } + + app.use(express.static(distPath)); + + // SPA路由处理 - 只处理非API请求 + app.get('*', (req, res, next) => { + if (req.path.startsWith('/api')) { + return next(); // 让后续的404处理器处理API请求 + } + + if (fs.existsSync(indexPath)) { + res.sendFile(indexPath); + } else { + console.error('index.html文件不存在:', indexPath); + res.status(404).json({ + error: { + code: 'STATIC_FILE_NOT_FOUND', + message: '静态文件不存在,请检查构建过程' + } + }); + } + }); +} + +// 404处理 +app.use('*', (req, res) => { + res.status(404).json({ + error: { + code: 'NOT_FOUND', + message: '请求的资源不存在' + } + }); +}); + +// 错误处理中间件 +app.use(errorHandler); + +// 启动服务器 +const server = app.listen(PORT, '0.0.0.0', () => { + console.log(`🚀 服务器运行在 http://0.0.0.0:${PORT}`); + console.log(`📊 数据库文件: ${path.resolve('./numerology.db')}`); + console.log(`🌍 环境: ${process.env.NODE_ENV || 'development'}`); + console.log(`🏥 健康检查: http://0.0.0.0:${PORT}/api/health`); +}); + +// 优雅关闭 +process.on('SIGTERM', () => { + console.log('收到SIGTERM信号,开始优雅关闭...'); + server.close(() => { + console.log('HTTP服务器已关闭'); + dbManager.close(); + process.exit(0); + }); +}); + +process.on('SIGINT', () => { + console.log('收到SIGINT信号,开始优雅关闭...'); + server.close(() => { + console.log('HTTP服务器已关闭'); + dbManager.close(); + process.exit(0); + }); +}); + +// 未捕获异常处理 +process.on('uncaughtException', (error) => { + console.error('未捕获的异常:', error); + dbManager.close(); + process.exit(1); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error('未处理的Promise拒绝:', reason); + console.error('Promise:', promise); +}); + +module.exports = app; \ No newline at end of file diff --git a/src/components/CompleteQimenAnalysis.tsx b/src/components/CompleteQimenAnalysis.tsx index f591c30..0b21365 100644 --- a/src/components/CompleteQimenAnalysis.tsx +++ b/src/components/CompleteQimenAnalysis.tsx @@ -362,8 +362,8 @@ const CompleteQimenAnalysis: React.FC = ({ analysis, classNa )} {/* 专业九宫格奇门盘 */} -
-
+
+
{gridPositions.map((row, rowIndex) => row.map((palaceIndex, colIndex) => { const palace = qimenPan.dipan[palaceIndex]; @@ -374,21 +374,22 @@ const CompleteQimenAnalysis: React.FC = ({ analysis, classNa
{/* 顶部行:宫位信息 */} -
+
-
{palaceNames[palaceIndex]}
-
{palaceNumbers[palaceIndex]}
+
{palaceNames[palaceIndex]}
+
{palaceNumbers[palaceIndex]}
-
{palaceElements[palaceIndex]}
-
{palaceZhi[palaceIndex]}
+
{palaceElements[palaceIndex]}
+
{palaceZhi[palaceIndex]}
@@ -396,21 +397,21 @@ const CompleteQimenAnalysis: React.FC = ({ analysis, classNa
{/* 天干 - 最大最显眼 */} {palace?.gan && ( -
+
{palace.gan}
)} {/* 九星 */} {palace?.star && ( -
+
{palace.star}
)}
{/* 底部行:门神信息 */} -
+
{/* 左下角:八门 */}
{palace?.door && (