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

相关推荐
Amagi.4 分钟前
Spring中Bean的作用域
java·后端·spring
2402_8575893627 分钟前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
J老熊36 分钟前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
Benaso39 分钟前
Rust 快速入门(一)
开发语言·后端·rust
sco528239 分钟前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
原机小子1 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端
吾日三省吾码1 小时前
详解JVM类加载机制
后端
努力的布布2 小时前
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
java·后端·spring
PacosonSWJTU2 小时前
spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)
java·后端·springmvc
记得开心一点嘛2 小时前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala