提交 b87c127c 作者: duanxincheng

纪要生成服务优化

父级 8a23a94c
...@@ -122,6 +122,11 @@ ...@@ -122,6 +122,11 @@
<version>2.7.0</version> <version>2.7.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.7.0</version> <!-- 与你的 Spring Boot 版本保持一致 -->
</dependency>
<dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>1.15</version> <version>1.15</version>
...@@ -313,6 +318,24 @@ ...@@ -313,6 +318,24 @@
<artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version> <version>${mybatis-plus.version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>3.0.10</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
......
...@@ -7,6 +7,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; ...@@ -7,6 +7,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication @SpringBootApplication
@EnableScheduling @EnableScheduling
@MapperScan("com.cmeeting.mapper.primary")
public class TencentMeetingCallbackApplication { public class TencentMeetingCallbackApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(TencentMeetingCallbackApplication.class, args); SpringApplication.run(TencentMeetingCallbackApplication.class, args);
......
package com.cmeeting.config;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
// 主数据源配置
@Configuration
@MapperScan(basePackages = "com.cmeeting.mapper.primary", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig {
@Primary
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "masterSqlSessionFactory")
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/primary/*.xml"));
return bean.getObject();
}
@Primary
@Bean(name = "masterSqlSessionTemplate")
public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
\ No newline at end of file
package com.cmeeting.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(
basePackages = "com.cmeeting.mapper.primary", // 主数据源的 Mapper 包路径
sqlSessionFactoryRef = "primarySqlSessionFactory"
)
public class PrimaryDataSourceConfig {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary") // 绑定主数据源配置
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
// 可额外配置 MyBatis 的 mapperLocations、typeAliases 等
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return factoryBean.getObject();
}
@Bean(name = "primaryTransactionManager")
public PlatformTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
\ No newline at end of file
package com.cmeeting.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(
basePackages = "com.cmeeting.mapper.secondary", // 次数据源的 Mapper 包路径
sqlSessionFactoryRef = "secondarySqlSessionFactory"
)
public class SecondaryDataSourceConfig {
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary") // 绑定次数据源配置
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
// 可额外配置 MyBatis 的 mapperLocations、typeAliases 等
// 指定次数据源的 XML 映射文件路径
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/secondary/*.xml"));
return factoryBean.getObject();
}
@Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
\ No newline at end of file
package com.cmeeting.config;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
// 从数据源配置
@Configuration
@MapperScan(basePackages = "com.cmeeting.mapper.secondary", sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveDataSourceConfig {
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveSqlSessionFactory")
public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/secondary/*.xml"));
return bean.getObject();
}
@Bean(name = "slaveSqlSessionTemplate")
public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
...@@ -25,7 +25,7 @@ public class TencentMeetingController { ...@@ -25,7 +25,7 @@ public class TencentMeetingController {
} }
@GetMapping("/getMeetingFiles") @GetMapping("/getMeetingFiles")
public void getMeetingFiles(TencentMeetingVO.TencentMeetingRequest requestVO){ public void getMeetingFiles(){
tecentMeetingService.getMeetingFiles(requestVO); tecentMeetingService.getMeetingFiles();
} }
} }
...@@ -2,6 +2,7 @@ package com.cmeeting.email; ...@@ -2,6 +2,7 @@ package com.cmeeting.email;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.activation.DataHandler; import javax.activation.DataHandler;
import javax.activation.DataSource; import javax.activation.DataSource;
...@@ -13,12 +14,13 @@ import java.util.Properties; ...@@ -13,12 +14,13 @@ import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@Slf4j @Slf4j
@Service
public class EmailSender { public class EmailSender {
@Value("${email.sender}") @Value("${email.sender}")
private String SENDER; private String SENDER;
@Value("${email.sender.pwd}") @Value("${email.sender-pwd}")
private String EMAIL_PWD; private String EMAIL_PWD;
@Value("${email.smtp.host}") @Value("${email.smtp-host}")
private String SMTP_HOST; private String SMTP_HOST;
private static final Integer MAX_RETRY = 3; private static final Integer MAX_RETRY = 3;
...@@ -30,10 +32,10 @@ public class EmailSender { ...@@ -30,10 +32,10 @@ public class EmailSender {
* @param recordFileId 转录文件ID * @param recordFileId 转录文件ID
* @return * @return
*/ */
public boolean sendEmailWithAttachment(String toEmail, String subject, String attachmentPath, String recordFileId) { public boolean sendEmailWithAttachment(String toEmail, String subject, String attachmentPath, String recordFileId) throws Exception {
// 邮件服务器配置 // 邮件服务器配置
Properties props = new Properties(); Properties props = new Properties();
props.put("mail.smtp.host", "smtp.office365.com"); props.put("mail.smtp.host", SMTP_HOST);
props.put("mail.smtp.auth", "true"); props.put("mail.smtp.auth", "true");
// props.put("mail.smtp.port", "465"); // props.put("mail.smtp.port", "465");
...@@ -50,7 +52,8 @@ public class EmailSender { ...@@ -50,7 +52,8 @@ public class EmailSender {
Session session = Session.getInstance(props, Session session = Session.getInstance(props,
new javax.mail.Authenticator() { new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() { protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("cmeeting_assistant@cimc.com", "scyou@xih45g6@xih4"); // return new PasswordAuthentication("cmeeting_assistant@cimc.com", "scyou@xih45g6@xih4");
return new PasswordAuthentication(SENDER, EMAIL_PWD);
} }
}); });
String body = "您好:\n" + String body = "您好:\n" +
...@@ -58,60 +61,60 @@ public class EmailSender { ...@@ -58,60 +61,60 @@ public class EmailSender {
" 附件为您本次会议的会议纪要,烦请下载查看"; " 附件为您本次会议的会议纪要,烦请下载查看";
AtomicInteger retryCount = new AtomicInteger(0); AtomicInteger retryCount = new AtomicInteger(0);
try {
boolean isSent = false; boolean isSent = false;
while (retryCount.intValue() < MAX_RETRY && !isSent){ while (retryCount.intValue() < MAX_RETRY && !isSent){
// 创建邮件消息 try {
Message message = new MimeMessage(session); // 创建邮件消息
message.setFrom(new InternetAddress("cmeeting_assistant@cimc.com")); Message message = new MimeMessage(session);
message.setRecipients(Message.RecipientType.TO, message.setFrom(new InternetAddress(SENDER));
InternetAddress.parse(toEmail)); message.setRecipients(Message.RecipientType.TO,
message.setSubject(subject); InternetAddress.parse(toEmail));
message.setSubject(subject);
// 创建消息体部分(正文)
MimeBodyPart messageBodyPart = new MimeBodyPart(); // 创建消息体部分(正文)
messageBodyPart.setText(body); MimeBodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(body);
// 创建多部分消息
Multipart multipart = new MimeMultipart(); // 创建多部分消息
Multipart multipart = new MimeMultipart();
// 添加文本部分
multipart.addBodyPart(messageBodyPart); // 添加文本部分
multipart.addBodyPart(messageBodyPart);
// 添加附件部分
if (attachmentPath != null && !attachmentPath.isEmpty()) { // 添加附件部分
MimeBodyPart attachmentPart = new MimeBodyPart(); if (attachmentPath != null && !attachmentPath.isEmpty()) {
DataSource source = new FileDataSource(attachmentPath); MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.setDataHandler(new DataHandler(source)); DataSource source = new FileDataSource(attachmentPath);
attachmentPart.setFileName(new File(attachmentPath).getName()); attachmentPart.setDataHandler(new DataHandler(source));
multipart.addBodyPart(attachmentPart); attachmentPart.setFileName(new File(attachmentPath).getName());
} multipart.addBodyPart(attachmentPart);
}
// 设置完整消息内容
message.setContent(multipart);
// 发送邮件 // 设置完整消息内容
Transport.send(message); message.setContent(multipart);
log.error("邮件已成功发送: recordFileId->{}", recordFileId);
isSent = true; // 发送邮件
} Transport.send(message);
} catch (MessagingException e) { log.error("邮件已成功发送: recordFileId->{}", recordFileId);
//todo 邮件失败记录 isSent = true;
// 异常处理 } catch (MessagingException e) {
retryCount.getAndIncrement(); //todo 邮件失败记录
if (retryCount.intValue() > MAX_RETRY) { // 异常处理
log.error("邮件发送达到最大重试次数: recordFileId->{}", recordFileId); retryCount.getAndIncrement();
throw new RuntimeException(e); if (retryCount.intValue() > MAX_RETRY) {
} log.error("邮件发送达到最大重试次数: recordFileId->{}", recordFileId);
// 指数退避 throw new RuntimeException(e);
try { }
Thread.sleep((long) Math.pow(2, retryCount.intValue()) * 1000); // 指数退避
} catch (InterruptedException ie) { try {
Thread.currentThread().interrupt(); Thread.sleep((long) Math.pow(2, retryCount.intValue()) * 1000);
throw new RuntimeException("邮件发送重试失败", ie); } catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("邮件发送重试失败", ie);
}
return false;
}
} }
return false;
}
return true; return true;
} }
} }
\ No newline at end of file
...@@ -90,18 +90,7 @@ public class CmeetingJob { ...@@ -90,18 +90,7 @@ public class CmeetingJob {
logger.info("结束时间: " + now.format(formatter) + " | Unix 时间戳: " + nowTimestamp); logger.info("结束时间: " + now.format(formatter) + " | Unix 时间戳: " + nowTimestamp);
logger.info("----------------------------------"); logger.info("----------------------------------");
// dojob(beforeDayTimestamp, nowTimestamp); List<TencentMeetingVO.RecordFile> meetingFiles = tecentMeetingService.getMeetingFiles();
AtomicInteger currentPage = new AtomicInteger(1);
TencentMeetingVO.TencentMeetingRequest request = TencentMeetingVO.TencentMeetingRequest.builder()
.page(1)
.pageSize(10)
.startTime(beforeDayTimestamp)
.endTime(nowTimestamp)
.operatorId(tencentAdminUserId)
.operatorIdType(1)
.build();
List<TencentMeetingVO.RecordFile> meetingFiles = tecentMeetingService.getMeetingFiles(request);
if (meetingFiles == null || meetingFiles.isEmpty()) { if (meetingFiles == null || meetingFiles.isEmpty()) {
......
...@@ -5,8 +5,18 @@ import cn.chatbot.openai.completion.chat.ChatMessage; ...@@ -5,8 +5,18 @@ import cn.chatbot.openai.completion.chat.ChatMessage;
import cn.chatbot.openai.completion.chat.ChatMessageRole; import cn.chatbot.openai.completion.chat.ChatMessageRole;
import cn.chatbot.openai.completion.chat.Message; import cn.chatbot.openai.completion.chat.Message;
import cn.chatbot.openai.service.LLMService; import cn.chatbot.openai.service.LLMService;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import 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.email.EmailSender; import com.cmeeting.email.EmailSender;
import com.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.service.MeetingInfoService;
import com.cmeeting.util.MinioUtils;
import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.XWPFTemplate;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.tencentcloudapi.wemeet.Client; import com.tencentcloudapi.wemeet.Client;
...@@ -27,6 +37,7 @@ import okhttp3.Response; ...@@ -27,6 +37,7 @@ import okhttp3.Response;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor; import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.stereotype.Service;
import java.io.*; import java.io.*;
import java.math.BigInteger; import java.math.BigInteger;
...@@ -46,6 +57,7 @@ import java.util.stream.Collectors; ...@@ -46,6 +57,7 @@ import java.util.stream.Collectors;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@Slf4j @Slf4j
@Service
public class FileProcessTask { public class FileProcessTask {
private String recordFileId; private String recordFileId;
private String meetingId; private String meetingId;
...@@ -62,136 +74,139 @@ public class FileProcessTask { ...@@ -62,136 +74,139 @@ public class FileProcessTask {
private String tencentSecretKey; private String tencentSecretKey;
private String tencentAdminUserId; private String tencentAdminUserId;
private MeetingInfoMapper meetingInfoMapper;
private MinioUtils minioUtils;
private EmailSender emailSender;
// 实际处理逻辑 // 实际处理逻辑
public void process() { public void process() {
try { boolean isSuccess = false;
boolean isSuccess = false; while (retryCount <= MAX_RETRY && !isSuccess) {
while (retryCount <= MAX_RETRY && !isSuccess) { try {
Client client = new Client.Builder() Client client = new Client.Builder()
.withAppId(tencentAppId).withSdkId(tencentSdkId) .withAppId(tencentAppId).withSdkId(tencentSdkId)
.withSecret(tencentSecretId,tencentSecretKey) .withSecret(tencentSecretId,tencentSecretKey)
.build(); .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));
Map<String,Object> participantsMap = new ConcurrentHashMap<>();
//主持人角色,以下角色都可以表示主持人
//用户角色:
//0:普通成员角色
//1:创建者角色
//2:主持人
//3:创建者+主持人
//4:游客
//5:游客+主持人
//6:联席主持人
//7:创建者+联席主持人
List<Long> hostRoleList = Arrays.asList(2L,3L,5L,6L,7L);
try {
MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetResponse participantsResponse =
client.meetings().v1MeetingsMeetingIdParticipantsGet(participantsRequest, participantsAuthenticatorBuilder);
V1MeetingsMeetingIdParticipantsGet200Response data = participantsResponse.getData();
String scheduleStartTime = data.getScheduleStartTime();
String scheduleEndTime = data.getScheduleEndTime();
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String zonedDateTimeStart = Instant.ofEpochSecond(Long.valueOf(scheduleStartTime)).atZone(zoneId).format(df);
// String zonedDateTimeEnd = Instant.ofEpochSecond(Long.valueOf(scheduleEndTime)).atZone(zoneId).format(df);
participantsMap.put("meeting_date",zonedDateTimeStart);
participantsMap.put("meeting_location","线上腾讯会议");
List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = data.getParticipants();
participantsMap.put("meeting_participants",
participants.stream()
.map(item->new String(Base64.getDecoder().decode(item.getUserName()))).collect(Collectors.joining("、")));
Optional<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> host = participants.stream().filter(item -> hostRoleList.contains(item.getUserRole())).findFirst();
participantsMap.put("meeting_host",host.isPresent() ? new String(Base64.getDecoder().decode(host.get().getUserName())) : "未知主持人");
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
throw new RuntimeException(e);
}
//查询录制转写详情
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); 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));
Map<String,Object> participantsMap = new ConcurrentHashMap<>();
//主持人角色,以下角色都可以表示主持人
//用户角色:
//0:普通成员角色
//1:创建者角色
//2:主持人
//3:创建者+主持人
//4:游客
//5:游客+主持人
//6:联席主持人
//7:创建者+联席主持人
List<Long> hostRoleList = Arrays.asList(2L,3L,5L,6L,7L);
try {
MeetingsApi.ApiV1MeetingsMeetingIdParticipantsGetResponse participantsResponse =
client.meetings().v1MeetingsMeetingIdParticipantsGet(participantsRequest, participantsAuthenticatorBuilder);
V1MeetingsMeetingIdParticipantsGet200Response data = participantsResponse.getData();
String scheduleStartTime = data.getScheduleStartTime();
String scheduleEndTime = data.getScheduleEndTime();
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String zonedDateTimeStart = Instant.ofEpochSecond(Long.valueOf(scheduleStartTime)).atZone(zoneId).format(df);
// String zonedDateTimeEnd = Instant.ofEpochSecond(Long.valueOf(scheduleEndTime)).atZone(zoneId).format(df);
// 1. 下载文件 participantsMap.put("meeting_date",zonedDateTimeStart);
byte[] fileData = downloadFile(downloadUrl); participantsMap.put("meeting_location","线上腾讯会议");
List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = data.getParticipants();
participantsMap.put("meeting_participants",
participants.stream()
.map(item->new String(Base64.getDecoder().decode(item.getUserName()))).distinct().collect(Collectors.joining("、")));
Optional<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> host = participants.stream().filter(item -> hostRoleList.contains(item.getUserRole())).findFirst();
participantsMap.put("meeting_host",host.isPresent() ? new String(Base64.getDecoder().decode(host.get().getUserName())) : "未知主持人");
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
throw new RuntimeException(e);
}
// 2. 将二进制文件转换为文本 //查询录制转写详情
String recordTextContent = new String(fileData); 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);
// String recordTextContent = new String(Files.readAllBytes(Paths.get("/20250321145249-天达物流AIGC维修助手需要沟通-转写原文版-1.txt"))); // String recordTextContent = new String(Files.readAllBytes(Paths.get("/20250321145249-天达物流AIGC维修助手需要沟通-转写原文版-1.txt")));
if(StringUtils.isEmpty(recordTextContent.replaceAll("\\n","").trim())){ if(StringUtils.isEmpty(recordTextContent.replaceAll("\\n","").trim())){
log.info("获取的转录文本为空,跳过纪要生成,URL:{},fileRecordId:{}", fileType, downloadUrl,recordFileId); log.info("获取的转录文本为空,跳过纪要生成,URL:{},fileRecordId:{}", fileType, downloadUrl,recordFileId);
continue; continue;
} }
// 3. 处理文件 (调用Claude API等) // 3. 处理文件 (调用Claude API等)
String processedResult = processWithClaude(recordTextContent); String processedResult = processWithClaude(recordTextContent);
// 4. 保存结果 // 4. 保存结果
saveResult(savePath, processedResult, participantsMap); saveResult(savePath, processedResult, participantsMap, fileData);
}
} }
} else {
log.info("No AI meeting transcripts found for record file {}", recordFileId);
} }
} else { } else {
log.warn("Empty response for record file: {}", recordFileId); log.info("No AI meeting transcripts found for record file {}", recordFileId);
} }
isSuccess = true;
} } else {
} catch (Exception e) { log.warn("Empty response for record file: {}", recordFileId);
// 异常处理
retryCount++;
if (retryCount > MAX_RETRY) {
log.error("达到最大重试次数: {}", recordFileId);
throw new RuntimeException(e);
} }
// 指数退避 isSuccess = true;
try { } catch (Exception e) {
Thread.sleep((long) Math.pow(2, retryCount) * 1000); // 异常处理
} catch (InterruptedException ie) { retryCount++;
Thread.currentThread().interrupt(); if (retryCount > MAX_RETRY) {
throw new RuntimeException("重试失败", ie); log.error("达到最大重试次数: {}", recordFileId);
throw new RuntimeException(e);
}
// 指数退避
try {
Thread.sleep((long) Math.pow(2, retryCount) * 1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("重试失败", ie);
}
} }
} }
} }
private byte[] downloadFile(String url) { private byte[] downloadFile(String url) {
...@@ -238,7 +253,7 @@ public class FileProcessTask { ...@@ -238,7 +253,7 @@ public class FileProcessTask {
int maxTokens = 5000; int maxTokens = 5000;
List<Message> messages = new ArrayList<>(); List<Message> messages = new ArrayList<>();
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "你是会议纪要助手,基于提供的会议记录,生成一份详细的会议纪要,并严格按照指定的XML格式输出。\\n\\n要求:\\n1. 会议名称:应简洁明了地反映会议主题,不超过30个字\\n2. 会议目的:需清晰阐述召开此次会议的原因和期望达成的目标,不超过50字\\n3. 会议要点:需全面概括会议内容,让读者理解会议讨论的核心问题,不要遗漏任何有价值的信息,每个要点需用序号标注并另起一行\\n4. 会议内容:需详细记录会议的背景信息、主要讨论内容、各参会人员的发言要点、达成的共识和决策等。内容应有逻辑性,分段清晰,重点突出,不可遗漏关键信息\\n5. 会议沟通内容:需按照一定的顺序组织内容,详细描述每个事项的内容,不要使用流水账形式,应包含具体的沟通过程、各方观点和达成的一致意见\\n6. 会后跟进事项:详细描述会议结束之后,每个后续事项的责任人、事项内容、完成时间(若会议记录中没有提及,则填写待定)等信息,事项要具体到责任人\\n\\n注意事项:\\n1. 所有内容需基于原始会议记录,不要添加虚构信息\\n2. 会议纪要内容需详细充分,建议总字数不少于3000字,实际字数可根据原始会议记录的丰富程度进行适当调整\\n\\n输出格式必须严格按照以下XML结构,不允许有任何嵌套标签:\\n\\n<meeting_name>会议名称</meeting_name>\\n<meeting_purpose>会议目的</meeting_purpose>\\n<meeting_key_points>会议要点</meeting_key_points>\\n<meeting_content>会议内容</meeting_content>\\n<meeting_communication>会议沟通内容</meeting_communication>\\n<meeting_follow_up>\\n在此填写会议跟踪事项,格式为:\\n1. 内容:XXX;责任人:XXX;完成时间:XXXX-XX-XX\\n2. 内容:XXX;责任人:XXX;完成时间:XXXX-XX-XX\\n...\\n</meeting_follow_up>\\n\\n完成XML格式的会议纪要后,必须在XML之外单独添加猜您想问部分,生成3-4条与会议内容相关的推荐问题。\\n\\n会议记录如下:\\n"); ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "你是会议纪要助手,基于提供的会议记录,生成一份详细的会议纪要,并严格按照指定的XML格式输出。\\n\\n要求:\\n1. 会议名称:应简洁明了地反映会议主题,不超过30个字\\n2. 会议目的:需清晰阐述召开此次会议的原因和期望达成的目标,不超过50字\\n3. 会议要点:需全面概括会议内容,让读者理解会议讨论的核心问题,不要遗漏任何有价值的信息,每个要点需用序号标注并另起一行\\n4. 会议内容:需详细记录会议的背景信息、主要讨论内容、各参会人员的发言要点、达成的共识和决策等。内容应有逻辑性,分段清晰,重点突出,不可遗漏关键信息\\n5. 会议沟通内容:需按照一定的顺序组织内容,详细描述每个事项的内容,不要使用流水账形式,应包含具体的沟通过程、各方观点和达成的一致意见\\n6. 会后跟踪事项:详细描述会议结束之后,每个后续事项的责任人、事项内容、完成时间(若会议记录中没有提及,则填写待定)等信息,事项要具体到责任人\\n\\n注意事项:\\n1. 所有内容需基于原始会议记录,不要添加虚构信息\\n2. 会议纪要内容需详细充分,建议总字数不少于3000字,实际字数可根据原始会议记录的丰富程度进行适当调整\\n\\n输出格式必须严格按照以下XML结构,不允许有任何嵌套标签:\\n\\n<meeting_name label=\\\"会议名称\\\">会议名称</meeting_name>\\n<meeting_purpose label=\\\"会议目的\\\">会议目的</meeting_purpose>\\n<meeting_key_points label=\\\"会议要点\\\">会议要点</meeting_key_points>\\n<meeting_content label=\\\"会议内容\\\">会议内容</meeting_content>\\n<meeting_communication label=\\\"会议沟通内容\\\">会议沟通内容</meeting_communication>\\n<meeting_follow_up label=\\\"会后跟踪事项\\\">\\n在此填写会后跟踪事项,格式为:\\n1. 内容:XXX;责任人:XXX;完成时间:XXXX-XX-XX\\n2. 内容:XXX;责任人:XXX;完成时间:XXXX-XX-XX\\n...\\n</meeting_follow_up>\\n\\n完成XML格式的会议纪要后,必须在XML之外单独添加猜您想问部分,生成3-4条与会议内容相关的推荐问题。\\n\\n会议记录如下:");
messages.add(chatMessage); messages.add(chatMessage);
chatMessage = new ChatMessage(ChatMessageRole.ASSISTANT.value(), "好的请提供会议记录"); chatMessage = new ChatMessage(ChatMessageRole.ASSISTANT.value(), "好的请提供会议记录");
messages.add(chatMessage); messages.add(chatMessage);
...@@ -251,36 +266,88 @@ public class FileProcessTask { ...@@ -251,36 +266,88 @@ public class FileProcessTask {
return ret; return ret;
} }
private void saveResult(String path, String content, Map<String,Object> participantsMap) { private void saveResult(String path, String content, Map<String,Object> participantsMap, byte[] recordData) {
String nowTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HHmmss")); String nowTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HHmmss"));
String targetPath = path + nowTime + ".md"; // 改为.md扩展名; String targetPath = path + nowTime + ".json"; // 改为.json扩展名;
String targetFileName; String targetFileName;
String meetingName; String meetingName;
// 保存处理结果 String xml = extractXmlFromMarkdown(content);
try {
// 将字符串写入Markdown文件
Files.write(Paths.get(targetPath), content.getBytes());
log.info("Markdown文件已保存到: {}", targetPath);
// convert(targetPath,targetWordPath); // 将xml格式的内容转换为特殊json
String markdownContent = new String(Files.readAllBytes(Paths.get(targetPath))); String json = convertXmlToJSON(xml);
Map<String, Object> dataModel = convertXmlToMap(markdownContent); String recordContentPath = meetingId + "-recordContent-" + IdUtil.fastSimpleUUID() + "txt";
minioUtils.upload(recordContentPath,recordData);
String recordXmlPath = meetingId + "-recordXmlPath-" + IdUtil.fastSimpleUUID() + "xml";
try {
//写入json文件,这里的json文件用于用户可编辑
Files.write(Paths.get(targetPath), json.getBytes());
log.info("json文件已保存到: {}", targetPath);
//将xml格式的内容转换为map,用于填充模板
Map<String, Object> dataModel = convertXmlToMap(xml);
dataModel.putAll(participantsMap); dataModel.putAll(participantsMap);
XWPFTemplate template = XWPFTemplate.compile("/data_net_template.docx").render(dataModel); XWPFTemplate template = XWPFTemplate.compile("/data_net_template.docx").render(dataModel);
meetingName = dataModel.get("meeting_name") != null ? String.valueOf(dataModel.get("meeting_name")) : "腾讯会议纪要"; meetingName = dataModel.get("meeting_name") != null ? String.valueOf(dataModel.get("meeting_name")) : "腾讯会议纪要";
targetFileName = meetingName + "_" + nowTime; targetFileName = meetingName + "_" + nowTime;
template.writeAndClose(new FileOutputStream(path + targetFileName + ".docx")); template.writeAndClose(new FileOutputStream(path + targetFileName + ".docx"));
byte[] recordXmlData = Files.readAllBytes(Paths.get(path + targetFileName + ".docx"));
minioUtils.upload(recordXmlPath,recordXmlData);
} catch (Exception e) { } catch (Exception e) {
log.error("填充会议纪要失败: {}", e.getMessage(), e); log.error("填充会议纪要失败: {}", e.getMessage(), e);
throw new RuntimeException("填充会议纪要失败"); throw new RuntimeException("填充会议纪要失败");
} }
MeetingInfo meetingInfo = meetingInfoMapper.selectOne(new LambdaQueryWrapper<MeetingInfo>().eq(MeetingInfo::getMeetingId,meetingId));
//邮件推送 Boolean isPushed;
EmailSender emailSender = new EmailSender(); try {
emailSender.sendEmailWithAttachment("duanxincheng@chatbot.cn",meetingName,path + targetFileName + ".docx",recordFileId); //邮件推送
emailSender.sendEmailWithAttachment("duanxincheng@chatbot.cn",meetingName,path + targetFileName + ".docx",recordFileId);
// emailSender.sendEmailWithAttachment("xuwentao@chatbot.cn",meetingName,path + targetFileName + ".docx",recordFileId); // emailSender.sendEmailWithAttachment("xuwentao@chatbot.cn",meetingName,path + targetFileName + ".docx",recordFileId);
// emailSender.sendEmailWithAttachment("jiaqi.cai@cimc.com",meetingName,path + targetFileName + ".docx",recordFileId); // emailSender.sendEmailWithAttachment("jiaqi.cai@cimc.com",meetingName,path + targetFileName + ".docx",recordFileId);
isPushed = Boolean.TRUE;
} catch (Exception e) {
isPushed = Boolean.FALSE;
e.printStackTrace();
log.error(e.getMessage());
}
meetingInfoMapper.update(meetingInfo,
new LambdaUpdateWrapper<MeetingInfo>()
.eq(MeetingInfo::getMeetingId,meetingId)
.set(MeetingInfo::getRecordContent,recordContentPath)
.set(MeetingInfo::getRecordXml,recordXmlPath)
.set(MeetingInfo::getIsGenerated,Boolean.TRUE)
.set(MeetingInfo::getIsPushed,isPushed)
);
}
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 + "/" + 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;
} }
public static String call_llm(String apiAddr, String model, String token, List<Message> messages, int maxTokens) { public static String call_llm(String apiAddr, String model, String token, List<Message> messages, int maxTokens) {
...@@ -312,17 +379,23 @@ public class FileProcessTask { ...@@ -312,17 +379,23 @@ public class FileProcessTask {
service.shutdownExecutor(); service.shutdownExecutor();
return stringBuilder.toString(); return stringBuilder.toString();
} }
public static Map<String, Object> convertXmlToMap(String markdownContent) throws Exception { public static Map<String, Object> convertXmlToMap(String xml) throws Exception {
String xmlContent = extractXmlFromMarkdown(markdownContent);
XmlMapper xmlMapper = new XmlMapper(); XmlMapper xmlMapper = new XmlMapper();
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
// 先将 XML 读取为 Map // 先将 XML 读取为 Map
Map<?, ?> xmlMap = xmlMapper.readValue(xmlContent, Map.class); Map<?, ?> xmlMap = xmlMapper.readValue(xml, Map.class);
// 转换为更标准的 Map<String, Object> // 转换为更标准的 Map<String, Object>
return objectMapper.convertValue(xmlMap, Map.class); Map<String,Object> map = objectMapper.convertValue(xmlMap, Map.class);
//特殊处理map格式
for (Map.Entry<String, Object> entry : map.entrySet()) {
Map<String,Object> value = (Map<String, Object>) entry.getValue();
Object realValue = value.get("");
entry.setValue(realValue);
}
return map;
} }
/** /**
...@@ -348,7 +421,8 @@ public class FileProcessTask { ...@@ -348,7 +421,8 @@ public class FileProcessTask {
public FileProcessTask(String recordFileId, String meetingId, String subMeetingId, String savePath, Map<String, Object> metadata, String tencentAppId, public FileProcessTask(String recordFileId, String meetingId, String subMeetingId, String savePath, Map<String, Object> metadata, String tencentAppId,
String tencentSdkId, String tencentSecretId, String tencentSecretKey, String tencentAdminUserId) { String tencentSdkId, String tencentSecretId, String tencentSecretKey, String tencentAdminUserId,
MeetingInfoMapper meetingInfoMapper, MinioUtils minioUtils, EmailSender emailSender) {
this.recordFileId = recordFileId; this.recordFileId = recordFileId;
this.savePath = savePath; this.savePath = savePath;
this.metadata = metadata; this.metadata = metadata;
...@@ -359,5 +433,8 @@ public class FileProcessTask { ...@@ -359,5 +433,8 @@ public class FileProcessTask {
this.tencentAdminUserId = tencentAdminUserId; this.tencentAdminUserId = tencentAdminUserId;
this.meetingId = meetingId; this.meetingId = meetingId;
this.subMeetingId = subMeetingId; this.subMeetingId = subMeetingId;
this.meetingInfoMapper = meetingInfoMapper;
this.minioUtils = minioUtils;
this.emailSender = emailSender;
} }
} }
\ No newline at end of file
package com.cmeeting.mapper.primary;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cmeeting.pojo.MeetingInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
public interface MeetingInfoMapper extends BaseMapper<MeetingInfo> {
void batchInsert(@Param("meetingSaveList")List<MeetingInfo> meetingSaveList);
List<String> getAllMeetingIds();
}
\ No newline at end of file
package com.cmeeting.pojo; package com.cmeeting.pojo;
import java.util.Objects; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/** /**
* 会议信息实体类 * 会议信息实体类
*/ */
public class MeetingInfo { @Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("cmt_meeting_info")
public class MeetingInfo implements Serializable {
/**
* 主键id
*/
@TableId(type = IdType.AUTO)
private Integer id;
/** /**
* 会议主题 * 会议主题
*/ */
...@@ -15,6 +34,11 @@ public class MeetingInfo { ...@@ -15,6 +34,11 @@ public class MeetingInfo {
* 会议ID(字符串类型) * 会议ID(字符串类型)
*/ */
private String meetingId; private String meetingId;
/**
* 子会议ID
*/
private String subMeetingId;
/** /**
* 会议号码 * 会议号码
...@@ -22,153 +46,49 @@ public class MeetingInfo { ...@@ -22,153 +46,49 @@ public class MeetingInfo {
private String meetingCode; private String meetingCode;
/** /**
* 用户ID * 主持人
*/ */
private String userId; private String hostId;
/** /**
* 用户昵称 * 参会人员名单
*/ */
private String nickName; private String participantUserIds;
/** /**
* 会议开始时间(时间戳) * 会议开始时间(时间戳)
*/ */
private Long startTime; private LocalDateTime startTime;
/** /**
* 会议结束时间(时间戳) * 会议结束时间(时间戳)
*/ */
private Long endTime; private LocalDateTime endTime;
/** /**
* 参会人数 * 是否生成会议纪要完成
*/ */
private Integer participantsNum; private Boolean isGenerated;
/** /**
* 会议总时长(秒) * 推送邮件许可 为false不推送
*/ */
private Integer meetingDuration; private Boolean emailPushAccess;
/** /**
* 用户参会时长(秒) * 是否推送邮件完成
*/ */
private Integer userMeetingDuration; private Boolean isPushed;
/**
// 构造方法 * 同步时间
public MeetingInfo() { */
} private LocalDateTime syncTime;
// Getter 和 Setter 方法
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getMeetingId() {
return meetingId;
}
public void setMeetingId(String meetingId) {
this.meetingId = meetingId;
}
public String getMeetingCode() {
return meetingCode;
}
public void setMeetingCode(String meetingCode) {
this.meetingCode = meetingCode;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public Long getStartTime() {
return startTime;
}
public void setStartTime(Long startTime) {
this.startTime = startTime;
}
public Long getEndTime() {
return endTime;
}
public void setEndTime(Long endTime) {
this.endTime = endTime;
}
public Integer getParticipantsNum() {
return participantsNum;
}
public void setParticipantsNum(Integer participantsNum) {
this.participantsNum = participantsNum;
}
public Integer getMeetingDuration() {
return meetingDuration;
}
public void setMeetingDuration(Integer meetingDuration) {
this.meetingDuration = meetingDuration;
}
public Integer getUserMeetingDuration() {
return userMeetingDuration;
}
public void setUserMeetingDuration(Integer userMeetingDuration) {
this.userMeetingDuration = userMeetingDuration;
}
// toString 方法
@Override
public String toString() {
return "MeetingInfo{" +
"subject='" + subject + '\'' +
", meetingId='" + meetingId + '\'' +
", meetingCode='" + meetingCode + '\'' +
", userId='" + userId + '\'' +
", nickName='" + nickName + '\'' +
", startTime=" + startTime +
", endTime=" + endTime +
", participantsNum=" + participantsNum +
", meetingDuration=" + meetingDuration +
", userMeetingDuration=" + userMeetingDuration +
'}';
}
// equals 和 hashCode 方法 /**
@Override * 转录文本
public boolean equals(Object o) { */
if (this == o) return true; private String recordContent;
if (o == null || getClass() != o.getClass()) return false;
MeetingInfo that = (MeetingInfo) o;
return Objects.equals(meetingId, that.meetingId);
}
@Override /**
public int hashCode() { * 纪要xml
return Objects.hash(meetingId); */
} private String recordXml;
} }
\ No newline at end of file
package com.cmeeting.service; package com.cmeeting.service;
import com.cmeeting.email.EmailSender;
import com.cmeeting.job.FileProcessTask; import com.cmeeting.job.FileProcessTask;
import com.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.vo.TencentMeetingVO; import com.cmeeting.vo.TencentMeetingVO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -8,6 +11,7 @@ import org.springframework.beans.factory.annotation.Value; ...@@ -8,6 +11,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -33,6 +37,12 @@ public class FileProcessProducer { ...@@ -33,6 +37,12 @@ public class FileProcessProducer {
private String tencentSecretKey; private String tencentSecretKey;
@Value(value = "${tencent.admin.userId}") @Value(value = "${tencent.admin.userId}")
private String tencentAdminUserId; private String tencentAdminUserId;
@Resource
private MeetingInfoMapper meetingInfoMapper;
@Resource
private MinioUtils minioUtils;
@Resource
private EmailSender emailSender;
// 批量提交任务 // 批量提交任务
public void submitBatchTasks(List<TencentMeetingVO.RecordFile> recordFiles, String baseSavePath) { public void submitBatchTasks(List<TencentMeetingVO.RecordFile> recordFiles, String baseSavePath) {
...@@ -50,7 +60,10 @@ public class FileProcessProducer { ...@@ -50,7 +60,10 @@ public class FileProcessProducer {
tencentSdkId, tencentSdkId,
tencentSecretId, tencentSecretId,
tencentSecretKey, tencentSecretKey,
tencentAdminUserId tencentAdminUserId,
meetingInfoMapper,
minioUtils,
emailSender
); );
// 提交任务到线程池 // 提交任务到线程池
......
package com.cmeeting.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cmeeting.pojo.MeetingInfo;
public interface MeetingInfoService extends IService<MeetingInfo> {
}
...@@ -11,6 +11,6 @@ public interface TecentMeetingService extends IService<TencentMeetingUser> { ...@@ -11,6 +11,6 @@ public interface TecentMeetingService extends IService<TencentMeetingUser> {
void doUsers(); void doUsers();
List<TencentMeetingVO.RecordFile> getMeetingFiles(TencentMeetingVO.TencentMeetingRequest requestVO); List<TencentMeetingVO.RecordFile> getMeetingFiles();
} }
package com.cmeeting.service.impl;
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 org.springframework.stereotype.Service;
@Service
public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, MeetingInfo> implements MeetingInfoService {
}
package com.cmeeting.service.impl; package com.cmeeting.service.impl;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.mapper.primary.TecentMeetingMapper; import com.cmeeting.mapper.primary.TecentMeetingMapper;
import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.pojo.TencentMeetingUser; import com.cmeeting.pojo.TencentMeetingUser;
import com.cmeeting.service.TecentMeetingService; import com.cmeeting.service.TecentMeetingService;
import com.cmeeting.vo.TencentMeetingVO; import com.cmeeting.vo.TencentMeetingVO;
...@@ -11,6 +14,7 @@ import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator; ...@@ -11,6 +14,7 @@ import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator;
import com.tencentcloudapi.wemeet.core.exception.ClientException; import com.tencentcloudapi.wemeet.core.exception.ClientException;
import com.tencentcloudapi.wemeet.core.exception.ServiceException; import com.tencentcloudapi.wemeet.core.exception.ServiceException;
import com.tencentcloudapi.wemeet.service.meetings.api.MeetingsApi; import com.tencentcloudapi.wemeet.service.meetings.api.MeetingsApi;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsGet200ResponseMeetingInfoListInnerCurrentCoHostsInner;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdGet200Response; import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdGet200Response;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner; import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner;
import com.tencentcloudapi.wemeet.service.records.api.RecordsApi; import com.tencentcloudapi.wemeet.service.records.api.RecordsApi;
...@@ -35,6 +39,7 @@ import java.nio.charset.StandardCharsets; ...@@ -35,6 +39,7 @@ import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
...@@ -42,6 +47,7 @@ import java.util.ArrayList; ...@@ -42,6 +47,7 @@ import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static com.cmeeting.WeComAndTencentMeeting.fetchUsersInBatches; import static com.cmeeting.WeComAndTencentMeeting.fetchUsersInBatches;
import static com.cmeeting.WeComAndTencentMeeting.markDuplicateNamesTecent; import static com.cmeeting.WeComAndTencentMeeting.markDuplicateNamesTecent;
...@@ -51,6 +57,8 @@ import static com.cmeeting.WeComAndTencentMeeting.markDuplicateNamesTecent; ...@@ -51,6 +57,8 @@ import static com.cmeeting.WeComAndTencentMeeting.markDuplicateNamesTecent;
public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,TencentMeetingUser> implements TecentMeetingService { public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,TencentMeetingUser> implements TecentMeetingService {
@Resource @Resource
private TecentMeetingMapper tecentMeetingMapper; private TecentMeetingMapper tecentMeetingMapper;
@Resource
private MeetingInfoMapper meetingInfoMapper;
private static final String HMAC_ALGORITHM = "HmacSHA256"; private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
...@@ -93,98 +101,114 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te ...@@ -93,98 +101,114 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
} }
@Override @Override
public List<TencentMeetingVO.RecordFile> getMeetingFiles(TencentMeetingVO.TencentMeetingRequest requestVO) { public List<TencentMeetingVO.RecordFile> getMeetingFiles() {
StringBuffer url = new StringBuffer();
url.append("https://api.meeting.qq.com/v1/corp/records?page_size="+requestVO.getPageSize()+"&page="+ requestVO.getPage()
+"&operator_id="+requestVO.getOperatorId()+"&operator_id_type=1&query_record_type=0");
// 1.构造 client 客户端(jwt 鉴权需要配置 appId sdkId secretID 和 secretKey)
Client client = new Client.Builder() Client client = new Client.Builder()
.withAppId(tencentAppId).withSdkId(tencentSdkId) .withAppId(tencentAppId).withSdkId(tencentSdkId)
.withSecret(tencentSecretId,tencentSecretKey) .withSecret(tencentSecretId,tencentSecretKey)
.build(); .build();
// 2.构造 JWT 鉴权器
AuthenticatorBuilder<JWTAuthenticator> authenticatorBuilder =
new JWTAuthenticator.Builder()
.nonce(BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt())))
.timestamp(String.valueOf(System.currentTimeMillis() / 1000L));
List<TencentMeetingVO.RecordFile> recordFileUrlList = new ArrayList<>(); List<TencentMeetingVO.RecordFile> recordFileUrlList = new ArrayList<>();
// 3.查询近两天的会议录制列表 List<MeetingInfo> meetingSaveList = new ArrayList<>();
// 查询近两天的会议录制列表
try { try {
ZonedDateTime now = ZonedDateTime.now(); ZonedDateTime now = ZonedDateTime.now();
String startTime = String.valueOf(now.minusDays(2).toEpochSecond());
String endTime = String.valueOf(now.toEpochSecond());
AtomicInteger currentPage = new AtomicInteger(1); AtomicInteger currentPage = new AtomicInteger(1);
RecordsApi.ApiV1RecordsGetRequest request = Long totalPage = 1L;
new RecordsApi.ApiV1RecordsGetRequest.Builder()
.operatorId(tencentAdminUserId)
.operatorIdType("1")
.startTime(String.valueOf(now.minusDays(2).toEpochSecond()))
.endTime(String.valueOf(now.toEpochSecond()))
.pageSize("20")
.page(String.valueOf(currentPage.getAndIncrement()))
.mediaSetType("0")
.queryRecordType("0")
.build();
RecordsApi.ApiV1RecordsGetResponse response =
client.records().v1RecordsGet(request, authenticatorBuilder);
V1RecordsGet200Response data = response.getData();
if (data != null && data.getRecordMeetings() != null && !data.getRecordMeetings().isEmpty()) {
List<V1RecordsGet200ResponseRecordMeetingsInner> meetings = data.getRecordMeetings();
//录制状态: //目前已存储的会议id
//1:录制中 List<String> meetingIds = meetingInfoMapper.getAllMeetingIds();
//2:转码中 while (currentPage.intValue() <= totalPage.intValue()){
//3:转码完成 RecordsApi.ApiV1RecordsGetRequest request =
if(meetings.stream().allMatch(item->item.getState() != 3)){ new RecordsApi.ApiV1RecordsGetRequest.Builder()
return null; .operatorId(tencentAdminUserId)
} .operatorIdType("1")
for (V1RecordsGet200ResponseRecordMeetingsInner meeting : meetings) { .startTime(startTime)
if(meeting.getState() != 3) continue; .endTime(endTime)
.pageSize("20")
.page(String.valueOf(currentPage.getAndIncrement()))
.mediaSetType("0")
.queryRecordType("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();
//查询会议详情 //录制状态:
String meetingId = meeting.getMeetingId(); //1:录制中
String subMeetingId = null; //2:转码中
MeetingsApi.ApiV1MeetingsMeetingIdGetRequest meetingRequest = //3:转码完成
new MeetingsApi.ApiV1MeetingsMeetingIdGetRequest.Builder(meetingId) if(meetings.stream().allMatch(item->item.getState() != 3)){
.operatorId(tencentAdminUserId) return null;
.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){
subMeetingId = meetingInfo.getCurrentSubMeetingId();
}
}
} catch (ClientException e) {
throw new RuntimeException(e);
} catch (ServiceException e) {
throw new RuntimeException(e);
} }
for (V1RecordsGet200ResponseRecordMeetingsInner meeting : meetings) {
if(meeting.getState() != 3) continue;
List<V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner> recordFiles = meeting.getRecordFiles(); //查询会议详情
for (V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner recordFile : recordFiles) { String meetingId = meeting.getMeetingId();
TencentMeetingVO.RecordFile recordFileItem = TencentMeetingVO.RecordFile.builder() String subMeetingId = null;
.recordFileId(recordFile.getRecordFileId()) MeetingsApi.ApiV1MeetingsMeetingIdGetRequest meetingRequest =
.meetingId(meetingId).subMeetingId(subMeetingId).build(); new MeetingsApi.ApiV1MeetingsMeetingIdGetRequest.Builder(meetingId)
recordFileUrlList.add(recordFileItem); .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){
subMeetingId = meetingInfo.getCurrentSubMeetingId();
}
//如果数据库中已有相同会议id的记录,跳过同步
if(!meetingIds.contains(meetingId)){
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()))
// .hostId(meetingInfo.getHosts().stream().map(V1MeetingsGet200ResponseMeetingInfoListInnerCurrentCoHostsInner::getUserid).collect(Collectors.joining(",")))
// .participantUserIds(meetingInfo.getParticipants().stream().map(V1MeetingsGet200ResponseMeetingInfoListInnerCurrentCoHostsInner::getUserid).collect(Collectors.joining(",")))
.isGenerated(Boolean.FALSE).emailPushAccess(Boolean.TRUE).isPushed(Boolean.FALSE).syncTime(LocalDateTime.now())
.subMeetingId(subMeetingId)
.build();
meetingSaveList.add(meetingItem);
List<V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner> recordFiles = meeting.getRecordFiles();
for (V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner recordFile : recordFiles) {
TencentMeetingVO.RecordFile recordFileItem = TencentMeetingVO.RecordFile.builder()
.recordFileId(recordFile.getRecordFileId()).meetingId(meetingId).subMeetingId(subMeetingId).build();
recordFileUrlList.add(recordFileItem);
}
}
}
} catch (ClientException e) {
throw new RuntimeException(e);
} catch (ServiceException e) {
throw new RuntimeException(e);
}
} }
} }
} }
if(meetingSaveList.size() > 0){
meetingInfoMapper.batchInsert(meetingSaveList);
}
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage()); log.error(e.getMessage());
throw new RuntimeException(e.getMessage()); throw new RuntimeException(e.getMessage());
......
package com.cmeeting.util;
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;
@Component
public class MinioUtils {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.bucketName}")
private String bucketName;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
private MinioClient minioClient;
public synchronized MinioClient getMinioClient() {
try {
if(minioClient == null){
minioClient = new MinioClient(endpoint, accessKey, secretKey);
}
return minioClient;
}catch (Exception e){
throw new RuntimeException(e);
}
}
public InputStream getFile(MinioClient minioClient, String fileName){
try{
InputStream inputStream = minioClient.getObject(bucketName, fileName);
return inputStream;
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void upload(String path,InputStream inputStream) throws Exception{
getMinioClient().putObject(bucketName, path, inputStream, "application/octet-stream" );
}
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);
}
}
public Boolean isFileExist(MinioClient minioClient, String fileName){
boolean found = false;
try{
InputStream inputStream = minioClient.getObject(bucketName, fileName);
found= true;
return found;
}catch (Exception e){
found=false;
return false;
}
}
}
server.port=8080
# ????????
tencent.meeting.token=QQZNb7xWQB47MpZF4C2DFAkv8
tencent.meeting.aesKey=agy6ALUePp34lljWz1uIQWa7yQq3dgxxQNmfaN9GROm
# application.yml
# spring.datasource.url=jdbc:mysql://192.168.10.155:3306/cmeeting?useSSL=false&characterEncoding=utf8
# spring.datasource.username=root
# spring.datasource.password=qizhi123
# spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# ?????primary?
spring.datasource.primary.jdbc-url=jdbc:mysql://192.168.10.155:3306/robot-standard-enterprise-aigc?useSSL=false&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.primary.username=root
spring.datasource.primary.password=qizhi123
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
# ?????secondary?
spring.datasource.secondary.jdbc-url=jdbc:mysql://192.168.10.155:3306/user-admin?useSSL=false&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.secondary.username=root
spring.datasource.secondary.password=qizhi123
spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
# MyBatis ??
# mybatis.mapper-locations=classpath:mapper/primary/*.xml
mybatis.type-aliases-package=com.cmeeting.pojo\
# ??????
mybatis.configuration.map-underscore-to-camel-case: true
logging.level.com.zaxxer.hikari=INFO
# tencent meeting
# local
tencent.appId=211153201
tencent.sdkId=28370276340
tencent.secretId=BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6
tencent.secretKey=3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO
#tencent.admin.userId=woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA
tencent.admin.userId=woaJARCQAAhkyWGuf8n9InhZsxQstjjA
# prod
#tencent.appId=210468336
#tencent.sdkId=28790143843
#tencent.secretId=0ks7u8cgQ8DGVtlYZeRA9TxZCjvUT3oL
#tencent.secretKey=gQU09rkJjiQfiGcUYdhiKq5Ol6LebXg4w7F7Ol0rwvvdv3Xy
#tencent.admin.userId=woaJARCQAAftcvU6GGoOn66rdSZ4IrOA
email.sender=cmeeting_assistant@cimc.com
email.sender.pwd=scyou@xih45g6@xih4
email.smtp.host=smtp.office365.com
server:
port: 8080
# ????????
# application.yml
# spring.datasource.url=jdbc:mysql://192.168.10.155:3306/cmeeting?useSSL=false&characterEncoding=utf8
# spring.datasource.username=root
# spring.datasource.password=qizhi123
# spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# ?????primary?
#spring.datasource.primary.jdbc-url=jdbc:mysql://192.168.10.154:3307/aigc-zhongji-test?useSSL=false&characterEncoding=utf8&serverTimezone=UTC
##spring.datasource.primary.username=root
##spring.datasource.primary.password=123456
##spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
##
### ?????secondary?
##spring.datasource.secondary.jdbc-url=jdbc:mysql://192.168.10.154:3307/useradmin-zhongji-test?useSSL=false&characterEncoding=utf8&serverTimezone=UTC
##spring.datasource.secondary.username=root
##spring.datasource.secondary.password=123456
##spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
spring:
datasource:
# 主数据源
master:
jdbc-url: jdbc:mysql://192.168.10.154:3307/aigc-zhongji-test?useSSL=false&characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
# 从数据源
slave:
jdbc-url: jdbc:mysql://192.168.10.154:3307/useradmin-zhongji-test?useSSL=false&characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
# MyBatis ??
# mybatis.mapper-locations=classpath:mapper/primary/*.xml
mybatis.type-aliases-package: com.cmeeting.pojo\
# ??????
mybatis:
configuration:
map-underscore-to-camel-case: true
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
# 也建议开启
global-config:
db-config:
column-underline: true
#logging:
# level:
# com:
# zaxxer:
# hikari: INFO
############################################################## minIO
MINIO_ADDRESS: http://192.168.10.154:9000
MINIO_BUCKET: zhongji
MINIO_USERNAME: minio
MINIO_PASSWORD: minio123
#Minio服务所在地址
minio.endpoint: ${MINIO_ADDRESS}
#存储桶名称
minio.bucketName: ${MINIO_BUCKET}
#访问的key
minio.accessKey: ${MINIO_USERNAME}
#访问的秘钥
minio.secretKey: ${MINIO_PASSWORD}
############################################################## minIO
############################################################## tencent meeting
# local
#tencent.appId=211153201
#tencent.sdkId=28370276340
#tencent.secretId=BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6
#tencent.secretKey=3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO
#tencent.admin.userId=woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA
#tencent.admin.userId=woaJARCQAAhkyWGuf8n9InhZsxQstjjA
# prod
tencent:
appId: 210468336
sdkId: 28790143843
secretId: 0ks7u8cgQ8DGVtlYZeRA9TxZCjvUT3oL
secretKey: gQU09rkJjiQfiGcUYdhiKq5Ol6LebXg4w7F7Ol0rwvvdv3Xy
admin.userId: woaJARCQAAftcvU6GGoOn66rdSZ4IrOA
meeting:
token: QQZNb7xWQB47MpZF4C2DFAkv8
aesKey: agy6ALUePp34lljWz1uIQWa7yQq3dgxxQNmfaN9GROm
email:
sender: cmeeting_assistant@cimc.com
sender-pwd: scyou@xih45g6@xih4
smtp-host: smtp.office365.com
############################################################## tencent meeting
logging:
level:
com.cmeeting.mapper.primary: TRACE
com.cmeeting.mapper.secondary: TRACE
root: DEBUG
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cmeeting.mapper.primary.MeetingInfoMapper">
<insert id="batchInsert" parameterType="list">
INSERT IGNORE INTO cmt_meeting_info (subject, meeting_id, meeting_code, host_id, participant_user_ids, start_time,
end_time, is_generated, email_push_access, is_pushed, sync_time, sub_meeting_id, record_content, record_xml)
VALUES
<foreach collection="meetingSaveList" item="meeting" separator=",">
(
#{meeting.subject},
#{meeting.meetingId},
#{meeting.meetingCode},
#{meeting.hostId},
#{meeting.participantUserIds},
#{meeting.startTime},
#{meeting.endTime},
#{meeting.isGenerated},
#{meeting.emailPushAccess},
#{meeting.isPushed},
#{meeting.syncTime},
#{meeting.subMeetingId},
#{meeting.recordContent},
#{meeting.recordXml}
)
</foreach>
</insert>
<select id="getAllMeetingIds" resultType="java.lang.String">
select meeting_id from cmt_meeting_info
</select>
</mapper>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论