11 KiB
11 KiB
合集排序功能修复文档
问题描述
前端合集模块中,通过拖拽调整切片顺序后失败,toast提示【更新合集顺序失败】。
问题原因分析
-
后端API问题:
PUT /collections/{collection_id}端点返回500错误,因为tags字段验证失败- 没有专门的排序端点,前端试图通过更新
clip_ids字段来实现排序 CollectionUpdateschema没有正确处理metadata字段的更新
-
前端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文件
解决方案:
- 添加新的
reorderCollectionClipsAPI方法 - 修改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"]
使用建议
-
推荐使用专门的排序端点:
PATCH /collections/{collection_id}/reorder- 语义更清晰
- 参数更简单
- 专门为排序优化
-
PUT端点用于完整更新:当需要更新合集的其他信息时使用
-
前端拖拽排序:现在应该能正常工作,不再出现【更新合集顺序失败】的错误
经验总结
- API设计:为特定功能创建专门的端点,而不是复用通用端点
- 数据验证:确保schema字段有正确的默认值和类型转换
- 错误处理:提供清晰的错误信息和状态码
- 测试验证:修复后及时测试,确保功能正常工作
- 数据库更新:对于JSON字段的更新,直接使用SQLAlchemy的
update语句比ORM的setattr更可靠 - 问题排查:通过模拟前端调用和逐步测试,能快速定位问题根源
- 多版本文件:注意项目中可能存在多个版本的相同文件,都需要同步更新
- 缓存问题:前端可能有缓存,需要清除缓存或重启服务