import express, { type Express, type Request, type Response } from 'express'; import cors from 'cors'; import { API_PREFIX } from '@classhelper/shared'; import { env } from './config/env.js'; import { pingDatabase } from './lib/db.js'; import { sendOk } from './lib/http.js'; import { logger } from './lib/logger.js'; import { errorHandler, notFoundHandler } from './middleware/error.js'; import { apiModules } from './modules/registry.js'; async function healthHandler(_req: Request, res: Response): Promise { sendOk( res, { status: 'ok', version: '0.1.0', uptimeSeconds: Math.round(process.uptime()), database: { provider: env.databaseProvider, connected: await pingDatabase(), }, modules: apiModules.map((module) => module.name), }, '服务运行正常', ); } /** * 根路径落地页。 * API 全部挂在 /api 下,根路径本身不是接口;这里给出一个可读的服务首页, * 避免浏览器直接访问 4000 端口时看到 404 JSON 而误以为服务没起来。 */ function renderLandingPage(): string { const moduleRows = apiModules .map((module) => `
  • ${API_PREFIX}${module.basePath} ${module.name}
  • `) .join(''); return ` 班级小助手 · 后端服务

    班级小助手 · 后端服务 running

    版本 0.1.0 · 运行环境 ${env.nodeEnv} · 数据库 ${env.databaseProvider}

    健康检查:${API_PREFIX}/health · 管理端开发地址:http://127.0.0.1:5173

    已挂载模块(${apiModules.length} 个)
    提示:本服务只提供 REST API 与 WebSocket,没有网页界面。
    网页界面请访问 Web 管理端 http://127.0.0.1:5173(pnpm dev:web), WebSocket 端点为 ws://127.0.0.1:${env.port}/socket.io。
    `; } /** 组装 Express 应用:中间件 -> 健康检查 -> 模块路由 -> 404 -> 统一错误处理 */ export function createApp(): Express { const app = express(); app.disable('x-powered-by'); app.use(cors({ origin: env.corsOrigins, credentials: true })); app.use(express.json({ limit: '2mb' })); app.use(express.urlencoded({ extended: true })); app.use((req, _res, next) => { logger.debug(`${req.method} ${req.originalUrl}`); next(); }); // 服务首页(浏览器可读);如果客户端显式要 JSON,则返回结构化信息 app.get('/', (req, res) => { if (req.accepts(['html', 'json']) === 'json') { sendOk( res, { service: 'class-helper-server', version: '0.1.0', apiPrefix: API_PREFIX, webAdmin: 'http://127.0.0.1:5173', socket: `/socket.io`, modules: apiModules.map((module) => `${API_PREFIX}${module.basePath}`), }, '服务运行正常,API 前缀为 /api', ); return; } res.type('html').send(renderLandingPage()); }); // 浏览器会自动请求 favicon,直接返回 204,避免污染日志 app.get('/favicon.ico', (_req, res) => { res.status(204).end(); }); // 健康检查(同时暴露已挂载模块列表,便于确认模块化装配结果) app.get(`${API_PREFIX}/health`, healthHandler); app.get('/health', healthHandler); // 模块化装配:注册表里的每个模块挂载到 /api for (const module of apiModules) { const mountPath = `${API_PREFIX}${module.basePath}`; app.use(mountPath, module.router); logger.info(`已挂载模块 ${module.name.padEnd(14)} -> ${mountPath}`); } app.use(notFoundHandler); app.use(errorHandler); return app; }