Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
cmeeting
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
翟斌
cmeeting
Commits
0cc18764
提交
0cc18764
authored
7月 22, 2025
作者:
洪东保
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
定时任务修改
父级
cdaad871
显示空白字符变更
内嵌
并排
正在显示
11 个修改的文件
包含
431 行增加
和
414 行删除
+431
-414
src/main/java/com/cmeeting/controller/RecordTemplateController.java
+1
-1
src/main/java/com/cmeeting/controller/UnifiedController.java
+0
-1
src/main/java/com/cmeeting/job/CmeetingJob.java
+28
-27
src/main/java/com/cmeeting/job/FileProcessTask.java
+144
-182
src/main/java/com/cmeeting/pojo/MeetingInfo.java
+1
-0
src/main/java/com/cmeeting/service/FileProcessProducer.java
+7
-17
src/main/java/com/cmeeting/service/impl/RecordTemplatePermissionServiceImpl.java
+1
-2
src/main/java/com/cmeeting/service/impl/TencentMeetingServiceImpl.java
+85
-115
src/main/java/com/cmeeting/util/RedisUtils.java
+9
-0
src/main/java/com/cmeeting/util/TencentMeetingApiUtil.java
+154
-68
src/main/resources/application.yml
+1
-1
没有找到文件。
src/main/java/com/cmeeting/controller/RecordTemplateController.java
浏览文件 @
0cc18764
...
...
@@ -170,7 +170,7 @@ public class RecordTemplateController {
* 模板测试效果
* @param file 用户自主上传的转录文件
* @param meetingInstId 历史会议主键id
* @param
id 模板id
* @param
content 模板提示词
* @return
*/
@PostMapping
(
"/testGenerate"
)
...
...
src/main/java/com/cmeeting/controller/UnifiedController.java
浏览文件 @
0cc18764
...
...
@@ -127,7 +127,6 @@ public class UnifiedController {
//取消预约会议
}
// //todo 待测试
// @GetMapping("/sameNameInsertTid")
// public void sameNameInsertTid(String corpid, String corpsecret) throws IOException {
// String weComToken = getWeComToken(corpid,corpsecret);
...
...
src/main/java/com/cmeeting/job/CmeetingJob.java
浏览文件 @
0cc18764
...
...
@@ -43,17 +43,18 @@ public class CmeetingJob {
@Value
(
"${isDev}"
)
private
Boolean
isDev
;
// @PostConstruct
public
void
weComUserInit
(){
// @PostConstruct
public
void
weComUserInit
()
{
weComUserSync
();
}
// @PostConstruct
public
void
tencentUserInit
(){
// @PostConstruct
public
void
tencentUserInit
()
{
TencentUserSync
();
}
// @PostConstruct
public
void
userBindInit
(){
// @PostConstruct
public
void
userBindInit
()
{
userBind
();
}
...
...
@@ -109,28 +110,28 @@ public class CmeetingJob {
log
.
info
(
"-------关联企微腾会人员定时任务结束--------"
);
}
@Scheduled
(
fixedRate
=
20
*
60
*
1000
,
initialDelay
=
2
*
60
*
1000
)
@Scheduled
(
fixedRate
=
20
*
60
*
1000
,
initialDelay
=
2
*
60
*
1000
)
public
void
execute
()
{
if
(
isDev
)
{
return
;
}
//查出企微id和腾会id的关联关系
List
<
UserId
>
userIdRelations
=
userIdMapper
.
selectList
(
null
);
Map
<
String
,
String
>
widTidRelations
=
userIdRelations
.
stream
().
collect
(
Collectors
.
toMap
(
UserId:
:
getWid
,
UserId:
:
getTid
));
Map
<
String
,
String
>
tidWidRelations
=
userIdRelations
.
stream
().
collect
(
Collectors
.
toMap
(
UserId:
:
getTid
,
UserId:
:
getWid
));
Map
<
String
,
String
>
widTidRelations
=
userIdRelations
.
stream
().
collect
(
Collectors
.
toMap
(
UserId:
:
getWid
,
UserId:
:
getTid
));
Map
<
String
,
String
>
tidWidRelations
=
userIdRelations
.
stream
().
collect
(
Collectors
.
toMap
(
UserId:
:
getTid
,
UserId:
:
getWid
));
//查出企微的人员信息
List
<
WeComUser
>
weComUserList
=
weComService
.
list
();
Map
<
String
,
WeComUser
>
weComUserMap
=
weComUserList
.
stream
().
collect
(
Collectors
.
toMap
(
WeComUser:
:
getUserId
,
Function
.
identity
()));
Map
<
String
,
WeComUser
>
weComUserMap
=
weComUserList
.
stream
().
collect
(
Collectors
.
toMap
(
WeComUser:
:
getUserId
,
Function
.
identity
()));
//智能体授权人员
List
<
UserDTO
>
accessUserIds
=
tencentMeetingService
.
getAccessUserIds
(
widTidRelations
);
if
(
CollectionUtils
.
isEmpty
(
accessUserIds
))
{
log
.
info
(
"无生成纪要权限的人员"
);
return
;
}
else
{
}
else
{
log
.
info
(
"生成纪要权限人员:->{}"
,
accessUserIds
.
stream
().
map
(
UserDTO:
:
getWid
).
collect
(
Collectors
.
joining
(
","
)));
}
List
<
TencentMeetingVO
.
RecordFile
>
meetingFiles
=
tencentMeetingService
.
getMeetingFiles
(
accessUserIds
,
weComUserMap
);
List
<
TencentMeetingVO
.
RecordFile
>
meetingFiles
=
tencentMeetingService
.
getMeetingFiles
(
accessUserIds
,
weComUserMap
);
if
(
meetingFiles
==
null
||
meetingFiles
.
isEmpty
())
{
log
.
info
(
"没有录制文件需要处理"
);
...
...
@@ -141,14 +142,14 @@ public class CmeetingJob {
List
<
UserDTO
.
TemplateAuthorizedUserDTO
>
authorizedUsers
=
meetingRecordTemplateService
.
selectAuthorizedUsers
();
// 提交处理任务
producer
.
submitBatchTasks
(
meetingFiles
,
authorizedUsers
,
tidWidRelations
,
Boolean
.
FALSE
);
producer
.
submitBatchTasks
(
meetingFiles
,
authorizedUsers
,
tidWidRelations
,
Boolean
.
FALSE
);
}
/**
* 定时扫描早于一小时之前的,所有未重试过的会议,重新生成纪要
*/
@Scheduled
(
fixedRate
=
30
*
60
*
1000
,
initialDelay
=
10
*
60
*
1000
)
@Scheduled
(
fixedRate
=
30
*
60
*
1000
,
initialDelay
=
10
*
60
*
1000
)
public
void
meetingMinutesRetry
()
{
if
(
isDev
)
{
return
;
...
...
@@ -160,9 +161,9 @@ public class CmeetingJob {
//查出所有早于一小时前的,生成失败且未重试过的会议
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
))
.
eq
(
MeetingInfo:
:
getIsGenerated
,
Boolean
.
FALSE
)
.
eq
(
MeetingInfo:
:
getGenerateRetry
,
Boolean
.
FALSE
)
.
le
(
MeetingInfo:
:
getSyncTime
,
LocalDateTime
.
now
().
minusHours
(
1
))
);
if
(
meetingInfoList
==
null
||
meetingInfoList
.
isEmpty
())
{
...
...
@@ -180,13 +181,13 @@ public class CmeetingJob {
//查出企微id和腾会id的关联关系
List
<
UserId
>
userIdRelations
=
userIdMapper
.
selectList
(
null
);
Map
<
String
,
String
>
tidWidRelations
=
userIdRelations
.
stream
().
collect
(
Collectors
.
toMap
(
UserId:
:
getTid
,
UserId:
:
getWid
));
Map
<
String
,
String
>
tidWidRelations
=
userIdRelations
.
stream
().
collect
(
Collectors
.
toMap
(
UserId:
:
getTid
,
UserId:
:
getWid
));
//获取模板授权的人员
List
<
UserDTO
.
TemplateAuthorizedUserDTO
>
authorizedUsers
=
meetingRecordTemplateService
.
selectAuthorizedUsers
();
// 提交处理任务
producer
.
submitBatchTasks
(
meetingFiles
,
authorizedUsers
,
tidWidRelations
,
Boolean
.
TRUE
);
producer
.
submitBatchTasks
(
meetingFiles
,
authorizedUsers
,
tidWidRelations
,
Boolean
.
TRUE
);
log
.
info
(
"-------生成纪要重试定时任务结束--------"
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
...
...
@@ -196,7 +197,7 @@ public class CmeetingJob {
/**
* 定时扫描早于一小时之前的,所有邮件推送未重试过的会议,重新推送邮件
*/
@Scheduled
(
fixedRate
=
30
*
60
*
1000
,
initialDelay
=
15
*
60
*
1000
)
@Scheduled
(
fixedRate
=
30
*
60
*
1000
,
initialDelay
=
15
*
60
*
1000
)
public
void
emailPushRetry
()
{
if
(
isDev
)
{
return
;
...
...
@@ -208,11 +209,11 @@ public class CmeetingJob {
//查出所有早于一小时前的,邮件推送失败且未重试过的会议
List
<
MeetingInfo
>
meetingInfoList
=
meetingInfoService
.
list
(
new
LambdaQueryWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getIsGenerated
,
Boolean
.
TRUE
)
.
eq
(
MeetingInfo:
:
getEmailPushAccess
,
Boolean
.
TRUE
)
.
eq
(
MeetingInfo:
:
getIsPushed
,
Boolean
.
FALSE
)
.
eq
(
MeetingInfo:
:
getPushRetry
,
Boolean
.
FALSE
)
.
le
(
MeetingInfo:
:
getSyncTime
,
LocalDateTime
.
now
().
minusHours
(
1
))
.
eq
(
MeetingInfo:
:
getIsGenerated
,
Boolean
.
TRUE
)
.
eq
(
MeetingInfo:
:
getEmailPushAccess
,
Boolean
.
TRUE
)
.
eq
(
MeetingInfo:
:
getIsPushed
,
Boolean
.
FALSE
)
.
eq
(
MeetingInfo:
:
getPushRetry
,
Boolean
.
FALSE
)
.
le
(
MeetingInfo:
:
getSyncTime
,
LocalDateTime
.
now
().
minusHours
(
1
))
);
if
(
meetingInfoList
==
null
||
meetingInfoList
.
isEmpty
())
{
...
...
@@ -229,9 +230,9 @@ public class CmeetingJob {
//查出企微id和腾会id的关联关系
List
<
UserId
>
userIdRelations
=
userIdMapper
.
selectList
(
null
);
Map
<
String
,
String
>
tidWidRelations
=
userIdRelations
.
stream
().
collect
(
Collectors
.
toMap
(
UserId:
:
getTid
,
UserId:
:
getWid
));
Map
<
String
,
String
>
tidWidRelations
=
userIdRelations
.
stream
().
collect
(
Collectors
.
toMap
(
UserId:
:
getTid
,
UserId:
:
getWid
));
// 提交处理任务
producer
.
submitEmailPushTasks
(
meetingFiles
,
tidWidRelations
);
producer
.
submitEmailPushTasks
(
meetingFiles
,
tidWidRelations
);
log
.
info
(
"-------邮件推送重试定时任务结束--------"
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
...
...
src/main/java/com/cmeeting/job/FileProcessTask.java
浏览文件 @
0cc18764
...
...
@@ -12,6 +12,7 @@ import cn.chatbot.openai.service.LLMService;
import
cn.hutool.core.date.DateUtil
;
import
cn.hutool.core.io.FileUtil
;
import
cn.hutool.core.util.IdUtil
;
import
cn.hutool.core.util.StrUtil
;
import
com.alibaba.fastjson.JSON
;
import
com.alibaba.fastjson.JSONObject
;
import
com.alibaba.fastjson.TypeReference
;
...
...
@@ -24,6 +25,7 @@ import com.cmeeting.constant.UserAdminRouteConstant;
import
com.cmeeting.dto.DocResultDto
;
import
com.cmeeting.dto.UserDTO
;
import
com.cmeeting.email.EmailSender
;
import
com.cmeeting.exception.RobotBaseException
;
import
com.cmeeting.log.service.ProcessLogService
;
import
com.cmeeting.mapper.primary.MeetingInfoMapper
;
import
com.cmeeting.mapper.primary.MeetingRecordTemplateMapper
;
...
...
@@ -86,23 +88,19 @@ public class FileProcessTask {
private
int
retryCount
=
0
;
private
static
final
int
MAX_RETRY
=
3
;
private
String
tencentAppId
;
private
String
tencentSdkId
;
private
String
tencentSecretId
;
private
String
tencentSecretKey
;
private
String
tencentAdminUserId
;
private
String
llmApiAddr
;
private
Boolean
finalRetry
;
//表示是兜底重试机制
private
MeetingInfoMapper
meetingInfoMapper
;
private
MinioUtils
minioUtils
;
private
RedisUtils
redisUtils
;
private
EmailSender
emailSender
;
private
MeetingRecordTemplateMapper
meetingRecordTemplateMapper
;
private
ProcessLogService
processLogService
;
//获取模板授权的人员
private
List
<
UserDTO
.
TemplateAuthorizedUserDTO
>
authorizedUsers
;
//腾会id企微id对应关系
private
Map
<
String
,
String
>
tidWidRelations
;
private
Map
<
String
,
String
>
tidWidRelations
;
private
UserAdminConfig
userAdminConfig
;
//获取到的token
private
String
adminToken
;
...
...
@@ -112,16 +110,17 @@ public class FileProcessTask {
// 实际处理逻辑
public
void
process
()
{
// TODO 根据meeting和subMeetingId上锁
// boolean getKey = redisUtils.setnx("meeting_" + meetingId + subMeetingId, meetingId, 200000);
// while (getKey) {
// Thread.sleep(1000);
// }
boolean
isSuccess
=
false
;
while
(
retryCount
<=
MAX_RETRY
&&
!
isSuccess
)
{
Client
client
=
new
Client
.
Builder
()
.
withAppId
(
tencentAppId
).
withSdkId
(
tencentSdkId
)
.
withSecret
(
tencentSecretId
,
tencentSecretKey
)
.
build
();
try
{
//已保存的会议信息
MeetingInfo
meetingInfo
=
meetingInfoMapper
.
selectOne
(
new
LambdaQueryWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
));
String
meetingDate
=
meetingInfo
.
getStartTime
().
toLocalDate
().
format
(
DateTimeFormatter
.
ISO_LOCAL_DATE
);
//下面再查一遍会议信息的意义是,为了获取会议中的子会议Id,如果是周期会议,需要保存下来(重要)
...
...
@@ -191,136 +190,63 @@ public class FileProcessTask {
// }
// 获取参会成员明细
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetRequest
participantsRequest
=
new
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetRequest
.
Builder
(
meetingId
).
subMeetingId
(
subMeetingId
).
operatorId
(
tencentAdminUserId
).
operatorIdType
(
"1"
).
build
();
AuthenticatorBuilder
<
JWTAuthenticator
>
participantsAuthenticatorBuilder
=
new
JWTAuthenticator
.
Builder
()
.
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
));
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetResponse
participantsResponse
=
client
.
meetings
().
v1MeetingsMeetingIdParticipantsGet
(
participantsRequest
,
participantsAuthenticatorBuilder
);
V1MeetingsMeetingIdParticipantsGet200Response
participantsData
=
participantsResponse
.
getData
();
V1MeetingsMeetingIdParticipantsGet200Response
participantsData
=
TencentMeetingApiUtil
.
ApiV1MeetingsMeetingIdParticipantsGetRequest
(
meetingId
,
subMeetingId
);
if
(
participantsData
==
null
)
{
throw
new
RobotBaseException
(
"获取参会成员明细失败, meetingId: "
+
meetingId
);
}
List
<
V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner
>
participants
=
participantsData
.
getParticipants
();
String
participantNames
=
participants
.
stream
().
map
(
item
->
new
String
(
Base64
.
getDecoder
().
decode
(
item
.
getUserName
()))).
distinct
().
collect
(
Collectors
.
joining
(
"、"
));
meetingInfo
.
setParticipantUsers
(
participantNames
);
//每场会议可能会分段录制,查出每个文件的转录记录后拼接
StringBu
ffer
recordTextBuffer
=
new
StringBuff
er
();
StringBu
ilder
recordTextBuffer
=
new
StringBuild
er
();
for
(
String
recordFileId
:
recordFileIdList
)
{
//查询录制转写详情
RecordsApi
.
ApiV1AddressesRecordFileIdGetRequest
addressRequest
=
new
RecordsApi
.
ApiV1AddressesRecordFileIdGetRequest
.
Builder
(
recordFileId
)
.
operatorId
(
tencentAdminUserId
)
.
operatorIdType
(
"1"
)
.
build
();
RecordsApi
.
ApiV1AddressesRecordFileIdGetResponse
addressResponse
=
client
.
records
().
v1AddressesRecordFileIdGet
(
addressRequest
,
new
JWTAuthenticator
.
Builder
().
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
)));
// 处理响应
if
(
addressResponse
!=
null
&&
addressResponse
.
getData
()
!=
null
)
{
log
.
info
(
"Successfully got address for record file {}"
,
recordFileId
);
V1AddressesRecordFileIdGet200Response
addressData
=
addressResponse
.
getData
();
// 获取AI会议转录文件
List
<
V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner
>
transcripts
=
addressData
.
getAiMeetingTranscripts
();
if
(
transcripts
!=
null
&&
!
transcripts
.
isEmpty
())
{
log
.
info
(
"Found {} AI meeting transcripts for record file {}"
,
transcripts
.
size
(),
recordFileId
);
// 处理每个转录文件
for
(
V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner
transcript
:
transcripts
)
{
String
fileType
=
transcript
.
getFileType
();
String
downloadUrl
=
transcript
.
getDownloadAddress
();
if
(
"txt"
.
equalsIgnoreCase
(
fileType
))
{
log
.
info
(
"AI Transcript - Type: {}, URL: {}"
,
fileType
,
downloadUrl
);
// 1. 下载文件
byte
[]
fileData
=
downloadFile
(
downloadUrl
);
// 2. 将二进制文件转换为文本
String
recordTextContent
=
new
String
(
fileData
);
if
(
StringUtils
.
isNotEmpty
(
recordTextContent
.
replaceAll
(
"\\n"
,
""
).
trim
())){
recordTextBuffer
.
append
(
"\n\n"
);
recordTextBuffer
.
append
(
recordTextContent
);
}
}
}
}
else
{
log
.
info
(
"No AI meeting transcripts found for record file {}"
,
recordFileId
);
}
}
else
{
log
.
warn
(
"Empty response for record file: {}"
,
recordFileId
);
}
recordTextBuffer
.
append
(
TencentMeetingApiUtil
.
ApiV1AddressesRecordFileIdGetRequest
(
recordFileId
));
}
if
(
StringUtils
.
isEmpty
(
recordTextBuffer
.
toString
().
replaceAll
(
"\\n"
,
""
).
trim
()))
{
log
.
info
(
"获取的转录文本为空,跳过纪要生成,meetingId:{},fileRecordId:{}"
,
meetingId
,
recordFileIdList
.
toString
());
processLogService
.
log
(
meetingId
,
subMeetingId
,
"获取的转录文本为空,跳过纪要生成"
);
if
(
StringUtils
.
isEmpty
(
recordTextBuffer
.
toString
().
replaceAll
(
"\\n"
,
""
).
trim
()))
{
log
.
info
(
"获取的转录文本为空,跳过纪要生成,meetingId:{},fileRecordId:{}"
,
meetingId
,
recordFileIdList
.
toString
());
processLogService
.
log
(
meetingId
,
subMeetingId
,
"获取的转录文本为空,跳过纪要生成"
);
throw
new
RuntimeException
(
"获取的转录文本为空,跳过纪要生成"
);
}
// 3. 处理文件 (调用Claude API等)
String
choiceTemplateType
=
choiceTemplateType
(
meetingInfo
.
getSubject
(),
recordTextBuffer
.
toString
());
log
.
info
(
"choiceTemplateType->{}"
,
choiceTemplateType
);
//获取系统模板
List
<
MeetingRecordTemplate
>
recordTemplateList
=
meetingRecordTemplateMapper
.
selectList
(
new
LambdaQueryWrapper
<
MeetingRecordTemplate
>().
eq
(
MeetingRecordTemplate:
:
getType
,
RecordTemplateConstant
.
TEMPLATE_TYPE_SYSTEM
)
.
eq
(
MeetingRecordTemplate:
:
getIsDel
,
Boolean
.
FALSE
).
isNotNull
(
MeetingRecordTemplate:
:
getTemplate
));
M
ap
<
Integer
,
List
<
String
>>
authorizedUserMap
=
authorizedUsers
.
stream
().
collect
(
Collectors
.
toMap
(
item
->
item
.
getRecordTemplateId
(),
item
->
item
.
getUserIdList
())
);
// TODO 1. 根据转录文件内容recordTextBuffer判断使用模板类型
String
choiceTemplateType
=
choiceTemplateType
(
meetingInfo
.
getSubject
(),
recordTextBuffer
.
toString
());
log
.
info
(
"choiceTemplateType->{}"
,
choiceTemplateType
);
// TODO 2. 获取这个会议需要使用的一个模板
// 2.1 封装一个方法:入参(a.工号/腾讯会议uid,b.choiceTemplateType);结果(一个模板MeetingRecordTemplate)
M
eetingRecordTemplate
template
=
new
MeetingRecordTemplate
(
);
List
<
EmailPush
.
Attachment
>
attachments
=
new
ArrayList
<>();
String
hostUid
=
meetingInfo
.
getHostUid
();
String
toUserCode
=
tidWidRelations
.
get
(
meetingInfo
.
getHostUid
());
if
(!
tidWidRelations
.
containsKey
(
hostUid
)){
log
.
info
(
"用户{}暂未关联企微信息,无法生成纪要文件"
,
hostUid
);
processLogService
.
log
(
meetingId
,
subMeetingId
,
"用户"
+
hostUid
+
"暂未关联企微信息,无法生成纪要文件"
);
continue
;
}
String
processedResult
=
null
;
for
(
MeetingRecordTemplate
template
:
recordTemplateList
)
{
//判断本次纪要有模板生成权限
if
(!
authorizedUserMap
.
containsKey
(
template
.
getId
())){
log
.
info
(
"模板{}暂未授权给任意对象"
,
template
.
getName
());
processLogService
.
log
(
meetingId
,
subMeetingId
,
"模板"
+
template
.
getName
()+
"暂未授权给任意对象"
);
continue
;
}
List
<
String
>
authorizedUserIds
=
authorizedUserMap
.
get
(
template
.
getId
());
if
(!
authorizedUserIds
.
contains
(
tidWidRelations
.
get
(
hostUid
))){
log
.
info
(
"用户{}暂无模板{}权限"
,
hostUid
,
template
.
getName
());
processLogService
.
log
(
meetingId
,
subMeetingId
,
"用户"
+
hostUid
+
"暂无模板"
+
template
.
getName
()+
"权限"
);
if
(!
tidWidRelations
.
containsKey
(
hostUid
))
{
log
.
info
(
"用户{}暂未关联企微信息,无法生成纪要文件"
,
hostUid
);
processLogService
.
log
(
meetingId
,
subMeetingId
,
"用户"
+
hostUid
+
"暂未关联企微信息,无法生成纪要文件"
);
continue
;
}
else
{
log
.
info
(
"用户{}允许应用模板{}"
,
hostUid
,
template
.
getName
());
processLogService
.
log
(
meetingId
,
subMeetingId
,
"用户"
+
hostUid
+
"允许应用模板"
+
template
.
getName
());
}
//暂时让所有模板共用一个提示词,两个模板输出同样的结果
if
(
StringUtils
.
isEmpty
(
processedResult
)){
processedResult
=
processWithClaude
(
recordTextBuffer
.
toString
(),
meetingDate
,
participantNames
,
template
.
getPrompt
());
}
String
minutesPath
=
saveResult
(
processedResult
,
recordTextBuffer
.
toString
().
getBytes
(
StandardCharsets
.
UTF_8
),
meetingInfo
,
toUserCode
,
template
);
try
(
InputStream
is
=
new
FileInputStream
(
minutesPath
)){
String
processedResult
=
processWithClaude
(
recordTextBuffer
.
toString
(),
meetingDate
,
participantNames
,
template
.
getPrompt
());
String
minutesPath
=
saveResult
(
processedResult
,
recordTextBuffer
.
toString
().
getBytes
(
StandardCharsets
.
UTF_8
),
meetingInfo
,
toUserCode
,
template
);
try
(
InputStream
is
=
new
FileInputStream
(
minutesPath
))
{
byte
[]
meetingMinutesBytes
=
IOUtils
.
toByteArray
(
is
);
EmailPush
.
Attachment
attachment
=
EmailPush
.
Attachment
.
builder
().
name
(
meetingInfo
.
getSubject
()+
"会议纪要_"
+
template
.
getName
()).
bytes
(
meetingMinutesBytes
).
build
();
EmailPush
.
Attachment
attachment
=
EmailPush
.
Attachment
.
builder
().
name
(
meetingInfo
.
getSubject
()
+
"会议纪要_"
+
template
.
getName
()).
bytes
(
meetingMinutesBytes
).
build
();
attachments
.
add
(
attachment
);
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
finally
{
}
finally
{
FileUtil
.
del
(
minutesPath
);
}
}
if
(
CollectionUtils
.
isEmpty
(
attachments
)){
log
.
info
(
"用户{}暂无任何模板权限,纪要生成失败"
,
hostUid
);
if
(
CollectionUtils
.
isEmpty
(
attachments
))
{
log
.
info
(
"用户{}暂无任何模板权限,纪要生成失败"
,
hostUid
);
isSuccess
=
false
;
continue
;
}
if
(!
tidWidRelations
.
containsKey
(
meetingInfo
.
getHostUid
()))
{
if
(!
tidWidRelations
.
containsKey
(
meetingInfo
.
getHostUid
()))
{
log
.
error
(
"邮件推送重试失败: 主持人对应关系未配置。meetingId {}"
,
meetingId
);
processLogService
.
log
(
meetingId
,
subMeetingId
,
"邮件推送重试失败: 主持人对应关系未配置。meetingId "
+
meetingId
);
processLogService
.
log
(
meetingId
,
subMeetingId
,
"邮件推送重试失败: 主持人对应关系未配置。meetingId "
+
meetingId
);
continue
;
}
EmailPush
emailPushBuilder
=
EmailPush
.
builder
()
.
toEmail
(
meetingInfo
.
getEmail
())
.
meetingId
(
meetingId
)
...
...
@@ -338,20 +264,20 @@ public class FileProcessTask {
StringWriter
sw
=
new
StringWriter
();
PrintWriter
pw
=
new
PrintWriter
(
sw
);
e
.
printStackTrace
(
pw
);
processLogService
.
log
(
meetingId
,
subMeetingId
,
sw
.
toString
());
processLogService
.
log
(
meetingId
,
subMeetingId
,
sw
.
toString
());
// 异常处理
retryCount
++;
if
(
retryCount
>
MAX_RETRY
)
{
log
.
error
(
"达到最大重试次数:meetingId {}"
,
meetingId
);
//如果是兜底重试,最终还是失败了,设置会议的重试状态为已重试
if
(
finalRetry
)
{
if
(
finalRetry
)
{
meetingInfoMapper
.
update
(
null
,
new
LambdaUpdateWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
)
.
set
(
MeetingInfo:
:
getGenerateRetry
,
Boolean
.
TRUE
));
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
)
.
set
(
MeetingInfo:
:
getGenerateRetry
,
Boolean
.
TRUE
));
}
}
else
{
}
else
{
// 指数退避
try
{
Thread
.
sleep
((
long
)
Math
.
pow
(
2
,
retryCount
)
*
1000
);
...
...
@@ -373,7 +299,7 @@ public class FileProcessTask {
Response
response
=
client
.
newCall
(
request
).
execute
();
if
(!
response
.
isSuccessful
())
throw
new
IOException
(
"Unexpected code "
+
response
);
return
response
.
body
().
bytes
();
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"下载文件失败"
,
e
);
}
}
...
...
@@ -403,25 +329,62 @@ public class FileProcessTask {
/**
* 提供会议转录文件和会议主题,判断会议类型
* @param subject
* @param textContent
*
* @param subject 会议主题
* @param transcript 转录文件内容
* @return
*/
private
String
choiceTemplateType
(
String
subject
,
String
textConten
t
)
{
private
String
choiceTemplateType
(
String
subject
,
String
transcrip
t
)
{
String
token
=
"AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO"
;
String
apiAddr
=
llmApiAddr
+
"/llm/sse-invoke"
;
String
model
=
"arn:aws:bedrock:us-east-1:491822380689:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0"
;
int
maxTokens
=
5000
;
String
prompt
=
"请先对以下会议转写记录进行简要总结(不超过200字),然后根据会议主题以及总结内容判断该会议最可能属于哪种类型:\\n\\n会议主题: {0}\\n会议转写记录: {1}\\n\\n第一步:请简要总结会议的主要内容和目的(不超过200字)。\\n\\n第二步:根据上述总结和会议主题,将会议分类为以下类型之一:\\n1. 项目沟通会 - 与具体项目进展、问题讨论相关的会议\\n2. 重要会议 - 高层决策、战略规划等重要会议\\n3. 启动会 - 项目启动、活动筹备等初始会议\\n4. 其他 - 不符合以上任何分类\\n\\n分类权重判断规则:\\n- 基础权重分配:会议内容总结(70%)、会议主题(30%)\\n- 会议主题权重动态调整:\\n * 如果会议主题包含明确分类关键词(如\\\"启动会\\\"、\\\"项目沟通\\\"、\\\"战略决策\\\"等),则会议主题权重提升至60%,总结内容权重调整为40%\\n * 如果会议主题过于简单或模糊(如仅包含\\\"沟通\\\"、\\\"讨论\\\"、\\\"会议\\\"等通用词),则会议主题权重降低至15%,总结内容权重提升至85%\\n- 一致性判断:如果会议主题和总结内容指向不同分类,优先采用内容总结的分类,除非会议主题非常明确且规范\\n\\n输出格式:\\n请只返回分类名称(如\\\"项目沟通会\\\"),不要包含其他内容(如生成的会议总结等)。"
;
// String prompt = "请先对以下会议转写记录进行简要总结(不超过200字),然后根据会议主题以及总结内容判断该会议最可能属于哪种类型:\\n\\n会议主题: {0}\\n会议转写记录: {1}\\n\\n第一步:请简要总结会议的主要内容和目的(不超过200字)。\\n\\n第二步:根据上述总结和会议主题,将会议分类为以下类型之一:\\n1. 项目沟通会 - 与具体项目进展、问题讨论相关的会议\\n2. 重要会议 - 高层决策、战略规划等重要会议\\n3. 启动会 - 项目启动、活动筹备等初始会议\\n4. 其他 - 不符合以上任何分类\\n\\n分类权重判断规则:\\n- 基础权重分配:会议内容总结(70%)、会议主题(30%)\\n- 会议主题权重动态调整:\\n * 如果会议主题包含明确分类关键词(如\\\"启动会\\\"、\\\"项目沟通\\\"、\\\"战略决策\\\"等),则会议主题权重提升至60%,总结内容权重调整为40%\\n * 如果会议主题过于简单或模糊(如仅包含\\\"沟通\\\"、\\\"讨论\\\"、\\\"会议\\\"等通用词),则会议主题权重降低至15%,总结内容权重提升至85%\\n- 一致性判断:如果会议主题和总结内容指向不同分类,优先采用内容总结的分类,除非会议主题非常明确且规范\\n\\n输出格式:\\n请只返回分类名称(如\\\"项目沟通会\\\"),不要包含其他内容(如生成的会议总结等)。";
String
prompt
=
"## 角色定义\n"
+
"你是一位会议分类师,擅长对各种类型会议进行分类。\n"
+
"\n"
+
"请对以下会议转写记录进行全面而精炼的总结(尽量控制在500字以内,字数可以根据会议转写记录长度适当进行调整),然后根据会议主题以及总结内容判断该会议属于的类型:\n"
+
"\n"
+
"会议主题: {subject}\n"
+
"会议转写记录: {transcript}\n"
+
"\n"
+
"识别会议类型步骤如下:\n"
+
"第一步:识别会议名称/标题(如:项目例会,数据问题讨论);如果识别不出来,标注“暂无”;\n"
+
"第二步:根据识别出来的会议名称进行分类(如:数据问题讨论,讨论会)。如果会议名称如:快速会议,张三预定的会议,分类为“其他”。如果是暂无,则进入第三步:\n"
+
"第三步:总结会议的主要内容和目的(尽量控制在500字以内,字数可以根据会议转写记录长度适当进行调整)。确保涵盖所有关键讨论内容、重要决策和行动项,捕捉所有实质性内容。摘要应当完整反映会议的核心目的,不遗漏重要信息。\n"
+
"\n"
+
"第三步:根据上述内容和目的总结,将会议分类为以下类型之一:\n"
+
"1. 项目例会 - 会议有周期标签。要讨论很多件事情,每件事情讨论时间很短。\n"
+
"2. 启动会 - 项目启动、活动筹备等初始会议,各方代表发言。\n"
+
"3. 讨论会 - 只针对一到两个事件针对性讨论,可能是讨论方案。\n"
+
"4. 其他 - 不符合上面任意一类。\n"
+
"\n"
+
"\n"
+
"输出格式:\n"
+
"{{\n"
+
" \"classification\": 类型\n"
+
"}}\n"
+
"\n"
+
"注意:\n"
+
"1.请严格按照输出格式以JSON格式返回数据,所有字段都必须包含。\n"
+
"2.输出必须是严格有效的JSON格式,可以直接被解析。不要带有json标记,不要包含其他内容。"
;
//占位符信息替换
String
formatPrompt
=
formatMessage
(
prompt
,
subject
,
t
extConten
t
);
String
formatPrompt
=
formatMessage
(
prompt
,
subject
,
t
ranscrip
t
);
List
<
Message
>
messages
=
new
ArrayList
<>();
ChatMessage
chatMessage
=
new
ChatMessage
(
ChatMessageRole
.
USER
.
value
(),
formatPrompt
);
messages
.
add
(
chatMessage
);
// 调用Claude API处理文件
String
ret
=
call_llm
(
apiAddr
,
model
,
token
,
messages
,
maxTokens
);
return
ret
;
String
response
=
call_llm
(
apiAddr
,
model
,
token
,
messages
,
maxTokens
);
JSONObject
object
=
JSONObject
.
parseObject
(
response
);
String
retStr
=
"通用会议"
;
if
(
object
!=
null
)
{
String
classification
=
object
.
getString
(
"classification"
);
if
(
StrUtil
.
isNotBlank
(
classification
))
{
retStr
=
classification
;
}
}
return
retStr
;
}
private
String
formatMessage
(
String
pattern
,
Object
...
args
)
{
...
...
@@ -430,13 +393,14 @@ public class FileProcessTask {
/**
* 大模型生成纪要xml
*
* @param textContent 转录文件
* @param meetingDate 会议日期
* @param participantNames 参会人员
* @param prompt 提示词
* @return
*/
private
String
processWithClaude
(
String
textContent
,
String
meetingDate
,
String
participantNames
,
String
prompt
)
{
private
String
processWithClaude
(
String
textContent
,
String
meetingDate
,
String
participantNames
,
String
prompt
)
{
// //将文件传送给大模型处理
// String token = "AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO";
// String apiAddr = llmApiAddr + "/llm/sse-invoke";
...
...
@@ -461,20 +425,21 @@ public class FileProcessTask {
llmApiAddr
+
"/llm/sse-invoke"
,
"Bearer AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO"
,
20000
);
LLMResult
llmResult
=
MeetingProcess
.
processMeeting
(
prompt
,
textContent
,
meetingDate
,
participantNames
,
baseLLM
,
new
ArrayList
<>());
LLMResult
llmResult
=
MeetingProcess
.
processMeeting
(
prompt
,
textContent
,
meetingDate
,
participantNames
,
baseLLM
,
new
ArrayList
<>());
// DebugOutputTool.println(llmResult.respond);
return
llmResult
.
respond
;
}
/**
* 保存会议纪要相关的文件
*
* @param content 大模型返回的不规则xml
* @param recordData 转录文本
* @param meetingInfo 会议对象
* @param toUserCode 人员工号
* @param meetingRecordTemplate 模板信息
*/
private
String
saveResult
(
String
content
,
byte
[]
recordData
,
MeetingInfo
meetingInfo
,
String
toUserCode
,
MeetingRecordTemplate
meetingRecordTemplate
)
{
private
String
saveResult
(
String
content
,
byte
[]
recordData
,
MeetingInfo
meetingInfo
,
String
toUserCode
,
MeetingRecordTemplate
meetingRecordTemplate
)
{
String
meetingName
;
//转录文件临时存储路径
// String recordContentPath = meetingId + "-recordContent-" + IdUtil.fastSimpleUUID() + ".txt";
...
...
@@ -508,17 +473,17 @@ public class FileProcessTask {
});
DocResultDto
docResultDto
=
docResultDtoList
.
get
(
0
);
String
previewPath
=
docResultDto
.
getPreviewPath
();
recordContentPath
=
previewPath
.
replaceAll
(
fileDownloadPath
,
""
);
recordContentPath
=
previewPath
.
replaceAll
(
fileDownloadPath
,
""
);
meetingInfo
.
setTransDocId
(
docResultDto
.
getId
());
}
else
{
processLogService
.
log
(
meetingId
,
subMeetingId
,
"填充会议纪要失败,上传转录文件到向量知识库失败"
);
}
else
{
processLogService
.
log
(
meetingId
,
subMeetingId
,
"填充会议纪要失败,上传转录文件到向量知识库失败"
);
throw
new
RuntimeException
(
"填充会议纪要失败"
);
}
//去除内容中除了xml内容以外其他的信息,格式化xml
String
xml
=
extractXmlFromMarkdown
(
content
);
// minioUtils.upload(recordContentPath,recordData);
minioUtils
.
upload
(
recordXmlPath
,
xml
.
getBytes
(
StandardCharsets
.
UTF_8
));
minioUtils
.
upload
(
recordXmlPath
,
xml
.
getBytes
(
StandardCharsets
.
UTF_8
));
//将xml格式的内容转换为map,用于填充模板
Map
<
String
,
Object
>
dataModel
=
convertXmlToMap
(
xml
);
//判断会议名称关键词,如果是用户自己定义的主题,不做修改
...
...
@@ -527,23 +492,23 @@ public class FileProcessTask {
// 3**的周期会议
// 4**预定的网络研讨会
String
[]
keywords
=
{
"预定的会议"
,
"的快速会议"
,
"的周期会议"
,
"预定的网络研讨会"
};
boolean
hostCustomSubject
=
Arrays
.
stream
(
keywords
).
noneMatch
(
item
->
subject
.
contains
(
item
));
if
(
hostCustomSubject
)
{
boolean
hostCustomSubject
=
Arrays
.
stream
(
keywords
).
noneMatch
(
item
->
subject
.
contains
(
item
));
if
(
hostCustomSubject
)
{
meetingName
=
subject
;
dataModel
.
put
(
"meeting_name"
,
subject
);
}
else
{
dataModel
.
put
(
"meeting_name"
,
subject
);
}
else
{
meetingName
=
dataModel
.
get
(
"meeting_name"
)
!=
null
?
String
.
valueOf
(
dataModel
.
get
(
"meeting_name"
))
:
subject
;
meetingInfo
.
setSubject
(
meetingName
);
}
meetingMinutesFileName
=
meetingName
+
"_"
+
meetingRecordTemplate
.
getName
();
//追加参会人员信息
Map
<
String
,
Object
>
participantsMap
=
new
ConcurrentHashMap
<>();
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_date"
,
meetingDate
);
participantsMap
.
put
(
"meeting_location"
,
"线上腾讯会议"
);
participantsMap
.
put
(
"meeting_participants"
,
meetingInfo
.
getParticipantUsers
());
participantsMap
.
put
(
"meeting_host"
,
meetingInfo
.
getHost
());
participantsMap
.
put
(
"meeting_host"
,
meetingInfo
.
getHost
());
dataModel
.
putAll
(
participantsMap
);
XWPFTemplate
template
;
...
...
@@ -554,27 +519,27 @@ public class FileProcessTask {
}
meetingMinutesPath
=
savePath
+
meetingMinutesFileName
+
".docx"
;
template
.
writeAndClose
(
new
FileOutputStream
(
meetingMinutesPath
));
processLogService
.
log
(
meetingId
,
subMeetingId
,
"填充会议纪要成功"
);
processLogService
.
log
(
meetingId
,
subMeetingId
,
"填充会议纪要成功"
);
}
catch
(
Exception
e
)
{
log
.
error
(
"填充会议纪要失败: {}"
,
e
.
getMessage
(),
e
);
StringWriter
sw
=
new
StringWriter
();
PrintWriter
pw
=
new
PrintWriter
(
sw
);
e
.
printStackTrace
(
pw
);
processLogService
.
log
(
meetingId
,
subMeetingId
,
"填充会议纪要失败"
+
sw
.
toString
());
processLogService
.
log
(
meetingId
,
subMeetingId
,
"填充会议纪要失败"
+
sw
.
toString
());
throw
new
RuntimeException
(
"填充会议纪要失败"
);
}
meetingInfoMapper
.
update
(
meetingInfo
,
new
LambdaUpdateWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
)
.
set
(
MeetingInfo:
:
getRecordContent
,
recordContentPath
)
.
set
(
MeetingInfo:
:
getRecordXml
,
recordXmlPath
)
.
set
(
MeetingInfo:
:
getParticipantUsers
,
meetingInfo
.
getParticipantUsers
())
.
set
(
MeetingInfo:
:
getIsGenerated
,
Boolean
.
TRUE
)
.
set
(
MeetingInfo:
:
getTemplateId
,
meetingRecordTemplate
.
getId
())
.
set
(
MeetingInfo:
:
getTransDocId
,
meetingInfo
.
getTransDocId
())
.
set
(
MeetingInfo:
:
getSubject
,
meetingInfo
.
getSubject
())
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
)
.
set
(
MeetingInfo:
:
getRecordContent
,
recordContentPath
)
.
set
(
MeetingInfo:
:
getRecordXml
,
recordXmlPath
)
.
set
(
MeetingInfo:
:
getParticipantUsers
,
meetingInfo
.
getParticipantUsers
())
.
set
(
MeetingInfo:
:
getIsGenerated
,
Boolean
.
TRUE
)
.
set
(
MeetingInfo:
:
getTemplateId
,
meetingRecordTemplate
.
getId
())
.
set
(
MeetingInfo:
:
getTransDocId
,
meetingInfo
.
getTransDocId
())
.
set
(
MeetingInfo:
:
getSubject
,
meetingInfo
.
getSubject
())
);
meetingInfo
.
setRecordContent
(
recordContentPath
);
meetingInfo
.
setRecordXml
(
recordXmlPath
);
...
...
@@ -592,17 +557,17 @@ public class FileProcessTask {
StringWriter
sw
=
new
StringWriter
();
PrintWriter
pw
=
new
PrintWriter
(
sw
);
e
.
printStackTrace
(
pw
);
processLogService
.
log
(
meetingId
,
subMeetingId
,
"【邮件推送】:"
+
sw
.
toString
());
processLogService
.
log
(
meetingId
,
subMeetingId
,
"【邮件推送】:"
+
sw
.
toString
());
throw
new
RuntimeException
(
e
);
}
if
(
isPushed
)
processLogService
.
log
(
meetingId
,
subMeetingId
,
"用户允许邮件推送,推送邮件至"
+
emailPushBuilder
.
getToEmail
());
if
(
isPushed
)
processLogService
.
log
(
meetingId
,
subMeetingId
,
"用户允许邮件推送,推送邮件至"
+
emailPushBuilder
.
getToEmail
());
meetingInfoMapper
.
update
(
null
,
new
LambdaUpdateWrapper
<
MeetingInfo
>()
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
)
.
set
(
MeetingInfo:
:
getIsPushed
,
isPushed
)
.
eq
(
MeetingInfo:
:
getMeetingId
,
meetingId
)
.
eq
(
subMeetingId
!=
null
,
MeetingInfo:
:
getSubMeetingId
,
subMeetingId
)
.
set
(
MeetingInfo:
:
getIsPushed
,
isPushed
)
);
}
...
...
@@ -615,14 +580,14 @@ public class FileProcessTask {
// 正确获取节点和属性的方式
List
<
Map
>
list
=
new
ArrayList
<>();
Iterator
<
String
>
iterator
=
rootNode
.
fieldNames
();
while
(
iterator
.
hasNext
()){
while
(
iterator
.
hasNext
())
{
String
tagName
=
iterator
.
next
();
JsonNode
subNode
=
rootNode
.
path
(
tagName
);
String
displayName
=
subNode
.
path
(
"label"
).
asText
();
String
content
=
subNode
.
path
(
""
).
asText
();
list
.
add
(
new
HashMap
(){{
put
(
"key"
,
tagName
+
"/"
+
displayName
);
put
(
"value"
,
content
);
list
.
add
(
new
HashMap
()
{{
put
(
"key"
,
tagName
+
"/"
+
displayName
);
put
(
"value"
,
content
);
}});
}
// 构建JSON对象
...
...
@@ -662,12 +627,13 @@ public class FileProcessTask {
StringWriter
sw
=
new
StringWriter
();
PrintWriter
pw
=
new
PrintWriter
(
sw
);
e
.
printStackTrace
(
pw
);
processLogService
.
log
(
meetingId
,
subMeetingId
,
"【大模型处理异常】:"
+
sw
.
toString
());
processLogService
.
log
(
meetingId
,
subMeetingId
,
"【大模型处理异常】:"
+
sw
.
toString
());
throw
new
RuntimeException
(
e
);
}
service
.
shutdownExecutor
();
return
stringBuilder
.
toString
();
}
public
static
Map
<
String
,
Object
>
convertXmlToMap
(
String
xml
)
throws
Exception
{
XmlMapper
xmlMapper
=
new
XmlMapper
();
...
...
@@ -677,12 +643,12 @@ public class FileProcessTask {
Map
<?,
?>
xmlMap
=
xmlMapper
.
readValue
(
xml
,
Map
.
class
);
// 转换为更标准的 Map<String, Object>
Map
<
String
,
Object
>
map
=
objectMapper
.
convertValue
(
xmlMap
,
Map
.
class
);
Map
<
String
,
Object
>
map
=
objectMapper
.
convertValue
(
xmlMap
,
Map
.
class
);
//特殊处理map格式
for
(
Map
.
Entry
<
String
,
Object
>
entry
:
map
.
entrySet
())
{
Map
<
String
,
Object
>
value
=
(
Map
<
String
,
Object
>)
entry
.
getValue
();
Map
<
String
,
Object
>
value
=
(
Map
<
String
,
Object
>)
entry
.
getValue
();
//取出正确的value并设置
String
realValue
=
String
.
valueOf
(
value
.
get
(
""
)).
replaceAll
(
"^\\n+"
,
""
);
String
realValue
=
String
.
valueOf
(
value
.
get
(
""
)).
replaceAll
(
"^\\n+"
,
""
);
//内容段首移除换行,段末追加换行(会议名称结尾不换行
entry
.
setValue
(
realValue
.
endsWith
(
"\n"
)
||
"meeting_name"
.
equals
(
entry
.
getKey
())
||
"meeting_purpose"
.
equals
(
entry
.
getKey
())
?
realValue
:
realValue
+
"\n"
);
}
...
...
@@ -691,6 +657,7 @@ public class FileProcessTask {
/**
* markdown转xml
*
* @param markdown
* @return
*/
...
...
@@ -698,40 +665,35 @@ public class FileProcessTask {
StringBuffer
sb
;
try
{
int
start
=
markdown
.
indexOf
(
"<"
);
if
(
start
==
-
1
)
{
processLogService
.
log
(
meetingId
,
subMeetingId
,
"markdown转xml失败,未输出正确的xml格式,markdown内容:"
+
markdown
);
if
(
start
==
-
1
)
{
processLogService
.
log
(
meetingId
,
subMeetingId
,
"markdown转xml失败,未输出正确的xml格式,markdown内容:"
+
markdown
);
}
int
end
=
markdown
.
lastIndexOf
(
">"
)
+
1
;
sb
=
new
StringBuffer
();
sb
.
append
(
"<root>"
);
String
xml
=
markdown
.
substring
(
start
,
end
).
trim
().
replaceAll
(
"\n\n"
,
"\n"
);
String
xml
=
markdown
.
substring
(
start
,
end
).
trim
().
replaceAll
(
"\n\n"
,
"\n"
);
sb
.
append
(
xml
);
sb
.
append
(
"</root>"
);
}
catch
(
Exception
e
)
{
log
.
info
(
"markdown转xml,markdown->{}"
,
markdown
);
log
.
info
(
"markdown转xml,markdown->{}"
,
markdown
);
throw
new
RuntimeException
(
e
.
getMessage
());
}
return
sb
.
toString
();
}
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
,
MeetingRecordTemplateMapper
meetingRecordTemplateMapper
,
String
llmApiAddr
,
Boolean
finalRetry
,
ProcessLogService
processLogService
,
List
<
UserDTO
.
TemplateAuthorizedUserDTO
>
authorizedUsers
,
Map
<
String
,
String
>
tidWidRelations
,
UserAdminConfig
userAdminConfig
,
String
adminToken
,
String
applicationId
,
String
fileDownloadPath
,
String
permTenantId
)
{
public
FileProcessTask
(
List
<
String
>
recordFileIdList
,
String
meetingId
,
String
subMeetingId
,
String
savePath
,
Map
<
String
,
Object
>
metadata
,
MeetingInfoMapper
meetingInfoMapper
,
MinioUtils
minioUtils
,
RedisUtils
redisUtils
,
EmailSender
emailSender
,
MeetingRecordTemplateMapper
meetingRecordTemplateMapper
,
String
llmApiAddr
,
Boolean
finalRetry
,
ProcessLogService
processLogService
,
List
<
UserDTO
.
TemplateAuthorizedUserDTO
>
authorizedUsers
,
Map
<
String
,
String
>
tidWidRelations
,
UserAdminConfig
userAdminConfig
,
String
adminToken
,
String
applicationId
,
String
fileDownloadPath
,
String
permTenantId
)
{
this
.
recordFileIdList
=
recordFileIdList
;
this
.
savePath
=
savePath
;
this
.
metadata
=
metadata
;
this
.
tencentAppId
=
tencentAppId
;
this
.
tencentSdkId
=
tencentSdkId
;
this
.
tencentSecretId
=
tencentSecretId
;
this
.
tencentSecretKey
=
tencentSecretKey
;
this
.
tencentAdminUserId
=
tencentAdminUserId
;
this
.
meetingId
=
meetingId
;
this
.
subMeetingId
=
subMeetingId
;
this
.
meetingInfoMapper
=
meetingInfoMapper
;
this
.
minioUtils
=
minioUtils
;
this
.
redisUtils
=
redisUtils
;
this
.
emailSender
=
emailSender
;
this
.
meetingRecordTemplateMapper
=
meetingRecordTemplateMapper
;
this
.
llmApiAddr
=
llmApiAddr
;
...
...
src/main/java/com/cmeeting/pojo/MeetingInfo.java
浏览文件 @
0cc18764
...
...
@@ -89,6 +89,7 @@ public class MeetingInfo implements Serializable {
/**
* 推送邮件许可 为false不推送
*/
private
Boolean
emailGenerateAccess
;
private
Boolean
emailPushAccess
;
/**
* 是否推送邮件完成
...
...
src/main/java/com/cmeeting/service/FileProcessProducer.java
浏览文件 @
0cc18764
...
...
@@ -9,6 +9,7 @@ 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.util.RedisUtils
;
import
com.cmeeting.util.UserAdminConfig
;
import
com.cmeeting.util.UserAdminTokenUtil
;
import
com.cmeeting.vo.TencentMeetingVO
;
...
...
@@ -35,16 +36,6 @@ public class FileProcessProducer {
@Autowired
private
FileProcessCallbackHandler
callbackHandler
;
@Value
(
value
=
"${tencent.appId}"
)
private
String
tencentAppId
;
@Value
(
value
=
"${tencent.sdkId}"
)
private
String
tencentSdkId
;
@Value
(
value
=
"${tencent.secretId}"
)
private
String
tencentSecretId
;
@Value
(
value
=
"${tencent.secretKey}"
)
private
String
tencentSecretKey
;
@Value
(
value
=
"${tencent.admin.userId}"
)
private
String
tencentAdminUserId
;
@Value
(
value
=
"${llm.api-addr}"
)
private
String
llmApiAddr
;
@Value
(
value
=
"${tencent.base-save-path}"
)
...
...
@@ -60,6 +51,8 @@ public class FileProcessProducer {
@Resource
private
MinioUtils
minioUtils
;
@Resource
private
RedisUtils
redisUtils
;
@Resource
private
EmailSender
emailSender
;
@Resource
private
ProcessLogService
processLogService
;
...
...
@@ -71,13 +64,14 @@ public class FileProcessProducer {
/**
* 批量提交生成纪要任务
*
* @param recordFiles 转录文件信息
* @param authorizedUsers 模板授权的人员
* @param tidWidRelations 腾会id企微id对应关系
* @param finalRetry 是否为最终重试
* @param finalRetry
*/
public
void
submitBatchTasks
(
List
<
TencentMeetingVO
.
RecordFile
>
recordFiles
,
List
<
UserDTO
.
TemplateAuthorizedUserDTO
>
authorizedUsers
,
Map
<
String
,
String
>
tidWidRelations
,
Boolean
finalRetry
)
{
public
void
submitBatchTasks
(
List
<
TencentMeetingVO
.
RecordFile
>
recordFiles
,
List
<
UserDTO
.
TemplateAuthorizedUserDTO
>
authorizedUsers
,
Map
<
String
,
String
>
tidWidRelations
,
Boolean
finalRetry
)
{
List
<
Future
<?>>
futures
=
new
ArrayList
<>();
String
adminToken
=
UserAdminTokenUtil
.
getUserAdminToken
();
for
(
TencentMeetingVO
.
RecordFile
recordFile
:
recordFiles
)
{
...
...
@@ -88,13 +82,9 @@ public class FileProcessProducer {
recordFile
.
getSubMeetingId
(),
baseSavePath
,
Collections
.
emptyMap
(),
tencentAppId
,
tencentSdkId
,
tencentSecretId
,
tencentSecretKey
,
tencentAdminUserId
,
meetingInfoMapper
,
minioUtils
,
redisUtils
,
emailSender
,
meetingRecordTemplateMapper
,
llmApiAddr
,
...
...
@@ -123,7 +113,7 @@ public class FileProcessProducer {
}
// 批量提交邮箱推送重试任务
public
void
submitEmailPushTasks
(
List
<
TencentMeetingVO
.
RecordFile
>
recordFiles
,
Map
<
String
,
String
>
tidWidRelations
)
{
public
void
submitEmailPushTasks
(
List
<
TencentMeetingVO
.
RecordFile
>
recordFiles
,
Map
<
String
,
String
>
tidWidRelations
)
{
List
<
Future
<?>>
futures
=
new
ArrayList
<>();
for
(
TencentMeetingVO
.
RecordFile
recordFile
:
recordFiles
)
{
...
...
src/main/java/com/cmeeting/service/impl/RecordTemplatePermissionServiceImpl.java
浏览文件 @
0cc18764
...
...
@@ -302,7 +302,7 @@ public class RecordTemplatePermissionServiceImpl extends ServiceImpl<RecordTempl
* @return
*/
private
String
extractXmlFromMarkdown
(
String
markdown
)
{
StringBuffer
sb
;
StringBuffer
sb
=
null
;
try
{
int
start
=
markdown
.
indexOf
(
"<"
);
int
end
=
markdown
.
lastIndexOf
(
">"
)
+
1
;
...
...
@@ -313,7 +313,6 @@ public class RecordTemplatePermissionServiceImpl extends ServiceImpl<RecordTempl
sb
.
append
(
"</root>"
);
}
catch
(
Exception
e
)
{
log
.
info
(
"markdown转xml,markdown->{}"
,
markdown
);
throw
new
RuntimeException
(
e
.
getMessage
());
}
return
sb
.
toString
();
}
...
...
src/main/java/com/cmeeting/service/impl/TencentMeetingServiceImpl.java
浏览文件 @
0cc18764
...
...
@@ -51,7 +51,7 @@ import java.util.stream.Collectors;
@Service
@Slf4j
public
class
TencentMeetingServiceImpl
extends
ServiceImpl
<
TecentMeetingMapper
,
TencentMeetingUser
>
implements
TencentMeetingService
{
public
class
TencentMeetingServiceImpl
extends
ServiceImpl
<
TecentMeetingMapper
,
TencentMeetingUser
>
implements
TencentMeetingService
{
@Resource
private
TecentMeetingMapper
tecentMeetingMapper
;
@Resource
...
...
@@ -84,8 +84,6 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
private
String
permissionApplicationId
;
@Value
(
value
=
"${permission.tenantId}"
)
private
String
permissionTenantId
;
@Resource
private
TencentMeetingApiUtil
tencentMeetingApiUtil
;
@Override
public
void
batchInsert
(
List
<
TencentMeetingUser
>
users
)
{
...
...
@@ -96,7 +94,8 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
@Override
public
void
doUsers
()
{
// 获取到全部用户
List
<
TencentMeetingUser
>
users
=
fetchUsersInBatches
();;
List
<
TencentMeetingUser
>
users
=
fetchUsersInBatches
();
;
// 检查重名并设置标志
markDuplicateNamesTecent
(
users
);
// 批量插入数据库(分批次)
...
...
@@ -110,19 +109,15 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
/**
* 获取会议信息以及会议对应的转录文件信息
*
* @param accessUserIds 允许生成会议纪要的人员
* @param weComUserMap 企微可见范围内现有的人员
* @return
*/
@Override
public
List
<
TencentMeetingVO
.
RecordFile
>
getMeetingFiles
(
List
<
UserDTO
>
accessUserIds
,
Map
<
String
,
WeComUser
>
weComUserMap
)
{
Client
client
=
new
Client
.
Builder
()
.
withAppId
(
tencentAppId
).
withSdkId
(
tencentSdkId
)
.
withSecret
(
tencentSecretId
,
tencentSecretKey
)
.
build
();
public
List
<
TencentMeetingVO
.
RecordFile
>
getMeetingFiles
(
List
<
UserDTO
>
accessUserIds
,
Map
<
String
,
WeComUser
>
weComUserMap
)
{
List
<
TencentMeetingVO
.
RecordFile
>
meetingFiles
=
new
ArrayList
<>();
List
<
TencentMeetingVO
.
RecordFile
>
recordFileUrlList
=
new
ArrayList
<>();
//
List<TencentMeetingVO.RecordFile> recordFileUrlList = new ArrayList<>();
List
<
MeetingInfo
>
meetingSaveList
=
new
ArrayList
<>();
// 查询近searchDays天的会议录制列表
try
{
...
...
@@ -139,148 +134,120 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
List
<
TencentMeetingVO
.
SimpleMeetingInfo
>
meetingIds
=
meetingInfoMapper
.
getAllMeetingIds
();
List
<
TencentMeetingUser
>
meetingUsers
=
tecentMeetingMapper
.
getAlluser
();
Map
<
String
,
String
>
meetingMap
=
meetingUsers
.
stream
().
collect
(
Collectors
.
toMap
(
TencentMeetingUser:
:
getUserId
,
TencentMeetingUser:
:
getUserName
));
while
(
currentPage
.
intValue
()
<=
totalPage
){
while
(
currentPage
.
intValue
()
<=
totalPage
)
{
CorpRecordsVO
data
=
fetchMeetingRecords
(
tencentAdminUserId
,
1
,
startTime
,
endTime
,
currentPage
.
getAndIncrement
(),
20
);
//设置总页数
if
(
data
!=
null
&&
data
.
getRecordMeetings
()
!=
null
&&
!
data
.
getRecordMeetings
().
isEmpty
())
{
List
<
CorpRecordsVO
.
RecordMeeting
>
meetings
=
data
.
getRecordMeetings
();
for
(
CorpRecordsVO
.
RecordMeeting
meeting
:
meetings
)
{
//录制文件未转码完成,跳过 1:录制中 2:转码中 3:转码完成
if
(
meeting
.
getState
()
!=
3
)
{
if
(
meeting
.
getState
()
!=
3
)
{
continue
;
}
log
.
info
(
"【会议检索】转录文件的meetingId->{},recordFileId->{}"
,
meeting
.
getMeetingId
(),
meeting
.
getMeetingRecordId
());
log
.
info
(
"【会议检索】转录文件的meetingId->{},recordFileId->{}"
,
meeting
.
getMeetingId
(),
meeting
.
getMeetingRecordId
());
//查询会议详情
String
meetingId
=
meeting
.
getMeetingId
();
String
subMeetingId
=
null
;
LocalDateTime
mediaStartTime
=
LocalDateTime
.
ofInstant
(
Instant
.
ofEpochMilli
(
Long
.
valueOf
(
meeting
.
getMediaStartTime
())),
ZoneId
.
systemDefault
());
try
{
String
userid
=
meeting
.
getUserid
();
log
.
info
(
"【周期会议扫描】:查询用户的已结束会议列表...meetingCode->{},userId->{}"
,
meeting
.
getMeetingCode
(),
userid
);
log
.
info
(
"【周期会议扫描】:查询用户的已结束会议列表...meetingCode->{},userId->{}"
,
meeting
.
getMeetingCode
(),
userid
);
//获取子会议id
MeetingsApi
.
ApiV1HistoryMeetingsUseridGetRequest
historyMeetingRequest
=
new
MeetingsApi
.
ApiV1HistoryMeetingsUseridGetRequest
.
Builder
(
userid
)
.
pageSize
(
"20"
)
.
page
(
"1"
)
.
meetingCode
(
meeting
.
getMeetingCode
())
.
startTime
(
String
.
valueOf
(
mediaStartTime
.
toLocalDate
().
atStartOfDay
().
atZone
(
ZoneId
.
systemDefault
()).
toEpochSecond
()))
.
endTime
(
String
.
valueOf
(
mediaStartTime
.
toLocalDate
().
atStartOfDay
().
plusDays
(
1
).
atZone
(
ZoneId
.
systemDefault
()).
toEpochSecond
()))
.
build
();
MeetingsApi
.
ApiV1HistoryMeetingsUseridGetResponse
historyMeetingResponse
=
client
.
meetings
().
v1HistoryMeetingsUseridGet
(
historyMeetingRequest
,
new
JWTAuthenticator
.
Builder
()
.
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
()))).
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
)));
V1HistoryMeetingsUseridGet200Response
historyMeetingResponseData
=
historyMeetingResponse
.
getData
();
List
<
V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner
>
historyMeetingInfoList
=
historyMeetingResponseData
.
getMeetingInfoList
();
if
(
CollectionUtils
.
isEmpty
(
historyMeetingInfoList
)){
V1HistoryMeetingsUseridGet200Response
v1HistoryMeetingsUseridGet200Response
=
TencentMeetingApiUtil
.
ApiV1HistoryMeetingsUseridGetRequest
(
userid
,
meeting
.
getMeetingCode
(),
String
.
valueOf
(
mediaStartTime
.
toLocalDate
().
atStartOfDay
().
atZone
(
ZoneId
.
systemDefault
()).
toEpochSecond
()),
String
.
valueOf
(
mediaStartTime
.
toLocalDate
().
atStartOfDay
().
plusDays
(
1
).
atZone
(
ZoneId
.
systemDefault
()).
toEpochSecond
()));
if
(
v1HistoryMeetingsUseridGet200Response
==
null
)
{
continue
;
}
List
<
V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner
>
historyMeetingInfoList
=
v1HistoryMeetingsUseridGet200Response
.
getMeetingInfoList
();
if
(
CollectionUtils
.
isEmpty
(
historyMeetingInfoList
))
{
log
.
error
(
"会议未结束,获取子会议id信息失败"
);
continue
;
}
V1HistoryMeetingsUseridGet200ResponseMeetingInfoListInner
historyMeeting
=
historyMeetingInfoList
.
get
(
0
);
//如果是周期会议
if
(
historyMeeting
.
getMeetingType
()
==
1
)
{
if
(
historyMeeting
.
getMeetingType
()
==
1
)
{
subMeetingId
=
historyMeeting
.
getSubMeetingId
();
}
//如果数据库中已有相同会议id的记录,跳过同步
String
finalSubMeetingId
=
subMeetingId
;
if
(!
meetingIds
.
stream
().
anyMatch
(
item
->
item
.
getMeetingId
().
equals
(
meetingId
)
&&
Objects
.
equals
(
item
.
getSubMeetingId
(),
finalSubMeetingId
)))
{
log
.
info
(
"【会议检索】新的会议meetingId->{}"
,
meeting
.
getMeetingId
());
if
(
meetingIds
.
stream
().
noneMatch
(
item
->
item
.
getMeetingId
().
equals
(
meetingId
)
&&
Objects
.
equals
(
item
.
getSubMeetingId
(),
finalSubMeetingId
)))
{
log
.
info
(
"【会议检索】新的会议meetingId->{}"
,
meeting
.
getMeetingId
());
List
<
CorpRecordsVO
.
RecordFile
>
recordFiles
=
meeting
.
getRecordFiles
();
//按转录文件时间升序,便于后续的内容拼接
List
<
String
>
recordFileIdList
=
recordFiles
.
stream
().
sorted
(
Comparator
.
comparingLong
(
CorpRecordsVO
.
RecordFile
::
getRecordStartTime
))
.
map
(
CorpRecordsVO
.
RecordFile
::
getRecordFileId
).
collect
(
Collectors
.
toList
());
TencentMeetingVO
.
RecordFile
recordFileItem
=
TencentMeetingVO
.
RecordFile
.
builder
()
.
recordFileIdList
(
recordFileIdList
).
meetingId
(
meetingId
).
subMeetingId
(
subMeetingId
).
build
();
// TencentMeetingVO.RecordFile recordFileItem = TencentMeetingVO.RecordFile.builder()
// .recordFileIdList(recordFileIdList).meetingId(meetingId).subMeetingId(subMeetingId).build();
String
hostId
;
String
hostName
;
//优先使用会议列表中已有的主持人字段
if
(
StringUtils
.
isNotEmpty
(
meeting
.
getHostUserId
()))
{
if
(
StringUtils
.
isNotEmpty
(
meeting
.
getHostUserId
()))
{
hostId
=
meeting
.
getHostUserId
();
hostName
=
meetingMap
.
containsKey
(
hostId
)
?
meetingMap
.
get
(
hostId
)
:
null
;
hostName
=
meetingMap
.
getOrDefault
(
hostId
,
null
)
;
log
.
info
(
"从会议列表中成功获取到主持人信息"
);
}
else
{
}
else
{
//判断主持人是否存在,如果主持人未参会,是查不到主持人的
//如果主持人未参会,使用会议详情中的创建人作为主持人
MeetingsApi
.
ApiV1MeetingsMeetingIdGetRequest
meetingRequest
=
new
MeetingsApi
.
ApiV1MeetingsMeetingIdGetRequest
.
Builder
(
meetingId
)
.
operatorId
(
tencentAdminUserId
)
.
operatorIdType
(
"1"
)
.
instanceid
(
"0"
)
.
build
();
MeetingsApi
.
ApiV1MeetingsMeetingIdGetResponse
meetingResponse
=
client
.
meetings
().
v1MeetingsMeetingIdGet
(
meetingRequest
,
new
JWTAuthenticator
.
Builder
()
.
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
)));
V1MeetingsMeetingIdGet200Response
meetingResponseData
=
meetingResponse
.
getData
();
V1MeetingsMeetingIdGet200Response
meetingResponseData
=
TencentMeetingApiUtil
.
ApiV1MeetingsMeetingIdGetRequest
(
meetingId
);
if
(
meetingResponseData
==
null
)
{
continue
;
}
List
<
V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner
>
meetingInfoList
=
meetingResponseData
.
getMeetingInfoList
();
V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner
meetingInfo
=
meetingInfoList
.
get
(
0
);
//会议详情中有主持人信息
if
(!
CollectionUtils
.
isEmpty
(
meetingInfo
.
getCurrentHosts
()))
{
if
(!
CollectionUtils
.
isEmpty
(
meetingInfo
.
getCurrentHosts
()))
{
log
.
info
(
"尝试从会议详情中获取主持人信息"
);
hostId
=
meetingInfo
.
getCurrentHosts
().
get
(
0
).
getUserid
();
hostName
=
meetingMap
.
containsKey
(
hostId
)
?
meetingMap
.
get
(
hostId
)
:
null
;
// processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认会议创建人为主持人");
}
else
{
hostName
=
meetingMap
.
getOrDefault
(
hostId
,
null
);
}
else
{
log
.
info
(
"尝试从参会人员列表中获取主持人信息"
);
// 获取参会成员明细
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
();
// 用户角色
// 0:普通成员角色 1:创建者角色 2:主持人 3:创建者+主持人 4:游客 5:游客+主持人 6:联席主持人 7:创建者+联席主持人
// 主持人角色
List
<
Long
>
hostRoleList
=
Arrays
.
asList
(
2L
,
3L
,
5L
,
6L
,
7L
);
V1MeetingsMeetingIdParticipantsGet200Response
participantsData
=
TencentMeetingApiUtil
.
ApiV1MeetingsMeetingIdParticipantsGetRequest
(
meetingId
,
subMeetingId
);
if
(
participantsData
==
null
)
{
continue
;
}
List
<
V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner
>
participants
=
participantsData
.
getParticipants
();
Optional
<
V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner
>
host
=
participants
.
stream
().
filter
(
item
->
hostRoleList
.
contains
(
item
.
getUserRole
())).
findFirst
();
if
(
host
.
isPresent
())
{
if
(
host
.
isPresent
())
{
hostId
=
host
.
get
().
getUserid
();
hostName
=
new
String
(
Base64
.
getDecoder
().
decode
(
host
.
get
().
getUserName
()));
}
else
{
}
else
{
log
.
error
(
"未找到主持人,默认没有生成纪要权限"
);
// processLogService.log(meeting.getMeetingId(),subMeetingId,"未找到主持人,默认没有生成纪要权限");
continue
;
}
}
}
//判断是否有权限生成纪要
boolean
generateAccess
=
accessUserIds
.
stream
().
anyMatch
(
item
->
item
.
getTid
().
equals
(
hostId
));
if
(!
generateAccess
){
log
.
error
(
"【权限校验】主持人{}没有生成纪要权限,跳过生成"
,
hostId
);
// processLogService.log(meeting.getMeetingId(),subMeetingId,"【权限校验】主持人"+hostId+"没有生成纪要权限,跳过生成");
continue
;
}
log
.
info
(
"【权限校验】主持人{}允许生成纪要"
,
hostId
);
// processLogService.log(meeting.getMeetingId(),subMeetingId,"【权限校验】主持人"+hostId+"允许生成纪要");
boolean
generateAccess
;
boolean
emailPushAccess
;
// 1. 智能体需要授权
generateAccess
=
accessUserIds
.
stream
().
anyMatch
(
item
->
item
.
getTid
().
equals
(
hostId
));
log
.
info
(
"智能体是否授权: {}"
,
generateAccess
);
UserDTO
userDTO
=
accessUserIds
.
stream
().
filter
(
item
->
item
.
getTid
().
equals
(
hostId
)).
findFirst
().
get
();
String
email
=
userDTO
.
getEmail
();
Boolean
emailPushAccess
=
weComUserMap
.
containsKey
(
userDTO
.
getWid
())
?
weComUserMap
.
get
(
userDTO
.
getWid
()).
getEmailPushAccess
()
:
Boolean
.
FALSE
;
// TODO 要改
// 2. 腾讯会议和企业微信信息同步过并且绑定关系
// 3. 用户自己没有关闭会议纪要功能
// 满足以上所有则emailGenerateAccess为ture
generateAccess
=
weComUserMap
.
containsKey
(
userDTO
.
getWid
())
?
weComUserMap
.
get
(
userDTO
.
getWid
()).
getEmailPushAccess
()
:
Boolean
.
FALSE
;
// 是否推送邮箱
emailPushAccess
=
weComUserMap
.
containsKey
(
userDTO
.
getWid
())
?
weComUserMap
.
get
(
userDTO
.
getWid
()).
getEmailPushAccess
()
:
Boolean
.
FALSE
;
log
.
info
(
"推送邮箱: {}"
,
emailPushAccess
);
// 查询会议开始和结束时间
MeetingInfo
startAndEndTimeByMeetingId
=
t
encentMeetingApiUtil
.
getStartAndEndTimeByMeetingId
(
meetingId
);
MeetingInfo
startAndEndTimeByMeetingId
=
T
encentMeetingApiUtil
.
getStartAndEndTimeByMeetingId
(
meetingId
);
//会议基本信息保存
MeetingInfo
meetingItem
=
MeetingInfo
.
builder
().
meetingId
(
meetingId
).
meetingCode
(
meeting
.
getMeetingCode
())
.
subject
(
meeting
.
getSubject
())
.
startTime
(
startAndEndTimeByMeetingId
!=
null
?
startAndEndTimeByMeetingId
.
getStartTime
()
:
mediaStartTime
)
.
endTime
(
startAndEndTimeByMeetingId
!=
null
?
startAndEndTimeByMeetingId
.
getEndTime
()
:
null
)
.
isGenerated
(
Boolean
.
FALSE
).
emailPushAccess
(
emailPushAccess
).
isPushed
(
Boolean
.
FALSE
).
syncTime
(
LocalDateTime
.
now
())
.
isGenerated
(
Boolean
.
FALSE
).
emailGenerateAccess
(
generateAccess
)
.
emailPushAccess
(
emailPushAccess
).
isPushed
(
Boolean
.
FALSE
).
syncTime
(
LocalDateTime
.
now
())
.
subMeetingId
(
subMeetingId
).
generateRetry
(
Boolean
.
FALSE
).
pushRetry
(
Boolean
.
FALSE
)
.
host
(
hostName
)
.
hostUid
(
hostId
)
...
...
@@ -289,17 +256,16 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
.
recordFileId
(
String
.
join
(
","
,
recordFileIdList
))
.
email
(
email
)
.
build
();
recordFileUrlList
.
add
(
recordFileItem
);
//
recordFileUrlList.add(recordFileItem);
meetingSaveList
.
add
(
meetingItem
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
continue
;
log
.
error
(
e
.
getMessage
());
}
}
}
}
if
(
meetingSaveList
.
size
()
>
0
)
{
if
(
meetingSaveList
.
size
()
>
0
)
{
Map
<
String
,
List
<
MeetingInfo
>>
meetingSaveMap
=
meetingSaveList
.
stream
().
collect
(
Collectors
.
groupingBy
(
item
->
item
.
getMeetingId
()
+
"_"
+
(
item
.
getSubMeetingId
()
!=
null
?
item
.
getSubMeetingId
()
:
"null"
)));
...
...
@@ -321,7 +287,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
StringWriter
sw
=
new
StringWriter
();
PrintWriter
pw
=
new
PrintWriter
(
sw
);
e
.
printStackTrace
(
pw
);
processLogService
.
log
(
null
,
null
,
sw
.
toString
());
processLogService
.
log
(
null
,
null
,
sw
.
toString
());
throw
new
RuntimeException
(
e
.
getMessage
());
}
return
meetingFiles
;
...
...
@@ -329,12 +295,13 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
/**
* 获取有权限的人员
*
* @param userIdRelations
* @return
*/
@Override
public
List
<
UserDTO
>
getAccessUserIds
(
Map
<
String
,
String
>
userIdRelations
)
{
try
{
public
List
<
UserDTO
>
getAccessUserIds
(
Map
<
String
,
String
>
userIdRelations
)
{
try
{
//权限控制
List
<
CoreModulePermissions
>
auths
=
authMapper
.
getAuthByTargetId
(
permissionApplicationId
,
permissionTenantId
);
//授权的部门,type为1的为人员,为0表示部门
...
...
@@ -343,25 +310,25 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
for
(
CoreModulePermissions
authDept
:
authDepts
)
{
String
deptId
=
authDept
.
getRelId
();
String
tenantId
=
authDept
.
getTenantId
();
getDeptPath
(
deptPath
,
deptId
,
tenantId
);
getDeptPath
(
deptPath
,
deptId
,
tenantId
);
}
//已被授权部门下的userid
List
<
String
>
accessUserIds
=
!
CollectionUtils
.
isEmpty
(
deptPath
)
?
sysUserSyncMapper
.
getUsersByDept
(
deptPath
,
permissionTenantId
)
:
new
ArrayList
<>();
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
))
{
if
(!
CollectionUtils
.
isEmpty
(
accessUserIds
))
{
//查出人员邮箱+邮件推送许可
List
<
UserDTO
>
userEmailList
=
sysUserSyncMapper
.
getUserEmail
(
permissionTenantId
);
Map
<
String
,
UserDTO
>
userEmailMap
=
CollectionUtils
.
isEmpty
(
userEmailList
)
?
new
HashMap
<>()
:
userEmailList
.
stream
().
collect
(
Collectors
.
toMap
(
UserDTO:
:
getWid
,
Function
.
identity
(),(
existing
,
replacement
)
->
existing
));
:
userEmailList
.
stream
().
collect
(
Collectors
.
toMap
(
UserDTO:
:
getWid
,
Function
.
identity
(),
(
existing
,
replacement
)
->
existing
));
List
<
UserDTO
>
accessUsers
=
new
ArrayList
<>();
for
(
String
accessUserId
:
accessUserIds
)
{
if
(
userIdRelations
.
containsKey
(
accessUserId
))
{
if
(
userIdRelations
.
containsKey
(
accessUserId
))
{
UserDTO
accessUser
=
new
UserDTO
();
accessUser
.
setWid
(
accessUserId
);
accessUser
.
setTid
(
userIdRelations
.
get
(
accessUserId
));
if
(
userEmailMap
.
containsKey
(
accessUserId
))
{
if
(
userEmailMap
.
containsKey
(
accessUserId
))
{
UserDTO
emailMsg
=
userEmailMap
.
get
(
accessUserId
);
accessUser
.
setEmail
(
emailMsg
.
getEmail
());
accessUser
.
setEmailPushAccess
(
emailMsg
.
getEmailPushAccess
());
...
...
@@ -372,31 +339,32 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
return
accessUsers
;
}
return
new
ArrayList
<>();
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
StringWriter
sw
=
new
StringWriter
();
PrintWriter
pw
=
new
PrintWriter
(
sw
);
e
.
printStackTrace
(
pw
);
processLogService
.
log
(
null
,
null
,
sw
.
toString
());
processLogService
.
log
(
null
,
null
,
sw
.
toString
());
throw
new
RuntimeException
(
e
);
}
}
/**
* 获取部门的路径
*
* @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
;
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
))
{
if
(!
deptPath
.
contains
(
subDeptId
))
{
deptPath
.
add
(
subDeptId
);
getDeptPath
(
deptPath
,
subDeptId
,
tenantId
);
getDeptPath
(
deptPath
,
subDeptId
,
tenantId
);
}
}
...
...
@@ -404,6 +372,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
/**
* 拉取账户级的会议记录列表
*
* @param operatorId
* @param operatorIdType
* @param startTime
...
...
@@ -469,10 +438,11 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
/**
* 腾讯会议通过通讯录获取员工信息
*
* @return
*/
private
List
<
TencentMeetingUser
>
fetchUsersInBatches
()
{
if
(
redisUtils
.
hasKey
(
"TENCENT_USER_ARRAY"
))
{
if
(
redisUtils
.
hasKey
(
"TENCENT_USER_ARRAY"
))
{
return
(
List
<
TencentMeetingUser
>)
redisUtils
.
get
(
"TENCENT_USER_ARRAY"
);
}
//构造client客户端
...
...
@@ -553,7 +523,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
throw
new
RuntimeException
(
e
);
}
}
redisUtils
.
set
(
"TENCENT_USER_ARRAY"
,
resultList
);
redisUtils
.
set
(
"TENCENT_USER_ARRAY"
,
resultList
);
// 输出结果
System
.
out
.
printf
(
"\n所有用户获取完成,共获取 %d 个用户\n"
,
resultList
.
size
());
return
resultList
;
...
...
@@ -600,7 +570,7 @@ public class TencentMeetingServiceImpl extends ServiceImpl<TecentMeetingMapper,T
return
null
;
}
String
hostUserId
=
meetingInfo
.
getCurrentHosts
().
get
(
0
).
getUserid
();
log
.
info
(
"成功获取主持人userid: {}"
,
hostUserId
);
log
.
info
(
"成功获取主持人userid: {}"
,
hostUserId
);
return
hostUserId
;
}
catch
(
Exception
e
)
{
return
null
;
...
...
src/main/java/com/cmeeting/util/RedisUtils.java
浏览文件 @
0cc18764
...
...
@@ -141,6 +141,15 @@ public class RedisUtils {
}
}
public
boolean
setnx
(
String
key
,
Object
value
,
long
time
)
{
try
{
return
redisTemplate
.
opsForValue
().
setIfAbsent
(
key
,
value
,
time
,
TimeUnit
.
SECONDS
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 递增
*
...
...
src/main/java/com/cmeeting/util/TencentMeetingApiUtil.java
浏览文件 @
0cc18764
...
...
@@ -11,11 +11,15 @@ 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.
V1MeetingsMeetingIdGet200Response
;
import
com.tencentcloudapi.wemeet.service.
meetings.model.V1MeetingsMeetingIdGet200ResponseMeetingInfoListInner
;
import
com.tencentcloudapi.wemeet.service.
meetings.model.V1MeetingsMeetingIdParticipants
Get200Response
;
import
com.tencentcloudapi.wemeet.service.
meetings.model.V1MeetingsMeetingIdParticipantsGet200ResponseParticipan
tsInner
;
import
com.tencentcloudapi.wemeet.service.meetings.model.
*
;
import
com.tencentcloudapi.wemeet.service.
records.api.RecordsApi
;
import
com.tencentcloudapi.wemeet.service.
records.model.V1AddressesRecordFileId
Get200Response
;
import
com.tencentcloudapi.wemeet.service.
records.model.V1AddressesRecordFileIdGet200ResponseAiMeetingTranscrip
tsInner
;
import
lombok.extern.slf4j.Slf4j
;
import
okhttp3.OkHttpClient
;
import
okhttp3.Request
;
import
okhttp3.Response
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.http.client.methods.HttpGet
;
import
org.apache.http.impl.client.CloseableHttpClient
;
import
org.apache.http.impl.client.HttpClients
;
...
...
@@ -24,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value;
import
org.springframework.stereotype.Component
;
import
javax.annotation.PostConstruct
;
import
java.io.IOException
;
import
java.math.BigInteger
;
import
java.security.InvalidKeyException
;
import
java.security.NoSuchAlgorithmException
;
...
...
@@ -32,6 +37,7 @@ import java.time.Instant;
import
java.time.LocalDateTime
;
import
java.time.ZoneId
;
import
java.time.ZonedDateTime
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.Optional
;
import
java.util.Random
;
...
...
@@ -70,6 +76,7 @@ public class TencentMeetingApiUtil {
tencentAdminUserId
=
tmpAdminUserId
;
client
=
getClient
();
}
private
Client
getClient
()
{
if
(
client
==
null
)
{
synchronized
(
TencentMeetingApiUtil
.
class
)
{
...
...
@@ -88,6 +95,7 @@ public class TencentMeetingApiUtil {
/**
* 查询会议开始结束时间
* Api: /v1/meetings/{meeting_id}
*
* @param meetingId 腾讯会议id
* @return
*/
...
...
@@ -119,81 +127,159 @@ public class TencentMeetingApiUtil {
return
null
;
}
/**
* 查询用户已结束会议列表
*
* @param userId 用户id 必填
* @param meetingCode 会议码 非必填
* @param startTime 会议开始时间 非必填
* @param endTime 会议结束时间 非必填
* @return 已结束会议列表
* @throws ServiceException
* @throws ClientException
*/
public
static
V1HistoryMeetingsUseridGet200Response
ApiV1HistoryMeetingsUseridGetRequest
(
String
userId
,
String
meetingCode
,
String
startTime
,
String
endTime
)
{
MeetingsApi
.
ApiV1HistoryMeetingsUseridGetRequest
historyMeetingRequest
=
new
MeetingsApi
.
ApiV1HistoryMeetingsUseridGetRequest
.
Builder
(
userId
)
.
pageSize
(
"20"
)
.
page
(
"1"
)
.
meetingCode
(
meetingCode
)
.
startTime
(
startTime
)
.
endTime
(
endTime
)
.
build
();
MeetingsApi
.
ApiV1HistoryMeetingsUseridGetResponse
historyMeetingResponse
=
null
;
try
{
historyMeetingResponse
=
client
.
meetings
().
v1HistoryMeetingsUseridGet
(
historyMeetingRequest
,
new
JWTAuthenticator
.
Builder
()
.
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
()))).
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
)));
return
historyMeetingResponse
.
getData
();
}
catch
(
ClientException
|
ServiceException
e
)
{
log
.
error
(
"ApiV1HistoryMeetingsUseridGetRequest error: {}"
,
e
.
getMessage
());
}
return
null
;
}
private
void
getSomething
()
throws
Exception
{
int
total
=
100000000
;
int
count
=
0
;
ZonedDateTime
now
=
ZonedDateTime
.
now
();
long
startTime
=
now
.
minusDays
(
31
).
toEpochSecond
();
long
endTime
=
now
.
toEpochSecond
();
String
TENCENT_APPID
=
"210468336"
;
String
TENCENT_SDKID
=
"28790143843"
;
String
TENCENT_SECRETID
=
"0ks7u8cgQ8DGVtlYZeRA9TxZCjvUT3oL"
;
String
TENCENT_SECRETKEY
=
"gQU09rkJjiQfiGcUYdhiKq5Ol6LebXg4w7F7Ol0rwvvdv3Xy"
;
String
TENCENT_ADMIN_USERID
=
"woaJARCQAAftcvU6GGoOn66rdSZ4IrOA"
;
int
i
=
3
;
while
(
i
*
20
<
total
)
{
String
uri
=
String
.
format
(
"/v1/corp/records?start_time=%d&end_time=%d&page=%d&page_size=%d&operator_id=%s&operator_id_type=%d"
,
startTime
,
endTime
,
i
++,
20
,
"woaJARCQAAftcvU6GGoOn66rdSZ4IrOA"
,
1
);
String
httpMethod
=
"GET"
;
String
nonce
=
String
.
valueOf
(
new
Random
().
nextInt
(
100000
));
String
timestamp
=
String
.
valueOf
(
Instant
.
now
().
getEpochSecond
());
// 3. 生成签名
String
signature
=
SignatureUtil
.
generateSignature
(
TENCENT_SECRETID
,
TENCENT_SECRETKEY
,
httpMethod
,
nonce
,
timestamp
,
uri
,
""
);
// 4. 发送请求
try
(
CloseableHttpClient
httpClient
=
HttpClients
.
createDefault
())
{
HttpGet
request
=
new
HttpGet
(
"https://api.meeting.qq.com"
+
uri
);
request
.
setHeader
(
"X-TC-Key"
,
TENCENT_SECRETID
);
request
.
setHeader
(
"X-TC-Timestamp"
,
timestamp
);
request
.
setHeader
(
"X-TC-Nonce"
,
nonce
);
request
.
setHeader
(
"X-TC-Signature"
,
signature
);
request
.
setHeader
(
"AppId"
,
TENCENT_APPID
);
request
.
setHeader
(
"SdkId"
,
TENCENT_SDKID
);
/**
* 根据会议id查询会议详情
*
* @param meetingId
* @return
*/
public
static
V1MeetingsMeetingIdGet200Response
ApiV1MeetingsMeetingIdGetRequest
(
String
meetingId
)
{
MeetingsApi
.
ApiV1MeetingsMeetingIdGetRequest
meetingRequest
=
new
MeetingsApi
.
ApiV1MeetingsMeetingIdGetRequest
.
Builder
(
meetingId
)
.
operatorId
(
tencentAdminUserId
)
.
operatorIdType
(
"1"
)
.
instanceid
(
"0"
)
.
build
();
MeetingsApi
.
ApiV1MeetingsMeetingIdGetResponse
meetingResponse
=
null
;
try
{
meetingResponse
=
client
.
meetings
().
v1MeetingsMeetingIdGet
(
meetingRequest
,
new
JWTAuthenticator
.
Builder
()
.
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
)));
return
meetingResponse
.
getData
();
}
catch
(
ClientException
|
ServiceException
e
)
{
log
.
error
(
"ApiV1MeetingsMeetingIdGetRequest error: {}"
,
e
.
getMessage
());
}
return
null
;
}
// 5. 解析响应
String
response
=
EntityUtils
.
toString
(
httpClient
.
execute
(
request
).
getEntity
());
JsonObject
jsonResponse
=
JsonParser
.
parseString
(
response
).
getAsJsonObject
();
Gson
gson
=
new
GsonBuilder
()
.
setFieldNamingPolicy
(
FieldNamingPolicy
.
LOWER_CASE_WITH_UNDERSCORES
)
.
create
();
/**
* 获取参会成员明细
*
* @param meetingId 会议id
* @param subMeetingId 子会议id
* @return
*/
public
static
V1MeetingsMeetingIdParticipantsGet200Response
ApiV1MeetingsMeetingIdParticipantsGetRequest
(
String
meetingId
,
String
subMeetingId
)
{
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetRequest
participantsRequest
=
new
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetRequest
.
Builder
(
meetingId
).
subMeetingId
(
subMeetingId
).
operatorId
(
tencentAdminUserId
).
operatorIdType
(
"1"
).
build
();
AuthenticatorBuilder
<
JWTAuthenticator
>
participantsAuthenticatorBuilder
=
new
JWTAuthenticator
.
Builder
()
.
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
));
MeetingsApi
.
ApiV1MeetingsMeetingIdParticipantsGetResponse
participantsResponse
;
try
{
participantsResponse
=
client
.
meetings
().
v1MeetingsMeetingIdParticipantsGet
(
participantsRequest
,
participantsAuthenticatorBuilder
);
return
participantsResponse
.
getData
();
}
catch
(
ClientException
|
ServiceException
e
)
{
log
.
error
(
"ApiV1MeetingsMeetingIdParticipantsGetRequest error: {}"
,
e
.
getMessage
());
}
return
null
;
}
// 将 JsonObject 转换为实体类
CorpRecordsVO
corpRecords
=
gson
.
fromJson
(
jsonResponse
,
CorpRecordsVO
.
class
);
if
(
corpRecords
==
null
)
{
return
;
/**
* 获取转录文件内容
*
* @param recordFileId 转录文件id
* @return
*/
public
static
String
ApiV1AddressesRecordFileIdGetRequest
(
String
recordFileId
)
{
//查询录制转写详情
RecordsApi
.
ApiV1AddressesRecordFileIdGetRequest
addressRequest
=
new
RecordsApi
.
ApiV1AddressesRecordFileIdGetRequest
.
Builder
(
recordFileId
)
.
operatorId
(
tencentAdminUserId
)
.
operatorIdType
(
"1"
)
.
build
();
RecordsApi
.
ApiV1AddressesRecordFileIdGetResponse
addressResponse
;
try
{
addressResponse
=
client
.
records
().
v1AddressesRecordFileIdGet
(
addressRequest
,
new
JWTAuthenticator
.
Builder
().
nonce
(
BigInteger
.
valueOf
(
Math
.
abs
((
new
SecureRandom
()).
nextInt
())))
.
timestamp
(
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000L
)));
}
catch
(
ClientException
|
ServiceException
e
)
{
throw
new
RuntimeException
(
e
);
}
for
(
CorpRecordsVO
.
RecordMeeting
recordMeeting
:
corpRecords
.
getRecordMeetings
())
{
String
subject
=
recordMeeting
.
getSubject
();
if
(
subject
.
contains
(
"启动会"
))
{
count
++;
System
.
out
.
println
(
"会议名: "
+
subject
);
String
meetingRecordId
=
recordMeeting
.
getMeetingRecordId
();
System
.
out
.
println
(
"meetingRecordId: "
+
meetingRecordId
);
List
<
CorpRecordsVO
.
RecordFile
>
recordFiles
=
recordMeeting
.
getRecordFiles
();
if
(
CollUtil
.
isEmpty
(
recordFiles
))
{
continue
;
// 处理响应
if
(
addressResponse
!=
null
&&
addressResponse
.
getData
()
!=
null
)
{
log
.
info
(
"Successfully got address for record file {}"
,
recordFileId
);
V1AddressesRecordFileIdGet200Response
addressData
=
addressResponse
.
getData
();
// 获取AI会议转录文件
List
<
V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner
>
transcripts
=
addressData
.
getAiMeetingTranscripts
();
if
(
transcripts
!=
null
&&
!
transcripts
.
isEmpty
())
{
log
.
info
(
"Found {} AI meeting transcripts for record file {}"
,
transcripts
.
size
(),
recordFileId
);
// 处理每个转录文件
for
(
V1AddressesRecordFileIdGet200ResponseAiMeetingTranscriptsInner
transcript
:
transcripts
)
{
String
fileType
=
transcript
.
getFileType
();
String
downloadUrl
=
transcript
.
getDownloadAddress
();
if
(
"txt"
.
equalsIgnoreCase
(
fileType
))
{
log
.
info
(
"AI Transcript - Type: {}, URL: {}"
,
fileType
,
downloadUrl
);
// 1. 下载文件
byte
[]
fileData
=
downloadFile
(
downloadUrl
);
// 2. 将二进制文件转换为文本
String
recordTextContent
=
new
String
(
fileData
);
if
(
StringUtils
.
isNotEmpty
(
recordTextContent
.
replaceAll
(
"\\n"
,
""
).
trim
()))
{
return
"\n\n"
+
recordTextContent
;
}
System
.
out
.
print
(
"recordFileId: "
);
for
(
CorpRecordsVO
.
RecordFile
recordFile
:
recordFiles
)
{
String
recordFileId
=
recordFile
.
getRecordFileId
();
System
.
out
.
print
(
recordFileId
+
","
);
}
}
}
else
{
log
.
info
(
"No AI meeting transcripts found for record file {}"
,
recordFileId
);
}
if
(
count
>=
3
)
{
break
;
}
else
{
log
.
warn
(
"Empty response for record file: {}"
,
recordFileId
)
;
}
return
""
;
}
/**
* 根据url下载文件
*
* @param url
* @return
*/
private
static
byte
[]
downloadFile
(
String
url
)
{
// 实现文件下载逻辑
OkHttpClient
client
=
new
OkHttpClient
();
Request
request
=
new
Request
.
Builder
().
url
(
url
).
build
();
try
{
Response
response
=
client
.
newCall
(
request
).
execute
();
if
(!
response
.
isSuccessful
())
throw
new
IOException
(
"Unexpected code "
+
response
);
return
response
.
body
().
bytes
();
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"下载文件失败"
,
e
);
}
}
}
src/main/resources/application.yml
浏览文件 @
0cc18764
...
...
@@ -112,7 +112,7 @@ email:
smtp-host
:
${EMAIL_SMTP_HOST}
push-switch
:
true
#邮件推送总开关,高优先级
environment
:
test
#test推给本公司人员,prod推给用户
test-receiver
:
duanxincheng
@chatbot.cn
#用于测试的收方邮箱
test-receiver
:
hongdongbao
@chatbot.cn
#用于测试的收方邮箱
llm
:
api-addr
:
${LLM_API_ADDR}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论