admin
2021-09-17 a2c56bd6b79d2b8ca2c4c44a254ad2958fb72bca
推送服务完善
24个文件已修改
56个文件已添加
6897 ■■■■■ 已修改文件
facade-push/src/main/java/com/ks/push/pojo/DO/BPushMessage.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
facade-push/src/main/java/com/ks/push/pojo/Query/BPushTaskQuery.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
lib-common/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-goldcorn/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/pom.xml 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/android/AndroidNotification.java 735 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/android/BadgeNotification.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/android/Button.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/android/ClickAction.java 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/android/Color.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/android/LightSettings.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/apns/Alert.java 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/apns/ApnsHeaders.java 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/apns/ApnsHmsOptions.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/apns/Aps.java 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/exception/HuaweiException.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/exception/HuaweiMesssagingException.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/message/AndroidConfig.java 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/message/ApnsConfig.java 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/message/Message.java 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/message/Notification.java 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/message/TokenMessage.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/message/TopicMessage.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/message/WebPushConfig.java 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/HuaweiApp.java 260 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/HuaweiCredential.java 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/HuaweiMessageClient.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/HuaweiMessageClientImpl.java 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/HuaweiMessaging.java 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/HuaweiOption.java 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/HuaweiScheduledExecutor.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/HuaweiService.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/HuaweiThreadManager.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/ImplHuaweiTrampolines.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/ThreadManager.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/messaging/TokenRefresher.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/model/Importance.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/model/TopicOperation.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/model/Urgency.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/model/Visibility.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/reponse/SendResponse.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/reponse/TopicListResponse.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/reponse/TopicSendResponse.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/util/CollectionUtils.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/util/IgnoreSSLUtils.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/util/InitAppUtils.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/util/ResponceCodeProcesser.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/util/ValidatorUtils.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/util/push/HuaWeiPushUtil.java 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/util/push/MeiZuPushUtil.java 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/util/push/OppoPushUtil.java 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/util/push/VIVOPushUtil.java 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/util/push/XiaoMiPushUtil.java 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/webpush/WebActions.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/webpush/WebHmsOptions.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/webpush/WebNotification.java 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/huawei/push/webpush/WebpushHeaders.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/PushApplication.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/config/RedisConfig.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/consumer/mq/PushTaskConsumer.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/controller/PushCallbackController.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/controller/admin/AdminPushTaskController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/dao/BPushDeviceTokenDao.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/dao/BPushTaskDao.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/dto/mq/InvalidDeviceTokenInfo.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/manager/BPushPlatformAppInfoManager.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/manager/CMQManager.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/manager/PushDeviceTokenManager.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/manager/PushExcuteResultManager.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/manager/PushManager.java 110 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/service/remote/BDeviceTokenServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/utils/Constant.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/java/com/ks/push/utils/PushUtil.java 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/resources/application-dev.yml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/resources/application-pro.yml 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/resources/hw_push_url.properties 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/resources/logback.xml 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/resources/static/index.html 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/resources/static/pushplatform-appinfo-list.html 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
service-push/src/main/resources/static/pushtask-list.html 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
facade-push/src/main/java/com/ks/push/pojo/DO/BPushMessage.java
@@ -21,6 +21,8 @@
    private Map<String, String> extras;
    public String getTitle() {
        return title;
    }
@@ -68,4 +70,5 @@
    public void setAndroidHostPath(String androidHostPath) {
        this.androidHostPath = androidHostPath;
    }
}
facade-push/src/main/java/com/ks/push/pojo/Query/BPushTaskQuery.java
@@ -1,7 +1,10 @@
package com.ks.push.pojo.Query;
import org.springframework.data.domain.Sort;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class BPushTaskQuery implements Serializable {
@@ -11,5 +14,7 @@
    public Date minStartPushTime;
    public Date maxStartPushTime;
    public List<Sort.Order> sortList;
}
lib-common/pom.xml
@@ -15,7 +15,7 @@
        <dependency>
            <groupId>com.yeshi</groupId>
            <artifactId>utils</artifactId>
            <version>0.0.6</version>
            <version>0.1.0</version>
        </dependency>
    </dependencies>
service-goldcorn/pom.xml
@@ -155,7 +155,7 @@
        <dependency>
            <groupId>com.yeshi</groupId>
            <artifactId>utils</artifactId>
            <version>0.0.6</version>
            <version>0.1.0</version>
            <!--<scope>compile</scope>-->
        </dependency>
service-push/pom.xml
@@ -160,8 +160,7 @@
        <dependency>
            <groupId>com.yeshi</groupId>
            <artifactId>utils</artifactId>
            <version>0.0.6</version>
            <scope>compile</scope>
            <version>0.1.1</version>
        </dependency>
        <dependency>
@@ -198,6 +197,72 @@
            <version>2.1.2</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.4.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
        <dependency>
            <groupId>vpush</groupId>
            <artifactId>vpush-server-sdk</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>opush</groupId>
            <artifactId>opush-server-sdk</artifactId>
            <version>1.0.6</version>
        </dependency>
        <dependency>
            <groupId>mipush</groupId>
            <artifactId>MiPush_SDK_Server_Http</artifactId>
            <version>1.0.11</version>
        </dependency>
        <dependency>
            <groupId>com.googlecode.json-simple</groupId>
            <artifactId>json-simple</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>com.meizu.flyme</groupId>
            <artifactId>push-server-sdk</artifactId>
            <version>1.2.8.20190114_release</version>
        </dependency>
    </dependencies>
service-push/src/main/java/com/huawei/push/android/AndroidNotification.java
New file
@@ -0,0 +1,735 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.android;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.message.Notification;
import com.huawei.push.model.Importance;
import com.huawei.push.model.Visibility;
import com.huawei.push.util.CollectionUtils;
import com.huawei.push.util.ValidatorUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
public class AndroidNotification {
    private static final String COLOR_PATTERN = "^#[0-9a-fA-F]{6}$";
    private static final String URL_PATTERN = "^https.*";
    private static final String VIBRATE_PATTERN = "[0-9]+|[0-9]+[sS]|[0-9]+[.][0-9]{1,9}|[0-9]+[.][0-9]{1,9}[sS]";
    @JSONField(name = "title")
    private String title;
    @JSONField(name = "body")
    private String body;
    @JSONField(name = "icon")
    private String icon;
    @JSONField(name = "color")
    private String color;
    @JSONField(name = "sound")
    private String sound;
    @JSONField(name = "default_sound")
    private boolean defaultSound;
    @JSONField(name = "tag")
    private String tag;
    @JSONField(name = "click_action")
    private ClickAction clickAction;
    @JSONField(name = "body_loc_key")
    private String bodyLocKey;
    @JSONField(name = "body_loc_args")
    private List<String> bodyLocArgs = new ArrayList<>();
    @JSONField(name = "title_loc_key")
    private String titleLocKey;
    @JSONField(name = "title_loc_args")
    private List<String> titleLocArgs = new ArrayList<>();
    @JSONField(name = "multi_lang_key")
    private JSONObject multiLangKey;
    @JSONField(name = "channel_id")
    private String channelId;
    @JSONField(name = "notify_summary")
    private String notifySummary;
    @JSONField(name = "image")
    private String image;
    @JSONField(name = "style")
    private Integer style;
    @JSONField(name = "big_title")
    private String bigTitle;
    @JSONField(name = "big_body")
    private String bigBody;
    @JSONField(name = "auto_clear")
    private Integer autoClear;
    @JSONField(name = "notify_id")
    private Integer notifyId;
    @JSONField(name = "group")
    private String group;
    @JSONField(name = "badge")
    private BadgeNotification badge;
    @JSONField(name = "ticker")
    private String ticker;
    @JSONField(name = "auto_cancel")
    private boolean autoCancel;
    @JSONField(name = "when")
    private String when;
    @JSONField(name = "local_only")
    private Boolean localOnly;
    @JSONField(name = "importance")
    private String importance;
    @JSONField(name = "use_default_vibrate")
    private boolean useDefaultVibrate;
    @JSONField(name = "use_default_light")
    private boolean useDefaultLight;
    @JSONField(name = "vibrate_config")
    private List<String> vibrateConfig = new ArrayList<>();
    @JSONField(name = "visibility")
    private String visibility;
    @JSONField(name = "light_settings")
    private LightSettings lightSettings;
    @JSONField(name = "foreground_show")
    private boolean foregroundShow;
    @JSONField(name = "inbox_content")
    private List<String> inboxContent;
    @JSONField(name = "buttons")
    private List<Button> buttons;
    private AndroidNotification(Builder builder) {
        this.title = builder.title;
        this.body = builder.body;
        this.icon = builder.icon;
        this.color = builder.color;
        this.sound = builder.sound;
        this.defaultSound = builder.defaultSound;
        this.tag = builder.tag;
        this.clickAction = builder.clickAction;
        this.bodyLocKey = builder.bodyLocKey;
        if (!CollectionUtils.isEmpty(builder.bodyLocArgs)) {
            this.bodyLocArgs.addAll(builder.bodyLocArgs);
        } else {
            this.bodyLocArgs = null;
        }
        this.titleLocKey = builder.titleLocKey;
        if (!CollectionUtils.isEmpty(builder.titleLocArgs)) {
            this.titleLocArgs.addAll(builder.titleLocArgs);
        } else {
            this.titleLocArgs = null;
        }
        if (builder.multiLangkey != null) {
            this.multiLangKey = builder.multiLangkey;
        } else {
            this.multiLangKey = null;
        }
        this.channelId = builder.channelId;
        this.notifySummary = builder.notifySummary;
        this.image = builder.image;
        this.style = builder.style;
        this.bigTitle = builder.bigTitle;
        this.bigBody = builder.bigBody;
        this.autoClear = builder.autoClear;
        this.notifyId = builder.notifyId;
        this.group = builder.group;
        if (null != builder.badge) {
            this.badge = builder.badge;
        } else {
            this.badge = null;
        }
        this.ticker = builder.ticker;
        this.autoCancel = builder.autoCancel;
        this.when = builder.when;
        this.importance = builder.importance;
        this.useDefaultVibrate = builder.useDefaultVibrate;
        this.useDefaultLight = builder.useDefaultLight;
        if (!CollectionUtils.isEmpty(builder.vibrateConfig)) {
            this.vibrateConfig = builder.vibrateConfig;
        } else {
            this.vibrateConfig = null;
        }
        this.visibility = builder.visibility;
        this.lightSettings = builder.lightSettings;
        this.foregroundShow = builder.foregroundShow;
        if (!CollectionUtils.isEmpty(builder.inboxContent)) {
            this.inboxContent = builder.inboxContent;
        } else {
            this.inboxContent = null;
        }
        if (!CollectionUtils.isEmpty(builder.buttons)) {
            this.buttons = builder.buttons;
        } else {
            this.buttons = null;
        }
    }
    /**
     * check androidNotification's parameters
     *
     * @param notification which is in message
     */
    public void check(Notification notification) {
        if (null != notification) {
            ValidatorUtils.checkArgument(StringUtils.isNotEmpty(notification.getTitle()) || StringUtils.isNotEmpty(this.title), "title should be set");
            ValidatorUtils.checkArgument(StringUtils.isNotEmpty(notification.getBody()) || StringUtils.isNotEmpty(this.body), "body should be set");
        }
        if (StringUtils.isNotEmpty(this.color)) {
            ValidatorUtils.checkArgument(this.color.matches(AndroidNotification.COLOR_PATTERN), "Wrong color format, color must be in the form #RRGGBB");
        }
        if (this.clickAction != null) {
            this.clickAction.check();
        }
        if (!CollectionUtils.isEmpty(this.bodyLocArgs)) {
            ValidatorUtils.checkArgument(StringUtils.isNotEmpty(this.bodyLocKey), "bodyLocKey is required when specifying bodyLocArgs");
        }
        if (!CollectionUtils.isEmpty(this.titleLocArgs)) {
            ValidatorUtils.checkArgument(StringUtils.isNotEmpty(this.titleLocKey), "titleLocKey is required when specifying titleLocArgs");
        }
        if (StringUtils.isNotEmpty(this.image)) {
            ValidatorUtils.checkArgument(this.image.matches(URL_PATTERN), "notifyIcon must start with https");
        }
        //Style 0,1,2
        if (this.style != null) {
            boolean isTrue = this.style == 0 ||
                    this.style == 1 ||
                    this.style == 2 ||
                    this.style == 3;
            ValidatorUtils.checkArgument(isTrue, "style should be one of 0:default, 1: big text, 2: big picture");
            if (this.style == 1) {
                ValidatorUtils.checkArgument(StringUtils.isNotEmpty(this.bigTitle) && StringUtils.isNotEmpty(this.bigBody), "title and body are required when style = 1");
            } else if (this.style == 3) {
                ValidatorUtils.checkArgument(!CollectionUtils.isEmpty(this.inboxContent) && this.inboxContent.size() <= 5, "inboxContent is required when style = 3 and at most 5 inbox content is needed");
            }
        }
        if (this.autoClear != null) {
            ValidatorUtils.checkArgument(this.autoClear.intValue() > 0, "auto clear should positive value");
        }
        if (badge != null) {
            this.badge.check();
        }
        if (this.importance != null) {
            ValidatorUtils.checkArgument(StringUtils.equals(this.importance, Importance.LOW.getValue())
                            || StringUtils.equals(this.importance, Importance.NORMAL.getValue())
                            || StringUtils.equals(this.importance, Importance.HIGH.getValue()),
                    "importance shouid be [HIGH, NORMAL, LOW]");
        }
        if (!CollectionUtils.isEmpty(this.vibrateConfig)) {
            ValidatorUtils.checkArgument(this.vibrateConfig.size() <= 10, "vibrate_config array size cannot be more than 10");
            for (String vibrateTiming : this.vibrateConfig) {
                ValidatorUtils.checkArgument(vibrateTiming.matches(AndroidNotification.VIBRATE_PATTERN), "Wrong vibrate timing format");
                long vibrateTimingValue = (long) (1000 * Double
                        .valueOf(StringUtils.substringBefore(vibrateTiming.toLowerCase(Locale.getDefault()), "s")));
                ValidatorUtils.checkArgument(vibrateTimingValue > 0 && vibrateTimingValue < 60000, "Vibrate timing duration must be greater than 0 and less than 60s");
            }
        }
        if (this.visibility != null) {
            ValidatorUtils.checkArgument(StringUtils.equals(this.visibility, Visibility.VISIBILITY_UNSPECIFIED.getValue())
                            || StringUtils.equals(this.visibility, Visibility.PRIVATE.getValue())
                            || StringUtils.equals(this.visibility, Visibility.PUBLIC.getValue())
                            || StringUtils.equals(this.visibility, Visibility.SECRET.getValue()),
                    "visibility shouid be [VISIBILITY_UNSPECIFIED, PRIVATE, PUBLIC, SECRET]");
        }
        // multiLangKey
        if (null != this.multiLangKey) {
            Iterator<String> keyIterator = this.multiLangKey.keySet().iterator();
            while (keyIterator.hasNext()) {
                String key = keyIterator.next();
                ValidatorUtils.checkArgument((this.multiLangKey.get(key) instanceof JSONObject), "multiLangKey value should be JSONObject");
                JSONObject contentsObj = multiLangKey.getJSONObject(key);
                if (contentsObj != null) {
                    ValidatorUtils.checkArgument(contentsObj.keySet().size() <= 3, "Only three lang property can carry");
                }
            }
        }
        if (this.lightSettings != null) {
            this.lightSettings.check();
        }
        if (!CollectionUtils.isEmpty(this.buttons)) {
            ValidatorUtils.checkArgument(this.buttons.size() <= 3, "Only three buttons can carry");
            for (Button button : this.buttons) {
                button.check();
            }
        }
    }
    /**
     * getter
     */
    public String getTitle() {
        return title;
    }
    public String getBody() {
        return body;
    }
    public String getIcon() {
        return icon;
    }
    public String getColor() {
        return color;
    }
    public String getSound() {
        return sound;
    }
    public Boolean isDefaultSound() {
        return defaultSound;
    }
    public String getTag() {
        return tag;
    }
    public ClickAction getClickAction() {
        return clickAction;
    }
    public String getBodyLocKey() {
        return bodyLocKey;
    }
    public List<String> getBodyLocArgs() {
        return bodyLocArgs;
    }
    public String getTitleLocKey() {
        return titleLocKey;
    }
    public List<String> getTitleLocArgs() {
        return titleLocArgs;
    }
    public JSONObject getMultiLangKey() {
        return multiLangKey;
    }
    public String getChannelId() {
        return channelId;
    }
    public String getNotifySummary() {
        return notifySummary;
    }
    public String getImage() {
        return image;
    }
    public Integer getStyle() {
        return style;
    }
    public String getBigTitle() {
        return bigTitle;
    }
    public String getBigBody() {
        return bigBody;
    }
    public Integer getAutoClear() {
        return autoClear;
    }
    public Integer getNotifyId() {
        return notifyId;
    }
    public String getGroup() {
        return group;
    }
    public BadgeNotification getBadge() {
        return badge;
    }
    public String getTicker() {
        return ticker;
    }
    public String getWhen() {
        return when;
    }
    public String getImportance() {
        return importance;
    }
    public List<String> getVibrateConfig() {
        return vibrateConfig;
    }
    public String getVisibility() {
        return visibility;
    }
    public LightSettings getLightSettings() {
        return lightSettings;
    }
    public boolean isAutoCancel() {
        return autoCancel;
    }
    public Boolean getLocalOnly() {
        return localOnly;
    }
    public boolean isUseDefaultVibrate() {
        return useDefaultVibrate;
    }
    public boolean isUseDefaultLight() {
        return useDefaultLight;
    }
    public boolean isForegroundShow() {
        return foregroundShow;
    }
    public List<String> getInboxContent() {
        return inboxContent;
    }
    public List<Button> getButtons() {
        return buttons;
    }
    /**
     * builder
     *
     * @return
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private String title;
        private String body;
        private String icon;
        private String color;
        private String sound;
        private boolean defaultSound;
        private String tag;
        private ClickAction clickAction;
        private String bodyLocKey;
        private List<String> bodyLocArgs = new ArrayList<>();
        private String titleLocKey;
        private List<String> titleLocArgs = new ArrayList<>();
        private JSONObject multiLangkey;
        private String channelId;
        private String notifySummary;
        private String image;
        private Integer style;
        private String bigTitle;
        private String bigBody;
        private Integer autoClear;
        private Integer notifyId;
        private String group;
        private BadgeNotification badge;
        private String ticker;
        private boolean autoCancel = true;
        private String when;
        private String importance;
        private boolean useDefaultVibrate;
        private boolean useDefaultLight;
        private List<String> vibrateConfig = new ArrayList<>();
        private String visibility;
        private LightSettings lightSettings;
        private boolean foregroundShow;
        private List<String> inboxContent = new ArrayList<>();
        private List<Button> buttons = new ArrayList<Button>();
        private Builder() {
        }
        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }
        public Builder setBody(String body) {
            this.body = body;
            return this;
        }
        public Builder setIcon(String icon) {
            this.icon = icon;
            return this;
        }
        public Builder setColor(String color) {
            this.color = color;
            return this;
        }
        public Builder setSound(String sound) {
            this.sound = sound;
            return this;
        }
        public Builder setDefaultSound(boolean defaultSound) {
            this.defaultSound = defaultSound;
            return this;
        }
        public Builder setTag(String tag) {
            this.tag = tag;
            return this;
        }
        public Builder setClickAction(ClickAction clickAction) {
            this.clickAction = clickAction;
            return this;
        }
        public Builder setBodyLocKey(String bodyLocKey) {
            this.bodyLocKey = bodyLocKey;
            return this;
        }
        public Builder addBodyLocArgs(String arg) {
            this.bodyLocArgs.add(arg);
            return this;
        }
        public Builder addAllBodyLocArgs(List<String> args) {
            this.bodyLocArgs.addAll(args);
            return this;
        }
        public Builder setTitleLocKey(String titleLocKey) {
            this.titleLocKey = titleLocKey;
            return this;
        }
        public Builder addTitleLocArgs(String arg) {
            this.titleLocArgs.add(arg);
            return this;
        }
        public Builder addAllTitleLocArgs(List<String> args) {
            this.titleLocArgs.addAll(args);
            return this;
        }
        public Builder setMultiLangkey(JSONObject multiLangkey) {
            this.multiLangkey = multiLangkey;
            return this;
        }
        public Builder setChannelId(String channelId) {
            this.channelId = channelId;
            return this;
        }
        public Builder setNotifySummary(String notifySummary) {
            this.notifySummary = notifySummary;
            return this;
        }
        public Builder setImage(String image) {
            this.image = image;
            return this;
        }
        public Builder setStyle(Integer style) {
            this.style = style;
            return this;
        }
        public Builder setBigTitle(String bigTitle) {
            this.bigTitle = bigTitle;
            return this;
        }
        public Builder setBigBody(String bigBody) {
            this.bigBody = bigBody;
            return this;
        }
        public Builder setAutoClear(Integer autoClear) {
            this.autoClear = autoClear;
            return this;
        }
        public Builder setNotifyId(Integer notifyId) {
            this.notifyId = notifyId;
            return this;
        }
        public Builder setGroup(String group) {
            this.group = group;
            return this;
        }
        public Builder setBadge(BadgeNotification badge) {
            this.badge = badge;
            return this;
        }
        public Builder setTicker(String ticker) {
            this.ticker = ticker;
            return this;
        }
        public Builder setAutoCancel(boolean autoCancel) {
            this.autoCancel = autoCancel;
            return this;
        }
        public Builder setWhen(String when) {
            this.when = when;
            return this;
        }
        public Builder setImportance(String importance) {
            this.importance = importance;
            return this;
        }
        public Builder setUseDefaultVibrate(boolean useDefaultVibrate) {
            this.useDefaultVibrate = useDefaultVibrate;
            return this;
        }
        public Builder setUseDefaultLight(boolean useDefaultLight) {
            this.useDefaultLight = useDefaultLight;
            return this;
        }
        public Builder addVibrateConfig(String vibrateTiming) {
            this.vibrateConfig.add(vibrateTiming);
            return this;
        }
        public Builder addAllVibrateConfig(List<String> vibrateTimings) {
            this.vibrateConfig.addAll(vibrateTimings);
            return this;
        }
        public Builder setVisibility(String visibility) {
            this.visibility = visibility;
            return this;
        }
        public Builder setLightSettings(LightSettings lightSettings) {
            this.lightSettings = lightSettings;
            return this;
        }
        public Builder setForegroundShow(boolean foregroundShow) {
            this.foregroundShow = foregroundShow;
            return this;
        }
        public Builder addInboxContent(String inboxContent) {
            this.inboxContent.add(inboxContent);
            return this;
        }
        public Builder addAllInboxContent(List<String> inboxContents) {
            this.inboxContent.addAll(inboxContents);
            return this;
        }
        public Builder addButton(Button button) {
            this.buttons.add(button);
            return this;
        }
        public Builder addAllButtons(List<Button> buttons) {
            this.buttons.addAll(buttons);
            return this;
        }
        public AndroidNotification build() {
            return new AndroidNotification(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/android/BadgeNotification.java
New file
@@ -0,0 +1,98 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.android;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
public class BadgeNotification {
    @JSONField(name = "add_num")
    private Integer addNum;
    @JSONField(name = "class")
    private String badgeClass;
    @JSONField(name = "set_num")
    private Integer setNum;
    public Integer getAddNum() {
        return addNum;
    }
    public String getBadgeClass() {
        return badgeClass;
    }
    public Integer getSetNum() {
        return setNum;
    }
    public BadgeNotification(Integer addNum, String badgeClass) {
        this.addNum = builder().addNum;
        this.badgeClass = badgeClass;
    }
    public BadgeNotification(Builder builder) {
        this.addNum = builder.addNum;
        this.badgeClass = builder.badgeClass;
        this.setNum = builder.setNum;
    }
    public void check() {
        if (this.addNum != null) {
            ValidatorUtils.checkArgument(this.addNum.intValue() > 0 && this.addNum.intValue() < 100, "add_num should locate between 0 and 100");
        }
        if (this.setNum != null) {
            ValidatorUtils.checkArgument(this.setNum.intValue() >= 0 && this.setNum.intValue() < 100, "set_num should locate between 0 and 100");
        }
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private Integer addNum;
        private String badgeClass;
        private Integer setNum;
        public Builder setAddNum(Integer addNum) {
            this.addNum = addNum;
            return this;
        }
        public Builder setSetNum(Integer setNum) {
            this.setNum = setNum;
            return this;
        }
        public Builder setBadgeClass(String badgeClass) {
            this.badgeClass = badgeClass;
            return this;
        }
        public BadgeNotification build() {
            return new BadgeNotification(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/android/Button.java
New file
@@ -0,0 +1,121 @@
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2029. All rights reserved.
 */
package com.huawei.push.android;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
import org.apache.commons.lang3.StringUtils;
/**
 * 功能描述
 *
 * @author l00282963
 * @since 2020-01-19
 */
public class Button {
    @JSONField(name = "name")
    private String name;
    @JSONField(name = "action_type")
    private Integer actionType;
    @JSONField(name = "intent_type")
    private Integer intentType;
    @JSONField(name = "intent")
    private String intent;
    @JSONField(name = "data")
    private String data;
    public String getName() {
        return name;
    }
    public Integer getActionType() {
        return actionType;
    }
    public Integer getIntentType() {
        return intentType;
    }
    public String getIntent() {
        return intent;
    }
    public String getData() {
        return data;
    }
    public Button(Builder builder) {
        this.name = builder.name;
        this.actionType = builder.actionType;
        this.intentType = builder.intentType;
        this.intent = builder.intent;
        this.data = builder.data;
    }
    public void check() {
        if (this.actionType != null && this.actionType == 4) {
            ValidatorUtils.checkArgument(StringUtils.isNotEmpty(this.data), "data is needed when actionType is 4");
        }
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        @JSONField(name = "name")
        private String name;
        @JSONField(name = "actionType")
        private Integer actionType;
        @JSONField(name = "intentType")
        private Integer intentType;
        @JSONField(name = "intent")
        private String intent;
        @JSONField(name = "data")
        private String data;
        public Builder setName(String name) {
            this.name = name;
            return this;
        }
        public Builder setActionType(Integer actionType) {
            this.actionType = actionType;
            return this;
        }
        public Builder setIntentType(Integer intentType) {
            this.intentType = intentType;
            return this;
        }
        public Builder setIntent(String intent) {
            this.intent = intent;
            return this;
        }
        public Builder setData(String data) {
            this.data = data;
            return this;
        }
        public Button build() {
            return new Button(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/android/ClickAction.java
New file
@@ -0,0 +1,151 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.android;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
import org.apache.commons.lang3.StringUtils;
public class ClickAction {
    private static final String PATTERN = "^https.*";
    @JSONField(name = "type")
    private Integer type;
    @JSONField(name = "intent")
    private String intent;
    @JSONField(name = "url")
    private String url;
    @JSONField(name = "rich_resource")
    private String richResource;
    @JSONField(name = "action")
    private String action;
    private ClickAction(Builder builder) {
        this.type = builder.type;
        switch (this.type) {
            case 1:
                this.intent = builder.intent;
                this.action = builder.action;
                break;
            case 2:
                this.url = builder.url;
                break;
            case 4:
                this.richResource = builder.richResource;
                break;
        }
    }
    /**
     * check clickAction's parameters
     */
    public void check() {
        boolean isTrue = this.type == 1 ||
                this.type == 2 ||
                this.type == 3 ||
                this.type == 4;
        ValidatorUtils.checkArgument(isTrue, "click type should be one of 1: customize action, 2: open url, 3: open app, 4: open rich media");
        switch (this.type) {
            case 1:
                ValidatorUtils.checkArgument(StringUtils.isNotEmpty(this.intent) || StringUtils.isNotEmpty(this.action), "intent or action is required when click type=1");
                break;
            case 2:
                ValidatorUtils.checkArgument(StringUtils.isNotEmpty(this.url), "url is required when click type=2");
                ValidatorUtils.checkArgument(this.url.matches(PATTERN), "url must start with https");
                break;
            case 4:
                ValidatorUtils.checkArgument(StringUtils.isNotEmpty(this.richResource), "richResource is required when click type=4");
                ValidatorUtils.checkArgument(this.richResource.matches(PATTERN), "richResource must start with https");
                break;
        }
    }
    /**
     * getter
     */
    public int getType() {
        return type;
    }
    public String getIntent() {
        return intent;
    }
    public String getUrl() {
        return url;
    }
    public String getRichResource() {
        return richResource;
    }
    public String getAction() {
        return action;
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private Integer type;
        private String intent;
        private String url;
        private String richResource;
        private String action;
        private Builder() {
        }
        public Builder setType(Integer type) {
            this.type = type;
            return this;
        }
        public Builder setIntent(String intent) {
            this.intent = intent;
            return this;
        }
        public Builder setUrl(String url) {
            this.url = url;
            return this;
        }
        public Builder setRichResource(String richResource) {
            this.richResource = richResource;
            return this;
        }
        public Builder setAction(String action) {
            this.action = action;
            return this;
        }
        public ClickAction build() {
            return new ClickAction(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/android/Color.java
New file
@@ -0,0 +1,105 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.android;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
public class Color {
    private final float zero = -0.000001f;
    private final float one = 1.000001f;
    @JSONField(name = "alpha")
    private Float alpha = new Float(1.0);
    @JSONField(name = "red")
    private Float red = new Float(0.0);
    @JSONField(name = "green")
    private Float green = new Float(0.0);
    @JSONField(name = "blue")
    private Float blue = new Float(0.0);
    public Color(Builder builder) {
        this.alpha = builder.alpha;
        this.red = builder.red;
        this.green = builder.green;
        this.blue = builder.blue;
    }
    public double getAlpha() {
        return alpha;
    }
    public Float getRed() {
        return red;
    }
    public Float getGreen() {
        return green;
    }
    public Float getBlue() {
        return blue;
    }
    public void check() {
        ValidatorUtils.checkArgument(this.alpha > zero && this.alpha < one, "Alpha shoube locate between [0,1]");
        ValidatorUtils.checkArgument(this.red > zero && this.red < one, "Red shoube locate between [0,1]");
        ValidatorUtils.checkArgument(this.green > zero && this.green < one, "Green shoube locate between [0,1]");
        ValidatorUtils.checkArgument(this.blue > zero && this.blue < one, "Blue shoube locate between [0,1]");
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private Float alpha = new Float(1.0);
        private Float red = new Float(0.0);
        private Float green = new Float(0.0);
        private Float blue = new Float(0.0);
        public Builder setAlpha(Float alpha) {
            this.alpha = alpha;
            return this;
        }
        public Builder setRed(Float red) {
            this.red = red;
            return this;
        }
        public Builder setGreen(Float green) {
            this.green = green;
            return this;
        }
        public Builder setBlue(Float blue) {
            this.blue = blue;
            return this;
        }
        public Color build() {
            return new Color(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/android/LightSettings.java
New file
@@ -0,0 +1,101 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.android;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
public class LightSettings {
    private static final String LIGTH_DURATION_PATTERN = "\\d+|\\d+[sS]|\\d+.\\d{1,9}|\\d+.\\d{1,9}[sS]";
    @JSONField(name = "color")
    private Color color;
    @JSONField(name = "light_on_duration")
    private String lightOnDuration;
    @JSONField(name = "light_off_duration")
    private String lightOffDuration;
    public LightSettings(Builder builder) {
        this.color = builder.color;
        this.lightOnDuration = builder.lightOnDuration;
        this.lightOffDuration = builder.lightOffDuration;
    }
    public Color getColor() {
        return color;
    }
    public String getLightOnDuration() {
        return lightOnDuration;
    }
    public String getLightOffDuration() {
        return lightOffDuration;
    }
    /**
     * 参数校验
     */
    public void check() {
        ValidatorUtils.checkArgument(this.color != null, "color must be selected when light_settings is set");
        if (this.color != null) {
            this.color.check();
        }
        ValidatorUtils.checkArgument(this.lightOnDuration != null, "light_on_duration must be selected when light_settings is set");
        ValidatorUtils.checkArgument(this.lightOffDuration != null, "light_off_duration must be selected when light_settings is set");
        ValidatorUtils.checkArgument(this.lightOnDuration.matches(LIGTH_DURATION_PATTERN), "light_on_duration format is wrong");
        ValidatorUtils.checkArgument(this.lightOffDuration.matches(LIGTH_DURATION_PATTERN), "light_off_duration format is wrong");
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private Color color;
        private String lightOnDuration;
        private String lightOffDuration;
        public Builder setColor(Color color) {
            this.color = color;
            return this;
        }
        public Builder setLightOnDuration(String lightOnDuration) {
            this.lightOnDuration = lightOnDuration;
            return this;
        }
        public Builder setLightOffDuration(String lightOffDuration) {
            this.lightOffDuration = lightOffDuration;
            return this;
        }
        public LightSettings build() {
            return new LightSettings(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/apns/Alert.java
New file
@@ -0,0 +1,186 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.apns;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.CollectionUtils;
import com.huawei.push.util.ValidatorUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class Alert {
    @JSONField(name = "title")
    private String title;
    @JSONField(name = "body")
    private String body;
    @JSONField(name = "title-loc-key")
    private String titleLocKey;
    @JSONField(name = "title-loc-args")
    private List<String> titleLocArgs = new ArrayList<String>();
    @JSONField(name = "action-loc-key")
    private String actionLocKey;
    @JSONField(name = "loc-key")
    private String locKey;
    @JSONField(name = "loc-args")
    private List<String> locArgs = new ArrayList<String>();
    @JSONField(name = "launch-image")
    private String launchImage;
    public String getTitle() {
        return title;
    }
    public String getBody() {
        return body;
    }
    public String getTitleLocKey() {
        return titleLocKey;
    }
    public List<String> getTitleLocArgs() {
        return titleLocArgs;
    }
    public String getActionLocKey() {
        return actionLocKey;
    }
    public String getLocKey() {
        return locKey;
    }
    public List<String> getLocArgs() {
        return locArgs;
    }
    public String getLaunchImage() {
        return launchImage;
    }
    private Alert(Builder builder) {
        this.title = builder.title;
        this.body = builder.body;
        this.titleLocKey = builder.titleLocKey;
        if (!CollectionUtils.isEmpty(builder.titleLocArgs)) {
            this.titleLocArgs.addAll(builder.titleLocArgs);
        } else {
            this.titleLocArgs = null;
        }
        this.actionLocKey = builder.actionLocKey;
        this.locKey = builder.locKey;
        if (!CollectionUtils.isEmpty(builder.locArgs)) {
            this.locArgs.addAll(builder.locArgs);
        } else {
            this.locArgs = null;
        }
        this.launchImage = builder.launchImage;
    }
    public void check() {
        if (!CollectionUtils.isEmpty(this.locArgs)) {
            ValidatorUtils.checkArgument(StringUtils.isNotEmpty(this.locKey), "locKey is required when specifying locArgs");
        }
        if (!CollectionUtils.isEmpty(this.titleLocArgs)) {
            ValidatorUtils.checkArgument(StringUtils.isNotEmpty(this.titleLocKey), "titleLocKey is required when specifying titleLocArgs");
        }
    }
    /**
     * builder
     *
     * @return
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private String title;
        private String body;
        private String titleLocKey;
        private List<String> titleLocArgs = new ArrayList<String>();
        private String actionLocKey;
        private String locKey;
        private List<String> locArgs = new ArrayList<String>();
        private String launchImage;
        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }
        public Builder setBody(String body) {
            this.body = body;
            return this;
        }
        public Builder setTitleLocKey(String titleLocKey) {
            this.titleLocKey = titleLocKey;
            return this;
        }
        public Builder setAddAllTitleLocArgs(List<String> titleLocArgs) {
            this.titleLocArgs.addAll(titleLocArgs);
            return this;
        }
        public Builder setAddTitleLocArg(String titleLocArg) {
            this.titleLocArgs.add(titleLocArg);
            return this;
        }
        public Builder setActionLocKey(String actionLocKey) {
            this.actionLocKey = actionLocKey;
            return this;
        }
        public Builder setLocKey(String locKey) {
            this.locKey = locKey;
            return this;
        }
        public Builder AddAllLocArgs(List<String> locArgs) {
            this.locArgs.addAll(locArgs);
            return this;
        }
        public Builder AddLocArg(String locArg) {
            this.locArgs.add(locArg);
            return this;
        }
        public Builder setLaunchImage(String launchImage) {
            this.launchImage = launchImage;
            return this;
        }
        public Alert build() {
            return new Alert(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/apns/ApnsHeaders.java
New file
@@ -0,0 +1,146 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.apns;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
public class ApnsHeaders {
    private static final String AUTHORIZATION_PATTERN = "^bearer*";
    private static final String APN_ID_PATTERN = "[0-9a-z]{8}(-[0-9a-z]{4}){3}-[0-9a-z]{12}";
    private static final int SEND_IMMEDIATELY = 10;
    private static final int SEND_BY_GROUP = 5;
    @JSONField(name = "authorization")
    private String authorization;
    @JSONField(name = "apns-id")
    private String apnsId;
    @JSONField(name = "apns-expiration")
    private Long apnsExpiration;
    @JSONField(name = "apns-priority")
    private String apnsPriority;
    @JSONField(name = "apns-topic")
    private String apnsTopic;
    @JSONField(name = "apns-collapse-id")
    private String apnsCollapseId;
    public String getAuthorization() {
        return authorization;
    }
    public String getApnsId() {
        return apnsId;
    }
    public Long getApnsExpiration() {
        return apnsExpiration;
    }
    public String getApnsPriority() {
        return apnsPriority;
    }
    public String getApnsTopic() {
        return apnsTopic;
    }
    public String getApnsCollapseId() {
        return apnsCollapseId;
    }
    public void check() {
        if (this.authorization != null) {
            ValidatorUtils.checkArgument(this.authorization.matches(AUTHORIZATION_PATTERN), "authorization must start with bearer");
        }
        if (this.apnsId != null) {
            ValidatorUtils.checkArgument(this.apnsId.matches(APN_ID_PATTERN), "apns-id format error");
        }
        if (this.apnsPriority != null) {
            ValidatorUtils.checkArgument(Integer.parseInt(this.apnsPriority) == SEND_BY_GROUP ||
                    Integer.parseInt(this.apnsPriority) == SEND_IMMEDIATELY, "apns-priority should be SEND_BY_GROUP:5  or SEND_IMMEDIATELY:10");
        }
        if (this.apnsCollapseId != null) {
            ValidatorUtils.checkArgument(this.apnsCollapseId.getBytes().length < 64, "Number of apnsCollapseId bytes should be less than 64");
        }
    }
    private ApnsHeaders(Builder builder) {
        this.authorization = builder.authorization;
        this.apnsId = builder.apnsId;
        this.apnsExpiration = builder.apnsExpiration;
        this.apnsPriority = builder.apnsPriority;
        this.apnsTopic = builder.apnsTopic;
        this.apnsCollapseId = builder.apnsCollapseId;
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private String authorization;
        private String apnsId;
        private Long apnsExpiration;
        private String apnsPriority;
        private String apnsTopic;
        private String apnsCollapseId;
        public Builder setAuthorization(String authorization) {
            this.authorization = authorization;
            return this;
        }
        public Builder setApnsId(String apnsId) {
            this.apnsId = apnsId;
            return this;
        }
        public Builder setApnsExpiration(Long apnsExpiration) {
            this.apnsExpiration = apnsExpiration;
            return this;
        }
        public Builder setApnsPriority(String apnsPriority) {
            this.apnsPriority = apnsPriority;
            return this;
        }
        public Builder setApnsTopic(String apnsTopic) {
            this.apnsTopic = apnsTopic;
            return this;
        }
        public Builder setApnsCollapseId(String apnsCollapseId) {
            this.apnsCollapseId = apnsCollapseId;
            return this;
        }
        public ApnsHeaders build() {
            return new ApnsHeaders(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/apns/ApnsHmsOptions.java
New file
@@ -0,0 +1,65 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.apns;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
public class ApnsHmsOptions {
    private static final int TEST_USER = 1;
    private static final int FORMAL_USER = 1;
    private static final int VOIP_USER = 1;
    @JSONField(name = "target_user_type")
    private Integer targetUserType;
    public Integer getTargetUserType() {
        return targetUserType;
    }
    private ApnsHmsOptions(Builder builder){
        this.targetUserType = builder.targetUserType;
    }
    public void check(){
        if (targetUserType != null) {
            ValidatorUtils.checkArgument(this.targetUserType.intValue() == TEST_USER
                            || this.targetUserType.intValue() == FORMAL_USER
                            || this.targetUserType.intValue() == VOIP_USER,
                    "targetUserType should be [TEST_USER: 1, FORMAL_USER: 2, VOIP_USER: 3]");
        }
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private Integer targetUserType;
        public Builder setTargetUserType(Integer targetUserType) {
            this.targetUserType = targetUserType;
            return this;
        }
        public ApnsHmsOptions build(){
            return new ApnsHmsOptions(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/apns/Aps.java
New file
@@ -0,0 +1,129 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.apns;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
public class Aps {
    @JSONField(name = "alert")
    private Object alert;
    @JSONField(name = "badge")
    private Integer badge;
    @JSONField(name = "sound")
    private String sound;
    @JSONField(name = "content-available")
    private Integer contentAvailable;
    @JSONField(name = "category")
    private String category;
    @JSONField(name = "thread-id")
    private String threadId;
    public Object getAlert() {
        return alert;
    }
    public Integer getBadge() {
        return badge;
    }
    public String getSound() {
        return sound;
    }
    public Integer getContentAvailable() {
        return contentAvailable;
    }
    public String getCategory() {
        return category;
    }
    public String getThreadId() {
        return threadId;
    }
    public void check() {
        if (this.alert != null) {
            if(this.alert instanceof Alert){
                ((Alert) this.alert).check();
            }else{
                ValidatorUtils.checkArgument((this.alert instanceof String), "Alter should be Dictionary or String");
            }
        }
    }
    private Aps(Builder builder) {
        this.alert = builder.alert;
        this.badge = builder.badge;
        this.sound = builder.sound;
        this.contentAvailable = builder.contentAvailable;
        this.category = builder.category;
        this.threadId = builder.threadId;
    }
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private Object alert;
        private Integer badge;
        private String sound;
        private Integer contentAvailable;
        private String category;
        private String threadId;
        public Builder setAlert(Object alert) {
            this.alert = alert;
            return this;
        }
        public Builder setBadge(Integer badge) {
            this.badge = badge;
            return this;
        }
        public Builder setSound(String sound) {
            this.sound = sound;
            return this;
        }
        public Builder setContentAvailable(Integer contentAvailable) {
            this.contentAvailable = contentAvailable;
            return this;
        }
        public Builder setCategory(String category) {
            this.category = category;
            return this;
        }
        public Builder setThreadId(String threadId) {
            this.threadId = threadId;
            return this;
        }
        public Aps build() {
            return new Aps(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/exception/HuaweiException.java
New file
@@ -0,0 +1,35 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.exception;
import com.google.common.base.Strings;
import com.huawei.push.util.ValidatorUtils;
/**
 * exceptions
 */
public class HuaweiException extends Exception {
    public HuaweiException(String detailMessage) {
        super(detailMessage);
        ValidatorUtils.checkArgument(!Strings.isNullOrEmpty(detailMessage), "Detail message must not be empty");
    }
    public HuaweiException(String detailMessage, Throwable cause) {
        super(detailMessage, cause);
        ValidatorUtils.checkArgument(!Strings.isNullOrEmpty(detailMessage), "Detail message must not be empty");
    }
}
service-push/src/main/java/com/huawei/push/exception/HuaweiMesssagingException.java
New file
@@ -0,0 +1,39 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.exception;
import com.google.common.base.Strings;
import com.huawei.push.util.ValidatorUtils;
public class HuaweiMesssagingException extends HuaweiException {
    private final String errorCode;
    public HuaweiMesssagingException(String errorCode, String message) {
        super(message);
        ValidatorUtils.checkArgument(!Strings.isNullOrEmpty(errorCode));
        this.errorCode = errorCode;
    }
    public HuaweiMesssagingException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        ValidatorUtils.checkArgument(!Strings.isNullOrEmpty(errorCode));
        this.errorCode = errorCode;
    }
    public String getErrorCode() {
        return errorCode;
    }
}
service-push/src/main/java/com/huawei/push/message/AndroidConfig.java
New file
@@ -0,0 +1,201 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.message;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.android.AndroidNotification;
import com.huawei.push.model.Urgency;
import com.huawei.push.util.ValidatorUtils;
import org.apache.commons.lang3.StringUtils;
public class AndroidConfig {
    private static final String TTL_PATTERN = "\\d+|\\d+[sS]|\\d+.\\d{1,9}|\\d+.\\d{1,9}[sS]";
    @JSONField(name = "collapse_key")
    private Integer collapseKey;
    @JSONField(name = "urgency")
    private String urgency;
    @JSONField(name = "category")
    private String category;
    @JSONField(name = "ttl")
    private String ttl;
    @JSONField(name = "bi_tag")
    private String biTag;
    @JSONField(name = "fast_app_target")
    private Integer fastAppTargetType;
    @JSONField(name = "data")
    private String data;
    @JSONField(name = "notification")
    private AndroidNotification notification;
    public AndroidConfig(Builder builder) {
        this.collapseKey = builder.collapseKey;
        this.urgency = builder.urgency;
        this.category = builder.category;
        if (null != builder.ttl) {
            this.ttl = builder.ttl;
        } else {
            this.ttl = null;
        }
        this.biTag = builder.biTag;
        this.fastAppTargetType = builder.fastAppTargetType;
        this.data = builder.data;
        this.notification = builder.notification;
    }
    /**
     * check androidConfig's parameters
     *
     * @param notification whcic is in message
     */
    public void check(Notification notification) {
        if (this.collapseKey != null) {
            ValidatorUtils.checkArgument((this.collapseKey >= -1 && this.collapseKey <= 100), "Collapse Key should be [-1, 100]");
        }
        if (this.urgency != null) {
            ValidatorUtils.checkArgument(StringUtils.equals(this.urgency, Urgency.HIGH.getValue())
                            || StringUtils.equals(this.urgency, Urgency.NORMAL.getValue()),
                    "urgency shouid be [HIGH, NORMAL]");
        }
        if (StringUtils.isNotEmpty(this.ttl)) {
            ValidatorUtils.checkArgument(this.ttl.matches(AndroidConfig.TTL_PATTERN), "The TTL's format is wrong");
        }
        if (this.fastAppTargetType != null) {
            ValidatorUtils.checkArgument(this.fastAppTargetType == 1 || this.fastAppTargetType == 2, "Invalid fast app target type[one of 1 or 2]");
        }
        if (null != this.notification) {
            this.notification.check(notification);
        }
    }
    /**
     * getter
     */
    public Integer getCollapseKey() {
        return collapseKey;
    }
    public String getTtl() {
        return ttl;
    }
    public String getCategory() {
        return category;
    }
    public String getBiTag() {
        return biTag;
    }
    public Integer getFastAppTargetType() {
        return fastAppTargetType;
    }
    public AndroidNotification getNotification() {
        return notification;
    }
    public String getUrgency() {
        return urgency;
    }
    public String getData() {
        return data;
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private Integer collapseKey;
        private String urgency;
        private String category;
        private String ttl;
        private String biTag;
        private Integer fastAppTargetType;
        private String data;
        private AndroidNotification notification;
        private Builder() {
        }
        public Builder setCollapseKey(Integer collapseKey) {
            this.collapseKey = collapseKey;
            return this;
        }
        public Builder setUrgency(String urgency) {
            this.urgency = urgency;
            return this;
        }
        public Builder setCategory(String category) {
            this.category = category;
            return this;
        }
        /**
         * time-to-live
         */
        public Builder setTtl(String ttl) {
            this.ttl = ttl;
            return this;
        }
        public Builder setBiTag(String biTag) {
            this.biTag = biTag;
            return this;
        }
        public Builder setFastAppTargetType(Integer fastAppTargetType) {
            this.fastAppTargetType = fastAppTargetType;
            return this;
        }
        public Builder setData(String data) {
            this.data = data;
            return this;
        }
        public Builder setNotification(AndroidNotification notification) {
            this.notification = notification;
            return this;
        }
        public AndroidConfig build() {
            return new AndroidConfig(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/message/ApnsConfig.java
New file
@@ -0,0 +1,127 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.message;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.apns.ApnsHeaders;
import com.huawei.push.apns.ApnsHmsOptions;
import com.huawei.push.apns.Aps;
import com.huawei.push.util.CollectionUtils;
import java.util.HashMap;
import java.util.Map;
public class ApnsConfig {
    @JSONField(name = "hms_options")
    private ApnsHmsOptions hmsOptions;
    @JSONField(name = "headers")
    private ApnsHeaders apnsHeaders;
    @JSONField(name = "payload")
    private Map<String, Object> payload = new HashMap<>();
    public void check() {
        if (this.hmsOptions != null) {
            this.hmsOptions.check();
        }
        if (this.apnsHeaders != null) {
            this.apnsHeaders.check();
        }
        if (this.payload != null) {
            if (this.payload.get("aps") != null) {
                Aps aps = (Aps) this.payload.get("aps");
                aps.check();
            }
        }
    }
    public ApnsConfig(Builder builder) {
        this.hmsOptions = builder.hmsOptions;
        this.apnsHeaders = builder.apnsHeaders;
        if (!CollectionUtils.isEmpty(builder.payload) || builder.aps != null) {
            if (!CollectionUtils.isEmpty(builder.payload)) {
                this.payload.putAll(builder.payload);
            }
            if (builder.aps != null) {
                this.payload.put("aps", builder.aps);
            }
        } else {
            this.payload = null;
        }
    }
    public ApnsHmsOptions getHmsOptions() {
        return hmsOptions;
    }
    public Map<String, Object> getPayload() {
        return payload;
    }
    public ApnsHeaders getApnsHeaders() {
        return apnsHeaders;
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private ApnsHmsOptions hmsOptions;
        private Map<String, Object> payload = new HashMap<>();
        private ApnsHeaders apnsHeaders;
        private Aps aps;
        public Builder setHmsOptions(ApnsHmsOptions hmsOptions) {
            this.hmsOptions = hmsOptions;
            return this;
        }
        public Builder addPayload(String key, Object value) {
            this.payload.put(key, value);
            return this;
        }
        public Builder addAllPayload(Map<String, Object> map) {
            this.payload.putAll(map);
            return this;
        }
        public Builder setApnsHeaders(ApnsHeaders apnsHeaders) {
            this.apnsHeaders = apnsHeaders;
            return this;
        }
        public Builder addPayloadAps(Aps aps) {
            this.aps = aps;
            return this;
        }
        public Builder addPayload(Aps aps) {
            this.aps = aps;
            return this;
        }
        public ApnsConfig build() {
            return new ApnsConfig(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/message/Message.java
New file
@@ -0,0 +1,215 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.message;
import com.alibaba.fastjson.annotation.JSONField;
import com.google.common.base.Strings;
import com.google.common.primitives.Booleans;
import com.huawei.push.util.CollectionUtils;
import com.huawei.push.util.ValidatorUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class Message {
    @JSONField(name = "data")
    private String data;
    @JSONField(name = "notification")
    private Notification notification;
    @JSONField(name = "android")
    private AndroidConfig androidConfig;
    @JSONField(name = "apns")
    private ApnsConfig apns;
    @JSONField(name = "webpush")
    private WebPushConfig webpush;
    @JSONField(name = "token")
    private List<String> token = new ArrayList<>();
    @JSONField(name = "topic")
    private String topic;
    @JSONField(name = "condition")
    private String condition;
    private Message(Builder builder) {
        this.data = builder.data;
        this.notification = builder.notification;
        this.androidConfig = builder.androidConfig;
        this.apns = builder.apns;
        this.webpush = builder.webpush;
        if (!CollectionUtils.isEmpty(builder.token)) {
            this.token.addAll(builder.token);
        } else {
            this.token = null;
        }
        this.topic = builder.topic;
        this.condition = builder.condition;
        /** check after message is created */
        check();
    }
    /**
     * check message's parameters
     */
    public void check() {
        int count = Booleans.countTrue(
                !CollectionUtils.isEmpty(this.token),
                !Strings.isNullOrEmpty(this.topic),
                !Strings.isNullOrEmpty(this.condition)
        );
        ValidatorUtils.checkArgument(count == 1, "Exactly one of token, topic or condition must be specified");
        boolean isEmptyData = StringUtils.isEmpty(data);
        if (this.notification != null) {
            this.notification.check();
        }
        if (null != this.androidConfig) {
            this.androidConfig.check(this.notification);
        }
        if (this.apns != null) {
            this.apns.check();
        }
        if (this.webpush != null) {
            this.webpush.check();
        }
    }
    /**
     * getter
     */
    public String getData() {
        return data;
    }
    public Notification getNotification() {
        return notification;
    }
    public AndroidConfig getAndroidConfig() {
        return androidConfig;
    }
    public ApnsConfig getApns() {
        return apns;
    }
    public WebPushConfig getWebpush() {
        return webpush;
    }
    public List<String> getToken() {
        return token;
    }
    public String getTopic() {
        return topic;
    }
    public String getCondition() {
        return condition;
    }
    /**
     * builder
     *
     * @return
     */
    public static Builder builder() {
        return new Builder();
    }
    /**
     * push message builder
     */
    public static class Builder {
        private String data;
        private Notification notification;
        private AndroidConfig androidConfig;
        private ApnsConfig apns;
        private WebPushConfig webpush;
        private List<String> token = new ArrayList<>();
        private String topic;
        private String condition;
        private Builder() {
        }
        public Builder setData(String data) {
            this.data = data;
            return this;
        }
        public Builder setNotification(Notification notification) {
            this.notification = notification;
            return this;
        }
        public Builder setAndroidConfig(AndroidConfig androidConfig) {
            this.androidConfig = androidConfig;
            return this;
        }
        public Builder setApns(ApnsConfig apns) {
            this.apns = apns;
            return this;
        }
        public Builder setWebpush(WebPushConfig webpush) {
            this.webpush = webpush;
            return this;
        }
        public Builder addToken(String token) {
            this.token.add(token);
            return this;
        }
        public Builder addAllToken(List<String> tokens) {
            this.token.addAll(tokens);
            return this;
        }
        public Builder setTopic(String topic) {
            this.topic = topic;
            return this;
        }
        public Builder setCondition(String condition) {
            this.condition = condition;
            return this;
        }
        public Message build() {
            return new Message(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/message/Notification.java
New file
@@ -0,0 +1,122 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.message;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
import java.util.Locale;
public class Notification {
    @JSONField(name = "title")
    private String title;
    @JSONField(name = "body")
    private String body;
    @JSONField(name = "image")
    private String image;
    public Notification() {
    }
    public Notification(String title, String body) {
        this.title = title;
        this.body = body;
    }
    public Notification(String title, String body, String image) {
        this.title = title;
        this.body = body;
        this.image = image;
    }
    public Notification(Builder builder) {
        this.title = builder.title;
        this.body = builder.body;
        this.image = builder.image;
    }
    public void check() {
        if (this.image != null) {
            ValidatorUtils.checkArgument(this.image.toLowerCase(Locale.getDefault()).trim().startsWith("https"), "image's url should start with HTTPS");
        }
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getBody() {
        return body;
    }
    public void setBody(String body) {
        this.body = body;
    }
    public String getImage() {
        return image;
    }
    public void setImage(String image) {
        this.image = image;
    }
    /**
     * builder
     *
     * @return
     */
    public static Builder builder() {
        return new Builder();
    }
    /**
     * push message builder
     */
    public static class Builder {
        private String title;
        private String body;
        private String image;
        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }
        public Builder setBody(String body) {
            this.body = body;
            return this;
        }
        public Builder setImage(String image) {
            this.image = image;
            return this;
        }
        public Notification build() {
            return new Notification(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/message/TokenMessage.java
New file
@@ -0,0 +1,53 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.message;
import com.alibaba.fastjson.annotation.JSONField;
public class TokenMessage {
    @JSONField(name = "token")
    private String token;
    public String getToken() {
        return token;
    }
    public TokenMessage(Builder builder){
        this.token = builder.token;
    }
    /**
     * builder
     *
     * @return
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private String token;
        public Builder setToken(String token) {
            this.token = token;
            return this;
        }
        public TokenMessage build(){
            return new TokenMessage(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/message/TopicMessage.java
New file
@@ -0,0 +1,96 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.message;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
public class TopicMessage {
    @JSONField(name = "topic")
    private String topic;
    @JSONField(name = "tokenArray")
    private List<String> tokenArray = new ArrayList<String>();
    @JSONField(name = "token")
    private String token;
    public String getTopic() {
        return topic;
    }
    public List<String> getTokenArray() {
        return tokenArray;
    }
    public String getToken() {
        return token;
    }
    private TopicMessage(Builder builder) {
        this.topic = builder.topic;
        if (!CollectionUtils.isEmpty(builder.tokenArray)) {
            this.tokenArray.addAll(builder.tokenArray);
        } else {
            this.tokenArray = null;
        }
        this.token = builder.token;
    }
    /**
     * builder
     *
     * @return
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private String topic;
        private List<String> tokenArray = new ArrayList<String>();
        private String token;
        public Builder setTopic(String topic) {
            this.topic = topic;
            return this;
        }
        public Builder addToken(String token) {
            this.tokenArray.add(token);
            return this;
        }
        public Builder addAllToken(List<String> tokenArray) {
            this.tokenArray.addAll(tokenArray);
            return this;
        }
        public Builder setToken(String token) {
            this.token = token;
            return this;
        }
        public TopicMessage build() {
            return new TopicMessage(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/message/WebPushConfig.java
New file
@@ -0,0 +1,111 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.message;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.webpush.WebHmsOptions;
import com.huawei.push.webpush.WebNotification;
import com.huawei.push.webpush.WebpushHeaders;
public class WebPushConfig {
    @JSONField(name = "headers")
    private WebpushHeaders headers;
    @JSONField(name = "data")
    private String data;
    @JSONField(name = "notification")
    private WebNotification notification;
    @JSONField(name = "hms_options")
    private WebHmsOptions webHmsOptions;
    public WebpushHeaders getHeaders() {
        return headers;
    }
    public String getData() {
        return data;
    }
    public WebNotification getNotification() {
        return notification;
    }
    public WebHmsOptions getWebHmsOptions() {
        return webHmsOptions;
    }
    public WebPushConfig(Builder builder) {
        this.headers = builder.headers;
        this.data = builder.data;
        this.notification = builder.notification;
        this.webHmsOptions = builder.webHmsOptions;
    }
    public void check() {
        if (this.headers != null) {
            this.headers.check();
        }
        if (this.notification != null) {
            this.notification.check();
        }
        if (this.webHmsOptions != null) {
            this.webHmsOptions.check();
        }
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private WebpushHeaders headers;
        private String data;
        private WebNotification notification;
        private WebHmsOptions webHmsOptions;
        public Builder setHeaders(WebpushHeaders headers) {
            this.headers = headers;
            return this;
        }
        public Builder setData(String data) {
            this.data = data;
            return this;
        }
        public Builder setNotification(WebNotification notification) {
            this.notification = notification;
            return this;
        }
        public Builder setWebHmsOptions(WebHmsOptions webHmsOptions) {
            this.webHmsOptions = webHmsOptions;
            return this;
        }
        public WebPushConfig build() {
            return new WebPushConfig(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/messaging/HuaweiApp.java
New file
@@ -0,0 +1,260 @@
/* Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *  2019.12.15-Changed constructor HuaweiApp
 *  2019.12.15-Changed method initializeApp
 *                  Huawei Technologies Co., Ltd.
 *
 */
package com.huawei.push.messaging;
import com.google.common.collect.ImmutableList;
import com.huawei.push.util.ValidatorUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
 * The entry point of Huawei Java SDK.
 * <p>{@link HuaweiApp#initializeApp(HuaweiOption)} initializes the app instance.
 */
public class HuaweiApp {
    private static final Logger logger = LoggerFactory.getLogger(HuaweiApp.class);
    private String appId;
    private HuaweiOption option;
    /** Global lock */
    private static final Object appsLock = new Object();
    /** Store a map of <appId, HuaweiApp> */
    private static final Map<String, HuaweiApp> instances = new HashMap<>();
    /** HuaweiMessaging can be added in the pattern of service, whcih is designed for Scalability */
    private final Map<String, HuaweiService> services = new HashMap<>();
    private TokenRefresher tokenRefresher;
    private volatile ScheduledExecutorService scheduledExecutor;
    private ThreadManager threadManager;
    private ThreadManager.HuaweiExecutors executors;
    private final AtomicBoolean deleted = new AtomicBoolean();
    /** lock for synchronizing all internal HuaweiApp state changes */
    private final Object lock = new Object();
    private HuaweiApp(HuaweiOption option) {
        ValidatorUtils.checkArgument(option != null, "HuaweiOption must not be null");
        this.option = option;
        this.appId = option.getCredential().getAppId();
        this.tokenRefresher = new TokenRefresher(this);
        this.threadManager = option.getThreadManager();
        this.executors = threadManager.getHuaweiExecutors(this);
    }
    public HuaweiOption getOption() {
        return option;
    }
    public String getAppId() {
        return appId;
    }
    /**
     * Returns the instance identified by the unique appId, or throws if it does not exist.
     *
     * @param appId represents the id of the {@link HuaweiApp} instance.
     * @return the {@link HuaweiApp} corresponding to the id.
     * @throws IllegalStateException if the {@link HuaweiApp} was not initialized, either via {@link
     *     #initializeApp(HuaweiOption)} or {@link #getApps()}.
     */
    public static HuaweiApp getInstance(HuaweiOption option) {
        String appId = option.getCredential().getAppId();
        synchronized (appsLock) {
            HuaweiApp app = instances.get(appId);
            if (app != null) {
                return app;
            }
//            String errorMessage = MessageFormat.format("HuaweiApp with id {0} doesn't exist", appId);
//            throw new IllegalStateException(errorMessage);
            return initializeApp(option);
        }
    }
    /**
     * Initializes the {@link HuaweiApp} instance using the given option.
     *
     * @throws IllegalStateException if the app instance has already been initialized.
     */
    public static HuaweiApp initializeApp(HuaweiOption option) {
        String appId = option.getCredential().getAppId();
        final HuaweiApp app;
        synchronized (appsLock) {
            if (!instances.containsKey(appId)) {
                ValidatorUtils.checkState(!instances.containsKey(appId), "HuaweiApp with id " + appId + " already exists!");
                app = new HuaweiApp(option);
                instances.put(appId, app);
                app.startTokenRefresher();
            } else {
                app = getInstance(option);
            }
        }
        return app;
    }
    /** Returns a list of all HuaweiApps. */
    public static List<HuaweiApp> getApps() {
        synchronized (appsLock) {
            return ImmutableList.copyOf(instances.values());
        }
    }
    /**
     * Get all appIds which are be stored in the instances
     */
    public static List<String> getAllAppIds() {
        Set<String> allAppIds = new HashSet<>();
        synchronized (appsLock) {
            for (HuaweiApp app : instances.values()) {
                allAppIds.add(app.getAppId());
            }
        }
        List<String> sortedIdList = new ArrayList<>(allAppIds);
        Collections.sort(sortedIdList);
        return sortedIdList;
    }
    /**
     * Deletes the {@link HuaweiApp} and all its data. All calls to this {@link HuaweiApp}
     * instance will throw once it has been called.
     *
     * <p>A no-op if delete was called before.
     */
    public void delete() {
        synchronized (lock) {
            boolean valueChanged = deleted.compareAndSet(false /* expected */, true);
            if (!valueChanged) {
                return;
            }
            try {
                this.getOption().getHttpClient().close();
                this.getOption().getCredential().getHttpClient().close();
            } catch (IOException e) {
                logger.debug("Fail to close httpClient");
            }
            for (HuaweiService service : services.values()) {
                service.destroy();
            }
            services.clear();
            tokenRefresher.stop();
            threadManager.releaseHuaweiExecutors(this, executors);
            if (scheduledExecutor != null) {
                scheduledExecutor.shutdown();
                scheduledExecutor = null;
            }
        }
        synchronized (appsLock) {
            instances.remove(this.getAppId());
        }
    }
    /**
     * Check the app is not deleted, whcic is the premisi of some methods
     */
    private void checkNotDeleted() {
        String errorMessage = MessageFormat.format("HuaweiApp with id {0} was deleted", getAppId());
        ValidatorUtils.checkState(!deleted.get(), errorMessage);
    }
    /**
     * Singleton mode, ensure the scheduleExecutor is singleton
     */
    private ScheduledExecutorService singleScheduledExecutorService() {
        if (scheduledExecutor == null) {
            synchronized (lock) {
                checkNotDeleted();
                if (scheduledExecutor == null) {
                    scheduledExecutor = new HuaweiScheduledExecutor(getThreadFactory(), "huawei-scheduled-worker");
                }
            }
        }
        return scheduledExecutor;
    }
    public ThreadFactory getThreadFactory() {
        return threadManager.getThreadFactory();
    }
    private ScheduledExecutorService getScheduledExecutorService() {
        return singleScheduledExecutorService();
    }
    ScheduledFuture<?> schedule(Runnable runnable, long initialDelay, long period) {
        return getScheduledExecutorService().scheduleWithFixedDelay(runnable, initialDelay, period, TimeUnit.MILLISECONDS);
    }
    /**
     * Add service to the app, such as HuaweiMessaging, other services can be added if needed
     */
    void addService(HuaweiService service) {
        synchronized (lock) {
            checkNotDeleted();
            ValidatorUtils.checkArgument(!services.containsKey(service.getId()), "service already exists");
            services.put(service.getId(), service);
        }
    }
    HuaweiService getService(String id) {
        synchronized (lock) {
            return services.get(id);
        }
    }
    /**
     * Start the scheduled task for refreshing token automatically
     */
    public void startTokenRefresher() {
        synchronized (lock) {
            checkNotDeleted();
            tokenRefresher.start();
        }
    }
    /** It is just for test */
    public static void clearInstancesForTest() {
        synchronized (appsLock) {
            //copy before delete
            for (HuaweiApp app : ImmutableList.copyOf(instances.values())) {
                app.delete();
            }
            instances.clear();
        }
    }
}
service-push/src/main/java/com/huawei/push/messaging/HuaweiCredential.java
New file
@@ -0,0 +1,169 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.messaging;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Accept the appId and appSecret given by the user and build the credential
 * Every app has a credential which is for certification
 */
public class HuaweiCredential {
    private static final Logger logger = LoggerFactory.getLogger(HuaweiCredential.class);
    private final String PUSH_AT_URL = ResourceBundle.getBundle("hw_push_url").getString("token_server");
    private String appId;
    private String appSecret;
    private String accessToken;
    private long expireIn;
    private Lock lock;
    private CloseableHttpClient httpClient;
    private HuaweiCredential(Builder builder) {
        this.lock = new ReentrantLock();
        this.appId = builder.appId;
        this.appSecret = builder.appSecret;
        if (builder.httpClient == null) {
            httpClient = HttpClients.createDefault();
        } else {
            this.httpClient = builder.httpClient;
        }
    }
    /**
     * Refresh accessToken via HCM manually.
     */
    public final void refreshToken() {
        try {
            executeRefresh();
        } catch (IOException e) {
            logger.debug("Fail to refresh token!", e);
        }
    }
    private void executeRefresh() throws IOException {
        String requestBody = createRequestBody(appId, appSecret);
        HttpPost httpPost = new HttpPost(PUSH_AT_URL);
        StringEntity entity = new StringEntity(requestBody);
        httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
        httpPost.setEntity(entity);
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String jsonStr = EntityUtils.toString(response.getEntity());
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) {
            JSONObject jsonObject = JSONObject.parseObject(jsonStr);
            this.accessToken = jsonObject.getString("access_token");
            this.expireIn = jsonObject.getLong("expires_in") * 1000L;
        } else {
            logger.debug("Fail to refresh token!");
        }
    }
    private String createRequestBody(String appId, String appSecret) {
        return MessageFormat.format("grant_type=client_credentials&client_secret={0}&client_id={1}", appSecret, appId);
    }
    /**
     * getter
     */
    public final String getAccessToken() {
        this.lock.lock();
        String tmp;
        try {
            tmp = this.accessToken;
        } finally {
            this.lock.unlock();
        }
        return tmp;
    }
    public final long getExpireIn() {
        this.lock.lock();
        long tmp;
        try {
            tmp = this.expireIn;
        } finally {
            this.lock.unlock();
        }
        return tmp;
    }
    protected CloseableHttpClient getHttpClient() {
        return httpClient;
    }
    public String getAppId() {
        return appId;
    }
    /**
     * Builder for constructing {@link HuaweiCredential}.
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private String appId;
        private String appSecret;
        private CloseableHttpClient httpClient;
        private Builder() {
        }
        public Builder setAppId(String appId) {
            this.appId = appId;
            return this;
        }
        public Builder setAppSecret(String appSecret) {
            this.appSecret = appSecret;
            return this;
        }
        public Builder setHttpClient(CloseableHttpClient httpClient) {
            this.httpClient = httpClient;
            return this;
        }
        public HuaweiCredential build() {
            return new HuaweiCredential(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/messaging/HuaweiMessageClient.java
New file
@@ -0,0 +1,39 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.messaging;
import com.huawei.push.exception.HuaweiMesssagingException;
import com.huawei.push.message.Message;
import com.huawei.push.message.TopicMessage;
import com.huawei.push.reponse.SendResponse;
/**
 * sending messages interface
 */
public interface HuaweiMessageClient {
    /**
     * Sends the given message with HCM.
     *
     * @param message      message {@link Message}
     * @param validateOnly A boolean indicating whether to send message for test. or not.
     * @return {@link SendResponse}.
     * @throws HuaweiMesssagingException
     */
    SendResponse send(Message message, boolean validateOnly, String accessToken) throws HuaweiMesssagingException;
    SendResponse send(TopicMessage message, String operation, String accessToken) throws HuaweiMesssagingException;
}
service-push/src/main/java/com/huawei/push/messaging/HuaweiMessageClientImpl.java
New file
@@ -0,0 +1,216 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.messaging;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.huawei.push.exception.HuaweiMesssagingException;
import com.huawei.push.message.Message;
import com.huawei.push.message.TopicMessage;
import com.huawei.push.model.TopicOperation;
import com.huawei.push.reponse.SendResponse;
import com.huawei.push.reponse.TopicListResponse;
import com.huawei.push.reponse.TopicSendResponse;
import com.huawei.push.util.ValidatorUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
public class HuaweiMessageClientImpl implements HuaweiMessageClient {
    private static final String PUSH_URL = ResourceBundle.getBundle("hw_push_url").getString("push_open_url");
    private final String HcmPushUrl;
    private String hcmTopicUrl;
    private String hcmGroupUrl;
    private String hcmTokenUrl;
    private final CloseableHttpClient httpClient;
    private HuaweiMessageClientImpl(Builder builder) {
        this.HcmPushUrl = MessageFormat.format(PUSH_URL + "/v1/{0}/messages:send", builder.appId);
        this.hcmTopicUrl = MessageFormat.format(PUSH_URL + "/v1/{0}/topic:{1}", builder.appId);
        ValidatorUtils.checkArgument(builder.httpClient != null, "requestFactory must not be null");
        this.httpClient = builder.httpClient;
    }
    /**
     * getter
     */
    public String getHcmSendUrl() {
        return HcmPushUrl;
    }
    public CloseableHttpClient getHttpClient() {
        return httpClient;
    }
    @Override
    public SendResponse send(Message message, boolean validateOnly, String accessToken) throws HuaweiMesssagingException {
        try {
            return sendRequest(message, validateOnly, accessToken);
        } catch (IOException e) {
            throw new HuaweiMesssagingException(HuaweiMessaging.INTERNAL_ERROR, "Error while calling HCM backend service", e);
        }
    }
    @Override
    public SendResponse send(TopicMessage message, String operation, String accessToken) throws HuaweiMesssagingException {
        try {
            return sendRequest(message, operation, accessToken);
        } catch (IOException e) {
            throw new HuaweiMesssagingException(HuaweiMessaging.INTERNAL_ERROR, "Error while calling HCM backend service", e);
        }
    }
    private SendResponse sendRequest(TopicMessage message, String operation, String accessToken) throws IOException, HuaweiMesssagingException {
        this.hcmTopicUrl = MessageFormat.format(hcmTopicUrl, "", operation);
        HttpPost httpPost = new HttpPost(this.hcmTopicUrl);
        StringEntity entity = new StringEntity(JSON.toJSONString(message), "UTF-8");
        httpPost.setHeader("Authorization", "Bearer " + accessToken);
        httpPost.setHeader("Content-Type", "application/json;charset=utf-8");
        httpPost.setEntity(entity);
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String rpsContent = EntityUtils.toString(response.getEntity());
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) {
            JSONObject jsonObject = JSONObject.parseObject(rpsContent);
            String code = jsonObject.getString("code");
            String msg = jsonObject.getString("msg");
            String requestId = jsonObject.getString("requestId");
            if (StringUtils.equals(code, "80000000")) {
                SendResponse sendResponse;
                if (StringUtils.equals(operation, TopicOperation.LIST.getValue())) {
                    JSONArray topics = jsonObject.getJSONArray("topics");
                    sendResponse = TopicListResponse.fromCode(code, msg, requestId, topics);
                } else {
                    Integer failureCount = jsonObject.getInteger("failureCount");
                    Integer successCount = jsonObject.getInteger("successCount");
                    JSONArray errors = jsonObject.getJSONArray("errors");
                    sendResponse = TopicSendResponse.fromCode(code, msg, requestId, failureCount, successCount, errors);
                }
                return sendResponse;
            } else {
                String errorMsg = MessageFormat.format("error code : {0}, error message : {1}", String.valueOf(code), msg);
                throw new HuaweiMesssagingException(HuaweiMessaging.KNOWN_ERROR, errorMsg);
            }
        }
        HttpResponseException exception = new HttpResponseException(statusCode, rpsContent);
        throw createExceptionFromResponse(exception);
    }
    /**
     * send request
     *
     * @param message     message {@link Message}
     * @param validateOnly A boolean indicating whether to send message for test or not.
     * @param accessToken  A String for oauth
     * @return {@link SendResponse}
     * @throws IOException If a error occurs when sending request
     */
    private SendResponse sendRequest(Message message, boolean validateOnly, String accessToken) throws IOException, HuaweiMesssagingException {
        Map<String, Object> map = createRequestMap(message, validateOnly);
        HttpPost httpPost = new HttpPost(this.HcmPushUrl);
        StringEntity entity = new StringEntity(JSON.toJSONString(map), "UTF-8");
//        String aqa = JSON.toJSONString(map);
        httpPost.setHeader("Authorization", "Bearer " + accessToken);
        httpPost.setHeader("Content-Type", "application/json;charset=utf-8");
        httpPost.setEntity(entity);
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String rpsContent = EntityUtils.toString(response.getEntity());
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) {
            JSONObject jsonObject = JSONObject.parseObject(rpsContent);
            String code = jsonObject.getString("code");
            String msg = jsonObject.getString("msg");
            String requestId = jsonObject.getString("requestId");
            if (StringUtils.equals(code, "80000000")) {
                return SendResponse.fromCode(code, msg, requestId);
            } else {
                String errorMsg = MessageFormat.format("error code : {0}, error message : {1}", String.valueOf(code), msg);
                throw new HuaweiMesssagingException(HuaweiMessaging.KNOWN_ERROR, errorMsg);
            }
        }
        HttpResponseException exception = new HttpResponseException(statusCode, rpsContent);
        throw createExceptionFromResponse(exception);
    }
    /**
     * create the map of the request body, mostly for wrapping the message with validate_only
     *
     * @param message      A non-null {@link Message} to be sent.
     * @param validateOnly A boolean indicating whether to send message for test or not.
     * @return a map of request
     */
    private Map<String, Object> createRequestMap(Message message, boolean validateOnly) {
        return new HashMap<String, Object>() {
            {
                put("validate_only", validateOnly);
                put("message", message);
            }
        };
    }
    private HuaweiMesssagingException createExceptionFromResponse(HttpResponseException e) {
        String msg = MessageFormat.format("Unexpected HTTP response with status : {0}, body : {1}", e.getStatusCode(), e.getMessage());
        return new HuaweiMesssagingException(HuaweiMessaging.UNKNOWN_ERROR, msg, e);
    }
    static HuaweiMessageClientImpl fromApp(HuaweiApp app) {
        String appId = ImplHuaweiTrampolines.getAppId(app);
        return HuaweiMessageClientImpl.builder()
                .setAppId(appId)
                .setHttpClient(app.getOption().getHttpClient())
                .build();
    }
    static Builder builder() {
        return new Builder();
    }
    static final class Builder {
        private String appId;
        private CloseableHttpClient httpClient;
        private Builder() {
        }
        public Builder setAppId(String appId) {
            this.appId = appId;
            return this;
        }
        public Builder setHttpClient(CloseableHttpClient httpClient) {
            this.httpClient = httpClient;
            return this;
        }
        public HuaweiMessageClientImpl build() {
            return new HuaweiMessageClientImpl(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/messaging/HuaweiMessaging.java
New file
@@ -0,0 +1,182 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.messaging;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.huawei.push.exception.HuaweiMesssagingException;
import com.huawei.push.message.Message;
import com.huawei.push.message.TopicMessage;
import com.huawei.push.model.TopicOperation;
import com.huawei.push.reponse.SendResponse;
import com.huawei.push.util.ValidatorUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * This class is the entrance for all server-side HCM actions.
 *
 * <p>You can get a instance of {@link com.huawei.push.messaging.HuaweiMessaging}
 * by a instance of {@link com.huawei.push.messaging.HuaweiApp}, and then use it to send a message
 */
public class HuaweiMessaging {
    private static final Logger logger = LoggerFactory.getLogger(HuaweiMessaging.class);
    static final String INTERNAL_ERROR = "internal error";
    static final String UNKNOWN_ERROR = "unknown error";
    static final String KNOWN_ERROR = "known error";
    private final HuaweiApp app;
    private final Supplier<? extends HuaweiMessageClient> messagingClient;
    private HuaweiMessaging(Builder builder) {
        this.app = builder.app;
        this.messagingClient = Suppliers.memoize(builder.messagingClient);
    }
    /**
     * Gets the {@link HuaweiMessaging} instance for the specified {@link HuaweiApp}.
     *
     * @return The {@link HuaweiMessaging} instance for the specified {@link HuaweiApp}.
     */
    public static synchronized HuaweiMessaging getInstance(HuaweiApp app) {
        HuaweiMessagingService service = ImplHuaweiTrampolines.getService(app, SERVICE_ID, HuaweiMessagingService.class);
        if (service == null) {
            service = ImplHuaweiTrampolines.addService(app, new HuaweiMessagingService(app));
        }
        return service.getInstance();
    }
    private static HuaweiMessaging fromApp(final HuaweiApp app) {
        return HuaweiMessaging.builder()
                .setApp(app)
                .setMessagingClient(() -> HuaweiMessageClientImpl.fromApp(app))
                .build();
    }
    HuaweiMessageClient getMessagingClient() {
        return messagingClient.get();
    }
    /**
     * Sends the given {@link Message} via HCM.
     *
     * @param message A non-null {@link Message} to be sent.
     * @return {@link SendResponse}.
     * @throws HuaweiMesssagingException If an error occurs while handing the message off to HCM for
     *                                   delivery.
     */
    public SendResponse sendMessage(Message message) throws HuaweiMesssagingException {
        return sendMessage(message, false);
    }
    /**
     * @param topicMessage topicmessage
     * @return topic subscribe response
     * @throws HuaweiMesssagingException
     */
    public SendResponse subscribeTopic(TopicMessage topicMessage) throws HuaweiMesssagingException {
        final HuaweiMessageClient messagingClient = getMessagingClient();
        return messagingClient.send(topicMessage, TopicOperation.SUBSCRIBE.getValue(), ImplHuaweiTrampolines.getAccessToken(app));
    }
    /**
     * @param topicMessage topic Message
     * @return topic unsubscribe response
     * @throws HuaweiMesssagingException
     */
    public SendResponse unsubscribeTopic(TopicMessage topicMessage) throws HuaweiMesssagingException {
        final HuaweiMessageClient messagingClient = getMessagingClient();
        return messagingClient.send(topicMessage, TopicOperation.UNSUBSCRIBE.getValue(), ImplHuaweiTrampolines.getAccessToken(app));
    }
    /**
     * @param topicMessage topic Message
     * @return topic list
     * @throws HuaweiMesssagingException
     */
    public SendResponse listTopic(TopicMessage topicMessage) throws HuaweiMesssagingException {
        final HuaweiMessageClient messagingClient = getMessagingClient();
        return messagingClient.send(topicMessage, TopicOperation.LIST.getValue(), ImplHuaweiTrampolines.getAccessToken(app));
    }
    /**
     * Sends message {@link Message}
     *
     * <p>If the {@code validateOnly} option is set to true, the message will not be actually sent. Instead
     * HCM performs all the necessary validations, and emulates the send operation.
     *
     * @param message      message {@link Message} to be sent.
     * @param validateOnly a boolean indicating whether to send message for test or not.
     * @return {@link SendResponse}.
     * @throws HuaweiMesssagingException exception.
     */
    public SendResponse sendMessage(Message message, boolean validateOnly) throws HuaweiMesssagingException {
        ValidatorUtils.checkArgument(message != null, "message must not be null");
        final HuaweiMessageClient messagingClient = getMessagingClient();
        return messagingClient.send(message, validateOnly, ImplHuaweiTrampolines.getAccessToken(app));
    }
    /**
     * HuaweiMessagingService
     */
    private static final String SERVICE_ID = HuaweiMessaging.class.getName();
    private static class HuaweiMessagingService extends HuaweiService<HuaweiMessaging> {
        HuaweiMessagingService(HuaweiApp app) {
            super(SERVICE_ID, HuaweiMessaging.fromApp(app));
        }
        @Override
        public void destroy() {
        }
    }
    /**
     * Builder for constructing {@link HuaweiMessaging}.
     */
    static Builder builder() {
        return new Builder();
    }
    static class Builder {
        private HuaweiApp app;
        private Supplier<? extends HuaweiMessageClient> messagingClient;
        private Builder() {
        }
        public Builder setApp(HuaweiApp app) {
            this.app = app;
            return this;
        }
        public Builder setMessagingClient(Supplier<? extends HuaweiMessageClient> messagingClient) {
            this.messagingClient = messagingClient;
            return this;
        }
        public HuaweiMessaging build() {
            return new HuaweiMessaging(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/messaging/HuaweiOption.java
New file
@@ -0,0 +1,111 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.messaging;
import com.huawei.push.util.IgnoreSSLUtils;
import com.huawei.push.util.ValidatorUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
/** Configurable HCM options. */
public class HuaweiOption {
    private static final Logger logger = LoggerFactory.getLogger(HuaweiOption.class);
    private final HuaweiCredential credential;
    private final CloseableHttpClient httpClient;
    private final ThreadManager threadManager;
    private HuaweiOption(HuaweiOption.Builder builder) {
        ValidatorUtils.checkArgument(builder.credential != null, "HuaweiOption must be initialized with setCredential()");
        this.credential = builder.credential;
        ValidatorUtils.checkArgument(builder.httpClient != null, "HuaweiOption must be initialized with a non-null httpClient");
        this.httpClient = builder.httpClient;
        ValidatorUtils.checkArgument(builder.threadManager != null, "HuaweiOption must be initialized with a non-null threadManager");
        this.threadManager = builder.threadManager;
    }
    /**
     * Returns a instance of HuaweiCredential used for refreshing token.
     *
     * @return A <code>HuaweiCredential</code> instance.
     */
    public HuaweiCredential getCredential() {
        return credential;
    }
    /**
     * Returns a instance of httpclient used for sending http request.
     *
     * @return A <code>httpclient</code> instance.
     */
    public CloseableHttpClient getHttpClient() {
        return httpClient;
    }
    public ThreadManager getThreadManager() {
        return threadManager;
    }
    /**
     * Builder for constructing {@link HuaweiOption}.
     */
    public static Builder builder() {
        return new Builder();
    }
    public static final class Builder {
        private HuaweiCredential credential;
        private CloseableHttpClient httpClient;
        {
            try {
                httpClient = IgnoreSSLUtils.createClient();
            } catch (KeyManagementException | NoSuchAlgorithmException e) {
                logger.debug("Fail to create httpClient for sending message", e);
            }
        }
        private ThreadManager threadManager = HuaweiThreadManager.DEFAULT_THREAD_MANAGER;
        public Builder() {}
        public Builder setCredential(HuaweiCredential credential) {
            this.credential = credential;
            return this;
        }
        public Builder setHttpClient(CloseableHttpClient httpClient) {
            this.httpClient = httpClient;
            return this;
        }
        public Builder setThreadManager(ThreadManager threadManager) {
            this.threadManager = threadManager;
            return this;
        }
        public HuaweiOption build() {
            return new HuaweiOption(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/messaging/HuaweiScheduledExecutor.java
New file
@@ -0,0 +1,59 @@
/* Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *  2019.12.15-Changed constructor HuaweiScheduledExecutor
 *                  Huawei Technologies Co., Ltd.
 *
 */
package com.huawei.push.messaging;
import com.google.common.base.Strings;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import static com.google.common.base.Preconditions.checkArgument;
/**
 * A single-threaded scheduled executor implementation.
 */
public class HuaweiScheduledExecutor extends ScheduledThreadPoolExecutor {
    public HuaweiScheduledExecutor(ThreadFactory threadFactory, String name) {
        this(threadFactory, name, null);
    }
    public HuaweiScheduledExecutor(ThreadFactory threadFactory, String name, Thread.UncaughtExceptionHandler handler) {
        super(1, decorateThreadFactory(threadFactory, name, handler));
        setRemoveOnCancelPolicy(true);
    }
    static ThreadFactory getThreadFactoryWithName(ThreadFactory threadFactory, String name) {
        return decorateThreadFactory(threadFactory, name, null);
    }
    private static ThreadFactory decorateThreadFactory(
            ThreadFactory threadFactory, String name, Thread.UncaughtExceptionHandler handler) {
        checkArgument(!Strings.isNullOrEmpty(name));
        ThreadFactoryBuilder builder = new ThreadFactoryBuilder()
                .setThreadFactory(threadFactory)
                .setNameFormat(name)
                .setDaemon(true);
        if (handler != null) {
            builder.setUncaughtExceptionHandler(handler);
        }
        return builder.build();
    }
}
service-push/src/main/java/com/huawei/push/messaging/HuaweiService.java
New file
@@ -0,0 +1,40 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.messaging;
/**
 * the services exposed from the HCM SDK
 * Each instance of the class is associated with one instance of the HuaweiApp
 */
public abstract class HuaweiService<T> {
    private String id;
    protected T instance;
    protected HuaweiService(String id, T instance) {
        this.id = id;
        this.instance = instance;
    }
    public String getId() {
        return id;
    }
    public T getInstance() {
        return instance;
    }
    public abstract void destroy();
}
service-push/src/main/java/com/huawei/push/messaging/HuaweiThreadManager.java
New file
@@ -0,0 +1,94 @@
/* Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *  2019.12.15-Changed method releaseExecutor
 *                  Huawei Technologies Co., Ltd.
 *
 */
package com.huawei.push.messaging;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/** Default ThreadManager implementations used by the HCM SDK */
public class HuaweiThreadManager {
    public static final ThreadManager DEFAULT_THREAD_MANAGER = new DefaultThreadManager();
    /**
     * An abstract ThreadManager implementation that uses the same executor service
     * across all active apps. The executor service is initialized when the first app is initialized,
     * and terminated when the last app is deleted. This class is thread safe.
     */
    abstract static class GlobalThreadManager extends ThreadManager {
        private final Object lock = new Object();
        private final Set<String> apps = new HashSet<>();
        private ExecutorService executorService;
        @Override
        protected ExecutorService getExecutor(HuaweiApp app) {
            synchronized (lock) {
                if (executorService == null) {
                    executorService = doInit();
                }
                apps.add(app.getOption().getCredential().getAppId());
                return executorService;
            }
        }
        @Override
        protected void releaseExecutor(HuaweiApp app, ExecutorService executor) {
            synchronized (lock) {
                String appId = app.getOption().getCredential().getAppId();
                if (apps.remove(appId) && apps.isEmpty()) {
                    doCleanup(executorService);
                    executorService = null;
                }
            }
        }
        /**
         * Initializes the executor service. Called when the first application is initialized.
         */
        protected abstract ExecutorService doInit();
        /**
         * Cleans up the executor service. Called when the last application is deleted.
         */
        protected abstract void doCleanup(ExecutorService executorService);
    }
    private static class DefaultThreadManager extends GlobalThreadManager {
        @Override
        protected ExecutorService doInit() {
            ThreadFactory threadFactory = HuaweiScheduledExecutor.getThreadFactoryWithName(getThreadFactory(), "huawei-default-%d");
            return Executors.newCachedThreadPool(threadFactory);
        }
        @Override
        protected void doCleanup(ExecutorService executorService) {
            executorService.shutdown();
        }
        @Override
        protected ThreadFactory getThreadFactory() {
            return Executors.defaultThreadFactory();
        }
    }
}
service-push/src/main/java/com/huawei/push/messaging/ImplHuaweiTrampolines.java
New file
@@ -0,0 +1,53 @@
/* Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *  2019.12.15-added method getAccessToken
 *                  Huawei Technologies Co., Ltd.
 *
 */
package com.huawei.push.messaging;
import org.apache.http.client.HttpClient;
/**
 * Provides trampolines into package-private APIs used by components of HCM
 */
public final class ImplHuaweiTrampolines {
    private ImplHuaweiTrampolines() {}
    public static HuaweiCredential getCredential(HuaweiApp app) {
        return app.getOption().getCredential();
    }
    public static String getAccessToken(HuaweiApp app) {
        return app.getOption().getCredential().getAccessToken();
    }
    public static String getAppId(HuaweiApp app) {
        return app.getOption().getCredential().getAppId();
    }
    public static HttpClient getHttpClient(HuaweiApp app) {
        return app.getOption().getHttpClient();
    }
    public static <T extends HuaweiService> T getService(HuaweiApp app, String id, Class<T> type) {
        return type.cast(app.getService(id));
    }
    public static <T extends HuaweiService> T addService(HuaweiApp app, T service) {
        app.addService(service);
        return service;
    }
}
service-push/src/main/java/com/huawei/push/messaging/ThreadManager.java
New file
@@ -0,0 +1,81 @@
/* Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *  2019.12.15-changed method getHuaweiExecutors
 *  2019.12.15-changed method releaseHuaweiExecutors
 *  2019.12.15-changed method getExecutor
 *  2019.12.15-changed method releaseExecutor
 *                  Huawei Technologies Co., Ltd.
 *
 */
package com.huawei.push.messaging;
import com.huawei.push.util.ValidatorUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
/**
 * An interface that controls the thread pools and thread factories used by the HCM SDK. Each
 * instance of HuaweiApp uses an implementation of this interface to create and manage
 * threads.
 */
public abstract class ThreadManager {
    final HuaweiExecutors getHuaweiExecutors(HuaweiApp app) {
        return new HuaweiExecutors(getExecutor(app));
    }
    final void releaseHuaweiExecutors(HuaweiApp app, HuaweiExecutors executor) {
        releaseExecutor(app, executor.userExecutor);
    }
    /**
     * Returns the main thread pool for an app.
     *
     * @param app A {@link HuaweiApp} instance.
     * @return A non-null <code>ExecutorService</code> instance.
     */
    protected abstract ExecutorService getExecutor(HuaweiApp app);
    /**
     * Cleans up the thread pool associated with an app.
     * This method is invoked when an app is deleted.
     *
     * @param app A {@link HuaweiApp} instance.
     * @return A non-null <code>ExecutorService</code> instance.
     */
    protected abstract void releaseExecutor(HuaweiApp app, ExecutorService executor);
    /**
     * Returns the <code>ThreadFactory</code> to be used for creating long-lived threads. This is
     * used mainly to create the long-lived worker threads for the scheduled (periodic) tasks started by the SDK.
     * The SDK guarantees clean termination of all threads started via this <code>ThreadFactory</code>, when the user
     * calls {@link HuaweiApp#delete()}.
     *
     * <p>If long-lived threads cannot be supported in the current runtime, this method may
     * throw a {@code RuntimeException}.
     *
     * @return A non-null <code>ThreadFactory</code>.
     */
    protected abstract ThreadFactory getThreadFactory();
    static final class HuaweiExecutors {
        private ExecutorService userExecutor;
        private HuaweiExecutors(ExecutorService userExecutor) {
            ValidatorUtils.checkArgument(userExecutor != null, "ExecutorService must not be null");
            this.userExecutor = userExecutor;
        }
    }
}
service-push/src/main/java/com/huawei/push/messaging/TokenRefresher.java
New file
@@ -0,0 +1,96 @@
/* Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *  2019.12.15-changed constructor TokenRefresher
 *                  Huawei Technologies Co., Ltd.
 *
 */
package com.huawei.push.messaging;
import com.huawei.push.util.ValidatorUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Future;
/**
 * Utility class for scheduling proactive token refresh events. Each HuaweiApp should have
 * its own instance of this class. TokenRefresher schedules token refresh events when token is expired.
 *
 * <p>This class is thread safe. It will handle only one token change event at a time. It also
 * cancels any pending token refresh events, before scheduling a new one.
 */
public class TokenRefresher {
    private static final Logger logger = LoggerFactory.getLogger(TokenRefresher.class);
    private HuaweiApp app;
    private HuaweiCredential credential;
    private Future future;
    public TokenRefresher(HuaweiApp app) {
        ValidatorUtils.checkArgument(app != null, "app must not be null");
        this.app = app;
        this.credential = app.getOption().getCredential();
    }
    protected void scheduleNext(Runnable task, long initialDelay, long period) {
        logger.debug("Scheduling next token refresh in {} milliseconds", period);
        try {
            future = app.schedule(task, initialDelay, period);
        } catch (UnsupportedOperationException e) {
            logger.debug("Failed to schedule token refresh event", e);
        }
    }
    /**
     * Schedule a forced token refresh to be executed after a specified period.
     *
     * @param period in milliseconds, after which the token should be forcibly refreshed.
     */
    public void scheduleRefresh(final long period) {
        cancelPrevious();
        scheduleNext(() -> credential.refreshToken(), period, period);
    }
    private void cancelPrevious() {
        if (future != null) {
            future.cancel(true);
        }
    }
    /**
     * Starts the TokenRefresher if not already started. Starts refreshing when token is expired. If no active
     * token is present, or if the available token is expired soon, this will also refresh immediately.
     */
    final synchronized void start() {
        logger.debug("Starting the proactive token refresher");
        String accessToken = credential.getAccessToken();
        long refreshDelay;
        if (accessToken == null || StringUtils.isEmpty(accessToken)) {
            credential.refreshToken();
        }
        refreshDelay = credential.getExpireIn();
        scheduleRefresh(refreshDelay);
    }
    final synchronized void stop() {
        cancelPrevious();
        logger.debug("Stopped the proactive token refresher");
    }
}
service-push/src/main/java/com/huawei/push/model/Importance.java
New file
@@ -0,0 +1,48 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.model;
public enum Importance {
    /**
     * LOW
     */
    LOW("LOW"),
    /**
     * NORMAL
     */
    NORMAL("NORMAL"),
    /**
     * HIGH
     */
    HIGH("HIGH");
    private String value;
    private Importance(String value) {
        this.value = value;
    }
    /**
     * Gets value *
     *
     * @return the value
     */
    public String getValue() {
        return value;
    }
}
service-push/src/main/java/com/huawei/push/model/TopicOperation.java
New file
@@ -0,0 +1,37 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.model;
public enum  TopicOperation {
    SUBSCRIBE("subscribe"),
    UNSUBSCRIBE("unsubscribe"),
    LIST("list");
    private String value;
    private TopicOperation(String value) {
        this.value = value;
    }
    /**
     * Gets value *
     *
     * @return the value
     */
    public String getValue() {
        return value;
    }
}
service-push/src/main/java/com/huawei/push/model/Urgency.java
New file
@@ -0,0 +1,36 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.model;
public enum Urgency {
    HIGH("HIGH"),
    NORMAL("NORMAL");
    private String value;
    private Urgency(String value) {
        this.value = value;
    }
    /**
     * Gets value *
     *
     * @return the value
     */
    public String getValue() {
        return value;
    }
}
service-push/src/main/java/com/huawei/push/model/Visibility.java
New file
@@ -0,0 +1,38 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.model;
public enum Visibility {
    VISIBILITY_UNSPECIFIED("VISIBILITY_UNSPECIFIED"),
    PRIVATE("PRIVATE"),
    PUBLIC("PUBLIC"),
    SECRET("SECRET");
    private String value;
    private Visibility(String value) {
        this.value = value;
    }
    /**
     * Gets value *
     *
     * @return the value
     */
    public String getValue() {
        return value;
    }
}
service-push/src/main/java/com/huawei/push/reponse/SendResponse.java
New file
@@ -0,0 +1,48 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.reponse;
/**
 * A class of reponse which exposed to developer
 */
public class SendResponse {
    private final String code;
    private final String msg;
    private final String requestId;
    protected SendResponse(String coede, String msg, String requestId) {
        this.code = coede;
        this.msg = msg;
        this.requestId = requestId;
    }
    public String getCode() {
        return code;
    }
    public String getMsg() {
        return msg;
    }
    public String getRequestId() {
        return requestId;
    }
    public static SendResponse fromCode(String coede, String msg, String requestId) {
        return new SendResponse(coede, msg, requestId);
    }
}
service-push/src/main/java/com/huawei/push/reponse/TopicListResponse.java
New file
@@ -0,0 +1,35 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.reponse;
import com.alibaba.fastjson.JSONArray;
public class TopicListResponse extends SendResponse {
    private final JSONArray topics;
    public JSONArray getTopics() {
        return topics;
    }
    private TopicListResponse(String code, String msg, String requestId, JSONArray topics) {
        super(code, msg, requestId);
        this.topics = topics;
    }
    public static TopicListResponse fromCode(String code, String msg, String requestId, JSONArray topics) {
        return new TopicListResponse(code, msg, requestId, topics);
    }
}
service-push/src/main/java/com/huawei/push/reponse/TopicSendResponse.java
New file
@@ -0,0 +1,47 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.reponse;
import com.alibaba.fastjson.JSONArray;
public final class TopicSendResponse extends SendResponse {
    private final Integer failureCount;
    private final Integer successCount;
    private final JSONArray errors;
    public Integer getFailureCount() {
        return failureCount;
    }
    public Integer getSuccessCount() {
        return successCount;
    }
    public JSONArray getErrors() {
        return errors;
    }
    private TopicSendResponse(String code, String msg, String requestId, Integer failureCount, Integer successCount, JSONArray errors) {
        super(code,msg,requestId);
        this.failureCount = failureCount;
        this.successCount = successCount;
        this.errors = errors;
    }
    public static TopicSendResponse fromCode(String code, String msg, String requestId,Integer failureCount,Integer successCount,JSONArray errors ) {
        return new TopicSendResponse(code, msg, requestId,failureCount,successCount,errors);
    }
}
service-push/src/main/java/com/huawei/push/util/CollectionUtils.java
New file
@@ -0,0 +1,35 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.util;
import java.util.Collection;
import java.util.Map;
/**
 * A tool for processing collections
 */
public class CollectionUtils {
    public CollectionUtils() {
    }
    public static boolean isEmpty(Collection<?> collection) {
        return collection == null || collection.isEmpty();
    }
    public static boolean isEmpty(Map<?, ?> map) {
        return map == null || map.isEmpty();
    }
}
service-push/src/main/java/com/huawei/push/util/IgnoreSSLUtils.java
New file
@@ -0,0 +1,79 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.util;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
/**
 * A tool for ignoring ssl when creating httpclient
 */
public class IgnoreSSLUtils {
    private static SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        X509TrustManager trustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(
                    java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                    String paramString) throws CertificateException {
            }
            @Override
            public void checkServerTrusted(
                    java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                    String paramString) throws CertificateException {
            }
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        sc.init(null, new TrustManager[]{trustManager}, null);
        return sc;
    }
    /**
     * for ignoring verify SSL
     */
    public static CloseableHttpClient createClient() throws KeyManagementException, NoSuchAlgorithmException {
        SSLContext sslcontext = createIgnoreVerifySSL();
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", new SSLConnectionSocketFactory(sslcontext))
                .build();
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        HttpClients.custom().setConnectionManager(connManager);
        return HttpClients.custom().setConnectionManager(connManager).build();
    }
}
service-push/src/main/java/com/huawei/push/util/InitAppUtils.java
New file
@@ -0,0 +1,29 @@
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2024. All rights reserved.
 */
package com.huawei.push.util;
import com.huawei.push.messaging.HuaweiApp;
import com.huawei.push.messaging.HuaweiCredential;
import com.huawei.push.messaging.HuaweiOption;
import java.util.ResourceBundle;
public class InitAppUtils {
    public static HuaweiApp initializeApp(String appId, String appSecret) {
        HuaweiCredential credential = HuaweiCredential.builder()
                .setAppId(appId)
                .setAppSecret(appSecret)
                .build();
        // Create HuaweiOption
        HuaweiOption option = HuaweiOption.builder()
                .setCredential(credential)
                .build();
        // Initialize HuaweiApp
//        return HuaweiApp.initializeApp(option);
        return HuaweiApp.getInstance(option);
    }
}
service-push/src/main/java/com/huawei/push/util/ResponceCodeProcesser.java
New file
@@ -0,0 +1,61 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.util;
import java.util.HashMap;
import java.util.Map;
/**
 * A tool for processing the responce code
 */
public class ResponceCodeProcesser {
    private static final Map<Integer, String> codeMap = new HashMap<Integer, String>() {
        {
            put(80000000, "Success");
            put(80100000, "Some tokens are right, the others are illegal");
            put(80100001, "Parameters check error");
            put(80100002, "Token number should be one when send sys message");
            put(80100003, "Message structure error");
            put(80100004, "TTL is less than current time, please check");
            put(80100013, "Collapse_key is illegal, please check");
            put(80100016, "Message contians sensitive information, please check");
            put(80200001, "Oauth authentication error");
            put(80200003, "Oauth Token expired");
            put(80300002, "APP is forbidden to send");
            put(80300007, "Invalid Token");
            put(80300008, "The message body size exceeds the default value set by the system (4K)");
            put(80300010, "Tokens exceed the default value");
            put(81000001, "System inner error");
            put(82000001, "GroupKey or groupName error");
            put(82000002, "GroupKey and groupName do not match");
            put(82000003, "Token array is null");
            put(82000004, "Group do not exist");
            put(82000005, "Group do not belond to this app");
            put(82000006, "Token array or group number is transfinited");
            put(82000007, "Invalid topic");
            put(82000008, "Token array null or transfinited");
            put(82000009, "Topic num transfinited, at most 2000");
            put(82000010, "Some token is wrong");
            put(82000011, "Token is null");
            put(82000012, "Data storage location is not selected");
            put(82000013, "Data storage location does not match the actual data");
        }
    };
    public static String process(Integer code) {
        return codeMap.getOrDefault(code, "Unknown code");
    }
}
service-push/src/main/java/com/huawei/push/util/ValidatorUtils.java
New file
@@ -0,0 +1,40 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.util;
/**
 * A tool for validating the parameters
 */
public class ValidatorUtils {
    public static void checkArgument(boolean expression) {
        if (!expression) {
            throw new IllegalArgumentException();
        }
    }
    public static void checkArgument(boolean expression, Object errorMessage) {
        if (!expression) {
            throw new IllegalArgumentException(String.valueOf(errorMessage));
        }
    }
    public static void checkState(boolean expression, Object errorMessage) {
        if (!expression) {
            throw new IllegalStateException(String.valueOf(errorMessage));
        }
    }
}
service-push/src/main/java/com/huawei/push/util/push/HuaWeiPushUtil.java
New file
@@ -0,0 +1,168 @@
package com.huawei.push.util.push;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.huawei.push.android.AndroidNotification;
import com.huawei.push.android.ClickAction;
import com.huawei.push.message.AndroidConfig;
import com.huawei.push.messaging.HuaweiApp;
import com.huawei.push.messaging.HuaweiMessaging;
import com.huawei.push.reponse.SendResponse;
import com.huawei.push.util.InitAppUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yeshi.utils.push.entity.PushAppInfo;
import org.yeshi.utils.push.entity.PushMessage;
import org.yeshi.utils.push.exception.MeiZuPushException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
/**
 * 华为推送
 * sdk链接:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Examples-V5/server-sample-code-0000001050986079-V5
 */
public class HuaWeiPushUtil {
    static Logger logger = LoggerFactory.getLogger(HuaWeiPushUtil.class);
    private static AndroidNotification createMessage(PushMessage message) {
        String title = message.getTitle();
        String content = message.getContent();
        while (title.length() > 40 - 3) {
            title = title.substring(0, title.length() - 1);
        }
        if (title.length() == 40 - 3) {
            title += "...";
        }
        while (content.length() > 1024 - 3) {
            content = content.substring(0, content.length() - 1);
        }
        if (content.length() == 1024 - 3) {
            title += "...";
        }
        String uri = String.format("intent://%s?#Intent;scheme=%s;launchFlags=0x4000000;", message.getActivityHostPath(), message.getActivityScheme());
        //增加Activity的跳转参数
        Map<String, String> activityParams = message.getActivityParams();
        for (Iterator<String> its = activityParams.keySet().iterator(); its.hasNext(); ) {
            String k = its.next();
            logger.info("activityParams:{}-{}", k, activityParams.get(k));
            try {
                if (activityParams.get(k) != null) {
                    uri += String.format("S.%s=%s;", k, URLEncoder.encode(activityParams.get(k), "UTF-8"));
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        uri += "end";
        //参数说明链接:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/https-send-api-0000001050986197-V5
        AndroidNotification androidNotification = AndroidNotification.builder()
                .setTitle(title)
                .setBody(content)
                .setClickAction(ClickAction.builder()
                        //消息点击行为类型,取值如下:
                        //1:打开应用自定义页面
                        //2:点击后打开特定URL
                        //3:点击后打开应用
                        .setType(1)
                        //设置打开特定URL,本字段填写需要打开的URL, 当type为2时必选。
//                        .setUrl("https://www.huawei.com")
                        //自定义页面中intent的实现,请参见指定intent参数​。
                        //当type为1时,字段intent和action至少二选一。
                        .setIntent(uri)
                        .build())
                //0:默认样式
                //1:大文本样式
                //3:Inbox样式
//                .setStyle(1)
                .build();
        return androidNotification;
    }
    /**
     * 推送通知
     *
     * @param appInfo
     * @param message
     * @param bigTag
     * @param tokens  不能超过1000个
     * @return
     * @throws IOException
     * @throws MeiZuPushException
     */
    public static String pushNotificationByTokens(PushAppInfo appInfo, PushMessage message,String bigTag, List<String> tokens) throws Exception {
        HuaweiApp app = InitAppUtils.initializeApp(appInfo.getAppId(), appInfo.getAppSecret());
        HuaweiMessaging huaweiMessaging = HuaweiMessaging.getInstance(app);
        AndroidNotification androidNotification = createMessage(message);
        //参数说明:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/https-send-api-0000001050986197-V5#ZH-CN_TOPIC_0000001124288117__p837521812163
        AndroidConfig androidConfig = AndroidConfig.builder().setCollapseKey(-1)
                //透传消息投递优先级,取值如下:
                //
                //HIGH
                //NORMAL
                //设置为HIGH时需要申请权限,请参见申请特殊权限。
                //
                //HIGH级别消息到达用户手机时可强制拉起应用进程。
//                .setUrgency(Urgency.HIGH.getValue())
                //消息缓存时间,单位是秒
//                .setTtl("10000s")
                //批量任务消息标识,消息回执时会返回给应用服务器,应用服务器可以识别bi_tag对消息的下发情况进行统计分析。
                .setBiTag(bigTag)
                .setNotification(androidNotification)
                .build();
        com.huawei.push.message.Message msg = com.huawei.push.message.Message.builder()
                .setAndroidConfig(androidConfig)
                .addAllToken(tokens)
                .build();
        SendResponse response = huaweiMessaging.sendMessage(msg);
        logger.info("华为推送响应结果:{}", new Gson().toJson(response));
        return response.getRequestId();
    }
    public static void main(String[] args) {
        PushAppInfo appInfo = new PushAppInfo("104190661", "", "5acdb54adb1b99c80a7a68b3bacdfea45c342df0535f040304ebb343c929b3bc");
        appInfo.setPackageName("com.tejia.lijin");
        Map<String, String> activityParams = new HashMap<>();
        activityParams.put("activity", "com.tejia.lijin.app.ui.PushOpenClickActivity");
        JSONObject jumpParams = new JSONObject();
        jumpParams.put("url", "https://s.click.taobao.com/KnCv9iu");
        jumpParams.put("from", "push");
        activityParams.put("params", jumpParams.toJSONString());
        activityParams.put("type", "baichuan");
        PushMessage message = new PushMessage("大家好", "大家下午号", null, "tejiapush", "com.huawei.codelabpush/deeplink", activityParams);
        String[] ids = new String[]{
                "IQAAAACy0jK5AACFYBY3hZ7tobGQokkDBMNYPPqx_XZuIyMhiqyG8wuJoy099BmeJlIW2vIlqPXArpMw8a-av7ZZPRfBAgRQPjq317-Fvf4PQ3dyDg", "IQAAAACy0jK5AACR2ZSxytr2xPCaNh9ejS56WZphjyQlp3RaCeePbrnpd0GanNYYpQztD32lgSmOlEL6GjMBBsLyK8g4Qo59jsPMoMwZbMBk_gppEA", "IQAAAACy0jK5AAC3fru7bLf-Bsk8QuNJOSPCUi82ZHtN93YPPbbwCdd3IARs_RON3NfZ2a6uWf3yp5hzW-eKy4vIE_5qWqCUeDsaKRsCtzG1-8smIQ"
        };
        try {
            HuaWeiPushUtil.pushNotificationByTokens(appInfo, message,"null", Arrays.asList(ids));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static class AuthTokenInfo {
        public String token;
        public long expireTime;
    }
}
service-push/src/main/java/com/huawei/push/util/push/MeiZuPushUtil.java
New file
@@ -0,0 +1,115 @@
package com.huawei.push.util.push;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.meizu.push.sdk.constant.PushType;
import com.meizu.push.sdk.server.IFlymePush;
import com.meizu.push.sdk.server.constant.ResultPack;
import com.meizu.push.sdk.server.model.push.PushResult;
import com.meizu.push.sdk.server.model.push.VarnishedMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yeshi.utils.push.entity.PushAppInfo;
import org.yeshi.utils.push.entity.PushMessage;
import org.yeshi.utils.push.exception.MeiZuPushException;
import java.io.IOException;
import java.util.*;
/**
 * 魅族推送
 * 推送文档:http://open.res.flyme.cn/fileserver/upload/file/201803/e174a5709f134f64aae3fb168aec8ea3.pdf
 */
public class MeiZuPushUtil {
    static Logger logger = LoggerFactory.getLogger(MeiZuPushUtil.class);
    private static VarnishedMessage createMessage(PushAppInfo appInfo, PushMessage message) {
        VarnishedMessage.Builder builder = new VarnishedMessage.Builder().appId(Long.parseLong(appInfo.getAppId()))
                .title(message.getTitle())
                .content(message.getContent())
                //点击动作 (0,"打开应用"),(1,"打开应用页面"),(2,"打 开URI页面"),(3, "应用客户端自定义")【非必填, 默认值为0】
                .clickType(1);
        if (message.getActivity() != null) {
            builder = builder.activity(message.getActivity());
        }
        if (message.getActivityParams() != null && message.getActivityParams().size() > 0) {
            JSONObject params = new JSONObject();
            for (Iterator<String> its = message.getActivityParams().keySet().iterator(); its.hasNext(); ) {
                String key = its.next();
                params.put(key, message.getActivityParams().get(key));
            }
            builder = builder.parameters(params);
        }
        return builder.build();
    }
    /**
     * 推送通知
     *
     * @param appInfo
     * @param message
     * @param pushIds 不能超过1000个
     * @return
     * @throws IOException
     * @throws MeiZuPushException
     */
    public static String pushNotificationByPushId(PushAppInfo appInfo, PushMessage message, List<String> pushIds) throws IOException, MeiZuPushException {
        //推送标题限制字数1-32,推送内容限制字数1-100
        IFlymePush push = new IFlymePush(appInfo.getAppSecret());
        VarnishedMessage msg = createMessage(appInfo, message);
        ResultPack<PushResult> result = push.pushMessage(msg, pushIds, 2);
        logger.info("MEIZU推送响应结果:{}", result.code() + "-" + result.comment());
        if (result.isSucceed()) {
            return result.value().getMsgId();
        } else {
            throw new MeiZuPushException(result.code(), result.comment());
        }
    }
    /**
     * 推送所有
     *
     * @param appInfo
     * @param message
     * @return
     * @throws IOException
     * @throws MeiZuPushException
     */
    public static String pushNotificationAll(PushAppInfo appInfo, PushMessage message) throws IOException, MeiZuPushException {
        IFlymePush push = new IFlymePush(appInfo.getAppSecret());
        VarnishedMessage msg = createMessage(appInfo, message);
        ResultPack<Long> result = push.pushToApp(PushType.STATUSBAR, msg);
        if (result.isSucceed()) {
            return result.value() + "";
        } else {
            throw new MeiZuPushException(result.code(), result.comment());
        }
    }
    public static void main(String[] args) {
        PushAppInfo appInfo = new PushAppInfo("139085", "dc40ea74e19948a683cb1876d5a8813e", "6497adb8bde044c6a32c0a2766332ca3");
        Map<String, String> activityParams = new HashMap<>();
        activityParams.put("activity", "test");
        JSONObject jumpParams = new JSONObject();
        jumpParams.put("id", 123123);
        activityParams.put("params", jumpParams.toJSONString());
        PushMessage message = new PushMessage("你好啊", "下午好,hello world", "com.weikou.beibeivideo.ui.push.OpenClickActivity", null, null, activityParams);
        String[] ids = new String[]{
                "UCI4e0f4070047c4949057e76446d6474484500447b05"
        };
        try {
            MeiZuPushUtil.pushNotificationByPushId(appInfo, message, Arrays.asList(ids));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MeiZuPushException e) {
            e.printStackTrace();
        }
    }
}
service-push/src/main/java/com/huawei/push/util/push/OppoPushUtil.java
New file
@@ -0,0 +1,185 @@
package com.huawei.push.util.push;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.ks.push.utils.Constant;
import com.oppo.push.server.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yeshi.utils.StringUtil;
import org.yeshi.utils.push.entity.PushAppInfo;
import org.yeshi.utils.push.entity.PushMessage;
import org.yeshi.utils.push.exception.MeiZuPushException;
import org.yeshi.utils.push.exception.OppoPushException;
import java.io.IOException;
import java.util.*;
/**
 * OPPO推送
 * 推送文档:https://open.oppomobile.com/wiki/doc#id=10203
 */
public class OppoPushUtil {
    static Logger logger = LoggerFactory.getLogger(OppoPushUtil.class);
    private static Notification getNotification(PushMessage message) {
        Notification notification = new Notification();
        //推送回执
        notification.setCallBackUrl(Constant.PUSH_CALLBACK_OPPO_URL);
        String title = message.getTitle();
        String content = message.getContent();
        if (title.length() > 50) {
            title = title.substring(0, 50 - 3) + "...";
        }
        if (content.length() > 50) {
            content = content.substring(0, 50 - 3) + "...";
        }
/**
 * 以下参数必填项
 */
        //字数限制1~32
        notification.setTitle(title);
        //字数限制200以内,中英文均以一个计算
        notification.setContent(content);
/**
 * 以下参数非必填项, 如果需要使用可以参考OPPO push服务端api文档进行设置
 */
        //通知栏样式 1. 标准样式  2. 长文本样式  3. 大图样式 【非必填,默认1-标准样式】
        notification.setStyle(1);
// App开发者自定义消息Id,OPPO推送平台根据此ID做去重处理,对于广播推送相同appMessageId只会保存一次,对于单推相同appMessageId只会推送一次
        notification.setAppMessageId(UUID.randomUUID().toString());
// 应用接收消息到达回执的回调URL,字数限制200以内,中英文均以一个计算
//        notification.setCallBackUrl("http://www.test.com");
// App开发者自定义回执参数,字数限制50以内,中英文均以一个计算
//        notification.setCallBackParameter("");
// 点击动作类型0,启动应用;1,打开应用内页(activity的intent action);2,打开网页;4,打开应用内页(activity);【非必填,默认值为0】;5,Intent scheme URL
        notification.setClickActionType(4);
// 应用内页地址【click_action_type为1或4时必填,长度500】
        notification.setClickActionActivity(message.getActivity());
// 网页地址【click_action_type为2必填,长度500】
//        notification.setClickActionUrl("http://www.test.com");
        if (message.getActivityParams() != null && message.getActivityParams().size() > 0) {
            JSONObject params = new JSONObject();
            for (Iterator<String> its = message.getActivityParams().keySet().iterator(); its.hasNext(); ) {
                String key = its.next();
                params.put(key, message.getActivityParams().get(key));
            }
            // 动作参数,打开应用内页或网页时传递给应用或网页【JSON格式,非必填】,字符数不能超过4K,示例:{"key1":"value1","key2":"value2"}
            notification.setActionParameters(params.toJSONString());
        }
// 展示类型 (0, “即时”),(1, “定时”)
        notification.setShowTimeType(0);
// 定时展示开始时间(根据time_zone转换成当地时间),时间的毫秒数
//        notification.setShowStartTime(System.currentTimeMillis() + 1000 * 60 * 3);
// 定时展示结束时间(根据time_zone转换成当地时间),时间的毫秒数
//        notification.setShowEndTime(System.currentTimeMillis() + 1000 * 60 * 5);
// 是否进离线消息,【非必填,默认为True】
        notification.setOffLine(true);
// 离线消息的存活时间(time_to_live) (单位:秒), 【off_line值为true时,必填,最长3天】
        notification.setOffLineTtl(24 * 3600);
// 时区,默认值:(GMT+08:00)北京,香港,新加坡
//        notification.setTimeZone("GMT+08:00");
// 0:不限联网方式, 1:仅wifi推送
        notification.setNetworkType(0);
        //若推送消息时不带通道推送, Android 8及以上的手机将收不到消息
        notification.setChannelId("channel_android");
        return notification;
    }
    /**
     * 推送通知
     *
     * @param appInfo
     * @param message
     * @param regIds  不能超过1000个
     * @return
     * @throws IOException
     * @throws MeiZuPushException
     */
    public static String pushNotificationByRegIds(PushAppInfo appInfo, PushMessage message, List<String> regIds) throws Exception, OppoPushException {
        //推送标题限制字数1-32,推送内容限制字数1-100
        Sender sender = new Sender(appInfo.getAppKey(), appInfo.getAppSecret());
        Result result = null;
        if (regIds.size() > 1) {
            Map batch = new HashMap();  // batch最大为1000
            for (String regId : regIds) {
                batch.put(Target.build(regId), getNotification(message));
            }
            result = sender.unicastBatchNotification(batch); //发送批量单推消息
        } else {
            Target target = Target.build(regIds.get(0)); //创建发送对象
            result = sender.unicastNotification(getNotification(message), target);  //发送单推消息
        }
        logger.info("OPPO推送响应结果:{}", new Gson().toJson(result));
        if (result.getStatusCode() == 200) {
            if (result.getReturnCode().getCode() == 0) {
                return result.getMessageId();
            } else {
                throw new OppoPushException(result.getReturnCode().getCode() + "", result.getReturnCode().getMessage());
            }
        } else {
            throw new OppoPushException("网络请求出错");
        }
    }
    /**
     * 推送所有
     *
     * @param appInfo
     * @param message
     * @return
     * @throws IOException
     * @throws MeiZuPushException
     */
    public static String pushNotificationAll(PushAppInfo appInfo, PushMessage message, List<String> regIds) throws Exception, OppoPushException {
        Sender sender = new Sender(appInfo.getAppKey(), appInfo.getAppSecret());
        Notification broadNotification = getNotification(message);// 创建通知栏消息体
        Result saveResult = sender.saveNotification(broadNotification); // 发送保存消息体请求
        saveResult.getStatusCode(); // 获取http请求状态码
        saveResult.getReturnCode(); // 获取平台返回码
        String messageId = saveResult.getMessageId(); //获取messageId
        Target target = new Target(); // 创建广播目标
        target.setTargetValue(StringUtil.concat(regIds, ";"));
        Result broadResult = sender.broadcastNotification(messageId, target); // 发送广播消息
        broadResult.getTaskId(); // 获取广播taskId
        List<Result.BroadcastErrorResult> errorList = broadResult.getBroadcastErrorResults();
        if (errorList.size() > 0) { // 如果大小为0,代表所有目标发送成功
            for (Result.BroadcastErrorResult error : errorList) {
                error.getErrorCode(); // 错误码
                error.getTargetValue(); // 目标
            }
            return broadResult.getTaskId();
        } else {
            return broadResult.getTaskId();
        }
    }
    public static void main(String[] args) {
        PushAppInfo appInfo = new PushAppInfo("30514447", "78b911aa08e1468baf24ddea6c7ca23e", "fda37b51cc7f4d4d8b3d326455ec58c0");
        Map<String, String> activityParams = new HashMap<>();
        activityParams.put("activity", "test");
        JSONObject jumpParams = new JSONObject();
        jumpParams.put("id", 123123);
        activityParams.put("params", jumpParams.toJSONString());
        PushMessage message = new PushMessage("你好啊", "下午好,hello world", "com.weikou.beibeivideo.ui.push.OpenClickActivity", null, null, activityParams);
        String[] ids = new String[]{
                "OPPO_CN_fbaf9e7431c18d4ac4a08089d124c6ca"
        };
        try {
            OppoPushUtil.pushNotificationAll(appInfo, message, Arrays.asList(ids));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
service-push/src/main/java/com/huawei/push/util/push/VIVOPushUtil.java
New file
@@ -0,0 +1,212 @@
package com.huawei.push.util.push;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.vivo.push.sdk.notofication.Message;
import com.vivo.push.sdk.notofication.Result;
import com.vivo.push.sdk.notofication.TargetMessage;
import com.vivo.push.sdk.server.Sender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yeshi.utils.push.entity.PushAppInfo;
import org.yeshi.utils.push.entity.PushMessage;
import org.yeshi.utils.push.exception.MeiZuPushException;
import org.yeshi.utils.push.exception.VIVOPushException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
/**
 * vivo 推送
 * 推送文档:https://dev.vivo.com.cn/documentCenter/doc/363
 */
public class VIVOPushUtil {
    private static Map<String, AuthTokenInfo> tokenMap = new HashMap<>();
    static Logger logger= LoggerFactory.getLogger(VIVOPushUtil.class);
    private static int computeContentLength(String content) {
        int count = 0;
        for (int i = 0; i < content.length(); i++) {
//英文和数字算一个字符,其他算2个字符
            if (content.charAt(i) >= 48 && content.charAt(i) <= 57) {//数字
                count++;
            } else if (content.charAt(i) >= 65 && content.charAt(i) <= 90) {//大写字母
                count++;
            } else if (content.charAt(i) >= 97 && content.charAt(i) <= 122) {//小写字母
                count++;
            } else {
                count += 2;
            }
        }
        return count;
    }
    private static Message.Builder createMessage(PushMessage message) {
        String title = message.getTitle();
        String content = message.getContent();
        while (computeContentLength(title) > 40 - 3) {
            title = title.substring(0, title.length() - 1);
        }
        if (computeContentLength(title) == 40 - 3) {
            title += "...";
        }
        while (computeContentLength(content) > 100 - 3) {
            content = content.substring(0, content.length() - 1);
        }
        if (computeContentLength(content) == 100 - 3) {
            title += "...";
        }
        String uri = String.format("intent://%s?#Intent;scheme=%s;launchFlags=0x4000000;", message.getActivityHostPath(), message.getActivityScheme());
        //增加Activity的跳转参数
        for (Iterator<String> its = message.getActivityParams().keySet().iterator(); its.hasNext(); ) {
            String k = its.next();
            try {
                if (message.getActivityParams().get(k) != null) {
                    uri += String.format("S.%s=%s;", k, URLEncoder.encode(message.getActivityParams().get(k), "UTF-8"));
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        uri += "end";
        Message.Builder builder = new Message.Builder();
        builder = builder
                //必填项,设置通知标题(用于通知栏消息),最大20个汉字(一个汉字等于两个英文字符,即最大不超过40个英文字符)
                .title(title)
                //必填项,设置通知内容(用于通知栏消息) 最大50个汉字(一个汉字等于两个英文字符,即最大不超过100个英文字符)
                .content(content)
                //,设置点击跳转类型,value类型支持以下值  1:打开APP首页  2:打开链接  3:自定义  4:打开app内指定页面
                .skipType(4)
                //跳转类型为2时,跳转内容最大1000个字符,
                //跳转类型为3或4时,跳转内容最大1024个字符
                .skipContent(uri)
                // 1:无 2:响铃 3:振动 4:响铃和振动
                .notifyType(4)
                //推送模式 0:正式推送;1:测试推送,不填默认为0
                .pushMode(message.isTest() ? 1 : 0)
                //消息类型 0:运营类消息,1:系统类消息
                .classification(1)
                .requestId(UUID.randomUUID().toString());
        if (message.getActivityParams() != null) {
            builder = builder.clientCustomMap(message.getActivityParams());
        }
        return builder.timeToLive(3600);
    }
    private static Sender getSender(PushAppInfo appInfo) throws Exception {
        Sender sender = new Sender(appInfo.getAppSecret());//注册登录开发平台网站获取到的appSecret
        sender.initPool(20, 10);//设置连接池参数,可选项
        String appId = appInfo.getAppId();
        String token = null;
        if (tokenMap.get(appId) == null || tokenMap.get(appId).expireTime < System.currentTimeMillis()) {
            Result result = sender.getToken(Integer.parseInt(appInfo.getAppId()), appInfo.getAppKey());//注册登录开发平台网站获取到的appId和appKey
            token = result.getAuthToken();
            if (token != null) {
                AuthTokenInfo tokenInfo = new AuthTokenInfo();
                tokenInfo.expireTime = System.currentTimeMillis() + 1000 * 60 * 20L;
                tokenInfo.token = token;
                tokenMap.put(appId, tokenInfo);
            }
        }
        sender.setAuthToken(tokenMap.get(appId).token);
        return sender;
    }
    /**
     * 推送通知
     *
     * @param appInfo
     * @param message
     * @param regIds  不能超过1000个
     * @return
     * @throws IOException
     * @throws MeiZuPushException
     */
    public static String pushNotificationByRegIds(PushAppInfo appInfo, PushMessage message, List<String> regIds) throws Exception, VIVOPushException {
        Sender sender = getSender(appInfo);
        Message.Builder builder = createMessage(message);
        Message saveList = builder.build();// 构建要保存的批量推送消息体
        Result result = null;
        if (regIds.size() > 1) {
            result = sender.saveListPayLoad(saveList);// 发送保存群推消息请求
            String taskId = result.getTaskId();
            TargetMessage targetMessage = new TargetMessage.Builder()
                    .requestId(UUID.randomUUID().toString())
                    .regIds(new HashSet<>(regIds)).taskId(taskId).build();// 构建批量推送的消息体
            result = sender.sendToList(targetMessage);// 批量推送给用户
        } else {
            result = sender.sendSingle(builder.regId(regIds.get(0)).build());
        }
        logger.info("VIVO推送响应结果:{}", new Gson().toJson(result));
        if (result.getResult() == 0)// 成功
        {
            return result.getTaskId();
        } else {
            throw new VIVOPushException(result.getResult() + "", result.getDesc());
        }
    }
    /**
     * 推送所有(暂未对外开放)
     * //     *
     * //     * @param appInfo
     * //     * @param message
     * //     * @return
     * //     * @throws IOException
     * //     * @throws MeiZuPushException
     * //
     */
//    public static String pushNotificationAll(PushAppInfo appInfo, PushMessage message) throws Exception, OppoPushException {
//        Sender sender = getSender(appInfo);
//        Message.Builder builder = createMessage(message);
//        Result result = sender.sendToAll(builder.build());
//        if (result.getResult() == 0)// 成功
//        {
//            return result.getTaskId();
//        } else {
//            throw new VIVOPushException(result.getResult() + "", result.getDesc());
//        }
//    }
    public static void main(String[] args) {
        PushAppInfo appInfo = new PushAppInfo("105477463", "8a622972d8857cd29df47a0754be73be", "14bf9175-bc72-4d9d-bbcd-ba4ca78dc8fe");
        Map<String, String> activityParams = new HashMap<>();
        activityParams.put("activity", "com.tejia.lijin.app.ui.invite.ShareBrowserActivity");
        activityParams.put("type", "web");
        JSONObject jumpParams = new JSONObject();
        jumpParams.put("url", "http://www.baidu.com");
        activityParams.put("params", jumpParams.toJSONString());
        PushMessage message = new PushMessage("祝大家春节快乐", "下午好,hello world", "", "tejiapush", "com.huawei.codelabpush/deeplink", activityParams);
        String[] ids = new String[]{
                "16239830400127746325913"
        };
        try {
            VIVOPushUtil.pushNotificationByRegIds(appInfo, message, Arrays.asList(ids));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static class AuthTokenInfo {
        public String token;
        public long expireTime;
    }
}
service-push/src/main/java/com/huawei/push/util/push/XiaoMiPushUtil.java
New file
@@ -0,0 +1,156 @@
package com.huawei.push.util.push;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.ks.push.utils.Constant;
import com.xiaomi.xmpush.server.Constants;
import com.xiaomi.xmpush.server.Message;
import com.xiaomi.xmpush.server.Result;
import com.xiaomi.xmpush.server.Sender;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yeshi.utils.push.entity.PushAppInfo;
import org.yeshi.utils.push.entity.PushMessage;
import org.yeshi.utils.push.exception.MeiZuPushException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
/**
 * 小米推送
 * 推送文档:https://dev.mi.com/console/doc/detail?pId=1278
 */
public class XiaoMiPushUtil {
    static Logger logger = LoggerFactory.getLogger(XiaoMiPushUtil.class);
    private static Message createMessage(PushAppInfo appInfo, PushMessage message) {
        String title = message.getTitle();
        String content = message.getContent();
        if (title.length() > 50) {
            title = title.substring(0, 50 - 3) + "...";
        }
        if (content.length() > 128) {
            content = content.substring(0, 128 - 3) + "...";
        }
        //示例:intent:#Intent;component=com.doudou.ysvideo/com.weikou.beibeivideo.ui.push.OpenClickActivity;S.activity=test;S.params=%7B%22id%22%3A%22123123%22%7D;end
        String uri = String.format("intent:#Intent;component=%s/%s;", appInfo.getPackageName(), message.getActivity().replace(appInfo.getPackageName(), ""));
        //增加Activity的跳转参数
        for (Iterator<String> its = message.getActivityParams().keySet().iterator(); its.hasNext(); ) {
            String k = its.next();
            try {
                if (message.getActivityParams().get(k) != null) {
                    uri += String.format("S.%s=%s;", k, URLEncoder.encode(message.getActivityParams().get(k), "UTF-8"));
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        uri += "end";
        Message msg = new Message.Builder()
                //设置在通知栏展示的通知的标题, 不允许全是空白字符, 长度小于50, 一个中英文字符均计算为1(通知栏消息必填)
                .title(title)
                //设置在通知栏展示的通知描述, 不允许全是空白字符, 长度小于128, 一个中英文字符均计算为1(通知栏消息必填)
                .description(content)
                .restrictedPackageName(appInfo.getPackageName())
                //1:使用默认提示音提示
                //2:使用默认震动提示
                //4:使用默认led灯光提示
                //-1(系统默认值):以上三种效果都有
                //0:以上三种效果都无,即静默推送。
                //并且支持1,2,4的任意OR运算来实现声音、震动和闪光灯的任意组合。
                .notifyType(1)     // 使用默认提示音提示
                //可选项, 对app提供一些扩展功能(除了这些扩展功能,
                // 开发者还可以定义一些key和value来控制客户端的行为,
                // 注:key和value的字符数不能超过1024, 至多可以设置10个key-value键值对)
                //.extra()
                .extra(Constants.EXTRA_PARAM_NOTIFY_EFFECT, Constants.NOTIFY_ACTIVITY)
                // 启动Activity的intent uri
                .extra(Constants.EXTRA_PARAM_INTENT_URI, uri)
                //消息回执 https://dev.mi.com/console/doc/detail?pId=1278#_3_6
                .extra("callback", Constant.PUSH_CALLBACK_XM_URL)
                .extra("callback.param", appInfo.getAppId())
                .extra("callback.type", "19")//收到消息送达、消息点击和目标设备无效三种状态的回执
                .build();
        return msg;
    }
    /**
     * 推送通知
     *
     * @param appInfo
     * @param message
     * @param regIds  不能超过1000个
     * @return
     * @throws IOException
     * @throws MeiZuPushException
     */
    public static String pushNotificationByRegIds(PushAppInfo appInfo, PushMessage message, List<String> regIds) throws IOException {
        //推送标题限制字数1-32,推送内容限制字数1-100
        Sender sender = new Sender(appInfo.getAppSecret());
        Message msg = createMessage(appInfo, message);
        Result result = sender.unionSend(msg, regIds, 3);
        logger.info("小米推送响应结果:{}", new Gson().toJson(result));
        return result.getMessageId();
    }
    /**
     * 推送所有
     *
     * @param appInfo
     * @param message
     * @return
     * @throws IOException
     * @throws MeiZuPushException
     */
    public static String pushNotificationAll(PushAppInfo appInfo, PushMessage message) throws IOException, ParseException {
        Sender sender = new Sender(appInfo.getAppSecret());
        Message msg = createMessage(appInfo, message);
        Result result = sender.broadcastAll(msg, 3);
        logger.info("小米推送响应结果:{}", new Gson().toJson(result));
        return result.getMessageId();
    }
    public static void main(String[] args) {
        PushAppInfo appInfo = new PushAppInfo("2882303761519871448", "5161987181448", "OcdMXvErcGtf1XyXnwrhOg==");
        appInfo.setPackageName("com.tejia.lijin");
        Map<String, String> activityParams = new HashMap<>();
        activityParams.put("activity", "com.weikou.beibeivideo.ui.media.VideoDetailActivity2");
        JSONObject jumpParams = new JSONObject();
        jumpParams.put("Id", "8219668");
        jumpParams.put("Share", 0);
        jumpParams.put("ThirdType", 0);
        activityParams.put("params", jumpParams.toJSONString());
        PushMessage message = new PushMessage("她们创业的那些事儿", "讲述了三个不同年龄、不同性格的女生因缘际会在一起创业的故事,在这条不被看好的创业之路上,她们终将学会,对鸟事一笑置之,不是认输,而是成长!", "com.tejia.lijin.app.ui.PushOpenClickActivity", null, null, activityParams);
        String[] ids = new String[]{
                "t76FQBHolG/YQ9V7qmhtsTbLXNDcCNHfSfNK2k3iKI6B8RYL9FPSWJvXDaBNKy8N"
        };
        try {
            XiaoMiPushUtil.pushNotificationByRegIds(appInfo, message, Arrays.asList(ids));
        } catch (Exception e) {
            e.printStackTrace();
        }
//        try {
//            XiaoMiPushUtil.pushNotificationByRegIds(appInfo, message, Arrays.asList(ids));
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
    }
}
service-push/src/main/java/com/huawei/push/webpush/WebActions.java
New file
@@ -0,0 +1,84 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.webpush;
import com.alibaba.fastjson.annotation.JSONField;
public class WebActions {
    @JSONField(name = "action")
    private String action;
    @JSONField(name = "icon")
    private String icon;
    @JSONField(name = "title")
    private String title;
    public String getAction() {
        return action;
    }
    public String getIcon() {
        return icon;
    }
    public String getTitle() {
        return title;
    }
    public void check(){
    }
    public WebActions(Builder builder) {
        this.action = builder.action;
        this.icon = builder.icon;
        this.title = builder.title;
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private String action;
        private String icon;
        private String title;
        public Builder setAction(String action) {
            this.action = action;
            return this;
        }
        public Builder setIcon(String icon) {
            this.icon = icon;
            return this;
        }
        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }
        public WebActions build() {
            return new WebActions(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/webpush/WebHmsOptions.java
New file
@@ -0,0 +1,67 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.webpush;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
import org.apache.commons.lang3.StringUtils;
import java.net.MalformedURLException;
import java.net.URL;
public class WebHmsOptions {
    @JSONField(name = "link")
    private String link;
    public String getLink() {
        return link;
    }
    public WebHmsOptions(Builder builder) {
        this.link = builder.link;
    }
    public void check() {
        if (!StringUtils.isEmpty(this.link)) {
            try {
                new URL(link);
            } catch (MalformedURLException e) {
                ValidatorUtils.checkArgument(false, "Invalid link");
            }
        }
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private String link;
        public Builder setLink(String link) {
            this.link = link;
            return this;
        }
        public WebHmsOptions build() {
            return new WebHmsOptions(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/webpush/WebNotification.java
New file
@@ -0,0 +1,273 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.webpush;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.CollectionUtils;
import com.huawei.push.util.ValidatorUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class WebNotification {
    private static String[] DIR_VALUE = {"auto", "ltr", "rtl"};
    @JSONField(name = "title")
    private String title;
    @JSONField(name = "body")
    private String body;
    @JSONField(name = "icon")
    private String icon;
    @JSONField(name = "image")
    private String image;
    @JSONField(name = "lang")
    private String lang;
    @JSONField(name = "tag")
    private String tag;
    @JSONField(name = "badge")
    private String badge;
    @JSONField(name = "dir")
    private String dir;
    @JSONField(name = "vibrate")
    private List<Object> vibrate = new ArrayList<Object>();
    @JSONField(name = "renotify")
    private boolean renotify;
    @JSONField(name = "require_interaction")
    private boolean requireInteraction;
    @JSONField(name = "silent")
    private boolean silent;
    @JSONField(name = "timestamp")
    private Long timestamp;
    @JSONField(name = "actions")
    private List<WebActions> actions = new ArrayList<WebActions>();
    public String getTitle() {
        return title;
    }
    public String getBody() {
        return body;
    }
    public String getIcon() {
        return icon;
    }
    public String getImage() {
        return image;
    }
    public String getLang() {
        return lang;
    }
    public String getTag() {
        return tag;
    }
    public String getBadge() {
        return badge;
    }
    public String getDir() {
        return dir;
    }
    public List<Object> getVibrate() {
        return vibrate;
    }
    public boolean isRenotify() {
        return renotify;
    }
    public boolean isRequireInteraction() {
        return requireInteraction;
    }
    public boolean isSilent() {
        return silent;
    }
    public Long getTimestamp() {
        return timestamp;
    }
    public List<WebActions> getActions() {
        return actions;
    }
    public void check() {
        if (this.dir != null) {
            ValidatorUtils.checkArgument(Arrays.stream(DIR_VALUE).anyMatch(value -> value.equals(dir)), "Invalid dir");
        }
        if (this.vibrate != null) {
            for (Object obj : vibrate) {
                ValidatorUtils.checkArgument((obj instanceof Double || obj instanceof Integer), "Invalid vibrate");
            }
        }
    }
    public WebNotification(Builder builder) {
        this.title = builder.title;
        this.body = builder.body;
        this.icon = builder.icon;
        this.image = builder.image;
        this.lang = builder.lang;
        this.tag = builder.tag;
        this.badge = builder.badge;
        this.dir = builder.dir;
        if (!CollectionUtils.isEmpty(builder.vibrate)) {
            this.vibrate.addAll(builder.vibrate);
        } else {
            this.vibrate = null;
        }
        this.renotify = builder.renotify;
        this.requireInteraction = builder.requireInteraction;
        this.silent = builder.silent;
        this.timestamp = builder.timestamp;
        if (!CollectionUtils.isEmpty(builder.actions)) {
            this.actions.addAll(builder.actions);
        } else {
            this.actions = null;
        }
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private String title;
        private String body;
        private String icon;
        private String image;
        private String lang;
        private String tag;
        private String badge;
        private String dir;
        private List<Object> vibrate = new ArrayList<Object>();
        private boolean renotify;
        private boolean requireInteraction;
        private boolean silent;
        private Long timestamp;
        private List<WebActions> actions = new ArrayList<WebActions>();
        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }
        public Builder setBody(String body) {
            this.body = body;
            return this;
        }
        public Builder setIcon(String icon) {
            this.icon = icon;
            return this;
        }
        public Builder setImage(String image) {
            this.image = image;
            return this;
        }
        public Builder setLang(String lang) {
            this.lang = lang;
            return this;
        }
        public Builder setTag(String tag) {
            this.tag = tag;
            return this;
        }
        public Builder setBadge(String badge) {
            this.badge = badge;
            return this;
        }
        public Builder setDir(String dir) {
            this.dir = dir;
            return this;
        }
        public Builder addAllVibrate(List<Object> vibrate) {
            this.vibrate.addAll(vibrate);
            return this;
        }
        public Builder addVibrate(Object vibrate) {
            this.vibrate.add(vibrate);
            return this;
        }
        public Builder setRenotify(boolean renotify) {
            this.renotify = renotify;
            return this;
        }
        public Builder setRequireInteraction(boolean requireInteraction) {
            this.requireInteraction = requireInteraction;
            return this;
        }
        public Builder setSilent(boolean silent) {
            this.silent = silent;
            return this;
        }
        public Builder setTimestamp(Long timestamp) {
            this.timestamp = timestamp;
            return this;
        }
        public Builder addAllActions(List<WebActions> actions) {
            this.actions.addAll(actions);
            return this;
        }
        public Builder addAction(WebActions action) {
            this.actions.add(action);
            return this;
        }
        public WebNotification build() {
            return new WebNotification(this);
        }
    }
}
service-push/src/main/java/com/huawei/push/webpush/WebpushHeaders.java
New file
@@ -0,0 +1,95 @@
/*
 * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package com.huawei.push.webpush;
import com.alibaba.fastjson.annotation.JSONField;
import com.huawei.push.util.ValidatorUtils;
import java.util.Arrays;
public class WebpushHeaders {
    private static String TTL_PATTERN = "[0-9]+|[0-9]+[sS]";
    private static String[] URGENCY_VALUE = {"very-low", "low", "normal", "high"};
    @JSONField(name = "ttl")
    private String ttl;
    @JSONField(name = "topic")
    private String topic;
    @JSONField(name = "urgency")
    private String urgency;
    public String getTtl() {
        return ttl;
    }
    public String getTopic() {
        return topic;
    }
    public String getUrgency() {
        return urgency;
    }
    public WebpushHeaders(Builder builder) {
        this.ttl = builder.ttl;
        this.topic = builder.topic;
        this.urgency = builder.urgency;
    }
    public void check() {
        if (this.ttl != null) {
            ValidatorUtils.checkArgument(this.ttl.matches(TTL_PATTERN), "Invalid ttl format");
        }
        if (this.urgency != null) {
            ValidatorUtils.checkArgument(Arrays.stream(URGENCY_VALUE).anyMatch(value -> value.equals(urgency)),"Invalid urgency");
        }
    }
    /**
     * builder
     */
    public static Builder builder() {
        return new Builder();
    }
    public static class Builder {
        private String ttl;
        private String topic;
        private String urgency;
        public Builder setTtl(String ttl) {
            this.ttl = ttl;
            return this;
        }
        public Builder setTopic(String topic) {
            this.topic = topic;
            return this;
        }
        public Builder setUrgency(String urgency) {
            this.urgency = urgency;
            return this;
        }
        public WebpushHeaders build() {
            return new WebpushHeaders(this);
        }
    }
}
service-push/src/main/java/com/ks/push/PushApplication.java
@@ -1,6 +1,10 @@
package com.ks.push;
import com.ks.push.consumer.mq.PushTaskConsumer;
import com.ks.push.dto.mq.InvalidDeviceTokenInfo;
import com.ks.push.manager.CMQManager;
import com.ks.push.manager.PushDeviceTokenManager;
import com.ks.push.pojo.DO.BPushDeviceToken;
import com.ks.push.pojo.DO.PushPlatform;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.mybatis.spring.annotation.MapperScan;
@@ -8,14 +12,13 @@
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.yeshi.utils.mq.JobThreadExecutorServiceImpl;
import javax.annotation.Resource;
import java.util.List;
@SpringBootApplication
@EnableTransactionManagement
@@ -26,6 +29,9 @@
    @Resource
    private PushTaskConsumer pushTaskConsumer;
    @Resource
    private PushDeviceTokenManager pushDeviceTokenManager;
    public static void main(String[] args) {
        SpringApplication.run(PushApplication.class, args);
@@ -52,7 +58,31 @@
                });
            }
        }
        //清理无效token
        new JobThreadExecutorServiceImpl().run(new Runnable() {
            @Override
            public void run() {
                try {
                    List<CMQManager.MQMsgConsumeResult> list =  CMQManager.getInstance().consumeInvalidDeviceTokenQueue(16);
                    if (list != null) {
                        logger.info("清理无效token数量:" + list.size());
                        for (CMQManager.MQMsgConsumeResult result : list) {
                            InvalidDeviceTokenInfo tokenInfo = (InvalidDeviceTokenInfo) result.getData();
                            List<BPushDeviceToken> tokenList = pushDeviceTokenManager.list(tokenInfo.getAppCode(), tokenInfo.getPushPlatform(), tokenInfo.getToken(), 1, 20);
                            if (tokenList != null) {
                                for (BPushDeviceToken token : tokenList) {
                                    logger.info("清理无效token id-{}" + token.getId());
                                    //删除
                                    pushDeviceTokenManager.deleteByPrimaryKey(token.getId());
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
service-push/src/main/java/com/ks/push/config/RedisConfig.java
@@ -1,30 +1,95 @@
package com.ks.push.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Value("${spring.redis.host}")
    private String addr;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.password}")
    private String auth;
    @Value("${spring.redis.database}")
    private int database;
    @Value("${spring.redis.lettuce.pool.max-active}")
    private int maxActive;
    @Value("${spring.redis.lettuce.pool.max-wait}")
    private int maxWait;
    @Value("${spring.redis.lettuce.pool.max-idle}")
    private int maxIdle;
    @Value("${spring.redis.lettuce.pool.min-idle}")
    private int minIdle;
    @Value("${spring.redis.lettuce.shutdown-timeout}")
    private int shutDownTimeout;
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    public LettuceConnectionFactory lettuceConnectionFactory() {
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxIdle(maxIdle);
        genericObjectPoolConfig.setMinIdle(minIdle);
        genericObjectPoolConfig.setMaxTotal(maxActive);
        genericObjectPoolConfig.setMaxWaitMillis(maxWait);
        genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(1000);
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setDatabase(database);
        redisStandaloneConfiguration.setHostName(addr);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(auth));
        LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofMillis(timeout))
                .shutdownTimeout(Duration.ofMillis(shutDownTimeout))
                .poolConfig(genericObjectPoolConfig)
                .build();
        LettuceConnectionFactory factory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
        //允许多个连接公用一个物理连接,默认为true
//        factory.setShareNativeConnection(true);
// 检验链接是否可用,默认为false
        factory.setValidateConnection(true);
        return factory;
    }
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setConnectionFactory(lettuceConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
@@ -37,4 +102,14 @@
        template.afterPropertiesSet();
        return template;
    }
    @Bean
    public JedisPool jedisPool() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(maxActive);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setTestOnBorrow(true);
        return new JedisPool(poolConfig, addr, port, timeout, auth, database);
    }
}
service-push/src/main/java/com/ks/push/consumer/mq/PushTaskConsumer.java
@@ -42,13 +42,13 @@
        if (platform == null) {
            return;
        }
        logger.info("消费消息开始:{}  Thread:{}", platform.name(),Thread.currentThread().getId());
//        logger.info("消费消息开始:{}  Thread:{}", platform.name(), Thread.currentThread().getId());
        List<CMQManager.MQMsgConsumeResult> resultList = null;
        try {
            resultList = CMQManager.getInstance().consumePushQueue(platform, 1);
        } catch (Exception e) {
        }
        logger.info("消费消息结束:{} Thread:{}", platform.name(),Thread.currentThread().getId());
//        logger.info("消费消息结束:{} Thread:{}", platform.name(), Thread.currentThread().getId());
        if (resultList != null) {
            for (CMQManager.MQMsgConsumeResult result : resultList) {
                try {
@@ -59,10 +59,12 @@
                            BPushPlatformAppInfo platformAppInfo = bPushPlatformAppInfoManager.selectByAppCodeAndPlatform(task.getAppCode(), platform);
                            if (platformAppInfo != null) {
                                try {
                                    PushUtil.pushNotifyCation(platform, platformAppInfo.getPushAppInfo(), task.getMessage(), dataSet.getDeviceTokenList());
                                    logger.info("推送任务执行成功,taskId-{},batchId-{}", dataSet.getTaskId(), dataSet.getBatchId());
                                    PushUtil.pushNotifyCation(platform, platformAppInfo.getPushAppInfo(), task.getMessage(), task.getId() + "#" + dataSet.getBatchId(), dataSet.getDeviceTokenList());
                                    logger.info("{}推送任务执行成功,taskId-{},batchId-{}", platform.name(), dataSet.getTaskId(), dataSet.getBatchId());
                                } catch (Exception e) {
                                    e.printStackTrace();
                                    logger.error("推送出错:ttaskId-{},batchId-{},错误原因:{}", task.getId(), dataSet.getBatchId(), e.getMessage());
                                    logger.error("推送出错", e);
                                }
                            }
                        } else {
@@ -77,11 +79,12 @@
                        logger.error("任务为空,taskId-{},batchId-{}", dataSet.getTaskId(), dataSet.getBatchId());
                    }
                    //删除消息
                    logger.info("推送任务执行完成,taskId-{},batchId-{}", dataSet.getTaskId(), dataSet.getBatchId());
                    CMQManager.getInstance().deleteMsg(result.getQueueName(), result.getReceiptHandle());
                    pushExcuteResultManager.batchPushFinish(dataSet, platform);
                    logger.info("{}推送任务执行结束,taskId-{},batchId-{}", platform.name(), dataSet.getTaskId(), dataSet.getBatchId());
                } catch (Exception e) {
//                    e.printStackTrace();
                    logger.error("推送出错:", e);
                }
            }
service-push/src/main/java/com/ks/push/controller/PushCallbackController.java
New file
@@ -0,0 +1,132 @@
package com.ks.push.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ks.push.dto.mq.InvalidDeviceTokenInfo;
import com.ks.push.manager.BPushPlatformAppInfoManager;
import com.ks.push.manager.CMQManager;
import com.ks.push.pojo.DO.PushPlatform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.yeshi.utils.StringUtil;
import org.yeshi.utils.wx.WXUtil;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
 * @author Administrator
 * @title: PushCallbackController
 * @description: 各大手机推送平台的消息回执
 * @date 2021/9/15 12:17
 */
@Controller
@RequestMapping("callback")
public class PushCallbackController {
    Logger logger = LoggerFactory.getLogger(PushCallbackController.class);
    @Resource
    private BPushPlatformAppInfoManager bPushPlatformAppInfoManager;
    //回调接口详情:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/msg-receipt-guide-0000001050040176#ZH-CN_TOPIC_0000001087208860__p121151147184318
    @ResponseBody
    @RequestMapping("hw")
    public String hw(HttpServletRequest request) {
        String content = WXUtil.getContent(request);
        if (StringUtil.isNullOrEmpty(content)) {
            return "";
        }
        JSONObject json = JSONObject.parseObject(content);
        JSONArray statuses = json.getJSONArray("statuses");
        for (int i = 0; i < statuses.size(); i++) {
            JSONObject statuse = statuses.getJSONObject(i);
            String appId = statuse.getString("appid");
            String bigTag = statuse.getString("biTag");
            String token = statuse.getString("token");
            String appCode = bPushPlatformAppInfoManager.selectAppCode(appId, PushPlatform.hw);
            //0-成功
            //2-应用未安装
            //5-指定的Token在当前Android终端用户下不存在
            //6-通知栏消息不展示
            //10-非活跃设备消息,设备为非活跃设备(终端设备未接入网络达30天),消息不进行下发。
            //15-离线用户消息覆盖
            //27-在终端设备上目标应用进程不存在导致透传消息被缓存
            //102-消息频控丢弃
            //144-profileId不存在
            //201-消息发送管控
            int status = statuse.getInteger("status");
            if (status == 0) {
                logger.debug("华为消息回执-推送成功:bigTag-{}  token-{}", bigTag, token);
            } else {
                logger.debug("华为消息回执-推送失败:bigTag-{}  token-{} status-{}", bigTag, token, status);
                if (status == 2 || status == 5 || status == 10) {
                    //删除无效设备
                    CMQManager.getInstance().addInvalidDevieToken(new InvalidDeviceTokenInfo(appCode, PushPlatform.hw, token));
                }
            }
        }
        JSONObject data = new JSONObject();
        data.put("code", 0);
        data.put("message", "success");
        return data.toJSONString();
    }
    //回执消息的格式:https://dev.mi.com/console/doc/detail?pId=1278#_3_6
    @ResponseBody
    @RequestMapping("xm")
    public String xm(HttpServletRequest request) {
        String content = WXUtil.getContent(request);
        logger.debug("小米消息回执-{}", content);
        JSONObject json = JSONObject.parseObject(content);
        for (String msgId : json.keySet()) {
            JSONObject data = json.getJSONObject(msgId);
            String appId = data.getString("param");
            String appCode = bPushPlatformAppInfoManager.selectAppCode(appId, PushPlatform.xm);
            int type = data.getInteger("type");
            switch (type) {
                //送达
                case 1:
                    break;
                //点击
                case 2:
                    break;
                //无法找到目标设备
                case 16:
                    String regIdStr = data.getString("targets");
                    String[] regIds = regIdStr.split(",");
                    for (String rid : regIds) {
                        //删除无效设备
                        CMQManager.getInstance().addInvalidDevieToken(new InvalidDeviceTokenInfo(appCode, PushPlatform.xm, rid));
                    }
                    break;
            }
        }
        JSONObject data = new JSONObject();
        data.put("code", 0);
        data.put("message", "success");
        return "";
    }
    @ResponseBody
    @RequestMapping("oppo")
    public String oppo(HttpServletRequest request) {
        String content = WXUtil.getContent(request);
        logger.debug("oppo消息回执-{}", content);
        JSONObject data = new JSONObject();
        data.put("code", 0);
        data.put("message", "success");
        return "";
    }
}
service-push/src/main/java/com/ks/push/controller/admin/AdminPushTaskController.java
@@ -15,6 +15,7 @@
import com.ks.push.vo.BPushTaskVO;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Controller;
@@ -27,6 +28,7 @@
import javax.servlet.http.HttpSession;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -47,6 +49,7 @@
        query.appCode = appCode;
        query.state = state;
        query.messageTitle = key;
        query.sortList= Arrays.asList(new Sort.Order[]{new Sort.Order(Sort.Direction.DESC,"createTime")});
        List<BPushTask> list = bPushTaskService.list(query, page, limit);
service-push/src/main/java/com/ks/push/dao/BPushDeviceTokenDao.java
@@ -20,7 +20,7 @@
    public void updateSelective(BPushDeviceToken bean) {
        Query query = new Query();
        Update update = new Update();
        query.addCriteria(Criteria.where("id").is(bean.getId()));
        query.addCriteria(Criteria.where("_id").is(bean.getId()));
        if (bean.getAppCode() != null) {
            update.set("appCode", bean.getAppCode());
        }
@@ -112,6 +112,10 @@
            andList.add(Criteria.where("updateTime").lt(daoQuery.maxUpdateTime));
        }
        if (daoQuery.token != null) {
            andList.add(Criteria.where("token").lt(daoQuery.token));
        }
        Criteria[] ands = new Criteria[andList.size()];
        andList.toArray(ands);
@@ -144,6 +148,7 @@
        public List<String> uidList;
        public List<String> deviceIdList;
        public List<String> brandNameList;
        public String token;
        public Date minUpdateTime;
        public Date maxUpdateTime;
        public Date minCreateTime;
service-push/src/main/java/com/ks/push/dao/BPushTaskDao.java
@@ -3,6 +3,7 @@
import com.ks.push.pojo.DO.BPushTask;
import com.ks.lib.common.dao.MongodbBaseDao;
import com.ks.push.pojo.Query.BPushTaskQuery;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
@@ -78,6 +79,9 @@
        Query query = getQuery(daoQuery);
        query.skip(start);
        query.limit(count);
        if (daoQuery.sortList != null) {
            query.with(Sort.by(daoQuery.sortList));
        }
        return findList(query);
    }
service-push/src/main/java/com/ks/push/dto/mq/InvalidDeviceTokenInfo.java
New file
@@ -0,0 +1,63 @@
package com.ks.push.dto.mq;
import com.ks.push.pojo.DO.PushPlatform;
import java.io.Serializable;
import java.util.Date;
/**
 * @author Administrator
 * @title: InvalidDeviceTokenInfo
 * @description: 无效设备信息
 * @date 2021/9/17 11:26
 */
public class InvalidDeviceTokenInfo implements Serializable {
    private String appCode;
    private PushPlatform pushPlatform;
    private String token;
    private Date createTime;
    public InvalidDeviceTokenInfo() {
    }
    public InvalidDeviceTokenInfo(String appCode, PushPlatform pushPlatform, String token) {
        this.appCode = appCode;
        this.pushPlatform = pushPlatform;
        this.token = token;
        this.createTime = new Date();
    }
    public String getAppCode() {
        return appCode;
    }
    public void setAppCode(String appCode) {
        this.appCode = appCode;
    }
    public PushPlatform getPushPlatform() {
        return pushPlatform;
    }
    public void setPushPlatform(PushPlatform pushPlatform) {
        this.pushPlatform = pushPlatform;
    }
    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }
    public Date getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}
service-push/src/main/java/com/ks/push/manager/BPushPlatformAppInfoManager.java
@@ -43,4 +43,15 @@
    }
    public String selectAppCode(String appId, PushPlatform pushPlatform) {
        Query query = new Query();
        query.addCriteria(Criteria.where("pushAppInfo.appId").is(appId).and("platform").is(pushPlatform));
        BPushPlatformAppInfo appInfo = bPushPlatformAppInfoDao.findOne(query);
        if (appInfo == null) {
            return null;
        }
        return appInfo.getAppCode();
    }
}
service-push/src/main/java/com/ks/push/manager/CMQManager.java
@@ -2,6 +2,7 @@
import com.google.gson.Gson;
import com.ks.push.dto.BPushDeviceDataSet;
import com.ks.push.dto.mq.InvalidDeviceTokenInfo;
import com.ks.push.pojo.DO.PushPlatform;
import com.qcloud.cmq.Message;
import org.slf4j.Logger;
@@ -41,6 +42,11 @@
     */
    public static String PUSH_MZ = "bpush-mz";
    /**
     * 无效设备队列
     */
    public static String PUSH_TOKEN_INVALID = "bpush-token-invalid";
    static {
        cmqUtil = CMQUtil.getInstance(secretId, secretKey);
        // 最大消息为1M
@@ -49,6 +55,7 @@
        cmqUtil.createQueue(PUSH_OPPO);
        cmqUtil.createQueue(PUSH_VIVO);
        cmqUtil.createQueue(PUSH_MZ);
        cmqUtil.createQueue(PUSH_TOKEN_INVALID);
        logger.info("创建队列完毕");
    }
@@ -117,6 +124,7 @@
        return null;
    }
    /**
     * 删除消息
     *
@@ -128,6 +136,38 @@
    }
    /**
     * 发送无效设备消息
     *
     * @param info
     */
    public void addInvalidDevieToken(InvalidDeviceTokenInfo info) {
        if (info == null) {
            return;
        }
        cmqUtil.sendMsg(PUSH_TOKEN_INVALID, new Gson().toJson(info));
    }
    /**
     * 消费无效设备消息
     *
     * @param count
     */
    public List<MQMsgConsumeResult> consumeInvalidDeviceTokenQueue(int count) throws Exception {
        List<Message> list = cmqUtil.recieveMsg(count, PUSH_TOKEN_INVALID);
        if (list != null) {
            List<MQMsgConsumeResult> resultList = new ArrayList<>();
            for (Message msg : list) {
                String result = msg.msgBody;
                InvalidDeviceTokenInfo tokenInfo = new Gson().fromJson(result, InvalidDeviceTokenInfo.class);
                resultList.add(new MQMsgConsumeResult(tokenInfo, PUSH_TOKEN_INVALID, msg.receiptHandle));
            }
            return resultList;
        }
        return null;
    }
    public static class MQMsgConsumeResult {
        private String queueName;
        private Object data;
service-push/src/main/java/com/ks/push/manager/PushDeviceTokenManager.java
@@ -39,4 +39,17 @@
        return bPushDeviceTokenDao.count(getDaoQuery(appCode, platform, filter));
    }
    public List<BPushDeviceToken> list(String appCode, PushPlatform platform, String token, int page, int pageSize) {
        BPushDeviceTokenDao.DaoQuery daoQuery = new BPushDeviceTokenDao.DaoQuery();
        daoQuery.appCode = appCode;
        daoQuery.token = token;
        daoQuery.type = platform;
        return bPushDeviceTokenDao.list(daoQuery, (page - 1) * pageSize, pageSize);
    }
    public void deleteByPrimaryKey(String id) {
        bPushDeviceTokenDao.delete(id);
    }
}
service-push/src/main/java/com/ks/push/manager/PushExcuteResultManager.java
@@ -6,6 +6,8 @@
import com.ks.push.pojo.DO.BPushTask;
import com.ks.push.pojo.DO.BPushTaskExcuteResult;
import com.ks.push.pojo.DO.PushPlatform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -16,11 +18,15 @@
@Component
public class PushExcuteResultManager {
    private Logger logger = LoggerFactory.getLogger(PushExcuteResultManager.class);
    @Resource
    private BPushTaskExcuteResultMapper bPushTaskExcuteResultMapper;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    private RedisTemplate<String, String> redisTemplate;
    @Resource
    private BPushTaskDao bPushTaskDao;
@@ -37,7 +43,6 @@
        result.setId(BPushTaskExcuteResult.createId(taskId, pushPlatform));
        //初始化
        bPushTaskExcuteResultMapper.insertSelective(result);
        String key = getRedisKey(taskId, pushPlatform);
        redisTemplate.delete(key);
        return result;
@@ -70,10 +75,12 @@
     */
    public void batchPushFinish(BPushDeviceDataSet dataSet, PushPlatform pushPlatform) {
        String key = getRedisKey(dataSet.getTaskId(), pushPlatform);
        redisTemplate.opsForSet().remove(key, dataSet.getBatchId());
        Long size = redisTemplate.opsForSet().size(key);
        redisTemplate.opsForSet().remove(key, dataSet.getBatchId());
        Long afterSize = redisTemplate.opsForSet().size(key);
        logger.info("{}推送剩余数量 taskId-{} size-{} afterSize-{}", pushPlatform.name(), dataSet.getTaskId(), size + "", afterSize + "");
        //判断是否推送完成
        if (size == null || size == 0L) {
        if (afterSize == null || afterSize == 0L) {
            //已经推送完了
            String id = BPushTaskExcuteResult.createId(dataSet.getTaskId(), pushPlatform);
            BPushTaskExcuteResult update = new BPushTaskExcuteResult();
@@ -92,6 +99,7 @@
                updateTask.setStateDesc("推送完成");
                updateTask.setUpdateTime(new Date());
                bPushTaskDao.updateSelective(updateTask);
                logger.info("{}推送完成 taskId-{}", pushPlatform.name(), dataSet.getTaskId());
            }
        }
    }
service-push/src/main/java/com/ks/push/manager/PushManager.java
@@ -36,10 +36,10 @@
    private BPushPlatformAppInfoManager bPushPlatformAppInfoManager;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    private PushExcuteResultManager pushExcuteResultManager;
    @Resource
    private PushExcuteResultManager pushExcuteResultManager;
    private RedisTemplate redisTemplate;
    /**
@@ -81,70 +81,72 @@
        }
        String key = "task-" + taskId;
        if (!redisTemplate.opsForValue().setIfAbsent(key, "1", 5 * 60, TimeUnit.SECONDS)) {
        Boolean s = redisTemplate.opsForValue().setIfAbsent(key, "1", 5 * 60, TimeUnit.SECONDS);
        if (s == null || !s) {
            throw new BPushTaskException(BPushTaskException.CODE_BUSY, "服务器繁忙,请稍后再试");
        }
        logger.info("开始启动推送#taskId:{}", task.getId());
        updateState(taskId, BPushTask.STATE_PUSHING, null);
        boolean hasDevice = false;
        try {
            // 查询可推送的平台
            List<BPushPlatformAppInfo> list = bPushPlatformAppInfoManager.listByAppCode(task.getAppCode());
            //先初始化推送结果数据数据
            List<BPushTaskExcuteResult> resultList = new ArrayList<>();
            Map<String, Long> resultCountMap = new HashMap<>();
            for (BPushPlatformAppInfo appInfo : list) {
                long count = pushDeviceTokenManager.count(task.getAppCode(), appInfo.getPlatform(), task.getFilter());
                if (count > 0) {
                    hasDevice = true;
                    //初始化推送结果数据
                    BPushTaskExcuteResult result = pushExcuteResultManager.initPushExcuteResult(taskId, appInfo.getPlatform(), count);
                    resultCountMap.put(result.getId(), count);
                    resultList.add(result);
                }
        // 查询可推送的平台
        List<BPushPlatformAppInfo> list = bPushPlatformAppInfoManager.listByAppCode(task.getAppCode());
        //先初始化推送结果数据数据
        List<BPushTaskExcuteResult> resultList = new ArrayList<>();
        Map<String, Long> resultCountMap = new HashMap<>();
        for (BPushPlatformAppInfo appInfo : list) {
            long count = pushDeviceTokenManager.count(task.getAppCode(), appInfo.getPlatform(), task.getFilter());
            if (count > 0) {
                hasDevice = true;
                //初始化推送结果数据
                BPushTaskExcuteResult result = pushExcuteResultManager.initPushExcuteResult(taskId, appInfo.getPlatform(), count);
                resultCountMap.put(result.getId(), count);
                resultList.add(result);
            }
            for (BPushTaskExcuteResult result : resultList) {
                long count = resultCountMap.get(result.getId());
                int pageSize = 500;
                int totalPage = (int) (count % pageSize == 0 ? count / pageSize : count / pageSize + 1);
                long totalValidCount = 0L;
                //初始化批量推送的计数
                for (int page = 0; page < totalPage; page++) {
                    pushExcuteResultManager.startBatchPush(taskId, result.getPushPlatform(), page + "");
                }
                for (int page = 0; page < totalPage; page++) {
                    List<BPushDeviceToken> deviceTokenList = pushDeviceTokenManager.list(task.getAppCode(), result.getPushPlatform(), task.getFilter(), page + 1, pageSize);
                    List<String> tokenList = new ArrayList<>();
                    for (BPushDeviceToken deviceToken : deviceTokenList) {
                        //TODO 时间判断
                        tokenList.add(deviceToken.getToken());
                    }
                    totalValidCount += tokenList.size();
                    BPushDeviceDataSet dataSet = new BPushDeviceDataSet(tokenList, page + "", taskId);
                    //最后一页
                    if (page == totalPage - 1 && totalValidCount != count) {
                        //修改总数
                        pushExcuteResultManager.setDeviceCount(result.getId(), totalValidCount);
                    }
                    CMQManager.getInstance().addToPushQueue(result.getPushPlatform(), dataSet);
                }
                logger.info("加入推送队列#任务Id:{}#平台:{}#推送数量:{}", task.getId(), result.getPushPlatform().name(), count);
            }
        } finally {
            redisTemplate.delete(key);
        }
        //没有可推送的设备
        for (BPushTaskExcuteResult result : resultList) {
            long count = resultCountMap.get(result.getId());
            int pageSize = 500;
            int totalPage = (int) (count % pageSize == 0 ? count / pageSize : count / pageSize + 1);
            long totalValidCount = 0L;
            //初始化批量推送的计数
            for (int page = 0; page < totalPage; page++) {
                pushExcuteResultManager.startBatchPush(taskId, result.getPushPlatform(), page + "");
            }
            for (int page = 0; page < totalPage; page++) {
                List<BPushDeviceToken> deviceTokenList = pushDeviceTokenManager.list(task.getAppCode(), result.getPushPlatform(), task.getFilter(), page + 1, pageSize);
                List<String> tokenList = new ArrayList<>();
                for (BPushDeviceToken deviceToken : deviceTokenList) {
                    //TODO 时间判断
                    tokenList.add(deviceToken.getToken());
                }
                totalValidCount += tokenList.size();
                BPushDeviceDataSet dataSet = new BPushDeviceDataSet(tokenList, page + "", taskId);
                //最后一页
                if (page == totalPage - 1 && totalValidCount != count) {
                    //修改总数
                    pushExcuteResultManager.setDeviceCount(result.getId(), totalValidCount);
                }
                CMQManager.getInstance().addToPushQueue(result.getPushPlatform(), dataSet);
            }
            logger.info("加入推送队列#任务Id:{}#平台:{}#推送数量:{}", task.getId(), result.getPushPlatform().name(), count);
        }
        if (!hasDevice) {
            updateState(taskId, BPushTask.STATE_FINSIH, "没有满足条件的可推送设备");
        }
        //没有可推送的设备
        logger.info("启动推送结束#taskId:{}", task.getId());
    }
service-push/src/main/java/com/ks/push/service/remote/BDeviceTokenServiceImpl.java
@@ -60,8 +60,12 @@
    @Override
    public void unBindUid(String appCode, String deviceId) {
        String id = BPushDeviceToken.createId(appCode, deviceId);
        if (bPushDeviceTokenDao.get(id) == null) {
            return;
        }
        Update update = new Update();
        update.set("uid", null);
        update.set("updateTime", new Date());
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(id));
        bPushDeviceTokenDao.update(query, update);
@@ -70,6 +74,9 @@
    @Override
    public void bindUid(String appCode, String deviceId, String uid) {
        String id = BPushDeviceToken.createId(appCode, deviceId);
        if (bPushDeviceTokenDao.get(id) == null) {
            return;
        }
        Update update = new Update();
        update.set("uid", uid);
        Query query = new Query();
service-push/src/main/java/com/ks/push/utils/Constant.java
New file
@@ -0,0 +1,16 @@
package com.ks.push.utils;
/**
 * @author Administrator
 * @title: Constant
 * @description: TODO
 * @date 2021/9/17 10:58
 */
public class Constant {
    //消息回执
    public final static String PUSH_CALLBACK_XM_URL = "https://cb.push.banliapp.com:444/callback/xm";
    public final static String PUSH_CALLBACK_OPPO_URL = "https://cb.push.banliapp.com:444/callback/oppo";
    public final static String PUSH_CALLBACK_VIVO_URL = "https://cb.push.banliapp.com:444/callback/vivo";
}
service-push/src/main/java/com/ks/push/utils/PushUtil.java
@@ -1,14 +1,23 @@
package com.ks.push.utils;
import com.google.gson.Gson;
import com.huawei.push.util.push.*;
import com.ks.push.pojo.DO.BPushMessage;
import com.ks.push.pojo.DO.PushPlatform;
import org.yeshi.utils.push.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yeshi.utils.StringUtil;
import org.yeshi.utils.push.entity.PushAppInfo;
import org.yeshi.utils.push.entity.PushMessage;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class PushUtil {
    static Logger logger = LoggerFactory.getLogger(PushUtil.class);
    /**
     * 推送通知
@@ -19,7 +28,7 @@
     * @param tokenList
     * @throws Exception
     */
    public static void pushNotifyCation(PushPlatform platform, PushAppInfo appInfo, BPushMessage pushMessage, List<String> tokenList) throws Exception {
    public static void pushNotifyCation(PushPlatform platform, PushAppInfo appInfo, BPushMessage pushMessage, String identifyCode, List<String> tokenList) throws Exception {
        if (appInfo == null) {
            throw new Exception("应用参数为空");
        }
@@ -31,12 +40,30 @@
        if (tokenList == null || tokenList.size() == 0) {
            throw new Exception("token为空");
        }
        PushMessage message = new PushMessage(pushMessage.getTitle(), pushMessage.getContent(), pushMessage.getAndroidActivity(), pushMessage.getAndroidActivityScheme(), pushMessage.getAndroidHostPath(), pushMessage.getExtras());
        logger.info("{}推送应用信息:{}", platform.name(), new Gson().toJson(appInfo));
        logger.info("{}推送内容信息:{}", platform.name(), new Gson().toJson(pushMessage));
        logger.info("{}推送token信息:{}", platform.name(), new Gson().toJson(tokenList));
        Map<String, String> extras = null;
        if (pushMessage.getExtras() != null) {
            extras = new HashMap<>();
            for (Iterator<String> its = pushMessage.getExtras().keySet().iterator(); its.hasNext(); ) {
                String key = its.next();
                String value = pushMessage.getExtras().get(key);
                if (!StringUtil.isNullOrEmpty(key) && !StringUtil.isNullOrEmpty(value)) {
                    extras.put(key, value);
                }
            }
        }
        PushMessage message = new PushMessage(pushMessage.getTitle(), pushMessage.getContent(), pushMessage.getAndroidActivity(), pushMessage.getAndroidActivityScheme(), pushMessage.getAndroidHostPath(), extras);
        if (platform == PushPlatform.xm) {
            pushXM(appInfo, message, tokenList);
        } else if (platform == PushPlatform.hw) {
            pushHW(appInfo, message, tokenList);
            pushHW(appInfo, message, identifyCode, tokenList);
        } else if (platform == PushPlatform.oppo) {
            pushOPPO(appInfo, message, tokenList);
        } else if (platform == PushPlatform.vivo) {
@@ -63,11 +90,12 @@
     *
     * @param appInfo
     * @param message
     * @param bigTag
     * @param tokenList
     * @throws Exception
     */
    private static void pushHW(PushAppInfo appInfo, PushMessage message, List<String> tokenList) throws Exception {
        HuaWeiPushUtil.pushNotificationByTokens(appInfo, message, tokenList);
    private static void pushHW(PushAppInfo appInfo, PushMessage message, String bigTag, List<String> tokenList) throws Exception {
        HuaWeiPushUtil.pushNotificationByTokens(appInfo, message, bigTag, tokenList);
    }
    /**
service-push/src/main/resources/application-dev.yml
@@ -21,24 +21,25 @@
      authentication-database: admin
  redis:
    host: 193.112.35.168 #193.112.34.40
    port: 6379
    port: 6380
    #Yeshi2016@
    password: 'crs-43yhgz0i:Yeshi2016@'
    timeout: 5000ms
    timeout: 5000
    database: 10
    lettuce:
      pool:
        max-active: 1024
        max-wait: 10000ms
        max-idle: 200
        time-between-eviction-runs: 60000ms
        min-idle: 8
      jedis:
        pool:
          max-active: 1024
        max-active: 200 #连接池最大连接数
        max-wait: 5000 #连接池最大阻塞等待时间
        max-idle: 50 #连接池中的最大空闲连接
        time-between-eviction-runs: 60000 #每隔多长时间运行一次空闲连接回收器(独立线程)
        min-idle: 0 #连接池中的最小空闲连接
      shutdown-timeout: 5000 #连接关闭超时时间
    jedis:
      pool:
          max-total: 1024
          max-wait: 10000ms
          max-idle: 200
          time-between-eviction-runs: 60000ms
          test_on_borrow: true
          min-idle: 8
@@ -71,6 +72,19 @@
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
xxl:
  job:
    accessToken:
    executor:
      appname: service-push
      ip:
      logpath: /data/applogs/xxl-job/jobhandler
      logretentiondays: -1
      port: 9999
    admin:
      addresses: http://1.14.235.17:9000/xxl-job-admin
dubbo:
  application:
    name: push-service
service-push/src/main/resources/application-pro.yml
@@ -24,21 +24,22 @@
    port: 6379
    #Yeshi2016@
    password: 'crs-43yhgz0i:Yeshi2016@'
    timeout: 5000ms
    timeout: 5000
    database: 10
    lettuce:
      pool:
        max-active: 1024
        max-wait: 10000ms
        max-idle: 200
        time-between-eviction-runs: 60000ms
        min-idle: 8
        max-active: 200 #连接池最大连接数
        max-wait: 5000 #连接池最大阻塞等待时间
        max-idle: 50 #连接池中的最大空闲连接
        time-between-eviction-runs: 60000 #每隔多长时间运行一次空闲连接回收器(独立线程)
        min-idle: 5 #连接池中的最小空闲连接
      shutdown-timeout: 5000 #连接关闭超时时间
    jedis:
      pool:
        max-active: 1024
        max-total: 1024
        max-wait: 10000ms
        max-idle: 200
        time-between-eviction-runs: 60000ms
        test_on_borrow: true
        min-idle: 8
service-push/src/main/resources/hw_push_url.properties
New file
@@ -0,0 +1,2 @@
token_server = https://oauth-login.cloud.huawei.com/oauth2/v2/token
push_open_url = https://push-api.cloud.huawei.com
service-push/src/main/resources/logback.xml
@@ -61,6 +61,34 @@
        </filter>
    </appender>
    <appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.filePath}/debug.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.filePath}/debug/debug.log.gz.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${log.maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <appender name="redisAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.filePath}/redis.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.filePath}/redis/redis.log.gz.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${log.maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>
    <!--name表示为哪一个logger指定层级和输出的方式
       additivity表示叠加祖先的输出方式(默认为true,会叠加),所以com.lxc.o2o以及其子类都会输出在控制台中,因为这个logger继承了root中的appender
       level表示级别大于等于${log.level}的信息才会输出,输出方式为配置的appender,
@@ -74,6 +102,14 @@
    <logger name="io.seata" level="ERROR"></logger>
    <logger name="System.out" level="INFO"></logger>
    <logger name="org.springframework.data.redis" level="DEBUG">
        <appender-ref ref="redisAppender"></appender-ref>
    </logger>
    <logger name="io.lettuce.core" level="DEBUG">
        <appender-ref ref="redisAppender"></appender-ref>
    </logger>
    <!-- 一切logger都会继承自root,root默认的层级level为debug -->
    <root level="INFO">
@@ -81,5 +117,6 @@
        <appender-ref ref="STDOUT"></appender-ref>
        <appender-ref ref="errorAppender"></appender-ref>
        <appender-ref ref="infoAppender"></appender-ref>
        <appender-ref ref="debugAppender"></appender-ref>
    </root>
</configuration>
service-push/src/main/resources/static/index.html
@@ -2,7 +2,7 @@
<html class="x-admin-sm">
<head>
    <meta charset="UTF-8">
    <title>金币管理系统</title>
    <title>推送管理系统</title>
    <meta name="renderer" content="webkit|ie-comp|ie-stand">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport"
@@ -30,7 +30,7 @@
<!-- 顶部开始 -->
<div class="container">
    <div class="logo">
        <a href="./index.html">金币管理系统</a></div>
        <a href="./index.html">推送系统</a></div>
    <div class="left_open">
        <a><i title="展开左侧栏" class="iconfont">&#xe699;</i></a>
    </div>
service-push/src/main/resources/static/pushplatform-appinfo-list.html
@@ -3,7 +3,7 @@
<head>
    <meta charset="UTF-8">
    <title>金币管理系统</title>
    <title>推送管理系统</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport"
service-push/src/main/resources/static/pushtask-list.html
@@ -3,7 +3,7 @@
<head>
    <meta charset="UTF-8">
    <title>金币管理系统</title>
    <title>推送管理系统</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport"