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

相关推荐
.生产的驴5 小时前
SpringBoot 接口限流Lua脚本接合Redis 服务熔断 自定义注解 接口保护
java·大数据·数据库·spring boot·redis·后端·lua
一切皆有迹可循5 小时前
SpringBoot整合MinIO快速入门:实现分布式文件存储与管理
spring boot·分布式·后端
洛可可白6 小时前
Spring Boot中自定义注解的创建与使用
java·spring boot·后端
追逐时光者7 小时前
一款 .NET 开源、免费、轻量级且非侵入性的防火墙软件
后端·.net
Asthenia04128 小时前
Netty ServerBootstrap Handler链与Pipeline分析
后端
uhakadotcom8 小时前
New Relic入门指南:性能监控与API应用
后端·面试·github
霍徵琅8 小时前
Julia语言的测试覆盖率
开发语言·后端·golang
独泪了无痕9 小时前
数据库开发必备:理解DDL、DML、DQL和DCL
数据库·后端
吃饭了呀呀呀9 小时前
🐳 《Android》 安卓开发教程 - 自定义 Toast
android·后端
_yingty_10 小时前
GO语言入门经典-反射3(Value 与对象的值)
开发语言·前端·后端·学习·golang