53 lines
2.2 KiB
TypeScript
53 lines
2.2 KiB
TypeScript
/**
|
||
* 客户端静态资源托管(任务 A1.5)。
|
||
*
|
||
* 构建后的客户端(client/app/dist)由平台服务托管,这样一个端口就能跑起完整平台——
|
||
* 桌面端与浏览器都指向同一个服务地址。未构建时返回 false,由调用方按 404 处理。
|
||
*/
|
||
import { existsSync } from 'node:fs';
|
||
import { readFile } from 'node:fs/promises';
|
||
import type { ServerResponse } from 'node:http';
|
||
import { extname, join, normalize, resolve, sep } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const MIME_TYPES: Record<string, string> = {
|
||
'.html': 'text/html; charset=utf-8',
|
||
'.js': 'text/javascript; charset=utf-8',
|
||
'.css': 'text/css; charset=utf-8',
|
||
'.json': 'application/json; charset=utf-8',
|
||
'.svg': 'image/svg+xml',
|
||
'.png': 'image/png',
|
||
'.jpg': 'image/jpeg',
|
||
'.woff2': 'font/woff2',
|
||
};
|
||
|
||
/** 默认目录:仓库内 client/app/dist;可用 APS_CLIENT_DIST 覆盖 */
|
||
export function clientDistDir(): string {
|
||
const override = process.env.APS_CLIENT_DIST;
|
||
if (override && override.trim() !== '') return resolve(override);
|
||
// server/dist/entry/static.js → 仓库根
|
||
return fileURLToPath(new URL('../../../client/app/dist', import.meta.url));
|
||
}
|
||
|
||
export function createStaticHandler(root = clientDistDir()): (urlPath: string, res: ServerResponse) => Promise<boolean> {
|
||
const rootDir = resolve(root);
|
||
const indexFile = join(rootDir, 'index.html');
|
||
|
||
return async function serveStatic(urlPath: string, res: ServerResponse): Promise<boolean> {
|
||
if (!existsSync(indexFile)) return false;
|
||
|
||
const relativePath = normalize(decodeURIComponent(urlPath)).replace(/^[/\\]+/, '');
|
||
const candidate = resolve(rootDir, relativePath);
|
||
const insideRoot = candidate === rootDir || candidate.startsWith(rootDir + sep);
|
||
if (!insideRoot) return false; // 目录穿越
|
||
|
||
const file = relativePath === '' || !existsSync(candidate) || !extname(candidate) ? indexFile : candidate;
|
||
const body = await readFile(file);
|
||
res.writeHead(200, {
|
||
'content-type': MIME_TYPES[extname(file)] ?? 'application/octet-stream',
|
||
'cache-control': file === indexFile ? 'no-store' : 'public, max-age=3600',
|
||
});
|
||
res.end(body);
|
||
return true;
|
||
};
|
||
} |