Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
cmeeting
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
翟斌
cmeeting
Commits
e0b7d23d
提交
e0b7d23d
authored
5月 30, 2025
作者:
duanxincheng
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
二次重试机制;模板创建与维护;会议纪要生成权限控制
父级
b87c127c
隐藏空白字符变更
内嵌
并排
正在显示
40 个修改的文件
包含
2399 行增加
和
754 行删除
+2399
-754
pom.xml
+4
-4
src/main/java/com/cmeeting/TencentMeetingCallbackController.java
+1
-1
src/main/java/com/cmeeting/WeComAndTencentMeeting.java
+239
-239
src/main/java/com/cmeeting/controller/TencentMeetingController.java
+4
-5
src/main/java/com/cmeeting/controller/UnifiedController.java
+26
-26
src/main/java/com/cmeeting/controller/WeComcontroller.java
+0
-1
src/main/java/com/cmeeting/dto/UserDTO.java
+35
-0
src/main/java/com/cmeeting/email/EmailSender.java
+59
-54
src/main/java/com/cmeeting/job/CmeetingJob.java
+166
-33
src/main/java/com/cmeeting/job/EmailPushTask.java
+188
-0
src/main/java/com/cmeeting/job/FileProcessTask.java
+133
-129
src/main/java/com/cmeeting/mapper/primary/AuthMapper.java
+2
-1
src/main/java/com/cmeeting/mapper/primary/MeetingRecordTemplateMapper.java
+12
-0
src/main/java/com/cmeeting/mapper/primary/UserIdMapper.java
+2
-1
src/main/java/com/cmeeting/mapper/primary/WeComUserMapper.java
+1
-1
src/main/java/com/cmeeting/mapper/secondary/SysUserSysMapper.java
+12
-0
src/main/java/com/cmeeting/pojo/MeetingInfo.java
+26
-2
src/main/java/com/cmeeting/pojo/MeetingRecordTemplate.java
+75
-0
src/main/java/com/cmeeting/pojo/TencentMeetingUser.java
+16
-26
src/main/java/com/cmeeting/pojo/UserId.java
+9
-20
src/main/java/com/cmeeting/pojo/WeComUser.java
+16
-22
src/main/java/com/cmeeting/service/FileProcessCallbackHandler.java
+1
-1
src/main/java/com/cmeeting/service/FileProcessProducer.java
+51
-6
src/main/java/com/cmeeting/service/MeetingRecordTemplateService.java
+7
-0
src/main/java/com/cmeeting/service/TecentMeetingService.java
+7
-1
src/main/java/com/cmeeting/service/WeComService.java
+7
-0
src/main/java/com/cmeeting/service/impl/MeetingRecordTemplateServiceImpl.java
+12
-0
src/main/java/com/cmeeting/service/impl/TecentMeetingServiceImpl.java
+359
-50
src/main/java/com/cmeeting/service/impl/WeComServiceImpl.java
+156
-46
src/main/java/com/cmeeting/util/MinioUtils.java
+10
-0
src/main/java/com/cmeeting/util/RedisUtils.java
+641
-0
src/main/java/com/cmeeting/util/WeComUserService.java
+0
-19
src/main/java/com/cmeeting/vo/TencentMeetingVO.java
+4
-1
src/main/resources/application.yml
+65
-50
src/main/resources/mapper/primary/AuthMapper.xml
+3
-2
src/main/resources/mapper/primary/MeetingInfoMapper.xml
+10
-5
src/main/resources/mapper/primary/UserIdMapper.xml
+6
-5
src/main/resources/mapper/primary/WeComUserMapper.xml
+6
-3
src/main/resources/mapper/secondary/SysUserSysMapper.xml
+28
-0
src/main/resources/template/data_net_template.docx
+0
-0
没有找到文件。
pom.xml
浏览文件 @
e0b7d23d
...
...
@@ -330,11 +330,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>
junit
</groupId>
<artifactId>
junit
</artifactId>
<version>
RELEASE
</version>
<scope>
test
</scope>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-data-redis
</artifactId>
<version>
2.7.0
</version>
<!-- 与你的Spring Boot版本一致 -->
</dependency>
</dependencies>
...
...
src/main/java/com/cmeeting/TencentMeetingCallbackController.java
浏览文件 @
e0b7d23d
...
...
@@ -146,7 +146,7 @@ public class TencentMeetingCallbackController {
//通过智能体id查询该id下的部门和用户
String
targetId
=
"1815393211829587968"
;
//职能体id
String
tenantId
=
"1806976109082972160"
;
//租户id
List
<
CoreModulePermissions
>
auths
=
authMapper
.
getAuthByTarg
r
tId
(
targetId
,
tenantId
);
List
<
CoreModulePermissions
>
auths
=
authMapper
.
getAuthByTarg
e
tId
(
targetId
,
tenantId
);
// 创建两个集合分别存储type=0(部门)和type=1(员工)的数据
List
<
CoreModulePermissions
>
type0List
=
new
ArrayList
<>();
List
<
CoreModulePermissions
>
type1List
=
new
ArrayList
<>();
...
...
src/main/java/com/cmeeting/WeComAndTencentMeeting.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
;
import
com.cmeeting.pojo.TencentMeetingUser
;
import
com.cmeeting.pojo.WeComUser
;
import
com.google.gson.Gson
;
import
com.google.gson.JsonArray
;
import
com.google.gson.JsonElement
;
import
com.google.gson.JsonObject
;
import
java.io.BufferedReader
;
import
java.io.InputStreamReader
;
import
java.net.HttpURLConnection
;
import
java.net.URL
;
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.user_manager.api.UserManagerApi
;
import
com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200Response
;
import
com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200ResponseUsersInner
;
import
org.springframework.stereotype.Service
;
import
java.math.BigInteger
;
import
java.security.SecureRandom
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.stream.Collectors
;
@Service
public
class
WeComAndTencentMeeting
{
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"
;
public
static
void
main
(
String
[]
args
)
{
try
{
// 示例:获取部门ID为6的用户列表
int
departmentId
=
6
;
JsonObject
result
=
getUserListByDepartment
(
departmentId
);
// 处理返回结果
if
(
result
.
get
(
"errcode"
).
getAsInt
()
==
0
)
{
System
.
out
.
println
(
"企微获取用户列表成功:"
);
JsonArray
userList
=
result
.
getAsJsonArray
(
"userlist"
);
for
(
int
i
=
0
;
i
<
userList
.
size
();
i
++)
{
JsonObject
user
=
userList
.
get
(
i
).
getAsJsonObject
();
System
.
out
.
println
(
"姓名: "
+
user
.
get
(
"name"
).
getAsString
()
+
", UserID: "
+
user
.
get
(
"userid"
).
getAsString
());
}
}
else
{
System
.
out
.
println
(
"企微获取用户列表失败: "
+
result
.
get
(
"errmsg"
).
getAsString
());
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
/**
* 腾讯会议通过通讯录获取员工信息
*/
// 1. 构造client客户端
Client
client
=
new
Client
.
Builder
()
.
withAppId
(
"211153201"
).
withSdkId
(
"28370276340"
)
.
withSecret
(
"BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6"
,
"3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO"
)
.
build
();
// 2. 开始循环获取用户列表
fetchUsersInBatches
(
client
,
3
);
}
/**
* 将企业微信返回的JSON转换为WeComUser列表
*/
public
static
List
<
WeComUser
>
convertJsonToWeComUsers
(
JsonObject
json
)
{
List
<
WeComUser
>
users
=
new
ArrayList
<>();
if
(
json
.
has
(
"userlist"
)
&&
json
.
get
(
"userlist"
).
isJsonArray
())
{
JsonArray
userList
=
json
.
getAsJsonArray
(
"userlist"
);
Gson
gson
=
new
Gson
();
for
(
JsonElement
element
:
userList
)
{
JsonObject
userJson
=
element
.
getAsJsonObject
();
WeComUser
user
=
new
WeComUser
();
// 根据企业微信API实际返回字段调整
user
.
setUserId
(
userJson
.
get
(
"userid"
).
getAsString
());
user
.
setUserName
(
userJson
.
get
(
"name"
).
getAsString
());
// 其他字段设置...
users
.
add
(
user
);
}
}
return
users
;
}
/**
* 标记企业微信重名用户
*/
public
static
void
markDuplicateNames
(
List
<
WeComUser
>
users
)
{
// 按姓名分组,统计每个名字出现的次数
Map
<
String
,
Long
>
nameCountMap
=
users
.
stream
()
.
collect
(
Collectors
.
groupingBy
(
WeComUser:
:
getUserName
,
Collectors
.
counting
()));
// 设置是否重名标志
users
.
forEach
(
user
->
{
if
(
nameCountMap
.
get
(
user
.
getUserName
())
>
1
)
{
user
.
setIsRepeatName
(
"1"
);
// 重名
}
else
{
user
.
setIsRepeatName
(
"0"
);
// 不重名
}
});
}
/**
* 标记企业微信重名用户
*/
public
static
void
markDuplicateNamesTecent
(
List
<
TencentMeetingUser
>
users
)
{
// 按姓名分组,统计每个名字出现的次数
Map
<
String
,
Long
>
nameCountMap
=
users
.
stream
()
.
collect
(
Collectors
.
groupingBy
(
TencentMeetingUser:
:
getUserName
,
Collectors
.
counting
()));
// 设置是否重名标志
users
.
forEach
(
user
->
{
if
(
nameCountMap
.
get
(
user
.
getUserName
())
>
1
)
{
user
.
setIsrepeatName
(
"1"
);
// 重名
}
else
{
user
.
setIsrepeatName
(
"0"
);
// 不重名
}
});
}
/**
* 根据企业微信部门ID获取用户列表
*
* @param departmentId 部门ID
* @return 包含用户列表的JsonObject
* @throws Exception
*/
public
static
JsonObject
getUserListByDepartment
(
int
departmentId
)
throws
Exception
{
String
urlStr
=
BASE_URL
+
"?access_token="
+
ACCESS_TOKEN
+
"&department_id="
+
departmentId
;
URL
url
=
new
URL
(
urlStr
);
HttpURLConnection
conn
=
(
HttpURLConnection
)
url
.
openConnection
();
conn
.
setRequestMethod
(
"GET"
);
int
responseCode
=
conn
.
getResponseCode
();
if
(
responseCode
==
HttpURLConnection
.
HTTP_OK
)
{
BufferedReader
in
=
new
BufferedReader
(
new
InputStreamReader
(
conn
.
getInputStream
()));
String
inputLine
;
StringBuilder
response
=
new
StringBuilder
();
while
((
inputLine
=
in
.
readLine
())
!=
null
)
{
response
.
append
(
inputLine
);
}
in
.
close
();
// 使用Gson解析JSON响应
Gson
gson
=
new
Gson
();
return
gson
.
fromJson
(
response
.
toString
(),
JsonObject
.
class
);
}
else
{
throw
new
RuntimeException
(
"HTTP GET请求失败,错误码: "
+
responseCode
);
}
}
/**
* 腾讯会议获取员工信息
*/
/**
* 分批次获取用户列表
*
* @param client 客户端实例
* @param pageSize 每页大小
* @return
*/
public
static
List
<
TencentMeetingUser
>
fetchUsersInBatches
(
Client
client
,
int
pageSize
)
{
int
currentPage
=
1
;
boolean
hasMore
=
true
;
int
totalUsers
=
0
;
List
<
TencentMeetingUser
>
userList
=
new
ArrayList
<>();
// 创建集合存储用户数据
while
(
hasMore
)
{
try
{
// 1. 构造请求参数
UserManagerApi
.
ApiV1UsersListGetRequest
request
=
new
UserManagerApi
.
ApiV1UsersListGetRequest
.
Builder
()
.
page
(
String
.
valueOf
(
currentPage
))
.
pageSize
(
String
.
valueOf
(
pageSize
))
.
operatorId
(
"woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA"
)
.
operatorIdType
(
"1"
)
.
build
();
// 2. 构造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
);
// 3. 发送请求
UserManagerApi
.
ApiV1UsersListGetResponse
response
=
client
.
user_manager
().
v1UsersListGet
(
request
,
authenticatorBuilder
);
// 4. 处理响应并转换为TencentMeetingUser对象
V1UsersListGet200Response
responseData
=
response
.
getData
();
List
<
V1UsersListGet200ResponseUsersInner
>
users
=
responseData
.
getUsers
();
int
currentSize
=
users
.
size
();
totalUsers
+=
currentSize
;
System
.
out
.
printf
(
"第 %d 页,获取到 %d 个用户:\n"
,
currentPage
,
currentSize
);
for
(
V1UsersListGet200ResponseUsersInner
user
:
users
)
{
// 创建TencentMeetingUser对象并设置属性
TencentMeetingUser
meetingUser
=
new
TencentMeetingUser
();
meetingUser
.
setUserId
(
user
.
getUserid
());
meetingUser
.
setUserName
(
user
.
getUsername
());
meetingUser
.
setIsrepeatName
(
"0"
);
// 默认设为不重名,可根据实际业务调整
userList
.
add
(
meetingUser
);
// 添加到集合
System
.
out
.
printf
(
"用户ID: %s, 用户名: %s\n"
,
user
.
getUserid
(),
user
.
getUsername
());
}
// 5. 检查是否还有更多数据
if
(
currentSize
==
0
||
currentSize
<
pageSize
)
{
hasMore
=
false
;
System
.
out
.
printf
(
"\n所有用户获取完成,共获取 %d 个用户\n"
,
totalUsers
);
}
else
{
currentPage
++;
}
}
catch
(
ClientException
e
)
{
System
.
out
.
printf
(
"客户端错误: %s\n"
,
e
);
throw
new
RuntimeException
(
e
);
}
catch
(
ServiceException
e
)
{
System
.
out
.
printf
(
"服务端错误: %s\n"
,
e
);
System
.
out
.
printf
(
"完整响应: %s\n"
,
new
String
(
e
.
getApiResp
().
getRawBody
()));
throw
new
RuntimeException
(
e
);
}
}
return
userList
;
// 返回用户集合
}
}
//
package com.cmeeting;
//
//
import com.cmeeting.pojo.TencentMeetingUser;
//
import com.cmeeting.pojo.WeComUser;
//
import com.google.gson.Gson;
//
import com.google.gson.JsonArray;
//
import com.google.gson.JsonElement;
//
import com.google.gson.JsonObject;
//
//
import java.io.BufferedReader;
//
import java.io.InputStreamReader;
//
import java.net.HttpURLConnection;
//
import java.net.URL;
//
//
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.user_manager.api.UserManagerApi;
//
import com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200Response;
//
import com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200ResponseUsersInner;
//
import org.springframework.stereotype.Service;
//
//
import java.math.BigInteger;
//
import java.security.SecureRandom;
//
import java.util.ArrayList;
//
import java.util.List;
//
import java.util.Map;
//
import java.util.stream.Collectors;
//
@Service
//
public class WeComAndTencentMeeting {
//
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";
//
public static void main(String[] args) {
//
try {
//
// 示例:获取部门ID为6的用户列表
//
int departmentId = 6;
//
JsonObject result = getUserListByDepartment(departmentId);
//
// 处理返回结果
//
if (result.get("errcode").getAsInt() == 0) {
//
System.out.println("企微获取用户列表成功:");
//
JsonArray userList = result.getAsJsonArray("userlist");
//
for (int i = 0; i < userList.size(); i++) {
//
JsonObject user = userList.get(i).getAsJsonObject();
//
System.out.println("姓名: " + user.get("name").getAsString() +
//
", UserID: " + user.get("userid").getAsString());
//
}
//
} else {
//
System.out.println("企微获取用户列表失败: " + result.get("errmsg").getAsString());
//
}
//
} catch (Exception e) {
//
e.printStackTrace();
//
}
//
/**
//
* 腾讯会议通过通讯录获取员工信息
//
*/
//
// 1. 构造client客户端
//
Client client = new Client.Builder()
//
.withAppId("211153201").withSdkId("28370276340")
//
.withSecret("BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6", "3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO")
//
.build();
//
//
// 2. 开始循环获取用户列表
//
fetchUsersInBatches(client, 3);
//
}
//
/**
//
* 将企业微信返回的JSON转换为WeComUser列表
//
*/
//
public static List<WeComUser> convertJsonToWeComUsers(JsonObject json) {
//
List<WeComUser> users = new ArrayList<>();
//
//
if (json.has("userlist") && json.get("userlist").isJsonArray()) {
//
JsonArray userList = json.getAsJsonArray("userlist");
//
Gson gson = new Gson();
//
//
for (JsonElement element : userList) {
//
JsonObject userJson = element.getAsJsonObject();
//
WeComUser user = new WeComUser();
//
//
// 根据企业微信API实际返回字段调整
//
user.setUserId(userJson.get("userid").getAsString());
//
user.setUserName(userJson.get("name").getAsString());
//
//
// 其他字段设置...
//
users.add(user);
//
}
//
}
//
//
return users;
//
}
//
//
/**
//
* 标记企业微信重名用户
//
*/
//
public static void markDuplicateNames(List<WeComUser> users) {
//
// 按姓名分组,统计每个名字出现的次数
//
Map<String, Long> nameCountMap = users.stream()
//
.collect(Collectors.groupingBy(WeComUser::getUserName, Collectors.counting()));
//
//
// 设置是否重名标志
//
users.forEach(user -> {
//
if (nameCountMap.get(user.getUserName()) > 1) {
//
user.setIsRepeatName("1"); // 重名
//
} else {
//
user.setIsRepeatName("0"); // 不重名
//
}
//
});
//
}
//
//
/**
//
* 标记企业微信重名用户
//
*/
//
public static void markDuplicateNamesTecent(List<TencentMeetingUser> users) {
//
// 按姓名分组,统计每个名字出现的次数
//
Map<String, Long> nameCountMap = users.stream()
//
.collect(Collectors.groupingBy(TencentMeetingUser::getUserName, Collectors.counting()));
//
//
// 设置是否重名标志
//
users.forEach(user -> {
//
if (nameCountMap.get(user.getUserName()) > 1) {
//
user.setIsrepeatName("1"); // 重名
//
} else {
//
user.setIsrepeatName("0"); // 不重名
//
}
//
});
//
}
//
//
/**
//
* 根据企业微信部门ID获取用户列表
//
*
//
* @param departmentId 部门ID
//
* @return 包含用户列表的JsonObject
//
* @throws Exception
//
*/
//
public static JsonObject getUserListByDepartment(int departmentId) throws Exception {
//
String urlStr = BASE_URL + "?access_token=" + ACCESS_TOKEN + "&department_id=" + departmentId;
//
//
URL url = new URL(urlStr);
//
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//
conn.setRequestMethod("GET");
//
//
int responseCode = conn.getResponseCode();
//
if (responseCode == HttpURLConnection.HTTP_OK) {
//
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
//
String inputLine;
//
StringBuilder response = new StringBuilder();
//
//
while ((inputLine = in.readLine()) != null) {
//
response.append(inputLine);
//
}
//
in.close();
//
//
// 使用Gson解析JSON响应
//
Gson gson = new Gson();
//
return gson.fromJson(response.toString(), JsonObject.class);
//
} else {
//
throw new RuntimeException("HTTP GET请求失败,错误码: " + responseCode);
//
}
//
}
//
//
/**
//
* 腾讯会议获取员工信息
//
*/
//
/**
//
* 分批次获取用户列表
//
*
//
* @param client 客户端实例
//
* @param pageSize 每页大小
//
* @return
//
*/
//
public static List<TencentMeetingUser> fetchUsersInBatches(Client client, int pageSize) {
//
int currentPage = 1;
//
boolean hasMore = true;
//
int totalUsers = 0;
//
List<TencentMeetingUser> userList = new ArrayList<>(); // 创建集合存储用户数据
//
//
while (hasMore) {
//
try {
//
// 1. 构造请求参数
//
UserManagerApi.ApiV1UsersListGetRequest request =
//
new UserManagerApi.ApiV1UsersListGetRequest.Builder()
//
.page(String.valueOf(currentPage))
//
.pageSize(String.valueOf(pageSize))
//
.operatorId("woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA")
//
.operatorIdType("1")
//
.build();
//
//
// 2. 构造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);
//
//
// 3. 发送请求
//
UserManagerApi.ApiV1UsersListGetResponse response =
//
client.user_manager().v1UsersListGet(request, authenticatorBuilder);
//
//
// 4. 处理响应并转换为TencentMeetingUser对象
//
V1UsersListGet200Response responseData = response.getData();
//
List<V1UsersListGet200ResponseUsersInner> users = responseData.getUsers();
//
int currentSize = users.size();
//
totalUsers += currentSize;
//
//
System.out.printf("第 %d 页,获取到 %d 个用户:\n", currentPage, currentSize);
//
//
for (V1UsersListGet200ResponseUsersInner user : users) {
//
// 创建TencentMeetingUser对象并设置属性
//
TencentMeetingUser meetingUser = new TencentMeetingUser();
//
meetingUser.setUserId(user.getUserid());
//
meetingUser.setUserName(user.getUsername());
//
meetingUser.setIsrepeatName("0"); // 默认设为不重名,可根据实际业务调整
//
//
userList.add(meetingUser); // 添加到集合
//
System.out.printf("用户ID: %s, 用户名: %s\n", user.getUserid(), user.getUsername());
//
}
//
//
// 5. 检查是否还有更多数据
//
if (currentSize == 0 || currentSize < pageSize) {
//
hasMore = false;
//
System.out.printf("\n所有用户获取完成,共获取 %d 个用户\n", totalUsers);
//
} else {
//
currentPage++;
//
}
//
//
} catch (ClientException e) {
//
System.out.printf("客户端错误: %s\n", e);
//
throw new RuntimeException(e);
//
} catch (ServiceException e) {
//
System.out.printf("服务端错误: %s\n", e);
//
System.out.printf("完整响应: %s\n", new String(e.getApiResp().getRawBody()));
//
throw new RuntimeException(e);
//
}
//
}
//
//
return userList; // 返回用户集合
//
//
}
//
}
src/main/java/com/cmeeting/controller/TencentMeetingController.java
浏览文件 @
e0b7d23d
...
...
@@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.RestController;
import
java.util.List
;
import
static
com
.
cmeeting
.
WeComAndTencentMeeting
.*;
@RestController
@RequestMapping
(
"/tencent"
)
...
...
@@ -24,8 +23,8 @@ public class TencentMeetingController {
tecentMeetingService
.
doUsers
();
}
@GetMapping
(
"/getMeetingFiles"
)
public
void
getMeetingFiles
(){
tecentMeetingService
.
getMeetingFiles
();
}
//
@GetMapping("/getMeetingFiles")
//
public void getMeetingFiles(){
//
tecentMeetingService.getMeetingFiles();
//
}
}
src/main/java/com/cmeeting/controller/UnifiedController.java
浏览文件 @
e0b7d23d
...
...
@@ -127,32 +127,32 @@ public class UnifiedController {
//取消预约会议
}
//todo 待测试
@GetMapping
(
"/sameNameInsertTid"
)
public
void
sameNameInsertTid
(
String
corpid
,
String
corpsecret
)
throws
IOException
{
String
weComToken
=
getWeComToken
(
corpid
,
corpsecret
);
List
<
WeComUser
>
sameNameUsers
=
weComUserMapper
.
getSameName
();
List
<
UserId
>
userIds
=
new
ArrayList
<>();
for
(
WeComUser
user
:
sameNameUsers
)
{
Map
<
String
,
String
>
meetingCodeAndMeetingid
=
createMeeting
(
user
.
getUserId
(),
weComToken
);
String
meetingId
=
meetingCodeAndMeetingid
.
get
(
"meetingid"
);
String
meetingCode
=
meetingCodeAndMeetingid
.
get
(
"meeting_code"
);
if
(
meetingCode
==
null
||
meetingCode
.
isEmpty
())
{
System
.
err
.
println
(
"会议创建失败,跳过用户: "
+
user
.
getUserId
());
continue
;
}
// 3.2 查询会议详情,获取主持人腾讯会议 userid(假设是 host_userid)
String
hostUserId
=
getMeetingHost
(
meetingCode
);
if
(
hostUserId
==
null
||
hostUserId
.
isEmpty
())
{
System
.
err
.
println
(
"获取主持人失败,跳过会议: "
+
meetingCode
);
cancelMeeting
(
meetingId
,
weComToken
);
// 尝试取消无效会议
continue
;
}
UserId
userId
=
new
UserId
(
user
.
getUserName
(),
user
.
getUserId
(),
hostUserId
);
userIds
.
add
(
userId
);
}
userIdMapper
.
insertUsers
(
userIds
);
}
//
//todo 待测试
//
@GetMapping("/sameNameInsertTid")
//
public void sameNameInsertTid(String corpid, String corpsecret) throws IOException {
//
String weComToken = getWeComToken(corpid,corpsecret);
//
List<WeComUser> sameNameUsers = weComUserMapper.getSameName();
//
List<UserId> userIds = new ArrayList<>();
//
for (WeComUser user : sameNameUsers) {
//
Map<String, String> meetingCodeAndMeetingid = createMeeting(user.getUserId(), weComToken);
//
String meetingId = meetingCodeAndMeetingid.get("meetingid");
//
String meetingCode = meetingCodeAndMeetingid.get("meeting_code");
//
if (meetingCode == null || meetingCode.isEmpty()) {
//
System.err.println("会议创建失败,跳过用户: " + user.getUserId());
//
continue;
//
}
//
// 3.2 查询会议详情,获取主持人腾讯会议 userid(假设是 host_userid)
//
String hostUserId = getMeetingHost(meetingCode);
//
if (hostUserId == null || hostUserId.isEmpty()) {
//
System.err.println("获取主持人失败,跳过会议: " + meetingCode);
//
cancelMeeting(meetingId, weComToken); // 尝试取消无效会议
//
continue;
//
}
//
UserId userId = new UserId(user.getUserName(), user.getUserId(), hostUserId);
//
userIds.add(userId);
//
}
//
userIdMapper.insertUsers(userIds);
//
}
/**
* 通过企业微信接口取消预约会议
...
...
src/main/java/com/cmeeting/controller/WeComcontroller.java
浏览文件 @
e0b7d23d
...
...
@@ -18,7 +18,6 @@ import java.net.URL;
import
java.util.ArrayList
;
import
java.util.List
;
import
static
com
.
cmeeting
.
WeComAndTencentMeeting
.*;
@RestController
@RequestMapping
(
"/wecom"
)
...
...
src/main/java/com/cmeeting/dto/UserDTO.java
0 → 100644
浏览文件 @
e0b7d23d
// vo/LoginVO.java
package
com
.
cmeeting
.
dto
;
import
lombok.AllArgsConstructor
;
import
lombok.Builder
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
java.util.List
;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public
class
UserDTO
{
/**
* 员工姓名
*/
private
String
userName
;
/**
* 企业微信userid
*/
private
String
wid
;
/**
* 腾讯会议userid
*/
private
String
tid
;
/**
* 邮箱
*/
private
String
email
;
}
\ No newline at end of file
src/main/java/com/cmeeting/email/EmailSender.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
email
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Service
;
...
...
@@ -29,10 +30,10 @@ public class EmailSender {
* @param toEmail 收件人
* @param subject 邮件主题
* @param attachmentPath 附件路径
* @param
recordFileId 转录文件ID
* @param
meetingId 会议id
* @return
*/
public
boolean
sendEmailWithAttachment
(
String
toEmail
,
String
subject
,
String
attachmentPath
,
String
recordFileId
)
throws
Exception
{
public
boolean
sendEmailWithAttachment
(
String
toEmail
,
String
subject
,
String
attachmentPath
,
String
meetingId
)
{
// 邮件服务器配置
Properties
props
=
new
Properties
();
props
.
put
(
"mail.smtp.host"
,
SMTP_HOST
);
...
...
@@ -61,60 +62,63 @@ public class EmailSender {
" 附件为您本次会议的会议纪要,烦请下载查看"
;
AtomicInteger
retryCount
=
new
AtomicInteger
(
0
);
boolean
isSent
=
false
;
while
(
retryCount
.
intValue
()
<
MAX_RETRY
&&
!
isSent
){
try
{
// 创建邮件消息
Message
message
=
new
MimeMessage
(
session
);
message
.
setFrom
(
new
InternetAddress
(
SENDER
));
message
.
setRecipients
(
Message
.
RecipientType
.
TO
,
InternetAddress
.
parse
(
toEmail
));
message
.
setSubject
(
subject
);
// 创建消息体部分(正文)
MimeBodyPart
messageBodyPart
=
new
MimeBodyPart
();
messageBodyPart
.
setText
(
body
);
// 创建多部分消息
Multipart
multipart
=
new
MimeMultipart
();
// 添加文本部分
multipart
.
addBodyPart
(
messageBodyPart
);
// 添加附件部分
if
(
attachmentPath
!=
null
&&
!
attachmentPath
.
isEmpty
())
{
MimeBodyPart
attachmentPart
=
new
MimeBodyPart
();
DataSource
source
=
new
FileDataSource
(
attachmentPath
);
attachmentPart
.
setDataHandler
(
new
DataHandler
(
source
));
attachmentPart
.
setFileName
(
new
File
(
attachmentPath
).
getName
());
multipart
.
addBodyPart
(
attachmentPart
);
}
boolean
isSent
=
false
;
if
(
StringUtils
.
isEmpty
(
toEmail
)){
log
.
error
(
"收件邮箱为空,推送失败"
);
return
false
;
}
while
(
retryCount
.
intValue
()
<
MAX_RETRY
&&
!
isSent
){
try
{
// 创建邮件消息
Message
message
=
new
MimeMessage
(
session
);
message
.
setFrom
(
new
InternetAddress
(
SENDER
));
message
.
setRecipients
(
Message
.
RecipientType
.
TO
,
InternetAddress
.
parse
(
toEmail
));
message
.
setSubject
(
subject
);
// 创建消息体部分(正文)
MimeBodyPart
messageBodyPart
=
new
MimeBodyPart
();
messageBodyPart
.
setText
(
body
);
// 创建多部分消息
Multipart
multipart
=
new
MimeMultipart
();
// 添加文本部分
multipart
.
addBodyPart
(
messageBodyPart
);
// 添加附件部分
if
(
attachmentPath
!=
null
&&
!
attachmentPath
.
isEmpty
())
{
MimeBodyPart
attachmentPart
=
new
MimeBodyPart
();
DataSource
source
=
new
FileDataSource
(
attachmentPath
);
attachmentPart
.
setDataHandler
(
new
DataHandler
(
source
));
attachmentPart
.
setFileName
(
new
File
(
attachmentPath
).
getName
());
multipart
.
addBodyPart
(
attachmentPart
);
}
// 设置完整消息内容
message
.
setContent
(
multipart
);
// 发送邮件
Transport
.
send
(
message
);
log
.
error
(
"邮件已成功发送: recordFileId->{}"
,
recordFileId
);
isSent
=
true
;
}
catch
(
MessagingException
e
)
{
//todo 邮件失败记录
// 异常处理
retryCount
.
getAndIncrement
();
if
(
retryCount
.
intValue
()
>
MAX_RETRY
)
{
log
.
error
(
"邮件发送达到最大重试次数: recordFileId->{}"
,
recordFileId
);
throw
new
RuntimeException
(
e
);
}
// 指数退避
try
{
Thread
.
sleep
((
long
)
Math
.
pow
(
2
,
retryCount
.
intValue
())
*
1000
);
}
catch
(
InterruptedException
ie
)
{
Thread
.
currentThread
().
interrupt
();
throw
new
RuntimeException
(
"邮件发送重试失败"
,
ie
);
}
return
false
;
// 设置完整消息内容
message
.
setContent
(
multipart
);
// 发送邮件
Transport
.
send
(
message
);
log
.
error
(
"邮件已成功发送: meetingId->{}"
,
meetingId
);
isSent
=
true
;
}
catch
(
MessagingException
e
)
{
//todo 邮件失败记录
// 异常处理
retryCount
.
getAndIncrement
();
if
(
retryCount
.
intValue
()
>
MAX_RETRY
)
{
log
.
error
(
"邮件发送达到最大重试次数: meetingId->{}"
,
meetingId
);
throw
new
RuntimeException
(
e
);
}
// 指数退避
try
{
Thread
.
sleep
((
long
)
Math
.
pow
(
2
,
retryCount
.
intValue
())
*
1000
);
}
catch
(
InterruptedException
ie
)
{
Thread
.
currentThread
().
interrupt
();
throw
new
RuntimeException
(
"邮件发送重试失败"
,
ie
);
}
}
return
true
;
}
return
isSent
;
}
}
\ No newline at end of file
src/main/java/com/cmeeting/job/CmeetingJob.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
job
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.cmeeting.dto.UserDTO
;
import
com.cmeeting.mapper.primary.UserIdMapper
;
import
com.cmeeting.pojo.MeetingInfo
;
import
com.cmeeting.pojo.UserId
;
import
com.cmeeting.pojo.WeComUser
;
import
com.cmeeting.service.FileProcessProducer
;
import
com.cmeeting.service.MeetingInfoService
;
import
com.cmeeting.service.TecentMeetingService
;
import
com.cmeeting.service.WeComService
;
import
com.cmeeting.vo.TencentMeetingVO
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.scheduling.annotation.Scheduled
;
import
org.springframework.stereotype.Component
;
import
org.springframework.util.CollectionUtils
;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
import
java.time.ZoneId
;
import
java.time.format.DateTimeFormatter
;
import
java.util.*
;
import
java.util.concurrent.atomic.AtomicInteger
;
@Component
@Slf4j
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
<>());
@Autowired
private
WeComService
weComService
;
@Autowired
private
TecentMeetingService
tecentMeetingService
;
@Value
(
value
=
"${tencent.appId}"
)
private
String
tencentAppId
;
@Value
(
value
=
"${tencent.sdkId}"
)
private
String
tencentSdkId
;
@Value
(
value
=
"${tencent.secretId}"
)
private
String
tencentSecretId
;
@Value
(
value
=
"${tencent.secretKey}"
)
private
String
tencentSecretKey
;
@Value
(
value
=
"${tencent.admin.userId}"
)
private
String
tencentAdminUserId
;
@Autowired
private
MeetingInfoService
meetingInfoService
;
@Autowired
private
UserIdMapper
userIdMapper
;
@Autowired
private
FileProcessProducer
producer
;
/**
* 企微人员定时同步
*/
// @Scheduled(fixedRate = 5 * 60 * 1000
)
@Scheduled
(
cron
=
"0 30 6 * * ?"
)
public
void
weComUserSync
()
{
try
{
log
ger
.
info
(
"-------企微人员定时同步任务开始-------"
);
log
ger
.
info
(
"当前时间: "
+
LocalDate
.
now
().
format
(
DateTimeFormatter
.
ISO_LOCAL_DATE
));
log
.
info
(
"-------企微人员定时同步任务开始-------"
);
log
.
info
(
"当前时间: "
+
LocalDate
.
now
().
format
(
DateTimeFormatter
.
ISO_LOCAL_DATE
));
weComService
.
doUsers
();
log
ger
.
info
(
"-------企微人员定时同步任务结束--------"
);
log
.
info
(
"-------企微人员定时同步任务结束--------"
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
/**
*
企微
人员定时同步
*
腾讯会议
人员定时同步
*/
@Scheduled
(
cron
=
"0 0 7 * * ?"
)
// @Scheduled(fixedRate = 5 * 60 * 1000)
public
void
TencentUserSync
()
{
try
{
log
ger
.
info
(
"-------腾讯会议人员定时同步任务开始-------"
);
log
ger
.
info
(
"当前时间: "
+
LocalDate
.
now
().
format
(
DateTimeFormatter
.
ISO_LOCAL_DATE
));
log
.
info
(
"-------腾讯会议人员定时同步任务开始-------"
);
log
.
info
(
"当前时间: "
+
LocalDate
.
now
().
format
(
DateTimeFormatter
.
ISO_LOCAL_DATE
));
tecentMeetingService
.
doUsers
();
log
ger
.
info
(
"-------腾讯会议人员定时同步任务结束--------"
);
log
.
info
(
"-------腾讯会议人员定时同步任务结束--------"
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
@Scheduled
(
fixedRate
=
5
*
60
*
1000
)
/**
* 绑定企微和腾会人员关系
*/
// @Scheduled(fixedRate = 5 * 60 * 1000)
public
void
userBind
()
{
String
weComToken
=
weComService
.
getToken
();
//查出没有建立关联的企微人员
List
<
WeComUser
>
noBindUsers
=
weComService
.
noBindUsers
();
List
<
UserId
>
userIds
=
new
ArrayList
<>();
// List<WeComUser> sameNameUsers = noBindUsers.stream().filter(item -> "1".equals(item.getIsRepeatName())).collect(Collectors.toList());
// for (WeComUser user : sameNameUsers) {
// Map<String, String> meetingCodeAndMeetingid = weComService.createTempMeeting(user.getUserId(), weComToken);
// String meetingId = meetingCodeAndMeetingid.get("meetingid");
// String meetingCode = meetingCodeAndMeetingid.get("meeting_code");
// if (meetingCode == null || meetingCode.isEmpty()) {
// System.err.println("会议创建失败,跳过用户: " + user.getUserId());
// continue;
// }
// // 3.2 查询会议详情,获取主持人腾讯会议 userid(假设是 host_userid)
// String hostUserId = tecentMeetingService.getMeetingHost(meetingCode);
// if (hostUserId == null || hostUserId.isEmpty()) {
// System.err.println("获取主持人失败,跳过会议: " + meetingCode);
// weComService.cancelMeeting(meetingId, weComToken); // 尝试取消无效会议
// continue;
// }
// UserId userId = new UserId(user.getUserName(), user.getUserId(), hostUserId);
// userIds.add(userId);
// }
// List<WeComUser> normalUsers = noBindUsers.stream().filter(item -> "0".equals(item.getIsRepeatName())).collect(Collectors.toList());
for
(
WeComUser
user
:
noBindUsers
)
{
Map
<
String
,
String
>
meetingCodeAndMeetingid
=
weComService
.
createTempMeeting
(
user
.
getUserId
(),
weComToken
);
String
meetingId
=
meetingCodeAndMeetingid
.
get
(
"meetingid"
);
String
meetingCode
=
meetingCodeAndMeetingid
.
get
(
"meeting_code"
);
if
(
meetingCode
==
null
||
meetingCode
.
isEmpty
())
{
System
.
err
.
println
(
"会议创建失败,跳过用户: "
+
user
.
getUserId
());
continue
;
}
// 3.2 查询会议详情,获取主持人腾讯会议 userid(假设是 host_userid)
String
hostUserId
=
tecentMeetingService
.
getMeetingHost
(
meetingCode
);
if
(
hostUserId
==
null
||
hostUserId
.
isEmpty
())
{
System
.
err
.
println
(
"获取主持人失败,跳过会议: "
+
meetingCode
);
weComService
.
cancelMeeting
(
meetingId
,
weComToken
);
// 尝试取消无效会议
continue
;
}
UserId
userId
=
UserId
.
builder
().
userName
(
user
.
getUserName
()).
wid
(
user
.
getUserId
()).
tid
(
hostUserId
).
build
();
userIds
.
add
(
userId
);
}
userIdMapper
.
insertUsers
(
userIds
);
}
@Scheduled
(
fixedRate
=
5
*
60
*
1000
,
initialDelay
=
2
*
60
*
1000
)
// @Scheduled(fixedRate = 5 * 60 * 1000)
public
void
execute
()
{
// 定义时间格式化器
DateTimeFormatter
formatter
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd HH:mm:ss"
);
...
...
@@ -86,19 +137,101 @@ public class CmeetingJob {
Long
nowTimestamp
=
now
.
atZone
(
ZoneId
.
systemDefault
()).
toEpochSecond
();
Long
beforeDayTimestamp
=
beforeDay
.
atZone
(
ZoneId
.
systemDefault
()).
toEpochSecond
();
//日志记录
logger
.
info
(
"起始时间: "
+
beforeDay
.
format
(
formatter
)
+
" | Unix 时间戳: "
+
beforeDayTimestamp
);
logger
.
info
(
"结束时间: "
+
now
.
format
(
formatter
)
+
" | Unix 时间戳: "
+
nowTimestamp
);
logger
.
info
(
"----------------------------------"
);
List
<
TencentMeetingVO
.
RecordFile
>
meetingFiles
=
tecentMeetingService
.
getMeetingFiles
();
log
.
info
(
"起始时间: "
+
beforeDay
.
format
(
formatter
)
+
" | Unix 时间戳: "
+
beforeDayTimestamp
);
log
.
info
(
"结束时间: "
+
now
.
format
(
formatter
)
+
" | Unix 时间戳: "
+
nowTimestamp
);
log
.
info
(
"----------------------------------"
);
List
<
UserDTO
>
accessUserIds
=
tecentMeetingService
.
getAccessUserIds
();
if
(
CollectionUtils
.
isEmpty
(
accessUserIds
))
{
log
.
info
(
"无生成纪要权限的人员"
);
return
;
}
List
<
TencentMeetingVO
.
RecordFile
>
meetingFiles
=
tecentMeetingService
.
getMeetingFiles
(
accessUserIds
);
if
(
meetingFiles
==
null
||
meetingFiles
.
isEmpty
())
{
log
ger
.
info
(
"没有录制文件需要处理"
);
log
.
info
(
"没有录制文件需要处理"
);
return
;
}
// 提交处理任务
producer
.
submitBatchTasks
(
meetingFiles
,
"/"
);
producer
.
submitBatchTasks
(
meetingFiles
,
"/save/"
,
Boolean
.
FALSE
);
}
/**
* 定时扫描早于一小时之前的,所有未重试过的会议,重新生成纪要
*/
@Scheduled
(
fixedRate
=
30
*
60
*
1000
,
initialDelay
=
10
*
60
*
1000
)
public
void
meetingMinutesRetry
()
{
try
{
log
.
info
(
"-------生成纪要重试定时任务开始-------"
);
log
.
info
(
"当前时间: "
+
LocalDate
.
now
().
format
(
DateTimeFormatter
.
ISO_LOCAL_DATE
));
//查出所有早于一小时前的,生成失败且未重试过的会议
List
<
MeetingInfo
>
meetingInfoList
=
meetingInfoService
.
list
(
new
LambdaQueryWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getIsGenerated
,
Boolean
.
FALSE
)
.
eq
(
MeetingInfo:
:
getGenerateRetry
,
Boolean
.
FALSE
)
.
le
(
MeetingInfo:
:
getSyncTime
,
LocalDateTime
.
now
().
minusHours
(
1
))
);
if
(
meetingInfoList
==
null
||
meetingInfoList
.
isEmpty
())
{
log
.
info
(
"没有生成失败的会议需要重试"
);
return
;
}
List
<
TencentMeetingVO
.
RecordFile
>
meetingFiles
=
new
ArrayList
<>();
for
(
MeetingInfo
meetingInfo
:
meetingInfoList
)
{
TencentMeetingVO
.
RecordFile
recordFile
=
TencentMeetingVO
.
RecordFile
.
builder
()
.
meetingId
(
meetingInfo
.
getMeetingId
())
.
subMeetingId
(
meetingInfo
.
getSubMeetingId
())
.
recordFileIdList
(
Arrays
.
asList
(
meetingInfo
.
getRecordFileId
().
split
(
","
))).
build
();
meetingFiles
.
add
(
recordFile
);
}
// List<UserDTO> accessUserIds = tecentMeetingService.getAccessUserIds();
// 提交处理任务
producer
.
submitBatchTasks
(
meetingFiles
,
"/save/"
,
Boolean
.
TRUE
);
log
.
info
(
"-------生成纪要重试定时任务结束--------"
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
/**
* 定时扫描早于一小时之前的,所有邮件推送未重试过的会议,重新推送邮件
*/
@Scheduled
(
fixedRate
=
30
*
60
*
1000
,
initialDelay
=
15
*
60
*
1000
)
public
void
emailPushRetry
()
{
try
{
log
.
info
(
"-------邮件推送重试定时任务开始-------"
);
log
.
info
(
"当前时间: "
+
LocalDate
.
now
().
format
(
DateTimeFormatter
.
ISO_LOCAL_DATE
));
//查出所有早于一小时前的,邮件推送失败且未重试过的会议
List
<
MeetingInfo
>
meetingInfoList
=
meetingInfoService
.
list
(
new
LambdaQueryWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getIsGenerated
,
Boolean
.
TRUE
)
.
eq
(
MeetingInfo:
:
getEmailPushAccess
,
Boolean
.
TRUE
)
.
le
(
MeetingInfo:
:
getSyncTime
,
LocalDateTime
.
now
().
minusHours
(
1
))
);
if
(
meetingInfoList
==
null
||
meetingInfoList
.
isEmpty
())
{
log
.
info
(
"没有推送失败的邮件需要重试"
);
return
;
}
List
<
TencentMeetingVO
.
RecordFile
>
meetingFiles
=
new
ArrayList
<>();
for
(
MeetingInfo
meetingInfo
:
meetingInfoList
)
{
TencentMeetingVO
.
RecordFile
recordFile
=
TencentMeetingVO
.
RecordFile
.
builder
()
.
meetingId
(
meetingInfo
.
getMeetingId
())
.
subMeetingId
(
meetingInfo
.
getSubMeetingId
()).
build
();
meetingFiles
.
add
(
recordFile
);
}
// 提交处理任务
producer
.
submitEmailPushTasks
(
meetingFiles
,
"/save/"
);
log
.
info
(
"-------邮件推送重试定时任务结束--------"
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
}
src/main/java/com/cmeeting/job/EmailPushTask.java
0 → 100644
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
job
;
import
cn.chatbot.openai.completion.chat.ChatCompletionRequest
;
import
cn.chatbot.openai.completion.chat.ChatMessage
;
import
cn.chatbot.openai.completion.chat.ChatMessageRole
;
import
cn.chatbot.openai.completion.chat.Message
;
import
cn.chatbot.openai.service.LLMService
;
import
cn.hutool.core.util.IdUtil
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper
;
import
com.cmeeting.email.EmailSender
;
import
com.cmeeting.mapper.primary.MeetingInfoMapper
;
import
com.cmeeting.mapper.primary.MeetingRecordTemplateMapper
;
import
com.cmeeting.pojo.MeetingInfo
;
import
com.cmeeting.pojo.MeetingRecordTemplate
;
import
com.cmeeting.util.MinioUtils
;
import
com.deepoove.poi.XWPFTemplate
;
import
com.fasterxml.jackson.databind.JsonNode
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.fasterxml.jackson.dataformat.xml.XmlMapper
;
import
com.tencentcloudapi.wemeet.Client
;
import
com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder
;
import
com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator
;
import
com.tencentcloudapi.wemeet.service.meetings.api.MeetingsApi
;
import
com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdParticipantsGet200Response
;
import
com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner
;
import
com.tencentcloudapi.wemeet.service.records.api.RecordsApi
;
import
com.tencentcloudapi.wemeet.service.records.model.V1AddressesRecordFileIdGet200Response
;
import
com.tencentcloudapi.wemeet.service.records.model.V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
okhttp3.OkHttpClient
;
import
okhttp3.Request
;
import
okhttp3.Response
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.poi.xwpf.extractor.XWPFWordExtractor
;
import
org.apache.poi.xwpf.usermodel.XWPFDocument
;
import
org.springframework.stereotype.Service
;
import
java.io.*
;
import
java.math.BigInteger
;
import
java.nio.charset.Charset
;
import
java.nio.charset.StandardCharsets
;
import
java.nio.file.Files
;
import
java.nio.file.Paths
;
import
java.security.SecureRandom
;
import
java.time.Instant
;
import
java.time.LocalDateTime
;
import
java.time.ZoneId
;
import
java.time.format.DateTimeFormatter
;
import
java.util.*
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
java.util.stream.Collectors
;
@Data
@NoArgsConstructor
@Slf4j
@Service
public
class
EmailPushTask
{
private
String
meetingId
;
private
String
subMeetingId
;
private
String
savePath
;
private
Map
<
String
,
Object
>
metadata
;
private
static
final
int
MAX_RETRY
=
3
;
private
MeetingInfoMapper
meetingInfoMapper
;
private
MinioUtils
minioUtils
;
private
EmailSender
emailSender
;
private
MeetingRecordTemplateMapper
meetingRecordTemplateMapper
;
// 实际处理逻辑
public
void
process
()
{
Boolean
isSuccess
=
Boolean
.
FALSE
;
AtomicInteger
retryCount
=
new
AtomicInteger
(
0
);
MeetingInfo
meetingInfo
=
meetingInfoMapper
.
selectOne
(
new
LambdaQueryWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
));
while
(
retryCount
.
intValue
()
<=
MAX_RETRY
&&
!
isSuccess
)
{
try
{
String
nowTime
=
LocalDateTime
.
now
().
format
(
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd-HHmmss"
));
String
targetFileName
;
String
meetingName
;
String
recordXmlPath
=
meetingId
+
"-recordXmlPath-"
+
IdUtil
.
fastSimpleUUID
()
+
"xml"
;
String
xml
;
try
(
InputStream
is
=
minioUtils
.
getFile
(
recordXmlPath
)){
xml
=
convertInputStreamToString
(
is
);
}
catch
(
Exception
e
){
log
.
error
(
e
.
getMessage
());
continue
;
}
//将xml格式的内容转换为map,用于填充模板
Map
<
String
,
Object
>
dataModel
=
convertXmlToMap
(
xml
);
//追加参会人员信息
Map
<
String
,
Object
>
participantsMap
=
new
ConcurrentHashMap
<>();
participantsMap
.
put
(
"meeting_date"
,
meetingInfo
.
getStartTime
().
toLocalDate
().
format
(
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd"
)));
participantsMap
.
put
(
"meeting_location"
,
"线上腾讯会议"
);
participantsMap
.
put
(
"meeting_participants"
,
meetingInfo
.
getParticipantUsers
());
participantsMap
.
put
(
"meeting_host"
,
meetingInfo
.
getHost
());
dataModel
.
putAll
(
participantsMap
);
String
path
=
Thread
.
currentThread
().
getContextClassLoader
().
getResource
(
""
).
getPath
();
XWPFTemplate
template
=
XWPFTemplate
.
compile
(
path
+
"template/data_net_template.docx"
).
render
(
dataModel
);
meetingName
=
dataModel
.
get
(
"meeting_name"
)
!=
null
?
String
.
valueOf
(
dataModel
.
get
(
"meeting_name"
))
:
"腾讯会议纪要"
;
targetFileName
=
meetingName
+
"_"
+
nowTime
;
template
.
writeAndClose
(
new
FileOutputStream
(
savePath
+
targetFileName
+
".docx"
));
byte
[]
recordXmlData
=
Files
.
readAllBytes
(
Paths
.
get
(
savePath
+
targetFileName
+
".docx"
));
minioUtils
.
upload
(
recordXmlPath
,
recordXmlData
);
//邮件推送
isSuccess
=
emailSender
.
sendEmailWithAttachment
(
"duanxincheng@chatbot.cn"
,
meetingName
,
savePath
+
targetFileName
+
".docx"
,
meetingId
);
}
catch
(
Exception
e
)
{
// 异常处理
int
currentRetryCount
=
retryCount
.
getAndIncrement
();
if
(
currentRetryCount
>
MAX_RETRY
)
{
log
.
error
(
"达到最大重试次数:meetingId {}"
,
meetingId
);
}
else
{
// 指数退避
try
{
Thread
.
sleep
((
long
)
Math
.
pow
(
2
,
currentRetryCount
)
*
1000
);
}
catch
(
InterruptedException
ie
)
{
Thread
.
currentThread
().
interrupt
();
log
.
error
(
"重试失败: {}"
,
ie
.
getMessage
());
}
}
}
}
meetingInfoMapper
.
update
(
meetingInfo
,
new
LambdaUpdateWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
)
.
set
(
MeetingInfo:
:
getIsPushed
,
isSuccess
)
.
set
(
MeetingInfo:
:
getPushRetry
,
Boolean
.
TRUE
)
);
}
public
static
Map
<
String
,
Object
>
convertXmlToMap
(
String
xml
)
throws
Exception
{
XmlMapper
xmlMapper
=
new
XmlMapper
();
ObjectMapper
objectMapper
=
new
ObjectMapper
();
// 先将 XML 读取为 Map
Map
<?,
?>
xmlMap
=
xmlMapper
.
readValue
(
xml
,
Map
.
class
);
// 转换为更标准的 Map<String, Object>
Map
<
String
,
Object
>
map
=
objectMapper
.
convertValue
(
xmlMap
,
Map
.
class
);
//特殊处理map格式
for
(
Map
.
Entry
<
String
,
Object
>
entry
:
map
.
entrySet
())
{
Map
<
String
,
Object
>
value
=
(
Map
<
String
,
Object
>)
entry
.
getValue
();
Object
realValue
=
value
.
get
(
""
);
entry
.
setValue
(
realValue
);
}
return
map
;
}
public
static
String
convertInputStreamToString
(
InputStream
inputStream
)
throws
IOException
{
StringBuilder
result
=
new
StringBuilder
();
try
(
BufferedReader
reader
=
new
BufferedReader
(
new
InputStreamReader
(
inputStream
,
StandardCharsets
.
UTF_8
)))
{
String
line
;
while
((
line
=
reader
.
readLine
())
!=
null
)
{
result
.
append
(
line
);
// 如果需要保留换行符,可添加:result.append("\n");
}
}
return
result
.
toString
();
}
public
EmailPushTask
(
String
meetingId
,
String
subMeetingId
,
String
savePath
,
Map
<
String
,
Object
>
metadata
,
MeetingInfoMapper
meetingInfoMapper
,
MinioUtils
minioUtils
,
EmailSender
emailSender
,
MeetingRecordTemplateMapper
meetingRecordTemplateMapper
)
{
this
.
savePath
=
savePath
;
this
.
metadata
=
metadata
;
this
.
meetingId
=
meetingId
;
this
.
subMeetingId
=
subMeetingId
;
this
.
meetingInfoMapper
=
meetingInfoMapper
;
this
.
minioUtils
=
minioUtils
;
this
.
emailSender
=
emailSender
;
this
.
meetingRecordTemplateMapper
=
meetingRecordTemplateMapper
;
}
}
\ No newline at end of file
src/main/java/com/cmeeting/job/FileProcessTask.java
浏览文件 @
e0b7d23d
...
...
@@ -10,9 +10,13 @@ import cn.hutool.core.util.IdUtil;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper
;
import
com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper
;
import
com.cmeeting.dto.UserDTO
;
import
com.cmeeting.email.EmailSender
;
import
com.cmeeting.mapper.primary.MeetingInfoMapper
;
import
com.cmeeting.mapper.primary.MeetingRecordTemplateMapper
;
import
com.cmeeting.pojo.MeetingInfo
;
import
com.cmeeting.pojo.MeetingRecordTemplate
;
import
com.cmeeting.pojo.UserId
;
import
com.cmeeting.service.MeetingInfoService
;
import
com.cmeeting.util.MinioUtils
;
import
com.deepoove.poi.XWPFTemplate
;
...
...
@@ -37,11 +41,13 @@ import okhttp3.Response;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.poi.xwpf.extractor.XWPFWordExtractor
;
import
org.apache.poi.xwpf.usermodel.XWPFDocument
;
import
org.springframework.core.io.ClassPathResource
;
import
org.springframework.stereotype.Service
;
import
java.io.*
;
import
java.math.BigInteger
;
import
java.nio.charset.Charset
;
import
java.nio.charset.StandardCharsets
;
import
java.nio.file.Files
;
import
java.nio.file.Paths
;
import
java.security.SecureRandom
;
...
...
@@ -59,7 +65,7 @@ import java.util.stream.Collectors;
@Slf4j
@Service
public
class
FileProcessTask
{
private
String
recordFileId
;
private
List
<
String
>
recordFileIdList
;
private
String
meetingId
;
private
String
subMeetingId
;
private
String
savePath
;
...
...
@@ -73,136 +79,113 @@ public class FileProcessTask {
private
String
tencentSecretId
;
private
String
tencentSecretKey
;
private
String
tencentAdminUserId
;
private
String
llmApiAddr
;
private
Boolean
finalRetry
;
//表示是兜底重试机制
private
MeetingInfoMapper
meetingInfoMapper
;
private
MinioUtils
minioUtils
;
private
EmailSender
emailSender
;
private
MeetingRecordTemplateMapper
meetingRecordTemplateMapper
;
// 实际处理逻辑
public
void
process
()
{
boolean
isSuccess
=
false
;
while
(
retryCount
<=
MAX_RETRY
&&
!
isSuccess
)
{
try
{
Client
client
=
new
Client
.
Builder
()
.
withAppId
(
tencentAppId
).
withSdkId
(
tencentSdkId
)
.
withSecret
(
tencentSecretId
,
tencentSecretKey
)
.
build
();
// 获取参会成员明细
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetRequest
participantsRequest
=
new
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetRequest
.
Builder
(
meetingId
).
subMeetingId
(
subMeetingId
).
operatorId
(
tencentAdminUserId
).
operatorIdType
(
"1"
).
build
();
AuthenticatorBuilder
<
JWTAuthenticator
>
participantsAuthenticatorBuilder
=
new
JWTAuthenticator
.
Builder
()
.
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
));
Map
<
String
,
Object
>
participantsMap
=
new
ConcurrentHashMap
<>();
//主持人角色,以下角色都可以表示主持人
//用户角色:
//0:普通成员角色
//1:创建者角色
//2:主持人
//3:创建者+主持人
//4:游客
//5:游客+主持人
//6:联席主持人
//7:创建者+联席主持人
List
<
Long
>
hostRoleList
=
Arrays
.
asList
(
2L
,
3L
,
5L
,
6L
,
7L
);
try
{
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetResponse
participantsResponse
=
client
.
meetings
().
v1MeetingsMeetingIdParticipantsGet
(
participantsRequest
,
participantsAuthenticatorBuilder
);
V1MeetingsMeetingIdParticipantsGet200Response
data
=
participantsResponse
.
getData
();
String
scheduleStartTime
=
data
.
getScheduleStartTime
();
String
scheduleEndTime
=
data
.
getScheduleEndTime
();
ZoneId
zoneId
=
ZoneId
.
of
(
"Asia/Shanghai"
);
DateTimeFormatter
df
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd"
);
String
zonedDateTimeStart
=
Instant
.
ofEpochSecond
(
Long
.
valueOf
(
scheduleStartTime
)).
atZone
(
zoneId
).
format
(
df
);
// String zonedDateTimeEnd = Instant.ofEpochSecond(Long.valueOf(scheduleEndTime)).atZone(zoneId).format(df);
participantsMap
.
put
(
"meeting_date"
,
zonedDateTimeStart
);
participantsMap
.
put
(
"meeting_location"
,
"线上腾讯会议"
);
List
<
V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner
>
participants
=
data
.
getParticipants
();
participantsMap
.
put
(
"meeting_participants"
,
participants
.
stream
()
.
map
(
item
->
new
String
(
Base64
.
getDecoder
().
decode
(
item
.
getUserName
()))).
distinct
().
collect
(
Collectors
.
joining
(
"、"
)));
Optional
<
V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner
>
host
=
participants
.
stream
().
filter
(
item
->
hostRoleList
.
contains
(
item
.
getUserRole
())).
findFirst
();
participantsMap
.
put
(
"meeting_host"
,
host
.
isPresent
()
?
new
String
(
Base64
.
getDecoder
().
decode
(
host
.
get
().
getUserName
()))
:
"未知主持人"
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
log
.
error
(
e
.
getMessage
());
throw
new
RuntimeException
(
e
);
}
//查询录制转写详情
RecordsApi
.
ApiV1AddressesRecordFileIdGetRequest
addressRequest
=
new
RecordsApi
.
ApiV1AddressesRecordFileIdGetRequest
.
Builder
(
recordFileId
)
.
operatorId
(
tencentAdminUserId
)
.
operatorIdType
(
"1"
)
.
build
();
RecordsApi
.
ApiV1AddressesRecordFileIdGetResponse
addressResponse
=
client
.
records
().
v1AddressesRecordFileIdGet
(
addressRequest
,
new
JWTAuthenticator
.
Builder
().
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
)));
// 处理响应
if
(
addressResponse
!=
null
&&
addressResponse
.
getData
()
!=
null
)
{
log
.
info
(
"Successfully got address for record file {}"
,
recordFileId
);
V1AddressesRecordFileIdGet200Response
addressData
=
addressResponse
.
getData
();
// 获取AI会议转录文件
List
<
V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner
>
transcripts
=
addressData
.
getAiMeetingTranscripts
();
if
(
transcripts
!=
null
&&
!
transcripts
.
isEmpty
())
{
log
.
info
(
"Found {} AI meeting transcripts for record file {}"
,
transcripts
.
size
(),
recordFileId
);
// 处理每个转录文件
for
(
V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner
transcript
:
transcripts
)
{
String
fileType
=
transcript
.
getFileType
();
String
downloadUrl
=
transcript
.
getDownloadAddress
();
if
(
"txt"
.
equalsIgnoreCase
(
fileType
))
{
log
.
info
(
"AI Transcript - Type: {}, URL: {}"
,
fileType
,
downloadUrl
);
// 1. 下载文件
byte
[]
fileData
=
downloadFile
(
downloadUrl
);
// 2. 将二进制文件转换为文本
String
recordTextContent
=
new
String
(
fileData
);
// String recordTextContent = new String(Files.readAllBytes(Paths.get("/20250321145249-天达物流AIGC维修助手需要沟通-转写原文版-1.txt")));
if
(
StringUtils
.
isEmpty
(
recordTextContent
.
replaceAll
(
"\\n"
,
""
).
trim
())){
log
.
info
(
"获取的转录文本为空,跳过纪要生成,URL:{},fileRecordId:{}"
,
fileType
,
downloadUrl
,
recordFileId
);
continue
;
MeetingInfo
meetingInfo
=
meetingInfoMapper
.
selectOne
(
new
LambdaQueryWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
));
//每场会议可能会分段录制,查出每个文件的转录记录后拼接
StringBuffer
recordTextBuffer
=
new
StringBuffer
();
for
(
String
recordFileId
:
recordFileIdList
)
{
//查询录制转写详情
RecordsApi
.
ApiV1AddressesRecordFileIdGetRequest
addressRequest
=
new
RecordsApi
.
ApiV1AddressesRecordFileIdGetRequest
.
Builder
(
recordFileId
)
.
operatorId
(
tencentAdminUserId
)
.
operatorIdType
(
"1"
)
.
build
();
RecordsApi
.
ApiV1AddressesRecordFileIdGetResponse
addressResponse
=
client
.
records
().
v1AddressesRecordFileIdGet
(
addressRequest
,
new
JWTAuthenticator
.
Builder
().
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
)));
// 处理响应
if
(
addressResponse
!=
null
&&
addressResponse
.
getData
()
!=
null
)
{
log
.
info
(
"Successfully got address for record file {}"
,
recordFileId
);
V1AddressesRecordFileIdGet200Response
addressData
=
addressResponse
.
getData
();
// 获取AI会议转录文件
List
<
V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner
>
transcripts
=
addressData
.
getAiMeetingTranscripts
();
if
(
transcripts
!=
null
&&
!
transcripts
.
isEmpty
())
{
log
.
info
(
"Found {} AI meeting transcripts for record file {}"
,
transcripts
.
size
(),
recordFileId
);
// 处理每个转录文件
for
(
V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner
transcript
:
transcripts
)
{
String
fileType
=
transcript
.
getFileType
();
String
downloadUrl
=
transcript
.
getDownloadAddress
();
if
(
"txt"
.
equalsIgnoreCase
(
fileType
))
{
log
.
info
(
"AI Transcript - Type: {}, URL: {}"
,
fileType
,
downloadUrl
);
// 1. 下载文件
byte
[]
fileData
=
downloadFile
(
downloadUrl
);
// 2. 将二进制文件转换为文本
String
recordTextContent
=
new
String
(
fileData
);
// byte[] fileData = Files.readAllBytes(Paths.get("/20250514132555-中集AIGC项目例会-转写智能优化版.txt"));
// String recordTextContent = new String(fileData);
if
(
StringUtils
.
isNotEmpty
(
recordTextContent
.
replaceAll
(
"\\n"
,
""
).
trim
())){
recordTextBuffer
.
append
(
"\n\n"
);
recordTextBuffer
.
append
(
recordTextContent
);
}
}
}
// 3. 处理文件 (调用Claude API等)
String
processedResult
=
processWithClaude
(
recordTextContent
);
// 4. 保存结果
saveResult
(
savePath
,
processedResult
,
participantsMap
,
fileData
);
}
else
{
log
.
info
(
"No AI meeting transcripts found for record file {}"
,
recordFileId
);
}
}
else
{
log
.
warn
(
"Empty response for record file: {}"
,
recordFileId
);
}
}
else
{
log
.
info
(
"No AI meeting transcripts found for record file {}"
,
recordFileId
);
}
if
(
StringUtils
.
isEmpty
(
recordTextBuffer
.
toString
().
replaceAll
(
"\\n"
,
""
).
trim
())){
log
.
info
(
"获取的转录文本为空,跳过纪要生成,meetingId:{},fileRecordId:{}"
,
meetingId
,
recordFileIdList
.
toString
());
throw
new
RuntimeException
(
"获取的转录文本为空,跳过纪要生成"
);
}
// 3. 处理文件 (调用Claude API等)
String
processedResult
=
processWithClaude
(
recordTextBuffer
.
toString
());
}
else
{
log
.
warn
(
"Empty response for record file: {}"
,
recordFileId
);
}
isSuccess
=
true
;
// 4. 保存结果
saveResult
(
savePath
,
processedResult
,
recordTextBuffer
.
toString
().
getBytes
(
StandardCharsets
.
UTF_8
),
meetingInfo
);
isSuccess
=
true
;
}
catch
(
Exception
e
)
{
// 异常处理
retryCount
++;
if
(
retryCount
>
MAX_RETRY
)
{
log
.
error
(
"达到最大重试次数: {}"
,
recordFileId
);
throw
new
RuntimeException
(
e
);
}
// 指数退避
try
{
Thread
.
sleep
((
long
)
Math
.
pow
(
2
,
retryCount
)
*
1000
);
}
catch
(
InterruptedException
ie
)
{
Thread
.
currentThread
().
interrupt
();
throw
new
RuntimeException
(
"重试失败"
,
ie
);
log
.
error
(
"达到最大重试次数:meetingId {}"
,
meetingId
);
//如果是兜底重试,最终还是失败了,设置会议的重试状态为已重试
if
(
finalRetry
){
meetingInfoMapper
.
update
(
null
,
new
LambdaUpdateWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
)
.
set
(
MeetingInfo:
:
getGenerateRetry
,
Boolean
.
TRUE
));
}
}
else
{
// 指数退避
try
{
Thread
.
sleep
((
long
)
Math
.
pow
(
2
,
retryCount
)
*
1000
);
}
catch
(
InterruptedException
ie
)
{
Thread
.
currentThread
().
interrupt
();
throw
new
RuntimeException
(
"重试失败"
,
ie
);
}
}
}
}
...
...
@@ -248,12 +231,13 @@ public class FileProcessTask {
private
String
processWithClaude
(
String
textContent
)
{
//将文件传送给大模型处理
String
token
=
"AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO"
;
String
apiAddr
=
"https://bedrock.chatbot.cn
/llm/sse-invoke"
;
String
apiAddr
=
llmApiAddr
+
"
/llm/sse-invoke"
;
String
model
=
"anthropic.claude-3-5-sonnet-20240620-v1:0"
;
int
maxTokens
=
5000
;
//获取系统模板
MeetingRecordTemplate
meetingRecordTemplate
=
meetingRecordTemplateMapper
.
selectById
(
1
);
List
<
Message
>
messages
=
new
ArrayList
<>();
ChatMessage
chatMessage
=
new
ChatMessage
(
ChatMessageRole
.
USER
.
value
(),
"你是会议纪要助手,基于提供的会议记录,生成一份详细的会议纪要,并严格按照指定的XML格式输出。\\n\\n要求:\\n1. 会议名称:应简洁明了地反映会议主题,不超过30个字\\n2. 会议目的:需清晰阐述召开此次会议的原因和期望达成的目标,不超过50字\\n3. 会议要点:需全面概括会议内容,让读者理解会议讨论的核心问题,不要遗漏任何有价值的信息,每个要点需用序号标注并另起一行\\n4. 会议内容:需详细记录会议的背景信息、主要讨论内容、各参会人员的发言要点、达成的共识和决策等。内容应有逻辑性,分段清晰,重点突出,不可遗漏关键信息\\n5. 会议沟通内容:需按照一定的顺序组织内容,详细描述每个事项的内容,不要使用流水账形式,应包含具体的沟通过程、各方观点和达成的一致意见\\n6. 会后跟踪事项:详细描述会议结束之后,每个后续事项的责任人、事项内容、完成时间(若会议记录中没有提及,则填写待定)等信息,事项要具体到责任人\\n\\n注意事项:\\n1. 所有内容需基于原始会议记录,不要添加虚构信息\\n2. 会议纪要内容需详细充分,建议总字数不少于3000字,实际字数可根据原始会议记录的丰富程度进行适当调整\\n\\n输出格式必须严格按照以下XML结构,不允许有任何嵌套标签:\\n\\n<meeting_name label=\\\"会议名称\\\">会议名称</meeting_name>\\n<meeting_purpose label=\\\"会议目的\\\">会议目的</meeting_purpose>\\n<meeting_key_points label=\\\"会议要点\\\">会议要点</meeting_key_points>\\n<meeting_content label=\\\"会议内容\\\">会议内容</meeting_content>\\n<meeting_communication label=\\\"会议沟通内容\\\">会议沟通内容</meeting_communication>\\n<meeting_follow_up label=\\\"会后跟踪事项\\\">\\n在此填写会后跟踪事项,格式为:\\n1. 内容:XXX;责任人:XXX;完成时间:XXXX-XX-XX\\n2. 内容:XXX;责任人:XXX;完成时间:XXXX-XX-XX\\n...\\n</meeting_follow_up>\\n\\n完成XML格式的会议纪要后,必须在XML之外单独添加猜您想问部分,生成3-4条与会议内容相关的推荐问题。\\n\\n会议记录如下:"
);
ChatMessage
chatMessage
=
new
ChatMessage
(
ChatMessageRole
.
USER
.
value
(),
meetingRecordTemplate
.
getPrompt
()
);
messages
.
add
(
chatMessage
);
chatMessage
=
new
ChatMessage
(
ChatMessageRole
.
ASSISTANT
.
value
(),
"好的请提供会议记录"
);
messages
.
add
(
chatMessage
);
...
...
@@ -266,7 +250,7 @@ public class FileProcessTask {
return
ret
;
}
private
void
saveResult
(
String
path
,
String
content
,
Map
<
String
,
Object
>
participantsMap
,
byte
[]
recordData
)
{
private
void
saveResult
(
String
path
,
String
content
,
byte
[]
recordData
,
MeetingInfo
meetingInfo
)
{
String
nowTime
=
LocalDateTime
.
now
().
format
(
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd-HHmmss"
));
String
targetPath
=
path
+
nowTime
+
".json"
;
// 改为.json扩展名;
String
targetFileName
;
...
...
@@ -284,36 +268,51 @@ public class FileProcessTask {
log
.
info
(
"json文件已保存到: {}"
,
targetPath
);
//将xml格式的内容转换为map,用于填充模板
Map
<
String
,
Object
>
dataModel
=
convertXmlToMap
(
xml
);
//追加参会人员信息
Map
<
String
,
Object
>
participantsMap
=
new
ConcurrentHashMap
<>();
DateTimeFormatter
df
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd"
);
String
meetingDate
=
meetingInfo
.
getStartTime
().
toLocalDate
().
format
(
df
);
participantsMap
.
put
(
"meeting_date"
,
meetingDate
);
participantsMap
.
put
(
"meeting_location"
,
"线上腾讯会议"
);
participantsMap
.
put
(
"meeting_participants"
,
meetingInfo
.
getParticipantUsers
());
participantsMap
.
put
(
"meeting_host"
,
meetingInfo
.
getHost
());
dataModel
.
putAll
(
participantsMap
);
XWPFTemplate
template
=
XWPFTemplate
.
compile
(
"/data_net_template.docx"
).
render
(
dataModel
);
ClassPathResource
resource
=
new
ClassPathResource
(
"template/data_net_template.docx"
);
Properties
properties
=
new
Properties
();
meetingName
=
dataModel
.
get
(
"meeting_name"
)
!=
null
?
String
.
valueOf
(
dataModel
.
get
(
"meeting_name"
))
:
"腾讯会议纪要"
;
targetFileName
=
meetingName
+
"_"
+
nowTime
;
template
.
writeAndClose
(
new
FileOutputStream
(
path
+
targetFileName
+
".docx"
));
byte
[]
recordXmlData
=
Files
.
readAllBytes
(
Paths
.
get
(
path
+
targetFileName
+
".docx"
));
minioUtils
.
upload
(
recordXmlPath
,
recordXmlData
);
try
(
InputStream
inputStream
=
resource
.
getInputStream
())
{
XWPFTemplate
template
=
XWPFTemplate
.
compile
(
inputStream
).
render
(
dataModel
);
template
.
writeAndClose
(
new
FileOutputStream
(
path
+
targetFileName
+
".docx"
));
byte
[]
recordXmlData
=
Files
.
readAllBytes
(
Paths
.
get
(
path
+
targetFileName
+
".docx"
));
minioUtils
.
upload
(
recordXmlPath
,
recordXmlData
);
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
catch
(
Exception
e
)
{
log
.
error
(
"填充会议纪要失败: {}"
,
e
.
getMessage
(),
e
);
throw
new
RuntimeException
(
"填充会议纪要失败"
);
}
MeetingInfo
meetingInfo
=
meetingInfoMapper
.
selectOne
(
new
LambdaQueryWrapper
<
MeetingInfo
>().
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
));
Boolean
isPushed
;
try
{
log
.
info
(
"开始邮件推送------"
);
if
(
meetingInfo
.
getEmailPushAccess
()){
log
.
info
(
"用户允许邮件推送,准备推送邮件至{}------"
,
meetingInfo
.
getEmail
());
//邮件推送
emailSender
.
sendEmailWithAttachment
(
"duanxincheng@chatbot.cn"
,
meetingName
,
path
+
targetFileName
+
".docx"
,
recordFile
Id
);
// emailSender.sendEmailWithAttachment("xuwentao@chatbot.cn",meetingName,path + targetFileName + ".docx",
recordFile
Id);
// emailSender.sendEmailWithAttachment("jiaqi.cai@cimc.com",meetingName,path + targetFileName + ".docx",
recordFile
Id);
isPushed
=
Boolean
.
TRUE
;
}
catch
(
Exception
e
)
{
isPushed
=
emailSender
.
sendEmailWithAttachment
(
"duanxincheng@chatbot.cn"
,
meetingName
,
path
+
targetFileName
+
".docx"
,
meeting
Id
);
// emailSender.sendEmailWithAttachment("xuwentao@chatbot.cn",meetingName,path + targetFileName + ".docx",
meeting
Id);
// emailSender.sendEmailWithAttachment("jiaqi.cai@cimc.com",meetingName,path + targetFileName + ".docx",
meeting
Id);
}
else
{
log
.
info
(
"用户关闭了邮件推送,推送终止------"
);
isPushed
=
Boolean
.
FALSE
;
e
.
printStackTrace
();
log
.
error
(
e
.
getMessage
());
}
meetingInfoMapper
.
update
(
meetingInfo
,
new
LambdaUpdateWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
)
.
set
(
MeetingInfo:
:
getRecordContent
,
recordContentPath
)
.
set
(
MeetingInfo:
:
getRecordXml
,
recordXmlPath
)
.
set
(
MeetingInfo:
:
getIsGenerated
,
Boolean
.
TRUE
)
...
...
@@ -420,10 +419,11 @@ public class FileProcessTask {
}
public
FileProcessTask
(
String
recordFileId
,
String
meetingId
,
String
subMeetingId
,
String
savePath
,
Map
<
String
,
Object
>
metadata
,
String
tencentAppId
,
public
FileProcessTask
(
List
<
String
>
recordFileIdList
,
String
meetingId
,
String
subMeetingId
,
String
savePath
,
Map
<
String
,
Object
>
metadata
,
String
tencentAppId
,
String
tencentSdkId
,
String
tencentSecretId
,
String
tencentSecretKey
,
String
tencentAdminUserId
,
MeetingInfoMapper
meetingInfoMapper
,
MinioUtils
minioUtils
,
EmailSender
emailSender
)
{
this
.
recordFileId
=
recordFileId
;
MeetingInfoMapper
meetingInfoMapper
,
MinioUtils
minioUtils
,
EmailSender
emailSender
,
MeetingRecordTemplateMapper
meetingRecordTemplateMapper
,
String
llmApiAddr
,
Boolean
finalRetry
)
{
this
.
recordFileIdList
=
recordFileIdList
;
this
.
savePath
=
savePath
;
this
.
metadata
=
metadata
;
this
.
tencentAppId
=
tencentAppId
;
...
...
@@ -436,5 +436,8 @@ public class FileProcessTask {
this
.
meetingInfoMapper
=
meetingInfoMapper
;
this
.
minioUtils
=
minioUtils
;
this
.
emailSender
=
emailSender
;
this
.
meetingRecordTemplateMapper
=
meetingRecordTemplateMapper
;
this
.
llmApiAddr
=
llmApiAddr
;
this
.
finalRetry
=
finalRetry
;
}
}
\ No newline at end of file
src/main/java/com/cmeeting/mapper/primary/AuthMapper.java
浏览文件 @
e0b7d23d
...
...
@@ -8,5 +8,6 @@ import java.util.List;
@Mapper
public
interface
AuthMapper
{
List
<
CoreModulePermissions
>
getAuthByTargrtId
(
@Param
(
"targetId"
)
String
targetId
,
@Param
(
"tenantId"
)
String
tenantId
);
List
<
CoreModulePermissions
>
getAuthByTargetId
(
@Param
(
"targetId"
)
String
targetId
,
@Param
(
"tenantId"
)
String
tenantId
);
}
src/main/java/com/cmeeting/mapper/primary/MeetingRecordTemplateMapper.java
0 → 100644
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
mapper
.
primary
;
import
com.baomidou.mybatisplus.core.mapper.BaseMapper
;
import
com.cmeeting.pojo.MeetingRecordTemplate
;
import
org.apache.ibatis.annotations.Mapper
;
@Mapper
public
interface
MeetingRecordTemplateMapper
extends
BaseMapper
<
MeetingRecordTemplate
>
{
}
\ No newline at end of file
src/main/java/com/cmeeting/mapper/primary/UserIdMapper.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
mapper
.
primary
;
import
com.baomidou.mybatisplus.core.mapper.BaseMapper
;
import
com.cmeeting.pojo.UserId
;
import
org.apache.ibatis.annotations.Mapper
;
import
java.util.List
;
@Mapper
public
interface
UserIdMapper
{
public
interface
UserIdMapper
extends
BaseMapper
<
UserId
>
{
void
insertUsers
(
List
<
UserId
>
userIds
);
List
<
UserId
>
getUsers
();
...
...
src/main/java/com/cmeeting/mapper/primary/WeComUserMapper.java
浏览文件 @
e0b7d23d
...
...
@@ -7,7 +7,7 @@ import java.util.List;
public
interface
WeComUserMapper
extends
BaseMapper
<
WeComUser
>
{
List
<
WeComUser
>
getSameName
()
;
List
<
WeComUser
>
noBindUsers
()
;
WeComUser
selectById
(
Integer
id
);
int
insert
(
WeComUser
user
);
...
...
src/main/java/com/cmeeting/mapper/secondary/SysUserSysMapper.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
mapper
.
secondary
;
import
com.cmeeting.dto.UserDTO
;
import
org.apache.ibatis.annotations.Mapper
;
import
org.apache.ibatis.annotations.Param
;
import
java.util.List
;
@Mapper
public
interface
SysUserSysMapper
{
String
getCompanyEmail
(
String
wid
,
String
tenantId
);
String
getParentDeptId
(
String
deptId
,
String
tenantId
);
List
<
String
>
getSubDeptId
(
@Param
(
value
=
"deptId"
)
String
deptId
,
@Param
(
value
=
"tenantId"
)
String
tenantId
);
List
<
String
>
getUsersByDept
(
@Param
(
value
=
"deptIds"
)
List
<
String
>
deptIds
,
@Param
(
value
=
"tenantId"
)
String
tenantId
);
List
<
UserDTO
>
getUserEmail
(
@Param
(
value
=
"tenantId"
)
String
tenantId
);
}
src/main/java/com/cmeeting/pojo/MeetingInfo.java
浏览文件 @
e0b7d23d
...
...
@@ -20,6 +20,8 @@ import java.time.LocalDateTime;
@Accessors
(
chain
=
true
)
@TableName
(
"cmt_meeting_info"
)
public
class
MeetingInfo
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
26238487532381000L
;
/**
* 主键id
*/
...
...
@@ -48,12 +50,17 @@ public class MeetingInfo implements Serializable {
/**
* 主持人
*/
private
String
hostId
;
private
String
host
;
/**
* 主持人uid
*/
private
String
hostUid
;
/**
* 参会人员名单
*/
private
String
participantUser
Id
s
;
private
String
participantUsers
;
/**
* 会议开始时间(时间戳)
...
...
@@ -78,6 +85,14 @@ public class MeetingInfo implements Serializable {
*/
private
Boolean
isPushed
;
/**
* 会议纪要重新生成标识
*/
private
Boolean
generateRetry
;
/**
* 邮件推送重试标识
*/
private
Boolean
pushRetry
;
/**
* 同步时间
*/
private
LocalDateTime
syncTime
;
...
...
@@ -91,4 +106,12 @@ public class MeetingInfo implements Serializable {
* 纪要xml
*/
private
String
recordXml
;
/**
* 每个会议对应的转录文件id,多个用逗号分隔,按参会前后升序
*/
private
String
recordFileId
;
/**
* 邮箱
*/
private
String
email
;
}
\ No newline at end of file
src/main/java/com/cmeeting/pojo/MeetingRecordTemplate.java
0 → 100644
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
pojo
;
import
com.baomidou.mybatisplus.annotation.IdType
;
import
com.baomidou.mybatisplus.annotation.TableId
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
lombok.*
;
import
lombok.experimental.Accessors
;
import
java.io.Serializable
;
import
java.time.LocalDateTime
;
/**
* 会议纪要模板实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@EqualsAndHashCode
(
callSuper
=
false
)
@Accessors
(
chain
=
true
)
@TableName
(
"cmt_meeting_record_template"
)
public
class
MeetingRecordTemplate
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
26238487532381000L
;
/**
* 主键id
*/
@TableId
(
type
=
IdType
.
AUTO
)
private
Integer
id
;
/**
* 模板名称
*/
private
String
name
;
/**
* 模板类型(系统模板/自定义模板)
*/
private
String
type
;
/**
* 会议类型
*/
private
String
meetingType
;
/**
* 会议类型描述
*/
private
String
typeDetail
;
/**
* 提示词内容
*/
private
String
prompt
;
/**
* 创建时间
*/
private
LocalDateTime
createTime
;
/**
* 创建时间
*/
private
LocalDateTime
updateTime
;
/**
* 创建人
*/
private
String
createUser
;
/**
* 更新人
*/
private
String
updateUser
;
}
\ No newline at end of file
src/main/java/com/cmeeting/pojo/TencentMeetingUser.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
pojo
;
import
lombok.Data
;
import
com.baomidou.mybatisplus.annotation.IdType
;
import
com.baomidou.mybatisplus.annotation.TableId
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
lombok.*
;
import
lombok.experimental.Accessors
;
@Data
import
java.io.Serializable
;
public
class
TencentMeetingUser
{
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@EqualsAndHashCode
(
callSuper
=
false
)
@Accessors
(
chain
=
true
)
@TableName
(
"user_tencentmeeting"
)
public
class
TencentMeetingUser
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
26238487532381000L
;
/**
* 主键ID
*/
@TableId
(
type
=
IdType
.
AUTO
)
private
Integer
id
;
/**
...
...
@@ -25,26 +37,4 @@ public class TencentMeetingUser {
* 是否是重名用户(1:重名, 0:不重名)
*/
private
String
isrepeatName
;
// 无参构造方法
public
TencentMeetingUser
()
{
}
// 全参构造方法
public
TencentMeetingUser
(
Integer
id
,
String
userName
,
String
userId
,
String
isrepeatName
)
{
this
.
id
=
id
;
this
.
userName
=
userName
;
this
.
userId
=
userId
;
this
.
isrepeatName
=
isrepeatName
;
}
@Override
public
String
toString
()
{
return
"TencentMeetingUser{"
+
"id="
+
id
+
", userName='"
+
userName
+
'\''
+
", userId='"
+
userId
+
'\''
+
", isrepeatName='"
+
isrepeatName
+
'\''
+
'}'
;
}
}
src/main/java/com/cmeeting/pojo/UserId.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
pojo
;
import
lombok.Data
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
lombok.*
;
import
lombok.experimental.Accessors
;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@EqualsAndHashCode
(
callSuper
=
false
)
@Accessors
(
chain
=
true
)
@TableName
(
"userid"
)
public
class
UserId
{
/**
* 主键ID
...
...
@@ -24,23 +32,4 @@ public class UserId {
*/
private
String
tid
;
// 无参构造方法
public
UserId
()
{
}
// 全参构造方法
public
UserId
(
Integer
id
,
String
userName
,
String
wid
,
String
tid
)
{
this
.
id
=
id
;
this
.
userName
=
userName
;
this
.
wid
=
wid
;
this
.
tid
=
tid
;
}
// 全参构造方法
public
UserId
(
String
userName
,
String
wid
,
String
tid
)
{
this
.
userName
=
userName
;
this
.
wid
=
wid
;
this
.
tid
=
tid
;
}
}
src/main/java/com/cmeeting/pojo/WeComUser.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
pojo
;
import
com.baomidou.mybatisplus.annotation.IdType
;
import
com.baomidou.mybatisplus.annotation.TableId
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
com.baomidou.mybatisplus.extension.activerecord.Model
;
import
lombok.Data
;
import
lombok.*
;
import
lombok.experimental.Accessors
;
import
java.io.Serializable
;
@Data
public
class
WeComUser
extends
Model
implements
Serializable
{
@AllArgsConstructor
@NoArgsConstructor
@Builder
@EqualsAndHashCode
(
callSuper
=
false
)
@Accessors
(
chain
=
true
)
@TableName
(
"user_wecom"
)
public
class
WeComUser
implements
Serializable
{
private
static
final
long
serialVersionUID
=
-
26238487532381000L
;
@TableId
(
type
=
IdType
.
AUTO
)
private
Integer
id
;
private
String
userName
;
private
String
userId
;
private
String
isRepeatName
;
private
String
email
;
public
WeComUser
()
{
}
public
WeComUser
(
Integer
id
,
String
userName
,
String
userId
,
String
isRepeatName
)
{
this
.
id
=
id
;
this
.
userName
=
userName
;
this
.
userId
=
userId
;
this
.
isRepeatName
=
isRepeatName
;
}
@Override
public
String
toString
()
{
return
"WeComUser{"
+
"id="
+
id
+
", userName='"
+
userName
+
'\''
+
", userId='"
+
userId
+
'\''
+
", isRepeatName='"
+
isRepeatName
+
'\''
+
", email='"
+
email
+
'\''
+
'}'
;
}
}
src/main/java/com/cmeeting/service/FileProcessCallbackHandler.java
浏览文件 @
e0b7d23d
...
...
@@ -11,7 +11,7 @@ public class FileProcessCallbackHandler {
// 单个任务完成回调
public
void
onComplete
(
FileProcessTask
task
)
{
// 可以记录任务状态、发送通知等
log
.
info
(
"任务处理完成:
{}"
,
task
.
getRecordFile
Id
());
log
.
info
(
"任务处理完成:
meetingId {}"
,
task
.
getMeeting
Id
());
// 更新数据库状态等
// taskRepository.updateStatus(task.getId(), "COMPLETED");
...
...
src/main/java/com/cmeeting/service/FileProcessProducer.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
service
;
import
com.cmeeting.dto.UserDTO
;
import
com.cmeeting.email.EmailSender
;
import
com.cmeeting.job.EmailPushTask
;
import
com.cmeeting.job.FileProcessTask
;
import
com.cmeeting.mapper.primary.MeetingInfoMapper
;
import
com.cmeeting.mapper.primary.MeetingRecordTemplateMapper
;
import
com.cmeeting.pojo.UserId
;
import
com.cmeeting.util.MinioUtils
;
import
com.cmeeting.vo.TencentMeetingVO
;
import
lombok.extern.slf4j.Slf4j
;
...
...
@@ -37,24 +41,33 @@ public class FileProcessProducer {
private
String
tencentSecretKey
;
@Value
(
value
=
"${tencent.admin.userId}"
)
private
String
tencentAdminUserId
;
@Value
(
value
=
"${llm.api-addr}"
)
private
String
llmApiAddr
;
@Resource
private
MeetingInfoMapper
meetingInfoMapper
;
@Resource
private
MeetingRecordTemplateMapper
meetingRecordTemplateMapper
;
@Resource
private
MinioUtils
minioUtils
;
@Resource
private
EmailSender
emailSender
;
// 批量提交任务
public
void
submitBatchTasks
(
List
<
TencentMeetingVO
.
RecordFile
>
recordFiles
,
String
baseSavePath
)
{
/**
* 批量提交生成纪要任务
* @param recordFiles 转录文件信息
* @param baseSavePath 存储临时路径
* @param finalRetry
*/
public
void
submitBatchTasks
(
List
<
TencentMeetingVO
.
RecordFile
>
recordFiles
,
String
baseSavePath
,
Boolean
finalRetry
)
{
List
<
Future
<?>>
futures
=
new
ArrayList
<>();
for
(
TencentMeetingVO
.
RecordFile
recordFile
:
recordFiles
)
{
// 为每个URL创建任务
FileProcessTask
task
=
new
FileProcessTask
(
recordFile
.
getRecordFileId
(),
recordFile
.
getRecordFileId
List
(),
recordFile
.
getMeetingId
(),
recordFile
.
getSubMeetingId
(),
"/save/"
,
baseSavePath
,
Collections
.
emptyMap
(),
tencentAppId
,
tencentSdkId
,
...
...
@@ -63,7 +76,10 @@ public class FileProcessProducer {
tencentAdminUserId
,
meetingInfoMapper
,
minioUtils
,
emailSender
emailSender
,
meetingRecordTemplateMapper
,
llmApiAddr
,
finalRetry
);
// 提交任务到线程池
...
...
@@ -78,6 +94,35 @@ public class FileProcessProducer {
// 可以添加一个监控线程来检查所有任务完成情况
monitorTaskCompletion
(
futures
);
}
// 批量提交邮箱推送重试任务
public
void
submitEmailPushTasks
(
List
<
TencentMeetingVO
.
RecordFile
>
recordFiles
,
String
baseSavePath
)
{
List
<
Future
<?>>
futures
=
new
ArrayList
<>();
for
(
TencentMeetingVO
.
RecordFile
recordFile
:
recordFiles
)
{
// 为每个URL创建任务
EmailPushTask
task
=
new
EmailPushTask
(
recordFile
.
getMeetingId
(),
recordFile
.
getSubMeetingId
(),
baseSavePath
,
Collections
.
emptyMap
(),
meetingInfoMapper
,
minioUtils
,
emailSender
,
meetingRecordTemplateMapper
);
// 提交任务到线程池
Future
<?>
future
=
fileProcessExecutor
.
submit
(()
->
{
task
.
process
();
});
futures
.
add
(
future
);
}
// 可以添加一个监控线程来检查所有任务完成情况
monitorTaskCompletion
(
futures
);
}
private
void
monitorTaskCompletion
(
List
<
Future
<?>>
futures
)
{
new
Thread
(()
->
{
...
...
src/main/java/com/cmeeting/service/MeetingRecordTemplateService.java
0 → 100644
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
service
;
import
com.baomidou.mybatisplus.extension.service.IService
;
import
com.cmeeting.pojo.MeetingRecordTemplate
;
public
interface
MeetingRecordTemplateService
extends
IService
<
MeetingRecordTemplate
>
{
}
src/main/java/com/cmeeting/service/TecentMeetingService.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
service
;
import
com.baomidou.mybatisplus.extension.service.IService
;
import
com.cmeeting.dto.UserDTO
;
import
com.cmeeting.pojo.TencentMeetingUser
;
import
com.cmeeting.pojo.UserId
;
import
com.cmeeting.vo.TencentMeetingVO
;
import
java.util.List
;
...
...
@@ -11,6 +13,10 @@ public interface TecentMeetingService extends IService<TencentMeetingUser> {
void
doUsers
();
List
<
TencentMeetingVO
.
RecordFile
>
getMeetingFiles
();
List
<
TencentMeetingVO
.
RecordFile
>
getMeetingFiles
(
List
<
UserDTO
>
accessUserIds
);
List
<
UserDTO
>
getAccessUserIds
();
String
getMeetingHost
(
String
meetingCode
);
}
src/main/java/com/cmeeting/service/WeComService.java
浏览文件 @
e0b7d23d
...
...
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import
com.cmeeting.pojo.WeComUser
;
import
java.util.List
;
import
java.util.Map
;
public
interface
WeComService
extends
IService
<
WeComUser
>
{
...
...
@@ -12,4 +13,10 @@ public interface WeComService extends IService<WeComUser> {
void
doUsers
()
throws
Exception
;
String
getToken
();
List
<
WeComUser
>
noBindUsers
();
Map
<
String
,
String
>
createTempMeeting
(
String
wid
,
String
token
);
void
cancelMeeting
(
String
meetingId
,
String
weComToken
);
}
src/main/java/com/cmeeting/service/impl/MeetingRecordTemplateServiceImpl.java
0 → 100644
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
service
.
impl
;
import
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
;
import
com.cmeeting.mapper.primary.MeetingRecordTemplateMapper
;
import
com.cmeeting.pojo.MeetingRecordTemplate
;
import
com.cmeeting.service.MeetingRecordTemplateService
;
import
org.springframework.stereotype.Service
;
@Service
public
class
MeetingRecordTemplateServiceImpl
extends
ServiceImpl
<
MeetingRecordTemplateMapper
,
MeetingRecordTemplate
>
implements
MeetingRecordTemplateService
{
}
\ No newline at end of file
src/main/java/com/cmeeting/service/impl/TecentMeetingServiceImpl.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
service
.
impl
;
import
cn.hutool.core.util.IdUtil
;
import
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
;
import
com.cmeeting.dto.UserDTO
;
import
com.cmeeting.mapper.primary.AuthMapper
;
import
com.cmeeting.mapper.primary.MeetingInfoMapper
;
import
com.cmeeting.mapper.primary.TecentMeetingMapper
;
import
com.cmeeting.mapper.primary.UserIdMapper
;
import
com.cmeeting.mapper.secondary.SysUserSysMapper
;
import
com.cmeeting.pojo.CoreModulePermissions
;
import
com.cmeeting.pojo.MeetingInfo
;
import
com.cmeeting.pojo.TencentMeetingUser
;
import
com.cmeeting.pojo.UserId
;
import
com.cmeeting.service.TecentMeetingService
;
import
com.cmeeting.util.RedisUtils
;
import
com.cmeeting.vo.TencentMeetingVO
;
import
com.tencentcloudapi.wemeet.Client
;
import
com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder
;
...
...
@@ -14,44 +20,35 @@ 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.meetings.api.MeetingsApi
;
import
com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsGet200ResponseMeetingInfoListInnerCurrentCoHostsInner
;
import
com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdGet200Response
;
import
com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner
;
import
com.tencentcloudapi.wemeet.service.meetings.model.*
;
import
com.tencentcloudapi.wemeet.service.records.api.RecordsApi
;
import
com.tencentcloudapi.wemeet.service.records.model.*
;
import
com.tencentcloudapi.wemeet.service.user_manager.api.UserManagerApi
;
import
com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200Response
;
import
com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200ResponseUsersInner
;
import
lombok.extern.slf4j.Slf4j
;
import
okhttp3.Headers
;
import
okhttp3.OkHttpClient
;
import
okhttp3.Request
;
import
okhttp3.Response
;
import
org.apache.commons.lang3.StringUtils
;
import
org.json.JSONObject
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.CollectionUtils
;
import
javax.annotation.Resource
;
import
javax.crypto.Mac
;
import
javax.crypto.spec.SecretKeySpec
;
import
java.io.IOException
;
import
java.math.BigInteger
;
import
java.nio.charset.StandardCharsets
;
import
java.security.InvalidKeyException
;
import
java.security.NoSuchAlgorithmException
;
import
java.security.SecureRandom
;
import
java.time.Instant
;
import
java.time.LocalDateTime
;
import
java.time.ZoneId
;
import
java.time.ZonedDateTime
;
import
java.util.ArrayList
;
import
java.util.Base64
;
import
java.util.List
;
import
java.time.*
;
import
java.util.*
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.TimeUnit
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
java.util.stream.Collectors
;
import
static
com
.
cmeeting
.
WeComAndTencentMeeting
.
fetchUsersInBatches
;
import
static
com
.
cmeeting
.
WeComAndTencentMeeting
.
markDuplicateNamesTecent
;
@Service
@Slf4j
public
class
TecentMeetingServiceImpl
extends
ServiceImpl
<
TecentMeetingMapper
,
TencentMeetingUser
>
implements
TecentMeetingService
{
...
...
@@ -59,9 +56,18 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
private
TecentMeetingMapper
tecentMeetingMapper
;
@Resource
private
MeetingInfoMapper
meetingInfoMapper
;
@Resource
private
AuthMapper
authMapper
;
@Resource
private
SysUserSysMapper
sysUserSyncMapper
;
@Resource
private
UserIdMapper
userIdMapper
;
@Resource
private
RedisUtils
redisUtils
;
private
static
final
String
HMAC_ALGORITHM
=
"HmacSHA256"
;
private
static
final
char
[]
HEX_CHAR
=
{
'0'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'a'
,
'b'
,
'c'
,
'd'
,
'e'
,
'f'
};
private
final
Object
lock
=
new
Object
();
@Value
(
value
=
"${tencent.appId}"
)
private
String
tencentAppId
;
...
...
@@ -73,6 +79,10 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
private
String
tencentSecretKey
;
@Value
(
value
=
"${tencent.admin.userId}"
)
private
String
tencentAdminUserId
;
@Value
(
value
=
"${permission.applicationId}"
)
private
String
permissionApplicationId
;
@Value
(
value
=
"${permission.tenantId}"
)
private
String
permissionTenantId
;
@Override
public
void
batchInsert
(
List
<
TencentMeetingUser
>
users
)
{
...
...
@@ -82,26 +92,21 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
@Override
public
void
doUsers
()
{
/**
* 腾讯会议通过通讯录获取员工信息
*/
// 1. 构造client客户端
Client
client
=
new
Client
.
Builder
()
.
withAppId
(
tencentAppId
).
withSdkId
(
tencentSdkId
)
.
withSecret
(
tencentSecretId
,
tencentSecretKey
)
.
build
();
// 2.获取到全部用户
List
<
TencentMeetingUser
>
users
=
fetchUsersInBatches
(
client
,
3
);;
// 3. 检查重名并设置标志
// 获取到全部用户
List
<
TencentMeetingUser
>
users
=
fetchUsersInBatches
();;
// 检查重名并设置标志
markDuplicateNamesTecent
(
users
);
// 批量插入数据库(分批次)
int
batchSize
=
10
;
for
(
int
i
=
0
;
i
<
users
.
size
();
i
+=
batchSize
)
{
List
<
TencentMeetingUser
>
batch
=
users
.
subList
(
i
,
Math
.
min
(
i
+
batchSize
,
users
.
size
()));
batchInsert
(
batch
);
}
// 4. 批量插入数据库
batchInsert
(
users
);
}
@Override
public
List
<
TencentMeetingVO
.
RecordFile
>
getMeetingFiles
()
{
public
List
<
TencentMeetingVO
.
RecordFile
>
getMeetingFiles
(
List
<
UserDTO
>
accessUserIds
)
{
Client
client
=
new
Client
.
Builder
()
.
withAppId
(
tencentAppId
).
withSdkId
(
tencentSdkId
)
.
withSecret
(
tencentSecretId
,
tencentSecretKey
)
...
...
@@ -112,7 +117,7 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
// 查询近两天的会议录制列表
try
{
ZonedDateTime
now
=
ZonedDateTime
.
now
();
String
startTime
=
String
.
valueOf
(
now
.
minusDays
(
2
).
toEpochSecond
());
String
startTime
=
String
.
valueOf
(
now
.
minusDays
(
5
).
toEpochSecond
());
String
endTime
=
String
.
valueOf
(
now
.
toEpochSecond
());
AtomicInteger
currentPage
=
new
AtomicInteger
(
1
);
Long
totalPage
=
1L
;
...
...
@@ -129,7 +134,6 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
.
pageSize
(
"20"
)
.
page
(
String
.
valueOf
(
currentPage
.
getAndIncrement
()))
.
mediaSetType
(
"0"
)
.
queryRecordType
(
"0"
)
.
build
();
RecordsApi
.
ApiV1RecordsGetResponse
response
=
client
.
records
().
v1RecordsGet
(
request
,
new
JWTAuthenticator
.
Builder
()
...
...
@@ -149,8 +153,11 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
return
null
;
}
for
(
V1RecordsGet200ResponseRecordMeetingsInner
meeting
:
meetings
)
{
//会议没结束,跳过
if
(
meeting
.
getState
()
!=
3
)
continue
;
log
.
info
(
"【会议检索】转录文件的meetingId->{},recordFileId->{}"
,
meeting
.
getMeetingId
(),
meeting
.
getMeetingRecordId
());
//查询会议详情
String
meetingId
=
meeting
.
getMeetingId
();
String
subMeetingId
=
null
;
...
...
@@ -175,27 +182,85 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
//1:周期性会议
Long
meetingType
=
meetingInfo
.
getMeetingType
();
if
(
meetingType
.
intValue
()
==
1
){
subMeetingId
=
meetingInfo
.
getCurrentSubMeetingId
();
//如果是周期会议,获取子会议的ID,用于查询参会人员
List
<
V1MeetingsMeetingIdGet200ResponseMeetingInfoListInnerSubMeetingsInner
>
subMeetings
=
meetingInfo
.
getSubMeetings
();
LocalDate
meetingStartDate
=
Instant
.
ofEpochMilli
(
meeting
.
getMediaStartTime
()).
atZone
(
ZoneId
.
systemDefault
()).
toLocalDate
();
Optional
<
V1MeetingsMeetingIdGet200ResponseMeetingInfoListInnerSubMeetingsInner
>
subMeeting
=
subMeetings
.
stream
().
filter
(
item
->
Instant
.
ofEpochSecond
(
Long
.
valueOf
(
item
.
getStartTime
())).
atZone
(
ZoneId
.
systemDefault
()).
toLocalDate
().
equals
(
meetingStartDate
))
.
findFirst
();
if
(!
subMeeting
.
isPresent
()){
System
.
out
.
println
(
"周期会议"
+
meetingId
+
"未知子会议ID"
);
continue
;
}
subMeetingId
=
subMeeting
.
get
().
getSubMeetingId
();
}
//如果数据库中已有相同会议id的记录,跳过同步
if
(!
meetingIds
.
contains
(
meetingId
)){
log
.
info
(
"【会议检索】新的会议meetingId->{},开始持久化"
,
meeting
.
getMeetingId
());
List
<
V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner
>
recordFiles
=
meeting
.
getRecordFiles
();
//按转录文件时间升序,便于后续的内容拼接
List
<
String
>
recordFileIdList
=
recordFiles
.
stream
().
sorted
(
Comparator
.
comparingLong
(
V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner:
:
getRecordStartTime
))
.
map
(
V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner:
:
getRecordFileId
).
collect
(
Collectors
.
toList
());
TencentMeetingVO
.
RecordFile
recordFileItem
=
TencentMeetingVO
.
RecordFile
.
builder
()
.
recordFileIdList
(
recordFileIdList
).
meetingId
(
meetingId
).
subMeetingId
(
subMeetingId
).
build
();
// 获取参会成员明细
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetRequest
participantsRequest
=
new
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetRequest
.
Builder
(
meetingId
).
subMeetingId
(
subMeetingId
).
operatorId
(
tencentAdminUserId
).
operatorIdType
(
"1"
).
build
();
AuthenticatorBuilder
<
JWTAuthenticator
>
participantsAuthenticatorBuilder
=
new
JWTAuthenticator
.
Builder
()
.
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
));
//主持人角色,以下角色都可以表示主持人
//用户角色:
//0:普通成员角色
//1:创建者角色
//2:主持人
//3:创建者+主持人
//4:游客
//5:游客+主持人
//6:联席主持人
//7:创建者+联席主持人
List
<
Long
>
hostRoleList
=
Arrays
.
asList
(
2L
,
3L
,
5L
,
6L
,
7L
);
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetResponse
participantsResponse
=
client
.
meetings
().
v1MeetingsMeetingIdParticipantsGet
(
participantsRequest
,
participantsAuthenticatorBuilder
);
V1MeetingsMeetingIdParticipantsGet200Response
participantsData
=
participantsResponse
.
getData
();
List
<
V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner
>
participants
=
participantsData
.
getParticipants
();
Optional
<
V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner
>
host
=
participants
.
stream
().
filter
(
item
->
hostRoleList
.
contains
(
item
.
getUserRole
())).
findFirst
();
String
email
;
if
(
host
.
isPresent
())
{
String
tid
=
host
.
get
().
getUserid
();
//判断是否有权限生成纪要
boolean
generateAccess
=
accessUserIds
.
stream
().
anyMatch
(
item
->
item
.
getTid
().
equals
(
tid
));
if
(!
generateAccess
){
log
.
error
(
"【权限校验】主持人{}没有生成纪要权限,跳过生成"
,
tid
);
continue
;
}
log
.
info
(
"【权限校验】主持人{}允许生成纪要"
,
tid
);
UserDTO
userDTO
=
accessUserIds
.
stream
().
filter
(
item
->
item
.
getTid
().
equals
(
tid
)).
findFirst
().
get
();
email
=
userDTO
.
getEmail
();
}
else
{
log
.
error
(
"未找到主持人,默认没有生成纪要权限"
);
continue
;
}
//会议基本信息保存
MeetingInfo
meetingItem
=
MeetingInfo
.
builder
().
meetingId
(
meetingId
).
meetingCode
(
meetingInfo
.
getMeetingCode
())
.
subject
(
meetingInfo
.
getSubject
())
.
startTime
(
LocalDateTime
.
ofInstant
(
Instant
.
ofEpochSecond
(
Long
.
valueOf
(
meetingInfo
.
getStartTime
())),
ZoneId
.
systemDefault
()))
.
endTime
(
LocalDateTime
.
ofInstant
(
Instant
.
ofEpochSecond
(
Long
.
valueOf
(
meetingInfo
.
getEndTime
())),
ZoneId
.
systemDefault
()))
// .hostId(meetingInfo.getHosts().stream().map(V1MeetingsGet200ResponseMeetingInfoListInnerCurrentCoHostsInner::getUserid).collect(Collectors.joining(",")))
// .participantUserIds(meetingInfo.getParticipants().stream().map(V1MeetingsGet200ResponseMeetingInfoListInnerCurrentCoHostsInner::getUserid).collect(Collectors.joining(",")))
.
isGenerated
(
Boolean
.
FALSE
).
emailPushAccess
(
Boolean
.
TRUE
).
isPushed
(
Boolean
.
FALSE
).
syncTime
(
LocalDateTime
.
now
())
.
subMeetingId
(
subMeetingId
)
.
subMeetingId
(
subMeetingId
).
generateRetry
(
Boolean
.
FALSE
).
pushRetry
(
Boolean
.
FALSE
)
.
host
(
host
.
isPresent
()
?
new
String
(
Base64
.
getDecoder
().
decode
(
host
.
get
().
getUserName
()))
:
"未知主持人"
)
.
hostUid
(
host
.
isPresent
()
?
host
.
get
().
getUserid
()
:
null
)
.
participantUsers
(
participants
.
stream
()
.
map
(
item
->
new
String
(
Base64
.
getDecoder
().
decode
(
item
.
getUserName
()))).
distinct
().
collect
(
Collectors
.
joining
(
"、"
)))
.
recordFileId
(
recordFileIdList
.
stream
().
collect
(
Collectors
.
joining
(
","
)))
.
email
(
email
)
.
build
();
recordFileUrlList
.
add
(
recordFileItem
);
meetingSaveList
.
add
(
meetingItem
);
List
<
V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner
>
recordFiles
=
meeting
.
getRecordFiles
();
for
(
V1RecordsGet200ResponseRecordMeetingsInnerRecordFilesInner
recordFile
:
recordFiles
)
{
TencentMeetingVO
.
RecordFile
recordFileItem
=
TencentMeetingVO
.
RecordFile
.
builder
()
.
recordFileId
(
recordFile
.
getRecordFileId
()).
meetingId
(
meetingId
).
subMeetingId
(
subMeetingId
).
build
();
recordFileUrlList
.
add
(
recordFileItem
);
}
}
}
}
catch
(
ClientException
e
)
{
...
...
@@ -207,7 +272,16 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
}
}
if
(
meetingSaveList
.
size
()
>
0
){
meetingInfoMapper
.
batchInsert
(
meetingSaveList
);
Map
<
String
,
List
<
MeetingInfo
>>
meetingSaveMap
=
meetingSaveList
.
stream
().
collect
(
Collectors
.
groupingBy
(
item
->
item
.
getMeetingId
()
+
"_"
+
(
item
.
getSubMeetingId
()
!=
null
?
item
.
getSubMeetingId
()
:
"null"
)));
List
<
MeetingInfo
>
finalSaveList
=
new
ArrayList
<>();
for
(
Map
.
Entry
<
String
,
List
<
MeetingInfo
>>
entry
:
meetingSaveMap
.
entrySet
())
{
MeetingInfo
meetingInfo
=
entry
.
getValue
().
get
(
0
);
meetingInfo
.
setRecordFileId
(
entry
.
getValue
().
stream
().
map
(
MeetingInfo:
:
getRecordFileId
).
collect
(
Collectors
.
joining
(
","
)));
finalSaveList
.
add
(
meetingInfo
);
}
meetingInfoMapper
.
batchInsert
(
finalSaveList
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
e
.
getMessage
());
...
...
@@ -217,6 +291,71 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
}
/**
* 获取有权限的人员
* @return
*/
@Override
public
List
<
UserDTO
>
getAccessUserIds
()
{
//权限控制
List
<
CoreModulePermissions
>
auths
=
authMapper
.
getAuthByTargetId
(
permissionApplicationId
,
permissionTenantId
);
//授权的部门,type为1的为人员,为0表示部门
List
<
CoreModulePermissions
>
authDepts
=
auths
.
stream
().
filter
(
item
->
item
.
getType
().
equals
(
0
)).
collect
(
Collectors
.
toList
());
List
<
String
>
deptPath
=
new
ArrayList
<>();
for
(
CoreModulePermissions
authDept
:
authDepts
)
{
String
deptId
=
authDept
.
getRelId
();
String
tenantId
=
authDept
.
getTenantId
();
getDeptPath
(
deptPath
,
deptId
,
tenantId
);
}
//已被授权部门下的userid
List
<
String
>
accessUserIds
=
!
CollectionUtils
.
isEmpty
(
deptPath
)
?
sysUserSyncMapper
.
getUsersByDept
(
deptPath
,
permissionTenantId
)
:
new
ArrayList
<>();
//已被直接授权的人员追加进去
accessUserIds
.
addAll
(
auths
.
stream
().
filter
(
item
->
item
.
getType
().
equals
(
1
)).
map
(
CoreModulePermissions:
:
getRelId
).
collect
(
Collectors
.
toList
()));
if
(!
CollectionUtils
.
isEmpty
(
accessUserIds
)){
//查出人员邮箱
List
<
UserDTO
>
userEmailList
=
sysUserSyncMapper
.
getUserEmail
(
permissionTenantId
);
Map
<
String
,
String
>
userEmailMap
=
CollectionUtils
.
isEmpty
(
userEmailList
)
?
new
HashMap
<>()
:
userEmailList
.
stream
().
collect
(
Collectors
.
toMap
(
UserDTO:
:
getWid
,
UserDTO:
:
getEmail
));
//查出企微id和腾会id的关联关系
Map
<
String
,
String
>
userIds
=
userIdMapper
.
selectList
(
null
).
stream
().
collect
(
Collectors
.
toMap
(
UserId:
:
getWid
,
UserId:
:
getTid
));
List
<
UserDTO
>
accessUsers
=
new
ArrayList
<>();
for
(
String
accessUserId
:
accessUserIds
)
{
if
(
userIds
.
containsKey
(
accessUserId
)){
UserDTO
accessUser
=
new
UserDTO
();
accessUser
.
setWid
(
accessUserId
);
accessUser
.
setTid
(
userIds
.
get
(
accessUserId
));
if
(
userEmailMap
.
containsKey
(
accessUserId
))
accessUser
.
setEmail
(
userEmailMap
.
get
(
accessUserId
));
accessUsers
.
add
(
accessUser
);
}
}
return
accessUsers
;
}
return
new
ArrayList
<>();
}
/**
* 获取部门的路径
* @param deptPath
* @param deptId
* @param tenantId
*/
private
void
getDeptPath
(
List
<
String
>
deptPath
,
String
deptId
,
String
tenantId
)
{
if
(!
deptPath
.
contains
(
deptId
))
deptPath
.
add
(
deptId
);
List
<
String
>
subDeptIds
=
sysUserSyncMapper
.
getSubDeptId
(
deptId
,
tenantId
);
if
(
CollectionUtils
.
isEmpty
(
subDeptIds
))
return
;
for
(
String
subDeptId
:
subDeptIds
)
{
//部门id去重
if
(!
deptPath
.
contains
(
subDeptId
)){
deptPath
.
add
(
subDeptId
);
getDeptPath
(
deptPath
,
subDeptId
,
tenantId
);
}
}
}
/**
* 生成请求腾会接口的请求头
* @param httpMethod http请求方法 GET/POST/PUT等
* @param requestUri 请求uri,eg:/v1/meetings
...
...
@@ -285,4 +424,174 @@ public class TecentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,Te
}
return
new
String
(
buf
);
}
/**
* 标记企业微信重名用户
*/
public
static
void
markDuplicateNamesTecent
(
List
<
TencentMeetingUser
>
users
)
{
// 按姓名分组,统计每个名字出现的次数
Map
<
String
,
Long
>
nameCountMap
=
users
.
stream
()
.
collect
(
Collectors
.
groupingBy
(
TencentMeetingUser:
:
getUserName
,
Collectors
.
counting
()));
// 设置是否重名标志
users
.
forEach
(
user
->
{
if
(
nameCountMap
.
get
(
user
.
getUserName
())
>
1
)
{
user
.
setIsrepeatName
(
"1"
);
// 重名
}
else
{
user
.
setIsrepeatName
(
"0"
);
// 不重名
}
});
}
/**
* 腾讯会议通过通讯录获取员工信息
* @return
*/
private
List
<
TencentMeetingUser
>
fetchUsersInBatches
()
{
if
(
redisUtils
.
hasKey
(
"TENCENT_USER_ARRAY"
)){
return
(
List
<
TencentMeetingUser
>)
redisUtils
.
get
(
"TENCENT_USER_ARRAY"
);
}
//构造client客户端
Client
client
=
new
Client
.
Builder
()
.
withAppId
(
tencentAppId
).
withSdkId
(
tencentSdkId
)
.
withSecret
(
tencentSecretId
,
tencentSecretKey
)
.
build
();
//先请求一条数据,获取到数据总量
UserManagerApi
.
ApiV1UsersListGetRequest
firstRequest
=
new
UserManagerApi
.
ApiV1UsersListGetRequest
.
Builder
()
.
page
(
String
.
valueOf
(
1
))
.
pageSize
(
String
.
valueOf
(
1
))
.
operatorId
(
tencentAdminUserId
)
.
operatorIdType
(
"1"
)
.
build
();
UserManagerApi
.
ApiV1UsersListGetResponse
firstResponse
;
try
{
firstResponse
=
client
.
user_manager
().
v1UsersListGet
(
firstRequest
,
new
JWTAuthenticator
.
Builder
()
.
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
)));
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
log
.
error
(
"获取腾会人员信息失败"
);
return
new
ArrayList
<>();
}
Long
totalUsers
=
firstResponse
.
getData
().
getTotalCount
();
// 使用线程安全的集合存储结果
List
<
TencentMeetingUser
>
resultList
=
Collections
.
synchronizedList
(
new
ArrayList
<>());
Integer
pageSize
=
20
;
int
maxPage
=
(
totalUsers
.
intValue
()
+
pageSize
-
1
)
/
pageSize
;
for
(
int
currentPage
=
1
;
currentPage
<=
maxPage
;
currentPage
++)
{
try
{
// 1. 构造请求参数
UserManagerApi
.
ApiV1UsersListGetRequest
request
=
new
UserManagerApi
.
ApiV1UsersListGetRequest
.
Builder
()
.
page
(
String
.
valueOf
(
currentPage
))
.
pageSize
(
String
.
valueOf
(
pageSize
))
.
operatorId
(
tencentAdminUserId
)
.
operatorIdType
(
"1"
)
.
build
();
// 2. 构造JWT鉴权器
AuthenticatorBuilder
<
JWTAuthenticator
>
authenticatorBuilder
=
new
JWTAuthenticator
.
Builder
()
.
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
));
// 3. 发送请求
UserManagerApi
.
ApiV1UsersListGetResponse
response
=
client
.
user_manager
().
v1UsersListGet
(
request
,
authenticatorBuilder
);
V1UsersListGet200Response
responseData
=
response
.
getData
();
List
<
V1UsersListGet200ResponseUsersInner
>
users
=
responseData
.
getUsers
();
System
.
out
.
printf
(
"第 %d 页,获取到 %d 个用户:\n"
,
currentPage
,
responseData
.
getCurrentSize
());
// 4. 处理响应并转换为TencentMeetingUser对象
for
(
V1UsersListGet200ResponseUsersInner
user
:
users
)
{
// 创建TencentMeetingUser对象并设置属性
TencentMeetingUser
meetingUser
=
new
TencentMeetingUser
();
meetingUser
.
setUserId
(
user
.
getUserid
());
meetingUser
.
setUserName
(
user
.
getUsername
());
meetingUser
.
setIsrepeatName
(
"0"
);
// 默认设为不重名,可根据实际业务调整
resultList
.
add
(
meetingUser
);
// 添加到集合
System
.
out
.
printf
(
"用户ID: %s, 用户名: %s\n"
,
user
.
getUserid
(),
user
.
getUsername
());
}
}
catch
(
ClientException
e
)
{
System
.
out
.
printf
(
"客户端错误: %s\n"
,
e
);
throw
new
RuntimeException
(
e
);
}
catch
(
ServiceException
e
)
{
System
.
out
.
printf
(
"服务端错误: %s\n"
,
e
);
System
.
out
.
printf
(
"完整响应: %s\n"
,
new
String
(
e
.
getApiResp
().
getRawBody
()));
throw
new
RuntimeException
(
e
);
}
}
redisUtils
.
set
(
"TENCENT_USER_ARRAY"
,
resultList
);
// 输出结果
System
.
out
.
printf
(
"\n所有用户获取完成,共获取 %d 个用户\n"
,
resultList
.
size
());
return
resultList
;
}
/**
* 通过meetingId获取到会议主持人对应的腾讯会议userid
*
* @param meetingCode
* @return
*/
@Override
public
String
getMeetingHost
(
String
meetingCode
)
{
/**
* 腾讯会议参数
*/
Client
client
=
new
Client
.
Builder
()
.
withAppId
(
tencentAppId
).
withSdkId
(
tencentSdkId
)
.
withSecret
(
tencentSecretId
,
tencentSecretKey
)
.
build
();
MeetingsApi
.
ApiV1MeetingsGetRequest
request
=
new
MeetingsApi
.
ApiV1MeetingsGetRequest
.
Builder
()
.
userid
(
tencentAdminUserId
)
.
instanceid
(
"1"
)
.
meetingCode
(
meetingCode
)
.
build
();
AuthenticatorBuilder
<
JWTAuthenticator
>
authenticatorBuilder
=
new
JWTAuthenticator
.
Builder
().
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
()))).
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
));
try
{
MeetingsApi
.
ApiV1MeetingsGetResponse
response
=
client
.
meetings
().
v1MeetingsGet
(
request
,
authenticatorBuilder
);
// response from `v1MeetingsGet`: V1MeetingsGet200Response
System
.
out
.
printf
(
"Response from `MeetingsApi.v1MeetingsGet`: \nheader: %s\n%s\n"
,
response
.
getHeader
(),
response
.
getData
());
// 提取主持人 userid
// 5. 处理响应数据
if
(
response
==
null
||
response
.
getData
()
==
null
)
{
throw
new
RuntimeException
(
"API响应数据为空"
);
}
// 6. 使用更安全的方式解析响应
V1MeetingsGet200Response
responseData
=
response
.
getData
();
if
(
responseData
.
getMeetingInfoList
()
==
null
||
responseData
.
getMeetingInfoList
().
isEmpty
())
{
throw
new
RuntimeException
(
"未找到会议信息"
);
}
V1MeetingsGet200ResponseMeetingInfoListInner
meetingInfo
=
responseData
.
getMeetingInfoList
().
get
(
0
);
if
(
meetingInfo
.
getCurrentHosts
()
==
null
||
meetingInfo
.
getCurrentHosts
().
isEmpty
())
{
throw
new
RuntimeException
(
"会议没有主持人"
);
}
String
hostUserId
=
meetingInfo
.
getCurrentHosts
().
get
(
0
).
getUserid
();
if
(
hostUserId
==
null
||
hostUserId
.
isEmpty
())
{
throw
new
RuntimeException
(
"主持人userid为空"
);
}
System
.
out
.
println
(
"成功获取主持人userid: {}"
+
hostUserId
);
return
hostUserId
;
}
catch
(
ClientException
e
)
{
System
.
out
.
printf
(
"Error when calling `MeetingsApi.v1MeetingsGet`: %s\n"
,
e
);
throw
new
RuntimeException
(
e
);
}
catch
(
ServiceException
e
)
{
System
.
out
.
printf
(
"Error when calling `MeetingsApi.v1MeetingsGet`: %s\n"
,
e
);
System
.
out
.
printf
(
"Full HTTP response: %s\n"
,
new
String
(
e
.
getApiResp
().
getRawBody
()));
throw
new
RuntimeException
(
e
);
}
}
}
src/main/java/com/cmeeting/service/impl/WeComServiceImpl.java
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
service
.
impl
;
import
com.alibaba.fastjson.JSONArray
;
import
com.alibaba.fastjson.JSONObject
;
import
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
;
import
com.cmeeting.mapper.primary.WeComUserMapper
;
import
com.cmeeting.pojo.WeComUser
;
import
com.cmeeting.service.WeComService
;
import
com.google.gson.Gson
;
import
com.google.gson.JsonArray
;
import
com.google.gson.JsonObject
;
import
okhttp3.OkHttpClient
;
import
okhttp3.Request
;
import
okhttp3.Response
;
import
org.json.JSONObject
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
com.cmeeting.util.RedisUtils
;
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.meetings.api.MeetingsApi
;
import
com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsGet200Response
;
import
com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsGet200ResponseMeetingInfoListInner
;
import
okhttp3.*
;
import
org.springframework.stereotype.Service
;
import
javax.annotation.Resource
;
import
java.io.BufferedReader
;
import
java.io.IOException
;
import
java.io.InputStreamReader
;
import
java.math.BigInteger
;
import
java.net.HttpURLConnection
;
import
java.net.URL
;
import
java.util.Collection
;
import
java.util.List
;
import
static
com
.
cmeeting
.
WeComAndTencentMeeting
.
convertJsonToWeComUsers
;
import
static
com
.
cmeeting
.
WeComAndTencentMeeting
.
markDuplicateNames
;
import
java.security.SecureRandom
;
import
java.util.*
;
import
java.util.stream.Collectors
;
@Service
public
class
WeComServiceImpl
extends
ServiceImpl
<
WeComUserMapper
,
WeComUser
>
implements
WeComService
{
private
static
final
String
BASE_URL
=
"https://qyapi.weixin.qq.com/cgi-bin/user/simplelist"
;
private
static
final
String
ACCESS_TOKEN
=
"z8g0fkzlVBMKrwi67CRwE7gcnGqjOMrTK6LDih06vs1ixpV9BkrVWQJkoF3HFf5a2UrMvTKEBewOo7wPGvVxNjV_ZGOp_dUxTfnCG6kRUK9LKXPlhskGOI3mcI3ZZpGqpPcgjCsE877hh1BYTB_i3t5X_q1tZfv0K636o3a0HyAMf4GlzviCd1vQHfkGf3p15FjnATwDV8ddhMZo8U-_uA"
;
private
static
final
String
CORP_ID
=
"wx34544d057db97ffd"
;
private
static
final
String
CORP_SECRET
=
"7YLePWG7rJqkQFnAB4FeylqAXpmu7q5qv_NOeSGNbm0"
;
private
static
final
String
TOKEN_KEY
=
"ZHONGJI_WECOM_KEY"
;
@Resource
private
WeComUserMapper
weComUserMapper
;
@Resource
private
RedisUtils
redisUtils
;
@Override
public
void
batchInsert
(
List
<
WeComUser
>
users
)
{
...
...
@@ -46,35 +48,42 @@ public class WeComServiceImpl extends ServiceImpl<WeComUserMapper, WeComUser> i
@Override
public
void
doUsers
()
throws
Exception
{
// 示例:获取部门ID为6的用户列表
int
departmentId
=
6
;
JsonObject
departmentList
=
getDepartmentList
();
JsonObject
result
=
getUserListByDepartment
(
departmentId
);
// 处理返回结果
if
(
result
.
get
(
"errcode"
).
getAsInt
()
==
0
)
{
System
.
out
.
println
(
"企微获取用户列表成功:"
);
JsonArray
userList
=
result
.
getAsJsonArray
(
"userlist"
);
//获取所有可见范围下的部门
JSONArray
departmentList
=
getDepartmentList
();
Iterator
<
Object
>
iterator
=
departmentList
.
iterator
();
List
<
WeComUser
>
users
=
new
ArrayList
<>();
while
(
iterator
.
hasNext
()){
JSONObject
department
=
(
JSONObject
)
iterator
.
next
();
String
departmentId
=
String
.
valueOf
(
department
.
get
(
"id"
));
//获取部门下的所有人员
JSONArray
userList
=
getUserListByDepartment
(
departmentId
);
for
(
int
i
=
0
;
i
<
userList
.
size
();
i
++)
{
JsonObject
user
=
userList
.
get
(
i
).
getAsJsonObject
();
System
.
out
.
println
(
"姓名: "
+
user
.
get
(
"name"
).
getAsString
()
+
", UserID: "
+
user
.
get
(
"userid"
).
getAsString
());
JSONObject
userJson
=
(
JSONObject
)
userList
.
get
(
i
);
System
.
out
.
println
(
"姓名: "
+
userJson
.
get
(
"name"
)
+
", UserID: "
+
userJson
.
get
(
"userid"
));
// 转换为WeComUser集合
WeComUser
user
=
new
WeComUser
();
user
.
setUserId
(
userJson
.
getString
(
"userid"
));
user
.
setUserName
(
userJson
.
getString
(
"name"
));
users
.
add
(
user
);
}
// 2. 转换为WeComUser集合
List
<
WeComUser
>
users
=
convertJsonToWeComUsers
(
result
);
// 3. 检查重名并设置标志
markDuplicateNames
(
users
);
// 4. 批量插入数据库
batchInsert
(
users
);
}
// 检查重名并设置标志
markDuplicateNames
(
users
);
// 批量插入数据库
batchInsert
(
users
);
}
@Override
public
String
getToken
()
{
if
(
redisUtils
.
hasKey
(
TOKEN_KEY
)){
return
String
.
valueOf
(
redisUtils
.
get
(
TOKEN_KEY
));
}
//获取token
// String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ww1fd8778458e9f1e8&corpsecret=uFRq9Xi8-dVY90LydXYBhjc91JnnfkPUR6lHDdeJ_fo";
String
url
=
"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid="
+
CORP_ID
+
"&corpsecret="
+
CORP_SECRET
;
String
accessToken
=
""
;
String
accessToken
;
OkHttpClient
client
=
new
OkHttpClient
();
Request
request
=
new
Request
.
Builder
()
.
url
(
url
)
...
...
@@ -84,8 +93,9 @@ public class WeComServiceImpl extends ServiceImpl<WeComUserMapper, WeComUser> i
try
(
Response
response
=
client
.
newCall
(
request
).
execute
())
{
if
(
response
.
isSuccessful
())
{
String
responseBody
=
response
.
body
().
string
();
JSONObject
jsonResponse
=
new
JSON
Object
(
responseBody
);
JSONObject
jsonResponse
=
JSONObject
.
parse
Object
(
responseBody
);
accessToken
=
jsonResponse
.
getString
(
"access_token"
);
redisUtils
.
set
(
TOKEN_KEY
,
accessToken
,
Integer
.
valueOf
(
jsonResponse
.
getString
(
"expires_in"
))
-
300
);
}
else
{
throw
new
RuntimeException
(
"Failed to fetch token. HTTP Code: "
+
response
.
code
());
}
...
...
@@ -95,13 +105,95 @@ public class WeComServiceImpl extends ServiceImpl<WeComUserMapper, WeComUser> i
return
accessToken
;
}
@Override
public
List
<
WeComUser
>
noBindUsers
()
{
List
<
WeComUser
>
noBindUsers
=
weComUserMapper
.
noBindUsers
();
return
noBindUsers
;
}
/**
* 创建预约会议(传入 wid 作为主持人)
*/
@Override
public
Map
<
String
,
String
>
createTempMeeting
(
String
wid
,
String
token
)
{
String
url
=
"https://qyapi.weixin.qq.com/cgi-bin/meeting/create?access_token="
+
token
;
org
.
json
.
JSONObject
body
=
new
org
.
json
.
JSONObject
()
.
put
(
"admin_userid"
,
wid
)
// 主持人 userid
.
put
(
"title"
,
"自动创建会议"
)
.
put
(
"meeting_start"
,
System
.
currentTimeMillis
()
/
1000
+
3600
)
// 1小时后开始
.
put
(
"meeting_duration"
,
3600
)
.
put
(
"invitees"
,
new
org
.
json
.
JSONObject
()
// 添加 invitees 字段
.
put
(
"userid"
,
new
String
[]{
wid
})
// 将 wid 作为参会人
);
Request
request
=
new
Request
.
Builder
()
.
url
(
url
)
.
post
(
RequestBody
.
create
(
MediaType
.
parse
(
"application/json"
),
body
.
toString
()))
.
build
();
try
(
Response
response
=
new
OkHttpClient
().
newCall
(
request
).
execute
())
{
org
.
json
.
JSONObject
json
=
new
org
.
json
.
JSONObject
(
response
.
body
().
string
());
if
(
json
.
getInt
(
"errcode"
)
!=
0
)
{
return
null
;
// 或者抛出异常
}
Map
<
String
,
String
>
result
=
new
HashMap
<>();
result
.
put
(
"meetingid"
,
json
.
getString
(
"meetingid"
));
result
.
put
(
"meeting_code"
,
json
.
getString
(
"meeting_code"
));
return
result
;
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
e
);
}
}
/**
* 通过企业微信接口取消预约会议
*
* @param meetingId
*/
@Override
public
void
cancelMeeting
(
String
meetingId
,
String
weComToken
)
{
// 1. 构造请求URL
String
url
=
"https://qyapi.weixin.qq.com/cgi-bin/meeting/cancel?access_token="
+
weComToken
;
// 2. 构造请求体(JSON格式)
org
.
json
.
JSONObject
requestBody
=
new
org
.
json
.
JSONObject
();
requestBody
.
put
(
"meetingid"
,
meetingId
);
// 3. 发送POST请求
OkHttpClient
client
=
new
OkHttpClient
();
RequestBody
body
=
RequestBody
.
create
(
MediaType
.
parse
(
"application/json"
),
requestBody
.
toString
()
);
Request
request
=
new
Request
.
Builder
()
.
url
(
url
)
.
post
(
body
)
.
build
();
try
(
Response
response
=
client
.
newCall
(
request
).
execute
())
{
// 4. 解析响应
String
responseData
=
response
.
body
().
string
();
org
.
json
.
JSONObject
jsonResponse
=
new
org
.
json
.
JSONObject
(
responseData
);
if
(
jsonResponse
.
getInt
(
"errcode"
)
==
0
)
{
System
.
out
.
println
(
"会议取消成功: "
+
meetingId
);
}
else
{
System
.
err
.
println
(
"会议取消失败: "
+
responseData
);
}
}
catch
(
IOException
e
)
{
System
.
err
.
println
(
"取消会议请求异常: "
+
e
.
getMessage
());
}
}
/**
* 获取企微部门列表
*
* @return 包含用户列表的JsonObject
* @throws Exception
*/
public
J
sonObject
getDepartmentList
()
throws
Exception
{
public
J
SONArray
getDepartmentList
()
throws
Exception
{
String
urlStr
=
"https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token="
+
getToken
();
URL
url
=
new
URL
(
urlStr
);
...
...
@@ -119,9 +211,9 @@ public class WeComServiceImpl extends ServiceImpl<WeComUserMapper, WeComUser> i
}
in
.
close
();
// 使用Gson解析JSON响应
Gson
gson
=
new
Gson
(
);
return
gson
.
fromJson
(
response
.
toString
(),
JsonObject
.
class
)
;
JSONObject
jsonResult
=
JSONObject
.
parseObject
(
response
.
toString
());
JSONArray
departmentArray
=
(
JSONArray
)
jsonResult
.
get
(
"department_id"
);
return
departmentArray
;
}
else
{
throw
new
RuntimeException
(
"HTTP GET请求失败,错误码: "
+
responseCode
);
}
...
...
@@ -134,8 +226,8 @@ public class WeComServiceImpl extends ServiceImpl<WeComUserMapper, WeComUser> i
* @return 包含用户列表的JsonObject
* @throws Exception
*/
public
J
sonObject
getUserListByDepartment
(
int
departmentId
)
throws
Exception
{
String
urlStr
=
BASE_URL
+
"?access_token="
+
ACCESS_TOKEN
+
"&department_id="
+
departmentId
;
public
J
SONArray
getUserListByDepartment
(
String
departmentId
)
throws
Exception
{
String
urlStr
=
"https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token="
+
getToken
()
+
"&department_id="
+
departmentId
;
URL
url
=
new
URL
(
urlStr
);
HttpURLConnection
conn
=
(
HttpURLConnection
)
url
.
openConnection
();
...
...
@@ -152,11 +244,29 @@ public class WeComServiceImpl extends ServiceImpl<WeComUserMapper, WeComUser> i
}
in
.
close
();
// 使用Gson解析JSON响应
Gson
gson
=
new
Gson
(
);
return
gson
.
fromJson
(
response
.
toString
(),
JsonObject
.
class
)
;
JSONObject
jsonResult
=
JSONObject
.
parseObject
(
response
.
toString
());
JSONArray
userList
=
(
JSONArray
)
jsonResult
.
get
(
"userlist"
);
return
userList
;
}
else
{
throw
new
RuntimeException
(
"HTTP GET请求失败,错误码: "
+
responseCode
);
}
}
/**
* 标记企业微信重名用户
*/
public
static
void
markDuplicateNames
(
List
<
WeComUser
>
users
)
{
// 按姓名分组,统计每个名字出现的次数
Map
<
String
,
Long
>
nameCountMap
=
users
.
stream
()
.
collect
(
Collectors
.
groupingBy
(
WeComUser:
:
getUserName
,
Collectors
.
counting
()));
// 设置是否重名标志
users
.
forEach
(
user
->
{
if
(
nameCountMap
.
get
(
user
.
getUserName
())
>
1
)
{
user
.
setIsRepeatName
(
"1"
);
// 重名
}
else
{
user
.
setIsRepeatName
(
"0"
);
// 不重名
}
});
}
}
src/main/java/com/cmeeting/util/MinioUtils.java
浏览文件 @
e0b7d23d
...
...
@@ -33,6 +33,16 @@ public class MinioUtils {
}
}
public
InputStream
getFile
(
String
fileName
){
try
{
getMinioClient
();
InputStream
inputStream
=
minioClient
.
getObject
(
bucketName
,
fileName
);
return
inputStream
;
}
catch
(
Exception
e
){
throw
new
RuntimeException
(
e
);
}
}
public
InputStream
getFile
(
MinioClient
minioClient
,
String
fileName
){
try
{
InputStream
inputStream
=
minioClient
.
getObject
(
bucketName
,
fileName
);
...
...
src/main/java/com/cmeeting/util/RedisUtils.java
0 → 100644
浏览文件 @
e0b7d23d
package
com
.
cmeeting
.
util
;
import
org.springframework.data.redis.core.BoundListOperations
;
import
org.springframework.data.redis.core.RedisTemplate
;
import
org.springframework.stereotype.Component
;
import
org.springframework.util.CollectionUtils
;
import
javax.annotation.Resource
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
import
java.util.concurrent.TimeUnit
;
/**
* @author 刘振萌
* @date 2020/5/19 20:07
*/
@Component
public
class
RedisUtils
{
@Resource
private
RedisTemplate
<
Object
,
Object
>
redisTemplate
;
/**
* 通过key删除value
*
* @param keys
* @return
*/
public
boolean
removeKey
(
List
<
String
>
keys
)
{
return
redisTemplate
.
delete
(
keys
);
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public
boolean
expire
(
String
key
,
long
time
)
{
try
{
if
(
time
>
0
)
{
redisTemplate
.
expire
(
key
,
time
,
TimeUnit
.
SECONDS
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public
long
getExpire
(
String
key
)
{
return
redisTemplate
.
getExpire
(
key
,
TimeUnit
.
SECONDS
);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public
boolean
hasKey
(
String
key
)
{
try
{
return
redisTemplate
.
hasKey
(
key
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings
(
"unchecked"
)
public
void
del
(
String
...
key
)
{
if
(
key
!=
null
&&
key
.
length
>
0
)
{
if
(
key
.
length
==
1
)
{
redisTemplate
.
delete
(
key
[
0
]);
}
else
{
redisTemplate
.
delete
(
CollectionUtils
.
arrayToList
(
key
));
}
}
}
//============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public
Object
get
(
String
key
)
{
return
key
==
null
?
null
:
redisTemplate
.
opsForValue
().
get
(
key
);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public
boolean
set
(
String
key
,
Object
value
)
{
try
{
redisTemplate
.
opsForValue
().
set
(
key
,
value
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public
boolean
set
(
String
key
,
Object
value
,
long
time
)
{
try
{
if
(
time
>
0
)
{
redisTemplate
.
opsForValue
().
set
(
key
,
value
,
time
,
TimeUnit
.
SECONDS
);
}
else
{
set
(
key
,
value
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public
long
incr
(
String
key
,
long
delta
)
{
if
(
delta
<
0
)
{
throw
new
RuntimeException
(
"递增因子必须大于0"
);
}
return
redisTemplate
.
opsForValue
().
increment
(
key
,
delta
);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public
long
decr
(
String
key
,
long
delta
)
{
if
(
delta
<
0
)
{
throw
new
RuntimeException
(
"递减因子必须大于0"
);
}
return
redisTemplate
.
opsForValue
().
increment
(
key
,
-
delta
);
}
//================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public
Object
hget
(
String
key
,
String
item
)
{
return
redisTemplate
.
opsForHash
().
get
(
key
,
item
);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public
Map
<
Object
,
Object
>
hmget
(
String
key
)
{
return
redisTemplate
.
opsForHash
().
entries
(
key
);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public
boolean
hmset
(
String
key
,
Map
<
String
,
Object
>
map
)
{
try
{
redisTemplate
.
opsForHash
().
putAll
(
key
,
map
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public
boolean
hmset
(
String
key
,
Map
<
String
,
Object
>
map
,
long
time
)
{
try
{
redisTemplate
.
opsForHash
().
putAll
(
key
,
map
);
if
(
time
>
0
)
{
expire
(
key
,
time
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public
boolean
hset
(
String
key
,
String
item
,
Object
value
)
{
try
{
redisTemplate
.
opsForHash
().
put
(
key
,
item
,
value
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public
boolean
hset
(
String
key
,
String
item
,
Object
value
,
long
time
)
{
try
{
redisTemplate
.
opsForHash
().
put
(
key
,
item
,
value
);
if
(
time
>
0
)
{
expire
(
key
,
time
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public
void
hdel
(
String
key
,
Object
...
item
)
{
redisTemplate
.
opsForHash
().
delete
(
key
,
item
);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public
boolean
hHasKey
(
String
key
,
String
item
)
{
return
redisTemplate
.
opsForHash
().
hasKey
(
key
,
item
);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public
double
hincr
(
String
key
,
String
item
,
double
by
)
{
return
redisTemplate
.
opsForHash
().
increment
(
key
,
item
,
by
);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public
double
hdecr
(
String
key
,
String
item
,
double
by
)
{
return
redisTemplate
.
opsForHash
().
increment
(
key
,
item
,
-
by
);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public
Set
<
Object
>
sGet
(
String
key
)
{
try
{
return
redisTemplate
.
opsForSet
().
members
(
key
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
null
;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public
boolean
sHasKey
(
String
key
,
Object
value
)
{
try
{
return
redisTemplate
.
opsForSet
().
isMember
(
key
,
value
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public
long
sSet
(
String
key
,
Object
...
values
)
{
try
{
return
redisTemplate
.
opsForSet
().
add
(
key
,
values
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public
long
sSetAndTime
(
String
key
,
long
time
,
Object
...
values
)
{
try
{
Long
count
=
redisTemplate
.
opsForSet
().
add
(
key
,
values
);
if
(
time
>
0
)
{
expire
(
key
,
time
);
}
return
count
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
public
long
sGetSetSize
(
String
key
)
{
try
{
return
redisTemplate
.
opsForSet
().
size
(
key
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public
long
setRemove
(
String
key
,
Object
...
values
)
{
try
{
Long
count
=
redisTemplate
.
opsForSet
().
remove
(
key
,
values
);
return
count
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public
List
<
Object
>
lGet
(
String
key
,
long
start
,
long
end
)
{
try
{
return
redisTemplate
.
opsForList
().
range
(
key
,
start
,
end
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
null
;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
public
long
lGetListSize
(
String
key
)
{
try
{
return
redisTemplate
.
opsForList
().
size
(
key
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public
Object
lGetIndex
(
String
key
,
long
index
)
{
try
{
return
redisTemplate
.
opsForList
().
index
(
key
,
index
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
null
;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public
boolean
lSet
(
String
key
,
Object
value
)
{
try
{
redisTemplate
.
opsForList
().
rightPush
(
key
,
value
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public
boolean
lSet
(
String
key
,
Object
value
,
long
time
)
{
try
{
redisTemplate
.
opsForList
().
rightPush
(
key
,
value
);
if
(
time
>
0
)
{
expire
(
key
,
time
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public
boolean
lSet
(
String
key
,
List
<
Object
>
value
)
{
try
{
redisTemplate
.
opsForList
().
rightPushAll
(
key
,
value
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public
boolean
lSet
(
String
key
,
List
<
Object
>
value
,
long
time
)
{
try
{
redisTemplate
.
opsForList
().
rightPushAll
(
key
,
value
);
if
(
time
>
0
)
{
expire
(
key
,
time
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public
boolean
lUpdateIndex
(
String
key
,
long
index
,
Object
value
)
{
try
{
redisTemplate
.
opsForList
().
set
(
key
,
index
,
value
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public
long
lRemove
(
String
key
,
long
count
,
Object
value
)
{
try
{
Long
remove
=
redisTemplate
.
opsForList
().
remove
(
key
,
count
,
value
);
return
remove
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
/**
* 模糊查询获取key值
*
* @param pattern
* @return
*/
public
Set
keys
(
String
pattern
)
{
return
redisTemplate
.
keys
(
pattern
);
}
/**
* 使用Redis的消息队列
*
* @param channel
* @param message 消息内容
*/
public
void
convertAndSend
(
String
channel
,
Object
message
)
{
redisTemplate
.
convertAndSend
(
channel
,
message
);
}
//=========BoundListOperations 用法 start============
/**
* 根据起始结束序号遍历Redis中的list
*
* @param listKey
* @param start 起始序号
* @param end 结束序号
* @return
*/
public
List
<
Object
>
rangeList
(
String
listKey
,
long
start
,
long
end
)
{
//绑定操作
BoundListOperations
<
Object
,
Object
>
boundValueOperations
=
redisTemplate
.
boundListOps
(
listKey
);
//查询数据
return
boundValueOperations
.
range
(
start
,
end
);
}
/**
* 弹出右边的值 --- 并且移除这个值
*
* @param listKey
*/
public
Object
rifhtPop
(
String
listKey
)
{
//绑定操作
BoundListOperations
<
Object
,
Object
>
boundValueOperations
=
redisTemplate
.
boundListOps
(
listKey
);
return
boundValueOperations
.
rightPop
();
}
public
String
getInitTokenKey
(
String
sessionId
,
String
robotId
)
{
return
"INIT_ROBOT_TOKEN"
+
sessionId
+
"_"
+
robotId
;
}
public
String
getAnswerTokenKey
(
String
sessionId
,
String
robotId
)
{
return
"ANSWER_TOKEN_KEY"
+
sessionId
+
"_"
+
robotId
;
}
}
\ No newline at end of file
src/main/java/com/cmeeting/util/WeComUserService.java
浏览文件 @
e0b7d23d
...
...
@@ -9,8 +9,6 @@ import com.google.gson.JsonElement;
import
java.util.*
;
import
java.util.stream.Collectors
;
import
static
com
.
cmeeting
.
WeComAndTencentMeeting
.
getUserListByDepartment
;
public
class
WeComUserService
{
// 假设这是你的MyBatis Mapper
...
...
@@ -21,23 +19,6 @@ public class WeComUserService {
}
/**
* 处理部门用户数据并存入数据库
*/
public
void
processAndSaveDepartmentUsers
(
int
departmentId
)
throws
Exception
{
// 1. 获取企业微信API数据
JsonObject
result
=
getUserListByDepartment
(
departmentId
);
// 2. 转换为WeComUser集合
List
<
WeComUser
>
users
=
convertJsonToWeComUsers
(
result
);
// 3. 检查重名并设置标志
markDuplicateNames
(
users
);
// 4. 批量插入数据库
weComUserMapper
.
batchInsert
(
users
);
}
/**
* 将企业微信返回的JSON转换为WeComUser列表
*/
private
List
<
WeComUser
>
convertJsonToWeComUsers
(
JsonObject
json
)
{
...
...
src/main/java/com/cmeeting/vo/TencentMeetingVO.java
浏览文件 @
e0b7d23d
...
...
@@ -6,6 +6,8 @@ import lombok.Builder;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
java.util.List
;
@Data
@Builder
public
class
TencentMeetingVO
{
...
...
@@ -30,6 +32,6 @@ public class TencentMeetingVO {
public
static
class
RecordFile
{
private
String
meetingId
;
//如果是周期会议,这个id表示主会议
private
String
subMeetingId
;
//如果是周期会议,这个id表示子会议
private
String
recordFileId
;
private
List
<
String
>
recordFileIdList
;
}
}
\ No newline at end of file
src/main/resources/application.yml
浏览文件 @
e0b7d23d
server
:
port
:
8080
# ????????
# application.yml
# spring.datasource.url=jdbc:mysql://192.168.10.155:3306/cmeeting?useSSL=false&characterEncoding=utf8
# spring.datasource.username=root
# spring.datasource.password=qizhi123
# spring.datasource.driver-class-name=com.mysql.jdbc.Driver
############################################################## minio
MINIO_ADDRESS
:
http://192.168.10.154:9000
MINIO_BUCKET
:
zhongji
MINIO_USERNAME
:
minio
MINIO_PASSWORD
:
minio123
############################################################## redis
REDIS_ADDRESS
:
192.168.10.154
REDIS_PORT
:
6380
REDIS_PASS
:
standard123
REDIS_DATABASE
:
8
############################################################## llm
# local
LLM_API_ADDR
:
https://bedrock.chatbot.cn
# prod
#LLM_API_ADDR: http://10.56.1.150:8000
############################################################## tencent meeting
# local
#TENCENT_APPID: 211153201
#TENCENT_SDKID: 28370276340
#TENCENT_SECRETID: BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6
#TENCENT_SECRETKEY: 3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO
#TENCENT_ADMIN_USERID: woaJARCQAAhkyWGuf8n9InhZsxQstjjA
#TENCENT_ADMIN_USERID: woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA
# prod
TENCENT_APPID
:
210468336
TENCENT_SDKID
:
28790143843
TENCENT_SECRETID
:
0ks7u8cgQ8DGVtlYZeRA9TxZCjvUT3oL
TENCENT_SECRETKEY
:
gQU09rkJjiQfiGcUYdhiKq5Ol6LebXg4w7F7Ol0rwvvdv3Xy
TENCENT_ADMIN_USERID
:
woaJARCQAAftcvU6GGoOn66rdSZ4IrOA
############################################################## email
EMAIL_SENDER
:
cmeeting_assistant@cimc.com
EMAIL_SENDER_PWD
:
scyou@xih45g6@xih4
EMAIL_SMTP_HOST
:
smtp.office365.com
############################################################## permission
PERMISSION_APPLiCATION_ID
:
1928343847335759872
PERMISSION_TENANT_ID
:
1928343652791357440
# ?????primary?
#spring.datasource.primary.jdbc-url=jdbc:mysql://192.168.10.154:3307/aigc-zhongji-test?useSSL=false&characterEncoding=utf8&serverTimezone=UTC
##spring.datasource.primary.username=root
##spring.datasource.primary.password=123456
##spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
##
### ?????secondary?
##spring.datasource.secondary.jdbc-url=jdbc:mysql://192.168.10.154:3307/useradmin-zhongji-test?useSSL=false&characterEncoding=utf8&serverTimezone=UTC
##spring.datasource.secondary.username=root
##spring.datasource.secondary.password=123456
##spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
spring
:
datasource
:
...
...
@@ -34,12 +56,23 @@ spring:
username
:
root
password
:
123456
driver-class-name
:
com.mysql.jdbc.Driver
redis
:
database
:
${REDIS_DATABASE}
host
:
${REDIS_ADDRESS}
jedis
:
pool
:
max-active
:
8
max-idle
:
8
max-wait
:
30000ms
min-idle
:
1
port
:
${REDIS_PORT}
timeout
:
6000ms
password
:
${REDIS_PASS}
# MyBatis ??
# mybatis.mapper-locations=classpath:mapper/primary/*.xml
mybatis.type-aliases-package
:
com.cmeeting.pojo\
# ??????
mybatis
:
configuration
:
map-underscore-to-camel-case
:
true
...
...
@@ -52,19 +85,6 @@ global-config:
db-config
:
column-underline
:
true
#logging:
# level:
# com:
# zaxxer:
# hikari: INFO
############################################################## minIO
MINIO_ADDRESS
:
http://192.168.10.154:9000
MINIO_BUCKET
:
zhongji
MINIO_USERNAME
:
minio
MINIO_PASSWORD
:
minio123
#Minio服务所在地址
minio.endpoint
:
${MINIO_ADDRESS}
#存储桶名称
...
...
@@ -73,33 +93,28 @@ minio.bucketName: ${MINIO_BUCKET}
minio.accessKey
:
${MINIO_USERNAME}
#访问的秘钥
minio.secretKey
:
${MINIO_PASSWORD}
############################################################## minIO
############################################################## tencent meeting
# local
#tencent.appId=211153201
#tencent.sdkId=28370276340
#tencent.secretId=BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6
#tencent.secretKey=3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO
#tencent.admin.userId=woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA
#tencent.admin.userId=woaJARCQAAhkyWGuf8n9InhZsxQstjjA
# prod
tencent
:
appId
:
210468336
sdkId
:
28790143843
secretId
:
0ks7u8cgQ8DGVtlYZeRA9TxZCjvUT3oL
secretKey
:
gQU09rkJjiQfiGcUYdhiKq5Ol6LebXg4w7F7Ol0rwvvdv3Xy
admin.userId
:
woaJARCQAAftcvU6GGoOn66rdSZ4IrOA
appId
:
${TENCENT_APPID}
sdkId
:
${TENCENT_SDKID}
secretId
:
${TENCENT_SECRETID}
secretKey
:
${TENCENT_SECRETKEY}
admin.userId
:
${TENCENT_ADMIN_USERID}
meeting
:
token
:
QQZNb7xWQB47MpZF4C2DFAkv8
aesKey
:
agy6ALUePp34lljWz1uIQWa7yQq3dgxxQNmfaN9GROm
email
:
sender
:
cmeeting_assistant@cimc.com
sender-pwd
:
scyou@xih45g6@xih4
smtp-host
:
smtp.office365.com
############################################################## tencent meeting
sender
:
${EMAIL_SENDER}
sender-pwd
:
${EMAIL_SENDER_PWD}
smtp-host
:
${EMAIL_SMTP_HOST}
llm
:
api-addr
:
${LLM_API_ADDR}
permission
:
applicationId
:
${PERMISSION_APPLiCATION_ID}
tenantId
:
${PERMISSION_TENANT_ID}
logging
:
level
:
...
...
src/main/resources/mapper/primary/AuthMapper.xml
浏览文件 @
e0b7d23d
...
...
@@ -2,7 +2,7 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace=
"com.cmeeting.mapper.primary.AuthMapper"
>
<select
id=
"getAuthByTarg
r
tId"
resultType=
"com.cmeeting.pojo.CoreModulePermissions"
>
<select
id=
"getAuthByTarg
e
tId"
resultType=
"com.cmeeting.pojo.CoreModulePermissions"
>
SELECT
id,
type,
...
...
@@ -16,7 +16,8 @@
FROM
core_module_permissions
WHERE
target_id = #{targetId}
user_type = 2
and target_id = #{targetId}
<if
test=
"tenantId != null and tenantId != ''"
>
AND tenant_id = #{tenantId}
</if>
...
...
src/main/resources/mapper/primary/MeetingInfoMapper.xml
浏览文件 @
e0b7d23d
...
...
@@ -2,16 +2,17 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace=
"com.cmeeting.mapper.primary.MeetingInfoMapper"
>
<insert
id=
"batchInsert"
parameterType=
"list"
>
INSERT IGNORE INTO cmt_meeting_info (subject, meeting_id, meeting_code, host_id, participant_user_ids, start_time,
end_time, is_generated, email_push_access, is_pushed, sync_time, sub_meeting_id, record_content, record_xml)
INSERT IGNORE INTO cmt_meeting_info (subject, meeting_id, meeting_code, host, participant_users, start_time,
end_time, is_generated, email_push_access, is_pushed, sync_time, sub_meeting_id, record_content, record_xml, generate_retry,
push_retry, record_file_id,email)
VALUES
<foreach
collection=
"meetingSaveList"
item=
"meeting"
separator=
","
>
(
#{meeting.subject},
#{meeting.meetingId},
#{meeting.meetingCode},
#{meeting.host
Id
},
#{meeting.participantUser
Id
s},
#{meeting.host},
#{meeting.participantUsers},
#{meeting.startTime},
#{meeting.endTime},
#{meeting.isGenerated},
...
...
@@ -20,7 +21,11 @@ end_time, is_generated, email_push_access, is_pushed, sync_time, sub_meeting_id,
#{meeting.syncTime},
#{meeting.subMeetingId},
#{meeting.recordContent},
#{meeting.recordXml}
#{meeting.recordXml},
#{meeting.generateRetry},
#{meeting.pushRetry},
#{meeting.recordFileId},
#{meeting.email}
)
</foreach>
</insert>
...
...
src/main/resources/mapper/primary/UserIdMapper.xml
浏览文件 @
e0b7d23d
...
...
@@ -4,9 +4,9 @@
<!-- 批量插入用户ID信息 -->
<insert
id=
"insertUsers"
parameterType=
"List"
>
INSERT INTO userid (
user
N
ame,
W
id,
T
id
user
_n
ame,
w
id,
t
id
) VALUES
<foreach
collection=
"list"
item=
"item"
separator=
","
>
(
...
...
@@ -19,14 +19,14 @@
<!-- 更新 Tid 字段 -->
<update
id=
"updateUser"
parameterType=
"com.cmeeting.pojo.UserId"
>
UPDATE userid
SET
T
id = #{tid}
SET
t
id = #{tid}
WHERE wid = #{wid}
</update>
<select
id=
"getUsers"
resultType=
"com.cmeeting.pojo.UserId"
>
select * from userid
</select>
<select
id=
"getWidByTid"
resultType=
"java.lang.String"
>
select
W
id from userid WHERE Tid = #{operatorUserId}
select
w
id from userid WHERE Tid = #{operatorUserId}
</select>
</mapper>
\ No newline at end of file
src/main/resources/mapper/primary/WeComUserMapper.xml
浏览文件 @
e0b7d23d
...
...
@@ -30,8 +30,10 @@
<select
id=
"getAlluser"
resultType=
"com.cmeeting.pojo.WeComUser"
>
SELECT * FROM user_wecom
</select>
<select
id=
"getSameName"
resultType=
"com.cmeeting.pojo.WeComUser"
>
SELECT * FROM user_wecom
WHERE isrepeat_name = "1"
<select
id=
"noBindUsers"
resultType=
"com.cmeeting.pojo.WeComUser"
>
SELECT t1.*
FROM user_wecom t1
left join userid t2 on t1.user_id = t2.Wid
WHERE t2.id is null
</select>
</mapper>
\ No newline at end of file
src/main/resources/mapper/secondary/SysUserSysMapper.xml
浏览文件 @
e0b7d23d
...
...
@@ -9,4 +9,31 @@
AND tenant_id = #{tenantId}
LIMIT 1
</select>
<select
id=
"getParentDeptId"
resultType=
"java.lang.String"
>
SELECT parent_id
FROM sys_user_sync_category
WHERE dept_id = #{deptId}
AND tenant_id = #{tenantId}
LIMIT 1
</select>
<select
id=
"getSubDeptId"
resultType=
"java.lang.String"
>
SELECT dept_id
FROM sys_user_sync_category
WHERE parent_id = #{deptId}
AND tenant_id = #{tenantId}
</select>
<select
id=
"getUsersByDept"
resultType=
"java.lang.String"
>
select t1.user_id as wId
from sys_user_sync t1
where t1.dept_id in
<foreach
collection=
"deptIds"
item=
"deptId"
separator=
","
open=
"("
close=
")"
>
#{deptId}
</foreach>
</select>
<select
id=
"getUserEmail"
resultType=
"com.cmeeting.dto.UserDTO"
>
select t1.user_id as wId,IFNULL(t1.email,t1.company_email) as email
from sys_user_sync t1
where t1.email is not null
or t1.company_email is not null
</select>
</mapper>
\ No newline at end of file
src/main/resources/template/data_net_template.docx
0 → 100644
浏览文件 @
e0b7d23d
File added
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论