新增IM聊天
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package org.jeecg.modules.im.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.jeecg.modules.im.vo.SysImMessageVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
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.SysImSendMessageDTO;
|
||||
import org.jeecg.modules.im.service.ISysImChatService;
|
||||
import org.jeecg.modules.im.vo.SysImContactVO;
|
||||
import org.jeecg.modules.im.vo.SysImConversationVO;
|
||||
import org.jeecg.modules.im.vo.SysImMessageVO;
|
||||
import org.jeecg.modules.system.entity.SysUser;
|
||||
import org.jeecg.modules.system.service.ISysUserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 租户 IM 聊天
|
||||
*/
|
||||
@Tag(name = "IM聊天")
|
||||
@RestController
|
||||
@RequestMapping("/sys/im/chat")
|
||||
@Slf4j
|
||||
public class SysImChatController {
|
||||
|
||||
@Autowired
|
||||
private ISysImChatService imChatService;
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
|
||||
private LoginUser currentUser() {
|
||||
return (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
}
|
||||
|
||||
private Integer resolveTenantId(LoginUser user, HttpServletRequest request) {
|
||||
Integer tenantId = oConvertUtils.getInt(TokenUtils.getTenantIdByRequest(request), 0);
|
||||
if (tenantId != null && tenantId > 0) {
|
||||
return tenantId;
|
||||
}
|
||||
SysUser sysUser = sysUserService.getById(user.getId());
|
||||
if (sysUser != null && sysUser.getLoginTenantId() != null && sysUser.getLoginTenantId() > 0) {
|
||||
return sysUser.getLoginTenantId();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】会话列表接口-----------
|
||||
@Operation(summary = "IM聊天-会话列表")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@GetMapping("/conversations")
|
||||
public Result<List<SysImConversationVO>> conversations(HttpServletRequest request) {
|
||||
LoginUser user = currentUser();
|
||||
return Result.OK(imChatService.listConversations(user.getId(), resolveTenantId(user, request)));
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】会话列表接口-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】打开单聊接口-----------
|
||||
@Operation(summary = "IM聊天-打开单聊")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@PostMapping("/open")
|
||||
public Result<SysImConversationVO> open(@RequestParam(name = "targetUserId") String targetUserId, HttpServletRequest request) {
|
||||
LoginUser user = currentUser();
|
||||
return Result.OK(imChatService.openSingleConversation(user.getId(), resolveTenantId(user, request), user.getOrgCode(), targetUserId));
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】打开单聊接口-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】消息列表接口-----------
|
||||
@Operation(summary = "IM聊天-消息列表")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@GetMapping("/messages")
|
||||
public Result<IPage<SysImMessageVO>> messages(
|
||||
@RequestParam(name = "conversationId") String conversationId,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "20") Integer pageSize,
|
||||
@RequestParam(name = "startTime", required = false) String startTime,
|
||||
@RequestParam(name = "beforeTime", required = false) String beforeTime) {
|
||||
LoginUser user = currentUser();
|
||||
return Result.OK(imChatService.listMessages(user.getId(), conversationId, pageNo, pageSize, startTime, beforeTime));
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】消息列表接口-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】发送消息接口-----------
|
||||
@Operation(summary = "IM聊天-发送消息")
|
||||
@RequiresPermissions("sys:im:chat:send")
|
||||
@PostMapping("/send")
|
||||
public Result<SysImMessageVO> send(@RequestBody SysImSendMessageDTO dto, HttpServletRequest request) {
|
||||
LoginUser user = currentUser();
|
||||
return Result.OK(imChatService.sendMessage(user.getId(), resolveTenantId(user, request), dto));
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】发送消息接口-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】标记已读接口-----------
|
||||
@Operation(summary = "IM聊天-标记已读")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@PutMapping("/read")
|
||||
public Result<String> read(@RequestParam(name = "conversationId") String conversationId) {
|
||||
LoginUser user = currentUser();
|
||||
imChatService.markRead(user.getId(), conversationId);
|
||||
return Result.OK();
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】标记已读接口-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】本部门成员接口-----------
|
||||
@Operation(summary = "IM聊天-本部门成员")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@GetMapping("/deptMembers")
|
||||
public Result<List<SysImContactVO>> deptMembers(@RequestParam(name = "keyword", required = false) String keyword, HttpServletRequest request) {
|
||||
LoginUser user = currentUser();
|
||||
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:cursor ---date:20260528 for:【IM聊天-OA】联系人接口(兼容保留,同本部门)-----------
|
||||
@Operation(summary = "IM聊天-租户联系人")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@GetMapping("/contacts")
|
||||
public Result<List<SysImContactVO>> contacts(@RequestParam(name = "keyword", required = false) String keyword, HttpServletRequest request) {
|
||||
LoginUser user = currentUser();
|
||||
return Result.OK(imChatService.listDeptMembers(user.getId(), resolveTenantId(user, request), user.getOrgCode(), keyword));
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】联系人接口(兼容保留,同本部门)-----------
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.jeecg.modules.im.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 发送 IM 消息 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "发送IM消息")
|
||||
public class SysImSendMessageDTO {
|
||||
|
||||
@Schema(description = "会话ID")
|
||||
private String conversationId;
|
||||
@Schema(description = "消息内容")
|
||||
private String content;
|
||||
@Schema(description = "消息类型 text/image/file")
|
||||
private String msgType;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.jeecg.modules.im.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* IM 会话
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_im_conversation")
|
||||
public class SysImConversation implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
/** 会话类型 single单聊 */
|
||||
private String convType;
|
||||
/** 单聊唯一键 */
|
||||
private String userPairKey;
|
||||
/** 租户ID */
|
||||
private Integer tenantId;
|
||||
/** 最后一条消息摘要 */
|
||||
private String lastContent;
|
||||
/** 最后消息时间 */
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date lastTime;
|
||||
private String createBy;
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
private String updateBy;
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.jeecg.modules.im.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* IM 会话成员
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_im_conversation_member")
|
||||
public class SysImConversationMember implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
private String conversationId;
|
||||
private String userId;
|
||||
private Integer unreadCount;
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date lastReadTime;
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.jeecg.modules.im.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* IM 消息
|
||||
*/
|
||||
@Data
|
||||
@TableName("sys_im_message")
|
||||
public class SysImMessage implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
private String conversationId;
|
||||
private String senderId;
|
||||
private String content;
|
||||
/** 消息类型 text/image/file */
|
||||
private String msgType;
|
||||
private Integer tenantId;
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.jeecg.modules.im.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.jeecg.modules.im.entity.SysImConversation;
|
||||
import org.jeecg.modules.im.vo.SysImConversationVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IM 会话 Mapper
|
||||
*/
|
||||
public interface SysImConversationMapper extends BaseMapper<SysImConversation> {
|
||||
|
||||
/**
|
||||
* 查询当前用户的会话列表
|
||||
*/
|
||||
List<SysImConversationVO> listMyConversations(@Param("userId") String userId, @Param("tenantId") Integer tenantId);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.jeecg.modules.im.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.jeecg.modules.im.entity.SysImConversationMember;
|
||||
|
||||
/**
|
||||
* IM 会话成员 Mapper
|
||||
*/
|
||||
public interface SysImConversationMemberMapper extends BaseMapper<SysImConversationMember> {
|
||||
|
||||
/**
|
||||
* 未读数 +1(排除发送人)
|
||||
*/
|
||||
int incrementUnreadExceptSender(@Param("conversationId") String conversationId, @Param("senderId") String senderId);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.jeecg.modules.im.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.jeecg.modules.im.entity.SysImMessage;
|
||||
|
||||
/**
|
||||
* IM 消息 Mapper
|
||||
*/
|
||||
public interface SysImMessageMapper extends BaseMapper<SysImMessage> {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.jeecg.modules.im.mapper.SysImConversationMapper">
|
||||
|
||||
<select id="listMyConversations" resultType="org.jeecg.modules.im.vo.SysImConversationVO">
|
||||
SELECT
|
||||
c.id AS conversationId,
|
||||
c.conv_type AS convType,
|
||||
c.last_content AS lastContent,
|
||||
c.last_time AS lastTime,
|
||||
m.unread_count AS unreadCount,
|
||||
u.id AS targetUserId,
|
||||
u.realname AS targetRealname,
|
||||
u.username AS targetUsername,
|
||||
u.avatar AS targetAvatar
|
||||
FROM sys_im_conversation_member m
|
||||
INNER JOIN sys_im_conversation c ON c.id = m.conversation_id
|
||||
INNER JOIN sys_im_conversation_member om ON om.conversation_id = c.id AND om.user_id != m.user_id
|
||||
INNER JOIN sys_user u ON u.id = om.user_id
|
||||
WHERE m.user_id = #{userId}
|
||||
AND c.tenant_id = #{tenantId}
|
||||
AND c.conv_type = 'single'
|
||||
ORDER BY c.last_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.jeecg.modules.im.mapper.SysImConversationMemberMapper">
|
||||
|
||||
<update id="incrementUnreadExceptSender">
|
||||
UPDATE sys_im_conversation_member
|
||||
SET unread_count = unread_count + 1
|
||||
WHERE conversation_id = #{conversationId}
|
||||
AND user_id != #{senderId}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.jeecg.modules.im.service;
|
||||
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
import org.jeecg.modules.im.dto.SysImSendMessageDTO;
|
||||
|
||||
import org.jeecg.modules.im.vo.SysImContactVO;
|
||||
|
||||
import org.jeecg.modules.im.vo.SysImConversationVO;
|
||||
|
||||
import org.jeecg.modules.im.vo.SysImMessageVO;
|
||||
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* IM 聊天服务
|
||||
|
||||
*/
|
||||
|
||||
public interface ISysImChatService {
|
||||
|
||||
|
||||
|
||||
List<SysImConversationVO> listConversations(String userId, Integer tenantId);
|
||||
|
||||
|
||||
|
||||
SysImConversationVO openSingleConversation(String userId, Integer tenantId, String orgCode, String targetUserId);
|
||||
|
||||
|
||||
|
||||
IPage<SysImMessageVO> listMessages(String userId, String conversationId, Integer pageNo, Integer pageSize, String startTime, String beforeTime);
|
||||
|
||||
|
||||
|
||||
SysImMessageVO sendMessage(String userId, Integer tenantId, SysImSendMessageDTO dto);
|
||||
|
||||
|
||||
|
||||
void markRead(String userId, String conversationId);
|
||||
|
||||
|
||||
|
||||
List<SysImContactVO> listDeptMembers(String userId, Integer tenantId, String orgCode, String keyword);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
package org.jeecg.modules.im.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.SysImSendMessageDTO;
|
||||
import org.jeecg.modules.im.entity.SysImConversation;
|
||||
import org.jeecg.modules.im.entity.SysImConversationMember;
|
||||
import org.jeecg.modules.im.entity.SysImMessage;
|
||||
import org.jeecg.modules.im.mapper.SysImConversationMapper;
|
||||
import org.jeecg.modules.im.mapper.SysImConversationMemberMapper;
|
||||
import org.jeecg.modules.im.mapper.SysImMessageMapper;
|
||||
import org.jeecg.modules.im.service.ISysImChatService;
|
||||
import org.jeecg.modules.im.vo.SysImContactVO;
|
||||
import org.jeecg.modules.im.vo.SysImConversationVO;
|
||||
import org.jeecg.modules.im.vo.SysImMessageVO;
|
||||
import org.jeecg.modules.message.websocket.WebSocket;
|
||||
import org.jeecg.modules.system.entity.SysDepart;
|
||||
import org.jeecg.modules.system.entity.SysUser;
|
||||
import org.jeecg.modules.system.entity.SysUserDepart;
|
||||
import org.jeecg.modules.system.mapper.SysDepartMapper;
|
||||
import org.jeecg.modules.system.mapper.SysUserDepartMapper;
|
||||
import org.jeecg.modules.system.mapper.SysUserMapper;
|
||||
import org.jeecg.modules.system.mapper.SysUserTenantMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* IM 聊天服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SysImChatServiceImpl implements ISysImChatService {
|
||||
|
||||
private static final String CONV_TYPE_SINGLE = "single";
|
||||
private static final String MSG_TYPE_TEXT = "text";
|
||||
|
||||
@Autowired
|
||||
private SysImConversationMapper conversationMapper;
|
||||
@Autowired
|
||||
private SysImConversationMemberMapper memberMapper;
|
||||
@Autowired
|
||||
private SysImMessageMapper messageMapper;
|
||||
@Autowired
|
||||
private SysUserMapper userMapper;
|
||||
@Autowired
|
||||
private SysUserTenantMapper userTenantMapper;
|
||||
@Autowired
|
||||
private SysUserDepartMapper userDepartMapper;
|
||||
@Autowired
|
||||
private SysDepartMapper departMapper;
|
||||
@Autowired
|
||||
private WebSocket webSocket;
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】会话列表-----------
|
||||
@Override
|
||||
public List<SysImConversationVO> listConversations(String userId, Integer tenantId) {
|
||||
if (oConvertUtils.isEmpty(userId) || tenantId == null || tenantId <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return conversationMapper.listMyConversations(userId, tenantId);
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】会话列表-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】打开单聊会话-----------
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SysImConversationVO openSingleConversation(String userId, Integer tenantId, String orgCode, String targetUserId) {
|
||||
String pairKey = buildPairKey(userId, targetUserId);
|
||||
SysImConversation conversation = conversationMapper.selectOne(new LambdaQueryWrapper<SysImConversation>()
|
||||
.eq(SysImConversation::getTenantId, tenantId)
|
||||
.eq(SysImConversation::getUserPairKey, pairKey));
|
||||
Date now = new Date();
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】已有会话快速打开-----------
|
||||
if (conversation != null) {
|
||||
return buildConversationVo(conversation, userId, targetUserId);
|
||||
}
|
||||
validateTenantChat(userId, tenantId, orgCode, targetUserId);
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】已有会话快速打开-----------
|
||||
conversation = new SysImConversation();
|
||||
conversation.setConvType(CONV_TYPE_SINGLE);
|
||||
conversation.setUserPairKey(pairKey);
|
||||
conversation.setTenantId(tenantId);
|
||||
conversation.setCreateBy(userId);
|
||||
conversation.setCreateTime(now);
|
||||
conversation.setUpdateTime(now);
|
||||
conversationMapper.insert(conversation);
|
||||
createMember(conversation.getId(), userId, now);
|
||||
createMember(conversation.getId(), targetUserId, now);
|
||||
return buildConversationVo(conversation.getId(), userId, tenantId, targetUserId);
|
||||
}
|
||||
//update-end---author:cursor ---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) {
|
||||
assertMember(userId, conversationId);
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】默认本周消息+向上翻页加载-----------
|
||||
Date start = parseMessageTime(startTime);
|
||||
Date before = parseMessageTime(beforeTime);
|
||||
int currentPage = (start != null || before != null) ? 1 : (pageNo == null || pageNo < 1 ? 1 : pageNo);
|
||||
int size = pageSize == null || pageSize < 1 ? 20 : pageSize;
|
||||
Page<SysImMessage> page = new Page<>(currentPage, size);
|
||||
if (currentPage == 1) {
|
||||
page.setSearchCount(false);
|
||||
}
|
||||
LambdaQueryWrapper<SysImMessage> wrapper = new LambdaQueryWrapper<SysImMessage>()
|
||||
.eq(SysImMessage::getConversationId, conversationId);
|
||||
if (start != null) {
|
||||
wrapper.ge(SysImMessage::getCreateTime, start);
|
||||
}
|
||||
if (before != null) {
|
||||
wrapper.lt(SysImMessage::getCreateTime, before);
|
||||
}
|
||||
wrapper.orderByDesc(SysImMessage::getCreateTime);
|
||||
IPage<SysImMessage> messagePage = messageMapper.selectPage(page, wrapper);
|
||||
Page<SysImMessageVO> voPage = new Page<>(currentPage, size, messagePage.getTotal());
|
||||
List<SysImMessageVO> records = toMessageVoList(messagePage.getRecords(), userId);
|
||||
Collections.reverse(records);
|
||||
voPage.setRecords(records);
|
||||
return voPage;
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】默认本周消息+向上翻页加载-----------
|
||||
}
|
||||
|
||||
private Date parseMessageTime(String timeText) {
|
||||
if (oConvertUtils.isEmpty(timeText)) {
|
||||
return null;
|
||||
}
|
||||
Date date = DateUtils.parseDatetime(timeText);
|
||||
if (date == null) {
|
||||
date = DateUtils.str2Date(timeText, DateUtils.date_sdf.get());
|
||||
}
|
||||
return date;
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】消息分页-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】发送消息-----------
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SysImMessageVO sendMessage(String userId, Integer tenantId, SysImSendMessageDTO dto) {
|
||||
if (dto == null || oConvertUtils.isEmpty(dto.getConversationId()) || oConvertUtils.isEmpty(dto.getContent())) {
|
||||
throw new JeecgBootException("消息内容不能为空");
|
||||
}
|
||||
assertMember(userId, dto.getConversationId());
|
||||
Date now = new Date();
|
||||
SysImMessage message = new SysImMessage();
|
||||
message.setConversationId(dto.getConversationId());
|
||||
message.setSenderId(userId);
|
||||
message.setContent(dto.getContent().trim());
|
||||
message.setMsgType(oConvertUtils.isEmpty(dto.getMsgType()) ? MSG_TYPE_TEXT : dto.getMsgType());
|
||||
message.setTenantId(tenantId);
|
||||
message.setCreateTime(now);
|
||||
messageMapper.insert(message);
|
||||
|
||||
SysImConversation conversation = conversationMapper.selectById(dto.getConversationId());
|
||||
conversation.setLastContent(truncate(message.getContent(), 200));
|
||||
conversation.setLastTime(now);
|
||||
conversation.setUpdateTime(now);
|
||||
conversationMapper.updateById(conversation);
|
||||
|
||||
memberMapper.incrementUnreadExceptSender(dto.getConversationId(), userId);
|
||||
SysImMessageVO messageVo = toMessageVo(message, userId);
|
||||
pushChatMessage(dto.getConversationId(), userId, messageVo);
|
||||
return messageVo;
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】发送消息-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】标记已读-----------
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void markRead(String userId, String conversationId) {
|
||||
SysImConversationMember member = getMember(userId, conversationId);
|
||||
if (member == null) {
|
||||
return;
|
||||
}
|
||||
member.setUnreadCount(0);
|
||||
member.setLastReadTime(new Date());
|
||||
memberMapper.updateById(member);
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】标记已读-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】本部门成员列表(含会话摘要)-----------
|
||||
@Override
|
||||
public List<SysImContactVO> listDeptMembers(String userId, Integer tenantId, String orgCode, String keyword) {
|
||||
String resolvedOrgCode = resolveOrgCode(userId, tenantId, orgCode);
|
||||
if (oConvertUtils.isEmpty(resolvedOrgCode)) {
|
||||
throw new JeecgBootException("未获取到当前部门,请切换部门后重试");
|
||||
}
|
||||
List<SysUser> users = userDepartMapper.querySameDepartUserList(resolvedOrgCode, userId, tenantId, keyword);
|
||||
if (users == null || users.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<String, SysImConversationVO> convMap = new HashMap<>(16);
|
||||
if (tenantId != null && tenantId > 0) {
|
||||
for (SysImConversationVO conv : conversationMapper.listMyConversations(userId, tenantId)) {
|
||||
if (oConvertUtils.isNotEmpty(conv.getTargetUserId())) {
|
||||
convMap.put(conv.getTargetUserId(), conv);
|
||||
}
|
||||
}
|
||||
}
|
||||
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.toList());
|
||||
result.sort(Comparator
|
||||
.comparing(SysImContactVO::getLastTime, Comparator.nullsLast(Comparator.reverseOrder()))
|
||||
.thenComparing(item -> oConvertUtils.getString(item.getRealname(), item.getUsername())));
|
||||
return result;
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】本部门成员列表(含会话摘要)-----------
|
||||
|
||||
private SysImContactVO toContactVo(SysUser user) {
|
||||
SysImContactVO vo = new SysImContactVO();
|
||||
vo.setId(user.getId());
|
||||
vo.setUsername(user.getUsername());
|
||||
vo.setRealname(user.getRealname());
|
||||
vo.setAvatar(user.getAvatar());
|
||||
vo.setOrgCodeTxt(user.getOrgCodeTxt());
|
||||
vo.setUnreadCount(0);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private String resolveOrgCode(String userId, Integer tenantId, String orgCode) {
|
||||
if (oConvertUtils.isNotEmpty(orgCode)) {
|
||||
return orgCode;
|
||||
}
|
||||
if (tenantId != null && tenantId > 0) {
|
||||
List<SysUserDepart> departs = userDepartMapper.getTenantUserDepart(userId, String.valueOf(tenantId));
|
||||
if (departs != null && !departs.isEmpty()) {
|
||||
SysDepart depart = departMapper.selectById(departs.get(0).getDepId());
|
||||
if (depart != null && oConvertUtils.isNotEmpty(depart.getOrgCode())) {
|
||||
return depart.getOrgCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
List<SysUserDepart> departs = userDepartMapper.getUserDepartByUid(userId);
|
||||
if (departs != null && !departs.isEmpty()) {
|
||||
SysDepart depart = departMapper.selectById(departs.get(0).getDepId());
|
||||
if (depart != null) {
|
||||
return depart.getOrgCode();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createMember(String conversationId, String userId, Date now) {
|
||||
SysImConversationMember member = new SysImConversationMember();
|
||||
member.setConversationId(conversationId);
|
||||
member.setUserId(userId);
|
||||
member.setUnreadCount(0);
|
||||
member.setCreateTime(now);
|
||||
memberMapper.insert(member);
|
||||
}
|
||||
|
||||
private SysImConversationVO buildConversationVo(String conversationId, String userId, Integer tenantId, String targetUserId) {
|
||||
SysImConversation conversation = conversationMapper.selectById(conversationId);
|
||||
if (conversation == null) {
|
||||
return new SysImConversationVO();
|
||||
}
|
||||
return buildConversationVo(conversation, userId, targetUserId);
|
||||
}
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】已有会话快速打开-----------
|
||||
private SysImConversationVO buildConversationVo(SysImConversation conversation, String userId, String targetUserId) {
|
||||
SysUser target = userMapper.selectById(targetUserId);
|
||||
SysImConversationMember member = getMember(userId, conversation.getId());
|
||||
SysImConversationVO vo = new SysImConversationVO();
|
||||
vo.setConversationId(conversation.getId());
|
||||
vo.setConvType(CONV_TYPE_SINGLE);
|
||||
vo.setLastContent(conversation.getLastContent());
|
||||
vo.setLastTime(conversation.getLastTime());
|
||||
vo.setUnreadCount(member == null ? 0 : member.getUnreadCount());
|
||||
if (target != null) {
|
||||
vo.setTargetUserId(target.getId());
|
||||
vo.setTargetRealname(target.getRealname());
|
||||
vo.setTargetUsername(target.getUsername());
|
||||
vo.setTargetAvatar(target.getAvatar());
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】已有会话快速打开-----------
|
||||
|
||||
private void validateTenantChat(String userId, Integer tenantId, String orgCode, String targetUserId) {
|
||||
if (oConvertUtils.isEmpty(targetUserId)) {
|
||||
throw new JeecgBootException("请选择聊天对象");
|
||||
}
|
||||
if (userId.equals(targetUserId)) {
|
||||
throw new JeecgBootException("不能与自己聊天");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw new JeecgBootException("请先选择租户");
|
||||
}
|
||||
Integer selfExist = userTenantMapper.userTenantIzExist(userId, tenantId);
|
||||
Integer targetExist = userTenantMapper.userTenantIzExist(targetUserId, tenantId);
|
||||
if (selfExist == null || selfExist <= 0 || targetExist == null || targetExist <= 0) {
|
||||
throw new JeecgBootException("仅支持与当前租户内用户聊天");
|
||||
}
|
||||
SysUser target = userMapper.selectById(targetUserId);
|
||||
if (target == null) {
|
||||
throw new JeecgBootException("聊天对象不存在");
|
||||
}
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】限制同部门聊天-----------
|
||||
String resolvedOrgCode = resolveOrgCode(userId, tenantId, orgCode);
|
||||
if (oConvertUtils.isEmpty(resolvedOrgCode)) {
|
||||
throw new JeecgBootException("未获取到当前部门,请切换部门后重试");
|
||||
}
|
||||
if (userDepartMapper.countUserInDepartOrgCode(targetUserId, resolvedOrgCode, tenantId) <= 0) {
|
||||
throw new JeecgBootException("仅支持与同部门用户聊天");
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】限制同部门聊天-----------
|
||||
}
|
||||
|
||||
private String buildPairKey(String userId1, String userId2) {
|
||||
if (userId1.compareTo(userId2) <= 0) {
|
||||
return userId1 + "_" + userId2;
|
||||
}
|
||||
return userId2 + "_" + userId1;
|
||||
}
|
||||
|
||||
private SysImConversationMember getMember(String userId, String conversationId) {
|
||||
return memberMapper.selectOne(new LambdaQueryWrapper<SysImConversationMember>()
|
||||
.eq(SysImConversationMember::getConversationId, conversationId)
|
||||
.eq(SysImConversationMember::getUserId, userId));
|
||||
}
|
||||
|
||||
private void assertMember(String userId, String conversationId) {
|
||||
if (getMember(userId, conversationId) == null) {
|
||||
throw new JeecgBootException("无权访问该会话");
|
||||
}
|
||||
}
|
||||
|
||||
private SysImMessageVO toMessageVo(SysImMessage message, String currentUserId) {
|
||||
return toMessageVo(message, currentUserId, null);
|
||||
}
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】消息列表批量填充发送人-----------
|
||||
private List<SysImMessageVO> toMessageVoList(List<SysImMessage> messages, String currentUserId) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> senderIds = messages.stream()
|
||||
.map(SysImMessage::getSenderId)
|
||||
.filter(oConvertUtils::isNotEmpty)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
Map<String, SysUser> userMap = new HashMap<>(senderIds.size());
|
||||
if (!senderIds.isEmpty()) {
|
||||
List<SysUser> users = userMapper.selectBatchIds(senderIds);
|
||||
if (users != null) {
|
||||
for (SysUser user : users) {
|
||||
userMap.put(user.getId(), user);
|
||||
}
|
||||
}
|
||||
}
|
||||
List<SysImMessageVO> result = new ArrayList<>(messages.size());
|
||||
for (SysImMessage message : messages) {
|
||||
result.add(toMessageVo(message, currentUserId, userMap));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private SysImMessageVO toMessageVo(SysImMessage message, String currentUserId, Map<String, SysUser> userMap) {
|
||||
SysImMessageVO vo = new SysImMessageVO();
|
||||
vo.setId(message.getId());
|
||||
vo.setConversationId(message.getConversationId());
|
||||
vo.setSenderId(message.getSenderId());
|
||||
vo.setContent(message.getContent());
|
||||
vo.setMsgType(message.getMsgType());
|
||||
vo.setCreateTime(message.getCreateTime());
|
||||
vo.setMine(currentUserId.equals(message.getSenderId()));
|
||||
SysUser sender = userMap == null ? null : userMap.get(message.getSenderId());
|
||||
if (sender == null && userMap == null) {
|
||||
sender = userMapper.selectById(message.getSenderId());
|
||||
}
|
||||
if (sender != null) {
|
||||
vo.setSenderName(sender.getRealname());
|
||||
vo.setSenderAvatar(sender.getAvatar());
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】消息列表批量填充发送人-----------
|
||||
|
||||
private void pushChatMessage(String conversationId, String senderId, SysImMessageVO messageVo) {
|
||||
List<SysImConversationMember> members = memberMapper.selectList(new LambdaQueryWrapper<SysImConversationMember>()
|
||||
.eq(SysImConversationMember::getConversationId, conversationId));
|
||||
for (SysImConversationMember member : members) {
|
||||
if (senderId.equals(member.getUserId())) {
|
||||
continue;
|
||||
}
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put(WebsocketConst.MSG_CMD, WebsocketConst.MSG_CHAT);
|
||||
obj.put(WebsocketConst.MSG_USER_ID, member.getUserId());
|
||||
obj.put("conversationId", conversationId);
|
||||
obj.put("messageId", messageVo.getId());
|
||||
obj.put("senderId", messageVo.getSenderId());
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】WebSocket推送补全头像字段-----------
|
||||
obj.put("senderName", messageVo.getSenderName());
|
||||
obj.put("senderAvatar", messageVo.getSenderAvatar());
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】WebSocket推送补全头像字段-----------
|
||||
obj.put("content", messageVo.getContent());
|
||||
obj.put("msgType", messageVo.getMsgType());
|
||||
obj.put("createTime", messageVo.getCreateTime());
|
||||
webSocket.sendMessage(member.getUserId(), obj.toJSONString());
|
||||
}
|
||||
}
|
||||
|
||||
private String truncate(String content, int maxLen) {
|
||||
if (content == null) {
|
||||
return null;
|
||||
}
|
||||
return content.length() <= maxLen ? content : content.substring(0, maxLen);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.jeecg.modules.im.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IM 联系人 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "IM联系人")
|
||||
public class SysImContactVO {
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private String id;
|
||||
@Schema(description = "账号")
|
||||
private String username;
|
||||
@Schema(description = "姓名")
|
||||
private String realname;
|
||||
@Schema(description = "头像")
|
||||
private String avatar;
|
||||
@Schema(description = "部门")
|
||||
private String orgCodeTxt;
|
||||
@Schema(description = "会话ID")
|
||||
private String conversationId;
|
||||
@Schema(description = "最后消息摘要")
|
||||
private String lastContent;
|
||||
@Schema(description = "最后消息时间")
|
||||
private java.util.Date lastTime;
|
||||
@Schema(description = "未读数")
|
||||
private Integer unreadCount;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.jeecg.modules.im.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* IM 会话列表 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "IM会话")
|
||||
public class SysImConversationVO {
|
||||
|
||||
@Schema(description = "会话ID")
|
||||
private String conversationId;
|
||||
@Schema(description = "会话类型")
|
||||
private String convType;
|
||||
@Schema(description = "最后消息摘要")
|
||||
private String lastContent;
|
||||
@Schema(description = "最后消息时间")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date lastTime;
|
||||
@Schema(description = "未读数")
|
||||
private Integer unreadCount;
|
||||
@Schema(description = "对方用户ID")
|
||||
private String targetUserId;
|
||||
@Schema(description = "对方姓名")
|
||||
private String targetRealname;
|
||||
@Schema(description = "对方账号")
|
||||
private String targetUsername;
|
||||
@Schema(description = "对方头像")
|
||||
private String targetAvatar;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.jeecg.modules.im.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* IM 消息 VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "IM消息")
|
||||
public class SysImMessageVO {
|
||||
|
||||
@Schema(description = "消息ID")
|
||||
private String id;
|
||||
@Schema(description = "会话ID")
|
||||
private String conversationId;
|
||||
@Schema(description = "发送人ID")
|
||||
private String senderId;
|
||||
@Schema(description = "发送人姓名")
|
||||
private String senderName;
|
||||
@Schema(description = "发送人头像")
|
||||
private String senderAvatar;
|
||||
@Schema(description = "消息内容")
|
||||
private String content;
|
||||
@Schema(description = "消息类型")
|
||||
private String msgType;
|
||||
@Schema(description = "是否本人发送")
|
||||
private Boolean mine;
|
||||
@Schema(description = "发送时间")
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -102,9 +102,17 @@ public interface SysUserDepartMapper extends BaseMapper<SysUserDepart>{
|
||||
|
||||
/**
|
||||
* 通过用户id集合获取用户id和部门code
|
||||
*
|
||||
* @param userIdList
|
||||
* @return
|
||||
*/
|
||||
List<SysUserSysDepPostModel> getUserDepPostByUserIds(@Param("userIdList") List<String> userIdList);
|
||||
|
||||
/**
|
||||
* 查询同部门用户(精确 orgCode,不含下级部门)
|
||||
*/
|
||||
List<SysUser> querySameDepartUserList(@Param("orgCode") String orgCode, @Param("excludeUserId") String excludeUserId,
|
||||
@Param("tenantId") Integer tenantId, @Param("keyword") String keyword);
|
||||
|
||||
/**
|
||||
* 用户是否属于指定 orgCode 部门
|
||||
*/
|
||||
int countUserInDepartOrgCode(@Param("userId") String userId, @Param("orgCode") String orgCode, @Param("tenantId") Integer tenantId);
|
||||
}
|
||||
|
||||
@@ -142,4 +142,40 @@
|
||||
#{userId}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<!-- IM聊天:同部门用户(精确 orgCode) -->
|
||||
<select id="querySameDepartUserList" resultType="org.jeecg.modules.system.entity.SysUser">
|
||||
select distinct a.* from sys_user a
|
||||
inner join sys_user_depart b on b.user_id = a.id
|
||||
inner join sys_depart c on b.dep_id = c.id
|
||||
where a.del_flag = 0 and a.status = 1
|
||||
and c.org_code = #{orgCode}
|
||||
and a.username != '_reserve_user_external'
|
||||
<if test="excludeUserId != null and excludeUserId != ''">
|
||||
and a.id != #{excludeUserId}
|
||||
</if>
|
||||
<if test="tenantId != null and tenantId > 0">
|
||||
and a.id in (
|
||||
select user_id from sys_user_tenant where tenant_id = #{tenantId} and status = '1'
|
||||
)
|
||||
</if>
|
||||
<if test="keyword != null and keyword != ''">
|
||||
<bind name="bindKeyword" value="'%' + keyword + '%'"/>
|
||||
and (a.username like #{bindKeyword} or a.realname like #{bindKeyword})
|
||||
</if>
|
||||
order by a.sort, a.realname
|
||||
</select>
|
||||
|
||||
<select id="countUserInDepartOrgCode" resultType="int">
|
||||
select count(1) from sys_user a
|
||||
inner join sys_user_depart b on b.user_id = a.id
|
||||
inner join sys_depart c on b.dep_id = c.id
|
||||
where a.del_flag = 0 and a.id = #{userId}
|
||||
and c.org_code = #{orgCode}
|
||||
<if test="tenantId != null and tenantId > 0">
|
||||
and a.id in (
|
||||
select user_id from sys_user_tenant where tenant_id = #{tenantId} and status = '1'
|
||||
)
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,70 @@
|
||||
-- 租户 IM 聊天:表结构 + 我的租户菜单
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sys_im_conversation` (
|
||||
`id` varchar(32) NOT NULL COMMENT '主键',
|
||||
`conv_type` varchar(10) NOT NULL DEFAULT 'single' COMMENT '会话类型 single单聊',
|
||||
`user_pair_key` varchar(80) DEFAULT NULL COMMENT '单聊唯一键(较小userId_较大userId)',
|
||||
`tenant_id` int DEFAULT NULL COMMENT '租户ID',
|
||||
`last_content` varchar(500) DEFAULT NULL COMMENT '最后一条消息摘要',
|
||||
`last_time` datetime DEFAULT NULL COMMENT '最后消息时间',
|
||||
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_im_conv_pair` (`tenant_id`, `user_pair_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='IM会话表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sys_im_conversation_member` (
|
||||
`id` varchar(32) NOT NULL COMMENT '主键',
|
||||
`conversation_id` varchar(32) NOT NULL COMMENT '会话ID',
|
||||
`user_id` varchar(32) NOT NULL COMMENT '用户ID',
|
||||
`unread_count` int NOT NULL DEFAULT 0 COMMENT '未读数',
|
||||
`last_read_time` datetime DEFAULT NULL COMMENT '最后已读时间',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_im_member` (`conversation_id`, `user_id`),
|
||||
KEY `idx_im_member_user` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='IM会话成员表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sys_im_message` (
|
||||
`id` varchar(32) NOT NULL COMMENT '主键',
|
||||
`conversation_id` varchar(32) NOT NULL COMMENT '会话ID',
|
||||
`sender_id` varchar(32) NOT NULL COMMENT '发送人ID',
|
||||
`content` text COMMENT '消息内容',
|
||||
`msg_type` varchar(20) NOT NULL DEFAULT 'text' COMMENT '消息类型 text/image/file',
|
||||
`tenant_id` int DEFAULT NULL COMMENT '租户ID',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '发送时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_im_msg_conv_time` (`conversation_id`, `create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='IM消息表';
|
||||
|
||||
INSERT IGNORE INTO `sys_permission` (
|
||||
`id`, `parent_id`, `name`, `url`, `component`, `is_route`, `component_name`, `redirect`,
|
||||
`menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_leaf`, `keep_alive`,
|
||||
`hidden`, `hide_tab`, `description`, `create_by`, `create_time`, `update_by`, `update_time`,
|
||||
`del_flag`, `rule_flag`, `status`, `internal_or_external`
|
||||
) VALUES (
|
||||
'1995000000000000110', '1674708136602542082', 'IM聊天', '/my/ImChat',
|
||||
'system/im/ImChat', 1, 'ImChat', NULL,
|
||||
1, NULL, '0', 1.10, 0, 'ant-design:message-outlined', 0, 1,
|
||||
0, 0, '租户内用户即时聊天', 'admin', NOW(), 'admin', NOW(),
|
||||
0, 0, '1', 0
|
||||
);
|
||||
|
||||
INSERT IGNORE INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
VALUES ('1995000000000000111', '1995000000000000110', '查询', 2, 'sys:im:chat:list', '1', 1.00, 0, 1, 0, '1', 0, 'admin', NOW());
|
||||
|
||||
INSERT IGNORE INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
VALUES ('1995000000000000112', '1995000000000000110', '发送', 2, 'sys:im:chat:send', '1', 2.00, 0, 1, 0, '1', 0, 'admin', NOW());
|
||||
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, p.id, NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r
|
||||
CROSS JOIN `sys_permission` p
|
||||
WHERE r.`role_code` = 'admin'
|
||||
AND p.`id` IN ('1995000000000000110', '1995000000000000111', '1995000000000000112')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.`id` AND rp.`permission_id` = p.`id`
|
||||
);
|
||||
Reference in New Issue
Block a user