IOS消息推送服务端实现

一、命令行粗略走一下流程

获取需要的配置信息

开发者平台:

idmsa.apple.com/IDMSWebAuth...

这里能拿到发送消息需要的各种配置信息 只有管理员才能登陆查看 我们使用token方式推送,这里只列举该方式需要的配置

js 复制代码
push.ios.teamId = ##teamId## 
push.ios.keyId = ##keyid## 
push.ios.authKey.path = /home/key/AuthKey.p8 
push.ios.topic = ##topic##

命令行验证消息发送

官方文档

developer.apple.com/documentati...

执行命令

js 复制代码
## 配置信息
TEAM_ID=##teamid##
TOKEN_KEY_FILE_NAME=/home/key/AuthKey.p8
AUTH_KEY_ID=##keyid##
TOPIC=##topic##
DEVICE_TOKEN=##设备id##
APNS_HOST_NAME=api.push.apple.com
## 生成请求需要的参数
JWT_ISSUE_TIME=$(date +%s)
JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"
## 发起http2请求
curl -v --header "apns-topic: $TOPIC" --header "apns-push-type: alert" --header "authorization: bearer $AUTHENTICATION_TOKEN" --data '{"aps":{"alert":"test"}}' --http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}

如下是一个调用成功的返回,并且app应该收到了推送消息。

经过上面的步骤就能确认自己的配置信息及环境是否有问题了。

二、编码实现

官方的发送方式有证书和令牌两种,我们选择了令牌的方式 文档如下:

使用令牌发送推送通知

选择的框架:pushy

文档:github.com/jchambers/p...

pushy底层发起http2请求到apple服务器使用的netty.

注意:依赖的jar包如果冲突,大概率会导致发送消息失败,我们这里是直接将其他项目依赖的netty包都排除掉,使用的pushy自己的的netty包。

核心代码

pom.xml

js 复制代码
<dependency>
    <groupId>com.eatthepath</groupId>
    <artifactId>pushy</artifactId>
    <version>0.15.4</version>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-tcnative-boringssl-static</artifactId>
    <version>2.0.62.Final</version>
    <scope>runtime</scope>
</dependency>

IosPushHandlerFactory 生成apnsclient

js 复制代码
new ApnsClientBuilder()
        .setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST)
        .setSigningKey(ApnsSigningKey.loadFromPkcs8File(new File(getAuthKeyPath()),
                getTeamId(), getKeyId()))
        .build();

IosPushHandler 构建请求包及发起推送请求

js 复制代码
public SimpleApnsPushNotification buildPayLoad(AppPushRecord record, AppUserToken userToken) {
    if (!PhoneType.IPHONE.getModel().equals(userToken.getPhoneType()) && !PhoneType.IPAD.getModel().equals(userToken.getPhoneType())) {
        throw new ServiceException("设备类型为" + userToken.getPhoneType() + ",不能使用ios推送!");
    }

    Integer unReadCount = userToken.getUnReadCount();
    if (unReadCount == null || (unReadCount != null && unReadCount.intValue() < 0)) {
        unReadCount = 0;
    }

    HashMap<String, Object> userInfo = new HashMap<String, Object>() {{
        put("type", record.getType());
        put("minorType", record.getMinorType());
        put("params", record.getParams());
        put("imgUrl", record.getImgUrl());
        put("recordId", record.getId());
    }};

    final ApnsPayloadBuilder payloadBuilder = new SimpleApnsPayloadBuilder();
    payloadBuilder.setAlertBody(record.getMessage())
            .setAlertTitle(record.getTitle())
            .setSound("default")
            .setBadgeNumber(unReadCount)
            .setAttributes(userInfo)
            .build();

    final String payload = payloadBuilder.build();

    SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(userToken.getDeviceToken(), topic, payload);
    logger.info(String.format("push to %d : %s", userToken.getUserId(), JSON.toJSONString(pushNotification)));
    return pushNotification;
}
js 复制代码
public PushNotificationResponse toPush(SimpleApnsPushNotification n, String dvcToken) {
    try {
        PushNotificationFuture<SimpleApnsPushNotification, PushNotificationResponse<SimpleApnsPushNotification>>
                sendNotificationFuture = service.sendNotification(n);
        PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse =
                sendNotificationFuture.get(30, TimeUnit.SECONDS);
        logger.info("NotificationResponse result= " + new Gson().toJson(pushNotificationResponse));
        if (pushNotificationResponse.isAccepted()) {
            logger.info("ios push success");
        } else {
            logger.info("ios push failure");
        }
        return pushNotificationResponse;
    } catch (TimeoutException e) {
        throw new RuntimeException(e);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } catch (ExecutionException e) {
        throw new RuntimeException(e);
    } finally {
      
    }

问题排查

1、验证网络问题

不要 ping api.push.apple.com

可以 curl api.push.apple.com/ 返回

js 复制代码
{
    "reason": "MethodNotAllowed"
}

2、如果使用命令行调用没问题,调用push接口返回类似shakehande ,socket报错,大概率是jar冲突

相关推荐
华仔啊31 分钟前
为什么 keySet() 是 HashMap 遍历的雷区?90% 的人踩过
java·后端
9号达人43 分钟前
Java 13 新特性详解与实践
java·后端·面试
用户490558160812544 分钟前
keepalived原理之持有vip是什么意思
后端
想用offer打牌1 小时前
线程池踩坑之一:将其放在类的成员变量
后端·面试·代码规范
心月狐的流火号1 小时前
Redis 的高性能引擎 Reactor 详解与基于 Go 手写 Redis
redis·后端
橙序员小站1 小时前
搞定系统设计题:如何设计一个支付系统?
java·后端·面试
Java水解1 小时前
Spring Boot + ONNX Runtime模型部署
spring boot·后端
Java水解1 小时前
Spring Security6.3.x使用指南
后端·spring
魂尾ac1 小时前
Django + Vue3 前后端分离技术实现自动化测试平台从零到有系列 <第一章> 之 注册登录实现
后端·python·django·vue