Skip to content

Commit bca3b07

Browse files
authored
feat: add dataset link (#430)
* fix: file count error after deletion * fix: delete dataset tooltip * fix: unify deletion pop-up text
1 parent 6813555 commit bca3b07

File tree

16 files changed

+300
-103
lines changed

16 files changed

+300
-103
lines changed

backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetFileApplicationService.java

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -226,34 +226,50 @@ public DatasetFile getDatasetFile(Dataset dataset, String fileId, String prefix)
226226

227227
/**
228228
* 删除文件
229+
* 使用悲观锁防止并发删除导致 fileCount 计算不准确
229230
*/
230231
@Transactional
231232
public void deleteDatasetFile(String datasetId, String fileId, String prefix) {
232-
Dataset dataset = datasetRepository.getById(datasetId);
233+
// 使用悲观锁获取 dataset,确保并发安全
234+
Dataset dataset = datasetRepository.getByIdWithLock(datasetId);
233235
DatasetFile file = getDatasetFile(dataset, fileId, prefix);
234-
dataset.setFiles(new ArrayList<>(Collections.singleton(file)));
236+
237+
// 先删除数据库记录
235238
datasetFileRepository.removeById(fileId);
236-
if (CommonUtils.isUUID(fileId)) {
237-
dataset.removeFile(file);
238-
}
239-
datasetRepository.updateById(dataset);
240-
// 删除文件时,上传到数据集中的文件会同时删除数据库中的记录和文件系统中的文件,归集过来的文件仅删除数据库中的记录
239+
240+
// 删除物理文件(仅删除在数据集路径下的文件)
241241
if (file.getFilePath().startsWith(dataset.getPath())) {
242242
try {
243243
Path filePath = validateAndResolvePath(file.getFilePath(), dataset.getPath());
244244
Files.deleteIfExists(filePath);
245245
} catch (IOException ex) {
246+
log.error("删除物理文件失败: {}", file.getFilePath(), ex);
246247
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR);
247248
}
248249
}
250+
251+
// 重新计算文件统计信息(确保数据准确)
252+
List<DatasetFile> remainingFiles = datasetFileRepository.findAllByDatasetId(datasetId);
253+
dataset.setFileCount((long) remainingFiles.size());
254+
dataset.setSizeBytes(remainingFiles.stream()
255+
.mapToLong(f -> f.getFileSize() != null ? f.getFileSize().longValue() : 0L)
256+
.sum());
257+
dataset.setUpdatedAt(LocalDateTime.now());
258+
259+
datasetRepository.updateById(dataset);
260+
261+
log.info("删除文件成功 - datasetId: {}, fileId: {}, fileName: {}, fileCount: {}, sizeBytes: {}",
262+
datasetId, fileId, file.getFileName(), dataset.getFileCount(), dataset.getSizeBytes());
249263
}
250264

251265
/**
252266
* 批量删除文件
267+
* 使用悲观锁防止并发删除导致 fileCount 计算不准确
253268
*/
254269
@Transactional
255270
public void batchDeleteFiles(String datasetId, BatchDeleteFilesRequest request) {
256-
Dataset dataset = datasetRepository.getById(datasetId);
271+
// 使用悲观锁获取 dataset,确保并发安全
272+
Dataset dataset = datasetRepository.getByIdWithLock(datasetId);
257273
if (dataset == null) {
258274
throw BusinessException.of(DataManagementErrorCode.DATASET_NOT_FOUND);
259275
}
@@ -266,6 +282,7 @@ public void batchDeleteFiles(String datasetId, BatchDeleteFilesRequest request)
266282
List<DatasetFile> filesToDelete = new ArrayList<>();
267283
List<String> failedFileIds = new ArrayList<>();
268284

285+
// 先删除数据库记录
269286
for (String fileId : fileIds) {
270287
try {
271288
DatasetFile file = getDatasetFile(dataset, fileId, request.getPrefix());
@@ -277,20 +294,8 @@ public void batchDeleteFiles(String datasetId, BatchDeleteFilesRequest request)
277294
}
278295
}
279296

280-
// 更新数据集(避免 ConcurrentModificationException)
281-
List<DatasetFile> datasetFiles = dataset.getFiles();
282-
if (datasetFiles != null) {
283-
// 创建一个新的列表来存储要保留的文件
284-
List<DatasetFile> remainingFiles = new ArrayList<>(datasetFiles);
285-
// 移除要删除的文件
286-
remainingFiles.removeAll(filesToDelete);
287-
dataset.setFiles(remainingFiles);
288-
}
289-
datasetRepository.updateById(dataset);
290-
291-
// 删除文件系统中的文件
297+
// 删除物理文件(仅删除在数据集路径下的文件)
292298
for (DatasetFile file : filesToDelete) {
293-
// 上传到数据集中的文件会同时删除数据库中的记录和文件系统中的文件,归集过来的文件仅删除数据库中的记录
294299
if (file.getFilePath().startsWith(dataset.getPath())) {
295300
try {
296301
Path filePath = validateAndResolvePath(file.getFilePath(), dataset.getPath());
@@ -303,6 +308,19 @@ public void batchDeleteFiles(String datasetId, BatchDeleteFilesRequest request)
303308
}
304309
}
305310

311+
// 重新计算文件统计信息(确保数据准确)
312+
List<DatasetFile> remainingFiles = datasetFileRepository.findAllByDatasetId(datasetId);
313+
dataset.setFileCount((long) remainingFiles.size());
314+
dataset.setSizeBytes(remainingFiles.stream()
315+
.mapToLong(f -> f.getFileSize() != null ? f.getFileSize().longValue() : 0L)
316+
.sum());
317+
dataset.setUpdatedAt(LocalDateTime.now());
318+
319+
datasetRepository.updateById(dataset);
320+
321+
log.info("批量删除文件成功 - datasetId: {}, successCount: {}, failedCount: {}, fileCount: {}, sizeBytes: {}",
322+
datasetId, filesToDelete.size(), failedFileIds.size(), dataset.getFileCount(), dataset.getSizeBytes());
323+
306324
// 如果有失败的文件,记录日志但不抛出异常
307325
if (!failedFileIds.isEmpty()) {
308326
log.warn("Failed to delete {} files out of {}", failedFileIds.size(), fileIds.size());
@@ -643,7 +661,8 @@ private void zipDirectory(Path sourceDir, Path basePath, ZipArchiveOutputStream
643661
*/
644662
@Transactional
645663
public void deleteDirectory(String datasetId, String prefix) {
646-
Dataset dataset = datasetRepository.getById(datasetId);
664+
// 使用悲观锁获取 dataset,防止并发删除导致 fileCount 不准确
665+
Dataset dataset = datasetRepository.getByIdWithLock(datasetId);
647666
if (dataset == null) {
648667
throw BusinessException.of(DataManagementErrorCode.DATASET_NOT_FOUND);
649668
}
@@ -694,6 +713,10 @@ public void deleteDirectory(String datasetId, String prefix) {
694713
})
695714
.collect(Collectors.toList());
696715

716+
log.info("删除目录开始 - datasetId: {}, prefix: {}, fileCount: {}, filesToDelete: {}",
717+
datasetId, prefix, dataset.getFileCount(), filesToDelete.size());
718+
719+
// 删除数据库记录
697720
for (DatasetFile file : filesToDelete) {
698721
datasetFileRepository.removeById(file.getId());
699722
}
@@ -702,20 +725,22 @@ public void deleteDirectory(String datasetId, String prefix) {
702725
try {
703726
deleteDirectoryRecursively(normalized);
704727
} catch (IOException e) {
705-
log.error("Failed to delete directory {} for dataset {}", normalized, datasetId, e);
728+
log.error("删除目录失败: datasetId={}, prefix={}", datasetId, prefix, e);
706729
throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR);
707730
}
708731

709-
// 更新数据集(避免 ConcurrentModificationException,先获取文件列表再删除)
710-
List<DatasetFile> datasetFiles = dataset.getFiles();
711-
if (datasetFiles != null) {
712-
// 创建一个新的列表来存储要保留的文件
713-
List<DatasetFile> remainingFiles = new ArrayList<>(datasetFiles);
714-
// 移除要删除的文件
715-
remainingFiles.removeAll(filesToDelete);
716-
dataset.setFiles(remainingFiles);
717-
}
732+
// 重新计算文件统计信息(确保数据准确)
733+
List<DatasetFile> remainingFiles = datasetFileRepository.findAllByDatasetId(datasetId);
734+
dataset.setFileCount((long) remainingFiles.size());
735+
dataset.setSizeBytes(remainingFiles.stream()
736+
.mapToLong(f -> f.getFileSize() != null ? f.getFileSize().longValue() : 0L)
737+
.sum());
738+
dataset.setUpdatedAt(LocalDateTime.now());
739+
718740
datasetRepository.updateById(dataset);
741+
742+
log.info("删除目录成功 - datasetId: {}, prefix: {}, deletedCount: {}, fileCount: {}, sizeBytes: {}",
743+
datasetId, prefix, filesToDelete.size(), dataset.getFileCount(), dataset.getSizeBytes());
719744
}
720745

721746
/**

backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/mapper/DatasetMapper.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@
55
import com.datamate.datamanagement.interfaces.dto.AllDatasetStatisticsResponse;
66
import org.apache.ibatis.annotations.Mapper;
77
import org.apache.ibatis.annotations.Param;
8+
import org.apache.ibatis.annotations.Select;
89
import org.apache.ibatis.session.RowBounds;
910

1011
import java.util.List;
1112

1213
@Mapper
1314
public interface DatasetMapper extends BaseMapper<Dataset> {
1415
Dataset findById(@Param("id") String id);
16+
17+
/**
18+
* 使用悲观锁查询数据集(FOR UPDATE)
19+
*/
20+
@Select("SELECT * FROM t_dm_datasets WHERE id = #{id} FOR UPDATE")
21+
Dataset findByIdWithLock(@Param("id") String id);
1522
Dataset findByName(@Param("name") String name);
1623
List<Dataset> findByStatus(@Param("status") String status);
1724
List<Dataset> findByCreatedBy(@Param("createdBy") String createdBy, RowBounds rowBounds);

backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/DatasetRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
public interface DatasetRepository extends IRepository<Dataset> {
2020
Dataset findByName(String name);
2121

22+
/**
23+
* 使用悲观锁获取数据集(用于更新操作,防止并发冲突)
24+
*/
25+
Dataset getByIdWithLock(String id);
26+
2227
List<Dataset> findByCriteria(String type, String status, String keyword, List<String> tagList, RowBounds bounds);
2328

2429
long countByCriteria(String type, String status, String keyword, List<String> tagList);

backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/DatasetRepositoryImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public Dataset findByName(String name) {
3131
return datasetMapper.selectOne(new LambdaQueryWrapper<Dataset>().eq(Dataset::getName, name));
3232
}
3333

34+
@Override
35+
public Dataset getByIdWithLock(String id) {
36+
return datasetMapper.findByIdWithLock(id);
37+
}
38+
3439
@Override
3540
public List<Dataset> findByCriteria(String type, String status, String keyword, List<String> tagList,
3641
RowBounds bounds) {

frontend/src/i18n/locales/en/common.json

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@
9494
"updateSuccess": "Task updated successfully",
9595
"updateFailed": "Failed to update task",
9696
"deleteSuccess": "Task deleted",
97-
"deleteConfirm": "Are you sure you want to delete this task? This action cannot be undone.",
98-
"deleteConfirmMessage": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.",
97+
"deleteTitle": "Confirm delete collection task",
98+
"deleteDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.",
9999
"confirmDelete": "Delete",
100100
"cancel": "Cancel"
101101
},
@@ -375,10 +375,14 @@
375375
"fileDownloadSuccess": "File downloaded successfully"
376376
},
377377
"confirm": {
378-
"deleteDatasetTitle": "Confirm delete this dataset?",
378+
"deleteDatasetTitle": "Confirm delete dataset",
379379
"deleteDatasetDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.",
380-
"deleteFolderTitle": "Confirm delete folder?",
381-
"deleteFolderDesc": "Deleting folder \"{{name}}\" will remove all files and subfolders. This action cannot be undone.",
380+
"deleteFileTitle": "Confirm delete file",
381+
"deleteFileDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.",
382+
"deleteFolderTitle": "Confirm delete folder",
383+
"deleteFolderDesc": "Are you sure you want to delete \"{{itemName}}\" and all its contents? This action cannot be undone.",
384+
"batchDeleteTitle": "Confirm batch delete",
385+
"batchDeleteDesc": "Are you sure you want to delete the selected {{count}} item(s)? This action cannot be undone.",
382386
"deleteConfirm": "Delete",
383387
"deleteCancel": "Cancel"
384388
},
@@ -664,7 +668,7 @@
664668
"deleteFailed": "Task deletion failed, please try again later"
665669
},
666670
"confirm": {
667-
"deleteTitle": "Confirm delete this task?",
671+
"deleteTitle": "Confirm delete ratio task",
668672
"deleteDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.",
669673
"okText": "Delete",
670674
"cancelText": "Cancel"
@@ -750,8 +754,8 @@
750754
"delete": "Delete"
751755
},
752756
"confirm": {
753-
"deleteTitle": "Confirm delete this ratio task?",
754-
"deleteDesc": "This task cannot be recovered after deletion. Please proceed with caution.",
757+
"deleteTitle": "Confirm delete ratio task",
758+
"deleteDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone.",
755759
"okText": "Delete",
756760
"cancelText": "Cancel"
757761
},
@@ -1446,7 +1450,12 @@
14461450
"taskDeleted": "Task deleted successfully",
14471451
"taskDetailFailed": "Failed to fetch task details",
14481452
"taskPaused": "Task paused",
1449-
"taskStarted": "Task started"
1453+
"taskStarted": "Task started",
1454+
"deleteFailed": "Failed to delete task, please try again later"
1455+
},
1456+
"confirm": {
1457+
"deleteTitle": "Confirm delete data processing task",
1458+
"deleteDesc": "Are you sure you want to delete \"{{itemName}}\"? This action cannot be undone."
14501459
}
14511460
},
14521461
"template": {
@@ -2270,8 +2279,8 @@
22702279
"cot": "Chain of Thought Generation"
22712280
},
22722281
"confirm": {
2273-
"deleteTitle": "Confirm delete this task?",
2274-
"deleteContent": "Task name: {{name}}",
2282+
"deleteTitle": "Confirm delete synthesis task",
2283+
"deleteDesc": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
22752284
"archiveTitle": "Confirm archive this synthesis task?",
22762285
"archiveContent": "Task name: {{name}}"
22772286
},
@@ -2567,9 +2576,9 @@
25672576
"delete": "Delete Task"
25682577
},
25692578
"confirm": {
2570-
"deleteTitle": "Confirm delete this synthesis task?",
2571-
"deleteDescription": "This task cannot be recovered after deletion, please proceed with caution.",
2572-
"okText": "Confirm Delete",
2579+
"deleteTitle": "Confirm delete synthesis task",
2580+
"deleteDescription": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
2581+
"okText": "Delete",
25732582
"cancelText": "Cancel"
25742583
},
25752584
"messages": {

frontend/src/i18n/locales/zh/common.json

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@
9494
"updateSuccess": "任务更新成功",
9595
"updateFailed": "任务更新失败",
9696
"deleteSuccess": "任务已删除",
97-
"deleteConfirm": "确定要删除该任务吗?此操作不可撤销。",
98-
"deleteConfirmMessage": "确定要删除「{{itemName}}」吗?删除后将无法恢复。",
97+
"deleteTitle": "确认删除归集任务",
98+
"deleteDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?",
9999
"confirmDelete": "删除",
100100
"cancel": "取消"
101101
},
@@ -375,10 +375,14 @@
375375
"fileDownloadSuccess": "文件下载成功"
376376
},
377377
"confirm": {
378-
"deleteDatasetTitle": "确认删除该数据集?",
379-
"deleteDatasetDesc": "确定要删除「{{itemName}}」吗?删除后将无法恢复。",
380-
"deleteFolderTitle": "确认删除文件夹?",
381-
"deleteFolderDesc": "删除文件夹 \"{{name}}\" 将同时删除其中的所有文件和子文件夹,此操作不可恢复。",
378+
"deleteDatasetTitle": "确认删除数据集",
379+
"deleteDatasetDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?",
380+
"deleteFileTitle": "确认删除文件",
381+
"deleteFileDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?",
382+
"deleteFolderTitle": "确认删除文件夹",
383+
"deleteFolderDesc": "删除「{{itemName}}」及其所有内容后将无法恢复,确定要执行此操作吗?",
384+
"batchDeleteTitle": "确认批量删除",
385+
"batchDeleteDesc": "删除选中的 {{count}} 个项目后将无法恢复,确定要执行此操作吗?",
382386
"deleteConfirm": "删除",
383387
"deleteCancel": "取消"
384388
},
@@ -664,8 +668,8 @@
664668
"deleteFailed": "任务删除失败,请稍后重试"
665669
},
666670
"confirm": {
667-
"deleteTitle": "确认删除该任务?",
668-
"deleteDesc": "确定要删除「{{itemName}}」吗?删除后将无法恢复。",
671+
"deleteTitle": "确认删除配比任务",
672+
"deleteDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?",
669673
"okText": "删除",
670674
"cancelText": "取消"
671675
},
@@ -750,8 +754,8 @@
750754
"delete": "删除"
751755
},
752756
"confirm": {
753-
"deleteTitle": "确认删除该配比任务?",
754-
"deleteDesc": "删除后该任务将无法恢复,请谨慎操作。",
757+
"deleteTitle": "确认删除配比任务",
758+
"deleteDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?",
755759
"okText": "删除",
756760
"cancelText": "取消"
757761
},
@@ -1446,7 +1450,12 @@
14461450
"taskDeleted": "任务删除成功",
14471451
"taskDetailFailed": "获取任务详情失败",
14481452
"taskPaused": "任务已暂停",
1449-
"taskStarted": "任务已启动"
1453+
"taskStarted": "任务已启动",
1454+
"deleteFailed": "任务删除失败,请稍后重试"
1455+
},
1456+
"confirm": {
1457+
"deleteTitle": "确认删除数据处理任务",
1458+
"deleteDesc": "删除「{{itemName}}」后将无法恢复,确定要执行此操作吗?"
14501459
}
14511460
},
14521461
"template": {
@@ -2270,8 +2279,8 @@
22702279
"cot": "链式推理生成"
22712280
},
22722281
"confirm": {
2273-
"deleteTitle": "确认删除任务?",
2274-
"deleteContent": "任务名:{{name}}",
2282+
"deleteTitle": "确认删除合成任务",
2283+
"deleteDesc": "删除「{{name}}」后将无法恢复,确定要执行此操作吗?",
22752284
"archiveTitle": "确认归档该合成任务?",
22762285
"archiveContent": "任务名:{{name}}"
22772286
},
@@ -2567,9 +2576,9 @@
25672576
"delete": "删除任务"
25682577
},
25692578
"confirm": {
2570-
"deleteTitle": "确认删除该合成任务?",
2571-
"deleteDescription": "删除后将无法恢复,请谨慎操作。",
2572-
"okText": "确认删除",
2579+
"deleteTitle": "确认删除合成任务",
2580+
"deleteDescription": "删除「{{name}}」后将无法恢复,确定要执行此操作吗?",
2581+
"okText": "删除",
25732582
"cancelText": "取消"
25742583
},
25752584
"messages": {

0 commit comments

Comments
 (0)