提交接口进度完善

This commit is contained in:
super
2026-04-03 00:53:52 +08:00
parent 99236c9feb
commit fba85ca365
7 changed files with 182 additions and 28 deletions

View File

@@ -12,6 +12,7 @@ zn_username=%E8%87%AA%E5%8A%A8%E5%8C%96_Robot
client_name=ShuFuAI
java_api_base=http://8.136.19.173:18080
java_api_base=http://127.0.0.1:18080
# java_api_base=http://8.136.19.173:18080

View File

@@ -17,4 +17,7 @@ public class DeleteBrandTaskDetailVo {
@Schema(description = "任务结果项快照")
private List<DeleteBrandResultItemVo> items = new ArrayList<>();
@Schema(description = "文件级进度快照")
private List<DeleteBrandTaskFileProgressVo> fileProgress = new ArrayList<>();
}

View File

@@ -0,0 +1,27 @@
package com.nanri.aiimage.modules.deletebrand.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "删除品牌任务中的文件级进度")
public class DeleteBrandTaskFileProgressVo {
@Schema(description = "文件标识(优先 fileKey")
private String fileKey;
@Schema(description = "源文件名")
private String sourceFilename;
@Schema(description = "已处理行数")
private Integer processedRows;
@Schema(description = "总行数")
private Integer totalRows;
@Schema(description = "百分比0-100")
private Integer percent;
@Schema(description = "文件状态PENDING/RUNNING/COMPLETED/FAILED")
private String status;
}

View File

@@ -25,6 +25,7 @@ import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandResultItemVo;
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo;
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDeletionStatusVo;
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDetailVo;
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskFileProgressVo;
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskItemVo;
import com.nanri.aiimage.modules.file.service.LocalFileStorageService;
import com.nanri.aiimage.modules.file.service.oss.OssStorageService;
@@ -569,9 +570,11 @@ public class DeleteBrandRunService {
DeleteBrandLineProgressVo progressVo = buildLineProgress(task.getId(), progress);
DeleteBrandTaskDetailVo detail = new DeleteBrandTaskDetailVo();
List<DeleteBrandResultItemVo> taskItems = resolveTaskResultItems(task);
detail.setTask(taskVo);
detail.setLine_progress(progressVo);
detail.setItems(resolveTaskResultItems(task));
detail.setItems(taskItems);
detail.setFileProgress(buildFileProgress(task, taskItems, progressVo));
return detail;
}
@@ -602,6 +605,73 @@ public class DeleteBrandRunService {
}
}
private List<DeleteBrandTaskFileProgressVo> buildFileProgress(FileTaskEntity task,
List<DeleteBrandResultItemVo> taskItems,
DeleteBrandLineProgressVo progressVo) {
if (taskItems == null || taskItems.isEmpty()) {
return List.of();
}
String taskStatus = task == null ? null : task.getStatus();
DeleteBrandLineProgressInfoVo info = progressVo == null ? null : progressVo.getInfo();
Integer finishedFiles = info == null ? null : info.getFinished_files();
String runningFileName = info == null ? null : info.getFile_name();
Integer currentLine = info == null ? null : info.getCurrent_line();
Integer totalLines = info == null ? null : info.getTotal_lines();
List<DeleteBrandTaskFileProgressVo> fileProgress = new ArrayList<>();
int completedAssigned = 0;
int normalizedFinished = finishedFiles == null ? 0 : Math.max(0, finishedFiles);
for (DeleteBrandResultItemVo item : taskItems) {
if (item == null) {
continue;
}
DeleteBrandTaskFileProgressVo fp = new DeleteBrandTaskFileProgressVo();
fp.setFileKey(item.getFileKey());
fp.setSourceFilename(item.getSourceFilename());
Integer itemTotalRows = item.getTotalRows() == null ? 0 : Math.max(item.getTotalRows(), 0);
fp.setTotalRows(itemTotalRows);
String status = "PENDING";
int processedRows = 0;
int percent = 0;
if ("SUCCESS".equals(taskStatus)) {
status = "COMPLETED";
processedRows = itemTotalRows;
percent = 100;
} else if ("FAILED".equals(taskStatus)) {
status = "FAILED";
processedRows = Math.min(itemTotalRows, Math.max(0, currentLine == null ? 0 : currentLine));
percent = itemTotalRows > 0 ? Math.min(100, (int) Math.round(processedRows * 100.0 / itemTotalRows)) : 0;
} else {
if (completedAssigned < normalizedFinished) {
status = "COMPLETED";
processedRows = itemTotalRows;
percent = 100;
completedAssigned++;
} else if (runningFileName != null && runningFileName.equals(item.getSourceFilename())) {
status = "RUNNING";
processedRows = Math.min(itemTotalRows, Math.max(0, currentLine == null ? 0 : currentLine));
if (totalLines != null && totalLines > 0) {
percent = Math.min(100, Math.max(0, (int) Math.round(processedRows * 100.0 / totalLines)));
} else if (itemTotalRows > 0) {
percent = Math.min(100, Math.max(0, (int) Math.round(processedRows * 100.0 / itemTotalRows)));
}
}
}
fp.setProcessedRows(processedRows);
fp.setPercent(percent);
fp.setStatus(status);
fileProgress.add(fp);
}
return fileProgress;
}
private FileTaskEntity refreshIndexedMatchesIfNeeded(FileTaskEntity task) {
if (task == null || task.getResultJson() == null || task.getResultJson().isBlank()) {
return task;

View File

@@ -115,13 +115,13 @@
<div v-if="shouldShowProgress(item)" class="delete-brand-progress-block">
<div class="delete-brand-progress-header">
<span>任务进度</span>
<span>{{ formatProgressPercent(item.taskId!) }}%</span>
<span>{{ formatProgressPercent(item) }}%</span>
</div>
<div class="delete-brand-progress-bar">
<div class="delete-brand-progress-bar-fill"
:style="{ width: `${formatProgressPercent(item.taskId!)}%` }"></div>
:style="{ width: `${formatProgressPercent(item)}%` }"></div>
</div>
<div class="files delete-brand-progress">{{ formatProgress(item.taskId!) }}</div>
<div class="files delete-brand-progress">{{ formatProgress(item) }}</div>
</div>
<div v-if="item.previewRows?.length" class="files split-entry-list delete-brand-preview">
{{ formatPreview(item.previewRows) }}
@@ -167,13 +167,13 @@
<div v-if="shouldShowProgress(item)" class="delete-brand-progress-block">
<div class="delete-brand-progress-header">
<span>任务进度</span>
<span>{{ formatProgressPercent(item.taskId!) }}%</span>
<span>{{ formatProgressPercent(item) }}%</span>
</div>
<div class="delete-brand-progress-bar">
<div class="delete-brand-progress-bar-fill"
:style="{ width: `${formatProgressPercent(item.taskId!)}%` }"></div>
:style="{ width: `${formatProgressPercent(item)}%` }"></div>
</div>
<div class="files delete-brand-progress">{{ formatProgress(item.taskId!) }}</div>
<div class="files delete-brand-progress">{{ formatProgress(item) }}</div>
</div>
<div v-if="item.previewRows?.length" class="files split-entry-list delete-brand-preview">
{{ formatPreview(item.previewRows) }}
@@ -421,25 +421,30 @@ function shouldShowProgress(item: DeleteBrandResultItem) {
if (!taskId) return false
const progress = taskDetails.value[taskId]?.line_progress
const status = getTaskStatus(taskId)
const multiFileTask = isMultiFileTask(taskId)
const qs = getQueueStatus(item)
// 已完成/终态后隐藏进度条。
if (qs === '本文件解析完毕' || qs === '已被全局判定为终态') {
return false
}
if (multiFileTask) {
const sessionItem = findSessionItem(taskId, item)
// 多文件任务:未入队的文件不展示同一个 task 的公共进度,避免“两个文件同时在跑”的错觉。
if (!sessionItem?._pushed) {
const isActiveItem = getItemKey(item) === activeItemKey.value
const isProgressTarget = Boolean(progress?.has_progress && progress?.info?.file_name === item.sourceFilename)
// 多文件任务下,展示规则更宽松:
// 1) 当前活跃文件2) 后端进度指向的文件3) 已推送文件4) 已完成文件(任务未终态时保留)。
if (isActiveItem || isProgressTarget || sessionItem?._pushed) {
return true
}
if ((status === 'RUNNING' || status === 'PENDING' || !status) && sessionItem?._completed) {
return true
}
return false
}
if (progress?.has_progress && progress?.info?.file_name) {
return progress.info.file_name === item.sourceFilename
}
return true
// 单文件任务:完成/终态后隐藏。
if (qs === '本文件解析完毕' || qs === '已被全局判定为终态') {
return false
}
if (progress?.has_progress || progress?.info) {
@@ -507,10 +512,40 @@ function updateSummary(items: DeleteBrandResultItem[]) {
}
}
function formatProgress(taskId: number) {
function getFileProgress(item: DeleteBrandResultItem) {
const taskId = item.taskId
if (!taskId) return null
const detail = taskDetails.value[taskId]
const fileProgress = detail?.fileProgress || []
return fileProgress.find((fp) => {
if (fp.fileKey && item.fileKey) return fp.fileKey === item.fileKey
if (fp.sourceFilename && item.sourceFilename) return fp.sourceFilename === item.sourceFilename
return false
}) || null
}
function formatProgress(item: DeleteBrandResultItem) {
const taskId = item.taskId
if (!taskId) return ''
const detail = taskDetails.value[taskId]
const info = detail?.line_progress?.info
const fp = getFileProgress(item)
if (fp) {
const parts: string[] = []
const processedRows = fp.processedRows ?? 0
const totalRows = fp.totalRows ?? 0
if (fp.status) parts.push(`文件状态:${fp.status}`)
parts.push(`文件进度:${processedRows}/${totalRows}`)
if (info?.finished_files != null) {
const total = info.file_total && info.file_total > 0 ? `/${info.file_total}` : ''
parts.push(`已完成文件:${info.finished_files}${total}`)
}
if (info?.phase) parts.push(`阶段:${info.phase}`)
return parts.join('')
}
if (!detail?.line_progress?.has_progress || !info) {
const status = detail?.task?.status
return status ? `任务状态:${status}` : ''
@@ -532,8 +567,15 @@ function formatProgress(taskId: number) {
return parts.join('')
}
function formatProgressPercent(taskId: number) {
function formatProgressPercent(item: DeleteBrandResultItem) {
const taskId = item.taskId
if (!taskId) return 0
const fp = getFileProgress(item)
if (fp) {
return Math.max(0, Math.min(100, fp.percent ?? 0))
}
const info = taskDetails.value[taskId]?.line_progress?.info
if (!info) return 0
@@ -578,20 +620,21 @@ function getQueueStatus(item: DeleteBrandResultItem) {
return '已被全局判定为终态'
}
// 多文件:未推送的文件必须保持“未入队”,避免被同 task 的公共 RUNNING 状态误判成“处理中”。
if (multiFileTask && !sessionItem?._pushed && !sessionItem?._completed) {
return '未入队'
}
// 先看前端会话态:一旦该文件已判定完成,优先展示完成,避免被 RUNNING 覆盖导致链式推进卡住。
if (sessionItem?._completed) {
return '本文件解析完毕'
}
// 多文件场景下,如果后端进度明确指向当前文件,即使本地 _pushed 丢失也应判定为处理中(例如刷新页面后恢复场景)。
if (status === 'RUNNING' && info?.has_progress && info?.info?.file_name === item.sourceFilename) {
return '处理中'
}
// 多文件:未推送且未完成,才视为未入队。
if (multiFileTask && !sessionItem?._pushed && !sessionItem?._completed) {
return '未入队'
}
if (multiFileTask && status === 'SUCCESS') {
return '本文件解析完毕'
}

View File

@@ -200,10 +200,20 @@ export interface DeleteBrandTaskItem {
downloadFilename?: string
}
export interface DeleteBrandTaskFileProgress {
fileKey?: string
sourceFilename?: string
processedRows?: number
totalRows?: number
percent?: number
status?: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED'
}
export interface DeleteBrandTaskDetailVo {
task: DeleteBrandTaskItem
line_progress: DeleteBrandLineProgress
items?: DeleteBrandResultItem[]
fileProgress?: DeleteBrandTaskFileProgress[]
}
export interface DeleteBrandTaskBatchVo {