feature: 对接转发消息、通知模块

This commit is contained in:
乾乾
2025-09-20 19:00:51 +08:00
parent 9e5293af4b
commit 376d3b36dc
91 changed files with 1194 additions and 1233 deletions

View File

@@ -731,6 +731,30 @@ CREATE TABLE `im_role` (
INSERT INTO `im_role` VALUES (1, '超级管理员', '2024-07-10 11:17:15.089', '2024-07-10 11:17:15.089', 1, 1, NULL, 0);
INSERT INTO `im_role` VALUES (2, 'HuLa群聊管理员', '2024-07-10 11:17:15.091', '2024-11-26 12:00:22.452', 1, 1, NULL, 0);
DROP TABLE IF EXISTS `im_notice`;
CREATE TABLE `im_notice` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`event_type` tinyint NOT NULL COMMENT '通知类型:1-好友申请;2-群申请;3-群邀请;5-移除群成员;6-好友被申请;7-被邀请进群',
`type` tinyint NOT NULL DEFAULT 1 COMMENT '通知类型 1群聊 2加好友',
`sender_id` bigint NOT NULL COMMENT '发起人UID',
`receiver_id` bigint NOT NULL COMMENT '接收人UID',
`apply_id` bigint NULL DEFAULT NULL COMMENT '申请ID',
`operate_id` bigint NOT NULL COMMENT '房间ID',
`content` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '0' COMMENT '通知消息 [申请时填写]',
`status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '处理状态:0-未处理;1-已同意;2-已拒绝',
`is_read` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已读',
`tenant_id` bigint NOT NULL COMMENT '租户id',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`create_by` bigint NOT NULL COMMENT '创建人',
`update_by` bigint NOT NULL COMMENT '创建人',
`is_del` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_receiver_type`(`receiver_id` ASC, `event_type` ASC) USING BTREE,
INDEX `idx_sender`(`sender_id` ASC) USING BTREE,
INDEX `idx_related`(`apply_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 75169527849987 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '统一通知表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for im_room
-- ----------------------------

View File

@@ -1 +0,0 @@
NACOS_VERSION=v3.0.2

View File

@@ -1,97 +0,0 @@
version: "3.8"
services:
nacos:
image: nacos/nacos-server:${NACOS_VERSION}
container_name: nacos-standalone-mysql
env_file:
- ./env/nacos-standalone-mysql.env
volumes:
- ./nacos/standalone-logs/:/home/nacos/logs
ports:
- "8080:8080"
- "8848:8848"
- "9848:9848"
depends_on:
mysql:
condition: service_healthy
restart: always
mysql:
container_name: mysql
restart: always
image: mysql:8.0.30
env_file:
- ./env/mysql.env
volumes:
- ./mysql/data:/var/lib/mysql
ports:
- "13306:3306"
healthcheck:
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
interval: 5s
timeout: 10s
retries: 10
redis:
image: redis:latest
container_name: redis
restart: always
ports:
- '16379:6379'
volumes:
- ./redis/data:/data
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf
- ./redis/logs:/logs
#配置文件启动
command: redis-server /usr/local/etc/redis/redis.conf --requirepass luo123456
# rocketmq
rocketmq-namesrv:
image: apache/rocketmq:5.3.2
container_name: rocketmq-namesrv
ports:
- 9876:9876
command: sh mqnamesrv
volumes:
- ./rocketmq/namesrv/logs:/home/rocketmq/logs
- ./rocketmq/namesrv/store:/home/rocketmq/store
rocketmq-broker:
image: apache/rocketmq:5.3.2
container_name: rocketmq-broker
ports:
- 10909:10909
- 10911:10911
- 10912:10912
environment:
- NAMESRV_ADDR=rocketmq-namesrv:9876
volumes:
- ./rocketmq/broker/logs:/home/rocketmq/logs
- ./rocketmq/broker/store:/home/rocketmq/store
- ./rocketmq/broker/conf/broker.conf:/home/rocketmq/rocketmq-5.3.2/conf/broker.conf
- ./rocketmq/broker/conf/plain_acl.yml:/home/rocketmq/rocketmq-5.3.2/conf/plain_acl.yml
depends_on:
- rocketmq-namesrv
command: sh mqbroker -c /home/rocketmq/rocketmq-5.3.2/conf/broker.conf
rocketmq-proxy:
image: apache/rocketmq:5.3.2
container_name: rocketmq-proxy
depends_on:
- rocketmq-broker
- rocketmq-namesrv
ports:
- 8082:8080
- 8081:8081
restart: on-failure
environment:
- NAMESRV_ADDR=rocketmq-namesrv:9876
command: sh mqproxy
jenkins:
image: jenkins/jenkins:lts-jdk21
container_name: jenkins
user: root
restart: always
ports:
- "20000:8080" # Web UI 端口
- "50000:50000" # Agent 连接端口
volumes:
- /home/jenkins/work:/var/jenkins_home # 数据持久化
- /var/run/docker.sock:/var/run/docker.sock # 宿主机 Docker 控制
- /usr/bin/docker:/usr/bin/docker # 宿主机 Docker 命令

View File

@@ -1,12 +0,0 @@
# mysql root 密码
MYSQL_ROOT_PASSWORD=123456
# 创建nacos 账号和密码
MYSQL_DATABASE=nacos
MYSQL_USER=nacos
MYSQL_PASSWORD=n2SWa3j45a6AdmZE
LANG=C.UTF-8
# 允许远程访问
MYSQL_ROOT_HOST=%
# 支持utf8mb4字符集
MYSQL_CHARSET=utf8mb4
MYSQL_COLLATION=utf8mb4_unicode_ci

View File

@@ -1,15 +0,0 @@
# 这里要用mysql 的容器名称
PREFER_HOST_MODE=mysql
MODE=standalone
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_SERVICE_HOST=mysql
MYSQL_SERVICE_DB_NAME=nacos
# 端口配置docker 内部的端口不用配置宿主机的端口如果配置宿主机的端口那么PREFER_HOST_MODE 需要配置host.docker.internal
MYSQL_SERVICE_PORT=3306
MYSQL_SERVICE_USER=nacos
# 密码必须要和mysql中创建的密码一致
MYSQL_SERVICE_PASSWORD=n2SWa3j45a6AdmZE
MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
NACOS_AUTH_IDENTITY_KEY=2222
NACOS_AUTH_IDENTITY_VALUE=2xxx
NACOS_AUTH_TOKEN=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg=

View File

@@ -1,6 +0,0 @@
REDIS_PASSWORD="luo123456" # 16位复杂度与mysql业务密码同级
REDIS_DATABASES=0 # 默认数据库数量
REDIS_MAXMEMORY=2gb # 内存限制
REDIS_APPENDONLY=yes # 启用AOF持久化
REDIS_PROTECTED_MODE=yes # 保护模式
REDIS_RENAME_COMMANDS="FLUSHDB:_,FLUSHALL:_,CONFIG:_" # 禁用危险命令

View File

@@ -1,11 +0,0 @@
## 📝 修改环境配置
上传目录下的docker文件夹到服务器/home/docker下面, 需要修改env文件夹rocketmq文件夹里面的配置, 特别是[broker.conf](docker/rocketmq/broker/conf/broker.conf)里面brokerIP1的值
## 🛠️ 启动命令
- **仔细阅读**: docker-compose.yml 的内容redis的密码也在这里面设置的、./env 文件夹下面的内容,里面包含了账号密码等信息
- **打开目录**: 当前文件夹下输入 cmd 回车
- **执行命令**: docker-compose up -d
- **导入nacos数据库**: [mysql-schema.sql](../mysql-schema.sql)
- **导入nacos命名空间数据**: [nacos_config_export_20250816090745.zip](../../nacos/nacos_config_export_20250816090745.zip)

View File

@@ -1,105 +0,0 @@
#所属集群名字
brokerClusterName=DefaultCluster
# 启用Proxy模式可选默认NONE
proxyMode=NONE
# 启用Controller模式集群部署需配置
enableControllerMode=false
#broker名字注意此处不同的配置文件填写的不一样如果在broker-a.properties使用:broker-a,
#在broker-b.properties使用:broker-b
brokerName=broker-a
#0 表示Master>0 表示Slave
brokerId=0
#nameServer地址分号分割
#namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
namesrvAddr=rocketmq-namesrv:9876
#启动IP,如果 docker 报 com.alibaba.rocketmq.remoting.exception.RemotingConnectException: connect to <192.168.0.120:10909> failed
# 解决方式1 加上一句producer.setVipChannelEnabled(false);解决方式2 brokerIP1 设置宿主机IP不要使用docker 内部IP
brokerIP1=rocketmq-broker
#在发送消息时自动创建服务器不存在的topic默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic建议线下开启线上关闭 这里仔细看是falsefalsefalse
autoCreateTopicEnable=true
enableAutoCreateSystemTopic=true
# 必须设置为true与proxy协调
enableAutoCreateSubscriptionGroup=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#此参数控制是否开启密码,不开启可设置false
aclEnable=false
#删除文件时间点默认凌晨4点
deleteWhen=04
#文件保留时间默认48小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mappedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条根据业务情况调整
mappedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/home/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/home/rocketmq/store/commitlog
#消费队列存储
storePathConsumeQueue=/home/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/home/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/home/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/home/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
# 延迟消息配置
#timerWheelEnable=false
#enableScheduleMessage=false
# 确保此目录存在且可写
#timerStorePath=/home/rocketmq/store/timerwheel
#timerFlushIntervalMs=1000
#timerPrecisionMs=1000
#scheduleMessageServiceThreadPoolNums=4
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=ASYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘 SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
# 堆外内存限制(需与 JVM 参数一致)
maxDirectMemorySize=1g
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128

View File

@@ -1,24 +0,0 @@
globalWhiteRemoteAddresses:
# - 47.100.93.*
# - 156.254.120.*
accounts:
- accessKey: RocketMQ
secretKey: mq000000
whiteRemoteAddress:
admin: true
defaultTopicPerm: PUB|SUB
defaultGroupPerm: PUB|SUB
topicPerms:
- topicA=DENY
- topicB=PUB|SUB
- topicC=SUB
groupPerms:
- groupA=DENY
- groupB=PUB|SUB
- groupC=SUB
- accessKey: earthearth
secretKey: mq000000
whiteRemoteAddress:
admin: true

View File

@@ -1,3 +0,0 @@
{
"rocketMQClusterName": "DefaultCluster"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -1,44 +0,0 @@
#!/usr/bin/env bash
set -x
set -eo pipefail
# Check if a custom parameter has been set, otherwise use default values
DB_PORT="${DB_PORT:=5432}"
SUPERUSER="${SUPERUSER:=postgres}"
SUPERUSER_PWD="${SUPERUSER_PWD:=postgres}"
# Allow to skip Docker if a dockerized Postgres database is already running
if [[ -z "${SKIP_DOCKER}" ]]
then
# if a postgres container is running, print instructions to kill it and exit
RUNNING_POSTGRES_CONTAINER=$(docker ps --filter 'name=postgres' --format '{{.ID}}')
if [[ -n $RUNNING_POSTGRES_CONTAINER ]]; then
echo >&2 "there is a postgres container already running, kill it with"
echo >&2 " docker kill ${RUNNING_POSTGRES_CONTAINER}"
exit 1
fi
CONTAINER_NAME="postgres"
# Launch postgres using Docker
docker run \
--env POSTGRES_USER=${SUPERUSER} \
--env POSTGRES_PASSWORD=${SUPERUSER_PWD} \
--health-cmd="pg_isready -U ${SUPERUSER} || exit 1" \
--health-interval=1s \
--health-timeout=5s \
--health-retries=5 \
--publish "${DB_PORT}":5432 \
--detach \
--name "${CONTAINER_NAME}" \
postgres -N 1000
# ^ Increased maximum number of connections for testing purposes
until [ \
"$(docker inspect -f "{{.State.Health.Status}}" ${CONTAINER_NAME})" == \
"healthy" \
]; do
>&2 echo "Postgres is still unavailable - sleeping"
sleep 1
done
fi
>&2 echo "Postgres is up and running on port ${DB_PORT} - running migrations now!"

View File

@@ -1,179 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/******************************************/
/* 表名称 = config_info */
/******************************************/
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) DEFAULT NULL COMMENT 'group_id',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL COMMENT 'configuration description',
`c_use` varchar(64) DEFAULT NULL COMMENT 'configuration usage',
`effect` varchar(64) DEFAULT NULL COMMENT '配置生效的描述',
`type` varchar(64) DEFAULT NULL COMMENT '配置的类型',
`c_schema` text COMMENT '配置的模式',
`encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
/******************************************/
/* 表名称 = config_info since 2.5.0 */
/******************************************/
CREATE TABLE `config_info_gray` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`src_user` text COMMENT 'src_user',
`src_ip` varchar(100) DEFAULT NULL COMMENT 'src_ip',
`gmt_create` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_create',
`gmt_modified` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_modified',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`gray_name` varchar(128) NOT NULL COMMENT 'gray_name',
`gray_rule` text NOT NULL COMMENT 'gray_rule',
`encrypted_data_key` varchar(256) NOT NULL DEFAULT '' COMMENT 'encrypted_data_key',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfogray_datagrouptenantgray` (`data_id`,`group_id`,`tenant_id`,`gray_name`),
KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`),
KEY `idx_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='config_info_gray';
/******************************************/
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` bigint(20) NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识',
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
/******************************************/
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID空字符表示整个集群',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限单位为字节0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数0表示使用默认值',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限单位为字节0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
/******************************************/
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(20) unsigned NOT NULL COMMENT 'id',
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`op_type` char(10) DEFAULT NULL COMMENT 'operation type',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥',
`publish_type` varchar(50) DEFAULT 'formal' COMMENT 'publish type gray or formal',
`gray_name` varchar(50) DEFAULT NULL COMMENT 'gray name',
`ext_info` longtext DEFAULT NULL COMMENT 'ext info',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
/******************************************/
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限单位为字节0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限单位为字节0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
CREATE TABLE `tenant_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) default '' COMMENT 'tenant_id',
`tenant_name` varchar(128) default '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE `users` (
`username` varchar(50) NOT NULL PRIMARY KEY COMMENT 'username',
`password` varchar(500) NOT NULL COMMENT 'password',
`enabled` boolean NOT NULL COMMENT 'enabled'
);
CREATE TABLE `roles` (
`username` varchar(50) NOT NULL COMMENT 'username',
`role` varchar(50) NOT NULL COMMENT 'role',
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL COMMENT 'role',
`resource` varchar(128) NOT NULL COMMENT 'resource',
`action` varchar(8) NOT NULL COMMENT 'action',
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);

View File

@@ -1,51 +0,0 @@
services:
rocketmq-namesrv:
image: apache/rocketmq:5.3.2
container_name: rocketmq-namesrv-test
networks:
- rocketmq-net
ports:
- 9886:9876
command: sh mqnamesrv
volumes:
- ./rocketmq/namesrv/logs:/home/rocketmq/logs
- ./rocketmq/namesrv/store:/home/rocketmq/store
rocketmq-broker:
image: apache/rocketmq:5.3.2
container_name: rocketmq-broker-test
networks:
- rocketmq-net
ports:
- 11909:10909
- 11911:10911
- 11912:10912
environment:
- NAMESRV_ADDR=rocketmq-namesrv:9876
volumes:
- ./rocketmq/broker/logs:/home/rocketmq/logs
- ./rocketmq/broker/store:/home/rocketmq/store
- ./rocketmq/broker/conf/broker.conf:/home/rocketmq/rocketmq-5.3.2/conf/broker.conf
- ./rocketmq/broker/conf/plain_acl.yml:/home/rocketmq/rocketmq-5.3.2/conf/plain_acl.yml
depends_on:
- rocketmq-namesrv
command: sh mqbroker -c /home/rocketmq/rocketmq-5.3.2/conf/broker.conf
rocketmq-proxy:
image: apache/rocketmq:5.3.2
container_name: rocketmq-proxy-test
networks:
- rocketmq-net
depends_on:
- rocketmq-broker
- rocketmq-namesrv
ports:
- 8182:8080
- 8181:8081
restart: on-failure
environment:
- NAMESRV_ADDR=rocketmq-namesrv:9876
command: sh mqproxy
networks:
rocketmq-net:
driver: bridge

View File

@@ -1,104 +0,0 @@
#所属集群名字
brokerClusterName=DefaultCluster
# 启用Proxy模式可选默认NONE
proxyMode=NONE
# 启用Controller模式集群部署需配置
enableControllerMode=false
#broker名字注意此处不同的配置文件填写的不一样如果在broker-a.properties使用:broker-a,
#在broker-b.properties使用:broker-b
brokerName=broker-a
#0 表示Master>0 表示Slave
brokerId=0
#nameServer地址分号分割
#namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
namesrvAddr=rocketmq-namesrv:9876
#启动IP,如果 docker 报 com.alibaba.rocketmq.remoting.exception.RemotingConnectException: connect to <192.168.0.120:10909> failed
# 解决方式1 加上一句producer.setVipChannelEnabled(false);解决方式2 brokerIP1 设置宿主机IP不要使用docker 内部IP
brokerIP1=192.168.1.37
#在发送消息时自动创建服务器不存在的topic默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic建议线下开启线上关闭 这里仔细看是falsefalsefalse
autoCreateTopicEnable=true
enableAutoCreateSystemTopic=true
# 必须设置为true与proxy协调
enableAutoCreateSubscriptionGroup=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#此参数控制是否开启密码,不开启可设置false
aclEnable=false
#删除文件时间点默认凌晨4点
deleteWhen=04
#文件保留时间默认48小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mappedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条根据业务情况调整
mappedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/home/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/home/rocketmq/store/commitlog
#消费队列存储
storePathConsumeQueue=/home/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/home/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/home/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/home/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
# 延迟消息配置
#timerWheelEnable=false
#enableScheduleMessage=false
# 确保此目录存在且可写
#timerStorePath=/home/rocketmq/store/timerwheel
#timerFlushIntervalMs=1000
#timerPrecisionMs=1000
#scheduleMessageServiceThreadPoolNums=4
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=ASYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘 SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
# 堆外内存限制(需与 JVM 参数一致)
maxDirectMemorySize=1g
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128

View File

@@ -1,24 +0,0 @@
globalWhiteRemoteAddresses:
# - 47.100.93.*
# - 156.254.120.*
accounts:
- accessKey: RocketMQ
secretKey: mq000000
whiteRemoteAddress:
admin: true
defaultTopicPerm: PUB|SUB
defaultGroupPerm: PUB|SUB
topicPerms:
- topicA=DENY
- topicB=PUB|SUB
- topicC=SUB
groupPerms:
- groupA=DENY
- groupB=PUB|SUB
- groupC=SUB
- accessKey: earthearth
secretKey: mq000000
whiteRemoteAddress:
admin: true

View File

@@ -1,3 +0,0 @@
{
"rocketMQClusterName": "DefaultCluster"
}

View File

@@ -1,95 +0,0 @@
## 📝 修改环境配置
上传目录下的docker文件夹到服务器/home/docker下面, 需要修改env文件夹rocketmq文件夹里面的配置, 特别是[broker.conf](docker/rocketmq/broker/conf/broker.conf)里面brokerIP1的值
## 🛠️ 启动命令
- **提高权限**: sudo chmod -R 777 /home/docker/rocketmq
- **开放端口**: 先去你购物服务器的平台里面的安全组里面放行mysql、redis、rocketmq部署的端口再去1panel或宝塔下面放行
- **执行命令**: cd /home/docker & docker-compose up -d
- **导入mysql数据库**: install文件夹下面有一个nacos[mysql-schema.sql](mysql-schema.sql)的sql文件需要导入到数据库里面不然nacos启动不了
- **重试nacos**: 如果还不行就删了nacos的容器重新执行 docker-compose up -d
## 🖼️ 效果预览
![环境部署效果.png](image/%E7%8E%AF%E5%A2%83%E9%83%A8%E7%BD%B2%E6%95%88%E6%9E%9C.png)
## 🛠️ 安装项目环境
- **开放端口**: 安全组里面放行jenkins部署的端口再去1panel或宝塔下面放行, 我这里配置的是20000
- **安装JDK21**: https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz [安装在宿主机]
- **安装git、maven**: 我机器自带的git、https://maven.apache.org/download.cgi 下载然后上传解压即可 [安装在宿主机]
## 🛠️ jenkins 环境配置
- **查看密码**: docker logs -f jenkins | grep "initialAdminPassword"
![jenkins密码.png](image/jenkins%E5%AF%86%E7%A0%81.png)
- **进入jenkins**: http://ip:20000输入上一步查看得到的密码配置各项基础信息
- **安装插件**: 自行选择默认推荐的就行ant、Gradle等一些没用的插件可以取消勾选
- **下载必选jenkins插件**: http://ip:20000/manage/pluginManager/
```
Git Parameter
Git plugin
Maven Integration
gitee
Publish Over SSH
```
- **系统全局配置**: http://ip:20000/manage/configure 配置环境变量
```
PATH
/opt/java/openjdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/var/jenkins_home/maven/bin:/root/bin
```
![img.png](image/img.png)
- **系统全局配置**: http://ip:20000/manage/configure 配置项目地址; 添加凭证时必须选giteeAPI, util、cloud两个项目地址都需要配进去,点击测试链接按钮必须返回成功
![img_2.png](image/img_2.png)
![img_1.png](image/img_1.png)
- **系统全局配置**: http://ip:20000/manage/configureTools/ 配置jenkins内部maven; 项目环境中下载的maven解压到 /home/jenkins/work 下面
![img_4.png](image/img_4.png)
![img_3.png](image/img_3.png)
## 🛠️ 创建util、cloud项目流水线
- **创建util**: 输入名称、选择流水线
![img_8.png](image/img_8.png)
- **配置util**: 根据步骤,仔细对比这上面的内容
![img_6.png](image/img_6.png)
- **创建账号凭证**: 这一步创建的账号是username!!!注意看是你登录gitee的账号密码。不是apikey!
![img_5.png](image/img_5.png)
- **创建cloud**: 输入名称、选择流水线
![img_7.png](image/img_7.png)
- **配置cloud**: 根据步骤,仔细对比这上面的内容
![img_9.png](image/img_9.png)
## 🛠️ 编译 util、cloud
- **install util**: 因为cloud依赖 util, 所以先安装util到maven本地仓库
![img.png](image/img_11.png)
- **install cloud**: 必须等util模块install完成才行这样cloud模块就可以通过maven本地仓库找到util依赖
![img_1.png](image/img_10.png)
## 🛠️ 运行项目
- **开放端口**: 10911 [rocketMQ通信用的]
- **授权目录**:
```
chmod 777 /home/docker/rocketmq/broker/logs
chmod 777 /home/docker/rocketmq/broker/store
chmod 777 /home/docker/rocketmq/namesrv/logs
chmod 777 /home/docker/rocketmq/namesrv/store
```
- **导入数据库**: luohuo_im_01、luohuo_dev
- **执行命令**: /bin/bash /home/jenkins/work/workspace/luohuo-cloud/src/main/bin/all-start.sh
![img_3.png](image/img_12.png)
- **注意事项**: 消息推送采用CLUSTERING模式单个mq只能被一个cloud项目连接多个cloud可能会导致消息接收不到; 若需要多次连接必须docker再开一个rocketmq的test版本单独连接直接copy[rocketmqtest](rocketmqtest)目录 docker-compose up -d即可
## 🛠️ 本地视频语音电话
- **浏览器输入地址**: chrome://flags/
- **搜索关键字**: Insecure origins treated as secure
- **开放端口和ip**: http://192.168.1.37:6130,http://192.168.1.24:6130,http://192.168.1.26:6130
```

View File

@@ -26,6 +26,11 @@ public class RedisKey {
*/
public static final String FEED_MEDIA = BASE_KEY + "feedMedia";
/**
* 用户徽章
*/
public static final String USER_ITEM = BASE_KEY + "userItem";
/**
* 朋友圈权限
*/
@@ -41,6 +46,11 @@ public class RedisKey {
*/
public static final String USER_INFO_FORMAT = "userInfo:uid_%d";
/**
* 消息缓存
*/
public static final String ROOM_MSG_FORMAT = "msg:%d";
/**
* 房间详情
*/

View File

@@ -16,12 +16,15 @@ public class GroupInviteMemberEvent extends ApplicationEvent {
private final Long roomId;
// 消息接收人
private final Long uid;
// 是否是申请进群
private final Boolean applyFor;
public GroupInviteMemberEvent(Object source, Long roomId, List<Long> memberList, Long uid) {
public GroupInviteMemberEvent(Object source, Long roomId, List<Long> memberList, Long uid, Boolean applyFor) {
super(source);
this.memberList = memberList;
this.roomId = roomId;
this.uid = uid;
this.applyFor = applyFor;
}
}

View File

@@ -40,7 +40,7 @@ public class GroupInviteMemberListener {
List<Long> uidList = event.getMemberList();
Long roomId = event.getRoomId();
User user = userCache.get(event.getUid());
ChatMessageReq chatMessageReq = RoomAdapter.buildGroupAddMessage(roomId, user, userCache.getBatch(uidList));
ChatMessageReq chatMessageReq = RoomAdapter.buildGroupAddMessage(event.getApplyFor(), roomId, user, userCache.getBatch(uidList));
chatService.sendMsg(chatMessageReq, user.getId());
}

View File

@@ -1,5 +1,6 @@
package com.luohuo.flex.im.common.event.listener;
import com.luohuo.flex.im.core.user.service.cache.ItemCache;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -12,7 +13,6 @@ import com.luohuo.flex.im.domain.entity.ItemConfig;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.domain.entity.UserBackpack;
import com.luohuo.flex.im.domain.enums.ItemTypeEnum;
import com.luohuo.flex.im.core.user.service.cache.ItemCache;
import java.util.Objects;
@@ -42,7 +42,7 @@ public class ItemReceiveListener {
@EventListener(classes = ItemReceiveEvent.class)
public void wear(ItemReceiveEvent event) {
UserBackpack userBackpack = event.getUserBackpack();
ItemConfig itemConfig = itemCache.getById(userBackpack.getItemId());
ItemConfig itemConfig = itemCache.get(userBackpack.getItemId());
if (ItemTypeEnum.BADGE.getType().equals(itemConfig.getType())) {
User user = userDao.getById(userBackpack.getUid());
if (Objects.isNull(user.getItemId())) {

View File

@@ -2,7 +2,7 @@ package com.luohuo.flex.im.common.event.listener;
import com.luohuo.flex.im.common.event.MessageRecallEvent;
import com.luohuo.flex.model.entity.dto.ChatMsgRecallDTO;
import com.luohuo.flex.im.core.chat.service.cache.MsgPlusCache;
import com.luohuo.flex.im.core.chat.service.cache.MsgCache;
import com.luohuo.flex.im.core.user.service.adapter.WsAdapter;
import com.luohuo.flex.im.core.user.service.impl.PushService;
import jakarta.annotation.Resource;
@@ -22,7 +22,7 @@ import static com.luohuo.flex.im.common.config.ThreadPoolConfig.LUOHUO_EXECUTOR;
@Component
public class MessageRecallListener {
@Resource
private MsgPlusCache msgPlusCache;
private MsgCache msgCache;
@Resource
private PushService pushService;
@@ -31,7 +31,7 @@ public class MessageRecallListener {
public void evictMsg(MessageRecallEvent event) {
ChatMsgRecallDTO recallDTO = event.getRecallDTO();
// msgCache.evictMsg(recallDTO.getMsgId());
msgPlusCache.delete(recallDTO.getMsgId());
msgCache.delete(recallDTO.getMsgId());
}
@Async(LUOHUO_EXECUTOR)

View File

@@ -1,14 +1,14 @@
package com.luohuo.flex.im.common.event.listener;
import com.luohuo.flex.im.core.user.dao.NoticeDao;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
import com.luohuo.flex.im.common.event.UserApplyEvent;
import com.luohuo.flex.im.core.user.dao.UserApplyDao;
import com.luohuo.flex.im.domain.entity.UserApply;
import com.luohuo.flex.model.entity.ws.WSFriendApply;
import com.luohuo.flex.model.entity.ws.WSNotice;
import com.luohuo.flex.im.core.user.service.adapter.WsAdapter;
import com.luohuo.flex.im.core.user.service.impl.PushService;
@@ -22,8 +22,8 @@ import static com.luohuo.flex.im.common.config.ThreadPoolConfig.LUOHUO_EXECUTOR;
@Slf4j
@Component
public class UserApplyListener {
@Resource
private UserApplyDao userApplyDao;
@Resource
private NoticeDao noticeDao;
@Resource
private PushService pushService;
@@ -32,7 +32,7 @@ public class UserApplyListener {
@TransactionalEventListener(classes = UserApplyEvent.class, fallbackExecution = true)
public void notifyFriend(UserApplyEvent event) {
UserApply userApply = event.getUserApply();
WSFriendApply resp = userApplyDao.getUnReadCount(userApply.getUid(), userApply.getTargetId());
WSNotice resp = noticeDao.getUnReadCount(userApply.getUid(), userApply.getTargetId());
pushService.sendPushMsg(WsAdapter.buildApplySend(resp), userApply.getTargetId(), userApply.getUid());
}

View File

@@ -1,15 +1,9 @@
package com.luohuo.flex.im.core.chat.service;
import com.luohuo.flex.im.domain.vo.request.*;
import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp;
import com.luohuo.flex.im.domain.dto.MsgReadInfoDTO;
import com.luohuo.flex.im.domain.entity.Message;
import com.luohuo.flex.im.domain.vo.request.ChatMessageBaseReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageMarkReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageMemberReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessagePageReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReadInfoReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReadReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReq;
import com.luohuo.flex.im.domain.vo.response.ChatMessageReadResp;
import com.luohuo.flex.model.entity.ws.ChatMessageResp;
import jakarta.annotation.Nullable;
@@ -31,7 +25,7 @@ public interface ChatService {
/**
* 获取所有消息
*/
List<ChatMessageResp> getMsgList(Long lastOptTime, Long receiveUid);
List<ChatMessageResp> getMsgList(MsgReq msgReq, Long receiveUid);
/**
* 根据消息获取消息前端展示的物料

View File

@@ -28,7 +28,6 @@ import com.luohuo.flex.im.domain.vo.request.room.ReadAnnouncementsParam;
import com.luohuo.flex.im.domain.vo.request.room.RoomGroupReq;
import com.luohuo.flex.im.domain.vo.response.AnnouncementsResp;
import com.luohuo.flex.im.domain.vo.response.ChatMemberListResp;
import com.luohuo.flex.model.entity.ws.ChatMessageResp;
import com.luohuo.flex.im.domain.vo.response.ChatRoomResp;
import com.luohuo.flex.im.domain.vo.response.MemberResp;
import com.luohuo.flex.im.domain.vo.req.MergeMessageReq;
@@ -182,7 +181,7 @@ public interface RoomAppService {
* 合并消息
* @return
*/
ChatMessageResp mergeMessage(Long uid, MergeMessageReq req);
void mergeMessage(Long uid, MergeMessageReq req);
/**
* 查询全部群成员
@@ -193,7 +192,7 @@ public interface RoomAppService {
/**
* 解散群聊
*/
void exitGroup(Long uid, @Valid MemberExitReq request);
void exitGroup(Boolean isGroup, Long uid, @Valid MemberExitReq request);
/**
* 添加管理员

View File

@@ -86,7 +86,7 @@ public class MemberAdapter {
return wsBaseResp;
}
public static WsBaseResp<WSMemberChange> buildMemberRemoveWS(Long roomId, Integer totalNun, List<Long> uidList, Integer type) {
public static WsBaseResp<WSMemberChange> buildMemberRemoveWS(Long roomId, Integer totalNun, Integer onlineNum, List<Long> uidList, Integer type) {
WsBaseResp<WSMemberChange> wsBaseResp = new WsBaseResp<>();
wsBaseResp.setType(WSRespTypeEnum.memberChange.getType());
WSMemberChange wsMemberChange = new WSMemberChange();
@@ -98,6 +98,7 @@ public class MemberAdapter {
}).collect(Collectors.toList());
wsMemberChange.setUserList(states);
wsMemberChange.setTotalNum(totalNun);
wsMemberChange.setOnlineNum(onlineNum);
wsMemberChange.setRoomId(roomId+"");
wsMemberChange.setChangeType(type);
wsBaseResp.setData(wsMemberChange);

View File

@@ -7,20 +7,20 @@ import com.luohuo.flex.im.domain.entity.Announcements;
import com.luohuo.flex.im.domain.entity.Message;
import com.luohuo.flex.im.domain.entity.MessageMark;
import com.luohuo.flex.im.domain.entity.msg.AudioCallMsgDTO;
import com.luohuo.flex.im.domain.entity.msg.BodyDTO;
import com.luohuo.flex.im.domain.entity.msg.VideoCallMsgDTO;
import com.luohuo.flex.im.domain.entity.msg.MergeMsg;
import com.luohuo.flex.im.domain.entity.msg.MergeMsgDTO;
import com.luohuo.flex.im.domain.entity.msg.NoticeMsgDTO;
import com.luohuo.flex.im.domain.enums.ApplyReadStatusEnum;
import com.luohuo.flex.im.domain.enums.ApplyStatusEnum;
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
import com.luohuo.flex.im.domain.vo.req.room.UserApplyResp;
import com.luohuo.flex.model.entity.ws.WSFriendApply;
import com.luohuo.flex.model.entity.ws.WSNotice;
import com.luohuo.flex.model.enums.MessageMarkTypeEnum;
import com.luohuo.flex.im.domain.enums.MessageStatusEnum;
import com.luohuo.flex.im.domain.enums.MessageTypeEnum;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReq;
import com.luohuo.flex.im.domain.vo.request.msg.TextMsgReq;
import com.luohuo.flex.im.domain.entity.msg.TextMsgReq;
import com.luohuo.flex.model.entity.ws.ChatMessageResp;
import com.luohuo.flex.im.domain.vo.response.ReadAnnouncementsResp;
import com.luohuo.flex.im.core.chat.service.strategy.msg.AbstractMsgHandler;
@@ -164,13 +164,12 @@ public class MessageAdapter {
/**
* 合并消息
*/
public static ChatMessageReq buildMergeMsg(Long roomId, List<MergeMsg> messages) {
public static ChatMessageReq buildMergeMsg(Long roomId, List<Message> messages) {
ChatMessageReq chatMessageReq = new ChatMessageReq();
chatMessageReq.setRoomId(roomId);
chatMessageReq.setSkip(true);
chatMessageReq.setMsgType(MessageTypeEnum.MERGE.getType());
MergeMsgDTO mergeMsgDTO = new MergeMsgDTO();
mergeMsgDTO.setMessages(messages);
chatMessageReq.setBody(mergeMsgDTO);
chatMessageReq.setBody(new MergeMsgDTO(messages.stream().map(msg -> new BodyDTO(msg.getFromUid().toString(), msg.getId().toString())).toList()));
return chatMessageReq;
}
@@ -206,8 +205,8 @@ public class MessageAdapter {
* 邀请用户进群通知
* @param resp 通知数据
*/
public static WsBaseResp<WSFriendApply> buildInviteeUserAddGroupMessage(WSFriendApply resp) {
WsBaseResp<WSFriendApply> wsBaseResp = new WsBaseResp<>();
public static WsBaseResp<WSNotice> buildInviteeUserAddGroupMessage(WSNotice resp) {
WsBaseResp<WSNotice> wsBaseResp = new WsBaseResp<>();
wsBaseResp.setType(WSRespTypeEnum.NEW_APPLY.getType());
wsBaseResp.setData(resp);
return wsBaseResp;

View File

@@ -61,12 +61,12 @@ public class RoomAdapter {
* 创建群的消息
* @return
*/
public static ChatMessageReq buildGroupAddMessage(Long roomId, User inviter, Map<Long, User> member) {
public static ChatMessageReq buildGroupAddMessage(Boolean applyFor, Long roomId, User inviter, Map<Long, User> member) {
ChatMessageReq chatMessageReq = new ChatMessageReq();
chatMessageReq.setRoomId(roomId);
chatMessageReq.setMsgType(MessageTypeEnum.SYSTEM.getType());
String body = String.format("%s邀请%s加入群聊", inviter.getName(),
member.values().stream().map(User::getName).collect(Collectors.joining("")));
String body = applyFor? String.format("%s申请加入群聊", inviter.getName()):
String.format("%s邀请%s加入群聊", inviter.getName(), member.values().stream().map(User::getName).collect(Collectors.joining("")));
chatMessageReq.setBody(body);
return chatMessageReq;
}

View File

@@ -1,38 +1,44 @@
package com.luohuo.flex.im.core.chat.service.cache;
import com.luohuo.flex.im.common.constant.RedisKey;
import com.luohuo.flex.im.common.service.cache.AbstractRedisStringCache;
import com.luohuo.flex.im.core.chat.dao.MessageDao;
import com.luohuo.flex.im.domain.entity.Message;
import com.luohuo.flex.im.core.user.dao.BlackDao;
import com.luohuo.flex.im.core.user.dao.RoleDao;
import com.luohuo.flex.im.core.user.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 消息相关缓存
* @author nyh
*/
@Component
public class MsgCache {
@Resource
private UserDao userDao;
@Resource
private BlackDao blackDao;
@Resource
private RoleDao roleDao;
public class MsgCache extends AbstractRedisStringCache<Long, Message> {
@Resource
private MessageDao messageDao;
@Cacheable(cacheNames = "luohuo:msg", key = "#msgId", unless = "#result == null")
public Message getMsg(Long msgId) {
return messageDao.getById(msgId);
@Override
protected String getKey(Long messageId) {
return RedisKey.getKey(RedisKey.ROOM_MSG_FORMAT, messageId);
}
@CacheEvict(cacheNames = "luohuo:msg", key = "#msgId")
public Message evictMsg(Long msgId) {
return null;
@Override
protected Long getExpireSeconds() {
return -1L;
}
@Override
protected Map<Long, Message> load(List<Long> messageIds) {
return messageDao.listByIds(messageIds)
.stream().collect(Collectors.toMap(Message::getId, Function.identity()));
}
@Override
public void delete(Long messageId) {
super.delete(messageId);
}
}

View File

@@ -1,43 +0,0 @@
package com.luohuo.flex.im.core.chat.service.cache;
import com.luohuo.flex.im.common.service.cache.AbstractRedisStringCache;
import com.luohuo.flex.im.core.chat.dao.MessageDao;
import com.luohuo.flex.im.domain.entity.Message;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 升级消息相关缓存
* @author nyh
*/
@Component
public class MsgPlusCache extends AbstractRedisStringCache<Long, Message> {
@Resource
private MessageDao messageDao;
@Override
protected String getKey(Long messageId) {
return "msg" + messageId;
}
@Override
protected Long getExpireSeconds() {
return 3 * 60L;
}
@Override
protected Map<Long, Message> load(List<Long> messageIds) {
return messageDao.listByIds(messageIds)
.stream().collect(Collectors.toMap(Message::getId, Function.identity()));
}
@Override
public void delete(Long messageId) {
super.delete(messageId);
}
}

View File

@@ -51,7 +51,7 @@ public class RoomGroupCache extends AbstractRedisStringCache<Long, RoomGroup> {
/**
* 根据群号查询群信息
*/
@Cacheable(cacheNames = "room", key = "'findGroup'+#account")
@Cacheable(cacheNames = "luohuo:room:findGroup", key = "#account")
public List<RoomGroup> searchGroup(String account) {
return roomGroupDao.searchGroup(account);
}
@@ -61,7 +61,7 @@ public class RoomGroupCache extends AbstractRedisStringCache<Long, RoomGroup> {
* @param account
* @return
*/
@CacheEvict(cacheNames = "room", key = "'findGroup'+#account")
@CacheEvict(cacheNames = "luohuo:room:findGroup", key = "#account")
public List<Long> evictGroup(String account) {
return null;
}
@@ -69,7 +69,7 @@ public class RoomGroupCache extends AbstractRedisStringCache<Long, RoomGroup> {
/**
* 清除所有群组相关缓存
*/
@CacheEvict(cacheNames = "room", allEntries = true)
@CacheEvict(cacheNames = "luohuo:room:findGroup", allEntries = true)
public void evictAllCaches() {
}
}

View File

@@ -13,6 +13,7 @@ import com.luohuo.flex.im.core.chat.service.cache.MsgCache;
import com.luohuo.flex.im.core.user.dao.UserFriendDao;
import com.luohuo.flex.im.domain.entity.*;
import com.luohuo.flex.im.domain.enums.*;
import com.luohuo.flex.im.domain.vo.request.*;
import jakarta.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -26,13 +27,6 @@ import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp;
import com.luohuo.flex.im.common.event.MessageSendEvent;
import com.luohuo.flex.im.domain.dto.ChatMsgSendDto;
import com.luohuo.flex.im.domain.dto.MsgReadInfoDTO;
import com.luohuo.flex.im.domain.vo.request.ChatMessageBaseReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageMarkReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageMemberReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessagePageReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReadInfoReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReadReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReq;
import com.luohuo.flex.im.domain.vo.response.ChatMessageReadResp;
import com.luohuo.flex.model.entity.ws.ChatMessageResp;
import com.luohuo.flex.im.core.chat.service.ChatService;
@@ -48,7 +42,6 @@ import com.luohuo.flex.im.core.chat.service.strategy.msg.RecallMsgHandler;
import com.luohuo.flex.im.core.user.service.RoleService;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@@ -198,25 +191,26 @@ public class ChatServiceImpl implements ChatService {
}
@Override
public List<ChatMessageResp> getMsgList(Long lastOptTime, Long receiveUid) {
// 1. 获取用户所有未屏蔽的聊天室ID列表
List<Long> roomIds = getAccessibleRoomIds(receiveUid);
if (CollectionUtil.isEmpty(roomIds)) {
return Collections.emptyList();
public List<ChatMessageResp> getMsgList(MsgReq msgReq, Long receiveUid) {
List<Message> messages;
if(CollUtil.isEmpty(msgReq.getMsgIds())){
// 1. 获取用户所有未屏蔽的聊天室ID列表
List<Long> roomIds = getAccessibleRoomIds(receiveUid);
if (CollectionUtil.isEmpty(roomIds)) {
return Collections.emptyList();
}
// 2. 计算查询的时间范围最多15天
LocalDateTime effectiveStartTime = calculateStartTime(msgReq.getLastOptTime());
messages = messageDao.list(new LambdaQueryWrapper<Message>().in(Message::getRoomId, roomIds)
.between(Message::getCreateTime, effectiveStartTime, LocalDateTime.now()));
} else {
messages = getMsgByIds(msgReq.getMsgIds());
}
// 2. 批量查询所有房间的最后一条消息ID用于权限过滤
LambdaQueryWrapper<Message> wrapper = new LambdaQueryWrapper<Message>().in(Message::getRoomId, roomIds);
if(ObjectUtil.isNotNull(lastOptTime) && lastOptTime > 0) {
wrapper.ge(Message::getCreateTime, TimeUtils.getDateTimeOfTimestamp(lastOptTime)).le(Message::getCreateTime, LocalDateTime.now());
}else {
wrapper.ge(Message::getCreateTime, LocalDate.now().minusDays(15));
}
List<Message> messages = messageDao.list(wrapper);
Map<Long, List<Message>> groupedMessages = messages.stream().collect(Collectors.groupingBy(Message::getRoomId));
// 4. 转换为响应对象并返回
// 3. 转换为响应对象并返回
List<ChatMessageResp> baseMessages = new ArrayList<>();
for (Long roomId : groupedMessages.keySet()) {
baseMessages.addAll(getMsgRespBatch(groupedMessages.get(roomId), receiveUid));
@@ -224,6 +218,20 @@ public class ChatServiceImpl implements ChatService {
return baseMessages;
}
/**
* 计算查询的起始时间, 默认最近15天的消息内容
*/
private LocalDateTime calculateStartTime(Long lastOptTime) {
LocalDateTime defaultStartTime = LocalDateTime.now().minusDays(15);
if (ObjectUtil.isNotNull(lastOptTime) && lastOptTime > 0) {
LocalDateTime proposedTime = TimeUtils.getDateTimeOfTimestamp(lastOptTime);
return proposedTime.isAfter(defaultStartTime) ? proposedTime : defaultStartTime;
}
return defaultStartTime;
}
private Long getLastMsgId(Long roomId, Long receiveUid) {
Room room = roomCache.get(roomId);
AssertUtil.isNotEmpty(room, "房间号有误");
@@ -241,7 +249,7 @@ public class ChatServiceImpl implements ChatService {
AbstractMsgMarkStrategy strategy = MsgMarkFactory.getStrategyNoNull(request.getMarkType());
// 校验消息
Message message = msgCache.getMsg(request.getMsgId());
Message message = msgCache.get(request.getMsgId());
if (Objects.isNull(message)) {
return;
}
@@ -328,7 +336,7 @@ public class ChatServiceImpl implements ChatService {
@Override
public List<Message> getMsgByIds(List<Long> msgIds) {
return messageDao.listByIds(msgIds);
return msgCache.getBatch(msgIds).values().stream().collect(Collectors.toList());
}
@Override

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baidu.fsg.uid.UidGenerator;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.luohuo.basic.cache.repository.CachePlusOps;
@@ -13,21 +14,24 @@ import com.luohuo.basic.utils.SpringUtils;
import com.luohuo.basic.utils.TimeUtils;
import com.luohuo.flex.common.cache.PresenceCacheKeyBuilder;
import com.luohuo.flex.im.api.PresenceApi;
import com.luohuo.flex.im.common.event.GroupInviteMemberEvent;
import com.luohuo.flex.im.core.chat.dao.RoomFriendDao;
import com.luohuo.flex.im.core.chat.dao.RoomGroupDao;
import com.luohuo.flex.im.core.user.dao.NoticeDao;
import com.luohuo.flex.im.core.user.dao.UserApplyDao;
import com.luohuo.flex.im.core.user.dao.UserBackpackDao;
import com.luohuo.flex.im.core.user.dao.UserFriendDao;
import com.luohuo.flex.im.core.user.dao.UserPrivacyDao;
import com.luohuo.flex.im.core.user.service.NoticeService;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import com.luohuo.flex.im.domain.entity.*;
import com.luohuo.flex.im.domain.enums.ApplyStatusEnum;
import com.luohuo.flex.im.domain.enums.*;
import com.luohuo.flex.im.domain.vo.req.room.UserApplyResp;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReq;
import com.luohuo.flex.im.domain.vo.request.admin.AdminAddReq;
import com.luohuo.flex.im.domain.vo.request.admin.AdminRevokeReq;
import com.luohuo.flex.im.domain.vo.request.member.MemberExitReq;
import com.luohuo.flex.im.domain.entity.msg.TextMsgReq;
import com.luohuo.flex.model.enums.ChatActiveStatusEnum;
import com.luohuo.flex.im.domain.vo.request.contact.ContactAddReq;
import jakarta.validation.Valid;
@@ -52,11 +56,6 @@ import com.luohuo.flex.im.core.chat.dao.ContactDao;
import com.luohuo.flex.im.core.chat.dao.GroupMemberDao;
import com.luohuo.flex.im.core.chat.dao.MessageDao;
import com.luohuo.flex.im.domain.dto.RoomBaseInfo;
import com.luohuo.flex.im.domain.entity.msg.MergeMsg;
import com.luohuo.flex.im.domain.enums.GroupRoleAPPEnum;
import com.luohuo.flex.im.domain.enums.GroupRoleEnum;
import com.luohuo.flex.im.domain.enums.HotFlagEnum;
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
import com.luohuo.flex.im.domain.vo.request.ChatMessageMemberReq;
import com.luohuo.flex.im.domain.vo.request.ContactFriendReq;
import com.luohuo.flex.im.domain.vo.request.GroupAddReq;
@@ -74,7 +73,6 @@ import com.luohuo.flex.im.domain.vo.request.room.ReadAnnouncementsParam;
import com.luohuo.flex.im.domain.vo.request.room.RoomGroupReq;
import com.luohuo.flex.im.domain.vo.response.AnnouncementsResp;
import com.luohuo.flex.im.domain.vo.response.ChatMemberListResp;
import com.luohuo.flex.model.entity.ws.ChatMessageResp;
import com.luohuo.flex.im.domain.vo.response.ChatRoomResp;
import com.luohuo.flex.im.domain.vo.response.MemberResp;
import com.luohuo.flex.im.domain.vo.response.ReadAnnouncementsResp;
@@ -94,7 +92,6 @@ import com.luohuo.flex.im.core.chat.service.cache.RoomGroupCache;
import com.luohuo.flex.im.core.chat.service.strategy.msg.AbstractMsgHandler;
import com.luohuo.flex.im.core.chat.service.strategy.msg.MsgHandlerFactory;
import com.luohuo.flex.im.core.user.dao.UserDao;
import com.luohuo.flex.im.domain.enums.RoleTypeEnum;
import com.luohuo.flex.model.entity.WsBaseResp;
import com.luohuo.flex.im.domain.vo.req.MergeMessageReq;
import com.luohuo.flex.model.entity.ws.ChatMemberResp;
@@ -122,6 +119,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
private final UserBackpackDao userBackpackDao;
private final RoomGroupDao roomGroupDao;
private final RoomFriendDao roomFriendDao;
private final NoticeDao noticeDao;
private final UserApplyDao userApplyDao;
private ContactDao contactDao;
private RoomCache roomCache;
@@ -140,6 +138,8 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
private ChatService chatService;
private RoleService roleService;
private RoomService roomService;
private UidGenerator uidGenerator;
private NoticeService noticeService;
private GroupMemberCache groupMemberCache;
private PushService pushService;
private FriendService friendService;
@@ -170,7 +170,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 分页加载所有群
roomGroupDao.list().parallelStream().map(RoomGroup::getRoomId).forEach(this::warmUpGroupMemberCache);
List<Long> roomFriendList = roomFriendDao.list().parallelStream().map(RoomFriend::getRoomId).toList();
if(CollUtil.isNotEmpty(roomFriendList)) {
if (CollUtil.isNotEmpty(roomFriendList)) {
friendService.warmUpRoomMemberCache(roomFriendList);
}
// 分页加载所有用户有多少个群的缓存
@@ -225,11 +225,12 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 2. 最后组装会话信息(名称,头像,未读数等)
List<ChatRoomResp> result = buildContactResp(contactHashMap, uid, page.getList());
return CursorPageBaseResp.init(page, result,0L);
return CursorPageBaseResp.init(page, result, 0L);
}
/**
* 返回当前登录用户的全部会话
*
* @param uid
* @return
*/
@@ -329,6 +330,22 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 5. 增加管理员
groupMemberDao.addAdmin(roomGroup.getId(), request.getUidList());
// 每个被邀请的人都要收到邀请进群的消息
setAdminNotice(NoticeTypeEnum.GROUP_SET_ADMIN, uid, request.getUidList(), manageUidList, roomGroup.getName());
}
private void setAdminNotice(NoticeTypeEnum noticeTypeEnum, Long uid, List<Long> uidList, List<Long> manageUidList, String content) {
long uuid = uidGenerator.getUid();
uidList.stream().filter(id -> !manageUidList.contains(id)).forEach(id -> noticeService.createNotice(
RoomTypeEnum.GROUP,
noticeTypeEnum,
uid,
id,
uuid,
id,
content
));
}
/**
@@ -355,12 +372,13 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 4. 撤销管理员
groupMemberDao.revokeAdmin(roomGroup.getId(), request.getUidList());
setAdminNotice(NoticeTypeEnum.GROUP_RECALL_ADMIN, uid, request.getUidList(), new ArrayList<>(), roomGroup.getName());
}
/**
* @param roomId 房间id
* @param roomId 房间id
* @param groupId 群聊id
* @param uid 当前人员id
* @param uid 当前人员id
*/
@Override
public void createSystemFriend(Long roomId, Long groupId, Long uid) {
@@ -378,6 +396,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
/**
* 分页查询加群申请记录
*
* @param uid 登录用户id
* @param req 分页请求
*/
@@ -408,14 +427,15 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
/**
* 处理用户在线状态
*
* @param uidList 需要处理的用户
* @param roomId 在某个房间处理
* @param roomId 在某个房间处理
* @return
*/
// @Async(LUOHUO_EXECUTOR)
public void asyncOnline(List<Long> uidList, Long roomId, boolean online) {
Set<Long> onlineList = presenceApi.getOnlineUsersList(uidList).getData();
if(CollUtil.isEmpty(onlineList)){
if (CollUtil.isEmpty(onlineList)) {
return;
}
@@ -423,7 +443,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
for (Long uid : onlineList) {
CacheKey ougKey = PresenceCacheKeyBuilder.onlineUserGroupsKey(uid);
if(online) {
if (online) {
// 处理在线的状态
cachePlusOps.sAdd(ogmKey, uid);
cachePlusOps.sAdd(ougKey, roomId);
@@ -437,16 +457,17 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
/**
* 校验用户在群里的权限
*
* @param uid
* @return
*/
public GroupMember verifyGroupPermissions(Long uid, RoomGroup roomGroup) {
if(ObjectUtil.isNull(roomGroup)){
if (ObjectUtil.isNull(roomGroup)) {
throw new RuntimeException("群聊不存在!");
}
GroupMember groupMember = groupMemberDao.getMemberByGroupId(roomGroup.getId(), uid);
if(ObjectUtil.isNull(groupMember)){
if (ObjectUtil.isNull(groupMember)) {
throw new RuntimeException(StrUtil.format("您不是{}的群成员!", roomGroup.getName()));
}
return groupMember;
@@ -454,6 +475,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
/**
* 群主,管理员才可以修改
*
* @param uid
* @param request
* @return
@@ -464,7 +486,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
RoomGroup roomGroup = roomGroupCache.get(request.getId());
GroupMember groupMember = verifyGroupPermissions(uid, roomGroup);
if(GroupRoleEnum.MEMBER.getType().equals(groupMember.getRoleId())) {
if (GroupRoleEnum.MEMBER.getType().equals(groupMember.getRoleId())) {
return false;
}
@@ -474,7 +496,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
Boolean success = roomService.updateRoomInfo(roomGroup);
// 3.通知群里所有人群信息修改了
if(success){
if (success) {
roomGroupCache.delete(roomGroup.getId());
roomGroupCache.evictGroup(roomGroup.getAccount());
List<Long> memberUidList = groupMemberCache.getMemberExceptUidList(roomGroup.getRoomId());
@@ -490,7 +512,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
GroupMember member = verifyGroupPermissions(uid, roomGroup);
// 2.修改我的信息
boolean equals = member.getMyName().equals(StrUtil.isEmpty(request.getMyName())? "": request.getMyName());
boolean equals = member.getMyName().equals(StrUtil.isEmpty(request.getMyName()) ? "" : request.getMyName());
boolean success = groupMemberDao.update(null, Wrappers.<GroupMember>lambdaUpdate()
.set(GroupMember::getRemark, request.getRemark())
.set(GroupMember::getMyName, request.getMyName())
@@ -499,7 +521,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
groupMemberCache.evictMemberDetail(roomGroup.getRoomId(), uid);
// 3.通知群里所有人我的信息改变了
if(!equals && success){
if (!equals && success) {
List<Long> memberUidList = groupMemberCache.getMemberExceptUidList(roomGroup.getRoomId());
pushService.sendPushMsg(RoomAdapter.buildMyRoomGroupChangeWS(roomGroup.getRoomId(), uid, request.getMyName()), memberUidList, uid);
}
@@ -510,7 +532,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
public Boolean setTop(Long uid, ContactTopReq request) {
// 1.判断会话我有没有
Contact contact = contactDao.get(uid, request.getRoomId());
if(ObjectUtil.isNull(contact)){
if (ObjectUtil.isNull(contact)) {
return false;
}
@@ -524,7 +546,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
public Boolean pushAnnouncement(Long uid, AnnouncementsParam param) {
RoomGroup roomGroup = roomGroupCache.get(param.getRoomId());
List<Long> uids = roomService.getGroupUsers(roomGroup.getId(), false);
if(CollUtil.isNotEmpty(uids)){
if (CollUtil.isNotEmpty(uids)) {
LocalDateTime now = LocalDateTime.now();
Announcements announcements = new Announcements();
announcements.setContent(param.getContent());
@@ -547,7 +569,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
});
// 批量添加未读消息
Boolean saved = roomService.saveBatchAnnouncementsRecord(announcementsReadRecordList);
if(saved){
if (saved) {
// 发送公告消息、推送群成员公告内容
chatService.sendMsg(MessageAdapter.buildAnnouncementsMsg(param.getRoomId(), announcements), uid);
}
@@ -560,9 +582,9 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
public Boolean announcementEdit(Long uid, AnnouncementsParam param) {
RoomGroup roomGroup = roomGroupCache.get(param.getRoomId());
List<Long> uids = roomService.getGroupUsers(roomGroup.getId(), false);
if(CollUtil.isNotEmpty(uids)){
if (CollUtil.isNotEmpty(uids)) {
AnnouncementsResp announcement = roomService.getAnnouncement(param.getId());
if(ObjectUtil.isNull(announcement)){
if (ObjectUtil.isNull(announcement)) {
return false;
}
Announcements announcements = new Announcements();
@@ -572,7 +594,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
announcements.setTop(param.getTop());
announcements.setUpdateTime(TimeUtils.now());
Boolean edit = roomService.updateAnnouncement(announcements);
if(edit){
if (edit) {
chatService.sendMsg(MessageAdapter.buildAnnouncementsMsg(param.getRoomId(), announcements), uid);
}
return edit;
@@ -590,7 +612,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 1.更新已读状态
Boolean success = roomService.readAnnouncement(uid, param.getAnnouncementId());
if(success){
if (success) {
// 2.刷新最新的已读数量,通知所有人有人对 announcementId 已读了
roomAnnouncementsCache.add(param.getAnnouncementId(), uid);
@@ -610,7 +632,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 3.查询公告已读数量
Long count = roomAnnouncementsCache.get(param.getAnnouncementId());
if(count < 1){
if (count < 1) {
count = roomService.getAnnouncementReadCount(param.getAnnouncementId());
roomAnnouncementsCache.load(Arrays.asList(param.getAnnouncementId()));
}
@@ -630,7 +652,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
long count = userBackpackDao.countByUidAndItemId(uid, "HuLa项目贡献者专属徽章");
if(count == 0 && GroupRoleEnum.MEMBER.getType().equals(groupMember.getRoleId())) {
if (count == 0 && GroupRoleEnum.MEMBER.getType().equals(groupMember.getRoleId())) {
return false;
}
@@ -646,7 +668,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
public Boolean setNotification(Long uid, ContactNotificationReq request) {
// 1. 判断会话我有没有
Contact contact = contactDao.get(uid, request.getRoomId());
if(ObjectUtil.isNull(contact)){
if (ObjectUtil.isNull(contact)) {
return false;
}
@@ -654,20 +676,20 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
contact.setMuteNotification(request.getType());
// 3.通知所有设备我已经开启/关闭这个房间的免打扰
pushService.sendPushMsg(WsAdapter.buildContactNotification(request) , uid, uid);
pushService.sendPushMsg(WsAdapter.buildContactNotification(request), uid, uid);
return contactDao.updateById(contact);
}
@Override
public Boolean setShield(Long uid, ContactShieldReq request) {
Contact contact = contactDao.get(uid, request.getRoomId());
if(ObjectUtil.isNull(contact)){
if (ObjectUtil.isNull(contact)) {
return false;
}
String name;
Room room = roomCache.get(request.getRoomId());
if(room.getType().equals(RoomTypeEnum.GROUP.getType())){
if (room.getType().equals(RoomTypeEnum.GROUP.getType())) {
// 1. 把群成员的信息设置为禁止
name = roomGroupCache.get(request.getRoomId()).getName();
groupMemberDao.setMemberDeFriend(request.getRoomId(), uid, request.getState());
@@ -687,30 +709,70 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
}
@Override
public ChatMessageResp mergeMessage(Long uid, MergeMessageReq req) {
public void mergeMessage(Long uid, MergeMessageReq req) {
// 1. 校验人员是否在群里、或者有没有对方的好友
Room room = roomCache.get(req.getFromRoomId());
if(ObjectUtil.isNull(room)){
if (ObjectUtil.isNull(room)) {
throw new BizException("房间不存在");
}
if(room.getType().equals(RoomTypeEnum.GROUP.getType())){
if (room.getType().equals(RoomTypeEnum.GROUP.getType())) {
RoomGroup sourceRoomGroup = roomGroupCache.get(req.getFromRoomId());
verifyGroupPermissions(uid, sourceRoomGroup);
} else {
RoomFriend roomFriend = roomFriendCache.get(req.getFromRoomId());
if(ObjectUtil.isNull(roomFriend) || !roomFriend.getUid1().equals(uid) && !roomFriend.getUid1().equals(uid)) {
if (ObjectUtil.isNull(roomFriend) || !roomFriend.getUid1().equals(uid) && !roomFriend.getUid1().equals(uid)) {
throw new BizException("你们不是好友关系");
}
}
// 2. 当是转发单条消息的时候
List<Message> messagess = chatService.getMsgByIds(req.getMessageIds());
List<MergeMsg> msgs = messagess.stream().filter(message -> message.getRoomId().equals(req.getFromRoomId())).map(message -> new MergeMsg(message.getContent(), message.getCreateTime(), userCache.get(message.getFromUid()).getName())).collect(Collectors.toUnmodifiableList());
// 3. 发布合并消息
Long msgId = chatService.sendMsg(MessageAdapter.buildMergeMsg(req.getRoomId(), msgs), uid);
return chatService.getMsgResp(msgId, uid);
for (Long roomId : req.getRoomIds()) {
if (req.getType().equals(MergeTypeEnum.SINGLE.getType())) {
messagess.forEach(message -> {
ChatMessageReq messageReq = new ChatMessageReq();
messageReq.setMsgType(message.getType());
messageReq.setRoomId(roomId);
// 扩展消息需要另外解析
messageReq.setBody(analyze(message));
messageReq.setSkip(true);
chatService.sendMsg(messageReq, uid);
});
} else {
chatService.sendMsg(MessageAdapter.buildMergeMsg(roomId, messagess), uid);
}
}
}
/**
* 解析转发的消息
*
* @param message 数据库里面的消息实体
*/
private Object analyze(Message message) {
return switch (MessageTypeEnum.of(message.getType())) {
case TEXT -> {
TextMsgReq textMsgReq = new TextMsgReq();
textMsgReq.setContent(message.getContent());
textMsgReq.setReplyMsgId(message.getReplyMsgId());
textMsgReq.setAtUidList(message.getExtra().getAtUidList());
yield textMsgReq;
}
case RECALL -> message.getExtra().getRecall();
case IMG -> message.getExtra().getImgMsgDTO();
case FILE -> message.getExtra().getFileMsg();
case SOUND -> message.getExtra().getSoundMsgDTO();
case VIDEO -> message.getExtra().getVideoMsgDTO();
case EMOJI -> message.getExtra().getEmojisMsgDTO();
case BOT, SYSTEM -> null;
case MERGE -> message.getExtra().getMergeMsgDTO();
case NOTICE -> message.getExtra().getNoticeMsgDTO();
case VIDEO_CALL -> message.getExtra().getVideoCallMsgDTO();
case AUDIO_CALL -> message.getExtra().getAudioCallMsgDTO();
};
}
@Override
@@ -784,18 +846,6 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 7. 群主、管理员永远在前面
return chatMemberResps;
// return chatMemberResps.stream()
// .sorted((m1, m2) -> {
// // 群主 > 管理员> 普通成员
// int roleCompare = Integer.compare(m1.getRoleId(), m2.getRoleId());
//
// // 如果是相同角色
// if (roleCompare == 0) {
// return Integer.compare(m1.getActiveStatus(), m2.getActiveStatus());
// }
// // 不同角色:管理组始终排在普通组前面
// return roleCompare;
// }).collect(Collectors.toList());
}
@Override
@@ -827,11 +877,11 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 如果房间人员小于3人 那么直接解散群聊
CacheKey membersKey = PresenceCacheKeyBuilder.groupMembersKey(request.getRoomId());
Long memberNum = cachePlusOps.sCard(membersKey);
if(memberNum <= 3 && self.getRoleId().equals(GroupRoleEnum.LEADER.getType())) {
if (memberNum <= 3) {
MemberExitReq exitReq = new MemberExitReq();
exitReq.setRoomId(request.getRoomId());
exitReq.setAccount(roomGroup.getAccount());
exitGroup(uid, exitReq);
exitGroup(true, uid, exitReq);
return;
}
@@ -850,12 +900,12 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
AssertUtil.isNotEmpty(member, "用户已经移除");
// 发送移除事件告知群成员
if(transactionTemplate.execute(e -> {
if (transactionTemplate.execute(e -> {
groupMemberDao.removeById(member.getId());
// 1.5 移除会话
contactDao.removeByRoomId(room.getId(), Collections.singletonList(request.getUid()));
return true;
})){
})) {
// 移除群聊缓存
CacheKey uKey = PresenceCacheKeyBuilder.userGroupsKey(request.getUid());
cachePlusOps.sRem(membersKey, request.getUid());
@@ -864,14 +914,38 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 推送状态到前端
List<Long> memberUidList = groupMemberCache.getMemberExceptUidList(roomGroup.getRoomId());
if(!memberUidList.contains(request.getUid())){
if (!memberUidList.contains(request.getUid())) {
memberUidList.add(request.getUid());
}
WsBaseResp<WSMemberChange> ws = MemberAdapter.buildMemberRemoveWS(roomGroup.getRoomId(), (int) (memberNum - 1), Arrays.asList(member.getUid()), WSMemberChange.CHANGE_TYPE_REMOVE);
WsBaseResp<WSMemberChange> ws = MemberAdapter.buildMemberRemoveWS(roomGroup.getRoomId(), (int) (memberNum - 1), Math.toIntExact(cachePlusOps.sCard(PresenceCacheKeyBuilder.onlineGroupMembersKey(room.getId()))), Arrays.asList(member.getUid()), WSMemberChange.CHANGE_TYPE_REMOVE);
pushService.sendPushMsg(ws, memberUidList, uid);
groupMemberCache.evictMemberList(room.getId());
groupMemberCache.evictExceptMemberList(room.getId());
groupMemberCache.evictMemberDetail(room.getId(), removedUid);
long uuid = uidGenerator.getUid();
// 保存被删除人的通知
noticeService.createNotice(
RoomTypeEnum.GROUP,
NoticeTypeEnum.GROUP_MEMBER_DELETE,
uid,
removedUid,
uuid,
removedUid,
roomGroup.getName()
);
// 获取所有管理员
List<Long> managerIds = groupMemberDao.getGroupUsers(roomGroup.getId(), true);
managerIds.forEach(managerId -> noticeService.createNotice(
RoomTypeEnum.GROUP,
NoticeTypeEnum.GROUP_MEMBER_DELETE,
uid,
managerId,
uuid,
removedUid,
roomGroup.getName()
));
}
}
@@ -899,18 +973,38 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
}
// 2. 创建邀请记录
transactionTemplate.execute(e -> {
List<UserApply> invites = validUids.stream().map(inviteeUid -> new UserApply(uid, RoomTypeEnum.GROUP.getType(), roomGroup.getRoomId(), inviteeUid, StrUtil.format("{}邀请你加入{}", userSummaryCache.get(uid).getName(), roomGroup.getName()), ApplyStatusEnum.WAIT_APPROVAL.getCode(), UNREAD.getCode(), 0, false)).collect(Collectors.toList());
userApplyDao.saveBatch(invites);
return true;
});
List<UserApply> invites = validUids.stream().map(inviteeUid -> new UserApply(uid, RoomTypeEnum.GROUP.getType(), roomGroup.getRoomId(), inviteeUid, StrUtil.format("{}邀请你加入{}", userSummaryCache.get(uid).getName(), roomGroup.getName()), ApplyStatusEnum.WAIT_APPROVAL.getCode(), UNREAD.getCode(), 0, false)).collect(Collectors.toList());
transactionTemplate.execute(e -> userApplyDao.saveBatch(invites));
// 3. 通知被邀请的人进群
validUids.forEach(inviteId -> {
SummeryInfoDTO user = userSummaryCache.get(inviteId);
if(ObjectUtil.isNotNull(user)){
pushService.sendPushMsg(MessageAdapter.buildInviteeUserAddGroupMessage(userApplyDao.getUnReadCount(inviteId, inviteId)), inviteId, uid);
// 3. 通知被邀请的人进群, 通知时绑定通知id
List<Long> managerIds = groupMemberDao.getGroupUsers(roomGroup.getId(), true);
invites.forEach(invite -> {
SummeryInfoDTO user = userSummaryCache.get(invite.getTargetId());
if (ObjectUtil.isNotNull(user)) {
pushService.sendPushMsg(MessageAdapter.buildInviteeUserAddGroupMessage(noticeDao.getUnReadCount(invite.getTargetId(), invite.getTargetId())), invite.getTargetId(), uid);
}
// 每个被邀请的人都要收到邀请进群的消息
noticeService.createNotice(
RoomTypeEnum.GROUP,
NoticeTypeEnum.GROUP_INVITE_ME,
uid,
invite.getTargetId(),
invite.getId(),
invite.getTargetId(),
roomGroup.getName()
);
// 每个管理员都要收到邀请进群的消息
managerIds.forEach(managerId -> noticeService.createNotice(
RoomTypeEnum.GROUP,
NoticeTypeEnum.GROUP_INVITE,
uid,
managerId,
invite.getId(),
invite.getTargetId(),
roomGroup.getName()
));
});
}
@@ -922,7 +1016,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
*/
@Override
@RedissonLock(prefixKey = "exitGroup:", key = "#request.roomId")
public void exitGroup(Long uid, MemberExitReq request) {
public void exitGroup(Boolean isGroup, Long uid, MemberExitReq request) {
Long roomId = request.getRoomId();
// 1. 判断群聊是否存在
RoomGroup roomGroup = roomGroupCache.getByRoomId(roomId);
@@ -944,14 +1038,20 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
} else {
memberUidList = groupMemberCache.getMemberExceptUidList(roomGroup.getRoomId());
}
CacheKey gKey = PresenceCacheKeyBuilder.groupMembersKey(room.getId());
if (isGroup || isLord) {
User user = userCache.get(uid);
ChatMessageReq messageReq = new ChatMessageReq();
messageReq.setBody(StrUtil.format("{}解散了群聊", user.getName()));
messageReq.setMsgType(MessageTypeEnum.SYSTEM.getType());
chatService.sendMsg(messageReq, uid);
if (isLord) {
// 4.1 删除房间和群并清除缓存
transactionTemplate.execute(e -> {
boolean isDelRoom = roomService.removeById(roomId);
roomGroupCache.removeById(roomGroup.getId());
roomGroupCache.evictGroup(roomGroup.getAccount());
if(StrUtil.isNotEmpty(request.getAccount())){
if (StrUtil.isNotEmpty(request.getAccount())) {
roomGroupCache.evictGroup(request.getAccount());
}
AssertUtil.isTrue(isDelRoom, ResponseEnum.SYSTEM_BUSY.getMsg());
@@ -972,12 +1072,20 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
groupMemberCache.evictAllMemberDetails();
// 新版解散群聊
CacheKey uKey = PresenceCacheKeyBuilder.userGroupsKey(uid);
CacheKey gKey = PresenceCacheKeyBuilder.groupMembersKey(room.getId());
cachePlusOps.del(uKey, gKey);
asyncOnline(memberUidList, room.getId(), false);
pushService.sendPushMsg(RoomAdapter.buildGroupDissolution(roomGroup.getRoomId()), memberUidList, uid);
// pushService.sendPushMsg(RoomAdapter.buildGroupDissolution(roomGroup.getRoomId()), memberUidList, uid);
} else {
if(transactionTemplate.execute(e -> {
// 如果房间人员小于3人 那么直接解散群聊
if (cachePlusOps.sCard(gKey) <= 3) {
MemberExitReq exitReq = new MemberExitReq();
exitReq.setRoomId(request.getRoomId());
exitReq.setAccount(roomGroup.getAccount());
exitGroup(true, uid, exitReq);
return;
}
if (transactionTemplate.execute(e -> {
// 4.6 删除会话
Boolean isDelContact = contactDao.removeByRoomId(roomId, Collections.singletonList(uid));
AssertUtil.isTrue(isDelContact, "会话移除异常");
@@ -985,16 +1093,16 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
Boolean isDelGroupMember = groupMemberDao.removeByGroupId(roomGroup.getId(), Collections.singletonList(uid));
AssertUtil.isTrue(isDelGroupMember, "群成员移除失败");
return true;
})){
})) {
// 新版退出群聊
CacheKey uKey = PresenceCacheKeyBuilder.userGroupsKey(uid);
CacheKey gKey = PresenceCacheKeyBuilder.groupMembersKey(room.getId());
cachePlusOps.sRem(gKey, uid);
cachePlusOps.sRem(uKey, room.getId());
asyncOnline(Arrays.asList(uid), room.getId(), false);
// 4.8 发送移除事件告知群成员
WsBaseResp<WSMemberChange> ws = MemberAdapter.buildMemberRemoveWS(roomGroup.getRoomId(), Math.toIntExact(cachePlusOps.sCard(gKey)), Arrays.asList(uid), WSMemberChange.CHANGE_TYPE_QUIT);
WsBaseResp<WSMemberChange> ws = MemberAdapter.buildMemberRemoveWS(roomGroup.getRoomId(), Math.toIntExact(cachePlusOps.sCard(gKey)), Math.toIntExact(cachePlusOps.sCard(PresenceCacheKeyBuilder.onlineGroupMembersKey(room.getId()))), Arrays.asList(uid), WSMemberChange.CHANGE_TYPE_QUIT);
pushService.sendPushMsg(ws, memberUidList, uid);
groupMemberCache.evictMemberList(room.getId());
groupMemberCache.evictExceptMemberList(room.getId());
@@ -1007,20 +1115,20 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
@RedissonLock(prefixKey = "addGroup:", key = "#uid")
public Long addGroup(Long uid, GroupAddReq request) {
Map<Long, SummeryInfoDTO> userMap = userSummaryCache.getBatch(request.getUidList());
AssertUtil.isTrue(userMap.size() > 1,"群聊人数应大于2人");
AssertUtil.isTrue(userMap.size() > 1, "群聊人数应大于2人");
List<Long> uidList = new ArrayList<>(userMap.keySet());
AtomicReference<Long> roomIdAtomic = new AtomicReference(0L);
// 创建群组数据并推送数据到前端
if(transactionTemplate.execute(e -> {
if (transactionTemplate.execute(e -> {
RoomGroup roomGroup = roomService.createGroupRoom(uid, request);
// 批量保存群成员
List<GroupMember> groupMembers = RoomAdapter.buildGroupMemberBatch(uidList, roomGroup.getId());
groupMemberDao.saveBatch(groupMembers);
roomIdAtomic.set(roomGroup.getRoomId());
return true;
})){
})) {
// 发送邀请加群消息 ==> 触发每个人的会话
roomGroupCache.evictAllCaches();
// 处理新房间里面所有在线人员
@@ -1036,7 +1144,6 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
cachePlusOps.sAdd(PresenceCacheKeyBuilder.userGroupsKey(id), roomIdAtomic.get());
});
asyncOnline(uidList, roomIdAtomic.get(), true);
SpringUtils.publishEvent(new GroupInviteMemberEvent(this, roomIdAtomic.get(), request.getUidList(), uid));
SpringUtils.publishEvent(new GroupMemberAddEvent(this, roomIdAtomic.get(), Math.toIntExact(cachePlusOps.sCard(gKey)), Math.toIntExact(cachePlusOps.sCard(onlineGroupMembersKey)), request.getUidList(), uid));
}
return roomIdAtomic.get();
@@ -1073,8 +1180,8 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
/**
* @param contactMap 会话映射
* @param uid 当前登录的用户
* @param roomIds 所有会话对应的房间
* @param uid 当前登录的用户
* @param roomIds 所有会话对应的房间
* @return
*/
@NotNull
@@ -1093,7 +1200,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
Long roomId = room.getRoomId();
RoomBaseInfo roomBaseInfo = roomBaseInfoMap.get(roomId);
Contact contact = contactMap.get(StrUtil.format("{}_{}", uid, roomId));
if(ObjectUtil.isNotNull(contact)){
if (ObjectUtil.isNotNull(contact)) {
resp.setHide(contact.getHide());
resp.setShield(contact.getShield());
resp.setMuteNotification(contact.getMuteNotification());
@@ -1117,7 +1224,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
resp.setRemark(roomBaseInfo.getRemark());
resp.setMyName(roomBaseInfo.getMyName());
Message message = msgMap.get(room.getLastMsgId());
if(resp.getShield()){
if (resp.getShield()) {
resp.setText("您已屏蔽该会话");
} else {
if (Objects.nonNull(message)) {
@@ -1128,7 +1235,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
GroupMember messageUser = groupMemberCache.getMemberDetail(roomId, message.getFromUid());
if (ObjectUtil.isNotNull(messageUser)) {
// 当自己查看时,且最后一条消息是自己发送的,那么显示群备注
if (uid.equals(message.getFromUid()) && StrUtil.isNotEmpty(messageUser.getRemark())){
if (uid.equals(message.getFromUid()) && StrUtil.isNotEmpty(messageUser.getRemark())) {
resp.setRemark(messageUser.getRemark());
}
}
@@ -1156,6 +1263,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
/**
* 返回房间id与好友的映射
*
* @param roomIds
* @param uid
* @return
@@ -1204,7 +1312,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
roomBaseInfo.setRemark(member.getRemark());
roomBaseInfo.setName(roomGroup.getName());
roomBaseInfo.setRoleId(member.getRoleId());
}else {
} else {
roomBaseInfo.setName("会话异常");
roomBaseInfo.setMyName("会话异常");
roomBaseInfo.setRemark("会话异常");

View File

@@ -15,7 +15,7 @@ import com.luohuo.flex.im.domain.enums.MessageStatusEnum;
import com.luohuo.flex.im.domain.enums.MessageTypeEnum;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReq;
import com.luohuo.flex.im.core.chat.service.adapter.MessageAdapter;
import com.luohuo.flex.im.core.chat.service.cache.MsgPlusCache;
import com.luohuo.flex.im.core.chat.service.cache.MsgCache;
import java.lang.reflect.ParameterizedType;
import java.util.Objects;
import java.util.Optional;
@@ -26,7 +26,7 @@ public abstract class AbstractMsgHandler<T> {
private MessageDao messageDao;
@Resource
private MsgPlusCache msgPlusCache;
private MsgCache msgCache;
@Resource
private UserSummaryCache userSummaryCache;
@@ -82,7 +82,7 @@ public abstract class AbstractMsgHandler<T> {
*/
public ReplyMsg replyMsg(Message msg){
Optional<Message> reply = Optional.ofNullable(msg.getReplyMsgId())
.map(msgPlusCache::get)
.map(msgCache::get)
.filter(a -> Objects.equals(a.getStatus(), MessageStatusEnum.NORMAL.getStatus()));
// TODO 这里的缓存不会立即删除,导致撤回消息后回复的信息还有 (nyh -> 2024-07-14 03:46:34)
if (reply.isPresent()) {

View File

@@ -13,7 +13,7 @@ import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.domain.entity.msg.MessageExtra;
import com.luohuo.flex.im.domain.enums.MessageTypeEnum;
import com.luohuo.flex.im.domain.enums.RoleTypeEnum;
import com.luohuo.flex.im.domain.vo.request.msg.TextMsgReq;
import com.luohuo.flex.im.domain.entity.msg.TextMsgReq;
import com.luohuo.flex.im.domain.vo.response.msg.TextMsgResp;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

View File

@@ -1,6 +1,8 @@
package com.luohuo.flex.im.core.chat.service.strategy.msg;
import com.luohuo.flex.im.core.chat.dao.MessageDao;
import com.luohuo.flex.im.core.chat.service.cache.MsgCache;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.domain.entity.Message;
import com.luohuo.flex.im.domain.entity.msg.MergeMsgDTO;
import com.luohuo.flex.im.domain.entity.msg.MessageExtra;
@@ -8,7 +10,12 @@ import com.luohuo.flex.im.domain.enums.MessageTypeEnum;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static com.luohuo.flex.im.domain.enums.MessageTypeEnum.*;
/**
* 合并消息
@@ -17,15 +24,36 @@ import java.util.Optional;
@Component
@AllArgsConstructor
public class MergeMsgHandler extends AbstractMsgHandler<MergeMsgDTO> {
private MessageDao messageDao;
private MsgCache msgCache;
private UserCache userCache;
private MessageDao messageDao;
@Override
MessageTypeEnum getMsgTypeEnum() {
return MessageTypeEnum.MERGE;
return MERGE;
}
@Override
protected void saveMsg(Message message, MergeMsgDTO body) {
List<Message> messages = new ArrayList<>(msgCache.getBatch(body.getBody().stream()
.limit(3)
.map(item -> Long.parseLong(item.getMessageId()))
.collect(Collectors.toList())).values());
List<String> content = messages.stream().map(msg ->
userCache.get(msg.getFromUid()).getName() + ": " + switch (msg.getType()) {
case MERGE -> "[聊天记录]";
case SOUND -> "[语音]";
case VIDEO -> "[视频]";
case FILE -> "[文件]";
case IMG -> "[图片]";
case RECALL -> "[消息已撤回]";
case EMOJI -> "[表情]";
case NOTICE -> "[公告消息]";
default -> msg.getContent();
}).collect(Collectors.toList());
body.setContent(content);
MessageExtra extra = Optional.ofNullable(message.getExtra()).orElse(new MessageExtra());
Message update = new Message();
update.setId(message.getId());

View File

@@ -2,6 +2,7 @@ package com.luohuo.flex.im.core.chat.service.strategy.msg;
import com.luohuo.basic.utils.SpringUtils;
import com.luohuo.basic.utils.TimeUtils;
import com.luohuo.flex.im.core.chat.service.cache.MsgCache;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import lombok.AllArgsConstructor;
@@ -13,7 +14,6 @@ import com.luohuo.flex.im.domain.entity.Message;
import com.luohuo.flex.im.domain.entity.msg.MessageExtra;
import com.luohuo.flex.im.domain.entity.msg.MsgRecall;
import com.luohuo.flex.im.domain.enums.MessageTypeEnum;
import com.luohuo.flex.im.core.chat.service.cache.MsgCache;
import java.time.LocalDateTime;
import java.util.List;

View File

@@ -11,7 +11,7 @@ import com.luohuo.flex.im.core.chat.dao.MessageDao;
import com.luohuo.flex.im.domain.entity.Message;
import com.luohuo.flex.im.domain.entity.msg.MessageExtra;
import com.luohuo.flex.im.domain.enums.MessageTypeEnum;
import com.luohuo.flex.im.domain.vo.request.msg.TextMsgReq;
import com.luohuo.flex.im.domain.entity.msg.TextMsgReq;
import com.luohuo.flex.im.domain.vo.response.msg.TextMsgResp;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.domain.enums.RoleTypeEnum;
@@ -65,6 +65,10 @@ public class TextMsgHandler extends AbstractMsgHandler<TextMsgReq> {
}
}
private Object reverseFromBean(Objects t) {
return t;
}
@Override
public void saveMsg(Message msg, TextMsgReq body) {//插入文本内容
MessageExtra extra = Optional.ofNullable(msg.getExtra()).orElse(new MessageExtra());

View File

@@ -0,0 +1,73 @@
package com.luohuo.flex.im.core.user.dao;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luohuo.flex.im.core.user.mapper.NoticeMapper;
import com.luohuo.flex.im.domain.entity.Notice;
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
import com.luohuo.flex.im.domain.vo.resp.friend.FriendUnreadDto;
import com.luohuo.flex.model.entity.ws.WSNotice;
import org.springframework.stereotype.Service;
import java.util.List;
import static com.luohuo.flex.im.domain.enums.ApplyReadStatusEnum.READ;
import static com.luohuo.flex.im.domain.enums.ApplyReadStatusEnum.UNREAD;
/**
* <p>
* 通知 服务实现类
* </p>
*
* @author 乾乾
*/
@Service
public class NoticeDao extends ServiceImpl<NoticeMapper, Notice> {
public void markAsRead(Long noticeId) {
Notice dbNotice = baseMapper.selectById(noticeId);
Notice notice = new Notice();
notice.setId(dbNotice.getId());
notice.setIsRead(READ.getCode());
baseMapper.updateById(notice);
}
/**
* 查询当前用户的通知
* @param uid 登录用户
* @param onlyUnread 通知状态
* @return
*/
public IPage<Notice> getUserNotices(Long uid, boolean onlyUnread, Page<Notice> page) {
return lambdaQuery()
.eq(Notice::getReceiverId, uid)
.orderByDesc(Notice::getCreateTime)
.page(page);
}
public WSNotice getUnReadCount(Long uid, Long receiverId) {
List<FriendUnreadDto> unReadCountByTypeMap = baseMapper.getUnReadCountByType(receiverId, UNREAD.getCode());
WSNotice wsNotice = new WSNotice();
wsNotice.setUid(uid);
for (FriendUnreadDto friendUnreadDto : unReadCountByTypeMap) {
if(friendUnreadDto.getType().equals(RoomTypeEnum.FRIEND.getType())){
wsNotice.setUnReadCount4Friend(friendUnreadDto.getCount());
} else {
wsNotice.setUnReadCount4Group(friendUnreadDto.getCount());
}
}
return wsNotice;
}
public void readNotices(Long uid, List<Long> notices) {
lambdaUpdate()
.set(Notice::getIsRead, READ.getCode())
.eq(Notice::getIsRead, UNREAD.getCode())
.in(Notice::getId, notices)
.eq(Notice::getReceiverId, uid)
.update();
}
}

View File

@@ -11,8 +11,6 @@ import com.luohuo.flex.im.core.user.mapper.UserApplyMapper;
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq;
import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp;
import com.luohuo.flex.im.domain.vo.resp.friend.FriendUnreadDto;
import com.luohuo.flex.model.entity.ws.WSFriendApply;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@@ -48,54 +46,16 @@ public class UserApplyDao extends ServiceImpl<UserApplyMapper, UserApply> {
* @param initiator 方法调用方是否是申请记录发起方
* @return {@link UserApply }
*/
public UserApply getFriendApproving(Long uid, Long targetUid,boolean initiator) {
public UserApply getFriendApproving(Long uid, Long targetUid, boolean initiator) {
return lambdaQuery().eq(UserApply::getUid, uid)
.eq(UserApply::getTargetId, targetUid)
.eq(UserApply::getStatus, ApplyStatusEnum.WAIT_APPROVAL.getCode())
.eq(UserApply::getType, RoomTypeEnum.FRIEND.getType())
.notIn(initiator,UserApply::getDeleted,ApplyDeletedEnum.applyDeleted())
.notIn(!initiator,UserApply::getDeleted,ApplyDeletedEnum.targetDeleted())
.notIn(initiator,UserApply::getDeleted, ApplyDeletedEnum.applyDeleted())
.notIn(!initiator,UserApply::getDeleted, ApplyDeletedEnum.targetDeleted())
.one();
}
/**
* 返回好友、群聊申请的未读数量
* @param targetId
* @return
*/
public WSFriendApply getUnReadCount(Long uid, Long targetId) {
List<FriendUnreadDto> unReadCountByTypeMap = baseMapper.getUnReadCountByType(targetId, UNREAD.getCode(), ApplyDeletedEnum.NORMAL.getCode());
WSFriendApply wsFriendApply = new WSFriendApply();
wsFriendApply.setUid(uid);
// 构造需要的数据
for (FriendUnreadDto friendUnreadDto : unReadCountByTypeMap) {
if(friendUnreadDto.getType().equals(RoomTypeEnum.FRIEND.getType())){
wsFriendApply.setUnReadCount4Friend(friendUnreadDto.getCount());
} else {
wsFriendApply.setUnReadCount4Group(friendUnreadDto.getCount());
}
}
return wsFriendApply;
}
public IPage<UserApply> friendApplyPage(Long uid, Page<UserApply> page) {
return lambdaQuery()
.and(w -> w.eq(UserApply::getTargetId, uid).or().eq(UserApply::getUid, uid))
.eq(UserApply::getDeleted,false)
.orderByDesc(UserApply::getCreateTime)
.page(page);
}
public void readApples(Long uid, List<Long> applyIds) {
lambdaUpdate()
.set(UserApply::getReadStatus, READ.getCode())
.eq(UserApply::getReadStatus, UNREAD.getCode())
.in(UserApply::getId, applyIds)
.eq(UserApply::getTargetId, uid)
.update();
}
public void agree(Long applyId) {
lambdaUpdate()
.eq(UserApply::getId, applyId)

View File

@@ -0,0 +1,22 @@
package com.luohuo.flex.im.core.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.luohuo.flex.im.domain.entity.Notice;
import com.luohuo.flex.im.domain.vo.resp.friend.FriendUnreadDto;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* <p>
* 通知 Mapper 接口
* </p>
*
* @author 乾乾
*/
@Repository
public interface NoticeMapper extends BaseMapper<Notice> {
List<FriendUnreadDto> getUnReadCountByType(Long receiverId, Integer isRead);
}

View File

@@ -1,13 +1,9 @@
package com.luohuo.flex.im.core.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.luohuo.flex.im.domain.vo.resp.friend.FriendUnreadDto;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import com.luohuo.flex.im.domain.entity.UserApply;
import java.util.List;
/**
* <p>
* 用户申请表 Mapper 接口
@@ -18,5 +14,4 @@ import java.util.List;
@Repository
public interface UserApplyMapper extends BaseMapper<UserApply> {
List<FriendUnreadDto> getUnReadCountByType(@Param("targetId") Long targetId, @Param("readStatus") Integer readStatus, @Param("normal") Integer normal);
}

View File

@@ -1,14 +1,10 @@
package com.luohuo.flex.im.core.user.service;
import com.luohuo.flex.im.domain.entity.UserApply;
import com.luohuo.flex.im.domain.vo.req.PageBaseReq;
import com.luohuo.flex.im.domain.vo.req.friend.FriendApplyReq;
import com.luohuo.flex.im.domain.vo.request.RoomApplyReq;
import com.luohuo.flex.im.domain.vo.request.member.ApplyReq;
import com.luohuo.flex.im.domain.vo.request.member.GroupApplyHandleReq;
import com.luohuo.flex.im.domain.vo.res.PageBaseResp;
import com.luohuo.flex.im.domain.vo.resp.friend.FriendApplyResp;
import com.luohuo.flex.model.entity.ws.WSFriendApply;
import jakarta.validation.Valid;
/**
@@ -50,21 +46,6 @@ public interface ApplyService {
*/
void handlerApply(Long uid, @Valid ApplyReq request);
/**
* 分页查询好友申请
*
* @param request 请求
* @return {@link PageBaseResp}<{@link FriendApplyResp}>
*/
PageBaseResp<FriendApplyResp> pageApplyFriend(Long uid, PageBaseReq request);
/**
* 申请未读数
*
* @return {@link WSFriendApply}
*/
WSFriendApply unread(Long uid);
/**
* 删除申请
*

View File

@@ -0,0 +1,53 @@
package com.luohuo.flex.im.core.user.service;
import com.luohuo.flex.im.domain.entity.Notice;
import com.luohuo.flex.im.domain.enums.NoticeTypeEnum;
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
import com.luohuo.flex.im.domain.vo.req.PageBaseReq;
import com.luohuo.flex.im.domain.vo.res.NoticeVO;
import com.luohuo.flex.im.domain.vo.res.PageBaseResp;
import com.luohuo.flex.model.entity.ws.WSNotice;
public interface NoticeService {
Notice getByApplyId(Long uid, Long applyId);
/**
* 创建通知
* @param applyType 通知类型 1群聊 2单聊
* @param type 具体的事件类型
* @param senderId 发起人
* @param receiverId 接收人UID
* @param applyId 申请ID
* @param operate 被操作的人
* @param content 消息内容
*/
void createNotice(RoomTypeEnum applyType, NoticeTypeEnum type, Long senderId, Long receiverId, Long applyId, Long operate, String content);
/**
* 更新通知状态
* @param notice
*/
void updateNotice(Notice notice);
/**
* 批量更新通知状态
* @param notice
*/
void updateNotices(Notice notice);
/**
* 获取用户通知
*/
PageBaseResp<NoticeVO> getUserNotices(Long userId, PageBaseReq request);
/**
* 标记为已读
*/
void markAsRead(Long noticeId);
/**
* 查询未读数
*/
WSNotice unread(Long uid);
}

View File

@@ -2,6 +2,7 @@ package com.luohuo.flex.im.core.user.service;
import com.luohuo.flex.im.api.vo.UserRegisterVo;
import com.luohuo.flex.im.domain.dto.ItemInfoDTO;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.model.entity.base.IpInfo;
import com.luohuo.flex.model.vo.query.BindEmailReq;
@@ -59,6 +60,14 @@ public interface UserService {
*/
UserInfoResp getUserInfo(Long uid);
/**
* 查询用户列表
*
* @param uidList
*
*/
List<SummeryInfoDTO> getUserInfo(List<Long> uidList);
/**
* 修改用户名
*

View File

@@ -34,20 +34,6 @@ public class FriendAdapter {
return userApplyNew;
}
public static List<FriendApplyResp> buildFriendApplyList(List<UserApply> records) {
return records.stream().map(userApply -> {
FriendApplyResp friendApplyResp = new FriendApplyResp();
friendApplyResp.setUid(userApply.getUid());
friendApplyResp.setTargetId(userApply.getTargetId());
friendApplyResp.setType(userApply.getType());
friendApplyResp.setApplyId(userApply.getId());
friendApplyResp.setMsg(userApply.getMsg());
friendApplyResp.setStatus(userApply.getStatus());
friendApplyResp.setCreateTime(userApply.getCreateTime());
return friendApplyResp;
}).collect(Collectors.toList());
}
/**
* @param friendPage 好友列表
* @param onlineList 在线用户id

View File

@@ -5,7 +5,6 @@ import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
import com.luohuo.flex.im.domain.vo.req.room.UserApplyResp;
import com.luohuo.flex.model.entity.ws.*;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import com.luohuo.flex.im.domain.dto.ChatMessageMarkDTO;
import com.luohuo.flex.model.entity.dto.ChatMsgRecallDTO;
@@ -85,8 +84,8 @@ public class WsAdapter {
return wsBaseResp;
}
public static WsBaseResp<WSFriendApply> buildApplySend(WSFriendApply resp) {
WsBaseResp<WSFriendApply> wsBaseResp = new WsBaseResp<>();
public static WsBaseResp<WSNotice> buildApplySend(WSNotice resp) {
WsBaseResp<WSNotice> wsBaseResp = new WsBaseResp<>();
wsBaseResp.setType(WSRespTypeEnum.NEW_APPLY.getType());
wsBaseResp.setData(resp);
return wsBaseResp;

View File

@@ -1,32 +1,62 @@
package com.luohuo.flex.im.core.user.service.cache;
import jakarta.annotation.Resource;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import com.luohuo.flex.im.common.constant.RedisKey;
import com.luohuo.flex.im.common.service.cache.AbstractRedisStringCache;
import com.luohuo.flex.im.core.user.dao.ItemConfigDao;
import com.luohuo.flex.im.domain.entity.ItemConfig;
import jakarta.annotation.Resource;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 用户相关缓存
* 用户徽章基本信息的缓存
* @author nyh
*/
@Component
public class ItemCache {
// todo 多级缓存
public class ItemCache extends AbstractRedisStringCache<Long, ItemConfig> {
@Resource
private ItemConfigDao itemConfigDao;
@Resource
private ItemConfigDao itemConfigDao;
@Cacheable(cacheNames = "luohuo:item", key = "'itemsByType:'+#type")
public List<ItemConfig> getByType(Integer type) {
return itemConfigDao.getByType(type);
@Override
protected String getKey(Long uid) {
return RedisKey.getKey(RedisKey.USER_ITEM, uid);
}
@Cacheable(cacheNames = "luohuo:item", key = "'item:'+#itemId", unless = "#result == null")
public ItemConfig getById(Long itemId) {
return itemConfigDao.getById(itemId);
@Override
protected Long getExpireSeconds() {
return -1L;
}
@Override
protected Map<Long, ItemConfig> load(List<Long> uidList) {
List<ItemConfig> needLoadUserList = itemConfigDao.listByIds(uidList);
return needLoadUserList.stream().collect(Collectors.toMap(ItemConfig::getId, Function.identity()));
}
@Cacheable(cacheNames = "luohuo:userItem", key = "'itemsByType:'+#type")
public List<ItemConfig> getByType(Integer type) {
return itemConfigDao.getByType(type);
}
/**
* 获取所有徽章列表
*/
@Cacheable(cacheNames = "luohuo:userItem", key = "'allItems'")
public List<ItemConfig> getAllItems() {
return itemConfigDao.list();
}
/**
* 当有徽章数据变更时,清除所有徽章的缓存,确保下次获取时是最新数据
* 可以使用@CacheEvict注解
*/
@CacheEvict(cacheNames = "luohuo:userItem", key = "'all_items'")
public void evictAllItemsCache() {
}
}

View File

@@ -29,12 +29,14 @@ import com.luohuo.flex.im.core.user.dao.UserApplyDao;
import com.luohuo.flex.im.core.user.dao.UserFriendDao;
import com.luohuo.flex.im.core.user.service.ApplyService;
import com.luohuo.flex.im.core.user.service.FriendService;
import com.luohuo.flex.im.core.user.service.NoticeService;
import com.luohuo.flex.im.core.user.service.adapter.FriendAdapter;
import com.luohuo.flex.im.core.user.service.adapter.WsAdapter;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.dto.RequestApprovalDto;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import com.luohuo.flex.im.domain.entity.GroupMember;
import com.luohuo.flex.im.domain.entity.Notice;
import com.luohuo.flex.im.domain.entity.Room;
import com.luohuo.flex.im.domain.entity.RoomFriend;
import com.luohuo.flex.im.domain.entity.RoomGroup;
@@ -44,6 +46,8 @@ import com.luohuo.flex.im.domain.enums.ApplyDeletedEnum;
import com.luohuo.flex.im.domain.enums.ApplyReadStatusEnum;
import com.luohuo.flex.im.domain.enums.ApplyStatusEnum;
import com.luohuo.flex.im.domain.enums.GroupRoleEnum;
import com.luohuo.flex.im.domain.enums.NoticeStatusEnum;
import com.luohuo.flex.im.domain.enums.NoticeTypeEnum;
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
import com.luohuo.flex.im.domain.vo.req.PageBaseReq;
import com.luohuo.flex.im.domain.vo.req.friend.FriendApplyReq;
@@ -52,7 +56,6 @@ import com.luohuo.flex.im.domain.vo.request.member.ApplyReq;
import com.luohuo.flex.im.domain.vo.request.member.GroupApplyHandleReq;
import com.luohuo.flex.im.domain.vo.res.PageBaseResp;
import com.luohuo.flex.im.domain.vo.resp.friend.FriendApplyResp;
import com.luohuo.flex.model.entity.ws.WSFriendApply;
import com.luohuo.flex.model.redis.annotation.RedissonLock;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -90,6 +93,7 @@ public class ApplyServiceImpl implements ApplyService {
private RoomGroupCache roomGroupCache;
private GroupMemberDao groupMemberDao;
private FriendService friendService;
private NoticeService noticeService;
private CachePlusOps cachePlusOps;
private RoomAppService roomAppService;
private TransactionTemplate transactionTemplate;
@@ -120,6 +124,27 @@ public class ApplyServiceImpl implements ApplyService {
// 申请入库
UserApply newApply = FriendAdapter.buildFriendApply(uid, request);
userApplyDao.save(newApply);
noticeService.createNotice(
RoomTypeEnum.FRIEND,
NoticeTypeEnum.ADD_ME,
uid,
request.getTargetUid(),
newApply.getId(),
request.getTargetUid(),
request.getMsg()
);
noticeService.createNotice(
RoomTypeEnum.FRIEND,
NoticeTypeEnum.FRIEND_APPLY,
uid,
uid,
newApply.getId(),
request.getTargetUid(),
request.getMsg()
);
// 申请事件
SpringUtils.publishEvent(new UserApplyEvent(this, newApply));
return newApply;
@@ -175,8 +200,18 @@ public class ApplyServiceImpl implements ApplyService {
throw new BizException("无效的审批");
}
Notice notice = noticeService.getByApplyId(uid, invite.getId());
if(ObjectUtil.isNull(notice)){
throw new BizException("通知无法处理");
}
switch (request.getState()){
case 0 -> userApplyDao.updateStatus(request.getApplyId(), ApplyStatusEnum.REJECT);
case 0 -> {
userApplyDao.updateStatus(request.getApplyId(), ApplyStatusEnum.REJECT);
// 发送通知
notice.setStatus(NoticeStatusEnum.REJECTED.getStatus());
noticeService.updateNotice(notice);
}
case 2 -> {
// 处理加好友
invite.setStatus(request.getState());
@@ -219,45 +254,56 @@ public class ApplyServiceImpl implements ApplyService {
chatService.sendMsg(MessageAdapter.buildAgreeMsg(atomicRoomId.get(), true), uid);
}
// 通过好友申请
notice.setStatus(NoticeStatusEnum.ACCEPTED.getStatus());
noticeService.updateNotice(notice);
// 通知请求方已处理好友申请
SpringUtils.publishEvent(new UserApprovalEvent(this, RequestApprovalDto.builder().uid(uid).targetUid(invite.getUid()).build()));
} else {
// 处理加群
// 处理加群, 如果是申请进群那么用uid、否则是拉进群
Long infoUid = invite.getApplyFor() ? invite.getUid() : invite.getTargetId();
RoomGroup roomGroup = roomGroupCache.getByRoomId(invite.getRoomId());
Room room = roomCache.get(invite.getRoomId());
GroupMember member = groupMemberDao.getMemberByGroupId(roomGroup.getId(), uid);
GroupMember member = groupMemberDao.getMemberByGroupId(roomGroup.getId(), infoUid);
if(ObjectUtil.isNotNull(member)){
throw new BizException(StrUtil.format("{}已经在{}里", userSummaryCache.get(invite.getTargetId()).getName(), roomGroup.getName()));
}
transactionTemplate.execute(e -> {
groupMemberDao.save(MemberAdapter.buildMemberAdd(roomGroup.getId(), invite.getTargetId()));
groupMemberDao.save(MemberAdapter.buildMemberAdd(roomGroup.getId(), infoUid));
// 创建进群后的会话
chatService.createContact(uid, roomGroup.getRoomId());
chatService.createContact(infoUid, roomGroup.getRoomId());
// 更新邀请状态
userApplyDao.updateById(invite);
return true;
});
// 加群申请已同意
notice.setStatus(NoticeStatusEnum.ACCEPTED.getStatus());
noticeService.updateNotices(notice);
// 3.3 写入缓存
groupMemberCache.evictMemberList(invite.getRoomId());
groupMemberCache.evictExceptMemberList(invite.getRoomId());
CacheKey uKey = PresenceCacheKeyBuilder.userGroupsKey(invite.getTargetId());
CacheKey uKey = PresenceCacheKeyBuilder.userGroupsKey(infoUid);
CacheKey gKey = PresenceCacheKeyBuilder.groupMembersKey(room.getId());
CacheKey onlineGroupMembersKey = PresenceCacheKeyBuilder.onlineGroupMembersKey(room.getId());
cachePlusOps.sAdd(uKey, room.getId());
cachePlusOps.sAdd(gKey, invite.getTargetId());
roomAppService.asyncOnline(Arrays.asList(invite.getTargetId()), room.getId(), true);
cachePlusOps.sAdd(gKey, infoUid);
roomAppService.asyncOnline(Arrays.asList(infoUid), room.getId(), true);
SpringUtils.publishEvent(new GroupInviteMemberEvent(this, room.getId(), Arrays.asList(invite.getTargetId()), invite.getUid()));
SpringUtils.publishEvent(new GroupMemberAddEvent(this, room.getId(), Math.toIntExact(cachePlusOps.sCard(gKey)), Math.toIntExact(cachePlusOps.sCard(onlineGroupMembersKey)), Arrays.asList(invite.getTargetId()), invite.getUid()));
SpringUtils.publishEvent(new GroupInviteMemberEvent(this, room.getId(), Arrays.asList(infoUid), invite.getUid(), invite.getApplyFor()));
SpringUtils.publishEvent(new GroupMemberAddEvent(this, room.getId(), Math.toIntExact(cachePlusOps.sCard(gKey)), Math.toIntExact(cachePlusOps.sCard(onlineGroupMembersKey)), Arrays.asList(infoUid), uid));
}
}
case 3 -> {
checkRecord(request);
userApplyDao.updateStatus(request.getApplyId(), ApplyStatusEnum.IGNORE);
notice.setStatus(NoticeStatusEnum.IGNORE.getStatus());
noticeService.updateNotice(notice);
}
}
}
@@ -315,7 +361,7 @@ public class ApplyServiceImpl implements ApplyService {
roomAppService.asyncOnline(Arrays.asList(apply.getUid()), room.getId(), true);
// 5.5 发布成员增加事件
SpringUtils.publishEvent(new GroupInviteMemberEvent(this, room.getId(), Arrays.asList(apply.getUid()), apply.getUid()));
SpringUtils.publishEvent(new GroupInviteMemberEvent(this, room.getId(), Arrays.asList(apply.getUid()), apply.getUid(), false));
SpringUtils.publishEvent(new GroupMemberAddEvent(this, room.getId(), Math.toIntExact(cachePlusOps.sCard(gKey)), Math.toIntExact(cachePlusOps.sCard(onlineGroupMembersKey)), Collections.singletonList(apply.getUid()), apply.getUid()));
}
@@ -323,41 +369,6 @@ public class ApplyServiceImpl implements ApplyService {
pushService.sendPushMsg(WsAdapter.buildApplyResultWS(apply.getUid(), group.getRoomId(), uid, apply.getMsg(), apply.getStatus(), apply.getReadStatus()), apply.getUid(), uid);
}
/**
* 分页查询好友申请
*
* @param request 请求
* @return {@link PageBaseResp}<{@link FriendApplyResp}>
*/
@Override
public PageBaseResp<FriendApplyResp> pageApplyFriend(Long uid, PageBaseReq request) {
IPage<UserApply> userApplyIPage = userApplyDao.friendApplyPage(uid, request.plusPage());
if (CollectionUtil.isEmpty(userApplyIPage.getRecords())) {
return PageBaseResp.empty();
}
// 将这些申请列表设为已读
readApples(uid, userApplyIPage);
// 返回消息
return PageBaseResp.init(userApplyIPage, FriendAdapter.buildFriendApplyList(userApplyIPage.getRecords()));
}
private void readApples(Long uid, IPage<UserApply> userApplyIpage) {
List<Long> applyIds = userApplyIpage.getRecords()
.stream().map(UserApply::getId)
.collect(Collectors.toList());
userApplyDao.readApples(uid, applyIds);
}
/**
* 申请未读数
*
* @return {@link WSFriendApply}
*/
@Override
public WSFriendApply unread(Long uid) {
return userApplyDao.getUnReadCount(uid, uid);
}
@Override
@RedissonLock(prefixKey = "friend:deleteApprove", key = "#uid")
public void deleteApprove(Long uid, ApplyReq request) {

View File

@@ -0,0 +1,132 @@
package com.luohuo.flex.im.core.user.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.luohuo.flex.im.core.user.dao.NoticeDao;
import com.luohuo.flex.im.core.user.service.NoticeService;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import com.luohuo.flex.im.domain.entity.Notice;
import com.luohuo.flex.im.domain.enums.NoticeStatusEnum;
import com.luohuo.flex.im.domain.enums.NoticeTypeEnum;
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
import com.luohuo.flex.im.domain.vo.req.PageBaseReq;
import com.luohuo.flex.im.domain.vo.res.NoticeVO;
import com.luohuo.flex.im.domain.vo.res.PageBaseResp;
import com.luohuo.flex.model.entity.WSRespTypeEnum;
import com.luohuo.flex.model.entity.WsBaseResp;
import com.luohuo.flex.model.entity.ws.WSNotice;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class NoticeServiceImpl implements NoticeService {
private final NoticeDao noticeDao;
private final PushService pushService;
private final UserSummaryCache userSummaryCache;
private void pushNoticeToUser(Long receiverId, Notice notice) {
WsBaseResp<NoticeVO> wsMsg = new WsBaseResp<>();
wsMsg.setData(convertToVO(notice));
wsMsg.setType(WSRespTypeEnum.NOTICE.getType());
pushService.sendPushMsg(wsMsg, Collections.singletonList(receiverId), notice.getSenderId());
}
@Override
public Notice getByApplyId(Long uid, Long applyId) {
return noticeDao.getBaseMapper().selectOne(new QueryWrapper<Notice>().eq("receiver_id", uid).eq("apply_id", applyId));
}
@Override
public void createNotice(RoomTypeEnum applyType, NoticeTypeEnum type, Long senderId, Long receiverId, Long applyId, Long operate, String content) {
Notice notice = new Notice();
notice.setType(applyType.getType());
notice.setEventType(type.getType());
notice.setSenderId(senderId);
notice.setReceiverId(receiverId);
notice.setApplyId(applyId);
notice.setOperateId(operate);
notice.setContent(content);
notice.setStatus(NoticeStatusEnum.UNTREATED.getStatus());
noticeDao.save(notice);
// 实时推送
pushNoticeToUser(receiverId, notice);
}
@Override
public void updateNotice(Notice notice) {
noticeDao.update(Wrappers.<Notice>lambdaUpdate().set(Notice::getStatus, notice.getStatus()).eq(Notice::getApplyId, notice.getApplyId()));
// 实时推送
pushNoticeToUser(notice.getReceiverId(), notice);
}
public void updateNotices(Notice notice) {
noticeDao.update(Wrappers.<Notice>lambdaUpdate().set(Notice::getStatus, notice.getStatus()).eq(Notice::getApplyId, notice.getApplyId()));
List<Notice> notices = noticeDao.getBaseMapper().selectList(new QueryWrapper<Notice>().eq("apply_id", notice.getApplyId()));
for (Notice n : notices) {
// 实时推送
pushNoticeToUser(n.getReceiverId(), n);
}
}
private void readNotices(Long uid, IPage<Notice> noticeIPage) {
List<Long> notices = noticeIPage.getRecords()
.stream().map(Notice::getId)
.collect(Collectors.toList());
noticeDao.readNotices(uid, notices);
}
@Override
public PageBaseResp<NoticeVO> getUserNotices(Long uid, PageBaseReq request) {
IPage<Notice> noticeIPage = noticeDao.getUserNotices(uid, true, request.plusPage());
// 将这些通知设为已读
readNotices(uid, noticeIPage);
return PageBaseResp.init(noticeIPage, noticeIPage.getRecords().stream().map(notice -> convertToVO(notice)).collect(Collectors.toList()));
}
private NoticeVO convertToVO(Notice notice) {
NoticeVO vo = new NoticeVO();
vo.setId(notice.getId());
vo.setApplyId(notice.getApplyId());
vo.setSenderId(notice.getSenderId());
vo.setReceiverId(notice.getReceiverId());
vo.setOperateId(notice.getOperateId());
vo.setContent(notice.getContent());
vo.setEventType(notice.getEventType());
vo.setType(notice.getType());
vo.setStatus(notice.getStatus());
vo.setCreateTime(notice.getCreateTime());
vo.setRead(notice.getIsRead());
// 填充发送人信息
SummeryInfoDTO sender = userSummaryCache.get(notice.getSenderId());
vo.setSenderName(sender.getName());
vo.setSenderAvatar(sender.getAvatar());
// 填充接收人信息
SummeryInfoDTO receiver = userSummaryCache.get(notice.getReceiverId());
vo.setReceiverName(receiver.getName());
vo.setReceiverAvatar(receiver.getAvatar());
return vo;
}
@Override
public void markAsRead(Long noticeId) {
noticeDao.markAsRead(noticeId);
}
@Override
public WSNotice unread(Long uid) {
return noticeDao.getUnReadCount(uid, uid);
}
}

View File

@@ -1,6 +1,7 @@
package com.luohuo.flex.im.core.user.service.impl;
import com.luohuo.basic.utils.SpringUtils;
import com.luohuo.flex.im.core.user.service.cache.ItemCache;
import com.luohuo.flex.model.redis.annotation.RedissonLock;
import com.luohuo.flex.im.common.enums.IdempotentEnum;
import com.luohuo.flex.im.common.enums.YesOrNoEnum;
@@ -10,7 +11,6 @@ import com.luohuo.flex.im.domain.entity.ItemConfig;
import com.luohuo.flex.im.domain.entity.UserBackpack;
import com.luohuo.flex.im.domain.enums.ItemTypeEnum;
import com.luohuo.flex.im.core.user.service.UserBackpackService;
import com.luohuo.flex.im.core.user.service.cache.ItemCache;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -49,7 +49,7 @@ public class UserBackpackServiceImpl implements UserBackpackService {
return;
}
// 业务检查
ItemConfig itemConfig = itemCache.getById(itemId);
ItemConfig itemConfig = itemCache.get(itemId);
if (ItemTypeEnum.BADGE.getType().equals(itemConfig.getType())) {
// 徽章类型做唯一性检查
Integer countByValidItemId = userBackpackDao.getCountByValidItemId(uid, itemId);

View File

@@ -1,5 +1,6 @@
package com.luohuo.flex.im.core.user.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -13,6 +14,7 @@ import com.luohuo.flex.im.api.vo.UserRegisterVo;
import com.luohuo.flex.im.common.event.UserRegisterEvent;
import com.luohuo.flex.im.core.chat.service.RoomAppService;
import com.luohuo.flex.im.core.user.service.cache.DefUserCache;
import com.luohuo.flex.im.core.user.service.cache.ItemCache;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.model.entity.base.IpInfo;
import lombok.AllArgsConstructor;
@@ -49,11 +51,11 @@ import com.luohuo.flex.im.domain.vo.resp.user.BadgeResp;
import com.luohuo.flex.im.domain.vo.resp.user.UserInfoResp;
import com.luohuo.flex.im.core.user.service.UserService;
import com.luohuo.flex.im.core.user.service.adapter.UserAdapter;
import com.luohuo.flex.im.core.user.service.cache.ItemCache;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -120,6 +122,11 @@ public class UserServiceImpl implements UserService {
return UserAdapter.buildUserInfoResp(userInfo, countByValidItemId);
}
@Override
public List<SummeryInfoDTO> getUserInfo(List<Long> uidList) {
return new ArrayList<>(userSummaryCache.getBatch(uidList).values());
}
@Override
@Transactional
public void modifyInfo(Long uid, ModifyNameReq req) {
@@ -213,8 +220,20 @@ public class UserServiceImpl implements UserService {
@Override
public List<ItemInfoDTO> getItemInfo(ItemInfoReq req) {//简单做,更新时间可判断被修改
if(CollUtil.isEmpty(req.getReqList())){
List<ItemConfig> allItems = itemCache.getAllItems();
return allItems.stream().map(itemConfig -> {
ItemInfoDTO dto = new ItemInfoDTO();
dto.setItemId(itemConfig.getId());
dto.setImg(itemConfig.getImg());
dto.setDescribe(itemConfig.getDescribe());
return dto;
}).collect(Collectors.toList());
}
return req.getReqList().stream().map(a -> {
ItemConfig itemConfig = itemCache.getById(a.getItemId());
ItemConfig itemConfig = itemCache.get(a.getItemId());
if (Objects.nonNull(a.getLastModifyTime()) && a.getLastModifyTime() >= TimeUtils.getTime(itemConfig.getUpdateTime())) {
return ItemInfoDTO.skip(a.getItemId());
}

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.luohuo.flex.im.core.user.mapper.UserApplyMapper">
<mapper namespace="com.luohuo.flex.im.core.user.mapper.NoticeMapper">
<select id="getUnReadCountByType" resultType="com.luohuo.flex.im.domain.vo.resp.friend.FriendUnreadDto">
SELECT type, COUNT(*) as count
FROM im_user_apply
WHERE target_id = #{targetId} AND deleted = #{normal} AND read_status = #{readStatus}
GROUP BY type, `status` order by `status`
FROM im_notice
WHERE receiver_id = #{receiverId} AND is_read = #{isRead}
GROUP BY type
</select>
</mapper>

View File

@@ -4,21 +4,16 @@ import com.luohuo.basic.base.R;
import com.luohuo.basic.context.ContextUtil;
import com.luohuo.flex.im.core.user.service.ApplyService;
import com.luohuo.flex.im.domain.entity.UserApply;
import com.luohuo.flex.im.domain.vo.req.PageBaseReq;
import com.luohuo.flex.im.domain.vo.req.friend.FriendApplyReq;
import com.luohuo.flex.im.domain.vo.request.RoomApplyReq;
import com.luohuo.flex.im.domain.vo.request.member.ApplyReq;
import com.luohuo.flex.im.domain.vo.request.member.GroupApplyHandleReq;
import com.luohuo.flex.im.domain.vo.res.PageBaseResp;
import com.luohuo.flex.im.domain.vo.resp.friend.FriendApplyResp;
import com.luohuo.flex.model.entity.ws.WSFriendApply;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -36,26 +31,12 @@ public class ApplyController {
@Resource
private ApplyService applyService;
@GetMapping("/page")
@Operation(summary = "申请列表 [好友、群聊]")
public R<PageBaseResp<FriendApplyResp>> page(@Valid PageBaseReq request) {
Long uid = ContextUtil.getUid();
return R.success(applyService.pageApplyFriend(uid, request));
}
@PostMapping("/apply")
@Operation(summary = "好友申请")
public R<UserApply> apply(@Valid @RequestBody FriendApplyReq request) {
return R.success(applyService.handlerApply(ContextUtil.getUid(), request));
}
@GetMapping("/unread")
@Operation(summary = "申请未读数")
public R<WSFriendApply> unread() {
Long uid = ContextUtil.getUid();
return R.success(applyService.unread(uid));
}
@Operation(summary ="审批别人邀请的进群、好友申请")
@PostMapping("/handler/apply")
public R<Void> handlerApply(@Valid @RequestBody ApplyReq request) {

View File

@@ -0,0 +1,45 @@
package com.luohuo.flex.im.controller;
import com.luohuo.basic.base.R;
import com.luohuo.basic.context.ContextUtil;
import com.luohuo.flex.im.core.user.service.NoticeService;
import com.luohuo.flex.im.domain.vo.req.NoticeReadReq;
import com.luohuo.flex.im.domain.vo.req.PageBaseReq;
import com.luohuo.flex.im.domain.vo.res.NoticeVO;
import com.luohuo.flex.im.domain.vo.res.PageBaseResp;
import com.luohuo.flex.model.entity.ws.WSNotice;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/room/notice")
public class NoticeController {
@Resource
private NoticeService noticeService;
@GetMapping("/page")
public R<PageBaseResp<NoticeVO>> getNotices(@Valid PageBaseReq request) {
Long uid = ContextUtil.getUid();
return R.success(noticeService.getUserNotices(uid, request));
}
@PostMapping("/read")
public R<Void> markAsRead(@RequestBody NoticeReadReq req) {
noticeService.markAsRead(req.getNoticeId());
return R.success();
}
@GetMapping("/unread")
@Operation(summary = "通知未读数")
public R<WSNotice> unread() {
Long uid = ContextUtil.getUid();
return R.success(noticeService.unread(uid));
}
}

View File

@@ -2,6 +2,7 @@ package com.luohuo.flex.im.controller.chat;
import com.luohuo.basic.tenant.core.aop.TenantIgnore;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.vo.request.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
@@ -12,19 +13,11 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.luohuo.basic.base.R;
import com.luohuo.basic.context.ContextUtil;
import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp;
import com.luohuo.flex.im.domain.dto.MsgReadInfoDTO;
import com.luohuo.flex.im.domain.vo.request.ChatMessageBaseReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageMarkReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageMemberReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessagePageReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReadInfoReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReadReq;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReq;
import com.luohuo.flex.im.domain.vo.response.ChatMessageReadResp;
import com.luohuo.flex.model.entity.ws.ChatMessageResp;
import com.luohuo.flex.im.core.chat.service.ChatService;
@@ -63,11 +56,11 @@ public class ChatController {
return R.success(msgPage);
}
@GetMapping("/msg/list")
@PostMapping("/msg/list")
@Operation(summary ="消息列表")
// @FrequencyControl(time = 120, count = 20, target = FrequencyControl.Target.IP)
public R<List<ChatMessageResp>> getMsgPage(@RequestParam(value = "lastOptTime", required = false) Long lastOptTime) {
List<ChatMessageResp> msgPage = chatService.getMsgList(lastOptTime, ContextUtil.getUid());
public R<List<ChatMessageResp>> getMsgPage(@RequestBody MsgReq msgReq) {
List<ChatMessageResp> msgPage = chatService.getMsgList(msgReq, ContextUtil.getUid());
// Set<String> blackMembers = getBlackUidSet();
// msgPage.removeIf(a -> blackMembers.contains(a.getFromUser().getUid().toString()));
return R.success(msgPage);

View File

@@ -31,7 +31,6 @@ import com.luohuo.flex.im.domain.vo.request.room.AnnouncementsParam;
import com.luohuo.flex.im.domain.vo.request.room.ReadAnnouncementsParam;
import com.luohuo.flex.im.domain.vo.request.room.RoomGroupReq;
import com.luohuo.flex.im.domain.vo.response.ChatMemberListResp;
import com.luohuo.flex.model.entity.ws.ChatMessageResp;
import com.luohuo.flex.im.domain.vo.response.MemberResp;
import com.luohuo.flex.im.core.chat.service.RoomAppService;
import com.luohuo.flex.im.domain.vo.req.MergeMessageReq;
@@ -95,7 +94,7 @@ public class RoomController {
@DeleteMapping("/group/member/exit")
@Operation(summary ="退出群聊 | 解散群聊")
public R<Boolean> exitGroup(@Valid @RequestBody MemberExitReq request) {
roomService.exitGroup(ContextUtil.getUid(), request);
roomService.exitGroup(false, ContextUtil.getUid(), request);
return R.success();
}
@@ -190,7 +189,8 @@ public class RoomController {
@Operation(summary = "合并消息")
@PostMapping("mergeMessage")
public R<ChatMessageResp> mergeMessage(@Validated @RequestBody MergeMessageReq req){
return R.success(roomService.mergeMessage(ContextUtil.getUid(), req));
public R<Void> mergeMessage(@Validated @RequestBody MergeMessageReq req){
roomService.mergeMessage(ContextUtil.getUid(), req);
return R.success();
}
}

View File

@@ -2,7 +2,9 @@ package com.luohuo.flex.im.controller.user;
import com.luohuo.basic.tenant.core.aop.TenantIgnore;
import com.luohuo.flex.im.api.vo.UserRegisterVo;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import com.luohuo.flex.model.entity.base.RefreshIpInfo;
import com.luohuo.flex.model.entity.base.UserReq;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
@@ -46,6 +48,12 @@ public class UserController {
return R.success(userService.refreshIpInfo(refreshIpInfo.getUid(), refreshIpInfo.getIpInfo()));
}
@PostMapping("getUserByIds")
@Operation(summary ="查询用户id")
public R<List<SummeryInfoDTO>> getUserByIds(@RequestBody UserReq userReq) {
return R.success(userService.getUserInfo(userReq.getUidList()));
}
@GetMapping("/checkEmail")
@Operation(summary ="绑定邮箱")
@TenantIgnore

View File

@@ -0,0 +1,67 @@
package com.luohuo.flex.im.domain.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.luohuo.basic.base.entity.Entity;
import com.luohuo.flex.im.domain.enums.NoticeStatusEnum;
import com.luohuo.flex.im.domain.enums.NoticeTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 统一通知表
* </p>
*
* @author 乾乾
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("im_notice")
public class Notice extends Entity<Long> {
private static final long serialVersionUID = 1L;
/**
* @see NoticeTypeEnum
*/
@Schema(description = "通知类型:1-好友申请;2-群申请;3-群事件")
private Integer eventType;
@Schema(description = "通知类型 1群聊 2加好友")
private Integer type;
@Schema(description = "发起人UID")
@TableField("sender_id")
private Long senderId;
@Schema(description = "接收人UID")
@TableField("receiver_id")
private Long receiverId;
@Schema(description = "申请ID")
@TableField("apply_id")
private Long applyId;
@Schema(description = "被操作的人")
@TableField("operate_id")
private Long operateId;
@Schema(description = "通知内容 申请时填写的, 进群、移除时是群聊的名称")
private String content;
/**
* @see NoticeStatusEnum
*/
@Schema(description = "处理状态:0-未处理;1-已同意;2-已拒绝;3-忽略")
private Integer status;
@Schema(description = "是否已读")
@TableField("is_read")
private Integer isRead;
@Schema(description = "租户id")
@TableField("tenant_id")
private Long tenantId;
}

View File

@@ -0,0 +1,20 @@
package com.luohuo.flex.im.domain.entity.msg;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class BodyDTO implements Serializable {
@Schema(description = "用户uid")
private String uid;
@Schema(description = "消息id")
private String messageId;
}

View File

@@ -1,33 +0,0 @@
package com.luohuo.flex.im.domain.entity.msg;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 合并消息实体
* @author 乾乾
*/
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class MergeMsg implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description ="内容")
private String content;
@Schema(description = "发送时间")
private LocalDateTime createdTime;
@Schema(description ="发送人名称")
private String name;
}

View File

@@ -22,12 +22,19 @@ public class MergeMsgDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description ="合并的内容")
private List<MergeMsg> messages;
@Schema(description ="消息id")
private List<BodyDTO> body;
@Schema(description ="回复的消息id")
private Long replyMsgId;
@Schema(description ="预览的消息内容")
private List<String> content;
@Schema(description ="父消息如果没有父消息返回的是null")
private ReplyMsg reply;
public MergeMsgDTO(List<BodyDTO> body) {
this.body = body;
}
}

View File

@@ -1,4 +1,4 @@
package com.luohuo.flex.im.domain.vo.request.msg;
package com.luohuo.flex.im.domain.entity.msg;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
@@ -21,7 +21,7 @@ import java.util.List;
public class TextMsgReq {
@NotBlank(message = "内容不能为空")
@Size(max = 1024, message = "消息内容过长服务器扛不住啊兄dei")
@Size(max = 1024, message = "消息内容过长")
@Schema(description ="消息内容")
private String content;

View File

@@ -0,0 +1,34 @@
package com.luohuo.flex.im.domain.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.io.Serializable;
import java.util.stream.Stream;
/**
* 转发类型的枚举
*
* @author 乾乾
*/
@RequiredArgsConstructor
@Getter
public enum MergeTypeEnum implements Serializable {
SINGLE(1, "单一转发"),
MERGE(2, "合并转发");
private final Integer type;
private final String desc;
/**
* 根据当前枚举的name匹配
*/
public static MergeTypeEnum match(Integer val) {
return Stream.of(values()).parallel().filter(item -> item.getType().equals(val)).findAny().orElse(SINGLE);
}
public static MergeTypeEnum get(Integer val) {
return match(val);
}
}

View File

@@ -0,0 +1,35 @@
package com.luohuo.flex.im.domain.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.io.Serializable;
import java.util.stream.Stream;
/**
* 事件处理的枚举
*
* @author 乾乾
*/
@RequiredArgsConstructor
@Getter
public enum NoticeStatusEnum implements Serializable {
UNTREATED(0, "待审批"),
ACCEPTED(1, "已同意"),
REJECTED(2, "已拒绝"),
IGNORE(3, "已忽略");
private final Integer status;
private final String desc;
/**
* 根据当前枚举的name匹配
*/
public static NoticeStatusEnum match(Integer val) {
return Stream.of(values()).parallel().filter(item -> item.getStatus().equals(val)).findAny().orElse(ACCEPTED);
}
public static NoticeStatusEnum get(Integer val) {
return match(val);
}
}

View File

@@ -0,0 +1,40 @@
package com.luohuo.flex.im.domain.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.io.Serializable;
import java.util.stream.Stream;
/**
* 事件通知的枚举
*
* @author 乾乾
*/
@RequiredArgsConstructor
@Getter
public enum NoticeTypeEnum implements Serializable {
FRIEND_APPLY(1, "好友申请"),
ADD_ME(6, "好友被申请"),
GROUP_APPLY(2, "加群申请"),
GROUP_INVITE(3, "群邀请"),
GROUP_MEMBER_DELETE(5, "移除群成员"),
GROUP_INVITE_ME(7, "被邀请进群"),
GROUP_SET_ADMIN(8, "设置群管理员"),
GROUP_RECALL_ADMIN(9, "取消群管理员");
private final Integer type;
private final String desc;
/**
* 根据当前枚举的name匹配
*/
public static NoticeTypeEnum match(Integer val) {
return Stream.of(values()).parallel().filter(item -> item.getType().equals(val)).findAny().orElse(FRIEND_APPLY);
}
public static NoticeTypeEnum get(Integer val) {
return match(val);
}
}

View File

@@ -3,6 +3,7 @@ package com.luohuo.flex.im.domain.vo.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
@@ -19,9 +20,14 @@ public class MergeMessageReq implements Serializable {
@NotNull(message = "请选择消息来源房间")
private Long fromRoomId;
@Schema(description = "转发类型")
@NotNull(message = "转发类型不能为空")
private Integer type;
@Schema(description = "接收的房间ID")
@NotNull(message = "房间不能为空")
private Long roomId;
@Size(max = 100, message = "最多只能转发100条消息")
@NotEmpty(message = "请选择接收消息的房间")
private List<Long> roomIds;
@Schema(description = "合并消息的子消息列表")
@NotEmpty(message = "请选择要转发的消息")

View File

@@ -0,0 +1,14 @@
package com.luohuo.flex.im.domain.vo.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
@Schema(description = "标记通知已读请求")
public class NoticeReadReq {
@NotNull
@Schema(description = "通知ID", required = true)
private Long noticeId;
}

View File

@@ -0,0 +1,26 @@
package com.luohuo.flex.im.domain.vo.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 消息请求
* @author 乾乾
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MsgReq implements Serializable {
@Schema(description ="消息id")
private List<Long> msgIds;
@Schema(description ="最后一次ws连接的时间")
private Long lastOptTime;
}

View File

@@ -0,0 +1,59 @@
package com.luohuo.flex.im.domain.vo.res;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 通知视图对象
*/
@Data
@Schema(description = "通知视图对象")
public class NoticeVO {
@Schema(description = "主键ID")
private Long id;
@Schema(description = "事件类型:1-好友申请;2-群申请;3-群事件")
private Integer eventType;
@Schema(description = "通知类型 1群聊 2加好友")
private Integer type;
@Schema(description = "申请ID")
private Long applyId;
@Schema(description = "被操作的人")
private Long operateId;
@Schema(description = "通知内容 申请时填写的")
private String content;
@Schema(description = "处理状态:0-未处理;1-已同意;2-已拒绝;3-忽略")
private Integer status;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "是否已读")
private Integer read;
@Schema(description = "发送人名称")
private String senderName;
@Schema(description = "发送人头像")
private String senderAvatar;
@Schema(description = "接收人名称")
private String receiverName;
@Schema(description = "接收人头像")
private String receiverAvatar;
@Schema(description = "发送人ID")
private Long senderId;
@Schema(description = "接收人ID")
private Long receiverId;
}

View File

@@ -86,7 +86,7 @@ public class IpServiceImpl implements IpService, DisposableBean {
public static IpDetail getIpDetailOrNull(String ip) {
String body = HttpUtil.get("https://ip.taobao.com/outGetIpInfo?ip=" + ip + "&accessKey=alibaba-inc");
try {
R<IpDetail> result = JsonUtils.toObj(body, new TypeReference<>() {});
R<IpDetail> result = JsonUtils.toObj(body.replace("XX", "").replace("xx", ""), new TypeReference<>() {});
return result.getData();
} catch (Exception ignored) {
}
@@ -100,7 +100,7 @@ public class IpServiceImpl implements IpService, DisposableBean {
for (int i = 0; i < 100; i++) {
int finalI = i;
EXECUTOR.execute(() -> {
IpDetail ipDetail = tryGetIpDetailOrNullTreeTimes("113.90.36.126");
IpDetail ipDetail = tryGetIpDetailOrNullTreeTimes("127.0.0.1");
if (Objects.nonNull(ipDetail)) {
LocalDateTime date = LocalDateTime.now();
System.out.println(String.format("第%d次成功,目前耗时:%dms", finalI, (TimeUtils.getTime(date) - TimeUtils.getTime(begin))));

View File

@@ -25,23 +25,26 @@ public enum WSRespTypeEnum {
USER_STATE_CHANGE("userStateChange", "用户状态改变", null),
ROOM_INFO_CHANGE("roomInfoChange", "管理员修改群聊信息", ChatRoomGroupChange.class),
MY_ROOM_INFO_CHANGE("myRoomInfoChange", "自己修改我在群里的信息", ChatMyRoomGroupChange.class),
ROOM_GROUP_MSG("roomGroupMsg", "用户申请加群通知消息", null),
GROUP_APPLY_NOTICE("GroupApplyNotice", "管理员用户申请加群通知消息", null),
TOKEN_EXPIRED("tokenExpired", "使前端的token失效意味着前端需要重新登录", OffLineResp.class),
INVALID_USER("invalidUser", "拉黑用户", WSBlack.class),
MSG_MARK_ITEM("msgMarkItem", "消息标记", WSMsgMark.class),
MSG_RECALL("msgRecall", "消息撤回", WSMsgRecall.class),
NOTICE("notice", "通知总线", null),
REQUEST_APPROVAL_FRIEND("requestApprovalFriend", "同意好友请求", WSFriendApproval.class),
NEW_APPLY("newApply", "好友申请、群聊邀请", WSNotice.class),
ROOM_DISSOLUTION("roomDissolution", "群解散", null),
ROOM_GROUP_MSG("roomGroupMsg", "用户申请加群通知消息", null),
GROUP_APPLY_NOTICE("GroupApplyNotice", "管理员用户申请加群通知消息", null),
ROOM_GROUP_NOTICE_READ_MSG("roomGroupNoticeReadMsg", "群公告已读", null),
FEED_SEND_MSG("feedSendMsg", "朋友圈发布", null),
ROOM_DISSOLUTION("roomDissolution", "群解散", null),
ROOM_NOTIFICATION("roomNotification", "会话消息接收类型改变", null),
SHIELD("shield", "你已屏蔽好友的消息", null),
UNBLOCK("unblock", "你已解除屏蔽好友的消息", null),
NEW_APPLY("newApply", "好友申请、群聊邀请", WSFriendApply.class),
memberChange("memberChange", "成员变动", WSMemberChange.class),
OFFLINE("offline", "下线通知", WSOnlineNotify.class),
WSReconnect("WSReconnect", "ws消息重连", null),
REQUEST_APPROVAL_FRIEND("requestApprovalFriend", "同意好友请求", WSFriendApproval.class),
JoinVideo("JoinVideo", "加入视频会议", null),
VideoCallRequest("VideoCallRequest","发起通话请求", null),
StartSignaling("StartSignaling","开始呼叫", null),

View File

@@ -0,0 +1,19 @@
package com.luohuo.flex.model.entity.base;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 用户列表
* @author 乾乾
*/
@Data
@NoArgsConstructor
public class UserReq implements Serializable {
private static final long serialVersionUID = 1L;
private List<Long> uidList;
}

View File

@@ -14,7 +14,7 @@ import java.io.Serializable;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WSFriendApply implements Serializable {
public class WSNotice implements Serializable {
@Schema(description ="申请人、被邀请人")
private Long uid;
@Schema(description ="好友申请列表的未读数")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 KiB

After

Width:  |  Height:  |  Size: 464 KiB