

# 使用 Amazon SNS 通知调用 Lambda 函数
<a name="with-sns"></a>

您可以使用 Lambda 函数处理 Amazon Simple Notification Service (Amazon SNS) 通知。Amazon SNS 支持将 Lambda 函数作为发送到主题的消息的目标。您可以将函数订阅到同一账户或其他 AWS 账户中的主题。有关详细的演练过程，请参阅[教程：将 AWS Lambda 与 Amazon Simple Notification Service 结合使用](with-sns-example.md)。

Lambda 仅支持标准 SNS 主题的 SNS 触发器。不支持 FIFO 主题。

Lambda 通过对消息进行排队和处理重试来异步处理 SNS 消息。如果无法联系到 Lambda 或消息被拒绝，Amazon SNS 将在几个小时内以递增的间隔重试。有关详细信息，请参阅 Amazon SNS 常见问题中的[可靠性](https://aws.amazon.com/sns/faqs/#Reliability)。

**警告**  
Lambda 异步调用至少处理每个事件一次，有可能出现重复处理记录的情况。为避免与重复事件相关的潜在问题，我们强烈建议您将函数代码设为幂等性。要了解更多信息，请参阅 AWS 知识中心的[如何让我的 Lambda 函数保持幂等性](https://repost.aws/knowledge-center/lambda-function-idempotent)。

## Powertools for AWS Lambda 中的幂等性实用程序
<a name="services-sns-powertools-idempotency"></a>

Powertools for AWS Lambda 中的幂等性实用程序使您的 Lambda 函数具有等性。它适用于 Python、TypeScript、Java 和 .NET。有关更多信息，请参阅 *Powertools for AWS Lambda (Python) 文档*中的[幂等性实用程序](https://docs.powertools.aws.dev/lambda/python/latest/utilities/idempotency/)、*Powertools for AWS Lambda (TypeScript) 文档*中的[幂等性实用程序](https://docs.aws.amazon.com/powertools/typescript/2.1.1/utilities/idempotency/)、*Powertools for AWS Lambda (Java) 文档*中的[幂等性实用程序](https://docs.powertools.aws.dev/lambda/java/latest/utilities/idempotency/)以及*Powertools for AWS Lambda (.NET) 文档*中的[幂等性实用程序](https://docs.powertools.aws.dev/lambda/dotnet/utilities/idempotency/)。

**Topics**
+ [Powertools for AWS Lambda 中的幂等性实用程序](#services-sns-powertools-idempotency)
+ [使用控制台为 Lambda 函数添​​加 Amazon SNS 主题触发器](#sns-trigger-console)
+ [为 Lambda 函数手动添加 Amazon SNS 主题触发器](#sns-trigger-manual)
+ [示例 SNS 事件形状](#sns-sample-event)
+ [教程：将 AWS Lambda 与 Amazon Simple Notification Service 结合使用](with-sns-example.md)

## 使用控制台为 Lambda 函数添​​加 Amazon SNS 主题触发器
<a name="sns-trigger-console"></a>

要添加 SNS 主题作为 Lambda 函数的触发器，最简单的方法是使用 Lambda 控制台。当您通过控制台添加触发器时，Lambda 会自动设置必要的权限和订阅以开始从 SNS 主题接收事件。

**添加 SNS 主题作为 Lambda 函数的触发器（控制台）**

1. 打开 Lamba 控制台的[函数页面](https://console.aws.amazon.com/lambda/home#/functions)。

1. 选择您要为其添加触发器的函数的名称。

1. 选择**配置**，然后选择**触发器**。

1. 选择**添加触发器**。

1. 在**触发器配置**下的下拉菜单中，选择 **SNS**。

1. 对于 **SNS 主题**，请选择要订阅的 SNS 主题。

## 为 Lambda 函数手动添加 Amazon SNS 主题触发器
<a name="sns-trigger-manual"></a>

要手动为 Lambda 函数设置 SNS 触发器，您需要完成以下步骤：
+ 为函数定义基于资源的策略，以允许 SNS 调用该函数。
+ 将 Lambda 函数订阅至 Amazon SNS 主题。
**注意**  
如果您的 SNS 主题和 Lambda 函数位于不同的 AWS 账户中，则还需要授予额外权限以允许跨账户订阅 SNS 主题。有关更多信息，请参阅[授予 Amazon SNS 订阅的跨账户权限](with-sns-example.md#with-sns-subscription-grant-permission)。

您可以使用 AWS Command Line Interface（AWS CLI）来完成这两个步骤。首先，要为允许 SNS 调用的 Lambda 函数定义基于资源的策略，请使用以下 AWS CLI 命令。请务必将 `--function-name` 的值替换为您的 Lambda 函数名称，将 `--source-arn` 的值替换为您的 SNS 主题 ARN。

```
aws lambda add-permission --function-name example-function \
    --source-arn arn:aws:sns:us-east-1:123456789012:sns-topic-for-lambda \
    --statement-id function-with-sns --action "lambda:InvokeFunction" \
    --principal sns.amazonaws.com
```

要将您的函数订阅到 SNS 主题，请使用以下 AWS CLI 命令。将 `--topic-arn` 的值替换为您的 SNS 主题 ARN，将 `--notification-endpoint` 的值替换为您的 Lambda 函数 ARN。

```
aws sns subscribe --protocol lambda \
    --region us-east-1 \
    --topic-arn arn:aws:sns:us-east-1:123456789012:sns-topic-for-lambda \
    --notification-endpoint arn:aws:lambda:us-east-1:123456789012:function:example-function
```

## 示例 SNS 事件形状
<a name="sns-sample-event"></a>

Amazon SNS 通过包含消息和元数据的事件[异步](invocation-async.md)调用您的函数。

**Example Amazon SNS 消息事件**  

```
{
  "Records": [
    {
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:us-east-1:123456789012:sns-lambda:21be56ed-a058-49f5-8c98-aedd2564c486",
      "EventSource": "aws:sns",
      "Sns": {
        "SignatureVersion": "1",
        "Timestamp": "2019-01-02T12:45:07.000Z",
        "Signature": "tcc6faL2yUC6dgZdmrwh1Y4cGa/ebXEkAi6RibDsvpi+tE/1+82j...65r==",
        "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-ac565b8b1a6c5d002d285f9598aa1d9b.pem",
        "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
        "Message": "Hello from SNS!",
        "MessageAttributes": {
          "Test": {
            "Type": "String",
            "Value": "TestString"
          },
          "TestBinary": {
            "Type": "Binary",
            "Value": "TestBinary"
          }
        },
        "Type": "Notification",
        "UnsubscribeUrl": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&amp;SubscriptionArn=arn:aws:sns:us-east-1:123456789012:test-lambda:21be56ed-a058-49f5-8c98-aedd2564c486",
        "TopicArn":"arn:aws:sns:us-east-1:123456789012:sns-lambda",
        "Subject": "TestInvoke"
      }
    }
  ]
}
```

# 教程：将 AWS Lambda 与 Amazon Simple Notification Service 结合使用
<a name="with-sns-example"></a>

在本教程中，您将使用某个 AWS 账户 中的 Lambda 函数，订阅独立 AWS 账户 中的 Amazon Simple Notiﬁcation Service（Amazon SNS）主题。将消息发布到 Amazon SNS 主题时，Lambda 函数会读取消息内容并将其输出到 Amazon CloudWatch Logs。要完成此教程，需要使用 AWS Command Line Interface（AWS CLI）。

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/services-sns-tutorial/sns_tut_resources.png)


要完成本教程，请执行以下步骤：
+ 在**账户 A** 中，创建 Amazon SNS 主题。
+ 在**账户 B** 中，创建可从该主题读取消息的 Lambda 函数。
+ 在**账户 B** 中，创建该主题的订阅。
+ 在**账户 A** 中将消息发布到 Amazon SNS 主题，并确认**账户 B** 中的 Lambda 函数将其输出到 CloudWatch Logs。

通过完成这些步骤，您将了解如何配置 Amazon SNS 主题以调用 Lambda 函数。您还将了解如何创建 AWS Identity and Access Management（IAM）策略，向其他 AWS 账户 中的资源授予调用 Lambda 的权限。

在本教程中，您将使用两个独立的 AWS 账户。AWS CLI 命令通过以下方式对此进行说明：使用两个名为 `accountA` 和 `accountB` 的命名配置文件，每个文件配置为用于不同的 AWS 账户。要了解如何配置 AWS CLI 以使用不同的配置文件，请参阅《AWS Command Line Interface User Guide for Version 2**》中的 [Configuration and credential file settings](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)。确保为两个配置文件配置相同的默认值 AWS 区域。

如果您为两个 AWS 账户 创建的 AWS CLI 配置文件使用不同名称，或者使用默认配置文件和一个命名配置文件，请根据需要按照以下步骤修改 AWS CLI 命令。

## 先决条件
<a name="with-sns-prereqs"></a>

### 安装 AWS Command Line Interface
<a name="install_aws_cli"></a>

如果您尚未安装 AWS Command Line Interface，请按照[安装或更新最新版本的 AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) 中的步骤进行安装。

本教程需要命令行终端或 Shell 来运行命令。在 Linux 和 macOS 中，可使用您首选的 Shell 和程序包管理器。

**注意**  
在 Windows 中，操作系统的内置终端不支持您经常与 Lambda 一起使用的某些 Bash CLI 命令（例如 `zip`）。[安装 Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10)，获取 Ubuntu 和 Bash 与 Windows 集成的版本。

## 创建 Amazon SNS 主题（账户 A）
<a name="with-sns-create-topic"></a>

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/services-sns-tutorial/sns_tut_steps_1.png)


**创建 主题**
+ 在**账户 A** 中，使用以下 AWS CLI 命令创建 Amazon SNS 标准主题。

  ```
  aws sns create-topic --name sns-topic-for-lambda --profile accountA
  ```

  您应该可以看到类似于如下所示的输出内容。

  ```
  {
      "TopicArn": "arn:aws:sns:us-west-2:123456789012:sns-topic-for-lambda"
  }
  ```

  记下主题的 Amazon 资源名称（ARN）。稍后在本教程中向 Lambda 函数添加权限以订阅主题时，将需要使用此 ARN。

## 创建函数执行角色（账户 B）
<a name="with-sns-example-create-iam-role"></a>

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/services-sns-tutorial/sns_tut_steps_2.png)


执行角色是一个 IAM 角色，用于向 Lambda 函数授予访问 AWS 服务 和资源的权限。在**账户 B** 中创建函数之前，您需要创建一个角色，向该函数授予将日志写入 CloudWatch Logs 的基本权限。我们将在后续步骤中添加读取 Amazon SNS 主题的权限。

**创建执行角色**

1. 在**账户 B** 中，打开 IAM 控制台中的[角色页面](https://console.aws.amazon.com/iam/home#/roles)。

1. 选择**创建角色**。

1. 在**可信实体类型**中选择 **AWS 服务**。

1. 在**使用案例**中选择 **Lambda**。

1. 选择**下一步**。

1. 通过执行以下操作，向角色添加基本权限策略：

   1. 在**权限策略**搜索框中输入 **AWSLambdaBasicExecutionRole**。

   1. 选择**下一步**。

1. 通过执行以下操作，完成角色创建：

   1. 在**角色详细信息**下的**角色名称**中输入 **lambda-sns-role**。

   1. 选择**创建角色**。

## 创建 Lambda 函数（账户 B）
<a name="with-sns-example-create-test-function"></a>

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/services-sns-tutorial/sns_tut_steps_3.png)


创建一个处理您的 Amazon SNS 消息的 Lambda 函数。函数代码会将每条记录的消息内容记录到 Amazon CloudWatch Logs 中。

本教程使用 Node.js 24 运行时系统，但我们还提供了其他运行时系统语言的示例代码。您可以选择以下框中的选项卡，查看适用于您感兴趣的运行时系统的代码。您将在此步骤中使用的 JavaScript 代码是 **JavaScript** 选项卡中显示的第一个示例。

------
#### [ .NET ]

**适用于 .NET 的 SDK**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/integration-sns-to-lambda)存储库中查找完整示例，并了解如何进行设置和运行。
使用 .NET 将 SNS 事件与 Lambda 结合使用。  

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
using Amazon.Lambda.Core;
using Amazon.Lambda.SNSEvents;


// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace SnsIntegration;

public class Function
{
    public async Task FunctionHandler(SNSEvent evnt, ILambdaContext context)
    {
        foreach (var record in evnt.Records)
        {
            await ProcessRecordAsync(record, context);
        }
        context.Logger.LogInformation("done");
    }

    private async Task ProcessRecordAsync(SNSEvent.SNSRecord record, ILambdaContext context)
    {
        try
        {
            context.Logger.LogInformation($"Processed record {record.Sns.Message}");

            // TODO: Do interesting work based on the new message
            await Task.CompletedTask;
        }
        catch (Exception e)
        {
            //You can use Dead Letter Queue to handle failures. By configuring a Lambda DLQ.
            context.Logger.LogError($"An error occurred");
            throw;
        }
    }
}
```

------
#### [ Go ]

**适用于 Go 的 SDK V2**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/integration-sns-to-lambda)存储库中查找完整示例，并了解如何进行设置和运行。
使用 Go 将 SNS 事件与 Lambda 结合使用。  

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package main

import (
	"context"
	"fmt"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

func handler(ctx context.Context, snsEvent events.SNSEvent) {
	for _, record := range snsEvent.Records {
		processMessage(record)
	}
	fmt.Println("done")
}

func processMessage(record events.SNSEventRecord) {
	message := record.SNS.Message
	fmt.Printf("Processed message: %s\n", message)
	// TODO: Process your record here
}

func main() {
	lambda.Start(handler)
}
```

------
#### [ Java ]

**适用于 Java 的 SDK 2.x**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/integration-sns-to-lambda)存储库中查找完整示例，并了解如何进行设置和运行。
通过 Java 将 SNS 事件与 Lambda 结合使用。  

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package example;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.SNSEvent;
import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord;


import java.util.Iterator;
import java.util.List;

public class SNSEventHandler implements RequestHandler<SNSEvent, Boolean> {
    LambdaLogger logger;

    @Override
    public Boolean handleRequest(SNSEvent event, Context context) {
        logger = context.getLogger();
        List<SNSRecord> records = event.getRecords();
        if (!records.isEmpty()) {
            Iterator<SNSRecord> recordsIter = records.iterator();
            while (recordsIter.hasNext()) {
                processRecord(recordsIter.next());
            }
        }
        return Boolean.TRUE;
    }

    public void processRecord(SNSRecord record) {
        try {
            String message = record.getSNS().getMessage();
            logger.log("message: " + message);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
```

------
#### [ JavaScript ]

**SDK for JavaScript（v3）**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/blob/main/integration-sns-to-lambda)存储库中查找完整示例，并了解如何进行设置和运行。
使用 JavaScript 将 SNS 事件与 Lambda 结合使用。  

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
exports.handler = async (event, context) => {
  for (const record of event.Records) {
    await processMessageAsync(record);
  }
  console.info("done");
};

async function processMessageAsync(record) {
  try {
    const message = JSON.stringify(record.Sns.Message);
    console.log(`Processed message ${message}`);
    await Promise.resolve(1); //Placeholder for actual async work
  } catch (err) {
    console.error("An error occurred");
    throw err;
  }
}
```
使用 TypeScript 将 SNS 事件与 Lambda 结合使用。  

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { SNSEvent, Context, SNSHandler, SNSEventRecord } from "aws-lambda";

export const functionHandler: SNSHandler = async (
  event: SNSEvent,
  context: Context
): Promise<void> => {
  for (const record of event.Records) {
    await processMessageAsync(record);
  }
  console.info("done");
};

async function processMessageAsync(record: SNSEventRecord): Promise<any> {
  try {
    const message: string = JSON.stringify(record.Sns.Message);
    console.log(`Processed message ${message}`);
    await Promise.resolve(1); //Placeholder for actual async work
  } catch (err) {
    console.error("An error occurred");
    throw err;
  }
}
```

------
#### [ PHP ]

**适用于 PHP 的 SDK**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/integration-sns-to-lambda)存储库中查找完整示例，并了解如何进行设置和运行。
通过 PHP 将 SNS 事件与 Lambda 结合使用。  

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
<?php

/* 
Since native PHP support for AWS Lambda is not available, we are utilizing Bref's PHP functions runtime for AWS Lambda.
For more information on Bref's PHP runtime for Lambda, refer to: https://bref.sh/docs/runtimes/function

Another approach would be to create a custom runtime. 
A practical example can be found here: https://aws.amazon.com/blogs/apn/aws-lambda-custom-runtime-for-php-a-practical-example/
*/

// Additional composer packages may be required when using Bref or any other PHP functions runtime.
// require __DIR__ . '/vendor/autoload.php';

use Bref\Context\Context;
use Bref\Event\Sns\SnsEvent;
use Bref\Event\Sns\SnsHandler;

class Handler extends SnsHandler
{
    public function handleSns(SnsEvent $event, Context $context): void
    {
        foreach ($event->getRecords() as $record) {
            $message = $record->getMessage();

            // TODO: Implement your custom processing logic here
            // Any exception thrown will be logged and the invocation will be marked as failed

            echo "Processed Message: $message" . PHP_EOL;
        }
    }
}

return new Handler();
```

------
#### [ Python ]

**适用于 Python 的 SDK（Boto3）**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/integration-sns-to-lambda)存储库中查找完整示例，并了解如何进行设置和运行。
使用 Python 将 SNS 事件与 Lambda 结合使用。  

```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
def lambda_handler(event, context):
    for record in event['Records']:
        process_message(record)
    print("done")

def process_message(record):
    try:
        message = record['Sns']['Message']
        print(f"Processed message {message}")
        # TODO; Process your record here
        
    except Exception as e:
        print("An error occurred")
        raise e
```

------
#### [ Ruby ]

**适用于 Ruby 的 SDK**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/integration-sns-to-lambda)存储库中查找完整示例，并了解如何进行设置和运行。
通过 Ruby 将 SNS 事件与 Lambda 结合使用。  

```
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
def lambda_handler(event:, context:)
  event['Records'].map { |record| process_message(record) }
end

def process_message(record)
  message = record['Sns']['Message']
  puts("Processing message: #{message}")
rescue StandardError => e
  puts("Error processing message: #{e}")
  raise
end
```

------
#### [ Rust ]

**适用于 Rust 的 SDK**  
 查看 GitHub，了解更多信息。在[无服务器示例](https://github.com/aws-samples/serverless-snippets/tree/main/integration-sns-to-lambda)存储库中查找完整示例，并了解如何进行设置和运行。
通过 Rust 将 SNS 事件与 Lambda 结合使用。  

```
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use aws_lambda_events::event::sns::SnsEvent;
use aws_lambda_events::sns::SnsRecord;
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use tracing::info;

// Built with the following dependencies:
//  aws_lambda_events = { version = "0.10.0", default-features = false, features = ["sns"] }
//  lambda_runtime = "0.8.1"
//  tokio = { version = "1", features = ["macros"] }
//  tracing = { version = "0.1", features = ["log"] }
//  tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }

async fn function_handler(event: LambdaEvent<SnsEvent>) -> Result<(), Error> {
    for event in event.payload.records {
        process_record(&event)?;
    }
    
    Ok(())
}

fn process_record(record: &SnsRecord) -> Result<(), Error> {
    info!("Processing SNS Message: {}", record.sns.message);

    // Implement your record handling code here.

    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .with_target(false)
        .without_time()
        .init();

    run(service_fn(function_handler)).await
}
```

------

**创建函数**

1. 为项目创建一个目录，然后切换到该目录。

   ```
   mkdir sns-tutorial
   cd sns-tutorial
   ```

1. 将示例 JavaScript 代码复制到名为 `index.js` 的新文件中。

1. 使用以下 `zip` 命令创建部署包。

   ```
   zip function.zip index.js
   ```

1. 运行以下 AWS CLI 命令，在**账户 B** 中创建 Lambda 函数。

   ```
   aws lambda create-function --function-name Function-With-SNS \
       --zip-file fileb://function.zip --handler index.handler --runtime nodejs24.x \
       --role arn:aws:iam::<AccountB_ID>:role/lambda-sns-role  \
       --timeout 60 --profile accountB
   ```

   您应该可以看到类似于如下所示的输出内容。

   ```
   {
       "FunctionName": "Function-With-SNS",
       "FunctionArn": "arn:aws:lambda:us-west-2:123456789012:function:Function-With-SNS",
       "Runtime": "nodejs24.x",
       "Role": "arn:aws:iam::123456789012:role/lambda_basic_role",
       "Handler": "index.handler",
       ...
       "RuntimeVersionConfig": {
           "RuntimeVersionArn": "arn:aws:lambda:us-west-2::runtime:7d5f06b69c951da8a48b926ce280a9daf2e8bb1a74fc4a2672580c787d608206"
       }
   }
   ```

1. 记下函数的 Amazon 资源名称（ARN）。稍后在本教程中添加权限以允许 Amazon SNS 调用函数时，将需要使用此 ARN。

## 向函数添加权限（账户 B）
<a name="with-sns-create-function-permissions"></a>

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/services-sns-tutorial/sns_tut_steps_4.png)


要让 Amazon SNS 调用函数，您需要在[基于资源的策略](access-control-resource-based.md)语句中向其授予权限。您可以使用 AWS CLI `add-permission` 命令添加此语句。

**授予 Amazon SNS 调用函数的权限**
+ 在**账户 B** 中，使用之前记下的 Amazon SNS 主题的 ARN 运行以下 AWS CLI 命令。

  ```
  aws lambda add-permission --function-name Function-With-SNS \
      --source-arn arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda \
      --statement-id function-with-sns --action "lambda:InvokeFunction" \
      --principal sns.amazonaws.com --profile accountB
  ```

  您应该可以看到类似于如下所示的输出内容。

  ```
  {
      "Statement": "{\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":
        \"arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda\"}},
        \"Action\":[\"lambda:InvokeFunction\"],
        \"Resource\":\"arn:aws:lambda:us-east-1:<AccountB_ID>:function:Function-With-SNS\",
        \"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"},
        \"Sid\":\"function-with-sns\"}"
  }
  ```

**注意**  
如果在[选择加入型 AWS 区域](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-regions.html) 中托管具有 Amazon SNS 主题的账户，则需要在主体中指定区域。例如，假设您正在亚太地区（香港）区域使用一个 Amazon SNS 主题，则需要为主体指定 `sns.ap-east-1.amazonaws.com`，而不是 `sns.amazonaws.com`。

## 授予 Amazon SNS 订阅的跨账户权限（账户 A）
<a name="with-sns-subscription-grant-permission"></a>

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/services-sns-tutorial/sns_tut_steps_5.png)


要让**账户 B** 中的 Lambda 函数订阅您在**账户 A** 中创建的 Amazon SNS 主题，您需要向**账户 B** 授予订阅主题的权限。您可以使用 AWS CLI `add-permission` 命令授予此权限。

**授予账户 B 订阅主题的权限**
+ 在**账户 A** 中，运行以下 AWS CLI 命令。使用之前记下的 Amazon SNS 主题的 ARN。

  ```
  aws sns add-permission --label lambda-access --aws-account-id <AccountB_ID> \
      --topic-arn arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda \  
      --action-name Subscribe ListSubscriptionsByTopic --profile accountA
  ```

## 创建订阅（账户 B）
<a name="with-sns-create-subscription"></a>

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/services-sns-tutorial/sns_tut_steps_6.png)


在**账户 B** 中，您现在将 Lambda 函数订阅到教程开始时您在**账户 A** 中创建的 Amazon SNS 主题。当消息发送到该主题 (`sns-topic-for-lambda`) 后，Amazon SNS 会调用**账户 B** 中的 Lambda 函数 `Function-With-SNS`。

**创建订阅**
+ 在**账户 B** 中，运行以下 AWS CLI 命令。使用您在其中创建主题的默认区域以及主题和 Lambda 函数的 ARN。

  ```
  aws sns subscribe --protocol lambda \
      --region us-east-1 \
      --topic-arn arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda \
      --notification-endpoint arn:aws:lambda:us-east-1:<AccountB_ID>:function:Function-With-SNS \
      --profile accountB
  ```

  您应该可以看到类似于如下所示的输出内容。

  ```
  {
      "SubscriptionArn": "arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda:5d906xxxx-7c8x-45dx-a9dx-0484e31c98xx"
  }
  ```

## 将消息发布到主题（账户 A 和账户 B）
<a name="with-sns-publish-message"></a>

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/services-sns-tutorial/sns_tut_steps_7.png)


**账户 B** 中的 Lambda 函数现已订阅**账户 A** 中的 Amazon SNS 主题，现在可以通过将消息发布到主题来测试设置了。要确认 Amazon SNS 是否已调用 Lambda 函数，可以使用 CloudWatch Logs 查看函数的输出。

**将消息发布到主题并查看函数的输出**

1. 在文本文件中输入 `Hello World`，并将其另存为 `message.txt`。

1. 在保存文本文件的同一目录中，在**账户 A** 中运行以下 AWS CLI 命令。将 ARN 用于您自己的主题。

   ```
   aws sns publish --message file://message.txt --subject Test \
       --topic-arn arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda \
       --profile accountA
   ```

   这将返回一个具有唯一标识符的消息 ID，表明 Amazon SNS 服务已接受该消息。然后，Amazon SNS 则尝试将消息传输给主题订阅用户。要确认 Amazon SNS 是否已调用 Lambda 函数，可以使用 CloudWatch Logs 查看函数的输出：

1. 在**账户 B** 中，打开 Amazon CloudWatch 控制台的[日志组](https://console.aws.amazon.com/cloudwatch/home#logsV2:log-groups)页面。

1. 选择函数 (`/aws/lambda/Function-With-SNS`) 的日志组。

1. 选择最新的日志流。

1. 如果函数已正确调用，您将看到类似于以下内容的输出，其中会显示发布到主题的消息内容。

   ```
   2023-07-31T21:42:51.250Z c1cba6b8-ade9-4380-aa32-d1a225da0e48 INFO Processed message Hello World
   2023-07-31T21:42:51.250Z c1cba6b8-ade9-4380-aa32-d1a225da0e48 INFO done
   ```

## 清除资源
<a name="cleanup"></a>

除非您想要保留为本教程创建的资源，否则可立即将其删除。通过删除您不再使用的 AWS 资源，可防止您的 AWS 账户 产生不必要的费用。

在**账户 A** 中，清理您的 Amazon SNS 主题。

**删除 Amazon SNS 主题**

1. 打开 Amazon SNS 控制台中的 [Topics（主题）页面](https://console.aws.amazon.com//sns/home#topics:)。

1. 选择您已创建的主题。

1. 选择**删除**。

1. 在文本输入字段中输入 **delete me**。

1. 选择**删除**。

在**账户 B** 中，清理您的执行角色、Lambda 函数和 Amazon SNS 订阅。

**删除执行角色**

1. 打开 IAM 控制台的[角色页面](https://console.aws.amazon.com/iam/home#/roles)。

1. 选择您创建的执行角色。

1. 选择**删除**。

1. 在文本输入字段中输入角色名称，然后选择**删除**。

**删除 Lambda 函数**

1. 打开 Lamba 控制台的 [Functions（函数）页面](https://console.aws.amazon.com/lambda/home#/functions)。

1. 选择您创建的函数。

1. 依次选择**操作**和**删除**。

1. 在文本输入字段中键入 **confirm**，然后选择 **Delete**（删除）。

**删除 Amazon SNS 订阅**

1. 在 Amazon SNS 控制台中打开 [Subscription（订阅）页面](https://console.aws.amazon.com//sns/home#subscriptions:)。

1. 选择您已创建的订阅。

1. 选择 **Delete**（删除），**Delete**（删除）。