feat: 引入会议状态枚举并更新相关逻辑

- 新增 `MeetingStatusEnum` 枚举类,定义会议状态及其相关方法
- 更新多个服务和控制器中的会议状态处理逻辑,使用 `MeetingStatusEnum` 代替硬编码的状态值
- 优化会议状态判断和设置的相关代码,提高可读性和维护性
dev_na
chenhao 2026-06-10 09:51:53 +08:00
parent 82dc485dcf
commit 1813b90c6a
15 changed files with 179 additions and 58 deletions

View File

@ -26,6 +26,7 @@ import com.imeeting.dto.biz.UnifiedMeetingStatusVO;
import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.AiTask;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.entity.biz.PromptTemplate; import com.imeeting.entity.biz.PromptTemplate;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.service.android.AndroidAuthService; import com.imeeting.service.android.AndroidAuthService;
import com.imeeting.service.android.AndroidChunkUploadService; import com.imeeting.service.android.AndroidChunkUploadService;
import com.imeeting.service.android.legacy.LegacyMeetingAdapterService; import com.imeeting.service.android.legacy.LegacyMeetingAdapterService;
@ -454,7 +455,7 @@ public class AndroidMeetingController {
AiTask asrTask = findLatestTask(meetingId, "ASR"); AiTask asrTask = findLatestTask(meetingId, "ASR");
AiTask summaryTask = findLatestTask(meetingId, "SUMMARY"); AiTask summaryTask = findLatestTask(meetingId, "SUMMARY");
boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus()); 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) ? meetingQueryService.getDetail(meetingId)
: null; : null;
boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank(); boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank();
@ -736,7 +737,11 @@ public class AndroidMeetingController {
} }
return meetingService.getOne(new LambdaQueryWrapper<Meeting>() return meetingService.getOne(new LambdaQueryWrapper<Meeting>()
.eq(Meeting::getSourceDeviceCode, deviceId) .eq(Meeting::getSourceDeviceCode, deviceId)
.in(Meeting::getStatus, 0, 1, 2) .in(Meeting::getStatus, MeetingStatusEnum.codesOf(
MeetingStatusEnum.INITIALIZING,
MeetingStatusEnum.TRANSCRIBING,
MeetingStatusEnum.SUMMARIZING
))
.orderByDesc(Meeting::getId) .orderByDesc(Meeting::getId)
.last("LIMIT 1")); .last("LIMIT 1"));
} }

View File

@ -6,6 +6,7 @@ import com.imeeting.dto.android.AndroidPublicMeetingSessionResultVO;
import com.imeeting.dto.android.AndroidPublicMeetingSessionRequest; import com.imeeting.dto.android.AndroidPublicMeetingSessionRequest;
import com.imeeting.entity.biz.AndroidPushMessage; import com.imeeting.entity.biz.AndroidPushMessage;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.enums.MeetingPushTypeEnum; import com.imeeting.enums.MeetingPushTypeEnum;
import com.imeeting.mapper.DeviceInfoMapper; import com.imeeting.mapper.DeviceInfoMapper;
import com.imeeting.service.android.AndroidAuthService; import com.imeeting.service.android.AndroidAuthService;
@ -113,7 +114,8 @@ public class AndroidPublicMeetingController {
if (meeting.getSourceDeviceCode() == null || !meeting.getSourceDeviceCode().equals(authContext.getDeviceId())) { if (meeting.getSourceDeviceCode() == null || !meeting.getSourceDeviceCode().equals(authContext.getDeviceId())) {
throw new RuntimeException("当前会议不属于该设备"); 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("当前会议状态不允许删除"); throw new RuntimeException("当前会议状态不允许删除");
} }
meetingCommandService.deleteMeeting(meetingId); meetingCommandService.deleteMeeting(meetingId);

View File

@ -21,6 +21,7 @@ import com.imeeting.entity.biz.AiTask;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.mapper.biz.MeetingTranscriptMapper; import com.imeeting.mapper.biz.MeetingTranscriptMapper;
import com.imeeting.entity.biz.PromptTemplate; import com.imeeting.entity.biz.PromptTemplate;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.service.android.legacy.LegacyMeetingAdapterService; import com.imeeting.service.android.legacy.LegacyMeetingAdapterService;
import com.imeeting.support.AndroidRequestLogHelper; import com.imeeting.support.AndroidRequestLogHelper;
import com.imeeting.service.biz.AiTaskService; import com.imeeting.service.biz.AiTaskService;
@ -256,7 +257,7 @@ public class LegacyMeetingController {
AiTask asrTask = findLatestTask(meetingId, "ASR"); AiTask asrTask = findLatestTask(meetingId, "ASR");
AiTask summaryTask = findLatestTask(meetingId, "SUMMARY"); AiTask summaryTask = findLatestTask(meetingId, "SUMMARY");
boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus()); 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) ? meetingQueryService.getDetail(meetingId)
: null; : null;
boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank(); boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank();
@ -428,7 +429,7 @@ public class LegacyMeetingController {
AiTask summaryTask = findLatestTask(meetingId, "SUMMARY"); AiTask summaryTask = findLatestTask(meetingId, "SUMMARY");
boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus()); 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); return new LegacyMeetingProcessingStatusResponse("completed", 100, STAGE_COMPLETED);
} }
if (isFailed(asrTask)) { if (isFailed(asrTask)) {

View File

@ -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<Integer> codesOf(MeetingStatusEnum... statuses) {
return Arrays.stream(statuses).map(MeetingStatusEnum::getCode).toList();
}
}

View File

@ -2,6 +2,7 @@ package com.imeeting.listener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.mapper.biz.MeetingMapper;
import com.imeeting.service.biz.AiTaskService; import com.imeeting.service.biz.AiTaskService;
import com.imeeting.support.TaskSecurityContextRunner; import com.imeeting.support.TaskSecurityContextRunner;
@ -33,7 +34,10 @@ public class MeetingTaskRecoveryListener implements ApplicationRunner {
List<Meeting> pendingMeetings = taskSecurityContextRunner.callAsPlatformAdmin(() -> List<Meeting> pendingMeetings = taskSecurityContextRunner.callAsPlatformAdmin(() ->
meetingMapper.selectList(new LambdaQueryWrapper<Meeting>() meetingMapper.selectList(new LambdaQueryWrapper<Meeting>()
.in(Meeting::getStatus, 1, 2) .in(Meeting::getStatus, MeetingStatusEnum.codesOf(
MeetingStatusEnum.TRANSCRIBING,
MeetingStatusEnum.SUMMARIZING
))
.eq(Meeting::getIsDeleted, 0)) .eq(Meeting::getIsDeleted, 0))
); );
if (pendingMeetings.isEmpty()) { if (pendingMeetings.isEmpty()) {
@ -46,10 +50,10 @@ public class MeetingTaskRecoveryListener implements ApplicationRunner {
meetingLockCache.clearDispatchLocks(meeting.getId()); meetingLockCache.clearDispatchLocks(meeting.getId());
meetingAsrPermitCache.clearRecoveryState(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()); log.info("Recovering ASR task for meeting {}", meeting.getId());
aiTaskService.dispatchTasks(meeting.getId(), meeting.getTenantId(), meeting.getCreatorId()); 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()); log.info("Recovering summary task for meeting {}", meeting.getId());
aiTaskService.dispatchSummaryTask(meeting.getId(), meeting.getTenantId(), meeting.getCreatorId()); aiTaskService.dispatchSummaryTask(meeting.getId(), meeting.getTenantId(), meeting.getCreatorId());
} }

View File

@ -39,6 +39,7 @@ public interface MeetingMapper extends BaseMapper<Meeting> {
FROM biz_meetings FROM biz_meetings
WHERE tenant_id = #{tenantId} WHERE tenant_id = #{tenantId}
AND source_device_code = #{deviceCode} AND source_device_code = #{deviceCode}
AND status=3
AND is_deleted = 0 AND is_deleted = 0
<if test="resetAt != null"> <if test="resetAt != null">
AND created_at &gt;= #{resetAt} AND created_at &gt;= #{resetAt}

View File

@ -12,6 +12,7 @@ import com.imeeting.entity.biz.AiTask;
import com.imeeting.entity.biz.LlmModel; import com.imeeting.entity.biz.LlmModel;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.entity.biz.MeetingTranscript; import com.imeeting.entity.biz.MeetingTranscript;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.mapper.biz.LlmModelMapper; import com.imeeting.mapper.biz.LlmModelMapper;
import com.imeeting.mapper.biz.MeetingTranscriptMapper; import com.imeeting.mapper.biz.MeetingTranscriptMapper;
import com.imeeting.service.android.legacy.LegacyMeetingAdapterService; import com.imeeting.service.android.legacy.LegacyMeetingAdapterService;
@ -183,7 +184,7 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS); meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS);
meeting.setAudioSaveMessage(null); meeting.setAudioSaveMessage(null);
meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED); meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED);
meeting.setStatus(1); meeting.setStatus(MeetingStatusEnum.TRANSCRIBING.getCode());
meetingService.updateById(meeting); meetingService.updateById(meeting);
resetOrCreateAsrTask(meetingId, profile); resetOrCreateAsrTask(meetingId, profile);
@ -252,7 +253,7 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS); meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS);
meeting.setAudioSaveMessage(null); meeting.setAudioSaveMessage(null);
meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED); meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED);
meeting.setStatus(1); meeting.setStatus(MeetingStatusEnum.TRANSCRIBING.getCode());
meetingService.updateById(meeting); meetingService.updateById(meeting);
resetOrCreateAsrTask(meetingId, profile); resetOrCreateAsrTask(meetingId, profile);

View File

@ -14,6 +14,7 @@ import com.imeeting.entity.biz.AiTask;
import com.imeeting.entity.biz.HotWord; import com.imeeting.entity.biz.HotWord;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.entity.biz.MeetingTranscript; import com.imeeting.entity.biz.MeetingTranscript;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.mapper.biz.AiTaskMapper; import com.imeeting.mapper.biz.AiTaskMapper;
import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.mapper.biz.MeetingMapper;
import com.imeeting.mapper.biz.MeetingTranscriptMapper; import com.imeeting.mapper.biz.MeetingTranscriptMapper;
@ -953,7 +954,7 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
Files.writeString(filePath, markdownContent, StandardCharsets.UTF_8); 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); taskRecord.setResultFilePath("meetings/" + meeting.getId() + "/summaries/" + fileName);
Map<String, Object> responseData = objectMapper.convertValue(respNode, Map.class); Map<String, Object> responseData = objectMapper.convertValue(respNode, Map.class);
responseData.put("summarySource", summarySource.toSnapshot()); responseData.put("summarySource", summarySource.toSnapshot());
@ -1206,22 +1207,22 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
int meetingStatus; int meetingStatus;
if (percent < 0) { if (percent < 0) {
stage = MeetingProgressStage.FAILED; stage = MeetingProgressStage.FAILED;
meetingStatus = 4; meetingStatus = MeetingStatusEnum.FAILED.getCode();
} else if (percent >= 100) { } else if (percent >= 100) {
stage = MeetingProgressStage.COMPLETED; stage = MeetingProgressStage.COMPLETED;
meetingStatus = 3; meetingStatus = MeetingStatusEnum.COMPLETED.getCode();
} else if (percent >= 90) { } else if (percent >= 90) {
stage = MeetingProgressStage.SUMMARY_RUNNING; stage = MeetingProgressStage.SUMMARY_RUNNING;
meetingStatus = 2; meetingStatus = MeetingStatusEnum.SUMMARIZING.getCode();
} else if (percent >= 85) { } else if (percent >= 85) {
stage = MeetingProgressStage.CHAPTER_RUNNING; stage = MeetingProgressStage.CHAPTER_RUNNING;
meetingStatus = 2; meetingStatus = MeetingStatusEnum.SUMMARIZING.getCode();
} else if (percent >= 5) { } else if (percent >= 5) {
stage = MeetingProgressStage.ASR_RUNNING; stage = MeetingProgressStage.ASR_RUNNING;
meetingStatus = 1; meetingStatus = MeetingStatusEnum.TRANSCRIBING.getCode();
} else { } else {
stage = MeetingProgressStage.QUEUED; stage = MeetingProgressStage.QUEUED;
meetingStatus = 1; meetingStatus = MeetingStatusEnum.TRANSCRIBING.getCode();
} }
meetingProgressService.markStage(meetingId, findLatestTaskForProgress(meetingId), meetingStatus, stage, percent, msg, eta); meetingProgressService.markStage(meetingId, findLatestTaskForProgress(meetingId), meetingStatus, stage, percent, msg, eta);
} }

View File

@ -27,6 +27,7 @@ import com.imeeting.entity.biz.HotWord;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.entity.biz.MeetingTranscript; import com.imeeting.entity.biz.MeetingTranscript;
import com.imeeting.entity.biz.MeetingTranscriptChapterVersion; import com.imeeting.entity.biz.MeetingTranscriptChapterVersion;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.service.android.AndroidMeetingPushService; import com.imeeting.service.android.AndroidMeetingPushService;
import com.imeeting.service.android.AndroidPendingMeetingDraftService; import com.imeeting.service.android.AndroidPendingMeetingDraftService;
import com.imeeting.service.android.AndroidPushMessageService; import com.imeeting.service.android.AndroidPushMessageService;
@ -485,7 +486,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
} }
realtimeMeetingSessionStateService.clear(meetingId); realtimeMeetingSessionStateService.clear(meetingId);
meeting.setStatus(2); meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
meetingService.updateById(meeting); meetingService.updateById(meeting);
updateMeetingProgress(meetingId, 85, "正在生成 AI 目录与总结...", 0); updateMeetingProgress(meetingId, 85, "正在生成 AI 目录与总结...", 0);
meetingDomainSupport.prewarmPlaybackAudioAfterCommit(meeting.getAudioUrl()); meetingDomainSupport.prewarmPlaybackAudioAfterCommit(meeting.getAudioUrl());
@ -824,7 +825,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
"RESUMMARY" "RESUMMARY"
); );
meeting.setLatestSummaryTaskId(createdSummaryTask.getId()); meeting.setLatestSummaryTaskId(createdSummaryTask.getId());
meeting.setStatus(2); meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
meetingService.updateById(meeting); meetingService.updateById(meeting);
aiTaskService.dispatchSummaryTask(meeting.getId(), meeting.getTenantId(), meeting.getCreatorId()); aiTaskService.dispatchSummaryTask(meeting.getId(), meeting.getTenantId(), meeting.getCreatorId());
@ -885,7 +886,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
aiTaskService.updateById(summaryTask); aiTaskService.updateById(summaryTask);
meetingPointsService.recordSummarySuccessCharge(meeting, 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()); meeting.setLatestSummaryTaskId(summaryTask.getId());
meetingService.updateById(meeting); meetingService.updateById(meeting);
aiTaskService.reconcileMeetingStatus(meeting.getId()); aiTaskService.reconcileMeetingStatus(meeting.getId());
@ -1012,7 +1013,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
"RESUMMARY" "RESUMMARY"
); );
meeting.setSummaryDetailLevel(effectiveSummaryDetailLevel); meeting.setSummaryDetailLevel(effectiveSummaryDetailLevel);
meeting.setStatus(2); meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
meetingService.updateById(meeting); meetingService.updateById(meeting);
if ("EXTERNAL_N8N".equalsIgnoreCase(summaryOrchestrationMode)) { if ("EXTERNAL_N8N".equalsIgnoreCase(summaryOrchestrationMode)) {
updateMeetingProgress(meetingId, 95, "等待外部总结编排...", 0); updateMeetingProgress(meetingId, 95, "等待外部总结编排...", 0);
@ -1047,7 +1048,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
if (asrTask == null || asrTask.getTaskConfig() == null || asrTask.getTaskConfig().get("asrModelId") == null) { if (asrTask == null || asrTask.getTaskConfig() == null || asrTask.getTaskConfig().get("asrModelId") == null) {
throw new RuntimeException("未找到可用的识别任务配置"); 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("当前会议转写任务仍在处理中,请勿重复重试"); throw new RuntimeException("当前会议转写任务仍在处理中,请勿重复重试");
} }
@ -1087,7 +1088,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
resetAiTask(summaryTask, new HashMap<>(summaryTask.getTaskConfig())); resetAiTask(summaryTask, new HashMap<>(summaryTask.getTaskConfig()));
aiTaskService.updateById(summaryTask); aiTaskService.updateById(summaryTask);
meeting.setStatus(1); meeting.setStatus(MeetingStatusEnum.TRANSCRIBING.getCode());
meetingService.updateById(meeting); meetingService.updateById(meeting);
clearLegacyDispatchState(meetingId); clearLegacyDispatchState(meetingId);
updateMeetingProgress(meetingId, 0, "已重新提交识别任务,等待 ASR 处理...", 0); updateMeetingProgress(meetingId, 0, "已重新提交识别任务,等待 ASR 处理...", 0);
@ -1101,7 +1102,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
if (meeting == null) { if (meeting == null) {
throw new RuntimeException("会议不存在"); throw new RuntimeException("会议不存在");
} }
if (meeting.getStatus() == 2) { if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.SUMMARIZING)) {
throw new RuntimeException("当前会议仍在处理中,请稍后再试"); throw new RuntimeException("当前会议仍在处理中,请稍后再试");
} }
long transcriptCount = transcriptMapper.selectCount(new LambdaQueryWrapper<MeetingTranscript>() long transcriptCount = transcriptMapper.selectCount(new LambdaQueryWrapper<MeetingTranscript>()
@ -1122,7 +1123,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
} }
resetAiTask(summaryTask, new HashMap<>(summaryTask.getTaskConfig())); resetAiTask(summaryTask, new HashMap<>(summaryTask.getTaskConfig()));
aiTaskService.updateById(summaryTask); aiTaskService.updateById(summaryTask);
meeting.setStatus(2); meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
meetingService.updateById(meeting); meetingService.updateById(meeting);
updateMeetingProgress(meetingId, 90, "已重新提交总结任务,正在生成总结...", 0); updateMeetingProgress(meetingId, 90, "已重新提交总结任务,正在生成总结...", 0);
dispatchSummaryTaskAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId()); dispatchSummaryTaskAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
@ -1135,7 +1136,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
if (meeting == null) { if (meeting == null) {
throw new RuntimeException("会议不存在"); throw new RuntimeException("会议不存在");
} }
if (meeting.getStatus() == 2) { if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.SUMMARIZING)) {
throw new RuntimeException("当前会议仍在处理中,请稍后再试"); throw new RuntimeException("当前会议仍在处理中,请稍后再试");
} }
long transcriptCount = transcriptMapper.selectCount(new LambdaQueryWrapper<MeetingTranscript>() long transcriptCount = transcriptMapper.selectCount(new LambdaQueryWrapper<MeetingTranscript>()
@ -1156,7 +1157,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
} }
resetAiTask(chapterTask, new HashMap<>(chapterTask.getTaskConfig())); resetAiTask(chapterTask, new HashMap<>(chapterTask.getTaskConfig()));
aiTaskService.updateById(chapterTask); aiTaskService.updateById(chapterTask);
meeting.setStatus(2); meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
meetingService.updateById(meeting); meetingService.updateById(meeting);
updateMeetingProgress(meetingId, 85, "已重新提交 AI 目录任务,正在生成目录...", 0); updateMeetingProgress(meetingId, 85, "已重新提交 AI 目录任务,正在生成目录...", 0);
dispatchChapterTaskAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId()); dispatchChapterTaskAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
@ -1340,22 +1341,22 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
int meetingStatus; int meetingStatus;
if (percent < 0) { if (percent < 0) {
stage = com.imeeting.common.MeetingProgressStage.FAILED; stage = com.imeeting.common.MeetingProgressStage.FAILED;
meetingStatus = 4; meetingStatus = MeetingStatusEnum.FAILED.getCode();
} else if (percent >= 100) { } else if (percent >= 100) {
stage = com.imeeting.common.MeetingProgressStage.COMPLETED; stage = com.imeeting.common.MeetingProgressStage.COMPLETED;
meetingStatus = 3; meetingStatus = MeetingStatusEnum.COMPLETED.getCode();
} else if (percent >= 90) { } else if (percent >= 90) {
stage = com.imeeting.common.MeetingProgressStage.SUMMARY_RUNNING; stage = com.imeeting.common.MeetingProgressStage.SUMMARY_RUNNING;
meetingStatus = 2; meetingStatus = MeetingStatusEnum.SUMMARIZING.getCode();
} else if (percent >= 85) { } else if (percent >= 85) {
stage = com.imeeting.common.MeetingProgressStage.CHAPTER_RUNNING; stage = com.imeeting.common.MeetingProgressStage.CHAPTER_RUNNING;
meetingStatus = 2; meetingStatus = MeetingStatusEnum.SUMMARIZING.getCode();
} else if (percent >= 5) { } else if (percent >= 5) {
stage = com.imeeting.common.MeetingProgressStage.ASR_RUNNING; stage = com.imeeting.common.MeetingProgressStage.ASR_RUNNING;
meetingStatus = 1; meetingStatus = MeetingStatusEnum.TRANSCRIBING.getCode();
} else { } else {
stage = com.imeeting.common.MeetingProgressStage.QUEUED; stage = com.imeeting.common.MeetingProgressStage.QUEUED;
meetingStatus = 1; meetingStatus = MeetingStatusEnum.TRANSCRIBING.getCode();
} }
meetingProgressService.markStageAfterCommitOrNow(meetingId, null, meetingStatus, stage, percent, message, eta); meetingProgressService.markStageAfterCommitOrNow(meetingId, null, meetingStatus, stage, percent, message, eta);
} }

View File

@ -568,25 +568,32 @@ public class MeetingDomainSupport {
); );
try { try {
ProcessBuilder processBuilder = new ProcessBuilder(command); ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start(); Process process = processBuilder.start();
byte[] output; byte[] stdout;
try (InputStream processStream = process.getInputStream()) { byte[] stderr;
output = processStream.readAllBytes(); try (InputStream stdoutStream = process.getInputStream();
InputStream stderrStream = process.getErrorStream()) {
stdout = stdoutStream.readAllBytes();
stderr = stderrStream.readAllBytes();
} }
if (!process.waitFor(30, TimeUnit.SECONDS)) { if (!process.waitFor(30, TimeUnit.SECONDS)) {
process.destroyForcibly(); process.destroyForcibly();
return null; return null;
} }
if (process.exitValue() != 0) { String stdoutText = new String(stdout, StandardCharsets.UTF_8).trim();
return null; 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 (duration != null && duration > 0D) {
if (raw.isBlank()) { return (int) Math.ceil(duration);
return null;
} }
double duration = Double.parseDouble(raw); if (process.exitValue() != 0 || !stderrText.isBlank() || !stdoutText.isBlank()) {
return duration > 0 ? (int) Math.ceil(duration) : null; log.warn("ffprobe returned no parsable duration, path={}, exitCode={}, stdout={}, stderr={}",
audioPath, process.exitValue(), stdoutText, stderrText);
}
return null;
} catch (Exception ex) { } catch (Exception ex) {
log.warn("ffprobe failed to resolve audio duration from path={}", audioPath, ex); log.warn("ffprobe failed to resolve audio duration from path={}", audioPath, ex);
return null; return null;
@ -599,12 +606,21 @@ public class MeetingDomainSupport {
} }
String trimmed = ffmpegPath.trim(); String trimmed = ffmpegPath.trim();
try { try {
Path ffmpeg = Paths.get(trimmed); Path configuredPath = Paths.get(trimmed);
Path fileName = ffmpeg.getFileName(); 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) { if (fileName != null) {
String normalizedName = fileName.toString().toLowerCase(); String normalizedName = fileName.toString().toLowerCase();
if ("ffprobe".equals(normalizedName) || "ffprobe.exe".equals(normalizedName)) {
return configuredPath.toString();
}
if ("ffmpeg".equals(normalizedName) || "ffmpeg.exe".equals(normalizedName)) { 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)) { if (Files.exists(sibling)) {
return sibling.toString(); return sibling.toString();
} }
@ -616,6 +632,29 @@ public class MeetingDomainSupport {
return "ffprobe"; 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) { private Path resolvePublicAudioPath(String audioUrl) {
if (audioUrl == null || audioUrl.isBlank()) { if (audioUrl == null || audioUrl.isBlank()) {
return null; return null;

View File

@ -8,6 +8,7 @@ import com.imeeting.dto.biz.MeetingVO;
import com.imeeting.dto.biz.UnifiedMeetingStatusVO; import com.imeeting.dto.biz.UnifiedMeetingStatusVO;
import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.AiTask;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.mapper.biz.AiTaskMapper; import com.imeeting.mapper.biz.AiTaskMapper;
import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.mapper.biz.MeetingMapper;
import com.imeeting.service.android.AndroidMeetingPushService; import com.imeeting.service.android.AndroidMeetingPushService;
@ -222,10 +223,10 @@ public class MeetingProgressServiceImpl implements MeetingProgressService {
.eq(AiTask::getMeetingId, meetingId) .eq(AiTask::getMeetingId, meetingId)
.orderByDesc(AiTask::getId) .orderByDesc(AiTask::getId)
.last("LIMIT 1")); .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); 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() String message = latestTask != null && latestTask.getErrorMsg() != null && !latestTask.getErrorMsg().isBlank()
? latestTask.getErrorMsg() ? latestTask.getErrorMsg()
: "处理失败"; : "处理失败";

View File

@ -10,6 +10,7 @@ import com.imeeting.dto.biz.MeetingVO;
import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.AiTask;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.entity.biz.MeetingTranscript; import com.imeeting.entity.biz.MeetingTranscript;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.service.biz.AiTaskService; import com.imeeting.service.biz.AiTaskService;
import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.mapper.biz.MeetingMapper;
import com.imeeting.mapper.biz.MeetingTranscriptMapper; import com.imeeting.mapper.biz.MeetingTranscriptMapper;
@ -186,12 +187,18 @@ public class MeetingQueryServiceImpl implements MeetingQueryService {
} }
stats.put("totalMeetings", meetingService.count(baseWrapper.clone())); 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); LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0);
stats.put("todayNew", meetingService.count(baseWrapper.clone().ge(Meeting::getCreatedAt, todayStart))); stats.put("todayNew", meetingService.count(baseWrapper.clone().ge(Meeting::getCreatedAt, todayStart)));
long totalFinished = meetingService.count(baseWrapper.clone().in(Meeting::getStatus, 3, 4)); long totalFinished = meetingService.count(baseWrapper.clone().in(
long success = meetingService.count(baseWrapper.clone().eq(Meeting::getStatus, 3)); 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)); stats.put("successRate", totalFinished == 0 ? 100 : (int) ((double) success / totalFinished * 100));
return stats; return stats;
} }

View File

@ -10,6 +10,7 @@ import com.imeeting.entity.biz.AiTask;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.entity.biz.MeetingTranscript; import com.imeeting.entity.biz.MeetingTranscript;
import com.imeeting.entity.biz.MeetingTranscriptChapterVersion; import com.imeeting.entity.biz.MeetingTranscriptChapterVersion;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.mapper.biz.AiTaskMapper; import com.imeeting.mapper.biz.AiTaskMapper;
import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.mapper.biz.MeetingMapper;
import com.imeeting.mapper.biz.MeetingTranscriptChapterVersionMapper; import com.imeeting.mapper.biz.MeetingTranscriptChapterVersionMapper;
@ -80,7 +81,7 @@ public class MeetingUnifiedStatusServiceImpl implements MeetingUnifiedStatusServ
if (meeting == null) { if (meeting == null) {
return UnifiedMeetingStatusStage.INITIALIZING; return UnifiedMeetingStatusStage.INITIALIZING;
} }
if (Integer.valueOf(3).equals(meeting.getStatus())) { if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED)) {
return UnifiedMeetingStatusStage.COMPLETED; return UnifiedMeetingStatusStage.COMPLETED;
} }
UnifiedMeetingStatusStage stageFromSnapshot = resolveStageFromSnapshot(snapshot); UnifiedMeetingStatusStage stageFromSnapshot = resolveStageFromSnapshot(snapshot);
@ -117,7 +118,7 @@ public class MeetingUnifiedStatusServiceImpl implements MeetingUnifiedStatusServ
} }
private UnifiedMeetingStatusStage resolveFailedStage(MeetingVO meeting) { 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; return null;
} }

View File

@ -6,6 +6,7 @@ import com.imeeting.dto.biz.RealtimeMeetingSessionState;
import com.imeeting.dto.biz.RealtimeMeetingSessionStatusVO; import com.imeeting.dto.biz.RealtimeMeetingSessionStatusVO;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.entity.biz.MeetingTranscript; import com.imeeting.entity.biz.MeetingTranscript;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.mapper.biz.MeetingMapper; import com.imeeting.mapper.biz.MeetingMapper;
import com.imeeting.mapper.biz.MeetingTranscriptMapper; import com.imeeting.mapper.biz.MeetingTranscriptMapper;
import com.imeeting.service.biz.RealtimeMeetingSessionStateService; import com.imeeting.service.biz.RealtimeMeetingSessionStateService;
@ -299,7 +300,7 @@ public class RealtimeMeetingSessionStateServiceImpl implements RealtimeMeetingSe
return vo; return vo;
} }
if (Integer.valueOf(2).equals(meeting.getStatus())) { if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.SUMMARIZING)) {
vo.setStatus("COMPLETING"); vo.setStatus("COMPLETING");
} else if (isDatabaseTerminalStatus(meeting.getStatus())) { } else if (isDatabaseTerminalStatus(meeting.getStatus())) {
vo.setStatus("COMPLETED"); vo.setStatus("COMPLETED");
@ -322,7 +323,8 @@ public class RealtimeMeetingSessionStateServiceImpl implements RealtimeMeetingSe
} }
private boolean isDatabaseTerminalStatus(Integer status) { 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) { private boolean isRedisTerminalStatus(String status) {

View File

@ -17,6 +17,7 @@ import com.imeeting.dto.biz.MeetingVO;
import com.imeeting.entity.biz.AiTask; import com.imeeting.entity.biz.AiTask;
import com.imeeting.entity.biz.Meeting; import com.imeeting.entity.biz.Meeting;
import com.imeeting.entity.biz.PromptTemplate; import com.imeeting.entity.biz.PromptTemplate;
import com.imeeting.enums.MeetingStatusEnum;
import com.imeeting.service.biz.AiTaskService; import com.imeeting.service.biz.AiTaskService;
import com.imeeting.service.biz.MeetingAccessService; import com.imeeting.service.biz.MeetingAccessService;
import com.imeeting.service.biz.MeetingProgressService; import com.imeeting.service.biz.MeetingProgressService;
@ -188,7 +189,7 @@ public class MeetingMcpToolService {
AiTask asrTask = findLatestTask(meetingId, "ASR"); AiTask asrTask = findLatestTask(meetingId, "ASR");
AiTask summaryTask = findLatestTask(meetingId, "SUMMARY"); AiTask summaryTask = findLatestTask(meetingId, "SUMMARY");
boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus()); 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) ? meetingQueryService.getDetail(meetingId)
: null; : null;
boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank(); boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank();
@ -340,7 +341,7 @@ public class MeetingMcpToolService {
AiTask summaryTask = findLatestTask(meetingId, "SUMMARY"); AiTask summaryTask = findLatestTask(meetingId, "SUMMARY");
boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus()); 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); return new LegacyMeetingProcessingStatusResponse("completed", 100, STAGE_COMPLETED);
} }
if (isFailed(asrTask)) { if (isFailed(asrTask)) {