我是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面試,對線面試官系列持續更新中!
【對線面試官+從零撰寫Java專案】 持續高強度更新中!求star!!原創不易!!求三連!!
Gitee鏈接:https://gitee.com/austin
GitHub鏈接:https://github.com/austin
更多的文章可往:文章的目錄導航轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/415215.html
標籤:Java
