提交 5df62d61 作者: 洪东保

日志

父级 23f53cb9
......@@ -120,7 +120,7 @@ public class FileProcessTask {
// 实际处理逻辑
public void process() {
// 根据meeting和subMeetingId上锁
boolean getKey = redisUtils.setnx("meeting_" + meetingId + subMeetingId, meetingId, 19*60);
boolean getKey = redisUtils.setnx("meeting_" + meetingId + subMeetingId, meetingId, 19 * 60);
if (!getKey) {
return;
}
......@@ -218,57 +218,62 @@ public class FileProcessTask {
throw new RuntimeException("获取的转录文本为空,跳过纪要生成");
}
// 1. 根据转录文件内容recordTextBuffer判断使用模板类型
Long choiceTemplateType = choiceTemplateType(meetingInfo.getSubject());
log.info("choiceTemplateType->{}", choiceTemplateType);
// 2. 获取这个会议需要使用的一个模板
MeetingRecordTemplate template = meetingRecordTemplateService.getEnabledRecordTemplate(choiceTemplateType, userIdMapper.getWidByTid(meetingInfo.getHostUid()));
if (template == null) {
throw new RobotBaseException("未找到模板!");
}
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 = processWithClaude(recordTextBuffer.toString(), meetingDate, participantNames, template.getPrompt());
String minutesPath = saveResult(processedResult, recordTextBuffer.toString().getBytes(StandardCharsets.UTF_8), meetingInfo, toUserCode, template);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd");
try (InputStream is = new FileInputStream(minutesPath)) {
byte[] meetingMinutesBytes = IOUtils.toByteArray(is);
EmailPush.Attachment attachment = EmailPush.Attachment.builder().name(meetingInfo.getSubject() + "会议纪要_" + fmt.format(meetingInfo.getStartTime())).bytes(meetingMinutesBytes).build();
attachments.add(attachment);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
FileUtil.del(minutesPath);
}
if (CollectionUtils.isEmpty(attachments)) {
log.info("用户{}暂无任何模板权限,纪要生成失败", hostUid);
continue;
}
if (!tidWidRelations.containsKey(meetingInfo.getHostUid())) {
log.error("邮件推送重试失败: 主持人对应关系未配置。meetingId {}", meetingId);
processLogService.log(meetingId, subMeetingId, "邮件推送重试失败: 主持人对应关系未配置。meetingId " + meetingId);
continue;
}
Boolean emailGenerateAccess = meetingInfo.getEmailGenerateAccess();
if (emailGenerateAccess) {
Long choiceTemplateType = choiceTemplateType(meetingInfo.getSubject());
log.info("choiceTemplateType->{}", choiceTemplateType);
// 2. 获取这个会议需要使用的一个模板
MeetingRecordTemplate template = meetingRecordTemplateService.getEnabledRecordTemplate(choiceTemplateType, userIdMapper.getWidByTid(meetingInfo.getHostUid()));
if (template == null) {
throw new RobotBaseException("未找到模板!");
}
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;
}
EmailPush emailPushBuilder = EmailPush.builder()
.toEmail(meetingInfo.getEmail())
.meetingId(meetingId)
.attachments(attachments)
.subject(meetingInfo.getSubject())
.meetingInstanceId(meetingInfo.getId())
.subMeetingId(meetingInfo.getSubMeetingId())
.toUserCode(toUserCode)
.toUser(meetingInfo.getHost())
.emailPushAccess(meetingInfo.getEmailPushAccess())
.build();
emailPush(emailPushBuilder);
isSuccess = true;
String processedResult = processWithClaude(recordTextBuffer.toString(), meetingDate, participantNames, template.getPrompt());
String minutesPath = saveResult(processedResult, recordTextBuffer.toString().getBytes(StandardCharsets.UTF_8), meetingInfo, toUserCode, template);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd");
try (InputStream is = new FileInputStream(minutesPath)) {
byte[] meetingMinutesBytes = IOUtils.toByteArray(is);
EmailPush.Attachment attachment = EmailPush.Attachment.builder().name(meetingInfo.getSubject() + "会议纪要_" + fmt.format(meetingInfo.getStartTime())).bytes(meetingMinutesBytes).build();
attachments.add(attachment);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
FileUtil.del(minutesPath);
}
if (CollectionUtils.isEmpty(attachments)) {
log.info("用户{}暂无任何模板权限,纪要生成失败", hostUid);
continue;
}
if (!tidWidRelations.containsKey(meetingInfo.getHostUid())) {
log.error("邮件推送重试失败: 主持人对应关系未配置。meetingId {}", meetingId);
processLogService.log(meetingId, subMeetingId, "邮件推送重试失败: 主持人对应关系未配置。meetingId " + meetingId);
continue;
}
EmailPush emailPushBuilder = EmailPush.builder()
.toEmail(meetingInfo.getEmail())
.meetingId(meetingId)
.attachments(attachments)
.subject(meetingInfo.getSubject())
.meetingInstanceId(meetingInfo.getId())
.subMeetingId(meetingInfo.getSubMeetingId())
.toUserCode(toUserCode)
.toUser(meetingInfo.getHost())
.emailPushAccess(meetingInfo.getEmailPushAccess())
.build();
emailPush(emailPushBuilder);
isSuccess = true;
}
} catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
......@@ -339,57 +344,56 @@ public class FileProcessTask {
/**
* 提供会议转录文件和会议主题,判断会议类型
*
* @param subject 会议主题
* @return
* 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;
* @param subject 会议主题
* @return 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 = "## 角色定义\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, transcript);
* List<Message> messages = new ArrayList<>();
* ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), formatPrompt);
* messages.add(chatMessage);
*
* // 调用Claude API处理文件
* 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;
* }
* }
* 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, transcript);
* List<Message> messages = new ArrayList<>();
* ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), formatPrompt);
* messages.add(chatMessage);
* <p>
* // 调用Claude API处理文件
* 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;
* }
* }
*/
private Long choiceTemplateType(String subject) {
Long type = 1L;
......@@ -461,7 +465,6 @@ public class FileProcessTask {
private String saveResult(String content, byte[] recordData, MeetingInfo meetingInfo, String toUserCode, MeetingRecordTemplate meetingRecordTemplate) {
String meetingName;
//转录文件临时存储路径
// String recordContentPath = meetingId + "-recordContent-" + IdUtil.fastSimpleUUID() + ".txt";
String recordContentPath;
//生成的xml临时存储路径
String recordXmlPath = meetingId + "-recordXmlPath-" + IdUtil.fastSimpleUUID() + ".xml";
......@@ -470,6 +473,8 @@ public class FileProcessTask {
//填充后的会议纪要word文件临时路径
String meetingMinutesPath;
try {
// TODO 转录文件加密并且直接上传到minio,不走layout和文档库
String subject = meetingInfo.getSubject();
String fileName = String.format(subject + "_转写原文_%s.txt", DateUtil.today());
MultipartFile multipartFile = new CustomMultipartFile(
......
......@@ -242,21 +242,21 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,
// 满足以上所有则emailGenerateAccess为ture
if (generateAccess) {
generateAccess = generateAccess && weComUserMap.containsKey(userDTO.getWid());
log.info("uid是否绑定企业微信: {}", generateAccess);
log.info("用户: {}, uid是否绑定企业微信: {}", userId, generateAccess);
}
if (generateAccess) {
List<SysUserSyncCategory> categoryList = sysUserSyncService.getCategoryListByUserId(userId);
List<String> deptIds = categoryList.stream().map(SysUserSyncCategory::getDeptId).collect(Collectors.toList());
generateAccess = generateAccess && modulePermissionService.checkAdminPermission(deptIds, userId);
log.info("超管是否授权给用户: {}", generateAccess);
log.info("用户: {}, 超管是否授权给用户: {}", userId, generateAccess);
}
if (generateAccess) {
generateAccess = generateAccess && modulePermissionService.personalStatus(userId);
log.info("个人是否开启纪要功能: {}", generateAccess);
log.info("用户: {}, 个人是否开启纪要功能: {}", userId, generateAccess);
}
// 是否推送邮箱
emailPushAccess = generateAccess && (weComUserMap.containsKey(userDTO.getWid()) ? weComUserMap.get(userDTO.getWid()).getEmailPushAccess() : Boolean.FALSE);
log.info("是否推送邮箱: {}", emailPushAccess);
log.info("用户: {},是否推送邮箱: {}", userId, emailPushAccess);
// 查询会议开始和结束时间
MeetingInfo startAndEndTimeByMeetingId = TencentMeetingApiUtil.getStartAndEndTimeByMeetingId(meetingId);
//会议基本信息保存
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论