feat(web): 移除 Docker 与反代部署,改为 Linux 业务服务并自带管理端托管
需求变更:Web 端只保留业务需求,反向代理交由另外的服务处理, 本项目仅提供 IIS / nginx 反代说明文档,不部署反代。 主要改动: - web-server: 新增管理端静态托管(ADMIN_DIR、SPA 深链接回退), 反代因此只需一条全匹配转发规则;新增 adminDir 配置项 - deploy/install.sh: 删除 nginx 安装与站点配置逻辑,改为原生 Linux 服务部署(systemd → OpenRC → SysVinit → 后台进程四级降级), 打印反代交接指引 - deploy/ctl.sh: 启停/状态/日志脚本 - deploy/proxy/: 新增反代说明文档(README.md、nginx.conf.example、 web.config.example),标注上传上限与超时必须同步调整 - 删除 deploy/nginx/、web-server/admin/nginx.conf、Dockerfile、 docker-compose.yml、.dockerignore - scripts/package-release.sh: 锚定排除规则 + 产物完整性校验 - 文档同步:README.txt、DSH.md、project.yaml、tasks/008、tasks/010
This commit is contained in:
@@ -34,8 +34,9 @@ export class AppConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听地址。原生部署(nginx 前置)时设为 127.0.0.1,
|
||||
* 仅允许本机反代访问,不直接暴露 Node 进程。
|
||||
* 监听地址。
|
||||
* 反向代理与后端同机时建议设为 127.0.0.1(仅本机可访问);
|
||||
* 代理在其他机器上时必须为 0.0.0.0,并用防火墙限制来源。
|
||||
*/
|
||||
get bindHost(): string {
|
||||
return this.config.get<string>('bindHost') ?? '0.0.0.0';
|
||||
@@ -45,6 +46,15 @@ export class AppConfig {
|
||||
return this.config.get<string>('storageDir') ?? resolve(process.cwd(), 'storage');
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理端静态站点目录。
|
||||
* 本服务自带管理端页面,反向代理只需把请求整体转发到本端口即可,
|
||||
* 不需要代理层再单独托管静态文件(见 deploy/proxy/README.md)。
|
||||
*/
|
||||
get adminDir(): string {
|
||||
return this.config.get<string>('adminDir') || resolve(process.cwd(), '..', 'admin');
|
||||
}
|
||||
|
||||
get uploadMaxBytes(): number {
|
||||
return this.config.get<number>('uploadMaxBytes') ?? 512 * 1024 * 1024;
|
||||
}
|
||||
@@ -85,6 +95,7 @@ export const configuration = () => ({
|
||||
port: Number(process.env.PORT ?? 3000),
|
||||
bindHost: process.env.HOST ?? '0.0.0.0',
|
||||
storageDir: process.env.STORAGE_DIR ?? resolve(process.cwd(), 'storage'),
|
||||
adminDir: process.env.ADMIN_DIR ?? resolve(process.cwd(), '..', 'admin'),
|
||||
uploadMaxBytes: Number(process.env.UPLOAD_MAX_BYTES ?? 512 * 1024 * 1024),
|
||||
assimpBin: process.env.ASSIMP_BIN ?? 'assimp',
|
||||
contractsDir: process.env.CONTRACTS_DIR ?? '',
|
||||
|
||||
+40
-1
@@ -1,8 +1,9 @@
|
||||
import { Logger, ValidationPipe } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { existsSync, mkdirSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { extname, join, resolve } from 'node:path';
|
||||
import { AppModule } from './app.module';
|
||||
import { AppConfig, configuration, validateEnv } from './config/configuration';
|
||||
|
||||
@@ -51,6 +52,39 @@ async function bootstrap(): Promise<void> {
|
||||
app.useStaticAssets(resolve(config.contractsDir), { prefix: '/contracts/', index: false });
|
||||
}
|
||||
|
||||
// ---- 管理端静态站点 ----
|
||||
// 本服务同时提供管理端页面,因此反向代理只需要把全部请求转发到本端口,
|
||||
// 不需要在代理层再单独配置一个静态站点(IIS / nginx 示例见 deploy/proxy/)。
|
||||
const adminDir = resolve(config.adminDir);
|
||||
const adminIndex = join(adminDir, 'index.html');
|
||||
if (existsSync(adminIndex)) {
|
||||
app.useStaticAssets(adminDir, { index: 'index.html', fallthrough: true });
|
||||
|
||||
// SPA 深链接回退:/songs、/models 这类前端路由在磁盘上没有对应文件,
|
||||
// 需要返回 index.html 交给前端路由处理。
|
||||
// 注意必须放行后端路径,否则会吞掉 API 请求。
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
if (req.method !== 'GET' && req.method !== 'HEAD') return next();
|
||||
const path = req.path;
|
||||
if (
|
||||
path.startsWith('/api/') ||
|
||||
path === '/health' ||
|
||||
path.startsWith('/static/') ||
|
||||
path.startsWith('/contracts/')
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
// 带扩展名的请求视为静态资源,未命中就让它 404,不要回退成 HTML
|
||||
if (extname(path) !== '') return next();
|
||||
res.sendFile(adminIndex);
|
||||
});
|
||||
} else {
|
||||
logger.warn(
|
||||
`未找到管理端构建产物(${adminIndex}),本服务仅提供 API。` +
|
||||
'如需管理界面,请部署 admin/ 目录并设置 ADMIN_DIR。',
|
||||
);
|
||||
}
|
||||
|
||||
app.enableShutdownHooks();
|
||||
|
||||
const db = config.db;
|
||||
@@ -60,6 +94,11 @@ async function bootstrap(): Promise<void> {
|
||||
logger.log(`健康检查:http://${config.bindHost}:${config.port}/health`);
|
||||
logger.log(`数据库:${db.host}:${db.port}/${db.name}(现有实例,本服务不管理 MySQL)`);
|
||||
logger.log(`文件存储:${storageDir}`);
|
||||
logger.log(
|
||||
existsSync(adminIndex)
|
||||
? `管理端站点:http://${config.bindHost}:${config.port}/(由本服务直接提供)`
|
||||
: '管理端站点:未部署(仅 API)',
|
||||
);
|
||||
logger.log(`契约版本:${JSON.stringify(config.contractVersions)}`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user