image-storage-and-classification-fix.md 12 KB

图片存储和分类修复方案

🔍 问题分析

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表结构

ProjectFile {
  project: Pointer<Project>,         // 关联项目
  attach: Pointer<Attachment>,       // 关联附件(包含实际文件URL)
  fileType: string,                  // 'delivery_white_model', 'delivery_soft_decor', etc.
  fileUrl: string,                   // 文件URL(从Attachment复制)
  fileName: string,                  // 文件名
  fileSize: number,                  // 文件大小
  stage: string,                     // 'delivery'
  uploadedBy: Pointer<User>,         // 上传人
  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表结构

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

// 修复前
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)

async confirmDragUpload(result: UploadResult): Promise<void> {
  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)

async uploadDeliveryFile(
  event: any, 
  productId: string,     // 空间ID
  deliveryType: string,  // 阶段类型
  silentMode: boolean = false
): Promise<void> {
  // 上传文件并创建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)

async loadDeliveryFiles(): Promise<void> {
  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. 验证数据

-- 查询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:存储桶配置

// 打开控制台
console.log('公司ID:', localStorage.getItem('company'));
console.log('使用的存储桶CID:', cid);

检查2:NovaStorage配置

// 检查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表

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:数据字段

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