fix: 注册后推送群成员信息事件、登录时更新imUser的物理地址并返回给userInfo、调整个人信息修改

This commit is contained in:
乾乾
2025-09-12 18:41:44 +08:00
parent 9deb4b5f8f
commit 9e5293af4b
87 changed files with 1278 additions and 805 deletions

View File

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

View File

@@ -0,0 +1,97 @@
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

@@ -0,0 +1,12 @@
# 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

@@ -0,0 +1,15 @@
# 这里要用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

@@ -0,0 +1,6 @@
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

@@ -0,0 +1,11 @@
## 📝 修改环境配置
上传目录下的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

@@ -0,0 +1,105 @@
#所属集群名字
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

@@ -0,0 +1,24 @@
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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -0,0 +1,44 @@
#!/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

@@ -0,0 +1,179 @@
/*
* 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

@@ -0,0 +1,51 @@
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

@@ -0,0 +1,104 @@
#所属集群名字
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

@@ -0,0 +1,24 @@
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

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

View File

@@ -0,0 +1,95 @@
## 📝 修改环境配置
上传目录下的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

@@ -56,41 +56,11 @@ public class RedisKey {
*/
public static final String ROOM_FRIEND_FORMAT = "room_friend:id_%d";
/**
* 群成员信息
*/
public static final String GROUP_MEMBER_INFO_FORMAT = "group_member_info:groupId_%d";
/**
* 群公告
*/
public static final String GROUP_ANNOUNCEMENTS_FORMAT = "groupInfo:announcements_%d";
/**
* 用户token存放 格式:终端:uid
*/
public static final String USER_TOKEN_UID_FORMAT = "userToken:%s:uid_%d";
/**
* 用户token存放 格式:终端:uid:uuid
*/
public static final String USER_TOKEN_FORMAT = "userToken:%s:uid_%d:%s";
/**
* 用户refreshToken存放
*/
public static final String USER_REFRESH_TOKEN_UID_FORMAT = "userRefreshToken:%s:uid_%d";
/**
* 用户refreshToken存放
*/
public static final String USER_REFRESH_TOKEN_FORMAT = "userRefreshToken:%s:uid_%d:%s";
/**
* 用户的信息更新时间
*/
public static final String USER_MODIFY_FORMAT = "userModify:uid_%d";
/**
* 用户的信息汇总
*/

View File

@@ -0,0 +1,27 @@
package com.luohuo.flex.im.common.event;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import java.util.List;
/**
* @author 邀请进群事件
*/
@Getter
public class GroupInviteMemberEvent extends ApplicationEvent {
// 变动的成员
private final List<Long> memberList;
private final Long roomId;
// 消息接收人
private final Long uid;
public GroupInviteMemberEvent(Object source, Long roomId, List<Long> memberList, Long uid) {
super(source);
this.memberList = memberList;
this.roomId = roomId;
this.uid = uid;
}
}

View File

@@ -0,0 +1,47 @@
package com.luohuo.flex.im.common.event.listener;
import com.luohuo.flex.im.common.event.GroupInviteMemberEvent;
import com.luohuo.flex.im.core.chat.service.ChatService;
import com.luohuo.flex.im.core.chat.service.adapter.RoomAdapter;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReq;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
import java.util.List;
import static com.luohuo.flex.im.common.config.ThreadPoolConfig.LUOHUO_EXECUTOR;
/**
* 邀请群成员监听器
*
* @author 乾乾
*/
@Slf4j
@Component
@AllArgsConstructor
public class GroupInviteMemberListener {
private ChatService chatService;
private UserCache userCache;
/**
* 触发邀请群员的消息
* @param event
*/
@Async(LUOHUO_EXECUTOR)
@TransactionalEventListener(classes = GroupInviteMemberEvent.class, fallbackExecution = true, phase = TransactionPhase.AFTER_COMMIT)
public void sendAddMsg(GroupInviteMemberEvent event) {
List<Long> uidList = event.getMemberList();
Long roomId = event.getRoomId();
User user = userCache.get(event.getUid());
ChatMessageReq chatMessageReq = RoomAdapter.buildGroupAddMessage(roomId, user, userCache.getBatch(uidList));
chatService.sendMsg(chatMessageReq, user.getId());
}
}

View File

@@ -1,16 +1,12 @@
package com.luohuo.flex.im.common.event.listener;
import com.luohuo.basic.cache.repository.CachePlusOps;
import com.luohuo.flex.im.api.PresenceApi;
import com.luohuo.flex.im.common.event.GroupMemberAddEvent;
import com.luohuo.flex.im.core.chat.dao.GroupMemberDao;
import com.luohuo.flex.im.domain.vo.request.ChatMessageReq;
import com.luohuo.flex.im.core.chat.service.ChatService;
import com.luohuo.flex.im.core.chat.service.adapter.MemberAdapter;
import com.luohuo.flex.im.core.chat.service.adapter.RoomAdapter;
import com.luohuo.flex.im.core.chat.service.cache.GroupMemberCache;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.core.user.service.cache.UserInfoCache;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.core.user.service.impl.PushService;
import com.luohuo.flex.model.entity.ws.ChatMember;
import lombok.AllArgsConstructor;
@@ -34,28 +30,12 @@ import static com.luohuo.flex.im.common.config.ThreadPoolConfig.LUOHUO_EXECUTOR;
@AllArgsConstructor
public class GroupMemberAddListener {
private CachePlusOps cachePlusOps;
private ChatService chatService;
private UserInfoCache userInfoCache;
private UserCache userCache;
private GroupMemberDao groupMemberDao;
private GroupMemberCache groupMemberCache;
private PresenceApi presenceApi;
private PushService pushService;
/**
* 触发群主邀请群员的消息
* @param event
*/
@Async(LUOHUO_EXECUTOR)
@TransactionalEventListener(classes = GroupMemberAddEvent.class, fallbackExecution = true, phase = TransactionPhase.AFTER_COMMIT)
public void sendAddMsg(GroupMemberAddEvent event) {
List<Long> uidList = event.getMemberList();
Long roomId = event.getRoomId();
User user = userInfoCache.get(event.getUid());
ChatMessageReq chatMessageReq = RoomAdapter.buildGroupAddMessage(roomId, user, userInfoCache.getBatch(uidList));
chatService.sendMsg(chatMessageReq, user.getId());
}
/**
* TODO 这里要做屏蔽群成员、所有群成员
* 群成员变动推送逻辑
@@ -68,7 +48,7 @@ public class GroupMemberAddListener {
List<Long> memberUidList = groupMemberCache.getMemberExceptUidList(roomId);
// 在线的用户
List<Long> onlineUids = presenceApi.getGroupOnlineMembers(roomId).getData();
Map<Long, User> map = userInfoCache.getBatch(event.getMemberList());
Map<Long, User> map = userCache.getBatch(event.getMemberList());
List<ChatMember> memberResps = groupMemberDao.getMemberListByUid(event.getMemberList());
pushService.sendPushMsg(MemberAdapter.buildMemberAddWS(roomId, event.getTotalNum(), onlineUids, memberResps, map), memberUidList, event.getUid());

View File

@@ -1,5 +1,6 @@
package com.luohuo.flex.im.common.event.listener;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
@@ -12,7 +13,6 @@ 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 com.luohuo.flex.im.core.user.service.cache.UserCache;
import java.util.Objects;
@@ -31,7 +31,7 @@ public class ItemReceiveListener {
@Resource
private ItemCache itemCache;
@Resource
private UserCache userCache;
private UserSummaryCache userSummaryCache;
/**
* 徽章类型,帮忙默认佩戴
@@ -47,7 +47,7 @@ public class ItemReceiveListener {
User user = userDao.getById(userBackpack.getUid());
if (Objects.isNull(user.getItemId())) {
userDao.wearingBadge(userBackpack.getUid(), userBackpack.getItemId());
userCache.userInfoChange(userBackpack.getUid());
userSummaryCache.delete(userBackpack.getUid());
}
}
}

View File

@@ -1,6 +1,7 @@
package com.luohuo.flex.im.common.event.listener;
import com.luohuo.basic.context.ContextUtil;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
@@ -11,7 +12,6 @@ import com.luohuo.flex.im.core.chat.dao.MessageDao;
import com.luohuo.flex.model.entity.WSRespTypeEnum;
import com.luohuo.flex.model.entity.WsBaseResp;
import com.luohuo.flex.model.entity.ws.WSBlack;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.core.user.service.impl.PushService;
import static com.luohuo.flex.im.common.config.ThreadPoolConfig.LUOHUO_EXECUTOR;
@@ -27,13 +27,13 @@ import static com.luohuo.flex.im.common.config.ThreadPoolConfig.LUOHUO_EXECUTOR;
public class UserBlackListener {
private final MessageDao messageDao;
private final UserCache userCache;
private final UserSummaryCache userSummaryCache;
private final PushService pushService;
@Async(LUOHUO_EXECUTOR)
@EventListener(classes = UserBlackEvent.class)
public void refreshRedis(UserBlackEvent event) {
userCache.evictBlackMap();
userSummaryCache.evictBlackMap();
}
@Async(LUOHUO_EXECUTOR)

View File

@@ -5,7 +5,7 @@ import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.luohuo.basic.base.R;
import com.luohuo.basic.context.ContextUtil;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.enums.BlackTypeEnum;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -26,11 +26,11 @@ import java.util.Set;
@RequiredArgsConstructor
public class BlackInterceptor implements HandlerInterceptor {
private final UserCache userCache;
private final UserSummaryCache userSummaryCache;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<Integer, Set<String>> blackMap = userCache.getBlackMap();
Map<Integer, Set<String>> blackMap = userSummaryCache.getBlackMap();
Long uid = ContextUtil.getUid();
if (isBlackList(uid, blackMap.get(BlackTypeEnum.UID.getType()))) {
response.setStatus(HttpStatus.OK.value());

View File

@@ -22,13 +22,9 @@ import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.luohuo.flex.im.domain.enums.GroupRoleEnum.ROLE_LIST;
/**
* <p>
* 群成员表 服务实现类
@@ -67,23 +63,6 @@ public class GroupMemberDao extends ServiceImpl<GroupMemberMapper, GroupMember>
.list();
}
/**
* 批量获取成员群角色
*
* @param groupId 群ID
* @param uidList 用户列表
* @return 成员群角色列表
*/
public Map<String, Integer> getMemberMapRole(Long groupId, List<Long> uidList) {
List<GroupMember> list = lambdaQuery()
.eq(GroupMember::getGroupId, groupId)
.in(GroupMember::getUid, uidList)
.in(GroupMember::getRoleId, ROLE_LIST)
.select(GroupMember::getUid, GroupMember::getRoleId)
.list();
return list.stream().collect(Collectors.toMap(member -> String.valueOf(member.getUid()), GroupMember::getRoleId));
}
/**
* 获取群聊人员
* isSpecialtrue -> 获取群主、管理员

View File

@@ -10,10 +10,8 @@ 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.request.member.MemberReq;
import com.luohuo.flex.im.domain.vo.response.ChatMessageReadResp;
import com.luohuo.flex.model.entity.ws.ChatMessageResp;
import com.luohuo.flex.model.entity.ws.ChatMemberResp;
import jakarta.annotation.Nullable;
import java.util.Collection;
@@ -51,15 +49,6 @@ public interface ChatService {
*/
ChatMessageResp getMsgResp(Long msgId, Long receiveUid);
/**
* 获取群成员列表
*
* @param memberUidList 成员id集和
* @param request 参数
* @return {@link CursorPageBaseResp }<{@link ChatMemberResp }>
*/
CursorPageBaseResp<ChatMemberResp> getMemberPage(List<Long> memberUidList, MemberReq request);
/**
* 获取消息列表
*

View File

@@ -44,10 +44,12 @@ public interface RoomAppService {
* @return
*/
Boolean createContact(Long uid, @Valid ContactAddReq request);
/**
* 获取会话列表--支持未登录态
*/
CursorPageBaseResp<ChatRoomResp> getContactPage(CursorPageBaseReq request, Long uid);
/**
* 获取用户所有会话列表
*/
@@ -58,22 +60,41 @@ public interface RoomAppService {
*/
MemberResp getGroupDetail(Long uid, long roomId);
CursorPageBaseResp<ChatMemberResp> getMemberPage(MemberReq request);
/**
* 获取群成员
*/
List<ChatMemberListResp> getMemberList(ChatMessageMemberReq request);
/**
* 移出群聊
*/
void delMember(Long uid, MemberDelReq request);
/**
* 邀请好友进群
*/
void addMember(Long uid, MemberAddReq request);
/**
* 创建群聊
*/
Long addGroup(Long uid, GroupAddReq request);
/**
* 获取单个会话
*/
ChatRoomResp getContactDetail(Long uid, Long roomId);
ChatRoomResp getContactDetailByFriend(Long uid, @Valid ContactFriendReq req);
/**
* 获取群聊列表
*/
List<MemberResp> groupList(Long uid);
/**
* 同步在线状态
*/
void asyncOnline(List<Long> uidList, Long roomId, boolean online);
/**

View File

@@ -1,7 +1,5 @@
package com.luohuo.flex.im.core.chat.service.adapter;
import com.luohuo.flex.im.domain.entity.IpDetail;
import com.luohuo.flex.im.domain.entity.IpInfo;
import com.luohuo.flex.model.entity.ws.ChatMember;
import com.luohuo.flex.model.enums.ChatActiveStatusEnum;
import lombok.extern.slf4j.Slf4j;
@@ -13,15 +11,12 @@ import com.luohuo.flex.im.domain.vo.response.ChatMemberListResp;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.model.entity.WSRespTypeEnum;
import com.luohuo.flex.model.entity.WsBaseResp;
import com.luohuo.flex.model.entity.ws.ChatMemberResp;
import com.luohuo.flex.model.entity.ws.WSFeedMemberResp;
import com.luohuo.flex.model.entity.ws.WSMemberChange;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static com.luohuo.flex.model.entity.ws.WSMemberChange.CHANGE_TYPE_ADD;
@@ -34,31 +29,6 @@ import static com.luohuo.flex.model.entity.ws.WSMemberChange.CHANGE_TYPE_ADD;
@Slf4j
public class MemberAdapter {
/**
* 将User对象转换为ChatMemberResp对象并注入实时在线状态
* @param list 用户列表
* @param onlineUids 用户在线状态表
* @return 转换后的群成员响应对象列表
*/
public static List<ChatMemberResp> buildMember(List<User> list, Set<Long> onlineUids) {
return list.stream().map(user -> {
ChatMemberResp resp = new ChatMemberResp();
resp.setUid(String.valueOf(user.getId()));
resp.setName(user.getName());
resp.setAvatar(user.getAvatar());
resp.setAccount(user.getAccount());
resp.setLocPlace(Optional.ofNullable(user.getIpInfo()).map(IpInfo::getUpdateIpDetail).map(IpDetail::getCity).orElse(null));
resp.setUserStateId(user.getUserStateId());
Boolean isOnline = onlineUids.contains(user.getId());
resp.setActiveStatus(isOnline? ChatActiveStatusEnum.ONLINE.getStatus(): ChatActiveStatusEnum.OFFLINE.getStatus());
// 最后活跃时间(离线用户显示)
if (!isOnline && user.getLastOptTime() != null) {
resp.setLastOptTime(user.getLastOptTime());
}
return resp;
}).collect(Collectors.toList());
}
public static List<ChatMemberListResp> buildMemberList(List<User> memberList) {
return memberList.stream()
.map(a -> {

View File

@@ -150,7 +150,6 @@ public class MessageAdapter {
return chatMessageReq;
}
public static ChatMessageReq buildAgreeMsg4Group(Long roomId, Long count, String userName) {
ChatMessageReq chatMessageReq = new ChatMessageReq();
chatMessageReq.setRoomId(roomId);

View File

@@ -1,29 +0,0 @@
package com.luohuo.flex.im.core.chat.service.helper;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import com.luohuo.flex.model.enums.ChatActiveStatusEnum;
/**
* 成员列表工具类
* @author nyh
*/
public class ChatMemberHelper {
private static final String SEPARATOR = "_";
public static Pair<ChatActiveStatusEnum, String> getCursorPair(String cursor) {
ChatActiveStatusEnum activeStatusEnum = ChatActiveStatusEnum.ONLINE;
String timeCursor = null;
if (StrUtil.isNotBlank(cursor)) {
String activeStr = cursor.split(SEPARATOR)[0];
String timeStr = cursor.split(SEPARATOR)[1];
activeStatusEnum = ChatActiveStatusEnum.of(Integer.parseInt(activeStr));
timeCursor = timeStr;
}
return Pair.of(activeStatusEnum, timeCursor);
}
public static String generateCursor(ChatActiveStatusEnum activeStatusEnum, String timeCursor) {
return activeStatusEnum.getStatus() + SEPARATOR + timeCursor;
}
}

View File

@@ -3,22 +3,16 @@ package com.luohuo.flex.im.core.chat.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.luohuo.basic.base.R;
import com.luohuo.basic.utils.SpringUtils;
import com.luohuo.basic.utils.TimeUtils;
import com.luohuo.flex.im.api.PresenceApi;
import com.luohuo.flex.im.core.chat.dao.*;
import com.luohuo.flex.im.core.chat.service.cache.GroupMemberCache;
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.model.enums.ChatActiveStatusEnum;
import jakarta.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -28,7 +22,6 @@ import org.springframework.transaction.annotation.Transactional;
import com.luohuo.basic.exception.BizException;
import com.luohuo.basic.validator.utils.AssertUtil;
import com.luohuo.flex.model.redis.annotation.RedissonLock;
import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq;
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;
@@ -40,24 +33,18 @@ 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.request.member.MemberReq;
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;
import com.luohuo.flex.im.core.chat.service.ContactService;
import com.luohuo.flex.im.core.chat.service.adapter.MemberAdapter;
import com.luohuo.flex.im.core.chat.service.adapter.MessageAdapter;
import com.luohuo.flex.im.core.chat.service.adapter.RoomAdapter;
import com.luohuo.flex.im.core.chat.service.cache.RoomCache;
import com.luohuo.flex.im.core.chat.service.cache.RoomGroupCache;
import com.luohuo.flex.im.core.chat.service.helper.ChatMemberHelper;
import com.luohuo.flex.im.core.chat.service.strategy.mark.AbstractMsgMarkStrategy;
import com.luohuo.flex.im.core.chat.service.strategy.mark.MsgMarkFactory;
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.chat.service.strategy.msg.RecallMsgHandler;
import com.luohuo.flex.im.core.user.dao.UserDao;
import com.luohuo.flex.model.entity.ws.ChatMemberResp;
import com.luohuo.flex.im.core.user.service.RoleService;
import java.time.Duration;
@@ -77,7 +64,6 @@ public class ChatServiceImpl implements ChatService {
private final GroupMemberCache groupMemberCache;
private MsgCache msgCache;
private MessageDao messageDao;
private UserDao userDao;
private MessageMarkDao messageMarkDao;
private RoomFriendDao roomFriendDao;
private RoleService roleService;
@@ -86,8 +72,6 @@ public class ChatServiceImpl implements ChatService {
private ContactDao contactDao;
private RoomCache roomCache;
private GroupMemberDao groupMemberDao;
private RoomGroupCache roomGroupCache;
private PresenceApi presenceApi;
/**
* 发送消息
@@ -190,63 +174,6 @@ public class ChatServiceImpl implements ChatService {
return getMsgResp(msg, receiveUid);
}
private String generateCursor(ChatActiveStatusEnum type, String innerCursor) {
return type.name() + "_" + innerCursor;
}
@Override
public CursorPageBaseResp<ChatMemberResp> getMemberPage(List<Long> memberUidList, MemberReq request) {
Pair<ChatActiveStatusEnum, String> pair = ChatMemberHelper.getCursorPair(request.getCursor());
ChatActiveStatusEnum activeStatusEnum = pair.getKey();
String timeCursor = pair.getValue();
// 1. 批量获取所有成员在线状态、分离在线用户与离线用户
Set<Long> onlineUids = presenceApi.getOnlineUsersList(memberUidList).getData();
Set<Long> offlineUids = memberUidList.stream().filter(uid -> !onlineUids.contains(uid)).collect(Collectors.toSet());
// 3. 动态分页组装
List<ChatMemberResp> resultList = new ArrayList<>();
Boolean isLast = Boolean.FALSE;
if (activeStatusEnum == ChatActiveStatusEnum.ONLINE) {
// 在线列表
CursorPageBaseResp<User> onlinePage = userDao.getCursorPage(onlineUids, new CursorPageBaseReq(request.getPageSize(), timeCursor));
// 添加在线列表
resultList.addAll(MemberAdapter.buildMember(onlinePage.getList(), onlineUids));
if (onlinePage.getIsLast()) {
// 如果是最后一页,从离线列表再补点数据
CursorPageBaseResp<User> offlinePage = userDao.getCursorPage(offlineUids, new CursorPageBaseReq(request.getPageSize() - onlinePage.getList().size(), null));
resultList.addAll(MemberAdapter.buildMember(offlinePage.getList(), onlineUids));
timeCursor = generateCursor(ChatActiveStatusEnum.OFFLINE, offlinePage.getCursor());
isLast = offlinePage.getIsLast();
} else {
timeCursor = generateCursor(ChatActiveStatusEnum.ONLINE, onlinePage.getCursor());
isLast = false;
}
} else if (activeStatusEnum == ChatActiveStatusEnum.OFFLINE) {
// 离线列表
CursorPageBaseResp<User> cursorPage = userDao.getCursorPage(offlineUids, new CursorPageBaseReq(request.getPageSize(), timeCursor));
// 添加离线线列表
resultList.addAll(MemberAdapter.buildMember(cursorPage.getList(), onlineUids));
timeCursor = cursorPage.getCursor();
isLast = cursorPage.getIsLast();
}
// 获取群成员角色ID
List<Long> uidList = resultList.stream().map(item -> Long.parseLong(item.getUid())).collect(Collectors.toList());
RoomGroup roomGroup = roomGroupCache.getByRoomId(request.getRoomId());
// 更新角色和群信息
Map<String, Integer> uidMapRole = groupMemberDao.getMemberMapRole(roomGroup.getId(), uidList);
resultList.forEach(member -> {
member.setRoleId(uidMapRole.get(member.getUid()));
GroupMember groupMember = groupMemberCache.getMemberDetail(request.getRoomId(), Long.parseLong(member.getUid()));
member.setId(groupMember.getId());
member.setMyName(groupMember.getMyName());
});
// 组装结果
return new CursorPageBaseResp<>(ChatMemberHelper.generateCursor(activeStatusEnum, timeCursor), isLast, resultList, groupMemberDao.lambdaQuery().eq(GroupMember::getGroupId, roomGroup.getId()).count());
}
@Override
public CursorPageBaseResp<ChatMessageResp> getMsgPage(ChatMessagePageReq request, Long receiveUid) {
// 1. 用最后一条消息id来限制被踢出的人能看见的最大一条消息

View File

@@ -4,7 +4,6 @@ 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.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.luohuo.basic.cache.repository.CachePlusOps;
@@ -14,12 +13,15 @@ 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.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.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.vo.req.room.UserApplyResp;
@@ -35,7 +37,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
@@ -102,7 +103,6 @@ import com.luohuo.flex.im.core.user.service.FriendService;
import com.luohuo.flex.im.core.user.service.RoleService;
import com.luohuo.flex.im.core.user.service.adapter.WsAdapter;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.core.user.service.cache.UserInfoCache;
import com.luohuo.flex.im.core.user.service.impl.PushService;
import java.time.LocalDateTime;
@@ -111,7 +111,6 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.luohuo.flex.im.common.config.ThreadPoolConfig.LUOHUO_EXECUTOR;
import static com.luohuo.flex.im.core.chat.constant.GroupConst.MAX_MANAGE_COUNT;
import static com.luohuo.flex.im.domain.enums.ApplyReadStatusEnum.UNREAD;
@@ -131,10 +130,10 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
private RoomGroupCache roomGroupCache;
private RoomFriendCache roomFriendCache;
private CachePlusOps cachePlusOps;
private UserInfoCache userInfoCache;
private UserCache userCache;
private MessageDao messageDao;
private HotRoomCache hotRoomCache;
private UserCache userCache;
private UserSummaryCache userSummaryCache;
private RoomAnnouncementsCache roomAnnouncementsCache;
private GroupMemberDao groupMemberDao;
private UserDao userDao;
@@ -371,6 +370,10 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
roomService.createGroupMember(groupId, uid);
// 创建系统消息
friendService.createSystemFriend(uid);
// 发送进群事件
CacheKey gKey = PresenceCacheKeyBuilder.groupMembersKey(roomId);
CacheKey onlineGroupMembersKey = PresenceCacheKeyBuilder.onlineGroupMembersKey(roomId);
SpringUtils.publishEvent(new GroupMemberAddEvent(this, roomId, Math.toIntExact(cachePlusOps.sCard(gKey)), Math.toIntExact(cachePlusOps.sCard(onlineGroupMembersKey)), Arrays.asList(uid), uid));
}
/**
@@ -673,7 +676,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
RoomFriend roomFriend = roomFriendCache.get(request.getRoomId());
roomService.updateState(uid.equals(roomFriend.getUid1()), roomFriend.getUid1(), roomFriend.getUid2(), request.getState());
name = userCache.getUserInfo(roomFriend.getUid1().equals(uid) ? roomFriend.getUid2() : roomFriend.getUid1()).getName();
name = userSummaryCache.get(roomFriend.getUid1().equals(uid) ? roomFriend.getUid2() : roomFriend.getUid1()).getName();
}
// 3. 通知所有设备我已经屏蔽这个房间
@@ -703,7 +706,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 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(), userInfoCache.get(message.getFromUid()).getName())).collect(Collectors.toUnmodifiableList());
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);
@@ -741,21 +744,6 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
.build();
}
@Override
public CursorPageBaseResp<ChatMemberResp> getMemberPage(MemberReq request) {
Room room = roomCache.get(request.getRoomId());
AssertUtil.isNotEmpty(room, "房间号有误");
List<Long> memberUidList;
if (isHotGroup(room)) {
// 全员群展示所有用户
memberUidList = null;
} else {// 只展示房间内的群成员
RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
memberUidList = groupMemberDao.getMemberUidList(roomGroup.getId(), null);
}
return chatService.getMemberPage(memberUidList, request);
}
@Override
public List<ChatMemberResp> listMember(MemberReq request) {
// 1. 基础校验
@@ -769,8 +757,8 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
List<ChatMemberResp> chatMemberResps = groupMemberDao.getMemberListByGroupId(roomGroupCache.get(request.getRoomId()).getId());
// 3. 批量获取用户信息
Set<Long> uids = chatMemberResps.stream().map(ChatMemberResp::getUid).map(Long::parseLong).collect(Collectors.toSet());
Map<Long, User> userInfoBatch = userCache.getUserInfoBatch(uids);
List<Long> uids = chatMemberResps.stream().map(ChatMemberResp::getUid).map(Long::parseLong).collect(Collectors.toList());
Map<Long, SummeryInfoDTO> batch = userSummaryCache.getBatch(uids);
// 5. 批量获取在线状态
Set<Long> onlineList = presenceApi.getOnlineUsersList(new ArrayList<>(uids)).getData();
@@ -778,15 +766,19 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
// 6. 填充用户信息和在线状态
chatMemberResps.forEach(item -> {
Long uid = Long.parseLong(item.getUid());
User user = userInfoBatch.get(uid);
SummeryInfoDTO user = batch.get(uid);
if (user != null) {
item.setActiveStatus(onlineList.contains(uid) ? ChatActiveStatusEnum.ONLINE.getStatus() : ChatActiveStatusEnum.OFFLINE.getStatus());
item.setLastOptTime(user.getLastOptTime());
item.setName(user.getName());
item.setAvatar(user.getAvatar());
item.setLocPlace(user.getLocPlace());
item.setAccount(user.getAccount());
item.setUserStateId(user.getUserStateId());
item.setItemIds(user.getItemIds());
item.setUserType(user.getUserType());
item.setWearingItemId(user.getWearingItemId());
}
});
@@ -817,7 +809,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
} else {
RoomGroup roomGroup = roomGroupCache.get(room.getId());
List<Long> memberUidList = groupMemberDao.getMemberUidList(roomGroup.getId(), null);
Map<Long, User> batch = userInfoCache.getBatch(memberUidList);
Map<Long, User> batch = userCache.getBatch(memberUidList);
return MemberAdapter.buildMemberList(batch);
}
}
@@ -908,14 +900,14 @@ 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("{}邀请你加入{}", userCache.getUserInfo(uid).getName(), roomGroup.getName()), ApplyStatusEnum.WAIT_APPROVAL.getCode(), UNREAD.getCode(), 0, false)).collect(Collectors.toList());
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;
});
// 3. 通知被邀请的人进群
validUids.forEach(inviteId -> {
User user = userCache.getUserInfo(inviteId);
SummeryInfoDTO user = userSummaryCache.get(inviteId);
if(ObjectUtil.isNotNull(user)){
pushService.sendPushMsg(MessageAdapter.buildInviteeUserAddGroupMessage(userApplyDao.getUnReadCount(inviteId, inviteId)), inviteId, uid);
}
@@ -1014,7 +1006,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
@Override
@RedissonLock(prefixKey = "addGroup:", key = "#uid")
public Long addGroup(Long uid, GroupAddReq request) {
Map<Long, User> userMap = userCache.getUserInfoBatch(request.getUidList().stream().collect(Collectors.toSet()));
Map<Long, SummeryInfoDTO> userMap = userSummaryCache.getBatch(request.getUidList());
AssertUtil.isTrue(userMap.size() > 1,"群聊人数应大于2人");
List<Long> uidList = new ArrayList<>(userMap.keySet());
@@ -1044,6 +1036,7 @@ 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();
@@ -1092,7 +1085,6 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
List<Long> msgIds = roomBaseInfoMap.values().stream().map(RoomBaseInfo::getLastMsgId).collect(Collectors.toList());
List<Message> messages = CollectionUtil.isEmpty(msgIds) ? new ArrayList<>() : messageDao.listByIds(msgIds);
Map<Long, Message> msgMap = messages.stream().collect(Collectors.toMap(Message::getId, Function.identity()));
// Map<Long, User> lastMsgUidMap = userInfoCache.getBatch(messages.stream().map(Message::getFromUid).collect(Collectors.toList()));
// 消息未读数
Map<Long, Integer> unReadCountMap = getUnReadCountMap(contactMap.values());
@@ -1174,7 +1166,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
}
Map<Long, RoomFriend> roomFriendMap = roomFriendCache.getBatch(roomIds);
Set<Long> friendUidSet = ChatAdapter.getFriendUidSet(roomFriendMap.values(), uid);
Map<Long, User> userBatch = userInfoCache.getBatch(new ArrayList<>(friendUidSet));
Map<Long, User> userBatch = userCache.getBatch(new ArrayList<>(friendUidSet));
return roomFriendMap.values()
.stream()
.collect(Collectors.toMap(RoomFriend::getRoomId, roomFriend -> {

View File

@@ -25,7 +25,7 @@ import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
import com.luohuo.flex.im.core.chat.service.RoomService;
import com.luohuo.flex.im.core.chat.service.adapter.ChatAdapter;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.core.user.service.cache.UserInfoCache;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.domain.vo.response.MemberResp;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
@@ -46,7 +46,7 @@ public class RoomServiceImpl implements RoomService {
private GroupMemberDao groupMemberDao;
private AnnouncementsDao announcementsDao;
private AnnouncementsReadRecordDao announcementsReadRecordDao;
private UserInfoCache userInfoCache;
private UserCache userCache;
private RoomGroupDao roomGroupDao;
@Override
@@ -86,7 +86,7 @@ public class RoomServiceImpl implements RoomService {
public RoomGroup createGroupRoom(Long uid, GroupAddReq groupAddReq) {
List<GroupMember> selfGroup = groupMemberDao.getSelfGroup(uid);
AssertUtil.isTrue(selfGroup.size() < 20, "每个人只能创建20个群");
User user = userInfoCache.get(uid);
User user = userCache.get(uid);
Room room = createRoom(RoomTypeEnum.GROUP);
// 插入群
RoomGroup roomGroup = ChatAdapter.buildGroupRoom(user, room.getId(), groupAddReq.getGroupName());
@@ -137,7 +137,7 @@ public class RoomServiceImpl implements RoomService {
public AnnouncementsResp getAnnouncement(Long id) {
AnnouncementsResp resp = new AnnouncementsResp();
BeanUtils.copyProperties(announcementsDao.getById(id), resp);
User user = userInfoCache.get(resp.getUid());
User user = userCache.get(resp.getUid());
resp.setUName(user.getName());
return resp;
}

View File

@@ -7,8 +7,8 @@ import com.luohuo.flex.im.core.frequencyControl.FrequencyControlException;
import com.luohuo.flex.im.core.frequencyControl.constant.FrequencyControlConstant;
import com.luohuo.flex.im.core.frequencyControl.dto.FrequencyControlDTO;
import com.luohuo.flex.im.core.frequencyControl.util.FrequencyControlUtil;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
@@ -40,18 +40,16 @@ public class WeChatMsgOperationServiceImpl implements WeChatMsgOperationService
private final String WE_CHAT_MSG_COLOR = "#A349A4";
@Resource
private UserCache userCache;
@Resource
private UserSummaryCache userSummaryCache;
@Resource
private WxMpService wxMpService;
@Override
public void publishChatMsgToWeChatUser(long senderUid, List<Long> receiverUidList, String msg) {
User sender = userCache.getUserInfo(senderUid);
Set uidSet = new HashSet();
uidSet.addAll(receiverUidList);
Map<Long, User> userMap = userCache.getUserInfoBatch(uidSet);
SummeryInfoDTO sender = userSummaryCache.get(senderUid);
Map<Long, SummeryInfoDTO> userMap = userSummaryCache.getBatch(receiverUidList);
userMap.values().forEach(user -> {
if (Objects.nonNull(user.getOpenId())) {
executor.execute(() -> {
@@ -81,7 +79,7 @@ public class WeChatMsgOperationServiceImpl implements WeChatMsgOperationService
/*
* 构造微信模板消息
*/
private WxMpTemplateMessage getAtMsgTemplate(User sender, String openId, String msg) {
private WxMpTemplateMessage getAtMsgTemplate(SummeryInfoDTO sender, String openId, String msg) {
return WxMpTemplateMessage.builder()
.toUser(openId)
.templateId(atMsgPublishTemplateId)
@@ -92,7 +90,7 @@ public class WeChatMsgOperationServiceImpl implements WeChatMsgOperationService
/*
* 构造微信消息模板的数据
*/
private List<WxMpTemplateData> generateAtMsgData(User sender, String msg) {
private List<WxMpTemplateData> generateAtMsgData(SummeryInfoDTO sender, String msg) {
List dataList = new ArrayList<WxMpTemplateData>();
// todo: 没有消息模板,暂不实现
dataList.add(new WxMpTemplateData("name", sender.getName(), WE_CHAT_MSG_COLOR));

View File

@@ -1,6 +1,8 @@
package com.luohuo.flex.im.core.chat.service.strategy.msg;
import cn.hutool.core.bean.BeanUtil;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.transaction.annotation.Transactional;
@@ -14,9 +16,6 @@ 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.domain.entity.User;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import java.lang.reflect.ParameterizedType;
import java.util.Objects;
import java.util.Optional;
@@ -30,7 +29,7 @@ public abstract class AbstractMsgHandler<T> {
private MsgPlusCache msgPlusCache;
@Resource
private UserCache userCache;
private UserSummaryCache userSummaryCache;
private Class<T> bodyClass;
@@ -89,11 +88,11 @@ public abstract class AbstractMsgHandler<T> {
if (reply.isPresent()) {
Message replyMessage = reply.get();
ReplyMsg replyMsgVO = new ReplyMsg();
replyMsgVO.setId(replyMessage.getId());
replyMsgVO.setUid(replyMessage.getFromUid());
replyMsgVO.setId(replyMessage.getId().toString());
replyMsgVO.setUid(replyMessage.getFromUid().toString());
replyMsgVO.setType(replyMessage.getType());
replyMsgVO.setBody(MsgHandlerFactory.getStrategyNoNull(replyMessage.getType()).showReplyMsg(replyMessage));
User replyUser = userCache.getUserInfo(replyMessage.getFromUid());
SummeryInfoDTO replyUser = userSummaryCache.get(replyMessage.getFromUid());
replyMsgVO.setUsername(replyUser.getName());
replyMsgVO.setCanCallback(YesOrNoEnum.toStatus(Objects.nonNull(msg.getGapCount()) && msg.getGapCount() <= MessageAdapter.CAN_CALLBACK_GAP_COUNT));
replyMsgVO.setGapCount(msg.getGapCount());

View File

@@ -6,7 +6,7 @@ import com.luohuo.flex.im.common.utils.discover.PrioritizedUrlDiscover;
import com.luohuo.flex.im.common.utils.sensitiveword.SensitiveWordBs;
import com.luohuo.flex.im.core.chat.dao.MessageDao;
import com.luohuo.flex.im.core.user.service.RoleService;
import com.luohuo.flex.im.core.user.service.cache.UserInfoCache;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.domain.UrlInfo;
import com.luohuo.flex.im.domain.entity.Message;
import com.luohuo.flex.im.domain.entity.User;
@@ -33,7 +33,7 @@ import java.util.stream.Collectors;
public class BotMsgHandler extends AbstractMsgHandler<TextMsgReq> {
private MessageDao messageDao;
private UserInfoCache userInfoCache;
private UserCache userCache;
private RoleService roleService;
private SensitiveWordBs sensitiveWordBs;
@@ -55,7 +55,7 @@ public class BotMsgHandler extends AbstractMsgHandler<TextMsgReq> {
if (CollectionUtil.isNotEmpty(body.getAtUidList())) {
//前端传入的@用户列表可能会重复,需要去重
List<Long> atUidList = body.getAtUidList().stream().distinct().collect(Collectors.toList());
Map<Long, User> batch = userInfoCache.getBatch(atUidList);
Map<Long, User> batch = userCache.getBatch(atUidList);
//如果@用户不存在userInfoCache 返回的map中依然存在该key但是value为null需要过滤掉再校验
long batchCount = batch.values().stream().filter(Objects::nonNull).count();
AssertUtil.equal((long)atUidList.size(), batchCount, "@用户不存在");

View File

@@ -2,6 +2,8 @@ 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.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import com.luohuo.flex.im.common.event.MessageRecallEvent;
@@ -12,8 +14,6 @@ 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 com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import java.time.LocalDateTime;
import java.util.List;
@@ -28,7 +28,7 @@ import java.util.Objects;
public class RecallMsgHandler extends AbstractMsgHandler<Object> {
private MessageDao messageDao;
private UserCache userCache;
private UserSummaryCache userSummaryCache;
private MsgCache msgCache;
@Override
@@ -44,7 +44,7 @@ public class RecallMsgHandler extends AbstractMsgHandler<Object> {
@Override
public Object showMsg(Message msg) {
MsgRecall recall = msg.getExtra().getRecall();
User userInfo = userCache.getUserInfo(recall.getRecallUid());
SummeryInfoDTO userInfo = userSummaryCache.get(recall.getRecallUid());
if (!Objects.equals(recall.getRecallUid(), msg.getFromUid())) {
return "管理员\"" + userInfo.getName() + "\"撤回了一条成员消息";
}

View File

@@ -16,7 +16,7 @@ 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;
import com.luohuo.flex.im.core.user.service.RoleService;
import com.luohuo.flex.im.core.user.service.cache.UserInfoCache;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import java.util.List;
import java.util.Map;
@@ -33,7 +33,7 @@ import java.util.stream.Collectors;
public class TextMsgHandler extends AbstractMsgHandler<TextMsgReq> {
private MessageDao messageDao;
private UserInfoCache userInfoCache;
private UserCache userCache;
private RoleService roleService;
private SensitiveWordBs sensitiveWordBs;
@@ -55,7 +55,7 @@ public class TextMsgHandler extends AbstractMsgHandler<TextMsgReq> {
if (CollectionUtil.isNotEmpty(body.getAtUidList())) {
//前端传入的@用户列表可能会重复,需要去重
List<Long> atUidList = body.getAtUidList().stream().distinct().collect(Collectors.toList());
Map<Long, User> batch = userInfoCache.getBatch(atUidList);
Map<Long, User> batch = userCache.getBatch(atUidList);
//如果@用户不存在userInfoCache 返回的map中依然存在该key但是value为null需要过滤掉再校验
long batchCount = batch.values().stream().filter(Objects::nonNull).count();
AssertUtil.equal((long)atUidList.size(), batchCount, "@用户不存在");

View File

@@ -2,9 +2,8 @@ package com.luohuo.flex.im.core.chat.service.strategy.msg;
import com.luohuo.basic.context.ContextUtil;
import com.luohuo.flex.im.core.chat.dao.MessageDao;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.entity.Message;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.domain.entity.msg.VideoCallMsgDTO;
import com.luohuo.flex.im.domain.entity.msg.MessageExtra;
import com.luohuo.flex.im.domain.enums.MessageTypeEnum;
@@ -22,7 +21,7 @@ import java.util.Optional;
public class VideoCallMsgHandler extends AbstractMsgHandler<VideoCallMsgDTO> {
@Resource
private UserCache userCache;
private UserSummaryCache userSummaryCache;
@Override
MessageTypeEnum getMsgTypeEnum() {
@@ -47,8 +46,7 @@ public class VideoCallMsgHandler extends AbstractMsgHandler<VideoCallMsgDTO> {
// ===== 群聊场景 =====
if (Boolean.TRUE.equals(dto.getIsGroup())) {
User user = userCache.getUserInfo(msg.getFromUid());
return dto.getBegin() ? user.getName() + "发起了视频通话" : "视频通话已结束";
return dto.getBegin() ? userSummaryCache.get(msg.getFromUid()).getName() + "发起了视频通话" : "视频通话已结束";
}
// ===== 私聊场景 =====

View File

@@ -2,13 +2,8 @@ package com.luohuo.flex.im.core.user.dao;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luohuo.flex.im.common.enums.NormalOrNoEnum;
import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq;
import com.luohuo.flex.im.domain.vo.req.user.ModifyNameReq;
import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp;
import com.luohuo.flex.im.common.utils.CursorUtils;
import com.luohuo.flex.im.domain.vo.response.ChatMemberListResp;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.core.user.mapper.UserMapper;
@@ -32,14 +27,6 @@ public class UserDao extends ServiceImpl<UserMapper, User> {
return getOne(wrapper);
}
public void modifyName(Long uid, ModifyNameReq req) {
User update = new User();
update.setId(uid);
update.setName(req.getName());
update.setResume(req.getResume());
updateById(update);
}
public void wearingBadge(Long uid, Long badgeId) {
User update = new User();
update.setId(uid);
@@ -67,18 +54,6 @@ public class UserDao extends ServiceImpl<UserMapper, User> {
}
/**
* @param memberUidList 在线或离线的群成员id
*/
public CursorPageBaseResp<User> getCursorPage(Set<Long> memberUidList, CursorPageBaseReq request) {
if(memberUidList == null || memberUidList.size() == 0){
return new CursorPageBaseResp<>();
}
return CursorUtils.getCursorPageByMysql(this, request, wrapper -> {
wrapper.in(CollectionUtils.isNotEmpty(memberUidList), User::getId, memberUidList);//普通群对uid列表做限制
}, User::getLastOptTime);
}
public int changeUserState(Long uid, Long userStateId) {
return baseMapper.changeUserState(uid, userStateId);
}

View File

@@ -133,23 +133,13 @@ public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> {
return baseMapper.selectObjs(queryWrapper).stream().map(obj -> (Long) obj).collect(Collectors.toList());
}
/**
* 当uid的朋友改变后需要调用此方法
* @param uid
* @return
*/
@CacheEvict(cacheNames = "user", key = "'findGroup'+#uid")
public List<Long> evictGroup(Long uid) {
return null;
}
/**
* 根据房间号+自己的id 定位好友关系
* @param roomId
* @param uid
* @return
*/
@Cacheable(cacheNames = "userFriend", key = "'room:'+#roomId+':uid:'+#uid", unless = "#result == null")
@Cacheable(cacheNames = "luohuo:userFriend", key = "'room:'+#roomId+':uid:'+#uid", unless = "#result == null")
public UserFriend getByRoomId(Long roomId, Long uid) {
return lambdaQuery()
.eq(UserFriend::getRoomId, roomId)
@@ -162,7 +152,7 @@ public class UserFriendDao extends ServiceImpl<UserFriendMapper, UserFriend> {
* @param roomId 房间ID
* @param uid 用户ID
*/
@CacheEvict(cacheNames = "userFriend", key = "'room:'+#roomId+':uid:'+#uid")
@CacheEvict(cacheNames = "luohuo:userFriend", key = "'room:'+#roomId+':uid:'+#uid")
public void evictFriendCache(Long roomId, Long uid) {
}

View File

@@ -2,14 +2,13 @@ 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;
import com.luohuo.flex.im.domain.vo.req.user.BlackReq;
import com.luohuo.flex.im.domain.vo.req.user.ItemInfoReq;
import com.luohuo.flex.im.domain.vo.req.user.ModifyAvatarReq;
import com.luohuo.flex.im.domain.vo.req.user.ModifyNameReq;
import com.luohuo.flex.im.domain.vo.req.user.SummeryInfoReq;
import com.luohuo.flex.im.domain.vo.req.user.WearingBadgeReq;
import com.luohuo.flex.im.domain.vo.resp.user.BadgeResp;
import com.luohuo.flex.im.domain.vo.resp.user.UserInfoResp;
@@ -25,6 +24,12 @@ import java.util.List;
*/
public interface UserService {
/**
* 刷新ip信息
* @param uid 用户id
*/
Boolean refreshIpInfo(Long uid, IpInfo ipInfo);
/**
* 校验邮箱是否存在
* @param email 邮箱
@@ -87,14 +92,6 @@ public interface UserService {
void black(BlackReq req);
/**
* 获取用户汇总信息
*
* @param req
* @return
*/
List<SummeryInfoDTO> getSummeryUserInfo(SummeryInfoReq req);
List<ItemInfoDTO> getItemInfo(ItemInfoReq req);
/**

View File

@@ -3,6 +3,7 @@ package com.luohuo.flex.im.core.user.service.adapter;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import com.luohuo.flex.im.common.enums.YesOrNoEnum;
import com.luohuo.flex.im.domain.entity.ItemConfig;
@@ -22,12 +23,6 @@ import java.util.stream.Collectors;
*/
public class UserAdapter {
public static User buildUser(String openId) {
User user = new User();
user.setOpenId(openId);
return user;
}
public static User buildAuthorizeUser(Long id, String account, WxOAuth2UserInfo userInfo) {
User user = new User();
user.setId(id);
@@ -43,10 +38,10 @@ public class UserAdapter {
return user;
}
public static UserInfoResp buildUserInfoResp(User userInfo, Integer countByValidItemId) {
public static UserInfoResp buildUserInfoResp(SummeryInfoDTO userInfo, Integer countByValidItemId) {
UserInfoResp userInfoResp = new UserInfoResp();
BeanUtil.copyProperties(userInfo, userInfoResp);
userInfoResp.setUid(userInfo.getId());
userInfoResp.setUid(userInfo.getUid());
userInfoResp.setModifyNameChance(countByValidItemId);
return userInfoResp;
}

View File

@@ -1,148 +1,39 @@
package com.luohuo.flex.im.core.user.service.cache;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.luohuo.basic.cache.repository.CachePlusOps;
import com.luohuo.basic.model.cache.CacheKey;
import com.luohuo.basic.utils.TimeUtils;
import com.luohuo.flex.im.common.constant.RedisKey;
import com.luohuo.flex.im.domain.vo.response.ChatMemberListResp;
import com.luohuo.flex.im.core.user.dao.BlackDao;
import com.luohuo.flex.im.common.service.cache.AbstractRedisStringCache;
import com.luohuo.flex.im.core.user.dao.UserDao;
import com.luohuo.flex.im.core.user.dao.UserRoleDao;
import com.luohuo.flex.im.domain.entity.Black;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.domain.entity.UserRole;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author 乾乾
* 用户基本信息的缓存
* @author nyh
*/
@Component
@RequiredArgsConstructor
public class UserCache {
public class UserCache extends AbstractRedisStringCache<Long, User> {
@Resource
private UserDao userDao;
private final UserDao userDao;
private final BlackDao blackDao;
private final UserRoleDao userRoleDao;
private final UserSummaryCache userSummaryCache;
private final CachePlusOps cachePlusOps;
/**
* 每小时一执行
*/
@Scheduled(cron = "0 0 * * * ?")
public void cleanExpiredBlacks() {
evictBlackMap();
}
public List<Long> getUserModifyTime(List<Long> uidList) {
List<String> keys = uidList.stream().map(uid -> RedisKey.getKey(RedisKey.USER_MODIFY_FORMAT, uid)).collect(Collectors.toList());
return cachePlusOps.mGet(keys, Long.class);
@Override
protected String getKey(Long uid) {
return RedisKey.getKey(RedisKey.USER_INFO_FORMAT, uid);
}
public void refreshUserModifyTime(Long uid) {
String key = RedisKey.getKey(RedisKey.USER_MODIFY_FORMAT, uid);
cachePlusOps.set(new CacheKey(key), TimeUtils.getTime());
@Override
protected Long getExpireSeconds() {
return 5 * 60L;
}
/**
* 获取用户信息,盘路缓存模式
*/
public User getUserInfo(Long uid) {//todo 后期做二级缓存
return getUserInfoBatch(Collections.singleton(uid)).get(uid);
@Override
protected Map<Long, User> load(List<Long> uidList) {
List<User> needLoadUserList = userDao.listByIds(uidList);
return needLoadUserList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
}
/**
* 获取用户信息,盘路缓存模式
*/
public Map<Long, User> getUserInfoBatch(Set<Long> uids) {
//批量组装key
List<String> keys = uids.stream().map(a -> RedisKey.getKey(RedisKey.USER_INFO_FORMAT, a)).collect(Collectors.toList());
//批量get
List<User> mget = cachePlusOps.mGet(keys, User.class);
Map<Long, User> map = mget.stream().filter(Objects::nonNull).collect(Collectors.toMap(User::getId, Function.identity()));
//发现差集——还需要load更新的uid
List<Long> needLoadUidList = uids.stream().filter(a -> !map.containsKey(a)).collect(Collectors.toList());
if (CollUtil.isNotEmpty(needLoadUidList)) {
//批量load
List<User> needLoadUserList = userDao.listByIds(needLoadUidList);
Map<String, User> redisMap = needLoadUserList.stream().collect(Collectors.toMap(a -> RedisKey.getKey(RedisKey.USER_INFO_FORMAT, a.getId()), Function.identity()));
cachePlusOps.mSet(redisMap, 5 * 60);
//加载回redis
map.putAll(needLoadUserList.stream().collect(Collectors.toMap(User::getId, Function.identity())));
}
return map;
}
/**
* 清空用户缓存,下次请求最新数据
* @param uid 用户id
**/
public void userInfoChange(Long uid) {
cachePlusOps.del(RedisKey.getKey(RedisKey.USER_INFO_FORMAT, uid));
//删除UserSummaryCache前端下次懒加载的时候可以获取到最新的数据
userSummaryCache.delete(uid);
refreshUserModifyTime(uid);
}
@Cacheable(cacheNames = "luohuo:user", key = "'blackList'")
public Map<Integer, Set<String>> getBlackMap() {
LocalDateTime now = LocalDateTime.now();
Map<Integer, List<Black>> collect = blackDao.getBaseMapper().selectList(new QueryWrapper<Black>().gt("deadline", now)).stream().collect(Collectors.groupingBy(Black::getType));
Map<Integer, Set<String>> result = new HashMap<>(collect.size());
for (Map.Entry<Integer, List<Black>> entry : collect.entrySet()) {
result.put(entry.getKey(), entry.getValue().stream().map(Black::getTarget).collect(Collectors.toSet()));
}
return result;
}
@CacheEvict(cacheNames = "luohuo:user", key = "'blackList'")
public Map<Integer, Set<String>> evictBlackMap() {
return null;
}
@Cacheable(cacheNames = "luohuo:user", key = "'roles'+#uid")
public Set<Long> getRoleSet(Long uid) {
List<UserRole> userRoles = userRoleDao.listByUid(uid);
return userRoles.stream()
.map(UserRole::getRoleId)
.collect(Collectors.toSet());
}
/**
* 根据key查询好友
* @param key
* @return
*/
@Cacheable(cacheNames = "luohuo:user", key = "'findFriend:'+#key")
public List<ChatMemberListResp> getFriend(String key) {
return userDao.getFriend(key);
}
/**
* 当 key 的数据改变后需要调用此方法
* @param key
* @return
*/
@CacheEvict(cacheNames = "luohuo:user", key = "'findFriend:'+#key")
public List<Long> evictFriend(String key) {
return null;
}
}

View File

@@ -1,39 +0,0 @@
package com.luohuo.flex.im.core.user.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.user.dao.UserDao;
import com.luohuo.flex.im.domain.entity.User;
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 UserInfoCache extends AbstractRedisStringCache<Long, User> {
@Resource
private UserDao userDao;
@Override
protected String getKey(Long uid) {
return RedisKey.getKey(RedisKey.USER_INFO_FORMAT, uid);
}
@Override
protected Long getExpireSeconds() {
return 5 * 60L;
}
@Override
protected Map<Long, User> load(List<Long> uidList) {
List<User> needLoadUserList = userDao.listByIds(uidList);
return needLoadUserList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
}
}

View File

@@ -1,18 +1,29 @@
package com.luohuo.flex.im.core.user.service.cache;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.BlackDao;
import com.luohuo.flex.im.core.user.dao.UserBackpackDao;
import com.luohuo.flex.im.core.user.dao.UserDao;
import com.luohuo.flex.im.core.user.dao.UserRoleDao;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import com.luohuo.flex.im.domain.entity.IpDetail;
import com.luohuo.flex.im.domain.entity.IpInfo;
import com.luohuo.flex.im.domain.entity.Black;
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.entity.UserRole;
import com.luohuo.flex.im.domain.enums.ItemTypeEnum;
import jakarta.annotation.Resource;
import com.luohuo.flex.im.domain.vo.response.ChatMemberListResp;
import com.luohuo.flex.model.entity.base.IpDetail;
import com.luohuo.flex.model.entity.base.IpInfo;
import lombok.AllArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -22,13 +33,22 @@ import java.util.stream.Collectors;
* @author nyh
*/
@Component
@AllArgsConstructor
public class UserSummaryCache extends AbstractRedisStringCache<Long, SummeryInfoDTO> {
@Resource
private UserInfoCache userInfoCache;
@Resource
private UserBackpackDao userBackpackDao;
@Resource
private ItemCache itemCache;
private final UserDao userDao;
private final UserCache userCache;
private final UserBackpackDao userBackpackDao;
private final ItemCache itemCache;
private final UserRoleDao userRoleDao;
private final BlackDao blackDao;
/**
* 每小时一执行
*/
@Scheduled(cron = "0 0 * * * ?")
public void cleanExpiredBlacks() {
evictBlackMap();
}
@Override
protected String getKey(Long uid) {
@@ -43,7 +63,7 @@ public class UserSummaryCache extends AbstractRedisStringCache<Long, SummeryInfo
@Override
protected Map<Long, SummeryInfoDTO> load(List<Long> uidList) {//后续可优化徽章信息也异步加载
//用户基本信息
Map<Long, User> userMap = userInfoCache.getBatch(uidList);
Map<Long, User> userMap = userCache.getBatch(uidList);
//用户徽章信息
List<ItemConfig> itemConfigs = itemCache.getByType(ItemTypeEnum.BADGE.getType());
List<Long> itemIds = itemConfigs.stream().map(ItemConfig::getId).collect(Collectors.toList());
@@ -66,7 +86,61 @@ public class UserSummaryCache extends AbstractRedisStringCache<Long, SummeryInfo
summeryInfoDTO.setWearingItemId(user.getItemId());
summeryInfoDTO.setItemIds(userBackpacks.stream().map(UserBackpack::getItemId).collect(Collectors.toList()));
summeryInfoDTO.setUserType(user.getUserType());
summeryInfoDTO.setEmail(user.getEmail());
summeryInfoDTO.setOpenId(user.getOpenId());
summeryInfoDTO.setSex(user.getSex());
summeryInfoDTO.setResume(user.getResume());
summeryInfoDTO.setLastOptTime(user.getLastOptTime());
return summeryInfoDTO;
}).filter(Objects::nonNull).collect(Collectors.toMap(SummeryInfoDTO::getUid, Function.identity()));
}
/**
* 根据key查询好友
* @param key
* @return
*/
@Cacheable(cacheNames = "luohuo:user", key = "'findFriend:'+#key")
public List<ChatMemberListResp> getFriend(String key) {
return userDao.getFriend(key);
}
/**
* 当 key 的数据改变后需要调用此方法
* @param key
* @return
*/
@CacheEvict(cacheNames = "luohuo:user", key = "'findFriend:'+#key")
public List<Long> evictFriend(String key) {
return null;
}
@Cacheable(cacheNames = "luohuo:user", key = "'roles:'+#uid")
public Set<Long> getRoleSet(Long uid) {
List<UserRole> userRoles = userRoleDao.listByUid(uid);
return userRoles.stream()
.map(UserRole::getRoleId)
.collect(Collectors.toSet());
}
@CacheEvict(cacheNames = "luohuo:user", key = "'roles'")
public Map<Integer, Set<String>> evictrolesSet() {
return null;
}
@Cacheable(cacheNames = "luohuo:user", key = "'blackList'")
public Map<Integer, Set<String>> getBlackMap() {
LocalDateTime now = LocalDateTime.now();
Map<Integer, List<Black>> collect = blackDao.getBaseMapper().selectList(new QueryWrapper<Black>().gt("deadline", now)).stream().collect(Collectors.groupingBy(Black::getType));
Map<Integer, Set<String>> result = new HashMap<>(collect.size());
for (Map.Entry<Integer, List<Black>> entry : collect.entrySet()) {
result.put(entry.getKey(), entry.getValue().stream().map(Black::getTarget).collect(Collectors.toSet()));
}
return result;
}
@CacheEvict(cacheNames = "luohuo:user", key = "'blackList'")
public Map<Integer, Set<String>> evictBlackMap() {
return null;
}
}

View File

@@ -11,6 +11,7 @@ import com.luohuo.basic.utils.SpringUtils;
import com.luohuo.basic.validator.utils.AssertUtil;
import com.luohuo.flex.common.cache.FriendCacheKeyBuilder;
import com.luohuo.flex.common.cache.PresenceCacheKeyBuilder;
import com.luohuo.flex.im.common.event.GroupInviteMemberEvent;
import com.luohuo.flex.im.common.event.GroupMemberAddEvent;
import com.luohuo.flex.im.common.event.UserApplyEvent;
import com.luohuo.flex.im.common.event.UserApprovalEvent;
@@ -30,13 +31,13 @@ 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.adapter.FriendAdapter;
import com.luohuo.flex.im.core.user.service.adapter.WsAdapter;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
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.Room;
import com.luohuo.flex.im.domain.entity.RoomFriend;
import com.luohuo.flex.im.domain.entity.RoomGroup;
import com.luohuo.flex.im.domain.entity.User;
import com.luohuo.flex.im.domain.entity.UserApply;
import com.luohuo.flex.im.domain.entity.UserFriend;
import com.luohuo.flex.im.domain.enums.ApplyDeletedEnum;
@@ -84,7 +85,7 @@ public class ApplyServiceImpl implements ApplyService {
private ChatService chatService;
private RoomCache roomCache;
private PushService pushService;
private UserCache userCache;
private UserSummaryCache userSummaryCache;
private GroupMemberCache groupMemberCache;
private RoomGroupCache roomGroupCache;
private GroupMemberDao groupMemberDao;
@@ -145,7 +146,7 @@ public class ApplyServiceImpl implements ApplyService {
// 获取到这个群的管理员的人的信息
List<Long> groupAdminIds = roomService.getGroupUsers(roomGroup.getId(), true);
User userInfo = userCache.getUserInfo(uid);
SummeryInfoDTO userInfo = userSummaryCache.get(uid);
String msg = StrUtil.format("用户{}申请加入群聊{}", userInfo.getName(), roomGroup.getName());
for (Long groupAdminId : groupAdminIds) {
@@ -226,7 +227,7 @@ public class ApplyServiceImpl implements ApplyService {
Room room = roomCache.get(invite.getRoomId());
GroupMember member = groupMemberDao.getMemberByGroupId(roomGroup.getId(), uid);
if(ObjectUtil.isNotNull(member)){
throw new BizException(StrUtil.format("{}已经在{}里", userCache.getUserInfo(invite.getTargetId()).getName(), roomGroup.getName()));
throw new BizException(StrUtil.format("{}已经在{}里", userSummaryCache.get(invite.getTargetId()).getName(), roomGroup.getName()));
}
transactionTemplate.execute(e -> {
@@ -250,6 +251,7 @@ public class ApplyServiceImpl implements ApplyService {
cachePlusOps.sAdd(gKey, invite.getTargetId());
roomAppService.asyncOnline(Arrays.asList(invite.getTargetId()), 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()));
}
}
@@ -313,6 +315,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 GroupMemberAddEvent(this, room.getId(), Math.toIntExact(cachePlusOps.sCard(gKey)), Math.toIntExact(cachePlusOps.sCard(onlineGroupMembersKey)), Collections.singletonList(apply.getUid()), apply.getUid()));
}

View File

@@ -10,6 +10,8 @@ import com.luohuo.flex.common.cache.FriendCacheKeyBuilder;
import com.luohuo.flex.common.cache.PresenceCacheKeyBuilder;
import com.luohuo.flex.common.constant.DefValConstants;
import com.luohuo.flex.im.api.PresenceApi;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import com.luohuo.flex.im.domain.enums.ApplyReadStatusEnum;
import com.luohuo.flex.im.domain.enums.ApplyStatusEnum;
import jakarta.annotation.PostConstruct;
@@ -42,7 +44,6 @@ import com.luohuo.flex.im.domain.vo.resp.friend.FriendCheckResp;
import com.luohuo.flex.im.domain.vo.resp.friend.FriendResp;
import com.luohuo.flex.im.core.user.service.FriendService;
import com.luohuo.flex.im.core.user.service.adapter.FriendAdapter;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import java.util.Arrays;
import java.util.List;
@@ -63,7 +64,7 @@ public class FriendServiceImpl implements FriendService, InitializingBean {
private RoomService roomService;
private ChatService chatService;
private UserDao userDao;
private UserCache userCache;
private UserSummaryCache userSummaryCache;
private BaseRedis baseRedis;
private CachePlusOps cachePlusOps;
private PresenceApi presenceApi;
@@ -151,7 +152,7 @@ public class FriendServiceImpl implements FriendService, InitializingBean {
@Override
public List<ChatMemberListResp> searchFriend(FriendReq friendReq) {
return userCache.getFriend(friendReq.getKey());
return userSummaryCache.getFriend(friendReq.getKey());
}
/**
@@ -179,7 +180,7 @@ public class FriendServiceImpl implements FriendService, InitializingBean {
// 发送一条同意消息。。我们已经是好友了,开始聊天吧
chatService.sendMsg(MessageAdapter.buildAgreeMsg(roomFriend.getRoomId(), true), uid);
// 系统账号在群内发送一条欢迎消息
User user = userCache.getUserInfo(uid);
SummeryInfoDTO user = userSummaryCache.get(uid);
Long total = cachePlusOps.inc("luohuo:user:total_count", 0, TimeUnit.DAYS); // 查询系统总注册人员
chatService.sendMsg(MessageAdapter.buildAgreeMsg4Group(DefValConstants.DEF_ROOM_ID, total, user.getName()), DefValConstants.DEF_BOT_ID);
}

View File

@@ -1,12 +1,11 @@
package com.luohuo.flex.im.core.user.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ConcurrentHashSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.luohuo.basic.cache.repository.CachePlusOps;
import com.luohuo.basic.model.cache.CacheKey;
import com.luohuo.basic.service.MQProducer;
import com.luohuo.flex.common.cache.FriendCacheKeyBuilder;
import com.luohuo.flex.common.cache.PresenceCacheKeyBuilder;
import com.luohuo.flex.common.constant.MqConstant;
import com.luohuo.flex.model.entity.dto.NodePushDTO;
@@ -140,30 +139,6 @@ public class PushService {
log.info("推送线程池已关闭");
}
/**
* 将消息推送给好友
* @param uid 推送给这个uid相关的所有人员
* @param type 前端监听的类型
* @param data 推送的数据
*/
public void pushFriends(Long uid, String type, Object data) {
// 1. 获取反向好友关系
CacheKey reverseKey = FriendCacheKeyBuilder.reverseFriendsKey(uid);
Set<Long> reverseFriends = cachePlusOps.sMembers(reverseKey).parallelStream()
.map(obj -> Long.parseLong(obj.toString()))
.collect(Collectors.toSet());
// 2. 创建通用响应对象
WsBaseResp commonResp = new WsBaseResp();
commonResp.setType(type);
commonResp.setData(data);
// 3. 推送给反向好友
if (CollUtil.isNotEmpty(reverseFriends)) {
sendPushMsg(commonResp, new ArrayList<>(reverseFriends), uid);
}
}
/**
* 将消息推送给跟我相关的所有房间
* @param uid 推送给这个uid相关的所有人员
@@ -173,32 +148,40 @@ public class PushService {
public void pushRoom(Long uid, String type, Object data) {
// 1. 获取用户所在群聊ID列表
CacheKey ugKey = PresenceCacheKeyBuilder.userGroupsKey(uid);
Set<Long> roomIds = cachePlusOps.sMembers(ugKey).parallelStream()
.map(obj -> Long.parseLong(obj.toString()))
.collect(Collectors.toSet());
Set<Long> roomIds = cachePlusOps.sMembers(ugKey).parallelStream().map(obj -> Long.parseLong(obj.toString())).collect(Collectors.toSet());
if (roomIds.isEmpty()) {
return;
}
// 2. 创建通用响应对象
WsBaseResp commonResp = new WsBaseResp();
commonResp.setType(type);
commonResp.setData(data);
// 3. 获取群聊在线成员
List<CacheKey> groupKeys = roomIds.stream().map(PresenceCacheKeyBuilder::onlineGroupMembersKey).collect(Collectors.toList());
// 3. 获取所有群聊在线成员并去重
Set<Long> allMemberIds = new ConcurrentHashSet<>();
// 5. 并行处理群聊推送
int pageSize = 200;
groupKeys.parallelStream().forEach(groupKey -> {
// 5.1 检查群是否在线
// 并行处理每个群聊的在线成员
roomIds.parallelStream().forEach(roomId -> {
CacheKey groupKey = PresenceCacheKeyBuilder.onlineGroupMembersKey(roomId);
// 检查群是否在线
if (cachePlusOps.sCard(groupKey) < 1L) return;
// 5.2 分批获取群成员
List<Long> memberIdList = cachePlusOps.sMembers(groupKey).stream()
// 获取群成员并添加到总集合中
cachePlusOps.sMembers(groupKey).stream()
.map(obj -> Long.parseLong(obj.toString()))
.collect(Collectors.toList());
// 5.3 分批推送
Lists.partition(memberIdList, pageSize).forEach(batch -> sendPushMsg(commonResp, batch, uid));
.forEach(allMemberIds::add);
});
// 4. 移除自己,避免给自己发送消息
// allMemberIds.remove(uid);
// 5. 分批推送
int pageSize = 200;
List<Long> memberIdList = new ArrayList<>(allMemberIds);
Lists.partition(memberIdList, pageSize).forEach(batch -> sendPushMsg(commonResp, batch, uid));
}
}

View File

@@ -1,11 +1,10 @@
package com.luohuo.flex.im.core.user.service.impl;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import com.luohuo.flex.im.domain.enums.RoleTypeEnum;
import com.luohuo.flex.im.core.user.service.RoleService;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import java.util.Set;
/**
@@ -15,11 +14,11 @@ import java.util.Set;
public class RoleServiceImpl implements RoleService {
@Resource
private UserCache userCache;
private UserSummaryCache userSummaryCache;
@Override
public boolean hasRole(Long uid, RoleTypeEnum roleTypeEnum) {
Set<Long> roleSet = userCache.getRoleSet(uid);
Set<Long> roleSet = userSummaryCache.getRoleSet(uid);
return isAdmin(roleSet) || roleSet.contains(roleTypeEnum.getId());
}

View File

@@ -11,10 +11,10 @@ import com.luohuo.flex.common.cache.PresenceCacheKeyBuilder;
import com.luohuo.flex.common.constant.DefValConstants;
import com.luohuo.flex.im.api.vo.UserRegisterVo;
import com.luohuo.flex.im.common.event.UserRegisterEvent;
import com.luohuo.flex.im.core.chat.service.ContactService;
import com.luohuo.flex.im.core.chat.service.RoomAppService;
import com.luohuo.flex.im.core.chat.service.RoomService;
import com.luohuo.flex.im.core.user.service.cache.DefUserCache;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.model.entity.base.IpInfo;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -44,21 +44,17 @@ import com.luohuo.flex.im.domain.vo.req.user.BlackReq;
import com.luohuo.flex.im.domain.vo.req.user.ItemInfoReq;
import com.luohuo.flex.im.domain.vo.req.user.ModifyAvatarReq;
import com.luohuo.flex.im.domain.vo.req.user.ModifyNameReq;
import com.luohuo.flex.im.domain.vo.req.user.SummeryInfoReq;
import com.luohuo.flex.im.domain.vo.req.user.WearingBadgeReq;
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.UserCache;
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.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -71,19 +67,30 @@ import java.util.stream.Collectors;
public class UserServiceImpl implements UserService {
public static final LocalDateTime MAX_DATE = LocalDateTime.of(2099, 12, 31, 00, 00, 00);
private final ContactService contactService;
private final RoomService roomService;
private final RoomAppService roomAppService;
private UserCache userCache;
private DefUserCache defUserCache;
private UserBackpackDao userBackpackDao;
private UserDao userDao;
private ItemConfigDao itemConfigDao;
private ItemCache itemCache;
private BlackDao blackDao;
private final DefUserCache defUserCache;
private final UserBackpackDao userBackpackDao;
private final UserDao userDao;
private final ItemConfigDao itemConfigDao;
private final ItemCache itemCache;
private final BlackDao blackDao;
private final CachePlusOps cachePlusOps;
private UserSummaryCache userSummaryCache;
private SensitiveWordBs sensitiveWordBs;
private final UserCache userCache;
private final UserSummaryCache userSummaryCache;
private final SensitiveWordBs sensitiveWordBs;
@Override
public Boolean refreshIpInfo(Long uid, IpInfo ipInfo) {
User user = new User();
user.setId(uid);
user.setIpInfo(ipInfo);
boolean updated = userDao.updateById(user);
// 清空缓存
userCache.delete(uid);
userSummaryCache.delete(uid);
return updated;
}
@Override
public Boolean checkEmail(String email) {
@@ -108,7 +115,7 @@ public class UserServiceImpl implements UserService {
@Override
public UserInfoResp getUserInfo(Long uid) {
User userInfo = userCache.getUserInfo(uid);
SummeryInfoDTO userInfo = userSummaryCache.get(uid);
Integer countByValidItemId = userBackpackDao.getCountByValidItemId(uid, ItemEnum.MODIFY_NAME_CARD.getId());
return UserAdapter.buildUserInfoResp(userInfo, countByValidItemId);
}
@@ -119,23 +126,27 @@ public class UserServiceImpl implements UserService {
// 判断名字是不是重复
String newName = req.getName();
AssertUtil.isFalse(sensitiveWordBs.hasSensitiveWord(newName), "名字中包含敏感词,请重新输入"); // 判断名字中有没有敏感词
// 名称可以重复
// User oldUser = userDao.getByName(newName);
// AssertUtil.isEmpty(oldUser, "名字已经被抢占了,请换一个哦~~");
// 判断改名卡够不够
UserBackpack firstValidItem = userBackpackDao.getFirstValidItem(uid, ItemEnum.MODIFY_NAME_CARD.getId());
AssertUtil.isNotEmpty(firstValidItem, "改名次数不够了,等后续活动送改名卡哦");
// 使用改名卡
boolean useSuccess = userBackpackDao.invalidItem(firstValidItem.getId());
// 用乐观锁,就不用分布式锁了
if (useSuccess) {
// 改名
userDao.modifyName(uid, req);
// 删除缓存
userSummaryCache.delete(uid);
userCache.userInfoChange(uid);
userCache.evictFriend(userCache.getUserInfo(uid).getAccount());
}
User user = userDao.getById(uid);
AssertUtil.isTrue(req.getAvatar().equals(user.getAvatar()) ||
(user.getAvatarUpdateTime() != null && user.getAvatarUpdateTime().plusDays(30).isBefore(LocalDateTime.now())),
"30天内只能修改一次头像");
// 更新
User update = new User();
update.setId(uid);
update.setSex(req.getSex());
update.setName(req.getName());
update.setResume(req.getResume());
if(StrUtil.isNotEmpty(req.getAvatar()) && !req.getAvatar().equals(user.getAvatar())){
update.setAvatar(req.getAvatar());
update.setAvatarUpdateTime(LocalDateTime.now());
}
userDao.updateById(update);
// 删除缓存
userSummaryCache.delete(uid);
userSummaryCache.evictFriend(userSummaryCache.get(uid).getAccount());
}
@Transactional(rollbackFor = Exception.class)
@@ -154,9 +165,8 @@ public class UserServiceImpl implements UserService {
updateUser.setId(user.getId());
userDao.updateById(updateUser);
// 删除缓存
userCache.userInfoChange(uid);
userSummaryCache.delete(uid);
userCache.evictFriend(user.getAccount());
userSummaryCache.evictFriend(user.getAccount());
}
@Override
@@ -180,8 +190,6 @@ public class UserServiceImpl implements UserService {
AssertUtil.equal(itemConfig.getType(), ItemTypeEnum.BADGE.getType(), "该徽章不可佩戴");
// 佩戴徽章
userDao.wearingBadge(uid, req.getBadgeId());
// 删除用户缓存
userCache.userInfoChange(uid);
userSummaryCache.delete(uid);
}
@@ -203,19 +211,6 @@ public class UserServiceImpl implements UserService {
SpringUtils.publishEvent(new UserBlackEvent(this, byId));
}
@Override
public List<SummeryInfoDTO> getSummeryUserInfo(SummeryInfoReq req) {
//需要前端同步的uid
List<Long> uidList = getNeedSyncUidList(req.getReqList());
//加载用户信息
Map<Long, SummeryInfoDTO> batch = userSummaryCache.getBatch(uidList);
return req.getReqList()
.stream()
.map(a -> batch.containsKey(a.getUid()) ? batch.get(a.getUid()) : SummeryInfoDTO.skip(a.getUid()))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Override
public List<ItemInfoDTO> getItemInfo(ItemInfoReq req) {//简单做,更新时间可判断被修改
return req.getReqList().stream().map(a -> {
@@ -261,30 +256,18 @@ public class UserServiceImpl implements UserService {
}
// 3. 修改邮箱
User userInfo = userCache.getUserInfo(uid);
userInfo.setEmail(req.getEmail());
boolean save = userDao.save(userInfo);
SummeryInfoDTO userInfo = userSummaryCache.get(uid);
User user = new User();
user.setId(userInfo.getUid());
user.setEmail(req.getEmail());
boolean save = userDao.updateById(user);
if(save){
cachePlusOps.hDel("emailCode", req.getUuid());
userCache.userInfoChange(uid);
userSummaryCache.delete(uid);
}
return save;
}
private List<Long> getNeedSyncUidList(List<SummeryInfoReq.infoReq> reqList) {
List<Long> needSyncUidList = new ArrayList<>();
List<Long> userModifyTime = userCache.getUserModifyTime(reqList.stream().map(SummeryInfoReq.infoReq::getUid).collect(Collectors.toList()));
for (int i = 0; i < reqList.size(); i++) {
SummeryInfoReq.infoReq infoReq = reqList.get(i);
Long modifyTime = userModifyTime.get(i);
if (Objects.isNull(infoReq.getLastModifyTime()) || (Objects.nonNull(modifyTime) && modifyTime > infoReq.getLastModifyTime())) {
needSyncUidList.add(infoReq.getUid());
}
}
return needSyncUidList;
}
public void blackIp(String ip) {
if (StrUtil.isBlank(ip)) {
return;

View File

@@ -8,7 +8,6 @@ import com.luohuo.flex.im.domain.vo.resp.user.UserInfoResp;
import com.luohuo.flex.im.domain.vo.resp.user.UserStateVo;
import com.luohuo.flex.im.core.user.service.UserService;
import com.luohuo.flex.im.core.user.service.UserStateService;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -26,7 +25,6 @@ public class UserStateServiceImpl implements UserStateService {
private UserStateDao userStateDao;
private UserSummaryCache userSummaryCache;
private UserCache userCache;
private UserService userService;
private PushService pushService;
@@ -48,11 +46,10 @@ public class UserStateServiceImpl implements UserStateService {
if (ObjectUtil.isNotNull(userState) && changeUserState){
// 1.清除缓存
userCache.userInfoChange(uid);
userSummaryCache.delete(uid);
// 2.推送数据
pushService.pushFriends(uid, WSRespTypeEnum.USER_STATE_CHANGE.getType(), new UserStateVo(uid, userState.getId()));
pushService.pushRoom(uid, WSRespTypeEnum.USER_STATE_CHANGE.getType(), new UserStateVo(uid, userState.getId()));
return true;
}else {
throw new RuntimeException("用户状态更新失败");

View File

@@ -1,6 +1,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 io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
@@ -29,7 +30,6 @@ import com.luohuo.flex.model.entity.ws.ChatMessageResp;
import com.luohuo.flex.im.core.chat.service.ChatService;
import com.luohuo.flex.im.core.frequencyControl.annotation.FrequencyControl;
import com.luohuo.flex.im.domain.enums.BlackTypeEnum;
import com.luohuo.flex.im.core.user.service.cache.UserCache;
import java.util.Collection;
import java.util.HashSet;
@@ -48,10 +48,10 @@ public class ChatController {
@Resource
private ChatService chatService;
@Resource
private UserCache userCache;
private UserSummaryCache userSummaryCache;
private Set<String> getBlackUidSet() {
return userCache.getBlackMap().getOrDefault(BlackTypeEnum.UID.getType(), new HashSet<>());
return userSummaryCache.getBlackMap().getOrDefault(BlackTypeEnum.UID.getType(), new HashSet<>());
}
@GetMapping("/msg/page")

View File

@@ -71,12 +71,6 @@ public class RoomController {
return R.success(roomService.groupList(uid));
}
@GetMapping("/group/member/page")
@Operation(summary ="群成员列表")
public R<CursorPageBaseResp<ChatMemberResp>> getMemberPage(@Valid MemberReq request) {
return R.success(roomService.getMemberPage(request));
}
@GetMapping("/group/listMember")
@Operation(summary ="群成员列表")
public R<List<ChatMemberResp>> listMember(@Valid MemberReq request) {

View File

@@ -2,6 +2,7 @@ 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.model.entity.base.RefreshIpInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
@@ -11,14 +12,12 @@ import com.luohuo.basic.base.R;
import com.luohuo.basic.context.ContextUtil;
import com.luohuo.basic.validator.utils.AssertUtil;
import com.luohuo.flex.im.domain.dto.ItemInfoDTO;
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
import com.luohuo.flex.im.domain.enums.RoleTypeEnum;
import com.luohuo.flex.model.vo.query.BindEmailReq;
import com.luohuo.flex.im.domain.vo.req.user.BlackReq;
import com.luohuo.flex.im.domain.vo.req.user.ItemInfoReq;
import com.luohuo.flex.im.domain.vo.req.user.ModifyAvatarReq;
import com.luohuo.flex.im.domain.vo.req.user.ModifyNameReq;
import com.luohuo.flex.im.domain.vo.req.user.SummeryInfoReq;
import com.luohuo.flex.im.domain.vo.req.user.WearingBadgeReq;
import com.luohuo.flex.im.domain.vo.resp.user.BadgeResp;
import com.luohuo.flex.im.domain.vo.resp.user.UserInfoResp;
@@ -41,6 +40,12 @@ public class UserController {
@Resource
private RoleService roleService;
@PostMapping("refreshIpInfo")
@Operation(summary ="刷新IP信息、物理地址")
public R<Boolean> refreshIpInfo(@RequestBody RefreshIpInfo refreshIpInfo) {
return R.success(userService.refreshIpInfo(refreshIpInfo.getUid(), refreshIpInfo.getIpInfo()));
}
@GetMapping("/checkEmail")
@Operation(summary ="绑定邮箱")
@TenantIgnore
@@ -74,6 +79,7 @@ public class UserController {
@PostMapping("/avatar")
@Operation(summary ="修改头像")
@Deprecated
// @FrequencyControl(target = FrequencyControl.Target.UID, time = 30, unit = TimeUnit.DAYS)
public R<Void> modifyAvatar(@Valid @RequestBody ModifyAvatarReq req) {
userService.modifyAvatar(ContextUtil.getUid(), req);
@@ -86,20 +92,14 @@ public class UserController {
return R.success(userService.bindEmail(ContextUtil.getUid(), req));
}
@PostMapping("/summary/userInfo/batch")
@Operation(summary ="用户聚合信息-返回的代表需要刷新的")
public R<List<SummeryInfoDTO>> getSummeryUserInfo(@Valid @RequestBody SummeryInfoReq req) {
return R.success(userService.getSummeryUserInfo(req));
}
@PostMapping("/badges/batch")
@Operation(summary ="徽章聚合信息-返回的代表需要刷新的")
public R<List<ItemInfoDTO>> getItemInfo(@Valid @RequestBody ItemInfoReq req) {
return R.success(userService.getItemInfo(req));
}
@PutMapping("/name")
@Operation(summary ="修改用户")
@PutMapping("/info")
@Operation(summary ="修改用户信息")
public R<Void> modifyInfo(@Valid @RequestBody ModifyNameReq req) {
userService.modifyInfo(ContextUtil.getUid(), req);
return R.success();

View File

@@ -1,15 +1,15 @@
package com.luohuo.flex.im.domain.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
/**
* 修改用户名
*
@@ -29,20 +29,32 @@ public class SummeryInfoDTO {
private Boolean needRefresh = Boolean.TRUE;
@Schema(description = "用户昵称")
private String name;
@Schema(description = "性别")
private Integer sex;
@Schema(description = "Hula号")
private String account;
@Schema(description = "用户头像")
private String avatar;
@Schema(description = "归属地")
private String locPlace;
@JsonIgnore
@Schema(description = "微信openId")
private String openId;
@JsonIgnore
@Schema(description = "邮箱")
private String email;
@Schema(description = "个人简介")
private String resume;
@Schema(description = "用户状态")
private Long userStateId;
@Schema(description = "佩戴的徽章id")
private Long wearingItemId;
@Schema(description = "用户类型")
private Integer userType;
@Schema(description = "最后一次上下线时间")
private LocalDateTime lastOptTime;
public static SummeryInfoDTO skip(Long uid) {
public static SummeryInfoDTO skip(Long uid) {
SummeryInfoDTO dto = new SummeryInfoDTO();
dto.setUid(uid);
dto.setNeedRefresh(Boolean.FALSE);

View File

@@ -1,34 +0,0 @@
package com.luohuo.flex.im.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 用户ip信息
* @author nyh
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IpDetail implements Serializable {
private static final long serialVersionUID = 1L;
private String area;
//注册时的ip
private String ip;
//最新登录的ip
private String isp;
private String isp_id;
private String city;
private String city_id;
private String country;
private String country_id;
private String region;
private String region_id;
}

View File

@@ -1,57 +0,0 @@
package com.luohuo.flex.im.domain.entity;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.Objects;
import java.util.Optional;
/**
* 用户ip信息
* @author nyh
*/
@Data
public class IpInfo implements Serializable {
private static final long serialVersionUID = 1L;
//注册时的ip
private String createIp;
//注册时的ip详情
private IpDetail createIpDetail;
//最新登录的ip
private String updateIp;
//最新登录的ip详情
private IpDetail updateIpDetail;
public void refreshIp(String ip) {
if (StringUtils.isEmpty(ip)) {
return;
}
updateIp = ip;
if (createIp == null) {
createIp = ip;
}
}
/**
* 需要刷新的ip这里判断更新ip就够初始化的时候ip也是相同的只需要设置的时候多设置进去就行
*/
public String needRefreshIp() {
boolean notNeedRefresh = Optional.ofNullable(updateIpDetail)
.map(IpDetail::getIp)
.filter(ip -> Objects.equals(ip, updateIp))
.isPresent();
return notNeedRefresh ? null : updateIp;
}
public void refreshIpDetail(IpDetail ipDetail) {
if (Objects.equals(createIp, ipDetail.getIp())) {
createIpDetail = ipDetail;
}
if (Objects.equals(updateIp, ipDetail.getIp())) {
updateIpDetail = ipDetail;
}
}
}

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.luohuo.basic.base.entity.Entity;
import com.luohuo.flex.im.enums.UserTypeEnum;
import com.luohuo.flex.model.entity.base.IpInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
@@ -126,12 +127,4 @@ public class User extends Entity<Long> {
*/
@TableField(value = "ip_info", typeHandler = JacksonTypeHandler.class)
private IpInfo ipInfo;
public void refreshIp(String ip) {
if (ipInfo == null) {
ipInfo = new IpInfo();
}
ipInfo.refreshIp(ip);
}
}

View File

@@ -14,9 +14,9 @@ import java.io.Serializable;
@NoArgsConstructor
public class ReplyMsg implements Serializable {
@Schema(description = "消息id")
private Long id;
private String id;
@Schema(description = "用户uid")
private Long uid;
private String uid;
@Schema(description = "用户名称")
private String username;
@Schema(description = "消息类型 1正常文本 2.撤回消息")

View File

@@ -1,17 +1,16 @@
package com.luohuo.flex.im.domain.vo.req.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 修改用户名
* @author nyh
@@ -22,6 +21,16 @@ import java.io.Serializable;
@NoArgsConstructor
public class ModifyNameReq implements Serializable {
@Schema(description = "性别")
private Integer sex;
@Schema(description = "手机号")
private String phone;
@NotEmpty
@Schema(description = "头像url")
private String avatar;
@NotNull
@Length(max = 8, message = "用户名可别取太长,不然我记不住噢")
@Schema(description = "用户名")

View File

@@ -5,6 +5,7 @@ import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
* @author nyh
@@ -15,6 +16,12 @@ public class UserInfoResp implements Serializable {
@Schema(description = "用户id")
private Long uid;
@Schema(description = "佩戴的徽章id")
private Long wearingItemId;
@Schema(description = "用户拥有的徽章id列表")
private List<Long> itemIds;
@Schema(description = "Hula号")
private String account;
@@ -30,6 +37,9 @@ public class UserInfoResp implements Serializable {
@Schema(description = "性别 1男 2女")
private Integer sex;
@Schema(description = "个人简介")
private String resume;
@Schema(description = "用户状态id")
private Long userStateId;

View File

@@ -4,6 +4,8 @@ import com.luohuo.basic.base.R;
import com.luohuo.flex.im.api.hystrix.ImUserApiFallback;
import com.luohuo.flex.im.api.vo.UserRegisterVo;
import com.luohuo.flex.im.domain.vo.resp.user.UserInfoResp;
import com.luohuo.flex.model.entity.base.IpInfo;
import com.luohuo.flex.model.entity.base.RefreshIpInfo;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import org.springframework.cloud.openfeign.FeignClient;
@@ -47,4 +49,11 @@ public interface ImUserApi {
*/
@PostMapping("/user/register")
R<Boolean> register(@Valid @RequestBody UserRegisterVo userRegisterVo);
/**
* 刷新IP信息、物理地址
* @param refreshIpInfo ip信息
*/
@PostMapping("/user/refreshIpInfo")
R<Boolean> refreshIpInfo(@RequestBody RefreshIpInfo refreshIpInfo);
}

View File

@@ -3,6 +3,7 @@ package com.luohuo.flex.im.api.hystrix;
import com.luohuo.basic.exception.BizException;
import com.luohuo.flex.im.api.vo.UserRegisterVo;
import com.luohuo.flex.im.domain.vo.resp.user.UserInfoResp;
import com.luohuo.flex.model.entity.base.RefreshIpInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import com.luohuo.basic.base.R;
@@ -38,4 +39,9 @@ public class ImUserApiFallback implements ImUserApi {
public R<Boolean> register(UserRegisterVo userRegisterVo) {
throw BizException.wrap("注册失败");
}
@Override
public R<Boolean> refreshIpInfo(RefreshIpInfo refreshIpInfo) {
return R.success(true);
}
}

View File

@@ -40,7 +40,7 @@ public class UserOnlineListener {
defUserService.updateById(defUser);
// 更新用户ip详情
ipService.refreshIpDetailAsync(userId, ipInfo);
ipService.refreshIpDetailAsync(event.getUid(), userId, ipInfo);
}
}

View File

@@ -8,9 +8,9 @@ import com.luohuo.flex.model.entity.base.IpInfo;
public interface IpService {
/**
* 异步更新用户ip详情
*
* @param uid 用户id
* @param uid Im用户id
* @param userId 系统用户id
* @param ipInfo IP信息
*/
void refreshIpDetailAsync(Long uid, IpInfo ipInfo);
void refreshIpDetailAsync(Long uid, Long userId, IpInfo ipInfo);
}

View File

@@ -9,10 +9,12 @@ import com.luohuo.basic.utils.JsonUtils;
import com.luohuo.basic.utils.TimeUtils;
import com.luohuo.flex.base.entity.tenant.DefUser;
import com.luohuo.flex.base.service.tenant.DefUserService;
import com.luohuo.flex.im.api.ImUserApi;
import com.luohuo.flex.model.entity.base.IpDetail;
import com.luohuo.flex.model.entity.base.IpInfo;
import com.luohuo.flex.model.entity.base.RefreshIpInfo;
import com.luohuo.flex.oauth.service.IpService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Service;
@@ -31,18 +33,18 @@ import java.util.concurrent.TimeUnit;
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class IpServiceImpl implements IpService, DisposableBean {
private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(500),
new NamedThreadFactory("refresh-ipDetail",false));
@Resource
private DefUserService defUserService;
private final ImUserApi userApi;
private final DefUserService defUserService;
@Override
public void refreshIpDetailAsync(Long uid, IpInfo ipInfo) {
public void refreshIpDetailAsync(Long uid, Long userId, IpInfo ipInfo) {
EXECUTOR.execute(() -> {
if (Objects.isNull(ipInfo)) {
return;
@@ -55,11 +57,13 @@ public class IpServiceImpl implements IpService, DisposableBean {
if (Objects.nonNull(ipDetail)) {
ipInfo.refreshIpDetail(ipDetail);
DefUser update = new DefUser();
update.setId(uid);
update.setId(userId);
update.setIpInfo(ipInfo);
defUserService.updateById(update);
userApi.refreshIpInfo(new RefreshIpInfo(uid, ipInfo));
} else {
log.error("get ip detail fail ip:{},uid:{}", ip, uid);
log.error("get ip detail fail ip:{},userId:{}", ip, userId);
}
});
}

View File

@@ -0,0 +1,26 @@
package com.luohuo.flex.model.entity.base;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 用户ip信息
* @author 乾乾
*/
@Data
@NoArgsConstructor
public class RefreshIpInfo implements Serializable {
private static final long serialVersionUID = 1L;
//更新人id
private Long uid;
//ip信息
private IpInfo ipInfo;
public RefreshIpInfo(Long uid, IpInfo ipInfo) {
this.uid = uid;
this.ipInfo = ipInfo;
}
}

View File

@@ -8,6 +8,7 @@ import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
* 成员列表的成员信息
@@ -53,4 +54,16 @@ public class ChatMemberResp implements Serializable {
@Schema(description = "用户状态id")
private Long userStateId;
@Schema(description = "用户拥有的徽章id列表")
private List<Long> itemIds;
@Schema(description = "佩戴的徽章id")
private Long wearingItemId;
/**
* 用户类型 UserTypeEnum
* 参见 {@link com.luohuo.flex.im.enums.UserTypeEnum}
*/
@Schema(description = "用户类型")
private Integer userType;
}

View File

@@ -16,11 +16,11 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
public class WSOnlineNotify {
@Schema(description = "新的上下线用户")
private Long uid;
private String uid;
@Schema(description = "指纹")
private String clientId;
@Schema(description = "当前房间的在线人数信息")
private Long roomId;
private String roomId;
@Schema(description = "最后一次上下线时间")
private Long lastOptTime;
@Schema(description = "在线人数")
@@ -30,7 +30,7 @@ public class WSOnlineNotify {
private Integer type;
public WSOnlineNotify(Long uid, String clientId, Long lastOptTime, Long onlineNum, Integer type) {
this.uid = uid;
this.uid = uid+"";
this.clientId = clientId;
this.lastOptTime = lastOptTime;
this.onlineNum = onlineNum;
@@ -38,8 +38,8 @@ public class WSOnlineNotify {
}
public WSOnlineNotify(Long roomId, Long uid, String clientId, Long lastOptTime, Long onlineNum, Integer type) {
this.roomId = roomId;
this.uid = uid;
this.roomId = roomId+"";
this.uid = uid+"";
this.clientId = clientId;
this.lastOptTime = lastOptTime;
this.onlineNum = onlineNum;

View File

@@ -257,7 +257,7 @@ public class SessionManager {
if (noOtherDevices) {
cachePlusOps.zAdd(onlineUsersKey, uid, millis);
updateGroupPresence(roomIds, uid, true);
pushDeviceStatusChange(roomIds, uid, clientId, true, onlineUsersKey);
pushDeviceStatusChange(roomIds, uid, clientId, WSRespTypeEnum.ONLINE.getType(), onlineUsersKey);
}
} else {
// 4. 下线逻辑
@@ -267,7 +267,7 @@ public class SessionManager {
if (noOtherDevices) {
cachePlusOps.zRemove(onlineUsersKey, uid);
updateGroupPresence(roomIds, uid, false);
pushDeviceStatusChange(roomIds, uid, clientId, false, onlineUsersKey);
pushDeviceStatusChange(roomIds, uid, clientId, WSRespTypeEnum.OFFLINE.getType(), onlineUsersKey);
}
}
}
@@ -276,12 +276,10 @@ public class SessionManager {
* 通知所有与自己有关的所有人
* @param uid 登录用户
* @param clientId 登录设备
* @param online 登录状态
* @param type 登录状态
* @param onlineKey 全局在线用户的key
*/
private void pushDeviceStatusChange(List<Long> roomIds, Long uid, String clientId, boolean online, String onlineKey) {
String type = online ? WSRespTypeEnum.ONLINE.getType() : WSRespTypeEnum.OFFLINE.getType();
private void pushDeviceStatusChange(List<Long> roomIds, Long uid, String clientId, String type, String onlineKey) {
// 1. 获取反向好友列表需要知道该用户在线状态的uid [推送数据各个不一致]
CacheKey reverseFriendsKey = FriendCacheKeyBuilder.reverseFriendsKey(uid);
Set<Long> friends = cachePlusOps.sMembers(reverseFriendsKey).stream().map(obj -> Long.parseLong(obj.toString())).collect(Collectors.toSet());

View File

@@ -165,10 +165,10 @@ public final class ContextUtil {
/**
* 设置子系统id
*
* @param employeeId 员工id
* @param uid 员工id
*/
public static void setUid(Object employeeId) {
set(ContextConstants.U_ID_HEADER, employeeId);
public static void setUid(Object uid) {
set(ContextConstants.U_ID_HEADER, uid);
}
/**