feat(api-water): 支持上传水印图片

This commit is contained in:
R524809
2026-09-04 14:27:59 +08:00
parent c2523fe349
commit 009082d0f4
15 changed files with 6484 additions and 6089 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ API 启动后会在 `/api-docs` 提供 Swagger UI,并在其 OpenAPI 文档中
| [`conventions.md`](./conventions.md) | 部分已实现 | 鉴权、全局上下文、响应、分页、错误、并发与异步任务。 |
| [`auth.md`](./auth.md) | 已实现 | 邮箱注册、登录、刷新会话、退出、当前用户。 |
| [`stores-and-platforms.md`](./stores-and-platforms.md) | 部分已实现 | 平台目录、页头上下文、店铺授权、门店 CRUD、默认店铺、操作查询与仓库读取;平台实际验证与同步 Worker 待实现。 |
| [`watermarks-and-assets.md`](./watermarks-and-assets.md) | 部分已实现 | 水印模板、字体、店铺推荐水印;素材直传与短时访问待实现。 |
| [`watermarks-and-assets.md`](./watermarks-and-assets.md) | 部分已实现 | 水印模板、字体、店铺推荐水印,以及水印图片直传、校验和短时预览;图片渲染派生待实现。 |
| [`pricing.md`](./pricing.md) | 已设计 | 汇率、物流价卡、即时试算、定价方案、报价快照衔接。 |
| [`collection-and-extension.md`](./collection-and-extension.md) | 已设计 | 插件授权、采集写入、采集箱查询、素材转存与状态。 |
| [`product-editor-and-publishing.md`](./product-editor-and-publishing.md) | 已设计 | 商品编辑、SKU、类目、属性、AI、媒体选择、定价确认与部分 SKU 首次上架。 |
+4 -4
View File
@@ -22,7 +22,7 @@ Headers:`Idempotency-Key` 必填。
}
```
首期允许 `image/png`、`image/jpeg`、`image/webp`。服务端按用途校验大小/尺寸策略,生成随机 `object_key` 和短时预签名上传地址。返回 `201 Created`:
首期允许 `image/png`、`image/jpeg`、`image/webp`,最大 5 MiB,图片宽高须为 1–8192 px。服务端按用途校验大小/尺寸策略,生成随机 `object_key` 和 10 分钟有效的预签名上传地址。返回 `201 Created`:
```json
{
@@ -50,7 +50,7 @@ Headers:`Idempotency-Key` 必填。
{ "checksum_sha256": "hex-sha256" }
```
服务端检查对象存在、文件大小、MIME/魔数、哈希和图片尺寸;校验通过后创建/复用 `media_asset` 并返回 `201 Created`。图片解码或安全扫描耗时时可先返回 `202` 与 `media_processing` operation。
服务端检查对象存在、文件大小、声明 MIME、文件魔数、哈希和图片尺寸;校验通过后创建/复用 `media_asset` 并返回 `201 Created`。校验失败时上传意图标记为失败,不能被水印模板引用。
```json
{
@@ -67,7 +67,7 @@ Headers:`Idempotency-Key` 必填。
}
```
`object_key` 是服务端内部字段,不放在普通客户端响应。上传意图、完成状态和对象清理需要在实现时增加持久化记录;它不能只存在于 Redis 或预签名 token 中。
`object_key` 是服务端内部字段,不放在普通客户端响应。上传意图和完成状态均持久化在数据库,不能只存在于 Redis 或预签名 token 中;过期和失败对象由后续清理任务回收。
### 1.3 临时预览 URL
@@ -77,7 +77,7 @@ Headers:`Idempotency-Key` 必填。
{ "purpose": "preview" }
```
返回短时 `url` 与 `expires_at`。仅当前工作空间的素材可签发;前端在 URL 过期后重新请求,不能把 URL 写回业务表或长期缓存。
返回 10 分钟有效的短时 `url` 与 `expires_at`。仅当前工作空间的素材可签发;前端在 URL 过期后重新请求,不能把 URL 写回业务表或长期缓存。
## 2. 可用字体
+14 -13
View File
@@ -90,12 +90,13 @@ NestJS API/Worker 通过环境变量拿到连接信息,本地 `.env` 示例:
```bash
# 对象存储(本地 MinIO / 生产 Kodo 只换这一组值)
S3_ENDPOINT=http://localhost:9000
S3_REGION=us-east-1 # MinIO 不校验 region,任意合法值即可
S3_BUCKET=atlas-dev-assets
S3_ACCESS_KEY=atlas-dev
S3_SECRET_KEY=atlas-dev-secret
S3_FORCE_PATH_STYLE=true # MinIO 必须用 path-style,见第 6.3 节
OBJECT_STORAGE_ENDPOINT=http://localhost:9000
OBJECT_STORAGE_BUCKET=atlas-dev-assets
OBJECT_STORAGE_FORCE_PATH_STYLE=true
# 生产或共享环境必须提供专用最小权限凭证:
OBJECT_STORAGE_ACCESS_KEY=atlas-dev
OBJECT_STORAGE_SECRET_KEY=atlas-dev-secret
# 本地也可仅设置 MINIO_ROOT_USER / MINIO_ROOT_PASSWORD,由 API 作为兼容回退读取。
```
## 4. 创建 Bucket
@@ -159,24 +160,24 @@ import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const s3 = new S3Client({
endpoint: process.env.S3_ENDPOINT!, // http://localhost:9000
region: process.env.S3_REGION!, // MinIO 任意值即可
endpoint: process.env.OBJECT_STORAGE_ENDPOINT!, // http://localhost:9000
region: 'us-east-1',
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY!,
secretAccessKey: process.env.S3_SECRET_KEY!,
accessKeyId: process.env.OBJECT_STORAGE_ACCESS_KEY!,
secretAccessKey: process.env.OBJECT_STORAGE_SECRET_KEY!,
},
forcePathStyle: true, // MinIO 必须;Kodo 走虚拟主机式,配置驱动即可
forcePathStyle: process.env.OBJECT_STORAGE_FORCE_PATH_STYLE === 'true',
});
// 预签名上传 URL:前端拿到后直传,浏览器不保存永久密钥
export async function presignUpload(objectKey: string, contentType: string) {
const cmd = new PutObjectCommand({ Bucket: process.env.S3_BUCKET, Key: objectKey, ContentType: contentType });
const cmd = new PutObjectCommand({ Bucket: process.env.OBJECT_STORAGE_BUCKET, Key: objectKey, ContentType: contentType });
return getSignedUrl(s3, cmd, { expiresIn: 600 }); // 10 分钟
}
// 预签名下载 URL:预览/下载私有文件
export async function presignDownload(objectKey: string) {
const cmd = new GetObjectCommand({ Bucket: process.env.S3_BUCKET, Key: objectKey });
const cmd = new GetObjectCommand({ Bucket: process.env.OBJECT_STORAGE_BUCKET, Key: objectKey });
return getSignedUrl(s3, cmd, { expiresIn: 900 }); // 15 分钟
}
```
+7 -1
View File
@@ -64,7 +64,13 @@
- 已实现字体目录、按工作空间隔离的水印模板列表/详情/创建/编辑/软删除,以及店铺推荐水印绑定与解绑。文字/图片模板均创建不可变渲染版本;仅改名不新增版本,渲染参数或类型变化会新增版本。
- 删除模板会解除所有当前店铺绑定,但不删除历史版本或图片资产。模板与店铺写操作均要求 `Idempotency-Key`;编辑要求 `If-Match`。
- 图片模板可校验已有 `media_asset` 的归属;素材直传、图片校验和短时预览 URL 尚未实现,不能在前端自行拼接对象存储地址。
- 图片模板只可引用当前工作空间内、已完成安全校验的 `media_asset`;素材直传、图片校验和短时预览 URL 的实现见 1.6,前端仍不能自行拼接对象存储地址。
### 1.6 开发进度(2026-09-04):水印图片素材直传
- 已实现仅供 `watermark_image` 使用的两阶段素材接口:创建 10 分钟有效的 MinIO/S3 预签名 PUT 上传意图、完成上传后验证对象存在、大小、SHA-256、PNG/JPEG/WebP 魔数与 1–8192 px 尺寸,并创建或复用工作空间内的 `media_asset`。
- 已实现素材短时预览 URL(10 分钟);普通 API 返回永不包含 bucket、object key 或长期 URL。图片模板只接受当前工作空间内、类型为 PNG/JPEG/WebP 的已验证素材。
- 本地环境默认使用 `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` 连接 `http://localhost:9000`;生产必须显式配置最小权限的 `OBJECT_STORAGE_ACCESS_KEY` / `OBJECT_STORAGE_SECRET_KEY`。
## 2. 已确认产品决策