@@ -36,6 +36,7 @@ import org.jeecg.modules.system.mapper.SysUserTenantMapper;
import org.jeecg.modules.system.service.ISysPermissionService ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.stereotype.Service ;
import org.springframework.transaction.annotation.Propagation ;
import org.springframework.transaction.annotation.Transactional ;
import java.util.ArrayList ;
@@ -64,6 +65,13 @@ public class SysImChatServiceImpl implements ISysImChatService {
private static final String MSG_IMAGE_PREVIEW = " [图片] " ;
private static final String MSG_BIZ_RECORD_PREVIEW = " [业务数据] " ;
private static final String IM_RECORD_QUERY_KEY = " imRecordId " ;
/** IM 工作通知公众号账号(审批等系统消息统一从此账号推送) */
private static final String WORK_NOTIFY_USERNAME = " im_work_notify " ;
private static final String WORK_NOTIFY_REALNAME = " 工作通知 " ;
private static final String WORK_NOTIFY_USER_ID = " 1995000000000000999 " ;
private static final String CONTACT_TYPE_WORK_NOTIFY = " work_notify " ;
private static final String CONTACT_TYPE_USER = " user " ;
private volatile String workNotifyUserIdCache ;
@Autowired
private ISysPermissionService sysPermissionService ;
@@ -107,7 +115,11 @@ public class SysImChatServiceImpl implements ISysImChatService {
if ( conversation ! = null ) {
return buildConversationVo ( conversation , userId , targetUserId ) ;
}
validateTenantChat ( userId , tenantId , orgCode , targetUserId ) ;
//update-begin---author:GHT ---date:20260610 for: 【IM审批通用化】工作通知公众号会话免同部门校验-----------
if ( ! isWorkNotifyUser ( targetUserId ) ) {
validateTenantChat ( userId , tenantId , orgCode , targetUserId ) ;
}
//update-end---author:GHT ---date:20260610 for: 【IM审批通用化】工作通知公众号会话免同部门校验-----------
//update-end---author:cursor ---date:20260528 for: 【IM聊天-OA】已有会话快速打开-----------
conversation = new SysImConversation ( ) ;
conversation . setConvType ( CONV_TYPE_SINGLE ) ;
@@ -439,6 +451,11 @@ public class SysImChatServiceImpl implements ISysImChatService {
throw new JeecgBootException ( " 消息内容不能为空 " ) ;
}
assertMember ( userId , dto . getConversationId ( ) ) ;
//update-begin---author:GHT ---date:20260610 for: 【IM审批通用化】工作通知公众号只读,禁止用户发消息-----------
if ( isWorkNotifyConversation ( dto . getConversationId ( ) ) ) {
throw new JeecgBootException ( " 工作通知为系统消息通道,不支持发送消息 " ) ;
}
//update-end---author:GHT ---date:20260610 for: 【IM审批通用化】工作通知公众号只读,禁止用户发消息-----------
Date now = new Date ( ) ;
SysImMessage message = new SysImMessage ( ) ;
message . setConversationId ( dto . getConversationId ( ) ) ;
@@ -480,8 +497,8 @@ public class SysImChatServiceImpl implements ISysImChatService {
if ( oConvertUtils . isEmpty ( fromUserId ) | | oConvertUtils . isEmpty ( toUserId ) | | oConvertUtils . isEmpty ( content ) ) {
return null ;
}
// 不给自己发送
if ( fromUserId . equals ( toUserId ) ) {
log . warn ( " IM系统消息跳过: 收发为同一人 toUserId={} " , toUserId ) ;
return null ;
}
Integer tenant = tenantId = = null ? 0 : tenantId ;
@@ -500,8 +517,17 @@ public class SysImChatServiceImpl implements ISysImChatService {
conversation . setCreateTime ( now ) ;
conversation . setUpdateTime ( now ) ;
conversationMapper . insert ( conversation ) ;
creat eMember( conversation . getId ( ) , fromUserId , now ) ;
createMember ( conversation . getId ( ) , toUserId , now ) ;
ensur eMember( conversation . getId ( ) , fromUserId , now ) ;
if ( ! fromUserId . equals ( toUserId ) ) {
ensureMember ( conversation . getId ( ) , toUserId , now ) ;
}
} else {
//update-begin---author:GHT ---date:20260610 for: 【IM审批通用化】已有会话补全缺失成员-----------
ensureMember ( conversation . getId ( ) , fromUserId , now ) ;
if ( ! fromUserId . equals ( toUserId ) ) {
ensureMember ( conversation . getId ( ) , toUserId , now ) ;
}
//update-end---author:GHT ---date:20260610 for: 【IM审批通用化】已有会话补全缺失成员-----------
}
// 写入消息
SysImMessage message = new SysImMessage ( ) ;
@@ -522,6 +548,22 @@ public class SysImChatServiceImpl implements ISysImChatService {
pushChatMessage ( conversation . getId ( ) , fromUserId , messageVo , conversation . getConvType ( ) ) ;
return messageVo ;
}
//update-begin---author:GHT ---date:20260610 for: 【IM审批通用化】审批待办统一由系统账号发给处理人-----------
@Override
@Transactional ( propagation = Propagation . REQUIRES_NEW , rollbackFor = Exception . class )
public SysImMessageVO sendApprovalHandlerMessage ( String toUserId , Integer tenantId , String content , String msgType ) {
if ( oConvertUtils . isEmpty ( toUserId ) | | oConvertUtils . isEmpty ( content ) ) {
return null ;
}
String fromUserId = ensureWorkNotifyUserId ( ) ;
if ( fromUserId . equals ( toUserId ) ) {
log . warn ( " 审批待办IM发送失败: 接收人不能是工作通知公众号 toUserId={} " , toUserId ) ;
return null ;
}
return sendSystemSingleMessage ( fromUserId , toUserId , tenantId , content , msgType ) ;
}
//update-end---author:GHT ---date:20260610 for: 【IM审批通用化】审批待办统一由系统账号发给处理人-----------
//update-end---author:GHT ---date:2026-05-29 for: 【QH-MES审批流设计】系统单聊消息(绕过同部门校验)-----
//update-begin---author:cursor ---date:20260528 for: 【IM聊天-OA】标记已读-----------
@@ -541,13 +583,19 @@ public class SysImChatServiceImpl implements ISysImChatService {
//update-begin---author:cursor ---date:20260528 for: 【IM聊天-OA】本部门成员列表( 含会话摘要) -----------
@Override
public List < SysImContactVO > listDeptMembers ( String userId , Integer tenantId , String orgCode , String keyword ) {
List < SysImContactVO > result = new ArrayList < > ( ) ;
//update-begin---author:GHT ---date:20260610 for: 【IM审批通用化】同事列表置顶工作通知公众号-----------
if ( matchWorkNotifyKeyword ( keyword ) ) {
result . add ( buildWorkNotifyContact ( userId , tenantId ) ) ;
}
String workNotifyId = ensureWorkNotifyUserId ( ) ;
String resolvedOrgCode = resolveOrgCode ( userId , tenantId , orgCode ) ;
if ( oConvertUtils . isEmpty ( resolvedOrgCode ) ) {
throw new JeecgBootException ( " 未获取到当前部门,请切换部门后重试 " ) ;
return result ;
}
List < SysUser > users = userDepartMapper . querySameDepartUserList ( resolvedOrgCode , userId , tenantId , keyword ) ;
if ( users = = null | | users . isEmpty ( ) ) {
return Collections . emptyList ( ) ;
return result ;
}
Map < String , SysImConversationVO > convMap = new HashMap < > ( 16 ) ;
if ( tenantId ! = null & & tenantId > 0 ) {
@@ -557,20 +605,26 @@ public class SysImChatServiceImpl implements ISysImChatService {
}
}
}
List < SysImContactVO > result = users . stream ( ) . map ( user - > {
SysImContactVO vo = toContactVo ( user ) ;
SysImConversationVO conv = convMap . get ( user . getId ( ) ) ;
if ( conv ! = null ) {
vo . setConversationId ( conv . getConversationId ( ) ) ;
vo . setLastContent ( conv . getLastContent ( ) ) ;
vo . setLastTime ( conv . getLastTime ( ) ) ;
vo . setUnreadCount ( conv . getUnreadCount ( ) ) ;
}
return vo ;
} ) . collect ( Collectors . toLis t ( ) ) ;
result . sort ( Comparator
List < SysImContactVO > colleagues = users . stream ( )
. filter ( user - > ! workNotifyId . equals ( user . getId ( ) ) & & ! WORK_NOTIFY_USERNAME . equals ( user . getUsername ( ) ) )
. map ( user - > {
SysImContactVO vo = toContactVo ( user ) ;
vo . setContactType ( CONTACT_TYPE_USER ) ;
SysImConversationVO conv = convMap . get( user . getId ( ) ) ;
if ( conv ! = null ) {
vo . setConversationId ( conv . getConversationId ( ) ) ;
vo . setLastContent ( conv . getLastContent ( ) ) ;
vo . setLastTime ( conv . getLastTime ( ) ) ;
vo . setUnreadCount ( conv . getUnreadCoun t ( ) ) ;
}
return vo ;
} )
. collect ( Collectors . toList ( ) ) ;
colleagues . sort ( Comparator
. comparing ( SysImContactVO : : getLastTime , Comparator . nullsLast ( Comparator . reverseOrder ( ) ) )
. thenComparing ( item - > oConvertUtils . getString ( item . getRealname ( ) , item . getUsername ( ) ) ) ) ;
result . addAll ( colleagues ) ;
//update-end---author:GHT ---date:20260610 for: 【IM审批通用化】同事列表置顶工作通知公众号-----------
return result ;
}
//update-end---author:cursor ---date:20260528 for: 【IM聊天-OA】本部门成员列表( 含会话摘要) -----------
@@ -583,9 +637,111 @@ public class SysImChatServiceImpl implements ISysImChatService {
vo . setAvatar ( user . getAvatar ( ) ) ;
vo . setOrgCodeTxt ( user . getOrgCodeTxt ( ) ) ;
vo . setUnreadCount ( 0 ) ;
vo . setContactType ( CONTACT_TYPE_USER ) ;
return vo ;
}
private SysImContactVO buildWorkNotifyContact ( String userId , Integer tenantId ) {
String workNotifyId = ensureWorkNotifyUserId ( ) ;
SysImContactVO vo = new SysImContactVO ( ) ;
vo . setId ( workNotifyId ) ;
vo . setUsername ( WORK_NOTIFY_USERNAME ) ;
vo . setRealname ( WORK_NOTIFY_REALNAME ) ;
vo . setUnreadCount ( 0 ) ;
vo . setContactType ( CONTACT_TYPE_WORK_NOTIFY ) ;
if ( tenantId ! = null & & tenantId > 0 ) {
SysImConversationVO conv = findSingleConversationSummary ( userId , tenantId , workNotifyId ) ;
if ( conv ! = null ) {
vo . setConversationId ( conv . getConversationId ( ) ) ;
vo . setLastContent ( conv . getLastContent ( ) ) ;
vo . setLastTime ( conv . getLastTime ( ) ) ;
vo . setUnreadCount ( conv . getUnreadCount ( ) ) ;
}
}
return vo ;
}
private SysImConversationVO findSingleConversationSummary ( String userId , Integer tenantId , String targetUserId ) {
String pairKey = buildPairKey ( userId , targetUserId ) ;
SysImConversation conversation = conversationMapper . selectOne ( new LambdaQueryWrapper < SysImConversation > ( )
. eq ( SysImConversation : : getTenantId , tenantId )
. eq ( SysImConversation : : getUserPairKey , pairKey ) ) ;
if ( conversation = = null ) {
return null ;
}
SysImConversationMember member = getMember ( userId , conversation . getId ( ) ) ;
SysImConversationVO vo = buildConversationVo ( conversation , userId , targetUserId ) ;
vo . setUnreadCount ( member = = null ? 0 : member . getUnreadCount ( ) ) ;
return vo ;
}
private boolean matchWorkNotifyKeyword ( String keyword ) {
if ( oConvertUtils . isEmpty ( keyword ) ) {
return true ;
}
String key = keyword . trim ( ) . toLowerCase ( ) ;
return WORK_NOTIFY_REALNAME . contains ( keyword . trim ( ) )
| | WORK_NOTIFY_REALNAME . toLowerCase ( ) . contains ( key )
| | WORK_NOTIFY_USERNAME . contains ( key )
| | key . contains ( " 工作 " )
| | key . contains ( " 通知 " ) ;
}
private String ensureWorkNotifyUserId ( ) {
if ( oConvertUtils . isNotEmpty ( workNotifyUserIdCache ) ) {
return workNotifyUserIdCache ;
}
synchronized ( this ) {
if ( oConvertUtils . isNotEmpty ( workNotifyUserIdCache ) ) {
return workNotifyUserIdCache ;
}
SysUser user = userMapper . selectOne ( new LambdaQueryWrapper < SysUser > ( )
. eq ( SysUser : : getUsername , WORK_NOTIFY_USERNAME )
. eq ( SysUser : : getDelFlag , 0 )
. last ( " LIMIT 1 " ) ) ;
if ( user = = null ) {
user = userMapper . selectById ( WORK_NOTIFY_USER_ID ) ;
}
if ( user = = null ) {
user = createWorkNotifyUser ( ) ;
}
workNotifyUserIdCache = user . getId ( ) ;
return workNotifyUserIdCache ;
}
}
private SysUser createWorkNotifyUser ( ) {
SysUser user = new SysUser ( ) ;
user . setId ( WORK_NOTIFY_USER_ID ) ;
user . setUsername ( WORK_NOTIFY_USERNAME ) ;
user . setRealname ( WORK_NOTIFY_REALNAME ) ;
user . setPassword ( " disabled " ) ;
user . setSalt ( " " ) ;
user . setStatus ( 2 ) ;
user . setDelFlag ( 0 ) ;
user . setCreateBy ( " system " ) ;
user . setCreateTime ( new Date ( ) ) ;
userMapper . insert ( user ) ;
return user ;
}
private boolean isWorkNotifyUser ( String userId ) {
return oConvertUtils . isNotEmpty ( userId ) & & userId . equals ( ensureWorkNotifyUserId ( ) ) ;
}
private boolean isWorkNotifyConversation ( String conversationId ) {
if ( oConvertUtils . isEmpty ( conversationId ) ) {
return false ;
}
SysImConversation conversation = conversationMapper . selectById ( conversationId ) ;
if ( conversation = = null | | ! CONV_TYPE_SINGLE . equals ( conversation . getConvType ( ) ) ) {
return false ;
}
String workNotifyId = ensureWorkNotifyUserId ( ) ;
String pairKey = conversation . getUserPairKey ( ) ;
return oConvertUtils . isNotEmpty ( pairKey ) & & pairKey . contains ( workNotifyId ) ;
}
private String resolveOrgCode ( String userId , Integer tenantId , String orgCode ) {
if ( oConvertUtils . isNotEmpty ( orgCode ) ) {
return orgCode ;
@@ -610,6 +766,17 @@ public class SysImChatServiceImpl implements ISysImChatService {
}
private void createMember ( String conversationId , String userId , Date now ) {
ensureMember ( conversationId , userId , now ) ;
}
/** 幂等创建会话成员,避免 uk_im_member 重复插入 */
private void ensureMember ( String conversationId , String userId , Date now ) {
if ( oConvertUtils . isEmpty ( conversationId ) | | oConvertUtils . isEmpty ( userId ) ) {
return ;
}
if ( getMember ( userId , conversationId ) ! = null ) {
return ;
}
SysImConversationMember member = new SysImConversationMember ( ) ;
member . setConversationId ( conversationId ) ;
member . setUserId ( userId ) ;
@@ -742,7 +909,11 @@ public class SysImChatServiceImpl implements ISysImChatService {
sender = userMapper . selectById ( message . getSenderId ( ) ) ;
}
if ( sender ! = null ) {
vo . setSenderName ( sender . getReal name ( ) ) ;
if ( WORK_NOTIFY_USERNAME . equals ( sender . getUser name ( ) ) ) {
vo . setSenderName ( WORK_NOTIFY_REALNAME ) ;
} else {
vo . setSenderName ( sender . getRealname ( ) ) ;
}
vo . setSenderAvatar ( sender . getAvatar ( ) ) ;
}
return vo ;