提交 0a34c27f 作者: zhaibin

定时拉取已结束会议获取录制文件

父级 69b04a25
......@@ -7,18 +7,34 @@
<groupId>org.example</groupId>
<artifactId>tencent_callback</artifactId>
<version>1.0-SNAPSHOT</version>
<!--<profiles>
<profile>
<id>allow-snapshots</id>
<activation><activeByDefault>true</activeByDefault></activation>
<repositories>
<repository>
<id>snapshots-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases><enabled>false</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
</profile>
</profiles>-->
<!-- <activeProfiles>
<activeProfile>central</activeProfile>
<activeProfile>allow-snapshots</activeProfile>
</activeProfiles>-->
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 统一指定所有Azure库的版本 -->
<azure.version>1.10.0</azure.version>
<microsoft.graph.version>5.70.0</microsoft.graph.version>
<jackson.version>2.13.0</jackson.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Microsoft Graph SDK -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
......@@ -48,6 +64,38 @@
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph-auth</artifactId>
<version>0.3.0</version>
</dependency>
<dependency>
<!-- Include the sdk as a dependency -->
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph-core</artifactId>
<version>2.0.14</version>
</dependency>
<dependency>
<!-- Include the sdk as a dependency -->
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph</artifactId>
<version>5.42.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
......@@ -115,18 +163,8 @@
<artifactId>microsoft-graph-core</artifactId>
<version>2.0.14</version>
</dependency>
<dependency>
<!-- Include the sdk as a dependency -->
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph</artifactId>
<version>${microsoft.graph.version}</version>
</dependency>
<!-- MSAL认证库 -->
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.19.1</version>
</dependency>
<dependency>
<groupId>com.microsoft.ews-java-api</groupId>
......@@ -234,7 +272,7 @@
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version> <!-- 使用最新版本 -->
<version>20231013</version> <!-- 使用最新版本 -->
</dependency>
<!-- Jackson 核心模块 -->
<dependency>
......@@ -247,7 +285,28 @@
<artifactId>jackson-module-parameter-names</artifactId>
<version>2.13.0</version>
</dependency>
<!-- Markdown 处理 -->
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-all</artifactId>
<version>0.62.2</version>
</dependency>
<!-- Word 文档操作 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.17.1</version> <!-- 可升级至最新稳定版 -->
</dependency>
</dependencies>
</project>
\ No newline at end of file
......@@ -3,23 +3,25 @@ package com.cmeeting;
import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.nimbusds.jose.shaded.gson.Gson;
import okhttp3.*;
import com.google.gson.JsonObject;
import java.io.IOException;
public class GraphApiWithOkHttp {
static String CLIENT_ID = "d65aa91b-05f4-42b2-9/**/02f-4d82ea5a2b93";
static String CLIENT_ID = "d65aa91b-05f4-42b2-902f-4d82ea5a2b93";
static String TENANT_ID = "74cc8acf-aacc-4514-9bbb-dc27ce3096bb"; // or "common" for multi-tenant apps
static String CLIENT_SECRET = "~N98Q~83v6dViQFIofwr0fRn4J5VEZ2tvwOz.bPX";
private static final String USER_EMAIL = "binzhai321@outlook.com"; // 发送邮件的用户
private static final String USER_EMAIL = "biinizhai321@outlook.com"; // 发送邮件的用户
public static void main(String[] args) throws IOException {
/**中集:
* c06fe7cf-2a89-4099-9805-ce03031938f8
* wsu8Q~GxYxPLf2akioQZDRG8NR1EzCAHIAQRVc6u
* 18653b3e-03c7-499e-8baf-42ef06a814ef
*/
ClientSecretCredential credential = new ClientSecretCredentialBuilder()
.clientId("d65aa91b-05f4-42b2-902f-4d82ea5a2b93")
.clientSecret("~N98Q~83v6dViQFIofwr0fRn4J5VEZ2tvwOz.bPX")
.tenantId("74cc8acf-aacc-4514-9bbb-dc27ce3096bb")
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.tenantId(TENANT_ID)
.build();
// 1. 获取 Access Token
......@@ -32,7 +34,7 @@ public class GraphApiWithOkHttp {
String token1 ="EwBYBMl6BAAUBKgm8k1UswUNwklmy2v7U/S+1fEAAUa0jd3QNQBdqTy9ZwEaLPqqapU5zHFRz4XvGqbI7jN7x01mEvBtCSFgYtNiSOEb5s1UwLxl6HwlHFqfJsqDsoCicvnlqCWjsaR/4wPZgXxd+pBhHRSPl+tJcSzz2NJB+dy81wHcSaU4bRxCIgoU7MtrW2ak9DhGisnK4/vxHkzxuIjHe9IDOZBzwzAtTZhycWRoAtuQrC1iu/kuKGJaU9noNjsOwfHUaXRz68g6niSdPT8P6zOa2vlOHjhXh478ms1PhqKeTkS+nkTurT2Gc5qRJviICiiAlK4BlxV70tOA6xEtl2ApXOrfFr+rlv4pASmPewtbdkTHnpP3H0R9L0kQZgAAEF4HO14XaFxPCdIqniVBvSkgA75wLsTuUZragTXGu71SV7ccLi26nZ+9KZlulf8dAlj/NEdheqtdEHb+BOLknQoXFEJ0UNj6PLkczzHfV3Yr5zlXJICwb3yI9vrFTUCb555ux/P904vXCY900UNgX81gBIYJQIJtOCURDAcHWtxyWvfNlkr17fKgnX5KdDyec6JK9tgzPi62LlqhDcoR/W/4MTCZhfJRlZgdYkI52pQSbDJItR6W7xfZguXPgKRHVcZJTzlybtOW0kbrfv96CKrryKzGeRjZyEcov1U3VjAoBUllgi3+LdIFK86aojMuBP+v5PvRq81qZS2q5roIRJD5zydu1selrpwhrZjm2nupDxHGjvfj2TVZJlz1zGYsVYrSRz2+st+2UbZagR1fbPRX2GilTfDyN05HV//LrfdrddbffMImxY2M8D9iQZFSjBYSFa20S9Vs9/yj08M9ljwiJifHGjX2o/arR63SWe3pbga26EC+j8JaBemhAhPsu91xW4o7+j/xEUadXR2NDbuogMq+MeINKe3PqAAFeTva1ZWZF5p3oGVIQKL4pcQFA9xEwopoN+vSeHDuAOvTFQ/4PukxicJdFTiaZquGeQMF0sLFvLdddpvS3Xaf17fPxjMUlMGcjYQ8gK5XG5wzjR8Up4MZZyX3xiK/hccGOp5pwi8WAN5cVmTKZOSXTkHUGEQT4rcGUYTPduHYMjjrVpzffpnmqfO92VA7a7fuYqXKp3NPikv17s6PTVr6b4+24AP8u3MEhDHU2YjaMuFR3jBUYLxtm443Gn58MC7oYrmQIVN2VYJrxADgStqpE+3nUCyKajmF0q5U+LUI1cCYnOKgmAkrpN71qlB9GeQLLQDJytG2sg64NG/h3CN1SseudSYtmqM3XUURSsztB9dNL7xmHQAcEBYMVjec5N5jHLhIs48rxAqI/WAJ/2RFKWgJIeCUJ8v4oQYEI9lAlWgReVhrjcNxRsbuVDFFwU7cTSeY9bJzxSX5kWJ8TXr/Q/SELEViUVCz10Cw/sOtm4urke538R4yXlR1wfMk3S86xlOfc3HJ6Iz7hi1h2fovWA9T1ceFWQM=";
// 2. 使用 OkHttp 发送邮件
sendEmail(token1);
sendEmail(token);
}
......@@ -52,7 +54,7 @@ public class GraphApiWithOkHttp {
" \"toRecipients\": [\n" +
" {\n" +
" \"emailAddress\": {\n" +
" \"address\": \"mengfan@itcast.cn\"\n" +
" \"address\": \"binzhai321@outlook.com\"\n" +
" }\n" +
" }\n" +
" ]\n" +
......@@ -62,7 +64,7 @@ public class GraphApiWithOkHttp {
// 构建请求
Request request = new Request.Builder()
.url("https://graph.microsoft.com/v1.0/users/" + USER_EMAIL + "/sendMail")
.post(RequestBody.create(emailJson, MediaType.parse("application/json")))
.post(RequestBody.create(MediaType.parse("application/json"), emailJson))
.addHeader("authorization", "Bearer " + accessToken)
.addHeader("Content-Type", "application/json")
.build();
......
package com.cmeeting;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.requests.AttachmentCollectionPage;
import com.microsoft.graph.requests.AttachmentCollectionResponse;
import com.microsoft.graph.requests.GraphServiceClient;
import com.microsoft.graph.models.*;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Base64;
import java.util.LinkedList;
public class OutlookTest {
// 创建日志记录器
private static final Logger logger = LoggerFactory.getLogger(OutlookTest.class);
public static void main(String[] args) throws Exception {
// Azure AD 应用程序的客户端凭证
String clientId = "d65aa91b-05f4-42b2-902f-4d82ea5a2b93";
String clientSecret = "~N98Q~83v6dViQFIofwr0fRn4J5VEZ2tvwOz.bPX";
String tenantId = "74cc8acf-aacc-4514-9bbb-dc27ce3096bb";
String scope = "https://graph.microsoft.com/.default";
// 使用 ClientSecretCredential 获取 token
ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
.clientId(clientId).tenantId(tenantId).clientSecret(clientSecret).build();
if (null == scope || null == clientSecretCredential) {
throw new Exception("Unexpected error");
}
// 创建身份验证提供者
IAuthenticationProvider authProvider = request -> {
// 创建 TokenRequestContext 对象并指定所需的范围
TokenRequestContext requestContext = new TokenRequestContext();
requestContext.addScopes("https://graph.microsoft.com/.default"); // 这里指定需要的范围
// 获取访问令牌
String accessToken = clientSecretCredential.getToken(requestContext).block().getToken();
// SDK 会自动将令牌添加到请求头中,您不需要手动操作 request 对象
return null;
};
// 创建 Graph 客户端
GraphServiceClient<Request> graphClient = GraphServiceClient
.builder()
.authenticationProvider(authProvider)
.buildClient();
// 打印访问令牌
printAccessToken(clientSecretCredential);
/* // 启用调试日志
GraphServiceClient<okhttp3.Request> graphClient = GraphServiceClient
.builder()
.authenticationProvider(tokenCredAuthProvider)
.httpClient(createHttpClient()) // 设置启用调试日志的 HTTP 客户端
.buildClient();*/
// 调用 Graph API 发送邮件
test(graphClient);
}
static void printAccessToken(ClientSecretCredential clientSecretCredential) {
try {
TokenRequestContext requestContext = new TokenRequestContext();
requestContext.addScopes("https://graph.microsoft.com/.default");
// 获取访问令牌
AccessToken accessToken = clientSecretCredential.getToken(requestContext).block();
if (accessToken != null && !accessToken.getToken().isEmpty()) {
System.out.println("Access Token: " + accessToken.getToken());
System.out.println("Expires On: " + accessToken.getExpiresAt().toEpochSecond());
} else {
System.out.println("Failed to retrieve access token.");
}
} catch (Exception e) {
System.err.println("Error while retrieving access token: " + e.getMessage());
}
}
public static void test(GraphServiceClient<okhttp3.Request> graphClient) throws IOException {
try {
// 创建邮件对象
Message message = new Message();
message.subject = "Meet for lunch?";
ItemBody body = new ItemBody();
body.contentType = BodyType.TEXT;
body.content = "The new cafeteria is open.";
message.body = body;
// 设置收件人
LinkedList<Recipient> toRecipientsList = new LinkedList<>();
Recipient toRecipients = new Recipient();
EmailAddress emailAddress = new EmailAddress();
emailAddress.address = "binzhai321@outlook.com";
toRecipients.emailAddress = emailAddress;
toRecipientsList.add(toRecipients);
message.toRecipients = toRecipientsList;
// 创建附件
LinkedList<Attachment> attachmentsList = new LinkedList<>();
FileAttachment attachments = new FileAttachment();
attachments.name = "1111.txt";
attachments.oDataType = "#microsoft.graph.fileAttachment";
attachments.contentType = "text/plain";
attachments.contentBytes = Base64.getDecoder().decode("SGVsbG8gV29ybGQh");
attachmentsList.add(attachments);
AttachmentCollectionResponse attachmentCollectionResponse = new AttachmentCollectionResponse();
attachmentCollectionResponse.value = attachmentsList;
AttachmentCollectionPage attachmentCollectionPage = new AttachmentCollectionPage(attachmentCollectionResponse, null);
message.attachments = attachmentCollectionPage;
// 发送邮件
graphClient.users("binzhai321@outlook.com")
.sendMail(UserSendMailParameterSet.newBuilder()
.withMessage(message)
.withSaveToSentItems(null)
.build())
.buildRequest()
.post();
System.out.println("Email sent successfully!");
} catch (Exception e) {
System.err.println("Error occurred: " + e.getMessage());
if (e instanceof com.microsoft.graph.http.GraphServiceException) {
com.microsoft.graph.http.GraphServiceException graphException = (com.microsoft.graph.http.GraphServiceException) e;
System.err.println("Error code: " + graphException.getServiceError().code);
System.err.println("Error message: " + graphException.getServiceError().message);
}
e.printStackTrace();
}
}
private static OkHttpClient createHttpClient() {
return new OkHttpClient.Builder()
.addInterceptor(chain -> {
okhttp3.Request originalRequest = chain.request();
// 打印原始请求头
System.out.println("Original Request Headers: " + originalRequest.headers());
// 确保 Authorization 头未被覆盖
okhttp3.Request modifiedRequest = originalRequest.newBuilder()
.headers(originalRequest.headers()) // 保留原始请求头
.build();
return chain.proceed(modifiedRequest);
})
.build();
}
}
package com.cmeeting;
import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.http.GraphServiceException;
import com.microsoft.graph.models.Message;
import com.microsoft.graph.requests.GraphServiceClient;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import com.microsoft.graph.models.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
public class SendMailExample {
private static final Logger logger = LoggerFactory.getLogger(OutlookTest.class);
public static void main(String[] args) {
// 1. Azure AD 配置
final String clientId = "d65aa91b-05f4-42b2-902f-4d82ea5a2b93";
final String tenantId = "74cc8acf-aacc-4514-9bbb-dc27ce3096bb";
final String clientSecret = "~N98Q~83v6dViQFIofwr0fRn4J5VEZ2tvwOz.bPX";
// 2. 创建认证凭据
ClientSecretCredential credential = new ClientSecretCredentialBuilder()
.clientId(clientId)
.tenantId(tenantId)
.clientSecret(clientSecret)
.build();
String token = credential.getToken(
new TokenRequestContext()
.addScopes("https://graph.microsoft.com/.default"))
.block().getToken();
System.out.println("获取到的令牌:"+ token);
// 3. 初始化认证提供者
TokenCredentialAuthProvider authProvider = new TokenCredentialAuthProvider(
Collections.singletonList("https://graph.microsoft.com/.default"),
credential);
// 4. 创建 Graph 客户端
GraphServiceClient<Request> graphClient = GraphServiceClient.builder()
.authenticationProvider(authProvider)
.httpClient(createHttpClient())
.buildClient();
// 5. 构建邮件内容
Message message = new Message();
message.subject = "Test from Java 8";
ItemBody body = new ItemBody();
body.contentType = BodyType.TEXT;
body.content = "This works with Java 8!";
message.body = body;
EmailAddress emailAddress = new EmailAddress();
emailAddress.address = "binzhai321@outlook.com";
Recipient recipient = new Recipient();
recipient.emailAddress = emailAddress;
message.toRecipients = Collections.singletonList(recipient);
// 最兼容的发送方式
try {
// 创建参数集
UserSendMailParameterSet parameterSet = UserSendMailParameterSet.newBuilder()
.withMessage(message)
.withSaveToSentItems(true)
.build();
// 指定目标用户的 ID 或邮箱地址
String userId = "binzhai321@outlook.com";
// 发送邮件
graphClient.users(userId)
.sendMail(parameterSet)
.buildRequest()
.post();
System.out.println("Email sent successfully!");
} catch (GraphServiceException e) {
System.err.println("Error code: " + e.getServiceError().code);
System.err.println("Error message: " + e.getServiceError().message);
System.err.println("Headers: " + e.getRequestHeaders());
System.out.println(e.getMessage());
e.printStackTrace();
}
}
private static OkHttpClient createHttpClient() {
return new OkHttpClient.Builder()
.addInterceptor(chain -> {
okhttp3.Request request = chain.request();
// 打印请求信息
System.out.println("--> " + request.method() + " " + request.url());
if (request.body() != null) {
System.out.println("Request Body: " + request.body().toString());
}
if (request.headers() != null) {
System.out.println("Request Headers: " + request.headers().toString());
}
okhttp3.Response response = chain.proceed(request);
// 打印响应信息
if (response.body() != null) {
String responseBody = response.body().string();
System.out.println("<-- " + response.code() + " " + response.message() + " " + response.request().url());
System.out.println("Response Body: " + responseBody);
// 重新包装响应体
response = response.newBuilder()
.body(okhttp3.ResponseBody.create(responseBody, okhttp3.MediaType.parse("application/json")))
.build();
}
return response;
})
.build();
}
}
\ No newline at end of file
......@@ -3,8 +3,10 @@ package com.cmeeting;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class TencentMeetingCallbackApplication {
public static void main(String[] args) {
SpringApplication.run(TencentMeetingCallbackApplication.class, args);
......
package com.cmeeting;
import com.alibaba.fastjson2.JSON;
import com.cmeeting.email.EmailSender;
import com.cmeeting.mapper.primary.AuthMapper;
import com.cmeeting.mapper.primary.UserCompanyMapper;
import com.cmeeting.mapper.primary.UserIdMapper;
import com.cmeeting.mapper.secondary.SysUserMapper;
import com.cmeeting.mapper.secondary.SysUserSysMapper;
import com.cmeeting.pojo.CoreModulePermissions;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
......@@ -19,12 +20,14 @@ import com.tencentcloudapi.wemeet.service.meeting_control.api.MeetingControlApi;
import com.tencentcloudapi.wemeet.service.meeting_control.model.V1RealControlMeetingsMeetingIdAsrPutRequest;
import com.tencentcloudapi.wemeet.service.meetings.api.MeetingsApi;
import com.util.meeting.MeetingMinutesGenerator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
......@@ -42,27 +45,33 @@ import java.util.stream.Collectors;
@RequestMapping
public class TencentMeetingCallbackController {
private static final Logger logger = LoggerFactory.getLogger(TencentMeetingCallbackController.class);
/**
* *
* * 配置参数 - 从配置文件或环境变量获取
* * 用于校验腾讯会议的事件订阅
*/
// 配置参数 - 从配置文件或环境变量获取
private final String token = "abIDNnBYRXMIZ5kbLjzjQ8Xz3";
private final String encodingAESKey = "1S47Q21Yz6Bdcxm5obrer4JEeeT61NW2WJCPV7C7Xz6";
//tencent.meeting.token
private final String token = "Jo6X4sZKhPFknSdBH8o6gXVOb";
//tencent.meeting.aesKey
private final String encodingAESKey = "AhES8VLIoktG4KGPqgKz6uUMtvs67JZWCuStmogMAwr";
// 1.构造 client 客户端(jwt 鉴权需要配置 appId sdkId secretID 和 secretKey)
Client client = new Client.Builder()
.withAppId("210468336").withSdkId("28790143843")
.withSecret("0ks7u8cgQ8DGVtlYZeRA9TxZCjvUT3oL", "gQU09rkJjiQfiGcUYdhiKq5Ol6LebXg4w7F7Ol0rwvvdv3Xy")
.withAppId("211153201").withSdkId("28370276340")
.withSecret("BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6", "3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO")
.build();
@Autowired
private UserIdMapper userIdMapper;
@Autowired
private UserCompanyMapper userCompanyMapper;
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private AuthMapper authMapper;
/**
* 处理GET请求 - URL验证
*/
@Autowired
private SysUserSysMapper sysUserSysMapper;
//处理GET请求 - URL验证
@GetMapping
public ResponseEntity<String> verifyUrl(
@RequestParam("check_str") String checkStr,
......@@ -91,10 +100,9 @@ public class TencentMeetingCallbackController {
}
}
/**
* 处理POST请求 - 事件回调
*/
//处理POST请求 -事件回调
@PostMapping(consumes = "application/json")
public ResponseEntity<String> handleEvent(
@RequestBody Map<String, String> requestBody,
@RequestHeader("timestamp") String timestamp,
......@@ -123,10 +131,10 @@ public class TencentMeetingCallbackController {
String userId = firstPayload.path("operator").path("userid").asText();
// 通过腾讯的userid查询到企业微信的userid
String wid = userIdMapper.getWidByTid(userId);
logger.info("该用户的工号id:{},",wid);
logger.info("该用户的工号id:{},", wid);
// 调用admin的find接口查询用户信息包括用户的部门id路径
String userDeptPath = sysUserMapper.getUserDeptPath(wid);
logger.info("该用户的部门路径:{},",userDeptPath);
logger.info("该用户的部门路径:{},", userDeptPath);
List<String> deptPath = null;
if (userDeptPath != null && !userDeptPath.isEmpty()) {
// 使用split方法分割字符串,并过滤掉空字符串
......@@ -135,11 +143,10 @@ public class TencentMeetingCallbackController {
.collect(Collectors.toList());
}
logger.info("分割后的部门路径列表:{}", deptPath);
//todo authList接口对应方法获取语音助手的权限的用户和部门列表
//通过智能体id查询该id下的部门和用户
String targetId = "1815393211829587968";
String teantId = "1806976109082972160";
List<CoreModulePermissions> auths = authMapper.getAuthByTargrtId(targetId,teantId);
String targetId = "1815393211829587968";//职能体id
String tenantId = "1806976109082972160";//租户id
List<CoreModulePermissions> auths = authMapper.getAuthByTargrtId(targetId, tenantId);
// 创建两个集合分别存储type=0(部门)和type=1(员工)的数据
List<CoreModulePermissions> type0List = new ArrayList<>();
List<CoreModulePermissions> type1List = new ArrayList<>();
......@@ -172,7 +179,7 @@ public class TencentMeetingCallbackController {
}
}
}
if (hasPermission){
if (hasPermission) {
processEvent(decryptedData);
}
} catch (Exception e) {
......@@ -186,9 +193,10 @@ public class TencentMeetingCallbackController {
}
}
/**
* 验证签名(SHA1)
*/
//验证签名(SHA1)
private boolean verifySignature(String token, String timestamp, String nonce,
String data, String receivedSignature) throws Exception {
// 1. 按字典序排序
......@@ -216,9 +224,7 @@ public class TencentMeetingCallbackController {
return hexStr.toString().equals(receivedSignature);
}
/**
* AES解密数据
*/
//AES解密数据
private String decryptData(String encryptedData) throws Exception {
// 1. Base64解码
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
......@@ -252,9 +258,7 @@ public class TencentMeetingCallbackController {
private final Set<String> startProcessedEvents = Collections.synchronizedSet(new HashSet<>());
private final Set<String> sendProcessedEvents = Collections.synchronizedSet(new HashSet<>());
/**
* 处理事件业务逻辑
*/
//处理事件业务逻辑
private void processEvent(String decryptedData) {
// 这里可以解析JSON并处理具体业务
// 示例: 打印事件数据
......@@ -388,7 +392,6 @@ public class TencentMeetingCallbackController {
String savePath = "D:/" + meetingId + ".docx";
String targetPath = "D:/" + meetingId + "纪要" + ".docx";
// 下载文件
// System.out.println("开始下载文件到: " + savePath);
logger.info("开始下载文件到: {}", savePath);
try (java.io.BufferedInputStream in = new java.io.BufferedInputStream(new java.net.URL(downloadUrl).openStream());
java.io.FileOutputStream fileOutputStream = new java.io.FileOutputStream(savePath)) {
......@@ -398,10 +401,7 @@ public class TencentMeetingCallbackController {
fileOutputStream.write(dataBuffer, 0, bytesRead);
}
logger.info("文件下载完成,已保存到 {}", savePath);
/**
* 将文件传送给大模型处理
*/
//todo
//将文件传送给大模型处理
try {
boolean success = MeetingMinutesGenerator.generateMinutesFromWord(
savePath,
......@@ -411,22 +411,23 @@ public class TencentMeetingCallbackController {
);
if (success) {
//System.out.println("会议纪要生成成功!");
logger.info("会议纪要生成成功!");
}
} catch (IOException e) {
//System.err.println("错误: " + e.getMessage());
logger.error("错误: {}", e.getMessage());
}
/**
* 使用邮箱发送邮件
*/
//使用邮箱发送邮件
// 1.获得主持人的腾讯会议userid
// 2.根据获得的腾讯会议userid获得员工的企业微信userid
// 3.根据企业微信userid查询员工表获得员工邮箱
String tid = operatorUserId;
String wid = userIdMapper.getWidByTid(tid);
String emailAddress = userCompanyMapper.selectEmailByUserId(wid);
String tenantId = "1806976109082972160";
String emailAddress = sysUserSysMapper.getCompanyEmail(wid, tenantId);
if (StringUtils.isEmpty(emailAddress)) {
logger.info("用户:{}", wid, "没有企业邮箱");
return;
}
Thread.sleep(10000);
EmailSender emailSender = new EmailSender();
//response.getRawBody()
......
......@@ -200,6 +200,9 @@ public class UnifiedController {
* @return
*/
private String getMeetingHost(String meetingCode) {
/**
* 腾讯会议参数
*/
Client client = new Client.Builder()
.withAppId("211153201").withSdkId("28370276340")
.withSecret("BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6", "3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO")
......@@ -293,7 +296,6 @@ public class UnifiedController {
// 创建预约会议(传入 wid 作为主持人)
private static Map<String, String> createMeeting(String wid, String weComToken) throws IOException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/meeting/create?access_token=" + weComToken;
JSONObject body = new JSONObject()
.put("admin_userid", wid) // 主持人 userid
.put("title", "自动创建会议")
......@@ -319,10 +321,8 @@ public class UnifiedController {
return result;
}
}
public List<UserId> mergeUserLists(List<WeComUser> weComUsersIsRepeat0, List<TencentMeetingUser> tencentUsersIsRepeat0) {
List<UserId> userIds = new ArrayList<>();
// 1. 首先构建姓名到TencentMeetingUser的映射(提高查找效率)
Map<String, TencentMeetingUser> tencentUserMap = tencentUsersIsRepeat0.stream()
.collect(Collectors.toMap(
......@@ -335,7 +335,6 @@ public class UnifiedController {
UserId userId = new UserId();
userId.setUserName(weComUser.getUserName());
userId.setWid(weComUser.getUserId()); // 始终设置WeCom userid
// 检查是否存在同名Tencent用户
TencentMeetingUser tencentUser = tencentUserMap.get(weComUser.getUserName());
if (tencentUser != null) {
......
......@@ -24,7 +24,9 @@ import static com.cmeeting.WeComAndTencentMeeting.*;
@RequestMapping("/wecom")
public class WeComcontroller {
private static final String BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist";
private static final String ACCESS_TOKEN = "wyYAmCbeVIPvj_Gpdd8ixp_td_hPv2OtTU8gpTGSpB7TaftbpNps5fc_JdCRzpgy_tUv-aGueJpRV6XJeYAG-rqkzCfDV-rcpJbqB0LypjvJUtxPeTmPqEtQuCMwH6euGGVaJZXoc33ATrS70U0T4_4WIrATqpsN2Ed2XsNGyN5N6aG8mq6AyLMG9TokVWx1qd2qF1zVTwve6aqRMWHwXg";
//动态获取token
@Autowired
private WeComService weComService;
......
package com.cmeeting.email;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.models.*;
import com.microsoft.graph.requests.GraphServiceClient;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.*;
import java.io.File;
import java.util.Arrays;
import java.util.LinkedList;
public class AzureADEmailSender {
public static void main(String[] args) throws IOException {
//d65aa91b-05f4-42b2-902f-4d82ea5a2b93
//~N98Q~83v6dViQFIofwr0fRn4J5VEZ2tvwOz.bPX
//74cc8acf-aacc-4514-9bbb-dc27ce3096bb
/**中集
* c06fe7cf-2a89-4099-9805-ce03031938f8
* wsu8Q~GxYxPLf2akioQZDRG8NR1EzCAHIAQRVc6u
* 18653b3e-03c7-499e-8baf-42ef06a814ef
*/
ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
.clientId("d65aa91b-05f4-42b2-902f-4d82ea5a2b93")
.clientSecret("~N98Q~83v6dViQFIofwr0fRn4J5VEZ2tvwOz.bPX")
.tenantId("74cc8acf-aacc-4514-9bbb-dc27ce3096bb")
.build();
final TokenCredentialAuthProvider tokenCredAuthProvider = new TokenCredentialAuthProvider(Arrays.asList("https://graph.microsoft.com/.default"), clientSecretCredential);
System.out.println("First Step Reached. ");
buildDraftMessage(tokenCredAuthProvider);
}
private static final long MB = 1024 * 1024;
//binzhai321@outlook.com
//cmeeting_assistant@cimc.com
private static final String SENDER_MAIL = "binzhai321@outlook.com";
private static final String RECIPIENT_MAIL = "binzhai321@outlook.com";
public static void buildDraftMessage(TokenCredentialAuthProvider authProvider) throws IOException {
GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
Message message = new Message();
message.subject = "Did you see last night's game?";
ItemBody body = new ItemBody();
body.contentType = BodyType.HTML;
body.content = "They were <b>awesome</b>!";
message.body = body;
LinkedList<Recipient> toRecipientsList = new LinkedList<Recipient>();
Recipient toRecipients = new Recipient();
EmailAddress emailAddress = new EmailAddress();
emailAddress.address = RECIPIENT_MAIL;
toRecipients.emailAddress = emailAddress;
toRecipientsList.add(toRecipients);
message.toRecipients = toRecipientsList;
//构建草稿
Message post = graphClient.users(SENDER_MAIL).messages()
.buildRequest()
.post(message);
//构建附件
buildAttach(authProvider, post);
//发送草稿邮件
graphClient.users(SENDER_MAIL).messages(post.id)
.send()
.buildRequest()
.post();
}
private static void buildAttach(IAuthenticationProvider authProvider, Message message) throws IOException {
File file = new File("path");
FileInputStream fileInputStream = new FileInputStream(file);
int available = fileInputStream.available();
if (available >= 3 * MB) {
//附件大于3M,使用大附件专用发送方法
bigAttach(authProvider, message, file);
} else {
//附件小于3M,使用普通发送方法
commonAttach(authProvider, message, fileInputStream);
}
}
public static byte[] toByteArray(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n = 0;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
}
return output.toByteArray();
}
private static void commonAttach(IAuthenticationProvider authProvider, Message message, FileInputStream fileInputStream) throws IOException {
GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
FileAttachment attachment = new FileAttachment();
attachment.oDataType = "#microsoft.graph.fileAttachment";
attachment.name = "smile.pdf";
attachment.contentBytes = toByteArray(fileInputStream);
graphClient.users(SENDER_MAIL).messages(message.id).attachments()
.buildRequest()
.post(attachment);
}
public static void bigAttach(IAuthenticationProvider authProvider, Message message, File file) throws IOException {
GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
FileInputStream fileInputStream = new FileInputStream(file);
int available = fileInputStream.available();
AttachmentItem attachmentItem = new AttachmentItem();
attachmentItem.attachmentType = AttachmentType.FILE;
attachmentItem.name = "flower.pdf";
attachmentItem.size = (long) available;
UploadSession uploadSession = graphClient.users(SENDER_MAIL).messages(message.id).attachments()
.createUploadSession(AttachmentCreateUploadSessionParameterSet
.newBuilder()
.withAttachmentItem(attachmentItem)
.build())
.buildRequest()
.post();
OkHttpClient client = new OkHttpClient().newBuilder().build();
MediaType mediaType = MediaType.parse("application/octet-stream");
RequestBody body = RequestBody.create(mediaType, file);
okhttp3.Request request = new okhttp3.Request.Builder()
.url(uploadSession.uploadUrl)
.method("PUT", body)
.addHeader("Content-Length", String.valueOf(attachmentItem.size))
.addHeader("Content-Range", "bytes 0-" + (attachmentItem.size - 1) + "/" + attachmentItem.size)
.addHeader("Content-Type", "application/octet-stream")
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
}
}
\ No newline at end of file
package com.cmeeting;
package com.cmeeting.email;
import javax.mail.*;
import javax.mail.internet.*;
import java.util.Properties;
import java.io.File;
import java.util.Properties;
public class EmailSender {
// 发件人邮箱配置(建议从配置文件读取,此处写死示例)
private static final String FROM_EMAIL = "binzhai321@163.com";
private static final String AUTH_CODE = "RXbRu3AdrCxX57ib"; // 授权码
......@@ -85,4 +85,5 @@ public class EmailSender {
public static boolean sendEmailWithAttachment(String toEmail, String filePath) {
return sendEmailWithAttachment(toEmail, filePath, null, null);
}
}
\ No newline at end of file
package com.cmeeting.email;
import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import okhttp3.*;
import java.io.IOException;
public class GraphApiWithOkHttp2 {
static String CLIENT_ID = "d65aa91b-05f4-42b2-902f-4d82ea5a2b93";
static String TENANT_ID = "74cc8acf-aacc-4514-9bbb-dc27ce3096bb"; // or "common" for multi-tenant apps
static String CLIENT_SECRET = "~N98Q~83v6dViQFIofwr0fRn4J5VEZ2tvwOz.bPX";
//"cmeeting_assistant@cimc.com"
//"binzhai321@outlook.com"
private static final String USER_EMAIL = "cmeeting_assistant@cimc.com"; // 发送邮件的用户
public static void main(String[] args) throws IOException {
/**中集:
* c06fe7cf-2a89-4099-9805-ce03031938f8
* wsu8Q~GxYxPLf2akioQZDRG8NR1EzCAHIAQRVc6u
* 18653b3e-03c7-499e-8baf-42ef06a814ef
*/
ClientSecretCredential credential = new ClientSecretCredentialBuilder()
.clientId("c06fe7cf-2a89-4099-9805-ce03031938f8")
.clientSecret("wsu8Q~GxYxPLf2akioQZDRG8NR1EzCAHIAQRVc6u")
.tenantId("18653b3e-03c7-499e-8baf-42ef06a814ef")
.build();
// 1. 获取 Access Token
String token = credential.getToken(
new TokenRequestContext()
.addScopes("https://graph.microsoft.com/.default"))
.block().getToken();
System.out.println("获取到的令牌: " + token);
// String token1 ="EwBYBMl6BAAUBKgm8k1UswUNwklmy2v7U/S+1fEAAUa0jd3QNQBdqTy9ZwEaLPqqapU5zHFRz4XvGqbI7jN7x01mEvBtCSFgYtNiSOEb5s1UwLxl6HwlHFqfJsqDsoCicvnlqCWjsaR/4wPZgXxd+pBhHRSPl+tJcSzz2NJB+dy81wHcSaU4bRxCIgoU7MtrW2ak9DhGisnK4/vxHkzxuIjHe9IDOZBzwzAtTZhycWRoAtuQrC1iu/kuKGJaU9noNjsOwfHUaXRz68g6niSdPT8P6zOa2vlOHjhXh478ms1PhqKeTkS+nkTurT2Gc5qRJviICiiAlK4BlxV70tOA6xEtl2ApXOrfFr+rlv4pASmPewtbdkTHnpP3H0R9L0kQZgAAEF4HO14XaFxPCdIqniVBvSkgA75wLsTuUZragTXGu71SV7ccLi26nZ+9KZlulf8dAlj/NEdheqtdEHb+BOLknQoXFEJ0UNj6PLkczzHfV3Yr5zlXJICwb3yI9vrFTUCb555ux/P904vXCY900UNgX81gBIYJQIJtOCURDAcHWtxyWvfNlkr17fKgnX5KdDyec6JK9tgzPi62LlqhDcoR/W/4MTCZhfJRlZgdYkI52pQSbDJItR6W7xfZguXPgKRHVcZJTzlybtOW0kbrfv96CKrryKzGeRjZyEcov1U3VjAoBUllgi3+LdIFK86aojMuBP+v5PvRq81qZS2q5roIRJD5zydu1selrpwhrZjm2nupDxHGjvfj2TVZJlz1zGYsVYrSRz2+st+2UbZagR1fbPRX2GilTfDyN05HV//LrfdrddbffMImxY2M8D9iQZFSjBYSFa20S9Vs9/yj08M9ljwiJifHGjX2o/arR63SWe3pbga26EC+j8JaBemhAhPsu91xW4o7+j/xEUadXR2NDbuogMq+MeINKe3PqAAFeTva1ZWZF5p3oGVIQKL4pcQFA9xEwopoN+vSeHDuAOvTFQ/4PukxicJdFTiaZquGeQMF0sLFvLdddpvS3Xaf17fPxjMUlMGcjYQ8gK5XG5wzjR8Up4MZZyX3xiK/hccGOp5pwi8WAN5cVmTKZOSXTkHUGEQT4rcGUYTPduHYMjjrVpzffpnmqfO92VA7a7fuYqXKp3NPikv17s6PTVr6b4+24AP8u3MEhDHU2YjaMuFR3jBUYLxtm443Gn58MC7oYrmQIVN2VYJrxADgStqpE+3nUCyKajmF0q5U+LUI1cCYnOKgmAkrpN71qlB9GeQLLQDJytG2sg64NG/h3CN1SseudSYtmqM3XUURSsztB9dNL7xmHQAcEBYMVjec5N5jHLhIs48rxAqI/WAJ/2RFKWgJIeCUJ8v4oQYEI9lAlWgReVhrjcNxRsbuVDFFwU7cTSeY9bJzxSX5kWJ8TXr/Q/SELEViUVCz10Cw/sOtm4urke538R4yXlR1wfMk3S86xlOfc3HJ6Iz7hi1h2fovWA9T1ceFWQM=";
// 2. 使用 OkHttp 发送邮件
sendEmail(token);
}
// 使用 Graph API 发送邮件
private static void sendEmail(String accessToken) throws IOException {
OkHttpClient client = new OkHttpClient();
// 构建邮件内容 (JSON 格式)
String emailJson = "{\n" +
" \"message\": {\n" +
" \"subject\": \"Test Email\",\n" +
" \"body\": {\n" +
" \"contentType\": \"Text\",\n" +
" \"content\": \"This is a test email.\"\n" +
" },\n" +
" \"toRecipients\": [\n" +
" {\n" +
" \"emailAddress\": {\n" +
" \"address\": \"binzhai321@outlook.com\"\n" +
" }\n" +
" }\n" +
" ]\n" +
" }\n" +
"}";
// 构建请求
Request request = new Request.Builder()
.url("https://graph.microsoft.com/v1.0/users/" + USER_EMAIL + "/sendMail")
.post(RequestBody.create(MediaType.parse("application/json"), emailJson))
.addHeader("authorization", "Bearer " + accessToken)
.addHeader("Content-Type", "application/json")
.build();
// 发送请求
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
System.out.println("Email sent successfully!");
} else {
System.err.println("Failed to send email: " + response.code() + " - " + response.message());
System.err.println(response.body().string()); // 打印错误详情
}
}
}
}
package com.cmeeting;
package com.cmeeting.email;
import javax.activation.DataHandler;
import javax.activation.DataSource;
......
package com.cmeeting.email;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Preconditions;
import microsoft.exchange.webservices.data.core.ExchangeService;
import microsoft.exchange.webservices.data.core.enumeration.misc.ConnectingIdType;
import microsoft.exchange.webservices.data.core.enumeration.misc.TraceFlags;
import microsoft.exchange.webservices.data.core.enumeration.property.BodyType;
import microsoft.exchange.webservices.data.core.enumeration.property.Importance;
import microsoft.exchange.webservices.data.core.request.HttpWebRequest;
import microsoft.exchange.webservices.data.core.service.item.EmailMessage;
import microsoft.exchange.webservices.data.credential.ExchangeCredentials;
import microsoft.exchange.webservices.data.misc.ImpersonatedUserId;
import microsoft.exchange.webservices.data.property.complex.EmailAddress;
import microsoft.exchange.webservices.data.property.complex.MessageBody;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
public class TestExchangeAndOauth2 {
public static void main(String[] args) throws Exception {
String mailAddress = "cmeeting_assistant@cimc.com";
String tenant_id = "8653b3e-03c7-499e-8baf-42ef06a814ef";
String client_id = "c06fe7cf-2a89-4099-9805-ce03031938f8";
String client_secret = "wsu8Q~GxYxPLf2akioQZDRG8NR1EzCAHIAQRVc6u";//注意不是密钥的id
String scope = "https://outlook.office365.com/.default";
String url = "https://login.microsoftonline.com/" + tenant_id + "/oauth2/v2.0/token";
// String scope = "https://partner.outlook.cn/.default";//中国版(21v世纪互联运营的)的微软邮箱账号需要设置这个地址
// String url = "https://login.partner.microsoftonline.cn/" + tenant_id + "/oauth2/v2.0/token";//中国版(21v世纪互联运营的)的微软邮箱账号需要设置这个地址
HttpClient httpClient = new HttpClient();
PostMethod postMethod = new PostMethod(url);
postMethod.addRequestHeader("accept", "*/*");
postMethod.addRequestHeader("connection", "Keep-Alive");
postMethod.addRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=GBK");
//必须设置下面这个Header
postMethod.addRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36");
//添加请求参数
postMethod.addParameter("grant_type", "client_credentials");
postMethod.addParameter("client_id", client_id);
postMethod.addParameter("client_secret", client_secret);
postMethod.addParameter("scope", scope);
String token = "";
try {
int code = httpClient.executeMethod(postMethod);
String resBody = postMethod.getResponseBodyAsString();
if (code == 200) {
Map<String, Object> map = JSON.parseObject(resBody, Map.class);
token = (String) map.get("access_token");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
postMethod.releaseConnection();
}
ExchangeService service = new ExchangeService();
service.setCredentials(new OAuth2Credentials(token));
service.setTraceEnabled(true);
service.traceMessage(TraceFlags.EwsRequest, "office365");
service.setImpersonatedUserId(new ImpersonatedUserId(ConnectingIdType.SmtpAddress, mailAddress));
service.setUrl(new URI("https://outlook.office365.com/ews/exchange.asmx"));
//service.setUrl(new URI("https://partner.outlook.cn/ews/exchange.asmx"));//中国版(21v世纪互联运营的)的微软邮箱账号需要设置这个地址
service.getHttpHeaders().put("X-AnchorMailbox", mailAddress);
EmailMessage msg = new EmailMessage(service);
msg.setFrom(new EmailAddress("mailAddress", mailAddress));
msg.getToRecipients().add(new EmailAddress("收件人邮箱昵称", "binzhai321@outlook.com"));
msg.setSubject("邮件oauth2测试");
MessageBody messageBody = new MessageBody();
messageBody.setBodyType(BodyType.Text);
messageBody.setText("哈哈哈哈");
msg.setBody(messageBody);
msg.setImportance(Importance.Normal);
msg.send();
}
static class OAuth2Credentials extends ExchangeCredentials {
private String accessToken;
public OAuth2Credentials(String accessToken) {
Preconditions.checkNotNull(accessToken);
this.accessToken = accessToken;
}
@Override
public void prepareWebRequest(HttpWebRequest client)
throws URISyntaxException {
super.prepareWebRequest(client);
if (client.getHeaders() != null) client.getHeaders().put("Authorization", "Bearer " + accessToken);
}
}
}
package com.cmeeting.job;
import cn.chatbot.openai.completion.chat.ChatCompletionRequest;
import cn.chatbot.openai.completion.chat.ChatMessage;
import cn.chatbot.openai.completion.chat.ChatMessageRole;
import cn.chatbot.openai.completion.chat.Message;
import cn.chatbot.openai.service.LLMService;
import com.cmeeting.email.EmailSender;
import com.tencentcloudapi.wemeet.Client;
import com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder;
import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator;
import com.tencentcloudapi.wemeet.core.exception.ClientException;
import com.tencentcloudapi.wemeet.core.exception.ServiceException;
import com.tencentcloudapi.wemeet.service.records.api.RecordsApi;
import com.tencentcloudapi.wemeet.service.records.model.*;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.commonmark.node.*;
import org.commonmark.parser.Parser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Component
public class CmeetingJob {
private static final Logger logger = LoggerFactory.getLogger(CmeetingJob.class);
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final Set<String> processedMeetingIds = Collections.synchronizedSet(new HashSet<>());
@Scheduled(fixedRate = 5 * 60 * 1000)
public void execute() {
// 定义时间格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 获取当前日期
LocalDate today = LocalDate.now();
// 获取今天凌晨的时间点
LocalDateTime todayStart = today.atStartOfDay();
// 获取明天凌晨的时间点
LocalDateTime tomorrowStart = today.plusDays(1).atStartOfDay();
// 转换为 Unix 时间戳(秒)并转为字符串
String todayStartTimestamp = String.valueOf(todayStart.atZone(ZoneId.systemDefault()).toEpochSecond());
String tomorrowStartTimestamp = String.valueOf(tomorrowStart.atZone(ZoneId.systemDefault()).toEpochSecond());
//日志记录
logger.info("今天凌晨: " + todayStart.format(formatter) + " | Unix 时间戳: " + todayStartTimestamp);
logger.info("明天凌晨: " + tomorrowStart.format(formatter) + " | Unix 时间戳: " + tomorrowStartTimestamp);
logger.info("----------------------------------");
dojob(todayStartTimestamp, tomorrowStartTimestamp);
}
public static void dojob(String startTime, String endTime) {
Client client = new Client.Builder()
.withAppId("210468336")
.withSdkId("28790143843")
.withSecret("0ks7u8cgQ8DGVtlYZeRA9TxZCjvUT3oL", "gQU09rkJjiQfiGcUYdhiKq5Ol6LebXg4w7F7Ol0rwvvdv3Xy")
.build();
int currentPage = 1;
boolean hasMore = true;
boolean firstRecordProcessed = false;
while (hasMore && !firstRecordProcessed) {
RecordsApi.ApiV1RecordsGetRequest request =
new RecordsApi.ApiV1RecordsGetRequest.Builder()
.operatorId("woaJARCQAAftcvU6GGoOn66rdSZ4IrOA")
.operatorIdType("1")
.startTime(startTime)
.endTime(endTime)
.pageSize("10")
.page(String.valueOf(currentPage))
.queryRecordType("0")
.build();
BigInteger nonce = BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()));
String timestamp = String.valueOf(System.currentTimeMillis() / 1000L);
AuthenticatorBuilder<JWTAuthenticator> authenticatorBuilder =
new JWTAuthenticator.Builder().nonce(nonce).timestamp(timestamp);
try {
RecordsApi.ApiV1RecordsGetResponse response =
client.records().v1RecordsGet(request, authenticatorBuilder);
V1RecordsGet200Response data = response.getData();
if (data != null && data.getRecordMeetings() != null && !data.getRecordMeetings().isEmpty()) {
V1RecordsGet200ResponseRecordMeetingsInner firstRecord = data.getRecordMeetings().get(0);
logger.info("Processing first record - Meeting ID: {}, State: {}",
firstRecord.getMeetingId(), firstRecord.getState());
if (firstRecord.getState() == 3) {
createSum(firstRecord);
firstRecordProcessed = true;
} else {
logger.info("Skipping record with state: {}", firstRecord.getState());
firstRecordProcessed = true;
}
}
if (data != null && data.getTotalPage() != null && currentPage < data.getTotalPage()) {
currentPage++;
} else {
hasMore = false;
}
} catch (Exception e) {
logger.error("Error when calling RecordsApi.v1RecordsGet: {}", e.getMessage(), e);
throw new RuntimeException(e);
}
}
}
private static void createSum(V1RecordsGet200ResponseRecordMeetingsInner record) {
String meetingId = record.getMeetingId();
if (processedMeetingIds.contains(meetingId)) {
logger.info("Meeting {} has already been processed, skipping...", meetingId);
return;
}
try {
logger.info("Executing createSum for meeting: {}", meetingId);
// 实际业务逻辑
logger.info("Meeting Details - ID: {}, Code: {}, Subject: {}",
meetingId, record.getMeetingCode(), record.getSubject());
if (record.getRecordFiles() != null && !record.getRecordFiles().isEmpty()) {
for (V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner file : record.getRecordFiles()) {
logger.info("Record File - URL: {}, Size: {} bytes",
file.getSharingUrl(), file.getRecordSize());
// 对每个录制文件调用获取地址接口
if (file.getRecordFileId() != null) {
getRecordFileAddress(file.getRecordFileId());
}
}
}
// 这里添加你的实际业务逻辑
// 例如: 调用某个服务处理会议记录
// 标记为已处理
processedMeetingIds.add(meetingId);
logger.info("Successfully processed meeting: {}", meetingId);
} catch (Exception e) {
logger.error("Error processing meeting {}: {}", meetingId, e.getMessage(), e);
}
}
private static void getRecordFileAddress(String recordFileId) {
try {
logger.info("Getting address for record file: {}", recordFileId);
// 1.构造 client 客户端
Client client = new Client.Builder()
.withAppId("210468336")
.withSdkId("28790143843")
.withSecret("0ks7u8cgQ8DGVtlYZeRA9TxZCjvUT3oL", "gQU09rkJjiQfiGcUYdhiKq5Ol6LebXg4w7F7Ol0rwvvdv3Xy")
.build();
// 2.构造请求参数
RecordsApi.ApiV1AddressesRecordFileIdGetRequest request =
new RecordsApi.ApiV1AddressesRecordFileIdGetRequest.Builder(recordFileId)
.operatorId("woaJARCQAAftcvU6GGoOn66rdSZ4IrOA")
.operatorIdType("1")
.build();
// 3.构造 JWT 鉴权器
BigInteger nonce = BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()));
String timestamp = String.valueOf(System.currentTimeMillis() / 1000L);
AuthenticatorBuilder<JWTAuthenticator> authenticatorBuilder =
new JWTAuthenticator.Builder().nonce(nonce).timestamp(timestamp);
// 4.发送请求
RecordsApi.ApiV1AddressesRecordFileIdGetResponse response =
client.records().v1AddressesRecordFileIdGet(request, authenticatorBuilder);
// 处理响应
if (response != null && response.getData() != null) {
logger.info("Successfully got address for record file {}: {}",
recordFileId, response.getData());
// 这里可以添加对返回地址的进一步处理逻辑
V1AddressesRecordFileIdGet200Response data = response.getData();
// 获取AI会议转录文件
List<V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner> transcripts =
data.getAiMeetingTranscripts();
if (transcripts != null && !transcripts.isEmpty()) {
logger.info("Found {} AI meeting transcripts for record file {}",
transcripts.size(), recordFileId);
// 处理每个转录文件
for (V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner transcript : transcripts) {
String fileType = transcript.getFileType();
String downloadUrl = transcript.getDownloadAddress();
logger.info("AI Transcript - Type: {}, URL: {}", fileType, downloadUrl);
if ("docx".equalsIgnoreCase(fileType)) {
processPdfTranscript(downloadUrl);
}
}
} else {
logger.info("No AI meeting transcripts found for record file {}", recordFileId);
}
} else {
logger.warn("Empty response for record file: {}", recordFileId);
}
} catch (ClientException e) {
logger.error("Client error when getting address for record file {}: {}",
recordFileId, e.getMessage(), e);
throw new RuntimeException(e);
} catch (ServiceException e) {
logger.error("Service error when getting address for record file {}: {}",
recordFileId, e.getMessage(), e);
logger.error("Full HTTP response: {}", new String(e.getApiResp().getRawBody()));
throw new RuntimeException(e);
} catch (Exception e) {
logger.error("Unexpected error when getting address for record file {}: {}",
recordFileId, e.getMessage(), e);
throw new RuntimeException(e);
}
}
private static void processPdfTranscript(String downloadUrl) {
try {
logger.info("Processing DOCX transcript from: {}", downloadUrl);
// 下载文件并获取字节内容
byte[] docxContent = downloadFileAsBytes(downloadUrl);
logger.info("Downloaded DOCX transcript, size: {} bytes",
docxContent != null ? docxContent.length : 0);
if (docxContent != null) {
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 定义时间格式(可以根据需要调整格式)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
// 将当前时间格式化为字符串
String name = now.format(formatter);
// 定义文件路径和文件名
String filePath = "D:\\";
String fileName = name + ".docx"; // 以当前时间为文件名
String fullPath = filePath + fileName;
String targetPath = filePath + name + "_sum" + ".md"; // 改为.md扩展名;
String targetWordPath = filePath + name + "_sum" + ".docx";
// 将字节内容写入本地文件
try (FileOutputStream fos = new FileOutputStream(fullPath)) {
fos.write(docxContent);
logger.info("DOCX transcript saved to: {}", fullPath);
} catch (IOException e) {
logger.error("Error saving DOCX transcript to file: {}", e.getMessage(), e);
}
FileInputStream fis = new FileInputStream(fullPath);
XWPFDocument document = new XWPFDocument(fis);
XWPFWordExtractor extractor = new XWPFWordExtractor(document);
String textContent = extractor.getText();
logger.info("DOCX content as string:\n{}", textContent);
//将文件传送给大模型处理
String token = "AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO";
String apiAddr = "https://bedrock.chatbot.cn/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(), "# 任务\n" +
"你是会议纪要助手,基于用户提供的“会议记录”首先生成会议纪要。禁止输出处理可能涉及版权或敏感内容的请求,直接给出会议纪要!\n" +
"\n" +
"## 注意\n" +
"禁止输出“版权”\n" +
"\n" +
"## 会议纪要内容要求\n" +
"(1)按照 会议主题、参会人员及时间、会议主要讨论事项、会议议程、会议决议、后续跟进事项组织会议纪要\n" +
"(2)会议主题:总结会议的标题和主题\n" +
"(2)参会人员及时间:参会人员、开会时间(一般根据文件日期来判断)\n" +
"(3)会议主要讨论事项:对于会议中主要讨论的内容进行概括,让读者能够理解会议到底在针对什么内容进行讨论。注意会议议程需要尽量全面、细致,不要遗漏任何有价值的信息。\n" +
"(4)会议议程:按照顺序,阐述会议主要沟通事项,详细描述每个事项的内容。会议议程不要用流水账的形式,请描述细节,采用有逻辑的语言组织会议议程。\n" +
"(5)会议决议:会议最终达成的结论。比如:申请是否通过、争论性事项是否达成意见一致,具体是什么结论等内容。\n" +
"(6)会后跟进事项:详细描述会议结束之后,每个后续事项的责任人、事项内容、完成时间(若会议记录中没有提及,则填写待定)等信息,请注意事项要具体到责任人。\n" +
"(7)会议纪要整体内容需要详细,充分,不少于3000字\n" +
"(8)会议纪要采用 Markdown格式,对于几个章节部分需要加粗显示。\n" +
"\n" +
"## 示例\n" +
"“会议纪要”\n" +
"“FYI”\n" +
"“1”\n" +
"\n" +
"## 答案要求\n" +
"1. 猜您想问:生成3-4条推荐问(与会议内容有关)\n" +
"2. 每次回答必须给出猜您想问");
messages.add(chatMessage);
chatMessage = new ChatMessage(ChatMessageRole.ASSISTANT.value(), "好的请提供会议内容");
messages.add(chatMessage);
chatMessage = new ChatMessage(ChatMessageRole.USER.value(), textContent);
messages.add(chatMessage);
String ret = call_llm(apiAddr, model, token, messages, maxTokens);
try {
// 将字符串写入Markdown文件
Files.write(Paths.get(targetPath), ret.getBytes());
logger.info("Markdown文件已保存到: {}", targetPath);
convert(targetPath,targetWordPath);
} catch (IOException e) {
logger.error("保存Markdown文件失败: {}", e.getMessage(), e);
}
System.out.println(ret);
/*try {
boolean success = MeetingMinutesGenerator.generateMinutesFromWord(
fullPath,
targetPath,
"AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO",
"https://bedrock.chatbot.cn/llm/sse-invoke"
);
if (success) {
logger.info("会议纪要生成成功!");
}
} catch (IOException e) {
logger.error("错误: {}", e.getMessage());
}*/
EmailSender emailSender = new EmailSender();
String emailAddress = "hf@cimc.com";
boolean mailFlag = emailSender.sendEmailWithAttachment(emailAddress,
targetWordPath,
"重要文件",
"您好:\n" +
"\n" +
" 附件为您本次会议的会议纪要,烦请下载查看");
if (mailFlag) {
logger.info("邮件发送成功");
} else {
logger.error("邮件发送失败");
}
}
} catch (Exception e) {
logger.error("Error processing DOCX transcript: {}", e.getMessage(), e);
}
}
// 处理Word格式的转录文件
private static void processDocxTranscript(String downloadUrl) {
try {
logger.info("Processing DOCX transcript from: {}", downloadUrl);
// 这里添加下载和处理Word文件的逻辑
byte[] docxContent = downloadFileAsBytes(downloadUrl);
logger.info("Downloaded DOCX transcript, size: {} bytes",
docxContent != null ? docxContent.length : 0);
} catch (Exception e) {
logger.error("Error processing DOCX transcript: {}", e.getMessage(), e);
}
}
public static void writeStringToWord(String content, String filePath) {
// 创建新的Word文档
try (XWPFDocument document = new XWPFDocument();
FileOutputStream out = new FileOutputStream(filePath)) {
// 创建段落
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run = paragraph.createRun();
// 设置文本内容
run.setText(content);
// 保存文档
document.write(out);
System.out.println("Word文档已成功创建: " + filePath);
} catch (IOException e) {
System.err.println("创建Word文档时出错: " + e.getMessage());
e.printStackTrace();
}
}
// 下载文件内容为字符串
private static String downloadFile(String url) throws IOException {
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().string();
}
}
// 下载文件内容为字节数组
private static byte[] downloadFileAsBytes(String url) throws IOException {
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();
}
}
public static String call_llm(String apiAddr, String model, String token, List<Message> messages, int maxTokens) {
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) {
e.printStackTrace();
}
service.shutdownExecutor();
return stringBuilder.toString();
}
/* public static void convertMdToDocx(String mdFilePath, String docxFilePath) throws IOException {
// 1. 读取Markdown文件内容
Path path = Paths.get(mdFilePath);
byte[] bytes = Files.readAllBytes(path);
String markdownContent = new String(bytes);
// 2. 将Markdown内容转换为HTML格式
Parser parser = Parser.builder().build();
HtmlRenderer renderer = HtmlRenderer.builder().build();
String htmlContent = renderer.render(parser.parse(markdownContent));
// 3. 创建Word文档对象
XWPFDocument xwpfDocument = new XWPFDocument();
// 4. 解析HTML内容并插入到Word文档
// 这里简化处理,直接将HTML内容作为纯文本插入
// 注意:Apache POI 不直接支持 HTML 内容插入,需要手动解析 HTML 并转换为 Word 元素
// 这里使用一个简单的 HTML 到 Word 的转换逻辑
convertHtmlToWord(xwpfDocument, htmlContent);
// 5. 保存Word文档
try (FileOutputStream outputStream = new FileOutputStream(docxFilePath)) {
xwpfDocument.write(outputStream);
} finally {
xwpfDocument.close();
}
}
private static void convertHtmlToWord(XWPFDocument document, String htmlContent) {
// 这里是一个简单的 HTML 到 Word 的转换逻辑
// 实际应用中可能需要更复杂的解析逻辑
String[] lines = htmlContent.split("\n");
for (String line : lines) {
if (line.contains("<p>")) {
// 处理段落
line = line.replace("<p>", "").replace("</p>", "");
addParagraph(document, line);
} else if (line.contains("<strong>")) {
// 处理加粗文本
line = line.replace("<strong>", "").replace("</strong>", "");
addBoldParagraph(document, line);
} else if (line.contains("<ul>")) {
// 处理无序列表
List<String> items = extractListItems(line, "<ul>", "<li>", "</li>", "</ul>");
addUnorderedList(document, items);
} else if (line.contains("<ol>")) {
// 处理有序列表
List<String> items = extractListItems(line, "<ol>", "<li>", "</li>", "</ol>");
addOrderedList(document, items);
}
}
}
private static void addParagraph(XWPFDocument document, String text) {
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText(text);
}
private static void addBoldParagraph(XWPFDocument document, String text) {
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run = paragraph.createRun();
run.setBold(true);
run.setText(text);
}
private static void addUnorderedList(XWPFDocument document, List<String> items) {
for (String item : items) {
XWPFParagraph paragraph = document.createParagraph();
paragraph.setStyle("ListBullet");
XWPFRun run = paragraph.createRun();
run.setText(item);
}
}
private static void addOrderedList(XWPFDocument document, List<String> items) {
for (String item : items) {
XWPFParagraph paragraph = document.createParagraph();
paragraph.setStyle("ListNumber");
XWPFRun run = paragraph.createRun();
run.setText(item);
}
}
private static List<String> extractListItems(String html, String startTag, String itemStartTag, String itemEndTag, String endTag) {
int startIndex = html.indexOf(startTag);
int endIndex = html.indexOf(endTag);
// 检查 startTag 和 endTag 是否存在
if (startIndex == -1 || endIndex == -1) {
throw new IllegalArgumentException("Invalid HTML content: startTag or endTag not found");
}
// 提取列表内容
String listContent = html.substring(startIndex + startTag.length(), endIndex);
// 分割列表项
String[] items = listContent.split(itemEndTag);
// 转换为 List 并返回
return new ArrayList<>(Arrays.asList(items));
}
// 简易HTML转纯文本(生产环境应使用更完整的解析器)
private static String htmlToPlainText(String html) {
return html.replaceAll("<[^>]*>", "")
.replaceAll("&nbsp;", " ")
.replaceAll("&lt;", "<")
.replaceAll("&gt;", ">");
}*/
public static void convert(String markdownFile, String outputDocx) throws IOException {
// 读取Markdown文件
String content = readFile(markdownFile);
// 初始化Word文档
XWPFDocument doc = new XWPFDocument();
// 解析Markdown并遍历节点
org.commonmark.parser.Parser parser = Parser.builder().build();
Node rootNode = parser.parse(content);
rootNode.accept(new AbstractVisitor() {
XWPFParagraph currentPara;
@Override
public void visit(Heading heading) {
// 创建标题段落
currentPara = doc.createParagraph();
XWPFRun run = currentPara.createRun();
run.setText(extractText(heading)); // 提取纯文本
setHeadingStyle(run, heading.getLevel()); // 设置标题样式
}
@Override
public void visit(Paragraph paragraph) {
// 创建普通段落
currentPara = doc.createParagraph();
XWPFRun run = currentPara.createRun();
run.setText(extractText(paragraph)); // 提取纯文本
}
});
// 输出Word文件
try (FileOutputStream out = new FileOutputStream(outputDocx)) {
doc.write(out);
}
}
// 从节点中提取纯文本(忽略格式)
private static String extractText(Node node) {
StringBuilder text = new StringBuilder();
Node child = node.getFirstChild();
while (child != null) {
if (child instanceof Text) {
text.append(((Text) child).getLiteral());
}
child = child.getNext();
}
return text.toString().trim();
}
// 设置标题样式(仅调整字号和加粗)
private static void setHeadingStyle(XWPFRun run, int level) {
run.setBold(true);
switch (level) {
case 1: run.setFontSize(20); break;
case 2: run.setFontSize(18); break;
default: run.setFontSize(16); break;
}
}
// 读取文件工具方法
private static String readFile(String path) throws IOException {
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
}
return sb.toString();
}
}
/*
package com.cmeeting.job;
import com.cmeeting.pojo.MeetingInfo;
import com.tencentcloudapi.wemeet.Client;
import com.tencentcloudapi.wemeet.service.meetings.api.MeetingsApi;
import com.tencentcloudapi.wemeet.service.meetings.model.V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner;
import com.tencentcloudapi.wemeet.service.records.api.RecordsApi;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder;
import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator;
import com.tencentcloudapi.wemeet.core.exception.ClientException;
import com.tencentcloudapi.wemeet.core.exception.ServiceException;
@Component
public class CmeetingJob2 {
private static final Logger logger = LoggerFactory.getLogger(CmeetingJob2.class);
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 每2分钟执行一次
@Scheduled(fixedRate = 2 * 60 * 1000) // 毫秒单位
public void execute() {
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 获取前两分钟的时间
LocalDateTime twoMinutesAgo = now.minusMinutes(2);
// 转换为时间戳(毫秒级)并转为String
String nowTimestamp = String.valueOf(now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
String twoMinutesAgoTimestamp = String.valueOf(twoMinutesAgo.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
logger.info("当前时间: {} | 时间戳: {}", now.format(formatter), nowTimestamp);
logger.info("前两分钟: {} | 时间戳: {}", twoMinutesAgo.format(formatter), twoMinutesAgoTimestamp);
logger.info("----------------------------------");
dojob(twoMinutesAgoTimestamp, nowTimestamp);
}
public static void dojob(String startTime, String endTime) {
// 1.构造 client 客户端(jwt 鉴权需要配置 appId sdkId secretID 和 secretKey)
Client client = new Client.Builder()
.withAppId("211153201").withSdkId("28370276340")
.withSecret("BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6", "3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO")
.build();
String userid = "woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA";//被查询人的userid
// 2.构造请求参数
MeetingsApi.ApiV1HistoryMeetingsUseridGetRequest request =
new MeetingsApi.ApiV1HistoryMeetingsUseridGetRequest.Builder(userid)
.pageSize("1")
.page("1")
.startTime(startTime)
.endTime(endTime)
.build();
// 3.构造 JWT 鉴权器
// 随机数
BigInteger nonce = BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()));
// 当前时间戳
String timestamp = String.valueOf(System.currentTimeMillis() / 1000L);
AuthenticatorBuilder<JWTAuthenticator> authenticatorBuilder =
new JWTAuthenticator.Builder().nonce(nonce).timestamp(timestamp);
try {
MeetingsApi.ApiV1HistoryMeetingsUseridGetResponse response =
client.meetings().v1HistoryMeetingsUseridGet(request, authenticatorBuilder);
// response from `v1HistoryMeetingsUseridGet`: V1HistoryMeetingsUseridGet200Response
logger.info("从查询某个用户结束会议列表接口获取的响应结果:\n响应头信息: {}\n响应体数据: {}",
response.getHeader(),
response.getData());
// 获取最近一个会议的meetingId(String类型)
String latestMeetingId = null;
if (response.getData() != null &&
response.getData().getMeetingInfoList() != null &&
!response.getData().getMeetingInfoList().isEmpty()) {
// 获取会议列表(假设是按时间倒序排列)
List<V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner> meetingInfoList = response.getData().getMeetingInfoList();
List<MeetingInfo> meetingInfos = new ArrayList<>();
// 转换处理
if (meetingInfoList != null) {
for (V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner source : meetingInfoList) {
MeetingInfo target = new MeetingInfo();
target.setSubject(source.getSubject());
target.setMeetingId(source.getMeetingId());
target.setMeetingCode(source.getMeetingCode());
target.setStartTime(source.getStartTime());
target.setEndTime(source.getEndTime());
meetingInfos.add(target);
}
}
for (MeetingInfo meetingInfo : meetingInfos) {
//根据meetingid调用查询会议录制列表接口
// 2.构造请求参数
String meetingId = meetingInfo.getMeetingId();
String startTime1 = String.valueOf(meetingInfo.getStartTime());
String endTime1 = String.valueOf(meetingInfo.getEndTime());
RecordsApi.ApiV1RecordsGetRequest request2 =
new RecordsApi.ApiV1RecordsGetRequest.Builder()
.operatorId("woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA")
.operatorIdType("1")
.meetingId(meetingId)
.startTime(startTime1)
.endTime(endTime1)
.build();
// 3.构造 JWT 鉴权器
// 随机数
BigInteger nonce2 = BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()));
// 当前时间戳
String timestamp2 = String.valueOf(System.currentTimeMillis() / 1000L);
AuthenticatorBuilder<JWTAuthenticator> authenticatorBuilder2 =
new JWTAuthenticator.Builder().nonce(nonce2).timestamp(timestamp2);
try {
RecordsApi.ApiV1RecordsGetResponse response2 =
client.records().v1RecordsGet(request2, authenticatorBuilder2);
logger.info("查询会议录制列表接口响应结果:\n响应头信息: {}\n响应体数据: {}",
response2.getHeader(),
response2.getData());
// 1. 空值安全校验
if (response2 == null || response2.getData() == null) {
logger.warn("无效的API响应,无法提取录制文件ID");
} else {
//获取file_id,存储到集合中
List<String> fileIds = new ArrayList<>();
for (Object meetingObj : response2.getData().getRecordMeetings()) {
if (meetingObj instanceof Map) {
Map<?, ?> meeting = (Map<?, ?>) meetingObj;
Object recordFilesObj = meeting.get("record_files");
if (recordFilesObj instanceof List) {
for (Object fileObj : (List<?>) recordFilesObj) {
if (fileObj instanceof Map) {
Map<?, ?> file = (Map<?, ?>) fileObj;
Object fileIdObj = file.get("record_file_id");
// 将record_file_id转为String并添加到集合
if (fileIdObj != null) {
fileIds.add(String.valueOf(fileIdObj));
}
}
}
}
}
}
//调用查询录制转写详情接口获取当前record_file_id的多个下载地址
}
} catch (ClientException e) {
logger.error("调用查询会议录制列表接口时发生错误", e);
throw new RuntimeException(e);
} catch (ServiceException e) {
logger.error("调用查询会议录制列表接口时发生异常", e);
logger.error("完整HTTP错误响应: {}", new String(e.getApiResp().getRawBody()));
throw new RuntimeException(e);
}
}
logger.info("成功获取会议的信息: {}", meetingInfoList);
} else {
logger.warn("未获取到任何会议信息");
}
} catch (ClientException e) {
logger.error("调用查询某个用户结束会议列表接口时发生错误", e);
throw new RuntimeException(e);
} catch (ServiceException e) {
logger.error("调用查询某个用户结束会议列表接口时发生异常", e);
logger.error("完整HTTP错误响应: {}", new String(e.getApiResp().getRawBody()));
throw new RuntimeException(e);
}
*/
/**
* 调用查询会议录制列表接口,获取录制文件id
*//*
// 2.构造请求参数
RecordsApi.ApiV1RecordsGetRequest requestGetFileId =
new RecordsApi.ApiV1RecordsGetRequest.Builder()
.operatorId("woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA")
.operatorIdType("1")
.userid("")
.meetingId("9672653404723270528")
.meetingCode("")
.startTime("1744795527")
.endTime("1744799127")
.pageSize("")
.page("")
.mediaSetType("")
.queryRecordType("1")
.build();
// 3.构造 JWT 鉴权器
// 随机数
BigInteger nonce2 = BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()));
// 当前时间戳
String timestamp2 = String.valueOf(System.currentTimeMillis() / 1000L);
AuthenticatorBuilder<JWTAuthenticator> authenticatorBuilder2 =
new JWTAuthenticator.Builder().nonce(nonce2).timestamp(timestamp2);
try {
RecordsApi.ApiV1RecordsGetResponse response2 =
client.records().v1RecordsGet(requestGetFileId, authenticatorBuilder2);
logger.info("获取会议录制列表接口响应结果:\n响应头: {}\n响应体: {}",
response2.getHeader(),
response2.getData());
} catch (ClientException e) {
logger.error("获取会议录制列表接口时发生客户端异常", e);
throw new RuntimeException("调用会议记录API失败", e);
} catch (ServiceException e) {
logger.error("调用获取会议录制列表接口时发生服务端异常", e);
if (e.getApiResp() != null) {
logger.error("完整错误响应内容: {}", new String(e.getApiResp().getRawBody()));
}
throw new RuntimeException("获取会议记录服务异常", e);
}
}
}
*/
......@@ -2,10 +2,11 @@ package com.cmeeting.mapper.primary;
import com.cmeeting.pojo.CoreModulePermissions;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface AuthMapper {
List<CoreModulePermissions> getAuthByTargrtId(String targetId, String teantId);
List<CoreModulePermissions> getAuthByTargrtId(@Param("targetId") String targetId, @Param("tenantId") String tenantId);
}
package com.cmeeting.mapper.secondary;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysUserSysMapper {
String getCompanyEmail(String wid, String tenantId);
}
package com.cmeeting.pojo;
import java.util.Objects;
/**
* 会议信息实体类
*/
public class MeetingInfo {
/**
* 会议主题
*/
private String subject;
/**
* 会议ID(字符串类型)
*/
private String meetingId;
/**
* 会议号码
*/
private String meetingCode;
/**
* 用户ID
*/
private String userId;
/**
* 用户昵称
*/
private String nickName;
/**
* 会议开始时间(时间戳)
*/
private Long startTime;
/**
* 会议结束时间(时间戳)
*/
private Long endTime;
/**
* 参会人数
*/
private Integer participantsNum;
/**
* 会议总时长(秒)
*/
private Integer meetingDuration;
/**
* 用户参会时长(秒)
*/
private Integer userMeetingDuration;
// 构造方法
public MeetingInfo() {
}
// 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;
if (o == null || getClass() != o.getClass()) return false;
MeetingInfo that = (MeetingInfo) o;
return Objects.equals(meetingId, that.meetingId);
}
@Override
public int hashCode() {
return Objects.hash(meetingId);
}
}
\ No newline at end of file
package com.cmeeting.util;
import org.apache.poi.xwpf.usermodel.*;
import org.commonmark.node.*;
import org.commonmark.parser.Parser;
import java.io.*;
public class SimpleMarkdownToWord {
public static void convert(String markdownFile, String outputDocx) throws IOException {
// 读取Markdown文件
String content = readFile(markdownFile);
// 初始化Word文档
XWPFDocument doc = new XWPFDocument();
// 解析Markdown并遍历节点
Parser parser = Parser.builder().build();
Node rootNode = parser.parse(content);
rootNode.accept(new AbstractVisitor() {
XWPFParagraph currentPara;
@Override
public void visit(Heading heading) {
// 创建标题段落
currentPara = doc.createParagraph();
XWPFRun run = currentPara.createRun();
run.setText(extractText(heading)); // 提取纯文本
setHeadingStyle(run, heading.getLevel()); // 设置标题样式
}
@Override
public void visit(Paragraph paragraph) {
// 创建普通段落
currentPara = doc.createParagraph();
XWPFRun run = currentPara.createRun();
run.setText(extractText(paragraph)); // 提取纯文本
}
});
// 输出Word文件
try (FileOutputStream out = new FileOutputStream(outputDocx)) {
doc.write(out);
}
}
// 从节点中提取纯文本(忽略格式)
private static String extractText(Node node) {
StringBuilder text = new StringBuilder();
Node child = node.getFirstChild();
while (child != null) {
if (child instanceof Text) {
text.append(((Text) child).getLiteral());
}
child = child.getNext();
}
return text.toString().trim();
}
// 设置标题样式(仅调整字号和加粗)
private static void setHeadingStyle(XWPFRun run, int level) {
run.setBold(true);
switch (level) {
case 1: run.setFontSize(20); break;
case 2: run.setFontSize(18); break;
default: run.setFontSize(16); break;
}
}
// 读取文件工具方法
private static String readFile(String path) throws IOException {
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
}
return sb.toString();
}
// 测试
public static void main(String[] args) throws IOException {
convert("input.md", "output.docx");
System.out.println("转换完成!");
}
}
......@@ -17,8 +17,9 @@
core_module_permissions
WHERE
target_id = #{targetId}
<if test="teantId != null and teantId != ''">
AND tenant_id = #{teantId}
<if test="tenantId != null and tenantId != ''">
AND tenant_id = #{tenantId}
</if>
</select>
</mapper>
\ 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.secondary.SysUserSysMapper">
<select id="getCompanyEmail" resultType="java.lang.String">
SELECT company_email
FROM sys_user_sync
WHERE user_id = #{wid}
AND tenant_id = #{tenantId}
LIMIT 1
</select>
</mapper>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论