我是3y,一年CRUD
經驗用十年的markdown
程式設計師???常年被譽為優質八股文選手
今天要做的就是實現austin-api
和austin-api-impl
模組的部分程式碼,這塊完成了之後模組之間的一整條鏈路就打通咯
austin專案核心功能:傳送訊息
專案出現意義:只要公司內有傳送訊息的需求,都應該要有類似austin
的專案,對各類訊息進行統一傳送處理。這有利於對功能的收攏,以及提高業務需求開發的效率
不多BB,開始今天的正題
01、介面設計
在austini-api
模組下定義傳送訊息的介面,在austin-api-impl
下實現具體的邏輯。我的介面實現定義:
public interface SendService {
/**
* 單模板單文案傳送介面
* @param sendRequest
* @return
*/
SendResponse send(SendRequest sendRequest);
/**
* 單模板多文案傳送介面
* @param batchSendRequest
* @return
*/
SendResponse batchSend(BatchSendRequest batchSendRequest);
}
對外提供的介面,除了需要提供Single介面,最好還提供個Batch介面。因為很有可能業務方是需要一次批量執行的(如果只有Single介面,那就需要多次遠端呼叫,這樣對業務而言就不太合適了)
我所定義的介面引數如下:
public class SendRequest {
/**
* 執行業務型別
*/
private String code;
/**
* 訊息模板Id
*/
private Long messageTemplateId;
/**
* 訊息相關的引數
*/
private MessageParam messageParam;
}
通過messageTemplateId
可以去資料庫查出整個模板的資訊,而MessageParam
則是業務自行傳入的引數(重要的是接收者以及文案的引數資訊),而code
則代表著當前請求要執行什麼業務型別的(可基於該code擴充套件,後面會繼續聊到)
02、程式碼實現
從流程可以看到,austin-api
接收到請求之後,是把訊息發到MQ
的
這樣做有什麼好處呢?假設某訊息的服務超時,austin-api
如果是直接呼叫下發介面服務,那可能會存在超時風險,拖垮整個介面效能。MQ在這是為了做非同步和解耦,並且在一定程度上抗住業務流量。
對於絕大多數傳送的訊息而言,業務方也不太關心是不是能在介面呼叫時就知道傳送結果,並且某些渠道在傳送的時候也不知道傳送的結果(最後的結果是非同步告知的,比如簡訊和PUSH推送)
基於以上的原因,引入MQ來承載介面的流量以及做非同步,是非常合理的事。
前兩天我在部落格平臺上發了一篇文章《面試官:系統需求多變時如何設計? 》,有網友評論了一把:
面試官:我懂了,回去等通知吧。 …… leader:小王,我們們那個可變系統的重構計劃寫的怎麼樣了? 小王:沒問題了,首先按找我們們的業務區分出責任鏈,然後在每個具體的步驟中部署指令碼,上層再增加一個服務編排的介面統一管理…… leader:聽起來有點意思,今天的候選人怎麼樣? 小王:別提了,嘴上說5年經驗有大型系統設計,連redis都沒用過。這不是快招聘季了嗎,招兩個實習生工具人進來給我打打下手就夠了。 leader:好,把時間節點和里程碑劃分一下,confluence上立項開幹吧。 小王:好嘞。
在這次實現中,我也是用了責任鏈模式,具體完整的程式碼大家就去Gitee拉就好了。很多同學拉完程式碼發現看不懂了,大家可以按照下面的圖去梳理下責任鏈的各個角色。如果實在看不懂,建議翻下我以前寫過的責任鏈文章(已經投稿過兩篇了)
回到程式碼實現吧,這次我實現的業務是:引數前置檢查->引數拼裝->傳送訊息
呀,都畫了這麼多圖了,先點個贊,關注一波先咯。
在這幾個流程中,可能你下次拉程式碼的時候,會看到有“後置檢查”,或者別的什麼的。但不管怎麼樣,加這種邏輯我再也不用在同一個類上寫各種if else啦。只要在某個節點處新增一個Action就完事了。
(注:這是第一版實現,後面肯定會在基礎上新增邏輯或註釋的,其實已經在寫了,但我一般是有個小階段再push程式碼,所以記得star下gitee方便看最新的程式碼)
先來說前置檢查吧,主要就判斷模板ID是否有傳入,訊息引數是否有傳入(對引數的常規檢查,如果有問題,直接break掉鏈路,返回告訴呼叫方有問題)
接著來看引數拼裝,這塊主要就是通過模板ID去查整個模板的內容,然後根據業務入參拼裝出自己的TaskInfo(任務訊息)。
可能有同學會有疑問❓:為什麼不能直接用模板的POJO呢?反而需要拼裝成TaskInfo?
其實還是比較好理解的,模板是作為給使用者去配置該訊息的資訊,這是最最原始的資訊。但是我們傳送的時候是需要做處理的。比如,我要在使用者寫好的URL連結上拼接引數,我要對佔位符進行替換真實的值,我要在模板的基礎上增加業務ID進而追蹤資料 等等等。
說白了,TaskInfo是基於模板的,在模板的基礎上新增了某些平臺性的欄位(businessId),解析出使用者設定的模板而想要傳送的真實內容等等。
在這裡,值得要說明的是msgContent
該欄位的說明。在模板中,該欄位我在資料庫註釋所下的定義是(這個欄位存入資料庫一定是JSON格式的):
`msg_content` varchar(600) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '訊息內容 佔位符用{$var}表示',
不同的渠道的JSON結構還不一樣:
- 簡訊:{"content":"","url":""}
- 郵件:{"content":"","subTitle":""}
- Push:{"content":"","subTitle":"","phoneImgUrl":""}
- 小程式:{"content":"","pagePath":"" .......}
第一反應,我是想把所有渠道可能用到的欄位都定義在TaskInfo下。後來感覺這樣不太好看,於是我就定義了各種Model
(不同的傳送渠道擁有著自己的內容模型)
於是,我在組裝TaskInfo的時候利用反射來進行對映,替換佔位符則藉助的是PropertyPlaceholderHelper
而傳送則很簡單了,我是直接把TaskInfo序列化為JSON,然後讀取的時候再反序列化就好了。
值得注意的是,因為TaskInfo用的是ContentModel來儲存著內容模型,所以我們在序列化JSON的時候需要把"類資訊"寫進去,不然在反序列的時候是拿不到子類的資料的。
03、總結
對於有原始碼的專案,其實我是不太願意每一步講解我寫的程式碼的。因為我認為我本身寫得也沒那麼複雜,也沒有炫技的成分在內。
但自從push了程式碼以後,在群裡提醒各位跟著做專案的小夥伴後,有好幾位向我反饋看不太懂,所以這篇我就單獨拎出來講講。
再回過頭看,其實在austin-api
層接收到請求之後,在傳送訊息至MQ之前,在這裡的操作都是非常簡單。其實是可以把通用業務做在這(比如說通用去重的功能),但經我考慮之後,還是不太合適。
austin-api
算是一個接入層,到目前為止它只是通過id去資料庫讀取配置,就沒有耗時的操作(這意味著他能承載的併發是極大的)。假設通過ID去資料庫讀取將來存在瓶頸,我們還可以考慮將配置從Redis甚至本地記憶體裡取。
這是由業務可以決定的:一個模板的變更往往並不多,即便快取存在強一致性的問題,但就那點點時間是完全可接受的。
Question :為什麼發個訊息需要MQ?
Answer:傳送訊息實際上是呼叫各個服務提供的API,假設某訊息的服務超時,austin-api
如果是直接呼叫服務,那存在超時風險,拖垮整個介面效能。MQ在這是為了做非同步和解耦,並且在一定程度上抗住業務流量。
Question:能簡單說下接入層做了什麼事嗎?
Answer:
關注我的微信公眾號【Java3y】除了技術我還會聊點日常,有些話只能悄悄說~ 【對線面試官+從零編寫Java專案】 持續高強度更新中!求star!!原創不易!!求三連!!
Gitee連結:https://gitee.com/austin
GitHub連結:https://github.com/austin