feat: 实现 AIO 容器部署
This commit is contained in:
@@ -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 ??
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user