实现IM聊天群聊功能,包括群聊列表和创建群聊接口,优化消息处理和未读消息统计,增强用户体验。
This commit is contained in:
@@ -11,6 +11,7 @@ import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.TokenUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.im.dto.SysImCreateGroupDTO;
|
||||
import org.jeecg.modules.im.dto.SysImSendMessageDTO;
|
||||
import org.jeecg.modules.im.service.ISysImChatService;
|
||||
import org.jeecg.modules.im.vo.SysImContactVO;
|
||||
@@ -136,4 +137,22 @@ public class SysImChatController {
|
||||
return Result.OK(imChatService.listDeptMembers(user.getId(), resolveTenantId(user, request), user.getOrgCode(), keyword));
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】联系人接口(兼容保留,同本部门)-----------
|
||||
|
||||
//update-begin---author:xsl ---date:20260528 for:【IM聊天-OA】群聊接口-----------
|
||||
@Operation(summary = "IM聊天-群聊列表")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@GetMapping("/groups")
|
||||
public Result<List<SysImConversationVO>> groups(HttpServletRequest request) {
|
||||
LoginUser user = currentUser();
|
||||
return Result.OK(imChatService.listGroupConversations(user.getId(), resolveTenantId(user, request)));
|
||||
}
|
||||
|
||||
@Operation(summary = "IM聊天-创建群聊")
|
||||
@RequiresPermissions("sys:im:chat:group")
|
||||
@PostMapping("/group/create")
|
||||
public Result<SysImConversationVO> createGroup(@RequestBody SysImCreateGroupDTO dto, HttpServletRequest request) {
|
||||
LoginUser user = currentUser();
|
||||
return Result.OK(imChatService.createGroupConversation(user.getId(), resolveTenantId(user, request), user.getOrgCode(), dto));
|
||||
}
|
||||
//update-end---author:xsl ---date:20260528 for:【IM聊天-OA】群聊接口-----------
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.jeecg.modules.im.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建 IM 群聊
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建IM群聊")
|
||||
public class SysImCreateGroupDTO {
|
||||
|
||||
@Schema(description = "群名称")
|
||||
private String groupName;
|
||||
|
||||
@Schema(description = "群成员用户ID(不含本人时可自动加入创建人)")
|
||||
private List<String> memberUserIds;
|
||||
}
|
||||
@@ -20,10 +20,14 @@ public class SysImConversation implements Serializable {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
/** 会话类型 single单聊 */
|
||||
/** 会话类型 single单聊 group群聊 */
|
||||
private String convType;
|
||||
/** 单聊唯一键 */
|
||||
private String userPairKey;
|
||||
/** 群名称 */
|
||||
private String groupName;
|
||||
/** 群主用户ID */
|
||||
private String ownerId;
|
||||
/** 租户ID */
|
||||
private Integer tenantId;
|
||||
/** 最后一条消息摘要 */
|
||||
|
||||
@@ -16,4 +16,9 @@ public interface SysImConversationMapper extends BaseMapper<SysImConversation> {
|
||||
* 查询当前用户的会话列表
|
||||
*/
|
||||
List<SysImConversationVO> listMyConversations(@Param("userId") String userId, @Param("tenantId") Integer tenantId);
|
||||
|
||||
/**
|
||||
* 查询当前用户的群聊列表
|
||||
*/
|
||||
List<SysImConversationVO> listMyGroupConversations(@Param("userId") String userId, @Param("tenantId") Integer tenantId);
|
||||
}
|
||||
|
||||
@@ -23,4 +23,26 @@
|
||||
ORDER BY c.last_time DESC
|
||||
</select>
|
||||
|
||||
<select id="listMyGroupConversations" resultType="org.jeecg.modules.im.vo.SysImConversationVO">
|
||||
SELECT
|
||||
c.id AS conversationId,
|
||||
c.conv_type AS convType,
|
||||
c.group_name AS groupName,
|
||||
c.owner_id AS ownerId,
|
||||
c.last_content AS lastContent,
|
||||
c.last_time AS lastTime,
|
||||
m.unread_count AS unreadCount,
|
||||
(
|
||||
SELECT COUNT(1)
|
||||
FROM sys_im_conversation_member gm
|
||||
WHERE gm.conversation_id = c.id
|
||||
) AS memberCount
|
||||
FROM sys_im_conversation_member m
|
||||
INNER JOIN sys_im_conversation c ON c.id = m.conversation_id
|
||||
WHERE m.user_id = #{userId}
|
||||
AND c.tenant_id = #{tenantId}
|
||||
AND c.conv_type = 'group'
|
||||
ORDER BY c.last_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -4,6 +4,7 @@ package org.jeecg.modules.im.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
import org.jeecg.modules.im.dto.SysImCreateGroupDTO;
|
||||
import org.jeecg.modules.im.dto.SysImSendMessageDTO;
|
||||
|
||||
import org.jeecg.modules.im.vo.SysImContactVO;
|
||||
@@ -34,7 +35,9 @@ public interface ISysImChatService {
|
||||
|
||||
SysImConversationVO openSingleConversation(String userId, Integer tenantId, String orgCode, String targetUserId);
|
||||
|
||||
List<SysImConversationVO> listGroupConversations(String userId, Integer tenantId);
|
||||
|
||||
SysImConversationVO createGroupConversation(String userId, Integer tenantId, String orgCode, SysImCreateGroupDTO dto);
|
||||
|
||||
IPage<SysImMessageVO> listMessages(String userId, String conversationId, Integer pageNo, Integer pageSize, String startTime, String beforeTime);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.jeecg.common.constant.WebsocketConst;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.im.dto.SysImCreateGroupDTO;
|
||||
import org.jeecg.modules.im.dto.SysImSendMessageDTO;
|
||||
import org.jeecg.modules.im.entity.SysImConversation;
|
||||
import org.jeecg.modules.im.entity.SysImConversationMember;
|
||||
@@ -41,6 +42,8 @@ import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -51,6 +54,7 @@ import java.util.stream.Collectors;
|
||||
public class SysImChatServiceImpl implements ISysImChatService {
|
||||
|
||||
private static final String CONV_TYPE_SINGLE = "single";
|
||||
private static final String CONV_TYPE_GROUP = "group";
|
||||
private static final String MSG_TYPE_TEXT = "text";
|
||||
private static final String MSG_TYPE_IMAGE = "image";
|
||||
private static final String MSG_TYPE_BIZ_RECORD = "biz_record";
|
||||
@@ -116,6 +120,84 @@ public class SysImChatServiceImpl implements ISysImChatService {
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】打开单聊会话-----------
|
||||
|
||||
//update-begin---author:xsl ---date:20260528 for:【IM聊天-OA】群聊会话列表与创建-----------
|
||||
@Override
|
||||
public List<SysImConversationVO> listGroupConversations(String userId, Integer tenantId) {
|
||||
if (oConvertUtils.isEmpty(userId) || tenantId == null || tenantId <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return conversationMapper.listMyGroupConversations(userId, tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SysImConversationVO createGroupConversation(String userId, Integer tenantId, String orgCode, SysImCreateGroupDTO dto) {
|
||||
if (dto == null || oConvertUtils.isEmpty(dto.getGroupName())) {
|
||||
throw new JeecgBootException("群名称不能为空");
|
||||
}
|
||||
String groupName = dto.getGroupName().trim();
|
||||
if (groupName.length() > 30) {
|
||||
throw new JeecgBootException("群名称不能超过30字");
|
||||
}
|
||||
List<String> memberIds = normalizeGroupMemberIds(userId, dto.getMemberUserIds());
|
||||
if (memberIds.size() < 2) {
|
||||
throw new JeecgBootException("群聊至少需要2名成员");
|
||||
}
|
||||
if (memberIds.size() > 50) {
|
||||
throw new JeecgBootException("群成员不能超过50人");
|
||||
}
|
||||
for (String memberId : memberIds) {
|
||||
if (userId.equals(memberId)) {
|
||||
continue;
|
||||
}
|
||||
validateTenantChat(userId, tenantId, orgCode, memberId);
|
||||
}
|
||||
Date now = new Date();
|
||||
SysImConversation conversation = new SysImConversation();
|
||||
conversation.setConvType(CONV_TYPE_GROUP);
|
||||
conversation.setGroupName(groupName);
|
||||
conversation.setOwnerId(userId);
|
||||
conversation.setTenantId(tenantId);
|
||||
conversation.setCreateBy(userId);
|
||||
conversation.setCreateTime(now);
|
||||
conversation.setUpdateTime(now);
|
||||
conversationMapper.insert(conversation);
|
||||
for (String memberId : memberIds) {
|
||||
createMember(conversation.getId(), memberId, now);
|
||||
}
|
||||
return buildGroupConversationVo(conversation, userId);
|
||||
}
|
||||
|
||||
private List<String> normalizeGroupMemberIds(String creatorId, List<String> memberUserIds) {
|
||||
Set<String> memberIds = new LinkedHashSet<>();
|
||||
memberIds.add(creatorId);
|
||||
if (memberUserIds != null) {
|
||||
for (String memberId : memberUserIds) {
|
||||
if (oConvertUtils.isNotEmpty(memberId)) {
|
||||
memberIds.add(memberId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(memberIds);
|
||||
}
|
||||
|
||||
private SysImConversationVO buildGroupConversationVo(SysImConversation conversation, String userId) {
|
||||
SysImConversationMember member = getMember(userId, conversation.getId());
|
||||
SysImConversationVO vo = new SysImConversationVO();
|
||||
vo.setConversationId(conversation.getId());
|
||||
vo.setConvType(CONV_TYPE_GROUP);
|
||||
vo.setGroupName(conversation.getGroupName());
|
||||
vo.setOwnerId(conversation.getOwnerId());
|
||||
vo.setLastContent(conversation.getLastContent());
|
||||
vo.setLastTime(conversation.getLastTime());
|
||||
vo.setUnreadCount(member == null ? 0 : member.getUnreadCount());
|
||||
Long memberCount = memberMapper.selectCount(new LambdaQueryWrapper<SysImConversationMember>()
|
||||
.eq(SysImConversationMember::getConversationId, conversation.getId()));
|
||||
vo.setMemberCount(memberCount == null ? 0 : memberCount.intValue());
|
||||
return vo;
|
||||
}
|
||||
//update-end---author:xsl ---date:20260528 for:【IM聊天-OA】群聊会话列表与创建-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】消息分页-----------
|
||||
@Override
|
||||
public IPage<SysImMessageVO> listMessages(String userId, String conversationId, Integer pageNo, Integer pageSize, String startTime, String beforeTime) {
|
||||
@@ -179,7 +261,15 @@ public class SysImChatServiceImpl implements ISysImChatService {
|
||||
|
||||
SysImConversation conversation = conversationMapper.selectById(dto.getConversationId());
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】图片消息会话摘要-----------
|
||||
conversation.setLastContent(truncate(resolveLastContent(message.getMsgType(), message.getContent()), 200));
|
||||
String lastContent = resolveLastContent(message.getMsgType(), message.getContent());
|
||||
if (CONV_TYPE_GROUP.equals(conversation.getConvType())) {
|
||||
SysUser sender = userMapper.selectById(userId);
|
||||
String senderName = sender != null ? oConvertUtils.getString(sender.getRealname(), sender.getUsername()) : "";
|
||||
if (oConvertUtils.isNotEmpty(senderName)) {
|
||||
lastContent = senderName + ": " + lastContent;
|
||||
}
|
||||
}
|
||||
conversation.setLastContent(truncate(lastContent, 200));
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】图片消息会话摘要-----------
|
||||
conversation.setLastTime(now);
|
||||
conversation.setUpdateTime(now);
|
||||
@@ -188,7 +278,7 @@ public class SysImChatServiceImpl implements ISysImChatService {
|
||||
memberMapper.incrementUnreadExceptSender(dto.getConversationId(), userId);
|
||||
SysImMessageVO messageVo = toMessageVo(message, userId);
|
||||
fillBizRecordReceiverPermission(messageVo, message, userId, new HashMap<>(4));
|
||||
pushChatMessage(dto.getConversationId(), userId, messageVo);
|
||||
pushChatMessage(dto.getConversationId(), userId, messageVo, conversation.getConvType());
|
||||
return messageVo;
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】发送消息-----------
|
||||
@@ -422,6 +512,10 @@ public class SysImChatServiceImpl implements ISysImChatService {
|
||||
if (!Boolean.TRUE.equals(vo.getMine()) || !MSG_TYPE_BIZ_RECORD.equals(vo.getMsgType())) {
|
||||
return;
|
||||
}
|
||||
SysImConversation conversation = conversationMapper.selectById(message.getConversationId());
|
||||
if (conversation == null || !CONV_TYPE_SINGLE.equals(conversation.getConvType())) {
|
||||
return;
|
||||
}
|
||||
String pagePath = extractBizRecordPagePath(vo.getContent());
|
||||
if (oConvertUtils.isEmpty(pagePath)) {
|
||||
return;
|
||||
@@ -509,7 +603,7 @@ public class SysImChatServiceImpl implements ISysImChatService {
|
||||
//update-end---author:xsl ---date:20260528 for:【IM聊天-OA】发送方提示接收方无功能权限-----------
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】消息列表批量填充发送人-----------
|
||||
|
||||
private void pushChatMessage(String conversationId, String senderId, SysImMessageVO messageVo) {
|
||||
private void pushChatMessage(String conversationId, String senderId, SysImMessageVO messageVo, String convType) {
|
||||
List<SysImConversationMember> members = memberMapper.selectList(new LambdaQueryWrapper<SysImConversationMember>()
|
||||
.eq(SysImConversationMember::getConversationId, conversationId));
|
||||
for (SysImConversationMember member : members) {
|
||||
@@ -520,6 +614,9 @@ public class SysImChatServiceImpl implements ISysImChatService {
|
||||
obj.put(WebsocketConst.MSG_CMD, WebsocketConst.MSG_CHAT);
|
||||
obj.put(WebsocketConst.MSG_USER_ID, member.getUserId());
|
||||
obj.put("conversationId", conversationId);
|
||||
//update-begin---author:xsl ---date:20260528 for:【IM聊天-OA】WebSocket推送会话类型区分群聊-----------
|
||||
obj.put("convType", convType);
|
||||
//update-end---author:xsl ---date:20260528 for:【IM聊天-OA】WebSocket推送会话类型区分群聊-----------
|
||||
obj.put("messageId", messageVo.getId());
|
||||
obj.put("senderId", messageVo.getSenderId());
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】WebSocket推送补全头像字段-----------
|
||||
|
||||
@@ -34,4 +34,10 @@ public class SysImConversationVO {
|
||||
private String targetUsername;
|
||||
@Schema(description = "对方头像")
|
||||
private String targetAvatar;
|
||||
@Schema(description = "群名称")
|
||||
private String groupName;
|
||||
@Schema(description = "群成员数")
|
||||
private Integer memberCount;
|
||||
@Schema(description = "群主用户ID")
|
||||
private String ownerId;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user