Files
autoclip/docs/COLLECTION_REORDER_FIX.md
T
2025-08-21 01:05:23 +08:00

11 KiB
Raw Blame History

合集排序功能修复文档

问题描述

前端合集模块中,通过拖拽调整切片顺序后失败,toast提示【更新合集顺序失败】。

问题原因分析

  1. 后端API问题:

    • PUT /collections/{collection_id} 端点返回500错误,因为tags字段验证失败
    • 没有专门的排序端点,前端试图通过更新clip_ids字段来实现排序
    • CollectionUpdate schema没有正确处理metadata字段的更新
  2. 前端API调用问题:

    • 前端调用projectApi.updateCollection(projectId, collectionId, { clip_ids: newClipIds })
    • 但后端期望的是metadata.clip_ids格式

修复方案

1. 修复后端PUT端点

问题:update_collection方法直接返回ORM对象,没有转换为CollectionResponse格式

解决方案:

  • 在PUT /collections/{collection_id}端点中添加完整的响应转换逻辑
  • 确保tags字段正确处理(空值转换为空列表)
  • 正确提取和返回clip_ids字段
@router.put("/{collection_id}", response_model=CollectionResponse)
async def update_collection(
    collection_id: str,
    collection_data: CollectionUpdate,
    collection_service: CollectionService = Depends(get_collection_service)
):
    """Update a collection."""
    try:
        collection = collection_service.update_collection(collection_id, collection_data)
        if not collection:
            raise HTTPException(status_code=404, detail="Collection not found")
        
        # Convert to response schema
        status_obj = getattr(collection, 'status', None)
        status_value = status_obj.value if hasattr(status_obj, 'value') else 'created'
        
        # 获取clip_ids
        clip_ids = []
        metadata = getattr(collection, 'collection_metadata', {}) or {}
        if metadata and 'clip_ids' in metadata:
            clip_ids = metadata['clip_ids']
        
        return CollectionResponse(
            id=str(getattr(collection, 'id', '')),
            project_id=str(getattr(collection, 'project_id', '')),
            name=str(getattr(collection, 'name', '')),
            description=str(getattr(collection, 'description', '')) if getattr(collection, 'description', None) else None,
            theme=getattr(collection, 'theme', None),
            status=status_value,
            tags=getattr(collection, 'tags', []) or [],  # 确保tags不为None
            metadata=getattr(collection, 'collection_metadata', {}) or {},
            created_at=getattr(collection, 'created_at', None) if isinstance(getattr(collection, 'created_at', None), (type(None), __import__('datetime').datetime)) else None,
            updated_at=getattr(collection, 'updated_at', None) if isinstance(getattr(collection, 'updated_at', None), (type(None), __import__('datetime').datetime)) else None,
            total_clips=getattr(collection, 'clips_count', 0) or 0,
            clip_ids=clip_ids
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

2. 添加专门的排序端点

问题:没有专门的排序API端点

解决方案:

  • 创建PATCH /collections/{collection_id}/reorder端点
  • 专门处理切片顺序的更新
  • 简化API调用,直接接收clip_ids数组
  • 关键修复:直接使用SQLAlchemy的update语句更新数据库,避免ORM更新问题
@router.patch("/{collection_id}/reorder", response_model=CollectionResponse)
async def reorder_collection_clips(
    collection_id: str,
    clip_ids: List[str],
    collection_service: CollectionService = Depends(get_collection_service)
):
    """Reorder clips in a collection."""
    try:
        # 获取合集
        collection = collection_service.get(collection_id)
        if not collection:
            raise HTTPException(status_code=404, detail="Collection not found")
        
        # 更新collection_metadata中的clip_ids
        metadata = getattr(collection, 'collection_metadata', {}) or {}
        metadata['clip_ids'] = clip_ids
        
        # 直接更新数据库中的collection_metadata字段
        from sqlalchemy import update
        from models.collection import Collection
        
        stmt = update(Collection).where(Collection.id == collection_id).values(
            collection_metadata=metadata
        )
        collection_service.db.execute(stmt)
        collection_service.db.commit()
        
        # 重新获取更新后的合集
        updated_collection = collection_service.get(collection_id)
        if not updated_collection:
            raise HTTPException(status_code=404, detail="Collection not found")
        
        # Convert to response schema
        status_obj = getattr(updated_collection, 'status', None)
        status_value = status_obj.value if hasattr(status_obj, 'value') else 'created'
        
        return CollectionResponse(
            id=str(getattr(updated_collection, 'id', '')),
            project_id=str(getattr(updated_collection, 'project_id', '')),
            name=str(getattr(updated_collection, 'name', '')),
            description=str(getattr(updated_collection, 'description', '')) if getattr(updated_collection, 'description', None) else None,
            theme=getattr(updated_collection, 'theme', None),
            status=status_value,
            tags=getattr(updated_collection, 'tags', []) or [],
            metadata=getattr(updated_collection, 'collection_metadata', {}) or {},
            created_at=getattr(updated_collection, 'created_at', None) if isinstance(getattr(updated_collection, 'created_at', None), (type(None), __import__('datetime').datetime)) else None,
            updated_at=getattr(updated_collection, 'updated_at', None) if isinstance(getattr(updated_collection, 'updated_at', None), (type(None), __import__('datetime').datetime)) else None,
            total_clips=getattr(updated_collection, 'clips_count', 0) or 0,
            clip_ids=clip_ids
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

3. 更新前端API调用

问题:前端使用错误的API调用方式,且存在多个版本的store文件

解决方案:

  • 添加新的reorderCollectionClips API方法
  • 修改store中的排序逻辑,使用新的API端点
  • 关键发现:需要同时修复frontend/src/store/useProjectStore.ts和shared/frontend/src/store/useProjectStore.ts两个文件
// 前端API
reorderCollectionClips: (collectionId: string, clipIds: string[]): Promise<Collection> => {
  return api.patch(`/collections/${collectionId}/reorder`, clipIds)
}

// Store中的调用
await projectApi.reorderCollectionClips(collectionId, newClipIds)

重要提醒:项目中有两个版本的store文件,都需要更新:

  • frontend/src/store/useProjectStore.ts ✅ 已修复
  • shared/frontend/src/store/useProjectStore.ts ✅ 已修复

修复结果

✅ 修复前

  • PUT端点返回500错误(tags字段验证失败)
  • 没有专门的排序端点
  • 前端排序失败,显示【更新合集顺序失败】

✅ 修复后

  • PUT端点正常工作,返回200状态码
  • 新增专门的排序端点PATCH /collections/{collection_id}/reorder
  • 前端排序成功,显示【合集顺序已更新】

📊 测试结果

新排序端点测试:

PATCH /collections/0e181e1a-52c2-42c2-9481-cc306e3b27f9/reorder
📥 响应状态: 200
✅ 排序成功: ['c8be1b33-679c-4ac6-9af6-2af21595e458', '0125c5ec-4ba5-41ac-b328-e1bc61ea9e69', '4ae8d564-234e-4a5f-86a3-840d65e59f59']

修复后的PUT端点测试:

PUT /collections/0e181e1a-52c2-42c2-9481-cc306e3b27f9
📥 响应状态: 200
✅ 更新成功: ['4ae8d564-234e-4a5f-86a3-840d65e59f59', 'c8be1b33-679c-4ac6-9af6-2af21595e458', '0125c5ec-4ba5-41ac-b328-e1bc61ea9e69']

完整功能测试:

🎯 完整测试合集排序功能
==================================================

1️⃣ 获取初始状态...
✅ 合集: 职场成长记
📋 初始clip_ids: ['c8be1b33-679c-4ac6-9af6-2af21595e458', '0125c5ec-4ba5-41ac-b328-e1bc61ea9e69', '4ae8d564-234e-4a5f-86a3-840d65e59f59']

2️⃣ 测试多次排序...
🔄 第一次排序:交换前两个元素
✅ 第一次排序成功: ['0125c5ec-4ba5-41ac-b328-e1bc61ea9e69', '4ae8d564-234e-4a5f-86a3-840d65e59f59', 'c8be1b33-679c-4ac6-9af6-2af21595e458']

🔄 第二次排序:再次交换前两个元素
✅ 第二次排序成功: ['4ae8d564-234e-4a5f-86a3-840d65e59f59', 'c8be1b33-679c-4ac6-9af6-2af21595e458', '0125c5ec-4ba5-41ac-b328-e1bc61ea9e69']

🔄 第三次排序:恢复到原始顺序
✅ 第三次排序成功: ['c8be1b33-679c-4ac6-9af6-2af21595e458', '0125c5ec-4ba5-41ac-b328-e1bc61ea9e69', '4ae8d564-234e-4a5f-86a3-840d65e59f59']

3️⃣ 最终验证...
✅ 排序功能完全正常!数据已恢复到原始顺序

4️⃣ 测试前端API兼容性...
✅ 前端API兼容性正常

==================================================
🎉 合集排序功能测试完成!

相关文件

后端文件

  • backend/api/v1/collections.py - 合集API路由
  • backend/services/collection_service.py - 合集服务
  • backend/schemas/collection.py - 合集数据模型

前端文件

  • frontend/src/services/api.ts - 前端API客户端
  • frontend/src/store/useProjectStore.ts - 前端状态管理
  • frontend/src/components/CollectionPreviewModal.tsx - 合集预览组件

测试文件

  • scripts/test_collection_reorder.py - 排序功能测试脚本

API端点说明

1. PUT /collections/{collection_id}

用途:更新合集信息 请求体:

{
  "name": "合集名称",
  "description": "合集描述",
  "metadata": {
    "clip_ids": ["clip_id_1", "clip_id_2", "clip_id_3"]
  }
}

2. PATCH /collections/{collection_id}/reorder

用途:重新排序合集中的切片 请求体:

["clip_id_2", "clip_id_1", "clip_id_3"]

使用建议

  1. 推荐使用专门的排序端点:PATCH /collections/{collection_id}/reorder

    • 语义更清晰
    • 参数更简单
    • 专门为排序优化
  2. PUT端点用于完整更新:当需要更新合集的其他信息时使用

  3. 前端拖拽排序:现在应该能正常工作,不再出现【更新合集顺序失败】的错误

经验总结

  1. API设计:为特定功能创建专门的端点,而不是复用通用端点
  2. 数据验证:确保schema字段有正确的默认值和类型转换
  3. 错误处理:提供清晰的错误信息和状态码
  4. 测试验证:修复后及时测试,确保功能正常工作
  5. 数据库更新:对于JSON字段的更新,直接使用SQLAlchemy的update语句比ORM的setattr更可靠
  6. 问题排查:通过模拟前端调用和逐步测试,能快速定位问题根源
  7. 多版本文件:注意项目中可能存在多个版本的相同文件,都需要同步更新
  8. 缓存问题:前端可能有缓存,需要清除缓存或重启服务