Files
atlas-seller-kit/apps/web/src/services/media.ts
T
2026-09-04 15:57:16 +08:00

72 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {
CreateMediaUploadIntentRequest,
MediaAccessUrl,
MediaAsset,
MediaUploadIntent,
} from '@atlas/contracts';
import { api } from '@/services/api';
/** 水印图片的用途与限制(docs/api/watermarks-and-assets.md §1) */
export const WATERMARK_IMAGE_ACCEPT = '.jpg,.jpeg,.png,.webp';
export const WATERMARK_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
/** 异步操作查询,契约见 docs/api/conventions.md §5(GET /api/operations/{operation_id}) */
export const mediaApi = {
createUploadIntent: (payload: CreateMediaUploadIntentRequest) =>
api.post<MediaUploadIntent>('/media/upload-intents', payload, {
headers: { 'Idempotency-Key': crypto.randomUUID() },
}),
completeUpload: (uploadId: string, checksumSha256: string) =>
api.post<{ asset: MediaAsset }>(
`/media/upload-intents/${uploadId}/complete`,
{ checksum_sha256: checksumSha256 },
{ headers: { 'Idempotency-Key': crypto.randomUUID() } }
),
/** 短时预览 URL:过期后重新请求,不能写回业务表或长期缓存 */
createPreviewUrl: (assetId: string) =>
api.post<MediaAccessUrl>(
`/media-assets/${assetId}/access-urls`,
{ purpose: 'preview' },
{ headers: { 'Idempotency-Key': crypto.randomUUID() } }
),
};
/** 浏览器端计算文件的 SHA-256 十六进制摘要(complete 校验用) */
export async function sha256Hex(file: File): Promise<string> {
const digest = await crypto.subtle.digest('SHA-256', await file.arrayBuffer());
return Array.from(new Uint8Array(digest))
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('');
}
/**
* 图片水印素材三步直传(docs/api/watermarks-and-assets.md §1):
* 创建上传意图 → 按返回的 method/url/headers 原样 PUT 到对象存储 → 完成校验换取素材。
* 返回已通过校验的素材,可直接用于图片水印模板的 image_asset_id。
*/
export async function uploadWatermarkImage(file: File): Promise<MediaAsset> {
const checksumSha256 = await sha256Hex(file);
const intent = await mediaApi.createUploadIntent({
purpose: 'watermark_image',
filename: file.name,
content_type: file.type as CreateMediaUploadIntentRequest['content_type'],
byte_size: file.size,
checksum_sha256: checksumSha256,
});
// 必须以服务端返回的 method、URL 与 headers 原样上传,不能自行拼接对象路径
const putResponse = await fetch(intent.upload.url, {
method: intent.upload.method,
headers: intent.upload.headers,
body: file,
});
if (!putResponse.ok) {
throw new Error(`图片上传到对象存储失败(HTTP ${putResponse.status})`);
}
const completed = await mediaApi.completeUpload(intent.upload_id, checksumSha256);
return completed.asset;
}