This repository was archived by the owner on Jun 15, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
232 lines (194 loc) · 6.95 KB
/
Copy pathindex.ts
File metadata and controls
232 lines (194 loc) · 6.95 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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import 'dotenv/config';
import os from 'os';
import fs from 'fs';
import path from 'path';
import express, { Request, Response, NextFunction } from 'express';
import cluster from 'cluster';
import helmet from 'helmet';
// @ts-ignore
import bodyParser from 'body-parser';
// @ts-ignore
import cookieParser from 'cookie-parser';
import http from 'http';
// @ts-ignore
import authorize from './utils/authorizer';
// @ts-ignore
import { globalLimiter, authLimiter } from './utils/limiter';
// @ts-ignore
import jwtParser from './utils/jwtParser';
// @ts-ignore
import logger from './utils/logger';
// @ts-ignore
import models from './models';
// @ts-ignore
import { connectDatabase, closeDatabase, syncDatabase, createDefaultAdmin } from './db';
// @ts-ignore
import authRoutes from './routes/auth';
// @ts-ignore
import userRoutes from './routes/user';
// @ts-ignore
import studentRoutes from './routes/student';
// @ts-ignore
import classRoutes from './routes/classes';
const NEED_INIT_DB = process.env.NEED_INIT === 'true';
const API_BASE_ROUTE = process.env.API_BASE_ROUTE || '/api';
const numCPUs = process.env.NODE_ENV === 'development' ? 1 : os.cpus().length;
if (cluster.isPrimary) {
logger.system.startup(`Master process started with PID ${process.pid}`);
const pidFile = path.join(__dirname, 'spm_backend.pid');
fs.writeFileSync(pidFile, process.pid.toString());
(async () => {
logger.system.database('connection_attempt');
const connected = await connectDatabase();
if (!connected) {
logger.system.error(new Error('Database connection failed'), 'master_process');
process.exit(1);
}
logger.system.database('sync_attempt', { needInit: NEED_INIT_DB });
const synced = await syncDatabase( NEED_INIT_DB );
if (!synced) {
logger.system.error(new Error('Database synchronization failed'), 'master_process');
process.exit(1);
}
// 创建默认管理员账号
logger.system.database('create_default_admin');
await createDefaultAdmin();
logger.system.database('initialization_completed');
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
logger.info(`Worker process ${i + 1} forked`);
}
})();
process.on('SIGINT', async () => {
logger.system.shutdown('Received SIGINT signal, shutting down server');
for (const id in cluster.workers) {
if (cluster.workers[id]) {
cluster.workers[id]!.kill('SIGINT');
}
}
await closeDatabase();
fs.unlinkSync(pidFile);
logger.system.shutdown('PID file removed, master process exiting');
process.exit(0);
});
process.on('SIGTERM', async () => {
logger.system.shutdown('Received SIGTERM signal, shutting down server');
for (const id in cluster.workers) {
if (cluster.workers[id]) {
cluster.workers[id]!.kill('SIGTERM');
}
}
await closeDatabase();
fs.unlinkSync(pidFile);
logger.system.shutdown('PID file removed, master process exiting');
process.exit(0);
});
process.on('uncaughtException', async (err) => {
logger.system.error(err, 'master_process_uncaught_exception');
await closeDatabase();
process.exit(1);
});
cluster.on('exit', (worker, code, signal) => {
logger.warn(`Worker process ${worker.process.pid} exited`, {
code,
signal,
action: 'worker_exit'
});
if (code !== 0 && !worker.exitedAfterDisconnect) {
logger.warn('Worker crashed, starting a new worker');
cluster.fork();
}
});
} else {
logger.system.startup(`Worker process ${process.pid} started`);
const app = express();
app.use(helmet());
const PORT = process.env.PORT || 8012;
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : ['http://localhost:3000'];
const server = http.createServer(app);
app.use(async (req: Request, res: Response, next: NextFunction) => {
const origin = req.headers.origin;
if (origin && ALLOWED_ORIGINS.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
app.use(globalLimiter);
app.use(bodyParser.json());
app.use(cookieParser());
app.use(jwtParser);
app.use(`${API_BASE_ROUTE}/v1/auth`, authLimiter, authRoutes);
app.use(`${API_BASE_ROUTE}/v1/user`, authorize(['admin']), userRoutes);
app.use(`${API_BASE_ROUTE}/v1/students`, authorize(['admin']), studentRoutes);
app.use(`${API_BASE_ROUTE}/v1/classes`, authorize(['admin']), classRoutes);
app.use(`/*path`, (req, res) => {
logger.warn(`API endpoint not found: ${req.originalUrl}`, {
method: req.method,
url: req.originalUrl,
ip: logger.getClientIP(req),
action: 'api_not_found'
});
res.status(404).json({
code: -1,
message: `API endpoint not found: ${req.originalUrl}`,
data: null
});
});
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
logger.system.error(err, 'global_error_handler');
res.status(500).json({
code: -1,
message: 'Internal server error',
data: process.env.NODE_ENV === 'development' ? err.message : null
});
});
server.listen(PORT, () => {
logger.system.startup(`Server listening on port ${PORT}`);
});
server.on('error', (error: any) => {
if (error.syscall !== 'listen') {
logger.system.error(error, 'server_error');
throw error;
}
if (error.code === 'EADDRINUSE') {
logger.error(`Port ${PORT} is already in use`, { port: PORT, action: 'port_in_use' });
process.exit(1);
} else {
logger.system.error(error, 'server_listen_error');
throw error;
}
});
process.on('uncaughtException', (err) => {
logger.system.error(err, 'worker_uncaught_exception');
server.close(() => {
logger.system.shutdown(`Worker process ${process.pid} closed due to uncaught exception`);
process.exit(1);
});
});
process.on('unhandledRejection', (reason, promise) => {
logger.system.error(new Error(`Unhandled promise rejection: ${reason}`), 'worker_unhandled_rejection');
});
process.on('SIGINT', () => {
logger.system.shutdown(`Worker process ${process.pid} received SIGINT signal`);
server.close(() => {
logger.system.shutdown(`Worker process ${process.pid} has closed HTTP server`);
process.exit(0);
});
});
process.on('SIGTERM', () => {
logger.system.shutdown(`Worker process ${process.pid} received SIGTERM signal`);
server.close(() => {
logger.system.shutdown(`Worker process ${process.pid} has closed HTTP server`);
process.exit(0);
});
});
process.on('exit', (code) => {
logger.info(`Worker process ${process.pid} exited`, { code, action: 'worker_exit' });
});
}