- 迁移57个老素材到新逻辑(本地zip + S3预览图) - 优化文件命名:SPU_作者_店铺_类型.zip - PSD文件单独存储,不打包进all.zip - 修复下载接口,优先使用本地zip文件 - 修复预览图重复问题 - 添加迁移脚本和验证脚本 - 更新文档和迁移计划 迁移统计: - 总素材数:77个 - 成功迁移:57个 - 新逻辑上传:18个 - 空记录:2个
15 KiB
15 KiB
任务-详情素材逻辑说明与迁移计划
文档目的: 记录"任务-详情"素材的老逻辑、新逻辑和迁移计划 创建时间: 2026-01-20 最后更新: 2026-01-20
一、迁移完成统计(2026-01-20)
最终结果
| 项目 | 数量 |
|---|---|
| 总素材数 | 77 个 |
| ✅ 已成功迁移 | 75 个 |
| ❌ 空记录(无需迁移) | 2 个 |
迁移详情
第一轮迁移(修复 zipFilePath='' 的素材):
- 第一批:9 个成功
- 第二批:8 个成功
- 第三批:8 个成功
- 第四批:3 个成功
- 小计:28 个素材
第二轮迁移(修复有 zipFilePath 但没有本地 zip 文件的素材):
- 第一批:10 个成功
- 第二批:9 个成功(1 个空素材失败)
- 第三批:9 个成功(1 个文件名问题)
- 第四批:1 个成功
- 小计:29 个素材
第三轮迁移(新逻辑上传的素材):
- 18 个素材使用新逻辑上传,无需迁移
总计本次迁移:57 个素材成功
空记录说明
2 个空记录(无任务号,无资产):
- CYJ0062 (紫苏) - 无任务号
- GBPM-SN0208-389 (瑶光) - 无任务号
这些是测试数据或上传失败的记录,不需要迁移。
验证结果
使用 scripts/verify-migrated-files.ts 验证:
- ✅ 所有文件存在:75 个
- ⚠️ 部分文件缺失:0 个
- ❌ 所有文件缺失:0 个
- ❌ 没有 zip 记录:2 个(空记录)
结论:迁移工作已全部完成!
二、老逻辑(已废弃)
2.1 数据流程
用户上传文件
↓
后台压缩生成预览图(像素偏高的预览图)
↓
预览图上传到 S3(存储类型:detail/main/sku)
↓
数据库保存 assetType = 'detail'/'main'/'sku'(指向 S3 URL)
↓
用户下载时:临时打包预览图并返回
2.2 关键特点
- 不保存原图:原图在临时目录,上传完成后会被删除
- 只有预览图:
material_asset表中只有 S3 预览图,没有本地 zip - 下载时动态打包:每次下载时,从 S3 下载预览图并临时打包
- 预览图质量高:像素偏高,接近原图
2.3 数据库结构(老逻辑)
| 表 | 字段 | 说明 |
|---|---|---|
materials |
zipFilePath |
空 |
materials |
zipFileName |
空 |
materials |
zipFileSize |
空 |
materials |
previewUrl |
S3 预览图 URL |
material_asset |
assetType |
'detail'/'main'/'sku' |
material_asset |
fileUrl |
S3 预览图 URL |
material_asset |
fileName |
预览图文件名(如 detail-preview-0.webp) |
2.4 示例数据
-- materials 表
{
id: "7d01a3ab-8a99-4966-9f5e-53b5ef6cfad1",
taskNo: "TASK202601201149497501-01-01",
productModel: "CJ0252",
previewUrl: "https://s3.yydd.ai/gongxiangpan/由壹点AI/product-package/detail-xxx.webp",
zipFilePath: NULL
}
-- material_asset 表
{
id: "xxx",
materialId: "7d01a3ab-8a99-4966-9f5e-53b5ef6cfad1",
assetType: "detail",
fileUrl: "https://s3.yydd.ai/gongxiangpan/由壹点AI/product-package/detail-xxx.webp",
fileName: "1200-欧.jpg"
}
三、新逻辑(当前使用)
3.1 数据流程
用户上传文件(分片上传)
↓
保存到本地临时目录
↓
├─→ 原图文件(临时目录)
│ ↓
│ 打包成分类 zip(main.zip / sku.zip / detail.zip)
│ ↓
│ 保存到本地永久目录
│ ↓
│ 数据库记录 assetType = 'main'/'sku'/'detail'(本地路径)
│
└─→ 压缩生成预览图(800×1200,200KB,webp 格式)
↓
上传到 S3
↓
数据库记录 assetType = 'main-preview'/'sku-preview'/'detail-preview'(S3 URL)
3.2 关键特点
- 保存原图:原图打包成 zip 保存到本地永久目录
- 预览图独立:预览图单独上传 S3,用于网页快速展示
- 下载直接返回 zip:下载时直接返回本地 zip 文件,无需动态打包
- 预览图质量优化:800×1200,200KB,节省带宽
3.3 数据库结构(新逻辑)
| 表 | 字段 | 说明 |
|---|---|---|
materials |
previewUrl |
第一张预览图 URL(S3) |
materials |
zipFilePath |
总 zip 文件路径(本地) |
materials |
zipFileName |
总 zip 文件名 |
materials |
zipFileSize |
总 zip 文件大小 |
material_asset |
assetType |
'main-preview'/'sku-preview'/'detail-preview'/'main'/'sku'/'detail'/'all'/'psd' |
material_asset |
fileUrl |
预览图是 S3 URL,zip 是本地路径 |
material_asset |
fileName |
预览图文件名 或 zip 文件名 |
3.4 资产类型说明
| assetType | 含义 | fileUrl 内容 | 用途 |
|---|---|---|---|
main-preview |
主图预览图 | S3 URL (webp) | 网页展示 |
sku-preview |
SKU图预览图 | S3 URL (webp) | 网页展示 |
detail-preview |
详情图预览图 | S3 URL (webp) | 网页展示 |
main |
主图 zip 包 | 本地路径 | 下载 |
sku |
SKU图 zip 包 | 本地路径 | 下载 |
detail |
详情图 zip 包 | 本地路径 | 下载 |
all |
总 zip 包(含分类zip + PSD) | 本地路径 | 下载 |
psd |
PSD 源文件 | 本地路径 | 下载 |
3.5 示例数据
-- materials 表
{
id: "404a427e-42a8-4251-ac57-01846c07dcab",
taskNo: "TASK202601161107108287-01-01",
productModel: "DSG0095",
previewUrl: "https://s3.yydd.ai/gongxiangpan/由壹点AI/product-package/main-preview-xxx.webp",
zipFilePath: "uploads/product-package/404a427e-42a8-4251-ac57-01846c07dcab/source.zip",
zipFileName: "DSG0095_任务-详情_404a427e.zip",
zipFileSize: 12345678
}
-- material_asset 表
{
id: "xxx",
materialId: "404a427e-42a8-4251-ac57-01846c07dcab",
assetType: "main-preview",
fileUrl: "https://s3.yydd.ai/gongxiangpan/由壹点AI/product-package/main-preview-xxx.webp",
fileName: "main-preview-0.webp",
sortOrder: 0
}
{
id: "yyy",
materialId: "404a427e-42a8-4251-ac57-01846c07dcab",
assetType: "main",
fileUrl: "uploads/product-package/404a427e-42a8-4251-ac57-01846c07dcab/main.zip",
fileName: "DSG0095_主图_404a427e.zip",
fileSize: 5678901,
sortOrder: 0
}
四、老素材迁移计划
4.1 迁移目标
将所有使用老逻辑的"任务-详情"素材(67 个,全部需要迁移)迁移到新逻辑。
重要说明
⚠️ 所有 67 个素材都是老逻辑,包括:
-
那 42 个"有 zipFilePath"的素材:
zipFileSize = 0,说明 zip 文件实际上没有正确创建- 可能是早期测试时部分功能不完整导致
- 需要重新生成 zip 文件
-
那 25 个"无 zipFilePath"的素材:
- 只有 S3 预览图,没有任何 zip 记录
- 需要下载预览图并打包成 zip
结论:67 个素材全部需要迁移
4.2 迁移步骤
步骤 1: 下载预览图
从 S3 下载当前存储的所有预览图:
// 1. 查询需要迁移的素材
const materialsToMigrate = await prisma.material.findMany({
where: {
materialType: '任务-详情',
deletedAt: null,
zipFilePath: '', // 无 zip 文件
taskNo: { not: null }, // 有任务号
},
select: { id: true, productModel: true, taskNo: true },
});
// 2. 对每个素材,下载所有预览图
for (const material of materialsToMigrate) {
const assets = await prisma.materialAsset.findMany({
where: { materialId: material.id },
orderBy: { sortOrder: 'asc' },
});
// 按 assetType 分组
const mainAssets = assets.filter(a => a.assetType === 'main');
const skuAssets = assets.filter(a => a.assetType === 'sku');
const detailAssets = assets.filter(a => a.assetType === 'detail');
// 下载预览图到临时目录
for (const asset of mainAssets) {
const buffer = await downloadFromS3(asset.fileUrl);
const tempPath = path.join(tempDir, asset.fileName);
await fs.writeFile(tempPath, buffer);
}
// ... 同样处理 skuAssets 和 detailAssets
}
步骤 2: 打包分类 zip
使用原图(预览图)打包成分类 zip:
// 使用 createZipFromFiles 函数打包
const zipInfos = {};
const zipFilesForAll = [];
// 打包主图
if (mainFiles.length > 0) {
zipInfos.main = await createZipFromFiles(
mainFiles,
material.id,
material.productModel,
'任务-详情',
'main'
);
zipFilesForAll.push({ path: zipInfos.main.filePath, name: zipInfos.main.fileName });
}
// 打包 SKU 图
if (skuFiles.length > 0) {
zipInfos.sku = await createZipFromFiles(
skuFiles,
material.id,
material.productModel,
'任务-详情',
'sku'
);
zipFilesForAll.push({ path: zipInfos.sku.filePath, name: zipInfos.sku.fileName });
}
// 打包详情图
if (detailFiles.length > 0) {
zipInfos.detail = await createZipFromFiles(
detailFiles,
material.id,
material.productModel,
'任务-详情',
'detail'
);
zipFilesForAll.push({ path: zipInfos.detail.filePath, name: zipInfos.detail.fileName });
}
步骤 3: 打包总 zip
// 打包总 zip(包含所有分类 zip + PSD)
const psdFilesForAll = [];
// 检查是否有 PSD 文件
const psdAssets = assets.filter(a => a.assetType === 'psd');
for (const psd of psdAssets) {
psdFilesForAll.push({ path: psd.fileUrl, name: psd.fileName });
}
zipInfos.all = await createZipFromZips(
zipFilesForAll,
psdFilesForAll,
material.id,
material.productModel,
'任务-详情'
);
步骤 4: 更新数据库
// 4.1 更新 assets 表(添加 main/sku/detail/all 类型的记录)
const assetRecords = [];
// 添加分类 zip 资产
if (zipInfos.main) {
assetRecords.push({
materialId: material.id,
assetType: 'main',
fileUrl: zipInfos.main.filePath,
fileName: zipInfos.main.fileName,
fileSize: BigInt(zipInfos.main.fileSize),
sortOrder: 0,
});
}
if (zipInfos.sku) {
assetRecords.push({
materialId: material.id,
assetType: 'sku',
fileUrl: zipInfos.sku.filePath,
fileName: zipInfos.sku.fileName,
fileSize: BigInt(zipInfos.sku.fileSize),
sortOrder: 0,
});
}
if (zipInfos.detail) {
assetRecords.push({
materialId: material.id,
assetType: 'detail',
fileUrl: zipInfos.detail.filePath,
fileName: zipInfos.detail.fileName,
fileSize: BigInt(zipInfos.detail.fileSize),
sortOrder: 0,
});
}
// 添加总 zip 资产
assetRecords.push({
materialId: material.id,
assetType: 'all',
fileUrl: zipInfos.all.filePath,
fileName: zipInfos.all.fileName,
fileSize: BigInt(zipInfos.all.fileSize),
sortOrder: 0,
});
await prisma.materialAsset.createMany({
data: assetRecords,
});
// 4.2 更新 materials 表
await prisma.material.update({
where: { id: material.id },
data: {
zipFilePath: zipInfos.all.filePath,
zipFileName: zipInfos.all.fileName,
zipFileSize: BigInt(zipInfos.all.fileSize),
},
});
步骤 5: 清理临时文件
// 删除下载的临时文件
for (const file of mainFiles.concat(skuFiles, detailFiles)) {
await fs.unlink(file.path);
}
4.3 迁移脚本
完整的迁移脚本:scripts/migrate-product-package-assets.ts
4.4 回滚计划
如果迁移失败,可以执行以下回滚操作:
-- 删除新增的资产记录
DELETE FROM material_asset
WHERE material_id IN (
SELECT id FROM materials
WHERE material_type = '任务-详情'
AND zip_file_path = ''
)
AND asset_type IN ('main', 'sku', 'detail', 'all');
-- 恢复 materials 表
UPDATE materials
SET
zip_file_path = '',
zip_file_name = '',
zip_file_size = 0
WHERE material_type = '任务-详情'
AND zip_file_path != '';
五、注意事项
5.1 任务平台接口兼容性
外部接口: /api/external/product-assets
- 返回结构: 保持不变,兼容新老格式
- 下载链接: 优先返回
all类型的 zip,如果没有则回退到老逻辑
外部下载接口: /api/external/download
- 处理逻辑: 优先查找
all类型的资产,如果没有则动态打包预览图
5.2 前端展示兼容性
详情页组件: components/detail/ProductPackageDetail.tsx
- 展示逻辑: 兼容
main-preview/main、sku-preview/sku、detail-preview/detail - 下载逻辑: 优先下载 zip 文件,如果没有则动态打包预览图
5.3 迁移前后对比
| 特性 | 迁移前(老逻辑) | 迁移后(新逻辑) |
|---|---|---|
| 预览图 | S3 | S3 |
| 原图存储 | 无 | 本地 zip |
| 下载方式 | 动态打包预览图 | 直接返回 zip |
| 下载速度 | 较慢(需要打包) | 快速(直接返回) |
| 存储空间 | 较少(只有预览图) | 较多(预览图 + 原图) |
六、执行计划
6.1 准备阶段
- ✅ 完成新逻辑的代码实现
- ✅ 完成前端兼容性修改
- ✅ 完成外部接口兼容性修改
- ✅ 编写迁移脚本
- ✅ 在生产环境测试新逻辑
- ✅ 在生产环境测试迁移脚本
6.2 执行阶段
- ✅ 备份生产数据库
- ✅ 执行迁移脚本(迁移 57 个老素材)
- ✅ 验证迁移结果
- ✅ 测试下载功能
- ✅ 测试任务平台接口
6.3 迁移完成
迁移时间:2026-01-20
迁移结果:
- 总素材数:77 个
- 成功迁移:57 个
- 新逻辑上传:18 个
- 空记录:2 个
验证通过:所有 75 个有效素材的本地 zip 文件均存在
七、附录
7.1 迁移完成总结
迁移时间:2026-01-20
迁移统计:
- 总素材数:77 个
- ✅ 成功迁移:57 个(从老逻辑迁移到新逻辑)
- ✅ 新逻辑上传:18 个(无需迁移)
- ❌ 空记录:2 个(无任务号,无资产,不需要迁移)
迁移过程:
- 第一轮:28 个素材(修复 zipFilePath='' 的素材)
- 第二轮:29 个素材(修复有 zipFilePath 但没有本地 zip 文件的素材)
- 验证:所有 75 个有效素材的本地 zip 文件均存在
空记录详情:
- CYJ0062 (紫苏) - 无任务号,无资产
- GBPM-SN0208-389 (瑶光) - 无任务号,无资产
这些是测试数据或上传失败的记录,可以忽略或删除。
7.2 相关文件
| 文件路径 | 说明 |
|---|---|
lib/task-material-processor.ts |
任务-详情素材处理逻辑 |
lib/product-package.ts |
产品全案相关函数 |
app/api/materials/upload/product-package/complete/route.ts |
任务-详情上传完成接口 |
app/api/external/product-assets/route.ts |
外部接口(任务平台) |
components/detail/ProductPackageDetail.tsx |
任务-详情详情页组件 |
scripts/migrate-product-package-old-materials.ts |
迁移脚本 |
scripts/verify-migrated-files.ts |
验证脚本 |
scripts/check-remaining-materials.ts |
检查剩余素材脚本 |
文档版本: 2.0.0 最后更新: 2026-01-20 状态: ✅ 迁移完成