Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
cmeeting
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
翟斌
cmeeting
Commits
0a34c27f
提交
0a34c27f
authored
4月 25, 2025
作者:
zhaibin
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
定时拉取已结束会议获取录制文件
父级
69b04a25
显示空白字符变更
内嵌
并排
正在显示
21 个修改的文件
包含
1630 行增加
和
363 行删除
+1630
-363
pom.xml
+78
-18
src/main/java/com/cmeeting/GraphApiWithOkHttp.java
+13
-11
src/main/java/com/cmeeting/OutlookTest.java
+0
-164
src/main/java/com/cmeeting/SendMailExample.java
+0
-118
src/main/java/com/cmeeting/TencentMeetingCallbackApplication.java
+2
-0
src/main/java/com/cmeeting/TencentMeetingCallbackController.java
+42
-41
src/main/java/com/cmeeting/controller/UnifiedController.java
+3
-4
src/main/java/com/cmeeting/controller/WeComcontroller.java
+2
-0
src/main/java/com/cmeeting/email/AzureADEmailSender.java
+145
-0
src/main/java/com/cmeeting/email/EmailSender.java
+5
-3
src/main/java/com/cmeeting/email/GraphApiWithOkHttp2.java
+85
-0
src/main/java/com/cmeeting/email/QQMailSender.java
+1
-1
src/main/java/com/cmeeting/email/TestExchangeAndOauth2.java
+103
-0
src/main/java/com/cmeeting/job/CmeetingJob.java
+631
-0
src/main/java/com/cmeeting/job/CmeetingJob2.java
+231
-0
src/main/java/com/cmeeting/mapper/primary/AuthMapper.java
+2
-1
src/main/java/com/cmeeting/mapper/secondary/SysUserSysMapper.java
+8
-0
src/main/java/com/cmeeting/pojo/MeetingInfo.java
+175
-0
src/main/java/com/cmeeting/util/SimpleMarkdownToWord.java
+87
-0
src/main/resources/mapper/primary/AuthMapper.xml
+4
-2
src/main/resources/mapper/secondary/SysUserSysMapper.xml
+13
-0
没有找到文件。
pom.xml
浏览文件 @
0a34c27f
...
...
@@ -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>
202
4030
3
</version>
<!-- 使用最新版本 -->
<version>
202
3101
3
</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
src/main/java/com/cmeeting/GraphApiWithOkHttp.java
浏览文件 @
0a34c27f
...
...
@@ -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
=
"bi
n
zhai321@outlook.com"
;
// 发送邮件的用户
private
static
final
String
USER_EMAIL
=
"bi
ini
zhai321@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
(
token
1
);
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
();
...
...
src/main/java/com/cmeeting/OutlookTest.java
deleted
100644 → 0
浏览文件 @
69b04a25
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
();
}
}
src/main/java/com/cmeeting/SendMailExample.java
deleted
100644 → 0
浏览文件 @
69b04a25
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
src/main/java/com/cmeeting/TencentMeetingCallbackApplication.java
浏览文件 @
0a34c27f
...
...
@@ -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
);
...
...
src/main/java/com/cmeeting/TencentMeetingCallbackController.java
浏览文件 @
0a34c27f
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
(
"21
0468336"
).
withSdkId
(
"28790143843
"
)
.
withSecret
(
"
0ks7u8cgQ8DGVtlYZeRA9TxZCjvUT3oL"
,
"gQU09rkJjiQfiGcUYdhiKq5Ol6LebXg4w7F7Ol0rwvvdv3Xy
"
)
.
withAppId
(
"21
1153201"
).
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
te
antId
=
"1806976109082972160"
;
List
<
CoreModulePermissions
>
auths
=
authMapper
.
getAuthByTargrtId
(
targetId
,
te
antId
);
String
targetId
=
"1815393211829587968"
;
//职能体id
String
te
nantId
=
"1806976109082972160"
;
//租户id
List
<
CoreModulePermissions
>
auths
=
authMapper
.
getAuthByTargrtId
(
targetId
,
ten
antId
);
// 创建两个集合分别存储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()
...
...
src/main/java/com/cmeeting/controller/UnifiedController.java
浏览文件 @
0a34c27f
...
...
@@ -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
)
{
...
...
src/main/java/com/cmeeting/controller/WeComcontroller.java
浏览文件 @
0a34c27f
...
...
@@ -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
;
...
...
src/main/java/com/cmeeting/email/AzureADEmailSender.java
0 → 100644
浏览文件 @
0a34c27f
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
src/main/java/com/cmeeting/EmailSender.java
→
src/main/java/com/cmeeting/
email/
EmailSender.java
浏览文件 @
0a34c27f
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
src/main/java/com/cmeeting/email/GraphApiWithOkHttp2.java
0 → 100644
浏览文件 @
0a34c27f
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
());
// 打印错误详情
}
}
}
}
src/main/java/com/cmeeting/QQMailSender.java
→
src/main/java/com/cmeeting/
email/
QQMailSender.java
浏览文件 @
0a34c27f
package
com
.
cmeeting
;
package
com
.
cmeeting
.
email
;
import
javax.activation.DataHandler
;
import
javax.activation.DataSource
;
...
...
src/main/java/com/cmeeting/email/TestExchangeAndOauth2.java
0 → 100644
浏览文件 @
0a34c27f
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
);
}
}
}
src/main/java/com/cmeeting/job/CmeetingJob.java
0 → 100644
浏览文件 @
0a34c27f
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(" ", " ")
.replaceAll("<", "<")
.replaceAll(">", ">");
}*/
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
();
}
}
src/main/java/com/cmeeting/job/CmeetingJob2.java
0 → 100644
浏览文件 @
0a34c27f
/*
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);
}
}
}
*/
src/main/java/com/cmeeting/mapper/primary/AuthMapper.java
浏览文件 @
0a34c27f
...
...
@@ -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
te
antId
);
List
<
CoreModulePermissions
>
getAuthByTargrtId
(
@Param
(
"targetId"
)
String
targetId
,
@Param
(
"tenantId"
)
String
ten
antId
);
}
src/main/java/com/cmeeting/mapper/secondary/SysUserSysMapper.java
0 → 100644
浏览文件 @
0a34c27f
package
com
.
cmeeting
.
mapper
.
secondary
;
import
org.apache.ibatis.annotations.Mapper
;
@Mapper
public
interface
SysUserSysMapper
{
String
getCompanyEmail
(
String
wid
,
String
tenantId
);
}
src/main/java/com/cmeeting/pojo/MeetingInfo.java
0 → 100644
浏览文件 @
0a34c27f
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
src/main/java/com/cmeeting/util/SimpleMarkdownToWord.java
0 → 100644
浏览文件 @
0a34c27f
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
(
"转换完成!"
);
}
}
src/main/resources/mapper/primary/AuthMapper.xml
浏览文件 @
0a34c27f
...
...
@@ -17,8 +17,9 @@
core_module_permissions
WHERE
target_id = #{targetId}
<if
test=
"te
antId != null and te
antId != ''"
>
AND tenant_id = #{teantId}
<if
test=
"te
nantId != null and ten
antId != ''"
>
AND tenant_id = #{te
n
antId}
</if>
</select>
</mapper>
\ No newline at end of file
src/main/resources/mapper/secondary/SysUserSysMapper.xml
0 → 100644
浏览文件 @
0a34c27f
<?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
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论