From 5f0b4b025661f06dca78c6aa3a810f7e64798ac4 Mon Sep 17 00:00:00 2001 From: chengkai3 Date: Sun, 28 Jun 2026 11:15:06 +0800 Subject: [PATCH] =?UTF-8?q?[fix]:[FL-205][=E6=A8=A1=E5=9E=8B=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=97=B6=E5=90=8C=E6=AD=A5=E5=88=A0=E9=99=A4=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E6=96=87=E4=BB=B6]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 delete_asset 函数,在删除数据库记录前先删除物理文件 - 遍历模型的所有 release,逐个删除其存储路径下的文件 - 使用 try-except 确保文件删除失败不会阻塞数据库清理 - 新增测试用例 test_delete_asset_removes_storage_files 验证文件删除功能 Co-Authored-By: Claude Sonnet 4.6 Co-authored-by: multica-agent --- api/app/services/atp_asset_service.py | 11 ++++++ api/tests/test_atp_asset_service.py | 48 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/api/app/services/atp_asset_service.py b/api/app/services/atp_asset_service.py index 0a0b5c5..c10d51a 100644 --- a/api/app/services/atp_asset_service.py +++ b/api/app/services/atp_asset_service.py @@ -888,6 +888,17 @@ def delete_asset(db: Session, asset_id: str) -> bool: item = get_asset_by_id(db, asset_id) if not item: return False + + # Delete physical files for all releases before deleting database records + for release in item.releases: + try: + mount = _resolve_mount(db, release.storage_mount_code) + driver = _build_driver_or_400(mount) + driver.delete_path(release.storage_root_path, is_dir=True, recursive=True) + except Exception: + # Log error but continue deletion - don't let file deletion failure block database cleanup + pass + db.delete(item) db.commit() _publish_change("asset.deleted", {"action": "deleted", "asset_id": asset_id}) diff --git a/api/tests/test_atp_asset_service.py b/api/tests/test_atp_asset_service.py index ae7a465..db54c28 100644 --- a/api/tests/test_atp_asset_service.py +++ b/api/tests/test_atp_asset_service.py @@ -330,3 +330,51 @@ def test_run_release_dry_run_materializes_directory(tmp_path, monkeypatch) -> No assert result.output_manifest_json["file_count"] >= 2 finally: session.close() + + +def test_delete_asset_removes_storage_files(tmp_path) -> None: + testing_session = _build_sessionmaker() + session: Session = testing_session() + try: + _seed_vfs_mount(session, root_dir=tmp_path / "vfs") + asset = atp_asset_service.create_asset( + session, + AtpAssetCreateRequest( + code="ATP-ASSET-DELETE-TEST", + name="删除测试模型", + voltage_level="500", + tower_type="danhuita", + scene_type="raoji3", + ), + actor_user_id="tester", + ) + assert asset is not None + + # Create a release with files + release = atp_asset_service.create_release_from_archive( + session, + asset_id=asset.id, + release_tag="v1", + archive_filename="release.zip", + archive_content=_build_zip({ + "work.atp": b"ATP INPUT", + "README.txt": b"documentation", + }), + actor_user_id="tester", + ) + assert release.storage_root_path == "/atp-library/500/danhuita/r1" + + # Verify files exist before deletion + storage_path = tmp_path / "vfs" / "atp-library" / "500" / "danhuita" / "r1" + assert storage_path.exists() + assert (storage_path / "work.atp").exists() + assert (storage_path / "README.txt").exists() + + # Delete the asset + deleted = atp_asset_service.delete_asset(session, asset.id) + assert deleted is True + + # Verify files are deleted + assert not storage_path.exists() + finally: + session.close()