diff --git a/backend/src/main/java/com/imeeting/controller/android/AndroidMeetingController.java b/backend/src/main/java/com/imeeting/controller/android/AndroidMeetingController.java index 1ca7c39..1e17951 100644 --- a/backend/src/main/java/com/imeeting/controller/android/AndroidMeetingController.java +++ b/backend/src/main/java/com/imeeting/controller/android/AndroidMeetingController.java @@ -26,6 +26,7 @@ import com.imeeting.dto.biz.UnifiedMeetingStatusVO; import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.PromptTemplate; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.service.android.AndroidAuthService; import com.imeeting.service.android.AndroidChunkUploadService; import com.imeeting.service.android.legacy.LegacyMeetingAdapterService; @@ -454,7 +455,7 @@ public class AndroidMeetingController { AiTask asrTask = findLatestTask(meetingId, "ASR"); AiTask summaryTask = findLatestTask(meetingId, "SUMMARY"); boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus()); - MeetingVO detail = (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) + MeetingVO detail = (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED) || summaryCompleted) ? meetingQueryService.getDetail(meetingId) : null; boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank(); @@ -736,7 +737,11 @@ public class AndroidMeetingController { } return meetingService.getOne(new LambdaQueryWrapper() .eq(Meeting::getSourceDeviceCode, deviceId) - .in(Meeting::getStatus, 0, 1, 2) + .in(Meeting::getStatus, MeetingStatusEnum.codesOf( + MeetingStatusEnum.INITIALIZING, + MeetingStatusEnum.TRANSCRIBING, + MeetingStatusEnum.SUMMARIZING + )) .orderByDesc(Meeting::getId) .last("LIMIT 1")); } diff --git a/backend/src/main/java/com/imeeting/controller/android/AndroidPublicMeetingController.java b/backend/src/main/java/com/imeeting/controller/android/AndroidPublicMeetingController.java index bffd5b2..75e54ee 100644 --- a/backend/src/main/java/com/imeeting/controller/android/AndroidPublicMeetingController.java +++ b/backend/src/main/java/com/imeeting/controller/android/AndroidPublicMeetingController.java @@ -6,6 +6,7 @@ import com.imeeting.dto.android.AndroidPublicMeetingSessionResultVO; import com.imeeting.dto.android.AndroidPublicMeetingSessionRequest; import com.imeeting.entity.biz.AndroidPushMessage; import com.imeeting.entity.biz.Meeting; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.enums.MeetingPushTypeEnum; import com.imeeting.mapper.DeviceInfoMapper; import com.imeeting.service.android.AndroidAuthService; @@ -113,7 +114,8 @@ public class AndroidPublicMeetingController { if (meeting.getSourceDeviceCode() == null || !meeting.getSourceDeviceCode().equals(authContext.getDeviceId())) { throw new RuntimeException("当前会议不属于该设备"); } - if (meeting.getStatus() != null && meeting.getStatus() > 2) { + if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED) + || MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.FAILED)) { throw new RuntimeException("当前会议状态不允许删除"); } meetingCommandService.deleteMeeting(meetingId); diff --git a/backend/src/main/java/com/imeeting/controller/android/legacy/LegacyMeetingController.java b/backend/src/main/java/com/imeeting/controller/android/legacy/LegacyMeetingController.java index aebd366..0a5aa9a 100644 --- a/backend/src/main/java/com/imeeting/controller/android/legacy/LegacyMeetingController.java +++ b/backend/src/main/java/com/imeeting/controller/android/legacy/LegacyMeetingController.java @@ -21,6 +21,7 @@ import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.Meeting; import com.imeeting.mapper.biz.MeetingTranscriptMapper; import com.imeeting.entity.biz.PromptTemplate; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.service.android.legacy.LegacyMeetingAdapterService; import com.imeeting.support.AndroidRequestLogHelper; import com.imeeting.service.biz.AiTaskService; @@ -256,7 +257,7 @@ public class LegacyMeetingController { AiTask asrTask = findLatestTask(meetingId, "ASR"); AiTask summaryTask = findLatestTask(meetingId, "SUMMARY"); boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus()); - MeetingVO detail = (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) + MeetingVO detail = (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED) || summaryCompleted) ? meetingQueryService.getDetail(meetingId) : null; boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank(); @@ -428,7 +429,7 @@ public class LegacyMeetingController { AiTask summaryTask = findLatestTask(meetingId, "SUMMARY"); boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus()); - if (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) { + if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED) || summaryCompleted) { return new LegacyMeetingProcessingStatusResponse("completed", 100, STAGE_COMPLETED); } if (isFailed(asrTask)) { diff --git a/backend/src/main/java/com/imeeting/enums/MeetingStatusEnum.java b/backend/src/main/java/com/imeeting/enums/MeetingStatusEnum.java new file mode 100644 index 0000000..20ee645 --- /dev/null +++ b/backend/src/main/java/com/imeeting/enums/MeetingStatusEnum.java @@ -0,0 +1,54 @@ +package com.imeeting.enums; + +import java.util.Arrays; +import java.util.List; + +public enum MeetingStatusEnum { + INITIALIZING(0, "初始化/待处理"), + TRANSCRIBING(1, "转写中"), + SUMMARIZING(2, "总结中"), + COMPLETED(3, "已完成"), + FAILED(4, "失败"); + + private final int code; + private final String description; + + MeetingStatusEnum(int code, String description) { + this.code = code; + this.description = description; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public boolean isTerminal() { + return this == COMPLETED || this == FAILED; + } + + public boolean isUnfinished() { + return this == INITIALIZING || this == TRANSCRIBING || this == SUMMARIZING; + } + + public static MeetingStatusEnum fromCode(Integer code) { + if (code == null) { + return null; + } + return Arrays.stream(values()) + .filter(item -> item.code == code) + .findFirst() + .orElse(null); + } + + public static boolean isCode(Integer code, MeetingStatusEnum status) { + return code != null && status != null && code == status.code; + } + + public static List codesOf(MeetingStatusEnum... statuses) { + return Arrays.stream(statuses).map(MeetingStatusEnum::getCode).toList(); + } +} diff --git a/backend/src/main/java/com/imeeting/listener/MeetingTaskRecoveryListener.java b/backend/src/main/java/com/imeeting/listener/MeetingTaskRecoveryListener.java index d078214..fe1954f 100644 --- a/backend/src/main/java/com/imeeting/listener/MeetingTaskRecoveryListener.java +++ b/backend/src/main/java/com/imeeting/listener/MeetingTaskRecoveryListener.java @@ -2,6 +2,7 @@ package com.imeeting.listener; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.imeeting.entity.biz.Meeting; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.service.biz.AiTaskService; import com.imeeting.support.TaskSecurityContextRunner; @@ -33,7 +34,10 @@ public class MeetingTaskRecoveryListener implements ApplicationRunner { List pendingMeetings = taskSecurityContextRunner.callAsPlatformAdmin(() -> meetingMapper.selectList(new LambdaQueryWrapper() - .in(Meeting::getStatus, 1, 2) + .in(Meeting::getStatus, MeetingStatusEnum.codesOf( + MeetingStatusEnum.TRANSCRIBING, + MeetingStatusEnum.SUMMARIZING + )) .eq(Meeting::getIsDeleted, 0)) ); if (pendingMeetings.isEmpty()) { @@ -46,10 +50,10 @@ public class MeetingTaskRecoveryListener implements ApplicationRunner { meetingLockCache.clearDispatchLocks(meeting.getId()); meetingAsrPermitCache.clearRecoveryState(meeting.getId()); - if (Integer.valueOf(1).equals(meeting.getStatus())) { + if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.TRANSCRIBING)) { log.info("Recovering ASR task for meeting {}", meeting.getId()); aiTaskService.dispatchTasks(meeting.getId(), meeting.getTenantId(), meeting.getCreatorId()); - } else if (Integer.valueOf(2).equals(meeting.getStatus())) { + } else if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.SUMMARIZING)) { log.info("Recovering summary task for meeting {}", meeting.getId()); aiTaskService.dispatchSummaryTask(meeting.getId(), meeting.getTenantId(), meeting.getCreatorId()); } diff --git a/backend/src/main/java/com/imeeting/mapper/biz/MeetingMapper.java b/backend/src/main/java/com/imeeting/mapper/biz/MeetingMapper.java index ea57538..29992c6 100644 --- a/backend/src/main/java/com/imeeting/mapper/biz/MeetingMapper.java +++ b/backend/src/main/java/com/imeeting/mapper/biz/MeetingMapper.java @@ -39,6 +39,7 @@ public interface MeetingMapper extends BaseMapper { FROM biz_meetings WHERE tenant_id = #{tenantId} AND source_device_code = #{deviceCode} + AND status=3 AND is_deleted = 0 AND created_at >= #{resetAt} diff --git a/backend/src/main/java/com/imeeting/service/android/legacy/impl/LegacyMeetingAdapterServiceImpl.java b/backend/src/main/java/com/imeeting/service/android/legacy/impl/LegacyMeetingAdapterServiceImpl.java index b45ec47..af6d07f 100644 --- a/backend/src/main/java/com/imeeting/service/android/legacy/impl/LegacyMeetingAdapterServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/android/legacy/impl/LegacyMeetingAdapterServiceImpl.java @@ -12,6 +12,7 @@ import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.LlmModel; import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.MeetingTranscript; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.mapper.biz.LlmModelMapper; import com.imeeting.mapper.biz.MeetingTranscriptMapper; import com.imeeting.service.android.legacy.LegacyMeetingAdapterService; @@ -183,7 +184,7 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS); meeting.setAudioSaveMessage(null); meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED); - meeting.setStatus(1); + meeting.setStatus(MeetingStatusEnum.TRANSCRIBING.getCode()); meetingService.updateById(meeting); resetOrCreateAsrTask(meetingId, profile); @@ -252,7 +253,7 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS); meeting.setAudioSaveMessage(null); meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED); - meeting.setStatus(1); + meeting.setStatus(MeetingStatusEnum.TRANSCRIBING.getCode()); meetingService.updateById(meeting); resetOrCreateAsrTask(meetingId, profile); diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java index 5ef02cb..7877bec 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java @@ -14,6 +14,7 @@ import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.HotWord; import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.MeetingTranscript; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.mapper.biz.AiTaskMapper; import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.mapper.biz.MeetingTranscriptMapper; @@ -953,7 +954,7 @@ public class AiTaskServiceImpl extends ServiceImpl impleme Files.writeString(filePath, markdownContent, StandardCharsets.UTF_8); - boolean alreadyCompleted = Integer.valueOf(3).equals(meeting.getStatus()); + boolean alreadyCompleted = MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED); taskRecord.setResultFilePath("meetings/" + meeting.getId() + "/summaries/" + fileName); Map responseData = objectMapper.convertValue(respNode, Map.class); responseData.put("summarySource", summarySource.toSnapshot()); @@ -1206,22 +1207,22 @@ public class AiTaskServiceImpl extends ServiceImpl impleme int meetingStatus; if (percent < 0) { stage = MeetingProgressStage.FAILED; - meetingStatus = 4; + meetingStatus = MeetingStatusEnum.FAILED.getCode(); } else if (percent >= 100) { stage = MeetingProgressStage.COMPLETED; - meetingStatus = 3; + meetingStatus = MeetingStatusEnum.COMPLETED.getCode(); } else if (percent >= 90) { stage = MeetingProgressStage.SUMMARY_RUNNING; - meetingStatus = 2; + meetingStatus = MeetingStatusEnum.SUMMARIZING.getCode(); } else if (percent >= 85) { stage = MeetingProgressStage.CHAPTER_RUNNING; - meetingStatus = 2; + meetingStatus = MeetingStatusEnum.SUMMARIZING.getCode(); } else if (percent >= 5) { stage = MeetingProgressStage.ASR_RUNNING; - meetingStatus = 1; + meetingStatus = MeetingStatusEnum.TRANSCRIBING.getCode(); } else { stage = MeetingProgressStage.QUEUED; - meetingStatus = 1; + meetingStatus = MeetingStatusEnum.TRANSCRIBING.getCode(); } meetingProgressService.markStage(meetingId, findLatestTaskForProgress(meetingId), meetingStatus, stage, percent, msg, eta); } diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingCommandServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingCommandServiceImpl.java index 2e0bf17..60e76ed 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingCommandServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingCommandServiceImpl.java @@ -27,6 +27,7 @@ import com.imeeting.entity.biz.HotWord; import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.MeetingTranscript; import com.imeeting.entity.biz.MeetingTranscriptChapterVersion; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.service.android.AndroidMeetingPushService; import com.imeeting.service.android.AndroidPendingMeetingDraftService; import com.imeeting.service.android.AndroidPushMessageService; @@ -485,7 +486,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { } realtimeMeetingSessionStateService.clear(meetingId); - meeting.setStatus(2); + meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode()); meetingService.updateById(meeting); updateMeetingProgress(meetingId, 85, "正在生成 AI 目录与总结...", 0); meetingDomainSupport.prewarmPlaybackAudioAfterCommit(meeting.getAudioUrl()); @@ -824,7 +825,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { "RESUMMARY" ); meeting.setLatestSummaryTaskId(createdSummaryTask.getId()); - meeting.setStatus(2); + meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode()); meetingService.updateById(meeting); aiTaskService.dispatchSummaryTask(meeting.getId(), meeting.getTenantId(), meeting.getCreatorId()); @@ -885,7 +886,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { aiTaskService.updateById(summaryTask); meetingPointsService.recordSummarySuccessCharge(meeting, summaryTask); - boolean alreadyCompleted = Integer.valueOf(3).equals(meeting.getStatus()); + boolean alreadyCompleted = MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED); meeting.setLatestSummaryTaskId(summaryTask.getId()); meetingService.updateById(meeting); aiTaskService.reconcileMeetingStatus(meeting.getId()); @@ -1012,7 +1013,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { "RESUMMARY" ); meeting.setSummaryDetailLevel(effectiveSummaryDetailLevel); - meeting.setStatus(2); + meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode()); meetingService.updateById(meeting); if ("EXTERNAL_N8N".equalsIgnoreCase(summaryOrchestrationMode)) { updateMeetingProgress(meetingId, 95, "等待外部总结编排...", 0); @@ -1047,7 +1048,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { if (asrTask == null || asrTask.getTaskConfig() == null || asrTask.getTaskConfig().get("asrModelId") == null) { throw new RuntimeException("未找到可用的识别任务配置"); } - if (Integer.valueOf(1).equals(asrTask.getStatus()) && !Integer.valueOf(4).equals(meeting.getStatus())) { + if (Integer.valueOf(1).equals(asrTask.getStatus()) && !MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.FAILED)) { throw new RuntimeException("当前会议转写任务仍在处理中,请勿重复重试"); } @@ -1087,7 +1088,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { resetAiTask(summaryTask, new HashMap<>(summaryTask.getTaskConfig())); aiTaskService.updateById(summaryTask); - meeting.setStatus(1); + meeting.setStatus(MeetingStatusEnum.TRANSCRIBING.getCode()); meetingService.updateById(meeting); clearLegacyDispatchState(meetingId); updateMeetingProgress(meetingId, 0, "已重新提交识别任务,等待 ASR 处理...", 0); @@ -1101,7 +1102,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { if (meeting == null) { throw new RuntimeException("会议不存在"); } - if (meeting.getStatus() == 2) { + if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.SUMMARIZING)) { throw new RuntimeException("当前会议仍在处理中,请稍后再试"); } long transcriptCount = transcriptMapper.selectCount(new LambdaQueryWrapper() @@ -1122,7 +1123,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { } resetAiTask(summaryTask, new HashMap<>(summaryTask.getTaskConfig())); aiTaskService.updateById(summaryTask); - meeting.setStatus(2); + meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode()); meetingService.updateById(meeting); updateMeetingProgress(meetingId, 90, "已重新提交总结任务,正在生成总结...", 0); dispatchSummaryTaskAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId()); @@ -1135,7 +1136,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { if (meeting == null) { throw new RuntimeException("会议不存在"); } - if (meeting.getStatus() == 2) { + if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.SUMMARIZING)) { throw new RuntimeException("当前会议仍在处理中,请稍后再试"); } long transcriptCount = transcriptMapper.selectCount(new LambdaQueryWrapper() @@ -1156,7 +1157,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { } resetAiTask(chapterTask, new HashMap<>(chapterTask.getTaskConfig())); aiTaskService.updateById(chapterTask); - meeting.setStatus(2); + meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode()); meetingService.updateById(meeting); updateMeetingProgress(meetingId, 85, "已重新提交 AI 目录任务,正在生成目录...", 0); dispatchChapterTaskAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId()); @@ -1340,22 +1341,22 @@ public class MeetingCommandServiceImpl implements MeetingCommandService { int meetingStatus; if (percent < 0) { stage = com.imeeting.common.MeetingProgressStage.FAILED; - meetingStatus = 4; + meetingStatus = MeetingStatusEnum.FAILED.getCode(); } else if (percent >= 100) { stage = com.imeeting.common.MeetingProgressStage.COMPLETED; - meetingStatus = 3; + meetingStatus = MeetingStatusEnum.COMPLETED.getCode(); } else if (percent >= 90) { stage = com.imeeting.common.MeetingProgressStage.SUMMARY_RUNNING; - meetingStatus = 2; + meetingStatus = MeetingStatusEnum.SUMMARIZING.getCode(); } else if (percent >= 85) { stage = com.imeeting.common.MeetingProgressStage.CHAPTER_RUNNING; - meetingStatus = 2; + meetingStatus = MeetingStatusEnum.SUMMARIZING.getCode(); } else if (percent >= 5) { stage = com.imeeting.common.MeetingProgressStage.ASR_RUNNING; - meetingStatus = 1; + meetingStatus = MeetingStatusEnum.TRANSCRIBING.getCode(); } else { stage = com.imeeting.common.MeetingProgressStage.QUEUED; - meetingStatus = 1; + meetingStatus = MeetingStatusEnum.TRANSCRIBING.getCode(); } meetingProgressService.markStageAfterCommitOrNow(meetingId, null, meetingStatus, stage, percent, message, eta); } diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingDomainSupport.java b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingDomainSupport.java index 2a8edc3..1b31682 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingDomainSupport.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingDomainSupport.java @@ -568,25 +568,32 @@ public class MeetingDomainSupport { ); try { ProcessBuilder processBuilder = new ProcessBuilder(command); - processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); - byte[] output; - try (InputStream processStream = process.getInputStream()) { - output = processStream.readAllBytes(); + byte[] stdout; + byte[] stderr; + try (InputStream stdoutStream = process.getInputStream(); + InputStream stderrStream = process.getErrorStream()) { + stdout = stdoutStream.readAllBytes(); + stderr = stderrStream.readAllBytes(); } if (!process.waitFor(30, TimeUnit.SECONDS)) { process.destroyForcibly(); return null; } - if (process.exitValue() != 0) { - return null; + String stdoutText = new String(stdout, StandardCharsets.UTF_8).trim(); + String stderrText = new String(stderr, StandardCharsets.UTF_8).trim(); + Double duration = extractDurationSeconds(stdoutText); + if (duration == null) { + duration = extractDurationSeconds(stderrText); } - String raw = new String(output, StandardCharsets.UTF_8).trim(); - if (raw.isBlank()) { - return null; + if (duration != null && duration > 0D) { + return (int) Math.ceil(duration); } - double duration = Double.parseDouble(raw); - return duration > 0 ? (int) Math.ceil(duration) : null; + if (process.exitValue() != 0 || !stderrText.isBlank() || !stdoutText.isBlank()) { + log.warn("ffprobe returned no parsable duration, path={}, exitCode={}, stdout={}, stderr={}", + audioPath, process.exitValue(), stdoutText, stderrText); + } + return null; } catch (Exception ex) { log.warn("ffprobe failed to resolve audio duration from path={}", audioPath, ex); return null; @@ -599,12 +606,21 @@ public class MeetingDomainSupport { } String trimmed = ffmpegPath.trim(); try { - Path ffmpeg = Paths.get(trimmed); - Path fileName = ffmpeg.getFileName(); + Path configuredPath = Paths.get(trimmed); + if (Files.isDirectory(configuredPath)) { + Path ffprobeInDir = configuredPath.resolve(isWindowsLikePath(trimmed) ? "ffprobe.exe" : "ffprobe"); + if (Files.exists(ffprobeInDir)) { + return ffprobeInDir.toString(); + } + } + Path fileName = configuredPath.getFileName(); if (fileName != null) { String normalizedName = fileName.toString().toLowerCase(); + if ("ffprobe".equals(normalizedName) || "ffprobe.exe".equals(normalizedName)) { + return configuredPath.toString(); + } if ("ffmpeg".equals(normalizedName) || "ffmpeg.exe".equals(normalizedName)) { - Path sibling = ffmpeg.resolveSibling(normalizedName.endsWith(".exe") ? "ffprobe.exe" : "ffprobe"); + Path sibling = configuredPath.resolveSibling(normalizedName.endsWith(".exe") ? "ffprobe.exe" : "ffprobe"); if (Files.exists(sibling)) { return sibling.toString(); } @@ -616,6 +632,29 @@ public class MeetingDomainSupport { return "ffprobe"; } + private Double extractDurationSeconds(String rawOutput) { + if (rawOutput == null || rawOutput.isBlank()) { + return null; + } + String[] lines = rawOutput.split("\\R"); + for (int i = lines.length - 1; i >= 0; i--) { + String candidate = lines[i] == null ? null : lines[i].trim(); + if (candidate == null || candidate.isEmpty()) { + continue; + } + try { + return Double.parseDouble(candidate); + } catch (NumberFormatException ignored) { + // Keep scanning upward until a numeric duration line is found. + } + } + return null; + } + + private boolean isWindowsLikePath(String path) { + return path != null && path.contains("\\"); + } + private Path resolvePublicAudioPath(String audioUrl) { if (audioUrl == null || audioUrl.isBlank()) { return null; diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingProgressServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingProgressServiceImpl.java index 6fb76be..829bcf2 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingProgressServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingProgressServiceImpl.java @@ -8,6 +8,7 @@ import com.imeeting.dto.biz.MeetingVO; import com.imeeting.dto.biz.UnifiedMeetingStatusVO; import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.Meeting; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.mapper.biz.AiTaskMapper; import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.service.android.AndroidMeetingPushService; @@ -222,10 +223,10 @@ public class MeetingProgressServiceImpl implements MeetingProgressService { .eq(AiTask::getMeetingId, meetingId) .orderByDesc(AiTask::getId) .last("LIMIT 1")); - if (meeting.getStatus() != null && meeting.getStatus() == 3) { + if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED)) { return buildSnapshot(meetingId, latestTask, meeting.getStatus(), MeetingProgressStage.COMPLETED, 100, "处理完成", 0); } - if (meeting.getStatus() != null && meeting.getStatus() == 4) { + if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.FAILED)) { String message = latestTask != null && latestTask.getErrorMsg() != null && !latestTask.getErrorMsg().isBlank() ? latestTask.getErrorMsg() : "处理失败"; diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingQueryServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingQueryServiceImpl.java index d19d9bd..f4cdcc2 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingQueryServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingQueryServiceImpl.java @@ -10,6 +10,7 @@ import com.imeeting.dto.biz.MeetingVO; import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.MeetingTranscript; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.service.biz.AiTaskService; import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.mapper.biz.MeetingTranscriptMapper; @@ -186,12 +187,18 @@ public class MeetingQueryServiceImpl implements MeetingQueryService { } stats.put("totalMeetings", meetingService.count(baseWrapper.clone())); - stats.put("processingTasks", meetingService.count(baseWrapper.clone().in(Meeting::getStatus, 1, 2))); + stats.put("processingTasks", meetingService.count(baseWrapper.clone().in( + Meeting::getStatus, + MeetingStatusEnum.codesOf(MeetingStatusEnum.TRANSCRIBING, MeetingStatusEnum.SUMMARIZING) + ))); LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0); stats.put("todayNew", meetingService.count(baseWrapper.clone().ge(Meeting::getCreatedAt, todayStart))); - long totalFinished = meetingService.count(baseWrapper.clone().in(Meeting::getStatus, 3, 4)); - long success = meetingService.count(baseWrapper.clone().eq(Meeting::getStatus, 3)); + long totalFinished = meetingService.count(baseWrapper.clone().in( + Meeting::getStatus, + MeetingStatusEnum.codesOf(MeetingStatusEnum.COMPLETED, MeetingStatusEnum.FAILED) + )); + long success = meetingService.count(baseWrapper.clone().eq(Meeting::getStatus, MeetingStatusEnum.COMPLETED.getCode())); stats.put("successRate", totalFinished == 0 ? 100 : (int) ((double) success / totalFinished * 100)); return stats; } diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingUnifiedStatusServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingUnifiedStatusServiceImpl.java index 7f17162..2ca3cd7 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingUnifiedStatusServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingUnifiedStatusServiceImpl.java @@ -10,6 +10,7 @@ import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.MeetingTranscript; import com.imeeting.entity.biz.MeetingTranscriptChapterVersion; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.mapper.biz.AiTaskMapper; import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.mapper.biz.MeetingTranscriptChapterVersionMapper; @@ -80,7 +81,7 @@ public class MeetingUnifiedStatusServiceImpl implements MeetingUnifiedStatusServ if (meeting == null) { return UnifiedMeetingStatusStage.INITIALIZING; } - if (Integer.valueOf(3).equals(meeting.getStatus())) { + if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED)) { return UnifiedMeetingStatusStage.COMPLETED; } UnifiedMeetingStatusStage stageFromSnapshot = resolveStageFromSnapshot(snapshot); @@ -117,7 +118,7 @@ public class MeetingUnifiedStatusServiceImpl implements MeetingUnifiedStatusServ } private UnifiedMeetingStatusStage resolveFailedStage(MeetingVO meeting) { - if (meeting == null || !Integer.valueOf(4).equals(meeting.getStatus())) { + if (meeting == null || !MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.FAILED)) { return null; } diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/RealtimeMeetingSessionStateServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/RealtimeMeetingSessionStateServiceImpl.java index 1a00c30..38578a1 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/RealtimeMeetingSessionStateServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/RealtimeMeetingSessionStateServiceImpl.java @@ -6,6 +6,7 @@ import com.imeeting.dto.biz.RealtimeMeetingSessionState; import com.imeeting.dto.biz.RealtimeMeetingSessionStatusVO; import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.MeetingTranscript; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.mapper.biz.MeetingTranscriptMapper; import com.imeeting.service.biz.RealtimeMeetingSessionStateService; @@ -299,7 +300,7 @@ public class RealtimeMeetingSessionStateServiceImpl implements RealtimeMeetingSe return vo; } - if (Integer.valueOf(2).equals(meeting.getStatus())) { + if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.SUMMARIZING)) { vo.setStatus("COMPLETING"); } else if (isDatabaseTerminalStatus(meeting.getStatus())) { vo.setStatus("COMPLETED"); @@ -322,7 +323,8 @@ public class RealtimeMeetingSessionStateServiceImpl implements RealtimeMeetingSe } private boolean isDatabaseTerminalStatus(Integer status) { - return Integer.valueOf(3).equals(status) || Integer.valueOf(4).equals(status); + return MeetingStatusEnum.isCode(status, MeetingStatusEnum.COMPLETED) + || MeetingStatusEnum.isCode(status, MeetingStatusEnum.FAILED); } private boolean isRedisTerminalStatus(String status) { diff --git a/backend/src/main/java/com/imeeting/service/mcp/MeetingMcpToolService.java b/backend/src/main/java/com/imeeting/service/mcp/MeetingMcpToolService.java index 5f9e46f..a6a4fb5 100644 --- a/backend/src/main/java/com/imeeting/service/mcp/MeetingMcpToolService.java +++ b/backend/src/main/java/com/imeeting/service/mcp/MeetingMcpToolService.java @@ -17,6 +17,7 @@ import com.imeeting.dto.biz.MeetingVO; import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.PromptTemplate; +import com.imeeting.enums.MeetingStatusEnum; import com.imeeting.service.biz.AiTaskService; import com.imeeting.service.biz.MeetingAccessService; import com.imeeting.service.biz.MeetingProgressService; @@ -188,7 +189,7 @@ public class MeetingMcpToolService { AiTask asrTask = findLatestTask(meetingId, "ASR"); AiTask summaryTask = findLatestTask(meetingId, "SUMMARY"); boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus()); - MeetingVO detail = (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) + MeetingVO detail = (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED) || summaryCompleted) ? meetingQueryService.getDetail(meetingId) : null; boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank(); @@ -340,7 +341,7 @@ public class MeetingMcpToolService { AiTask summaryTask = findLatestTask(meetingId, "SUMMARY"); boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus()); - if (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) { + if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED) || summaryCompleted) { return new LegacyMeetingProcessingStatusResponse("completed", 100, STAGE_COMPLETED); } if (isFailed(asrTask)) {