提交 0cc18764 作者: 洪东保

定时任务修改

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