背景
在一個(gè)系統(tǒng)中,資源,數(shù)據(jù)會(huì)持續(xù)不斷的更新。而用戶如果需要知道這些數(shù)據(jù)的更新,就需要一個(gè)系統(tǒng),將系統(tǒng)中不斷更新的數(shù)據(jù)流,發(fā)送給相關(guān)的用戶。
這個(gè)系統(tǒng)應(yīng)該具備如下的幾個(gè)功能:
- 可以根據(jù)配置信息,在資源,數(shù)據(jù)更新時(shí),生成更新的消息。
- 可以將消息發(fā)送給相關(guān)的用戶。
- 用戶可以根據(jù)自定義的配置,配置是否接收消息推送。
針對(duì)這幾個(gè)需求,來(lái)設(shè)計(jì)一個(gè)可靈活擴(kuò)展的消息系統(tǒng)。
系統(tǒng)設(shè)計(jì)
將系統(tǒng)拆分成2個(gè)部分:
- 消息的產(chǎn)生
- 消息的發(fā)送
消息的產(chǎn)生
當(dāng)資源的更新,觸發(fā)了資源上的消息產(chǎn)生規(guī)則,就會(huì)產(chǎn)生消息。對(duì)于資源的更新,可能會(huì)觸發(fā)多條消息產(chǎn)生規(guī)則,產(chǎn)生多條通知,發(fā)給不同的用戶不同的消息。所以這里每條消息產(chǎn)生規(guī)則,對(duì)應(yīng)一個(gè)消息模版。
消息的產(chǎn)生規(guī)則,由動(dòng)作觸發(fā)規(guī)則(rule),接受者(recipient)2部分組成。
- 動(dòng)作觸發(fā)規(guī)則是一個(gè)主謂短語(yǔ)(executor + action),或者主謂賓短語(yǔ)(executor + action + target),記錄了誰(shuí)對(duì)資源做了什么操作。消息產(chǎn)生規(guī)則作用于某一類的資源(executor / target)。這里的資源是一個(gè)抽象的對(duì)象類型。
- 接收者這里指的是某一個(gè)抽象的角色,描述了接受者與被操作資源或者動(dòng)作執(zhí)行者的關(guān)系。
比如:
- xxx@我
rule | recipient |
---|---|
somebody_@_somebody | user |
- xxx評(píng)論了我的一條微博
rule | recipient |
---|---|
somebody_comment_weibo | author |
- xxx給我發(fā)了一條私信
rule | recipient |
---|---|
somebody_send_message | user |
- 你的好友xxx登錄了
rule | recipient |
---|---|
somebody_login | friend |
還有一種情況,當(dāng)執(zhí)行了某種操作,觸發(fā)了多條條消息產(chǎn)生規(guī)則
比如:
當(dāng)管理員A刪除了一條微博,觸發(fā)了2條消息產(chǎn)生規(guī)則
- 系統(tǒng)刪除了你的微博
rule | recipient |
---|---|
somebody_delete_weibo | author |
- 系統(tǒng)刪除了你收藏的微博
rule | recipient |
---|---|
somebody_delate_weibo | follower |
消息的發(fā)送
系統(tǒng)根據(jù)用戶的訂閱,將消息發(fā)送給訂閱了消息的用戶。
用戶的訂閱,是用戶對(duì)具體資源(target / executor)上消息產(chǎn)生規(guī)則的訂閱((executor + action) / executor + action + target)。這里用戶訂閱的是具體的資源對(duì)象。
比如:
- xxx@我
特定資源 | 消息產(chǎn)生規(guī)則 |
---|---|
我(target) | somebody_@_someone |
- xxx評(píng)論了我的一條微博
特定資源 | 消息產(chǎn)生規(guī)則 |
---|---|
我的微博(target) | somebody_comment_weibo |
- xxx給我發(fā)了一條私信
特定資源 | 消息產(chǎn)生規(guī)則 |
---|---|
我(target) | sombody_send_message |
- 你的好友xxx登錄了
特定資源 | 消息產(chǎn)生規(guī)則 |
---|---|
好友(executor) | somebody_login |
消息的發(fā)送方式
消息的發(fā)送方式分為系統(tǒng)推送,和用戶拉取
- 推 push:推是當(dāng)消息產(chǎn)生時(shí),系統(tǒng)自動(dòng)將消息推送給訂閱用戶。
- 拉 pull:拉是當(dāng)消息產(chǎn)生時(shí),系統(tǒng)不自動(dòng)將消息推送給用戶,而是當(dāng)用戶主動(dòng)觸發(fā)拉取的動(dòng)作時(shí),獲得新的消息。
系統(tǒng)運(yùn)行發(fā)方式
當(dāng)某個(gè)具體資源的更新,觸發(fā)了這個(gè)資源上的消息產(chǎn)生規(guī)則,產(chǎn)生消息。系統(tǒng)再根據(jù)用戶在這個(gè)資源上的訂閱,將消息發(fā)送給訂閱了這個(gè)資源的消息的用戶。產(chǎn)生的消息和用戶通過(guò)用戶訂閱關(guān)聯(lián)起來(lái)。用戶可以根據(jù)訂閱的設(shè)置,來(lái)設(shè)置是否接收主動(dòng)推送的消息。
- 用戶設(shè)置好消息接收規(guī)則,確定哪些消息接收推送,哪些消息主動(dòng)拉取
- 系統(tǒng)自動(dòng)設(shè)置好各類資源的消息產(chǎn)生規(guī)則
- 資源在系統(tǒng)中創(chuàng)建時(shí),系統(tǒng)根據(jù)用戶的訂閱規(guī)則,位用戶訂閱新資源的各類消息創(chuàng)建規(guī)則,生成用戶訂閱
- 當(dāng)資源更新,觸發(fā)了資源的消息產(chǎn)生規(guī)則,產(chǎn)生消息
- 系統(tǒng)根據(jù)用戶的訂閱,確定把哪些消息發(fā)送給用戶,保存到用戶消息列表中
- 系統(tǒng)根據(jù)用戶設(shè)置的消息接收規(guī)則,決定是推送消息給用戶,還是讓用戶拉取消息
系統(tǒng)建模
根據(jù)以上的分析,可以知道,系統(tǒng)中有以下幾個(gè)實(shí)體類:
- 資源(target/executor)
資源可以是是系統(tǒng)中的各類對(duì)象模型
這里的資源可能是被操作的對(duì)象,也可能是動(dòng)作的執(zhí)行者。例如:
- 好友登錄:target 是 好友
- 有人評(píng)論了文章:target 是 文章
- 微博被刪除:target 是 微博
- 消息產(chǎn)生規(guī)則(rule)/消息訂閱規(guī)則
// 消息產(chǎn)生規(guī)則
[
{
'rule': 'user_update_weibo', // 觸發(fā)規(guī)則
'targetType': 'weibo', // 觸發(fā)規(guī)則作用的對(duì)象類型
'relationship': 'follow', // 觸發(fā)對(duì)象與訂閱對(duì)象的關(guān)系
'role': 'user', // 訂閱對(duì)象在系統(tǒng)中的角色
'obtainType': 0 // 消息的發(fā)送方式:0.推,1.拉
},
{
'rule': 'user_update_weibo',
'targetType': 'weibo',
'relationship': 'author',
'role': 'user',
'obtainType': 0
},
{
'rule': 'user_comment_weibo',
'targetType': 'weibo',
'relationship': 'author',
'role': 'user',
'obtainType': 0
},
{
'rule': 'user_delete_weibo',
'targetType': 'weibo',
'relationship': 'author',
'role': 'user',
'obtainType': 0
}
{
'rule': 'user_delete_weibo',
'targetType': 'weibo',
'relationship': 'follow',
'role': 'user',
'obtainType': 0
},
{
'rule': 'user_delete_weibo',
'targetType': 'weibo',
'relationship': 'admin',
'role': 'admin',
'obtainType': 1
},
{
'rule': 'user_follow_someone',
'targetType': 'user',
'relationship': 'self',
'role': 'user',
'obtainType': 0
},
{
'rule': 'user_publish_weibo',
'targetType': 'user',
'relationship': 'follow',
'role': 'user',
'obtainType': 0
},
{
'rule': 'user_login',
'targetType': 'user',
'relationship': 'friend',
'role': 'user',
'obtainType': 0
}
]
消息生成規(guī)則由如下幾部分組成:
- rule: 觸發(fā)規(guī)則
觸發(fā)規(guī)則用一個(gè)字符串表示,作為規(guī)則的唯一標(biāo)識(shí),也可以從字面上直接看出消息的規(guī)則,便于理解。每條規(guī)則是一個(gè)主謂短語(yǔ)(executor_action),或者主謂賓短語(yǔ)(executor_action_target)。
消息產(chǎn)生規(guī)則作用于某一類資源。但是對(duì)于不同的用戶,和資源的關(guān)系不同,收到的消息不同,所以生成消息的規(guī)則也不同。所以通過(guò)用戶和資源的關(guān)系,分組資源的消息產(chǎn)生規(guī)則。
- targetType: 觸發(fā)規(guī)則作用的對(duì)象類型
記錄觸發(fā)規(guī)則作用的對(duì)象類型,可以通過(guò)對(duì)象類型,查出這個(gè)對(duì)象上所有的觸發(fā)規(guī)則。
- relationship: 觸發(fā)對(duì)象與訂閱對(duì)象的關(guān)系
規(guī)則作用資源和訂閱者之間的關(guān)聯(lián)關(guān)系類別。當(dāng)資源被創(chuàng)建時(shí),或者用戶和資源發(fā)生關(guān)聯(lián)時(shí),根據(jù)用戶和資源的關(guān)系類型,訂閱不同的消息規(guī)則。
- role: 訂閱對(duì)象在系統(tǒng)中的角色
對(duì)于不同角色的用戶,對(duì)于同一種資源,需要配置的規(guī)則也不一樣,比如不需要把管理員的訂閱規(guī)則,保存到普通用戶設(shè)置里。這里的role,可以和系統(tǒng)里的角色系統(tǒng)相關(guān)聯(lián),為不同的角色,配置不同的訂閱規(guī)則。
- obtainType: 消息的發(fā)送方式:0.推,1.拉
消息產(chǎn)生規(guī)則相對(duì)固定,并且每次增加,需要實(shí)現(xiàn)相應(yīng)的消息模版,無(wú)法通過(guò)增加配置自動(dòng)生效。所以這里直接使用配置文件或者配置類保存信息,比較簡(jiǎn)單方便,不需要對(duì)外提供管理編輯的接口。
- 消息(notify)
create table notify (
id int,
rule varchar comment '消息產(chǎn)生規(guī)則',
obtain_type int comment '消息的獲取方式:0.推,1.拉',
target_id int comment '消息產(chǎn)生規(guī)則作用的對(duì)象',
target_type int comment '消息產(chǎn)生規(guī)則作用的對(duì)象類型',
content varchar comment '消息內(nèi)容',
sender_id int comment '消息發(fā)送者id',
sender_type int comment '消息發(fā)送者類型:0.系統(tǒng),1.用戶,...',
notify_type int comment '消息類型:0.公告,1.新聞,2.活動(dòng),3.feed,...',
create_time timestamp comment '消息創(chuàng)建時(shí)間'
) comment = '消息'
說(shuō)明下幾個(gè)重點(diǎn)的字段:
- target_id,可能是動(dòng)作操作對(duì)象的id,也可能是動(dòng)作執(zhí)行者的id。
- target_type,通過(guò)target_type,區(qū)分是不同的被操作對(duì)象還有動(dòng)作執(zhí)行者。
- sender_id,當(dāng)是系統(tǒng)發(fā)送的消息時(shí),這里可以為空,或者某個(gè)特殊id。
- sender_type,可以區(qū)分出是系統(tǒng)發(fā)送的還是用戶發(fā)送的。
- content,消息內(nèi)容中動(dòng)態(tài)的部分,可能需要?jiǎng)幼鲌?zhí)行者,動(dòng)作,被操作對(duì)象,或者其他各種相關(guān)資源的數(shù)據(jù)來(lái)填充。因?yàn)樾枰臄?shù)據(jù)無(wú)法確定,所以這里交給每個(gè)消息產(chǎn)生規(guī)則的實(shí)現(xiàn)者取實(shí)現(xiàn)相應(yīng)的消息模版。
- 消息接收者(recipient)
訂閱消息的用戶
- 訂閱(subscribe)
create table subscribe (
id int,
rule int comment '消息產(chǎn)生規(guī)則',
target_id int comment '消息產(chǎn)生規(guī)則作用的對(duì)象',
target_type int comment '消息產(chǎn)生規(guī)則作用的對(duì)象類型',
create_time timestamp comment '訂閱時(shí)間',
valid int comment '訂閱是否有效:0.無(wú)效,1有效'
) comment = '用戶訂閱'
- 訂閱設(shè)置(subscribeConfig)
保存具體用戶訂閱了哪些消息產(chǎn)生規(guī)則,以及針對(duì)這條規(guī)則,是否接收系統(tǒng)的主動(dòng)推送
create table subscribe_config (
id int,
rule int comment '消息產(chǎn)生規(guī)則',
enable_recieve int comment '針對(duì)這條規(guī)則,是否接收系統(tǒng)的主動(dòng)推送:0.不接收,1.接收'
) comment = '用戶訂閱設(shè)置'
- 用戶消息列表(recipientNotify)
create table recipient_notify (
id int,
recipient_id int comment '消息接收者id',
notify_id int comment '消息id',
create_time timestamp comment '消息創(chuàng)建時(shí)間',
read_time timestamp comment '用戶閱讀時(shí)間'
) comment = '用戶消息列表'
系統(tǒng)服務(wù)
根據(jù)系統(tǒng)的運(yùn)行方式,和系統(tǒng)建模。系統(tǒng)的服務(wù)需要以下幾個(gè)功能:
getAllRuleByObjectType(objectType) 獲取某類對(duì)象的所有消息產(chǎn)生規(guī)則
setPushConfig(user) 用戶設(shè)置獲取推送規(guī)則
設(shè)置用戶推送規(guī)則,是否接受推送
- subscribe(user, rule, target) 用戶訂閱
設(shè)置用戶訂閱,將用戶和消息產(chǎn)生規(guī)則和規(guī)則作用的具體對(duì)象關(guān)聯(lián)
- cancelSubscribe(user, rule, target) 取消用戶訂閱
取消用戶訂閱
- listAllSubscribe(user) 獲取用戶的所有訂閱
獲取用戶所有具體對(duì)象上的訂閱
- createNotify(rule, target, sender, source) 創(chuàng)建消息
根據(jù)消息產(chǎn)生規(guī)則,創(chuàng)建消息,source是消息內(nèi)容需要的所有數(shù)據(jù)
- pushNotify(receipient, notify) 推送消息到用戶消息列表
將消息保存到用戶消息列表,同時(shí)發(fā)送推送消息
- pullNotify(receipient, notify) 拉取消息到用戶消息列表
將消息保存到用戶消息列表
- getUserSubscribeConfig(user) 獲取用戶訂閱規(guī)則
獲取用戶訂閱的所有消息產(chǎn)生規(guī)則
getUsersBySubscribe(subscribe) 查詢所有訂閱了這條訂閱的用戶
getNotifyBySubscribe(subscribe) 查詢所有根據(jù)這條消息產(chǎn)生規(guī)則和作用目標(biāo),生成的消息
readRecipientNotify(now) 讀消息列表
設(shè)置讀取的消息的讀取時(shí)間
時(shí)序圖
消息的創(chuàng)建,訂閱,推送,拉取