提交 eb05d17a 作者: duanxincheng

Merge branch 'main' into staging0612-template_permission_control

# Conflicts:
#	src/main/java/com/cmeeting/job/CmeetingJob.java
#	src/main/java/com/cmeeting/job/FileProcessTask.java
......@@ -227,8 +227,8 @@ public class UserServiceImpl implements UserService {
private R loginByAD(ApplicationUserVO.Login login) {
// AD验证
// boolean auth = iLdapService.authenticate(login.getUsername().trim(), login.getPassword().trim());
if (true) {
boolean auth = iLdapService.authenticate(login.getUsername().trim(), login.getPassword().trim());
if (auth) {
SysUserSync sysUserSync = sysUserSysMapper.selectOne(new LambdaQueryWrapper<SysUserSync>()
.eq(SysUserSync::getTenantId, permissionTenantId)
.eq(SysUserSync::getUserId, login.getUsername().trim()));
......
......@@ -5,7 +5,9 @@ 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.mapper.primary.MeetingRecordTemplateMapper;
import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.service.MeetingInfoService;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.util.R;
......@@ -16,6 +18,7 @@ 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.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
......@@ -50,6 +53,8 @@ public class MeetingInfoController {
private MinioUtils minioUtils;
@Resource
private EmailSender emailSender;
@Resource
private MeetingRecordTemplateMapper meetingRecordTemplateMapper;
@PostMapping("/updateRecordXml")
public R updateRecordXml(@RequestBody MeetingInfoVO vo) {
......@@ -57,6 +62,17 @@ public class MeetingInfoController {
return R.ok(save);
}
/**
* 重新生成
* @param vo
* @return
*/
@PostMapping("/regenerateXml")
public R regenerateXml(@RequestBody MeetingInfoVO vo) {
boolean isSuccess = meetingInfoService.regenerateXml(vo);
return R.ok(isSuccess);
}
@PostMapping("/list")
public R list(@RequestBody MeetingInfoVO vo) {
IPage<MeetingInfo> page = meetingInfoService.getPage(vo);
......@@ -76,10 +92,21 @@ public class MeetingInfoController {
String json = convertXmlToJSON(xml);
vo.setRecordJson(json);
}
} catch (Exception e) {
e.printStackTrace();
// try(InputStream is = minioUtils.getFile(recordXml)){
// byte[] bytes = IOUtils.toByteArray(is);
// String json = convertXmlToJSON(new String(bytes));
// vo.setRecordJson(json);
// }catch (Exception ex){
// e.printStackTrace();
// }
}
try {
if(StringUtils.isNotEmpty(recordContent)){
vo.setRecordContent(minioUtils.getFileText(recordContent));
}
} catch (IOException e) {
} catch (Exception e) {
e.printStackTrace();
}
return R.ok(vo);
......@@ -98,7 +125,6 @@ public class MeetingInfoController {
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");
......@@ -140,13 +166,13 @@ public class MeetingInfoController {
participantsMap.put("meeting_host",meetingInfo.getHost());
dataModel.putAll(participantsMap);
ClassPathResource resource = new ClassPathResource("template/data_network.docx");
MeetingRecordTemplate meetingRecordTemplate = meetingRecordTemplateMapper.selectById(2);
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()) {
try (InputStream is = minioUtils.getFile(meetingRecordTemplate.getTemplate());OutputStream os = response.getOutputStream()) {
template = XWPFTemplate.compile(is).render(dataModel);
template.write(os);
} catch (IOException e) {
......@@ -179,10 +205,10 @@ public class MeetingInfoController {
participantsMap.put("meeting_host",meetingInfo.getHost());
dataModel.putAll(participantsMap);
ClassPathResource resource = new ClassPathResource("template/data_network.docx");
MeetingRecordTemplate meetingRecordTemplate = meetingRecordTemplateMapper.selectById(2);
XWPFTemplate template;
byte[] meetingMinutesBytes;
try (InputStream is = resource.getInputStream();ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
try (InputStream is = minioUtils.getFile(meetingRecordTemplate.getTemplate());ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
template = XWPFTemplate.compile(is).render(dataModel);
template.write(baos);
meetingMinutesBytes = baos.toByteArray();
......
......@@ -174,8 +174,8 @@ public class EmailSender {
AtomicInteger retryCount = new AtomicInteger(0);
boolean isSent = false;
// String toEmail = emailPushBuilder.getToEmail();
String toEmail = "duanxincheng@chatbot.cn";
String toEmail = emailPushBuilder.getToEmail();
// String toEmail = "duanxincheng@chatbot.cn";
String subject = emailPushBuilder.getSubject();
String meetingId = emailPushBuilder.getMeetingId();
if(StringUtils.isEmpty(toEmail)){
......
......@@ -91,8 +91,8 @@ public class CmeetingJob {
log.info("-------关联企微腾会人员定时任务结束--------");
}
// @Scheduled(fixedRate = 10 * 60 * 1000,initialDelay = 2 * 60 * 1000)
// @Scheduled(fixedRate = 10 * 60 * 1000)
// @Scheduled(fixedRate = 20 * 60 * 1000,initialDelay = 2 * 60 * 1000)
@Scheduled(fixedRate = 20 * 60 * 1000)
public void execute() {
// 定义时间格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
......@@ -136,7 +136,7 @@ public class CmeetingJob {
/**
* 定时扫描早于一小时之前的,所有未重试过的会议,重新生成纪要
*/
@Scheduled(fixedRate = 30 * 60 * 1000,initialDelay = 10 * 60 * 1000)
// @Scheduled(fixedRate = 30 * 60 * 1000,initialDelay = 10 * 60 * 1000)
// @Scheduled(fixedRate = 30 * 60 * 1000)
public void meetingMinutesRetry() {
try {
......@@ -180,7 +180,8 @@ public class CmeetingJob {
/**
* 定时扫描早于一小时之前的,所有邮件推送未重试过的会议,重新推送邮件
*/
@Scheduled(fixedRate = 30 * 60 * 1000,initialDelay = 15 * 60 * 1000)
// @Scheduled(fixedRate = 30 * 60 * 1000,initialDelay = 15 * 60 * 1000)
// @Scheduled(fixedRate = 30 * 60 * 1000)
public void emailPushRetry() {
try {
log.info("-------邮件推送重试定时任务开始-------");
......@@ -191,6 +192,8 @@ public class CmeetingJob {
meetingInfoService.list(new LambdaQueryWrapper<MeetingInfo>()
.eq(MeetingInfo::getIsGenerated,Boolean.TRUE)
.eq(MeetingInfo::getEmailPushAccess,Boolean.TRUE)
.eq(MeetingInfo::getIsPushed,Boolean.FALSE)
.eq(MeetingInfo::getPushRetry,Boolean.FALSE)
.le(MeetingInfo::getSyncTime,LocalDateTime.now().minusHours(1))
);
......
......@@ -52,10 +52,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.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
......@@ -104,46 +101,72 @@ public class FileProcessTask {
MeetingInfo meetingInfo = meetingInfoMapper.selectOne(new LambdaQueryWrapper<MeetingInfo>()
.eq(MeetingInfo::getMeetingId,meetingId)
.eq(subMeetingId != null, MeetingInfo::getSubMeetingId, subMeetingId));
String meetingDate = meetingInfo.getStartTime().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
//下面再查一遍会议信息的意义是,为了获取会议中的子会议Id,如果是周期会议,需要保存下来(重要)
MeetingsApi.ApiV1MeetingsMeetingIdGetRequest meetingRequest =
new MeetingsApi.ApiV1MeetingsMeetingIdGetRequest.Builder(meetingId)
.operatorId(tencentAdminUserId)
.operatorIdType("1")
.instanceid("0")
.build();
MeetingsApi.ApiV1MeetingsMeetingIdGetResponse meetingResponse =
client.meetings().v1MeetingsMeetingIdGet(meetingRequest, new JWTAuthenticator.Builder()
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
.timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
V1MeetingsMeetingIdGet200Response meetingResponseData = meetingResponse.getData();
List<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner> meetingInfoList = meetingResponseData.getMeetingInfoList();
V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner meetingItem = meetingInfoList.get(0);
//会议类型
//0:一次性会议
//1:周期性会议
Long meetingType = meetingItem.getMeetingType();
if(meetingType.intValue() == 1){
//如果是周期会议,获取子会议的ID,用于查询参会人员
List<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInnerSubMeetingsInner> subMeetings = meetingItem.getSubMeetings();
//如果主持人突然取消了后续的所有周期会议,subMeetings列表会返回null
if(!CollectionUtils.isEmpty(subMeetings)){
LocalDate meetingStartDate = meetingInfo.getStartTime().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");
retryCount++;
continue;
}
subMeetingId = subMeeting.get().getSubMeetingId();
}else{
subMeetingId = meetingItem.getCurrentSubMeetingId();
log.info("周期会议"+meetingId+"的子会议列表为空,获取当前子会议号");
// processLogService.log(meeting.getMeetingId(),subMeetingId,"周期会议"+meetingId+"的子会议列表为空,获取当前子会议号");
}
}
// MeetingsApi.ApiV1MeetingsMeetingIdGetRequest meetingRequest =
// new MeetingsApi.ApiV1MeetingsMeetingIdGetRequest.Builder(meetingId)
// .operatorId(tencentAdminUserId)
// .operatorIdType("1")
// .instanceid("0")
// .build();
// MeetingsApi.ApiV1MeetingsMeetingIdGetResponse meetingResponse =
// client.meetings().v1MeetingsMeetingIdGet(meetingRequest, new JWTAuthenticator.Builder()
// .nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
// .timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
// V1MeetingsMeetingIdGet200Response meetingResponseData = meetingResponse.getData();
// List<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner> meetingInfoList = meetingResponseData.getMeetingInfoList();
// V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner meetingItem = meetingInfoList.get(0);
// //会议类型
// //0:一次性会议
// //1:周期性会议
// Long meetingType = meetingItem.getMeetingType();
// if(meetingType.intValue() == 1){
// //如果是周期会议,获取子会议的ID,用于查询参会人员
// List<V1MeetingsMeetingIdGet200ResponseMeetingInfoListInnerSubMeetingsInner> subMeetings = meetingItem.getSubMeetings();
// //如果主持人突然取消了后续的所有周期会议,subMeetings列表会返回null
// if(!CollectionUtils.isEmpty(subMeetings)){
// LocalDate meetingStartDate = meetingInfo.getStartTime().toLocalDate();
//// long meetingDayStartTimeStamp = meetingStartDate.atStartOfDay().atZone(ZoneId.systemDefault()).toEpochSecond();
//// long meetingDayEndTimeStamp = meetingStartDate.plusDays(1).atStartOfDay().atZone(ZoneId.systemDefault()).toEpochSecond();
// 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");
// retryCount++;
// continue;
//// processLogService.log(meeting.getMeetingId(),subMeetingId,"周期会议"+meetingId+"未知子会议ID");
// //会议详情查不到历史的子会议id了,需要到用户已结束会议列表查一下
//// MeetingsApi.ApiV1HistoryMeetingsUseridGetRequest completedMeetingRequest =
//// new MeetingsApi.ApiV1HistoryMeetingsUseridGetRequest.Builder(tencentAdminUserId)
//// .pageSize("20")
//// .page("1")
//// .meetingCode(meetingInfo.getMeetingCode())
//// .startTime(String.valueOf(meetingDayStartTimeStamp))
//// .endTime(String.valueOf(meetingDayEndTimeStamp))
//// .build();
//// MeetingsApi.ApiV1HistoryMeetingsUseridGetResponse completedMeetingResponse =
//// client.meetings().v1HistoryMeetingsUseridGet(completedMeetingRequest, new JWTAuthenticator.Builder()
//// .nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
//// .timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
////
//// V1HistoryMeetingsUseridGet200Response completedMeetingData = completedMeetingResponse.getData();
//// List<V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner> completedMeetingList = completedMeetingData.getMeetingInfoList();
//// if(CollectionUtils.isEmpty(completedMeetingList)){
//// log.error("账户级周期会议"+meetingId+"未知子会议ID");
//// retryCount++;
//// continue;
//// }
//// subMeetingId = completedMeetingList.get(0).getSubMeetingId();
// }else{
// subMeetingId = subMeeting.get().getSubMeetingId();
// }
// }else{
// subMeetingId = meetingItem.getCurrentSubMeetingId();
// log.info("周期会议"+meetingId+"的子会议列表为空,获取当前子会议号");
//// processLogService.log(meeting.getMeetingId(),subMeetingId,"周期会议"+meetingId+"的子会议列表为空,获取当前子会议号");
// }
// }
// 获取参会成员明细
MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetRequest participantsRequest =
......@@ -220,34 +243,37 @@ public class FileProcessTask {
// 3. 处理文件 (调用Claude API等)
String choiceTemplateType = choiceTemplateType(meetingInfo.getSubject(),recordTextBuffer.toString());
log.info("choiceTemplateType->{}",choiceTemplateType);
//获取系统模板
MeetingRecordTemplate meetingRecordTemplate = meetingRecordTemplateMapper.selectById(1);
String processedResult = processWithClaude(recordTextBuffer.toString(),meetingDate,meetingRecordTemplate.getPrompt());
List<EmailPush.Attachment> attachments = new ArrayList<>();
for (UserDTO.TemplateAuthorizedUserDTO templateAuthorizedUserDTO : authorizedUsers) {
//如果模板授权人员不为空,开始生成纪要
if(!CollectionUtils.isEmpty(templateAuthorizedUserDTO.getUserIdList())){
MeetingRecordTemplate meetingRecordTemplate = templateAuthorizedUserDTO.getRecordTemplate();
//模板权限过滤
List<String> userIdList = templateAuthorizedUserDTO.getUserIdList();
if(!userIdList.contains(meetingInfo.getHostUid())){
log.info("用户{}没有纪要模板{}的生成权限,跳过该模板的生成流程",meetingInfo.getHostUid(),meetingRecordTemplate.getName());
processLogService.log(meetingId,subMeetingId,"用户"+meetingInfo.getHostUid()+"没有纪要模板"+meetingRecordTemplate.getName()+"的生成权限,跳过该模板的生成流程");
continue;
}
//根据模板生成纪要
String processedResult = processWithClaude(recordTextBuffer.toString(),meetingRecordTemplate.getPrompt());
// 4. 保存结果
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()+"会议纪要_"+meetingRecordTemplate.getName().replace("模板","")).bytes(meetingMinutesBytes).build();
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);
}finally {
FileUtil.del(dataNetworkMinutesPath);
}
}
}
String processedResult2 = processWithClaude(recordTextBuffer.toString(),meetingDate,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);
}finally {
FileUtil.del(groupOfficeMinutesPath);
}
EmailPush emailPushBuilder = EmailPush.builder().toEmail(meetingInfo.getEmail()).meetingId(meetingId).attachments(attachments).subject(meetingInfo.getSubject()).build();
emailPush(emailPushBuilder);
isSuccess = true;
......@@ -351,18 +377,19 @@ public class FileProcessTask {
* @param prompt 提示词
* @return
*/
private String processWithClaude(String textContent, String prompt) {
private String processWithClaude(String textContent, String meetingDate, 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;
List<Message> messages = new ArrayList<>();
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), prompt);
messages.add(chatMessage);
chatMessage = new ChatMessage(ChatMessageRole.ASSISTANT.value(), "好的请提供会议记录");
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), prompt.replaceAll("\\{transcript\\}",textContent).replaceAll("\\{meetingDate\\}",meetingDate));
messages.add(chatMessage);
chatMessage = new ChatMessage(ChatMessageRole.USER.value(), textContent);
// chatMessage = new ChatMessage(ChatMessageRole.ASSISTANT.value(), "好的请提供会议记录");
// messages.add(chatMessage);
// chatMessage = new ChatMessage(ChatMessageRole.USER.value(), textContent);
messages.add(chatMessage);
......
......@@ -2,6 +2,7 @@ package com.cmeeting.mapper.primary;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.vo.TencentMeetingVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
......@@ -12,6 +13,6 @@ import java.util.List;
public interface MeetingInfoMapper extends BaseMapper<MeetingInfo> {
void batchInsert(@Param("meetingSaveList")List<MeetingInfo> meetingSaveList);
List<String> getAllMeetingIds();
List<TencentMeetingVO.SimpleMeetingInfo> getAllMeetingIds();
}
\ No newline at end of file
......@@ -9,4 +9,6 @@ public interface MeetingInfoService extends IService<MeetingInfo> {
IPage<MeetingInfo> getPage(MeetingInfoVO vo);
boolean updateRecordXml(MeetingInfoVO vo);
boolean regenerateXml(MeetingInfoVO vo);
}
package com.cmeeting.service.impl;
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.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
......@@ -9,30 +15,76 @@ 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.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.service.MeetingInfoService;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.vo.EmailPush;
import com.cmeeting.vo.MeetingInfoVO;
import com.deepoove.poi.XWPFTemplate;
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.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.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Service
@Slf4j
public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, MeetingInfo> implements MeetingInfoService {
@Resource
private MeetingInfoMapper mapper;
@Resource
private MinioUtils minioUtils;
@Value(value = "${tencent.appId}")
private String tencentAppId;
@Value(value = "${tencent.sdkId}")
private String tencentSdkId;
@Value(value = "${tencent.secretId}")
private String tencentSecretId;
@Value(value = "${tencent.secretKey}")
private String tencentSecretKey;
@Value(value = "${tencent.admin.userId}")
private String tencentAdminUserId;
@Value(value = "${llm.api-addr}")
private String llmApiAddr;
@Value(value = "${tencent.base-save-path}")
private String baseSavePath;
@Resource
private ProcessLogService processLogService;
@Resource
private MeetingRecordTemplateMapper meetingRecordTemplateMapper;
@Resource
private MeetingInfoMapper meetingInfoMapper;
@Override
public IPage<MeetingInfo> getPage(MeetingInfoVO vo) {
......@@ -57,6 +109,254 @@ public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, Meeti
return update(null,updateWrapper);
}
@Override
public boolean regenerateXml(MeetingInfoVO vo) {
Client client = new Client.Builder()
.withAppId(tencentAppId).withSdkId(tencentSdkId)
.withSecret(tencentSecretId,tencentSecretKey)
.build();
MeetingInfo meetingInfo = mapper.selectById(vo.getId());
//已保存的会议信息
String meetingId = meetingInfo.getMeetingId();
String subMeetingId = meetingInfo.getSubMeetingId();
String recordFileIdArray = meetingInfo.getRecordFileId();
try{
if(StringUtils.isEmpty(recordFileIdArray)){
return false;
}
String meetingDate = meetingInfo.getStartTime().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
// 获取参会成员明细
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();
List<String> recordFileIdList = Arrays.asList(meetingInfo.getRecordFileId().split(","));
for (String recordFileId : recordFileIdList) {
//查询录制转写详情
RecordsApi.ApiV1AddressesRecordFileIdGetRequest addressRequest =
new RecordsApi.ApiV1AddressesRecordFileIdGetRequest.Builder(recordFileId)
.operatorId(tencentAdminUserId)
.operatorIdType("1")
.build();
RecordsApi.ApiV1AddressesRecordFileIdGetResponse addressResponse =
client.records().v1AddressesRecordFileIdGet(addressRequest,
new JWTAuthenticator.Builder().nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
.timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
// 处理响应
if (addressResponse != null && addressResponse.getData() != null) {
log.info("Successfully got address for record file {}", recordFileId);
V1AddressesRecordFileIdGet200Response addressData = addressResponse.getData();
// 获取AI会议转录文件
List<V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner> transcripts =
addressData.getAiMeetingTranscripts();
if (transcripts != null && !transcripts.isEmpty()) {
log.info("Found {} AI meeting transcripts for record file {}",
transcripts.size(), recordFileId);
// 处理每个转录文件
for (V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner transcript : transcripts) {
String fileType = transcript.getFileType();
String downloadUrl = transcript.getDownloadAddress();
if ("txt".equalsIgnoreCase(fileType)) {
log.info("AI Transcript - Type: {}, URL: {}", fileType, downloadUrl);
// 1. 下载文件
byte[] fileData = downloadFile(downloadUrl);
// 2. 将二进制文件转换为文本
String recordTextContent = new String(fileData);
if(StringUtils.isNotEmpty(recordTextContent.replaceAll("\\n","").trim())){
recordTextBuffer.append("\n\n");
recordTextBuffer.append(recordTextContent);
}
}
}
} else {
log.info("No AI meeting transcripts found for record file {}", recordFileId);
}
} else {
log.warn("Empty response for record file: {}", recordFileId);
}
}
if(StringUtils.isEmpty(recordTextBuffer.toString().replaceAll("\\n","").trim())){
log.info("获取的转录文本为空,跳过纪要生成,meetingId:{},fileRecordId:{}",meetingId,recordFileIdList.toString());
processLogService.log(meetingId,subMeetingId,"获取的转录文本为空,跳过纪要生成");
throw new RuntimeException("获取的转录文本为空,跳过纪要生成");
}
//获取系统模板
MeetingRecordTemplate meetingRecordTemplate = meetingRecordTemplateMapper.selectById(2);
String processedResult = processWithClaude(recordTextBuffer.toString(),meetingDate,meetingRecordTemplate.getPrompt(),meetingId,subMeetingId);
String groupOfficeMinutesPath = saveResult(processedResult, recordTextBuffer.toString().getBytes(StandardCharsets.UTF_8), meetingInfo, meetingRecordTemplate);
try(InputStream is = new FileInputStream(groupOfficeMinutesPath)){
byte[] meetingMinutesBytes = IOUtils.toByteArray(is);
minioUtils.upload(groupOfficeMinutesPath,meetingMinutesBytes);
meetingInfoMapper.update(meetingInfo,
new LambdaUpdateWrapper<MeetingInfo>()
.eq(MeetingInfo::getMeetingId,meetingId)
.eq(subMeetingId != null,MeetingInfo::getSubMeetingId,subMeetingId)
.set(MeetingInfo::getRecordXml,groupOfficeMinutesPath));
}catch (Exception e){
throw new RuntimeException(e);
}finally {
FileUtil.del(groupOfficeMinutesPath);
}
return true;
}catch (Exception e){
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
processLogService.log(meetingId,subMeetingId,sw.toString());
return false;
}
}
/**
* 保存会议纪要相关的文件
* @param content 大模型返回的不规则xml
* @param recordData 转录文本
* @param meetingInfo 会议对象
* @param meetingRecordTemplate 模板信息
*/
private String saveResult(String content, byte[] recordData, MeetingInfo meetingInfo,MeetingRecordTemplate meetingRecordTemplate) {
String meetingName;
String meetingId = meetingInfo.getMeetingId();
String subMeetingId = meetingInfo.getSubMeetingId();
//转录文件临时存储路径
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, meetingId, subMeetingId);
minioUtils.upload(recordContentPath,recordData);
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");
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);
XWPFTemplate template;
try (InputStream inputStream = minioUtils.getFile(meetingRecordTemplate.getTemplate())) {
template = XWPFTemplate.compile(inputStream).render(dataModel);
} catch (IOException e) {
throw new RuntimeException(e);
}
meetingMinutesPath = baseSavePath + meetingMinutesFileName + ".docx";
template.writeAndClose(new FileOutputStream(meetingMinutesPath));
processLogService.log(meetingId,subMeetingId,"填充会议纪要成功");
} catch (Exception e) {
log.error("填充会议纪要失败: {}", e.getMessage(), e);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
processLogService.log(meetingId,subMeetingId,"填充会议纪要失败"+sw.toString());
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 baseSavePath + meetingMinutesFileName + ".docx";
}
/**
* 大模型生成纪要xml
* @param textContent 转录文件
* @param prompt 提示词
* @return
*/
private String processWithClaude(String textContent, String meetingDate, String prompt,String meetingId, String subMeetingId) {
//将文件传送给大模型处理
String token = "AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO";
String apiAddr = llmApiAddr + "/llm/sse-invoke";
String model = "anthropic.claude-3-5-sonnet-20240620-v1:0";
int maxTokens = 5000;
List<Message> messages = new ArrayList<>();
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), prompt.replaceAll("\\{transcript\\}",textContent).replaceAll("\\{meetingDate\\}",meetingDate));
messages.add(chatMessage);
// chatMessage = new ChatMessage(ChatMessageRole.ASSISTANT.value(), "好的请提供会议记录");
// messages.add(chatMessage);
// chatMessage = new ChatMessage(ChatMessageRole.USER.value(), textContent);
messages.add(chatMessage);
// 调用Claude API处理文件
String ret = call_llm(apiAddr, model, token, messages, maxTokens,meetingId,subMeetingId);
return ret;
}
private String call_llm(String apiAddr, String model, String token, List<Message> messages, int maxTokens,String meetingId, String subMeetingId) {
LLMService service = new LLMService(token, apiAddr);
StringBuilder stringBuilder = new StringBuilder();
try {
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(model)
.messages(messages)
.stream(true)
.n(1)
.maxTokens(maxTokens)
.logitBias(new HashMap<>())
.build();
service.streamChatCompletion(chatCompletionRequest).doOnError(Throwable::printStackTrace).blockingForEach(chunk -> {
chunk.getChoices().stream().map(choice -> choice.getMessage().getContent())
.filter(Objects::nonNull).findFirst().ifPresent(o -> {
try {
stringBuilder.append(new String(o.getBytes(Charset.defaultCharset())));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
});
} catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
processLogService.log(meetingId,subMeetingId,"【大模型处理异常】:"+sw.toString());
throw new RuntimeException(e);
}
service.shutdownExecutor();
return stringBuilder.toString();
}
private String convertJSONToXml(String json) {
JSONArray jsonArray = JSON.parseArray(json);;
StringBuilder xmlBuilder = new StringBuilder();
......@@ -76,4 +376,63 @@ public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, Meeti
return xmlBuilder.toString();
}
private 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();
//取出正确的value并设置
String realValue = String.valueOf(value.get("")).replaceAll("^\\n+","");
//内容段首移除换行,段末追加换行(会议名称结尾不换行
entry.setValue(realValue.endsWith("\n") || "meeting_name".equals(entry.getKey()) || "meeting_purpose".equals(entry.getKey()) ? realValue : realValue + "\n");
}
return map;
}
/**
* markdown转xml
* @param markdown
* @return
*/
private String extractXmlFromMarkdown(String markdown,String meetingId, String subMeetingId) {
StringBuffer sb;
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>");
String xml = markdown.substring(start, end).trim().replaceAll("\n\n","\n");
sb.append(xml);
sb.append("</root>");
} catch (Exception e) {
log.info("markdown转xml,markdown->{}",markdown);
throw new RuntimeException(e.getMessage());
}
return sb.toString();
}
private byte[] downloadFile(String url) {
// 实现文件下载逻辑
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
try {
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
return response.body().bytes();
}catch (Exception e){
throw new RuntimeException("下载文件失败", e);
}
}
}
......@@ -329,6 +329,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
.withSecret(tencentSecretId,tencentSecretKey)
.build();
List<TencentMeetingVO.RecordFile> meetingFiles = new ArrayList<>();
List<TencentMeetingVO.RecordFile> recordFileUrlList = new ArrayList<>();
List<MeetingInfo> meetingSaveList = new ArrayList<>();
// 查询近两天的会议录制列表
......@@ -343,7 +344,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
Integer totalPage = firstData.getTotalPage();
//目前已存储的会议id
List<String> meetingIds = meetingInfoMapper.getAllMeetingIds();
List<TencentMeetingVO.SimpleMeetingInfo> meetingIds = meetingInfoMapper.getAllMeetingIds();
List<TencentMeetingUser> meetingUsers = tecentMeetingMapper.getAlluser();
Map<String, String> meetingMap = meetingUsers.stream().collect(Collectors.toMap(TencentMeetingUser::getUserId, TencentMeetingUser::getUserName));
while (currentPage.intValue() <= totalPage){
......@@ -356,11 +357,8 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
//1:录制中
//2:转码中
//3:转码完成
if(meetings.stream().allMatch(item->item.getState() != 3)){
return null;
}
for (CorpRecordsVO.RecordMeeting meeting : meetings) {
//会议没结束,跳过
//录制文件未转码完成,跳过
if(meeting.getState() != 3){
// processLogService.log(meeting.getMeetingId(),null,"会议未结束,跳过生成");
continue;
......@@ -372,15 +370,36 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
//查询会议详情
String meetingId = meeting.getMeetingId();
String subMeetingId = null;
MeetingsApi.ApiV1MeetingsMeetingIdGetRequest meetingRequest =
new MeetingsApi.ApiV1MeetingsMeetingIdGetRequest.Builder(meetingId)
.operatorId(tencentAdminUserId)
.operatorIdType("1")
.instanceid("0")
.build();
LocalDateTime mediaStartTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.valueOf(meeting.getMediaStartTime())), ZoneId.systemDefault());
try {
log.info("【周期会议扫描】:查询用户的已结束会议列表...userId->{}",meeting.getUserid());
//获取子会议id
MeetingsApi.ApiV1HistoryMeetingsUseridGetRequest historyMeetingRequest =
new MeetingsApi.ApiV1HistoryMeetingsUseridGetRequest.Builder(meeting.getUserid())
.pageSize("20")
.page("1")
.meetingCode(meeting.getMeetingCode())
.startTime(String.valueOf(mediaStartTime.toLocalDate().atStartOfDay().atZone(ZoneId.systemDefault()).toEpochSecond()))
.endTime(String.valueOf(mediaStartTime.toLocalDate().atStartOfDay().plusDays(1).atZone(ZoneId.systemDefault()).toEpochSecond()))
.build();
MeetingsApi.ApiV1HistoryMeetingsUseridGetResponse historyMeetingResponse =
client.meetings().v1HistoryMeetingsUseridGet(historyMeetingRequest, new JWTAuthenticator.Builder()
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()))).timestamp(String.valueOf(System.currentTimeMillis() / 1000L)));
V1HistoryMeetingsUseridGet200Response historyMeetingResponseData = historyMeetingResponse.getData();
List<V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner> historyMeetingInfoList = historyMeetingResponseData.getMeetingInfoList();
if(CollectionUtils.isEmpty(historyMeetingInfoList)){
log.error("会议未结束,获取子会议id信息失败");
continue;
}
V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner historyMeeting = historyMeetingInfoList.get(0);
//如果是周期会议
if(historyMeeting.getMeetingType() == 1){
subMeetingId = historyMeeting.getSubMeetingId();
}
//如果数据库中已有相同会议id的记录,跳过同步
if(!meetingIds.contains(meetingId)){
String finalSubMeetingId = subMeetingId;
if(!meetingIds.stream().anyMatch(item->item.getMeetingId().equals(meetingId) && Objects.equals(item.getSubMeetingId(), finalSubMeetingId))){
log.info("【会议检索】新的会议meetingId->{}",meeting.getMeetingId());
List<CorpRecordsVO.RecordFile> recordFiles = meeting.getRecordFiles();
//按转录文件时间升序,便于后续的内容拼接
......@@ -399,6 +418,12 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
}else{
//判断主持人是否存在,如果主持人未参会,是查不到主持人的
//如果主持人未参会,使用会议详情中的创建人作为主持人
MeetingsApi.ApiV1MeetingsMeetingIdGetRequest meetingRequest =
new MeetingsApi.ApiV1MeetingsMeetingIdGetRequest.Builder(meetingId)
.operatorId(tencentAdminUserId)
.operatorIdType("1")
.instanceid("0")
.build();
MeetingsApi.ApiV1MeetingsMeetingIdGetResponse meetingResponse =
client.meetings().v1MeetingsMeetingIdGet(meetingRequest, new JWTAuthenticator.Builder()
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
......@@ -469,7 +494,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
//会议基本信息保存
MeetingInfo meetingItem = MeetingInfo.builder().meetingId(meetingId).meetingCode(meeting.getMeetingCode())
.subject(meeting.getSubject())
.startTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.valueOf(meeting.getMediaStartTime())), ZoneId.systemDefault()))
.startTime(mediaStartTime)
// .endTime(LocalDateTime.ofInstant(Instant.ofEpochSecond(Long.valueOf(meeting.getEndTime())), ZoneId.systemDefault()))
.isGenerated(Boolean.FALSE).emailPushAccess(Boolean.TRUE).isPushed(Boolean.FALSE).syncTime(LocalDateTime.now())
.subMeetingId(subMeetingId).generateRetry(Boolean.FALSE).pushRetry(Boolean.FALSE)
......@@ -497,8 +522,13 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
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(",")));
List<String> recordFileIdList = entry.getValue().stream().flatMap(s -> Arrays.stream(s.getRecordFileId().split(","))).collect(Collectors.toList());
meetingInfo.setRecordFileId(recordFileIdList.stream().collect(Collectors.joining(",")));
finalSaveList.add(meetingInfo);
meetingFiles.add(TencentMeetingVO.RecordFile.builder()
.meetingId(meetingInfo.getMeetingId())
.subMeetingId(meetingInfo.getSubMeetingId())
.recordFileIdList(recordFileIdList).build());
}
meetingInfoMapper.batchInsert(finalSaveList);
}
......@@ -510,7 +540,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
processLogService.log(null,null,sw.toString());
throw new RuntimeException(e.getMessage());
}
return recordFileUrlList;
return meetingFiles;
}
/**
......
......@@ -34,4 +34,13 @@ public class TencentMeetingVO {
private String subMeetingId;//如果是周期会议,这个id表示子会议
private List<String> recordFileIdList;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class SimpleMeetingInfo{
private String meetingId;//如果是周期会议,这个id表示主会议
private String subMeetingId;//如果是周期会议,这个id表示子会议
}
}
\ No newline at end of file
......@@ -29,7 +29,7 @@
)
</foreach>
</insert>
<select id="getAllMeetingIds" resultType="java.lang.String">
select meeting_id from cmt_meeting_info
<select id="getAllMeetingIds" resultType="com.cmeeting.vo.TencentMeetingVO$SimpleMeetingInfo">
select meeting_id,sub_meeting_id from cmt_meeting_info
</select>
</mapper>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论