feat: 增强旧版会议控制器功能和相关测试
- 添加 `StringRedisTemplate` 和 `ObjectMapper` 依赖 - 更新 `buildListItem` 方法以构建会议列表项 - 增加实时进度解析和处理逻辑 - 优化会议预览数据响应构建 - 添加并更新相关单元测试以验证新功能的正确性dev_na
parent
658b7e6b59
commit
6d46998abe
|
|
@ -1,6 +1,9 @@
|
||||||
package com.imeeting.controller.android.legacy;
|
package com.imeeting.controller.android.legacy;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.imeeting.common.RedisKeys;
|
||||||
import com.imeeting.dto.android.legacy.LegacyApiResponse;
|
import com.imeeting.dto.android.legacy.LegacyApiResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordRequest;
|
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordRequest;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordResponse;
|
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordResponse;
|
||||||
|
|
@ -12,10 +15,11 @@ import com.imeeting.dto.android.legacy.LegacyMeetingListResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewDataResponse;
|
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewDataResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewResult;
|
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewResult;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingProcessingStatusResponse;
|
import com.imeeting.dto.android.legacy.LegacyMeetingProcessingStatusResponse;
|
||||||
|
import com.imeeting.dto.android.legacy.LegacyMeetingTagResponse;
|
||||||
import com.imeeting.dto.biz.MeetingVO;
|
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.mapper.biz.MeetingTranscriptMapper;
|
||||||
import com.imeeting.entity.biz.PromptTemplate;
|
import com.imeeting.entity.biz.PromptTemplate;
|
||||||
import com.imeeting.service.android.legacy.LegacyMeetingAdapterService;
|
import com.imeeting.service.android.legacy.LegacyMeetingAdapterService;
|
||||||
import com.imeeting.service.biz.AiTaskService;
|
import com.imeeting.service.biz.AiTaskService;
|
||||||
|
|
@ -24,12 +28,12 @@ import com.imeeting.service.biz.MeetingCommandService;
|
||||||
import com.imeeting.service.biz.MeetingQueryService;
|
import com.imeeting.service.biz.MeetingQueryService;
|
||||||
import com.imeeting.service.biz.MeetingService;
|
import com.imeeting.service.biz.MeetingService;
|
||||||
import com.imeeting.service.biz.PromptTemplateService;
|
import com.imeeting.service.biz.PromptTemplateService;
|
||||||
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
|
||||||
import com.unisbase.dto.PageResult;
|
import com.unisbase.dto.PageResult;
|
||||||
import com.unisbase.entity.SysUser;
|
import com.unisbase.entity.SysUser;
|
||||||
import com.unisbase.mapper.SysUserMapper;
|
import com.unisbase.mapper.SysUserMapper;
|
||||||
import com.unisbase.security.LoginUser;
|
import com.unisbase.security.LoginUser;
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
|
@ -44,6 +48,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -53,7 +58,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/meetings")
|
@RequestMapping("/api/meetings")
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class LegacyMeetingController {
|
public class LegacyMeetingController {
|
||||||
|
|
||||||
private static final String STAGE_DATA_INITIALIZATION = "data_initialization";
|
private static final String STAGE_DATA_INITIALIZATION = "data_initialization";
|
||||||
|
|
@ -70,6 +75,55 @@ public class LegacyMeetingController {
|
||||||
private final PromptTemplateService promptTemplateService;
|
private final PromptTemplateService promptTemplateService;
|
||||||
private final MeetingTranscriptMapper meetingTranscriptMapper;
|
private final MeetingTranscriptMapper meetingTranscriptMapper;
|
||||||
private final SysUserMapper sysUserMapper;
|
private final SysUserMapper sysUserMapper;
|
||||||
|
private final StringRedisTemplate redisTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public LegacyMeetingController(LegacyMeetingAdapterService legacyMeetingAdapterService,
|
||||||
|
MeetingQueryService meetingQueryService,
|
||||||
|
MeetingAccessService meetingAccessService,
|
||||||
|
MeetingCommandService meetingCommandService,
|
||||||
|
MeetingService meetingService,
|
||||||
|
AiTaskService aiTaskService,
|
||||||
|
PromptTemplateService promptTemplateService,
|
||||||
|
MeetingTranscriptMapper meetingTranscriptMapper,
|
||||||
|
SysUserMapper sysUserMapper) {
|
||||||
|
this(legacyMeetingAdapterService,
|
||||||
|
meetingQueryService,
|
||||||
|
meetingAccessService,
|
||||||
|
meetingCommandService,
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
promptTemplateService,
|
||||||
|
meetingTranscriptMapper,
|
||||||
|
sysUserMapper,
|
||||||
|
null,
|
||||||
|
new ObjectMapper());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public LegacyMeetingController(LegacyMeetingAdapterService legacyMeetingAdapterService,
|
||||||
|
MeetingQueryService meetingQueryService,
|
||||||
|
MeetingAccessService meetingAccessService,
|
||||||
|
MeetingCommandService meetingCommandService,
|
||||||
|
MeetingService meetingService,
|
||||||
|
AiTaskService aiTaskService,
|
||||||
|
PromptTemplateService promptTemplateService,
|
||||||
|
MeetingTranscriptMapper meetingTranscriptMapper,
|
||||||
|
SysUserMapper sysUserMapper,
|
||||||
|
StringRedisTemplate redisTemplate,
|
||||||
|
ObjectMapper objectMapper) {
|
||||||
|
this.legacyMeetingAdapterService = legacyMeetingAdapterService;
|
||||||
|
this.meetingQueryService = meetingQueryService;
|
||||||
|
this.meetingAccessService = meetingAccessService;
|
||||||
|
this.meetingCommandService = meetingCommandService;
|
||||||
|
this.meetingService = meetingService;
|
||||||
|
this.aiTaskService = aiTaskService;
|
||||||
|
this.promptTemplateService = promptTemplateService;
|
||||||
|
this.meetingTranscriptMapper = meetingTranscriptMapper;
|
||||||
|
this.sysUserMapper = sysUserMapper;
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorize("isAuthenticated()")
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
|
@ -123,7 +177,7 @@ public class LegacyMeetingController {
|
||||||
data.setHasMore(page != null && page < data.getTotalPages());
|
data.setHasMore(page != null && page < data.getTotalPages());
|
||||||
data.setMeetings(result.getRecords() == null
|
data.setMeetings(result.getRecords() == null
|
||||||
? List.of()
|
? List.of()
|
||||||
: result.getRecords().stream().map(LegacyMeetingItemResponse::from).toList());
|
: result.getRecords().stream().map(this::buildListItem).toList());
|
||||||
return LegacyApiResponse.ok(data);
|
return LegacyApiResponse.ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,6 +236,13 @@ public class LegacyMeetingController {
|
||||||
buildProcessingPreview(meeting, summaryTask, processingStatus("摘要已生成,可扫码查看", 100, STAGE_COMPLETED))
|
buildProcessingPreview(meeting, summaryTask, processingStatus("摘要已生成,可扫码查看", 100, STAGE_COMPLETED))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
|
||||||
|
return new LegacyMeetingPreviewResult(
|
||||||
|
"503",
|
||||||
|
buildFailureMessage(asrTask, "转译"),
|
||||||
|
buildProcessingPreview(meeting, summaryTask, processingStatus("转译或总结失败", 50, STAGE_AUDIO_TRANSCRIPTION))
|
||||||
|
);
|
||||||
|
}
|
||||||
if (isFailed(summaryTask)) {
|
if (isFailed(summaryTask)) {
|
||||||
return new LegacyMeetingPreviewResult(
|
return new LegacyMeetingPreviewResult(
|
||||||
"503",
|
"503",
|
||||||
|
|
@ -189,24 +250,36 @@ public class LegacyMeetingController {
|
||||||
buildProcessingPreview(meeting, summaryTask, processingStatus("转译或总结失败", 75, STAGE_SUMMARY_GENERATION))
|
buildProcessingPreview(meeting, summaryTask, processingStatus("转译或总结失败", 75, STAGE_SUMMARY_GENERATION))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
|
|
||||||
return new LegacyMeetingPreviewResult(
|
Integer realtimeProgress = resolveRealtimeProgress(meetingId);
|
||||||
"503",
|
if (realtimeProgress != null) {
|
||||||
buildFailureMessage(asrTask, "转译"),
|
if (realtimeProgress < 90) {
|
||||||
buildProcessingPreview(meeting, summaryTask, processingStatus("转译或总结失败", 45, STAGE_AUDIO_TRANSCRIPTION))
|
return new LegacyMeetingPreviewResult(
|
||||||
);
|
"400",
|
||||||
|
"浼氳姝e湪澶勭悊涓?",
|
||||||
|
buildProcessingPreview(meeting, summaryTask, processingStatus("姝e湪杞瘧闊抽", 50, STAGE_AUDIO_TRANSCRIPTION))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (realtimeProgress == 90) {
|
||||||
|
return new LegacyMeetingPreviewResult(
|
||||||
|
"400",
|
||||||
|
"浼氳姝e湪澶勭悊涓?",
|
||||||
|
buildProcessingPreview(meeting, summaryTask, processingStatus("姝e湪鐢熸垚鎬荤粨", 75, STAGE_SUMMARY_GENERATION))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long transcriptCount = meetingTranscriptMapper.selectCount(new LambdaQueryWrapper<MeetingTranscript>()
|
boolean isSummaryStage = isSummaryStage(meeting.getStatus(), summaryTask);
|
||||||
.eq(MeetingTranscript::getMeetingId, meetingId));
|
boolean isAsrStage = isAsrStage(meeting.getStatus(), asrTask, hasAudio(meeting), isSummaryStage);
|
||||||
if (isRunningSummary(summaryTask) || transcriptCount > 0 || Integer.valueOf(2).equals(meeting.getStatus())) {
|
|
||||||
|
if (!isAsrStage && !isSummaryStage) {
|
||||||
return new LegacyMeetingPreviewResult(
|
return new LegacyMeetingPreviewResult(
|
||||||
"400",
|
"400",
|
||||||
"会议正在处理中",
|
"会议正在处理中",
|
||||||
buildProcessingPreview(meeting, summaryTask, processingStatus("正在生成总结", 75, STAGE_SUMMARY_GENERATION))
|
buildProcessingPreview(meeting, summaryTask, processingStatus("会议数据准备中", 25, STAGE_DATA_INITIALIZATION))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isRunningAsr(asrTask) || (meeting.getAudioUrl() != null && !meeting.getAudioUrl().isBlank()) || Integer.valueOf(1).equals(meeting.getStatus())) {
|
if (!isSummaryStage) {
|
||||||
return new LegacyMeetingPreviewResult(
|
return new LegacyMeetingPreviewResult(
|
||||||
"400",
|
"400",
|
||||||
"会议正在处理中",
|
"会议正在处理中",
|
||||||
|
|
@ -216,7 +289,7 @@ public class LegacyMeetingController {
|
||||||
return new LegacyMeetingPreviewResult(
|
return new LegacyMeetingPreviewResult(
|
||||||
"400",
|
"400",
|
||||||
"会议正在处理中",
|
"会议正在处理中",
|
||||||
buildProcessingPreview(meeting, summaryTask, processingStatus("会议数据准备中", 25, STAGE_DATA_INITIALIZATION))
|
buildProcessingPreview(meeting, summaryTask, processingStatus("正在生成总结", 75, STAGE_SUMMARY_GENERATION))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,9 +297,9 @@ public class LegacyMeetingController {
|
||||||
LegacyMeetingPreviewDataResponse data = new LegacyMeetingPreviewDataResponse();
|
LegacyMeetingPreviewDataResponse data = new LegacyMeetingPreviewDataResponse();
|
||||||
data.setMeetingId(meeting.getId());
|
data.setMeetingId(meeting.getId());
|
||||||
data.setTitle(meeting.getTitle());
|
data.setTitle(meeting.getTitle());
|
||||||
data.setMeetingTime(meeting.getMeetingTime() == null ? null : meeting.getMeetingTime().toString());
|
data.setMeetingTime(formatDateTime(meeting.getMeetingTime()));
|
||||||
data.setSummary(detail.getSummaryContent());
|
data.setSummary(detail.getSummaryContent());
|
||||||
data.setCreatorUsername(meeting.getCreatorName());
|
data.setCreatorUsername(resolveCreatorDisplayName(meeting.getCreatorId(), meeting.getCreatorName()));
|
||||||
Long promptId = resolvePromptId(summaryTask);
|
Long promptId = resolvePromptId(summaryTask);
|
||||||
data.setPromptId(promptId);
|
data.setPromptId(promptId);
|
||||||
data.setPromptName(resolvePromptName(promptId));
|
data.setPromptName(resolvePromptName(promptId));
|
||||||
|
|
@ -238,14 +311,39 @@ public class LegacyMeetingController {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LegacyMeetingItemResponse buildListItem(MeetingVO meeting) {
|
||||||
|
LegacyMeetingItemResponse item = new LegacyMeetingItemResponse();
|
||||||
|
item.setMeetingId(meeting.getId());
|
||||||
|
item.setTitle(meeting.getTitle());
|
||||||
|
item.setMeetingTime(formatDateTime(meeting.getMeetingTime()));
|
||||||
|
item.setCreatedAt(formatDateTime(meeting.getCreatedAt()));
|
||||||
|
item.setCreatorId(meeting.getCreatorId());
|
||||||
|
item.setCreatorUsername(resolveCreatorDisplayName(meeting.getCreatorId(), meeting.getCreatorName()));
|
||||||
|
item.setAudioFilePath(meeting.getAudioUrl());
|
||||||
|
item.setAudioDuration(meeting.getDuration());
|
||||||
|
item.setAccessPassword(resolveAccessPassword(meeting.getId()));
|
||||||
|
|
||||||
|
List<Long> attendeeIds = meeting.getParticipantIds() == null ? List.of() : meeting.getParticipantIds();
|
||||||
|
item.setAttendeeIds(attendeeIds);
|
||||||
|
item.setAttendees(buildAttendees(attendeeIds));
|
||||||
|
item.setTags(buildTags(meeting.getTags()));
|
||||||
|
item.setSummary(resolveListSummary(meeting.getId()));
|
||||||
|
|
||||||
|
LegacyMeetingProcessingStatusResponse status = buildListStatus(meeting);
|
||||||
|
item.setOverallStatus(status.getOverallStatus());
|
||||||
|
item.setOverallProgress(status.getOverallProgress());
|
||||||
|
item.setCurrentStage(translateListStage(status.getCurrentStage()));
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
private LegacyMeetingPreviewDataResponse buildProcessingPreview(Meeting meeting,
|
private LegacyMeetingPreviewDataResponse buildProcessingPreview(Meeting meeting,
|
||||||
AiTask summaryTask,
|
AiTask summaryTask,
|
||||||
LegacyMeetingProcessingStatusResponse status) {
|
LegacyMeetingProcessingStatusResponse status) {
|
||||||
LegacyMeetingPreviewDataResponse data = new LegacyMeetingPreviewDataResponse();
|
LegacyMeetingPreviewDataResponse data = new LegacyMeetingPreviewDataResponse();
|
||||||
data.setMeetingId(meeting.getId());
|
data.setMeetingId(meeting.getId());
|
||||||
data.setTitle(meeting.getTitle());
|
data.setTitle(meeting.getTitle());
|
||||||
data.setMeetingTime(meeting.getMeetingTime() == null ? null : meeting.getMeetingTime().toString());
|
data.setMeetingTime(formatDateTime(meeting.getMeetingTime()));
|
||||||
data.setCreatorUsername(meeting.getCreatorName());
|
data.setCreatorUsername(resolveCreatorDisplayName(meeting.getCreatorId(), meeting.getCreatorName()));
|
||||||
Long promptId = resolvePromptId(summaryTask);
|
Long promptId = resolvePromptId(summaryTask);
|
||||||
data.setPromptId(promptId);
|
data.setPromptId(promptId);
|
||||||
data.setPromptName(resolvePromptName(promptId));
|
data.setPromptName(resolvePromptName(promptId));
|
||||||
|
|
@ -258,6 +356,50 @@ public class LegacyMeetingController {
|
||||||
return new LegacyMeetingProcessingStatusResponse(overallStatus, overallProgress, currentStage);
|
return new LegacyMeetingProcessingStatusResponse(overallStatus, overallProgress, currentStage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Integer resolveRealtimeProgress(Long meetingId) {
|
||||||
|
if (redisTemplate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String rawProgress = redisTemplate.opsForValue().get(RedisKeys.meetingProgressKey(meetingId));
|
||||||
|
if (rawProgress == null || rawProgress.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonNode progress = objectMapper.readTree(rawProgress);
|
||||||
|
return progress.hasNonNull("percent") ? progress.path("percent").asInt() : null;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LegacyMeetingProcessingStatusResponse buildListStatus(MeetingVO meeting) {
|
||||||
|
Long meetingId = meeting.getId();
|
||||||
|
AiTask asrTask = findLatestTask(meetingId, "ASR");
|
||||||
|
AiTask summaryTask = findLatestTask(meetingId, "SUMMARY");
|
||||||
|
boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus());
|
||||||
|
|
||||||
|
if (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) {
|
||||||
|
return new LegacyMeetingProcessingStatusResponse("completed", 100, STAGE_COMPLETED);
|
||||||
|
}
|
||||||
|
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
|
||||||
|
return new LegacyMeetingProcessingStatusResponse("failed", 50, STAGE_AUDIO_TRANSCRIPTION);
|
||||||
|
}
|
||||||
|
if (isFailed(summaryTask)) {
|
||||||
|
return new LegacyMeetingProcessingStatusResponse("failed", 75, STAGE_SUMMARY_GENERATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isSummaryStage = isSummaryStage(meeting.getStatus(), summaryTask);
|
||||||
|
boolean isAsrStage = isAsrStage(meeting.getStatus(), asrTask, hasAudio(meeting), isSummaryStage);
|
||||||
|
|
||||||
|
if (!isAsrStage && !isSummaryStage) {
|
||||||
|
return new LegacyMeetingProcessingStatusResponse("pending", 0, STAGE_DATA_INITIALIZATION);
|
||||||
|
}
|
||||||
|
if (isSummaryStage) {
|
||||||
|
return new LegacyMeetingProcessingStatusResponse("summarizing", 75, STAGE_SUMMARY_GENERATION);
|
||||||
|
}
|
||||||
|
return new LegacyMeetingProcessingStatusResponse("transcribing", 50, STAGE_AUDIO_TRANSCRIPTION);
|
||||||
|
}
|
||||||
|
|
||||||
private String buildFailureMessage(AiTask failedTask, String stageName) {
|
private String buildFailureMessage(AiTask failedTask, String stageName) {
|
||||||
String error = failedTask == null || failedTask.getErrorMsg() == null || failedTask.getErrorMsg().isBlank()
|
String error = failedTask == null || failedTask.getErrorMsg() == null || failedTask.getErrorMsg().isBlank()
|
||||||
? "处理失败"
|
? "处理失败"
|
||||||
|
|
@ -316,8 +458,11 @@ public class LegacyMeetingController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LegacyMeetingAttendeeResponse> buildAttendees(String participants) {
|
private List<LegacyMeetingAttendeeResponse> buildAttendees(String participants) {
|
||||||
List<Long> participantIds = parseParticipantIds(participants);
|
return buildAttendees(parseParticipantIds(participants));
|
||||||
if (participantIds.isEmpty()) {
|
}
|
||||||
|
|
||||||
|
private List<LegacyMeetingAttendeeResponse> buildAttendees(List<Long> participantIds) {
|
||||||
|
if (participantIds == null || participantIds.isEmpty()) {
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
Map<Long, SysUser> userMap = sysUserMapper.selectBatchIds(participantIds).stream()
|
Map<Long, SysUser> userMap = sysUserMapper.selectBatchIds(participantIds).stream()
|
||||||
|
|
@ -329,11 +474,23 @@ public class LegacyMeetingController {
|
||||||
String caption = user == null
|
String caption = user == null
|
||||||
? String.valueOf(userId)
|
? String.valueOf(userId)
|
||||||
: (user.getDisplayName() != null ? user.getDisplayName() : user.getUsername());
|
: (user.getDisplayName() != null ? user.getDisplayName() : user.getUsername());
|
||||||
return new LegacyMeetingAttendeeResponse(userId, caption);
|
String username = user == null ? null : user.getUsername();
|
||||||
|
return new LegacyMeetingAttendeeResponse(userId, username, caption);
|
||||||
})
|
})
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<LegacyMeetingTagResponse> buildTags(String rawTags) {
|
||||||
|
if (rawTags == null || rawTags.isBlank()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return Arrays.stream(rawTags.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(value -> !value.isEmpty())
|
||||||
|
.map(value -> new LegacyMeetingTagResponse(null, value))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
private List<Long> parseParticipantIds(String participants) {
|
private List<Long> parseParticipantIds(String participants) {
|
||||||
if (participants == null || participants.isBlank()) {
|
if (participants == null || participants.isBlank()) {
|
||||||
return List.of();
|
return List.of();
|
||||||
|
|
@ -360,6 +517,69 @@ public class LegacyMeetingController {
|
||||||
return normalized.isEmpty() ? null : normalized;
|
return normalized.isEmpty() ? null : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveListSummary(Long meetingId) {
|
||||||
|
MeetingVO detail = meetingQueryService.getDetail(meetingId);
|
||||||
|
if (detail == null || detail.getSummaryContent() == null || detail.getSummaryContent().isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String summary = detail.getSummaryContent().trim();
|
||||||
|
return summary.length() <= 240 ? summary : summary.substring(0, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveAccessPassword(Long meetingId) {
|
||||||
|
Meeting meeting = meetingService.getById(meetingId);
|
||||||
|
return meeting == null ? null : normalizePassword(meeting.getAccessPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveCreatorDisplayName(Long creatorId, String fallbackName) {
|
||||||
|
if (creatorId == null) {
|
||||||
|
return fallbackName;
|
||||||
|
}
|
||||||
|
SysUser creator = sysUserMapper.selectById(creatorId);
|
||||||
|
if (creator == null) {
|
||||||
|
return fallbackName;
|
||||||
|
}
|
||||||
|
if (creator.getDisplayName() != null && !creator.getDisplayName().isBlank()) {
|
||||||
|
return creator.getDisplayName();
|
||||||
|
}
|
||||||
|
if (creator.getUsername() != null && !creator.getUsername().isBlank()) {
|
||||||
|
return creator.getUsername();
|
||||||
|
}
|
||||||
|
return fallbackName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasAudio(Meeting meeting) {
|
||||||
|
return meeting.getAudioUrl() != null && !meeting.getAudioUrl().isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasAudio(MeetingVO meeting) {
|
||||||
|
return meeting.getAudioUrl() != null && !meeting.getAudioUrl().isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSummaryStage(Integer meetingStatus, AiTask summaryTask) {
|
||||||
|
return Integer.valueOf(2).equals(meetingStatus) || isRunningSummary(summaryTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAsrStage(Integer meetingStatus, AiTask asrTask, boolean hasAudio, boolean isSummaryStage) {
|
||||||
|
return Integer.valueOf(1).equals(meetingStatus)
|
||||||
|
|| isRunningAsr(asrTask)
|
||||||
|
|| (hasAudio && !isSummaryStage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatDateTime(LocalDateTime value) {
|
||||||
|
return value == null ? null : value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String translateListStage(String stage) {
|
||||||
|
if (STAGE_SUMMARY_GENERATION.equals(stage)) {
|
||||||
|
return "llm";
|
||||||
|
}
|
||||||
|
if (STAGE_COMPLETED.equals(stage)) {
|
||||||
|
return "completed";
|
||||||
|
}
|
||||||
|
return "transcription";
|
||||||
|
}
|
||||||
|
|
||||||
private LoginUser currentLoginUser() {
|
private LoginUser currentLoginUser() {
|
||||||
return (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
return (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.imeeting.entity.biz.ExternalApp;
|
import com.imeeting.entity.biz.ExternalApp;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class LegacyExternalAppItemResponse {
|
public class LegacyExternalAppItemResponse {
|
||||||
|
|
@ -47,7 +51,7 @@ public class LegacyExternalAppItemResponse {
|
||||||
response.setId(source.getId());
|
response.setId(source.getId());
|
||||||
response.setAppName(source.getAppName());
|
response.setAppName(source.getAppName());
|
||||||
response.setAppType(source.getAppType());
|
response.setAppType(source.getAppType());
|
||||||
response.setAppInfo(source.getAppInfo());
|
response.setAppInfo(normalizeAppInfo(source.getAppInfo()));
|
||||||
response.setIconUrl(source.getIconUrl());
|
response.setIconUrl(source.getIconUrl());
|
||||||
response.setDescription(source.getDescription());
|
response.setDescription(source.getDescription());
|
||||||
response.setSortOrder(source.getSortOrder());
|
response.setSortOrder(source.getSortOrder());
|
||||||
|
|
@ -58,4 +62,40 @@ public class LegacyExternalAppItemResponse {
|
||||||
response.setCreatorUsername(creatorUsername);
|
response.setCreatorUsername(creatorUsername);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> normalizeAppInfo(Map<String, Object> appInfo) {
|
||||||
|
if (appInfo == null || appInfo.isEmpty()) {
|
||||||
|
return appInfo;
|
||||||
|
}
|
||||||
|
Map<String, Object> normalized = new LinkedHashMap<>();
|
||||||
|
appInfo.forEach((key, value) -> normalized.put(toSnakeCase(key), normalizeValue(value)));
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object normalizeValue(Object value) {
|
||||||
|
if (value instanceof Map<?, ?> nestedMap) {
|
||||||
|
Map<String, Object> normalized = new LinkedHashMap<>();
|
||||||
|
nestedMap.forEach((key, nestedValue) -> normalized.put(toSnakeCase(String.valueOf(key)), normalizeValue(nestedValue)));
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
if (value instanceof List<?> list) {
|
||||||
|
List<Object> normalized = new ArrayList<>(list.size());
|
||||||
|
list.forEach(item -> normalized.add(normalizeValue(item)));
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toSnakeCase(String value) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
.replace('-', '_')
|
||||||
|
.replace(' ', '_')
|
||||||
|
.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2")
|
||||||
|
.replaceAll("([a-z0-9])([A-Z])", "$1_$2")
|
||||||
|
.replaceAll("_+", "_")
|
||||||
|
.toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,7 @@ public class LegacyMeetingAttendeeResponse {
|
||||||
@JsonProperty("user_id")
|
@JsonProperty("user_id")
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
|
||||||
private String caption;
|
private String caption;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package com.imeeting.dto.android.legacy;
|
package com.imeeting.dto.android.legacy;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.imeeting.dto.biz.MeetingVO;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class LegacyMeetingItemResponse {
|
public class LegacyMeetingItemResponse {
|
||||||
@JsonProperty("meeting_id")
|
@JsonProperty("meeting_id")
|
||||||
|
|
@ -14,11 +15,39 @@ public class LegacyMeetingItemResponse {
|
||||||
@JsonProperty("meeting_time")
|
@JsonProperty("meeting_time")
|
||||||
private String meetingTime;
|
private String meetingTime;
|
||||||
|
|
||||||
public static LegacyMeetingItemResponse from(MeetingVO source) {
|
private String summary;
|
||||||
LegacyMeetingItemResponse response = new LegacyMeetingItemResponse();
|
|
||||||
response.setMeetingId(source.getId());
|
@JsonProperty("created_at")
|
||||||
response.setTitle(source.getTitle());
|
private String createdAt;
|
||||||
response.setMeetingTime(source.getMeetingTime() == null ? null : source.getMeetingTime().toString());
|
|
||||||
return response;
|
@JsonProperty("creator_id")
|
||||||
}
|
private Long creatorId;
|
||||||
|
|
||||||
|
@JsonProperty("creator_username")
|
||||||
|
private String creatorUsername;
|
||||||
|
|
||||||
|
private List<LegacyMeetingAttendeeResponse> attendees;
|
||||||
|
|
||||||
|
@JsonProperty("attendee_ids")
|
||||||
|
private List<Long> attendeeIds;
|
||||||
|
|
||||||
|
private List<LegacyMeetingTagResponse> tags;
|
||||||
|
|
||||||
|
@JsonProperty("audio_file_path")
|
||||||
|
private String audioFilePath;
|
||||||
|
|
||||||
|
@JsonProperty("audio_duration")
|
||||||
|
private Integer audioDuration;
|
||||||
|
|
||||||
|
@JsonProperty("overall_status")
|
||||||
|
private String overallStatus;
|
||||||
|
|
||||||
|
@JsonProperty("overall_progress")
|
||||||
|
private Integer overallProgress;
|
||||||
|
|
||||||
|
@JsonProperty("current_stage")
|
||||||
|
private String currentStage;
|
||||||
|
|
||||||
|
@JsonProperty("access_password")
|
||||||
|
private String accessPassword;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
package com.imeeting.controller.android.legacy;
|
package com.imeeting.controller.android.legacy;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.imeeting.dto.android.legacy.LegacyApiResponse;
|
import com.imeeting.dto.android.legacy.LegacyApiResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordRequest;
|
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordRequest;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordResponse;
|
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordResponse;
|
||||||
|
import com.imeeting.dto.android.legacy.LegacyMeetingItemResponse;
|
||||||
|
import com.imeeting.dto.android.legacy.LegacyMeetingListResponse;
|
||||||
|
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewDataResponse;
|
||||||
import com.imeeting.dto.biz.MeetingVO;
|
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;
|
||||||
|
|
@ -15,11 +19,14 @@ import com.imeeting.service.biz.MeetingCommandService;
|
||||||
import com.imeeting.service.biz.MeetingQueryService;
|
import com.imeeting.service.biz.MeetingQueryService;
|
||||||
import com.imeeting.service.biz.MeetingService;
|
import com.imeeting.service.biz.MeetingService;
|
||||||
import com.imeeting.service.biz.PromptTemplateService;
|
import com.imeeting.service.biz.PromptTemplateService;
|
||||||
|
import com.unisbase.dto.PageResult;
|
||||||
import com.unisbase.entity.SysUser;
|
import com.unisbase.entity.SysUser;
|
||||||
import com.unisbase.mapper.SysUserMapper;
|
import com.unisbase.mapper.SysUserMapper;
|
||||||
import com.unisbase.security.LoginUser;
|
import com.unisbase.security.LoginUser;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.ValueOperations;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
|
|
@ -53,9 +60,10 @@ class LegacyMeetingControllerTest {
|
||||||
|
|
||||||
Meeting meeting = new Meeting();
|
Meeting meeting = new Meeting();
|
||||||
meeting.setId(8L);
|
meeting.setId(8L);
|
||||||
meeting.setTitle("项目复盘");
|
meeting.setTitle("retro");
|
||||||
meeting.setMeetingTime(LocalDateTime.of(2026, 4, 13, 10, 0));
|
meeting.setMeetingTime(LocalDateTime.of(2026, 4, 13, 10, 0));
|
||||||
meeting.setCreatorName("发起人");
|
meeting.setCreatorId(7L);
|
||||||
|
meeting.setCreatorName("owner");
|
||||||
meeting.setParticipants("2,3");
|
meeting.setParticipants("2,3");
|
||||||
meeting.setAccessPassword("123456");
|
meeting.setAccessPassword("123456");
|
||||||
meeting.setStatus(3);
|
meeting.setStatus(3);
|
||||||
|
|
@ -63,23 +71,30 @@ class LegacyMeetingControllerTest {
|
||||||
|
|
||||||
AiTask summaryTask = new AiTask();
|
AiTask summaryTask = new AiTask();
|
||||||
summaryTask.setTaskConfig(Map.of("promptId", 5L));
|
summaryTask.setTaskConfig(Map.of("promptId", 5L));
|
||||||
when(aiTaskService.getOne(any())).thenReturn(null, summaryTask);
|
when(aiTaskService.getOne(any())).thenReturn((AiTask) null, summaryTask);
|
||||||
|
|
||||||
MeetingVO detail = new MeetingVO();
|
MeetingVO detail = new MeetingVO();
|
||||||
detail.setSummaryContent("## 总结\n已完成");
|
detail.setSummaryContent("done");
|
||||||
when(meetingQueryService.getDetail(8L)).thenReturn(detail);
|
when(meetingQueryService.getDetail(8L)).thenReturn(detail);
|
||||||
|
|
||||||
PromptTemplate template = new PromptTemplate();
|
PromptTemplate template = new PromptTemplate();
|
||||||
template.setTemplateName("标准纪要");
|
template.setTemplateName("standard");
|
||||||
when(promptTemplateService.getById(5L)).thenReturn(template);
|
when(promptTemplateService.getById(5L)).thenReturn(template);
|
||||||
|
|
||||||
SysUser user2 = new SysUser();
|
SysUser user2 = new SysUser();
|
||||||
user2.setUserId(2L);
|
user2.setUserId(2L);
|
||||||
user2.setDisplayName("张三");
|
user2.setUsername("alice");
|
||||||
|
user2.setDisplayName("Alice");
|
||||||
SysUser user3 = new SysUser();
|
SysUser user3 = new SysUser();
|
||||||
user3.setUserId(3L);
|
user3.setUserId(3L);
|
||||||
user3.setDisplayName("李四");
|
user3.setUsername("bob");
|
||||||
|
user3.setDisplayName("Bob");
|
||||||
when(sysUserMapper.selectBatchIds(List.of(2L, 3L))).thenReturn(List.of(user2, user3));
|
when(sysUserMapper.selectBatchIds(List.of(2L, 3L))).thenReturn(List.of(user2, user3));
|
||||||
|
SysUser creator = new SysUser();
|
||||||
|
creator.setUserId(7L);
|
||||||
|
creator.setUsername("owner-login");
|
||||||
|
creator.setDisplayName("Owner Display");
|
||||||
|
when(sysUserMapper.selectById(7L)).thenReturn(creator);
|
||||||
|
|
||||||
LegacyMeetingController controller = new LegacyMeetingController(
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
mock(LegacyMeetingAdapterService.class),
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
|
@ -99,6 +114,388 @@ class LegacyMeetingControllerTest {
|
||||||
assertNotNull(response.getData());
|
assertNotNull(response.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listShouldReturnLegacyMeetingPayloadAlignedWithPythonResponse() {
|
||||||
|
MeetingQueryService meetingQueryService = mock(MeetingQueryService.class);
|
||||||
|
MeetingService meetingService = mock(MeetingService.class);
|
||||||
|
AiTaskService aiTaskService = mock(AiTaskService.class);
|
||||||
|
MeetingTranscriptMapper transcriptMapper = mock(MeetingTranscriptMapper.class);
|
||||||
|
SysUserMapper sysUserMapper = mock(SysUserMapper.class);
|
||||||
|
|
||||||
|
MeetingVO meeting = new MeetingVO();
|
||||||
|
meeting.setId(18L);
|
||||||
|
meeting.setTitle("weekly");
|
||||||
|
meeting.setMeetingTime(LocalDateTime.of(2026, 4, 14, 9, 30));
|
||||||
|
meeting.setCreatedAt(LocalDateTime.of(2026, 4, 14, 10, 0));
|
||||||
|
meeting.setCreatorId(7L);
|
||||||
|
meeting.setCreatorName("owner");
|
||||||
|
meeting.setParticipantIds(List.of(2L, 3L));
|
||||||
|
meeting.setTags("dev,weekly");
|
||||||
|
meeting.setAudioUrl("/api/static/meetings/18/source_audio.wav");
|
||||||
|
meeting.setDuration(366);
|
||||||
|
meeting.setStatus(3);
|
||||||
|
|
||||||
|
PageResult<List<MeetingVO>> pageResult = new PageResult<>();
|
||||||
|
pageResult.setTotal(1L);
|
||||||
|
pageResult.setRecords(List.of(meeting));
|
||||||
|
when(meetingQueryService.pageMeetings(any(), any(), any(), any(), any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean())).thenReturn(pageResult);
|
||||||
|
|
||||||
|
MeetingVO detail = new MeetingVO();
|
||||||
|
detail.setSummaryContent("summary");
|
||||||
|
when(meetingQueryService.getDetail(18L)).thenReturn(detail);
|
||||||
|
|
||||||
|
Meeting entity = new Meeting();
|
||||||
|
entity.setId(18L);
|
||||||
|
entity.setAccessPassword("123456");
|
||||||
|
when(meetingService.getById(18L)).thenReturn(entity);
|
||||||
|
|
||||||
|
SysUser user2 = new SysUser();
|
||||||
|
user2.setUserId(2L);
|
||||||
|
user2.setUsername("alice");
|
||||||
|
user2.setDisplayName("Alice");
|
||||||
|
SysUser user3 = new SysUser();
|
||||||
|
user3.setUserId(3L);
|
||||||
|
user3.setUsername("bob");
|
||||||
|
user3.setDisplayName("Bob");
|
||||||
|
when(sysUserMapper.selectBatchIds(List.of(2L, 3L))).thenReturn(List.of(user2, user3));
|
||||||
|
SysUser creator = new SysUser();
|
||||||
|
creator.setUserId(7L);
|
||||||
|
creator.setUsername("owner-login");
|
||||||
|
creator.setDisplayName("Owner Display");
|
||||||
|
when(sysUserMapper.selectById(7L)).thenReturn(creator);
|
||||||
|
when(aiTaskService.getOne(any())).thenReturn((AiTask) null, (AiTask) null);
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(new LoginUser(7L, 1L, "creator", false, false, Set.of()), null)
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
meetingQueryService,
|
||||||
|
mock(MeetingAccessService.class),
|
||||||
|
mock(MeetingCommandService.class),
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
mock(PromptTemplateService.class),
|
||||||
|
transcriptMapper,
|
||||||
|
sysUserMapper
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyApiResponse<LegacyMeetingListResponse> response = controller.list(null, 1, 10, null);
|
||||||
|
|
||||||
|
assertEquals("200", response.getCode());
|
||||||
|
assertNotNull(response.getData());
|
||||||
|
assertEquals(1L, response.getData().getTotal());
|
||||||
|
assertEquals(1, response.getData().getMeetings().size());
|
||||||
|
|
||||||
|
LegacyMeetingItemResponse item = response.getData().getMeetings().get(0);
|
||||||
|
assertEquals(18L, item.getMeetingId());
|
||||||
|
assertEquals("weekly", item.getTitle());
|
||||||
|
assertEquals("summary", item.getSummary());
|
||||||
|
assertEquals(7L, item.getCreatorId());
|
||||||
|
assertEquals("Owner Display", item.getCreatorUsername());
|
||||||
|
assertEquals("/api/static/meetings/18/source_audio.wav", item.getAudioFilePath());
|
||||||
|
assertEquals(366, item.getAudioDuration());
|
||||||
|
assertEquals("123456", item.getAccessPassword());
|
||||||
|
assertEquals("completed", item.getOverallStatus());
|
||||||
|
assertEquals(100, item.getOverallProgress());
|
||||||
|
assertEquals("completed", item.getCurrentStage());
|
||||||
|
assertEquals(List.of(2L, 3L), item.getAttendeeIds());
|
||||||
|
assertEquals(2, item.getAttendees().size());
|
||||||
|
assertEquals("alice", item.getAttendees().get(0).getUsername());
|
||||||
|
assertEquals("Alice", item.getAttendees().get(0).getCaption());
|
||||||
|
assertEquals(2, item.getTags().size());
|
||||||
|
assertEquals("dev", item.getTags().get(0).getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listShouldPreferTranscriptionStageBeforeSummaryStage() {
|
||||||
|
MeetingQueryService meetingQueryService = mock(MeetingQueryService.class);
|
||||||
|
MeetingService meetingService = mock(MeetingService.class);
|
||||||
|
AiTaskService aiTaskService = mock(AiTaskService.class);
|
||||||
|
MeetingTranscriptMapper transcriptMapper = mock(MeetingTranscriptMapper.class);
|
||||||
|
|
||||||
|
MeetingVO meeting = new MeetingVO();
|
||||||
|
meeting.setId(19L);
|
||||||
|
meeting.setTitle("status ordering");
|
||||||
|
meeting.setAudioUrl("/tmp/audio.wav");
|
||||||
|
meeting.setStatus(0);
|
||||||
|
|
||||||
|
PageResult<List<MeetingVO>> pageResult = new PageResult<>();
|
||||||
|
pageResult.setTotal(1L);
|
||||||
|
pageResult.setRecords(List.of(meeting));
|
||||||
|
when(meetingQueryService.pageMeetings(any(), any(), any(), any(), any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean())).thenReturn(pageResult);
|
||||||
|
when(meetingQueryService.getDetail(19L)).thenReturn(new MeetingVO());
|
||||||
|
when(meetingService.getById(19L)).thenReturn(new Meeting());
|
||||||
|
when(aiTaskService.getOne(any())).thenReturn((AiTask) null, (AiTask) null);
|
||||||
|
when(transcriptMapper.selectCount(any())).thenReturn(2L);
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(new LoginUser(7L, 1L, "creator", false, false, Set.of()), null)
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
meetingQueryService,
|
||||||
|
mock(MeetingAccessService.class),
|
||||||
|
mock(MeetingCommandService.class),
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
mock(PromptTemplateService.class),
|
||||||
|
transcriptMapper,
|
||||||
|
mock(SysUserMapper.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyApiResponse<LegacyMeetingListResponse> response = controller.list(null, 1, 10, null);
|
||||||
|
|
||||||
|
assertEquals("200", response.getCode());
|
||||||
|
assertNotNull(response.getData());
|
||||||
|
assertEquals(1, response.getData().getMeetings().size());
|
||||||
|
LegacyMeetingItemResponse item = response.getData().getMeetings().get(0);
|
||||||
|
assertEquals("transcribing", item.getOverallStatus());
|
||||||
|
assertEquals(50, item.getOverallProgress());
|
||||||
|
assertEquals("transcription", item.getCurrentStage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void previewDataShouldReportAsrFailureAtFiftyPercent() {
|
||||||
|
MeetingService meetingService = mock(MeetingService.class);
|
||||||
|
AiTaskService aiTaskService = mock(AiTaskService.class);
|
||||||
|
MeetingQueryService meetingQueryService = mock(MeetingQueryService.class);
|
||||||
|
MeetingTranscriptMapper transcriptMapper = mock(MeetingTranscriptMapper.class);
|
||||||
|
|
||||||
|
Meeting meeting = new Meeting();
|
||||||
|
meeting.setId(20L);
|
||||||
|
meeting.setTitle("asr failed");
|
||||||
|
meeting.setStatus(4);
|
||||||
|
when(meetingService.getById(20L)).thenReturn(meeting);
|
||||||
|
|
||||||
|
AiTask asrTask = new AiTask();
|
||||||
|
asrTask.setStatus(3);
|
||||||
|
asrTask.setErrorMsg("asr failed");
|
||||||
|
when(aiTaskService.getOne(any())).thenReturn(asrTask, (AiTask) null);
|
||||||
|
|
||||||
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
meetingQueryService,
|
||||||
|
mock(MeetingAccessService.class),
|
||||||
|
mock(MeetingCommandService.class),
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
mock(PromptTemplateService.class),
|
||||||
|
transcriptMapper,
|
||||||
|
mock(SysUserMapper.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyApiResponse<?> response = controller.previewData(20L);
|
||||||
|
|
||||||
|
assertEquals("503", response.getCode());
|
||||||
|
LegacyMeetingPreviewDataResponse data = (LegacyMeetingPreviewDataResponse) response.getData();
|
||||||
|
assertNotNull(data);
|
||||||
|
assertEquals(50, data.getProcessingStatus().getOverallProgress());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void previewDataShouldPrioritizeAsrFailureBeforeSummaryFailure() {
|
||||||
|
MeetingService meetingService = mock(MeetingService.class);
|
||||||
|
AiTaskService aiTaskService = mock(AiTaskService.class);
|
||||||
|
|
||||||
|
Meeting meeting = new Meeting();
|
||||||
|
meeting.setId(23L);
|
||||||
|
meeting.setTitle("both failed");
|
||||||
|
when(meetingService.getById(23L)).thenReturn(meeting);
|
||||||
|
|
||||||
|
AiTask asrTask = new AiTask();
|
||||||
|
asrTask.setStatus(3);
|
||||||
|
asrTask.setErrorMsg("asr failed");
|
||||||
|
AiTask summaryTask = new AiTask();
|
||||||
|
summaryTask.setStatus(3);
|
||||||
|
summaryTask.setErrorMsg("summary failed");
|
||||||
|
when(aiTaskService.getOne(any())).thenReturn(asrTask, summaryTask);
|
||||||
|
|
||||||
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
mock(MeetingQueryService.class),
|
||||||
|
mock(MeetingAccessService.class),
|
||||||
|
mock(MeetingCommandService.class),
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
mock(PromptTemplateService.class),
|
||||||
|
mock(MeetingTranscriptMapper.class),
|
||||||
|
mock(SysUserMapper.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyApiResponse<?> response = controller.previewData(23L);
|
||||||
|
|
||||||
|
assertEquals("503", response.getCode());
|
||||||
|
LegacyMeetingPreviewDataResponse data = (LegacyMeetingPreviewDataResponse) response.getData();
|
||||||
|
assertNotNull(data);
|
||||||
|
assertEquals(50, data.getProcessingStatus().getOverallProgress());
|
||||||
|
assertEquals("audio_transcription", data.getProcessingStatus().getCurrentStage());
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void previewDataShouldPreferTranscriptionStageBeforeSummaryStage() {
|
||||||
|
MeetingService meetingService = mock(MeetingService.class);
|
||||||
|
AiTaskService aiTaskService = mock(AiTaskService.class);
|
||||||
|
MeetingTranscriptMapper transcriptMapper = mock(MeetingTranscriptMapper.class);
|
||||||
|
|
||||||
|
Meeting meeting = new Meeting();
|
||||||
|
meeting.setId(22L);
|
||||||
|
meeting.setTitle("transcribing first");
|
||||||
|
meeting.setStatus(0);
|
||||||
|
meeting.setAudioUrl("/tmp/audio.wav");
|
||||||
|
when(meetingService.getById(22L)).thenReturn(meeting);
|
||||||
|
|
||||||
|
when(aiTaskService.getOne(any())).thenReturn((AiTask) null, (AiTask) null);
|
||||||
|
when(transcriptMapper.selectCount(any())).thenReturn(3L);
|
||||||
|
|
||||||
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
mock(MeetingQueryService.class),
|
||||||
|
mock(MeetingAccessService.class),
|
||||||
|
mock(MeetingCommandService.class),
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
mock(PromptTemplateService.class),
|
||||||
|
transcriptMapper,
|
||||||
|
mock(SysUserMapper.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyApiResponse<?> response = controller.previewData(22L);
|
||||||
|
|
||||||
|
assertEquals("400", response.getCode());
|
||||||
|
LegacyMeetingPreviewDataResponse data = (LegacyMeetingPreviewDataResponse) response.getData();
|
||||||
|
assertNotNull(data);
|
||||||
|
assertEquals(50, data.getProcessingStatus().getOverallProgress());
|
||||||
|
assertEquals("audio_transcription", data.getProcessingStatus().getCurrentStage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void previewDataShouldReportSummaryStageAtSeventyFivePercent() {
|
||||||
|
MeetingService meetingService = mock(MeetingService.class);
|
||||||
|
AiTaskService aiTaskService = mock(AiTaskService.class);
|
||||||
|
MeetingTranscriptMapper transcriptMapper = mock(MeetingTranscriptMapper.class);
|
||||||
|
|
||||||
|
Meeting meeting = new Meeting();
|
||||||
|
meeting.setId(21L);
|
||||||
|
meeting.setTitle("summary running");
|
||||||
|
meeting.setStatus(2);
|
||||||
|
when(meetingService.getById(21L)).thenReturn(meeting);
|
||||||
|
|
||||||
|
AiTask summaryTask = new AiTask();
|
||||||
|
summaryTask.setStatus(1);
|
||||||
|
when(aiTaskService.getOne(any())).thenReturn((AiTask) null, summaryTask);
|
||||||
|
when(transcriptMapper.selectCount(any())).thenReturn(0L);
|
||||||
|
|
||||||
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
mock(MeetingQueryService.class),
|
||||||
|
mock(MeetingAccessService.class),
|
||||||
|
mock(MeetingCommandService.class),
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
mock(PromptTemplateService.class),
|
||||||
|
transcriptMapper,
|
||||||
|
mock(SysUserMapper.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyApiResponse<?> response = controller.previewData(21L);
|
||||||
|
|
||||||
|
assertEquals("400", response.getCode());
|
||||||
|
LegacyMeetingPreviewDataResponse data = (LegacyMeetingPreviewDataResponse) response.getData();
|
||||||
|
assertNotNull(data);
|
||||||
|
assertEquals(75, data.getProcessingStatus().getOverallProgress());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void previewDataShouldReportSummaryStageAtSeventyFivePercentWhenAudioExists() {
|
||||||
|
MeetingService meetingService = mock(MeetingService.class);
|
||||||
|
AiTaskService aiTaskService = mock(AiTaskService.class);
|
||||||
|
MeetingTranscriptMapper transcriptMapper = mock(MeetingTranscriptMapper.class);
|
||||||
|
|
||||||
|
Meeting meeting = new Meeting();
|
||||||
|
meeting.setId(24L);
|
||||||
|
meeting.setTitle("summary running with audio");
|
||||||
|
meeting.setStatus(2);
|
||||||
|
meeting.setAudioUrl("/tmp/audio.wav");
|
||||||
|
when(meetingService.getById(24L)).thenReturn(meeting);
|
||||||
|
|
||||||
|
AiTask summaryTask = new AiTask();
|
||||||
|
summaryTask.setStatus(1);
|
||||||
|
when(aiTaskService.getOne(any())).thenReturn((AiTask) null, summaryTask);
|
||||||
|
when(transcriptMapper.selectCount(any())).thenReturn(3L);
|
||||||
|
|
||||||
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
mock(MeetingQueryService.class),
|
||||||
|
mock(MeetingAccessService.class),
|
||||||
|
mock(MeetingCommandService.class),
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
mock(PromptTemplateService.class),
|
||||||
|
transcriptMapper,
|
||||||
|
mock(SysUserMapper.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyApiResponse<?> response = controller.previewData(24L);
|
||||||
|
|
||||||
|
assertEquals("400", response.getCode());
|
||||||
|
LegacyMeetingPreviewDataResponse data = (LegacyMeetingPreviewDataResponse) response.getData();
|
||||||
|
assertNotNull(data);
|
||||||
|
assertEquals(75, data.getProcessingStatus().getOverallProgress());
|
||||||
|
assertEquals("summary_generation", data.getProcessingStatus().getCurrentStage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listShouldReportSummaryStageAtSeventyFivePercentWhenAudioExists() {
|
||||||
|
MeetingQueryService meetingQueryService = mock(MeetingQueryService.class);
|
||||||
|
MeetingService meetingService = mock(MeetingService.class);
|
||||||
|
AiTaskService aiTaskService = mock(AiTaskService.class);
|
||||||
|
MeetingTranscriptMapper transcriptMapper = mock(MeetingTranscriptMapper.class);
|
||||||
|
|
||||||
|
MeetingVO meeting = new MeetingVO();
|
||||||
|
meeting.setId(25L);
|
||||||
|
meeting.setTitle("summary stage with audio");
|
||||||
|
meeting.setAudioUrl("/tmp/audio.wav");
|
||||||
|
meeting.setStatus(2);
|
||||||
|
|
||||||
|
PageResult<List<MeetingVO>> pageResult = new PageResult<>();
|
||||||
|
pageResult.setTotal(1L);
|
||||||
|
pageResult.setRecords(List.of(meeting));
|
||||||
|
when(meetingQueryService.pageMeetings(any(), any(), any(), any(), any(), any(), any(), org.mockito.ArgumentMatchers.anyBoolean())).thenReturn(pageResult);
|
||||||
|
|
||||||
|
AiTask summaryTask = new AiTask();
|
||||||
|
summaryTask.setStatus(1);
|
||||||
|
when(aiTaskService.getOne(any())).thenReturn((AiTask) null, summaryTask);
|
||||||
|
when(transcriptMapper.selectCount(any())).thenReturn(3L);
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(new LoginUser(7L, 1L, "creator", false, false, Set.of()), null)
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
meetingQueryService,
|
||||||
|
mock(MeetingAccessService.class),
|
||||||
|
mock(MeetingCommandService.class),
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
mock(PromptTemplateService.class),
|
||||||
|
transcriptMapper,
|
||||||
|
mock(SysUserMapper.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyApiResponse<LegacyMeetingListResponse> response = controller.list(null, 1, 10, null);
|
||||||
|
|
||||||
|
assertEquals("200", response.getCode());
|
||||||
|
assertNotNull(response.getData());
|
||||||
|
assertEquals(1, response.getData().getMeetings().size());
|
||||||
|
LegacyMeetingItemResponse item = response.getData().getMeetings().get(0);
|
||||||
|
assertEquals("summarizing", item.getOverallStatus());
|
||||||
|
assertEquals(75, item.getOverallProgress());
|
||||||
|
assertEquals("llm", item.getCurrentStage());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void updateAccessPasswordShouldOnlyAllowCreator() {
|
void updateAccessPasswordShouldOnlyAllowCreator() {
|
||||||
MeetingAccessService meetingAccessService = mock(MeetingAccessService.class);
|
MeetingAccessService meetingAccessService = mock(MeetingAccessService.class);
|
||||||
|
|
@ -135,4 +532,86 @@ class LegacyMeetingControllerTest {
|
||||||
assertEquals(null, meeting.getAccessPassword());
|
assertEquals(null, meeting.getAccessPassword());
|
||||||
verify(meetingService).updateById(meeting);
|
verify(meetingService).updateById(meeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void previewDataShouldTranslateRealtimeProgressBelowNinetyToTranscription() {
|
||||||
|
MeetingService meetingService = mock(MeetingService.class);
|
||||||
|
AiTaskService aiTaskService = mock(AiTaskService.class);
|
||||||
|
MeetingTranscriptMapper transcriptMapper = mock(MeetingTranscriptMapper.class);
|
||||||
|
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
|
||||||
|
|
||||||
|
Meeting meeting = new Meeting();
|
||||||
|
meeting.setId(26L);
|
||||||
|
meeting.setTitle("progress translating");
|
||||||
|
meeting.setStatus(0);
|
||||||
|
when(meetingService.getById(26L)).thenReturn(meeting);
|
||||||
|
when(aiTaskService.getOne(any())).thenReturn((AiTask) null, (AiTask) null);
|
||||||
|
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||||
|
when(valueOperations.get("biz:meeting:progress:26")).thenReturn("{\"percent\":45}");
|
||||||
|
|
||||||
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
mock(MeetingQueryService.class),
|
||||||
|
mock(MeetingAccessService.class),
|
||||||
|
mock(MeetingCommandService.class),
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
mock(PromptTemplateService.class),
|
||||||
|
transcriptMapper,
|
||||||
|
mock(SysUserMapper.class),
|
||||||
|
redisTemplate,
|
||||||
|
new ObjectMapper()
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyApiResponse<?> response = controller.previewData(26L);
|
||||||
|
|
||||||
|
assertEquals("400", response.getCode());
|
||||||
|
LegacyMeetingPreviewDataResponse data = (LegacyMeetingPreviewDataResponse) response.getData();
|
||||||
|
assertNotNull(data);
|
||||||
|
assertEquals(50, data.getProcessingStatus().getOverallProgress());
|
||||||
|
assertEquals("audio_transcription", data.getProcessingStatus().getCurrentStage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void previewDataShouldTranslateRealtimeProgressAtNinetyToSummaryStage() {
|
||||||
|
MeetingService meetingService = mock(MeetingService.class);
|
||||||
|
AiTaskService aiTaskService = mock(AiTaskService.class);
|
||||||
|
MeetingTranscriptMapper transcriptMapper = mock(MeetingTranscriptMapper.class);
|
||||||
|
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
|
||||||
|
|
||||||
|
Meeting meeting = new Meeting();
|
||||||
|
meeting.setId(27L);
|
||||||
|
meeting.setTitle("summary by progress");
|
||||||
|
meeting.setStatus(0);
|
||||||
|
when(meetingService.getById(27L)).thenReturn(meeting);
|
||||||
|
when(aiTaskService.getOne(any())).thenReturn((AiTask) null, (AiTask) null);
|
||||||
|
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||||
|
when(valueOperations.get("biz:meeting:progress:27")).thenReturn("{\"percent\":90}");
|
||||||
|
|
||||||
|
LegacyMeetingController controller = new LegacyMeetingController(
|
||||||
|
mock(LegacyMeetingAdapterService.class),
|
||||||
|
mock(MeetingQueryService.class),
|
||||||
|
mock(MeetingAccessService.class),
|
||||||
|
mock(MeetingCommandService.class),
|
||||||
|
meetingService,
|
||||||
|
aiTaskService,
|
||||||
|
mock(PromptTemplateService.class),
|
||||||
|
transcriptMapper,
|
||||||
|
mock(SysUserMapper.class),
|
||||||
|
redisTemplate,
|
||||||
|
new ObjectMapper()
|
||||||
|
);
|
||||||
|
|
||||||
|
LegacyApiResponse<?> response = controller.previewData(27L);
|
||||||
|
|
||||||
|
assertEquals("400", response.getCode());
|
||||||
|
LegacyMeetingPreviewDataResponse data = (LegacyMeetingPreviewDataResponse) response.getData();
|
||||||
|
assertNotNull(data);
|
||||||
|
assertEquals(75, data.getProcessingStatus().getOverallProgress());
|
||||||
|
assertEquals("summary_generation", data.getProcessingStatus().getCurrentStage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.imeeting.service.android.legacy;
|
package com.imeeting.service.android.legacy;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.imeeting.dto.android.legacy.LegacyClientDownloadResponse;
|
import com.imeeting.dto.android.legacy.LegacyClientDownloadResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyExternalAppItemResponse;
|
import com.imeeting.dto.android.legacy.LegacyExternalAppItemResponse;
|
||||||
import com.imeeting.entity.biz.ClientDownload;
|
import com.imeeting.entity.biz.ClientDownload;
|
||||||
|
|
@ -16,6 +18,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
@ -93,4 +96,44 @@ class LegacyCatalogAdapterServiceImplTest {
|
||||||
assertEquals("管理员", responses.get(0).getCreatorUsername());
|
assertEquals("管理员", responses.get(0).getCreatorUsername());
|
||||||
assertEquals(1, responses.get(0).getIsActive());
|
assertEquals(1, responses.get(0).getIsActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listActiveExternalAppsShouldSerializeAppInfoKeysAsSnakeCase() throws Exception {
|
||||||
|
ExternalAppMapper externalAppMapper = mock(ExternalAppMapper.class);
|
||||||
|
SysUserMapper sysUserMapper = mock(SysUserMapper.class);
|
||||||
|
|
||||||
|
ExternalApp app = new ExternalApp();
|
||||||
|
app.setId(102L);
|
||||||
|
app.setAppName("鎵撳崱宸ュ叿");
|
||||||
|
app.setAppType("native");
|
||||||
|
app.setAppInfo(Map.of(
|
||||||
|
"versionName", "2.1.0",
|
||||||
|
"webUrl", "https://board.example.com",
|
||||||
|
"nestedConfig", Map.of("packageName", "com.example.clockin"),
|
||||||
|
"launchTargets", List.of(Map.of("apkUrl", "https://dl.example.com/app.apk"))
|
||||||
|
));
|
||||||
|
app.setStatus(1);
|
||||||
|
app.setCreatedBy(1L);
|
||||||
|
when(externalAppMapper.selectList(any())).thenReturn(List.of(app));
|
||||||
|
|
||||||
|
SysUser creator = new SysUser();
|
||||||
|
creator.setUserId(1L);
|
||||||
|
creator.setDisplayName("admin");
|
||||||
|
when(sysUserMapper.selectBatchIds(List.of(1L))).thenReturn(List.of(creator));
|
||||||
|
|
||||||
|
LegacyCatalogAdapterServiceImpl service = new LegacyCatalogAdapterServiceImpl(
|
||||||
|
mock(ClientDownloadMapper.class),
|
||||||
|
externalAppMapper,
|
||||||
|
sysUserMapper
|
||||||
|
);
|
||||||
|
|
||||||
|
List<LegacyExternalAppItemResponse> responses = service.listActiveExternalApps();
|
||||||
|
JsonNode json = new ObjectMapper().readTree(new ObjectMapper().writeValueAsBytes(responses.get(0)));
|
||||||
|
|
||||||
|
assertTrue(json.has("app_info"));
|
||||||
|
assertEquals("2.1.0", json.path("app_info").path("version_name").asText());
|
||||||
|
assertEquals("https://board.example.com", json.path("app_info").path("web_url").asText());
|
||||||
|
assertEquals("com.example.clockin", json.path("app_info").path("nested_config").path("package_name").asText());
|
||||||
|
assertEquals("https://dl.example.com/app.apk", json.path("app_info").path("launch_targets").get(0).path("apk_url").asText());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { menuRoutes,extraRoutes } from "./routes";
|
||||||
|
|
||||||
const Login = lazy(() => import("@/pages/auth/login"));
|
const Login = lazy(() => import("@/pages/auth/login"));
|
||||||
const ResetPassword = lazy(() => import("@/pages/auth/reset-password"));
|
const ResetPassword = lazy(() => import("@/pages/auth/reset-password"));
|
||||||
|
const MeetingPreview = lazy(() => import("@/pages/business/MeetingPreview"));
|
||||||
|
|
||||||
function RouteFallback() {
|
function RouteFallback() {
|
||||||
return <div className="app-page__empty-state" style={{ minHeight: 320 }}>Loading...</div>;
|
return <div className="app-page__empty-state" style={{ minHeight: 320 }}>Loading...</div>;
|
||||||
|
|
@ -31,6 +32,14 @@ export default function AppRoutes() {
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/reset-password" element={<ResetPassword />} />
|
<Route path="/reset-password" element={<ResetPassword />} />
|
||||||
|
<Route
|
||||||
|
path="/meetings/:id/preview"
|
||||||
|
element={
|
||||||
|
<RequireAuth>
|
||||||
|
<MeetingPreview />
|
||||||
|
</RequireAuth>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
element={
|
element={
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue