feat(ops): v4.0.7 HQ 活动图快链与勾选导出

HQ 指定一张活动图为勾选主合伙人合成 PNG/zip;城市合伙人页增加快链与单下/导出。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-01 15:12:10 +08:00
parent eeee55e215
commit cdc4b82931
21 changed files with 1041 additions and 28 deletions
@@ -0,0 +1,64 @@
/**
* Sequential Sharp compose bench (1 / 10 / 50) on a 1080×1920 template.
* Usage: node scripts/with-api-env.cjs npx ts-node --transpile-only scripts/bench-activity-poster-pack.ts
* or from dukang-api: npx ts-node --transpile-only scripts/bench-activity-poster-pack.ts
*/
import sharp from 'sharp';
import {
DEFAULT_ACTIVITY_POSTER_QR_SLOT,
activityPosterQrSlotPx,
activityPosterTemplateTooLarge,
} from '@dukang/shared-types';
async function png(width: number, height: number, r: number, g: number, b: number) {
return sharp({
create: { width, height, channels: 3, background: { r, g, b } },
})
.png()
.toBuffer();
}
async function compose(template: Buffer, qrPng: Buffer) {
const meta = await sharp(template).metadata();
if (!meta.width || !meta.height) throw new Error('no size');
if (activityPosterTemplateTooLarge(meta.width, meta.height)) throw new Error('too large');
const { left, top, size } = activityPosterQrSlotPx(
meta.width,
meta.height,
DEFAULT_ACTIVITY_POSTER_QR_SLOT.qrXPct,
DEFAULT_ACTIVITY_POSTER_QR_SLOT.qrYPct,
DEFAULT_ACTIVITY_POSTER_QR_SLOT.qrSizePct,
);
const qr = await sharp(qrPng).resize(size, size, { fit: 'fill' }).png().toBuffer();
return sharp(template).composite([{ input: qr, left, top }]).png().toBuffer();
}
function rssMb() {
return Math.round(process.memoryUsage().rss / 1024 / 1024);
}
async function bench(n: number, template: Buffer, qr: Buffer) {
const t0 = Date.now();
const rss0 = rssMb();
let last = 0;
for (let i = 0; i < n; i += 1) {
const out = await compose(template, qr);
last = out.length;
}
const ms = Date.now() - t0;
return { n, ms, avgMs: Math.round(ms / n), rss0, rss1: rssMb(), lastKb: Math.round(last / 1024) };
}
async function main() {
const template = await png(1080, 1920, 160, 32, 32);
const qr = await png(430, 430, 255, 255, 255);
console.log(`template=${Math.round(template.length / 1024)}KB qr=${Math.round(qr.length / 1024)}KB rss=${rssMb()}MB`);
for (const n of [1, 10, 50]) {
const row = await bench(n, template, qr);
console.log(
`n=${row.n} total=${row.ms}ms avg=${row.avgMs}ms rss ${row.rss0}->${row.rss1}MB out≈${row.lastKb}KB`,
);
}
}
void main();