在spring boot中消息推送系統設計與實現

推送系統做爲通用的組件,存在的價值主要有如下幾點html

  1. 會被多個業務項目使用,推送系統獨立維護可下降維護成本
  2. 推送系統通常都是調用三方api進行推送,三方api通常會有調用頻率/次數限制,被推送的消息須要走隊列來合理調用三方api,控制調用的頻率和次數
  3. 業務無關,通常推送系統設計成不須要關心業務邏輯

核心技術

  1. 消息隊列
  2. 三方服務api調用
    1. 安卓app推送
    2. 蘋果app推送
    3. 微信小程序推送
    4. 郵件推送
    5. 釘釘推送
    6. 短信推送

消息隊列選用阿里雲提供的rocketmq,官方文檔:help.aliyun.com/document_de…java

推送時序圖 android

推送系統交互時序圖

右鍵新窗口打開能夠查看高清大圖ios

能夠看到消息推送系統接入的第三方服務比較多,三方推送的質量很難統一,就須要考慮消息的推送的重試了git

思路流程

爲了控制併發,全部的推送都先發到rocketmq隊列裏,每次推送的個數經過控制隊列的消費的客戶端的數量來實現github

安卓和蘋果均可以使用信鴿的推送服務 redis

信鴿推送須要客戶端進行集成,客戶端sdk參考:xg.qq.com/xg/ctr_inde…spring

目前信鴿我的開發者仍然是能夠申請的,帳號創建後,建立andorid和ios項目 數據庫

記錄下這裏的 APP ID和SECRET KEY,服務端進行推送時須要這兩個參數

推送異常處理,推送異常時,須要重試,重試能夠利用消息隊列自己的重試機制,也能夠自行實現重試邏輯小程序

安卓app推送

官方文檔:xg.qq.com/docs/androi…

代碼庫:github.com/xingePush/x…

<!-- 信鴿推送客戶端 -->
<dependency>
    <groupId>com.github.xingePush</groupId>
    <artifactId>xinge</artifactId>
    <version>1.2.1</version>
</dependency>
複製代碼

核心代碼以下

Map<String, Object> messagePayload = new HashMap<String, Object>(1);
messagePayload.put("user_id", 1);
messagePayload.put("msg_title", "消息標題");
messagePayload.put("msg_content", "消息內容");
messagePayload.put("msg_type", 1);
messagePayload.put("data", Lists.newHashMap("order_id", 1));

XingeApp xinge = new XingeApp(androidAccessId, androidSecretKey);
MessageAndroid message = new MessageAndroid();
ClickAction action = new ClickAction();
action.setActionType(ClickAction.TYPE_ACTIVITY);
message.setAction(action);
message.setContent(JsonUtil.toJsonString(messagePayload));
message.setType(MessageAndroid.TYPE_MESSAGE);
message.setExpireTime(86400);
message.setCustom(new HashMap<String, Object>(1));
JSONObject response = xinge.pushSingleDevice("用戶的PushToken", message);
if (response.getInt(RET_CODE) != 0) {
    // 推送異常了,進行日誌記錄等
}
複製代碼

蘋果app推送

使用pushy庫進行推送

<!-- IOS推送客戶端 -->
<dependency>
    <groupId>com.turo</groupId>
    <artifactId>pushy</artifactId>
    <version>0.13.3</version>
</dependency>
複製代碼
Map<String, Object> aps = new HashMap<String, Object>(1);
aps.put("alert", "推送內容");
aps.put("sound", "default");
aps.put("badge", 1);

Map<String, Object> data = new HashMap<String, Object>(1);
data.put("msgTitle", "推送標題");
data.put("msgContent", "推送內容");
data.put("msgType", "1");
data.put("userId", "13288888888");
data.put("data", Lists.newHashMap("order_id", 1));

Map<String, Object> messagePayload = new HashMap<String, Object>(1);
messagePayload.put("aps", aps);
messagePayload.put("data", data);

ApnsClient apnsClient = new ApnsClientBuilder()
        .setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST)
        .setClientCredentials(this.getClass().getClassLoader().getResourceAsStream("推送證書相對resources目錄的路徑"), "")
        .build();

String payload = JsonUtil.toJsonString(messagePayload);
String token = TokenUtil.sanitizeTokenString("app用戶的pushToken");

SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, "com.suxiaolin.app1", payload);

PushNotificationFuture<SimpleApnsPushNotification, PushNotificationResponse<SimpleApnsPushNotification>>
        sendNotificationFuture = apnsClient.sendNotification(pushNotification);

final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse =
        sendNotificationFuture.get();

if (pushNotificationResponse.isAccepted()) {
    System.out.println("Push notification accepted by APNs gateway.");
} else {
    System.out.println("Notification rejected by the APNs gateway: " +
            pushNotificationResponse.getRejectionReason());

    if (pushNotificationResponse.getTokenInvalidationTimestamp() != null) {
        System.out.println("\t…and the token is invalid as of " +
                pushNotificationResponse.getTokenInvalidationTimestamp());
    }
}
複製代碼

使用信鴿庫進行推送

固然也可使用信鴿提供的ios推送,邏輯和安卓app的推送差很少

ios的信鴿客戶端能夠和android的客戶端共用,不須要引入新的jar包了

JSONObject messagePayload = new JSONObject();
Map<String, Object> custom = new HashMap<String, Object>();

messagePayload.put("title", "推送標題");
messagePayload.put("body", "推送內容");

messagePayload.put("user_id", 1);
messagePayload.put("msg_type", 1);
messagePayload.put("data", Lists.newArrayList("orderId", 1));

XingeApp xinge = new XingeApp(iosAccessId, iosSecretKey);
MessageIOS message = new MessageIOS();
message.setType(MessageIOS.TYPE_APNS_NOTIFICATION);
message.setExpireTime(86400);
message.setAlert(content);
message.setBadge(1);
message.setCategory("INVITE_CATEGORY");
message.setCustom(messagePayload);

response = xinge.pushSingleDevice("app用戶的pushToken", message, XingeApp.IOSENV_PROD);
if (response.getInt(RET_CODE) != 0) {
    // 推送異常了
}
複製代碼

小程序推送

官方文檔:mp.weixin.qq.com/wiki?t=reso…

能夠看到微信小程序推送接口是普通的post請求

小程序api地址:api.weixin.qq.com/cgi-bin/mes…

http請求,http請求庫能夠參考:HttpUtil

釘釘推送

官方文檔:open-doc.dingtalk.com/microapp/se… 代碼示例

public static boolean send(String content, String title, Set<String> receivers) {
    try {
        HttpUtil.ResponseWrap result = HttpUtil.postWrap(ddUrl,
                "{\n"
                        + " \"msgtype\": \"text\",\n"
                        + " \"text\": {\"content\":\"" + title + "\r\n" + content + "\n|"
                        + receivers.stream().map(r -> "@" + r).collect(Collectors.joining(" ")) + "\"},\n"
                        + " \"at\": {\n"
                        + " \"atMobiles\": [" + receivers.stream().map(r -> "\"" + r + "\"").collect(Collectors.joining(",")) + "], \n"
                        + " \"isAtAll\": false\n"
                        + " }\n"
                        + " }");
        return result.getStatusCode() == 200;
    } catch (Exception e) {
        return false;
    }
}
複製代碼

完整代碼參考 DingTalkUtil.java

使用http請求就能夠請求了

郵件推送

發送郵件可使用java的javax.mail庫,smtp協議發送郵件

<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4.7</version>
</dependency>
複製代碼

示例代碼參考:EmailSender.java

短信推送

短信服務商衆多,郵件通常有統一的smtp協議可使用,短信沒有協議,可是通常使用http發送短信 好比如下的短信服務商

  1. 253雲通信:zz.253.com/api_doc/kai…
  2. 短信服務- 又拍雲: www.upyun.com/products/sm…
  3. 消息&短信_MSGSMS_雲通訊_短信- 華爲雲: www.huaweicloud.com/product/msg…

消息隊列的特性

消息隊列消費異常後會自動進行重試

一些注意的點

微信小程序每次支付能夠生成一個推送碼,須要保存到數據庫或者緩存裏,而且每一個碼只能推送3條消息

由於消息隊列的消費在消息量大的時候具備必定的延時,這就爲取消消息推送提供了可能,能夠爲每條消息生成一個惟一的uuid,取消的時候把這個uuid設計進redis裏,推送時檢查這個uuid是否在redis裏決定推送與否

雖然推送存在不可控制的異常,好比三方推送服務出現了異常,可是也存在調用方傳遞參數異常,能夠推送接口調用的返回值判斷是否調用推送系統成功,也能夠記錄到日誌裏,這樣在調查異常緣由時就比較容易

消息隊列默認的重試次數,消費時長是沒法控制的,能夠對消息隊列的客戶端進行修改支持這個特性,參考:github.com/jibaole/spr… 核心邏輯是先給消息設置一個最大消費次數和消費時長,而後當消息消費次數和消費時長達到閾值時,直接置爲成功

ios使用信鴿推送時,須要上傳開發證書和生產證書,這兩個證書至少須要上傳一個

相關文章
相關標籤/搜索