Files
opendance-web/web-server/src/main.ts
T
zijunchiang 75498db1c0 feat(web): 部署方式由 Docker 改为 Linux 原生站点(nginx + systemd)
需求变更:Web 服务端不再使用 Docker,改为直接运行在 Linux 上的网站。

- 移除 deploy/docker-compose.yml、web-server/Dockerfile、
  web-server/admin/Dockerfile、web-server/.dockerignore
- 新增 deploy/install.sh:建服务用户与目录 → 装运行期依赖 → 迁移
  → 注册 systemd 服务 → 配置 nginx 站点 → 健康检查,一键完成
- 新增 deploy/systemd/opendance-api.service:ProtectSystem=strict 等
  安全加固,ReadWritePaths 限定为上传目录
- 新增 deploy/nginx/opendance.conf:同站点托管管理端静态资源 +
  反代 /api /health /static,client_max_body_size 512m,SPA 回退
- 新增 deploy/uninstall.sh:默认保留数据,--purge 才删除
- 新增配置项 HOST(AppConfig.bindHost):原生部署下 Node 只监听
  127.0.0.1,不直接对外暴露,实测外部 IP 不可达
- 同步更新 contracts/rest-api.yaml、DSH.md、project.yaml、README.txt、
  tasks/008、tasks/010 与发布打包脚本
2026-09-14 22:42:56 +08:00

71 lines
2.5 KiB
TypeScript

import { Logger, ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { existsSync, mkdirSync } from 'node:fs';
import { resolve } from 'node:path';
import { AppModule } from './app.module';
import { AppConfig, configuration, validateEnv } from './config/configuration';
async function bootstrap(): Promise<void> {
const logger = new Logger('Bootstrap');
// 启动前校验环境变量(生产环境禁止 DB_SYNCHRONIZE=true 等)
const problems = validateEnv(configuration());
if (problems.length > 0) {
for (const problem of problems) logger.warn(`环境变量问题:${problem}`);
if ((process.env.NODE_ENV ?? 'development') === 'production') {
logger.error('生产环境配置校验失败,启动中止');
process.exit(1);
}
}
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
logger: ['error', 'warn', 'log'],
});
const config = app.get(AppConfig);
app.useGlobalPipes(
new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: false,
transformOptions: { enableImplicitConversion: false },
}),
);
// Windows 客户端与管理端跨源访问
app.enableCors({
origin: true,
credentials: false,
exposedHeaders: ['Content-Disposition'],
});
// 上传文件静态访问:/static/<kind>/<yyyy>/<mm>/<sha256>.<ext>
const storageDir = resolve(config.storageDir);
if (!existsSync(storageDir)) mkdirSync(storageDir, { recursive: true });
app.useStaticAssets(storageDir, { prefix: '/static/', index: false, fallthrough: true });
// 契约快照静态访问(若存在):/contracts/rest-api.yaml 等
if (config.contractsDir && existsSync(config.contractsDir)) {
app.useStaticAssets(resolve(config.contractsDir), { prefix: '/contracts/', index: false });
}
app.enableShutdownHooks();
const db = config.db;
await app.listen(config.port, config.bindHost);
logger.log(`OpenDance Studio Web 服务端已启动:http://${config.bindHost}:${config.port}`);
logger.log(`健康检查:http://${config.bindHost}:${config.port}/health`);
logger.log(`数据库:${db.host}:${db.port}/${db.name}(现有实例,本服务不管理 MySQL)`);
logger.log(`文件存储:${storageDir}`);
logger.log(`契约版本:${JSON.stringify(config.contractVersions)}`);
}
void bootstrap().catch((error) => {
// eslint-disable-next-line no-console
console.error('服务启动失败:', error);
process.exit(1);
});