feat: 实现 AIO 容器部署

This commit is contained in:
SkyJourney
2026-07-26 16:55:36 +08:00
parent d1b5880a5b
commit 15dd2acfc0
13 changed files with 425 additions and 32 deletions
+57 -3
View File
@@ -56,6 +56,12 @@ export interface PdfEngineOptions {
launchBrowser?: () => Promise<Browser>;
}
export interface PdfEngineRuntimeLimits {
concurrency: number;
maxQueue: number;
timeoutMs: number;
}
interface PagedRuntimeResult {
pageCount: number;
contentHeight: number;
@@ -168,6 +174,53 @@ function normalizeRenderOrigin(value: string) {
return url.origin;
}
function readIntegerEnvironment(
environment: NodeJS.ProcessEnv,
name: string,
fallback: number,
minimum: number
) {
const rawValue = environment[name];
if (rawValue === undefined || rawValue.trim() === "") {
return fallback;
}
if (!/^\d+$/.test(rawValue)) {
throw new Error(`${name} 必须是整数`);
}
const value = Number.parseInt(rawValue, 10);
if (!Number.isSafeInteger(value) || value < minimum) {
throw new Error(`${name} 不能小于 ${minimum}`);
}
return value;
}
export function readPdfEngineRuntimeLimits(
environment: NodeJS.ProcessEnv = process.env
): PdfEngineRuntimeLimits {
return {
concurrency: readIntegerEnvironment(
environment,
"PDF_CONCURRENCY",
2,
1
),
maxQueue: readIntegerEnvironment(
environment,
"PDF_MAX_QUEUE",
8,
0
),
timeoutMs: readIntegerEnvironment(
environment,
"PDF_TIMEOUT_MS",
60_000,
1_000
)
};
}
export function isAllowedPdfRequestUrl(
requestUrl: string,
renderOrigin: string
@@ -189,6 +242,7 @@ export class PlaywrightPdfGenerator implements PdfGenerator {
private closed = false;
constructor(options: PdfEngineOptions = {}) {
const runtimeLimits = readPdfEngineRuntimeLimits();
this.renderOrigin = normalizeRenderOrigin(
options.renderOrigin ??
process.env.PDF_RENDER_ORIGIN ??
@@ -198,10 +252,10 @@ export class PlaywrightPdfGenerator implements PdfGenerator {
"/preview-frame.html?target=pdf",
this.renderOrigin
).href;
this.timeoutMs = options.timeoutMs ?? 60_000;
this.timeoutMs = options.timeoutMs ?? runtimeLimits.timeoutMs;
this.gate = new ConcurrencyGate(
options.concurrency ?? 2,
options.maxQueue ?? 8
options.concurrency ?? runtimeLimits.concurrency,
options.maxQueue ?? runtimeLimits.maxQueue
);
this.launchBrowser =
options.launchBrowser ??
+36
View File
@@ -8,6 +8,7 @@ import {
PdfRenderTimeoutError,
PlaywrightPdfGenerator,
isAllowedPdfRequestUrl,
readPdfEngineRuntimeLimits,
type PdfRenderPayload
} from "../src/pdf-engine.js";
@@ -102,6 +103,41 @@ describe("PDF 并发控制", () => {
});
});
describe("PDF 运行参数", () => {
it("读取容器环境变量", () => {
expect(
readPdfEngineRuntimeLimits({
PDF_CONCURRENCY: "1",
PDF_MAX_QUEUE: "4",
PDF_TIMEOUT_MS: "120000"
})
).toEqual({
concurrency: 1,
maxQueue: 4,
timeoutMs: 120_000
});
});
it("缺少环境变量时使用默认值", () => {
expect(readPdfEngineRuntimeLimits({})).toEqual({
concurrency: 2,
maxQueue: 8,
timeoutMs: 60_000
});
});
it.each([
["PDF_CONCURRENCY", "0"],
["PDF_MAX_QUEUE", "-1"],
["PDF_TIMEOUT_MS", "999"],
["PDF_CONCURRENCY", "1.5"]
])("拒绝无效的 %s", (name, value) => {
expect(() =>
readPdfEngineRuntimeLimits({ [name]: value })
).toThrow(name);
});
});
describe("PDF 网络策略", () => {
it("只允许渲染同源和内嵌资源", () => {
const origin = "http://127.0.0.1:5173";