完善多店铺开启多窗口、外观进度条等

This commit is contained in:
super
2026-05-01 21:09:54 +08:00
parent 70658041a5
commit aece8123c3
25 changed files with 945 additions and 183 deletions
@@ -20,6 +20,10 @@ public class AppearancePatentHistoryItemVo {
private String fileStatus;
private String fileError;
private Boolean fileReady;
private Integer fileProgressPercent;
private Integer fileProgressCurrent;
private Integer fileProgressTotal;
private String fileProgressMessage;
@Schema(description = "任务状态:PENDING=已解析待推送,RUNNING=执行中,SUCCESS=成功,FAILED=失败。", example = "SUCCESS")
private String taskStatus;
@Schema(description = "结果是否成功。true 表示任务完成并生成结果文件;false 表示失败或未完成。", example = "true")
@@ -34,8 +34,10 @@ import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
import com.nanri.aiimage.modules.task.model.entity.TaskFileJobEntity;
import com.nanri.aiimage.modules.task.model.entity.TaskChunkEntity;
import com.nanri.aiimage.modules.task.model.entity.TaskProgressSnapshotEntity;
import com.nanri.aiimage.modules.task.model.entity.TaskScopeStateEntity;
import com.nanri.aiimage.modules.task.service.TaskFileJobService;
import com.nanri.aiimage.modules.task.service.TaskProgressSnapshotService;
import com.nanri.aiimage.modules.task.service.TransientPayloadStorageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -59,6 +61,7 @@ import org.springframework.transaction.support.TransactionTemplate;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -106,6 +109,7 @@ public class AppearancePatentTaskService {
private final AppearancePatentTaskCacheService taskCacheService;
private final AppearancePatentProperties properties;
private final TaskFileJobService taskFileJobService;
private final TaskProgressSnapshotService taskProgressSnapshotService;
private final TransientPayloadStorageService transientPayloadStorageService;
private final PlatformTransactionManager transactionManager;
@@ -678,10 +682,10 @@ public class AppearancePatentTaskService {
int batchSize = Math.max(1, properties.getCozeBatchSize());
List<AppearancePatentResultRowDto> result = new ArrayList<>();
for (int i = 0; i < items.size(); i += batchSize) {
result.addAll(cozeClient.inspect(items.subList(i, Math.min(i + batchSize, items.size())), prompt));
if (progressHook != null) {
progressHook.run();
}
result.addAll(cozeClient.inspect(items.subList(i, Math.min(i + batchSize, items.size())), prompt));
}
return result;
}
@@ -697,9 +701,6 @@ public class AppearancePatentTaskService {
.orderByAsc(TaskChunkEntity::getChunkIndex));
log.info("[appearance-patent] async coze start taskId={} chunks={}", task.getId(), chunks.size());
for (TaskChunkEntity chunk : chunks) {
if (progressHook != null) {
progressHook.run();
}
Map<String, AppearancePatentResultRowDto> persistedRows = readChunkRows(chunk);
if (persistedRows.isEmpty()) {
continue;
@@ -1021,12 +1022,68 @@ public class AppearancePatentTaskService {
if (result == null || !MODULE_TYPE.equals(result.getModuleType())) {
throw new BusinessException("结果记录不存在");
}
Runnable progressHook = () -> taskFileJobService.touchRunning(job.getId());
List<TaskChunkEntity> chunks = taskChunkMapper.selectList(new LambdaQueryWrapper<TaskChunkEntity>()
.eq(TaskChunkEntity::getTaskId, task.getId())
.eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
.orderByAsc(TaskChunkEntity::getChunkIndex));
int cozeWorkUnits = countCozeWorkUnits(chunks, Math.max(1, properties.getCozeBatchSize()));
int totalProgressUnits = Math.max(3, cozeWorkUnits + 3);
int[] completedProgressUnits = {0};
saveFileBuildProgress(task, job, totalProgressUnits, completedProgressUnits[0], "正在请求 Coze");
Runnable progressHook = () -> {
taskFileJobService.touchRunning(job.getId());
completedProgressUnits[0] = Math.min(totalProgressUnits - 2, completedProgressUnits[0] + 1);
saveFileBuildProgress(task, job, totalProgressUnits, completedProgressUnits[0], "正在请求 Coze");
};
applyCozeToPersistedChunks(task, progressHook);
progressHook.run();
completedProgressUnits[0] = Math.max(completedProgressUnits[0], totalProgressUnits - 2);
saveFileBuildProgress(task, job, totalProgressUnits, completedProgressUnits[0], "正在组装 xlsx");
assembleResultWorkbook(task, result);
progressHook.run();
completedProgressUnits[0] = totalProgressUnits - 1;
saveFileBuildProgress(task, job, totalProgressUnits, completedProgressUnits[0], "正在上传结果文件");
fileResultMapper.updateById(result);
saveFileBuildProgress(task, job, totalProgressUnits, totalProgressUnits, "结果文件已生成");
}
private int countCozeWorkUnits(List<TaskChunkEntity> chunks, int batchSize) {
if (chunks == null || chunks.isEmpty()) {
return 0;
}
int total = 0;
for (TaskChunkEntity chunk : chunks) {
Map<String, AppearancePatentResultRowDto> persistedRows = readChunkRows(chunk);
if (persistedRows.isEmpty()) {
continue;
}
int unresolved = pickGroupRepresentativesForCoze(persistedRows.values()).size();
if (unresolved > 0) {
total += Math.max(1, (unresolved + batchSize - 1) / batchSize);
}
}
return total;
}
private void saveFileBuildProgress(FileTaskEntity task,
TaskFileJobEntity job,
int total,
int completed,
String message) {
if (task == null || task.getId() == null || job == null || job.getId() == null) {
return;
}
int safeTotal = Math.max(1, total);
int safeCompleted = Math.max(0, Math.min(completed, safeTotal));
taskProgressSnapshotService.save(
task.getId(),
MODULE_TYPE,
STATUS_RUNNING,
safeTotal,
safeCompleted,
0,
job.getScopeKey(),
message,
Map.of("phase", "RESULT_FILE", "jobId", job.getId())
);
}
public void cleanupResultFileJob(TaskFileJobEntity job) {
@@ -1045,6 +1102,14 @@ public class AppearancePatentTaskService {
private void assembleResultWorkbook(FileTaskEntity task, FileResultEntity result) {
AppearancePatentParsedPayloadDto parsed = readParsedPayload(task);
Map<String, AppearancePatentResultRowDto> resultMap = loadPersistedResultRows(task.getId());
long resolvedRows = parsed.getAllItems().stream()
.filter(row -> findResultRow(row, resultMap) != null || findResultRowByAsin(row.getAsin(), resultMap) != null)
.count();
long reasonRows = resultMap.values().stream()
.filter(this::hasReasonFields)
.count();
log.info("[appearance-patent] assemble workbook taskId={} parsedRows={} resultRows={} resolvedRows={} reasonRows={}",
task.getId(), parsed.getAllItems().size(), resultMap.size(), resolvedRows, reasonRows);
File outputDir = new File(storageProperties.getLocalTempDir(), "appearance-patent-result");
if (!outputDir.exists() && !outputDir.mkdirs()) {
throw new BusinessException("创建结果目录失败");
@@ -1087,8 +1152,8 @@ public class AppearancePatentTaskService {
headerStyle.setFont(font);
List<String> resultHeaders = new ArrayList<>(RESULT_HEADERS);
resultHeaders.add(4, "title");
resultHeaders.add(5, "url");
resultHeaders.add(4, "标题");
resultHeaders.add(5, "图片链接");
Row header = sheet.createRow(0);
for (int i = 0; i < resultHeaders.size(); i++) {
Cell cell = header.createCell(i);
@@ -1099,6 +1164,9 @@ public class AppearancePatentTaskService {
int rowIndex = 1;
for (AppearancePatentParsedRowVo parsedRow : parsed.getAllItems()) {
AppearancePatentResultRowDto resultRow = findResultRow(parsedRow, resultMap);
if (resultRow == null) {
resultRow = findResultRowByAsin(parsedRow.getAsin(), resultMap);
}
String missingReason = "";
if (resultRow == null) {
missingReason = hasPromptFields(parsedRow) ? "未匹配到检测结果" : "未送检:缺少标题或图片";
@@ -1162,12 +1230,28 @@ public class AppearancePatentTaskService {
if (normalizedAsin.isBlank() || resultMap == null || resultMap.isEmpty()) {
return null;
}
AppearancePatentResultRowDto fallback = null;
for (AppearancePatentResultRowDto row : resultMap.values()) {
if (row != null && normalizedAsin.equals(normalize(row.getAsin()).toUpperCase(Locale.ROOT))) {
if (row == null || !normalizedAsin.equals(normalize(row.getAsin()).toUpperCase(Locale.ROOT))) {
continue;
}
if (hasResolvedCozeFields(row) || hasReasonFields(row)) {
return row;
}
if (fallback == null) {
fallback = row;
}
}
return null;
return fallback;
}
private boolean hasReasonFields(AppearancePatentResultRowDto row) {
if (row == null) {
return false;
}
return !normalize(row.getAppearanceReason()).isBlank()
|| !normalize(row.getPatentReason()).isBlank()
|| !normalize(row.getTitleReason()).isBlank();
}
private ParsedWorkbook parseWorkbook(File input, AppearancePatentSourceFileDto source) {
@@ -1468,11 +1552,50 @@ public class AppearancePatentTaskService {
vo.setFileReady(row.getResultFileUrl() != null && !row.getResultFileUrl().isBlank());
if (job == null) {
vo.setFileStatus(Boolean.TRUE.equals(vo.getFileReady()) ? "SUCCESS" : null);
attachFileProgress(vo, row, job);
return;
}
vo.setFileJobId(job.getId());
vo.setFileStatus(job.getStatus());
vo.setFileError(job.getErrorMessage());
attachFileProgress(vo, row, job);
}
private void attachFileProgress(AppearancePatentHistoryItemVo vo, FileResultEntity row, TaskFileJobEntity job) {
if (row == null || row.getTaskId() == null) {
return;
}
if (Boolean.TRUE.equals(vo.getFileReady())) {
vo.setFileProgressCurrent(1);
vo.setFileProgressTotal(1);
vo.setFileProgressPercent(100);
vo.setFileProgressMessage("结果文件已生成");
return;
}
TaskProgressSnapshotEntity snapshot = taskProgressSnapshotService.find(row.getTaskId(), MODULE_TYPE);
if (snapshot == null) {
return;
}
int total = snapshot.getTotalCount() == null ? 0 : snapshot.getTotalCount();
int current = snapshot.getSuccessCount() == null ? 0 : snapshot.getSuccessCount();
if (total <= 0) {
return;
}
current = Math.max(0, Math.min(current, total));
int percent = Math.max(1, Math.min(99, (int) Math.floor(current * 100.0 / total)));
if (job != null && STATUS_RUNNING.equals(job.getStatus())) {
LocalDateTime baseTime = snapshot.getUpdatedAt() != null ? snapshot.getUpdatedAt() : job.getUpdatedAt();
long elapsedSeconds = baseTime == null ? 0 : Math.max(0, Duration.between(baseTime, LocalDateTime.now()).getSeconds());
if (current <= 0) {
percent = Math.max(percent, Math.min(35, 8 + (int) (elapsedSeconds / 6)));
} else if (current < total) {
percent = Math.max(percent, Math.min(92, percent + (int) (elapsedSeconds / 10)));
}
}
vo.setFileProgressCurrent(current);
vo.setFileProgressTotal(total);
vo.setFileProgressPercent(percent);
vo.setFileProgressMessage(snapshot.getMessage());
}
private String fmt(LocalDateTime t) {