/** * TK-FB-Project API Server * * 작업 관리 시스템의 메인 서버 애플리케이션 * * @author TK-FB-Project * @since 2025-12-11 * @version 2.2.0 */ require('dotenv').config(); const express = require('express'); const setupMiddlewares = require('./config/middleware'); const setupRoutes = require('./config/routes'); const { errorHandler } = require('./middlewares/errorHandler'); const logger = require('./utils/logger'); const cache = require('./utils/cache'); const app = express(); const PORT = process.env.PORT || 20005; // Trust proxy for accurate IP addresses app.set('trust proxy', 1); // 미들웨어 설정 setupMiddlewares(app); // 라우트 설정 setupRoutes(app); // 에러 핸들러 (모든 라우트 이후에 위치) app.use(errorHandler); // 404 핸들러 app.use((req, res) => { logger.warn('404 Not Found', { url: req.originalUrl, method: req.method }); res.status(404).json({ success: false, error: '요청하신 경로를 찾을 수 없습니다', path: req.originalUrl }); }); // 서버 시작 const server = app.listen(PORT, () => { logger.info(`서버 시작 완료`, { port: PORT, env: process.env.NODE_ENV || 'development', nodeVersion: process.version }); console.log(`\n🚀 서버가 포트 ${PORT}에서 실행 중입니다.`); console.log(`📚 API 문서: http://localhost:${PORT}/api-docs\n`); }); // Graceful Shutdown const gracefulShutdown = (signal) => { logger.info(`${signal} 신호 수신 - 서버 종료 시작`); console.log(`\n🛑 ${signal} 신호를 받았습니다. 서버를 종료합니다...`); server.close(async () => { logger.info('HTTP 서버 종료 완료'); console.log('✅ HTTP 서버가 정상적으로 종료되었습니다.'); // 리소스 정리 try { // DB 연결 종료는 각 요청에서 pool을 사용하므로 불필요 // Redis 종료 (사용 중인 경우) if (cache.redis) { await cache.redis.quit(); logger.info('캐시 시스템 종료 완료'); } } catch (error) { logger.error('리소스 정리 중 오류 발생', { error: error.message }); } process.exit(0); }); // 30초 후 강제 종료 setTimeout(() => { logger.error('강제 종료 - 정상 종료 시간 초과'); console.error('❌ 정상 종료 실패, 강제 종료합니다.'); process.exit(1); }, 30000); }; // 시그널 핸들러 등록 process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGINT', () => gracefulShutdown('SIGINT')); // 처리되지 않은 Promise 거부 process.on('unhandledRejection', (reason, promise) => { logger.error('처리되지 않은 Promise 거부', { reason: reason, promise: promise }); console.error('⚠️ 처리되지 않은 Promise 거부:', reason); if (process.env.NODE_ENV === 'development') { process.exit(1); } }); // 처리되지 않은 예외 process.on('uncaughtException', (error) => { logger.error('처리되지 않은 예외', { error: error.message, stack: error.stack }); console.error('💥 처리되지 않은 예외:', error); gracefulShutdown('UNCAUGHT_EXCEPTION'); }); // 캐시 시스템 초기화 (선택적) (async () => { try { if (cache.initRedis) { await cache.initRedis(); logger.info('캐시 시스템 초기화 완료'); console.log('✅ 캐시 시스템 초기화 완료'); } } catch (error) { logger.warn('캐시 시스템 초기화 실패 - 계속 진행', { error: error.message }); console.warn('⚠️ 캐시 시스템 초기화 실패:', error.message); } })(); module.exports = app;