feat: 更新 Android 会议控制器,添加 ASR 重试和总结重试功能

- 新增 `retryTranscription` 和 `retrySummary` 方法,支持重试 ASR 识别和会议总结
- 更新 `create` 方法,返回 `AndroidMeetingCreateResponse` 对象
- 更新 `list` 方法,返回 `AndroidMeetingListItemVO` 列表
- 添加辅助方法 `buildAndroidMeetingCreateResponse` 和 `buildAndroidMeetingListPage` 用于构建响应对象
- 引入 `h5BaseUrl` 配置项,用于生成会议预览 URL
dev_na
chenhao 2026-06-10 16:00:54 +08:00
parent 11c3ee1b09
commit 1e7a8ad83c
1 changed files with 122 additions and 12 deletions

View File

@ -6,7 +6,9 @@ import com.imeeting.common.MeetingConstants;
import com.imeeting.common.SysParamKeys;
import com.imeeting.common.exception.ExistingOfflineMeetingException;
import com.imeeting.dto.android.AndroidAuthContext;
import com.imeeting.dto.android.AndroidMeetingCreateResponse;
import com.imeeting.dto.android.AndroidMeetingConfigVo;
import com.imeeting.dto.android.AndroidMeetingListItemVO;
import com.imeeting.dto.android.AndroidOfflineMeetingConflictVO;
import com.imeeting.dto.android.AndroidOfflineMeetingFinishRequest;
import com.imeeting.dto.android.AndroidUnifiedMeetingStatusRequest;
@ -51,6 +53,9 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -65,7 +70,9 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
@ -84,6 +91,9 @@ public class AndroidMeetingController {
private static final String STAGE_SUMMARY_GENERATION = "summary_generation";
private static final String STAGE_COMPLETED = "completed";
@Value("${imeeting.h5.base-url:}")
private String h5BaseUrl;
private final AndroidAuthService androidAuthService;
private final AndroidChunkUploadService androidChunkUploadService;
private final LegacyMeetingAdapterService legacyMeetingAdapterService;
@ -138,7 +148,7 @@ public class AndroidMeetingController {
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "返回新创建的会议详情",
content = @Content(schema = @Schema(implementation = MeetingVO.class))
content = @Content(schema = @Schema(implementation = AndroidMeetingCreateResponse.class))
)
})
@PostMapping("/create")
@ -153,7 +163,8 @@ public class AndroidMeetingController {
// if (existingMeeting != null) {
// return new ApiResponse<>("409", "设备端已有会议", meetingQueryService.getDetailIgnoreTenant(existingMeeting.getId()));
// }
return ApiResponse.ok(legacyMeetingAdapterService.createMeeting(command, authContext, loginUser));
MeetingVO meeting = legacyMeetingAdapterService.createMeeting(command, authContext, loginUser);
return ApiResponse.ok(buildAndroidMeetingCreateResponse(meeting));
} catch (ExistingOfflineMeetingException ex) {
return new ApiResponse<>("409", "有未结束会议", new AndroidOfflineMeetingConflictVO(ex.getMeetingId()));
}
@ -247,11 +258,11 @@ public class AndroidMeetingController {
)
})
@GetMapping
public ApiResponse<PageResult<List<MeetingVO>>> list(HttpServletRequest request,
@RequestParam(value = "user_id", required = false) Long ignoredUserId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(value = "page_size", defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String title) {
public ApiResponse<PageResult<List<AndroidMeetingListItemVO>>> list(HttpServletRequest request,
@RequestParam(value = "user_id", required = false) Long ignoredUserId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(value = "page_size", defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String title) {
AndroidRequestLogHelper.logRequest(log, "Android会议", "分页查询会议接口",
"userId", ignoredUserId,
"page", page,
@ -259,7 +270,7 @@ public class AndroidMeetingController {
"title", title);
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
LoginUser loginUser = AndroidLoginUserSupport.requireLoginUser(authContext);
return ApiResponse.ok(meetingQueryService.pageMeetings(
PageResult<List<MeetingVO>> result = meetingQueryService.pageMeetings(
page,
pageSize,
title,
@ -269,7 +280,8 @@ public class AndroidMeetingController {
"all",
null,
AndroidLoginUserSupport.isAdmin(authContext)
));
);
return ApiResponse.ok(buildAndroidMeetingListPage(result));
}
@Operation(summary = "查询Android会议预览数据")
@ -323,14 +335,51 @@ public class AndroidMeetingController {
.build());
}
@Operation(summary = "更新Android会议访问密码")
@Operation(summary = "重试 Android 会议 ASR 识别")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "返回更新后的会议访问密码,传空时表示清空访问密码",
content = @Content(schema = @Schema(implementation = String.class))
description = "重试成功返回 true",
content = @Content(schema = @Schema(implementation = Boolean.class))
)
})
@PostMapping("/{meetingId}/transcripts/retry")
@Anonymous
public ApiResponse<Boolean> retryTranscription(HttpServletRequest request, @PathVariable Long meetingId) {
AndroidRequestLogHelper.logRequest(log, "Android会议", "重试会议 ASR 识别接口", "meetingId", meetingId);
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
LoginUser loginUser = authContext.isAnonymous() ? null : AndroidLoginUserSupport.requireLoginUser(authContext);
requireOperableOfflineMeeting(meetingId, authContext, loginUser);
meetingCommandService.retryTranscription(meetingId);
return ApiResponse.ok(true);
}
@Operation(summary = "重试 Android 会议总结")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "重试成功返回 true",
content = @Content(schema = @Schema(implementation = Boolean.class))
)
})
@PostMapping("/{meetingId}/summary/retry")
@Anonymous
public ApiResponse<Boolean> retrySummary(HttpServletRequest request, @PathVariable Long meetingId) {
AndroidRequestLogHelper.logRequest(log, "Android会议", "重试会议总结接口", "meetingId", meetingId);
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
LoginUser loginUser = authContext.isAnonymous() ? null : AndroidLoginUserSupport.requireLoginUser(authContext);
requireOperableOfflineMeeting(meetingId, authContext, loginUser);
meetingCommandService.retrySummary(meetingId);
return ApiResponse.ok(true);
}
@Operation(summary = "更新Android会议访问密码")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "返回更新后的会议访问密码,传空时表示清空访问密码",
content = @Content(schema = @Schema(implementation = String.class))
)
})
@PutMapping("/{meetingId}/access-password")
@Log(value = "修改Android会议访问密码", type = "Android会议管理")
public ApiResponse<String> updateAccessPassword(HttpServletRequest request,
@ -419,6 +468,67 @@ public class AndroidMeetingController {
return ApiResponse.ok(resultVo);
}
private AndroidMeetingCreateResponse buildAndroidMeetingCreateResponse(MeetingVO meeting) {
AndroidMeetingCreateResponse response = new AndroidMeetingCreateResponse();
if (meeting == null) {
return response;
}
BeanUtils.copyProperties(meeting, response);
response.setPreviewUrl(buildPreviewUrl(meeting.getId()));
return response;
}
private PageResult<List<AndroidMeetingListItemVO>> buildAndroidMeetingListPage(PageResult<List<MeetingVO>> source) {
PageResult<List<AndroidMeetingListItemVO>> result = new PageResult<>();
if (source == null) {
result.setTotal(0L);
result.setRecords(List.of());
return result;
}
result.setTotal(source.getTotal());
result.setRecords(source.getRecords() == null
? List.of()
: source.getRecords().stream().map(this::buildAndroidMeetingListItem).toList());
return result;
}
private AndroidMeetingListItemVO buildAndroidMeetingListItem(MeetingVO meeting) {
AndroidMeetingListItemVO item = new AndroidMeetingListItemVO();
if (meeting == null) {
return item;
}
BeanUtils.copyProperties(meeting, item);
item.setPreviewUrl(buildPreviewUrl(meeting.getId()));
item.setDayOffset(resolveDayOffset(meeting.getMeetingTime()));
return item;
}
private String buildPreviewUrl(Long meetingId) {
if (meetingId == null) {
return null;
}
String baseUrl = normalizeH5BaseUrl();
if (!StringUtils.hasText(baseUrl)) {
return null;
}
return baseUrl + "/meetings/" + meetingId + "/preview";
}
private String normalizeH5BaseUrl() {
String baseUrl = StringUtils.hasText(h5BaseUrl) ? h5BaseUrl.trim() : "";
if (!StringUtils.hasText(baseUrl)) {
return "";
}
return baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
}
private Long resolveDayOffset(LocalDateTime meetingTime) {
if (meetingTime == null) {
return null;
}
return ChronoUnit.DAYS.between(LocalDate.now(), meetingTime.toLocalDate());
}
private MeetingVO requireOperableOfflineMeeting(Long meetingId, AndroidAuthContext authContext, LoginUser loginUser) {
MeetingVO meeting = meetingQueryService.getDetailIgnoreTenant(meetingId);
if (meeting == null) {