提交 2089d7b6 作者: duanxincheng

纪要模板上传,邮件推送服务迭代,历史会议纪要接口(包括导出原文,导出会议纪要)

父级 4ce8c62e
...@@ -15,9 +15,9 @@ public class ThreadPoolConfig { ...@@ -15,9 +15,9 @@ public class ThreadPoolConfig {
public ThreadPoolTaskExecutor fileProcessExecutor() { public ThreadPoolTaskExecutor fileProcessExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数 (CPU密集型任务建议核心数+1) // 核心线程数 (CPU密集型任务建议核心数+1)
executor.setCorePoolSize(4); // 固定核心线程数,避免动态获取CPU核心数 executor.setCorePoolSize(2); // 固定核心线程数,避免动态获取CPU核心数
// 最大线程数 // 最大线程数
executor.setMaxPoolSize(4); executor.setMaxPoolSize(2);
// 队列容量 // 队列容量
executor.setQueueCapacity(1000); executor.setQueueCapacity(1000);
// 线程名前缀 // 线程名前缀
......
package com.cmeeting.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cmeeting.email.EmailSender;
import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.service.MeetingInfoService;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.util.R;
import com.cmeeting.vo.EmailPush;
import com.cmeeting.vo.MeetingInfoVO;
import com.deepoove.poi.XWPFTemplate;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.springframework.beans.BeanUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@RestController
@RequestMapping("/meetingInfo")
public class MeetingInfoController {
@Resource
private MeetingInfoService meetingInfoService;
@Resource
private MinioUtils minioUtils;
@Resource
private EmailSender emailSender;
@PostMapping("/updateRecordXml")
public R updateRecordXml(@RequestBody MeetingInfoVO vo) {
boolean save = meetingInfoService.updateRecordXml(vo);
return R.ok(save);
}
@PostMapping("/list")
public R list(@RequestBody MeetingInfoVO vo) {
IPage<MeetingInfo> page = meetingInfoService.getPage(vo);
return R.ok(page);
}
@PostMapping("/detail")
public R detail(@RequestBody MeetingInfoVO vo) {
MeetingInfo meetingInfo = meetingInfoService.getById(vo.getId());
BeanUtils.copyProperties(meetingInfo,vo);
String recordXml = vo.getRecordXml();
String recordContent = vo.getRecordContent();
try {
if(StringUtils.isNotEmpty(recordXml)){
//xml转json,用于前端的表单回显
String xml = minioUtils.getFileText(recordXml);
String json = convertXmlToJSON(xml);
vo.setRecordJson(json);
}
if(StringUtils.isNotEmpty(recordContent)){
vo.setRecordContent(minioUtils.getFileText(recordContent));
}
} catch (IOException e) {
e.printStackTrace();
}
return R.ok(vo);
}
/**
* 导出转写原文
*/
@PostMapping("/exportMeetingRecord")
public void exportMeetingRecord(@RequestBody MeetingInfoVO vo, HttpServletResponse response){
try {
MeetingInfo meetingInfo = meetingInfoService.getById(vo.getId());
String content = minioUtils.getFileText(meetingInfo.getRecordContent());
String fileName = String.format(meetingInfo.getSubject() + "_转写原文_%s.docx", DateUtil.today());
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf8"));
XWPFTemplate template;
try (XWPFDocument document = new XWPFDocument(); OutputStream os = response.getOutputStream()) {
// 将文本按行分割并逐行添加到 Word 文档
String[] lines = content.split("\\r?\\n");
for (String line : lines) {
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText(line);
}
// 输出文档
document.write(os);
} catch (IOException e) {
throw new RuntimeException(e);
}
} catch (Exception e) {
response.reset();
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
e.printStackTrace();
}
}
/**
* 导出会议纪要(本地下载)
*/
@PostMapping("/downloadMeetingMinutes")
public void downloadMeetingMinutes(@RequestBody MeetingInfoVO vo, HttpServletResponse response){
try {
MeetingInfo meetingInfo = meetingInfoService.getById(vo.getId());
String xml = minioUtils.getFileText(meetingInfo.getRecordXml());
Map<String, Object> dataModel = convertXmlToMap(xml);
//追加参会人员信息
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_participants", meetingInfo.getParticipantUsers());
participantsMap.put("meeting_host",meetingInfo.getHost());
dataModel.putAll(participantsMap);
ClassPathResource resource = new ClassPathResource("template/data_network.docx");
String fileName = String.format(meetingInfo.getSubject() + "_会议纪要_%s.docx", DateUtil.today());
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf8"));
XWPFTemplate template;
try (InputStream is = resource.getInputStream();OutputStream os = response.getOutputStream()) {
template = XWPFTemplate.compile(is).render(dataModel);
template.write(os);
} catch (IOException e) {
throw new RuntimeException(e);
}
} catch (Exception e) {
response.reset();
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
e.printStackTrace();
}
}
/**
* 导出会议纪要(推送邮件)
*/
@PostMapping("/pushMeetingMinutes")
public R exportMeetingMinutes(@RequestBody MeetingInfoVO vo, HttpServletResponse response){
try {
MeetingInfo meetingInfo = meetingInfoService.getById(vo.getId());
String xml = minioUtils.getFileText(meetingInfo.getRecordXml());
Map<String, Object> dataModel = convertXmlToMap(xml);
//追加参会人员信息
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_participants", meetingInfo.getParticipantUsers());
participantsMap.put("meeting_host",meetingInfo.getHost());
dataModel.putAll(participantsMap);
ClassPathResource resource = new ClassPathResource("template/data_network.docx");
XWPFTemplate template;
byte[] meetingMinutesBytes;
try (InputStream is = resource.getInputStream();ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
template = XWPFTemplate.compile(is).render(dataModel);
template.write(baos);
meetingMinutesBytes = baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
List<EmailPush.Attachment> attachments = new ArrayList<>();
EmailPush.Attachment attachment = EmailPush.Attachment.builder().name(meetingInfo.getSubject()+"_数据网络中心").bytes(meetingMinutesBytes).build();
attachments.add(attachment);
EmailPush emailPushBuilder = EmailPush.builder()
.toEmail(meetingInfo.getEmail())
.meetingId(meetingInfo.getMeetingId())
.attachments(attachments)
.subject(meetingInfo.getSubject()).build();
emailSender.sendEmailWithAttachment(emailPushBuilder);
return R.ok("发送纪要邮件成功");
} catch (Exception e) {
response.reset();
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
e.printStackTrace();
return R.error("发送纪要邮件失败");
}
}
public static Map<String, Object> convertXmlToMap(String xml) throws Exception {
XmlMapper xmlMapper = new XmlMapper();
ObjectMapper objectMapper = new ObjectMapper();
// 先将 XML 读取为 Map
Map<?, ?> xmlMap = xmlMapper.readValue(xml, Map.class);
// 转换为更标准的 Map<String, Object>
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();
Object realValue = value.get("");
entry.setValue(realValue);
}
return map;
}
private String convertXmlToJSON(String xml) {
String json;
try {
XmlMapper xmlMapper = new XmlMapper();
JsonNode rootNode = xmlMapper.readTree(xml.getBytes());
// 正确获取节点和属性的方式
List<Map> list = new ArrayList<>();
Iterator<String> iterator = rootNode.fieldNames();
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);
put("keyName",displayName);
put("value",content);
}});
}
// 构建JSON对象
ObjectMapper jsonMapper = new ObjectMapper();
json = jsonMapper.writeValueAsString(list);
} catch (IOException e) {
log.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
return json;
}
}
package com.cmeeting.controller; package com.cmeeting.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cmeeting.pojo.MeetingRecordTemplate; import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.service.MeetingRecordTemplateService; import com.cmeeting.service.MeetingRecordTemplateService;
import com.cmeeting.util.R; import com.cmeeting.util.R;
import com.cmeeting.vo.RecordTemplateVO; import com.cmeeting.vo.RecordTemplateVO;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime; import java.time.LocalDateTime;
...@@ -45,9 +44,37 @@ public class RecordTemplateController { ...@@ -45,9 +44,37 @@ public class RecordTemplateController {
return R.ok(save); return R.ok(save);
} }
/**
* 纪要模板列表,通过type区分为系统模板还是自定义模板
* @param vo
* @return
*/
@PostMapping("/list") @PostMapping("/list")
public R list(@RequestBody RecordTemplateVO vo) { public R list(@RequestBody RecordTemplateVO vo) {
IPage<MeetingRecordTemplate> page = recordTemplateService.getPage(vo); IPage<MeetingRecordTemplate> page = recordTemplateService.getPage(vo);
return R.ok(page); return R.ok(page);
} }
/**
* 普通用户纪要模板,查出有授权的系统模板和个人创建的自定义模板
* @param vo
* @return
*/
@PostMapping("/personalList")
public R personalList(@RequestBody RecordTemplateVO vo) {
IPage<MeetingRecordTemplate> page = recordTemplateService.getPersonalPage(vo);
return R.ok(page);
}
@PostMapping("/detail")
public R detail(@RequestBody RecordTemplateVO vo) {
MeetingRecordTemplate detail = recordTemplateService.getById(vo.getId());
return R.ok(detail);
}
@PostMapping("/templateUpload")
public R templateUpload(@RequestParam("file") MultipartFile file,@RequestParam("id") Integer id) {
String template = recordTemplateService.templateUpload(file, id);
return R.ok(template);
}
} }
package com.cmeeting.controller; package com.cmeeting.controller;
import com.cmeeting.pojo.TencentMeetingUser;
import com.cmeeting.service.TecentMeetingService; import com.cmeeting.service.TecentMeetingService;
import com.cmeeting.vo.TencentMeetingVO;
import com.tencentcloudapi.wemeet.Client;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController @RestController
@RequestMapping("/tencent") @RequestMapping("/tencentMeeting")
public class TencentMeetingController { public class TencentMeetingController {
@Autowired @Autowired
private TecentMeetingService tecentMeetingService; private TecentMeetingService tecentMeetingService;
...@@ -22,9 +15,4 @@ public class TencentMeetingController { ...@@ -22,9 +15,4 @@ public class TencentMeetingController {
public void addUsers() { public void addUsers() {
tecentMeetingService.doUsers(); tecentMeetingService.doUsers();
} }
// @GetMapping("/getMeetingFiles")
// public void getMeetingFiles(){
// tecentMeetingService.getMeetingFiles();
// }
} }
...@@ -5,6 +5,7 @@ import com.azure.core.credential.TokenRequestContext; ...@@ -5,6 +5,7 @@ import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.ClientSecretCredential; import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder; import com.azure.identity.ClientSecretCredentialBuilder;
import com.cmeeting.log.service.ProcessLogService; import com.cmeeting.log.service.ProcessLogService;
import com.cmeeting.vo.EmailPush;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider; import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.models.*; import com.microsoft.graph.models.*;
import com.microsoft.graph.models.Message; import com.microsoft.graph.models.Message;
...@@ -31,6 +32,7 @@ import java.nio.file.Paths; ...@@ -31,6 +32,7 @@ import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
...@@ -159,13 +161,10 @@ public class EmailSender { ...@@ -159,13 +161,10 @@ public class EmailSender {
/** /**
* 发送邮件,带附件 * 发送邮件,带附件
* @param toEmail * @param emailPushBuilder
* @param subject
* @param attachmentPath
* @param meetingId
* @return * @return
*/ */
public boolean sendEmailWithAttachment(String toEmail, String subject, String attachmentPath, String meetingId) { public boolean sendEmailWithAttachment(EmailPush emailPushBuilder) {
log.info("sendEmailWithAttachment start..."); log.info("sendEmailWithAttachment start...");
// 创建会话 // 创建会话
...@@ -175,6 +174,10 @@ public class EmailSender { ...@@ -175,6 +174,10 @@ public class EmailSender {
AtomicInteger retryCount = new AtomicInteger(0); AtomicInteger retryCount = new AtomicInteger(0);
boolean isSent = false; boolean isSent = false;
// String toEmail = emailPushBuilder.getToEmail();
String toEmail = "duanxincheng@chatbot.cn";
String subject = emailPushBuilder.getSubject();
String meetingId = emailPushBuilder.getMeetingId();
if(StringUtils.isEmpty(toEmail)){ if(StringUtils.isEmpty(toEmail)){
log.error("收件邮箱为空,推送失败"); log.error("收件邮箱为空,推送失败");
processLogService.log(meetingId,null,"收件邮箱为空,推送失败"); processLogService.log(meetingId,null,"收件邮箱为空,推送失败");
...@@ -204,7 +207,7 @@ public class EmailSender { ...@@ -204,7 +207,7 @@ public class EmailSender {
GraphServiceClient<Request> graphClient = GraphServiceClient.builder().authenticationProvider(tokenCredAuthProvider).buildClient(); GraphServiceClient<Request> graphClient = GraphServiceClient.builder().authenticationProvider(tokenCredAuthProvider).buildClient();
com.microsoft.graph.models.Message message = new Message(); com.microsoft.graph.models.Message message = new Message();
message.subject = subject; message.subject = subject + "会议纪要";
ItemBody body = new ItemBody(); ItemBody body = new ItemBody();
body.contentType = BodyType.TEXT; body.contentType = BodyType.TEXT;
body.content = "您好:\n\n 附件为您本次会议的会议纪要,烦请下载查看"; body.content = "您好:\n\n 附件为您本次会议的会议纪要,烦请下载查看";
...@@ -218,13 +221,15 @@ public class EmailSender { ...@@ -218,13 +221,15 @@ public class EmailSender {
message.toRecipients = toRecipientsList; message.toRecipients = toRecipientsList;
//构建附件 //构建附件
LinkedList<Attachment> attachmentsList = new LinkedList<>(); LinkedList<Attachment> attachmentsList = new LinkedList<>();
byte[] recordXmlData = Files.readAllBytes(Paths.get(attachmentPath)); for (EmailPush.Attachment attachment : emailPushBuilder.getAttachments()) {
FileAttachment attachments = new FileAttachment(); FileAttachment attachments = new FileAttachment();
attachments.name = attachmentPath.substring(attachmentPath.lastIndexOf("/") + 1); attachments.name = attachment.getName() + ".docx";
attachments.oDataType = "#microsoft.graph.fileAttachment"; attachments.oDataType = "#microsoft.graph.fileAttachment";
attachments.contentType="application/msword"; attachments.contentType="application/msword";
attachments.contentBytes = recordXmlData; attachments.contentBytes = attachment.getBytes();
attachmentsList.add(attachments); attachmentsList.add(attachments);
}
AttachmentCollectionResponse attachmentCollectionResponse = new AttachmentCollectionResponse(); AttachmentCollectionResponse attachmentCollectionResponse = new AttachmentCollectionResponse();
attachmentCollectionResponse.value = attachmentsList; attachmentCollectionResponse.value = attachmentsList;
......
...@@ -134,7 +134,7 @@ public class CmeetingJob { ...@@ -134,7 +134,7 @@ public class CmeetingJob {
} }
// @Scheduled(fixedRate = 5 * 60 * 1000,initialDelay = 2 * 60 * 1000) // @Scheduled(fixedRate = 5 * 60 * 1000,initialDelay = 2 * 60 * 1000)
@Scheduled(fixedRate = 5 * 60 * 1000) @Scheduled(fixedRate = 10 * 60 * 1000)
public void execute() { public void execute() {
// 定义时间格式化器 // 定义时间格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
......
package com.cmeeting.job; package com.cmeeting.job;
import cn.chatbot.openai.completion.chat.ChatCompletionRequest;
import cn.chatbot.openai.completion.chat.ChatMessage;
import cn.chatbot.openai.completion.chat.ChatMessageRole;
import cn.chatbot.openai.completion.chat.Message;
import cn.chatbot.openai.service.LLMService;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
...@@ -12,48 +7,27 @@ import com.cmeeting.email.EmailSender; ...@@ -12,48 +7,27 @@ import com.cmeeting.email.EmailSender;
import com.cmeeting.mapper.primary.MeetingInfoMapper; import com.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper; import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper;
import com.cmeeting.pojo.MeetingInfo; import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.util.MinioUtils; import com.cmeeting.util.MinioUtils;
import com.cmeeting.vo.EmailPush;
import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.XWPFTemplate;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.tencentcloudapi.wemeet.Client;
import com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder;
import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator;
import com.tencentcloudapi.wemeet.service.meetings.api.MeetingsApi;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdParticipantsGet200Response;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner;
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.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient; import org.apache.commons.io.IOUtils;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.*; import java.io.*;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
...@@ -105,9 +79,9 @@ public class EmailPushTask { ...@@ -105,9 +79,9 @@ public class EmailPushTask {
dataModel.putAll(participantsMap); dataModel.putAll(participantsMap);
String path = Thread.currentThread().getContextClassLoader().getResource("").getPath(); String path = Thread.currentThread().getContextClassLoader().getResource("").getPath();
ClassPathResource resource = new ClassPathResource("template/data_net_template.docx"); ClassPathResource resource = new ClassPathResource("template/data_network.docx");
meetingName = dataModel.get("meeting_name") != null ? String.valueOf(dataModel.get("meeting_name")) : "腾讯会议纪要"; meetingName = dataModel.get("meeting_name") != null ? String.valueOf(dataModel.get("meeting_name")) : "腾讯会议纪要";
targetFileName = meetingName + "_" + nowTime; targetFileName = meetingName + "_data_network";
try (InputStream inputStream = resource.getInputStream()) { try (InputStream inputStream = resource.getInputStream()) {
XWPFTemplate template = XWPFTemplate.compile(inputStream).render(dataModel); XWPFTemplate template = XWPFTemplate.compile(inputStream).render(dataModel);
template.writeAndClose(new FileOutputStream(path + targetFileName + ".docx")); template.writeAndClose(new FileOutputStream(path + targetFileName + ".docx"));
...@@ -118,8 +92,16 @@ public class EmailPushTask { ...@@ -118,8 +92,16 @@ public class EmailPushTask {
} }
//邮件推送 //邮件推送
// isSuccess = emailSender.sendEmailWithAttachment("duanxincheng@chatbot.cn",meetingName,savePath + targetFileName + ".docx",meetingId); List<EmailPush.Attachment> attachments = new ArrayList<>();
isSuccess = emailSender.sendEmailWithAttachment(meetingInfo.getEmail(),meetingName,savePath + targetFileName + ".docx",meetingId); try(InputStream is = new FileInputStream(path + targetFileName + ".docx")){
byte[] meetingMinutesBytes = IOUtils.toByteArray(is);
EmailPush.Attachment attachment = EmailPush.Attachment.builder().name(meetingInfo.getSubject()+"_数据网络中心").bytes(meetingMinutesBytes).build();
attachments.add(attachment);
}catch (Exception e){
throw new RuntimeException(e);
}
EmailPush emailPushBuilder = EmailPush.builder().toEmail(meetingInfo.getEmail()).meetingId(meetingId).attachments(attachments).subject(meetingInfo.getSubject()).build();
isSuccess = emailSender.sendEmailWithAttachment(emailPushBuilder);
} catch (Exception e) { } catch (Exception e) {
// 异常处理 // 异常处理
int currentRetryCount = retryCount.getAndIncrement(); int currentRetryCount = retryCount.getAndIncrement();
......
...@@ -5,21 +5,17 @@ import cn.chatbot.openai.completion.chat.ChatMessage; ...@@ -5,21 +5,17 @@ import cn.chatbot.openai.completion.chat.ChatMessage;
import cn.chatbot.openai.completion.chat.ChatMessageRole; import cn.chatbot.openai.completion.chat.ChatMessageRole;
import cn.chatbot.openai.completion.chat.Message; import cn.chatbot.openai.completion.chat.Message;
import cn.chatbot.openai.service.LLMService; import cn.chatbot.openai.service.LLMService;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.cmeeting.dto.UserDTO;
import com.cmeeting.email.EmailSender; import com.cmeeting.email.EmailSender;
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;
import com.cmeeting.pojo.MeetingInfo; import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.pojo.MeetingRecordTemplate; import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.pojo.UserId;
import com.cmeeting.service.MeetingInfoService;
import com.cmeeting.util.MinioUtils; import com.cmeeting.util.MinioUtils;
import com.cmeeting.vo.EmailPush;
import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.XWPFTemplate;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
...@@ -39,6 +35,7 @@ import lombok.extern.slf4j.Slf4j; ...@@ -39,6 +35,7 @@ import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor; import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFDocument;
...@@ -53,10 +50,7 @@ import java.nio.file.Files; ...@@ -53,10 +50,7 @@ import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
...@@ -103,6 +97,21 @@ public class FileProcessTask { ...@@ -103,6 +97,21 @@ public class FileProcessTask {
.eq(MeetingInfo::getMeetingId,meetingId) .eq(MeetingInfo::getMeetingId,meetingId)
.eq(subMeetingId != null, MeetingInfo::getSubMeetingId, subMeetingId)); .eq(subMeetingId != null, MeetingInfo::getSubMeetingId, 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 =
client.meetings().v1MeetingsMeetingIdParticipantsGet(participantsRequest, participantsAuthenticatorBuilder);
V1MeetingsMeetingIdParticipantsGet200Response participantsData = participantsResponse.getData();
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(); StringBuffer recordTextBuffer = new StringBuffer();
for (String recordFileId : recordFileIdList) { for (String recordFileId : recordFileIdList) {
...@@ -142,8 +151,6 @@ public class FileProcessTask { ...@@ -142,8 +151,6 @@ public class FileProcessTask {
// 2. 将二进制文件转换为文本 // 2. 将二进制文件转换为文本
String recordTextContent = new String(fileData); String recordTextContent = new String(fileData);
// byte[] fileData = Files.readAllBytes(Paths.get("/20250514132555-中集AIGC项目例会-转写智能优化版.txt"));
// String recordTextContent = new String(fileData);
if(StringUtils.isNotEmpty(recordTextContent.replaceAll("\\n","").trim())){ if(StringUtils.isNotEmpty(recordTextContent.replaceAll("\\n","").trim())){
recordTextBuffer.append("\n\n"); recordTextBuffer.append("\n\n");
recordTextBuffer.append(recordTextContent); recordTextBuffer.append(recordTextContent);
...@@ -165,11 +172,35 @@ public class FileProcessTask { ...@@ -165,11 +172,35 @@ public class FileProcessTask {
// 3. 处理文件 (调用Claude API等) // 3. 处理文件 (调用Claude API等)
String choiceTemplateType = choiceTemplateType(meetingInfo.getSubject(),recordTextBuffer.toString()); String choiceTemplateType = choiceTemplateType(meetingInfo.getSubject(),recordTextBuffer.toString());
log.info("choiceTemplateType->{}",choiceTemplateType); log.info("choiceTemplateType->{}",choiceTemplateType);
String processedResult = processWithClaude(recordTextBuffer.toString()); //获取系统模板
MeetingRecordTemplate meetingRecordTemplate = meetingRecordTemplateMapper.selectById(1);
String processedResult = processWithClaude(recordTextBuffer.toString(),meetingRecordTemplate.getPrompt());
// 4. 保存结果 // 4. 保存结果
saveResult(savePath, processedResult, recordTextBuffer.toString().getBytes(StandardCharsets.UTF_8),meetingInfo); MeetingRecordTemplate meetingRecordTemplate2 = meetingRecordTemplateMapper.selectById(2);
String dataNetworkMinutesPath = saveResult(processedResult, recordTextBuffer.toString().getBytes(StandardCharsets.UTF_8), meetingInfo, meetingRecordTemplate);
List<EmailPush.Attachment> attachments = new ArrayList<>();
try(InputStream is = new FileInputStream(dataNetworkMinutesPath)){
byte[] meetingMinutesBytes = IOUtils.toByteArray(is);
EmailPush.Attachment attachment = EmailPush.Attachment.builder().name(meetingInfo.getSubject()+"会议纪要_数据网络中心").bytes(meetingMinutesBytes).build();
attachments.add(attachment);
// emailPush(meetingMinutesBytes, meetingInfo.getSubject(), meetingInfo.getEmail(), meetingInfo.getEmailPushAccess());
}catch (Exception e){
throw new RuntimeException(e);
}
String processedResult2 = processWithClaude(recordTextBuffer.toString(),meetingRecordTemplate2.getPrompt());
String groupOfficeMinutesPath = saveResult(processedResult2, recordTextBuffer.toString().getBytes(StandardCharsets.UTF_8), meetingInfo, meetingRecordTemplate2);
try(InputStream is = new FileInputStream(groupOfficeMinutesPath)){
byte[] meetingMinutesBytes = IOUtils.toByteArray(is);
EmailPush.Attachment attachment = EmailPush.Attachment.builder().name(meetingInfo.getSubject()+"会议纪要_集团办").bytes(meetingMinutesBytes).build();
attachments.add(attachment);
// emailPush(meetingMinutesBytes, meetingInfo.getSubject(), meetingInfo.getEmail(), meetingInfo.getEmailPushAccess());
}catch (Exception e){
throw new RuntimeException(e);
}
EmailPush emailPushBuilder = EmailPush.builder().toEmail(meetingInfo.getEmail()).meetingId(meetingId).attachments(attachments).subject(meetingInfo.getSubject()).build();
emailPush(emailPushBuilder);
isSuccess = true; isSuccess = true;
} catch (Exception e) { } catch (Exception e) {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
...@@ -264,17 +295,21 @@ public class FileProcessTask { ...@@ -264,17 +295,21 @@ public class FileProcessTask {
private String formatMessage(String pattern, Object... args) { private String formatMessage(String pattern, Object... args) {
return MessageFormat.format(pattern, args); return MessageFormat.format(pattern, args);
} }
private String processWithClaude(String textContent) { /**
* 大模型生成纪要xml
* @param textContent 转录文件
* @param prompt 提示词
* @return
*/
private String processWithClaude(String textContent, String prompt) {
//将文件传送给大模型处理 //将文件传送给大模型处理
String token = "AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO"; String token = "AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO";
String apiAddr = llmApiAddr + "/llm/sse-invoke"; String apiAddr = llmApiAddr + "/llm/sse-invoke";
String model = "anthropic.claude-3-5-sonnet-20240620-v1:0"; String model = "anthropic.claude-3-5-sonnet-20240620-v1:0";
int maxTokens = 5000; int maxTokens = 5000;
//获取系统模板
MeetingRecordTemplate meetingRecordTemplate = meetingRecordTemplateMapper.selectById(1);
List<Message> messages = new ArrayList<>(); List<Message> messages = new ArrayList<>();
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), meetingRecordTemplate.getPrompt()); ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), prompt);
messages.add(chatMessage); messages.add(chatMessage);
chatMessage = new ChatMessage(ChatMessageRole.ASSISTANT.value(), "好的请提供会议记录"); chatMessage = new ChatMessage(ChatMessageRole.ASSISTANT.value(), "好的请提供会议记录");
messages.add(chatMessage); messages.add(chatMessage);
...@@ -287,24 +322,32 @@ public class FileProcessTask { ...@@ -287,24 +322,32 @@ public class FileProcessTask {
return ret; return ret;
} }
private void saveResult(String path, String content, byte[] recordData, MeetingInfo meetingInfo) { /**
String nowTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HHmmss")); * 保存会议纪要相关的文件
String targetPath = path + nowTime + ".json"; // 改为.json扩展名; * @param content 大模型返回的不规则xml
String targetFileName; * @param recordData 转录文本
* @param meetingInfo 会议对象
* @param meetingRecordTemplate 模板信息
*/
private String saveResult(String content, byte[] recordData, MeetingInfo meetingInfo,MeetingRecordTemplate meetingRecordTemplate) {
String meetingName; String meetingName;
String xml = extractXmlFromMarkdown(content); //转录文件临时存储路径
String recordContentPath = meetingId + "-recordContent-" + IdUtil.fastSimpleUUID() + ".txt";
// 将xml格式的内容转换为特殊json //生成的xml临时存储路径
String json = convertXmlToJSON(xml); String recordXmlPath = meetingId + "-recordXmlPath-" + IdUtil.fastSimpleUUID() + ".xml";
String recordContentPath = meetingId + "-recordContent-" + IdUtil.fastSimpleUUID() + "txt"; //填充后的会议纪要名称
String recordXmlPath = meetingId + "-recordXmlPath-" + IdUtil.fastSimpleUUID() + "xml"; String meetingMinutesFileName;
//填充后的会议纪要word文件临时路径
String meetingMinutesPath;
try { try {
//去除内容中除了xml内容以外其他的信息,格式化xml
String xml = extractXmlFromMarkdown(content);
minioUtils.upload(recordContentPath,recordData); minioUtils.upload(recordContentPath,recordData);
//写入json文件,这里的json文件用于用户可编辑 minioUtils.upload(recordXmlPath,xml.getBytes(StandardCharsets.UTF_8));
Files.write(Paths.get(targetPath), json.getBytes());
log.info("json文件已保存到: {}", targetPath);
//将xml格式的内容转换为map,用于填充模板 //将xml格式的内容转换为map,用于填充模板
Map<String, Object> dataModel = convertXmlToMap(xml); Map<String, Object> dataModel = convertXmlToMap(xml);
meetingName = dataModel.get("meeting_name") != null ? String.valueOf(dataModel.get("meeting_name")) : "腾讯会议纪要";
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");
...@@ -313,20 +356,16 @@ public class FileProcessTask { ...@@ -313,20 +356,16 @@ public class FileProcessTask {
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);
ClassPathResource resource = new ClassPathResource("template/data_net_template.docx");
meetingName = dataModel.get("meeting_name") != null ? String.valueOf(dataModel.get("meeting_name")) : "腾讯会议纪要";
targetFileName = meetingName + "_" + nowTime;
XWPFTemplate template; XWPFTemplate template;
try (InputStream inputStream = resource.getInputStream()) { try (InputStream inputStream = minioUtils.getFile(meetingRecordTemplate.getTemplate())) {
template = XWPFTemplate.compile(inputStream).render(dataModel); template = XWPFTemplate.compile(inputStream).render(dataModel);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
template.writeAndClose(new FileOutputStream(path + targetFileName + ".docx")); meetingMinutesPath = savePath + meetingMinutesFileName + ".docx";
byte[] recordXmlData = Files.readAllBytes(Paths.get(path + targetFileName + ".docx")); template.writeAndClose(new FileOutputStream(meetingMinutesPath));
minioUtils.upload(recordXmlPath,recordXmlData);
processLogService.log(meetingId,subMeetingId,"填充会议纪要成功"); processLogService.log(meetingId,subMeetingId,"填充会议纪要成功");
} catch (Exception e) { } catch (Exception e) {
log.error("填充会议纪要失败: {}", e.getMessage(), e); log.error("填充会议纪要失败: {}", e.getMessage(), e);
...@@ -337,14 +376,29 @@ public class FileProcessTask { ...@@ -337,14 +376,29 @@ public class FileProcessTask {
throw new RuntimeException("填充会议纪要失败"); 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)
);
meetingInfo.setRecordContent(recordContentPath);
meetingInfo.setRecordXml(recordXmlPath);
return savePath + meetingMinutesFileName + ".docx";
}
private void emailPush(EmailPush emailPushBuilder) {
Boolean isPushed; Boolean isPushed;
log.info("开始邮件推送------"); log.info("开始邮件推送------");
if(meetingInfo.getEmailPushAccess()){ if(true){
log.info("用户允许邮件推送,准备推送邮件至{}------",meetingInfo.getEmail()); log.info("用户允许邮件推送,准备推送邮件至{}------", emailPushBuilder.getToEmail());
//邮件推送 //邮件推送
isPushed = emailSender.sendEmailWithAttachment("duanxincheng@chatbot.cn",meetingName,path + targetFileName + ".docx",meetingId); isPushed = emailSender.sendEmailWithAttachment(emailPushBuilder);
try { try {
// isPushed = emailSender.sendEmailWithAttachment(meetingInfo.getEmail(),meetingName,path + targetFileName + ".docx",meetingId); // isPushed = emailSender.sendEmailWithAttachment(emailPushBuilder);
} catch (Exception e) { } catch (Exception e) {
log.error("邮件推送失败: {}", e.getMessage(), e); log.error("邮件推送失败: {}", e.getMessage(), e);
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
...@@ -354,22 +408,16 @@ public class FileProcessTask { ...@@ -354,22 +408,16 @@ public class FileProcessTask {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if(isPushed) if(isPushed)
processLogService.log(meetingId,subMeetingId,"用户允许邮件推送,推送邮件至"+meetingInfo.getEmail()); processLogService.log(meetingId,subMeetingId,"用户允许邮件推送,推送邮件至"+ emailPushBuilder.getToEmail());
// emailSender.sendEmailWithAttachment("xuwentao@chatbot.cn",meetingName,path + targetFileName + ".docx",meetingId);
// emailSender.sendEmailWithAttachment("jiaqi.cai@cimc.com",meetingName,path + targetFileName + ".docx",meetingId);
}else{ }else{
log.info("用户关闭了邮件推送,推送终止------"); log.info("用户关闭了邮件推送,推送终止------");
processLogService.log(meetingId,subMeetingId,"用户关闭了邮件推送,推送终止"); processLogService.log(meetingId,subMeetingId,"用户关闭了邮件推送,推送终止");
isPushed = Boolean.FALSE; isPushed = Boolean.FALSE;
} }
meetingInfoMapper.update(null,
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::getRecordXml,recordXmlPath)
.set(MeetingInfo::getIsGenerated,Boolean.TRUE)
.set(MeetingInfo::getIsPushed,isPushed) .set(MeetingInfo::getIsPushed,isPushed)
); );
} }
...@@ -460,10 +508,13 @@ public class FileProcessTask { ...@@ -460,10 +508,13 @@ public class FileProcessTask {
* @param markdown * @param markdown
* @return * @return
*/ */
private static String extractXmlFromMarkdown(String markdown) { private String extractXmlFromMarkdown(String markdown) {
StringBuffer sb = null; StringBuffer sb = null;
try { try {
int start = markdown.indexOf("<"); int start = markdown.indexOf("<");
if(start == -1){
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>");
......
...@@ -114,4 +114,13 @@ public class MeetingInfo implements Serializable { ...@@ -114,4 +114,13 @@ public class MeetingInfo implements Serializable {
* 邮箱 * 邮箱
*/ */
private String email; private String email;
/**
* 同步时间
*/
private LocalDateTime updateTime;
/**
* 模板ID
*/
private Integer templateId;
} }
\ No newline at end of file
...@@ -76,4 +76,9 @@ public class MeetingRecordTemplate implements Serializable { ...@@ -76,4 +76,9 @@ public class MeetingRecordTemplate implements Serializable {
* 删除标识 * 删除标识
*/ */
private Boolean isDel; private Boolean isDel;
/**
* 模板文件
*/
private String template;
} }
\ No newline at end of file
package com.cmeeting.service; package com.cmeeting.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.cmeeting.pojo.MeetingInfo; import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.vo.MeetingInfoVO;
public interface MeetingInfoService extends IService<MeetingInfo> { public interface MeetingInfoService extends IService<MeetingInfo> {
IPage<MeetingInfo> getPage(MeetingInfoVO vo);
boolean updateRecordXml(MeetingInfoVO vo);
} }
...@@ -4,7 +4,17 @@ import com.baomidou.mybatisplus.core.metadata.IPage; ...@@ -4,7 +4,17 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.cmeeting.pojo.MeetingRecordTemplate; import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.vo.RecordTemplateVO; import com.cmeeting.vo.RecordTemplateVO;
import org.springframework.web.multipart.MultipartFile;
public interface MeetingRecordTemplateService extends IService<MeetingRecordTemplate> { public interface MeetingRecordTemplateService extends IService<MeetingRecordTemplate> {
IPage<MeetingRecordTemplate> getPage(RecordTemplateVO vo); IPage<MeetingRecordTemplate> getPage(RecordTemplateVO vo);
IPage<MeetingRecordTemplate> getPersonalPage(RecordTemplateVO vo);
/**
* 上传附件模板
* @param file
* @param id
* @return
*/
String templateUpload(MultipartFile file,Integer id);
} }
package com.cmeeting.service.impl; package com.cmeeting.service.impl;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cmeeting.mapper.primary.MeetingInfoMapper; import com.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.pojo.MeetingInfo; import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.service.MeetingInfoService; import com.cmeeting.service.MeetingInfoService;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.vo.MeetingInfoVO;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service @Service
public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, MeetingInfo> implements MeetingInfoService { public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, MeetingInfo> implements MeetingInfoService {
@Resource
private MeetingInfoMapper mapper;
@Resource
private MinioUtils minioUtils;
@Override
public IPage<MeetingInfo> getPage(MeetingInfoVO vo) {
LambdaQueryWrapper<MeetingInfo> queryWrapper = new LambdaQueryWrapper<>();
IPage<MeetingInfo> resultPage = mapper.selectPage(new Page<>(vo.getCurrent(), vo.getSize()), queryWrapper);
return resultPage;
}
@Override
public boolean updateRecordXml(MeetingInfoVO vo) {
//前端的表单json转xml,xml格式的纪要内容便于生成会议纪要文件
String recordJson = vo.getRecordJson();
String recordXml = convertJSONToXml(recordJson);
String key = vo.getMeetingId() + "-recordXmlPath-" + IdUtil.fastSimpleUUID() + ".xml";
minioUtils.upload(key,recordXml.getBytes(StandardCharsets.UTF_8));
vo.setRecordXml(key);
LambdaUpdateWrapper<MeetingInfo> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(MeetingInfo::getId,vo.getId());
updateWrapper.set(MeetingInfo::getUpdateTime,LocalDateTime.now());
updateWrapper.set(MeetingInfo::getRecordXml,key);
return update(null,updateWrapper);
}
private String convertJSONToXml(String json) {
try {
// 解析 JSON 字符串
ObjectMapper jsonMapper = new ObjectMapper();
List<Map<String, String>> list = jsonMapper.readValue(json, List.class);
// 创建 XML 根节点
Map<String, Object> root = new HashMap<>();
// 处理每个 JSON 项
for (Map<String, String> item : list) {
String keyValue = item.get("key");
String keyName = item.get("keyName");
String value = item.get("value");
// 创建子节点
Map<String, Object> subNode = new HashMap<>();
subNode.put("label", keyName);
subNode.put("", value);
// 添加到根节点
root.put(keyValue, subNode);
}
// 转换为 XML
XmlMapper xmlMapper = new XmlMapper();
return xmlMapper.writeValueAsString(root);
} catch (IOException e) {
log.error("JSON 转 XML 失败: " + e.getMessage());
throw new RuntimeException("JSON 转 XML 失败", e);
}
}
} }
package com.cmeeting.service.impl; package com.cmeeting.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
...@@ -7,10 +10,15 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; ...@@ -7,10 +10,15 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper; import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper;
import com.cmeeting.pojo.MeetingRecordTemplate; import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.service.MeetingRecordTemplateService; import com.cmeeting.service.MeetingRecordTemplateService;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.vo.RecordTemplateVO; import com.cmeeting.vo.RecordTemplateVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.InputStream;
import java.util.Date;
import java.util.List; import java.util.List;
@Service @Service
...@@ -18,12 +26,58 @@ public class MeetingRecordTemplateServiceImpl extends ServiceImpl<MeetingRecordT ...@@ -18,12 +26,58 @@ public class MeetingRecordTemplateServiceImpl extends ServiceImpl<MeetingRecordT
@Resource @Resource
private MeetingRecordTemplateMapper mapper; private MeetingRecordTemplateMapper mapper;
@Autowired
private MinioUtils minioUtils;
@Override @Override
public IPage<MeetingRecordTemplate> getPage(RecordTemplateVO vo) { public IPage<MeetingRecordTemplate> getPage(RecordTemplateVO vo) {
LambdaQueryWrapper<MeetingRecordTemplate> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<MeetingRecordTemplate> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(MeetingRecordTemplate::getIsDel,Boolean.FALSE); queryWrapper.eq(MeetingRecordTemplate::getIsDel,Boolean.FALSE);
queryWrapper.eq(vo.getType() != null,MeetingRecordTemplate::getType,vo.getType());
queryWrapper.like(vo.getName() != null,MeetingRecordTemplate::getName,vo.getName());
queryWrapper.like(vo.getMeetingType() != null,MeetingRecordTemplate::getMeetingType,vo.getMeetingType());
IPage<MeetingRecordTemplate> resultPage = mapper.selectPage(new Page<>(vo.getCurrent(), vo.getSize()), queryWrapper); IPage<MeetingRecordTemplate> resultPage = mapper.selectPage(new Page<>(vo.getCurrent(), vo.getSize()), queryWrapper);
return resultPage; return resultPage;
} }
@Override
public IPage<MeetingRecordTemplate> getPersonalPage(RecordTemplateVO vo) {
LambdaQueryWrapper<MeetingRecordTemplate> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(MeetingRecordTemplate::getIsDel,Boolean.FALSE);
queryWrapper.eq(vo.getType() != null,MeetingRecordTemplate::getType,vo.getType());
queryWrapper.like(vo.getName() != null,MeetingRecordTemplate::getName,vo.getName());
queryWrapper.like(vo.getMeetingType() != null,MeetingRecordTemplate::getMeetingType,vo.getMeetingType());
IPage<MeetingRecordTemplate> resultPage = mapper.selectPage(new Page<>(vo.getCurrent(), vo.getSize()), queryWrapper);
return resultPage;
}
/**
* 上传附件模板到minio
* @param file
* @param id
* @return
*/
@Override
public String templateUpload(MultipartFile file, Integer id) {
MeetingRecordTemplate detail = getById(id);
String separator = "/";
String point = ".";
// 获取文件名
String originalFilename = file.getOriginalFilename();
// 获取文件后缀
String originalSuffix = FileUtil.getSuffix(originalFilename);
// 生成文件名
String nowDate = DateUtil.format(new Date(), "yyyy-MM-dd");
String uuid = IdUtil.fastSimpleUUID();
String originalFilePath = separator + nowDate + separator + uuid + point + originalSuffix;
// 获取文件流
try(InputStream is = file.getInputStream()){
minioUtils.upload(originalFilePath,is);
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}
detail.setTemplate(originalFilePath);
updateById(detail);
return originalFilePath;
}
} }
\ No newline at end of file
...@@ -14,7 +14,10 @@ import com.cmeeting.pojo.TencentMeetingUser; ...@@ -14,7 +14,10 @@ import com.cmeeting.pojo.TencentMeetingUser;
import com.cmeeting.pojo.UserId; import com.cmeeting.pojo.UserId;
import com.cmeeting.service.TecentMeetingService; import com.cmeeting.service.TecentMeetingService;
import com.cmeeting.util.RedisUtils; import com.cmeeting.util.RedisUtils;
import com.cmeeting.util.SignatureUtil;
import com.cmeeting.vo.CorpRecordsVO;
import com.cmeeting.vo.TencentMeetingVO; import com.cmeeting.vo.TencentMeetingVO;
import com.google.gson.*;
import com.tencentcloudapi.wemeet.Client; import com.tencentcloudapi.wemeet.Client;
import com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder; import com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder;
import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator; import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator;
...@@ -29,6 +32,11 @@ import com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200Re ...@@ -29,6 +32,11 @@ import com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200Re
import com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200ResponseUsersInner; import com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200ResponseUsersInner;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.Headers; import okhttp3.Headers;
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;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -73,7 +81,6 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te ...@@ -73,7 +81,6 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
private static final String HMAC_ALGORITHM = "HmacSHA256"; private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
private final Object lock = new Object();
@Value(value = "${tencent.appId}") @Value(value = "${tencent.appId}")
private String tencentAppId; private String tencentAppId;
...@@ -111,6 +118,223 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te ...@@ -111,6 +118,223 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
} }
// @Override
// public List<TencentMeetingVO.RecordFile> getMeetingFiles(List<UserDTO> accessUserIds) {
// Client client = new Client.Builder()
// .withAppId(tencentAppId).withSdkId(tencentSdkId)
// .withSecret(tencentSecretId,tencentSecretKey)
// .build();
//
// List<TencentMeetingVO.RecordFile> recordFileUrlList = new ArrayList<>();
// List<MeetingInfo> meetingSaveList = new ArrayList<>();
// // 查询近两天的会议录制列表
// try {
// ZonedDateTime now = ZonedDateTime.now();
// String startTime = String.valueOf(now.minusDays(2).toEpochSecond());
// String endTime = String.valueOf(now.toEpochSecond());
// AtomicInteger currentPage = new AtomicInteger(1);
// Long totalPage = 1L;
//
// //目前已存储的会议id
// List<String> meetingIds = meetingInfoMapper.getAllMeetingIds();
// while (currentPage.intValue() <= totalPage.intValue()){
// RecordsApi.ApiV1RecordsGetRequest request =
// new RecordsApi.ApiV1RecordsGetRequest.Builder()
// .operatorId(tencentAdminUserId)
// .operatorIdType("1")
// .startTime(startTime)
// .endTime(endTime)
// .pageSize("20")
// .page(String.valueOf(currentPage.getAndIncrement()))
// .mediaSetType("0")
// .build();
// RecordsApi.ApiV1RecordsGetResponse response =
// client.records().v1RecordsGet(request, new JWTAuthenticator.Builder()
// .nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
// .timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
// V1RecordsGet200Response data = response.getData();
// //设置总页数
// totalPage = data.getTotalPage();
// if (data != null && data.getRecordMeetings() != null && !data.getRecordMeetings().isEmpty()) {
// List<V1RecordsGet200ResponseRecordMeetingsInner> meetings = data.getRecordMeetings();
//
// //录制状态:
// //1:录制中
// //2:转码中
// //3:转码完成
// if(meetings.stream().allMatch(item->item.getState() != 3)){
// return null;
// }
// for (V1RecordsGet200ResponseRecordMeetingsInner meeting : meetings) {
// //会议没结束,跳过
// if(meeting.getState() != 3){
// processLogService.log(meeting.getMeetingId(),null,"会议未结束,跳过生成");
// continue;
// }
//
//
// log.info("【会议检索】转录文件的meetingId->{},recordFileId->{}",meeting.getMeetingId(),meeting.getMeetingRecordId());
//
// //查询会议详情
// String meetingId = meeting.getMeetingId();
// String subMeetingId = null;
// MeetingsApi.ApiV1MeetingsMeetingIdGetRequest meetingRequest =
// new MeetingsApi.ApiV1MeetingsMeetingIdGetRequest.Builder(meetingId)
// .operatorId(tencentAdminUserId)
// .operatorIdType("1")
// .instanceid("0")
// .build();
// try {
// 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();
// //尝试获取会议详情
// if(meetingInfoList != null && meetingInfoList.size() > 0){
// V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner meetingInfo = meetingInfoList.get(0);
// //会议类型
// //0:一次性会议
// //1:周期性会议
// Long meetingType = meetingInfo.getMeetingType();
// if(meetingType.intValue() == 1){
// //如果是周期会议,获取子会议的ID,用于查询参会人员
// List<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInnerSubMeetingsInner> subMeetings = meetingInfo.getSubMeetings();
// //如果主持人突然取消了后续的所有周期会议,subMeetings列表会返回null
// if(!CollectionUtils.isEmpty(subMeetings)){
// LocalDate meetingStartDate = Instant.ofEpochMilli(meeting.getMediaStartTime()).atZone(ZoneId.systemDefault()).toLocalDate();
// Optional<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInnerSubMeetingsInner> subMeeting = subMeetings.stream().filter(item ->
// Instant.ofEpochSecond(Long.valueOf(item.getStartTime())).atZone(ZoneId.systemDefault()).toLocalDate().equals(meetingStartDate))
// .findFirst();
// if(!subMeeting.isPresent()){
// log.error("周期会议"+meetingId+"未知子会议ID");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"周期会议"+meetingId+"未知子会议ID");
// continue;
// }
// subMeetingId = subMeeting.get().getSubMeetingId();
// }else{
// subMeetingId = meetingInfo.getCurrentSubMeetingId();
// log.info("周期会议"+meetingId+"的子会议列表为空,获取当前子会议号");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"周期会议"+meetingId+"的子会议列表为空,获取当前子会议号");
// }
// }
// //如果数据库中已有相同会议id的记录,跳过同步
// if(!meetingIds.contains(meetingId)){
// log.info("【会议检索】新的会议meetingId->{},开始持久化",meeting.getMeetingId());
// List<V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner> recordFiles = meeting.getRecordFiles();
// //按转录文件时间升序,便于后续的内容拼接
// List<String> recordFileIdList = recordFiles.stream().sorted(Comparator.comparingLong(V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner::getRecordStartTime))
// .map(V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner::getRecordFileId).collect(Collectors.toList());
// TencentMeetingVO.RecordFile recordFileItem = TencentMeetingVO.RecordFile.builder()
// .recordFileIdList(recordFileIdList).meetingId(meetingId).subMeetingId(subMeetingId).build();
//
// // 获取参会成员明细
// 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();
// List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = participantsData.getParticipants();
// Optional<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> host = participants.stream().filter(item -> hostRoleList.contains(item.getUserRole())).findFirst();
// String email;
// String hostId;
// String hostName;
// //判断主持人是否存在,如果主持人未参会,是查不到主持人的
// //如果主持人未参会,使用会议详情中的创建人作为主持人
// if(host.isPresent()) {
// hostId = host.get().getUserid();
// hostName = new String(Base64.getDecoder().decode(host.get().getUserName()));
// }else{
// //未找到主持人,读取会议详情中的创建人作为主持人
// List<V1MeetingsGet200ResponseMeetingInfoListInnerCurrentCoHostsInner> currentHosts = meetingInfo.getCurrentHosts();
// if(CollectionUtils.isEmpty(currentHosts)){
// log.error("未找到主持人,默认没有生成纪要权限");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认没有生成纪要权限");
// continue;
// }
// hostId = currentHosts.get(0).getUserid();
// hostName = tecentMeetingMapper.getUsernameByUserId(hostId);
// log.info("主持人会中缺席,默认会议创建人为主持人");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认会议创建人为主持人");
// }
// //判断是否有权限生成纪要
// 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+"允许生成纪要");
// UserDTO userDTO = accessUserIds.stream().filter(item -> item.getTid().equals(hostId)).findFirst().get();
// email = userDTO.getEmail();
//
// //会议基本信息保存
// MeetingInfo meetingItem = MeetingInfo.builder().meetingId(meetingId).meetingCode(meetingInfo.getMeetingCode())
// .subject(meetingInfo.getSubject())
// .startTime(LocalDateTime.ofInstant(Instant.ofEpochSecond(Long.valueOf(meetingInfo.getStartTime())), ZoneId.systemDefault()))
// .endTime(LocalDateTime.ofInstant(Instant.ofEpochSecond(Long.valueOf(meetingInfo.getEndTime())), ZoneId.systemDefault()))
// .isGenerated(Boolean.FALSE).emailPushAccess(Boolean.TRUE).isPushed(Boolean.FALSE).syncTime(LocalDateTime.now())
// .subMeetingId(subMeetingId).generateRetry(Boolean.FALSE).pushRetry(Boolean.FALSE)
// .host(hostName)
// .hostUid(hostId)
// .participantUsers(participants.stream()
// .map(item->new String(Base64.getDecoder().decode(item.getUserName()))).distinct().collect(Collectors.joining("、")))
// .recordFileId(recordFileIdList.stream().collect(Collectors.joining(",")))
// .email(email)
// .build();
// recordFileUrlList.add(recordFileItem);
// meetingSaveList.add(meetingItem);
// }
// }
// } catch (ClientException e) {
// throw new RuntimeException(e);
// } catch (ServiceException e) {
// throw new RuntimeException(e);
// }
// }
// }
// }
// if(meetingSaveList.size() > 0){
// Map<String, List<MeetingInfo>> meetingSaveMap = meetingSaveList.stream().collect(Collectors.groupingBy(
// item -> item.getMeetingId() + "_" +
// (item.getSubMeetingId() != null ? item.getSubMeetingId() : "null")));
// List<MeetingInfo> finalSaveList = new ArrayList<>();
// for (Map.Entry<String, List<MeetingInfo>> entry : meetingSaveMap.entrySet()) {
// MeetingInfo meetingInfo = entry.getValue().get(0);
// meetingInfo.setRecordFileId(entry.getValue().stream().map(MeetingInfo::getRecordFileId).collect(Collectors.joining(",")));
// finalSaveList.add(meetingInfo);
// }
// meetingInfoMapper.batchInsert(finalSaveList);
// }
// } catch (Exception e) {
// log.error(e.getMessage());
// StringWriter sw = new StringWriter();
// PrintWriter pw = new PrintWriter(sw);
// e.printStackTrace(pw);
// processLogService.log(null,null,sw.toString());
// throw new RuntimeException(e.getMessage());
// }
// return recordFileUrlList;
// }
@Override @Override
public List<TencentMeetingVO.RecordFile> getMeetingFiles(List<UserDTO> accessUserIds) { public List<TencentMeetingVO.RecordFile> getMeetingFiles(List<UserDTO> accessUserIds) {
Client client = new Client.Builder() Client client = new Client.Builder()
...@@ -123,33 +347,21 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te ...@@ -123,33 +347,21 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
// 查询近两天的会议录制列表 // 查询近两天的会议录制列表
try { try {
ZonedDateTime now = ZonedDateTime.now(); ZonedDateTime now = ZonedDateTime.now();
String startTime = String.valueOf(now.minusDays(2).toEpochSecond()); long startTime = now.minusDays(2).toEpochSecond();
String endTime = String.valueOf(now.toEpochSecond()); long endTime = now.toEpochSecond();
AtomicInteger currentPage = new AtomicInteger(1); AtomicInteger currentPage = new AtomicInteger(1);
Long totalPage = 1L; Integer totalPage = 1;
//目前已存储的会议id //目前已存储的会议id
List<String> meetingIds = meetingInfoMapper.getAllMeetingIds(); List<String> meetingIds = meetingInfoMapper.getAllMeetingIds();
while (currentPage.intValue() <= totalPage.intValue()){ List<TencentMeetingUser> meetingUsers = tecentMeetingMapper.getAlluser();
RecordsApi.ApiV1RecordsGetRequest request = Map<String, String> meetingMap = meetingUsers.stream().collect(Collectors.toMap(TencentMeetingUser::getUserId, TencentMeetingUser::getUserName));
new RecordsApi.ApiV1RecordsGetRequest.Builder() while (currentPage.intValue() <= totalPage){
.operatorId(tencentAdminUserId) CorpRecordsVO data = fetchMeetingRecords(tencentAdminUserId, 1, startTime, endTime, currentPage.getAndIncrement(), 20);
.operatorIdType("1")
.startTime(startTime)
.endTime(endTime)
.pageSize("20")
.page(String.valueOf(currentPage.getAndIncrement()))
.mediaSetType("0")
.build();
RecordsApi.ApiV1RecordsGetResponse response =
client.records().v1RecordsGet(request, new JWTAuthenticator.Builder()
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
.timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
V1RecordsGet200Response data = response.getData();
//设置总页数 //设置总页数
totalPage = data.getTotalPage(); totalPage = data.getTotalPage();
if (data != null && data.getRecordMeetings() != null && !data.getRecordMeetings().isEmpty()) { if (data != null && data.getRecordMeetings() != null && !data.getRecordMeetings().isEmpty()) {
List<V1RecordsGet200ResponseRecordMeetingsInner> meetings = data.getRecordMeetings(); List<CorpRecordsVO.RecordMeeting> meetings = data.getRecordMeetings();
//录制状态: //录制状态:
//1:录制中 //1:录制中
...@@ -158,10 +370,10 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te ...@@ -158,10 +370,10 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
if(meetings.stream().allMatch(item->item.getState() != 3)){ if(meetings.stream().allMatch(item->item.getState() != 3)){
return null; return null;
} }
for (V1RecordsGet200ResponseRecordMeetingsInner meeting : meetings) { for (CorpRecordsVO.RecordMeeting meeting : meetings) {
//会议没结束,跳过 //会议没结束,跳过
if(meeting.getState() != 3){ if(meeting.getState() != 3){
processLogService.log(meeting.getMeetingId(),null,"会议未结束,跳过生成"); // processLogService.log(meeting.getMeetingId(),null,"会议未结束,跳过生成");
continue; continue;
} }
...@@ -194,81 +406,91 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te ...@@ -194,81 +406,91 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
if(meetingType.intValue() == 1){ if(meetingType.intValue() == 1){
//如果是周期会议,获取子会议的ID,用于查询参会人员 //如果是周期会议,获取子会议的ID,用于查询参会人员
List<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInnerSubMeetingsInner> subMeetings = meetingInfo.getSubMeetings(); List<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInnerSubMeetingsInner> subMeetings = meetingInfo.getSubMeetings();
LocalDate meetingStartDate = Instant.ofEpochMilli(meeting.getMediaStartTime()).atZone(ZoneId.systemDefault()).toLocalDate(); //如果主持人突然取消了后续的所有周期会议,subMeetings列表会返回null
Optional<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInnerSubMeetingsInner> subMeeting = subMeetings.stream().filter(item -> if(!CollectionUtils.isEmpty(subMeetings)){
Instant.ofEpochSecond(Long.valueOf(item.getStartTime())).atZone(ZoneId.systemDefault()).toLocalDate().equals(meetingStartDate)) LocalDate meetingStartDate = Instant.ofEpochMilli(meeting.getMediaStartTime()).atZone(ZoneId.systemDefault()).toLocalDate();
.findFirst(); Optional<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInnerSubMeetingsInner> subMeeting = subMeetings.stream().filter(item ->
if(!subMeeting.isPresent()){ Instant.ofEpochSecond(Long.valueOf(item.getStartTime())).atZone(ZoneId.systemDefault()).toLocalDate().equals(meetingStartDate))
System.out.println("周期会议"+meetingId+"未知子会议ID"); .findFirst();
processLogService.log(meeting.getMeetingId(),subMeetingId,"周期会议"+meetingId+"未知子会议ID"); if(!subMeeting.isPresent()){
continue; log.error("周期会议"+meetingId+"未知子会议ID");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"周期会议"+meetingId+"未知子会议ID");
continue;
}
subMeetingId = subMeeting.get().getSubMeetingId();
}else{
subMeetingId = meetingInfo.getCurrentSubMeetingId();
log.info("周期会议"+meetingId+"的子会议列表为空,获取当前子会议号");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"周期会议"+meetingId+"的子会议列表为空,获取当前子会议号");
} }
subMeetingId = subMeeting.get().getSubMeetingId();
} }
//如果数据库中已有相同会议id的记录,跳过同步 //如果数据库中已有相同会议id的记录,跳过同步
if(!meetingIds.contains(meetingId)){ if(!meetingIds.contains(meetingId)){
log.info("【会议检索】新的会议meetingId->{},开始持久化",meeting.getMeetingId()); log.info("【会议检索】新的会议meetingId->{},开始持久化",meeting.getMeetingId());
List<V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner> recordFiles = meeting.getRecordFiles(); List<CorpRecordsVO.RecordFile> recordFiles = meeting.getRecordFiles();
//按转录文件时间升序,便于后续的内容拼接 //按转录文件时间升序,便于后续的内容拼接
List<String> recordFileIdList = recordFiles.stream().sorted(Comparator.comparingLong(V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner::getRecordStartTime)) List<String> recordFileIdList = recordFiles.stream().sorted(Comparator.comparingLong(CorpRecordsVO.RecordFile::getRecordStartTime))
.map(V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner::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();
// 获取参会成员明细
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();
List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = participantsData.getParticipants();
Optional<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> host = participants.stream().filter(item -> hostRoleList.contains(item.getUserRole())).findFirst();
String email;
String hostId; String hostId;
String hostName; String hostName;
//判断主持人是否存在,如果主持人未参会,是查不到主持人的 //优先使用会议列表中已有的主持人字段
//如果主持人未参会,使用会议详情中的创建人作为主持人 if(StringUtils.isNotEmpty(meeting.getHostUserId())){
if(host.isPresent()) { hostId = meeting.getHostUserId();
hostId = host.get().getUserid(); hostName = meetingMap.containsKey(hostId) ? meetingMap.get(hostId) :null;
hostName = new String(Base64.getDecoder().decode(host.get().getUserName())); }else if(!CollectionUtils.isEmpty(meetingInfo.getCurrentHosts())){
//判断主持人是否存在,如果主持人未参会,是查不到主持人的
//如果主持人未参会,使用会议详情中的创建人作为主持人
hostId = meetingInfo.getCurrentHosts().get(0).getUserid();
hostName = meetingMap.containsKey(hostId) ? meetingMap.get(hostId) :null;
log.info("主持人会中缺席,默认会议创建人为主持人");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认会议创建人为主持人");
}else{ }else{
//未找到主持人,读取会议详情中的创建人作为主持人 // 获取参会成员明细
List<V1MeetingsGet200ResponseMeetingInfoListInnerCurrentCoHostsInner> currentHosts = meetingInfo.getCurrentHosts(); MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest participantsRequest =
if(CollectionUtils.isEmpty(currentHosts)){ 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();
List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = participantsData.getParticipants();
Optional<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> host = participants.stream().filter(item -> hostRoleList.contains(item.getUserRole())).findFirst();
if(host.isPresent()) {
hostId = host.get().getUserid();
hostName = new String(Base64.getDecoder().decode(host.get().getUserName()));
}else{
log.error("未找到主持人,默认没有生成纪要权限"); log.error("未找到主持人,默认没有生成纪要权限");
processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认没有生成纪要权限"); // processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认没有生成纪要权限");
continue; continue;
} }
hostId = currentHosts.get(0).getUserid();
hostName = tecentMeetingMapper.getUsernameByUserId(hostId);
log.info("主持人会中缺席,默认会议创建人为主持人");
processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认会议创建人为主持人");
} }
String email;
//判断是否有权限生成纪要 //判断是否有权限生成纪要
boolean generateAccess = accessUserIds.stream().anyMatch(item -> item.getTid().equals(hostId)); boolean generateAccess = accessUserIds.stream().anyMatch(item -> item.getTid().equals(hostId));
if(!generateAccess){ if(!generateAccess){
log.error("【权限校验】主持人{}没有生成纪要权限,跳过生成",hostId); log.error("【权限校验】主持人{}没有生成纪要权限,跳过生成",hostId);
processLogService.log(meeting.getMeetingId(),subMeetingId,"【权限校验】主持人"+hostId+"没有生成纪要权限,跳过生成"); // processLogService.log(meeting.getMeetingId(),subMeetingId,"【权限校验】主持人"+hostId+"没有生成纪要权限,跳过生成");
continue; continue;
} }
log.info("【权限校验】主持人{}允许生成纪要",hostId); log.info("【权限校验】主持人{}允许生成纪要",hostId);
processLogService.log(meeting.getMeetingId(),subMeetingId,"【权限校验】主持人"+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();
email = userDTO.getEmail(); email = userDTO.getEmail();
...@@ -281,8 +503,8 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te ...@@ -281,8 +503,8 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
.subMeetingId(subMeetingId).generateRetry(Boolean.FALSE).pushRetry(Boolean.FALSE) .subMeetingId(subMeetingId).generateRetry(Boolean.FALSE).pushRetry(Boolean.FALSE)
.host(hostName) .host(hostName)
.hostUid(hostId) .hostUid(hostId)
.participantUsers(participants.stream() // .participantUsers(participants.stream()
.map(item->new String(Base64.getDecoder().decode(item.getUserName()))).distinct().collect(Collectors.joining("、"))) // .map(item->new String(Base64.getDecoder().decode(item.getUserName()))).distinct().collect(Collectors.joining("、")))
.recordFileId(recordFileIdList.stream().collect(Collectors.joining(","))) .recordFileId(recordFileIdList.stream().collect(Collectors.joining(",")))
.email(email) .email(email)
.build(); .build();
...@@ -290,10 +512,9 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te ...@@ -290,10 +512,9 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
meetingSaveList.add(meetingItem); meetingSaveList.add(meetingItem);
} }
} }
} catch (ClientException e) { } catch (Exception e) {
throw new RuntimeException(e); e.printStackTrace();
} catch (ServiceException e) { continue;
throw new RuntimeException(e);
} }
} }
} }
...@@ -395,73 +616,50 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te ...@@ -395,73 +616,50 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
} }
/** /**
* 生成请求腾会接口的请求头 * 拉取账户级的会议记录列表
* @param httpMethod http请求方法 GET/POST/PUT等 * @param operatorId
* @param requestUri 请求uri,eg:/v1/meetings * @param operatorIdType
* @return * @param startTime
*/ * @param endTime
private Headers generateHeaders(String httpMethod, String requestUri){ * @param page
//生成签名的时间戳 * @param pageSize
String headerTimestamp = String.valueOf(LocalDateTime.now().atZone(ZoneId.systemDefault()).toEpochSecond()); * @throws Exception
//生成随机正整数(暂定八位
String randomNumber = String.valueOf((int)(Math.random() * 99999999) + 1);
//生成签名
String signature = sign(tencentSecretId, tencentSecretKey, httpMethod, randomNumber, headerTimestamp, requestUri, "{}");
Headers headers = new Headers.Builder()
.add("Content-Type", "application/json")
.add("X-TC-Key", tencentSecretId)
.add("X-TC-Timestamp", headerTimestamp)
.add("X-TC-Nonce", randomNumber)
.add("X-TC-Signature",signature)
.add("AppId",tencentAppId)
.add("SdkId",tencentSdkId)
.add("X-TC-Registered","1")
.build();
return headers;
}
/**
* 生成签名,开发版本oracle jdk 1.8.0_221
*
* @param secretId 邮件下发的secret_id
* @param secretKey 邮件下发的secret_key
* @param httpMethod http请求方法 GET/POST/PUT等
* @param headerNonce X-TC-Nonce请求头,随机数
* @param headerTimestamp X-TC-Timestamp请求头,当前时间的秒级时间戳
* @param requestUri 请求uri,eg:/v1/meetings
* @param requestBody 请求体,没有的设为空串
* @return 签名,需要设置在请求头X-TC-Signature中
* @throws NoSuchAlgorithmException e
* @throws InvalidKeyException e
*/ */
private static String sign(String secretId, String secretKey, String httpMethod, String headerNonce, String headerTimestamp, String requestUri, String requestBody) { public CorpRecordsVO fetchMeetingRecords(String operatorId, int operatorIdType, long startTime, long endTime, int page, int pageSize)
String tobeSig = throws Exception {
httpMethod + "\nX-TC-Key=" + secretId + "&X-TC-Nonce=" + headerNonce + "&X-TC-Timestamp=" + headerTimestamp + "\n" + requestUri + "\n" + requestBody; String uri = String.format(
Mac mac; "/v1/corp/records?start_time=%d&end_time=%d&page=%d&page_size=%d&operator_id=%s&operator_id_type=%d",
try { startTime, endTime, page, pageSize, operatorId, operatorIdType
mac = Mac.getInstance(HMAC_ALGORITHM); );
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), mac.getAlgorithm());
mac.init(secretKeySpec); String httpMethod = "GET";
} catch (Exception e) { String nonce = String.valueOf(new Random().nextInt(100000));
log.error("获取腾会签名错误:"+e.getMessage()); String timestamp = String.valueOf(Instant.now().getEpochSecond());
throw new RuntimeException("请求失败");
} // 3. 生成签名
byte[] hash = mac.doFinal(tobeSig.getBytes(StandardCharsets.UTF_8)); String signature = SignatureUtil.generateSignature(tencentSecretId, tencentSecretKey, httpMethod, nonce, timestamp, uri, "");
String hexHash = bytesToHex(hash);
String signature = new String(Base64.getEncoder().encode(hexHash.getBytes(StandardCharsets.UTF_8))); // 4. 发送请求
return signature; try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
} HttpGet request = new HttpGet("https://api.meeting.qq.com" + uri);
request.setHeader("X-TC-Key", tencentSecretId);
private static String bytesToHex(byte[] bytes) { request.setHeader("X-TC-Timestamp", timestamp);
request.setHeader("X-TC-Nonce", nonce);
char[] buf = new char[bytes.length * 2]; request.setHeader("X-TC-Signature", signature);
int index = 0; request.setHeader("AppId", tencentAppId);
for (byte b : bytes) { request.setHeader("SdkId", tencentSdkId);
buf[index++] = HEX_CHAR[b >>> 4 & 0xf];
buf[index++] = HEX_CHAR[b & 0xf]; // 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();
// 将 JsonObject 转换为实体类
CorpRecordsVO corpRecords = gson.fromJson(jsonResponse, CorpRecordsVO.class);
return corpRecords;
} }
return new String(buf);
} }
/** /**
......
...@@ -4,10 +4,8 @@ import io.minio.MinioClient; ...@@ -4,10 +4,8 @@ import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream; import java.io.*;
import java.io.FileInputStream; import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.io.InputStream;
@Component @Component
public class MinioUtils { public class MinioUtils {
...@@ -33,6 +31,20 @@ public class MinioUtils { ...@@ -33,6 +31,20 @@ public class MinioUtils {
} }
} }
public String getFileText(String fileName) throws IOException {
InputStream is = getFile(fileName);
StringBuilder textBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
textBuilder.append(line);
textBuilder.append(System.lineSeparator());
}
}
return textBuilder.toString();
}
public InputStream getFile(String fileName){ public InputStream getFile(String fileName){
try{ try{
getMinioClient(); getMinioClient();
...@@ -57,11 +69,6 @@ public class MinioUtils { ...@@ -57,11 +69,6 @@ public class MinioUtils {
public void upload(String path,byte[] bytes) { public void upload(String path,byte[] bytes) {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) { try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
// 自动管理流的生命周期,无需手动close
int data;
while ((data = inputStream.read()) != -1) {
System.out.print((char) data);
}
upload(path,inputStream); upload(path,inputStream);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
......
package com.cmeeting.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class SignatureUtil {
static String HMAC_ALGORITHM = "HmacSHA256";
static char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
static String bytesToHex(byte[] bytes) {
char[] buf = new char[bytes.length * 2];
int index = 0;
for (byte b : bytes) {
buf[index++] = HEX_CHAR[b >>> 4 & 0xf];
buf[index++] = HEX_CHAR[b & 0xf];
}
return new String(buf);
}
public static String generateSignature(String secretId, String secretKey, String httpMethod, String headerNonce, String headerTimestamp, String requestUri, String requestBody)
throws NoSuchAlgorithmException, InvalidKeyException {
String tobeSig =
httpMethod + "\nX-TC-Key=" + secretId + "&X-TC-Nonce=" + headerNonce + "&X-TC-Timestamp=" + headerTimestamp + "\n" + requestUri + "\n" + requestBody;
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), mac.getAlgorithm());
mac.init(secretKeySpec);
byte[] hash = mac.doFinal(tobeSig.getBytes(StandardCharsets.UTF_8));
String hexHash = bytesToHex(hash);
return new String(Base64.getEncoder().encode(hexHash.getBytes(StandardCharsets.UTF_8)));
}
}
package com.cmeeting.vo;
import lombok.Data;
import java.util.List;
/**
* 会议纪要模板展示层
*/
/**
* 会议纪要模板展示层
*/
@Data
public class CorpRecordsVO {
private Integer totalCount;
private Integer totalPage;
private Integer currentPage;
private Integer currentSize;
private List<RecordMeeting> recordMeetings;
@Data
public class RecordMeeting {
private String meetingRecordId;
private String meetingId;
private String meetingCode;
private String userid;
private String hostUserId;
private Integer mediaStartTime;
private String subject;
private Integer state;
private Integer recordType;
private List<RecordFile> recordFiles;
}
@Data
public class RecordFile {
private String recordFileId;
private Integer recordStartTime;
private Integer recordEndTime;
private Integer recordSize;
private Integer sharingState;
private String sharingUrl;
private Boolean requiredSameCorp;
private Boolean requiredParticipant;
private Integer sharingExpire;
private Boolean allowDownload;
private String downloadAddress;
private String viewAddress;
}
}
\ No newline at end of file
package com.cmeeting.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
;
import java.util.List;
/**
* 会议纪要模板展示层
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EmailPush {
private String toEmail;
private String subject;
private String meetingId;
/**
* 附件
*/
private List<Attachment> attachments;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class Attachment {
private String name;
private byte[] bytes;
}
}
\ No newline at end of file
package com.cmeeting.vo;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 会议纪要模板展示层
*/
@Data
public class MeetingInfoVO {
/**
* 当前页
*/
private Integer current;
/**
* 每页最大数据行
*/
private Integer size;
/**
* 主键id
*/
private Integer id;
/**
* 会议主题
*/
private String subject;
/**
* 会议ID(字符串类型)
*/
private String meetingId;
/**
* 子会议ID
*/
private String subMeetingId;
/**
* 会议号码
*/
private String meetingCode;
/**
* 主持人
*/
private String host;
/**
* 主持人uid
*/
private String hostUid;
/**
* 参会人员名单
*/
private String participantUsers;
/**
* 会议开始时间(时间戳)
*/
private LocalDateTime startTime;
/**
* 会议结束时间(时间戳)
*/
private LocalDateTime endTime;
/**
* 是否生成会议纪要完成
*/
private Boolean isGenerated;
/**
* 推送邮件许可 为false不推送
*/
private Boolean emailPushAccess;
/**
* 是否推送邮件完成
*/
private Boolean isPushed;
/**
* 会议纪要重新生成标识
*/
private Boolean generateRetry;
/**
* 邮件推送重试标识
*/
private Boolean pushRetry;
/**
* 同步时间
*/
private LocalDateTime syncTime;
/**
* 转录文本
*/
private String recordContent;
/**
* 纪要xml
*/
private String recordXml;
/**
* 每个会议对应的转录文件id,多个用逗号分隔,按参会前后升序
*/
private String recordFileId;
/**
* 邮箱
*/
private String email;
/**
* 同步时间
*/
private LocalDateTime updateTime;
/**
* 模板ID
*/
private Integer templateId;
/**
* 会议纪要json
*/
private String recordJson;
}
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论