gongxiangp2/.kiro/specs/ui-fixes/S3素材统一迁移方案.md

438 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# S3素材统一迁移方案
## 背景
当前系统中素材分散在两个S3存储位置:
- **老位置**:`s3.1080p.hk/jsxiangche` - 老素材
- **新位置**:`s3.yydd.ai/gongxiangpan` - 新素材
**目标**:将所有素材统一迁移到新位置 `s3.yydd.ai/gongxiangpan`,方便后期维护。
---
## 项目存储结构
### 文件存储方式
1. **源文件(压缩包)** → 本地 `./uploads` 目录(Docker挂载)- **不需要迁移**(1,411个,699.5 GB)
2. **音频/视频源文件** → MinIO S3(zipFileName字段)- **需要迁移**(97个在老S3)
3. **预览图/视频转码文件** → MinIO S3 - **需要迁移**
### 数据库中的S3相关字段
| 字段 | 说明 | 存储内容 |
|------|------|----------|
| previewUrl | 预览图URL | 图片(webp/jpg) |
| zipFileName | 源文件名/音频视频URL | 本地文件名 或 S3音频/视频URL |
| zipFilePath | 源文件路径 | 本地路径(uploads/xxx 或 /app/uploads/xxx) |
| displayImages | 详情图数组 | 多张图片URL |
| videoSourceUrl | 视频源文件 | S3视频URL |
| videoPreview540Url | 540P预览视频 | S3视频URL |
| videoPreviewClipUrl | 视频片段 | S3视频URL |
| videoCoverUrl | 视频封面 | S3图片URL |
**重要说明**:
- `zipFilePath` 存储本地路径,有两种格式:`uploads/xxx.zip` 或 `/app/uploads/xxx.zip`
- 音频/视频类素材的源文件存储在 `zipFileName` 字段(S3 URL),`zipFilePath` 为空
---
## 迁移数据量统计
### 需要迁移的S3文件(从 s3.1080p.hk 到 s3.yydd.ai)
| 字段 | 总数 | 老S3 (需迁移) | 新S3 (已在目标) | 空/本地 |
|------|------|---------------|-----------------|---------|
| previewUrl | 1,525 | **1,114** | 313 | 97 |
| zipFileName (音频) | 1,525 | **97** | 13 | 1,415 |
| videoSourceUrl | 1,525 | **4** | 13 | 1,401 |
| videoPreview540Url | 1,525 | **122** | 1 | 1,402 |
| videoPreviewClipUrl | 1,525 | **122** | 1 | 1,402 |
| videoCoverUrl | 1,525 | **78** | 45 | 1,402 |
| displayImages | 2,665张 | **2,358** | 307 | - |
### 需要迁移的文件总数
- previewUrl: 1,114个
- zipFileName (音频): 97个
- videoSourceUrl: 4个
- videoPreview540Url: 122个
- videoPreviewClipUrl: 122个
- videoCoverUrl: 78个
- displayImages: 2,358个
- **总计:约 3,895 个文件**
---
## 文件大小估算
### 1. 音频/视频源文件(zipFileName字段,已知精确大小)
从数据库 `zipFileSize` 字段获取:
- 音乐:93个,**523.11 MB**(平均 5.62 MB/个)
- 主图视频源文件:4个,**183.06 MB**(平均 45.76 MB/个)
- **音频/视频源文件总计:706.17 MB**
### 2. 预览图(估算)
- 数量:1,114个
- 平均大小:约 50KB
- **预览图总计:约 55.7 MB**
### 3. 详情图(估算)
- 数量:2,358个
- 平均大小:约 100KB
- **详情图总计:约 235.8 MB**
### 4. 视频转码文件(估算)
视频时长统计:
- 种草视频:56个,总时长 16.53分钟
- 主图视频:39个,总时长 17.18分钟
- 安装视频:27个,总时长 77.33分钟
- **总时长:约 111分钟**
估算大小(540P视频约 2MB/分钟,片段约 0.5MB/分钟):
- videoPreview540Url:122个,约 **222 MB**
- videoPreviewClipUrl:122个,约 **55 MB**
- videoCoverUrl:78个,约 **4 MB**
- **视频转码文件总计:约 281 MB**
### 总数据量
| 类型 | 文件数 | 大小 |
|------|--------|------|
| 音频/视频源文件 | 101 | 706 MB |
| 预览图 | 1,114 | 56 MB |
| 详情图 | 2,358 | 236 MB |
| 视频转码文件 | 322 | 281 MB |
| **总计** | **3,895** | **约 1.28 GB**
---
## 迁移时间估算
### 网络速度参考
根据网络诊断测试结果:
- **s3.1080p.hk 公网下载速度**:20-33 KB/s(frp内网穿透,很慢)
- **s3.yydd.ai 公网下载速度**:类似(也是frp)
- **内网速度**:服务器在内网,速度快得多
### 迁移方式对比
#### 方案1:通过服务器内网中转(推荐)
**流程**:s3.1080p.hk → 192.168.58.20服务器 → s3.yydd.ai
**优点**:
- 服务器在内网,下载/上传速度快
- 可以并发处理多个文件
- 可以断点续传
**预计时间**:
- 假设内网速度:10 MB/s
- 数据量:1.28 GB
- 下载时间:1.28 GB ÷ 10 MB/s = **约 2分钟**
- 上传时间:1.28 GB ÷ 10 MB/s = **约 2分钟**
- **总计:约 4分钟**(理想情况)
**实际时间**:
- 考虑并发处理、网络波动、错误重试
- **预计:10-20分钟**
#### 方案2:直接S3间复制
**流程**:s3.1080p.hk → s3.yydd.ai(直接复制)
**缺点**:
- 两个S3可能不支持服务端复制
- 可能走公网,速度慢
- **不推荐**
---
## 迁移方案(推荐方案1)
### 迁移步骤
#### 1. 准备阶段
- [x] 统计需要迁移的文件数量和大小
- [x] 了解项目存储结构
- [ ] 备份数据库(防止迁移失败)
- [ ] 创建迁移脚本
- [ ] 在影子系统测试迁移脚本
#### 2. 迁移脚本功能
需要迁移的字段:
- `previewUrl` - 预览图
- `zipFileName` - 音频文件(仅当包含S3 URL时)
- `displayImages` - 详情图数组
- `videoSourceUrl` - 视频源文件
- `videoPreview540Url` - 540P预览视频
- `videoPreviewClipUrl` - 视频片段
- `videoCoverUrl` - 视频封面
脚本逻辑:
- 从数据库查询所有包含 `s3.1080p.hk` 的素材
- 遍历每个素材的每个S3字段:
- 下载文件到服务器临时目录
- 上传到 `s3.yydd.ai`
- 更新数据库中的URL
- 记录迁移日志(成功/失败)
- 支持断点续传(失败后可继续)
#### 3. 迁移执行
- 在影子系统先测试(少量素材)
- 验证迁移后的素材可正常访问
- 在正式系统执行完整迁移
- 迁移过程中系统可正常使用(只读)
#### 4. 验证阶段
- 检查数据库中所有URL已更新为 `s3.yydd.ai`
- 随机抽查素材可正常访问
- 检查详情图、视频预览可正常显示
- 确认数据量一致
#### 5. 清理阶段
- 确认迁移成功后,可选择删除 `s3.1080p.hk` 上的旧文件
- 更新 `.env` 配置,统一使用 `s3.yydd.ai`
- 重新部署应用
---
## 迁移脚本设计
### 脚本功能
```typescript
// scripts/migrate-s3-materials.ts
interface MigrationResult {
success: number;
failed: number;
skipped: number;
errors: Array<{ materialId: string; field: string; error: string }>;
}
// 需要迁移的S3字段
const S3_FIELDS = [
'previewUrl',
'zipFileName', // 仅当包含S3 URL时
'videoSourceUrl',
'videoPreview540Url',
'videoPreviewClipUrl',
'videoCoverUrl',
];
async function migrateMaterials() {
// 1. 查询所有需要迁移的素材(任意字段包含老S3 URL)
const materials = await prisma.$queryRaw`
SELECT * FROM materials
WHERE "deletedAt" IS NULL
AND (
"previewUrl" LIKE '%s3.1080p.hk%'
OR "zipFileName" LIKE '%s3.1080p.hk%'
OR "videoSourceUrl" LIKE '%s3.1080p.hk%'
OR "videoPreview540Url" LIKE '%s3.1080p.hk%'
OR "videoPreviewClipUrl" LIKE '%s3.1080p.hk%'
OR "videoCoverUrl" LIKE '%s3.1080p.hk%'
OR EXISTS (
SELECT 1 FROM unnest("displayImages") AS url
WHERE url LIKE '%s3.1080p.hk%'
)
)
`;
// 2. 遍历迁移
for (const material of materials) {
try {
const updates: Record<string, string> = {};
// 2.1 迁移各个S3字段
for (const field of S3_FIELDS) {
const url = material[field];
if (url && url.includes('s3.1080p.hk')) {
const newUrl = await migrateFile(url);
updates[field] = newUrl;
}
}
// 2.2 迁移详情图数组
let newDisplayImages = material.displayImages;
if (material.displayImages && material.displayImages.length > 0) {
newDisplayImages = [];
for (const imageUrl of material.displayImages) {
if (imageUrl.includes('s3.1080p.hk')) {
const newImageUrl = await migrateFile(imageUrl);
newDisplayImages.push(newImageUrl);
} else {
newDisplayImages.push(imageUrl);
}
}
updates.displayImages = newDisplayImages;
}
// 2.3 更新数据库
if (Object.keys(updates).length > 0) {
await prisma.material.update({
where: { id: material.id },
data: updates
});
}
console.log(`✅ Migrated: ${material.id}`);
} catch (error) {
console.error(`❌ Failed: ${material.id}`, error);
}
}
}
async function migrateFile(oldUrl: string): Promise<string> {
// 1. 从旧S3下载文件
const response = await fetch(oldUrl);
const buffer = await response.arrayBuffer();
// 2. 提取文件名和类型
const fileName = extractFileName(oldUrl);
const contentType = getContentType(fileName);
// 3. 上传到新S3
const newUrl = await uploadToNewS3(Buffer.from(buffer), fileName, contentType);
return newUrl;
}
```
### 并发控制
```typescript
// 使用 p-limit 控制并发数
import pLimit from 'p-limit';
const limit = pLimit(5); // 同时处理5个文件(避免内存溢出)
const promises = materials.map(material =>
limit(() => migrateMaterial(material))
);
await Promise.all(promises);
```
---
## 风险评估
### 高风险
- ❌ **数据丢失**:迁移过程中文件丢失
- 缓解措施:先备份数据库,迁移后验证
- ❌ **URL更新失败**:数据库更新失败导致URL不一致
- 缓解措施:使用事务,失败自动回滚
### 中风险
- ⚠️ **迁移时间过长**:影响用户使用
- 缓解措施:选择低峰期迁移,支持断点续传
- ⚠️ **网络不稳定**:下载/上传失败
- 缓解措施:自动重试机制,记录失败日志
### 低风险
- ℹ️ **旧文件残留**:迁移后旧文件未删除
- 缓解措施:迁移成功后手动清理
---
## 回滚方案
如果迁移失败,需要回滚:
### 回滚步骤
1. 停止迁移脚本
2. 恢复数据库备份
3. 检查已迁移的素材
4. 删除新S3上的文件(如果需要)
5. 重新执行迁移(修复问题后)
### 回滚SQL
```sql
-- 恢复备份(如果有)
-- 或者手动更新URL
UPDATE materials
SET
"previewUrl" = REPLACE("previewUrl", 's3.yydd.ai/gongxiangpan', 's3.1080p.hk/jsxiangche'),
"displayImages" = ARRAY(
SELECT REPLACE(url::text, 's3.yydd.ai/gongxiangpan', 's3.1080p.hk/jsxiangche')
FROM unnest("displayImages") AS url
)
WHERE "previewUrl" LIKE '%s3.yydd.ai%';
```
---
## 迁移后配置更新
### 1. 更新 .env 配置
```bash
# 旧配置(s3.1080p.hk)
S3_ENDPOINT=https://s3.1080p.hk
S3_BUCKET=jsxiangche
S3_PUBLIC_DOMAIN=https://s3.1080p.hk/jsxiangche
# 新配置(s3.yydd.ai)
S3_ENDPOINT=https://s3.yydd.ai
S3_BUCKET=gongxiangpan
S3_PUBLIC_DOMAIN=https://s3.yydd.ai/gongxiangpan
S3_FILE_PATH=由壹点AI/
```
### 2. 需要确认的新S3凭证
迁移前需要确认 `s3.yydd.ai` 的访问凭证:
- S3_ACCESS_KEY_ID
- S3_SECRET_ACCESS_KEY
### 3. 重新部署应用
```bash
# 部署到影子系统测试
.\deploy-local-build.ps1 -Target shadow
# 验证成功后部署到正式系统
.\deploy-local-build.ps1
```
---
## 执行计划
### 时间安排
- **准备阶段**:30分钟(创建脚本、配置凭证)
- **影子系统测试**:10分钟(迁移少量素材测试)
- **正式系统迁移**:10-20分钟(迁移全部素材)
- **验证阶段**:10分钟(检查数据完整性)
- **总计**:约 1小时
### 执行时间建议
- 选择低峰期(晚上或周末)
- 提前通知用户(如果需要)
- 准备回滚方案
---
## 总结
### 迁移数据量
- **文件数量**:3,895个文件
- **数据大小**:约 1.28 GB
- **预计时间**:10-20分钟(实际迁移)
### 文件类型分布
| 类型 | 文件数 | 大小 |
|------|--------|------|
| 音频/视频源文件 | 101 | 706 MB |
| 预览图 | 1,114 | 56 MB |
| 详情图 | 2,358 | 236 MB |
| 视频转码文件 | 322 | 281 MB |
| **总计** | **3,895** | **约 1.28 GB** |
### 推荐方案
- 使用方案1(通过服务器内网中转)
- 并发处理(5个文件同时)
- 支持断点续传
- 完整的日志记录
### 下一步
1. 确认 `s3.yydd.ai` 的访问凭证
2. 创建迁移脚本
3. 在影子系统测试
4. 执行正式迁移
5. 验证数据完整性
6. 更新配置并重新部署