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冲突

相关推荐
计算机学姐12 分钟前
基于SpringBoot的自习室座位预定系统【预约选座+日期时间段+协同过滤推荐算法+数据可视化统计】
java·vue.js·spring boot·后端·spring·信息可视化·tomcat
Marktowin34 分钟前
控制权限系列之(2)手把手教你使用基于角色的权限控制
后端
仙俊红1 小时前
Spring Cloud 核心组件部署方式速查表
后端·spring·spring cloud
码农幻想梦1 小时前
实验九 Restful和ajax实现
后端·ajax·restful
今天多喝热水2 小时前
SpEL(Spring Expression Language) 表达式
java·后端·spring
码农水水2 小时前
浅谈 MySQL InnoDB 的内存组件
java·开发语言·数据库·后端·mysql·面试
独自破碎E2 小时前
Spring Boot的多环境配置
java·spring boot·后端
Edward-tan2 小时前
【玩转全栈】----Django模板语法、请求与响应
后端·python·django
猫头鹰源码(同名B站)2 小时前
基于django+vue的时尚穿搭社区(商城)(前后端分离)
前端·javascript·vue.js·后端·python·django
Watermelo6173 小时前
随机扣款实现赛博共产主义,《明日方舟:终末地》公测支付事故复盘
数据库·后端·游戏程序·技术美术·用户体验·游戏策划·游戏美术