需求:
這個(gè)功能開(kāi)發(fā)的直接需求是為了提醒操作員即使處理庫(kù)位補(bǔ)貨, 在用戶操作分揀波次操作以后, 會(huì)出現(xiàn)有庫(kù)位庫(kù)存為負(fù)數(shù), 這種情況下需要有消息通知機(jī)制通知相關(guān)人員對(duì)相應(yīng)庫(kù)位進(jìn)行補(bǔ)貨處理;
1. 通知實(shí)體: 被通知的用戶實(shí)體可能是具體到某些登錄用戶, 也可能是某些Role 下的用戶 2. 通知狀態(tài): 消息窗口會(huì)采用浮動(dòng)窗口告知用戶有N條記錄未讀, 以及未讀消息摘要; 消息在被用戶閱讀之前是未讀狀態(tài), 讀取以后會(huì)改變狀態(tài)為已讀狀態(tài); 消息窗口不會(huì)通知用戶已讀消息。 3. 消息定制: 應(yīng)該提供能力供實(shí)施部門(mén)方便的定制一些業(yè)務(wù)規(guī)則, 來(lái)添加簡(jiǎn)單的新的消息類型和新的消息產(chǎn)生機(jī)制, 比如實(shí)施部門(mén)可以配置定時(shí)任務(wù)掃描DB中的業(yè)務(wù)表, 根據(jù)規(guī)則發(fā)現(xiàn)異常數(shù)據(jù), 并把相關(guān)信息以消息形式發(fā)送給WMS 客戶端;
設(shè)計(jì):
總體設(shè)計(jì)
客戶端如何獲取消息:
主流的方式無(wú)法長(zhǎng)連接推送和客戶端輪詢的拉取的方式, 為了簡(jiǎn)化Server 和客戶端的開(kāi)發(fā)復(fù)雜度, 我們選擇了客戶端輪詢的方式, 這樣可以直接沿用我們WMS 中已有的WebService 接口的方式來(lái)暴露消息;
存儲(chǔ):
消息體:
考慮到通知消息屬于非核心業(yè)務(wù), 并且對(duì)輪詢的方式會(huì)產(chǎn)生大量的請(qǐng)求, 所以我們不打算使用DB 做消息的存儲(chǔ), 以減少大量低優(yōu)先級(jí)的db 訪問(wèn)來(lái)拖累DB 性能和穩(wěn)定性;所以我們采用redis 來(lái)存儲(chǔ)每個(gè)訂閱者訂閱的消息;
消息訂閱定義:
要實(shí)現(xiàn)消息訂閱的可配置, 需要對(duì)消息類型, 倉(cāng)庫(kù), 訂閱者進(jìn)行關(guān)聯(lián), 這些信息存儲(chǔ)在DB中, 在系統(tǒng)啟動(dòng)的時(shí)候會(huì)把這些信息Load 到redis 中, 后續(xù)的界面操作針對(duì)這個(gè)數(shù)據(jù)的增刪改會(huì)同步修改redis 中的緩存數(shù)據(jù);
數(shù)據(jù)格式:
1. 客戶端訪問(wèn)App Server的接口是以xml 格式的方式進(jìn)行對(duì)接;
2. Server 內(nèi)部存儲(chǔ)在Redis 中的數(shù)據(jù)以protobuf 的格式進(jìn)行序列化和反序列化;具體在實(shí)現(xiàn)上使用Protostuff 生成protobuf runntime schema 對(duì)java 對(duì)象notification 進(jìn)行序列化反序列化
數(shù)據(jù)結(jié)構(gòu)
1. DB
消息訂閱定義:
已有表添加數(shù)據(jù):
GV_SYS_CODECLASS 中添加 code = 'SUBSCRIBE_TYP', CNCATEGROYNAME = '訂閱類別的記錄'
GV_SYS_CODEINFO 中添加 code = 'REP_NOTICE', CNCATEGROYNAME = '補(bǔ)貨通知', codeclass_id = #1 中的ID 的記錄
創(chuàng)建新表 GV_SUBSCRIBER
CREATE TABLE GV_SUBSCRIBER
( "ID" NUMBER(19,0) NOT NULL ENABLE,
//其他省略
"WHID" NUMBER(19,0),
"SUBSCRIBERID" VARCHAR2(255 CHAR),
"SUBSCRIBERTYPE" VARCHAR2(255 CHAR),
"MESSAGETYPE_ID" NUMBER(19,0),
PRIMARY KEY ("ID")
)
2. Redis
Redis 中存儲(chǔ)的
WMS_SUBSCRIBERS -->Set of subscriber
WMS_SUBSCRIBER_TYPE _$messageTypeId_$whID --> Set of subscriber
WMS_SUBSCRIBERED_MESSAGES_$subscriberId_$whID --> Ordered set of notification
以 “WMS_SUBSCRIBERS” 為key 存儲(chǔ) Subscriber 集合, 這個(gè)集合的目的是為了能夠取到當(dāng)前系統(tǒng)中所有subscriber, 然后可以遍歷 WMS_SUBSCRIBERED_MESSAGES_$subscriberId_$whID 鍵值列表, 刪除每個(gè)鍵值所對(duì)應(yīng)的有序集合中的過(guò)期通知; WMS_SUBSCRIBER_TYPE _$messageTypeId_$whID 鍵值對(duì)存儲(chǔ)的是從表GV_SUBSCRIBER 中加載的訂閱者集合, 每種類型的通知在每個(gè)倉(cāng)庫(kù)中的訂閱者(User 或Role)
其中WMS_SUBSCRIBERED_MESSAGES_$subscriberId_$whID 往Ordered Set 中新增element 的時(shí)候以該notification 當(dāng)前產(chǎn)生的時(shí)間戳為排序字段;
接口
1.通知消息產(chǎn)生:
通知消息產(chǎn)生的業(yè)務(wù)方需要知道當(dāng)前消息的通知類別ID 和需要發(fā)送的邏輯倉(cāng)庫(kù)ID
測(cè)試用例中的模擬代碼如下:
Notification notice = new Notification();
notice.setId(UUID.randomUUID().toString());
notice.setTitle("消息標(biāo)題" + dateformat.format(new Date()));
notice.setBody("消息體");
notice.setCreateTime(System.currentTimeMillis()); //必須是當(dāng)前時(shí)間戳
notice.setExpireTime(60*30);
notice.setWhId(119240L);
notice.setMessageTypeId(124719L);
nManager.addNotice(notice);
NotificationManager 會(huì)從redis 緩存中獲取 WMS_SUBSCRIBER_TYPE _$messageTypeId_$whID所對(duì)應(yīng)的所有Subscriber,然后會(huì)針對(duì)每一個(gè)Subscriber 調(diào)用redis 接口
向有序集合 WMS_SUBSCRIBERED_MESSAGES_$subscriberId_$whID --> Ordered set of notification 中添加Notfication
2. 前端消息獲取:
接口: PackNotificationVo listNotifications(
ClientProperty clientProperty, Long whId, List<Long> subscriberIdList, Long lastFetchTimeStamp)
客戶端程序需要維護(hù) whid, lastFetchTimeStamp 信息在本地, 每次請(qǐng)求把這個(gè)時(shí)間戳信息發(fā)送給服務(wù)器端,
客戶端行為的偽代碼就是
lastFetchTimeStamp = get($whid);
$newTimeStamp = 新的時(shí)間戳;
if($lastFetchTimeStamp& ==null ) $lastFetchTimeStamp = 默認(rèn)的當(dāng)前時(shí)間- 3天
call Server
展示新消息
set ($whid, 返回Notification 對(duì)象列表中創(chuàng)建時(shí)間最大的時(shí)間戳 )
服務(wù)器端的行為會(huì)把Notification在給定時(shí)間戳之后的 Ordered set 中的元素返回給客戶端
過(guò)期消息刪除
過(guò)期消息刪除使用定時(shí)任務(wù), 根據(jù)給定TTL, 即時(shí)出當(dāng)前時(shí)間減去TTL 的時(shí)間得到超時(shí)時(shí)間點(diǎn), 在這個(gè)時(shí)間點(diǎn)之后的Notification 都應(yīng)該被刪除;
- maxScore = nowTimeStamp - TTL ;
-
- shardedJedis.zremrangeByScore(WMS_SUBSCRIBERED_MESSAGES_$subscriberId_$whID, 0D, maxScore);
|