使用AWS的SNS(Simple Notification Server)实现终端设备间消息传递功能

使用场景:

在自有用户体系中,实现用户A给用户B发送消息,通知事务的处理结果。

举个例子:添加好友。
用户A发送好友请求给用户B,用户B收到请求后处理并将处理结果发送给用户A。

需求分析

  1. 选择一个消息推送平台:
    国内推送平台:极光、个推等,国外有GCM(Google Cloud Message)。
  2. 在自己的服务器上开发一个API。
    服务器接受用户A的请求消息,并通过推送平台将消息传递给用户B。 同理,用户B将处理结果发送给用户A。

通过上面的分析,我们结合服务器实现了功能需求。

但是,如果没有后台开发人员怎么办???

文章标题说的就是这个问题的解决方案。
AWS是亚马逊的云开发平台,类似国内的阿里云。SNS则是其中的一项服务,使应用程序、用户、终端设备能够通过云端即时发送或者接受消息。
我们不需要再开发一个API处理用户之间的消息传递,因为SNS已经全部做好了。我们需要做的便是选择一个推送平台、定义自己的消息格式,调用相关的SDK方法即可。

实现步骤(Android+GCM)

这里默认你已经有了AWS开发者账号。

1. 创建平台应用程序,得到ARN。

  • 进入SNS控制面板(控制台首页搜索SNS即可找到),依次点击“应用程序”-->“创建平台应用程序”


    使用AWS的SNS(Simple Notification Server)实现终端设备间消息传递功能_第1张图片
    创建平台应用程序
  • 应用程序名称:填写项目名称即可。
  • 推送通知平台:根据自己的需求进行选择,这里我选择GCM,通过谷歌推送来实现。
  • API密钥:在相应的推送平台注册应用后得到。
  • 获取GCM平台下API密钥
    a. 建议通过Firebase集成GCM,集成完好后,我们进入Google Cloud Platform,


    使用AWS的SNS(Simple Notification Server)实现终端设备间消息传递功能_第2张图片
    Google Cloud Platform

    b. 先选择通过Firebase创建的项目,然后在左侧菜单栏选择“API和服务”-->“凭据”。
    c. 复制红框内名称为“Server Key”的密钥(Firebase默认创建的3个密钥,分别作用于Browser、Server、Android)。
    因为我们想让AWS来处理消息传递,AWS属于Server一类。
    将获得的密钥粘贴到“创建应用平台程序”窗口的“API密钥”一栏,点击右下角“创建平台应用程序”,稍后界面显示如下:


    使用AWS的SNS(Simple Notification Server)实现终端设备间消息传递功能_第3张图片
    得到ARN

2. 将设备终端注册到ARN(代码实现),得到终端节点。
实现原理:集成GCM后,会有一个服务专门获取或更新token,我们只需要将这个token注册到ARN下即可。

//注册arn
private void sendRegistrationToServer(String token) {
        // Implement this method to send token to your app server.
        // 创建平台终端节点和管理设备令牌
        client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
        //retrieveEndpointArn方法是将保存在sp中的endpointArn取出,自己实现一下。
        String endpointArn = retrieveEndpointArn();
        boolean updateNeeded = false;
        boolean createNeeded = ("".equals(endpointArn));
        if (createNeeded) {
            // No platform endpoint ARN is stored; need to call createEndpoint.
            endpointArn = createEndpoint(token);
            createNeeded = false;
        }
        System.out.println("Retrieving platform endpoint data...");
        // Look up the platform endpoint and make sure the data in it is current, even if
        // it was just created.
        try {
            GetEndpointAttributesRequest geaReq =
                    new GetEndpointAttributesRequest()
                            .withEndpointArn(endpointArn);
            GetEndpointAttributesResult geaRes =
                    client.getEndpointAttributes(geaReq);
            updateNeeded = !geaRes.getAttributes().get("Token").equals(token)
                    || !geaRes.getAttributes().get("Enabled").equalsIgnoreCase("true");
        } catch (NotFoundException nfe) {
            // We had a stored ARN, but the platform endpoint associated with it
            // disappeared. Recreate it.
            createNeeded = true;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        if (createNeeded) {
            createEndpoint(token);
        }
        System.out.println("updateNeeded = " + updateNeeded);
        if (updateNeeded) {
            // The platform endpoint is out of sync with the current data;
            // update the token and enable it.
            System.out.println("Updating platform endpoint " + endpointArn);
            Map attribs = new HashMap();
            attribs.put("Token", token);
            attribs.put("Enabled", "true");
            SetEndpointAttributesRequest saeReq =
                    new SetEndpointAttributesRequest()
                            .withEndpointArn(endpointArn)
                            .withAttributes(attribs);
            client.setEndpointAttributes(saeReq);
        }
    }
    /**
     * @return never null
     */
    private String createEndpoint(String token) {
        String endpointArn = null;
        try {
            System.out.println("Creating platform endpoint with token " + token);
            //创建endpointArn
            String arn = "自己的arn";
            CreatePlatformEndpointRequest cpeReq =
                    new CreatePlatformEndpointRequest()
                            .withPlatformApplicationArn(arn)
                            .withToken(token);
            CreatePlatformEndpointResult cpeRes = client
                    .createPlatformEndpoint(cpeReq);
            endpointArn = cpeRes.getEndpointArn();
            //订阅主题
//String topic = "自己的主题";
// SubscribeRequest request = new SubscribeRequest()
// .withTopicArn(topic)
// .withProtocol("application")
// .withEndpoint(endpointArn);
// SubscribeResult subscribeResult = client.subscribe(request);
// Log.i(TAG, "subscribeResult: " + subscribeResult.getSubscriptionArn());
        } catch (InvalidParameterException ipe) {
            String message = ipe.getErrorMessage();
            System.out.println("Exception message: " + message);
            Pattern p = Pattern.compile(".*Endpoint (arn:aws:sns[^ ]+) already exists with the same token.*");
            Matcher m = p.matcher(message);
            if (m.matches()) {
                // The platform endpoint already exists for this token, but with
                // additional custom data that
                // createEndpoint doesn't want to overwrite. Just use the
                // existing platform endpoint.
                endpointArn = m.group(1);
            } else {
                // Rethrow the exception, the input is actually bad.
                throw ipe;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        //将endpointArn保存在sp中,自己实现一下。
        storeEndpointArn(endpointArn);
        return endpointArn;
    }

完成以上步骤后,在程序启动后,便可以得到如下图所示内容。


使用AWS的SNS(Simple Notification Server)实现终端设备间消息传递功能_第4张图片
终端节点
  • 令牌:GCM下发的token
  • 终端节点 ARN:SNS为每一个终端生成的唯一标识。

3. 推送消息到某一个终端节点。
在SNS控制台中,选择一个终端节点,点击“发布到终端节点”,在弹出的界面中,输入消息,点击发送即可。

4. 终端间互发消息(代码实现)。
经过以上3个步骤,每个用户启动应用程序后,都会注册到SNS,并生成各自特有的终端节点。
那么,终端之间发送消息就简单了。
用户A向用户B发送消息,只需要知道用户B的终端节点号码即可。

    /**
     * 给指定终端发送消息
     *
     * @param endpointArn 对方的终端节点
     * @param targetId 对方的id
     * @param gcmMsg 消息体
     * @return 发送成功后返回该条消息id
     */
    public static Observable sendMsgToDevice(final String endpointArn, final String targetId, final GCMMsg gcmMsg) {
        return Observable.fromCallable(new Callable() {
            @Override
            public String call() throws Exception {
                String mId = SpUtil.getString(Constants.USER_ID);
                //发消息
                AmazonSNSClient client = new AmazonSNSClient(CognitoClientManager.getCredentials());
                client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
                PublishRequest request = new PublishRequest();
                request.setTargetArn(endpointArn);
                request.setMessageStructure("json");
                String msg = "{\n" +
                        "\"GCM\": \"{ \\\"data\\\": { " +
                        "\\\"category\\\": \\\"" + gcmMsg.getCategory() + "\\\", " +
                        "\\\"title\\\": \\\"" + gcmMsg.getTitle() + "\\\", " +
                        "\\\"message\\\": \\\"" + gcmMsg.getMessage() + "\\\", " +
                        "\\\"url\\\": \\\"" + gcmMsg.getUrl() + "\\\", " +
                        "\\\"data\\\": \\\"" + gcmMsg.getData() + "\\\" } }\"\n" +
                        "}";
                request.setMessage(msg);
                PublishResult result = client.publish(request);
                Log.i("SNS", "messageId: " + result.getMessageId());
                return result.getMessageId();
            }
        });
    }

至此,终端间互发消息的需求就完成了,全部借助AWS的SNS服务,无需另外开发后台API,自己只需要调用相关的SDK方法即可。

你可能感兴趣的:(使用AWS的SNS(Simple Notification Server)实现终端设备间消息传递功能)