gongxiangp2/.kiro/specs/source-code-recovery/生产系统问题分析.md

638 lines
17 KiB
Markdown
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.

# 生产系统问题分析报告
**日期**: 2025-12-20
**分析人**: AI Assistant
**系统**: 由壹点3D素材平台
---
## 📋 问题描述
用户报告生产系统(http://192.168.58.20:4320)存在数据显示问题:
- 筛选"3D场景"类型时,瀑布流显示 **1026个素材**
- "使用店铺"筛选的"全部"按钮显示 **1530个素材**
- 差异:**504个素材** (1530 - 1026 = 504)
**初步分析**: 用户认为缺失了504个素材
**调查结果**:
- ✅ 1026个是正确的(370个"3D场景" + 656个"3DMAX")
- ❌ 1530个包含了6个软删除的素材(应该显示1524)
- ✅ 504个差异是其他素材类型(PSD、视频、图片等)
**真正的问题**: 统计API包含了软删除数据,导致"全部"按钮显示错误数字
---
## 🔍 调查过程
### 1. 数据库检查
#### 生产数据库 (`lobechat`)
```sql
-- 总记录数(包含软删除)
SELECT COUNT(*) FROM materials;
-- 结果: 1530
-- 未删除的素材
SELECT COUNT(*) FROM materials WHERE "deletedAt" IS NULL;
-- 结果: 1524
-- 已软删除的素材
SELECT COUNT(*) FROM materials WHERE "deletedAt" IS NOT NULL;
-- 结果: 6
```
**结论**: 数据库数据完整,没有被覆盖
#### 影子数据库 (`lobechat_stage`)
```sql
SELECT COUNT(*) FROM materials WHERE "deletedAt" IS NULL;
-- 结果: 1049
```
**结论**: 影子系统数据独立,没有混乱
### 2. API 检查
#### API 1: `/api/materials/stats`
```bash
curl http://192.168.58.20:4320/api/materials/stats
```
**返回结果**:
```json
{
"total": 1530,
"months": [
{"value": "2025-12", "label": "2025年12月", "count": 638},
{"value": "2025-11", "label": "2025年11月", "count": 818},
{"value": "2025-10", "label": "2025年10月", "count": 74}
]
}
```
**问题**: `total: 1530` 包含了6条软删除数据!
#### API 2: `/api/materials?page=1&pageSize=20`
```bash
curl http://192.168.58.20:4320/api/materials?page=1&pageSize=20
```
**返回结果**:
```json
{
"totalCount": 1524,
"totalPages": 77,
"currentPage": 1,
"materials": [... 1524条数据 ...]
}
```
**严重问题**:
- `totalCount` 正确 (1524)
- 但 `materials` 数组包含 **全部1524条数据**,而不是分页的20条!
- 无论请求第几页,都返回全部数据
---
## 🐛 发现的BUG
### BUG 1: 统计API包含软删除数据 ⚠️
**文件**: `app/api/materials/stats/route.ts`
**问题代码**:
```typescript
// 错误:没有过滤软删除数据
const total = await prisma.material.count()
```
**正确代码**:
```typescript
// 正确:过滤软删除数据
const total = await prisma.material.count({
where: { deletedAt: null }
})
```
**影响**:
- "使用店铺"的"全部"按钮显示 1530(错误)
- 应该显示 1524(正确)
---
### BUG 2: 分页API返回全部数据 🔥 严重
**文件**: `app/api/materials/route.ts`
**问题**: 分页逻辑失效,每次请求都返回全部1524条数据
**影响**:
1. **前端性能严重下降**
- 一次性加载1524条数据
- 每条数据包含图片URL、用户信息等
- 数据量约 1-2MB
2. **用户体验问题**
- 页面加载缓慢
- 滚动卡顿
- 用户报告只能看到1026个素材(可能是前端渲染限制或浏览器内存限制)
3. **服务器资源浪费**
- 每次请求都查询全部数据
- 数据库负载增加
**需要检查的代码**:
```typescript
// 检查 skip 和 take 是否正确使用
const materials = await prisma.material.findMany({
where,
skip: (page - 1) * pageSize, // 是否正确计算?
take: pageSize, // 是否正确应用?
})
```
---
### BUG 3: 分类统计未过滤软删除 ⚠️
**文件**: `app/api/categories/route.ts`
**问题代码**:
```typescript
// 错误:统计时没有过滤软删除数据
const count = await prisma.material.count({
where: {
categoryName: category.name,
},
})
```
**正确代码**:
```typescript
// 正确:过滤软删除数据
const count = await prisma.material.count({
where: {
categoryName: category.name,
deletedAt: null,
},
})
```
**影响**:
- 分类按钮显示的数字可能包含已删除素材
- 用户点击后看到的实际素材数量与按钮显示不符
---
## 📁 素材存储路径问题
### 背景
系统经历过存储位置迁移,存在新老两套存储路径:
### 老素材存储(2025-10-27之前)
- **预览图**: MinIO S3(老bucket配置)
- Endpoint: `https://s3.1080p.hk`
- Bucket: `jsxiangche`(老bucket,无路径前缀)
- URL格式: `https://s3.1080p.hk/jsxiangche/preview-{timestamp}-{hash}.webp`
- 示例: `https://s3.1080p.hk/jsxiangche/preview-1761537819211-edb4a190180c24f9.webp`
- **源文件(压缩包)**: 本地文件系统
- 路径: `uploads/material-{timestamp}-{hash}.zip`
- 相对路径格式(数据库存储)
- 示例: `uploads/material-1761537819728-8fb5090670b5b986.zip`
- **特征识别**:
- previewUrl 不包含 `由壹点AI/` 路径前缀
- previewUrl 使用 bucket `jsxiangche`(不是 `gongxiangpan`)
- zipFilePath 格式: `uploads/material-*.zip`
- 创建时间: 2025-10-27 及之前
### 新素材存储(2025-10-27之后)
- **预览图/视频缩略图**: MinIO S3(新路径配置)
- Endpoint: `https://s3.1080p.hk`
- Bucket: `jsxiangche`(同一bucket,但有路径前缀)
- 路径前缀: `由壹点AI/`
- URL格式: `https://s3.1080p.hk/jsxiangche/由壹点AI/video-thumb-{timestamp}-{hash}.jpg`
- 示例: `https://s3.1080p.hk/jsxiangche/由壹点AI/video-thumb-1766220802404-d6c20cf8f82c0c80.jpg`
- **源文件(压缩包/视频)**: 本地文件系统
- 路径: `uploads/video-{timestamp}-{hash}.mp4` 或 `uploads/material-{timestamp}-{hash}.zip`
- 相对路径格式(数据库存储)
- 示例: `uploads/video-1766220805356-02770716caf02d9c.mp4`
- **特征识别**:
- previewUrl 包含 `由壹点AI/` 路径前缀
- zipFilePath 格式: `uploads/video-*.mp4` 或 `uploads/material-*.zip`
- 创建时间: 2025-10-27 之后
### 存储路径对比
| 项目 | 老素材 | 新素材 |
|------|--------|--------|
| **S3 Endpoint** | `https://s3.1080p.hk` | `https://s3.1080p.hk` |
| **S3 Bucket** | `jsxiangche` | `jsxiangche` |
| **S3 路径前缀** | 无 | `由壹点AI/` |
| **预览图URL** | `https://s3.1080p.hk/jsxiangche/preview-*.webp` | `https://s3.1080p.hk/jsxiangche/由壹点AI/video-thumb-*.jpg` |
| **本地文件路径** | `uploads/material-*.zip` | `uploads/video-*.mp4` 或 `uploads/material-*.zip` |
| **本地存储目录** | `./uploads` (相对路径) | `./uploads` (相对路径) |
| **Docker挂载** | 容器内 `/app/uploads` | 容器内 `/app/uploads` |
| **时间分界点** | ≤ 2025-10-27 | > 2025-10-27 |
### 环境变量配置
**当前配置** (`.env.local`):
```bash
# MinIO S3 Storage
S3_ENDPOINT=https://s3.1080p.hk
S3_BUCKET=jsxiangche
S3_ACCESS_KEY_ID=AEvT1p0wSNjow3Asu9NV
S3_SECRET_ACCESS_KEY=u8nzFl1S6dNoT5PQaQhkHz8Du6iX0hVnPswtvHhs
S3_REGION=us-east-1
S3_PUBLIC_DOMAIN=https://s3.1080p.hk/jsxiangche
S3_ENABLE_PATH_STYLE=1
S3_FILE_PATH=由壹点AI/
# Local storage for zip files
UPLOAD_DIR=./uploads
```
**注意**:
- 老素材和新素材使用同一个 S3 bucket (`jsxiangche`)
- 区别在于新素材有路径前缀 `由壹点AI/`
- 本地文件都存储在 `./uploads` 目录
### ⚠️ 重要原则
1. **不要移动或修改老素材**
- 老素材保持在原位置
- 不要尝试迁移老素材到新位置
- 确保老素材路径可访问
2. **新素材统一使用新位置**
- 所有新上传的素材使用 MinIO S3 + 本地uploads
- 遵循新的命名和组织规则
3. **下载功能必须兼容新老路径**
- 根据数据库中的路径信息判断使用哪个存储
- 自动适配不同的路径格式
- 确保新老素材都能正常下载
### 数据库字段
```typescript
// materials 表相关字段
interface Material {
id: string;
previewUrl: string; // 预览图URL(可能是S3或老存储)
zipFileName: string; // 压缩包文件名
zipFilePath: string; // 本地压缩包路径(可能是老路径或新路径)
zipFileSize: bigint; // 文件大小
// ... 其他字段
}
```
### 路径判断逻辑(已从本地代码确认)
**下载功能** (`app/api/materials/download/route.ts`):
- 当前代码使用相对路径 `zipFilePath`
- 自动转换为绝对路径: `path.join(process.cwd(), zipFilePath)`
- 老素材和新素材都存储在 `./uploads` 目录
- **无需特殊判断逻辑**,因为都使用相同的本地存储目录
**代码片段**:
```typescript
// 普通素材:下载压缩包文件
let filePath = material.zipFilePath
if (!filePath) {
return NextResponse.json(
{ error: '素材没有可下载的文件' },
{ status: 404 }
)
}
// 确保使用绝对路径
if (!path.isAbsolute(filePath)) {
filePath = path.join(process.cwd(), filePath)
}
// 检查文件是否存在
await access(filePath)
const fileBuffer = await readFile(filePath)
```
**上传功能** (`app/api/materials/upload/route.ts`):
- 新素材使用环境变量 `UPLOAD_DIR` (默认 `./uploads`)
- 文件命名: `material-{timestamp}-{hash}.zip` 或 `video-{timestamp}-{hash}.mp4`
- 预览图上传到 S3,路径前缀: `由壹点AI/`
**代码片段**:
```typescript
const uploadDir = process.env.UPLOAD_DIR || './uploads'
await mkdir(uploadDir, { recursive: true })
const zipFileExt = path.extname(zipFile.name)
zipFileName = generateUniqueFileName('material', zipFileExt)
zipFilePath = path.join(uploadDir, zipFileName)
// 保存到数据库时使用相对路径
// zipFilePath = "uploads/material-1766220805356-02770716caf02d9c.zip"
```
### ✅ 生产系统验证结果(2025-12-20)
#### 1. S3 预览图验证
**老素材预览图**:
- URL: `https://s3.1080p.hk/jsxiangche/preview-1761537819211-edb4a190180c24f9.webp`
- 状态: ✅ 可访问
- 大小: 63,076 bytes
- 类型: image/webp
**新素材预览图**:
- URL: `https://s3.1080p.hk/jsxiangche/由壹点AI/video-thumb-1766220802404-d6c20cf8f82c0c80.jpg`
- 状态: ✅ 可访问
- 类型: image/jpeg
#### 2. 本地文件验证
**老素材文件** (2025-10-27):
```bash
# 数据库记录
zipFilePath: uploads/material-1761537819728-8fb5090670b5b986.zip
# 生产容器实际文件
/app/uploads/material-1761537819728-8fb5090670b5b986.zip
大小: 807.0M
状态: ✅ 存在
```
**新素材文件** (2025-12-19):
```bash
# 数据库记录
zipFilePath: uploads/video-1766119244101-6a457afe724a412d.mp4
# 生产容器实际文件
/app/uploads/video-1766119244101-6a457afe724a412d.mp4
大小: 50.2M
状态: ✅ 存在
```
#### 3. 存储目录统计
```bash
# 生产容器 /app/uploads 目录
总大小: 716GB
文件类型:
- material-*.zip (老素材压缩包)
- video-*.mp4 (新素材视频文件)
```
### 兼容性说明
✅ **当前代码已兼容新老素材**:
1. 本地文件都存储在 `./uploads` 目录
2. 数据库存储相对路径 `uploads/xxx.zip`
3. 下载时自动转换为绝对路径
4. 老素材和新素材使用相同的路径处理逻辑
✅ **S3 预览图兼容**:
1. 老素材: `https://s3.1080p.hk/jsxiangche/preview-*.webp`
2. 新素材: `https://s3.1080p.hk/jsxiangche/由壹点AI/video-thumb-*.jpg`
3. 前端直接使用 `previewUrl` 字段,无需特殊处理
✅ **生产系统验证通过**:
1. 老素材文件存在且可访问(807MB zip文件)
2. 新素材文件存在且可访问(50MB mp4文件)
3. S3 预览图都可以正常访问
4. 数据库路径与实际文件路径完全匹配
⚠️ **注意事项**:
- 不要修改 `uploads` 目录的文件(总大小716GB)
- 不要修改数据库中的 `zipFilePath` 字段
- 确保 Docker 挂载正确: 容器内 `/app/uploads` → 宿主机 `uploads` 目录
---
## 🎯 修复优先级
### 🔥 P0 - 紧急(影响用户体验)
1. **修复分页BUG** - `app/api/materials/route.ts`
- 问题:返回全部数据导致性能问题
- 影响:所有用户的浏览体验
- 预计修复时间:30分钟
### ⚠️ P1 - 高优先级(数据显示错误)
2. **修复统计API** - `app/api/materials/stats/route.ts`
- 问题:包含软删除数据
- 影响:"全部"按钮显示错误数字
- 预计修复时间:15分钟
3. **修复分类统计** - `app/api/categories/route.ts`
- 问题:分类统计包含软删除数据
- 影响:分类按钮数字不准确
- 预计修复时间:15分钟
### 📋 P2 - 中优先级(功能验证)
4. **验证下载功能** - `app/api/materials/download/route.ts`
- 确认:支持新老路径
- 测试:老素材和新素材都能正常下载
- 预计时间:30分钟
5. **验证上传功能** - 素材上传相关代码
- 确认:新素材使用新路径
- 测试:上传后能正常预览和下载
- 预计时间:30分钟
---
## 🔧 修复方案
### ❌ 方案 A: 从生产容器恢复源码(不可行)
**调查结果**:
- 生产容器使用 Next.js standalone 模式部署
- 容器内只包含编译后的代码:
- `.next/` - 编译后的JavaScript代码
- `node_modules/` - 依赖包
- `server.js` - 服务器入口
- `package.json` - 依赖配置
- `public/` - 静态资源
- `uploads/` - 用户上传文件(716GB)
- **没有TypeScript源码文件**(.ts/.tsx)
**结论**: 无法从生产容器恢复源码
---
### ✅ 方案 B: 修复本地代码的BUG(推荐)
**前提条件**:
- 本地代码虽然版本较旧(1.0.0 vs 2.2.0),但核心功能应该是完整的
- 已经识别出3个明确的BUG需要修复
- 可以通过修复这3个BUG来解决当前的数据显示问题
**优点**:
- 快速解决当前问题
- 不依赖生产容器的源码
- 可以在影子系统充分测试
- 修复后可以逐步完善其他功能
**步骤**:
1. 修复本地代码的3个BUG:
- BUG 1: `app/api/materials/stats/route.ts` - 统计API过滤软删除
- BUG 2: `app/api/materials/route.ts` - 修复分页逻辑
- BUG 3: `app/api/categories/route.ts` - 分类统计过滤软删除
2. 在本地构建并测试
3. 部署到影子系统(端口5320)
4. 执行完整功能测试
5. 验证通过后部署到生产系统(端口4320)
**预计时间**: 1-2小时
---
### 方案 C: 从编译代码反推(不推荐)❌
**缺点**:
- 编译后的代码已经被混淆和优化
- 反推工作量巨大且容易出错
- 无法保证准确性
**不推荐原因**: 成本高、风险大、不可靠
---
## 📊 数据统计
### 素材类型分布(生产数据库)
| 素材类型 | 数量 | 说明 |
|---------|------|------|
| 3DMAX | 656 | 3D场景文件 |
| 3D场景 | 370 | 3D场景文件 |
| 其他 | 101 | 其他类型 |
| 音乐 | 93 | 音乐文件 |
| PSD | 71 | PSD设计文件 |
| 种草视频 | 57 | 营销视频 |
| 买家秀图片 | 50 | 用户图片 |
| 主图视频 | 41 | 产品视频 |
| 安装视频 | 29 | 教程视频 |
| 实拍图片 | 19 | 实拍照片 |
| 详情页 | 17 | 详情页设计 |
| 材质 | 11 | 材质文件 |
| 模特 | 7 | 模特图片 |
| 标准模型 | 1 | 标准模型 |
| 小红书种草 | 1 | 小红书内容 |
| **总计** | **1524** | 未删除素材 |
**说明**:
- 用户筛选"3D场景"时看到1026个 = 370(3D场景)+ 656(3DMAX)
- 这是正常的筛选结果,不是数据缺失
- 其他504个素材是不同类型(PSD、视频、图片等)
### 素材店铺分布(生产数据库)
| 店铺 | 素材数量 |
|------|---------|
| 顾家 | 388 |
| 亿家达 | 302 |
| 朴美 | 224 |
| 卓禾 | 179 |
| 欧意朗 | 169 |
| 通用 | 159 |
| 索菲亚 | 99 |
| (空) | 4 |
| **总计** | **1524** |
### 最近上传(2025-12-20)
- 08:53 - 通用店铺种草视频 (2条)
- 08:22 - 通用店铺种草视频 (3条)
- 08:03 - 欧意朗 XG0232
- 07:50 - 顾家 GBGJ-AD0312书房
- 07:46 - 顾家 GBGJ-AD0312客厅
- ... (更多)
**观察**: 系统正常运行,用户持续上传素材
---
## ⚠️ 注意事项
### 1. 不要直接修改生产系统
- ❌ 不要在生产容器中直接修改代码
- ❌ 不要在生产数据库中直接修改数据
- ✅ 只从生产系统读取和复制文件
- ✅ 所有修改先在本地测试
- ✅ 测试通过后部署到影子系统
- ✅ 影子系统验证通过后才部署到生产
### 2. 保护用户数据
- ❌ 不要删除或覆盖 `uploads` 目录
- ❌ 不要修改老素材的存储路径
- ✅ 数据库操作前必须备份
- ✅ 保持老素材在原位置可访问
### 3. 环境隔离
- **生产系统** (端口4320):
- 数据库: `lobechat`
- Redis: 6379
- 容器: `youyidian-3d-material`
- **影子系统** (端口5320):
- 数据库: `lobechat_stage`
- Redis: 6380
- 容器: `youyidian-3d-material-shadow`
- ✅ 确保环境变量正确配置
- ✅ 部署脚本默认部署到生产,使用 `-Target shadow` 部署到影子
### 4. 测试验证
- ✅ 在影子系统完整测试所有功能
- ✅ 特别测试新老素材的下载功能
- ✅ 验证分页功能正常
- ✅ 验证统计数字正确
- ✅ 记录测试结果
---
## 📝 后续行动
### 立即执行
1. [ ] 从生产容器提取源码
2. [ ] 对比本地差异,生成差异报告
3. [ ] 恢复缺失和不一致的文件
### 测试验证
4. [ ] 在本地构建并测试
5. [ ] 部署到影子系统
6. [ ] 执行完整功能测试
7. [ ] 特别测试新老素材下载
### 生产部署
8. [ ] 备份生产数据库
9. [ ] 部署到生产系统
10. [ ] 验证生产系统功能
11. [ ] 监控系统运行状态
### 文档更新
12. [ ] 更新功能恢复进度文档
13. [ ] 记录修复的BUG
14. [ ] 更新部署文档
15. [ ] 建立Git版本控制
---
## 📞 联系信息
如有问题,请联系:
- 系统管理员
- 开发团队
---
**报告结束**