Compare commits
31 Commits
master
...
feature/im
Author | SHA1 | Date |
---|---|---|
|
da92b5a582 | |
|
b49339d08b | |
|
1f58fd2be4 | |
|
d67362043e | |
|
29a3ad42b6 | |
|
40be6ed727 | |
|
788c24dff4 | |
|
b540f8d46d | |
|
dd272cb54a | |
|
267477c973 | |
|
2c5287a9b2 | |
|
f1731e4446 | |
|
dc28c7e8a2 | |
|
bada82f8cc | |
|
651619d5ef | |
|
fa23ce144d | |
|
2b891cb432 | |
|
84cea03752 | |
|
51e724fc90 | |
|
4015b2f213 | |
|
0e79d8ec53 | |
|
9c1764e36e | |
|
04123e5987 | |
|
354fe6fcab | |
|
9cee1b3ceb | |
|
f694825435 | |
|
ff88d53b3b | |
|
77f3131ef3 | |
|
2d052ea752 | |
|
3696b666f4 | |
|
bb0b7056cf |
1
pom.xml
1
pom.xml
|
@ -25,6 +25,7 @@
|
||||||
<!-- <module>yudao-module-erp</module>-->
|
<!-- <module>yudao-module-erp</module>-->
|
||||||
<!-- <module>yudao-module-ai</module>-->
|
<!-- <module>yudao-module-ai</module>-->
|
||||||
<!-- <module>yudao-module-iot</module>-->
|
<!-- <module>yudao-module-iot</module>-->
|
||||||
|
<module>yudao-module-im</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"local": {
|
"local": {
|
||||||
"baseUrl": "http://127.0.0.1:48080/admin-api",
|
"baseUrl": "http://127.0.0.1:48080/admin-api",
|
||||||
"token": "test1",
|
"token": "test100",
|
||||||
"adminTenentId": "1",
|
"adminTenentId": "1",
|
||||||
|
|
||||||
"appApi": "http://127.0.0.1:48080/app-api",
|
"appApi": "http://127.0.0.1:48080/app-api",
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
Navicat Premium Data Transfer
|
||||||
|
|
||||||
|
Source Server : mysql8_root
|
||||||
|
Source Server Type : MySQL
|
||||||
|
Source Server Version : 80200
|
||||||
|
Source Host : chaojiniu.top:23306
|
||||||
|
Source Schema : ruoyi-vue-pro
|
||||||
|
|
||||||
|
Target Server Type : MySQL
|
||||||
|
Target Server Version : 80200
|
||||||
|
File Encoding : 65001
|
||||||
|
|
||||||
|
Date: 16/03/2024 15:45:29
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for im_conversation
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `im_conversation`;
|
||||||
|
CREATE TABLE `im_conversation` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||||
|
`user_id` bigint NOT NULL COMMENT '所属用户',
|
||||||
|
`conversation_type` tinyint NOT NULL COMMENT '类型:1 单聊;2 群聊;4 通知会话(预留)',
|
||||||
|
`target_id` bigint NOT NULL COMMENT '单聊时,用户编号;群聊时,群编号',
|
||||||
|
`no` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '会话标志 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId 群聊:g_groupId',
|
||||||
|
`pinned` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否置顶 0否 1是',
|
||||||
|
`last_read_time` datetime DEFAULT NULL COMMENT '最后已读时间',
|
||||||
|
`creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会话表';
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for im_group
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `im_group`;
|
||||||
|
CREATE TABLE `im_group` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||||
|
`group_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '群名字',
|
||||||
|
`owner_id` bigint NOT NULL COMMENT '群主id',
|
||||||
|
`head_image` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '群头像',
|
||||||
|
`head_image_thumb` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '群头像缩略图',
|
||||||
|
`notice` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '群公告',
|
||||||
|
`remark` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '群备注',
|
||||||
|
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='群';
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for im_group_member
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `im_group_member`;
|
||||||
|
CREATE TABLE `im_group_member` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||||
|
`group_id` bigint DEFAULT NULL COMMENT '群 id',
|
||||||
|
`user_id` bigint NOT NULL COMMENT '用户id',
|
||||||
|
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '昵称',
|
||||||
|
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像',
|
||||||
|
`alias_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '组内显示名称',
|
||||||
|
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
|
||||||
|
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='群成员';
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for im_inbox
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `im_inbox`;
|
||||||
|
CREATE TABLE `im_inbox` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||||
|
`user_id` bigint NOT NULL COMMENT '用户编号',
|
||||||
|
`message_id` bigint NOT NULL COMMENT '消息编号',
|
||||||
|
`sequence` bigint NOT NULL COMMENT '序号,按照 user 递增',
|
||||||
|
`creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='收件箱表';
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for im_message
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `im_message`;
|
||||||
|
CREATE TABLE `im_message` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||||
|
`client_message_id` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端消息编号 uuid,用于排重',
|
||||||
|
`sender_id` bigint NOT NULL COMMENT '发送人编号',
|
||||||
|
`receiver_id` bigint NOT NULL COMMENT '接收人编号',
|
||||||
|
`sender_nickname` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送人昵称',
|
||||||
|
`sender_avatar` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送人头像',
|
||||||
|
`conversation_type` tinyint NOT NULL COMMENT '会话类型',
|
||||||
|
`conversation_no` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '会话标志',
|
||||||
|
`content_type` tinyint NOT NULL COMMENT '消息类型',
|
||||||
|
`content` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息内容',
|
||||||
|
`send_time` datetime DEFAULT NULL COMMENT '发送时间',
|
||||||
|
`send_from` tinyint NOT NULL COMMENT '消息来源 100-用户发送;200-系统发送(一般是通知);',
|
||||||
|
`message_status` tinyint DEFAULT NULL COMMENT '消息状态',
|
||||||
|
`creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消息表';
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
|
@ -76,7 +76,7 @@ public class JsonWebSocketMessageHandler extends TextWebSocketHandler {
|
||||||
Long tenantId = WebSocketFrameworkUtils.getTenantId(session);
|
Long tenantId = WebSocketFrameworkUtils.getTenantId(session);
|
||||||
TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj));
|
TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj));
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload());
|
log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modules>
|
||||||
|
<module>yudao-module-im-api</module>
|
||||||
|
<module>yudao-module-im-biz</module>
|
||||||
|
</modules>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>yudao-module-im</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>
|
||||||
|
<!-- TODO 芋艿:这块后面再调整下 -->
|
||||||
|
im 模块,主要提供能力:
|
||||||
|
1. 通讯能力,例如:消息发送、消息接收、消息撤回、消息已读等。
|
||||||
|
2. 通讯会话,例如:单聊、群聊、聊天室等。
|
||||||
|
3. 通讯消息,例如:文本、图片、语音、视频、文件等。
|
||||||
|
4. 通讯消息的存储,例如:消息存储、消息索引、消息搜索等。
|
||||||
|
5. 通讯消息的推送,例如:消息推送、消息通知等。
|
||||||
|
6. 通讯消息的安全,例如:消息加密、消息签名等。
|
||||||
|
</description>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-module-im</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>yudao-module-im-api</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>
|
||||||
|
im 模块 API,暴露给其它模块调用
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,5 @@
|
||||||
|
/**
|
||||||
|
* @author anhaohao
|
||||||
|
* @since 2024/3/9 下午8:59
|
||||||
|
*/
|
||||||
|
package cn.iocoder.yudao.module.im.api;
|
|
@ -0,0 +1,28 @@
|
||||||
|
package cn.iocoder.yudao.module.im.enums;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 错误码枚举类
|
||||||
|
* <p>
|
||||||
|
* im 系统,使用 1-040-000-000 段
|
||||||
|
*/
|
||||||
|
public interface ErrorCodeConstants {
|
||||||
|
|
||||||
|
// ========== 会话 (1-040-100-000) ==========
|
||||||
|
ErrorCode CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_100_000, "会话不存在");
|
||||||
|
|
||||||
|
// ========== 收件箱 (1-040-200-000) ==========
|
||||||
|
ErrorCode INBOX_NOT_EXISTS = new ErrorCode(1_040_200_000, "收件箱不存在");
|
||||||
|
|
||||||
|
// ========== 消息 (1-040-300-000) ==========
|
||||||
|
ErrorCode MESSAGE_NOT_EXISTS = new ErrorCode(1_040_300_000, "消息不存在");
|
||||||
|
ErrorCode MESSAGE_RECEIVER_NOT_EXISTS = new ErrorCode(1_040_300_001, "接收人不存在");
|
||||||
|
|
||||||
|
// ========== 群 (1-040-400-000) ==========
|
||||||
|
ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1_040_400_000, "群不存在");
|
||||||
|
|
||||||
|
// ========== 群成员 (1-040-500-000) ==========
|
||||||
|
ErrorCode GROUP_MEMBER_NOT_EXISTS = new ErrorCode(1_040_500_000, "群成员不存在");
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package cn.iocoder.yudao.module.im.enums.conversation;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 会话类型枚举
|
||||||
|
* 参考 <a href="https://doc.rentsoft.cn/zh-Hans/sdks/enum/conversationType">“会话类型”</a> 文档
|
||||||
|
*
|
||||||
|
* @author anhaohao
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ImConversationTypeEnum implements IntArrayValuable {
|
||||||
|
|
||||||
|
SINGLE(1, "单聊"),
|
||||||
|
GROUP(3, "群聊"),
|
||||||
|
NOTIFICATION(4, "通知会话");
|
||||||
|
|
||||||
|
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ImConversationTypeEnum::getType).toArray();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
private final Integer type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名字
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成会话编号
|
||||||
|
*
|
||||||
|
* @param fromUserId 发送者编号
|
||||||
|
* @param receiverId 接收者编号
|
||||||
|
* @param conversationType 会话类型
|
||||||
|
* @return 会话编号
|
||||||
|
*/
|
||||||
|
public static String generateConversationNo(Long fromUserId, Long receiverId, Integer conversationType) {
|
||||||
|
final String SINGLE_PREFIX = "s_";
|
||||||
|
final String GROUP_PREFIX = "g_";
|
||||||
|
|
||||||
|
if (ImConversationTypeEnum.SINGLE.getType().equals(conversationType)) {
|
||||||
|
long minId = Math.min(fromUserId, receiverId);
|
||||||
|
long maxId = Math.max(fromUserId, receiverId);
|
||||||
|
return SINGLE_PREFIX + minId + "_" + maxId;
|
||||||
|
|
||||||
|
} else if (ImConversationTypeEnum.GROUP.getType().equals(conversationType)) {
|
||||||
|
return GROUP_PREFIX + receiverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] array() {
|
||||||
|
return ARRAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package cn.iocoder.yudao.module.im.enums.message;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 消息的类型枚举
|
||||||
|
* <p>
|
||||||
|
* 参考 <a href="https://doc.rentsoft.cn/zh-Hans/sdks/enum/messageContentType">“消息类型”</a> 文档
|
||||||
|
*
|
||||||
|
* @author anhaohao
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ImMessageContentTypeEnum implements IntArrayValuable {
|
||||||
|
|
||||||
|
TEXT(101, "文本消息"),
|
||||||
|
PICTURE(102, "图片消息"),
|
||||||
|
VOICE(103, "语音消息"),
|
||||||
|
VIDEO(104, "视频消息"),
|
||||||
|
FILE(105, "文件消息"),
|
||||||
|
AT_TEXT(106, "@消息"),
|
||||||
|
MERGE(107, "合并消息"),
|
||||||
|
CARD(108, "名片消息"),
|
||||||
|
LOCATION(109, "位置消息"),
|
||||||
|
CUSTOM(110, "自定义消息"),
|
||||||
|
REVOKE_RECEIPT(111, "撤回消息回执"),
|
||||||
|
C2C_RECEIPT(112, "单聊消息回执"),
|
||||||
|
TYPING(113, "输入状态"),
|
||||||
|
QUOTE(114, "引用消息"),
|
||||||
|
FACE(115, "表情消息"),
|
||||||
|
ADVANCED_REVOKE(118, "高级撤回消息"),
|
||||||
|
|
||||||
|
// ========== 好友通知 1200-1299 ===========
|
||||||
|
FRIEND_ADDED(1201, "双方成为好友通知"),
|
||||||
|
|
||||||
|
// ========== 系统通知 1400 ==========
|
||||||
|
OA_NOTIFICATION(1400, "系统通知"),
|
||||||
|
|
||||||
|
// ========== 群相关 1500-1599 ==========
|
||||||
|
GROUP_CREATED(1501, "群创建通知"),
|
||||||
|
GROUP_INFO_CHANGED(1502, "群信息改变通知"),
|
||||||
|
MEMBER_QUIT(1504, "群成员退出通知"),
|
||||||
|
GROUP_OWNER_CHANGED(1507, "群主更换通知"),
|
||||||
|
MEMBER_KICKED(1508, "群成员被踢通知"),
|
||||||
|
MEMBER_INVITED(1509, "邀请群成员通知"),
|
||||||
|
MEMBER_ENTER(1510, "群成员进群通知"),
|
||||||
|
GROUP_DISMISSED(1511, "解散群通知"),
|
||||||
|
GROUP_MEMBER_MUTED(1512, "群成员禁言通知"),
|
||||||
|
GROUP_MEMBER_CANCEL_MUTED(1513, "取消群成员禁言通知"),
|
||||||
|
GROUP_MUTED(1514, "群禁言通知"),
|
||||||
|
GROUP_CANCEL_MUTED(1515, "取消群禁言通知"),
|
||||||
|
GROUP_ANNOUNCEMENT_UPDATED(1519, "群公告改变通知"),
|
||||||
|
GROUP_NAME_UPDATED(1520, "群名称改变通知"),
|
||||||
|
|
||||||
|
// TODO 芋艿:其它
|
||||||
|
BURN_CHANGE(1701, "阅后即焚开启或关闭通知"),
|
||||||
|
REVOKE(2101, "撤回消息通知");;
|
||||||
|
|
||||||
|
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ImMessageContentTypeEnum::getType).toArray();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
private final Integer type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名字
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] array() {
|
||||||
|
return ARRAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package cn.iocoder.yudao.module.im.enums.message;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 消息的消息来源
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum ImMessageSourceEnum implements IntArrayValuable {
|
||||||
|
|
||||||
|
USER_SEND(100, "用户发送"),
|
||||||
|
SYSTEM_SEND(200, "系统发送");
|
||||||
|
|
||||||
|
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ImMessageSourceEnum::getSource).toArray();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
private final Integer source;
|
||||||
|
/**
|
||||||
|
* 名字
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] array() {
|
||||||
|
return ARRAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package cn.iocoder.yudao.module.im.enums.message;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 消息的状态枚举
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum ImMessageStatusEnum implements IntArrayValuable {
|
||||||
|
|
||||||
|
DRAFT(0, "草稿"),
|
||||||
|
SENDING(1, "发送中"),
|
||||||
|
SUCCESS(2, "发送成功"),
|
||||||
|
FAILURE(3, "发送失败"),
|
||||||
|
DELETED(4, "已删除"),
|
||||||
|
RECALL(5, "已撤回");
|
||||||
|
|
||||||
|
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ImMessageStatusEnum::getStatus).toArray();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
private final Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名字
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] array() {
|
||||||
|
return ARRAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>yudao-module-im</artifactId>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<artifactId>yudao-module-im-biz</artifactId>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>
|
||||||
|
im 模块,主要实现 im 模块的业务逻辑。
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-module-im-api</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Web 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- DB 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 工具类相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-excel</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test 测试相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,81 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.conversation;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationCreateReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationUpdateLastReadTimeReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationRespVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationUpdatePinnedReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationDO;
|
||||||
|
import cn.iocoder.yudao.module.im.service.conversation.ImConversationService;
|
||||||
|
import cn.iocoder.yudao.module.im.service.message.ImMessageService;
|
||||||
|
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||||
|
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||||
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||||
|
|
||||||
|
@Tag(name = "管理后台 - IM 会话")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/im/conversation")
|
||||||
|
@Validated
|
||||||
|
public class ImConversationController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ImConversationService imConversationService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ImMessageService imMessageService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
AdminUserApi adminUserApi;
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@Operation(summary = "获得用户的会话列表")
|
||||||
|
public CommonResult<List<ImConversationRespVO>> getConversationList() {
|
||||||
|
List<ImConversationDO> conversationList = imConversationService.getConversationList(getLoginUserId());
|
||||||
|
List<ImConversationRespVO> imConversationRespVOList = BeanUtils.toBean(conversationList, ImConversationRespVO.class);
|
||||||
|
// TODO @dylan:这块,交给前端聚合哈。im 这块,我们重前端,后端更多解决消息的通信和存储。
|
||||||
|
imConversationRespVOList.forEach(item -> {
|
||||||
|
// 处理个人图像和昵称
|
||||||
|
Long receiverId = item.getTargetId();
|
||||||
|
AdminUserRespDTO receiverUser = adminUserApi.getUser(receiverId);
|
||||||
|
if (receiverUser != null) {
|
||||||
|
item.setAvatar(receiverUser.getAvatar());
|
||||||
|
item.setNickname(receiverUser.getNickname());
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
return success(imConversationRespVOList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/update-pinned")
|
||||||
|
@Operation(summary = "置顶会话")
|
||||||
|
public CommonResult<Boolean> updatePinned(@Valid @RequestBody ImConversationUpdatePinnedReqVO updateReqVO) {
|
||||||
|
imConversationService.updatePinned(getLoginUserId(),updateReqVO);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/update-last-read-time")
|
||||||
|
@Operation(summary = "更新最后已读时间")
|
||||||
|
public CommonResult<Boolean> updateLastReadTime(@Valid @RequestBody ImConversationUpdateLastReadTimeReqVO updateReqVO) {
|
||||||
|
imConversationService.updateLastReadTime(getLoginUserId(),updateReqVO);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
@Operation(summary = "创建会话")
|
||||||
|
public CommonResult<ImConversationDO> createConversation(@Valid @RequestBody ImConversationCreateReqVO createReqVO) {
|
||||||
|
ImConversationDO conversation = imConversationService.createConversation(getLoginUserId(), createReqVO);
|
||||||
|
return success(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.conversation.vo;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 会话最后已读时间 Request VO")
|
||||||
|
@Data
|
||||||
|
public class ImConversationCreateReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "聊天对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||||
|
private Long targetId;
|
||||||
|
|
||||||
|
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@InEnum(value = ImConversationTypeEnum.class, message = "会话类型必须是 {value}")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.conversation.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 会话 Response VO")
|
||||||
|
@Data
|
||||||
|
public class ImConversationRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13905")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@Schema(description = "聊天对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||||
|
private Long targetId;
|
||||||
|
|
||||||
|
@Schema(description = "会话标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "s_1_2")
|
||||||
|
private String no;
|
||||||
|
|
||||||
|
@Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
private Boolean pinned;
|
||||||
|
|
||||||
|
@Schema(description = "最后已读时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-03-01 00:00:00")
|
||||||
|
private LocalDateTime lastReadTime;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
// TODO @dylan:这块,交给前端聚合哈。im 这块,我们重前端,后端更多解决消息的通信和存储。
|
||||||
|
// 1. 基础信息:根据会话类型,查询会话接受者的头像、昵称
|
||||||
|
// 2. 未读信息:前端自己增量拉取,基于本地 db 查看
|
||||||
|
|
||||||
|
@Schema(description = "会话接受者头像", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "会话接受者昵称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
@Schema(description = "最后一条消息的描述", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String lastMessageDescription;
|
||||||
|
|
||||||
|
@Schema(description = "未读消息条数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String unreadMessagesCount;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.conversation.vo;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 会话最后已读时间 Request VO")
|
||||||
|
@Data
|
||||||
|
public class ImConversationUpdateLastReadTimeReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "最后已读时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-03-01 00:00:00")
|
||||||
|
@NotNull(message = "最后已读时间不能为空")
|
||||||
|
private LocalDateTime lastReadTime;
|
||||||
|
|
||||||
|
@Schema(description = "聊天对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||||
|
private Long targetId;
|
||||||
|
|
||||||
|
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@InEnum(value = ImConversationTypeEnum.class, message = "会话类型必须是 {value}")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.conversation.vo;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 会话置顶 Request VO")
|
||||||
|
@Data
|
||||||
|
public class ImConversationUpdatePinnedReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@NotNull(message = "是否置顶不能为空")
|
||||||
|
private Boolean pinned;
|
||||||
|
|
||||||
|
@Schema(description = "聊天对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||||
|
private Long targetId;
|
||||||
|
|
||||||
|
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@InEnum(value = ImConversationTypeEnum.class, message = "会话类型必须是 {value}")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.group;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
|
||||||
|
import jakarta.validation.*;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
|
||||||
|
import cn.iocoder.yudao.module.im.service.group.ImGroupService;
|
||||||
|
|
||||||
|
// TODO @芋艿:得看看 create、update、delete、get、page 这几个接口,要保留哪些
|
||||||
|
@Tag(name = "管理后台 - 群")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/im/group")
|
||||||
|
@Validated
|
||||||
|
public class ImGroupController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ImGroupService imGroupService;
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
@Operation(summary = "创建群")
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group:create')")
|
||||||
|
public CommonResult<Long> createGroup(@Valid @RequestBody ImGroupSaveReqVO createReqVO) {
|
||||||
|
return success(imGroupService.createGroup(createReqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/update")
|
||||||
|
@Operation(summary = "更新群")
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group:update')")
|
||||||
|
public CommonResult<Boolean> updateGroup(@Valid @RequestBody ImGroupSaveReqVO updateReqVO) {
|
||||||
|
imGroupService.updateGroup(updateReqVO);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/delete")
|
||||||
|
@Operation(summary = "删除群")
|
||||||
|
@Parameter(name = "id", description = "编号", required = true)
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group:delete')")
|
||||||
|
public CommonResult<Boolean> deleteGroup(@RequestParam("id") Long id) {
|
||||||
|
imGroupService.deleteGroup(id);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/get")
|
||||||
|
@Operation(summary = "获得群")
|
||||||
|
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group:query')")
|
||||||
|
public CommonResult<ImGroupRespVO> getGroup(@RequestParam("id") Long id) {
|
||||||
|
ImGroupDO group = imGroupService.getGroup(id);
|
||||||
|
return success(BeanUtils.toBean(group, ImGroupRespVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/page")
|
||||||
|
@Operation(summary = "获得群分页")
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group:query')")
|
||||||
|
public CommonResult<PageResult<ImGroupRespVO>> getGroupPage(@Valid ImGroupPageReqVO pageReqVO) {
|
||||||
|
PageResult<ImGroupDO> pageResult = imGroupService.getGroupPage(pageReqVO);
|
||||||
|
return success(BeanUtils.toBean(pageResult, ImGroupRespVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.group.vo;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 群分页 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class ImGroupPageReqVO extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "群名字", example = "芋艿")
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
@Schema(description = "群主id", example = "31460")
|
||||||
|
private Long ownerId;
|
||||||
|
|
||||||
|
@Schema(description = "群头像")
|
||||||
|
private String headImage;
|
||||||
|
|
||||||
|
@Schema(description = "群头像缩略图")
|
||||||
|
private String headImageThumb;
|
||||||
|
|
||||||
|
@Schema(description = "群公告")
|
||||||
|
private String notice;
|
||||||
|
|
||||||
|
@Schema(description = "群备注", example = "你说的对")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||||
|
private LocalDateTime[] createTime;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.group.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import com.alibaba.excel.annotation.*;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 群 Response VO")
|
||||||
|
@Data
|
||||||
|
@ExcelIgnoreUnannotated
|
||||||
|
public class ImGroupRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1003")
|
||||||
|
@ExcelProperty("编号")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "群名字", example = "芋艿")
|
||||||
|
@ExcelProperty("群名字")
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
@Schema(description = "群主id", requiredMode = Schema.RequiredMode.REQUIRED, example = "31460")
|
||||||
|
@ExcelProperty("群主id")
|
||||||
|
private Long ownerId;
|
||||||
|
|
||||||
|
@Schema(description = "群头像")
|
||||||
|
@ExcelProperty("群头像")
|
||||||
|
private String headImage;
|
||||||
|
|
||||||
|
@Schema(description = "群头像缩略图")
|
||||||
|
@ExcelProperty("群头像缩略图")
|
||||||
|
private String headImageThumb;
|
||||||
|
|
||||||
|
@Schema(description = "群公告")
|
||||||
|
@ExcelProperty("群公告")
|
||||||
|
private String notice;
|
||||||
|
|
||||||
|
@Schema(description = "群备注", example = "你说的对")
|
||||||
|
@ExcelProperty("群备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@ExcelProperty("创建时间")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.group.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.*;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 群新增/修改 Request VO")
|
||||||
|
@Data
|
||||||
|
public class ImGroupSaveReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1003")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "群名字", example = "芋艿")
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
@Schema(description = "群主id", requiredMode = Schema.RequiredMode.REQUIRED, example = "31460")
|
||||||
|
@NotNull(message = "群主id不能为空")
|
||||||
|
private Long ownerId;
|
||||||
|
|
||||||
|
@Schema(description = "群头像")
|
||||||
|
private String headImage;
|
||||||
|
|
||||||
|
@Schema(description = "群头像缩略图")
|
||||||
|
private String headImageThumb;
|
||||||
|
|
||||||
|
@Schema(description = "群公告")
|
||||||
|
private String notice;
|
||||||
|
|
||||||
|
@Schema(description = "群备注", example = "你说的对")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.groupmember;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
|
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberPageReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberRespVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberSaveReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
|
||||||
|
import cn.iocoder.yudao.module.im.service.groupmember.ImGroupMemberService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||||
|
|
||||||
|
// TODO @芋艿:得看看 create、update、delete、get、page 这几个接口,要保留哪些
|
||||||
|
@Tag(name = "管理后台 - 群成员")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/im/group-member")
|
||||||
|
@Validated
|
||||||
|
public class ImGroupMemberController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ImGroupMemberService imGroupMemberService;
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
@Operation(summary = "创建群成员")
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group-member:create')")
|
||||||
|
public CommonResult<Long> createGroupMember(@Valid @RequestBody ImGroupMemberSaveReqVO createReqVO) {
|
||||||
|
return success(imGroupMemberService.createGroupMember(createReqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/update")
|
||||||
|
@Operation(summary = "更新群成员")
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group-member:update')")
|
||||||
|
public CommonResult<Boolean> updateGroupMember(@Valid @RequestBody ImGroupMemberSaveReqVO updateReqVO) {
|
||||||
|
imGroupMemberService.updateGroupMember(updateReqVO);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/delete")
|
||||||
|
@Operation(summary = "删除群成员")
|
||||||
|
@Parameter(name = "id", description = "编号", required = true)
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group-member:delete')")
|
||||||
|
public CommonResult<Boolean> deleteGroupMember(@RequestParam("id") Long id) {
|
||||||
|
imGroupMemberService.deleteGroupMember(id);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/get")
|
||||||
|
@Operation(summary = "获得群成员")
|
||||||
|
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group-member:query')")
|
||||||
|
public CommonResult<ImGroupMemberRespVO> getGroupMember(@RequestParam("id") Long id) {
|
||||||
|
ImGroupMemberDO groupMember = imGroupMemberService.getGroupMember(id);
|
||||||
|
return success(BeanUtils.toBean(groupMember, ImGroupMemberRespVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/page")
|
||||||
|
@Operation(summary = "获得群成员分页")
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group-member:query')")
|
||||||
|
public CommonResult<PageResult<ImGroupMemberRespVO>> getGroupMemberPage(@Valid ImGroupMemberPageReqVO pageReqVO) {
|
||||||
|
PageResult<ImGroupMemberDO> pageResult = imGroupMemberService.getGroupMemberPage(pageReqVO);
|
||||||
|
return success(BeanUtils.toBean(pageResult, ImGroupMemberRespVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/export-excel")
|
||||||
|
@Operation(summary = "导出群成员 Excel")
|
||||||
|
@PreAuthorize("@ss.hasPermission('im:group-member:export')")
|
||||||
|
public void exportGroupMemberExcel(@Valid ImGroupMemberPageReqVO pageReqVO,
|
||||||
|
HttpServletResponse response) throws IOException {
|
||||||
|
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||||
|
List<ImGroupMemberDO> list = imGroupMemberService.getGroupMemberPage(pageReqVO).getList();
|
||||||
|
// 导出 Excel
|
||||||
|
ExcelUtils.write(response, "群成员.xls", "数据", ImGroupMemberRespVO.class,
|
||||||
|
BeanUtils.toBean(list, ImGroupMemberRespVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.groupmember.vo;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 群成员分页 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class ImGroupMemberPageReqVO extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "群 id", example = "13279")
|
||||||
|
private Long groupId;
|
||||||
|
|
||||||
|
@Schema(description = "用户id", example = "21730")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "昵称", example = "芋艿")
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
@Schema(description = "头像")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "组内显示名称", example = "芋艿")
|
||||||
|
private String aliasName;
|
||||||
|
|
||||||
|
@Schema(description = "备注", example = "你说的对")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||||
|
private LocalDateTime[] createTime;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.groupmember.vo;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 群成员 Response VO")
|
||||||
|
@Data
|
||||||
|
@ExcelIgnoreUnannotated
|
||||||
|
public class ImGroupMemberRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17071")
|
||||||
|
@ExcelProperty("编号")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "群 id", example = "13279")
|
||||||
|
@ExcelProperty("群 id")
|
||||||
|
private Long groupId;
|
||||||
|
|
||||||
|
@Schema(description = "用户id", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730")
|
||||||
|
@ExcelProperty("用户id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "昵称", example = "芋艿")
|
||||||
|
@ExcelProperty("昵称")
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
@Schema(description = "头像")
|
||||||
|
@ExcelProperty("头像")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "组内显示名称", example = "芋艿")
|
||||||
|
@ExcelProperty("组内显示名称")
|
||||||
|
private String aliasName;
|
||||||
|
|
||||||
|
@Schema(description = "备注", example = "你说的对")
|
||||||
|
@ExcelProperty("备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@ExcelProperty("创建时间")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.groupmember.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 群成员新增/修改 Request VO")
|
||||||
|
@Data
|
||||||
|
public class ImGroupMemberSaveReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17071")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "群 id", example = "13279")
|
||||||
|
private Long groupId;
|
||||||
|
|
||||||
|
@Schema(description = "用户id", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730")
|
||||||
|
@NotNull(message = "用户id不能为空")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "昵称", example = "芋艿")
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
@Schema(description = "头像")
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Schema(description = "组内显示名称", example = "芋艿")
|
||||||
|
private String aliasName;
|
||||||
|
|
||||||
|
@Schema(description = "备注", example = "你说的对")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
### 请求 /send 接口 => 成功
|
||||||
|
POST {{baseUrl}}/im/message/send
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
Content-Type: application/json
|
||||||
|
tenant-id: {{adminTenentId}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"clientMessageId": "123",
|
||||||
|
"receiverId": 1,
|
||||||
|
"conversationType": 1,
|
||||||
|
"contentType": 101,
|
||||||
|
"content": "你好1"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 请求 /pull 接口 => 成功
|
||||||
|
GET {{baseUrl}}/im/message/pull?sequence=0&size=2
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
tenant-id: {{adminTenentId}}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.message;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.message.vo.*;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import cn.iocoder.yudao.module.im.service.message.ImMessageService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||||
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||||
|
|
||||||
|
@Tag(name = "管理后台 - IM 消息")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/im/message")
|
||||||
|
@Validated
|
||||||
|
public class ImMessageController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ImMessageService imMessageService;
|
||||||
|
|
||||||
|
@PostMapping("/send")
|
||||||
|
@Operation(summary = "发送消息")
|
||||||
|
public CommonResult<ImMessageSendRespVO> sendMessage(@Valid @RequestBody ImMessageSendReqVO imMessageSendReqVO) {
|
||||||
|
ImMessageDO message = imMessageService.sendMessage(getLoginUserId(), imMessageSendReqVO);
|
||||||
|
return success(BeanUtils.toBean(message, ImMessageSendRespVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pull")
|
||||||
|
@Operation(summary = "消息列表-拉取大于 sequence 的消息列表")
|
||||||
|
@Parameter(name = "sequence", description = "序号", required = true, example = "1")
|
||||||
|
@Parameter(name = "size", description = "条数", required = true, example = "10")
|
||||||
|
public CommonResult<List<ImMessageRespVO>> pullMessageList(@RequestParam("sequence") Long sequence,
|
||||||
|
@RequestParam("size") Integer size) {
|
||||||
|
List<ImMessageDO> messages = imMessageService.pullMessageList(getLoginUserId(), sequence, size);
|
||||||
|
return success(BeanUtils.toBean(messages, ImMessageRespVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@Operation(summary = "消息列表-根据接收人和发送时间进行分页查询")
|
||||||
|
public CommonResult<List<ImMessageRespVO>> getMessageList(@Valid ImMessageListReqVO listReqVO) {
|
||||||
|
|
||||||
|
Long loginUserId = getLoginUserId();
|
||||||
|
List<ImMessageDO> messagePage = imMessageService.getMessageList(listReqVO, loginUserId);
|
||||||
|
return success(BeanUtils.toBean(messagePage, ImMessageRespVO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.message.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
|
||||||
|
|
||||||
|
// TODO @dylan:看看,是不是融合到 ImMessageListReqVO 里
|
||||||
|
@Schema(description = "管理后台 - 消息列表 Request VO")
|
||||||
|
@Data
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class ImMessageListByNoReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-03-27 12:00:00")
|
||||||
|
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
|
||||||
|
private LocalDateTime sendTime;
|
||||||
|
|
||||||
|
@Schema(description = "会话编号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
|
||||||
|
@NotNull(message = "会话编号不能为空")
|
||||||
|
private String conversationNo ;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.message.vo;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 消息列表 Request VO")
|
||||||
|
@Data
|
||||||
|
public class ImMessageListReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
|
||||||
|
@NotNull(message = "接收人编号不能为空")
|
||||||
|
private Long receiverId;
|
||||||
|
|
||||||
|
// TODO @dylan:这个是不是不用传递呀。因为 http 连接,有当前的 userid 呀
|
||||||
|
@Schema(description = "会话所属人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
|
||||||
|
@NotNull(message = "会话所属人编号不能为空")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||||
|
@InEnum(value = ImConversationTypeEnum.class,message = "会话类型必须是 {value}")
|
||||||
|
@NotNull(message = "会话类型不能为空")
|
||||||
|
private Integer conversationType;
|
||||||
|
|
||||||
|
@Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-03-27 12:00:00")
|
||||||
|
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
|
||||||
|
private LocalDateTime sendTime;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.message.vo;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.message.ImMessageContentTypeEnum;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 消息 Response VO")
|
||||||
|
@Data
|
||||||
|
public class ImMessageRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12454")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@InEnum(value = ImConversationTypeEnum.class)
|
||||||
|
private Integer conversationType;
|
||||||
|
|
||||||
|
@Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
private Long senderId;
|
||||||
|
|
||||||
|
@Schema(description = "发送人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||||
|
private String senderNickname;
|
||||||
|
|
||||||
|
@Schema(description = "发送人头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://www.iocoder.cn/xxx.jpg")
|
||||||
|
private String senderAvatar;
|
||||||
|
|
||||||
|
@Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||||
|
private Long receiverId;
|
||||||
|
|
||||||
|
@Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@InEnum(value = ImMessageContentTypeEnum.class)
|
||||||
|
private Integer contentType;
|
||||||
|
|
||||||
|
@Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-03-27 12:00:00")
|
||||||
|
private LocalDateTime sendTime;
|
||||||
|
|
||||||
|
@Schema(description = "序号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
private Long sequence;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.message.vo;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.message.ImMessageContentTypeEnum;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 发送消息 Request VO")
|
||||||
|
@Data
|
||||||
|
public class ImMessageSendReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "客户端消息编号 uuid,用于排重", requiredMode = Schema.RequiredMode.REQUIRED, example = "3331")
|
||||||
|
@NotNull(message = "客户端消息编号不能为空")
|
||||||
|
private String clientMessageId;
|
||||||
|
|
||||||
|
@Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32494")
|
||||||
|
@NotNull(message = "接收人编号不能为空")
|
||||||
|
private Long receiverId;
|
||||||
|
|
||||||
|
@Schema(description = "会话类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||||
|
@InEnum(value = ImConversationTypeEnum.class)
|
||||||
|
@NotNull(message = "会话类型不能为空")
|
||||||
|
private Integer conversationType;
|
||||||
|
|
||||||
|
// TODO @dylan:这个是不是不用传递呀。因为 http 连接,有当前的 userid 呀
|
||||||
|
@Schema(description = "会话所属用户id", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||||
|
@NotNull(message = "会话所属用户id")
|
||||||
|
private Long conversationUserId;
|
||||||
|
|
||||||
|
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@InEnum(ImMessageContentTypeEnum.class)
|
||||||
|
@NotNull(message = "消息类型不能为空")
|
||||||
|
private Integer contentType;
|
||||||
|
|
||||||
|
@Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好")
|
||||||
|
@NotNull(message = "消息内容不能为空")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package cn.iocoder.yudao.module.im.controller.admin.message.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 发送消息 Response VO")
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ImMessageSendRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-03-27 12:00:00")
|
||||||
|
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||||
|
private LocalDateTime sendTime;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.conversation;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 会话 DO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@TableName("im_conversation")
|
||||||
|
@KeySequence("im_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ImConversationDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编号
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属用户
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话类型
|
||||||
|
* <p>
|
||||||
|
* 枚举 {@link ImConversationTypeEnum}
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
/**
|
||||||
|
* 聊天对象编号
|
||||||
|
* <p>
|
||||||
|
* 1. 单聊时,用户编号;
|
||||||
|
* 2. 群聊时,群编号
|
||||||
|
*/
|
||||||
|
private Long targetId;
|
||||||
|
/**
|
||||||
|
* 会话标志
|
||||||
|
*
|
||||||
|
* 1. 单聊:s_{userId}_{targetId},需要排序 userId 和 targetId
|
||||||
|
* 2. 群聊:g_groupId
|
||||||
|
*/
|
||||||
|
private String no;
|
||||||
|
/**
|
||||||
|
* 是否置顶
|
||||||
|
*/
|
||||||
|
private Boolean pinned;
|
||||||
|
/**
|
||||||
|
* 最后已读时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime lastReadTime;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.group;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 群信息 DO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@TableName("im_group")
|
||||||
|
@KeySequence("im_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ImGroupDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编号
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
// TODO @hao:name,如果这个表已经是 group 了,不用在带额外的
|
||||||
|
/**
|
||||||
|
* 群名字
|
||||||
|
*/
|
||||||
|
private String groupName;
|
||||||
|
// TODO @hao:关联字段;
|
||||||
|
/**
|
||||||
|
* 群主编号
|
||||||
|
*/
|
||||||
|
private Long ownerId;
|
||||||
|
// TODO @hao:头像使用 avatar 好了,整个项目统一;然后 Thumb 是不是不用存,这个更多是文件服务做裁剪
|
||||||
|
/**
|
||||||
|
* 群头像
|
||||||
|
*/
|
||||||
|
private String headImage;
|
||||||
|
/**
|
||||||
|
* 群头像缩略图
|
||||||
|
*/
|
||||||
|
private String headImageThumb;
|
||||||
|
/**
|
||||||
|
* 群公告
|
||||||
|
*/
|
||||||
|
private String notice;
|
||||||
|
/**
|
||||||
|
* 群备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.group;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 群成员 DO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@TableName("im_group_member")
|
||||||
|
@KeySequence("im_group_member_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ImGroupMemberDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编号
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
// TODO @hao:groupId 和 userId 都写下关联字段哈
|
||||||
|
/**
|
||||||
|
* 群编号
|
||||||
|
*/
|
||||||
|
private Long groupId;
|
||||||
|
/**
|
||||||
|
* 用户编号
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
// TODO @hao:nickname 和 avatar 是不是不用存储哈;
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
private String avatar;
|
||||||
|
/**
|
||||||
|
* 组内显示名称
|
||||||
|
*/
|
||||||
|
private String aliasName;
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.inbox;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
// TODO 我们要不要改成 ImMessageQueue 队列?从理解上,概念上,可能都更清晰一点哈。每个用户一个消息队列;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 收件箱 DO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@TableName("im_inbox")
|
||||||
|
@KeySequence("im_inbox_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ImInboxDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编号
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
/**
|
||||||
|
* 用户编号
|
||||||
|
* <p>
|
||||||
|
* 关联 {@link ImMessageDO#getSenderId()} 或者 {@link ImMessageDO#getReceiverId()}
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
/**
|
||||||
|
* 消息编号
|
||||||
|
* <p>
|
||||||
|
* 关联 {@link ImMessageDO#getId()}
|
||||||
|
*/
|
||||||
|
private Long messageId;
|
||||||
|
/**
|
||||||
|
* 序号,按照 user 递增
|
||||||
|
*/
|
||||||
|
private Long sequence;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.message;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.message.ImMessageContentTypeEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.message.ImMessageSourceEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
|
||||||
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 消息 DO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@TableName("im_message")
|
||||||
|
@KeySequence("im_message_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ImMessageDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编号
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
/**
|
||||||
|
* 客户端消息编号 uuid,用于排重
|
||||||
|
*/
|
||||||
|
private String clientMessageId;
|
||||||
|
/**
|
||||||
|
* 发送人编号 存储的是用户编号
|
||||||
|
*/
|
||||||
|
private Long senderId;
|
||||||
|
/**
|
||||||
|
* 接收人编号
|
||||||
|
* <p>
|
||||||
|
* 1. 单聊时,用户编号;群聊时,群编号
|
||||||
|
*/
|
||||||
|
private Long receiverId;
|
||||||
|
/**
|
||||||
|
* 消息发送者昵称
|
||||||
|
* <p>
|
||||||
|
* 冗余 AdminUserDO 的 nickname 字段
|
||||||
|
*/
|
||||||
|
private String senderNickname;
|
||||||
|
/**
|
||||||
|
* 消息发送者头像
|
||||||
|
* <p>
|
||||||
|
* 冗余 AdminUserDO 的 avatar 字段
|
||||||
|
*/
|
||||||
|
private String senderAvatar;
|
||||||
|
/**
|
||||||
|
* 会话类型 枚举 {@link ImConversationTypeEnum}
|
||||||
|
*/
|
||||||
|
private Integer conversationType;
|
||||||
|
/**
|
||||||
|
* 会话标志
|
||||||
|
* <p>
|
||||||
|
* 生成规则:{@link ImConversationTypeEnum#generateConversationNo(Long, Long, Integer)} 方法
|
||||||
|
*/
|
||||||
|
private String conversationNo;
|
||||||
|
/**
|
||||||
|
* 消息类型
|
||||||
|
* <p>
|
||||||
|
* 枚举 {@link ImMessageContentTypeEnum}
|
||||||
|
*/
|
||||||
|
private Integer contentType;
|
||||||
|
/**
|
||||||
|
* 消息内容
|
||||||
|
* <p>
|
||||||
|
* JSON 格式 对应 dal/dataobject/message/content 包
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
/**
|
||||||
|
* 发送时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime sendTime;
|
||||||
|
/**
|
||||||
|
* 消息来源
|
||||||
|
* <p>
|
||||||
|
* 枚举 {@link ImMessageSourceEnum}
|
||||||
|
*/
|
||||||
|
private Integer sendFrom;
|
||||||
|
/**
|
||||||
|
* 消息状态
|
||||||
|
* <p>
|
||||||
|
* 枚举 {@link ImMessageStatusEnum}
|
||||||
|
*/
|
||||||
|
private Integer messageStatus;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
// TODO @anhaohao:要有 IM;
|
||||||
|
// TODO @芋艿:后续要挪到 api 包下,主要是给外部接口使用
|
||||||
|
/**
|
||||||
|
* 语音消息的 {@link ImMessageDO 字段 content} 的内容
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class AudioMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音文件唯一 ID
|
||||||
|
*/
|
||||||
|
private String uuid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音文件的本地路径
|
||||||
|
*/
|
||||||
|
private String soundPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音文件下载地址
|
||||||
|
*/
|
||||||
|
private String sourceUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音文件大小
|
||||||
|
*/
|
||||||
|
private int dataSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音时长
|
||||||
|
*/
|
||||||
|
private int duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音文件类型
|
||||||
|
*/
|
||||||
|
private String soundType;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名片消息的 {@link ImMessageDO 字段 content} 的内容
|
||||||
|
*
|
||||||
|
* @author anhaohao
|
||||||
|
* @since 2024/3/23 下午5:53
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class CardMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户 ID
|
||||||
|
*/
|
||||||
|
private String userID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户头像
|
||||||
|
*/
|
||||||
|
private int faceURL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展字段
|
||||||
|
*/
|
||||||
|
private String ex;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件消息的 {@link ImMessageDO 字段 content} 的内容
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class FileMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件名
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* 文件 URL
|
||||||
|
*/
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片消息的 {@link ImMessageDO 字段 content} 的内容
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ImageMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片地址
|
||||||
|
*/
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地址位置消息的 {@link ImMessageDO 字段 content} 的内容
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class LocationMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地理位置
|
||||||
|
* <p>
|
||||||
|
* 例如说:中国 浙江省 杭州市 网商路 599号
|
||||||
|
*/
|
||||||
|
private String address;
|
||||||
|
/**
|
||||||
|
* 经度
|
||||||
|
*/
|
||||||
|
private Double longitude;
|
||||||
|
/**
|
||||||
|
* 纬度
|
||||||
|
*/
|
||||||
|
private Double latitude;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本消息的 {@link ImMessageDO 字段 content} 的内容
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class TextMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本消息的具体内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频消息的 {@link ImMessageDO 字段 content} 的内容
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class VideoMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频地址
|
||||||
|
*/
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.mysql.conversation;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 会话 Mapper
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ImConversationMapper extends BaseMapperX<ImConversationDO> {
|
||||||
|
|
||||||
|
default ImConversationDO selectByNo(String no){
|
||||||
|
return selectOne(ImConversationDO::getNo, no);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.mysql.group;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
|
||||||
|
|
||||||
|
// TODO @hao:这个也要有 Im 前缀
|
||||||
|
/**
|
||||||
|
* 群 Mapper
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ImGroupMapper extends BaseMapperX<ImGroupDO> {
|
||||||
|
|
||||||
|
default PageResult<ImGroupDO> selectPage(ImGroupPageReqVO reqVO) {
|
||||||
|
return selectPage(reqVO, new LambdaQueryWrapperX<ImGroupDO>()
|
||||||
|
.likeIfPresent(ImGroupDO::getGroupName, reqVO.getGroupName())
|
||||||
|
.eqIfPresent(ImGroupDO::getOwnerId, reqVO.getOwnerId())
|
||||||
|
.eqIfPresent(ImGroupDO::getHeadImage, reqVO.getHeadImage())
|
||||||
|
.eqIfPresent(ImGroupDO::getHeadImageThumb, reqVO.getHeadImageThumb())
|
||||||
|
.eqIfPresent(ImGroupDO::getNotice, reqVO.getNotice())
|
||||||
|
.eqIfPresent(ImGroupDO::getRemark, reqVO.getRemark())
|
||||||
|
.betweenIfPresent(ImGroupDO::getCreateTime, reqVO.getCreateTime())
|
||||||
|
.orderByDesc(ImGroupDO::getId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.mysql.groupmember;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberPageReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群成员 Mapper
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ImGroupMemberMapper extends BaseMapperX<ImGroupMemberDO> {
|
||||||
|
|
||||||
|
default PageResult<ImGroupMemberDO> selectPage(ImGroupMemberPageReqVO reqVO) {
|
||||||
|
return selectPage(reqVO, new LambdaQueryWrapperX<ImGroupMemberDO>()
|
||||||
|
.eqIfPresent(ImGroupMemberDO::getGroupId, reqVO.getGroupId())
|
||||||
|
.eqIfPresent(ImGroupMemberDO::getUserId, reqVO.getUserId())
|
||||||
|
.likeIfPresent(ImGroupMemberDO::getNickname, reqVO.getNickname())
|
||||||
|
.eqIfPresent(ImGroupMemberDO::getAvatar, reqVO.getAvatar())
|
||||||
|
.likeIfPresent(ImGroupMemberDO::getAliasName, reqVO.getAliasName())
|
||||||
|
.eqIfPresent(ImGroupMemberDO::getRemark, reqVO.getRemark())
|
||||||
|
.betweenIfPresent(ImGroupMemberDO::getCreateTime, reqVO.getCreateTime())
|
||||||
|
.orderByDesc(ImGroupMemberDO::getId));
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<ImGroupMemberDO> selectListByGroupId(Long groupId) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>().eq(ImGroupMemberDO::getGroupId, groupId));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.mysql.inbox;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.inbox.ImInboxDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 收件箱 Mapper
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ImInboxMapper extends BaseMapperX<ImInboxDO> {
|
||||||
|
|
||||||
|
default List<ImInboxDO> selectListByUserIdAndSequence(Long userId, Long sequence, Integer size) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ImInboxDO>()
|
||||||
|
.eq(ImInboxDO::getUserId, userId)
|
||||||
|
// .gtIfPresent() // TODO @hao:可以用这个,简化下面的 .gt(sequence != null, ImInboxDO::getSequence, sequence)
|
||||||
|
.gt(sequence != null, ImInboxDO::getSequence, sequence)
|
||||||
|
.orderByDesc(ImInboxDO::getSequence)
|
||||||
|
.last("LIMIT " + size));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.mysql.message;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// TODO @hao:IM 前缀
|
||||||
|
/**
|
||||||
|
* 消息 Mapper
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ImMessageMapper extends BaseMapperX<ImMessageDO> {
|
||||||
|
|
||||||
|
default List<ImMessageDO> selectMessageList(ImMessageDO message) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ImMessageDO>()
|
||||||
|
.eqIfPresent(ImMessageDO::getConversationNo, message.getConversationNo())
|
||||||
|
.eqIfPresent(ImMessageDO::getSendTime, message.getSendTime())
|
||||||
|
.orderByAsc(ImMessageDO::getSendTime));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.redis;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* im Redis Key 枚举类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface RedisKeyConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收件箱序号生成器
|
||||||
|
* KEY 格式: im:inbox:sequence:{userId}
|
||||||
|
* VALUE 数据类型: String
|
||||||
|
*/
|
||||||
|
String INBOX_SEQUENCE = "im_inbox_sequence:%s";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收件箱的分布式锁
|
||||||
|
* KEY 格式: im:inbox:lock:{userId}
|
||||||
|
* VALUE 数据类型: String
|
||||||
|
*/
|
||||||
|
String INBOX_LOCK = "im_inbox_lock:%s";
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.redis.inbox;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.redisson.api.RLock;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.INBOX_LOCK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收件箱的锁 Redis DAO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public class InboxLockRedisDAO {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedissonClient redissonClient;
|
||||||
|
|
||||||
|
private static String formatKey(Long id) {
|
||||||
|
return String.format(INBOX_LOCK, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void lock(Long id, Long timeoutMillis, Runnable runnable) {
|
||||||
|
String lockKey = formatKey(id);
|
||||||
|
RLock lock = redissonClient.getLock(lockKey);
|
||||||
|
try {
|
||||||
|
lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);
|
||||||
|
// 执行逻辑
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package cn.iocoder.yudao.module.im.dal.redis.inbox;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.INBOX_SEQUENCE;
|
||||||
|
|
||||||
|
// TODO @芋艿:这个名字,需要在考虑下;至少先搞个 IM 前缀
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序号生成器 Redis DAO
|
||||||
|
*
|
||||||
|
* @author anhaohao
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public class SequenceRedisDAO {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Long> redisTemplate;
|
||||||
|
|
||||||
|
private static String formatKey(Long userId) {
|
||||||
|
return String.format(INBOX_SEQUENCE, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成序号
|
||||||
|
*
|
||||||
|
* @param userId 用户编号
|
||||||
|
* @return 序号
|
||||||
|
*/
|
||||||
|
public Long generateSequence(Long userId) {
|
||||||
|
return redisTemplate.opsForValue().increment(formatKey(userId), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* 属于 erp 模块的 framework 封装
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
package cn.iocoder.yudao.module.im.framework;
|
|
@ -0,0 +1,22 @@
|
||||||
|
package cn.iocoder.yudao.module.im.framework.web.config;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
|
||||||
|
import org.springdoc.core.models.GroupedOpenApi;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* im 模块的 web 组件的 Configuration
|
||||||
|
*/
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
public class ImWebConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* im 模块的 API 分组
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi imGroupedOpenApi() {
|
||||||
|
return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("im");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* trade 模块的 web 配置
|
||||||
|
*/
|
||||||
|
package cn.iocoder.yudao.module.im.framework.web;
|
|
@ -0,0 +1,42 @@
|
||||||
|
package cn.iocoder.yudao.module.im.service.conversation;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationCreateReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationUpdateLastReadTimeReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationUpdatePinnedReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationDO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 会话 Service 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface ImConversationService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得用户的会话列表
|
||||||
|
*
|
||||||
|
* @return 会话列表
|
||||||
|
*/
|
||||||
|
List<ImConversationDO> getConversationList(Long loginUserId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 置顶会话
|
||||||
|
*
|
||||||
|
* @param loginUserId 登录用户编号
|
||||||
|
* @param updateReqVO 更新信息
|
||||||
|
*/
|
||||||
|
void updatePinned(Long loginUserId, ImConversationUpdatePinnedReqVO updateReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新最后已读时间
|
||||||
|
*
|
||||||
|
* @param loginUserId 登录用户编号
|
||||||
|
* @param updateReqVO 更新信息
|
||||||
|
*/
|
||||||
|
void updateLastReadTime(Long loginUserId, ImConversationUpdateLastReadTimeReqVO updateReqVO);
|
||||||
|
|
||||||
|
ImConversationDO createConversation(Long loginUserId, ImConversationCreateReqVO createReqVO);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package cn.iocoder.yudao.module.im.service.conversation;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||||
|
import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationCreateReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationUpdateLastReadTimeReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationUpdatePinnedReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationDO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.mysql.conversation.ImConversationMapper;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 会话 Service 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Validated
|
||||||
|
public class ImConversationServiceImpl implements ImConversationService {
|
||||||
|
|
||||||
|
private final String IM_CONVERSATION_ADD = "im-conversation-add";
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ImConversationMapper imConversationMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
WebSocketMessageSender webSocketMessageSender;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ImConversationDO> getConversationList(Long loginUserId) {
|
||||||
|
// 根据loginUserId判断targetId, 自己不能做targetId
|
||||||
|
// 如果loginUserId和targetId相同,则需要调换userId和targetId
|
||||||
|
List<ImConversationDO> conversationList = imConversationMapper.selectList();
|
||||||
|
|
||||||
|
// 过滤和自己无关的会话
|
||||||
|
List<ImConversationDO> conversationFilteredList = conversationList.stream().filter(item -> loginUserId.equals(item.getUserId()) || loginUserId.equals(item.getTargetId())).toList();
|
||||||
|
// 遍历判断loginUserId和targetId相同,相同则将userId设置为targetId, targetId设置为userId
|
||||||
|
conversationFilteredList.forEach(item -> {
|
||||||
|
if (item.getTargetId().equals(loginUserId)) {
|
||||||
|
Long targetId = item.getTargetId();
|
||||||
|
Long userId = item.getUserId();
|
||||||
|
item.setTargetId(userId);
|
||||||
|
item.setUserId(targetId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return conversationFilteredList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePinned(Long loginUserId, ImConversationUpdatePinnedReqVO updateReqVO) {
|
||||||
|
// 1. 获得会话编号
|
||||||
|
String no = ImConversationTypeEnum.generateConversationNo(loginUserId, updateReqVO.getTargetId(), updateReqVO.getType());
|
||||||
|
// 2. 查询会话
|
||||||
|
ImConversationDO conversation = imConversationMapper.selectByNo(no);
|
||||||
|
if (conversation == null) {
|
||||||
|
// 2.1. 不存在,则插入
|
||||||
|
conversation = insertConversation(no, loginUserId, updateReqVO.getTargetId(), updateReqVO.getType());
|
||||||
|
}
|
||||||
|
// 3. 更新会话
|
||||||
|
conversation.setPinned(updateReqVO.getPinned());
|
||||||
|
imConversationMapper.updateById(conversation);
|
||||||
|
// 4. 做对应更新的 notify 推送
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImConversationDO insertConversation(String no, Long userId, Long targetId, Integer type) {
|
||||||
|
ImConversationDO imConversationDO = new ImConversationDO();
|
||||||
|
imConversationDO.setNo(no);
|
||||||
|
imConversationDO.setUserId(userId);
|
||||||
|
imConversationDO.setTargetId(targetId);
|
||||||
|
imConversationDO.setType(type);
|
||||||
|
imConversationMapper.insert(imConversationDO);
|
||||||
|
return imConversationDO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateLastReadTime(Long loginUserId, ImConversationUpdateLastReadTimeReqVO updateReqVO) {
|
||||||
|
// 1. 获得会话编号
|
||||||
|
String no = ImConversationTypeEnum.generateConversationNo(loginUserId, updateReqVO.getTargetId(), updateReqVO.getType());
|
||||||
|
// 2. 查询会话
|
||||||
|
ImConversationDO conversation = imConversationMapper.selectByNo(no);
|
||||||
|
if (conversation == null) {
|
||||||
|
// 2.1. 不存在,则插入
|
||||||
|
conversation = insertConversation(no, loginUserId, updateReqVO.getTargetId(), updateReqVO.getType());
|
||||||
|
}
|
||||||
|
// 3. 更新会话
|
||||||
|
conversation.setLastReadTime(updateReqVO.getLastReadTime());
|
||||||
|
imConversationMapper.updateById(conversation);
|
||||||
|
// 4. 做对应更新的 notify 推送
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImConversationDO createConversation(Long loginUserId, ImConversationCreateReqVO createReqVO) {
|
||||||
|
// 1. 获得会话编号
|
||||||
|
String no = ImConversationTypeEnum.generateConversationNo(loginUserId, createReqVO.getTargetId(), createReqVO.getType());
|
||||||
|
// 2. 查询会话
|
||||||
|
ImConversationDO conversation = imConversationMapper.selectByNo(no);
|
||||||
|
|
||||||
|
if (conversation == null) {
|
||||||
|
// 2.1. 不存在,则插入
|
||||||
|
conversation = insertConversation(no, loginUserId, createReqVO.getTargetId(), createReqVO.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送打开会话的通知,并推送会话实体
|
||||||
|
// 给自己发送创建会话成功的通知, 方便多端登录的时候保持会话同时更新
|
||||||
|
webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), getLoginUserId(),
|
||||||
|
IM_CONVERSATION_ADD, conversation);
|
||||||
|
|
||||||
|
// 给接受者发送创建会话的通知
|
||||||
|
// TODO:[dylan] 接受者,在接收到消息的时候本地发现没有回话,就按照会话编号创建一个,因此可以不需要发送这个通知
|
||||||
|
webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), createReqVO.getTargetId(),
|
||||||
|
IM_CONVERSATION_ADD, conversation);
|
||||||
|
return conversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package cn.iocoder.yudao.module.im.service.group;
|
||||||
|
|
||||||
|
import jakarta.validation.*;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群 Service 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface ImGroupService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建群
|
||||||
|
*
|
||||||
|
* @param createReqVO 创建信息
|
||||||
|
* @return 编号
|
||||||
|
*/
|
||||||
|
Long createGroup(@Valid ImGroupSaveReqVO createReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新群
|
||||||
|
*
|
||||||
|
* @param updateReqVO 更新信息
|
||||||
|
*/
|
||||||
|
void updateGroup(@Valid ImGroupSaveReqVO updateReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除群
|
||||||
|
*
|
||||||
|
* @param id 编号
|
||||||
|
*/
|
||||||
|
void deleteGroup(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得群
|
||||||
|
*
|
||||||
|
* @param id 编号
|
||||||
|
* @return 群
|
||||||
|
*/
|
||||||
|
ImGroupDO getGroup(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得群分页
|
||||||
|
*
|
||||||
|
* @param pageReqVO 分页查询
|
||||||
|
* @return 群分页
|
||||||
|
*/
|
||||||
|
PageResult<ImGroupDO> getGroupPage(ImGroupPageReqVO pageReqVO);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package cn.iocoder.yudao.module.im.service.group;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群 Service 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Validated
|
||||||
|
public class ImGroupServiceImpl implements ImGroupService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ImGroupMapper imGroupMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long createGroup(ImGroupSaveReqVO createReqVO) {
|
||||||
|
// 插入
|
||||||
|
ImGroupDO group = BeanUtils.toBean(createReqVO, ImGroupDO.class);
|
||||||
|
imGroupMapper.insert(group);
|
||||||
|
// 返回
|
||||||
|
return group.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateGroup(ImGroupSaveReqVO updateReqVO) {
|
||||||
|
// 校验存在
|
||||||
|
validateGroupExists(updateReqVO.getId());
|
||||||
|
// 更新
|
||||||
|
ImGroupDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupDO.class);
|
||||||
|
imGroupMapper.updateById(updateObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteGroup(Long id) {
|
||||||
|
// 校验存在
|
||||||
|
validateGroupExists(id);
|
||||||
|
// 删除
|
||||||
|
imGroupMapper.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateGroupExists(Long id) {
|
||||||
|
if (imGroupMapper.selectById(id) == null) {
|
||||||
|
throw exception(GROUP_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImGroupDO getGroup(Long id) {
|
||||||
|
return imGroupMapper.selectById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<ImGroupDO> getGroupPage(ImGroupPageReqVO pageReqVO) {
|
||||||
|
return imGroupMapper.selectPage(pageReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package cn.iocoder.yudao.module.im.service.groupmember;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberPageReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.ImGroupMemberSaveReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群成员 Service 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface ImGroupMemberService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建群成员
|
||||||
|
*
|
||||||
|
* @param createReqVO 创建信息
|
||||||
|
* @return 编号
|
||||||
|
*/
|
||||||
|
Long createGroupMember(@Valid ImGroupMemberSaveReqVO createReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新群成员
|
||||||
|
*
|
||||||
|
* @param updateReqVO 更新信息
|
||||||
|
*/
|
||||||
|
void updateGroupMember(@Valid ImGroupMemberSaveReqVO updateReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除群成员
|
||||||
|
*
|
||||||
|
* @param id 编号
|
||||||
|
*/
|
||||||
|
void deleteGroupMember(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得群成员
|
||||||
|
*
|
||||||
|
* @param id 编号
|
||||||
|
* @return 群成员
|
||||||
|
*/
|
||||||
|
ImGroupMemberDO getGroupMember(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得群成员分页
|
||||||
|
*
|
||||||
|
* @param pageReqVO 分页查询
|
||||||
|
* @return 群成员分页
|
||||||
|
*/
|
||||||
|
PageResult<ImGroupMemberDO> getGroupMemberPage(ImGroupMemberPageReqVO pageReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据群组id查询群成员
|
||||||
|
*
|
||||||
|
* @param groupId 群组id
|
||||||
|
* @return 群成员列表
|
||||||
|
*/
|
||||||
|
List<ImGroupMemberDO> selectByGroupId(Long groupId);
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package cn.iocoder.yudao.module.im.service.groupmember;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.groupmember.vo.*;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.dal.mysql.groupmember.ImGroupMemberMapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群成员 Service 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Validated
|
||||||
|
public class ImGroupMemberServiceImpl implements ImGroupMemberService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ImGroupMemberMapper imGroupMemberMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long createGroupMember(ImGroupMemberSaveReqVO createReqVO) {
|
||||||
|
// 插入
|
||||||
|
ImGroupMemberDO groupMember = BeanUtils.toBean(createReqVO, ImGroupMemberDO.class);
|
||||||
|
imGroupMemberMapper.insert(groupMember);
|
||||||
|
// 返回
|
||||||
|
return groupMember.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateGroupMember(ImGroupMemberSaveReqVO updateReqVO) {
|
||||||
|
// 校验存在
|
||||||
|
validateGroupMemberExists(updateReqVO.getId());
|
||||||
|
// 更新
|
||||||
|
ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class);
|
||||||
|
imGroupMemberMapper.updateById(updateObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteGroupMember(Long id) {
|
||||||
|
// 校验存在
|
||||||
|
validateGroupMemberExists(id);
|
||||||
|
// 删除
|
||||||
|
imGroupMemberMapper.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateGroupMemberExists(Long id) {
|
||||||
|
if (imGroupMemberMapper.selectById(id) == null) {
|
||||||
|
throw exception(GROUP_MEMBER_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImGroupMemberDO getGroupMember(Long id) {
|
||||||
|
return imGroupMemberMapper.selectById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<ImGroupMemberDO> getGroupMemberPage(ImGroupMemberPageReqVO pageReqVO) {
|
||||||
|
return imGroupMemberMapper.selectPage(pageReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ImGroupMemberDO> selectByGroupId(Long groupId) {
|
||||||
|
return imGroupMemberMapper.selectListByGroupId(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package cn.iocoder.yudao.module.im.service.inbox;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 收件箱 Service 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface ImInboxService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存收件箱和发送消息
|
||||||
|
*
|
||||||
|
* @param imMessageDO 收件箱保存消息 Request VO
|
||||||
|
*/
|
||||||
|
void saveInboxAndSendMessage(ImMessageDO imMessageDO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得大于 sequence 的消息ids
|
||||||
|
*
|
||||||
|
* @param userId 用户编号
|
||||||
|
* @param sequence 序列号
|
||||||
|
* @param size 数量
|
||||||
|
* @return 消息编号列表
|
||||||
|
*/
|
||||||
|
List<Long> selectMessageIdsByUserIdAndSequence(Long userId, Long sequence, Integer size);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package cn.iocoder.yudao.module.im.service.inbox;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUnit;
|
||||||
|
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageRespVO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.inbox.ImInboxDO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.mysql.inbox.ImInboxMapper;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.redis.inbox.InboxLockRedisDAO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.redis.inbox.SequenceRedisDAO;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.service.groupmember.ImGroupMemberService;
|
||||||
|
import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收件箱 Service 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Validated
|
||||||
|
public class ImInboxServiceImpl implements ImInboxService {
|
||||||
|
|
||||||
|
private static final Long INBOX_LOCK_TIMEOUT = 120 * DateUnit.SECOND.getMillis();
|
||||||
|
private static final String IM_MESSAGE_RECEIVE = "im-message-receive";
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ImInboxMapper imInboxMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SequenceRedisDAO sequenceRedisDAO;
|
||||||
|
@Resource
|
||||||
|
private InboxLockRedisDAO inboxLockRedisDAO;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WebSocketSenderApi webSocketSenderApi;
|
||||||
|
@Resource
|
||||||
|
private ImGroupMemberService imGroupMemberService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveInboxAndSendMessage(ImMessageDO message) {
|
||||||
|
// 1. 保存收件箱 + 发送消息给发送人
|
||||||
|
saveInboxAndSendMessageForUser(message.getSenderId(), message);
|
||||||
|
// 2. 保存收件箱 + 发送消息给接收人
|
||||||
|
if (message.getConversationType().equals(ImConversationTypeEnum.SINGLE.getType())) {
|
||||||
|
// 2.1 如果是单聊,直接发送给接收人
|
||||||
|
saveInboxAndSendMessageForUser(message.getReceiverId(), message);
|
||||||
|
} else if (message.getConversationType().equals(ImConversationTypeEnum.GROUP.getType())) {
|
||||||
|
// 2.2 如果是群聊,发送给群聊的所有人
|
||||||
|
List<ImGroupMemberDO> groupMembers = imGroupMemberService.selectByGroupId(message.getReceiverId());
|
||||||
|
groupMembers.forEach(groupMemberDO -> saveInboxAndSendMessageForUser(groupMemberDO.getUserId(), message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Long> selectMessageIdsByUserIdAndSequence(Long userId, Long sequence, Integer size) {
|
||||||
|
List<ImInboxDO> imInboxDOS = imInboxMapper.selectListByUserIdAndSequence(userId, sequence, size);
|
||||||
|
return imInboxDOS.stream().map(ImInboxDO::getMessageId).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//TODO 多线程处理
|
||||||
|
public void saveInboxAndSendMessageForUser(Long userId, ImMessageDO message) {
|
||||||
|
inboxLockRedisDAO.lock(userId, INBOX_LOCK_TIMEOUT, () -> {
|
||||||
|
// 1. 生成序列号
|
||||||
|
Long userSequence = sequenceRedisDAO.generateSequence(userId);
|
||||||
|
// 2. 保存收件箱
|
||||||
|
ImInboxDO inbox = new ImInboxDO()
|
||||||
|
.setUserId(userId)
|
||||||
|
.setMessageId(message.getId())
|
||||||
|
.setSequence(userSequence);
|
||||||
|
imInboxMapper.insert(inbox);
|
||||||
|
// 3. 发送消息
|
||||||
|
sendAsyncMessage(userId, message, userSequence);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendAsyncMessage(Long userId, ImMessageDO message, Long userSequence) {
|
||||||
|
ImMessageRespVO messageRespVO = BeanUtils.toBean(message, ImMessageRespVO.class);
|
||||||
|
messageRespVO.setSequence(userSequence);
|
||||||
|
webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), userId, IM_MESSAGE_RECEIVE, messageRespVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package cn.iocoder.yudao.module.im.service.message;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageListByNoReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageListReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageSendReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息 Service 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface ImMessageService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得历史消息
|
||||||
|
*
|
||||||
|
* @param listReqVO 分页查询
|
||||||
|
* @return 消息分页
|
||||||
|
*/
|
||||||
|
List<ImMessageDO> getMessageList(ImMessageListReqVO listReqVO, Long loginUserId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得历史消息
|
||||||
|
*
|
||||||
|
* @param listReqVO 分页查询
|
||||||
|
* @return 消息分页
|
||||||
|
*/
|
||||||
|
List<ImMessageDO> getMessageListByConversationNo(ImMessageListByNoReqVO listReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取消息-大于 seq 的消息
|
||||||
|
*
|
||||||
|
* @param loginUserId 登录用户编号
|
||||||
|
* @param sequence 序列号
|
||||||
|
* @param size 数量
|
||||||
|
* @return 消息列表
|
||||||
|
*/
|
||||||
|
List<ImMessageDO> pullMessageList(Long loginUserId, Long sequence, Integer size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息
|
||||||
|
*
|
||||||
|
* @param loginUserId 登录用户编号
|
||||||
|
* @param message 消息
|
||||||
|
* @return 消息编号
|
||||||
|
*/
|
||||||
|
ImMessageDO sendMessage(Long loginUserId, ImMessageSendReqVO message);
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package cn.iocoder.yudao.module.im.service.message;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageListByNoReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageListReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.controller.admin.message.vo.ImMessageSendReqVO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImMessageDO;
|
||||||
|
import cn.iocoder.yudao.module.im.dal.mysql.message.ImMessageMapper;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.message.ImMessageSourceEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
|
||||||
|
import cn.iocoder.yudao.module.im.service.groupmember.ImGroupMemberService;
|
||||||
|
import cn.iocoder.yudao.module.im.service.inbox.ImInboxService;
|
||||||
|
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||||
|
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||||
|
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_RECEIVER_NOT_EXISTS;
|
||||||
|
import static cn.iocoder.yudao.module.im.enums.conversation.ImConversationTypeEnum.generateConversationNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息 Service 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Validated
|
||||||
|
@Slf4j
|
||||||
|
public class ImMessageServiceImpl implements ImMessageService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ImMessageMapper imMessageMapper;
|
||||||
|
@Resource
|
||||||
|
private AdminUserApi adminUserApi;
|
||||||
|
@Resource
|
||||||
|
private ImInboxService imInboxService;
|
||||||
|
@Resource
|
||||||
|
private ImGroupMemberService imGroupMemberService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ImMessageDO> getMessageList(ImMessageListReqVO listReqVO, Long loginUserId) {
|
||||||
|
|
||||||
|
String no = generateConversationNo(loginUserId, listReqVO.getReceiverId(), listReqVO.getConversationType());
|
||||||
|
|
||||||
|
ImMessageDO message = new ImMessageDO()
|
||||||
|
.setSendTime(listReqVO.getSendTime())
|
||||||
|
.setConversationNo(no);
|
||||||
|
|
||||||
|
return imMessageMapper.selectMessageList(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ImMessageDO> getMessageListByConversationNo(ImMessageListByNoReqVO listReqVO) {
|
||||||
|
|
||||||
|
// 1. 查询历史消息
|
||||||
|
ImMessageDO message = new ImMessageDO()
|
||||||
|
.setSendTime(listReqVO.getSendTime())
|
||||||
|
.setConversationNo(listReqVO.getConversationNo());
|
||||||
|
return imMessageMapper.selectMessageList(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ImMessageDO> pullMessageList(Long userId, Long sequence, Integer size) {
|
||||||
|
List<Long> messageIds = imInboxService.selectMessageIdsByUserIdAndSequence(userId, sequence, size);
|
||||||
|
if (CollUtil.isEmpty(messageIds)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return imMessageMapper.selectBatchIds(messageIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImMessageDO sendMessage(Long fromUserId, ImMessageSendReqVO imMessageSendReqVO) {
|
||||||
|
// 1. 保存消息
|
||||||
|
ImMessageDO message = saveMessage(fromUserId, imMessageSendReqVO);
|
||||||
|
// 2. 保存到收件箱,并发送消息
|
||||||
|
imInboxService.saveInboxAndSendMessage(message);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImMessageDO saveMessage(Long fromUserId, ImMessageSendReqVO message) {
|
||||||
|
// TODO 芋艿:消息格式的校验
|
||||||
|
// 1. 校验接收人是否存在
|
||||||
|
validateReceiverIdExists(message);
|
||||||
|
// 2. 查询发送人信息
|
||||||
|
AdminUserRespDTO fromUser = adminUserApi.getUser(fromUserId);
|
||||||
|
|
||||||
|
// 3. 生成conversationNo
|
||||||
|
String conversationNo = generateConversationNo(fromUserId, message.getReceiverId(), message.getConversationType());
|
||||||
|
|
||||||
|
// 4. 保存消息
|
||||||
|
ImMessageDO imMessageDO = BeanUtil.copyProperties(message, ImMessageDO.class)
|
||||||
|
.setSenderNickname(fromUser.getNickname()).setSenderAvatar(fromUser.getAvatar())
|
||||||
|
.setSenderId(fromUserId)
|
||||||
|
.setConversationNo(conversationNo)
|
||||||
|
.setSendFrom(ImMessageSourceEnum.USER_SEND.getSource())
|
||||||
|
.setMessageStatus(ImMessageStatusEnum.SENDING.getStatus())
|
||||||
|
.setSendTime(LocalDateTime.now());
|
||||||
|
imMessageMapper.insert(imMessageDO);
|
||||||
|
return imMessageDO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateReceiverIdExists(ImMessageSendReqVO message) {
|
||||||
|
if (message.getConversationType().equals(ImConversationTypeEnum.SINGLE.getType())) {
|
||||||
|
// 校验用户是否存在
|
||||||
|
AdminUserRespDTO receiverUser = adminUserApi.getUser(message.getReceiverId());
|
||||||
|
if (receiverUser == null) {
|
||||||
|
throw exception(MESSAGE_RECEIVER_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
} else if (message.getConversationType().equals(ImConversationTypeEnum.GROUP.getType())) {
|
||||||
|
// 校验群聊是否存在
|
||||||
|
List<ImGroupMemberDO> imGroupMemberDOS = imGroupMemberService.selectByGroupId(message.getReceiverId());
|
||||||
|
if (imGroupMemberDOS.isEmpty()) {
|
||||||
|
throw exception(MESSAGE_RECEIVER_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,9 +27,7 @@ import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||||
|
@ -103,13 +101,38 @@ public class UserController {
|
||||||
pageResult.getTotal()));
|
pageResult.getTotal()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @dylan:可以服用 getSimpleUserList 呀。
|
||||||
|
@GetMapping("/all")
|
||||||
|
@Operation(summary = "获得所有用户列表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('system:user:list')")
|
||||||
|
public CommonResult<List<UserRespVO>> getUserAll() {
|
||||||
|
List<AdminUserDO> result = userService.getUserListAll();
|
||||||
|
if (CollUtil.isEmpty(result)) {
|
||||||
|
return success(null);
|
||||||
|
}
|
||||||
|
// 拼接数据
|
||||||
|
Map<Long, DeptDO> deptMap = deptService.getDeptMap(
|
||||||
|
convertList(result, AdminUserDO::getDeptId));
|
||||||
|
return success(UserConvert.INSTANCE.convertList(result, deptMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping({"/list-all-simple", "/simple-list"})
|
@GetMapping({"/list-all-simple", "/simple-list"})
|
||||||
@Operation(summary = "获取用户精简信息列表", description = "只包含被开启的用户,主要用于前端的下拉选项")
|
@Operation(summary = "获取用户精简信息列表", description = "只包含被开启的用户,主要用于前端的下拉选项")
|
||||||
public CommonResult<List<UserSimpleRespVO>> getSimpleUserList() {
|
public CommonResult<List<UserSimpleRespVO>> getSimpleUserList(@RequestParam("id") Long deptId) {
|
||||||
List<AdminUserDO> list = userService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
List<AdminUserDO> list;
|
||||||
|
|
||||||
|
if (deptId != null) {
|
||||||
|
List<Long> deptIds = Collections.singletonList(deptId);
|
||||||
|
list = userService.getDeptUsers(deptIds);
|
||||||
|
} else {
|
||||||
|
list = userService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
// 拼接数据
|
// 拼接数据
|
||||||
Map<Long, DeptDO> deptMap = deptService.getDeptMap(
|
Map<Long, DeptDO> deptMap = deptService.getDeptMap(
|
||||||
convertList(list, AdminUserDO::getDeptId));
|
convertList(list, AdminUserDO::getDeptId));
|
||||||
|
|
||||||
return success(UserConvert.INSTANCE.convertSimpleList(list, deptMap));
|
return success(UserConvert.INSTANCE.convertSimpleList(list, deptMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,14 @@ public interface AdminUserService {
|
||||||
*/
|
*/
|
||||||
List<AdminUserDO> getUserList(Collection<Long> ids);
|
List<AdminUserDO> getUserList(Collection<Long> ids);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得全部用户列表
|
||||||
|
*
|
||||||
|
* @return 用户列表
|
||||||
|
*/
|
||||||
|
List<AdminUserDO> getUserListAll();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验用户们是否有效。如下情况,视为无效:
|
* 校验用户们是否有效。如下情况,视为无效:
|
||||||
* 1. 用户编号不存在
|
* 1. 用户编号不存在
|
||||||
|
@ -207,6 +215,13 @@ public interface AdminUserService {
|
||||||
*/
|
*/
|
||||||
List<AdminUserDO> getUserListByStatus(Integer status);
|
List<AdminUserDO> getUserListByStatus(Integer status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得指定部门的用户
|
||||||
|
* @param deptIds
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<AdminUserDO> getDeptUsers(Collection<Long> deptIds);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断密码是否匹配
|
* 判断密码是否匹配
|
||||||
*
|
*
|
||||||
|
|
|
@ -308,6 +308,11 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||||
return userMapper.selectBatchIds(ids);
|
return userMapper.selectBatchIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<AdminUserDO> getUserListAll() {
|
||||||
|
return userMapper.selectList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validateUserList(Collection<Long> ids) {
|
public void validateUserList(Collection<Long> ids) {
|
||||||
if (CollUtil.isEmpty(ids)) {
|
if (CollUtil.isEmpty(ids)) {
|
||||||
|
@ -509,6 +514,11 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||||
return userMapper.selectListByStatus(status);
|
return userMapper.selectListByStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AdminUserDO> getDeptUsers(Collection<Long> deptIds) {
|
||||||
|
return userMapper.selectListByDeptIds(deptIds);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
|
public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
|
||||||
return passwordEncoder.matches(rawPassword, encodedPassword);
|
return passwordEncoder.matches(rawPassword, encodedPassword);
|
||||||
|
|
|
@ -115,6 +115,13 @@
|
||||||
<!-- <version>${revision}</version>-->
|
<!-- <version>${revision}</version>-->
|
||||||
<!-- </dependency>-->
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
<!-- IM 相关模块。默认注释,保证编译速度 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-module-im-biz</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- spring boot 配置所需依赖 -->
|
<!-- spring boot 配置所需依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
|
|
@ -176,6 +176,7 @@ logging:
|
||||||
cn.iocoder.yudao.module.erp.dal.mysql: debug
|
cn.iocoder.yudao.module.erp.dal.mysql: debug
|
||||||
cn.iocoder.yudao.module.iot.dal.mysql: debug
|
cn.iocoder.yudao.module.iot.dal.mysql: debug
|
||||||
cn.iocoder.yudao.module.ai.dal.mysql: debug
|
cn.iocoder.yudao.module.ai.dal.mysql: debug
|
||||||
|
cn.iocoder.yudao.module.im.dal.mysql: debug
|
||||||
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示
|
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
|
|
Loading…
Reference in New Issue