Skip to content

Latest commit

 

History

History
342 lines (266 loc) · 9.02 KB

design.zh_CN.md

File metadata and controls

342 lines (266 loc) · 9.02 KB

deepin社区应用

请使用支持 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

关键API接口

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