提交 2089d7b6 作者: duanxincheng

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

父级 4ce8c62e
......@@ -15,9 +15,9 @@ public class ThreadPoolConfig {
public ThreadPoolTaskExecutor fileProcessExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数 (CPU密集型任务建议核心数+1)
executor.setCorePoolSize(4); // 固定核心线程数,避免动态获取CPU核心数
executor.setCorePoolSize(2); // 固定核心线程数,避免动态获取CPU核心数
// 最大线程数
executor.setMaxPoolSize(4);
executor.setMaxPoolSize(2);
// 队列容量
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;
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.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.service.MeetingRecordTemplateService;
import com.cmeeting.util.R;
import com.cmeeting.vo.RecordTemplateVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
......@@ -45,9 +44,37 @@ public class RecordTemplateController {
return R.ok(save);
}
/**
* 纪要模板列表,通过type区分为系统模板还是自定义模板
* @param vo
* @return
*/
@PostMapping("/list")
public R list(@RequestBody RecordTemplateVO vo) {
IPage<MeetingRecordTemplate> page = recordTemplateService.getPage(vo);
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;
import com.cmeeting.pojo.TencentMeetingUser;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/tencent")
@RequestMapping("/tencentMeeting")
public class TencentMeetingController {
@Autowired
private TecentMeetingService tecentMeetingService;
......@@ -22,9 +15,4 @@ public class TencentMeetingController {
public void addUsers() {
tecentMeetingService.doUsers();
}
// @GetMapping("/getMeetingFiles")
// public void getMeetingFiles(){
// tecentMeetingService.getMeetingFiles();
// }
}
......@@ -5,6 +5,7 @@ import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.cmeeting.log.service.ProcessLogService;
import com.cmeeting.vo.EmailPush;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.models.*;
import com.microsoft.graph.models.Message;
......@@ -31,6 +32,7 @@ import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
......@@ -159,13 +161,10 @@ public class EmailSender {
/**
* 发送邮件,带附件
* @param toEmail
* @param subject
* @param attachmentPath
* @param meetingId
* @param emailPushBuilder
* @return
*/
public boolean sendEmailWithAttachment(String toEmail, String subject, String attachmentPath, String meetingId) {
public boolean sendEmailWithAttachment(EmailPush emailPushBuilder) {
log.info("sendEmailWithAttachment start...");
// 创建会话
......@@ -175,6 +174,10 @@ public class EmailSender {
AtomicInteger retryCount = new AtomicInteger(0);
boolean isSent = false;
// String toEmail = emailPushBuilder.getToEmail();
String toEmail = "duanxincheng@chatbot.cn";
String subject = emailPushBuilder.getSubject();
String meetingId = emailPushBuilder.getMeetingId();
if(StringUtils.isEmpty(toEmail)){
log.error("收件邮箱为空,推送失败");
processLogService.log(meetingId,null,"收件邮箱为空,推送失败");
......@@ -204,7 +207,7 @@ public class EmailSender {
GraphServiceClient<Request> graphClient = GraphServiceClient.builder().authenticationProvider(tokenCredAuthProvider).buildClient();
com.microsoft.graph.models.Message message = new Message();
message.subject = subject;
message.subject = subject + "会议纪要";
ItemBody body = new ItemBody();
body.contentType = BodyType.TEXT;
body.content = "您好:\n\n 附件为您本次会议的会议纪要,烦请下载查看";
......@@ -218,13 +221,15 @@ public class EmailSender {
message.toRecipients = toRecipientsList;
//构建附件
LinkedList<Attachment> attachmentsList = new LinkedList<>();
byte[] recordXmlData = Files.readAllBytes(Paths.get(attachmentPath));
for (EmailPush.Attachment attachment : emailPushBuilder.getAttachments()) {
FileAttachment attachments = new FileAttachment();
attachments.name = attachmentPath.substring(attachmentPath.lastIndexOf("/") + 1);
attachments.name = attachment.getName() + ".docx";
attachments.oDataType = "#microsoft.graph.fileAttachment";
attachments.contentType="application/msword";
attachments.contentBytes = recordXmlData;
attachments.contentBytes = attachment.getBytes();
attachmentsList.add(attachments);
}
AttachmentCollectionResponse attachmentCollectionResponse = new AttachmentCollectionResponse();
attachmentCollectionResponse.value = attachmentsList;
......
......@@ -134,7 +134,7 @@ public class CmeetingJob {
}
// @Scheduled(fixedRate = 5 * 60 * 1000,initialDelay = 2 * 60 * 1000)
@Scheduled(fixedRate = 5 * 60 * 1000)
@Scheduled(fixedRate = 10 * 60 * 1000)
public void execute() {
// 定义时间格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
......
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 com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
......@@ -12,48 +7,27 @@ import com.cmeeting.email.EmailSender;
import com.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper;
import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.vo.EmailPush;
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 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.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
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.apache.commons.io.IOUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Data
@NoArgsConstructor
......@@ -105,9 +79,9 @@ public class EmailPushTask {
dataModel.putAll(participantsMap);
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")) : "腾讯会议纪要";
targetFileName = meetingName + "_" + nowTime;
targetFileName = meetingName + "_data_network";
try (InputStream inputStream = resource.getInputStream()) {
XWPFTemplate template = XWPFTemplate.compile(inputStream).render(dataModel);
template.writeAndClose(new FileOutputStream(path + targetFileName + ".docx"));
......@@ -118,8 +92,16 @@ public class EmailPushTask {
}
//邮件推送
// isSuccess = emailSender.sendEmailWithAttachment("duanxincheng@chatbot.cn",meetingName,savePath + targetFileName + ".docx",meetingId);
isSuccess = emailSender.sendEmailWithAttachment(meetingInfo.getEmail(),meetingName,savePath + targetFileName + ".docx",meetingId);
List<EmailPush.Attachment> attachments = new ArrayList<>();
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) {
// 异常处理
int currentRetryCount = retryCount.getAndIncrement();
......
......@@ -5,21 +5,17 @@ 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.date.DateUtil;
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.conditions.update.UpdateWrapper;
import com.cmeeting.dto.UserDTO;
import com.cmeeting.email.EmailSender;
import com.cmeeting.log.service.ProcessLogService;
import com.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper;
import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.pojo.UserId;
import com.cmeeting.service.MeetingInfoService;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.vo.EmailPush;
import com.deepoove.poi.XWPFTemplate;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
......@@ -39,6 +35,7 @@ import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
......@@ -53,10 +50,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.text.MessageFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
......@@ -103,6 +97,21 @@ public class FileProcessTask {
.eq(MeetingInfo::getMeetingId,meetingId)
.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();
for (String recordFileId : recordFileIdList) {
......@@ -142,8 +151,6 @@ public class FileProcessTask {
// 2. 将二进制文件转换为文本
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())){
recordTextBuffer.append("\n\n");
recordTextBuffer.append(recordTextContent);
......@@ -165,11 +172,35 @@ public class FileProcessTask {
// 3. 处理文件 (调用Claude API等)
String choiceTemplateType = choiceTemplateType(meetingInfo.getSubject(),recordTextBuffer.toString());
log.info("choiceTemplateType->{}",choiceTemplateType);
String processedResult = processWithClaude(recordTextBuffer.toString());
//获取系统模板
MeetingRecordTemplate meetingRecordTemplate = meetingRecordTemplateMapper.selectById(1);
String processedResult = processWithClaude(recordTextBuffer.toString(),meetingRecordTemplate.getPrompt());
// 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;
} catch (Exception e) {
StringWriter sw = new StringWriter();
......@@ -265,16 +296,20 @@ public class FileProcessTask {
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 apiAddr = llmApiAddr + "/llm/sse-invoke";
String model = "anthropic.claude-3-5-sonnet-20240620-v1:0";
int maxTokens = 5000;
//获取系统模板
MeetingRecordTemplate meetingRecordTemplate = meetingRecordTemplateMapper.selectById(1);
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);
chatMessage = new ChatMessage(ChatMessageRole.ASSISTANT.value(), "好的请提供会议记录");
messages.add(chatMessage);
......@@ -287,24 +322,32 @@ public class FileProcessTask {
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扩展名;
String targetFileName;
/**
* 保存会议纪要相关的文件
* @param content 大模型返回的不规则xml
* @param recordData 转录文本
* @param meetingInfo 会议对象
* @param meetingRecordTemplate 模板信息
*/
private String saveResult(String content, byte[] recordData, MeetingInfo meetingInfo,MeetingRecordTemplate meetingRecordTemplate) {
String meetingName;
String xml = extractXmlFromMarkdown(content);
// 将xml格式的内容转换为特殊json
String json = convertXmlToJSON(xml);
String recordContentPath = meetingId + "-recordContent-" + IdUtil.fastSimpleUUID() + "txt";
String recordXmlPath = meetingId + "-recordXmlPath-" + IdUtil.fastSimpleUUID() + "xml";
//转录文件临时存储路径
String recordContentPath = meetingId + "-recordContent-" + IdUtil.fastSimpleUUID() + ".txt";
//生成的xml临时存储路径
String recordXmlPath = meetingId + "-recordXmlPath-" + IdUtil.fastSimpleUUID() + ".xml";
//填充后的会议纪要名称
String meetingMinutesFileName;
//填充后的会议纪要word文件临时路径
String meetingMinutesPath;
try {
//去除内容中除了xml内容以外其他的信息,格式化xml
String xml = extractXmlFromMarkdown(content);
minioUtils.upload(recordContentPath,recordData);
//写入json文件,这里的json文件用于用户可编辑
Files.write(Paths.get(targetPath), json.getBytes());
log.info("json文件已保存到: {}", targetPath);
minioUtils.upload(recordXmlPath,xml.getBytes(StandardCharsets.UTF_8));
//将xml格式的内容转换为map,用于填充模板
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<>();
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd");
......@@ -313,20 +356,16 @@ public class FileProcessTask {
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_net_template.docx");
meetingName = dataModel.get("meeting_name") != null ? String.valueOf(dataModel.get("meeting_name")) : "腾讯会议纪要";
targetFileName = meetingName + "_" + nowTime;
XWPFTemplate template;
try (InputStream inputStream = resource.getInputStream()) {
try (InputStream inputStream = minioUtils.getFile(meetingRecordTemplate.getTemplate())) {
template = XWPFTemplate.compile(inputStream).render(dataModel);
} catch (IOException e) {
throw new RuntimeException(e);
}
template.writeAndClose(new FileOutputStream(path + targetFileName + ".docx"));
byte[] recordXmlData = Files.readAllBytes(Paths.get(path + targetFileName + ".docx"));
minioUtils.upload(recordXmlPath,recordXmlData);
meetingMinutesPath = savePath + meetingMinutesFileName + ".docx";
template.writeAndClose(new FileOutputStream(meetingMinutesPath));
processLogService.log(meetingId,subMeetingId,"填充会议纪要成功");
} catch (Exception e) {
log.error("填充会议纪要失败: {}", e.getMessage(), e);
......@@ -337,14 +376,29 @@ public class FileProcessTask {
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;
log.info("开始邮件推送------");
if(meetingInfo.getEmailPushAccess()){
log.info("用户允许邮件推送,准备推送邮件至{}------",meetingInfo.getEmail());
if(true){
log.info("用户允许邮件推送,准备推送邮件至{}------", emailPushBuilder.getToEmail());
//邮件推送
isPushed = emailSender.sendEmailWithAttachment("duanxincheng@chatbot.cn",meetingName,path + targetFileName + ".docx",meetingId);
isPushed = emailSender.sendEmailWithAttachment(emailPushBuilder);
try {
// isPushed = emailSender.sendEmailWithAttachment(meetingInfo.getEmail(),meetingName,path + targetFileName + ".docx",meetingId);
// isPushed = emailSender.sendEmailWithAttachment(emailPushBuilder);
} catch (Exception e) {
log.error("邮件推送失败: {}", e.getMessage(), e);
StringWriter sw = new StringWriter();
......@@ -354,22 +408,16 @@ public class FileProcessTask {
throw new RuntimeException(e);
}
if(isPushed)
processLogService.log(meetingId,subMeetingId,"用户允许邮件推送,推送邮件至"+meetingInfo.getEmail());
// emailSender.sendEmailWithAttachment("xuwentao@chatbot.cn",meetingName,path + targetFileName + ".docx",meetingId);
// emailSender.sendEmailWithAttachment("jiaqi.cai@cimc.com",meetingName,path + targetFileName + ".docx",meetingId);
processLogService.log(meetingId,subMeetingId,"用户允许邮件推送,推送邮件至"+ emailPushBuilder.getToEmail());
}else{
log.info("用户关闭了邮件推送,推送终止------");
processLogService.log(meetingId,subMeetingId,"用户关闭了邮件推送,推送终止");
isPushed = Boolean.FALSE;
}
meetingInfoMapper.update(meetingInfo,
meetingInfoMapper.update(null,
new LambdaUpdateWrapper<MeetingInfo>()
.eq(MeetingInfo::getMeetingId,meetingId)
.eq(subMeetingId != null,MeetingInfo::getSubMeetingId,subMeetingId)
.set(MeetingInfo::getRecordContent,recordContentPath)
.set(MeetingInfo::getRecordXml,recordXmlPath)
.set(MeetingInfo::getIsGenerated,Boolean.TRUE)
.set(MeetingInfo::getIsPushed,isPushed)
);
}
......@@ -460,10 +508,13 @@ public class FileProcessTask {
* @param markdown
* @return
*/
private static String extractXmlFromMarkdown(String markdown) {
private String extractXmlFromMarkdown(String markdown) {
StringBuffer sb = null;
try {
int start = markdown.indexOf("<");
if(start == -1){
processLogService.log(meetingId,subMeetingId,"markdown转xml失败,未输出正确的xml格式,markdown内容:"+markdown);
}
int end = markdown.lastIndexOf(">") + 1;
sb = new StringBuffer();
sb.append("<root>");
......
......@@ -114,4 +114,13 @@ public class MeetingInfo implements Serializable {
* 邮箱
*/
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 {
* 删除标识
*/
private Boolean isDel;
/**
* 模板文件
*/
private String template;
}
\ No newline at end of file
package com.cmeeting.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.vo.MeetingInfoVO;
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;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.vo.RecordTemplateVO;
import org.springframework.web.multipart.MultipartFile;
public interface MeetingRecordTemplateService extends IService<MeetingRecordTemplate> {
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;
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.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.pojo.MeetingInfo;
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 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
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;
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.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
......@@ -7,10 +10,15 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper;
import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.service.MeetingRecordTemplateService;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.vo.RecordTemplateVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
@Service
......@@ -18,12 +26,58 @@ public class MeetingRecordTemplateServiceImpl extends ServiceImpl<MeetingRecordT
@Resource
private MeetingRecordTemplateMapper mapper;
@Autowired
private MinioUtils minioUtils;
@Override
public IPage<MeetingRecordTemplate> getPage(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;
}
@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;
import com.cmeeting.pojo.UserId;
import com.cmeeting.service.TecentMeetingService;
import com.cmeeting.util.RedisUtils;
import com.cmeeting.util.SignatureUtil;
import com.cmeeting.vo.CorpRecordsVO;
import com.cmeeting.vo.TencentMeetingVO;
import com.google.gson.*;
import com.tencentcloudapi.wemeet.Client;
import com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder;
import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator;
......@@ -29,6 +32,11 @@ import com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200Re
import com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200ResponseUsersInner;
import lombok.extern.slf4j.Slf4j;
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.Value;
import org.springframework.stereotype.Service;
......@@ -73,7 +81,6 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
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 final Object lock = new Object();
@Value(value = "${tencent.appId}")
private String tencentAppId;
......@@ -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
public List<TencentMeetingVO.RecordFile> getMeetingFiles(List<UserDTO> accessUserIds) {
Client client = new Client.Builder()
......@@ -123,33 +347,21 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
// 查询近两天的会议录制列表
try {
ZonedDateTime now = ZonedDateTime.now();
String startTime = String.valueOf(now.minusDays(2).toEpochSecond());
String endTime = String.valueOf(now.toEpochSecond());
long startTime = now.minusDays(2).toEpochSecond();
long endTime = now.toEpochSecond();
AtomicInteger currentPage = new AtomicInteger(1);
Long totalPage = 1L;
Integer totalPage = 1;
//目前已存储的会议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();
List<TencentMeetingUser> meetingUsers = tecentMeetingMapper.getAlluser();
Map<String, String> meetingMap = meetingUsers.stream().collect(Collectors.toMap(TencentMeetingUser::getUserId, TencentMeetingUser::getUserName));
while (currentPage.intValue() <= totalPage){
CorpRecordsVO data = fetchMeetingRecords(tencentAdminUserId, 1, startTime, endTime, currentPage.getAndIncrement(), 20);
//设置总页数
totalPage = data.getTotalPage();
if (data != null && data.getRecordMeetings() != null && !data.getRecordMeetings().isEmpty()) {
List<V1RecordsGet200ResponseRecordMeetingsInner> meetings = data.getRecordMeetings();
List<CorpRecordsVO.RecordMeeting> meetings = data.getRecordMeetings();
//录制状态:
//1:录制中
......@@ -158,10 +370,10 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
if(meetings.stream().allMatch(item->item.getState() != 3)){
return null;
}
for (V1RecordsGet200ResponseRecordMeetingsInner meeting : meetings) {
for (CorpRecordsVO.RecordMeeting meeting : meetings) {
//会议没结束,跳过
if(meeting.getState() != 3){
processLogService.log(meeting.getMeetingId(),null,"会议未结束,跳过生成");
// processLogService.log(meeting.getMeetingId(),null,"会议未结束,跳过生成");
continue;
}
......@@ -194,27 +406,48 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
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()){
System.out.println("周期会议"+meetingId+"未知子会议ID");
processLogService.log(meeting.getMeetingId(),subMeetingId,"周期会议"+meetingId+"未知子会议ID");
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<CorpRecordsVO.RecordFile> recordFiles = meeting.getRecordFiles();
//按转录文件时间升序,便于后续的内容拼接
List<String> recordFileIdList = recordFiles.stream().sorted(Comparator.comparingLong(V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner::getRecordStartTime))
.map(V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner::getRecordFileId).collect(Collectors.toList());
List<String> recordFileIdList = recordFiles.stream().sorted(Comparator.comparingLong(CorpRecordsVO.RecordFile::getRecordStartTime))
.map(CorpRecordsVO.RecordFile::getRecordFileId).collect(Collectors.toList());
TencentMeetingVO.RecordFile recordFileItem = TencentMeetingVO.RecordFile.builder()
.recordFileIdList(recordFileIdList).meetingId(meetingId).subMeetingId(subMeetingId).build();
String hostId;
String hostName;
//优先使用会议列表中已有的主持人字段
if(StringUtils.isNotEmpty(meeting.getHostUserId())){
hostId = meeting.getHostUserId();
hostName = meetingMap.containsKey(hostId) ? meetingMap.get(hostId) :null;
}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{
// 获取参会成员明细
MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest participantsRequest =
new MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest
......@@ -239,36 +472,25 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
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,"未找到主持人,默认没有生成纪要权限");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认没有生成纪要权限");
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));
if(!generateAccess){
log.error("【权限校验】主持人{}没有生成纪要权限,跳过生成",hostId);
processLogService.log(meeting.getMeetingId(),subMeetingId,"【权限校验】主持人"+hostId+"没有生成纪要权限,跳过生成");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"【权限校验】主持人"+hostId+"没有生成纪要权限,跳过生成");
continue;
}
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();
email = userDTO.getEmail();
......@@ -281,8 +503,8 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
.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("、")))
// .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();
......@@ -290,10 +512,9 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
meetingSaveList.add(meetingItem);
}
}
} catch (ClientException e) {
throw new RuntimeException(e);
} catch (ServiceException e) {
throw new RuntimeException(e);
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
......@@ -395,73 +616,50 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
}
/**
* 生成请求腾会接口的请求头
* @param httpMethod http请求方法 GET/POST/PUT等
* @param requestUri 请求uri,eg:/v1/meetings
* @return
*/
private Headers generateHeaders(String httpMethod, String requestUri){
//生成签名的时间戳
String headerTimestamp = String.valueOf(LocalDateTime.now().atZone(ZoneId.systemDefault()).toEpochSecond());
//生成随机正整数(暂定八位
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
* 拉取账户级的会议记录列表
* @param operatorId
* @param operatorIdType
* @param startTime
* @param endTime
* @param page
* @param pageSize
* @throws Exception
*/
private static String sign(String secretId, String secretKey, String httpMethod, String headerNonce, String headerTimestamp, String requestUri, String requestBody) {
String tobeSig =
httpMethod + "\nX-TC-Key=" + secretId + "&X-TC-Nonce=" + headerNonce + "&X-TC-Timestamp=" + headerTimestamp + "\n" + requestUri + "\n" + requestBody;
Mac mac;
try {
mac = Mac.getInstance(HMAC_ALGORITHM);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), mac.getAlgorithm());
mac.init(secretKeySpec);
} catch (Exception e) {
log.error("获取腾会签名错误:"+e.getMessage());
throw new RuntimeException("请求失败");
}
byte[] hash = mac.doFinal(tobeSig.getBytes(StandardCharsets.UTF_8));
String hexHash = bytesToHex(hash);
String signature = new String(Base64.getEncoder().encode(hexHash.getBytes(StandardCharsets.UTF_8)));
return signature;
}
private 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];
public CorpRecordsVO fetchMeetingRecords(String operatorId, int operatorIdType, long startTime, long endTime, int page, int pageSize)
throws Exception {
String uri = String.format(
"/v1/corp/records?start_time=%d&end_time=%d&page=%d&page_size=%d&operator_id=%s&operator_id_type=%d",
startTime, endTime, page, pageSize, operatorId, operatorIdType
);
String httpMethod = "GET";
String nonce = String.valueOf(new Random().nextInt(100000));
String timestamp = String.valueOf(Instant.now().getEpochSecond());
// 3. 生成签名
String signature = SignatureUtil.generateSignature(tencentSecretId, tencentSecretKey, httpMethod, nonce, timestamp, uri, "");
// 4. 发送请求
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet("https://api.meeting.qq.com" + uri);
request.setHeader("X-TC-Key", tencentSecretId);
request.setHeader("X-TC-Timestamp", timestamp);
request.setHeader("X-TC-Nonce", nonce);
request.setHeader("X-TC-Signature", signature);
request.setHeader("AppId", tencentAppId);
request.setHeader("SdkId", tencentSdkId);
// 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;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.nio.charset.StandardCharsets;
@Component
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){
try{
getMinioClient();
......@@ -57,11 +69,6 @@ public class MinioUtils {
public void upload(String path,byte[] bytes) {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
// 自动管理流的生命周期,无需手动close
int data;
while ((data = inputStream.read()) != -1) {
System.out.print((char) data);
}
upload(path,inputStream);
} catch (Exception 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论