refactor: 移除会议完成推送逻辑并更新积分校验处理
- 移除 `AndroidMeetingPushService` 中的 `pushMeetingCompleted` 方法及相关调用 - 更新 `MeetingCommandServiceImpl` 和 `AiTaskServiceImpl`,移除对 `AndroidMeetingPushService` 的依赖 - 新增 `assertSufficientPointsBeforeAsrSubmit` 和 `assertSufficientPointsBeforeSummarySubmit` 方法到 `MeetingPointsService`,并在 `AiTaskServiceImpl` 中调用这些方法以确保在提交 ASR 和总结任务前有足够的积分 - 更新前端表单占位符和多语言文件中的文本dev_na
parent
b07bbe90a0
commit
178b920581
|
|
@ -38,7 +38,9 @@ import com.unisbase.annotation.Anonymous;
|
||||||
import com.unisbase.common.ApiResponse;
|
import com.unisbase.common.ApiResponse;
|
||||||
import com.unisbase.common.annotation.Log;
|
import com.unisbase.common.annotation.Log;
|
||||||
import com.unisbase.dto.PageResult;
|
import com.unisbase.dto.PageResult;
|
||||||
|
import com.unisbase.entity.SysTenant;
|
||||||
import com.unisbase.entity.SysUser;
|
import com.unisbase.entity.SysUser;
|
||||||
|
import com.unisbase.mapper.SysTenantMapper;
|
||||||
import com.unisbase.mapper.SysUserMapper;
|
import com.unisbase.mapper.SysUserMapper;
|
||||||
import com.unisbase.security.LoginUser;
|
import com.unisbase.security.LoginUser;
|
||||||
import com.unisbase.service.SysDictItemService;
|
import com.unisbase.service.SysDictItemService;
|
||||||
|
|
@ -90,6 +92,7 @@ public class AndroidMeetingController {
|
||||||
private static final String STAGE_AUDIO_TRANSCRIPTION = "audio_transcription";
|
private static final String STAGE_AUDIO_TRANSCRIPTION = "audio_transcription";
|
||||||
private static final String STAGE_SUMMARY_GENERATION = "summary_generation";
|
private static final String STAGE_SUMMARY_GENERATION = "summary_generation";
|
||||||
private static final String STAGE_COMPLETED = "completed";
|
private static final String STAGE_COMPLETED = "completed";
|
||||||
|
private static final String TENANT_CODE_HEADER = "X-Tenant-Code";
|
||||||
|
|
||||||
@Value("${imeeting.h5.base-url:}")
|
@Value("${imeeting.h5.base-url:}")
|
||||||
private String h5BaseUrl;
|
private String h5BaseUrl;
|
||||||
|
|
@ -103,6 +106,7 @@ public class AndroidMeetingController {
|
||||||
private final MeetingService meetingService;
|
private final MeetingService meetingService;
|
||||||
private final AiTaskService aiTaskService;
|
private final AiTaskService aiTaskService;
|
||||||
private final PromptTemplateService promptTemplateService;
|
private final PromptTemplateService promptTemplateService;
|
||||||
|
private final SysTenantMapper sysTenantMapper;
|
||||||
private final SysUserMapper sysUserMapper;
|
private final SysUserMapper sysUserMapper;
|
||||||
private final AiModelService aiModelService;
|
private final AiModelService aiModelService;
|
||||||
private final SysDictItemService dictItemService;
|
private final SysDictItemService dictItemService;
|
||||||
|
|
@ -120,6 +124,7 @@ public class AndroidMeetingController {
|
||||||
MeetingService meetingService,
|
MeetingService meetingService,
|
||||||
AiTaskService aiTaskService,
|
AiTaskService aiTaskService,
|
||||||
PromptTemplateService promptTemplateService,
|
PromptTemplateService promptTemplateService,
|
||||||
|
SysTenantMapper sysTenantMapper,
|
||||||
SysUserMapper sysUserMapper,
|
SysUserMapper sysUserMapper,
|
||||||
AiModelService aiModelService,
|
AiModelService aiModelService,
|
||||||
SysDictItemService dictItemService,
|
SysDictItemService dictItemService,
|
||||||
|
|
@ -135,6 +140,7 @@ public class AndroidMeetingController {
|
||||||
this.meetingService = meetingService;
|
this.meetingService = meetingService;
|
||||||
this.aiTaskService = aiTaskService;
|
this.aiTaskService = aiTaskService;
|
||||||
this.promptTemplateService = promptTemplateService;
|
this.promptTemplateService = promptTemplateService;
|
||||||
|
this.sysTenantMapper = sysTenantMapper;
|
||||||
this.sysUserMapper = sysUserMapper;
|
this.sysUserMapper = sysUserMapper;
|
||||||
this.meetingProgressService = meetingProgressService;
|
this.meetingProgressService = meetingProgressService;
|
||||||
this.aiModelService = aiModelService;
|
this.aiModelService = aiModelService;
|
||||||
|
|
@ -154,9 +160,12 @@ public class AndroidMeetingController {
|
||||||
@PostMapping("/create")
|
@PostMapping("/create")
|
||||||
@Anonymous
|
@Anonymous
|
||||||
@Log(value = "新增Android会议", type = "Android会议管理")
|
@Log(value = "新增Android会议", type = "Android会议管理")
|
||||||
public ApiResponse<Object> create(HttpServletRequest request, @RequestBody LegacyMeetingCreateRequest command) {
|
public ApiResponse<Object> create(HttpServletRequest request,
|
||||||
|
|
||||||
|
@RequestBody LegacyMeetingCreateRequest command) {
|
||||||
AndroidRequestLogHelper.logRequest(log, "Android会议", "创建离线会议接口", "request", command);
|
AndroidRequestLogHelper.logRequest(log, "Android会议", "创建离线会议接口", "request", command);
|
||||||
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||||||
|
resolvePublicDeviceTenantId(request, command, authContext);
|
||||||
LoginUser loginUser = authContext.isAnonymous() ? null : AndroidLoginUserSupport.requireLoginUser(authContext);
|
LoginUser loginUser = authContext.isAnonymous() ? null : AndroidLoginUserSupport.requireLoginUser(authContext);
|
||||||
try {
|
try {
|
||||||
// Meeting existingMeeting = findLatestUnfinishedMeetingByDevice(authContext.getDeviceId());
|
// Meeting existingMeeting = findLatestUnfinishedMeetingByDevice(authContext.getDeviceId());
|
||||||
|
|
@ -468,6 +477,26 @@ public class AndroidMeetingController {
|
||||||
return ApiResponse.ok(resultVo);
|
return ApiResponse.ok(resultVo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resolvePublicDeviceTenantId(HttpServletRequest request,
|
||||||
|
LegacyMeetingCreateRequest 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) {
|
private AndroidMeetingCreateResponse buildAndroidMeetingCreateResponse(MeetingVO meeting) {
|
||||||
AndroidMeetingCreateResponse response = new AndroidMeetingCreateResponse();
|
AndroidMeetingCreateResponse response = new AndroidMeetingCreateResponse();
|
||||||
if (meeting == null) {
|
if (meeting == null) {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
package com.imeeting.controller.biz;
|
package com.imeeting.controller.biz;
|
||||||
|
|
||||||
import com.imeeting.dto.android.AndroidGrpcConnectionSnapshotVO;
|
import com.imeeting.dto.android.AndroidGrpcConnectionSnapshotVO;
|
||||||
import com.imeeting.dto.biz.MeetingSummaryFinalizeDTO;
|
|
||||||
import com.imeeting.dto.biz.MeetingExternalWorkflowFailureDTO;
|
import com.imeeting.dto.biz.MeetingExternalWorkflowFailureDTO;
|
||||||
|
import com.imeeting.dto.biz.MeetingSummaryFinalizeDTO;
|
||||||
import com.imeeting.dto.biz.MeetingSummaryPromptContextRequestDTO;
|
import com.imeeting.dto.biz.MeetingSummaryPromptContextRequestDTO;
|
||||||
import com.imeeting.dto.biz.MeetingSummaryPromptContextVO;
|
import com.imeeting.dto.biz.MeetingSummaryPromptContextVO;
|
||||||
import com.imeeting.dto.biz.MeetingTranscriptChapterImportDTO;
|
import com.imeeting.dto.biz.MeetingTranscriptChapterImportDTO;
|
||||||
import com.imeeting.dto.biz.MeetingTranscriptChapterImportResultVO;
|
import com.imeeting.dto.biz.MeetingTranscriptChapterImportResultVO;
|
||||||
import com.imeeting.dto.biz.MeetingTranscriptSourceVO;
|
import com.imeeting.dto.biz.MeetingTranscriptSourceVO;
|
||||||
import com.imeeting.service.android.AndroidGatewayPushService;
|
import com.imeeting.service.android.AndroidGatewayPushService;
|
||||||
import com.imeeting.service.android.AndroidMeetingPushService;
|
|
||||||
import com.imeeting.service.biz.MeetingCommandService;
|
import com.imeeting.service.biz.MeetingCommandService;
|
||||||
import com.imeeting.service.biz.MeetingQueryService;
|
import com.imeeting.service.biz.MeetingQueryService;
|
||||||
import com.unisbase.common.ApiResponse;
|
import com.unisbase.common.ApiResponse;
|
||||||
|
|
@ -34,7 +33,6 @@ public class MeetingInternalWorkflowController {
|
||||||
private final MeetingCommandService meetingCommandService;
|
private final MeetingCommandService meetingCommandService;
|
||||||
private final MeetingQueryService meetingQueryService;
|
private final MeetingQueryService meetingQueryService;
|
||||||
private final AndroidGatewayPushService androidGatewayPushService;
|
private final AndroidGatewayPushService androidGatewayPushService;
|
||||||
private final AndroidMeetingPushService androidMeetingPushService;
|
|
||||||
private final UnisBaseProperties unisBaseProperties;
|
private final UnisBaseProperties unisBaseProperties;
|
||||||
|
|
||||||
@Value("${imeeting.summary-orchestration.mode:INTERNAL_BUILTIN}")
|
@Value("${imeeting.summary-orchestration.mode:INTERNAL_BUILTIN}")
|
||||||
|
|
@ -43,12 +41,10 @@ public class MeetingInternalWorkflowController {
|
||||||
public MeetingInternalWorkflowController(MeetingCommandService meetingCommandService,
|
public MeetingInternalWorkflowController(MeetingCommandService meetingCommandService,
|
||||||
MeetingQueryService meetingQueryService,
|
MeetingQueryService meetingQueryService,
|
||||||
AndroidGatewayPushService androidGatewayPushService,
|
AndroidGatewayPushService androidGatewayPushService,
|
||||||
AndroidMeetingPushService androidMeetingPushService,
|
|
||||||
UnisBaseProperties unisBaseProperties) {
|
UnisBaseProperties unisBaseProperties) {
|
||||||
this.meetingCommandService = meetingCommandService;
|
this.meetingCommandService = meetingCommandService;
|
||||||
this.meetingQueryService = meetingQueryService;
|
this.meetingQueryService = meetingQueryService;
|
||||||
this.androidGatewayPushService = androidGatewayPushService;
|
this.androidGatewayPushService = androidGatewayPushService;
|
||||||
this.androidMeetingPushService = androidMeetingPushService;
|
|
||||||
this.unisBaseProperties = unisBaseProperties;
|
this.unisBaseProperties = unisBaseProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,18 +124,6 @@ public class MeetingInternalWorkflowController {
|
||||||
return ApiResponse.ok(true);
|
return ApiResponse.ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "手工触发会议完成推送")
|
|
||||||
@PostMapping("/{meetingId}/push/meeting-completed")
|
|
||||||
public ApiResponse<Boolean> pushMeetingCompleted(HttpServletRequest request,
|
|
||||||
@PathVariable Long meetingId) {
|
|
||||||
if (!isInternalSecretValid(request)) {
|
|
||||||
return ApiResponse.error("Invalid internal secret");
|
|
||||||
}
|
|
||||||
|
|
||||||
androidMeetingPushService.pushMeetingCompleted(meetingId);
|
|
||||||
return ApiResponse.ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "查询 Android gRPC 连接详情")
|
@Operation(summary = "查询 Android gRPC 连接详情")
|
||||||
@GetMapping("/grpc/connections")
|
@GetMapping("/grpc/connections")
|
||||||
public ApiResponse<AndroidGrpcConnectionSnapshotVO> listGrpcConnections(HttpServletRequest request) {
|
public ApiResponse<AndroidGrpcConnectionSnapshotVO> listGrpcConnections(HttpServletRequest request) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import lombok.Getter;
|
||||||
public enum MeetingPushTypeEnum {
|
public enum MeetingPushTypeEnum {
|
||||||
PUBLIC_MEETING_LOGIN_CONFIRM("PUBLIC_MEETING_LOGIN_CONFIRM", "公有设备扫码登录确认消息"),
|
PUBLIC_MEETING_LOGIN_CONFIRM("PUBLIC_MEETING_LOGIN_CONFIRM", "公有设备扫码登录确认消息"),
|
||||||
MEETING_PENDING("MEETING_PENDING", "待开始会议通知"),
|
MEETING_PENDING("MEETING_PENDING", "待开始会议通知"),
|
||||||
MEETING_COMPLETED("MEETING_COMPLETED", "会议完成通知"),
|
|
||||||
MEETING_STATUS_CHANGED("MEETING_STATUS_CHANGED", "会议状态变更通知");
|
MEETING_STATUS_CHANGED("MEETING_STATUS_CHANGED", "会议状态变更通知");
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,5 @@ public interface AndroidMeetingPushService {
|
||||||
|
|
||||||
void pushPublicLoginConfirm(String deviceId, AndroidPublicLoginConfirmPayload payload);
|
void pushPublicLoginConfirm(String deviceId, AndroidPublicLoginConfirmPayload payload);
|
||||||
|
|
||||||
void pushMeetingCompleted(Long meetingId);
|
|
||||||
|
|
||||||
void pushMeetingStatusChanged(Long meetingId, String statusCode);
|
void pushMeetingStatusChanged(Long meetingId, String statusCode);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,28 +84,6 @@ public class AndroidMeetingPushServiceImpl implements AndroidMeetingPushService
|
||||||
deviceId, payload.getSessionId(), pushed);
|
deviceId, payload.getSessionId(), pushed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pushMeetingCompleted(Long meetingId) {
|
|
||||||
if (meetingId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Meeting meeting = meetingMapper.selectByIdIgnoreTenant(meetingId);
|
|
||||||
if (meeting == null || meeting.getTenantId() == null || meeting.getCreatorId() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PushMessage message = PushMessage.newBuilder()
|
|
||||||
.setMessageId("meeting_completed:" + meetingId + ":" + UUID.randomUUID())
|
|
||||||
.setTimestamp(System.currentTimeMillis())
|
|
||||||
.setType(MeetingPushTypeEnum.MEETING_COMPLETED.getCode())
|
|
||||||
.setTitle(resolveCompletedTitle(meeting))
|
|
||||||
.setContent(buildCompletedContent(meeting))
|
|
||||||
.setNeedAck(false)
|
|
||||||
.build();
|
|
||||||
int pushed = androidGatewayPushService.pushToUser(meeting.getTenantId(), meeting.getCreatorId(), message);
|
|
||||||
log.info("Android meeting completion push finished, meetingId={}, tenantId={}, creatorId={}, pushedConnections={}",
|
|
||||||
meetingId, meeting.getTenantId(), meeting.getCreatorId(), pushed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pushMeetingStatusChanged(Long meetingId, String statusCode) {
|
public void pushMeetingStatusChanged(Long meetingId, String statusCode) {
|
||||||
if (meetingId == null || statusCode == null || statusCode.isBlank()) {
|
if (meetingId == null || statusCode == null || statusCode.isBlank()) {
|
||||||
|
|
@ -131,23 +109,12 @@ public class AndroidMeetingPushServiceImpl implements AndroidMeetingPushService
|
||||||
private String resolvePendingTitle(Meeting meeting) {
|
private String resolvePendingTitle(Meeting meeting) {
|
||||||
String title = meeting.getTitle();
|
String title = meeting.getTitle();
|
||||||
if (title != null && !title.isBlank()) {
|
if (title != null && !title.isBlank()) {
|
||||||
return "待开始会议 " + title.trim();
|
return "待开始会议:" + title.trim();
|
||||||
}
|
}
|
||||||
LocalDateTime meetingTime = meeting.getMeetingTime();
|
LocalDateTime meetingTime = meeting.getMeetingTime();
|
||||||
return meetingTime == null
|
return meetingTime == null
|
||||||
? "待开始会议"
|
? "待开始会议"
|
||||||
: "待开始会议 " + TITLE_TIME_FORMATTER.format(meetingTime);
|
: "待开始会议:" + TITLE_TIME_FORMATTER.format(meetingTime);
|
||||||
}
|
|
||||||
|
|
||||||
private String resolveCompletedTitle(Meeting meeting) {
|
|
||||||
String title = meeting.getTitle();
|
|
||||||
if (title != null && !title.isBlank()) {
|
|
||||||
return "会议已完成 " + title.trim();
|
|
||||||
}
|
|
||||||
LocalDateTime meetingTime = meeting.getMeetingTime();
|
|
||||||
return meetingTime == null
|
|
||||||
? "会议已完成"
|
|
||||||
: "会议已完成 " + TITLE_TIME_FORMATTER.format(meetingTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildPendingContent(Meeting meeting) {
|
private String buildPendingContent(Meeting meeting) {
|
||||||
|
|
@ -161,12 +128,6 @@ public class AndroidMeetingPushServiceImpl implements AndroidMeetingPushService
|
||||||
return JSONUtil.toJsonStr(result);
|
return JSONUtil.toJsonStr(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildCompletedContent(Meeting meeting) {
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
result.put("meetingId", meeting.getId());
|
|
||||||
return JSONUtil.toJsonStr(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildStatusChangedContent(Long meetingId, String statusCode) {
|
private String buildStatusChangedContent(Long meetingId, String statusCode) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("meetingId", meetingId);
|
result.put("meetingId", meetingId);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@ public interface MeetingPointsService {
|
||||||
|
|
||||||
void recordSummarySuccessCharge(Meeting meeting, AiTask summaryTask);
|
void recordSummarySuccessCharge(Meeting meeting, AiTask summaryTask);
|
||||||
|
|
||||||
|
void assertSufficientPointsBeforeAsrSubmit(Meeting meeting, AiTask asrTask);
|
||||||
|
|
||||||
|
void assertSufficientPointsBeforeSummarySubmit(Meeting meeting, AiTask summaryTask);
|
||||||
|
|
||||||
void markSummaryChargeFailed(Long summaryTaskId, String failureReason);
|
void markSummaryChargeFailed(Long summaryTaskId, String failureReason);
|
||||||
|
|
||||||
String resolveLatestBlockedReason(Long summaryTaskId);
|
String resolveLatestBlockedReason(Long summaryTaskId);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import com.imeeting.enums.MeetingStatusEnum;
|
||||||
import com.imeeting.mapper.biz.AiTaskMapper;
|
import com.imeeting.mapper.biz.AiTaskMapper;
|
||||||
import com.imeeting.mapper.biz.MeetingMapper;
|
import com.imeeting.mapper.biz.MeetingMapper;
|
||||||
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
||||||
import com.imeeting.service.android.AndroidMeetingPushService;
|
|
||||||
import com.imeeting.support.TaskSecurityContextRunner;
|
import com.imeeting.support.TaskSecurityContextRunner;
|
||||||
import com.imeeting.service.biz.AiModelService;
|
import com.imeeting.service.biz.AiModelService;
|
||||||
import com.imeeting.service.biz.AiTaskService;
|
import com.imeeting.service.biz.AiTaskService;
|
||||||
|
|
@ -43,8 +42,6 @@ import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.transaction.support.TransactionSynchronization;
|
|
||||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
|
@ -65,6 +62,9 @@ import java.util.stream.Collectors;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> implements AiTaskService {
|
public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> implements AiTaskService {
|
||||||
|
|
||||||
|
private static final Duration ASR_SUBMIT_REQUEST_TIMEOUT = Duration.ofSeconds(30);
|
||||||
|
private static final Duration ASR_QUERY_REQUEST_TIMEOUT = Duration.ofSeconds(30);
|
||||||
|
|
||||||
private final MeetingMapper meetingMapper;
|
private final MeetingMapper meetingMapper;
|
||||||
private final MeetingTranscriptMapper transcriptMapper;
|
private final MeetingTranscriptMapper transcriptMapper;
|
||||||
private final AiModelService aiModelService;
|
private final AiModelService aiModelService;
|
||||||
|
|
@ -82,7 +82,6 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
private final TaskSecurityContextRunner taskSecurityContextRunner;
|
private final TaskSecurityContextRunner taskSecurityContextRunner;
|
||||||
private final MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger;
|
private final MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger;
|
||||||
private final SysParamService sysParamService;
|
private final SysParamService sysParamService;
|
||||||
private final AndroidMeetingPushService androidMeetingPushService;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("asrTaskExecutor")
|
@Qualifier("asrTaskExecutor")
|
||||||
|
|
@ -126,8 +125,7 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
MeetingSummaryPromptAssembler meetingSummaryPromptAssembler,
|
MeetingSummaryPromptAssembler meetingSummaryPromptAssembler,
|
||||||
TaskSecurityContextRunner taskSecurityContextRunner,
|
TaskSecurityContextRunner taskSecurityContextRunner,
|
||||||
MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger,
|
MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger,
|
||||||
SysParamService sysParamService,
|
SysParamService sysParamService) {
|
||||||
AndroidMeetingPushService androidMeetingPushService) {
|
|
||||||
this.meetingMapper = meetingMapper;
|
this.meetingMapper = meetingMapper;
|
||||||
this.transcriptMapper = transcriptMapper;
|
this.transcriptMapper = transcriptMapper;
|
||||||
this.aiModelService = aiModelService;
|
this.aiModelService = aiModelService;
|
||||||
|
|
@ -144,7 +142,6 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
this.taskSecurityContextRunner = taskSecurityContextRunner;
|
this.taskSecurityContextRunner = taskSecurityContextRunner;
|
||||||
this.meetingExternalSummaryWebhookTrigger = meetingExternalSummaryWebhookTrigger;
|
this.meetingExternalSummaryWebhookTrigger = meetingExternalSummaryWebhookTrigger;
|
||||||
this.sysParamService = sysParamService;
|
this.sysParamService = sysParamService;
|
||||||
this.androidMeetingPushService = androidMeetingPushService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -316,6 +313,7 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
reconcileMeetingStatus(meetingId);
|
reconcileMeetingStatus(meetingId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Re-summary failed for meeting {}", meetingId, e);
|
log.error("Re-summary failed for meeting {}", meetingId, e);
|
||||||
|
failPendingSummaryTask(sumTask, e.getMessage());
|
||||||
reconcileMeetingStatus(meetingId);
|
reconcileMeetingStatus(meetingId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -481,6 +479,19 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
}
|
}
|
||||||
|
|
||||||
private String processAsrTask(Meeting meeting, AiTask taskRecord) throws Exception {
|
private String processAsrTask(Meeting meeting, AiTask taskRecord) throws Exception {
|
||||||
|
try {
|
||||||
|
return doProcessAsrTask(meeting, taskRecord);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (taskRecord != null
|
||||||
|
&& !Integer.valueOf(2).equals(taskRecord.getStatus())
|
||||||
|
&& !Integer.valueOf(3).equals(taskRecord.getStatus())) {
|
||||||
|
updateAiTaskFail(taskRecord, buildAsrFailureMessage(ex));
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String doProcessAsrTask(Meeting meeting, AiTask taskRecord) throws Exception {
|
||||||
updateMeetingStatus(meeting.getId(), 1);
|
updateMeetingStatus(meeting.getId(), 1);
|
||||||
|
|
||||||
taskRecord.setStatus(1);
|
taskRecord.setStatus(1);
|
||||||
|
|
@ -644,6 +655,7 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
Map<String, Object> req = buildAsrRequest(meeting, taskRecord, asrModel);
|
Map<String, Object> req = buildAsrRequest(meeting, taskRecord, asrModel);
|
||||||
taskRecord.setRequestData(req);
|
taskRecord.setRequestData(req);
|
||||||
this.updateById(taskRecord);
|
this.updateById(taskRecord);
|
||||||
|
meetingPointsService.assertSufficientPointsBeforeAsrSubmit(meeting, taskRecord);
|
||||||
|
|
||||||
String respBody = postJson(submitUrl, req, asrModel.getApiKey());
|
String respBody = postJson(submitUrl, req, asrModel.getApiKey());
|
||||||
JsonNode submitNode = objectMapper.readTree(respBody);
|
JsonNode submitNode = objectMapper.readTree(respBody);
|
||||||
|
|
@ -875,6 +887,7 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
updateMeetingStatus(meeting.getId(), 2);
|
updateMeetingStatus(meeting.getId(), 2);
|
||||||
updateProgress(meeting.getId(), 90, "正在生成智能总结纪要...", 0);
|
updateProgress(meeting.getId(), 90, "正在生成智能总结纪要...", 0);
|
||||||
|
|
||||||
|
meetingPointsService.assertSufficientPointsBeforeSummarySubmit(meeting, taskRecord);
|
||||||
taskRecord.setStatus(1);
|
taskRecord.setStatus(1);
|
||||||
taskRecord.setStartedAt(LocalDateTime.now());
|
taskRecord.setStartedAt(LocalDateTime.now());
|
||||||
Map<String, Object> initialResponseData = new HashMap<>();
|
Map<String, Object> initialResponseData = new HashMap<>();
|
||||||
|
|
@ -954,7 +967,6 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
|
|
||||||
Files.writeString(filePath, markdownContent, StandardCharsets.UTF_8);
|
Files.writeString(filePath, markdownContent, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
boolean alreadyCompleted = MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED);
|
|
||||||
taskRecord.setResultFilePath("meetings/" + meeting.getId() + "/summaries/" + fileName);
|
taskRecord.setResultFilePath("meetings/" + meeting.getId() + "/summaries/" + fileName);
|
||||||
Map<String, Object> responseData = objectMapper.convertValue(respNode, Map.class);
|
Map<String, Object> responseData = objectMapper.convertValue(respNode, Map.class);
|
||||||
responseData.put("summarySource", summarySource.toSnapshot());
|
responseData.put("summarySource", summarySource.toSnapshot());
|
||||||
|
|
@ -976,7 +988,6 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
AiTask latestChapterTask = findLatestTask(meeting.getId(), "CHAPTER");
|
AiTask latestChapterTask = findLatestTask(meeting.getId(), "CHAPTER");
|
||||||
if (latestChapterTask != null && Integer.valueOf(2).equals(latestChapterTask.getStatus())) {
|
if (latestChapterTask != null && Integer.valueOf(2).equals(latestChapterTask.getStatus())) {
|
||||||
updateProgress(meeting.getId(), 100, "全流程分析完成", 0);
|
updateProgress(meeting.getId(), 100, "全流程分析完成", 0);
|
||||||
pushMeetingCompletedAfterCommitIfNeeded(meeting.getId(), alreadyCompleted);
|
|
||||||
} else {
|
} else {
|
||||||
updateProgress(meeting.getId(), 95, "总结生成完成,等待 AI 目录完成...", 0);
|
updateProgress(meeting.getId(), 95, "总结生成完成,等待 AI 目录完成...", 0);
|
||||||
}
|
}
|
||||||
|
|
@ -1107,10 +1118,12 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
}
|
}
|
||||||
updateMeetingStatus(meeting.getId(), 2);
|
updateMeetingStatus(meeting.getId(), 2);
|
||||||
try {
|
try {
|
||||||
|
meetingPointsService.assertSufficientPointsBeforeSummarySubmit(meeting, summaryTask);
|
||||||
var result = meetingExternalSummaryWebhookTrigger.trigger(meeting, summaryTask, chapterTask, triggerSource, force);
|
var result = meetingExternalSummaryWebhookTrigger.trigger(meeting, summaryTask, chapterTask, triggerSource, force);
|
||||||
this.updateById(summaryTask);
|
this.updateById(summaryTask);
|
||||||
updateProgress(meeting.getId(), 95, result.getMessage(), 0);
|
updateProgress(meeting.getId(), 95, result.getMessage(), 0);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
failPendingSummaryTask(summaryTask, ex.getMessage());
|
||||||
this.updateById(summaryTask);
|
this.updateById(summaryTask);
|
||||||
updateProgress(meeting.getId(), -1, "闂佽崵鍠愰悷杈╃不閹达絻浜归柛灞剧☉缁剁偤鏌″搴″箹闁?n8n 缂傚倸鍊搁崐褰掓偋濡ゅ啯鏆滈柟鐐綑缁剁偤寮堕崼顐函鐞? " + ex.getMessage(), 0);
|
updateProgress(meeting.getId(), -1, "闂佽崵鍠愰悷杈╃不閹达絻浜归柛灞剧☉缁剁偤鏌″搴″箹闁?n8n 缂傚倸鍊搁崐褰掓偋濡ゅ啯鏆滈柟鐐綑缁剁偤寮堕崼顐函鐞? " + ex.getMessage(), 0);
|
||||||
log.error("Failed to trigger external n8n webhook for meeting {}", meeting.getId(), ex);
|
log.error("Failed to trigger external n8n webhook for meeting {}", meeting.getId(), ex);
|
||||||
|
|
@ -1230,6 +1243,7 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
private String postJson(String url, Object body, String apiKey) throws Exception {
|
private String postJson(String url, Object body, String apiKey) throws Exception {
|
||||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||||
.uri(buildUri(url))
|
.uri(buildUri(url))
|
||||||
|
.timeout(ASR_SUBMIT_REQUEST_TIMEOUT)
|
||||||
.header("Content-Type", "application/json");
|
.header("Content-Type", "application/json");
|
||||||
if (apiKey != null && !apiKey.isBlank()) {
|
if (apiKey != null && !apiKey.isBlank()) {
|
||||||
builder.header("Authorization", "Bearer " + apiKey);
|
builder.header("Authorization", "Bearer " + apiKey);
|
||||||
|
|
@ -1241,7 +1255,9 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
}
|
}
|
||||||
|
|
||||||
private String get(String url, String apiKey) throws Exception {
|
private String get(String url, String apiKey) throws Exception {
|
||||||
HttpRequest.Builder builder = HttpRequest.newBuilder().uri(buildUri(url));
|
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||||
|
.uri(buildUri(url))
|
||||||
|
.timeout(ASR_QUERY_REQUEST_TIMEOUT);
|
||||||
if (apiKey != null && !apiKey.isBlank()) {
|
if (apiKey != null && !apiKey.isBlank()) {
|
||||||
builder.header("Authorization", "Bearer " + apiKey);
|
builder.header("Authorization", "Bearer " + apiKey);
|
||||||
}
|
}
|
||||||
|
|
@ -1319,22 +1335,6 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
meetingMapper.updateById(m);
|
meetingMapper.updateById(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushMeetingCompletedAfterCommitIfNeeded(Long meetingId, boolean alreadyCompleted) {
|
|
||||||
if (alreadyCompleted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
|
||||||
androidMeetingPushService.pushMeetingCompleted(meetingId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
|
||||||
@Override
|
|
||||||
public void afterCommit() {
|
|
||||||
androidMeetingPushService.pushMeetingCompleted(meetingId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private AiTask createAiTask(Long meetingId, String type, Map<String, Object> req) {
|
private AiTask createAiTask(Long meetingId, String type, Map<String, Object> req) {
|
||||||
AiTask task = new AiTask();
|
AiTask task = new AiTask();
|
||||||
task.setMeetingId(meetingId);
|
task.setMeetingId(meetingId);
|
||||||
|
|
@ -1362,6 +1362,17 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
meetingPointsService.markSummaryChargeFailed(task.getId(), error);
|
meetingPointsService.markSummaryChargeFailed(task.getId(), error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildAsrFailureMessage(Exception ex) {
|
||||||
|
if (ex == null) {
|
||||||
|
return "ASR task failed";
|
||||||
|
}
|
||||||
|
String message = ex.getMessage();
|
||||||
|
if (message == null || message.isBlank()) {
|
||||||
|
return "ASR task failed: " + ex.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
return "ASR task failed: " + message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import com.imeeting.entity.biz.Meeting;
|
||||||
import com.imeeting.entity.biz.MeetingTranscript;
|
import com.imeeting.entity.biz.MeetingTranscript;
|
||||||
import com.imeeting.entity.biz.MeetingTranscriptChapterVersion;
|
import com.imeeting.entity.biz.MeetingTranscriptChapterVersion;
|
||||||
import com.imeeting.enums.MeetingStatusEnum;
|
import com.imeeting.enums.MeetingStatusEnum;
|
||||||
import com.imeeting.service.android.AndroidMeetingPushService;
|
|
||||||
import com.imeeting.service.android.AndroidPendingMeetingDraftService;
|
import com.imeeting.service.android.AndroidPendingMeetingDraftService;
|
||||||
import com.imeeting.service.android.AndroidPushMessageService;
|
import com.imeeting.service.android.AndroidPushMessageService;
|
||||||
import com.imeeting.service.biz.AiTaskService;
|
import com.imeeting.service.biz.AiTaskService;
|
||||||
|
|
@ -83,7 +82,6 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
private final MeetingPointsService meetingPointsService;
|
private final MeetingPointsService meetingPointsService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger;
|
private final MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger;
|
||||||
private final AndroidMeetingPushService androidMeetingPushService;
|
|
||||||
private final AndroidPushMessageService androidPushMessageService;
|
private final AndroidPushMessageService androidPushMessageService;
|
||||||
private final AndroidPendingMeetingDraftService androidPendingMeetingDraftService;
|
private final AndroidPendingMeetingDraftService androidPendingMeetingDraftService;
|
||||||
private final MeetingLockCache meetingLockCache;
|
private final MeetingLockCache meetingLockCache;
|
||||||
|
|
@ -109,7 +107,6 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
MeetingPointsService meetingPointsService,
|
MeetingPointsService meetingPointsService,
|
||||||
ObjectMapper objectMapper,
|
ObjectMapper objectMapper,
|
||||||
MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger,
|
MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger,
|
||||||
AndroidMeetingPushService androidMeetingPushService,
|
|
||||||
AndroidPushMessageService androidPushMessageService,
|
AndroidPushMessageService androidPushMessageService,
|
||||||
AndroidPendingMeetingDraftService androidPendingMeetingDraftService,
|
AndroidPendingMeetingDraftService androidPendingMeetingDraftService,
|
||||||
MeetingLockCache meetingLockCache,
|
MeetingLockCache meetingLockCache,
|
||||||
|
|
@ -130,7 +127,6 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
this.meetingPointsService = meetingPointsService;
|
this.meetingPointsService = meetingPointsService;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.meetingExternalSummaryWebhookTrigger = meetingExternalSummaryWebhookTrigger;
|
this.meetingExternalSummaryWebhookTrigger = meetingExternalSummaryWebhookTrigger;
|
||||||
this.androidMeetingPushService = androidMeetingPushService;
|
|
||||||
this.androidPushMessageService = androidPushMessageService;
|
this.androidPushMessageService = androidPushMessageService;
|
||||||
this.androidPendingMeetingDraftService = androidPendingMeetingDraftService;
|
this.androidPendingMeetingDraftService = androidPendingMeetingDraftService;
|
||||||
this.meetingLockCache = meetingLockCache;
|
this.meetingLockCache = meetingLockCache;
|
||||||
|
|
@ -886,7 +882,6 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
aiTaskService.updateById(summaryTask);
|
aiTaskService.updateById(summaryTask);
|
||||||
meetingPointsService.recordSummarySuccessCharge(meeting, summaryTask);
|
meetingPointsService.recordSummarySuccessCharge(meeting, summaryTask);
|
||||||
|
|
||||||
boolean alreadyCompleted = MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED);
|
|
||||||
meeting.setLatestSummaryTaskId(summaryTask.getId());
|
meeting.setLatestSummaryTaskId(summaryTask.getId());
|
||||||
meetingService.updateById(meeting);
|
meetingService.updateById(meeting);
|
||||||
aiTaskService.reconcileMeetingStatus(meeting.getId());
|
aiTaskService.reconcileMeetingStatus(meeting.getId());
|
||||||
|
|
@ -898,7 +893,6 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
.last("LIMIT 1"));
|
.last("LIMIT 1"));
|
||||||
if (latestChapterTask != null && Integer.valueOf(2).equals(latestChapterTask.getStatus())) {
|
if (latestChapterTask != null && Integer.valueOf(2).equals(latestChapterTask.getStatus())) {
|
||||||
updateMeetingProgress(meeting.getId(), 100, "外部总结回填完成", 0);
|
updateMeetingProgress(meeting.getId(), 100, "外部总结回填完成", 0);
|
||||||
pushMeetingCompletedAfterCommitIfNeeded(meeting.getId(), alreadyCompleted);
|
|
||||||
} else {
|
} else {
|
||||||
updateMeetingProgress(meeting.getId(), 95, "外部总结回填完成,等待 AI 目录完成...", 0);
|
updateMeetingProgress(meeting.getId(), 95, "外部总结回填完成,等待 AI 目录完成...", 0);
|
||||||
}
|
}
|
||||||
|
|
@ -1320,22 +1314,6 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushMeetingCompletedAfterCommitIfNeeded(Long meetingId, boolean alreadyCompleted) {
|
|
||||||
if (alreadyCompleted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
|
||||||
androidMeetingPushService.pushMeetingCompleted(meetingId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
|
||||||
@Override
|
|
||||||
public void afterCommit() {
|
|
||||||
androidMeetingPushService.pushMeetingCompleted(meetingId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateMeetingProgress(Long meetingId, int percent, String message, int eta) {
|
private void updateMeetingProgress(Long meetingId, int percent, String message, int eta) {
|
||||||
com.imeeting.common.MeetingProgressStage stage;
|
com.imeeting.common.MeetingProgressStage stage;
|
||||||
int meetingStatus;
|
int meetingStatus;
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,10 @@ public class MeetingPointsServiceImpl implements MeetingPointsService {
|
||||||
throw new RuntimeException("分配积分必须大于0");
|
throw new RuntimeException("分配积分必须大于0");
|
||||||
}
|
}
|
||||||
|
|
||||||
MeetingPointsAccount publicAccount = getOrCreateAccountForMutation(tenantId, UNIFIED_ACCOUNT_USER_ID, 0L, false);
|
MeetingPointsAccount publicAccount = findAccountForMutation(tenantId, UNIFIED_ACCOUNT_USER_ID);
|
||||||
|
if (publicAccount == null) {
|
||||||
|
throw new RuntimeException("公共积分账户不存在");
|
||||||
|
}
|
||||||
long publicBefore = defaultLong(publicAccount.getCurrentBalance());
|
long publicBefore = defaultLong(publicAccount.getCurrentBalance());
|
||||||
if (publicBefore < safePoints) {
|
if (publicBefore < safePoints) {
|
||||||
throw new RuntimeException("公共账户积分不足");
|
throw new RuntimeException("公共账户积分不足");
|
||||||
|
|
@ -258,6 +261,33 @@ public class MeetingPointsServiceImpl implements MeetingPointsService {
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void assertSufficientPointsBeforeAsrSubmit(Meeting meeting, AiTask asrTask) {
|
||||||
|
if (!shouldEnforceBalance() || meeting == null || asrTask == null || meeting.getId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Integer durationSeconds = resolveEffectiveAudioDurationSeconds(meeting);
|
||||||
|
if (durationSeconds == null || durationSeconds <= 0) {
|
||||||
|
throw new RuntimeException("无法解析录音时长,不能校验积分余额");
|
||||||
|
}
|
||||||
|
ensureSufficientPoints(meeting, null, buildChargeSnapshot(durationSeconds).asrPoints(), "ASR_SUBMIT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void assertSufficientPointsBeforeSummarySubmit(Meeting meeting, AiTask summaryTask) {
|
||||||
|
if (!shouldEnforceBalance() || meeting == null || summaryTask == null || meeting.getId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String chargeTriggerType = resolveChargeTriggerType(summaryTask);
|
||||||
|
Integer durationSeconds = resolveEffectiveAudioDurationSeconds(meeting);
|
||||||
|
if (durationSeconds == null || durationSeconds <= 0) {
|
||||||
|
throw new RuntimeException("无法解析录音时长,不能校验积分余额");
|
||||||
|
}
|
||||||
|
ensureSufficientPoints(meeting, summaryTask, chargeTriggerType, durationSeconds, "SUMMARY_SUBMIT");
|
||||||
|
}
|
||||||
|
|
||||||
private MeetingSummaryChargeRecord getOrCreateChargeRecord(Meeting meeting, AiTask summaryTask, String chargeTriggerType, int durationSeconds) {
|
private MeetingSummaryChargeRecord getOrCreateChargeRecord(Meeting meeting, AiTask summaryTask, String chargeTriggerType, int durationSeconds) {
|
||||||
MeetingSummaryChargeRecord record = meetingSummaryChargeRecordMapper.selectForUpdateBySummaryTaskId(summaryTask.getId());
|
MeetingSummaryChargeRecord record = meetingSummaryChargeRecordMapper.selectForUpdateBySummaryTaskId(summaryTask.getId());
|
||||||
if (record != null) {
|
if (record != null) {
|
||||||
|
|
@ -297,6 +327,68 @@ public class MeetingPointsServiceImpl implements MeetingPointsService {
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensureSufficientPoints(Meeting meeting,
|
||||||
|
AiTask summaryTask,
|
||||||
|
String chargeTriggerType,
|
||||||
|
int durationSeconds,
|
||||||
|
String submitStage) {
|
||||||
|
MeetingSummaryChargeRecord record = getOrCreateChargeRecord(meeting, summaryTask, chargeTriggerType, durationSeconds);
|
||||||
|
long requiredPoints = defaultLong(record.getTotalPoints()) - defaultLong(record.getChargedTotalPoints());
|
||||||
|
if (requiredPoints <= 0L) {
|
||||||
|
clearBlockedReason(record);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long availableBalance = resolveAvailableBalanceForCheck(meeting.getTenantId(), record.getUserId());
|
||||||
|
if (availableBalance < requiredPoints) {
|
||||||
|
record.setBlockedReason("INSUFFICIENT_POINTS");
|
||||||
|
record.setFailureReason("INSUFFICIENT_POINTS at " + submitStage + ", required="
|
||||||
|
+ requiredPoints + ", available=" + availableBalance);
|
||||||
|
record.setBalanceBefore(availableBalance);
|
||||||
|
record.setBalanceAfter(availableBalance);
|
||||||
|
record.setSummaryStatus("BLOCKED");
|
||||||
|
saveOrUpdateRecord(record);
|
||||||
|
throw new RuntimeException("积分余额不足");
|
||||||
|
}
|
||||||
|
clearBlockedReason(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureSufficientPoints(Meeting meeting,
|
||||||
|
MeetingSummaryChargeRecord record,
|
||||||
|
long requiredPoints,
|
||||||
|
String submitStage) {
|
||||||
|
if (requiredPoints <= 0L) {
|
||||||
|
clearBlockedReason(record);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Long ownerUserId = record == null || record.getUserId() == null ? meeting.getCreatorId() : record.getUserId();
|
||||||
|
long availableBalance = resolveAvailableBalanceForCheck(meeting.getTenantId(), ownerUserId);
|
||||||
|
if (availableBalance < requiredPoints) {
|
||||||
|
if (record != null) {
|
||||||
|
record.setBlockedReason("INSUFFICIENT_POINTS");
|
||||||
|
record.setFailureReason("INSUFFICIENT_POINTS at " + submitStage + ", required="
|
||||||
|
+ requiredPoints + ", available=" + availableBalance);
|
||||||
|
record.setBalanceBefore(availableBalance);
|
||||||
|
record.setBalanceAfter(availableBalance);
|
||||||
|
record.setSummaryStatus("BLOCKED");
|
||||||
|
saveOrUpdateRecord(record);
|
||||||
|
}
|
||||||
|
throw new RuntimeException("积分余额不足");
|
||||||
|
}
|
||||||
|
clearBlockedReason(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearBlockedReason(MeetingSummaryChargeRecord record) {
|
||||||
|
if (record == null || record.getId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (record.getBlockedReason() != null || "BLOCKED".equals(record.getSummaryStatus())) {
|
||||||
|
record.setBlockedReason(null);
|
||||||
|
record.setFailureReason(null);
|
||||||
|
record.setSummaryStatus(isPointsEnabled() ? STATUS_PENDING : STATUS_DISABLED);
|
||||||
|
saveOrUpdateRecord(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void applyChargeSnapshot(MeetingSummaryChargeRecord record, Meeting meeting, String chargeTriggerType, int durationSeconds) {
|
private void applyChargeSnapshot(MeetingSummaryChargeRecord record, Meeting meeting, String chargeTriggerType, int durationSeconds) {
|
||||||
ChargeSnapshot snapshot = buildChargeSnapshot(durationSeconds);
|
ChargeSnapshot snapshot = buildChargeSnapshot(durationSeconds);
|
||||||
String accountMode = resolveAccountMode();
|
String accountMode = resolveAccountMode();
|
||||||
|
|
@ -328,7 +420,7 @@ public class MeetingPointsServiceImpl implements MeetingPointsService {
|
||||||
for (ChargeTarget target : chargeTargets) {
|
for (ChargeTarget target : chargeTargets) {
|
||||||
totalBalanceBefore += defaultLong(target.account().getCurrentBalance());
|
totalBalanceBefore += defaultLong(target.account().getCurrentBalance());
|
||||||
}
|
}
|
||||||
if (isBalanceEnforced() && totalBalanceBefore < chargeAmount) {
|
if (shouldEnforceBalance() && totalBalanceBefore < chargeAmount) {
|
||||||
record.setBlockedReason("INSUFFICIENT_POINTS");
|
record.setBlockedReason("INSUFFICIENT_POINTS");
|
||||||
saveOrUpdateRecord(record);
|
saveOrUpdateRecord(record);
|
||||||
throw new RuntimeException("积分余额不足");
|
throw new RuntimeException("积分余额不足");
|
||||||
|
|
@ -374,7 +466,7 @@ public class MeetingPointsServiceImpl implements MeetingPointsService {
|
||||||
if (remaining <= 0L) {
|
if (remaining <= 0L) {
|
||||||
return 0L;
|
return 0L;
|
||||||
}
|
}
|
||||||
if (isBalanceEnforced()) {
|
if (shouldEnforceBalance()) {
|
||||||
return Math.min(Math.max(currentBalance, 0L), remaining);
|
return Math.min(Math.max(currentBalance, 0L), remaining);
|
||||||
}
|
}
|
||||||
if (lastTarget) {
|
if (lastTarget) {
|
||||||
|
|
@ -429,6 +521,30 @@ public class MeetingPointsServiceImpl implements MeetingPointsService {
|
||||||
.last("LIMIT 1"));
|
.last("LIMIT 1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MeetingPointsAccount findAccountForMutation(Long tenantId, Long userId) {
|
||||||
|
if (tenantId == null || userId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return meetingPointsAccountMapper.selectForUpdate(tenantId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long resolveAvailableBalanceForCheck(Long tenantId, Long ownerUserId) {
|
||||||
|
Long personalUserId = ownerUserId == null ? UNIFIED_ACCOUNT_USER_ID : ownerUserId;
|
||||||
|
String accountMode = resolveAccountMode();
|
||||||
|
if (ACCOUNT_MODE_PUBLIC.equals(accountMode) || personalUserId.equals(UNIFIED_ACCOUNT_USER_ID)) {
|
||||||
|
return positiveBalance(findAccount(tenantId, UNIFIED_ACCOUNT_USER_ID));
|
||||||
|
}
|
||||||
|
if (ACCOUNT_MODE_PERSONAL.equals(accountMode)) {
|
||||||
|
return positiveBalance(findAccount(tenantId, personalUserId));
|
||||||
|
}
|
||||||
|
return positiveBalance(findAccount(tenantId, personalUserId))
|
||||||
|
+ positiveBalance(findAccount(tenantId, UNIFIED_ACCOUNT_USER_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
private long positiveBalance(MeetingPointsAccount account) {
|
||||||
|
return account == null ? 0L : Math.max(defaultLong(account.getCurrentBalance()), 0L);
|
||||||
|
}
|
||||||
|
|
||||||
private MeetingPointsAccount getOrCreateAccountForMutation(Long tenantId, Long userId, long initialBalance, boolean createInitLedger) {
|
private MeetingPointsAccount getOrCreateAccountForMutation(Long tenantId, Long userId, long initialBalance, boolean createInitLedger) {
|
||||||
MeetingPointsAccount account = meetingPointsAccountMapper.selectForUpdate(tenantId, userId);
|
MeetingPointsAccount account = meetingPointsAccountMapper.selectForUpdate(tenantId, userId);
|
||||||
if (account != null) {
|
if (account != null) {
|
||||||
|
|
@ -463,34 +579,34 @@ public class MeetingPointsServiceImpl implements MeetingPointsService {
|
||||||
String chargePriority = resolveChargePriority();
|
String chargePriority = resolveChargePriority();
|
||||||
List<ChargeTarget> targets = new ArrayList<>();
|
List<ChargeTarget> targets = new ArrayList<>();
|
||||||
if (ACCOUNT_MODE_PUBLIC.equals(accountMode)) {
|
if (ACCOUNT_MODE_PUBLIC.equals(accountMode)) {
|
||||||
targets.add(new ChargeTarget(ACCOUNT_MODE_PUBLIC, UNIFIED_ACCOUNT_USER_ID,
|
addChargeTargetIfAccountExists(targets, ACCOUNT_MODE_PUBLIC, tenantId, UNIFIED_ACCOUNT_USER_ID);
|
||||||
getOrCreateAccountForMutation(tenantId, UNIFIED_ACCOUNT_USER_ID, 0L, false)));
|
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
if (ACCOUNT_MODE_PERSONAL.equals(accountMode)) {
|
if (ACCOUNT_MODE_PERSONAL.equals(accountMode)) {
|
||||||
targets.add(new ChargeTarget(ACCOUNT_MODE_PERSONAL, personalUserId,
|
addChargeTargetIfAccountExists(targets, ACCOUNT_MODE_PERSONAL, tenantId, personalUserId);
|
||||||
getOrCreateAccountForMutation(tenantId, personalUserId, 0L, false)));
|
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
if (personalUserId.equals(UNIFIED_ACCOUNT_USER_ID)) {
|
if (personalUserId.equals(UNIFIED_ACCOUNT_USER_ID)) {
|
||||||
targets.add(new ChargeTarget(ACCOUNT_MODE_PUBLIC, UNIFIED_ACCOUNT_USER_ID,
|
addChargeTargetIfAccountExists(targets, ACCOUNT_MODE_PUBLIC, tenantId, UNIFIED_ACCOUNT_USER_ID);
|
||||||
getOrCreateAccountForMutation(tenantId, UNIFIED_ACCOUNT_USER_ID, 0L, false)));
|
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
if (CHARGE_PRIORITY_PUBLIC_FIRST.equals(chargePriority)) {
|
if (CHARGE_PRIORITY_PUBLIC_FIRST.equals(chargePriority)) {
|
||||||
targets.add(new ChargeTarget(ACCOUNT_MODE_PUBLIC, UNIFIED_ACCOUNT_USER_ID,
|
addChargeTargetIfAccountExists(targets, ACCOUNT_MODE_PUBLIC, tenantId, UNIFIED_ACCOUNT_USER_ID);
|
||||||
getOrCreateAccountForMutation(tenantId, UNIFIED_ACCOUNT_USER_ID, 0L, false)));
|
addChargeTargetIfAccountExists(targets, ACCOUNT_MODE_PERSONAL, tenantId, personalUserId);
|
||||||
targets.add(new ChargeTarget(ACCOUNT_MODE_PERSONAL, personalUserId,
|
|
||||||
getOrCreateAccountForMutation(tenantId, personalUserId, 0L, false)));
|
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
targets.add(new ChargeTarget(ACCOUNT_MODE_PERSONAL, personalUserId,
|
addChargeTargetIfAccountExists(targets, ACCOUNT_MODE_PERSONAL, tenantId, personalUserId);
|
||||||
getOrCreateAccountForMutation(tenantId, personalUserId, 0L, false)));
|
addChargeTargetIfAccountExists(targets, ACCOUNT_MODE_PUBLIC, tenantId, UNIFIED_ACCOUNT_USER_ID);
|
||||||
targets.add(new ChargeTarget(ACCOUNT_MODE_PUBLIC, UNIFIED_ACCOUNT_USER_ID,
|
|
||||||
getOrCreateAccountForMutation(tenantId, UNIFIED_ACCOUNT_USER_ID, 0L, false)));
|
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addChargeTargetIfAccountExists(List<ChargeTarget> targets, String accountMode, Long tenantId, Long userId) {
|
||||||
|
MeetingPointsAccount account = findAccountForMutation(tenantId, userId);
|
||||||
|
if (account != null) {
|
||||||
|
targets.add(new ChargeTarget(accountMode, userId, account));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ChargeSnapshot buildChargeSnapshot(int durationSeconds) {
|
private ChargeSnapshot buildChargeSnapshot(int durationSeconds) {
|
||||||
int chargedMinutes = toChargedMinutes(durationSeconds);
|
int chargedMinutes = toChargedMinutes(durationSeconds);
|
||||||
int unitMinutes = positiveInt(sysParamService.getCachedParamValue(SysParamKeys.MEETING_POINTS_UNIT_MINUTES, "1"), 1);
|
int unitMinutes = positiveInt(sysParamService.getCachedParamValue(SysParamKeys.MEETING_POINTS_UNIT_MINUTES, "1"), 1);
|
||||||
|
|
@ -557,7 +673,11 @@ public class MeetingPointsServiceImpl implements MeetingPointsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isBalanceEnforced() {
|
private boolean isBalanceEnforced() {
|
||||||
return Boolean.parseBoolean(sysParamService.getCachedParamValue(SysParamKeys.MEETING_POINTS_ENFORCE_BALANCE, "false"));
|
return Boolean.parseBoolean(sysParamService.getCachedParamValue(SysParamKeys.MEETING_POINTS_ENFORCE_BALANCE, "true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldEnforceBalance() {
|
||||||
|
return isPointsEnabled() && isBalanceEnforced();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveAccountMode() {
|
private String resolveAccountMode() {
|
||||||
|
|
|
||||||
|
|
@ -351,7 +351,7 @@
|
||||||
"kickDeviceConfirm": "Kick this device offline?",
|
"kickDeviceConfirm": "Kick this device offline?",
|
||||||
"kickSucceeded": "Device has been kicked offline",
|
"kickSucceeded": "Device has been kicked offline",
|
||||||
"resetStats": "Reset stats",
|
"resetStats": "Reset stats",
|
||||||
"resetStatsConfirm": "Reset this device's homepage statistics?",
|
"resetStatsConfirm": "Reset this device's statistics?",
|
||||||
"resetStatsSucceeded": "Device statistics reset",
|
"resetStatsSucceeded": "Device statistics reset",
|
||||||
"deleteDevice": "Delete this device?",
|
"deleteDevice": "Delete this device?",
|
||||||
"weatherCityName": "Weather City",
|
"weatherCityName": "Weather City",
|
||||||
|
|
|
||||||
|
|
@ -358,7 +358,11 @@
|
||||||
"searchSelectUser": "搜索并选择用户",
|
"searchSelectUser": "搜索并选择用户",
|
||||||
"deviceCodeRequired": "请输入设备编码",
|
"deviceCodeRequired": "请输入设备编码",
|
||||||
"deviceCodePlaceholder": "输入唯一设备编码",
|
"deviceCodePlaceholder": "输入唯一设备编码",
|
||||||
"deviceNamePlaceholder": "例如:A 会议室录音设备"
|
"deviceNamePlaceholder": "例如:A 会议室录音设备",
|
||||||
|
"weatherCityName": "城市",
|
||||||
|
"statsResetAt": "重置时间",
|
||||||
|
"resetStatsConfirm": "确认重置?"
|
||||||
|
|
||||||
},
|
},
|
||||||
"dashboardExt": {
|
"dashboardExt": {
|
||||||
"processing": "处理中",
|
"processing": "处理中",
|
||||||
|
|
|
||||||
|
|
@ -385,7 +385,7 @@ export default function Devices() {
|
||||||
<Input placeholder={t("devicesExt.deviceNamePlaceholder")} />
|
<Input placeholder={t("devicesExt.deviceNamePlaceholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("devicesExt.weatherCityName")} name="weatherCityName">
|
<Form.Item label={t("devicesExt.weatherCityName")} name="weatherCityName">
|
||||||
<Input placeholder={t("devicesExt.weatherCityNamePlaceholder")} />
|
<Input placeholder={t("devicesExt.weatherCityName")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("common.status")} name="status" initialValue={1}>
|
<Form.Item label={t("common.status")} name="status" initialValue={1}>
|
||||||
<Select options={statusDict.map((item) => ({ value: Number(item.itemValue), label: item.itemLabel }))} />
|
<Select options={statusDict.map((item) => ({ value: Number(item.itemValue), label: item.itemLabel }))} />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue