Files
suanming/server/index.cjs
patdelphi 9231651ae1 feat: 完成分析结果下载功能实现
- 新增DownloadButton组件,支持Markdown、PDF、PNG三种格式下载
- 实现后端下载API接口(/api/download)
- 添加Markdown、PDF、PNG三种格式生成器
- 集成下载按钮到所有分析结果页面
- 修复API路径配置问题,确保开发环境正确访问后端
- 添加下载历史记录功能和数据库表结构
- 完善错误处理和用户反馈机制
2025-08-21 12:44:40 +08:00

195 lines
5.3 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 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 { 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('数据库连接成功');
} 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:"],
},
},
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);
// 静态文件服务 (用于生产环境)
// 强制在 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;