项目概述
本项目实现图片缩略图生成服务:校验上传、在内容寻址键下存储原图、异步生成多个尺寸并从中提供变体。上传与生成缩略图是小功能,却有很长的被利用清单——畸形文件、解压炸弹,或挤占请求路径的昂贵解码。
架构与关键决策
上传端点按字节而非声明类型校验内容并强制大小上限,在内容哈希下存原图并以该哈希为键入队;工作进程解码一次并产出每个配置尺寸,按哈希与尺寸派生确定性键,使重试覆盖同一对象而不新建。内容寻址使整条管线幂等:同样的字节两次上传映射同一键,不产生第二份原图或第二组变体。元数据剥离移除 EXIF 与位置信息,因为用于缩略图的图片常携带上传者无意公开的信息。
实施与测试
上传真实图片断言每个配置尺寸存在且尺寸正确;上传带图片扩展名的畸形文件断言被拒;相同字节二次上传断言同一键且不新建变体;断言变体不含元数据;断言重新生成覆盖同一键。
限制与后续
- 固定尺寸与格式集合,不支持任意变换。
- 小图也走异步,客户端需轮询或等待通知。
- 尺寸配置变化不会自动重生成,需显式再生成。
代码示例
pnpm init
pnpm add sharp @aws-sdk/client-s3 express
pnpm add -D typescript tsx vitest
thumbnail-service/
├── src/upload.ts
├── src/worker.ts
├── src/variants.ts
├── src/storage.ts
├── tests/thumbnail.test.ts
└── package.json
if (!['image/jpeg','image/png','image/webp'].includes(file.mimetype)) throw new Error('unsupported');
const key = 'orig/' + sha256(buffer) + ext;
for (const w of [200, 600, 1200]) await sharp(input).resize({ width: w, withoutEnlargement: true }).toFile(path(w));
await sharp(input).webp({ quality: 80 }).toBuffer();
await sharp(input).rotate().withMetadata({ orientation: 1 }).toBuffer();
pnpm vitest run