提交 16e0183d 作者: 洪东保

重新生成纪要异步处理

父级 89816a5c
......@@ -69,4 +69,30 @@ public class ThreadPoolConfig {
executor.initialize();
return executor;
}
@Bean("regenerateProcessExecutor")
public ThreadPoolTaskExecutor regenerateProcessExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数 (CPU密集型任务建议核心数+1)
executor.setCorePoolSize(1); // 固定核心线程数,避免动态获取CPU核心数
// 最大线程数
executor.setMaxPoolSize(5);
// 队列容量
executor.setQueueCapacity(1000);
// 线程名前缀
executor.setThreadNamePrefix("regenerate-process-");
// 明确设置所有必要属性
executor.setAllowCoreThreadTimeOut(false); // 核心线程不允许超时
executor.setWaitForTasksToCompleteOnShutdown(true); // 优雅关闭
executor.setAwaitTerminationSeconds(60); // 等待任务完成的最大时间
// 拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化前打印配置检查
log.info("Initializing RegenerateThreadPool: core={}, max={}",
executor.getCorePoolSize(),
executor.getMaxPoolSize());
executor.initialize();
return executor;
}
}
\ No newline at end of file
......@@ -150,6 +150,10 @@ public class MeetingInfo implements Serializable {
* 转录文件知识库id
*/
private String transDocId;
/**
* 是否在重新生成
*/
private Boolean reprocess = false;
@TableField(exist = false)
private String userId;
......
......@@ -41,6 +41,8 @@ import com.cmeeting.service.MeetingInfoService;
import com.cmeeting.service.SysUserSyncService;
import com.cmeeting.util.AESUtils;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.util.RedisUtils;
import com.cmeeting.util.TencentMeetingApiUtil;
import com.cmeeting.util.page.PageUtil;
import com.cmeeting.vo.MeetingInfoVO;
import com.fasterxml.jackson.databind.ObjectMapper;
......@@ -61,6 +63,7 @@ import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
......@@ -85,16 +88,6 @@ public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, Meeti
private MeetingInfoMapper mapper;
@Resource
private MinioUtils minioUtils;
@Value(value = "${tencent.appId}")
private String tencentAppId;
@Value(value = "${tencent.sdkId}")
private String tencentSdkId;
@Value(value = "${tencent.secretId}")
private String tencentSecretId;
@Value(value = "${tencent.secretKey}")
private String tencentSecretKey;
@Value(value = "${tencent.admin.userId}")
private String tencentAdminUserId;
@Value(value = "${llm.api-addr}")
private String llmApiAddr;
@Value("${permission.tenantId}")
......@@ -118,6 +111,11 @@ public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, Meeti
@Resource
private ISysUserSyncCategoryService iSysUserSyncCategoryService;
@Resource(name = "regenerateProcessExecutor")
private ThreadPoolTaskExecutor regenerateProcessExecutor;
@Resource
private RedisUtils redisUtils;
@Override
public IPage<MeetingInfo> getPage(MeetingInfoVO vo) {
RobotSecurityUser user = SecurityUtil.getUser();
......@@ -134,7 +132,7 @@ public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, Meeti
.between(vo.getStartTime() != null, MeetingInfo::getStartTime, vo.getStartTime(), vo.getEndTime())
.orderByDesc(MeetingInfo::getStartTime)
.select(MeetingInfo::getId, MeetingInfo::getMeetingId, MeetingInfo::getSubject, MeetingInfo::getHost, MeetingInfo::getHostUid,
MeetingInfo::getStartTime, MeetingInfo::getEndTime, MeetingInfo::getIsGenerated, MeetingInfo::getIsPushed);
MeetingInfo::getStartTime, MeetingInfo::getEndTime, MeetingInfo::getIsGenerated, MeetingInfo::getIsPushed, MeetingInfo::getReprocess);
Page<MeetingInfo> meetingInfoPage = mapper.selectPage(new Page<>(vo.getCurrent(), vo.getSize()), queryWrapper);
if (CollUtil.isNotEmpty(meetingInfoPage.getRecords())) {
List<MeetingInfo> records = meetingInfoPage.getRecords();
......@@ -170,14 +168,41 @@ public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, Meeti
public boolean regenerateXml(MeetingInfoVO vo) {
MeetingRecordTemplate meetingRecordTemplate = meetingRecordTemplateMapper.selectById(vo.getTemplateId());
if (meetingRecordTemplate == null) {
throw new RobotBaseException("param error! template is not exit.");
throw new RobotBaseException("param error! template is not exit!!");
}
Client client = new Client.Builder()
.withAppId(tencentAppId).withSdkId(tencentSdkId)
.withSecret(tencentSecretId,tencentSecretKey)
.build();
MeetingInfo meetingInfo = mapper.selectById(vo.getId());
//已保存的会议信息
if (meetingInfo == null) {
throw new RobotBaseException("meeting is not exit!!");
}
String key = "meet_process" + meetingInfo.getMeetingId() + "_" + (meetingInfo.getSubMeetingId() == null ? "" : meetingInfo.getSubMeetingId());
if (!redisUtils.setnx(key, 1, 240)) {
log.warn("key already exists in redis!, key: {}", key);
return false;
}
regenerateProcessExecutor.execute(()->{
try{
meetingInfoMapper.update(null,
new LambdaUpdateWrapper<MeetingInfo>()
.eq(MeetingInfo::getMeetingId, meetingInfo.getMeetingId())
.eq(meetingInfo.getSubMeetingId() != null, MeetingInfo::getSubMeetingId, meetingInfo.getSubMeetingId())
.set(MeetingInfo::getReprocess, true)
);
regenerateXml(meetingInfo, meetingRecordTemplate);
} finally {
meetingInfoMapper.update(null,
new LambdaUpdateWrapper<MeetingInfo>()
.eq(MeetingInfo::getMeetingId, meetingInfo.getMeetingId())
.eq(meetingInfo.getSubMeetingId() != null, MeetingInfo::getSubMeetingId, meetingInfo.getSubMeetingId())
.set(MeetingInfo::getReprocess, true)
);
redisUtils.del(key);
}
});
return true;
}
public boolean regenerateXml(MeetingInfo meetingInfo, MeetingRecordTemplate meetingRecordTemplate) {
String meetingId = meetingInfo.getMeetingId();
String subMeetingId = meetingInfo.getSubMeetingId();
String recordFileIdArray = meetingInfo.getRecordFileId();
......@@ -186,18 +211,8 @@ public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, Meeti
return false;
}
String meetingDate = meetingInfo.getStartTime().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
// 获取参会成员明细
MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest participantsRequest =
new MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest
.Builder(meetingId).subMeetingId(subMeetingId).operatorId(tencentAdminUserId).operatorIdType("1").build();
AuthenticatorBuilder<JWTAuthenticator> participantsAuthenticatorBuilder =
new JWTAuthenticator.Builder()
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
.timestamp(String.valueOf(System.currentTimeMillis() / 1000L));
MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetResponse participantsResponse =
client.meetings().v1MeetingsMeetingIdParticipantsGet(participantsRequest, participantsAuthenticatorBuilder);
V1MeetingsMeetingIdParticipantsGet200Response participantsData = participantsResponse.getData();
V1MeetingsMeetingIdParticipantsGet200Response participantsData = TencentMeetingApiUtil.ApiV1MeetingsMeetingIdParticipantsGetRequest(meetingId, subMeetingId);
List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = participantsData.getParticipants();
String participantNames = participants.stream().map(item -> new String(Base64.getDecoder().decode(item.getUserName()))).distinct().collect(Collectors.joining("、"));
meetingInfo.setParticipantUsers(participantNames);
......@@ -207,53 +222,7 @@ public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, Meeti
List<String> recordFileIdList = Arrays.asList(meetingInfo.getRecordFileId().split(","));
for (String recordFileId : recordFileIdList) {
//查询录制转写详情
RecordsApi.ApiV1AddressesRecordFileIdGetRequest addressRequest =
new RecordsApi.ApiV1AddressesRecordFileIdGetRequest.Builder(recordFileId)
.operatorId(tencentAdminUserId)
.operatorIdType("1")
.build();
RecordsApi.ApiV1AddressesRecordFileIdGetResponse addressResponse =
client.records().v1AddressesRecordFileIdGet(addressRequest,
new JWTAuthenticator.Builder().nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
.timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
// 处理响应
if (addressResponse != null && addressResponse.getData() != null) {
log.info("Successfully got address for record file {}", recordFileId);
V1AddressesRecordFileIdGet200Response addressData = addressResponse.getData();
// 获取AI会议转录文件
List<V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner> transcripts =
addressData.getAiMeetingTranscripts();
if (transcripts != null && !transcripts.isEmpty()) {
log.info("Found {} AI meeting transcripts for record file {}",
transcripts.size(), recordFileId);
// 处理每个转录文件
for (V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner transcript : transcripts) {
String fileType = transcript.getFileType();
String downloadUrl = transcript.getDownloadAddress();
if ("txt".equalsIgnoreCase(fileType)) {
log.info("AI Transcript - Type: {}, URL: {}", fileType, downloadUrl);
// 1. 下载文件
byte[] fileData = downloadFile(downloadUrl);
// 2. 将二进制文件转换为文本
String recordTextContent = new String(fileData);
if(StringUtils.isNotEmpty(recordTextContent.replaceAll("\\n","").trim())){
recordTextBuffer.append("\n\n");
recordTextBuffer.append(recordTextContent);
}
}
}
} else {
log.info("No AI meeting transcripts found for record file {}", recordFileId);
}
} else {
log.warn("Empty response for record file: {}", recordFileId);
}
recordTextBuffer.append(TencentMeetingApiUtil.ApiV1AddressesRecordFileIdGetRequest(recordFileId));
}
if(StringUtils.isEmpty(recordTextBuffer.toString().replaceAll("\\n","").trim())){
log.info("获取的转录文本为空,跳过纪要生成,meetingId:{},fileRecordId:{}",meetingId,recordFileIdList.toString());
......@@ -269,7 +238,7 @@ public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, Meeti
.eq(MeetingInfo::getMeetingId,meetingId)
.eq(subMeetingId != null,MeetingInfo::getSubMeetingId,subMeetingId)
.set(MeetingInfo::getRecordXml,meetingInfo.getRecordXml())
.set(MeetingInfo::getTemplateId,meetingRecordTemplate.getId())
.set(MeetingInfo::getTemplateId, meetingRecordTemplate.getId())
);
return true;
}catch (Exception e){
......
......@@ -94,6 +94,7 @@ public class MeetingInfoVO {
* 邮件推送重试标识
*/
private Boolean pushRetry;
private Boolean reprocess;
/**
* 同步时间
*/
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论