93 changed files with 4292 additions and 579 deletions
@ -0,0 +1,21 @@
|
||||
MIT License |
||||
|
||||
Copyright (c) 2018 tangmingyou |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
||||
@ -1,121 +1,86 @@
|
||||
## 基本介绍 |
||||
|
||||
`so-im` 是一款简单的可水平扩展的 `IM(即时通讯)`系统。 |
||||
|
||||
### 开发计划 |
||||
|
||||
- (05-08~05-11)router 一致性 hash,新增、删除、(备份处理)[滞后] |
||||
- 数据同步删除后,保留id一段时间,有请求进行重定向/转发 |
||||
- 新增:重算hash,发起数据同步,接收其他节点推送数据及数据更改日志,注册服务,其他节点删除数据 |
||||
- 正常删除节点:重算 hash,数据和更改日志推送给其他节点,取消注册,关闭服务 |
||||
- 宕机:备份服务接收全量同步数据和更改日志,服务探测到主服务不可用,注册到注册中心提供服务 |
||||
- (05-12~05-12) 备用计划:router 层 id 号段负载均衡 |
||||
- (05-13~05-14) dubbo 服务异步处理 |
||||
- 功能开发: |
||||
- entry-http entry 节点获取功能(05-13~05-13) |
||||
- (05-13~05-13) 用户注册功能 |
||||
- (05-14~05-14) 好友列表(在线状态:批量uid一致性hash, router查询) |
||||
- 用户查询 |
||||
- 添加好友 |
||||
- 好友在线状态列表 |
||||
- (05-15~05-15) 消息群发(im-router群消息路由,批量uid一致性哈希路由) |
||||
- 创群: |
||||
- 加群:发送申请,推送申请,推送回复 |
||||
- 删群 |
||||
- 群列表 |
||||
- (05-16~05-16) 聊天记录查询 |
||||
- (05-17~05-18) das 部分接口消息队列异步写 |
||||
- (05-19~05-20) 集群部署:docker swarm |
||||
- 监控:log4j2完善 + prometheus + grafana + loki + arthas |
||||
- (05-21~05-21) prometheus 服务发现,exporter(服务发现) 开发 |
||||
- (05-22~05-22) 容器监控,主机监控,数据库监控,日志收集 |
||||
- (05-23~05-24) 应用监控 |
||||
- (Dubbo线程池监控)[https://cloud.tencent.com/developer/article/1800906] |
||||
- 通讯指标监控,请求量,吞吐量,延时,请求节点分布,链路追踪 |
||||
- 数据库连接池监控 |
||||
- (05-25~05-25) 压测:压测开发 |
||||
- (05-26~05-26) client: 控制台完善,grallvm 打包 |
||||
- 后续: |
||||
- 通讯加密,服务链路SSL |
||||
- websocket 网关 |
||||
- web 页面开发 |
||||
- 异/同设备,多地登录 |
||||
|
||||
|
||||
### 资料 |
||||
一致性hash算法能否解决数据迁移的问题? |
||||
https://www.zhihu.com/question/521159623 |
||||
|
||||
dubbo + protobuf 兼容到 2.7.15 |
||||
|
||||
### TODO |
||||
|
||||
- router -> entry 负载均衡 |
||||
- router 新增节点顺时针相邻节点数据一致性哈希迁移 |
||||
- router 冗余节点存储数据不提供服务 |
||||
- dubbo 服务异步处理提升吞吐量 |
||||
- das 消息队列异步写 |
||||
- entry 监控,http 查询 entry 地址返回接口 |
||||
- 功能开发: |
||||
- 消息群发(im-router 群消息路由,批量uid一致性哈希路由) |
||||
- 好友列表(在线状态:批量uid一致性hash, router查询) |
||||
- 聊天记录查询 |
||||
- 异/同设备,多地登录 |
||||
- 集群部署, docker swarm, k8s, jenkens |
||||
- 服务监控 |
||||
- websocket 网关 |
||||
- 考虑 dubbo 使用 grpc service |
||||
|
||||
|
||||
模块列表 |
||||
- 接入层 entry |
||||
- 逻辑层 logic |
||||
- 内存存储层 router |
||||
- 固化存储层 das |
||||
|
||||
功能组件: |
||||
1.0 |
||||
|
||||
- IOC: guice |
||||
- 通信层: netty |
||||
- 序列化: protobuf |
||||
- 内存存储: |
||||
- Caffeine |
||||
- Memcache |
||||
- 分布式缓存:redis |
||||
- 注册中心:etcd、直连 |
||||
- 处理队列:disruptor |
||||
- 持久化: |
||||
- mysql 存储全量历史消息 |
||||
- mongodb 存储直接拉取的未读消息 |
||||
- 日志:log4j2 |
||||
- 监控:prometheus、grafana |
||||
|
||||
- ID生成器 |
||||
|
||||
2.0 |
||||
|
||||
- quarkus |
||||
|
||||
集群负载均衡: |
||||
子网1,子网2,公网 |
||||
子网1和2不相通 |
||||
|
||||
dubbo native image |
||||
https://dubbo.apache.org/zh/docs/references/graalvm/support-graalvm/ |
||||
|
||||
|
||||
entry <--> client 通信 |
||||
- serviceId --> paramClass --> serviceHandler |
||||
|
||||
entry <--> logic dubbo service |
||||
client jconsle cmd |
||||
|
||||
router <--> cache |
||||
das <--> db |
||||
router <--> das |
||||
|
||||
|
||||
das shardingjdbc |
||||
table struct, sharding roles |
||||
logic biz |
||||
|
||||
请求响应消息队列异步处理,减少线程 cpu 占用, dubbo async |
||||
客户端和TCP接入层通过 netty+protobuf 通信。 |
||||
|
||||
后端服务通过 dubbo 服务间调用。 |
||||
|
||||
|
||||
|
||||
## 功能演示 |
||||
|
||||
好友、群、单聊、群聊、内置命令 |
||||
|
||||
 |
||||
|
||||
|
||||
|
||||
<video id="video" controls="" preload="none" poster="doc/image/README/soim.jpg"> |
||||
<source id="mp4" src="doc/image/README/soim.mp4" type="video/mp4"> |
||||
</videos> |
||||
|
||||
|
||||
|
||||
## 消息转发流程 |
||||
|
||||
 |
||||
|
||||
|
||||
|
||||
## 内置命令 |
||||
|
||||
| 命令 | 选项 | 描述 | |
||||
| ------- | ------------------------------------------------------------ | ---------------------------------------------------- | |
||||
| login | -u 账号<br />-p 密码 | 登录并连接tcp接入层 | |
||||
| send | -u 好友id 单聊消息<br />-g 群id 群消息 | 发送消息 | |
||||
| users | | 在线用户列表 | |
||||
| friends | | 我的好友列表 | |
||||
| group | | 查询我的群聊列表 | |
||||
| group | create 群聊名称<br />search 群聊名称(右模糊)<br />join 群聊id<br />users | 创建群聊<br />搜索群聊<br />加入群聊<br />群用户列表 | |
||||
| search | -u 用户账号(右模糊) | 搜索用户 | |
||||
| add | -u 用户id | 添加好友 | |
||||
| me | | 我的信息 | |
||||
| exit | | 退出客户端 | |
||||
|
||||
|
||||
|
||||
## TODO LIST |
||||
|
||||
- [x] 好友、基础单聊 |
||||
- [x] 群搜索、添加、列表、基础群聊 |
||||
- [x] 消息通过MQ持久化到MySQL分表库 |
||||
- [x] 启动路由层新节点根据一致性hash迁移其他节点内存数据 |
||||
- [x] 接入层负载均衡 |
||||
- [x] 分布式分段id生成服务 |
||||
- [ ] 聊天记录查询 |
||||
- [ ] 优化命令行客户端打印输出 |
||||
- [ ] 群聊,单聊 消息已读确认 |
||||
- [ ] 未读离线消息推送 |
||||
- [ ] 客户端接入层消息加密 |
||||
- [ ] dubbo service SSL 加密 |
||||
- [ ] 路由层状态服务冗余节点备份,自动切换 |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 启动步骤 |
||||
|
||||
### 创建数据库 |
||||
|
||||
 |
||||
|
||||
|
||||
|
||||
### 中间件配置 |
||||
|
||||
配置 nacos、mysql、rabbitmq、redis 服务地址 |
||||
|
||||
 |
||||
|
||||
|
||||
|
||||
### 启动如下服务 |
||||
|
||||
 |
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 286 KiB |
Binary file not shown.
@ -0,0 +1,782 @@
|
||||
/* |
||||
Navicat Premium Data Transfer |
||||
|
||||
Source Server : mysql |
||||
Source Server Type : MySQL |
||||
Source Server Version : 80022 |
||||
Source Host : 127.0.0.1:3306 |
||||
Source Schema : soim_db |
||||
|
||||
Target Server Type : MySQL |
||||
Target Server Version : 80022 |
||||
File Encoding : 65001 |
||||
|
||||
Date: 12/06/2022 15:11:15 |
||||
*/ |
||||
|
||||
SET NAMES utf8mb4; |
||||
SET FOREIGN_KEY_CHECKS = 0; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_friend_0 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_friend_0`; |
||||
CREATE TABLE `im_friend_0` ( |
||||
`id` bigint(0) NOT NULL, |
||||
`relation_id` bigint(0) NOT NULL COMMENT '好友关系id(消息存储路由分片)', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`fid` bigint(0) NOT NULL COMMENT '好友id', |
||||
`status` tinyint(0) NOT NULL COMMENT '状态:0删除,1正常,2临时好友(群单聊)', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NOT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友关系表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_friend_1 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_friend_1`; |
||||
CREATE TABLE `im_friend_1` ( |
||||
`id` bigint(0) NOT NULL, |
||||
`relation_id` bigint(0) NOT NULL COMMENT '好友关系id(消息存储路由分片)', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`fid` bigint(0) NOT NULL COMMENT '好友id', |
||||
`status` tinyint(0) NOT NULL COMMENT '状态:0删除,1正常,2临时好友(群单聊)', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NOT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友关系表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_friend_2 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_friend_2`; |
||||
CREATE TABLE `im_friend_2` ( |
||||
`id` bigint(0) NOT NULL, |
||||
`relation_id` bigint(0) NOT NULL COMMENT '好友关系id(消息存储路由分片)', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`fid` bigint(0) NOT NULL COMMENT '好友id', |
||||
`status` tinyint(0) NOT NULL COMMENT '状态:0删除,1正常,2临时好友(群单聊)', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NOT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友关系表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_friend_3 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_friend_3`; |
||||
CREATE TABLE `im_friend_3` ( |
||||
`id` bigint(0) NOT NULL, |
||||
`relation_id` bigint(0) NOT NULL COMMENT '好友关系id(消息存储路由分片)', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`fid` bigint(0) NOT NULL COMMENT '好友id', |
||||
`status` tinyint(0) NOT NULL COMMENT '状态:0删除,1正常,2临时好友(群单聊)', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NOT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友关系表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_friend_4 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_friend_4`; |
||||
CREATE TABLE `im_friend_4` ( |
||||
`id` bigint(0) NOT NULL, |
||||
`relation_id` bigint(0) NOT NULL COMMENT '好友关系id(消息存储路由分片)', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`fid` bigint(0) NOT NULL COMMENT '好友id', |
||||
`status` tinyint(0) NOT NULL COMMENT '状态:0删除,1正常,2临时好友(群单聊)', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NOT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友关系表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_friend_5 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_friend_5`; |
||||
CREATE TABLE `im_friend_5` ( |
||||
`id` bigint(0) NOT NULL, |
||||
`relation_id` bigint(0) NOT NULL COMMENT '好友关系id(消息存储路由分片)', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`fid` bigint(0) NOT NULL COMMENT '好友id', |
||||
`status` tinyint(0) NOT NULL COMMENT '状态:0删除,1正常,2临时好友(群单聊)', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NOT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友关系表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_friend_6 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_friend_6`; |
||||
CREATE TABLE `im_friend_6` ( |
||||
`id` bigint(0) NOT NULL, |
||||
`relation_id` bigint(0) NOT NULL COMMENT '好友关系id(消息存储路由分片)', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`fid` bigint(0) NOT NULL COMMENT '好友id', |
||||
`status` tinyint(0) NOT NULL COMMENT '状态:0删除,1正常,2临时好友(群单聊)', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NOT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友关系表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_friend_7 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_friend_7`; |
||||
CREATE TABLE `im_friend_7` ( |
||||
`id` bigint(0) NOT NULL, |
||||
`relation_id` bigint(0) NOT NULL COMMENT '好友关系id(消息存储路由分片)', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`fid` bigint(0) NOT NULL COMMENT '好友id', |
||||
`status` tinyint(0) NOT NULL COMMENT '状态:0删除,1正常,2临时好友(群单聊)', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NOT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友关系表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_0 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_0`; |
||||
CREATE TABLE `im_group_0` ( |
||||
`id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`master_uid` bigint(0) NOT NULL COMMENT '群主id', |
||||
`status` int(0) NOT NULL COMMENT '状态:0删除,1.正常,3禁用,4.解散', |
||||
`group_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '群聊名称', |
||||
`user_limit` int(0) NOT NULL COMMENT '群聊人数限制', |
||||
`user_num` int(0) NOT NULL COMMENT '当前群聊人数', |
||||
`create_time` datetime(0) NOT NULL COMMENT '创建时间', |
||||
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_1 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_1`; |
||||
CREATE TABLE `im_group_1` ( |
||||
`id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`master_uid` bigint(0) NOT NULL COMMENT '群主id', |
||||
`status` int(0) NOT NULL COMMENT '状态:0删除,1.正常,3禁用,4.解散', |
||||
`group_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '群聊名称', |
||||
`user_limit` int(0) NOT NULL COMMENT '群聊人数限制', |
||||
`user_num` int(0) NOT NULL COMMENT '当前群聊人数', |
||||
`create_time` datetime(0) NOT NULL COMMENT '创建时间', |
||||
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_2 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_2`; |
||||
CREATE TABLE `im_group_2` ( |
||||
`id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`master_uid` bigint(0) NOT NULL COMMENT '群主id', |
||||
`status` int(0) NOT NULL COMMENT '状态:0删除,1.正常,3禁用,4.解散', |
||||
`group_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '群聊名称', |
||||
`user_limit` int(0) NOT NULL COMMENT '群聊人数限制', |
||||
`user_num` int(0) NOT NULL COMMENT '当前群聊人数', |
||||
`create_time` datetime(0) NOT NULL COMMENT '创建时间', |
||||
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_3 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_3`; |
||||
CREATE TABLE `im_group_3` ( |
||||
`id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`master_uid` bigint(0) NOT NULL COMMENT '群主id', |
||||
`status` int(0) NOT NULL COMMENT '状态:0删除,1.正常,3禁用,4.解散', |
||||
`group_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '群聊名称', |
||||
`user_limit` int(0) NOT NULL COMMENT '群聊人数限制', |
||||
`user_num` int(0) NOT NULL COMMENT '当前群聊人数', |
||||
`create_time` datetime(0) NOT NULL COMMENT '创建时间', |
||||
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_0 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_0`; |
||||
CREATE TABLE `im_group_message_0` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_1 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_1`; |
||||
CREATE TABLE `im_group_message_1` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_2 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_2`; |
||||
CREATE TABLE `im_group_message_2` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_3 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_3`; |
||||
CREATE TABLE `im_group_message_3` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_4 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_4`; |
||||
CREATE TABLE `im_group_message_4` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_5 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_5`; |
||||
CREATE TABLE `im_group_message_5` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_6 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_6`; |
||||
CREATE TABLE `im_group_message_6` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_7 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_7`; |
||||
CREATE TABLE `im_group_message_7` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_8 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_8`; |
||||
CREATE TABLE `im_group_message_8` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_9 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_9`; |
||||
CREATE TABLE `im_group_message_9` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_10 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_10`; |
||||
CREATE TABLE `im_group_message_10` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_11 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_11`; |
||||
CREATE TABLE `im_group_message_11` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_12 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_12`; |
||||
CREATE TABLE `im_group_message_12` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_13 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_13`; |
||||
CREATE TABLE `im_group_message_13` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_14 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_14`; |
||||
CREATE TABLE `im_group_message_14` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_message_15 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_message_15`; |
||||
CREATE TABLE `im_group_message_15` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`sender` bigint(0) NOT NULL COMMENT '发送者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_user_0 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_user_0`; |
||||
CREATE TABLE `im_group_user_0` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NULL DEFAULT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`last_active` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊用户列表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_user_1 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_user_1`; |
||||
CREATE TABLE `im_group_user_1` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NULL DEFAULT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`last_active` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊用户列表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_user_2 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_user_2`; |
||||
CREATE TABLE `im_group_user_2` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NULL DEFAULT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`last_active` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊用户列表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_user_3 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_user_3`; |
||||
CREATE TABLE `im_group_user_3` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NULL DEFAULT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`last_active` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊用户列表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_user_4 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_user_4`; |
||||
CREATE TABLE `im_group_user_4` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NULL DEFAULT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`last_active` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊用户列表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_user_5 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_user_5`; |
||||
CREATE TABLE `im_group_user_5` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NULL DEFAULT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`last_active` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊用户列表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_user_6 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_user_6`; |
||||
CREATE TABLE `im_group_user_6` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NULL DEFAULT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`last_active` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊用户列表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_group_user_7 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_group_user_7`; |
||||
CREATE TABLE `im_group_user_7` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`group_id` bigint(0) NOT NULL COMMENT '群聊id', |
||||
`uid` bigint(0) NOT NULL COMMENT '用户id', |
||||
`unread_num` int(0) NOT NULL COMMENT '群未读消息数量', |
||||
`unread_offset_id` bigint(0) NULL DEFAULT NULL COMMENT '群未读消息id偏移量(大于等于该id的都未读)', |
||||
`last_active` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群聊用户列表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_message_0 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_message_0`; |
||||
CREATE TABLE `im_message_0` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`relation_id` bigint(0) NULL DEFAULT NULL, |
||||
`sender` bigint(0) NOT NULL COMMENT '发送人id', |
||||
`receiver` bigint(0) NOT NULL COMMENT '接受者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_message_1 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_message_1`; |
||||
CREATE TABLE `im_message_1` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`relation_id` bigint(0) NULL DEFAULT NULL, |
||||
`sender` bigint(0) NOT NULL COMMENT '发送人id', |
||||
`receiver` bigint(0) NOT NULL COMMENT '接受者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_message_2 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_message_2`; |
||||
CREATE TABLE `im_message_2` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`relation_id` bigint(0) NULL DEFAULT NULL, |
||||
`sender` bigint(0) NOT NULL COMMENT '发送人id', |
||||
`receiver` bigint(0) NOT NULL COMMENT '接受者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_message_3 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_message_3`; |
||||
CREATE TABLE `im_message_3` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`relation_id` bigint(0) NULL DEFAULT NULL, |
||||
`sender` bigint(0) NOT NULL COMMENT '发送人id', |
||||
`receiver` bigint(0) NOT NULL COMMENT '接受者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_message_4 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_message_4`; |
||||
CREATE TABLE `im_message_4` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`relation_id` bigint(0) NULL DEFAULT NULL, |
||||
`sender` bigint(0) NOT NULL COMMENT '发送人id', |
||||
`receiver` bigint(0) NOT NULL COMMENT '接受者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_message_5 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_message_5`; |
||||
CREATE TABLE `im_message_5` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`relation_id` bigint(0) NULL DEFAULT NULL, |
||||
`sender` bigint(0) NOT NULL COMMENT '发送人id', |
||||
`receiver` bigint(0) NOT NULL COMMENT '接受者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_message_6 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_message_6`; |
||||
CREATE TABLE `im_message_6` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`relation_id` bigint(0) NULL DEFAULT NULL, |
||||
`sender` bigint(0) NOT NULL COMMENT '发送人id', |
||||
`receiver` bigint(0) NOT NULL COMMENT '接受者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_message_7 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_message_7`; |
||||
CREATE TABLE `im_message_7` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`relation_id` bigint(0) NULL DEFAULT NULL, |
||||
`sender` bigint(0) NOT NULL COMMENT '发送人id', |
||||
`receiver` bigint(0) NOT NULL COMMENT '接受者id', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', |
||||
PRIMARY KEY (`id`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '聊天消息' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_segment_id |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_segment_id`; |
||||
CREATE TABLE `im_segment_id` ( |
||||
`biz_tag` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '业务标签', |
||||
`current_id` bigint(0) NOT NULL COMMENT '当前id值', |
||||
`init_step` bigint(0) NOT NULL COMMENT '业务标签初始步长', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', |
||||
`version` bigint(0) NOT NULL COMMENT '版本号', |
||||
PRIMARY KEY (`biz_tag`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '分段id' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_segment_id |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_segment_id`; |
||||
CREATE TABLE `im_segment_id` ( |
||||
`biz_tag` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '业务标签', |
||||
`current_id` bigint(0) NOT NULL COMMENT '当前id值', |
||||
`init_step` bigint(0) NOT NULL COMMENT '业务标签初始步长', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', |
||||
`version` bigint(0) NOT NULL COMMENT '版本号', |
||||
PRIMARY KEY (`biz_tag`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '分段id' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Records of im_segment_id |
||||
-- ---------------------------- |
||||
INSERT INTO `im_segment_id` VALUES ('im_friend', 12002, 1000, '2022-06-03 19:45:15', '2022-06-12 13:08:59', 2); |
||||
INSERT INTO `im_segment_id` VALUES ('im_friend_relation_id', 12002, 1000, '2022-06-03 19:45:15', '2022-06-12 13:08:59', 2); |
||||
INSERT INTO `im_segment_id` VALUES ('im_group', 12002, 1000, '2022-06-05 09:52:41', '2022-06-07 17:39:39', 2); |
||||
INSERT INTO `im_segment_id` VALUES ('im_group_message', 16006, 1000, '2022-06-07 16:45:41', '2022-06-12 13:09:48', 6); |
||||
INSERT INTO `im_segment_id` VALUES ('im_group_user', 13003, 1000, '2022-06-05 09:52:42', '2022-06-07 17:39:39', 3); |
||||
INSERT INTO `im_segment_id` VALUES ('im_message', 35025, 1000, '2022-06-03 19:55:01', '2022-06-12 13:09:10', 25); |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_user_0 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_user_0`; |
||||
CREATE TABLE `im_user_0` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`status` int(0) NOT NULL COMMENT '状态:0删除,1.正常,2.禁用', |
||||
`account` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录账号', |
||||
`nickname` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户昵称', |
||||
`password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码', |
||||
`phone` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`last_login_time` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间', |
||||
`last_active_time` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
PRIMARY KEY (`id`) USING BTREE, |
||||
INDEX `idx_account_status_0`(`account`, `status`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Records of im_user_0 |
||||
-- ---------------------------- |
||||
INSERT INTO `im_user_0` VALUES (728220, 1, 'zeus', '宙斯', '123456', '17882451901', '2022-04-14 10:50:55', NULL, NULL); |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_user_1 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_user_1`; |
||||
CREATE TABLE `im_user_1` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`status` int(0) NOT NULL COMMENT '状态:0删除,1.正常,2.禁用', |
||||
`account` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录账号', |
||||
`nickname` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户昵称', |
||||
`password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码', |
||||
`phone` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`last_login_time` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间', |
||||
`last_active_time` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
PRIMARY KEY (`id`) USING BTREE, |
||||
INDEX `idx_account_status`(`account`, `status`) USING BTREE, |
||||
INDEX `idx_account_status_1`(`account`, `status`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Records of im_user_1 |
||||
-- ---------------------------- |
||||
INSERT INTO `im_user_1` VALUES (727717, 1, 'prometheus', '普罗米修斯', '123456', '17882451908', '2022-04-14 10:46:23', NULL, NULL); |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_user_2 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_user_2`; |
||||
CREATE TABLE `im_user_2` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`status` int(0) NOT NULL COMMENT '状态:0删除,1.正常,2.禁用', |
||||
`account` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录账号', |
||||
`nickname` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户昵称', |
||||
`password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码', |
||||
`phone` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`last_login_time` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间', |
||||
`last_active_time` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
PRIMARY KEY (`id`) USING BTREE, |
||||
INDEX `idx_account_status`(`account`, `status`) USING BTREE, |
||||
INDEX `idx_account_status_2`(`account`, `status`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Records of im_user_2 |
||||
-- ---------------------------- |
||||
INSERT INTO `im_user_2` VALUES (729726, 1, 'poros', '波罗斯', '123456', '17882451902', '2022-04-14 10:53:39', NULL, NULL); |
||||
|
||||
-- ---------------------------- |
||||
-- Table structure for im_user_3 |
||||
-- ---------------------------- |
||||
DROP TABLE IF EXISTS `im_user_3`; |
||||
CREATE TABLE `im_user_3` ( |
||||
`id` bigint(0) NOT NULL COMMENT 'id', |
||||
`status` int(0) NOT NULL COMMENT '状态:0删除,1.正常,2.禁用', |
||||
`account` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录账号', |
||||
`nickname` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户昵称', |
||||
`password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码', |
||||
`phone` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号', |
||||
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', |
||||
`last_login_time` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间', |
||||
`last_active_time` datetime(0) NULL DEFAULT NULL COMMENT '最后活跃时间', |
||||
PRIMARY KEY (`id`) USING BTREE, |
||||
INDEX `idx_account_status_3`(`account`, `status`) USING BTREE |
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; |
||||
|
||||
-- ---------------------------- |
||||
-- Records of im_user_3 |
||||
-- ---------------------------- |
||||
SET FOREIGN_KEY_CHECKS = 1; |
||||
@ -0,0 +1,40 @@
|
||||
package net.sopod.soim.das.user.cache; |
||||
|
||||
import com.google.common.collect.HashBasedTable; |
||||
import com.google.common.collect.Table; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.function.BiFunction; |
||||
|
||||
/** |
||||
* FriendRelationCache |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-12 09:38 |
||||
*/ |
||||
@Service |
||||
public class FriendRelationCache { |
||||
|
||||
private final Table<Long, Long, Long> friendRelationTable = HashBasedTable.create();; |
||||
|
||||
public Long computeIfAbsent(Long uid, Long fid, BiFunction<Long, Long, Long> mappingFunction) { |
||||
Long relationId; |
||||
if (null != (relationId = friendRelationTable.get(uid, fid))) { |
||||
return relationId; |
||||
} |
||||
// TODO lockFunc(uid, fid)
|
||||
synchronized (FriendRelationCache.class) { |
||||
if (null != (relationId = friendRelationTable.get(uid, fid))) { |
||||
return relationId; |
||||
} |
||||
relationId = mappingFunction.apply(uid, fid); |
||||
friendRelationTable.put(uid, fid, relationId); |
||||
} |
||||
return relationId; |
||||
} |
||||
|
||||
public Long remove(Long uid, Long fid) { |
||||
return friendRelationTable.remove(uid, fid); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,176 @@
|
||||
package net.sopod.soim.entry.http.client; |
||||
|
||||
import com.google.protobuf.MessageLite; |
||||
import io.netty.bootstrap.Bootstrap; |
||||
import io.netty.channel.Channel; |
||||
import io.netty.channel.ChannelInitializer; |
||||
import io.netty.channel.ChannelOption; |
||||
import io.netty.channel.nio.NioEventLoopGroup; |
||||
import io.netty.channel.socket.SocketChannel; |
||||
import io.netty.channel.socket.nio.NioSocketChannel; |
||||
import io.netty.util.AttributeKey; |
||||
import net.sopod.soim.common.constant.AppConstant; |
||||
import net.sopod.soim.common.dubbo.exception.SoimException; |
||||
import net.sopod.soim.common.util.netty.Varint32FrameCodec; |
||||
import net.sopod.soim.data.msg.monitor.EntryMonitor; |
||||
import net.sopod.soim.data.serialize.ImMessage; |
||||
import net.sopod.soim.data.serialize.ImMessageCodec; |
||||
import net.sopod.soim.entry.http.service.ImMessageReqHolder; |
||||
import org.apache.commons.lang3.tuple.Pair; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import java.util.Objects; |
||||
import java.util.concurrent.CompletableFuture; |
||||
|
||||
/** |
||||
* EntryClient |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-11 13:17 |
||||
*/ |
||||
public class EntryClient { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EntryClient.class); |
||||
|
||||
public static AttributeKey<EntryClient> IM_ENTRY_CLIENT_KEY = AttributeKey.valueOf(EntryClient.class, "IM_ENTRY_CLIENT_KEY"); |
||||
|
||||
private final String entryAddress; |
||||
|
||||
private final ImMessageReqHolder imMessageReqHolder; |
||||
|
||||
private Channel clientChannel; |
||||
|
||||
private NioEventLoopGroup eventLoopGroup; |
||||
|
||||
private int entryConnections; |
||||
|
||||
private long updateTime; |
||||
|
||||
public EntryClient(String entryAddress) { |
||||
this.entryAddress = entryAddress; |
||||
this.imMessageReqHolder = new ImMessageReqHolder(); |
||||
} |
||||
|
||||
public boolean connect() { |
||||
this.eventLoopGroup = new NioEventLoopGroup(2); |
||||
Bootstrap b = new Bootstrap() |
||||
.group(this.eventLoopGroup) |
||||
.channel(NioSocketChannel.class) |
||||
.handler(new ChannelInitializer<SocketChannel>() { |
||||
@Override |
||||
protected void initChannel(SocketChannel ch) throws Exception { |
||||
ch.pipeline() |
||||
.addLast(new Varint32FrameCodec()) |
||||
.addLast(new ImMessageCodec.ImMessage2ByteEncoder()) |
||||
.addLast(new ImMessageInboundHandler()); |
||||
} |
||||
}) |
||||
.option(ChannelOption.TCP_NODELAY, true); |
||||
String[] hostPort = this.entryAddress.split(":"); |
||||
try { |
||||
this.clientChannel = b.connect(hostPort[0], Integer.parseInt(hostPort[1])).await().channel(); |
||||
} catch (InterruptedException e) { |
||||
logger.error("im-entry 连接失败: ", e); |
||||
return false; |
||||
} |
||||
// 设置 MessageHolder 到 channel
|
||||
this.clientChannel.attr(ImMessageReqHolder.IM_MESSAGE_HOLDER_KEY).set(this.imMessageReqHolder); |
||||
// 设置 client 到 channel
|
||||
this.clientChannel.attr(EntryClient.IM_ENTRY_CLIENT_KEY).set(this); |
||||
EntryMonitor.ReqMonitorAuth req = EntryMonitor.ReqMonitorAuth.newBuilder() |
||||
.setSecurity(AppConstant.IM_ENTRY_MONITOR_SECURITY) |
||||
.build(); |
||||
CompletableFuture<?> future = this.send0(req); |
||||
EntryMonitor.ResMonitorAuth res = (EntryMonitor.ResMonitorAuth) future.join(); |
||||
if (!res.getSuccess()) { |
||||
logger.error("{} 认证失败", this.entryAddress); |
||||
// 关闭连接
|
||||
this.close(); |
||||
return false; |
||||
} |
||||
logger.info("认证成功: {}", this.entryAddress); |
||||
return true; |
||||
} |
||||
|
||||
private CompletableFuture<?> send0(MessageLite message) { |
||||
if (this.clientChannel == null |
||||
|| !this.clientChannel.isActive()) { |
||||
// TODO 尝试重连
|
||||
throw new SoimException("连接已关闭"); |
||||
} |
||||
// 构建 ImMessage
|
||||
ImMessage imMessage = ImMessageCodec.encodeImProto(message); |
||||
// serialNo, Future
|
||||
Pair<Integer, CompletableFuture<Object>> futurePair = imMessageReqHolder.nextSerialNo(); |
||||
imMessage.setSerialNo(futurePair.getLeft()); |
||||
try { |
||||
Throwable cause = this.clientChannel.writeAndFlush(imMessage).await().cause(); |
||||
if (cause != null) { |
||||
throw new SoimException(cause); |
||||
} |
||||
} catch (InterruptedException e) { |
||||
throw new SoimException("发送消息失败", e); |
||||
} |
||||
return futurePair.getRight(); |
||||
} |
||||
|
||||
/** |
||||
* 发送消息 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public <T> CompletableFuture<T> send(MessageLite message) { |
||||
return (CompletableFuture<T>) send0(message); |
||||
} |
||||
|
||||
public String getEntryAddress() { |
||||
return entryAddress; |
||||
} |
||||
|
||||
public void updateEntryStatus(int connections, long time) { |
||||
// logger.info("{} entry status: conns={}", this.entryAddress, connections);
|
||||
this.entryConnections = connections; |
||||
this.updateTime = time; |
||||
} |
||||
|
||||
/** |
||||
* 关闭 monitor client 连接 |
||||
*/ |
||||
public void close() { |
||||
if (this.clientChannel != null) { |
||||
this.clientChannel.close(); |
||||
this.clientChannel.attr(ImMessageReqHolder.IM_MESSAGE_HOLDER_KEY).set(null); |
||||
this.clientChannel.attr(EntryClient.IM_ENTRY_CLIENT_KEY).set(null); |
||||
logger.info("entry monitor client {} closed.", this.entryAddress); |
||||
} |
||||
if (this.eventLoopGroup != null) { |
||||
this.eventLoopGroup.shutdownGracefully(); |
||||
} |
||||
} |
||||
|
||||
public int getEntryConnections() { |
||||
return entryConnections; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (o == null || getClass() != o.getClass()) return false; |
||||
EntryClient that = (EntryClient) o; |
||||
return Objects.equals(entryAddress, that.entryAddress); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash(entryAddress); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "EntryClient{" + |
||||
"entryAddress='" + entryAddress + '\'' + |
||||
", entryConnections=" + entryConnections + |
||||
", updateTime=" + updateTime + |
||||
'}'; |
||||
} |
||||
} |
||||
@ -0,0 +1,48 @@
|
||||
package net.sopod.soim.entry.http.client; |
||||
|
||||
import io.netty.buffer.ByteBuf; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.channel.SimpleChannelInboundHandler; |
||||
import net.sopod.soim.data.serialize.ImMessage; |
||||
import net.sopod.soim.data.serialize.ImMessageCodec; |
||||
import net.sopod.soim.entry.http.service.ImMessageReqHolder; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
/** |
||||
* ImMessageInboundHandler |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-11 15:12 |
||||
*/ |
||||
public class ImMessageInboundHandler extends SimpleChannelInboundHandler<ByteBuf> { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ImMessageInboundHandler.class); |
||||
|
||||
@Override |
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception { |
||||
super.channelActive(ctx); |
||||
logger.info("channel active..."); |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
||||
EntryClient entryClient = ctx.channel().attr(EntryClient.IM_ENTRY_CLIENT_KEY).get(); |
||||
if (entryClient != null) { |
||||
entryClient.close(); |
||||
} |
||||
super.channelInactive(ctx); |
||||
} |
||||
|
||||
@Override |
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception { |
||||
ImMessage imMessage = ImMessageCodec.decodeImMessage(byteBuf); |
||||
// 请求序列号,complete 对应 CompletableFuture
|
||||
int serialNo = imMessage.getSerialNo(); |
||||
|
||||
ImMessageReqHolder imMessageReqHolder = ctx.channel().attr(ImMessageReqHolder.IM_MESSAGE_HOLDER_KEY).get(); |
||||
imMessageReqHolder.complete(serialNo, imMessage.getDecodeBody()); |
||||
} |
||||
|
||||
} |
||||
@ -1,65 +0,0 @@
|
||||
package net.sopod.soim.entry.http.config; |
||||
|
||||
import com.alibaba.nacos.api.NacosFactory; |
||||
import com.alibaba.nacos.api.exception.NacosException; |
||||
import com.alibaba.nacos.api.naming.NamingService; |
||||
import com.alibaba.nacos.api.naming.listener.Event; |
||||
import com.alibaba.nacos.api.naming.listener.NamingEvent; |
||||
import com.alibaba.nacos.api.naming.pojo.Instance; |
||||
import net.sopod.soim.common.constant.AppConstant; |
||||
import org.apache.dubbo.common.URL; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Value; |
||||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.core.Ordered; |
||||
|
||||
import java.util.List; |
||||
import java.util.Properties; |
||||
|
||||
/** |
||||
* ApplicationOnReady |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-09 11:16 |
||||
*/ |
||||
@Configuration |
||||
public class ApplicationOnReady implements ApplicationListener<ApplicationReadyEvent>, Ordered { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApplicationOnReady.class); |
||||
|
||||
private final String serverAddress; |
||||
|
||||
public ApplicationOnReady(@Value("${dubbo.registry.address}") String serverAddress) { |
||||
this.serverAddress = URL.valueOf(serverAddress).getAddress(); |
||||
} |
||||
|
||||
@Override |
||||
public void onApplicationEvent(ApplicationReadyEvent readyEvent) { |
||||
// NamingServer 获取 im-entry 实例,连接上,
|
||||
Properties properties = new Properties(); |
||||
properties.put("serverAddr", serverAddress); |
||||
|
||||
NamingService namingService = null; |
||||
try { |
||||
namingService = NacosFactory.createNamingService(properties); |
||||
List<Instance> allInstances = namingService.getAllInstances(AppConstant.APP_IM_ENTRY_NAME); |
||||
logger.info("im-entry instance...: {}", allInstances); |
||||
namingService.subscribe(AppConstant.APP_IM_ENTRY_NAME, (Event event) -> { |
||||
NamingEvent ne = (NamingEvent) event; |
||||
logger.info("namingEvent: {}", ne); |
||||
}); |
||||
|
||||
} catch (NacosException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int getOrder() { |
||||
return Ordered.HIGHEST_PRECEDENCE; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,175 @@
|
||||
package net.sopod.soim.entry.http.config; |
||||
|
||||
import com.alibaba.nacos.api.NacosFactory; |
||||
import com.alibaba.nacos.api.exception.NacosException; |
||||
import com.alibaba.nacos.api.naming.NamingService; |
||||
import com.alibaba.nacos.api.naming.listener.Event; |
||||
import com.alibaba.nacos.api.naming.listener.NamingEvent; |
||||
import com.alibaba.nacos.api.naming.pojo.Instance; |
||||
import net.sopod.soim.common.constant.AppConstant; |
||||
import net.sopod.soim.common.dubbo.exception.SoimException; |
||||
import net.sopod.soim.common.util.Collects; |
||||
import net.sopod.soim.common.util.netty.FastThreadLocalThreadFactory; |
||||
import net.sopod.soim.data.msg.monitor.EntryMonitor; |
||||
import net.sopod.soim.entry.http.client.EntryClient; |
||||
import org.apache.dubbo.common.URL; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Value; |
||||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.util.*; |
||||
import java.util.concurrent.*; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* ApplicationOnReady |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-09 11:16 |
||||
*/ |
||||
@Component |
||||
// @Configuration
|
||||
public class ImEntryMonitor implements ApplicationListener<ApplicationReadyEvent>, Ordered { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ImEntryMonitor.class); |
||||
|
||||
private final String nacosServerAddress; |
||||
|
||||
private NamingService namingService; |
||||
|
||||
private final Map<String, EntryClient> entryClientMap = new ConcurrentHashMap<>(8); |
||||
|
||||
private final ScheduledExecutorService scheduledExecutorService; |
||||
|
||||
public ImEntryMonitor(@Value("${dubbo.registry.address}") String nacosServerAddress) { |
||||
this.nacosServerAddress = URL.valueOf(nacosServerAddress).getAddress(); |
||||
this.scheduledExecutorService = Executors.newScheduledThreadPool(1, |
||||
new FastThreadLocalThreadFactory("entry-monitor-%d", Thread.NORM_PRIORITY)); |
||||
this.scheduledExecutorService.scheduleWithFixedDelay(() -> { |
||||
try { |
||||
for (Map.Entry<String, EntryClient> entry : entryClientMap.entrySet()) { |
||||
try { |
||||
EntryClient entryClient = entry.getValue(); |
||||
// 定时获取状态信息
|
||||
EntryMonitor.ReqEntryStatus req = EntryMonitor.ReqEntryStatus.newBuilder().build(); |
||||
CompletableFuture<EntryMonitor.ResEntryStatus> future = entryClient.send(req); |
||||
EntryMonitor.ResEntryStatus res = future.join(); |
||||
entryClient.updateEntryStatus(res.getConnections(), res.getStatusTime()); |
||||
} catch (SoimException e) { |
||||
logger.error("获取状态信息失败", e); |
||||
entryClientMap.remove(entry.getKey(), entry.getValue()); |
||||
} |
||||
} |
||||
} catch (Exception e) { |
||||
logger.error("定时拉取状态信息错误", e); |
||||
} |
||||
}, 5, 5, TimeUnit.SECONDS); |
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> { |
||||
if (namingService != null) { |
||||
try { |
||||
namingService.shutDown(); |
||||
} catch (NacosException e) { |
||||
logger.error("nacos naming server shutdown exception", e); |
||||
} |
||||
scheduledExecutorService.shutdownNow(); |
||||
entryClientMap.values().forEach(EntryClient::close); |
||||
logger.info("entry monitor shutdown."); |
||||
} |
||||
})); |
||||
} |
||||
|
||||
@Override |
||||
public void onApplicationEvent(ApplicationReadyEvent readyEvent) { |
||||
// NamingServer 获取 im-entry 实例,连接上,
|
||||
Properties properties = new Properties(); |
||||
properties.put("serverAddr", nacosServerAddress); |
||||
try { |
||||
this.namingService = NacosFactory.createNamingService(properties); |
||||
// 初次启动获取所有 im-entry 服务节点
|
||||
List<Instance> imEntryInstances = this.namingService.getAllInstances(AppConstant.APP_IM_ENTRY_NAME); |
||||
this.buildEntryMonitorMap(imEntryInstances); |
||||
// 监听 im-entry 服务变化
|
||||
this.namingService.subscribe(AppConstant.APP_IM_ENTRY_NAME, (Event event) -> { |
||||
if (!(event instanceof NamingEvent)) { |
||||
return; |
||||
} |
||||
NamingEvent ne = (NamingEvent) event; |
||||
try { |
||||
this.buildEntryMonitorMap(ne.getInstances()); |
||||
}catch (Exception e) { |
||||
logger.error("构建im-entry服务节点失败:", e); |
||||
} |
||||
}); |
||||
|
||||
} catch (NacosException e) { |
||||
logger.error("nacos exception", e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 重新构建 im-entry 服务节点记录 |
||||
*/ |
||||
private synchronized void buildEntryMonitorMap(List<Instance> imEntryInstances) { |
||||
HashSet<String> instanceAddresses = new HashSet<>(Collects.mapCapacity(imEntryInstances.size())); |
||||
imEntryInstances.forEach(instance -> { |
||||
String address = instance.getIp() + ":" + (instance.getPort() + AppConstant.IM_ENTRY_SERVER_OFFSET); |
||||
instanceAddresses.add(address); |
||||
}); |
||||
logger.info("im-entry instances: {}", instanceAddresses); |
||||
// 关闭失效im-entry
|
||||
List<String> invalidateAddresses = new ArrayList<>(); |
||||
for (Map.Entry<String, EntryClient> entry : entryClientMap.entrySet()) { |
||||
if (!instanceAddresses.contains(entry.getKey())) { |
||||
invalidateAddresses.add(entry.getKey()); |
||||
} |
||||
} |
||||
for (String invalidateAddress : invalidateAddresses) { |
||||
EntryClient invalidClient = entryClientMap.remove(invalidateAddress); |
||||
invalidClient.close(); |
||||
logger.info("invalid im-entry {} closed.", invalidateAddress); |
||||
} |
||||
|
||||
// 添加新的 im-entry 连接
|
||||
for (String instanceAddress : instanceAddresses) { |
||||
if (entryClientMap.containsKey(instanceAddress)) { |
||||
continue; |
||||
} |
||||
// nacos 注册时, entry server 还未启动完成,延时 5s 连接
|
||||
this.scheduledExecutorService.schedule(() -> { |
||||
EntryClient entryClient = new EntryClient(instanceAddress); |
||||
logger.info("连接: {}", instanceAddress); |
||||
boolean connected = entryClient.connect(); |
||||
if (!connected) { |
||||
logger.error("im-entry {} 连接失败", instanceAddress); |
||||
return; |
||||
} |
||||
entryClientMap.put(instanceAddress, entryClient); |
||||
}, 5, TimeUnit.SECONDS); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取一个活跃的 im-entry 服务器地址 |
||||
*/ |
||||
public String getOneActiveImEntryAddress() { |
||||
if (entryClientMap.size() == 0) { |
||||
return null; |
||||
} |
||||
Collection<EntryClient> clients = entryClientMap.values(); |
||||
List<EntryClient> sortedClients = clients.stream() |
||||
.sorted(Comparator.comparing(EntryClient::getEntryConnections)) |
||||
.collect(Collectors.toList()); |
||||
logger.info("entrys: {}", sortedClients); |
||||
return sortedClients.get(0).getEntryAddress(); |
||||
} |
||||
|
||||
@Override |
||||
public int getOrder() { |
||||
return Ordered.HIGHEST_PRECEDENCE; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,37 @@
|
||||
package net.sopod.soim.entry.http.controller; |
||||
|
||||
import com.alibaba.nacos.api.naming.pojo.healthcheck.impl.Http; |
||||
import lombok.AllArgsConstructor; |
||||
import net.sopod.soim.entry.http.client.EntryClient; |
||||
import net.sopod.soim.entry.http.config.ImEntryMonitor; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.http.HttpRequest; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.util.Iterator; |
||||
|
||||
/** |
||||
* MonitorController |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-11 16:13 |
||||
*/ |
||||
@RestController |
||||
@AllArgsConstructor |
||||
@RequestMapping("/monitor") |
||||
public class MonitorController { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EntryClient.class); |
||||
|
||||
private ImEntryMonitor imEntryMonitor; |
||||
|
||||
@GetMapping("/entryHost") |
||||
public String getEntryHost() { |
||||
return imEntryMonitor.getOneActiveImEntryAddress(); |
||||
} |
||||
|
||||
} |
||||
@ -1,13 +1,30 @@
|
||||
package net.sopod.soim.entry.http.service; |
||||
|
||||
import com.alibaba.nacos.api.NacosFactory; |
||||
import com.alibaba.nacos.api.exception.NacosException; |
||||
import com.alibaba.nacos.api.naming.NamingService; |
||||
import com.alibaba.nacos.api.naming.listener.Event; |
||||
import com.alibaba.nacos.api.naming.listener.NamingEvent; |
||||
import com.alibaba.nacos.api.naming.pojo.Instance; |
||||
import net.sopod.soim.common.constant.AppConstant; |
||||
import net.sopod.soim.entry.http.config.ImEntryMonitor; |
||||
import org.apache.dubbo.common.URL; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Value; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.List; |
||||
import java.util.Properties; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* ImMonitorService |
||||
* |
||||
* @author tmy |
||||
* @date 2022-04-13 23:27 |
||||
*/ |
||||
@Service |
||||
public class ImEntryMonitorService { |
||||
|
||||
|
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,58 @@
|
||||
package net.sopod.soim.entry.http.service; |
||||
|
||||
import com.google.protobuf.MessageLite; |
||||
import io.netty.util.AttributeKey; |
||||
import org.apache.commons.lang3.tuple.Pair; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import java.util.concurrent.CompletableFuture; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
||||
/** |
||||
* MessageQueueHolder |
||||
* 发送一条消息产生一个序列号,在队列中等待响应消息 |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-02 17:29 |
||||
*/ |
||||
public class ImMessageReqHolder { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ImMessageReqHolder.class); |
||||
|
||||
public static AttributeKey<ImMessageReqHolder> IM_MESSAGE_HOLDER_KEY = AttributeKey.valueOf(ImMessageReqHolder.class, "IM_MESSAGE_HOLDER_KEY"); |
||||
|
||||
private final AtomicInteger serialNoGen; |
||||
|
||||
// TODO 超时处理
|
||||
private final ConcurrentHashMap<Integer, CompletableFuture<Object>> futureMap; |
||||
|
||||
public ImMessageReqHolder() { |
||||
this.serialNoGen = new AtomicInteger(); |
||||
this.futureMap = new ConcurrentHashMap<>(); |
||||
} |
||||
|
||||
public Pair<Integer, CompletableFuture<Object>> nextSerialNo() { |
||||
serialNoGen.compareAndSet(Integer.MAX_VALUE, 0); |
||||
int serialNo = serialNoGen.incrementAndGet(); |
||||
CompletableFuture<Object> future = new CompletableFuture<>(); |
||||
futureMap.put(serialNo, future); |
||||
logger.debug("put future: {}", serialNo); |
||||
return Pair.of(serialNo, future); |
||||
} |
||||
|
||||
/** |
||||
* TODO complete fail |
||||
*/ |
||||
public void complete(int serialNo, MessageLite msg) { |
||||
CompletableFuture<Object> completableFuture = futureMap.remove(serialNo); |
||||
if (completableFuture != null) { |
||||
completableFuture.complete(msg); |
||||
} else { |
||||
// TODO dispatch im-entry 主动消息
|
||||
logger.warn("no future wait for msg: {}", msg); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,5 @@
|
||||
FROM openjdk:11 |
||||
ARG JAR_FILE |
||||
ENV JAR_FILE ${JAR_FILE} |
||||
ADD target/${JAR_FILE} / |
||||
CMD java -jar $JAVA_OPTS /${JAR_FILE} |
||||
@ -0,0 +1,33 @@
|
||||
package net.sopod.soim.entry.handlers.monitor; |
||||
|
||||
import com.google.protobuf.MessageLite; |
||||
import lombok.AllArgsConstructor; |
||||
import net.sopod.soim.common.util.ImClock; |
||||
import net.sopod.soim.data.msg.monitor.EntryMonitor; |
||||
import net.sopod.soim.entry.server.AccountRegistry; |
||||
import net.sopod.soim.entry.server.handler.ImContext; |
||||
import net.sopod.soim.entry.server.handler.MonitorMessageHandler; |
||||
import net.sopod.soim.entry.server.session.Monitor; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
/** |
||||
* ReqEntryStatusHandler |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-11 14:39 |
||||
*/ |
||||
@Service |
||||
@AllArgsConstructor |
||||
public class ReqEntryStatusHandler extends MonitorMessageHandler<EntryMonitor.ReqEntryStatus> { |
||||
|
||||
private final AccountRegistry accountRegistry; |
||||
|
||||
@Override |
||||
public MessageLite handle(ImContext ctx, Monitor monitor, EntryMonitor.ReqEntryStatus req) { |
||||
return EntryMonitor.ResEntryStatus.newBuilder() |
||||
.setConnections(accountRegistry.getRegistryAccountSize()) |
||||
.setStatusTime(ImClock.millis()) |
||||
.build(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,38 @@
|
||||
package net.sopod.soim.entry.handlers.monitor; |
||||
|
||||
import com.google.protobuf.MessageLite; |
||||
import net.sopod.soim.common.constant.AppConstant; |
||||
import net.sopod.soim.common.util.ImClock; |
||||
import net.sopod.soim.data.msg.monitor.EntryMonitor; |
||||
import net.sopod.soim.data.msg.task.Tasks; |
||||
import net.sopod.soim.entry.delay.NetUserDelayTaskManager; |
||||
import net.sopod.soim.entry.server.handler.ImContext; |
||||
import net.sopod.soim.entry.server.handler.NetUserMessageHandler; |
||||
import net.sopod.soim.entry.server.session.Monitor; |
||||
import net.sopod.soim.entry.server.session.NetUser; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/** |
||||
* ReqMonitorAuthHandler |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-11 14:30 |
||||
*/ |
||||
@Service |
||||
public class ReqMonitorAuthHandler extends NetUserMessageHandler<EntryMonitor.ReqMonitorAuth> { |
||||
|
||||
@Override |
||||
public MessageLite handle(ImContext ctx, NetUser netUser, EntryMonitor.ReqMonitorAuth msg) { |
||||
EntryMonitor.ResMonitorAuth.Builder resBuilder = EntryMonitor.ResMonitorAuth.newBuilder(); |
||||
if (!AppConstant.IM_ENTRY_MONITOR_SECURITY.equals(msg.getSecurity())) { |
||||
// 认证失败 6 秒后会关闭连接
|
||||
return resBuilder.setSuccess(false).build(); |
||||
} |
||||
Monitor monitor = new Monitor(netUser.channel()); |
||||
netUser.upgradeMonitor(monitor); |
||||
return resBuilder.setSuccess(true).build(); |
||||
} |
||||
|
||||
} |
||||
@ -1,40 +0,0 @@
|
||||
package net.sopod.soim.entry.server; |
||||
|
||||
import net.sopod.soim.entry.config.EntryServerConfig; |
||||
import net.sopod.soim.entry.worker.WorkerGroup; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.boot.ApplicationArguments; |
||||
import org.springframework.boot.ApplicationRunner; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
/** |
||||
* ServerRunner |
||||
* |
||||
* @author tmy |
||||
* @date 2022-03-29 00:22 |
||||
*/ |
||||
// @Component
|
||||
@Deprecated |
||||
public class EntryServerRunner implements ApplicationRunner { |
||||
private static final Logger logger = LoggerFactory.getLogger(EntryServerRunner.class); |
||||
|
||||
private final EntryServerConfig config; |
||||
|
||||
public EntryServerRunner(EntryServerConfig config) { |
||||
this.config = config; |
||||
} |
||||
|
||||
@Override |
||||
public void run(ApplicationArguments args) { |
||||
EntryServer entryServer = new EntryServer(this.config.getName(), this.config.getPort()); |
||||
entryServer.startServer(err -> { |
||||
logger.error("EntryServer 启动失败:", err); |
||||
}); |
||||
Runtime.getRuntime().addShutdownHook(new Thread(entryServer::shutdown)); |
||||
|
||||
// 启动消息消费队列组
|
||||
WorkerGroup.init(config.getWorkerSize()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,30 @@
|
||||
package net.sopod.soim.entry.server.handler; |
||||
|
||||
import com.google.protobuf.MessageLite; |
||||
import net.sopod.soim.common.dubbo.exception.SoimException; |
||||
import net.sopod.soim.entry.server.session.Account; |
||||
import net.sopod.soim.entry.server.session.Monitor; |
||||
import net.sopod.soim.entry.server.session.NetUser; |
||||
|
||||
/** |
||||
* MonitorMessageHandler |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-11 14:41 |
||||
*/ |
||||
public abstract class MonitorMessageHandler<T> implements MessageHandler<T> { |
||||
|
||||
@Override |
||||
public void exec(ImContext ctx, NetUser netUser, T req) { |
||||
if (!netUser.isMonitor()) { |
||||
throw new SoimException("NetUser is not Monitor!" + netUser); |
||||
} |
||||
MessageLite res = this.handle(ctx, (Monitor) netUser, req); |
||||
if (res != null) { |
||||
netUser.writeNow(ctx, res); |
||||
} |
||||
} |
||||
|
||||
public abstract MessageLite handle(ImContext ctx, Monitor monitor, T req); |
||||
|
||||
} |
||||
@ -0,0 +1,32 @@
|
||||
package net.sopod.soim.entry.server.session; |
||||
|
||||
import io.netty.channel.Channel; |
||||
|
||||
/** |
||||
* Monitor |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-11 14:31 |
||||
*/ |
||||
public class Monitor extends NetUser { |
||||
|
||||
private long lastActive; |
||||
|
||||
public Monitor(Channel channel) { |
||||
super(channel); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isMonitor() { |
||||
return true; |
||||
} |
||||
|
||||
public void setLastActive(long lastActive) { |
||||
this.lastActive = lastActive; |
||||
} |
||||
|
||||
public long getLastActive() { |
||||
return lastActive; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
<parent> |
||||
<artifactId>so-im</artifactId> |
||||
<groupId>net.sopod</groupId> |
||||
<version>1.0.0</version> |
||||
</parent> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<artifactId>im-launcher</artifactId> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter</artifactId> |
||||
<exclusions><!-- 去掉springboot默认logback日志配置 --> |
||||
<exclusion> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-logging</artifactId> |
||||
</exclusion> |
||||
</exclusions> |
||||
</dependency> |
||||
</dependencies> |
||||
|
||||
</project> |
||||
@ -0,0 +1,15 @@
|
||||
package net.sopod.soim.launcher; |
||||
|
||||
import org.springframework.core.Ordered; |
||||
|
||||
/** |
||||
* LauncherService |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-10 21:33 |
||||
*/ |
||||
public interface LauncherService extends Ordered { |
||||
|
||||
void launcher(); |
||||
|
||||
} |
||||
@ -0,0 +1,34 @@
|
||||
package net.sopod.soim.launcher; |
||||
|
||||
import net.sopod.soim.launcher.util.PropsUtil; |
||||
import org.springframework.boot.SpringApplication; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.InputStreamReader; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.*; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* SoimApplication |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-10 19:48 |
||||
*/ |
||||
public class SoimApplication { |
||||
|
||||
public static void run(Class<?> primarySource, String...args) { |
||||
buildApplicationProperties(); |
||||
SpringApplication.run(primarySource, args); |
||||
} |
||||
|
||||
private static void buildApplicationProperties() { |
||||
List<LauncherService> launcherServices = new ArrayList<>(); |
||||
ServiceLoader.load(LauncherService.class).forEach(launcherServices::add); |
||||
launcherServices.stream().sorted(Comparator.comparing(LauncherService::getOrder)) |
||||
.collect(Collectors.toList()) |
||||
.forEach(LauncherService::launcher); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,52 @@
|
||||
package net.sopod.soim.launcher.service; |
||||
|
||||
import net.sopod.soim.launcher.LauncherService; |
||||
import net.sopod.soim.launcher.util.PropsUtil; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.InputStreamReader; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.Map; |
||||
import java.util.Properties; |
||||
|
||||
/** |
||||
* PropertiesLauncherService |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-10 21:34 |
||||
*/ |
||||
public class PropertiesLauncherService implements LauncherService { |
||||
|
||||
@Override |
||||
public void launcher() { |
||||
System.out.println("启动参数初始化..."); |
||||
PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); |
||||
Resource resource = resourceResolver.getResource("classpath:launcher.properties"); |
||||
try { |
||||
InputStream launcherPropsIn = resource.getInputStream(); |
||||
// InputStream launcherPropsIn = this.getClass().getResourceAsStream("launcher.properties");
|
||||
InputStreamReader launcherPropsReader = new InputStreamReader(launcherPropsIn, StandardCharsets.UTF_8); |
||||
Properties properties = new Properties(); |
||||
Properties sysProps = System.getProperties(); |
||||
properties.load(launcherPropsReader); |
||||
// 设置 properties
|
||||
for (Map.Entry<Object, Object> entry : properties.entrySet()) { |
||||
String key = String.valueOf(entry.getKey()); |
||||
String value = String.valueOf(entry.getValue()); |
||||
PropsUtil.setProperty(sysProps, key, value); |
||||
} |
||||
} catch (IOException e) { |
||||
throw new IllegalStateException("启动参数初始化失败", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int getOrder() { |
||||
return Ordered.HIGHEST_PRECEDENCE; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,21 @@
|
||||
package net.sopod.soim.launcher.util; |
||||
|
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import java.util.Properties; |
||||
|
||||
/** |
||||
* PropsUtil |
||||
* |
||||
* @author tmy |
||||
* @date 2022-06-10 20:03 |
||||
*/ |
||||
public class PropsUtil { |
||||
|
||||
public static void setProperty(Properties props, String key, String value) { |
||||
if (StringUtils.isEmpty(props.getProperty(key))) { |
||||
props.setProperty(key, value); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1 @@
|
||||
net.sopod.soim.launcher.service.PropertiesLauncherService |
||||
@ -0,0 +1,55 @@
|
||||
# dubbo registry nacos |
||||
dubbo.registry.address=nacos://ip:port |
||||
dubbo.registry.group=so-im |
||||
|
||||
|
||||
# shardingsphere datasource (before DataSourceAutoConfiguration) |
||||
spring.shardingsphere.datasource.names=ds1 |
||||
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource |
||||
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver |
||||
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://ip:port/soim_db?serverTimezone=GMT%2B8 |
||||
spring.shardingsphere.datasource.ds1.username=username |
||||
spring.shardingsphere.datasource.ds1.password=password |
||||
spring.shardingsphere.datasource.ds1.minimumIdle=1 |
||||
spring.shardingsphere.datasource.ds1.maximumPoolSize=4 |
||||
spring.shardingsphere.datasource.ds1.connectionTimeout=2000 |
||||
spring.shardingsphere.datasource.ds1.idleTimeout=300000 |
||||
spring.shardingsphere.datasource.ds1.maxLifeTime=600000 |
||||
spring.shardingsphere.datasource.ds1.keepaliveTime=30000 |
||||
spring.shardingsphere.datasource.ds1.validateTimeout=2000 |
||||
|
||||
|
||||
# spring datasource DataSourceAutoConfiguration |
||||
spring.datasource.type=com.zaxxer.hikari.HikariDataSource |
||||
spring.datasource.url=jdbc:mysql://ip:port/soim_db?serverTimezone=GMT%2B8 |
||||
spring.datasource.username=username |
||||
spring.datasource.password=password |
||||
spring.datasource.hikari.minimum-idle=1 |
||||
spring.datasource.hikari.maximum-pool-size=8 |
||||
spring.datasource.hikari.connection-timeout=2000 |
||||
# 5分钟空闲关闭 |
||||
spring.datasource.hikari.idle-timeout=300000 |
||||
# 10分钟最大存活时间 |
||||
spring.datasource.hikari.max-lifetime=600000 |
||||
spring.datasource.hikari.validation-timeout=2000 |
||||
# datasource.hikari.connection-init-sql: select 1 |
||||
# 连接存活时间,小于maxLifetime, 最小30秒, 空闲30秒后移除连接测试通过再添加回池 |
||||
spring.datasource.hikari.keepalive-time=30000 |
||||
|
||||
|
||||
# rabbitmq |
||||
spring.rabbitmq.host=ip |
||||
spring.rabbitmq.port=port |
||||
spring.rabbitmq.username=username |
||||
spring.rabbitmq.password=password |
||||
|
||||
|
||||
# redis |
||||
redis.host=ip |
||||
redis.port=port |
||||
redis.password=password |
||||
redis.lettuce.pool.max-active=100 |
||||
redis.lettuce.pool.max-idle=100 |
||||
redis.lettuce.pool.max-wait=1000 |
||||
redis.lettuce.pool.min-idle=2 |
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,29 @@
|
||||
syntax="proto3"; |
||||
|
||||
option java_multiple_files = false; |
||||
option java_outer_classname = "EntryMonitor"; |
||||
|
||||
package net.sopod.soim.data.msg.monitor; |
||||
|
||||
// im-entry 监控消息 |
||||
|
||||
message ReqMonitorAuth { |
||||
// string host = 1; |
||||
string security = 2; |
||||
} |
||||
|
||||
message ResMonitorAuth { |
||||
bool success = 1; |
||||
} |
||||
|
||||
message ReqEntryStatus { |
||||
|
||||
} |
||||
|
||||
message ResEntryStatus { |
||||
// 状态生成时间 |
||||
int64 statusTime = 5; |
||||
// 连接数 |
||||
int32 connections = 6; |
||||
// TODO...其他信息 |
||||
} |
||||
@ -1,30 +1,34 @@
|
||||
net.sopod.soim.data.msg.group.Group$ReqUserGroups=10000 |
||||
net.sopod.soim.data.msg.group.Group$GroupInfo=10001 |
||||
net.sopod.soim.data.msg.user.UserMsg$UserInfo=10000 |
||||
net.sopod.soim.data.msg.group.Group$ReqGroupUsers=10001 |
||||
net.sopod.soim.data.msg.group.Group$ResSearchGroup=10002 |
||||
net.sopod.soim.data.msg.hello.HelloPB$Hello=10003 |
||||
net.sopod.soim.data.msg.user.Friend$ReqAddFriend=10004 |
||||
net.sopod.soim.data.msg.auth.Auth$ResTokenAuth=10005 |
||||
net.sopod.soim.data.msg.chat.Chat$TextChat=10006 |
||||
net.sopod.soim.data.msg.user.AccountSearch$ReqAccountSearch=10007 |
||||
net.sopod.soim.data.msg.net.HeartBeat$Pong=10008 |
||||
net.sopod.soim.data.msg.auth.Auth$ReqTokenAuth=10009 |
||||
net.sopod.soim.data.msg.group.Group$ReqGroupUsers=10010 |
||||
net.sopod.soim.data.msg.user.UserGroup$ResOnlineUserList=10011 |
||||
net.sopod.soim.data.msg.user.AccountSearch$ResAccountSearch=10012 |
||||
net.sopod.soim.data.msg.group.Group$ReqCreateGroup=10013 |
||||
net.sopod.soim.data.msg.user.Friend$ResFriendList=10014 |
||||
net.sopod.soim.data.msg.group.Group$ReqSearchGroup=10015 |
||||
net.sopod.soim.data.msg.group.Group$ReqGroupMessage=10016 |
||||
net.sopod.soim.data.msg.group.Group$ResGroupUsers=10017 |
||||
net.sopod.soim.data.msg.hello.HelloPB$World=10018 |
||||
net.sopod.soim.data.msg.group.Group$ReqJoinGroup=10019 |
||||
net.sopod.soim.data.msg.user.UserGroup$ReqOnlineUserList=10020 |
||||
net.sopod.soim.data.msg.group.Group$UserInfo=10021 |
||||
net.sopod.soim.data.msg.group.Group$ResGroupMessage=10022 |
||||
net.sopod.soim.data.msg.user.UserMsg$UserInfo=10023 |
||||
net.sopod.soim.data.msg.group.Group$ResUserGroups=10024 |
||||
net.sopod.soim.data.msg.user.Friend$ResAddFriend=10025 |
||||
net.sopod.soim.data.msg.net.HeartBeat$Ping=10026 |
||||
net.sopod.soim.data.msg.task.Tasks$NetUserDelayCloseTask=10027 |
||||
net.sopod.soim.data.msg.user.Friend$ReqFriendList=10028 |
||||
net.sopod.soim.data.msg.common.Res$ResState=10029 |
||||
net.sopod.soim.data.msg.user.UserGroup$ReqOnlineUserList=10004 |
||||
net.sopod.soim.data.msg.group.Group$ResUserGroups=10005 |
||||
net.sopod.soim.data.msg.user.Friend$ResFriendList=10006 |
||||
net.sopod.soim.data.msg.task.Tasks$NetUserDelayCloseTask=10007 |
||||
net.sopod.soim.data.msg.group.Group$ReqSearchGroup=10008 |
||||
net.sopod.soim.data.msg.net.HeartBeat$Pong=10009 |
||||
net.sopod.soim.data.msg.chat.Chat$TextChat=10010 |
||||
net.sopod.soim.data.msg.group.Group$ReqGroupMessage=10011 |
||||
net.sopod.soim.data.msg.user.UserGroup$ResOnlineUserList=10012 |
||||
net.sopod.soim.data.msg.common.Res$ResState=10013 |
||||
net.sopod.soim.data.msg.user.AccountSearch$ResAccountSearch=10014 |
||||
net.sopod.soim.data.msg.group.Group$ReqJoinGroup=10015 |
||||
net.sopod.soim.data.msg.monitor.EntryMonitor$ResEntryStatus=10016 |
||||
net.sopod.soim.data.msg.hello.HelloPB$World=10017 |
||||
net.sopod.soim.data.msg.user.Friend$ReqFriendList=10018 |
||||
net.sopod.soim.data.msg.net.HeartBeat$Ping=10019 |
||||
net.sopod.soim.data.msg.monitor.EntryMonitor$ReqEntryStatus=10020 |
||||
net.sopod.soim.data.msg.user.AccountSearch$ReqAccountSearch=10021 |
||||
net.sopod.soim.data.msg.user.Friend$ResAddFriend=10022 |
||||
net.sopod.soim.data.msg.group.Group$ResGroupUsers=10023 |
||||
net.sopod.soim.data.msg.group.Group$ReqCreateGroup=10024 |
||||
net.sopod.soim.data.msg.group.Group$ResGroupMessage=10025 |
||||
net.sopod.soim.data.msg.monitor.EntryMonitor$ReqMonitorAuth=10026 |
||||
net.sopod.soim.data.msg.group.Group$ReqUserGroups=10027 |
||||
net.sopod.soim.data.msg.group.Group$UserInfo=10028 |
||||
net.sopod.soim.data.msg.user.Friend$ReqAddFriend=10029 |
||||
net.sopod.soim.data.msg.auth.Auth$ReqTokenAuth=10030 |
||||
net.sopod.soim.data.msg.monitor.EntryMonitor$ResMonitorAuth=10031 |
||||
net.sopod.soim.data.msg.group.Group$GroupInfo=10032 |
||||
net.sopod.soim.data.msg.auth.Auth$ResTokenAuth=10033 |
||||
|
||||
Loading…
Reference in new issue