feat(aihr): support cancelling running local import tasks
新增 POST /api/knowledge/doc/import-tasks/{id}/cancel,后台目录导入任务
运行中可取消;资料处理页任务列表接取消按钮与状态刷新。
This commit is contained in:
+6
@@ -13,6 +13,7 @@ import org.dromara.aihr.domain.AihrSopDto.VectorizeResponse;
|
|||||||
import org.dromara.aihr.service.AihrSopSeedService;
|
import org.dromara.aihr.service.AihrSopSeedService;
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
@@ -73,6 +74,11 @@ public class AihrSopController {
|
|||||||
return R.ok(sopSeedService.localImportTasks());
|
return R.ok(sopSeedService.localImportTasks());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/doc/import-tasks/{id}/cancel")
|
||||||
|
public R<LocalImportTaskResponse> cancelLocalImportTask(@PathVariable Long id) {
|
||||||
|
return R.ok(sopSeedService.cancelLocalImportTask(id));
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/processing/overview")
|
@GetMapping("/processing/overview")
|
||||||
public R<ProcessingOverviewResponse> processingOverview() {
|
public R<ProcessingOverviewResponse> processingOverview() {
|
||||||
return R.ok(sopSeedService.processingOverview());
|
return R.ok(sopSeedService.processingOverview());
|
||||||
|
|||||||
+40
-2
@@ -253,6 +253,18 @@ public class AihrSopSeedService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalImportTaskResponse cancelLocalImportTask(Long taskId) {
|
||||||
|
if (taskId == null) {
|
||||||
|
throw new IllegalArgumentException("任务 ID 不能为空");
|
||||||
|
}
|
||||||
|
jdbcTemplate.update("""
|
||||||
|
update aihr_knowledge_import_task
|
||||||
|
set status = 4, current_file = '', finished_time = now(), update_time = now(), remark = '用户已取消'
|
||||||
|
where tenant_id = ? and id = ? and status = 1
|
||||||
|
""", TENANT_ID, taskId);
|
||||||
|
return localImportTask(taskId).orElseThrow(() -> new IllegalArgumentException("导入任务不存在"));
|
||||||
|
}
|
||||||
|
|
||||||
private void runLocalImportTask(long taskId, LocalImportPlan plan) {
|
private void runLocalImportTask(long taskId, LocalImportPlan plan) {
|
||||||
try {
|
try {
|
||||||
if (plan.files().isEmpty()) {
|
if (plan.files().isEmpty()) {
|
||||||
@@ -260,10 +272,16 @@ public class AihrSopSeedService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LocalImportRun run = executeLocalImport(plan, taskId);
|
LocalImportRun run = executeLocalImport(plan, taskId);
|
||||||
|
if (isLocalImportTaskCanceled(taskId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
boolean allFailed = run.failed() > 0 && run.imported() == 0;
|
boolean allFailed = run.failed() > 0 && run.imported() == 0;
|
||||||
String remark = run.failed() == 0 ? "导入完成" : allFailed ? "全部文件导入失败,可重试同一目录" : "部分文件导入失败,可重试同一目录";
|
String remark = run.failed() == 0 ? "导入完成" : allFailed ? "全部文件导入失败,可重试同一目录" : "部分文件导入失败,可重试同一目录";
|
||||||
finishLocalImportTask(taskId, allFailed ? 3 : 2, run.imported(), run.failed(), remark);
|
finishLocalImportTask(taskId, allFailed ? 3 : 2, run.imported(), run.failed(), remark);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
if (isLocalImportTaskCanceled(taskId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
finishLocalImportTask(taskId, 3, 0, 0, e.getMessage());
|
finishLocalImportTask(taskId, 3, 0, 0, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,6 +291,9 @@ public class AihrSopSeedService {
|
|||||||
int imported = 0;
|
int imported = 0;
|
||||||
int failed = 0;
|
int failed = 0;
|
||||||
for (Path path : plan.files()) {
|
for (Path path : plan.files()) {
|
||||||
|
if (taskId != null && isLocalImportTaskCanceled(taskId)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
String fileName = path.getFileName().toString();
|
String fileName = path.getFileName().toString();
|
||||||
if (taskId != null) {
|
if (taskId != null) {
|
||||||
updateLocalImportTask(taskId, 1, imported, failed, fileName, "处理中");
|
updateLocalImportTask(taskId, 1, imported, failed, fileName, "处理中");
|
||||||
@@ -375,7 +396,7 @@ public class AihrSopSeedService {
|
|||||||
jdbcTemplate.update("""
|
jdbcTemplate.update("""
|
||||||
update aihr_knowledge_import_task
|
update aihr_knowledge_import_task
|
||||||
set status = ?, imported_files = ?, failed_files = ?, current_file = ?, update_time = now(), remark = ?
|
set status = ?, imported_files = ?, failed_files = ?, current_file = ?, update_time = now(), remark = ?
|
||||||
where tenant_id = ? and id = ?
|
where tenant_id = ? and id = ? and status <> 4
|
||||||
""", status, imported, failed, currentFile, limitRemark(remark), TENANT_ID, taskId);
|
""", status, imported, failed, currentFile, limitRemark(remark), TENANT_ID, taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,10 +404,23 @@ public class AihrSopSeedService {
|
|||||||
jdbcTemplate.update("""
|
jdbcTemplate.update("""
|
||||||
update aihr_knowledge_import_task
|
update aihr_knowledge_import_task
|
||||||
set status = ?, imported_files = ?, failed_files = ?, current_file = '', finished_time = now(), update_time = now(), remark = ?
|
set status = ?, imported_files = ?, failed_files = ?, current_file = '', finished_time = now(), update_time = now(), remark = ?
|
||||||
where tenant_id = ? and id = ?
|
where tenant_id = ? and id = ? and status <> 4
|
||||||
""", status, imported, failed, limitRemark(remark), TENANT_ID, taskId);
|
""", status, imported, failed, limitRemark(remark), TENANT_ID, taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isLocalImportTaskCanceled(long taskId) {
|
||||||
|
try {
|
||||||
|
Integer count = jdbcTemplate.queryForObject("""
|
||||||
|
select count(1)
|
||||||
|
from aihr_knowledge_import_task
|
||||||
|
where tenant_id = ? and id = ? and status = 4
|
||||||
|
""", Integer.class, TENANT_ID, taskId);
|
||||||
|
return count != null && count > 0;
|
||||||
|
} catch (DataAccessException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private LocalImportTaskResponse importTaskResponse(long id, String directory, String category, int status, int total, int imported, int failed, String currentFile, Timestamp createTime, Timestamp updateTime, String remark) {
|
private LocalImportTaskResponse importTaskResponse(long id, String directory, String category, int status, int total, int imported, int failed, String currentFile, Timestamp createTime, Timestamp updateTime, String remark) {
|
||||||
return new LocalImportTaskResponse(
|
return new LocalImportTaskResponse(
|
||||||
id,
|
id,
|
||||||
@@ -1121,6 +1155,7 @@ public class AihrSopSeedService {
|
|||||||
case 1 -> "导入中";
|
case 1 -> "导入中";
|
||||||
case 2 -> failed > 0 ? "部分失败" : "已完成";
|
case 2 -> failed > 0 ? "部分失败" : "已完成";
|
||||||
case 3 -> "失败";
|
case 3 -> "失败";
|
||||||
|
case 4 -> "已取消";
|
||||||
default -> "等待中";
|
default -> "等待中";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1132,6 +1167,9 @@ public class AihrSopSeedService {
|
|||||||
if (status == 2 && failed == 0) {
|
if (status == 2 && failed == 0) {
|
||||||
return "success";
|
return "success";
|
||||||
}
|
}
|
||||||
|
if (status == 4) {
|
||||||
|
return "info";
|
||||||
|
}
|
||||||
return status == 3 ? "danger" : "warning";
|
return status == 3 ? "danger" : "warning";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ CREATE TABLE IF NOT EXISTS `aihr_knowledge_import_task` (
|
|||||||
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
|
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
|
||||||
`directory` varchar(500) NOT NULL COMMENT '导入相对目录',
|
`directory` varchar(500) NOT NULL COMMENT '导入相对目录',
|
||||||
`category` varchar(100) NOT NULL COMMENT '知识库分类',
|
`category` varchar(100) NOT NULL COMMENT '知识库分类',
|
||||||
`status` tinyint DEFAULT 0 COMMENT '任务状态: 0等待, 1运行中, 2完成, 3失败',
|
`status` tinyint DEFAULT 0 COMMENT '任务状态: 0等待, 1运行中, 2完成, 3失败, 4取消',
|
||||||
`total_files` int DEFAULT 0 COMMENT '发现文件数',
|
`total_files` int DEFAULT 0 COMMENT '发现文件数',
|
||||||
`imported_files` int DEFAULT 0 COMMENT '已导入文件数',
|
`imported_files` int DEFAULT 0 COMMENT '已导入文件数',
|
||||||
`failed_files` int DEFAULT 0 COMMENT '失败文件数',
|
`failed_files` int DEFAULT 0 COMMENT '失败文件数',
|
||||||
|
|||||||
@@ -135,3 +135,10 @@ export function listLocalKnowledgeImportTasks(): Promise<ApiResult<LocalImportTa
|
|||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cancelLocalKnowledgeImportTask(id: number): Promise<ApiResult<LocalImportTask>> {
|
||||||
|
return request({
|
||||||
|
url: `/api/knowledge/doc/import-tasks/${id}/cancel`,
|
||||||
|
method: 'post'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export function uploadKnowledgeDoc(data: FormData): Promise<ApiResult<SopUploadR
|
|||||||
url: '/api/knowledge/doc/upload',
|
url: '/api/knowledge/doc/upload',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data,
|
data,
|
||||||
|
timeout: 180000,
|
||||||
headers: { repeatSubmit: false }
|
headers: { repeatSubmit: false }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,8 +193,9 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="currentFile" label="当前文件" min-width="220" show-overflow-tooltip />
|
<el-table-column prop="currentFile" label="当前文件" min-width="220" show-overflow-tooltip />
|
||||||
<el-table-column prop="updatedAt" label="更新时间" width="138" />
|
<el-table-column prop="updatedAt" label="更新时间" width="138" />
|
||||||
<el-table-column label="操作" width="84">
|
<el-table-column label="操作" width="104">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<el-button v-if="row.status === '导入中'" link type="danger" @click="cancelImportTask(row)">取消</el-button>
|
||||||
<el-button v-if="row.status !== '导入中'" link type="primary" @click="retryImportTask(row)">重试</el-button>
|
<el-button v-if="row.status !== '导入中'" link type="primary" @click="retryImportTask(row)">重试</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -237,6 +238,7 @@ import {
|
|||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||||
import {
|
import {
|
||||||
|
cancelLocalKnowledgeImportTask,
|
||||||
getProcessingOverview,
|
getProcessingOverview,
|
||||||
listLocalKnowledgeImportTasks,
|
listLocalKnowledgeImportTasks,
|
||||||
startLocalKnowledgeImportTask,
|
startLocalKnowledgeImportTask,
|
||||||
@@ -474,6 +476,26 @@ async function retryImportTask(row: LocalImportTask) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function cancelImportTask(row: LocalImportTask) {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确认取消导入任务「${row.directory}」?`, '取消导入', {
|
||||||
|
confirmButtonText: '取消任务',
|
||||||
|
cancelButtonText: '返回',
|
||||||
|
type: 'warning'
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
importTaskLoading.value = true;
|
||||||
|
try {
|
||||||
|
await cancelLocalKnowledgeImportTask(row.id);
|
||||||
|
ElMessage.success('已取消导入任务');
|
||||||
|
await Promise.all([loadImportTasks(), loadOverview()]);
|
||||||
|
} finally {
|
||||||
|
importTaskLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function scheduleImportPoll() {
|
function scheduleImportPoll() {
|
||||||
clearImportPoll();
|
clearImportPoll();
|
||||||
if (!runningImport.value) return;
|
if (!runningImport.value) return;
|
||||||
@@ -928,6 +950,11 @@ function fileBadgeClass(type: string) {
|
|||||||
background: #ffe8ea;
|
background: #ffe8ea;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pill-info {
|
||||||
|
color: #657184;
|
||||||
|
background: #eef2f7;
|
||||||
|
}
|
||||||
|
|
||||||
.table-footer {
|
.table-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
Reference in New Issue
Block a user