-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
114 lines (99 loc) · 3.09 KB
/
app.js
File metadata and controls
114 lines (99 loc) · 3.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const cookieParser = require('cookie-parser');
const dotenv = require('dotenv');
const { sequelize, syncDatabase } = require('./models');
const routes = require('./routes');
const { globalLimiter } = require('./middlewares/rateLimiter');
const csrfProtection = require('./middlewares/csrf');
const { AppError } = require('./utils/AppError');
const logger = require('./utils/logger');
// 加载环境变量
dotenv.config();
// 启动前校验必需的环境变量
if (!process.env.JWT_SECRET) {
logger.fatal('环境变量 JWT_SECRET 未设置。请在 .env 文件中配置后重启。');
process.exit(1);
}
const app = express();
// 中间件
app.use(helmet());
app.use(cors({
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
credentials: true,
}));
app.use(cookieParser());
app.use(globalLimiter);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/uploads', express.static('uploads'));
app.use('/api', csrfProtection);
// 请求日志
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
logger.info({
method: req.method, url: req.originalUrl,
status: res.statusCode, ms: Date.now() - start,
});
});
next();
});
app.get('/api/health', (req, res) => {
res.status(200).json({ status: 'ok', timestamp: new Date() });
});
// 路由
app.use('/api', routes);
// 404处理
app.use((req, res) => {
res.status(404).json({ success: false, message: '路由不存在', code: 'NOT_FOUND' });
});
// 全局错误处理
app.use((err, req, res, next) => {
// 已发送响应则交给 Express 默认处理
if (res.headersSent) {
return next(err);
}
// 可预期的业务错误(AppError 及其子类)
if (err.isOperational) {
return res.status(err.statusCode).json({
success: false,
message: err.message,
code: err.code,
});
}
// Sequelize 验证错误
if (err.name === 'SequelizeValidationError' || err.name === 'SequelizeUniqueConstraintError') {
const messages = err.errors ? err.errors.map(e => e.message).join('; ') : err.message;
return res.status(400).json({
success: false,
message: messages,
code: 'VALIDATION_ERROR',
});
}
// express-validator / JSON 解析错误
if (err.type === 'entity.parse.failed') {
return res.status(400).json({
success: false,
message: '请求体 JSON 格式无效',
code: 'VALIDATION_ERROR',
});
}
// 未知错误 — 生产环境隐藏细节
logger.error({ err, method: req.method, url: req.originalUrl }, 'Unhandled error');
res.status(500).json({
success: false,
message: process.env.NODE_ENV === 'development' ? err.message : '服务器内部错误',
code: 'INTERNAL_ERROR',
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
});
});
// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
// 同步数据库
await syncDatabase();
logger.info(`服务器在 http://localhost:${PORT} 运行中`);
});
module.exports = app;