app.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. #!/usr/bin/env node
  2. /**
  3. * 应用主入口文件 - 启动服务器并初始化所有组件
  4. */
  5. // 记录服务器启动时间 - 最先执行这行代码,确保第一时间记录
  6. global.serverStartTime = Date.now();
  7. const express = require('express');
  8. const session = require('express-session');
  9. const path = require('path');
  10. const http = require('http');
  11. const logger = require('./logger');
  12. const { ensureDirectoriesExist } = require('./init-dirs');
  13. const registerRoutes = require('./routes');
  14. const { requireLogin, sessionActivity, sanitizeRequestBody, securityHeaders } = require('./middleware/auth');
  15. // 记录服务器启动时间到日志
  16. console.log(`服务器启动,时间戳: ${global.serverStartTime}`);
  17. logger.warn(`服务器启动,时间戳: ${global.serverStartTime}`);
  18. // 使用 SQLite 存储 session - 替代文件存储
  19. const SQLiteStore = require('connect-sqlite3')(session);
  20. // 确保目录结构存在
  21. ensureDirectoriesExist().catch(err => {
  22. logger.error('创建必要目录失败:', err);
  23. process.exit(1);
  24. });
  25. // 初始化Express应用 - 确保正确初始化
  26. const app = express();
  27. const server = http.createServer(app);
  28. // 基本中间件配置
  29. app.use(express.json());
  30. app.use(express.urlencoded({ extended: true }));
  31. app.use(express.static(path.join(__dirname, 'web')));
  32. // 添加对documentation目录的静态访问
  33. app.use('/documentation', express.static(path.join(__dirname, 'documentation')));
  34. app.use(sessionActivity);
  35. app.use(sanitizeRequestBody);
  36. app.use(securityHeaders);
  37. // 会话配置 - 使用SQLite存储
  38. app.use(session({
  39. secret: process.env.SESSION_SECRET || 'hubcmdui-secret-key',
  40. resave: false,
  41. saveUninitialized: false,
  42. cookie: {
  43. secure: process.env.NODE_ENV === 'production',
  44. maxAge: 24 * 60 * 60 * 1000 // 24小时
  45. },
  46. store: new SQLiteStore({
  47. db: 'app.db',
  48. dir: path.join(__dirname, 'data'),
  49. table: 'sessions'
  50. })
  51. }));
  52. // 添加一个中间件来检查API请求的会话状态
  53. app.use('/api', (req, res, next) => {
  54. // 这些API端点不需要登录
  55. const publicEndpoints = [
  56. '/api/login',
  57. '/api/logout',
  58. '/api/check-session',
  59. '/api/health',
  60. '/api/system-status',
  61. '/api/system-resource-details',
  62. '/api/menu-items',
  63. '/api/config',
  64. '/api/monitoring-config',
  65. '/api/documentation',
  66. '/api/documentation/file'
  67. ];
  68. // 如果是公共API或用户已登录,则继续
  69. if (publicEndpoints.includes(req.path) ||
  70. publicEndpoints.some(endpoint => req.path.startsWith(endpoint)) ||
  71. (req.session && req.session.user)) {
  72. return next();
  73. }
  74. // 否则返回401未授权
  75. logger.warn(`未授权访问: ${req.path}`);
  76. return res.status(401).json({ error: 'Unauthorized' });
  77. });
  78. // 导入并注册所有路由
  79. registerRoutes(app);
  80. // 默认路由
  81. app.get('/', (req, res) => {
  82. res.sendFile(path.join(__dirname, 'web', 'index.html'));
  83. });
  84. app.get('/admin', (req, res) => {
  85. res.sendFile(path.join(__dirname, 'web', 'admin.html'));
  86. });
  87. // 404处理
  88. app.use((req, res) => {
  89. res.status(404).json({ error: 'Not Found' });
  90. });
  91. // 错误处理中间件
  92. app.use((err, req, res, next) => {
  93. logger.error('应用错误:', err);
  94. res.status(500).json({ error: '服务器内部错误', details: err.message });
  95. });
  96. // 启动服务器
  97. const PORT = process.env.PORT || 3000;
  98. server.listen(PORT, async () => {
  99. logger.info(`服务器已启动并监听端口 ${PORT}`);
  100. try {
  101. // 确保目录存在
  102. await ensureDirectoriesExist();
  103. // 启动Session清理任务
  104. await startSessionCleanupTask();
  105. logger.success('系统初始化完成');
  106. } catch (error) {
  107. logger.error('系统初始化失败:', error);
  108. }
  109. });
  110. // 启动定期清理过期会话的任务
  111. async function startSessionCleanupTask() {
  112. const database = require('./database/database');
  113. // 立即清理一次
  114. try {
  115. await database.cleanExpiredSessions();
  116. } catch (error) {
  117. logger.error('清理过期会话失败:', error);
  118. }
  119. // 每小时清理一次过期会话
  120. setInterval(async () => {
  121. try {
  122. await database.cleanExpiredSessions();
  123. } catch (error) {
  124. logger.error('定期清理过期会话失败:', error);
  125. }
  126. }, 60 * 60 * 1000); // 1小时
  127. }
  128. // 监听进程退出事件,确保数据库连接正确关闭
  129. process.on('SIGINT', async () => {
  130. logger.info('收到SIGINT信号,正在关闭服务器...');
  131. const database = require('./database/database');
  132. await database.close();
  133. process.exit(0);
  134. });
  135. process.on('SIGTERM', async () => {
  136. logger.info('收到SIGTERM信号,正在关闭服务器...');
  137. const database = require('./database/database');
  138. await database.close();
  139. process.exit(0);
  140. });
  141. // 注册进程事件处理 - 优雅关闭
  142. process.on('SIGINT', async () => {
  143. logger.info('接收到中断信号,正在关闭服务...');
  144. const database = require('./database/database');
  145. await database.close();
  146. server.close(() => {
  147. logger.info('服务器已关闭');
  148. process.exit(0);
  149. });
  150. });
  151. process.on('SIGTERM', async () => {
  152. logger.info('接收到终止信号,正在关闭服务...');
  153. const database = require('./database/database');
  154. await database.close();
  155. server.close(() => {
  156. logger.info('服务器已关闭');
  157. process.exit(0);
  158. });
  159. });
  160. module.exports = { app, server };
  161. // 路由注册函数
  162. function registerRoutes(app) {
  163. try {
  164. logger.info('开始注册路由...');
  165. // API端点
  166. app.use('/api', [
  167. require('./routes/index'),
  168. require('./routes/docker'),
  169. require('./routes/docs'),
  170. require('./routes/users'),
  171. require('./routes/menu'),
  172. require('./routes/server')
  173. ]);
  174. logger.info('基本API路由已注册');
  175. // 系统路由 - 函数式注册
  176. const systemRouter = require('./routes/system');
  177. app.use('/api/system', systemRouter);
  178. logger.info('系统路由已注册');
  179. // 认证路由 - 直接使用Router实例
  180. const authRouter = require('./routes/auth');
  181. app.use('/api', authRouter);
  182. logger.info('认证路由已注册');
  183. // 配置路由 - 使用 Express Router
  184. const configRouter = require('./routes/config');
  185. if (configRouter && typeof configRouter === 'object') {
  186. logger.info('配置路由是一个 Router 对象,正在注册...');
  187. app.use('/api/config', configRouter);
  188. logger.info('配置路由已注册');
  189. } else {
  190. logger.error('配置路由不是一个有效的 Router 对象,无法注册', typeof configRouter);
  191. }
  192. logger.success('✓ 所有路由已注册');
  193. } catch (error) {
  194. logger.error('路由注册失败:', error);
  195. }
  196. }