请使用支持 plantuml 渲染的 markdown 阅读器 vscode plugin
(调度服务)
(管理服务)
(登录服务)
(后台管理界面)
database 数据库
cloud 分发服务 {
node 分发节点1
node 分发节点2
}
(客户端1)
(客户端2)
(客户端3)
(客户端4)
后台管理界面 -r-> 管理服务
管理服务 --> 数据库: 写入消息
调度服务 --> 数据库: 获取节点列表
调度服务 --> 分发服务: 调度节点
登录服务 --> 数据库: 写入用户信息
分发节点1 -u-> 数据库: 读取消息
分发节点2 -u-> 数据库: 读取消息
客户端1 -u-> 分发节点1
客户端2 -u-> 分发节点1
客户端3 -u-> 分发节点2
客户端4 -u-> 分发节点2
客户端在连接分发节点之前,从调度服务获取分发节点的地址,调度服务选取负载较低的分发节点返回给客户端。
调度服务在把节点返回给客户端的时候,刷新了节点的负载。
frame 消息渠道 {
(用户消息)
(公共消息)
(群组消息)
}
frame 消息类型 {
(论坛私信)
(用户反馈)
(新闻)
(调查问卷)
(内测渠道)
(活动通知)
}
用户消息 --> (论坛私信)
用户消息 --> (用户反馈)
公共消息 --> (新闻)
公共消息 --> (调查问卷)
群组消息 --> (活动通知)
群组消息 --> (内测渠道)
群组消息 --> (新闻)
群组消息 --> (调查问卷)
消息分为公共消息、群组消息和用户消息:
公共渠道是默认订阅的,使用uuid做唯一标识,因为范围太广,刷新频率会设置的比较低,数据可加CDN缓存。
群组渠道为管理员手动为客户端分组,便于组播消息,和公共渠道一样使用uuid做维一标识。公共渠道算是特殊的群组渠道。
用户渠道仅在用户登录后订阅,使用用户ID做唯一标识,范围较小,刷新频率相对会设置的高一些,数据不缓存。
frame 消息状态 {
(未分发)
(未读)
(已读)
(已删除)
}
未分发 --> 未读
未读 -> 已读
已读 -u-> 已删除
状态解释:
未分发:客户端还未接收,消息标记为”未分发“
未读:消息已被客户端接收,但用户还未阅读
已读:消息已被用户阅读
已删除:消息已被用户删除
仅用户消息由服务端存储状态,群组消息和公共消息仅在客户端存储状态
frame 用户消息状态 {
(在线)
(离线)
}
在线 <==> 离线: 30分钟内无连接,标记离线
客户端分为前台进程和守护进程两个二进制程序,daemon负责从服务器监听消息变动,在消息变动后唤醒客户端。
消息订阅设计成可配置可扩展的,便于其他应用也可接入
@startuml
前台进程 -> 系统: 写入订阅配置
note right
包含感兴趣的消息类型和dbus回调接口
客户端安装时默认订阅新闻和调查问卷两种消息
end note
守护进程 -> 系统: 写入开启启动配置
note right
安装时设置开机自启动
end note
...开机...
系统 -> 系统: 用户登录
系统 -> 守护进程: 启动
守护进程 -> 系统: 生成并存储uuid
note right
作为客户端标识
end note
守护进程 -> 系统: 读取订阅配置
@enduml
客户端在安装时会设置开机自启,并添加两个消息类型的订阅
@startuml
-> 守护进程: 启动
守护进程 -> 守护进程: 从配置获取调度服务地址
loop 循环
守护进程 -> 调度服务: 获取节点
note right
传递客户端uuid,版本号,系统版本号信息。
end note
调度服务 -> 调度服务: 根据负载情况选取节点
调度服务 --> 守护进程: 返回一个节点
note right
包含节点服务地址和节点可用时长(默认2小时)
超过时长后客户端重新获取节点
还包含客户端需要监听的多个渠道(包含公共渠道和分组渠道)
end note
group 并行多个群组渠道
loop 循环直到超过节点可用时长
守护进程 -> 分发服务: 检查渠道是否有新内容
note right
传递本地缓存的change id
end note
return
note right
如果有变动返回各消息类型的change id
否则返回304状态并不返回数据
还包含循环等待时间(默认10分钟)
在等待时间之后,才能再次获取消息
end note
守护进程 -> 前台进程: 唤醒
守护进程 -> 守护进程: 保存新的change id
前台进程 -> 分发服务: 获取消息列表
note right
只能获取当前有效的消息
end note
前台进程 -> 前台进程: 标记消息未读已读
前台进程 -> 前台进程: 标记消息删除
前台进程 -> 分发服务: 标记消息已读
end group
end loop
end loop
@enduml
节点的刷新时间和消息的刷新时间都由服务端控制,可以根据负载情况动态调节。
守护进程仅获取消息是否有更新,然后唤醒订阅的进程,自身并不获取消息内容。
公共消息不记录客户端的消息状态,仅在消息阅读后,记录已读以用于分析。
用户 -> 客户端: 点击登录
客户端 -> 登录服务: 获取oauth2信息
客户端 -> deepinid: 传递oauth2信息
deepinid -> 用户: 显示登录界面
用户 -> deepinid: 输入账户密码登录
deepinid --> 客户端: 返回code
客户端 -> 登录服务: 使用code获取token
登录服务 -> deepinid服务: 检查code
登录服务 -> deepinid服务: 获取用户信息
客户端 <-- 登录服务: 返回token
客户端 -> 登录服务: 使用token获取用户信息
@startuml
-> 守护进程: 启动
守护进程 -> 守护进程: 从配置获取调度服务地址
loop 循环
守护进程 -> 调度服务: 获取节点
note right
传递用户token,客户端uuid,版本号,系统版本号信息。
end note
调度服务 -> 调度服务: 根据负载情况选取节点
调度服务 --> 守护进程: 返回一个节点
note right
包含节点服务地址和节点可用时长(默认2小时)
超过时长后客户端重新获取节点
还包含客户端需要监听的多个渠道(包含公共渠道和分组渠道)
end note
loop 循环直到超过节点可用时长
守护进程 -> 分发服务: 检查用户渠道是否有新内容
note right
传递本地缓存的change id
end note
return
note right
如果有变动返回各消息类型的change id
否则返回304状态并不返回数据
还包含循环等待时间(默认10分钟)
在等待时间之后,才能再次获取消息
end note
守护进程 -> 前台进程: 唤醒
守护进程 -> 守护进程: 保存新的change id
前台进程 -> 分发服务: 获取消息列表
note left
支持获取历史消息,翻页查看
end note
前台进程 -> 分发服务: 标记消息未读已读
前台进程 -> 分发服务: 标记消息删除
前台进程 -> 分发服务: 标记消息已读
end loop
end loop
@enduml
和公共渠道的区别主要在于所有接口都需要传递用户ID,以识别用户信息,消息的状态由服务器保存,可以在多个机器之间同步。
用户 -> 客户端: 点击互动交流
客户端 -> 登录服务: 获取论坛登录token,10s过期
客户端 -> 论坛: 在浏览器打开论坛,附带论坛登录token
论坛 -> 客户端: 识别token,确定登录状态
token使用jwt格式,有非对称签名保证防篡改,防伪造。
token过期时间应该尽可能短,避免在浏览器历史记录里泄露。
管理员 -> 管理服务: 创建消息
管理服务 -> 数据库: 写入消息内容
管理服务 -> redis: 刷新消息类型的change ID
管理服务 -> redis: 刷新消息渠道的change ID
change ID 用于表示消息的最后修改,通过对比change id判断渠道是否有新的消息。
论坛 -> 管理服务: 创建用户消息
管理服务 -> 数据库: 写入消息
管理服务 -> redis: 刷新消息类型ID
GET /api/public/machine/:machine_id/node
获取有效节点和可用渠道列表
GET /api/public/channel/:channel_id/topics
获取某个渠道的消息类型列表
GET /api/public/channel/:channel_id/topic/:topic_id/messages
获取某个类型的消息列表
GET /api/public/message/:id
获取单个消息的详细信息
PUT /api/public/message/:id/status
标记已读消息
GET /api/user/login
获取登录所需配置
POST /api/user/login
获取登录凭证
/api/user/topics
获取用户消息类型列表
/api/user/topic/:topic_id/message
获取某个类型的消息列表
GET /api/user/message/:id
获取用户消息的详细信息
PUT /api/public/message/:id/status
标记消息状态(已读、未读)
DELETE /api/public/message/:id/status
删除消息
POST /api/admin/news