1.7.2 社区版服务端
这里以社区版服务端源码(版本v1.10)为例,理解服务端SDK的设计。后端使用springboot+mybatis开发,依赖echatim-server-broker-community 中间件。
1.源码架构
`-- com
|-- annotation
| |-- MethodFor.java # socket.io api注解Method
| |-- Permission.java
| `-- TopicFor.java # socket.io api注解Topic
|-- commom
| |-- AppRespError.java # 社区版后端错误码定义
| |-- ConcurrentLimitedQueue.java
| |-- Config.java # springboot环境变量获取工具类
| |-- DBConst.java # 业务常量定义
| |-- DBEnum.java
| |-- LocalFileRespError.java # 社区版文件服务器错误码定义
| |-- LocalFileRoute.java
| |-- NumberGenerator.java
| |-- PermissionConst.java
| `-- Topic.java # 社区版Topic的定义, api规约定义
|-- echatim
| |-- ApplicationWrapper.java
| |-- CommonMapper.java
| |-- broker
| | |-- AppBrokerHook.java
| | |-- BrokerConfig.java
| | |-- SocketIOBroker.java # socket.io服务启动,Topic绑定,消息收发
| | |-- localsvc
| | | |-- ITopicSender.java
| | | |-- TopicDispatcher.java # 处理socket.io接受到的消息,转发到Service
| | | `-- TopicSender.java # 把消息通过socket.io发送出去
| | `-- storage
| | |-- AppStorageEntry.java
| | `-- event
| | |-- ClientDisconnectMessage.java
| | |-- ClientOnlineEvent.java
| | `-- LoginEventMessage.java
| |-- config
| | |-- EventBusConfig.java
| | |-- FileDownloadConfig.java
| | |-- MybatisPlusConfig.java
| | |-- SocketIOConfig.java
| | |-- Swagger2Config.java
| | |-- TopicFilterConfig.java
| | `-- ValidateConfig.java
| |-- controller # http api定义
| | |-- ClientController.java
| | |-- HistoryMessageController.java
| | |-- LoginController.java
| | |-- MessageController.java
| | |-- RoomController.java
| | |-- UserController.java
| | |-- UserRelationController.java
| | `-- fileserver # fileserver http api定义
| | |-- FileApi.java
| | |-- FileConfigDTO.java
| | |-- FileServerController.java
| | |-- FileSignatureCache.java
| | |-- FileUploadCacheDTO.java
| | |-- FileUploadCallbackForm.java
| | |-- FileUploadRequestDTO.java
| | `-- FileUploadRequestForm.java
| |-- dto
... 模型数据 ...
| |-- entity
... 模型数据 ...
| |-- filter
| | |-- HttpTopicInterceptor.java
| | |-- ProtocolHandlerInterceptor.java
| | `-- TopicInterceptor.java
| |-- form
... 模型数据 ...
| |-- helper
| | `-- PageHelper.java
| |-- mapper
| | |-- FileInfoMapper.java
| | |-- MessageMapper.java
| | |-- MessageStableOfflineMapper.java
| | |-- RoomMapper.java
| | |-- RoomUserMapper.java
| | |-- SdkAppMapper.java
| | |-- SdkUserMapper.java
| | |-- UserMapper.java
| | `-- UserRelationMapper.java
| `-- service # 业务逻辑实现
| `-- app
| |-- BaseService.java
| |-- HistoryMessageService.java
| |-- LoginService.java
| |-- MessageService.java
| |-- MessageStableOfflineService.java
| |-- RoomService.java
| |-- RoomUserService.java
| |-- SdkAppService.java
| |-- SdkUserService.java
| |-- UserRelationService.java
| |-- UserService.java
| `-- fileserver
| `-- FileInfoService.java
|-- event
| |-- AppStartup.java
| `-- EventBus.java
|-- exception # 一些全局异常, 若表单校验,业务异常等
| |-- FormValidationException.java
| |-- HttpGlobalExceptionAdvice.java
| |-- ServiceException.java
| `-- SocketGlobalExceptionAdvice.java
`-- utils
|-- Beans.java
|-- Pair.java
|-- ProtocolAnnotationUtils.java
|-- Streams.java
|-- Triple.java
|-- UUIDUtils.java
`-- mapper
|-- IDMapper.java
|-- KeyMapper.java
`-- Mapper.java
2.echatim-server-broker-community 中间件的功能
- 转发中间件: 路由转发消息到socket.io用户所在的机器(单机, 机器两种情况的不同处理)
- 存储中间件: 抽象化sdk用到的存储插件(guava, redis等)
- 消息中间件: 抽象化sdk用到的消息插件(eventBus, kafka等)
- 一些常用的工具类
3.接收消息, 定义http api与socket.io api统一的规范约定
前端与后端使用http api, socket.io api 两种通讯,为统一两种api,定义了TOPIC, METHOD 两个概念来约定。
TOPIC: 话题, 可以理解成一个业务模块
METHOD: TOPIC下有多个METHOD, 一个METHOD对应后端的一个业务方法。
对于socket.io api: 后端是通过TOPIC, METHOD两个关键字来定位到是使用哪个业务方法来处理这个api的。如以下的登录认证socket.io api 定义。
@TopicFor(value = Topic.CONNECTION.name) // 定义该业务模块的TOPIC:CONNECTION
@Service
public class LoginService extends ServiceImpl<UserMapper, User> {
... 略 ...
@MethodFor(value = Topic.CONNECTION.METHOD.AUTHORITY_REQUEST, consumer = UserLoginForm.class) // 绑定该业务方法处理对于的METHOD:AUTHORITY_REQUEST
public Resp<String> authority(@Valid UserLoginForm userLoginForm){
}
... 略 ...
}
后端从socket.io接受到的客户端消息中,能获取到Topic, Method两个变量定义,然后通过 @TopicFor, @MethodFor 两个注解,找到对应的匹配的业务处理方法,这一过程是TopicDispatcher中处理的。
对于http api: 后端也是通过TOPIC, METHOD两个关键字来定位到是使用哪个Controller method来处理这个api的。
@RestController
@RequestMapping(Topic.CONNECTION.base_uri)
public class LoginController extends ApiController {
@ApiOperation(value = "IM用户登录-登录")
@PostMapping("/"+Topic.CONNECTION.METHOD.AUTHORITY_REQUEST)
public Resp<String> sdkLogin(@RequestBody @Valid UserLoginForm userLoginForm) {
}
}
后端通过拼接的Topic.CONNECTION.base_uri + "/" + AUTHORITY_REQUEST 能定位到对应的Controller method.
通过这样的约定与定义,可以最大化复用后端的Service 业务代码。
4.socket.io接受与发送
socket.io的接受与发送主要在SocketIOBroker, TopicDispatcher, TopicSender 三个类中处理。
SocketIOBroker: 1. 负责socket.io 协议服务在springboot的环境下启动,销毁; 2. 绑定socket.io 要监听的所有Topic定义; 3. 启动对应的中间件(存储, 事件, 转发模块等)
TopicDispatcher: 处理消息输入, 将输入的消息转发到对应Service中的method 中处理,并返回处理结果。
TopicSender: 给定socket.io 的clientId, 向该client发送消息。业务并不会直接调用TopicSender 发消息,而是交给转发中间件来处理。
5.发送即时消息的流程
发送一个即时消息的流程如下: 客户端调用发消息的API(sendMessage) -> 后端保存消息到数据库,并创建ClusterDispatcherEvent对象,发送ClusterDispatcherEvent对象到转发中间件-> 转发中间件查找socket.io client 所在的机器,往该机器发送消息队列消息 -> socket.io client 所在机器发消息给客户端
对于单台机器的情况,所有的socket.io 客户端都连接到同一台机器,因此流程可以简化,直接在当前机器上查找clientId与登录用户账号的映射关系,把消息转发到该用户即可。