586 lines
30 KiB
Java
586 lines
30 KiB
Java
package com.imeeting.controller.android;
|
||
|
||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||
import com.imeeting.common.MeetingConstants;
|
||
import com.imeeting.common.SysParamKeys;
|
||
import com.imeeting.dto.android.AndroidAuthContext;
|
||
import com.imeeting.dto.android.AndroidOfflineMeetingCreateCommand;
|
||
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;
|
||
import com.imeeting.dto.android.AndroidUnifiedMeetingStatusResponse;
|
||
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordRequest;
|
||
import com.imeeting.dto.android.legacy.LegacyMeetingAttendeeResponse;
|
||
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewDataResponse;
|
||
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewResult;
|
||
import com.imeeting.dto.android.legacy.LegacyMeetingProcessingStatusResponse;
|
||
import com.imeeting.dto.android.legacy.LegacyUploadAudioResponse;
|
||
import com.imeeting.dto.biz.*;
|
||
import com.imeeting.entity.biz.AiTask;
|
||
import com.imeeting.entity.biz.Meeting;
|
||
import com.imeeting.entity.biz.PromptTemplate;
|
||
import com.imeeting.enums.BusinessErrorCodeEnum;
|
||
import com.imeeting.enums.MeetingStatusEnum;
|
||
import com.imeeting.service.android.AndroidAuthService;
|
||
import com.imeeting.service.android.AndroidChunkUploadService;
|
||
import com.imeeting.service.android.AndroidGatewayPushService;
|
||
import com.imeeting.service.android.AndroidMeetingPushService;
|
||
import com.imeeting.service.android.legacy.LegacyMeetingAdapterService;
|
||
import com.imeeting.support.AndroidRequestLogHelper;
|
||
import com.imeeting.service.biz.*;
|
||
import com.unisbase.annotation.Anonymous;
|
||
import com.unisbase.common.ApiResponse;
|
||
import com.unisbase.common.annotation.Log;
|
||
import com.unisbase.common.exception.BusinessException;
|
||
import com.unisbase.dto.PageResult;
|
||
import com.unisbase.entity.SysTenant;
|
||
import com.unisbase.entity.SysUser;
|
||
import com.unisbase.mapper.SysTenantMapper;
|
||
import com.unisbase.mapper.SysUserMapper;
|
||
import com.unisbase.security.LoginUser;
|
||
import com.unisbase.service.SysDictItemService;
|
||
import com.unisbase.service.SysParamService;
|
||
import com.unisbase.service.impl.SysDictItemServiceImpl;
|
||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||
import io.swagger.v3.oas.annotations.media.Content;
|
||
import io.swagger.v3.oas.annotations.media.Schema;
|
||
import io.swagger.v3.oas.annotations.Operation;
|
||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||
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.StopWatch;
|
||
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;
|
||
import org.springframework.web.bind.annotation.PostMapping;
|
||
import org.springframework.web.bind.annotation.PutMapping;
|
||
import org.springframework.web.bind.annotation.RequestBody;
|
||
import org.springframework.web.bind.annotation.RequestMapping;
|
||
import org.springframework.web.bind.annotation.RequestParam;
|
||
import org.springframework.web.bind.annotation.RestController;
|
||
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;
|
||
import java.util.Map;
|
||
import java.util.Objects;
|
||
import java.util.concurrent.CompletableFuture;
|
||
import java.util.stream.Collectors;
|
||
|
||
@Tag(name = "Android会议接口")
|
||
@RestController
|
||
@RequestMapping("/api/android/meetings")
|
||
@Slf4j
|
||
public class AndroidMeetingController {
|
||
|
||
private static final String STAGE_DATA_INITIALIZATION = "data_initialization";
|
||
private static final String STAGE_AUDIO_TRANSCRIPTION = "audio_transcription";
|
||
private static final String STAGE_SUMMARY_GENERATION = "summary_generation";
|
||
private static final String STAGE_COMPLETED = "completed";
|
||
private static final String TENANT_CODE_HEADER = "X-Tenant-Code";
|
||
|
||
@Value("${imeeting.h5.base-url:}")
|
||
private String h5BaseUrl;
|
||
|
||
private final AndroidAuthService androidAuthService;
|
||
private final AndroidMeetingPushService androidMeetingPushService;
|
||
private final AndroidChunkUploadService androidChunkUploadService;
|
||
private final LegacyMeetingAdapterService legacyMeetingAdapterService;
|
||
private final MeetingQueryService meetingQueryService;
|
||
private final MeetingAccessService meetingAccessService;
|
||
private final MeetingCommandService meetingCommandService;
|
||
private final MeetingService meetingService;
|
||
private final AiTaskService aiTaskService;
|
||
private final PromptTemplateService promptTemplateService;
|
||
private final SysTenantMapper sysTenantMapper;
|
||
private final SysUserMapper sysUserMapper;
|
||
private final AiModelService aiModelService;
|
||
private final HotWordGroupService hotWordGroupService;
|
||
private final SysDictItemService dictItemService;
|
||
private final SysParamService paramService;
|
||
private final MeetingProgressService meetingProgressService;
|
||
private final MeetingUnifiedStatusService meetingUnifiedStatusService;
|
||
|
||
@Autowired
|
||
public AndroidMeetingController(AndroidAuthService androidAuthService,
|
||
AndroidChunkUploadService androidChunkUploadService,
|
||
LegacyMeetingAdapterService legacyMeetingAdapterService,
|
||
MeetingQueryService meetingQueryService,
|
||
MeetingAccessService meetingAccessService,
|
||
MeetingCommandService meetingCommandService,
|
||
MeetingService meetingService,
|
||
AiTaskService aiTaskService,
|
||
PromptTemplateService promptTemplateService,
|
||
SysTenantMapper sysTenantMapper,
|
||
SysUserMapper sysUserMapper,
|
||
AiModelService aiModelService,
|
||
HotWordGroupService hotWordGroupService,
|
||
SysDictItemService dictItemService,
|
||
SysParamService paramService,
|
||
MeetingProgressService meetingProgressService,
|
||
AndroidMeetingPushService androidMeetingPushService,
|
||
MeetingUnifiedStatusService meetingUnifiedStatusService) {
|
||
this.androidAuthService = androidAuthService;
|
||
this.androidChunkUploadService = androidChunkUploadService;
|
||
this.legacyMeetingAdapterService = legacyMeetingAdapterService;
|
||
this.meetingQueryService = meetingQueryService;
|
||
this.meetingAccessService = meetingAccessService;
|
||
this.meetingCommandService = meetingCommandService;
|
||
this.meetingService = meetingService;
|
||
this.aiTaskService = aiTaskService;
|
||
this.promptTemplateService = promptTemplateService;
|
||
this.sysTenantMapper = sysTenantMapper;
|
||
this.sysUserMapper = sysUserMapper;
|
||
this.meetingProgressService = meetingProgressService;
|
||
this.aiModelService = aiModelService;
|
||
this.hotWordGroupService = hotWordGroupService;
|
||
this.paramService = paramService;
|
||
this.dictItemService = dictItemService;
|
||
this.meetingUnifiedStatusService = meetingUnifiedStatusService;
|
||
this.androidMeetingPushService = androidMeetingPushService;
|
||
}
|
||
|
||
@Operation(summary = "创建Android离线会议")
|
||
@ApiResponses({
|
||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||
responseCode = "200",
|
||
description = "返回新创建的会议详情",
|
||
content = @Content(schema = @Schema(implementation = AndroidMeetingCreateResponse.class))
|
||
)
|
||
})
|
||
@PostMapping("/create")
|
||
@Anonymous
|
||
@Log(value = "新增Android会议", type = "Android会议管理")
|
||
public ApiResponse<Object> create(HttpServletRequest request,
|
||
@RequestBody AndroidOfflineMeetingCreateCommand command) {
|
||
AndroidRequestLogHelper.logRequest(log, "Android会议", "创建离线会议接口", "request", command);
|
||
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||
resolvePublicDeviceTenantId(request, command, authContext);
|
||
LoginUser loginUser = authContext.isAnonymous() ? null : AndroidLoginUserSupport.requireLoginUser(authContext);
|
||
|
||
// Meeting existingMeeting = findLatestUnfinishedMeetingByDevice(authContext.getDeviceId());
|
||
// if (existingMeeting != null) {
|
||
// return new ApiResponse<>("409", "设备端已有会议", meetingQueryService.getDetailIgnoreTenant(existingMeeting.getId()));
|
||
// }
|
||
MeetingVO meeting = legacyMeetingAdapterService.createMeeting(command, authContext, loginUser);
|
||
return ApiResponse.ok(buildAndroidMeetingCreateResponse(meeting));
|
||
}
|
||
|
||
@Operation(summary = "上传Android会议音频")
|
||
@ApiResponses({
|
||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||
responseCode = "200",
|
||
description = "返回上传后的会议 ID 和音频地址",
|
||
content = @Content(schema = @Schema(implementation = LegacyUploadAudioResponse.class))
|
||
)
|
||
})
|
||
@PostMapping("/upload-audio")
|
||
@Anonymous
|
||
public ApiResponse<LegacyUploadAudioResponse> uploadAudio(HttpServletRequest request,
|
||
@RequestParam("id") Long meetingId,
|
||
@RequestParam(value = "prompt_id", required = false) Long promptId,
|
||
@RequestParam(value = "model_code", required = false) String modelCode,
|
||
@RequestParam(value = "force_replace", defaultValue = "false") boolean forceReplace,
|
||
@RequestParam("audio_file") MultipartFile audioFile) throws IOException {
|
||
AndroidRequestLogHelper.logRequest(log, "Android会议", "上传会议音频接口",
|
||
"meetingId", meetingId,
|
||
"promptId", promptId,
|
||
"modelCode", modelCode,
|
||
"forceReplace", forceReplace,
|
||
"audioFile", audioFile);
|
||
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||
if (authContext.isAnonymous()) {
|
||
return ApiResponse.ok(legacyMeetingAdapterService.uploadAndTriggerOfflineProcessForPublicDevice(
|
||
meetingId,
|
||
promptId,
|
||
modelCode,
|
||
forceReplace,
|
||
audioFile,
|
||
authContext
|
||
));
|
||
}
|
||
LoginUser loginUser = AndroidLoginUserSupport.requireLoginUser(authContext);
|
||
return ApiResponse.ok(legacyMeetingAdapterService.uploadAndTriggerOfflineProcess(
|
||
meetingId,
|
||
promptId,
|
||
modelCode,
|
||
forceReplace,
|
||
audioFile,
|
||
authContext,
|
||
loginUser
|
||
));
|
||
}
|
||
|
||
@Operation(summary = "结束 Android 离线会议录音阶段")
|
||
@ApiResponses({
|
||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||
responseCode = "200",
|
||
description = "结束成功返回 true",
|
||
content = @Content(schema = @Schema(implementation = Boolean.class))
|
||
)
|
||
})
|
||
@PostMapping("/{meetingId}/finish")
|
||
@Anonymous
|
||
public ApiResponse<Object> finishOfflineMeeting(HttpServletRequest request,
|
||
@PathVariable Long meetingId,
|
||
@RequestBody(required = false) AndroidOfflineMeetingFinishRequest command) throws IOException {
|
||
AndroidRequestLogHelper.logRequest(log, "Android会议", "结束离线会议录音阶段",
|
||
"meetingId", meetingId,
|
||
"request", command);
|
||
AndroidAuthContext authContext = androidAuthService.authenticateHttpIgnoreToken(request, true);
|
||
LoginUser loginUser = authContext.isAnonymous() ? null : AndroidLoginUserSupport.requireLoginUser(authContext);
|
||
requireOperableOfflineMeeting(meetingId, authContext, loginUser);
|
||
MeetingVO meeting = meetingQueryService.getDetailIgnoreTenant(meetingId);
|
||
LegacyUploadAudioResponse uploadResult = new LegacyUploadAudioResponse();
|
||
if (isUploadFinishedStage(command)) {
|
||
androidChunkUploadService.completeUploadAsync(
|
||
meeting.getId(),
|
||
command.getTotalChunks(),
|
||
authContext
|
||
);
|
||
// if (uploadResult == null) {
|
||
// throw new RuntimeException("分片上传完成后未生成结果");
|
||
// }
|
||
uploadResult.setMeetingId(meetingId);
|
||
}
|
||
meetingCommandService.finishOfflineMeeting(meeting.getId(), command == null ? null : command.getFinishStage());
|
||
return ApiResponse.ok(uploadResult);
|
||
}
|
||
|
||
@Operation(summary = "分页查询Android会议")
|
||
@ApiResponses({
|
||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||
responseCode = "200",
|
||
description = "返回当前用户可见的会议分页结果",
|
||
content = @Content(schema = @Schema(implementation = PageResult.class))
|
||
)
|
||
})
|
||
@GetMapping
|
||
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,
|
||
"pageSize", pageSize,
|
||
"title", title);
|
||
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||
LoginUser loginUser = AndroidLoginUserSupport.requireLoginUser(authContext);
|
||
PageResult<List<MeetingVO>> result = meetingQueryService.pageMeetings(
|
||
page,
|
||
pageSize,
|
||
title,
|
||
loginUser.getTenantId(),
|
||
loginUser.getUserId(),
|
||
AndroidLoginUserSupport.resolveDisplayName(authContext),
|
||
"all",
|
||
null,
|
||
AndroidLoginUserSupport.isAdmin(authContext)
|
||
);
|
||
return ApiResponse.ok(buildAndroidMeetingListPage(result));
|
||
}
|
||
|
||
|
||
@Operation(summary = "查询Android会议统一状态")
|
||
@ApiResponses({
|
||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||
responseCode = "200",
|
||
description = "返回统一状态及可选内容",
|
||
content = @Content(schema = @Schema(implementation = AndroidUnifiedMeetingStatusResponse.class))
|
||
)
|
||
})
|
||
@PostMapping("/{meetingId}/status")
|
||
@Anonymous
|
||
public ApiResponse<AndroidUnifiedMeetingStatusResponse> getUnifiedStatus(HttpServletRequest request,
|
||
@PathVariable Long meetingId,
|
||
@RequestBody(required = false) AndroidUnifiedMeetingStatusRequest command) {
|
||
AndroidRequestLogHelper.logRequest(log, "Android会议", "查询会议统一状态",
|
||
"meetingId", meetingId,
|
||
"request", command);
|
||
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||
LoginUser loginUser = authContext.isAnonymous() ? null : AndroidLoginUserSupport.requireLoginUser(authContext);
|
||
requireOperableOfflineMeeting(meetingId, authContext, loginUser);
|
||
MeetingVO meeting = meetingQueryService.getDetailIgnoreTenant(meetingId,false);
|
||
UnifiedMeetingStatusVO status = meetingUnifiedStatusService.resolve(meetingId);
|
||
boolean includeTranscript = Boolean.TRUE.equals(command == null ? null : command.getIncludeTranscript());
|
||
boolean includeSummary = Boolean.TRUE.equals(command == null ? null : command.getIncludeSummary());
|
||
List<MeetingTranscriptVO> transcripts = includeTranscript ? meetingQueryService.getTranscripts(meetingId) : null;
|
||
String summaryContent = includeSummary ? meeting.getSummaryContent() : null;
|
||
AndroidUnifiedMeetingStatusResponse build = AndroidUnifiedMeetingStatusResponse.builder()
|
||
.meetingId(meetingId)
|
||
.status(status)
|
||
.meeting(meeting)
|
||
.includesTranscript(includeTranscript)
|
||
.transcripts(transcripts)
|
||
.includesSummary(includeSummary)
|
||
.summaryContent(summaryContent)
|
||
.build();
|
||
log.info("[{}]{}.返回数据:[{}]", "Android会议", "查询会议统一状态", build);
|
||
return ApiResponse.ok(build);
|
||
}
|
||
|
||
@Operation(summary = "重试 Android 会议 ASR 识别")
|
||
@ApiResponses({
|
||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||
responseCode = "200",
|
||
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);
|
||
androidMeetingPushService.pushMeetingStatusChanged(meetingId, UnifiedMeetingStatusStage.TRANSCRIBING.getCode());
|
||
|
||
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);
|
||
androidMeetingPushService.pushMeetingStatusChanged(meetingId, UnifiedMeetingStatusStage.SUMMARIZING.getCode());
|
||
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,
|
||
@PathVariable Long meetingId,
|
||
@RequestBody(required = false) LegacyMeetingAccessPasswordRequest command) {
|
||
AndroidRequestLogHelper.logRequest(log, "Android会议", "更新会议访问密码接口",
|
||
"meetingId", meetingId,
|
||
"request", command);
|
||
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||
LoginUser loginUser = AndroidLoginUserSupport.requireLoginUser(authContext);
|
||
Meeting meeting = meetingAccessService.requireMeeting(meetingId);
|
||
if (!Objects.equals(meeting.getCreatorId(), loginUser.getUserId())) {
|
||
return ApiResponse.error("仅会议创建人可设置访问密码");
|
||
}
|
||
String password = normalizePassword(command == null ? null : command.getPassword());
|
||
meetingService.update(new LambdaUpdateWrapper<Meeting>()
|
||
.eq(Meeting::getId, meeting.getId())
|
||
.set(Meeting::getAccessPassword, password));
|
||
return ApiResponse.ok(password);
|
||
}
|
||
|
||
@Operation(summary = "删除Android会议")
|
||
@ApiResponses({
|
||
@io.swagger.v3.oas.annotations.responses.ApiResponse(
|
||
responseCode = "200",
|
||
description = "返回是否删除成功",
|
||
content = @Content(schema = @Schema(implementation = Boolean.class))
|
||
)
|
||
})
|
||
@DeleteMapping("/{meetingId}")
|
||
@Log(value = "删除Android会议", type = "Android会议管理")
|
||
public ApiResponse<Boolean> delete(HttpServletRequest request, @PathVariable Long meetingId) {
|
||
AndroidRequestLogHelper.logRequest(log, "Android会议", "删除会议接口", "meetingId", meetingId);
|
||
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||
LoginUser loginUser = AndroidLoginUserSupport.requireLoginUser(authContext);
|
||
Meeting meeting = meetingAccessService.requireMeeting(meetingId);
|
||
meetingAccessService.assertCanEditMeeting(meeting, loginUser);
|
||
meetingCommandService.deleteMeeting(meetingId);
|
||
return ApiResponse.ok(true);
|
||
}
|
||
|
||
@GetMapping("/config")
|
||
@Log(value = "获取会议配置", type = "Android会议管理")
|
||
@Operation(summary = "获取会议配置")
|
||
@Anonymous
|
||
public ApiResponse<AndroidMeetingConfigVo> config(HttpServletRequest request) {
|
||
AndroidRequestLogHelper.logRequest(log, "Android会议", "获取会议配置接口");
|
||
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||
LoginUser loginUser = AndroidLoginUserSupport.toLoginUser(authContext);
|
||
Long tenantId = loginUser != null ? loginUser.getTenantId() : authContext.getTenantId();
|
||
Long userId = loginUser != null ? loginUser.getUserId() : null;
|
||
boolean isPlatformAdmin = loginUser != null && Boolean.TRUE.equals(loginUser.getIsPlatformAdmin());
|
||
boolean isTenantAdmin = loginUser != null && Boolean.TRUE.equals(loginUser.getIsTenantAdmin());
|
||
AndroidMeetingConfigVo resultVo = new AndroidMeetingConfigVo();
|
||
PageResult<List<PromptTemplateVO>> promptTemplateList = promptTemplateService.pageTemplates(
|
||
1,
|
||
1000,
|
||
null,
|
||
null,
|
||
tenantId,
|
||
userId,
|
||
isPlatformAdmin,
|
||
isTenantAdmin
|
||
);
|
||
List<PromptTemplateVO> enabledTemplates = promptTemplateList.getRecords() == null
|
||
? List.of()
|
||
: promptTemplateList.getRecords().stream()
|
||
.filter(item -> Integer.valueOf(1).equals(item.getStatus()))
|
||
.toList();
|
||
resultVo.setTemplateList(enabledTemplates);
|
||
PageResult<List<AiModelVO>> modelList = aiModelService.pageModels(1, 1000, null, "LLM", tenantId, false);
|
||
List<AiModelVO> enabledModels = modelList.getRecords() == null
|
||
? List.of()
|
||
: modelList.getRecords().stream()
|
||
.filter(item -> Integer.valueOf(1).equals(item.getStatus()))
|
||
.toList();
|
||
resultVo.setModelsList(enabledModels);
|
||
resultVo.setHotWordGroupList(hotWordGroupService.listVisibleOptions(tenantId));
|
||
resultVo.setSummaryDegreeOfDetail(dictItemService.getItemsByTypeCode("summary_degree_detail"));
|
||
resultVo.setMaxMeetingDuration(Integer.valueOf(paramService.getParamValue(SysParamKeys.MEETING_MAX_MEETING_DURATION, "30")));
|
||
resultVo.setMinMeetingDuration(Integer.valueOf(paramService.getParamValue(SysParamKeys.MEETING_MIN_MEETING_DURATION, "10")));
|
||
resultVo.setMaxPauseDuration(Integer.valueOf(paramService.getParamValue(SysParamKeys.MEETING_MAX_PAUSE_DURATION, String.valueOf(60 * 4))));
|
||
BigDecimal bigDecimal = new BigDecimal(paramService.getParamValue(SysParamKeys.MEETING_MAX_PAUSE_DURATION, "99"));
|
||
bigDecimal = bigDecimal.setScale(2, RoundingMode.HALF_UP);
|
||
resultVo.setPacketLossRate(bigDecimal);
|
||
resultVo.setChunkUploadEnabled(Boolean.parseBoolean(paramService.getParamValue(SysParamKeys.MEETING_ANDROID_AUDIO_CHUNK_UPLOAD_ENABLED, "false")));
|
||
resultVo.setChunkDurationSeconds(Integer.valueOf(paramService.getParamValue(SysParamKeys.MEETING_ANDROID_AUDIO_CHUNK_DURATION_SECONDS, "60")));
|
||
|
||
return ApiResponse.ok(resultVo);
|
||
}
|
||
|
||
private void resolvePublicDeviceTenantId(HttpServletRequest request,
|
||
AndroidOfflineMeetingCreateCommand command,
|
||
AndroidAuthContext authContext) {
|
||
if (command == null || command.getTenantId() != null || authContext == null || !authContext.isAnonymous()) {
|
||
return;
|
||
}
|
||
String tenantCode = request == null ? null : request.getHeader(TENANT_CODE_HEADER);
|
||
if (!StringUtils.hasText(tenantCode)) {
|
||
throw new IllegalArgumentException("tenantCode不能为空");
|
||
}
|
||
SysTenant tenant = sysTenantMapper.selectOne(new LambdaQueryWrapper<SysTenant>()
|
||
.eq(SysTenant::getTenantCode, tenantCode.trim())
|
||
.eq(SysTenant::getIsDeleted, 0)
|
||
.last("LIMIT 1"));
|
||
if (tenant == null || tenant.getId() == null) {
|
||
throw new IllegalArgumentException("tenantCode无效,无法获取tenantId");
|
||
}
|
||
command.setTenantId(tenant.getId());
|
||
}
|
||
|
||
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 Meeting requireOperableOfflineMeeting(Long meetingId, AndroidAuthContext authContext, LoginUser loginUser) {
|
||
Meeting meeting = meetingAccessService.requireMeetingIgnoreTenant(meetingId);
|
||
if (!MeetingConstants.TYPE_OFFLINE.equals(meeting.getMeetingType())) {
|
||
throw new RuntimeException("当前会议不是离线会议");
|
||
}
|
||
if (authContext == null || authContext.getDeviceId() == null || authContext.getDeviceId().isBlank()) {
|
||
throw new RuntimeException("设备ID不能为空");
|
||
}
|
||
return meeting;
|
||
}
|
||
|
||
private boolean isUploadFinishedStage(AndroidOfflineMeetingFinishRequest command) {
|
||
return command != null
|
||
&& MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED.equalsIgnoreCase(command.getFinishStage());
|
||
}
|
||
|
||
private String normalizePassword(String password) {
|
||
if (password == null) {
|
||
return null;
|
||
}
|
||
String normalized = password.trim();
|
||
return normalized.isEmpty() ? null : normalized;
|
||
}
|
||
|
||
}
|