# 生产系统问题分析报告 **日期**: 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版本控制 --- ## 📞 联系信息 如有问题,请联系: - 系统管理员 - 开发团队 --- **报告结束**