# 图片存储和分类修复方案 ## 🔍 问题分析 ### 1. 631错误:no such bucket ``` {code: 631, message: 'xhr request failed, code: 631; response: {"error":"no such bucket"}'} ``` **原因**: - 存储桶不存在或配置错误 - `NovaStorage.withCid(cid)` 使用的公司ID对应的存储桶不存在 ### 2. 图片未正确归类显示 **原因**: - 上传失败导致没有文件 - AI分析结果保存到ProjectFile,但上传失败 --- ## ✅ 数据存储结构 ### ProjectFile表结构 ```typescript ProjectFile { project: Pointer, // 关联项目 attach: Pointer, // 关联附件(包含实际文件URL) fileType: string, // 'delivery_white_model', 'delivery_soft_decor', etc. fileUrl: string, // 文件URL(从Attachment复制) fileName: string, // 文件名 fileSize: number, // 文件大小 stage: string, // 'delivery' uploadedBy: Pointer, // 上传人 data: { // 扩展数据 spaceId: string, // 空间ID(Product ID) productId: string, // 同spaceId deliveryType: string, // 'white_model', 'soft_decor', 'rendering', 'post_process' uploadedFor: string, // 'delivery_execution' approvalStatus: string, // 'unverified', 'approved', 'rejected' aiAnalysis: { // 🔥 AI分析结果(点击确认后保存) suggestedStage: string, confidence: number, category: string, quality: {...}, technical: {...}, description: string, tags: string[] } } } ``` ### Attachment表结构 ```typescript Attachment { url: string, // OBS文件URL name: string, // 文件名 size: number, // 文件大小 mime: string, // MIME类型 md5: string, // 文件MD5 metadata: { // 元数据 projectId: string, fileType: string, spaceId: string, stage: string } } ``` --- ## 🔧 修复方案 ### 1. 修复存储桶配置 **文件**: `project-file.service.ts` **问题**:公司ID对应的存储桶不存在 **解决**:使用默认存储桶fallback ```typescript // 修复前 const cid = localStorage.getItem('company'); if (!cid) { throw new Error('公司ID未找到'); } const storage = await NovaStorage.withCid(cid); // 修复后 let cid = localStorage.getItem('company'); if (!cid) { console.warn('⚠️ 未找到公司ID,使用默认存储桶'); cid = 'cDL6R1hgSi'; // 默认公司ID } console.log(`📦 使用存储桶CID: ${cid}`); const storage = await NovaStorage.withCid(cid); ``` ### 2. 图片上传流程 ``` 用户拖拽图片 → drag-upload-modal ↓ AI分析图片(色彩+纹理检测) ↓ 用户点击"确认交付清单" ↓ 调用 confirmDragUpload(result) ↓ 遍历 result.files ↓ 对每个文件: 1. 调用 uploadDeliveryFile() - 上传到OBS存储(3次重试) - 创建Attachment记录 - 创建ProjectFile记录 - fileType: `delivery_${deliveryType}` - stage: 'delivery' - data.spaceId: 空间ID - data.deliveryType: 阶段类型 ↓ 2. 保存AI分析结果 - 获取最新上传的ProjectFile - 保存到 ProjectFile.data.aiAnalysis - 保存到 Project.date.imageAnalysis ↓ 刷新文件列表 loadDeliveryFiles() ↓ 显示在对应阶段 ``` ### 3. 图片加载流程 ``` loadDeliveryFiles() ↓ 查询ProjectFile表 - 条件:project = 当前项目 - 条件:fileType = `delivery_${deliveryType}` - 条件:stage = 'delivery' ↓ 过滤:data.spaceId = 当前空间ID ↓ 转换为DeliveryFile格式 - url: projectFile.get('fileUrl') - name: projectFile.get('fileName') - size: projectFile.get('fileSize') - deliveryType: data.deliveryType ↓ 显示在对应阶段 ``` ### 4. 关键方法 #### confirmDragUpload (stage-delivery.component.ts) ```typescript async confirmDragUpload(result: UploadResult): Promise { for (const fileItem of result.files) { // 1. 上传文件 await this.uploadDeliveryFile( mockEvent, fileItem.spaceId, // 空间ID(Product ID) fileItem.stageType, // 'white_model', 'soft_decor', etc. true // silentMode ); // 2. 保存AI分析结果(如果有) if (uploadFile.analysisResult) { const recentFiles = this.getProductDeliveryFiles( fileItem.spaceId, fileItem.stageType ); const latestFile = recentFiles[recentFiles.length - 1]; if (latestFile && latestFile.projectFile) { // 保存到ProjectFile.data.aiAnalysis projectFile.set('data', { ...existingData, aiAnalysis: uploadFile.analysisResult }); await projectFile.save(); // 保存到Project.date.imageAnalysis await this.imageAnalysisService.saveAnalysisResult(...); } } } // 3. 刷新文件列表 await this.loadDeliveryFiles(); } ``` #### uploadDeliveryFile (stage-delivery.component.ts) ```typescript async uploadDeliveryFile( event: any, productId: string, // 空间ID deliveryType: string, // 阶段类型 silentMode: boolean = false ): Promise { // 上传文件并创建ProjectFile记录 const projectFile = await this.projectFileService.uploadProjectFileWithRecord( file, projectId, `delivery_${deliveryType}`, // 🔥 fileType productId, // 🔥 spaceId 'delivery', // 🔥 stage { deliveryType: deliveryType, productId: productId, spaceId: productId, approvalStatus: 'unverified' } ); // 保存扩展数据 projectFile.set('data', { spaceId: productId, deliveryType: deliveryType, approvalStatus: 'unverified' }); await projectFile.save(); } ``` #### loadDeliveryFiles (stage-delivery.component.ts) ```typescript async loadDeliveryFiles(): Promise { for (const product of this.projectProducts) { // 初始化 this.deliveryFiles[product.id] = { white_model: [], soft_decor: [], rendering: [], post_process: [] }; // 加载各类型的交付文件 for (const deliveryType of this.deliveryTypes) { const files = await this.projectFileService.getProjectFiles( projectId, { fileType: `delivery_${deliveryType.id}`, // 🔥 查询条件 stage: 'delivery' } ); // 过滤当前产品的文件 const productFiles = files.filter(file => { const data = file.get('data'); return data?.productId === product.id || data?.spaceId === product.id; }); // 转换为DeliveryFile格式 this.deliveryFiles[product.id][deliveryType.id] = productFiles.map(pf => ({ id: pf.id, url: pf.get('fileUrl'), // 🔥 从ProjectFile读取 name: pf.get('fileName'), size: pf.get('fileSize'), deliveryType: deliveryType.id, projectFile: pf })); } } } ``` --- ## 📊 数据流图 ### 完整流程 ``` 【用户操作】 拖拽图片到弹窗 ↓ 【AI分析】 - 色彩检测 - 纹理检测 - 质量评估 - 阶段判定 ↓ 【显示结果】 在弹窗中显示分类 - 白模:无色彩+无纹理 - 软装:有色彩+有家具 - 渲染:有色彩+有灯光 - 后期:高质量+完整场景 ↓ 【用户确认】 点击"确认交付清单" ↓ 【上传文件】 for each file: 1. 上传到OBS(3次重试) 2. 创建Attachment记录 3. 创建ProjectFile记录 - fileType: delivery_白模/软装/渲染/后期 - data.spaceId: 空间ID - data.deliveryType: 阶段类型 4. 保存AI分析结果 - ProjectFile.data.aiAnalysis - Project.date.imageAnalysis ↓ 【刷新显示】 loadDeliveryFiles() - 查询ProjectFile表 - 按spaceId和deliveryType分类 - 显示在对应阶段 ``` --- ## 🧪 测试步骤 ### 1. 测试存储桶修复 ``` 1. 上传图片 2. 查看控制台日志: 📦 使用存储桶CID: xxx 📤 上传尝试 1/3: xxx.jpg ✅ 文件上传成功 3. 确认不再出现631错误 ``` ### 2. 测试AI分类 ``` 1. 上传纯白草图 → 应归类到"白模" 2. 上传有色彩图 → 应归类到"软装"/"渲染"/"后期" 3. 查看日志: 🎯 阶段判断依据: 有色彩: true/false 有纹理: true/false ``` ### 3. 测试图片显示 ``` 1. 点击"确认交付清单" 2. 等待上传完成 3. 查看各阶段: - 白模:显示白模图片 - 软装:显示软装图片 - 渲染:显示渲染图片 - 后期:显示后期图片 ``` ### 4. 验证数据 ```sql -- 查询ProjectFile表 SELECT * FROM ProjectFile WHERE project = 'XB56jBlvkd' AND fileType LIKE 'delivery_%' AND stage = 'delivery' ORDER BY createdAt DESC; -- 验证字段 - fileUrl: 应该有值(OBS URL) - fileName: 应该有值 - fileSize: 应该有值 - data.spaceId: 应该有值(Product ID) - data.deliveryType: 应该有值(white_model/soft_decor/etc.) - data.aiAnalysis: 应该有值(AI分析结果) ``` --- ## 🔍 故障排查 ### 如果仍然出现631错误 #### 检查1:存储桶配置 ```javascript // 打开控制台 console.log('公司ID:', localStorage.getItem('company')); console.log('使用的存储桶CID:', cid); ``` #### 检查2:NovaStorage配置 ```javascript // 检查fmode-ng版本 import { NovaStorage } from 'fmode-ng/core'; const storage = await NovaStorage.withCid('cDL6R1hgSi'); console.log('存储桶配置:', storage); ``` #### 检查3:OBS配置 联系管理员确认: - OBS存储桶是否存在 - 公司ID是否正确 - 存储桶权限是否正确 ### 如果图片没有显示 #### 检查1:上传是否成功 ``` 查看控制台日志: ✅ ProjectFile 创建成功: abc123 ✅ AI分析结果已保存到ProjectFile ``` #### 检查2:查询ProjectFile表 ```javascript const query = new Parse.Query('ProjectFile'); query.equalTo('project', project); query.equalTo('fileType', 'delivery_white_model'); const files = await query.find(); console.log('查询结果:', files.length, '个文件'); ``` #### 检查3:数据字段 ```javascript files.forEach(f => { console.log({ id: f.id, fileUrl: f.get('fileUrl'), fileName: f.get('fileName'), spaceId: f.get('data')?.spaceId, deliveryType: f.get('data')?.deliveryType }); }); ``` #### 检查4:loadDeliveryFiles是否调用 ``` 查看控制台日志: 已加载交付文件: {...} ``` --- ## 📝 文件修改清单 1. **project-file.service.ts** - 添加存储桶fallback - 使用默认CID 'cDL6R1hgSi' 2. **stage-delivery.component.ts** - confirmDragUpload:保存AI分析结果 - uploadDeliveryFile:上传文件并创建记录 - loadDeliveryFiles:加载并显示图片 3. **image-analysis.service.ts** - 添加色彩和纹理检测 - 优化白模判定逻辑 --- ## ✅ 总结 ### 核心问题 1. **631错误**:存储桶配置错误 → 使用默认存储桶fallback 2. **图片未显示**:上传失败导致 → 修复存储桶后自动解决 3. **分类不准确**:缺少色彩检测 → 添加色彩和纹理检测 ### 数据流 - **上传**:OBS → Attachment → ProjectFile(包含fileUrl, data.spaceId, data.deliveryType) - **保存**:AI分析结果 → ProjectFile.data.aiAnalysis + Project.date.imageAnalysis - **加载**:查询ProjectFile(按fileType和spaceId过滤)→ 显示在对应阶段 ### 关键字段 - `fileType`: `delivery_${deliveryType}` (用于查询) - `data.spaceId`: 空间ID (用于过滤) - `data.deliveryType`: 阶段类型 (用于分类) - `data.aiAnalysis`: AI分析结果 (用于显示) --- **创建时间**:2025-11-28