提交 0cc18764 作者: 洪东保

定时任务修改

父级 cdaad871
...@@ -170,7 +170,7 @@ public class RecordTemplateController { ...@@ -170,7 +170,7 @@ public class RecordTemplateController {
* 模板测试效果 * 模板测试效果
* @param file 用户自主上传的转录文件 * @param file 用户自主上传的转录文件
* @param meetingInstId 历史会议主键id * @param meetingInstId 历史会议主键id
* @param id 模板id * @param content 模板提示词
* @return * @return
*/ */
@PostMapping("/testGenerate") @PostMapping("/testGenerate")
......
...@@ -127,7 +127,6 @@ public class UnifiedController { ...@@ -127,7 +127,6 @@ public class UnifiedController {
//取消预约会议 //取消预约会议
} }
// //todo 待测试
// @GetMapping("/sameNameInsertTid") // @GetMapping("/sameNameInsertTid")
// public void sameNameInsertTid(String corpid, String corpsecret) throws IOException { // public void sameNameInsertTid(String corpid, String corpsecret) throws IOException {
// String weComToken = getWeComToken(corpid,corpsecret); // String weComToken = getWeComToken(corpid,corpsecret);
......
...@@ -43,17 +43,18 @@ public class CmeetingJob { ...@@ -43,17 +43,18 @@ public class CmeetingJob {
@Value("${isDev}") @Value("${isDev}")
private Boolean isDev; private Boolean isDev;
// @PostConstruct // @PostConstruct
public void weComUserInit(){ public void weComUserInit() {
weComUserSync(); weComUserSync();
} }
// @PostConstruct // @PostConstruct
public void tencentUserInit(){ public void tencentUserInit() {
TencentUserSync(); TencentUserSync();
} }
// @PostConstruct
public void userBindInit(){ // @PostConstruct
public void userBindInit() {
userBind(); userBind();
} }
...@@ -109,28 +110,28 @@ public class CmeetingJob { ...@@ -109,28 +110,28 @@ public class CmeetingJob {
log.info("-------关联企微腾会人员定时任务结束--------"); log.info("-------关联企微腾会人员定时任务结束--------");
} }
@Scheduled(fixedRate = 20 * 60 * 1000,initialDelay = 2 * 60 * 1000) @Scheduled(fixedRate = 20 * 60 * 1000, initialDelay = 2 * 60 * 1000)
public void execute() { public void execute() {
if (isDev) { if (isDev) {
return; return;
} }
//查出企微id和腾会id的关联关系 //查出企微id和腾会id的关联关系
List<UserId> userIdRelations = userIdMapper.selectList(null); List<UserId> userIdRelations = userIdMapper.selectList(null);
Map<String,String> widTidRelations = userIdRelations.stream().collect(Collectors.toMap(UserId::getWid,UserId::getTid)); Map<String, String> widTidRelations = userIdRelations.stream().collect(Collectors.toMap(UserId::getWid, UserId::getTid));
Map<String,String> tidWidRelations = userIdRelations.stream().collect(Collectors.toMap(UserId::getTid,UserId::getWid)); Map<String, String> tidWidRelations = userIdRelations.stream().collect(Collectors.toMap(UserId::getTid, UserId::getWid));
//查出企微的人员信息 //查出企微的人员信息
List<WeComUser> weComUserList = weComService.list(); List<WeComUser> weComUserList = weComService.list();
Map<String,WeComUser> weComUserMap = weComUserList.stream().collect(Collectors.toMap(WeComUser::getUserId, Function.identity())); Map<String, WeComUser> weComUserMap = weComUserList.stream().collect(Collectors.toMap(WeComUser::getUserId, Function.identity()));
//智能体授权人员 //智能体授权人员
List<UserDTO> accessUserIds = tencentMeetingService.getAccessUserIds(widTidRelations); List<UserDTO> accessUserIds = tencentMeetingService.getAccessUserIds(widTidRelations);
if (CollectionUtils.isEmpty(accessUserIds)) { if (CollectionUtils.isEmpty(accessUserIds)) {
log.info("无生成纪要权限的人员"); log.info("无生成纪要权限的人员");
return; return;
}else{ } else {
log.info("生成纪要权限人员:->{}", accessUserIds.stream().map(UserDTO::getWid).collect(Collectors.joining(","))); log.info("生成纪要权限人员:->{}", accessUserIds.stream().map(UserDTO::getWid).collect(Collectors.joining(",")));
} }
List<TencentMeetingVO.RecordFile> meetingFiles = tencentMeetingService.getMeetingFiles(accessUserIds,weComUserMap); List<TencentMeetingVO.RecordFile> meetingFiles = tencentMeetingService.getMeetingFiles(accessUserIds, weComUserMap);
if (meetingFiles == null || meetingFiles.isEmpty()) { if (meetingFiles == null || meetingFiles.isEmpty()) {
log.info("没有录制文件需要处理"); log.info("没有录制文件需要处理");
...@@ -141,14 +142,14 @@ public class CmeetingJob { ...@@ -141,14 +142,14 @@ public class CmeetingJob {
List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers = meetingRecordTemplateService.selectAuthorizedUsers(); List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers = meetingRecordTemplateService.selectAuthorizedUsers();
// 提交处理任务 // 提交处理任务
producer.submitBatchTasks(meetingFiles,authorizedUsers,tidWidRelations,Boolean.FALSE); producer.submitBatchTasks(meetingFiles, authorizedUsers, tidWidRelations, Boolean.FALSE);
} }
/** /**
* 定时扫描早于一小时之前的,所有未重试过的会议,重新生成纪要 * 定时扫描早于一小时之前的,所有未重试过的会议,重新生成纪要
*/ */
@Scheduled(fixedRate = 30 * 60 * 1000,initialDelay = 10 * 60 * 1000) @Scheduled(fixedRate = 30 * 60 * 1000, initialDelay = 10 * 60 * 1000)
public void meetingMinutesRetry() { public void meetingMinutesRetry() {
if (isDev) { if (isDev) {
return; return;
...@@ -160,9 +161,9 @@ public class CmeetingJob { ...@@ -160,9 +161,9 @@ public class CmeetingJob {
//查出所有早于一小时前的,生成失败且未重试过的会议 //查出所有早于一小时前的,生成失败且未重试过的会议
List<MeetingInfo> meetingInfoList = List<MeetingInfo> meetingInfoList =
meetingInfoService.list(new LambdaQueryWrapper<MeetingInfo>() meetingInfoService.list(new LambdaQueryWrapper<MeetingInfo>()
.eq(MeetingInfo::getIsGenerated,Boolean.FALSE) .eq(MeetingInfo::getIsGenerated, Boolean.FALSE)
.eq(MeetingInfo::getGenerateRetry,Boolean.FALSE) .eq(MeetingInfo::getGenerateRetry, Boolean.FALSE)
.le(MeetingInfo::getSyncTime,LocalDateTime.now().minusHours(1)) .le(MeetingInfo::getSyncTime, LocalDateTime.now().minusHours(1))
); );
if (meetingInfoList == null || meetingInfoList.isEmpty()) { if (meetingInfoList == null || meetingInfoList.isEmpty()) {
...@@ -180,13 +181,13 @@ public class CmeetingJob { ...@@ -180,13 +181,13 @@ public class CmeetingJob {
//查出企微id和腾会id的关联关系 //查出企微id和腾会id的关联关系
List<UserId> userIdRelations = userIdMapper.selectList(null); List<UserId> userIdRelations = userIdMapper.selectList(null);
Map<String,String> tidWidRelations = userIdRelations.stream().collect(Collectors.toMap(UserId::getTid,UserId::getWid)); Map<String, String> tidWidRelations = userIdRelations.stream().collect(Collectors.toMap(UserId::getTid, UserId::getWid));
//获取模板授权的人员 //获取模板授权的人员
List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers = meetingRecordTemplateService.selectAuthorizedUsers(); List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers = meetingRecordTemplateService.selectAuthorizedUsers();
// 提交处理任务 // 提交处理任务
producer.submitBatchTasks(meetingFiles,authorizedUsers,tidWidRelations, Boolean.TRUE); producer.submitBatchTasks(meetingFiles, authorizedUsers, tidWidRelations, Boolean.TRUE);
log.info("-------生成纪要重试定时任务结束--------"); log.info("-------生成纪要重试定时任务结束--------");
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
...@@ -196,7 +197,7 @@ public class CmeetingJob { ...@@ -196,7 +197,7 @@ public class CmeetingJob {
/** /**
* 定时扫描早于一小时之前的,所有邮件推送未重试过的会议,重新推送邮件 * 定时扫描早于一小时之前的,所有邮件推送未重试过的会议,重新推送邮件
*/ */
@Scheduled(fixedRate = 30 * 60 * 1000,initialDelay = 15 * 60 * 1000) @Scheduled(fixedRate = 30 * 60 * 1000, initialDelay = 15 * 60 * 1000)
public void emailPushRetry() { public void emailPushRetry() {
if (isDev) { if (isDev) {
return; return;
...@@ -208,11 +209,11 @@ public class CmeetingJob { ...@@ -208,11 +209,11 @@ public class CmeetingJob {
//查出所有早于一小时前的,邮件推送失败且未重试过的会议 //查出所有早于一小时前的,邮件推送失败且未重试过的会议
List<MeetingInfo> meetingInfoList = List<MeetingInfo> meetingInfoList =
meetingInfoService.list(new LambdaQueryWrapper<MeetingInfo>() meetingInfoService.list(new LambdaQueryWrapper<MeetingInfo>()
.eq(MeetingInfo::getIsGenerated,Boolean.TRUE) .eq(MeetingInfo::getIsGenerated, Boolean.TRUE)
.eq(MeetingInfo::getEmailPushAccess,Boolean.TRUE) .eq(MeetingInfo::getEmailPushAccess, Boolean.TRUE)
.eq(MeetingInfo::getIsPushed,Boolean.FALSE) .eq(MeetingInfo::getIsPushed, Boolean.FALSE)
.eq(MeetingInfo::getPushRetry,Boolean.FALSE) .eq(MeetingInfo::getPushRetry, Boolean.FALSE)
.le(MeetingInfo::getSyncTime,LocalDateTime.now().minusHours(1)) .le(MeetingInfo::getSyncTime, LocalDateTime.now().minusHours(1))
); );
if (meetingInfoList == null || meetingInfoList.isEmpty()) { if (meetingInfoList == null || meetingInfoList.isEmpty()) {
...@@ -229,9 +230,9 @@ public class CmeetingJob { ...@@ -229,9 +230,9 @@ public class CmeetingJob {
//查出企微id和腾会id的关联关系 //查出企微id和腾会id的关联关系
List<UserId> userIdRelations = userIdMapper.selectList(null); List<UserId> userIdRelations = userIdMapper.selectList(null);
Map<String,String> tidWidRelations = userIdRelations.stream().collect(Collectors.toMap(UserId::getTid,UserId::getWid)); Map<String, String> tidWidRelations = userIdRelations.stream().collect(Collectors.toMap(UserId::getTid, UserId::getWid));
// 提交处理任务 // 提交处理任务
producer.submitEmailPushTasks(meetingFiles,tidWidRelations); producer.submitEmailPushTasks(meetingFiles, tidWidRelations);
log.info("-------邮件推送重试定时任务结束--------"); log.info("-------邮件推送重试定时任务结束--------");
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
......
...@@ -12,6 +12,7 @@ import cn.chatbot.openai.service.LLMService; ...@@ -12,6 +12,7 @@ import cn.chatbot.openai.service.LLMService;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference;
...@@ -24,6 +25,7 @@ import com.cmeeting.constant.UserAdminRouteConstant; ...@@ -24,6 +25,7 @@ import com.cmeeting.constant.UserAdminRouteConstant;
import com.cmeeting.dto.DocResultDto; import com.cmeeting.dto.DocResultDto;
import com.cmeeting.dto.UserDTO; import com.cmeeting.dto.UserDTO;
import com.cmeeting.email.EmailSender; import com.cmeeting.email.EmailSender;
import com.cmeeting.exception.RobotBaseException;
import com.cmeeting.log.service.ProcessLogService; import com.cmeeting.log.service.ProcessLogService;
import com.cmeeting.mapper.primary.MeetingInfoMapper; import com.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper; import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper;
...@@ -86,23 +88,19 @@ public class FileProcessTask { ...@@ -86,23 +88,19 @@ public class FileProcessTask {
private int retryCount = 0; private int retryCount = 0;
private static final int MAX_RETRY = 3; private static final int MAX_RETRY = 3;
private String tencentAppId;
private String tencentSdkId;
private String tencentSecretId;
private String tencentSecretKey;
private String tencentAdminUserId;
private String llmApiAddr; private String llmApiAddr;
private Boolean finalRetry; //表示是兜底重试机制 private Boolean finalRetry; //表示是兜底重试机制
private MeetingInfoMapper meetingInfoMapper; private MeetingInfoMapper meetingInfoMapper;
private MinioUtils minioUtils; private MinioUtils minioUtils;
private RedisUtils redisUtils;
private EmailSender emailSender; private EmailSender emailSender;
private MeetingRecordTemplateMapper meetingRecordTemplateMapper; private MeetingRecordTemplateMapper meetingRecordTemplateMapper;
private ProcessLogService processLogService; private ProcessLogService processLogService;
//获取模板授权的人员 //获取模板授权的人员
private List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers; private List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers;
//腾会id企微id对应关系 //腾会id企微id对应关系
private Map<String,String> tidWidRelations; private Map<String, String> tidWidRelations;
private UserAdminConfig userAdminConfig; private UserAdminConfig userAdminConfig;
//获取到的token //获取到的token
private String adminToken; private String adminToken;
...@@ -112,16 +110,17 @@ public class FileProcessTask { ...@@ -112,16 +110,17 @@ public class FileProcessTask {
// 实际处理逻辑 // 实际处理逻辑
public void process() { public void process() {
// TODO 根据meeting和subMeetingId上锁
// boolean getKey = redisUtils.setnx("meeting_" + meetingId + subMeetingId, meetingId, 200000);
// while (getKey) {
// Thread.sleep(1000);
// }
boolean isSuccess = false; boolean isSuccess = false;
while (retryCount <= MAX_RETRY && !isSuccess) { while (retryCount <= MAX_RETRY && !isSuccess) {
Client client = new Client.Builder()
.withAppId(tencentAppId).withSdkId(tencentSdkId)
.withSecret(tencentSecretId,tencentSecretKey)
.build();
try { try {
//已保存的会议信息 //已保存的会议信息
MeetingInfo meetingInfo = meetingInfoMapper.selectOne(new LambdaQueryWrapper<MeetingInfo>() MeetingInfo meetingInfo = meetingInfoMapper.selectOne(new LambdaQueryWrapper<MeetingInfo>()
.eq(MeetingInfo::getMeetingId,meetingId) .eq(MeetingInfo::getMeetingId, meetingId)
.eq(subMeetingId != null, MeetingInfo::getSubMeetingId, subMeetingId)); .eq(subMeetingId != null, MeetingInfo::getSubMeetingId, subMeetingId));
String meetingDate = meetingInfo.getStartTime().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE); String meetingDate = meetingInfo.getStartTime().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
//下面再查一遍会议信息的意义是,为了获取会议中的子会议Id,如果是周期会议,需要保存下来(重要) //下面再查一遍会议信息的意义是,为了获取会议中的子会议Id,如果是周期会议,需要保存下来(重要)
...@@ -191,136 +190,63 @@ public class FileProcessTask { ...@@ -191,136 +190,63 @@ public class FileProcessTask {
// } // }
// 获取参会成员明细 // 获取参会成员明细
MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest participantsRequest = V1MeetingsMeetingIdParticipantsGet200Response participantsData = TencentMeetingApiUtil.ApiV1MeetingsMeetingIdParticipantsGetRequest(meetingId, subMeetingId);
new MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest if (participantsData == null) {
.Builder(meetingId).subMeetingId(subMeetingId).operatorId(tencentAdminUserId).operatorIdType("1").build(); throw new RobotBaseException("获取参会成员明细失败, meetingId: " + meetingId);
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();
List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = participantsData.getParticipants(); List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = participantsData.getParticipants();
String participantNames = participants.stream().map(item -> new String(Base64.getDecoder().decode(item.getUserName()))).distinct().collect(Collectors.joining("、")); String participantNames = participants.stream().map(item -> new String(Base64.getDecoder().decode(item.getUserName()))).distinct().collect(Collectors.joining("、"));
meetingInfo.setParticipantUsers(participantNames); meetingInfo.setParticipantUsers(participantNames);
//每场会议可能会分段录制,查出每个文件的转录记录后拼接 //每场会议可能会分段录制,查出每个文件的转录记录后拼接
StringBuffer recordTextBuffer = new StringBuffer(); StringBuilder recordTextBuffer = new StringBuilder();
for (String recordFileId : recordFileIdList) { for (String recordFileId : recordFileIdList) {
//查询录制转写详情 recordTextBuffer.append(TencentMeetingApiUtil.ApiV1AddressesRecordFileIdGetRequest(recordFileId));
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);
}
} }
if(StringUtils.isEmpty(recordTextBuffer.toString().replaceAll("\\n","").trim())){ if (StringUtils.isEmpty(recordTextBuffer.toString().replaceAll("\\n", "").trim())) {
log.info("获取的转录文本为空,跳过纪要生成,meetingId:{},fileRecordId:{}",meetingId,recordFileIdList.toString()); log.info("获取的转录文本为空,跳过纪要生成,meetingId:{},fileRecordId:{}", meetingId, recordFileIdList.toString());
processLogService.log(meetingId,subMeetingId,"获取的转录文本为空,跳过纪要生成"); processLogService.log(meetingId, subMeetingId, "获取的转录文本为空,跳过纪要生成");
throw new RuntimeException("获取的转录文本为空,跳过纪要生成"); throw new RuntimeException("获取的转录文本为空,跳过纪要生成");
} }
// 3. 处理文件 (调用Claude API等) // 3. 处理文件 (调用Claude API等)
String choiceTemplateType = choiceTemplateType(meetingInfo.getSubject(),recordTextBuffer.toString());
log.info("choiceTemplateType->{}",choiceTemplateType); // TODO 1. 根据转录文件内容recordTextBuffer判断使用模板类型
//获取系统模板 String choiceTemplateType = choiceTemplateType(meetingInfo.getSubject(), recordTextBuffer.toString());
List<MeetingRecordTemplate> recordTemplateList = meetingRecordTemplateMapper.selectList( log.info("choiceTemplateType->{}", choiceTemplateType);
new LambdaQueryWrapper<MeetingRecordTemplate>().eq(MeetingRecordTemplate::getType, RecordTemplateConstant.TEMPLATE_TYPE_SYSTEM) // TODO 2. 获取这个会议需要使用的一个模板
.eq(MeetingRecordTemplate::getIsDel,Boolean.FALSE).isNotNull(MeetingRecordTemplate::getTemplate)); // 2.1 封装一个方法:入参(a.工号/腾讯会议uid,b.choiceTemplateType);结果(一个模板MeetingRecordTemplate)
Map<Integer, List<String>> authorizedUserMap = authorizedUsers.stream().collect(Collectors.toMap(item -> item.getRecordTemplateId(), item -> item.getUserIdList())); MeetingRecordTemplate template = new MeetingRecordTemplate();
List<EmailPush.Attachment> attachments = new ArrayList<>(); List<EmailPush.Attachment> attachments = new ArrayList<>();
String hostUid = meetingInfo.getHostUid(); String hostUid = meetingInfo.getHostUid();
String toUserCode = tidWidRelations.get(meetingInfo.getHostUid()); String toUserCode = tidWidRelations.get(meetingInfo.getHostUid());
if(!tidWidRelations.containsKey(hostUid)){ if (!tidWidRelations.containsKey(hostUid)) {
log.info("用户{}暂未关联企微信息,无法生成纪要文件",hostUid); log.info("用户{}暂未关联企微信息,无法生成纪要文件", hostUid);
processLogService.log(meetingId,subMeetingId,"用户"+hostUid+"暂未关联企微信息,无法生成纪要文件"); processLogService.log(meetingId, subMeetingId, "用户" + hostUid + "暂未关联企微信息,无法生成纪要文件");
continue; continue;
} }
String processedResult = null;
for (MeetingRecordTemplate template : recordTemplateList) {
//判断本次纪要有模板生成权限
if(!authorizedUserMap.containsKey(template.getId())){
log.info("模板{}暂未授权给任意对象",template.getName());
processLogService.log(meetingId,subMeetingId,"模板"+template.getName()+"暂未授权给任意对象");
continue;
}
List<String> authorizedUserIds = authorizedUserMap.get(template.getId());
if(!authorizedUserIds.contains(tidWidRelations.get(hostUid))){
log.info("用户{}暂无模板{}权限",hostUid,template.getName());
processLogService.log(meetingId,subMeetingId,"用户"+hostUid+"暂无模板"+template.getName()+"权限");
continue;
}else{
log.info("用户{}允许应用模板{}",hostUid,template.getName());
processLogService.log(meetingId,subMeetingId,"用户"+hostUid+"允许应用模板"+template.getName());
}
//暂时让所有模板共用一个提示词,两个模板输出同样的结果
if(StringUtils.isEmpty(processedResult)){
processedResult = processWithClaude(recordTextBuffer.toString(),meetingDate,participantNames,template.getPrompt());
}
String minutesPath = saveResult(processedResult, recordTextBuffer.toString().getBytes(StandardCharsets.UTF_8), meetingInfo,toUserCode, template); String processedResult = processWithClaude(recordTextBuffer.toString(), meetingDate, participantNames, template.getPrompt());
try(InputStream is = new FileInputStream(minutesPath)){ String minutesPath = saveResult(processedResult, recordTextBuffer.toString().getBytes(StandardCharsets.UTF_8), meetingInfo, toUserCode, template);
byte[] meetingMinutesBytes = IOUtils.toByteArray(is); try (InputStream is = new FileInputStream(minutesPath)) {
EmailPush.Attachment attachment = EmailPush.Attachment.builder().name(meetingInfo.getSubject()+"会议纪要_"+template.getName()).bytes(meetingMinutesBytes).build(); byte[] meetingMinutesBytes = IOUtils.toByteArray(is);
attachments.add(attachment); EmailPush.Attachment attachment = EmailPush.Attachment.builder().name(meetingInfo.getSubject() + "会议纪要_" + template.getName()).bytes(meetingMinutesBytes).build();
}catch (Exception e){ attachments.add(attachment);
throw new RuntimeException(e); } catch (Exception e) {
}finally { throw new RuntimeException(e);
FileUtil.del(minutesPath); } finally {
} FileUtil.del(minutesPath);
} }
if(CollectionUtils.isEmpty(attachments)){ if (CollectionUtils.isEmpty(attachments)) {
log.info("用户{}暂无任何模板权限,纪要生成失败",hostUid); log.info("用户{}暂无任何模板权限,纪要生成失败", hostUid);
isSuccess = false; isSuccess = false;
continue; continue;
} }
if(!tidWidRelations.containsKey(meetingInfo.getHostUid())){ if (!tidWidRelations.containsKey(meetingInfo.getHostUid())) {
log.error("邮件推送重试失败: 主持人对应关系未配置。meetingId {}", meetingId); log.error("邮件推送重试失败: 主持人对应关系未配置。meetingId {}", meetingId);
processLogService.log(meetingId,subMeetingId,"邮件推送重试失败: 主持人对应关系未配置。meetingId "+meetingId); processLogService.log(meetingId, subMeetingId, "邮件推送重试失败: 主持人对应关系未配置。meetingId " + meetingId);
continue; continue;
} }
EmailPush emailPushBuilder = EmailPush.builder() EmailPush emailPushBuilder = EmailPush.builder()
.toEmail(meetingInfo.getEmail()) .toEmail(meetingInfo.getEmail())
.meetingId(meetingId) .meetingId(meetingId)
...@@ -338,20 +264,20 @@ public class FileProcessTask { ...@@ -338,20 +264,20 @@ public class FileProcessTask {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw); PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw); e.printStackTrace(pw);
processLogService.log(meetingId,subMeetingId,sw.toString()); processLogService.log(meetingId, subMeetingId, sw.toString());
// 异常处理 // 异常处理
retryCount++; retryCount++;
if (retryCount > MAX_RETRY) { if (retryCount > MAX_RETRY) {
log.error("达到最大重试次数:meetingId {}", meetingId); log.error("达到最大重试次数:meetingId {}", meetingId);
//如果是兜底重试,最终还是失败了,设置会议的重试状态为已重试 //如果是兜底重试,最终还是失败了,设置会议的重试状态为已重试
if(finalRetry){ if (finalRetry) {
meetingInfoMapper.update(null, meetingInfoMapper.update(null,
new LambdaUpdateWrapper<MeetingInfo>() new LambdaUpdateWrapper<MeetingInfo>()
.eq(MeetingInfo::getMeetingId,meetingId) .eq(MeetingInfo::getMeetingId, meetingId)
.eq(subMeetingId != null,MeetingInfo::getSubMeetingId,subMeetingId) .eq(subMeetingId != null, MeetingInfo::getSubMeetingId, subMeetingId)
.set(MeetingInfo::getGenerateRetry,Boolean.TRUE)); .set(MeetingInfo::getGenerateRetry, Boolean.TRUE));
} }
}else{ } else {
// 指数退避 // 指数退避
try { try {
Thread.sleep((long) Math.pow(2, retryCount) * 1000); Thread.sleep((long) Math.pow(2, retryCount) * 1000);
...@@ -364,7 +290,7 @@ public class FileProcessTask { ...@@ -364,7 +290,7 @@ public class FileProcessTask {
} }
} }
private byte[] downloadFile(String url) { private byte[] downloadFile(String url) {
// 实现文件下载逻辑 // 实现文件下载逻辑
OkHttpClient client = new OkHttpClient(); OkHttpClient client = new OkHttpClient();
...@@ -373,14 +299,14 @@ public class FileProcessTask { ...@@ -373,14 +299,14 @@ public class FileProcessTask {
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
return response.body().bytes(); return response.body().bytes();
}catch (Exception e){ } catch (Exception e) {
throw new RuntimeException("下载文件失败", e); throw new RuntimeException("下载文件失败", e);
} }
} }
private String getRecordTextContent(byte[] fileData) { private String getRecordTextContent(byte[] fileData) {
// 定义文件路径和文件名 // 定义文件路径和文件名
String fullPath = savePath + (System.currentTimeMillis() / 1000L) + ".docx"; String fullPath = savePath + (System.currentTimeMillis() / 1000L) + ".docx";
// 将字节内容写入本地文件 // 将字节内容写入本地文件
try (FileOutputStream fos = new FileOutputStream(fullPath)) { try (FileOutputStream fos = new FileOutputStream(fullPath)) {
fos.write(fileData); fos.write(fileData);
...@@ -403,25 +329,62 @@ public class FileProcessTask { ...@@ -403,25 +329,62 @@ public class FileProcessTask {
/** /**
* 提供会议转录文件和会议主题,判断会议类型 * 提供会议转录文件和会议主题,判断会议类型
* @param subject *
* @param textContent * @param subject 会议主题
* @param transcript 转录文件内容
* @return * @return
*/ */
private String choiceTemplateType(String subject,String textContent) { private String choiceTemplateType(String subject, String transcript) {
String token = "AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO"; String token = "AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO";
String apiAddr = llmApiAddr + "/llm/sse-invoke"; String apiAddr = llmApiAddr + "/llm/sse-invoke";
String model = "arn:aws:bedrock:us-east-1:491822380689:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0"; String model = "arn:aws:bedrock:us-east-1:491822380689:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0";
int maxTokens = 5000; int maxTokens = 5000;
String prompt = "请先对以下会议转写记录进行简要总结(不超过200字),然后根据会议主题以及总结内容判断该会议最可能属于哪种类型:\\n\\n会议主题: {0}\\n会议转写记录: {1}\\n\\n第一步:请简要总结会议的主要内容和目的(不超过200字)。\\n\\n第二步:根据上述总结和会议主题,将会议分类为以下类型之一:\\n1. 项目沟通会 - 与具体项目进展、问题讨论相关的会议\\n2. 重要会议 - 高层决策、战略规划等重要会议\\n3. 启动会 - 项目启动、活动筹备等初始会议\\n4. 其他 - 不符合以上任何分类\\n\\n分类权重判断规则:\\n- 基础权重分配:会议内容总结(70%)、会议主题(30%)\\n- 会议主题权重动态调整:\\n * 如果会议主题包含明确分类关键词(如\\\"启动会\\\"、\\\"项目沟通\\\"、\\\"战略决策\\\"等),则会议主题权重提升至60%,总结内容权重调整为40%\\n * 如果会议主题过于简单或模糊(如仅包含\\\"沟通\\\"、\\\"讨论\\\"、\\\"会议\\\"等通用词),则会议主题权重降低至15%,总结内容权重提升至85%\\n- 一致性判断:如果会议主题和总结内容指向不同分类,优先采用内容总结的分类,除非会议主题非常明确且规范\\n\\n输出格式:\\n请只返回分类名称(如\\\"项目沟通会\\\"),不要包含其他内容(如生成的会议总结等)。"; // String prompt = "请先对以下会议转写记录进行简要总结(不超过200字),然后根据会议主题以及总结内容判断该会议最可能属于哪种类型:\\n\\n会议主题: {0}\\n会议转写记录: {1}\\n\\n第一步:请简要总结会议的主要内容和目的(不超过200字)。\\n\\n第二步:根据上述总结和会议主题,将会议分类为以下类型之一:\\n1. 项目沟通会 - 与具体项目进展、问题讨论相关的会议\\n2. 重要会议 - 高层决策、战略规划等重要会议\\n3. 启动会 - 项目启动、活动筹备等初始会议\\n4. 其他 - 不符合以上任何分类\\n\\n分类权重判断规则:\\n- 基础权重分配:会议内容总结(70%)、会议主题(30%)\\n- 会议主题权重动态调整:\\n * 如果会议主题包含明确分类关键词(如\\\"启动会\\\"、\\\"项目沟通\\\"、\\\"战略决策\\\"等),则会议主题权重提升至60%,总结内容权重调整为40%\\n * 如果会议主题过于简单或模糊(如仅包含\\\"沟通\\\"、\\\"讨论\\\"、\\\"会议\\\"等通用词),则会议主题权重降低至15%,总结内容权重提升至85%\\n- 一致性判断:如果会议主题和总结内容指向不同分类,优先采用内容总结的分类,除非会议主题非常明确且规范\\n\\n输出格式:\\n请只返回分类名称(如\\\"项目沟通会\\\"),不要包含其他内容(如生成的会议总结等)。";
String prompt = "## 角色定义\n" +
"你是一位会议分类师,擅长对各种类型会议进行分类。\n" +
"\n" +
"请对以下会议转写记录进行全面而精炼的总结(尽量控制在500字以内,字数可以根据会议转写记录长度适当进行调整),然后根据会议主题以及总结内容判断该会议属于的类型:\n" +
"\n" +
"会议主题: {subject}\n" +
"会议转写记录: {transcript}\n" +
"\n" +
"识别会议类型步骤如下:\n" +
"第一步:识别会议名称/标题(如:项目例会,数据问题讨论);如果识别不出来,标注“暂无”;\n" +
"第二步:根据识别出来的会议名称进行分类(如:数据问题讨论,讨论会)。如果会议名称如:快速会议,张三预定的会议,分类为“其他”。如果是暂无,则进入第三步:\n" +
"第三步:总结会议的主要内容和目的(尽量控制在500字以内,字数可以根据会议转写记录长度适当进行调整)。确保涵盖所有关键讨论内容、重要决策和行动项,捕捉所有实质性内容。摘要应当完整反映会议的核心目的,不遗漏重要信息。\n" +
"\n" +
"第三步:根据上述内容和目的总结,将会议分类为以下类型之一:\n" +
"1. 项目例会 - 会议有周期标签。要讨论很多件事情,每件事情讨论时间很短。\n" +
"2. 启动会 - 项目启动、活动筹备等初始会议,各方代表发言。\n" +
"3. 讨论会 - 只针对一到两个事件针对性讨论,可能是讨论方案。\n" +
"4. 其他 - 不符合上面任意一类。\n" +
"\n" +
"\n" +
"输出格式:\n" +
"{{\n" +
" \"classification\": 类型\n" +
"}}\n" +
"\n" +
"注意:\n" +
"1.请严格按照输出格式以JSON格式返回数据,所有字段都必须包含。\n" +
"2.输出必须是严格有效的JSON格式,可以直接被解析。不要带有json标记,不要包含其他内容。";
//占位符信息替换 //占位符信息替换
String formatPrompt = formatMessage(prompt, subject, textContent); String formatPrompt = formatMessage(prompt, subject, transcript);
List<Message> messages = new ArrayList<>(); List<Message> messages = new ArrayList<>();
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), formatPrompt); ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), formatPrompt);
messages.add(chatMessage); messages.add(chatMessage);
// 调用Claude API处理文件 // 调用Claude API处理文件
String ret = call_llm(apiAddr, model, token, messages, maxTokens); String response = call_llm(apiAddr, model, token, messages, maxTokens);
return ret; JSONObject object = JSONObject.parseObject(response);
String retStr = "通用会议";
if (object != null) {
String classification = object.getString("classification");
if (StrUtil.isNotBlank(classification)) {
retStr = classification;
}
}
return retStr;
} }
private String formatMessage(String pattern, Object... args) { private String formatMessage(String pattern, Object... args) {
...@@ -430,13 +393,14 @@ public class FileProcessTask { ...@@ -430,13 +393,14 @@ public class FileProcessTask {
/** /**
* 大模型生成纪要xml * 大模型生成纪要xml
* @param textContent 转录文件 *
* @param meetingDate 会议日期 * @param textContent 转录文件
* @param meetingDate 会议日期
* @param participantNames 参会人员 * @param participantNames 参会人员
* @param prompt 提示词 * @param prompt 提示词
* @return * @return
*/ */
private String processWithClaude(String textContent, String meetingDate,String participantNames, String prompt) { private String processWithClaude(String textContent, String meetingDate, String participantNames, String prompt) {
// //将文件传送给大模型处理 // //将文件传送给大模型处理
// String token = "AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO"; // String token = "AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO";
// String apiAddr = llmApiAddr + "/llm/sse-invoke"; // String apiAddr = llmApiAddr + "/llm/sse-invoke";
...@@ -461,20 +425,21 @@ public class FileProcessTask { ...@@ -461,20 +425,21 @@ public class FileProcessTask {
llmApiAddr + "/llm/sse-invoke", llmApiAddr + "/llm/sse-invoke",
"Bearer AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO", "Bearer AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO",
20000); 20000);
LLMResult llmResult = MeetingProcess.processMeeting(prompt, textContent,meetingDate,participantNames, baseLLM, new ArrayList<>()); LLMResult llmResult = MeetingProcess.processMeeting(prompt, textContent, meetingDate, participantNames, baseLLM, new ArrayList<>());
// DebugOutputTool.println(llmResult.respond); // DebugOutputTool.println(llmResult.respond);
return llmResult.respond; return llmResult.respond;
} }
/** /**
* 保存会议纪要相关的文件 * 保存会议纪要相关的文件
* @param content 大模型返回的不规则xml *
* @param recordData 转录文本 * @param content 大模型返回的不规则xml
* @param meetingInfo 会议对象 * @param recordData 转录文本
* @param toUserCode 人员工号 * @param meetingInfo 会议对象
* @param toUserCode 人员工号
* @param meetingRecordTemplate 模板信息 * @param meetingRecordTemplate 模板信息
*/ */
private String saveResult(String content, byte[] recordData, MeetingInfo meetingInfo,String toUserCode,MeetingRecordTemplate meetingRecordTemplate) { private String saveResult(String content, byte[] recordData, MeetingInfo meetingInfo, String toUserCode, MeetingRecordTemplate meetingRecordTemplate) {
String meetingName; String meetingName;
//转录文件临时存储路径 //转录文件临时存储路径
// String recordContentPath = meetingId + "-recordContent-" + IdUtil.fastSimpleUUID() + ".txt"; // String recordContentPath = meetingId + "-recordContent-" + IdUtil.fastSimpleUUID() + ".txt";
...@@ -508,17 +473,17 @@ public class FileProcessTask { ...@@ -508,17 +473,17 @@ public class FileProcessTask {
}); });
DocResultDto docResultDto = docResultDtoList.get(0); DocResultDto docResultDto = docResultDtoList.get(0);
String previewPath = docResultDto.getPreviewPath(); String previewPath = docResultDto.getPreviewPath();
recordContentPath = previewPath.replaceAll(fileDownloadPath,""); recordContentPath = previewPath.replaceAll(fileDownloadPath, "");
meetingInfo.setTransDocId(docResultDto.getId()); meetingInfo.setTransDocId(docResultDto.getId());
}else{ } else {
processLogService.log(meetingId,subMeetingId,"填充会议纪要失败,上传转录文件到向量知识库失败"); processLogService.log(meetingId, subMeetingId, "填充会议纪要失败,上传转录文件到向量知识库失败");
throw new RuntimeException("填充会议纪要失败"); throw new RuntimeException("填充会议纪要失败");
} }
//去除内容中除了xml内容以外其他的信息,格式化xml //去除内容中除了xml内容以外其他的信息,格式化xml
String xml = extractXmlFromMarkdown(content); String xml = extractXmlFromMarkdown(content);
// minioUtils.upload(recordContentPath,recordData); // minioUtils.upload(recordContentPath,recordData);
minioUtils.upload(recordXmlPath,xml.getBytes(StandardCharsets.UTF_8)); minioUtils.upload(recordXmlPath, xml.getBytes(StandardCharsets.UTF_8));
//将xml格式的内容转换为map,用于填充模板 //将xml格式的内容转换为map,用于填充模板
Map<String, Object> dataModel = convertXmlToMap(xml); Map<String, Object> dataModel = convertXmlToMap(xml);
//判断会议名称关键词,如果是用户自己定义的主题,不做修改 //判断会议名称关键词,如果是用户自己定义的主题,不做修改
...@@ -527,23 +492,23 @@ public class FileProcessTask { ...@@ -527,23 +492,23 @@ public class FileProcessTask {
// 3**的周期会议 // 3**的周期会议
// 4**预定的网络研讨会 // 4**预定的网络研讨会
String[] keywords = {"预定的会议", "的快速会议", "的周期会议", "预定的网络研讨会"}; String[] keywords = {"预定的会议", "的快速会议", "的周期会议", "预定的网络研讨会"};
boolean hostCustomSubject = Arrays.stream(keywords).noneMatch(item->subject.contains(item)); boolean hostCustomSubject = Arrays.stream(keywords).noneMatch(item -> subject.contains(item));
if(hostCustomSubject){ if (hostCustomSubject) {
meetingName = subject; meetingName = subject;
dataModel.put("meeting_name",subject); dataModel.put("meeting_name", subject);
}else{ } else {
meetingName = dataModel.get("meeting_name") != null ? String.valueOf(dataModel.get("meeting_name")) : subject; meetingName = dataModel.get("meeting_name") != null ? String.valueOf(dataModel.get("meeting_name")) : subject;
meetingInfo.setSubject(meetingName); meetingInfo.setSubject(meetingName);
} }
meetingMinutesFileName = meetingName + "_" + meetingRecordTemplate.getName(); meetingMinutesFileName = meetingName + "_" + meetingRecordTemplate.getName();
//追加参会人员信息 //追加参会人员信息
Map<String,Object> participantsMap = new ConcurrentHashMap<>(); Map<String, Object> participantsMap = new ConcurrentHashMap<>();
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd"); DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String meetingDate = meetingInfo.getStartTime().toLocalDate().format(df); String meetingDate = meetingInfo.getStartTime().toLocalDate().format(df);
participantsMap.put("meeting_date",meetingDate); participantsMap.put("meeting_date", meetingDate);
participantsMap.put("meeting_location","线上腾讯会议"); participantsMap.put("meeting_location", "线上腾讯会议");
participantsMap.put("meeting_participants", meetingInfo.getParticipantUsers()); participantsMap.put("meeting_participants", meetingInfo.getParticipantUsers());
participantsMap.put("meeting_host",meetingInfo.getHost()); participantsMap.put("meeting_host", meetingInfo.getHost());
dataModel.putAll(participantsMap); dataModel.putAll(participantsMap);
XWPFTemplate template; XWPFTemplate template;
...@@ -554,27 +519,27 @@ public class FileProcessTask { ...@@ -554,27 +519,27 @@ public class FileProcessTask {
} }
meetingMinutesPath = savePath + meetingMinutesFileName + ".docx"; meetingMinutesPath = savePath + meetingMinutesFileName + ".docx";
template.writeAndClose(new FileOutputStream(meetingMinutesPath)); template.writeAndClose(new FileOutputStream(meetingMinutesPath));
processLogService.log(meetingId,subMeetingId,"填充会议纪要成功"); processLogService.log(meetingId, subMeetingId, "填充会议纪要成功");
} catch (Exception e) { } catch (Exception e) {
log.error("填充会议纪要失败: {}", e.getMessage(), e); log.error("填充会议纪要失败: {}", e.getMessage(), e);
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw); PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw); e.printStackTrace(pw);
processLogService.log(meetingId,subMeetingId,"填充会议纪要失败"+sw.toString()); processLogService.log(meetingId, subMeetingId, "填充会议纪要失败" + sw.toString());
throw new RuntimeException("填充会议纪要失败"); throw new RuntimeException("填充会议纪要失败");
} }
meetingInfoMapper.update(meetingInfo, meetingInfoMapper.update(meetingInfo,
new LambdaUpdateWrapper<MeetingInfo>() new LambdaUpdateWrapper<MeetingInfo>()
.eq(MeetingInfo::getMeetingId,meetingId) .eq(MeetingInfo::getMeetingId, meetingId)
.eq(subMeetingId != null,MeetingInfo::getSubMeetingId,subMeetingId) .eq(subMeetingId != null, MeetingInfo::getSubMeetingId, subMeetingId)
.set(MeetingInfo::getRecordContent,recordContentPath) .set(MeetingInfo::getRecordContent, recordContentPath)
.set(MeetingInfo::getRecordXml,recordXmlPath) .set(MeetingInfo::getRecordXml, recordXmlPath)
.set(MeetingInfo::getParticipantUsers,meetingInfo.getParticipantUsers()) .set(MeetingInfo::getParticipantUsers, meetingInfo.getParticipantUsers())
.set(MeetingInfo::getIsGenerated,Boolean.TRUE) .set(MeetingInfo::getIsGenerated, Boolean.TRUE)
.set(MeetingInfo::getTemplateId,meetingRecordTemplate.getId()) .set(MeetingInfo::getTemplateId, meetingRecordTemplate.getId())
.set(MeetingInfo::getTransDocId,meetingInfo.getTransDocId()) .set(MeetingInfo::getTransDocId, meetingInfo.getTransDocId())
.set(MeetingInfo::getSubject,meetingInfo.getSubject()) .set(MeetingInfo::getSubject, meetingInfo.getSubject())
); );
meetingInfo.setRecordContent(recordContentPath); meetingInfo.setRecordContent(recordContentPath);
meetingInfo.setRecordXml(recordXmlPath); meetingInfo.setRecordXml(recordXmlPath);
...@@ -592,17 +557,17 @@ public class FileProcessTask { ...@@ -592,17 +557,17 @@ public class FileProcessTask {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw); PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw); e.printStackTrace(pw);
processLogService.log(meetingId,subMeetingId,"【邮件推送】:"+sw.toString()); processLogService.log(meetingId, subMeetingId, "【邮件推送】:" + sw.toString());
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if(isPushed) if (isPushed)
processLogService.log(meetingId,subMeetingId,"用户允许邮件推送,推送邮件至"+ emailPushBuilder.getToEmail()); processLogService.log(meetingId, subMeetingId, "用户允许邮件推送,推送邮件至" + emailPushBuilder.getToEmail());
meetingInfoMapper.update(null, meetingInfoMapper.update(null,
new LambdaUpdateWrapper<MeetingInfo>() new LambdaUpdateWrapper<MeetingInfo>()
.eq(MeetingInfo::getMeetingId,meetingId) .eq(MeetingInfo::getMeetingId, meetingId)
.eq(subMeetingId != null,MeetingInfo::getSubMeetingId,subMeetingId) .eq(subMeetingId != null, MeetingInfo::getSubMeetingId, subMeetingId)
.set(MeetingInfo::getIsPushed,isPushed) .set(MeetingInfo::getIsPushed, isPushed)
); );
} }
...@@ -615,14 +580,14 @@ public class FileProcessTask { ...@@ -615,14 +580,14 @@ public class FileProcessTask {
// 正确获取节点和属性的方式 // 正确获取节点和属性的方式
List<Map> list = new ArrayList<>(); List<Map> list = new ArrayList<>();
Iterator<String> iterator = rootNode.fieldNames(); Iterator<String> iterator = rootNode.fieldNames();
while (iterator.hasNext()){ while (iterator.hasNext()) {
String tagName = iterator.next(); String tagName = iterator.next();
JsonNode subNode = rootNode.path(tagName); JsonNode subNode = rootNode.path(tagName);
String displayName = subNode.path("label").asText(); String displayName = subNode.path("label").asText();
String content = subNode.path("").asText(); String content = subNode.path("").asText();
list.add(new HashMap(){{ list.add(new HashMap() {{
put("key",tagName + "/" + displayName); put("key", tagName + "/" + displayName);
put("value",content); put("value", content);
}}); }});
} }
// 构建JSON对象 // 构建JSON对象
...@@ -651,23 +616,24 @@ public class FileProcessTask { ...@@ -651,23 +616,24 @@ public class FileProcessTask {
service.streamChatCompletion(chatCompletionRequest).doOnError(Throwable::printStackTrace).blockingForEach(chunk -> { service.streamChatCompletion(chatCompletionRequest).doOnError(Throwable::printStackTrace).blockingForEach(chunk -> {
chunk.getChoices().stream().map(choice -> choice.getMessage().getContent()) chunk.getChoices().stream().map(choice -> choice.getMessage().getContent())
.filter(Objects::nonNull).findFirst().ifPresent(o -> { .filter(Objects::nonNull).findFirst().ifPresent(o -> {
try { try {
stringBuilder.append(new String(o.getBytes(Charset.defaultCharset()))); stringBuilder.append(new String(o.getBytes(Charset.defaultCharset())));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}); });
}); });
} catch (Exception e) { } catch (Exception e) {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw); PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw); e.printStackTrace(pw);
processLogService.log(meetingId,subMeetingId,"【大模型处理异常】:"+sw.toString()); processLogService.log(meetingId, subMeetingId, "【大模型处理异常】:" + sw.toString());
throw new RuntimeException(e); throw new RuntimeException(e);
} }
service.shutdownExecutor(); service.shutdownExecutor();
return stringBuilder.toString(); return stringBuilder.toString();
} }
public static Map<String, Object> convertXmlToMap(String xml) throws Exception { public static Map<String, Object> convertXmlToMap(String xml) throws Exception {
XmlMapper xmlMapper = new XmlMapper(); XmlMapper xmlMapper = new XmlMapper();
...@@ -677,12 +643,12 @@ public class FileProcessTask { ...@@ -677,12 +643,12 @@ public class FileProcessTask {
Map<?, ?> xmlMap = xmlMapper.readValue(xml, Map.class); Map<?, ?> xmlMap = xmlMapper.readValue(xml, Map.class);
// 转换为更标准的 Map<String, Object> // 转换为更标准的 Map<String, Object>
Map<String,Object> map = objectMapper.convertValue(xmlMap, Map.class); Map<String, Object> map = objectMapper.convertValue(xmlMap, Map.class);
//特殊处理map格式 //特殊处理map格式
for (Map.Entry<String, Object> entry : map.entrySet()) { for (Map.Entry<String, Object> entry : map.entrySet()) {
Map<String,Object> value = (Map<String, Object>) entry.getValue(); Map<String, Object> value = (Map<String, Object>) entry.getValue();
//取出正确的value并设置 //取出正确的value并设置
String realValue = String.valueOf(value.get("")).replaceAll("^\\n+",""); String realValue = String.valueOf(value.get("")).replaceAll("^\\n+", "");
//内容段首移除换行,段末追加换行(会议名称结尾不换行 //内容段首移除换行,段末追加换行(会议名称结尾不换行
entry.setValue(realValue.endsWith("\n") || "meeting_name".equals(entry.getKey()) || "meeting_purpose".equals(entry.getKey()) ? realValue : realValue + "\n"); entry.setValue(realValue.endsWith("\n") || "meeting_name".equals(entry.getKey()) || "meeting_purpose".equals(entry.getKey()) ? realValue : realValue + "\n");
} }
...@@ -691,6 +657,7 @@ public class FileProcessTask { ...@@ -691,6 +657,7 @@ public class FileProcessTask {
/** /**
* markdown转xml * markdown转xml
*
* @param markdown * @param markdown
* @return * @return
*/ */
...@@ -698,40 +665,35 @@ public class FileProcessTask { ...@@ -698,40 +665,35 @@ public class FileProcessTask {
StringBuffer sb; StringBuffer sb;
try { try {
int start = markdown.indexOf("<"); int start = markdown.indexOf("<");
if(start == -1){ if (start == -1) {
processLogService.log(meetingId,subMeetingId,"markdown转xml失败,未输出正确的xml格式,markdown内容:"+markdown); processLogService.log(meetingId, subMeetingId, "markdown转xml失败,未输出正确的xml格式,markdown内容:" + markdown);
} }
int end = markdown.lastIndexOf(">") + 1; int end = markdown.lastIndexOf(">") + 1;
sb = new StringBuffer(); sb = new StringBuffer();
sb.append("<root>"); sb.append("<root>");
String xml = markdown.substring(start, end).trim().replaceAll("\n\n","\n"); String xml = markdown.substring(start, end).trim().replaceAll("\n\n", "\n");
sb.append(xml); sb.append(xml);
sb.append("</root>"); sb.append("</root>");
} catch (Exception e) { } catch (Exception e) {
log.info("markdown转xml,markdown->{}",markdown); log.info("markdown转xml,markdown->{}", markdown);
throw new RuntimeException(e.getMessage()); throw new RuntimeException(e.getMessage());
} }
return sb.toString(); return sb.toString();
} }
public FileProcessTask(List<String> recordFileIdList, String meetingId, String subMeetingId, String savePath, Map<String, Object> metadata, String tencentAppId, public FileProcessTask(List<String> recordFileIdList, String meetingId, String subMeetingId, String savePath, Map<String, Object> metadata,
String tencentSdkId, String tencentSecretId, String tencentSecretKey, String tencentAdminUserId, MeetingInfoMapper meetingInfoMapper, MinioUtils minioUtils, RedisUtils redisUtils, EmailSender emailSender, MeetingRecordTemplateMapper meetingRecordTemplateMapper,
MeetingInfoMapper meetingInfoMapper, MinioUtils minioUtils, EmailSender emailSender, MeetingRecordTemplateMapper meetingRecordTemplateMapper, String llmApiAddr, Boolean finalRetry, ProcessLogService processLogService, List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers, Map<String, String> tidWidRelations,
String llmApiAddr, Boolean finalRetry, ProcessLogService processLogService,List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers,Map<String,String> tidWidRelations, UserAdminConfig userAdminConfig, String adminToken, String applicationId, String fileDownloadPath, String permTenantId) {
UserAdminConfig userAdminConfig, String adminToken, String applicationId,String fileDownloadPath, String permTenantId) {
this.recordFileIdList = recordFileIdList; this.recordFileIdList = recordFileIdList;
this.savePath = savePath; this.savePath = savePath;
this.metadata = metadata; this.metadata = metadata;
this.tencentAppId = tencentAppId;
this.tencentSdkId = tencentSdkId;
this.tencentSecretId = tencentSecretId;
this.tencentSecretKey = tencentSecretKey;
this.tencentAdminUserId = tencentAdminUserId;
this.meetingId = meetingId; this.meetingId = meetingId;
this.subMeetingId = subMeetingId; this.subMeetingId = subMeetingId;
this.meetingInfoMapper = meetingInfoMapper; this.meetingInfoMapper = meetingInfoMapper;
this.minioUtils = minioUtils; this.minioUtils = minioUtils;
this.redisUtils = redisUtils;
this.emailSender = emailSender; this.emailSender = emailSender;
this.meetingRecordTemplateMapper = meetingRecordTemplateMapper; this.meetingRecordTemplateMapper = meetingRecordTemplateMapper;
this.llmApiAddr = llmApiAddr; this.llmApiAddr = llmApiAddr;
......
...@@ -37,7 +37,7 @@ public class MeetingInfo implements Serializable { ...@@ -37,7 +37,7 @@ public class MeetingInfo implements Serializable {
* 会议主题 * 会议主题
*/ */
private String subject; private String subject;
/** /**
* 会议ID(字符串类型) * 会议ID(字符串类型)
*/ */
...@@ -47,12 +47,12 @@ public class MeetingInfo implements Serializable { ...@@ -47,12 +47,12 @@ public class MeetingInfo implements Serializable {
* 子会议ID * 子会议ID
*/ */
private String subMeetingId; private String subMeetingId;
/** /**
* 会议号码 * 会议号码
*/ */
private String meetingCode; private String meetingCode;
/** /**
* 主持人 * 主持人
*/ */
...@@ -67,14 +67,14 @@ public class MeetingInfo implements Serializable { ...@@ -67,14 +67,14 @@ public class MeetingInfo implements Serializable {
* 参会人员名单 * 参会人员名单
*/ */
private String participantUsers; private String participantUsers;
/** /**
* 会议开始时间(时间戳) * 会议开始时间(时间戳)
*/ */
@DateTimeFormat(pattern = "yyy-MM-dd HH:mm:ss") @DateTimeFormat(pattern = "yyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
private LocalDateTime startTime; private LocalDateTime startTime;
/** /**
* 会议结束时间(时间戳) * 会议结束时间(时间戳)
*/ */
...@@ -89,6 +89,7 @@ public class MeetingInfo implements Serializable { ...@@ -89,6 +89,7 @@ public class MeetingInfo implements Serializable {
/** /**
* 推送邮件许可 为false不推送 * 推送邮件许可 为false不推送
*/ */
private Boolean emailGenerateAccess;
private Boolean emailPushAccess; private Boolean emailPushAccess;
/** /**
* 是否推送邮件完成 * 是否推送邮件完成
......
...@@ -9,6 +9,7 @@ import com.cmeeting.mapper.primary.MeetingInfoMapper; ...@@ -9,6 +9,7 @@ import com.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper; import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper;
import com.cmeeting.pojo.UserId; import com.cmeeting.pojo.UserId;
import com.cmeeting.util.MinioUtils; import com.cmeeting.util.MinioUtils;
import com.cmeeting.util.RedisUtils;
import com.cmeeting.util.UserAdminConfig; import com.cmeeting.util.UserAdminConfig;
import com.cmeeting.util.UserAdminTokenUtil; import com.cmeeting.util.UserAdminTokenUtil;
import com.cmeeting.vo.TencentMeetingVO; import com.cmeeting.vo.TencentMeetingVO;
...@@ -29,22 +30,12 @@ import java.util.concurrent.Future; ...@@ -29,22 +30,12 @@ import java.util.concurrent.Future;
@Service @Service
@Slf4j @Slf4j
public class FileProcessProducer { public class FileProcessProducer {
@Autowired @Autowired
private ThreadPoolTaskExecutor fileProcessExecutor; private ThreadPoolTaskExecutor fileProcessExecutor;
@Autowired @Autowired
private FileProcessCallbackHandler callbackHandler; private FileProcessCallbackHandler callbackHandler;
@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}") @Value(value = "${llm.api-addr}")
private String llmApiAddr; private String llmApiAddr;
@Value(value = "${tencent.base-save-path}") @Value(value = "${tencent.base-save-path}")
...@@ -60,6 +51,8 @@ public class FileProcessProducer { ...@@ -60,6 +51,8 @@ public class FileProcessProducer {
@Resource @Resource
private MinioUtils minioUtils; private MinioUtils minioUtils;
@Resource @Resource
private RedisUtils redisUtils;
@Resource
private EmailSender emailSender; private EmailSender emailSender;
@Resource @Resource
private ProcessLogService processLogService; private ProcessLogService processLogService;
...@@ -71,59 +64,56 @@ public class FileProcessProducer { ...@@ -71,59 +64,56 @@ public class FileProcessProducer {
/** /**
* 批量提交生成纪要任务 * 批量提交生成纪要任务
* @param recordFiles 转录文件信息 *
* @param recordFiles 转录文件信息
* @param authorizedUsers 模板授权的人员 * @param authorizedUsers 模板授权的人员
* @param tidWidRelations 腾会id企微id对应关系 * @param tidWidRelations 腾会id企微id对应关系
* @param finalRetry 是否为最终重试 * @param finalRetry 是否为最终重试
* @param finalRetry * @param finalRetry
*/ */
public void submitBatchTasks(List<TencentMeetingVO.RecordFile> recordFiles, List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers, Map<String,String> tidWidRelations, Boolean finalRetry) { public void submitBatchTasks(List<TencentMeetingVO.RecordFile> recordFiles, List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers, Map<String, String> tidWidRelations, Boolean finalRetry) {
List<Future<?>> futures = new ArrayList<>(); List<Future<?>> futures = new ArrayList<>();
String adminToken = UserAdminTokenUtil.getUserAdminToken(); String adminToken = UserAdminTokenUtil.getUserAdminToken();
for (TencentMeetingVO.RecordFile recordFile : recordFiles) { for (TencentMeetingVO.RecordFile recordFile : recordFiles) {
// 为每个URL创建任务 // 为每个URL创建任务
FileProcessTask task = new FileProcessTask( FileProcessTask task = new FileProcessTask(
recordFile.getRecordFileIdList(), recordFile.getRecordFileIdList(),
recordFile.getMeetingId(), recordFile.getMeetingId(),
recordFile.getSubMeetingId(), recordFile.getSubMeetingId(),
baseSavePath, baseSavePath,
Collections.emptyMap(), Collections.emptyMap(),
tencentAppId, meetingInfoMapper,
tencentSdkId, minioUtils,
tencentSecretId, redisUtils,
tencentSecretKey, emailSender,
tencentAdminUserId, meetingRecordTemplateMapper,
meetingInfoMapper, llmApiAddr,
minioUtils, finalRetry,
emailSender, processLogService,
meetingRecordTemplateMapper, authorizedUsers,
llmApiAddr, tidWidRelations,
finalRetry, userAdminConfig,
processLogService, adminToken,
authorizedUsers, applicationId,
tidWidRelations, fileDownloadPath,
userAdminConfig, permTenantId
adminToken,
applicationId,
fileDownloadPath,
permTenantId
); );
// 提交任务到线程池 // 提交任务到线程池
Future<?> future = fileProcessExecutor.submit(() -> { Future<?> future = fileProcessExecutor.submit(() -> {
task.process(); task.process();
callbackHandler.onComplete(task); // 回调处理 callbackHandler.onComplete(task); // 回调处理
}); });
futures.add(future); futures.add(future);
} }
// 可以添加一个监控线程来检查所有任务完成情况 // 可以添加一个监控线程来检查所有任务完成情况
monitorTaskCompletion(futures); monitorTaskCompletion(futures);
} }
// 批量提交邮箱推送重试任务 // 批量提交邮箱推送重试任务
public void submitEmailPushTasks(List<TencentMeetingVO.RecordFile> recordFiles,Map<String,String> tidWidRelations) { public void submitEmailPushTasks(List<TencentMeetingVO.RecordFile> recordFiles, Map<String, String> tidWidRelations) {
List<Future<?>> futures = new ArrayList<>(); List<Future<?>> futures = new ArrayList<>();
for (TencentMeetingVO.RecordFile recordFile : recordFiles) { for (TencentMeetingVO.RecordFile recordFile : recordFiles) {
...@@ -151,7 +141,7 @@ public class FileProcessProducer { ...@@ -151,7 +141,7 @@ public class FileProcessProducer {
// 可以添加一个监控线程来检查所有任务完成情况 // 可以添加一个监控线程来检查所有任务完成情况
monitorTaskCompletion(futures); monitorTaskCompletion(futures);
} }
private void monitorTaskCompletion(List<Future<?>> futures) { private void monitorTaskCompletion(List<Future<?>> futures) {
new Thread(() -> { new Thread(() -> {
for (Future<?> future : futures) { for (Future<?> future : futures) {
......
...@@ -302,7 +302,7 @@ public class RecordTemplatePermissionServiceImpl extends ServiceImpl<RecordTempl ...@@ -302,7 +302,7 @@ public class RecordTemplatePermissionServiceImpl extends ServiceImpl<RecordTempl
* @return * @return
*/ */
private String extractXmlFromMarkdown(String markdown) { private String extractXmlFromMarkdown(String markdown) {
StringBuffer sb; StringBuffer sb = null;
try { try {
int start = markdown.indexOf("<"); int start = markdown.indexOf("<");
int end = markdown.lastIndexOf(">") + 1; int end = markdown.lastIndexOf(">") + 1;
...@@ -313,7 +313,6 @@ public class RecordTemplatePermissionServiceImpl extends ServiceImpl<RecordTempl ...@@ -313,7 +313,6 @@ public class RecordTemplatePermissionServiceImpl extends ServiceImpl<RecordTempl
sb.append("</root>"); sb.append("</root>");
} catch (Exception e) { } catch (Exception e) {
log.info("markdown转xml,markdown->{}",markdown); log.info("markdown转xml,markdown->{}",markdown);
throw new RuntimeException(e.getMessage());
} }
return sb.toString(); return sb.toString();
} }
......
...@@ -51,7 +51,7 @@ import java.util.stream.Collectors; ...@@ -51,7 +51,7 @@ import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,TencentMeetingUser> implements TencentMeetingService { public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper, TencentMeetingUser> implements TencentMeetingService {
@Resource @Resource
private TecentMeetingMapper tecentMeetingMapper; private TecentMeetingMapper tecentMeetingMapper;
@Resource @Resource
...@@ -84,8 +84,6 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -84,8 +84,6 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
private String permissionApplicationId; private String permissionApplicationId;
@Value(value = "${permission.tenantId}") @Value(value = "${permission.tenantId}")
private String permissionTenantId; private String permissionTenantId;
@Resource
private TencentMeetingApiUtil tencentMeetingApiUtil;
@Override @Override
public void batchInsert(List<TencentMeetingUser> users) { public void batchInsert(List<TencentMeetingUser> users) {
...@@ -96,7 +94,8 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -96,7 +94,8 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
@Override @Override
public void doUsers() { public void doUsers() {
// 获取到全部用户 // 获取到全部用户
List<TencentMeetingUser> users = fetchUsersInBatches();; List<TencentMeetingUser> users = fetchUsersInBatches();
;
// 检查重名并设置标志 // 检查重名并设置标志
markDuplicateNamesTecent(users); markDuplicateNamesTecent(users);
// 批量插入数据库(分批次) // 批量插入数据库(分批次)
...@@ -110,19 +109,15 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -110,19 +109,15 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
/** /**
* 获取会议信息以及会议对应的转录文件信息 * 获取会议信息以及会议对应的转录文件信息
*
* @param accessUserIds 允许生成会议纪要的人员 * @param accessUserIds 允许生成会议纪要的人员
* @param weComUserMap 企微可见范围内现有的人员 * @param weComUserMap 企微可见范围内现有的人员
* @return * @return
*/ */
@Override @Override
public List<TencentMeetingVO.RecordFile> getMeetingFiles(List<UserDTO> accessUserIds,Map<String, WeComUser> weComUserMap) { public List<TencentMeetingVO.RecordFile> getMeetingFiles(List<UserDTO> accessUserIds, Map<String, WeComUser> weComUserMap) {
Client client = new Client.Builder()
.withAppId(tencentAppId).withSdkId(tencentSdkId)
.withSecret(tencentSecretId,tencentSecretKey)
.build();
List<TencentMeetingVO.RecordFile> meetingFiles = new ArrayList<>(); List<TencentMeetingVO.RecordFile> meetingFiles = new ArrayList<>();
List<TencentMeetingVO.RecordFile> recordFileUrlList = new ArrayList<>(); // List<TencentMeetingVO.RecordFile> recordFileUrlList = new ArrayList<>();
List<MeetingInfo> meetingSaveList = new ArrayList<>(); List<MeetingInfo> meetingSaveList = new ArrayList<>();
// 查询近searchDays天的会议录制列表 // 查询近searchDays天的会议录制列表
try { try {
...@@ -139,148 +134,120 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -139,148 +134,120 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
List<TencentMeetingVO.SimpleMeetingInfo> meetingIds = meetingInfoMapper.getAllMeetingIds(); List<TencentMeetingVO.SimpleMeetingInfo> meetingIds = meetingInfoMapper.getAllMeetingIds();
List<TencentMeetingUser> meetingUsers = tecentMeetingMapper.getAlluser(); List<TencentMeetingUser> meetingUsers = tecentMeetingMapper.getAlluser();
Map<String, String> meetingMap = meetingUsers.stream().collect(Collectors.toMap(TencentMeetingUser::getUserId, TencentMeetingUser::getUserName)); Map<String, String> meetingMap = meetingUsers.stream().collect(Collectors.toMap(TencentMeetingUser::getUserId, TencentMeetingUser::getUserName));
while (currentPage.intValue() <= totalPage){ while (currentPage.intValue() <= totalPage) {
CorpRecordsVO data = fetchMeetingRecords(tencentAdminUserId, 1, startTime, endTime, currentPage.getAndIncrement(), 20); CorpRecordsVO data = fetchMeetingRecords(tencentAdminUserId, 1, startTime, endTime, currentPage.getAndIncrement(), 20);
//设置总页数 //设置总页数
if (data != null && data.getRecordMeetings() != null && !data.getRecordMeetings().isEmpty()) { if (data != null && data.getRecordMeetings() != null && !data.getRecordMeetings().isEmpty()) {
List<CorpRecordsVO.RecordMeeting> meetings = data.getRecordMeetings(); List<CorpRecordsVO.RecordMeeting> meetings = data.getRecordMeetings();
for (CorpRecordsVO.RecordMeeting meeting : meetings) { for (CorpRecordsVO.RecordMeeting meeting : meetings) {
//录制文件未转码完成,跳过 1:录制中 2:转码中 3:转码完成 //录制文件未转码完成,跳过 1:录制中 2:转码中 3:转码完成
if(meeting.getState() != 3){ if (meeting.getState() != 3) {
continue; continue;
} }
log.info("【会议检索】转录文件的meetingId->{},recordFileId->{}",meeting.getMeetingId(),meeting.getMeetingRecordId()); log.info("【会议检索】转录文件的meetingId->{},recordFileId->{}", meeting.getMeetingId(), meeting.getMeetingRecordId());
//查询会议详情 //查询会议详情
String meetingId = meeting.getMeetingId(); String meetingId = meeting.getMeetingId();
String subMeetingId = null; String subMeetingId = null;
LocalDateTime mediaStartTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.valueOf(meeting.getMediaStartTime())), ZoneId.systemDefault()); LocalDateTime mediaStartTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.valueOf(meeting.getMediaStartTime())), ZoneId.systemDefault());
try { try {
String userid = meeting.getUserid(); String userid = meeting.getUserid();
log.info("【周期会议扫描】:查询用户的已结束会议列表...meetingCode->{},userId->{}",meeting.getMeetingCode(), userid); log.info("【周期会议扫描】:查询用户的已结束会议列表...meetingCode->{},userId->{}", meeting.getMeetingCode(), userid);
//获取子会议id //获取子会议id
MeetingsApi.ApiV1HistoryMeetingsUseridGetRequest historyMeetingRequest = V1HistoryMeetingsUseridGet200Response v1HistoryMeetingsUseridGet200Response = TencentMeetingApiUtil.ApiV1HistoryMeetingsUseridGetRequest(userid, meeting.getMeetingCode(),
new MeetingsApi.ApiV1HistoryMeetingsUseridGetRequest.Builder(userid) String.valueOf(mediaStartTime.toLocalDate().atStartOfDay().atZone(ZoneId.systemDefault()).toEpochSecond()),
.pageSize("20") String.valueOf(mediaStartTime.toLocalDate().atStartOfDay().plusDays(1).atZone(ZoneId.systemDefault()).toEpochSecond()));
.page("1") if (v1HistoryMeetingsUseridGet200Response == null) {
.meetingCode(meeting.getMeetingCode()) continue;
.startTime(String.valueOf(mediaStartTime.toLocalDate().atStartOfDay().atZone(ZoneId.systemDefault()).toEpochSecond())) }
.endTime(String.valueOf(mediaStartTime.toLocalDate().atStartOfDay().plusDays(1).atZone(ZoneId.systemDefault()).toEpochSecond())) List<V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner> historyMeetingInfoList = v1HistoryMeetingsUseridGet200Response.getMeetingInfoList();
.build(); if (CollectionUtils.isEmpty(historyMeetingInfoList)) {
MeetingsApi.ApiV1HistoryMeetingsUseridGetResponse historyMeetingResponse =
client.meetings().v1HistoryMeetingsUseridGet(historyMeetingRequest, new JWTAuthenticator.Builder()
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()))).timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
V1HistoryMeetingsUseridGet200Response historyMeetingResponseData = historyMeetingResponse.getData();
List<V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner> historyMeetingInfoList = historyMeetingResponseData.getMeetingInfoList();
if(CollectionUtils.isEmpty(historyMeetingInfoList)){
log.error("会议未结束,获取子会议id信息失败"); log.error("会议未结束,获取子会议id信息失败");
continue; continue;
} }
V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner historyMeeting = historyMeetingInfoList.get(0); V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner historyMeeting = historyMeetingInfoList.get(0);
//如果是周期会议 //如果是周期会议
if(historyMeeting.getMeetingType() == 1){ if (historyMeeting.getMeetingType() == 1) {
subMeetingId = historyMeeting.getSubMeetingId(); subMeetingId = historyMeeting.getSubMeetingId();
} }
//如果数据库中已有相同会议id的记录,跳过同步 //如果数据库中已有相同会议id的记录,跳过同步
String finalSubMeetingId = subMeetingId; String finalSubMeetingId = subMeetingId;
if(!meetingIds.stream().anyMatch(item->item.getMeetingId().equals(meetingId) && Objects.equals(item.getSubMeetingId(), finalSubMeetingId))){ if (meetingIds.stream().noneMatch(item -> item.getMeetingId().equals(meetingId) && Objects.equals(item.getSubMeetingId(), finalSubMeetingId))) {
log.info("【会议检索】新的会议meetingId->{}",meeting.getMeetingId()); log.info("【会议检索】新的会议meetingId->{}", meeting.getMeetingId());
List<CorpRecordsVO.RecordFile> recordFiles = meeting.getRecordFiles(); List<CorpRecordsVO.RecordFile> recordFiles = meeting.getRecordFiles();
//按转录文件时间升序,便于后续的内容拼接 //按转录文件时间升序,便于后续的内容拼接
List<String> recordFileIdList = recordFiles.stream().sorted(Comparator.comparingLong(CorpRecordsVO.RecordFile::getRecordStartTime)) List<String> recordFileIdList = recordFiles.stream().sorted(Comparator.comparingLong(CorpRecordsVO.RecordFile::getRecordStartTime))
.map(CorpRecordsVO.RecordFile::getRecordFileId).collect(Collectors.toList()); .map(CorpRecordsVO.RecordFile::getRecordFileId).collect(Collectors.toList());
TencentMeetingVO.RecordFile recordFileItem = TencentMeetingVO.RecordFile.builder() // TencentMeetingVO.RecordFile recordFileItem = TencentMeetingVO.RecordFile.builder()
.recordFileIdList(recordFileIdList).meetingId(meetingId).subMeetingId(subMeetingId).build(); // .recordFileIdList(recordFileIdList).meetingId(meetingId).subMeetingId(subMeetingId).build();
String hostId; String hostId;
String hostName; String hostName;
//优先使用会议列表中已有的主持人字段 //优先使用会议列表中已有的主持人字段
if(StringUtils.isNotEmpty(meeting.getHostUserId())){ if (StringUtils.isNotEmpty(meeting.getHostUserId())) {
hostId = meeting.getHostUserId(); hostId = meeting.getHostUserId();
hostName = meetingMap.containsKey(hostId) ? meetingMap.get(hostId) :null; hostName = meetingMap.getOrDefault(hostId, null);
log.info("从会议列表中成功获取到主持人信息"); log.info("从会议列表中成功获取到主持人信息");
}else{ } else {
//判断主持人是否存在,如果主持人未参会,是查不到主持人的 //判断主持人是否存在,如果主持人未参会,是查不到主持人的
//如果主持人未参会,使用会议详情中的创建人作为主持人 //如果主持人未参会,使用会议详情中的创建人作为主持人
MeetingsApi.ApiV1MeetingsMeetingIdGetRequest meetingRequest = V1MeetingsMeetingIdGet200Response meetingResponseData = TencentMeetingApiUtil.ApiV1MeetingsMeetingIdGetRequest(meetingId);
new MeetingsApi.ApiV1MeetingsMeetingIdGetRequest.Builder(meetingId) if (meetingResponseData == null) {
.operatorId(tencentAdminUserId) continue;
.operatorIdType("1") }
.instanceid("0")
.build();
MeetingsApi.ApiV1MeetingsMeetingIdGetResponse meetingResponse =
client.meetings().v1MeetingsMeetingIdGet(meetingRequest, new JWTAuthenticator.Builder()
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
.timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
V1MeetingsMeetingIdGet200Response meetingResponseData = meetingResponse.getData();
List<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner> meetingInfoList = meetingResponseData.getMeetingInfoList(); List<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner> meetingInfoList = meetingResponseData.getMeetingInfoList();
V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner meetingInfo = meetingInfoList.get(0); V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner meetingInfo = meetingInfoList.get(0);
//会议详情中有主持人信息 //会议详情中有主持人信息
if(!CollectionUtils.isEmpty(meetingInfo.getCurrentHosts())){ if (!CollectionUtils.isEmpty(meetingInfo.getCurrentHosts())) {
log.info("尝试从会议详情中获取主持人信息"); log.info("尝试从会议详情中获取主持人信息");
hostId = meetingInfo.getCurrentHosts().get(0).getUserid(); hostId = meetingInfo.getCurrentHosts().get(0).getUserid();
hostName = meetingMap.containsKey(hostId) ? meetingMap.get(hostId) :null; hostName = meetingMap.getOrDefault(hostId, null);
// processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认会议创建人为主持人"); } else {
}else{
log.info("尝试从参会人员列表中获取主持人信息"); log.info("尝试从参会人员列表中获取主持人信息");
// 获取参会成员明细 // 用户角色
MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest participantsRequest = // 0:普通成员角色 1:创建者角色 2:主持人 3:创建者+主持人 4:游客 5:游客+主持人 6:联席主持人 7:创建者+联席主持人
new MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest // 主持人角色
.Builder(meetingId).subMeetingId(subMeetingId).operatorId(tencentAdminUserId).operatorIdType("1").build(); List<Long> hostRoleList = Arrays.asList(2L, 3L, 5L, 6L, 7L);
AuthenticatorBuilder<JWTAuthenticator> participantsAuthenticatorBuilder = V1MeetingsMeetingIdParticipantsGet200Response participantsData = TencentMeetingApiUtil.ApiV1MeetingsMeetingIdParticipantsGetRequest(meetingId, subMeetingId);
new JWTAuthenticator.Builder() if (participantsData == null) {
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()))) continue;
.timestamp(String.valueOf(System.currentTimeMillis() / 1000L)); }
//主持人角色,以下角色都可以表示主持人
//用户角色:
//0:普通成员角色
//1:创建者角色
//2:主持人
//3:创建者+主持人
//4:游客
//5:游客+主持人
//6:联席主持人
//7:创建者+联席主持人
List<Long> hostRoleList = Arrays.asList(2L,3L,5L,6L,7L);
MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetResponse participantsResponse =
client.meetings().v1MeetingsMeetingIdParticipantsGet(participantsRequest, participantsAuthenticatorBuilder);
V1MeetingsMeetingIdParticipantsGet200Response participantsData = participantsResponse.getData();
List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = participantsData.getParticipants(); List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = participantsData.getParticipants();
Optional<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> host = participants.stream().filter(item -> hostRoleList.contains(item.getUserRole())).findFirst(); Optional<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> host = participants.stream().filter(item -> hostRoleList.contains(item.getUserRole())).findFirst();
if(host.isPresent()) { if (host.isPresent()) {
hostId = host.get().getUserid(); hostId = host.get().getUserid();
hostName = new String(Base64.getDecoder().decode(host.get().getUserName())); hostName = new String(Base64.getDecoder().decode(host.get().getUserName()));
}else{ } else {
log.error("未找到主持人,默认没有生成纪要权限"); log.error("未找到主持人,默认没有生成纪要权限");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认没有生成纪要权限");
continue; continue;
} }
} }
} }
//判断是否有权限生成纪要 //判断是否有权限生成纪要
boolean generateAccess = accessUserIds.stream().anyMatch(item -> item.getTid().equals(hostId)); boolean generateAccess;
if(!generateAccess){ boolean emailPushAccess;
log.error("【权限校验】主持人{}没有生成纪要权限,跳过生成",hostId); // 1. 智能体需要授权
// processLogService.log(meeting.getMeetingId(),subMeetingId,"【权限校验】主持人"+hostId+"没有生成纪要权限,跳过生成"); generateAccess = accessUserIds.stream().anyMatch(item -> item.getTid().equals(hostId));
continue; log.info("智能体是否授权: {}", generateAccess);
}
log.info("【权限校验】主持人{}允许生成纪要",hostId);
// processLogService.log(meeting.getMeetingId(),subMeetingId,"【权限校验】主持人"+hostId+"允许生成纪要");
UserDTO userDTO = accessUserIds.stream().filter(item -> item.getTid().equals(hostId)).findFirst().get(); UserDTO userDTO = accessUserIds.stream().filter(item -> item.getTid().equals(hostId)).findFirst().get();
String email = userDTO.getEmail(); String email = userDTO.getEmail();
Boolean emailPushAccess = weComUserMap.containsKey(userDTO.getWid()) // TODO 要改
? weComUserMap.get(userDTO.getWid()).getEmailPushAccess() : Boolean.FALSE; // 2. 腾讯会议和企业微信信息同步过并且绑定关系
// 3. 用户自己没有关闭会议纪要功能
// 满足以上所有则emailGenerateAccess为ture
generateAccess = weComUserMap.containsKey(userDTO.getWid()) ? weComUserMap.get(userDTO.getWid()).getEmailPushAccess() : Boolean.FALSE;
// 是否推送邮箱
emailPushAccess = weComUserMap.containsKey(userDTO.getWid()) ? weComUserMap.get(userDTO.getWid()).getEmailPushAccess() : Boolean.FALSE;
log.info("推送邮箱: {}", emailPushAccess);
// 查询会议开始和结束时间 // 查询会议开始和结束时间
MeetingInfo startAndEndTimeByMeetingId = tencentMeetingApiUtil.getStartAndEndTimeByMeetingId(meetingId); MeetingInfo startAndEndTimeByMeetingId = TencentMeetingApiUtil.getStartAndEndTimeByMeetingId(meetingId);
//会议基本信息保存 //会议基本信息保存
MeetingInfo meetingItem = MeetingInfo.builder().meetingId(meetingId).meetingCode(meeting.getMeetingCode()) MeetingInfo meetingItem = MeetingInfo.builder().meetingId(meetingId).meetingCode(meeting.getMeetingCode())
.subject(meeting.getSubject()) .subject(meeting.getSubject())
.startTime(startAndEndTimeByMeetingId != null ? startAndEndTimeByMeetingId.getStartTime() : mediaStartTime) .startTime(startAndEndTimeByMeetingId != null ? startAndEndTimeByMeetingId.getStartTime() : mediaStartTime)
.endTime(startAndEndTimeByMeetingId != null ? startAndEndTimeByMeetingId.getEndTime() : null) .endTime(startAndEndTimeByMeetingId != null ? startAndEndTimeByMeetingId.getEndTime() : null)
.isGenerated(Boolean.FALSE).emailPushAccess(emailPushAccess).isPushed(Boolean.FALSE).syncTime(LocalDateTime.now()) .isGenerated(Boolean.FALSE).emailGenerateAccess(generateAccess)
.emailPushAccess(emailPushAccess).isPushed(Boolean.FALSE).syncTime(LocalDateTime.now())
.subMeetingId(subMeetingId).generateRetry(Boolean.FALSE).pushRetry(Boolean.FALSE) .subMeetingId(subMeetingId).generateRetry(Boolean.FALSE).pushRetry(Boolean.FALSE)
.host(hostName) .host(hostName)
.hostUid(hostId) .hostUid(hostId)
...@@ -289,17 +256,16 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -289,17 +256,16 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
.recordFileId(String.join(",", recordFileIdList)) .recordFileId(String.join(",", recordFileIdList))
.email(email) .email(email)
.build(); .build();
recordFileUrlList.add(recordFileItem); // recordFileUrlList.add(recordFileItem);
meetingSaveList.add(meetingItem); meetingSaveList.add(meetingItem);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); log.error(e.getMessage());
continue;
} }
} }
} }
} }
if(meetingSaveList.size() > 0){ if (meetingSaveList.size() > 0) {
Map<String, List<MeetingInfo>> meetingSaveMap = meetingSaveList.stream().collect(Collectors.groupingBy( Map<String, List<MeetingInfo>> meetingSaveMap = meetingSaveList.stream().collect(Collectors.groupingBy(
item -> item.getMeetingId() + "_" + item -> item.getMeetingId() + "_" +
(item.getSubMeetingId() != null ? item.getSubMeetingId() : "null"))); (item.getSubMeetingId() != null ? item.getSubMeetingId() : "null")));
...@@ -321,7 +287,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -321,7 +287,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw); PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw); e.printStackTrace(pw);
processLogService.log(null,null,sw.toString()); processLogService.log(null, null, sw.toString());
throw new RuntimeException(e.getMessage()); throw new RuntimeException(e.getMessage());
} }
return meetingFiles; return meetingFiles;
...@@ -329,12 +295,13 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -329,12 +295,13 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
/** /**
* 获取有权限的人员 * 获取有权限的人员
*
* @param userIdRelations * @param userIdRelations
* @return * @return
*/ */
@Override @Override
public List<UserDTO> getAccessUserIds(Map<String,String> userIdRelations) { public List<UserDTO> getAccessUserIds(Map<String, String> userIdRelations) {
try{ try {
//权限控制 //权限控制
List<CoreModulePermissions> auths = authMapper.getAuthByTargetId(permissionApplicationId, permissionTenantId); List<CoreModulePermissions> auths = authMapper.getAuthByTargetId(permissionApplicationId, permissionTenantId);
//授权的部门,type为1的为人员,为0表示部门 //授权的部门,type为1的为人员,为0表示部门
...@@ -343,25 +310,25 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -343,25 +310,25 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
for (CoreModulePermissions authDept : authDepts) { for (CoreModulePermissions authDept : authDepts) {
String deptId = authDept.getRelId(); String deptId = authDept.getRelId();
String tenantId = authDept.getTenantId(); String tenantId = authDept.getTenantId();
getDeptPath(deptPath,deptId,tenantId); getDeptPath(deptPath, deptId, tenantId);
} }
//已被授权部门下的userid //已被授权部门下的userid
List<String> accessUserIds = !CollectionUtils.isEmpty(deptPath) ? sysUserSyncMapper.getUsersByDept(deptPath,permissionTenantId) : new ArrayList<>(); List<String> accessUserIds = !CollectionUtils.isEmpty(deptPath) ? sysUserSyncMapper.getUsersByDept(deptPath, permissionTenantId) : new ArrayList<>();
//已被直接授权的人员追加进去 //已被直接授权的人员追加进去
accessUserIds.addAll(auths.stream().filter(item -> item.getType().equals(1)).map(CoreModulePermissions::getRelId).collect(Collectors.toList())); accessUserIds.addAll(auths.stream().filter(item -> item.getType().equals(1)).map(CoreModulePermissions::getRelId).collect(Collectors.toList()));
if(!CollectionUtils.isEmpty(accessUserIds)){ if (!CollectionUtils.isEmpty(accessUserIds)) {
//查出人员邮箱+邮件推送许可 //查出人员邮箱+邮件推送许可
List<UserDTO> userEmailList = sysUserSyncMapper.getUserEmail(permissionTenantId); List<UserDTO> userEmailList = sysUserSyncMapper.getUserEmail(permissionTenantId);
Map<String, UserDTO> userEmailMap = CollectionUtils.isEmpty(userEmailList) ? new HashMap<>() Map<String, UserDTO> userEmailMap = CollectionUtils.isEmpty(userEmailList) ? new HashMap<>()
: userEmailList.stream().collect(Collectors.toMap(UserDTO::getWid, Function.identity(),(existing, replacement) -> existing)); : userEmailList.stream().collect(Collectors.toMap(UserDTO::getWid, Function.identity(), (existing, replacement) -> existing));
List<UserDTO> accessUsers = new ArrayList<>(); List<UserDTO> accessUsers = new ArrayList<>();
for (String accessUserId : accessUserIds) { for (String accessUserId : accessUserIds) {
if(userIdRelations.containsKey(accessUserId)){ if (userIdRelations.containsKey(accessUserId)) {
UserDTO accessUser = new UserDTO(); UserDTO accessUser = new UserDTO();
accessUser.setWid(accessUserId); accessUser.setWid(accessUserId);
accessUser.setTid(userIdRelations.get(accessUserId)); accessUser.setTid(userIdRelations.get(accessUserId));
if(userEmailMap.containsKey(accessUserId)){ if (userEmailMap.containsKey(accessUserId)) {
UserDTO emailMsg = userEmailMap.get(accessUserId); UserDTO emailMsg = userEmailMap.get(accessUserId);
accessUser.setEmail(emailMsg.getEmail()); accessUser.setEmail(emailMsg.getEmail());
accessUser.setEmailPushAccess(emailMsg.getEmailPushAccess()); accessUser.setEmailPushAccess(emailMsg.getEmailPushAccess());
...@@ -372,31 +339,32 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -372,31 +339,32 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
return accessUsers; return accessUsers;
} }
return new ArrayList<>(); return new ArrayList<>();
}catch (Exception e){ } catch (Exception e) {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw); PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw); e.printStackTrace(pw);
processLogService.log(null,null,sw.toString()); processLogService.log(null, null, sw.toString());
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
/** /**
* 获取部门的路径 * 获取部门的路径
*
* @param deptPath * @param deptPath
* @param deptId * @param deptId
* @param tenantId * @param tenantId
*/ */
private void getDeptPath(List<String> deptPath, String deptId, String tenantId) { private void getDeptPath(List<String> deptPath, String deptId, String tenantId) {
if(!deptPath.contains(deptId)) deptPath.add(deptId); if (!deptPath.contains(deptId)) deptPath.add(deptId);
List<String> subDeptIds = sysUserSyncMapper.getSubDeptId(deptId,tenantId); List<String> subDeptIds = sysUserSyncMapper.getSubDeptId(deptId, tenantId);
if(CollectionUtils.isEmpty(subDeptIds)) return; if (CollectionUtils.isEmpty(subDeptIds)) return;
for (String subDeptId : subDeptIds) { for (String subDeptId : subDeptIds) {
//部门id去重 //部门id去重
if(!deptPath.contains(subDeptId)){ if (!deptPath.contains(subDeptId)) {
deptPath.add(subDeptId); deptPath.add(subDeptId);
getDeptPath(deptPath,subDeptId,tenantId); getDeptPath(deptPath, subDeptId, tenantId);
} }
} }
...@@ -404,6 +372,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -404,6 +372,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
/** /**
* 拉取账户级的会议记录列表 * 拉取账户级的会议记录列表
*
* @param operatorId * @param operatorId
* @param operatorIdType * @param operatorIdType
* @param startTime * @param startTime
...@@ -469,10 +438,11 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -469,10 +438,11 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
/** /**
* 腾讯会议通过通讯录获取员工信息 * 腾讯会议通过通讯录获取员工信息
*
* @return * @return
*/ */
private List<TencentMeetingUser> fetchUsersInBatches() { private List<TencentMeetingUser> fetchUsersInBatches() {
if(redisUtils.hasKey("TENCENT_USER_ARRAY")){ if (redisUtils.hasKey("TENCENT_USER_ARRAY")) {
return (List<TencentMeetingUser>) redisUtils.get("TENCENT_USER_ARRAY"); return (List<TencentMeetingUser>) redisUtils.get("TENCENT_USER_ARRAY");
} }
//构造client客户端 //构造client客户端
...@@ -553,7 +523,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -553,7 +523,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
redisUtils.set("TENCENT_USER_ARRAY",resultList); redisUtils.set("TENCENT_USER_ARRAY", resultList);
// 输出结果 // 输出结果
System.out.printf("\n所有用户获取完成,共获取 %d 个用户\n", resultList.size()); System.out.printf("\n所有用户获取完成,共获取 %d 个用户\n", resultList.size());
return resultList; return resultList;
...@@ -600,7 +570,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T ...@@ -600,7 +570,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
return null; return null;
} }
String hostUserId = meetingInfo.getCurrentHosts().get(0).getUserid(); String hostUserId = meetingInfo.getCurrentHosts().get(0).getUserid();
log.info("成功获取主持人userid: {}",hostUserId); log.info("成功获取主持人userid: {}", hostUserId);
return hostUserId; return hostUserId;
} catch (Exception e) { } catch (Exception e) {
return null; return null;
......
...@@ -141,6 +141,15 @@ public class RedisUtils { ...@@ -141,6 +141,15 @@ public class RedisUtils {
} }
} }
public boolean setnx(String key, Object value, long time) {
try {
return redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/** /**
* 递增 * 递增
* *
......
...@@ -11,11 +11,15 @@ import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator; ...@@ -11,11 +11,15 @@ import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator;
import com.tencentcloudapi.wemeet.core.exception.ClientException; import com.tencentcloudapi.wemeet.core.exception.ClientException;
import com.tencentcloudapi.wemeet.core.exception.ServiceException; import com.tencentcloudapi.wemeet.core.exception.ServiceException;
import com.tencentcloudapi.wemeet.service.meetings.api.MeetingsApi; import com.tencentcloudapi.wemeet.service.meetings.api.MeetingsApi;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdGet200Response; import com.tencentcloudapi.wemeet.service.meetings.model.*;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner; import com.tencentcloudapi.wemeet.service.records.api.RecordsApi;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdParticipantsGet200Response; import com.tencentcloudapi.wemeet.service.records.model.V1AddressesRecordFileIdGet200Response;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner; import com.tencentcloudapi.wemeet.service.records.model.V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
...@@ -24,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value; ...@@ -24,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
...@@ -32,6 +37,7 @@ import java.time.Instant; ...@@ -32,6 +37,7 @@ import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
...@@ -70,6 +76,7 @@ public class TencentMeetingApiUtil { ...@@ -70,6 +76,7 @@ public class TencentMeetingApiUtil {
tencentAdminUserId = tmpAdminUserId; tencentAdminUserId = tmpAdminUserId;
client = getClient(); client = getClient();
} }
private Client getClient() { private Client getClient() {
if (client == null) { if (client == null) {
synchronized (TencentMeetingApiUtil.class) { synchronized (TencentMeetingApiUtil.class) {
...@@ -88,6 +95,7 @@ public class TencentMeetingApiUtil { ...@@ -88,6 +95,7 @@ public class TencentMeetingApiUtil {
/** /**
* 查询会议开始结束时间 * 查询会议开始结束时间
* Api: /v1/meetings/{meeting_id} * Api: /v1/meetings/{meeting_id}
*
* @param meetingId 腾讯会议id * @param meetingId 腾讯会议id
* @return * @return
*/ */
...@@ -119,81 +127,159 @@ public class TencentMeetingApiUtil { ...@@ -119,81 +127,159 @@ public class TencentMeetingApiUtil {
return null; return null;
} }
/**
* 查询用户已结束会议列表
*
* @param userId 用户id 必填
* @param meetingCode 会议码 非必填
* @param startTime 会议开始时间 非必填
* @param endTime 会议结束时间 非必填
* @return 已结束会议列表
* @throws ServiceException
* @throws ClientException
*/
public static V1HistoryMeetingsUseridGet200Response ApiV1HistoryMeetingsUseridGetRequest(String userId, String meetingCode, String startTime, String endTime) {
MeetingsApi.ApiV1HistoryMeetingsUseridGetRequest historyMeetingRequest =
new MeetingsApi.ApiV1HistoryMeetingsUseridGetRequest.Builder(userId)
.pageSize("20")
.page("1")
.meetingCode(meetingCode)
.startTime(startTime)
.endTime(endTime)
.build();
MeetingsApi.ApiV1HistoryMeetingsUseridGetResponse historyMeetingResponse = null;
try {
historyMeetingResponse = client.meetings().v1HistoryMeetingsUseridGet(historyMeetingRequest, new JWTAuthenticator.Builder()
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()))).timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
return historyMeetingResponse.getData();
} catch (ClientException | ServiceException e) {
log.error("ApiV1HistoryMeetingsUseridGetRequest error: {}", e.getMessage());
}
return null;
}
private void getSomething() throws Exception { /**
int total = 100000000; * 根据会议id查询会议详情
int count = 0; *
* @param meetingId
ZonedDateTime now = ZonedDateTime.now(); * @return
long startTime = now.minusDays(31).toEpochSecond(); */
long endTime = now.toEpochSecond(); public static V1MeetingsMeetingIdGet200Response ApiV1MeetingsMeetingIdGetRequest(String meetingId) {
MeetingsApi.ApiV1MeetingsMeetingIdGetRequest meetingRequest =
new MeetingsApi.ApiV1MeetingsMeetingIdGetRequest.Builder(meetingId)
String TENCENT_APPID = "210468336"; .operatorId(tencentAdminUserId)
String TENCENT_SDKID = "28790143843"; .operatorIdType("1")
String TENCENT_SECRETID = "0ks7u8cgQ8DGVtlYZeRA9TxZCjvUT3oL"; .instanceid("0")
String TENCENT_SECRETKEY = "gQU09rkJjiQfiGcUYdhiKq5Ol6LebXg4w7F7Ol0rwvvdv3Xy"; .build();
String TENCENT_ADMIN_USERID = "woaJARCQAAftcvU6GGoOn66rdSZ4IrOA"; MeetingsApi.ApiV1MeetingsMeetingIdGetResponse meetingResponse = null;
try {
int i = 3; meetingResponse = client.meetings().v1MeetingsMeetingIdGet(meetingRequest, new JWTAuthenticator.Builder()
while (i * 20 < total) { .nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
String uri = String.format( .timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
"/v1/corp/records?start_time=%d&end_time=%d&page=%d&page_size=%d&operator_id=%s&operator_id_type=%d", return meetingResponse.getData();
startTime, endTime, i++, 20, "woaJARCQAAftcvU6GGoOn66rdSZ4IrOA", 1 } catch (ClientException | ServiceException e) {
); log.error("ApiV1MeetingsMeetingIdGetRequest error: {}", e.getMessage());
}
String httpMethod = "GET"; return null;
String nonce = String.valueOf(new Random().nextInt(100000)); }
String timestamp = String.valueOf(Instant.now().getEpochSecond());
/**
// 3. 生成签名 * 获取参会成员明细
String signature = SignatureUtil.generateSignature(TENCENT_SECRETID, TENCENT_SECRETKEY, httpMethod, nonce, timestamp, uri, ""); *
* @param meetingId 会议id
// 4. 发送请求 * @param subMeetingId 子会议id
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { * @return
HttpGet request = new HttpGet("https://api.meeting.qq.com" + uri); */
request.setHeader("X-TC-Key", TENCENT_SECRETID); public static V1MeetingsMeetingIdParticipantsGet200Response ApiV1MeetingsMeetingIdParticipantsGetRequest(String meetingId, String subMeetingId) {
request.setHeader("X-TC-Timestamp", timestamp); MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest participantsRequest =
request.setHeader("X-TC-Nonce", nonce); new MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest
request.setHeader("X-TC-Signature", signature); .Builder(meetingId).subMeetingId(subMeetingId).operatorId(tencentAdminUserId).operatorIdType("1").build();
request.setHeader("AppId", TENCENT_APPID); AuthenticatorBuilder<JWTAuthenticator> participantsAuthenticatorBuilder =
request.setHeader("SdkId", TENCENT_SDKID); new JWTAuthenticator.Builder()
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
// 5. 解析响应 .timestamp(String.valueOf(System.currentTimeMillis() / 1000L));
String response = EntityUtils.toString(httpClient.execute(request).getEntity()); MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetResponse participantsResponse;
JsonObject jsonResponse = JsonParser.parseString(response).getAsJsonObject(); try {
Gson gson = new GsonBuilder() participantsResponse = client.meetings().v1MeetingsMeetingIdParticipantsGet(participantsRequest, participantsAuthenticatorBuilder);
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) return participantsResponse.getData();
.create(); } catch (ClientException | ServiceException e) {
log.error("ApiV1MeetingsMeetingIdParticipantsGetRequest error: {}", e.getMessage());
// 将 JsonObject 转换为实体类 }
CorpRecordsVO corpRecords = gson.fromJson(jsonResponse, CorpRecordsVO.class); return null;
if (corpRecords == null) { }
return;
} /**
for (CorpRecordsVO.RecordMeeting recordMeeting : corpRecords.getRecordMeetings()) { * 获取转录文件内容
String subject = recordMeeting.getSubject(); *
if (subject.contains("启动会")) { * @param recordFileId 转录文件id
count++; * @return
System.out.println("会议名: " + subject); */
String meetingRecordId = recordMeeting.getMeetingRecordId(); public static String ApiV1AddressesRecordFileIdGetRequest(String recordFileId) {
System.out.println("meetingRecordId: " + meetingRecordId); //查询录制转写详情
List<CorpRecordsVO.RecordFile> recordFiles = recordMeeting.getRecordFiles(); RecordsApi.ApiV1AddressesRecordFileIdGetRequest addressRequest =
if (CollUtil.isEmpty(recordFiles)) { new RecordsApi.ApiV1AddressesRecordFileIdGetRequest.Builder(recordFileId)
continue; .operatorId(tencentAdminUserId)
} .operatorIdType("1")
System.out.print("recordFileId: "); .build();
for (CorpRecordsVO.RecordFile recordFile : recordFiles) { RecordsApi.ApiV1AddressesRecordFileIdGetResponse addressResponse;
String recordFileId = recordFile.getRecordFileId(); try {
System.out.print(recordFileId + ","); addressResponse = client.records().v1AddressesRecordFileIdGet(addressRequest,
new JWTAuthenticator.Builder().nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
.timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
} catch (ClientException | ServiceException e) {
throw new RuntimeException(e);
}
// 处理响应
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())) {
return "\n\n" + recordTextContent;
} }
} }
} }
if (count >= 3) { } else {
break; log.info("No AI meeting transcripts found for record file {}", recordFileId);
}
} }
} else {
log.warn("Empty response for record file: {}", recordFileId);
} }
return "";
} }
/**
* 根据url下载文件
*
* @param url
* @return
*/
private static byte[] downloadFile(String url) {
// 实现文件下载逻辑
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
try {
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
return response.body().bytes();
} catch (Exception e) {
throw new RuntimeException("下载文件失败", e);
}
}
} }
...@@ -112,7 +112,7 @@ email: ...@@ -112,7 +112,7 @@ email:
smtp-host: ${EMAIL_SMTP_HOST} smtp-host: ${EMAIL_SMTP_HOST}
push-switch: true #邮件推送总开关,高优先级 push-switch: true #邮件推送总开关,高优先级
environment: test #test推给本公司人员,prod推给用户 environment: test #test推给本公司人员,prod推给用户
test-receiver: duanxincheng@chatbot.cn #用于测试的收方邮箱 test-receiver: hongdongbao@chatbot.cn #用于测试的收方邮箱
llm: llm:
api-addr: ${LLM_API_ADDR} api-addr: ${LLM_API_ADDR}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论