

# 开始使用示例应用程序和模式
<a name="example-apps"></a>

以下资源可用于快速创建和部署实现一些常见 Lambda 使用案例的无服务器应用。对于每个示例应用程序，都提供了有关使用 AWS 管理控制台 手动创建和配置资源或借助 IaC 使用 AWS Serverless Application Model 部署资源的说明。按照控制台说明，了解有关为每个应用程序配置单个 AWS 资源的更多信息，或使用 AWS SAM 说明快速部署资源（类似于生产环境中的操作）。

## 文件处理
<a name="examples-apps-file"></a>
+ **[PDF 加密应用程序](file-processing-app.md)**：创建一个当 PDF 文件上传到 Amazon Simple Storage Service 存储桶时对其进行加密并将其保存到另一个存储桶的无服务器应用程序，这对于在上传时保护敏感文档的安全很有用。
+ **[图像分析应用程序](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-example-s3.html)**：创建一个使用 Amazon Rekognition 从图像中提取文本的无服务器应用程序，这对于文档处理、内容审核和自动图像分析很有用。

## 数据库集成
<a name="examples-apps-database"></a>
+ **[队列到数据库应用程序](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-lambda-tutorial.html)**：创建一个将队列消息写入 Amazon RDS 数据库的无服务器应用程序，这对于处理用户注册和处理订单提交很有用。
+ **[数据库事件处理程序](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-example-ddb.html)**：创建一个响应 Amazon DynamoDB 表更改的无服务器应用程序，这对于审计日志记录、数据复制和自动化工作流很有用。

## 计划任务
<a name="examples-apps-scheduled"></a>
+ **[数据库维护应用程序](scheduled-task-app.md)**：创建一个无服务器应用程序，其使用 cron 计划自动从 Amazon DynamoDB 表中删除超过 12 个月的条目，这对于自动化数据库维护和数据生命周期管理很有用。
+ **[为 Lambda 函数创建 EventBridge 计划规则](https://docs.aws.amazon.com/eventbridge/latest/userguide/run-lambda-schedule.html)**：使用 EventBridge 中规则的计划表达式按定时计划触发 Lambda 函数。此格式使用 cron 语法，可以设置为一分钟的粒度。

## 长时间运行的工作流
<a name="examples-apps-workflows"></a>
+ **[订单处理应用程序](order-processing-app.md)**：使用持久性函数创建一个无服务器应用程序，该应用程序能够处理复杂的订单履行流程，包括支付处理、库存核查以及发货协调等。此示例演示如何构建能够长时间运行且能保持状态的工作流。

## 其他资源
<a name="examples-apps-additional-resources"></a>

使用下面的资源进一步探索 Lambda 和无服务器应用程序开发：
+ **[Serverless Land](https://serverlessland.com/)**：用于构建无服务器应用的即用型模式库。它可帮助开发人员使用 Lambda、API Gateway 和 EventBridge 等 AWS 服务更快地创建应用程序。该网站提供预先构建的解决方案和最佳实践，使开发无服务器系统变得更加容易。
+ **[Lambda 示例应用程序](https://docs.aws.amazon.com/lambda/latest/dg/lambda-samples.html)**：本指南 GitHub 存储库中提供的应用程序。这些示例演示如何使用各种语言和 AWS 服务。每个示例应用程序都包含用于轻松部署和清理的脚本以及支持资源。
+ **[使用 AWS SDK 的 Lambda 代码示例](https://docs.aws.amazon.com/lambda/latest/dg/service_code_examples.html)**：演示如何将 Lambda 与 AWS 软件开发工具包 (SDK) 结合使用的示例。这些示例包括基础知识、操作、场景和 AWS 社区贡献。示例涵盖基本操作、各项服务功能以及使用多项功能或 AWS 服务的特定任务。

# 创建无服务器文件处理应用程序
<a name="file-processing-app"></a>

Lambda 的一个常见用例是执行文件处理任务。例如，使用 Lambda 函数从 HTML 文件或图像自动创建 PDF 文件，或者在用户上传图像时创建缩略图。

在本示例中，当 PDF 文件上传至 Amazon Simple Storage Service（Amazon S3）存储桶时，创建好的应用程序会自动加密 PDF 文件。要实现此应用程序，您要创建以下资源：
+ S3 存储桶，供用户上传 PDF 文件
+ Python 中的 Lambda 函数，用于读取上传的文件并创建加密的、受密码保护的文件版本
+ 第二个 S3 存储桶，供 Lambda 保存加密文件

您还可以创建一个 AWS Identity and Access Management（IAM）策略，来授予 Lambda 函数对 S3 存储桶执行读写操作的权限。

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/ExampleApps/file_process_resources.png)


**提示**  
如果您是 Lambda 全新用户，建议先从教程[创建第一个 Lambda 函数](getting-started.md)开始，再创建此示例应用程序。

您可以使用 AWS 管理控制台 或 AWS Command Line Interface（AWS CLI）创建并配置资源来手动部署应用程序。您也可以使用 AWS Serverless Application Model（AWS SAM）来部署应用程序。AWS SAM 是基础设施即代码（IaC）工具。若借助 IaC，则不必手动创建资源，只需在代码中定义资源，就能自动部署这些资源。

若想在部署此示例应用程序之前，了解有关将 Lambda 与 IaC 结合使用的更多信息，请参阅[将 Lambda 与基础设施即代码（IaC）结合使用](foundation-iac.md)。

## 创建 Lambda 函数源代码文件
<a name="file-processing-app-download"></a>

在您的项目目录中创建以下文件：
+ `lambda_function.py` – 执行文件加密的 Lambda 函数的 Python 函数代码
+ `requirements.txt` – 定义 Python 函数代码所需的依赖项的清单文件

展开以下各部分，查看代码，继而详细了解每个文件所起的作用。要在本地计算机上创建文件，请复制并粘贴以下代码，或从 [aws-lambda-developer-guide GitHub 存储库](https://github.com/awsdocs/aws-lambda-developer-guide/tree/main/sample-apps/file-processing-python)下载文件。

### Python 函数代码
<a name="file-processing-app-function-code"></a>

将以下代码复制并粘贴到名为 `lambda_function.py` 的文件。

```
from pypdf import PdfReader, PdfWriter
import uuid
import os
from urllib.parse import unquote_plus
import boto3

# Create the S3 client to download and upload objects from S3
s3_client = boto3.client('s3')

def lambda_handler(event, context):
    # Iterate over the S3 event object and get the key for all uploaded files
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key = unquote_plus(record['s3']['object']['key']) # Decode the S3 object key to remove any URL-encoded characters
        download_path = f'/tmp/{uuid.uuid4()}.pdf' # Create a path in the Lambda tmp directory to save the file to 
        upload_path = f'/tmp/converted-{uuid.uuid4()}.pdf' # Create another path to save the encrypted file to
        
        # If the file is a PDF, encrypt it and upload it to the destination S3 bucket
        if key.lower().endswith('.pdf'):
            s3_client.download_file(bucket, key, download_path)
            encrypt_pdf(download_path, upload_path)
            encrypted_key = add_encrypted_suffix(key)
            s3_client.upload_file(upload_path, f'{bucket}-encrypted', encrypted_key)

# Define the function to encrypt the PDF file with a password
def encrypt_pdf(file_path, encrypted_file_path):
    reader = PdfReader(file_path)
    writer = PdfWriter()
    
    for page in reader.pages:
        writer.add_page(page)

    # Add a password to the new PDF
    writer.encrypt("my-secret-password")

    # Save the new PDF to a file
    with open(encrypted_file_path, "wb") as file:
        writer.write(file)

# Define a function to add a suffix to the original filename after encryption
def add_encrypted_suffix(original_key):
    filename, extension = original_key.rsplit('.', 1)
    return f'{filename}_encrypted.{extension}'
```

**注意**  
在此示例代码中，加密文件 (`my-secret-password`) 的密码被硬编码到函数代码中。在生产应用程序中，切勿在函数代码中包含密码等敏感信息。相反，[创建一个 AWS Secrets Manager 密钥](https://docs.aws.amazon.com/secretsmanager/latest/userguide/create_secret.html)，然后[使用 AWS 参数和密钥 Lambda 扩展](with-secrets-manager.md)在 Lambda 函数中检索您的凭证。

Python 函数代码包含三个函数：一个是 Lambda 在调用函数时运行的[处理程序函数](python-handler.md)，以及两个名为 `add_encrypted_suffix` 和 `encrypt_pdf` 的独立函数，处理程序调用它们来执行 PDF 加密。

在 Amazon S3 调用函数时，Lambda 会将一个 JSON 格式的*事件*参数传递给函数，该参数包含有关导致调用的事件详细信息。在本例中，此类信息包括 S3 存储桶的名称和上传文件的对象键。要了解有关 Amazon S3 事件对象格式的更多信息，请参阅[使用 Lambda 处理 Amazon S3 事件通知](with-s3.md)。

然后，函数使用 适用于 Python (Boto3) 的 AWS SDK 将事件对象中指定的 PDF 文件下载到其本地临时存储目录，再使用 [https://pypi.org/project/pypdf/](https://pypi.org/project/pypdf/) 库对文件进行加密。

最后，函数使用 Boto3 SDK 将加密文件存储在 S3 目标存储桶中。

### `requirements.txt` 清单文件
<a name="file-processing-app-dependencies"></a>

将以下代码复制并粘贴到名为 `requirements.txt` 的文件。

```
boto3
pypdf
```

在本示例中，函数代码只有两个不属于标准 Python 库的依赖项：一是适用于 Python 的 SDK（Boto3），二是函数用来执行 PDF 加密的 `pypdf` 程序包。

**注意**  
适用于 Python 的 SDK（Boto3）的一个版本作为 Lambda 运行时的一部分包含在内，因此无需将 Boto3 添加到函数的部署包中，代码即可运行。不过，为了完全控制函数依赖项并避免可能出现的版本不一致问题，Python 的最佳实践是将所有函数依赖项包含在函数的部署包中。请参阅 [Python 中的运行时系统依赖项](python-package.md#python-package-dependencies)，了解更多信息。

## 部署应用程序
<a name="file-processing-app-deploy"></a>

您可以手动或使用 AWS SAM 自动创建并部署此示例应用程序的资源。在生产环境中，建议使用 AWS SAM 之类的 IaC 工具来快速、可重复地部署整个无服务器应用程序，无需采用手动流程。

### 手动部署资源
<a name="file-processing-app-deploy-manual"></a>

手动部署应用程序：
+ 创建源和目标 Amazon S3 存储桶
+ 创建一个 Lambda 函数，用于加密 PDF 文件并将加密版文件保存到 S3 存储桶
+ 配置一个 Lambda 触发器，该触发器将在对象上传到源存储桶时调用函数

在开始之前，请确保在生成计算机上安装 [Python](https://www.python.org/downloads/)。

#### 创建两个 S3 存储桶
<a name="file-processing-app-deploy-manual-create-buckets"></a>

先创建两个 S3 存储桶。第一个是源存储桶，供您向其上传 PDF 文件。第二个是目标存储桶，供 Lambda 保存调用函数时加密的文件。

------
#### [ Console ]

**创建 S3 存储桶（控制台）**

1. 打开 Amazon S3 控制台的[通用存储桶s](https://console.aws.amazon.com/s3/buckets)页面。

1. 选择最接近您地理位置的 AWS 区域。您可以使用屏幕顶部的下拉列表更改区域。  
![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/console_region_select.png)

1. 选择 **Create bucket**（创建存储桶）。

1. 在 **General configuration**（常规配置）下，执行以下操作：

   1. 对于**存储桶类型**，确保选中**通用型**。

   1. 对于**存储桶名称**，输入符合 Amazon S3 [存储桶命名规则](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)的全局唯一名称。存储桶名称只能由小写字母、数字、句点（.）和连字符（-）组成。

1. 将所有其他选项设置为默认值并选择**创建存储桶**。

1. 重复步骤 1 到 4 以创建自己的目标存储桶。在**存储桶名称**中输入 `amzn-s3-demo-bucket-encrypted`，其中 `amzn-s3-demo-bucket` 是您刚刚创建的源存储桶的名称。

------
#### [ AWS CLI ]

在开始之前，请确保在生成计算机上[安装 AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)。

**创建 Amazon S3 存储桶（AWS CLI）**

1. 运行以下 CLI 命令来创建自己的源存储桶。您为存储桶选择的名称必须具有全局唯一性，并遵守 Amazon S3 [存储桶命名规则](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)。名称只能由小写字母、数字、句点（.）和连字符（-）组成。对于 `region` 和 `LocationConstraint`，请选择最接近您地理位置的 [AWS 区域](https://docs.aws.amazon.com/general/latest/gr/lambda-service.html)。

   ```
   aws s3api create-bucket --bucket amzn-s3-demo-bucket --region us-east-2 \
   --create-bucket-configuration LocationConstraint=us-east-2
   ```

   在本教程的后面部分，您必须在与源存储桶相同的 AWS 区域 中创建 Lambda 函数，因此请记下您选择的区域。

1. 运行以下命令来创建自己的目标存储桶。对于存储桶名称，必须使用 `amzn-s3-demo-bucket-encrypted`，其中 `amzn-s3-demo-bucket` 是您在步骤 1 中创建的源存储桶名称。对于 `region` 和 `LocationConstraint`，请选择与用于创建源存储桶时相同的 AWS 区域。

   ```
   aws s3api create-bucket --bucket amzn-s3-demo-bucket-encrypted --region us-east-2 \
   --create-bucket-configuration LocationConstraint=us-east-2
   ```

------

#### 创建执行角色
<a name="file-processing-app-deploy-manual-create-execution-role"></a>

执行角色是一个 IAM 角色，用于向 Lambda 函数授予访问 AWS 服务 和资源的权限。要授予函数对 Amazon S3 的读取和写入权限，必须附加 [AWS 托管式策略](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#aws-managed-policies) `AmazonS3FullAccess`。

------
#### [ Console ]

**创建执行角色并附加 `AmazonS3FullAccess` 托管策略（控制台）**

1. 在 IAM 控制台中，打开 [Roles](https://console.aws.amazon.com/iam/home/roles)（角色）页面。

1. 请选择 **Create role**（创建角色）。

1. 对于**可信实体类型**，选择 **AWS 服务**；对于**应用场景**，选择 **Lambda**。

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

1. 通过执行以下操作添加 `AmazonS3FullAccess` 托管策略：

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

   1. 选中该策略旁的复选框。

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

1. 在**角色详细信息**中，对**角色名称**输入 **LambdaS3Role**。

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

------
#### [ AWS CLI ]

**创建执行角色并附加 `AmazonS3FullAccess` 托管式策略（AWS CLI）**

1. 将下列 JSON 保存在名为 `trust-policy.json` 的文件中。此信任策略允许 Lambda 通过向服务主体 `lambda.amazonaws.com` 授予调用 AWS Security Token Service（AWS STS）`AssumeRole` 操作的权限来使用该角色的权限。  
****  

   ```
   {
     "Version":"2012-10-17",		 	 	 
     "Statement": [
       {
         "Effect": "Allow",
         "Principal": {
           "Service": "lambda.amazonaws.com"
         },
         "Action": "sts:AssumeRole"
       }
     ]
   }
   ```

1. 在保存 JSON 信任策略文档的目录中，运行以下 CLI 命令来创建执行角色。

   ```
   aws iam create-role --role-name LambdaS3Role --assume-role-policy-document file://trust-policy.json
   ```

1. 要附加 `AmazonS3FullAccess` 托管式策略，请运行下列 CLI 命令。

   ```
   aws iam attach-role-policy --role-name LambdaS3Role --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
   ```

------

#### 创建函数部署包
<a name="file-processing-app-deploy-manual-create-function-package"></a>

要创建函数，您需要创建包含函数代码和所有依赖项的*部署包*。对于此应用程序，函数代码使用单独的库来加密 PDF。

**创建部署包**

1. 导航到包含之前创建的或从 GitHub 下载的 `lambda_function.py` 和 `requirements.txt` 文件的项目目录，然后创建一个名为 `package` 的新目录。

1. 运行以下命令，将 `requirements.txt` 文件中指定的依赖项安装到 `package` 目录中。

   ```
   pip install -r requirements.txt --target ./package/
   ```

1. 创建一个包含应用程序代码及其依赖项的 .zip 文件。在 Linux 或 MacOS 中，从命令行界面运行以下命令。

   ```
   cd package
   zip -r ../lambda_function.zip .
   cd ..
   zip lambda_function.zip lambda_function.py
   ```

    在 Windows 中，使用您首选的压缩工具来创建 `lambda_function.zip` 文件。确保您的 `lambda_function.py` 文件和包含依赖项的文件夹都位于.zip 文件的根目录下。

您也可以使用 Python 虚拟环境创建部署包。请参阅 [将 .zip 文件归档用于 Python Lambda 函数](python-package.md)。

#### 创建 Lambda 函数
<a name="file-processing-app-deploy-manual-createfunction"></a>

现在，您可以使用在上一步中创建的部署包来部署 Lambda 函数。

------
#### [ Console ]

**创建函数（控制台）**

要使用控制台创建 Lambda 函数，首先要创建包含一些“Hello world”代码的基本函数。然后，通过上传在上一步中创建的 .zip 文件，将此代码替换为自己的函数代码。

为确保加密大型 PDF 文件时函数不会超时，必须配置该函数的内存和超时设置。您还要将函数的日志格式设置为 JSON。使用提供的测试脚本时，必须配置 JSON 格式的日志，以便其可以从 CloudWatch Logs 中读取函数的调用状态，从而确认是否成功调用。

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

1. 确保您在创建 S3 存储桶所在的同一 AWS 区域 内操作。您可以使用屏幕顶部的下拉列表更改区域。  
![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/console_region_select.png)

1. 选择**创建函数**。

1. 选择**从头开始创作**。

1. 在**基本信息**中，执行以下操作：

   1. 对于 **Function name（函数名称）**，请输入 `EncryptPDF`。

   1. 对于**运行时**，选择 **Python 3.12**。

   1. 对于**架构**，选择 **x86\$164**。

1. 通过执行以下操作，附加您在上一步中创建的执行角色：

   1. 展开**更改默认执行角色**部分。

   1. 选择**使用现有角色**。

   1. 在**现有角色**下，选择您的角色（`LambdaS3Role`）。

1. 选择**创建函数**。

**上传函数代码（控制台）**

1. 在**代码源**窗格中，选择**上传自**。

1. 选择 **.zip 文件**。

1. 选择**上传**。

1. 在文件选择器中，选择 .zip 文件，然后选择**打开**。

1. 选择**保存**。

**配置函数内存和超时（控制台）**

1. 选择函数的**配置**选项卡。

1. 在**常规配置**窗格中，选择**编辑**。

1. 将**内存**设置为 256 MB，并将**超时**设置为 15 秒。

1. 选择**保存**。

**配置日志格式（控制台）**

1. 选择函数的**配置**选项卡。

1. 选择**监控和操作工具**。

1. 在**日志记录配置**窗格中，选择**编辑**。

1. 对于**日志记录配置**，选择 **JSON**。

1. 选择**保存**。

------
#### [ AWS CLI ]

**创建函数（AWS CLI）**
+ 从包含 `lambda_function.zip` 文件的目录中运行以下命令。对于 `region` 参数，将 `us-east-2` 替换为创建 S3 存储桶时使用的区域。

  ```
  aws lambda create-function --function-name EncryptPDF \
  --zip-file fileb://lambda_function.zip --handler lambda_function.lambda_handler \
  --runtime python3.12 --timeout 15 --memory-size 256 \
  --role arn:aws:iam::123456789012:role/LambdaS3Role --region us-east-2 \
  --logging-config LogFormat=JSON
  ```

------

#### 配置 Amazon S3 触发器来调用函数
<a name="file-processing-app-deploy-manual-configure-s3-trigger"></a>

为了在将文件上传到源存储桶时运行 Lambda 函数，您需要为函数配置触发器。您可以使用控制台或 AWS CLI 配置 Amazon S3 触发器。

**重要**  
此程序将 S3 存储桶配置为每次在该存储桶中创建对象时调用您的函数。请确保仅在源存储桶上配置。如果您的 Lambda 函数在调用此函数的同一个存储桶中创建对象，则可以[在循环中持续调用](https://serverlessland.com/content/service/lambda/guides/aws-lambda-operator-guide/recursive-runaway)您的函数。这可能会导致您的 AWS 账户 产生额外费用。

------
#### [ Console ]

**配置 Amazon S3 触发器（控制台）**

1. 打开 Lambda 控制台的[函数页面](https://console.aws.amazon.com/lambda/home#/functions)，然后选择函数 (`EncryptPDF`)。

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

1. 选择 **S3**。

1. 在**存储桶**下，选择自己的源存储桶。

1. 在**事件类型**下，选择**所有对象创建事件**。

1. 在**递归调用**下，选中复选框以确认知晓不建议使用相同的 S3 存储桶用于输入和输出。您可以阅读 Serverless Land 中的 [Recursive patterns that cause run-away Lambda functions](https://serverlessland.com/content/service/lambda/guides/aws-lambda-operator-guide/recursive-runaway)，进一步了解 Lambda 中的递归调用模式。

1. 选择**添加**。

   在您使用 Lambda 控制台创建触发器时，Lambda 会自动创建[基于资源的策略](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html)，授予您选择的服务调用函数的权限。

------
#### [ AWS CLI ]

**配置 Amazon S3 触发器（AWS CLI）**

1. 向函数中添加[基于资源的策略](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html)，以允许 Amazon S3 源存储桶在添加文件时调用函数。基于资源的策略声明授予其他 AWS 服务 调用您函数的权限。要授予 Amazon S3 调用函数的权限，请运行以下 CLI 命令。请务必将 `source-account` 参数替换为您的 AWS 账户 ID 并使用自己的源存储桶名称。

   ```
   aws lambda add-permission --function-name EncryptPDF \
   --principal s3.amazonaws.com --statement-id s3invoke --action "lambda:InvokeFunction" \
   --source-arn arn:aws:s3:::amzn-s3-demo-bucket \
   --source-account 123456789012
   ```

   您使用此命令定义的策略允许 Amazon S3 仅在源存储桶上执行操作时调用函数。
**注意**  
虽然 S3 存储桶名称具有全局唯一性，但在使用基于资源的策略时，最佳做法是指定存储桶必须属于您的账户。这是因为，如果删除一个存储桶，则另一个 AWS 账户 账户可能会创建具有相同 Amazon 资源名称（ARN）的存储桶。

1. 将下列 JSON 保存在名为 `notification.json` 的文件中。在应用到您的源存储桶时，此 JSON 会将存储桶配置为在每次添加新对象时向 Lambda 函数发送通知。将 Lambda 函数 ARN 中的 AWS 账户 号码和 AWS 区域 替换为您自己的账号和区域。

   ```
   {
   "LambdaFunctionConfigurations": [
       {
         "Id": "EncryptPDFEventConfiguration",
         "LambdaFunctionArn": "arn:aws:lambda:us-east-2:123456789012:function:EncryptPDF",
         "Events": [ "s3:ObjectCreated:Put" ]
       }
     ]
   }
   ```

1. 运行以下 CLI 命令，将您创建的 JSON 文件中的通知设置应用到源存储桶。将 `amzn-s3-demo-bucket` 替换为源存储桶的名称。

   ```
   aws s3api put-bucket-notification-configuration --bucket amzn-s3-demo-bucket \
   --notification-configuration file://notification.json
   ```

   要了解有关 `put-bucket-notification-configuration` 命令和 `notification-configuration` 选项的更多信息，请参阅 *AWS CLI 命令参考*中的 [put-bucket-notification-configuration](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-bucket-notification-configuration.html)。

------

### 使用 AWS SAM 来部署资源
<a name="file-processing-app-deploy-sam"></a>

在开始之前，请确保您的生成计算机上安装了 [Docker](https://docs.docker.com/get-docker/) 和[最新版本的 AWS SAMCLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)。

1. 在项目目录中，将以下代码复制并粘贴到名为 `template.yaml` 的文件中。替换占位符存储桶名称：
   + 对于源存储桶，请将 `amzn-s3-demo-bucket` 替换为符合 [S3 存储桶命名规则](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)的任意名称。
   + 对于目标存储桶，将 `amzn-s3-demo-bucket-encrypted` 替换为 `<source-bucket-name>-encrypted`，其中 `<source-bucket>` 是您为源存储桶选择的名称。

   ```
   AWSTemplateFormatVersion: '2010-09-09'
   Transform: AWS::Serverless-2016-10-31
   
   Resources:
     EncryptPDFFunction:
       Type: AWS::Serverless::Function
       Properties:
         FunctionName: EncryptPDF
         Architectures: [x86_64]
         CodeUri: ./
         Handler: lambda_function.lambda_handler
         Runtime: python3.12
         Timeout: 15
         MemorySize: 256
         LoggingConfig:
           LogFormat: JSON
         Policies:
           - AmazonS3FullAccess
         Events:
           S3Event:
             Type: S3
             Properties:
               Bucket: !Ref PDFSourceBucket
               Events: s3:ObjectCreated:*
   
     PDFSourceBucket:
       Type: AWS::S3::Bucket
       Properties:
         BucketName: amzn-s3-demo-bucket
   
     EncryptedPDFBucket:
       Type: AWS::S3::Bucket
       Properties:
         BucketName: amzn-s3-demo-bucket-encrypted
   ```

   AWS SAM 模板定义了您为应用程序创建的资源。在本示例中，模板使用 `AWS::Serverless::Function` 类型定义一个 Lambda 函数，并使用 `AWS::S3::Bucket` 类型定义两个 S3 存储桶。模板中指定的存储桶名称是占位符。在使用 AWS SAM 部署应用程序之前，您需要编辑模板，使用符合 [S3 存储桶命名规则](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)的全局唯一名称重命名存储桶。[使用 AWS SAM 来部署资源](#file-processing-app-deploy-sam) 中将进一步介绍该步骤。

   Lambda 函数资源的定义使用 `S3Event` 事件属性为函数配置触发器。只要在源存储桶中创建对象，该触发器就会调用函数。

   函数定义还指定了要附加到函数[执行角色](lambda-intro-execution-role.md)的 AWS Identity and Access Management（IAM）策略。[AWS 托管式策略](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#aws-managed-policies) `AmazonS3FullAccess` 授予函数在 Amazon S3 中读取和写入对象所需的权限。

1. 在保存 `template.yaml`、`lambda_function.py` 和 `requirements.txt` 文件的目录中，运行以下命令。

   ```
   sam build --use-container
   ```

   此命令会收集应用程序的构建构件，并将其以适当的格式放置在适当的位置进行部署。指定 `--use-container` 选项会在类似 Lambda 的 Docker 容器中构建函数。我们在这里使用它，您无需在本地计算机上安装 Python 3.12 即可进行构建。

   在构建过程中，AWS SAM 会在以模板中的 `CodeUri` 属性指定的位置中查找 Lambda 函数代码。在本例中，我们将当前目录指定为位置（`./`）。

   如果存在 `requirements.txt` 文件，则 AWS SAM 使用该文件来收集指定的依赖项。默认情况下，AWS SAM 会创建包含函数代码和依赖项的 .zip 部署包。您也可以选择使用 [PackageType](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-packagetype) 属性将函数部署为容器映像。

1. 要部署应用程序并创建 AWS SAM 模板中指定的 Lambda 和 Amazon S3 资源，请运行以下命令。

   ```
   sam deploy --guided
   ```

   使用 `--guided` 标志意味着 AWS SAM 将向您显示提示，以指导您完成部署过程。对于此部署，请按 Enter 接受默认选项。

在部署过程中，AWS SAM 将在 AWS 账户 中创建以下资源：
+ 一个名为 `sam-app` 的 CloudFormation [堆栈](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-whatis-concepts.html#cfn-concepts-stacks)
+ 一个名为 `EncryptPDF` 的 Lambda 函数
+ 两个 S3 存储桶，其名称为编辑 `template.yaml` AWS SAM 模板文件时选择的名称
+ 一个函数的 IAM 执行角色，名称格式为 `sam-app-EncryptPDFFunctionRole-2qGaapHFWOQ8`

AWS SAM 创建完资源后，您将看到以下消息：

```
Successfully created/updated stack - sam-app in us-east-2
```

## 测试应用程序
<a name="file-processing-app-test"></a>

要测试应用程序，请将 PDF 文件上传到源存储桶，然后确认 Lambda 已在目标存储桶中创建加密版文件。在本示例中，您可以使用控制台或 AWS CLI 对此进行手动测试，也可以使用提供的测试脚本进行自动测试。

对于生产应用程序，可以使用单元测试等传统的测试方法和技术，来确认 Lambda 函数代码是否正常运行。最佳实践也是执行与所提供测试脚本中的测试类似的测试，这些测试使用真实的基于云的资源执行集成测试。在云端中执行集成测试可确认基础架构是否已正确部署，并确认事件是否按预期在不同的服务之间流动。要了解更多信息，请参阅[如何测试无服务器函数和应用程序](testing-guide.md)。

### 手动测试应用程序
<a name="file-processing-app-test-manual"></a>

您可以将 PDF 文件添加到 Amazon S3 源存储桶来手动测试函数。将文件添加到源存储桶时，应自动调用 Lambda 函数，并应在目标存储桶中存储加密版文件。

------
#### [ Console ]

**通过上传文件来测试应用程序（控制台）**

1. 要将 PDF 文件上传到 S3 存储桶，请执行以下操作：

   1. 打开 Amazon S3 控制台的[存储桶](https://console.aws.amazon.com/s3/buckets)页面，然后选择您的源存储桶。

   1. 选择**上传**。

   1. 选择**添加文件**，然后使用文件选择器选择要上传的 PDF 文件。

   1. 选择**打开**，然后选择**上传**。

1. 执行以下操作，验证 Lambda 是否已将加密版 PDF 文件保存在目标存储桶中：

   1. 导航回 Amazon S3 控制台的[存储桶](https://console.aws.amazon.com/s3/buckets)页面，然后选择您的目标存储桶。

   1. 在**对象**窗格中，现在应该可以到一个名称格式为 `filename_encrypted.pdf` 的文件（其中 `filename.pdf` 是已上传到源存储桶的文件的名称）。要下载加密的 PDF，请选择所需文件，然后选择**下载**。

   1. 确认是否可以使用 Lambda 函数保护的密码 (`my-secret-password`) 打开下载的文件。

------
#### [ AWS CLI ]

**通过上传文件来测试应用程序（AWS CLI）**

1. 在包含要上传的 PDF 文件的目录中，运行以下 CLI 命令。将 `--bucket` 参数替换为源存储桶的名称。对于 `--key` 和 `--body` 参数，请使用测试文件的文件名。

   ```
   aws s3api put-object --bucket amzn-s3-demo-bucket --key test.pdf --body ./test.pdf
   ```

1. 验证函数是否已创建加密版文件并已保存到目标 S3 存储桶中。运行以下 CLI 命令，将 `amzn-s3-demo-bucket-encrypted` 替换为自己的目标存储桶的名称。

   ```
   aws s3api list-objects-v2 --bucket amzn-s3-demo-bucket-encrypted
   ```

   如果函数成功运行，您将看到类似于以下内容的输出。目标存储桶应包含名称格式为 `<your_test_file>_encrypted.pdf` 的文件，其中 `<your_test_file>` 是已上传文件的名称。

   ```
   {
       "Contents": [
           {
               "Key": "test_encrypted.pdf",
               "LastModified": "2023-06-07T00:15:50+00:00",
               "ETag": "\"7781a43e765a8301713f533d70968a1e\"",
               "Size": 2763,
               "StorageClass": "STANDARD"
           }
       ]
   }
   ```

1. 要下载 Lambda 保存在目标存储桶中的文件，请运行以下 CLI 命令。将 `--bucket` 参数替换为目标存储桶的名称。对于 `--key` 参数，请使用文件名 `<your_test_file>_encrypted.pdf`，其中 `<your_test_file>` 是已上传的测试文件的名称。

   ```
   aws s3api get-object --bucket amzn-s3-demo-bucket-encrypted --key test_encrypted.pdf my_encrypted_file.pdf
   ```

   该命令会将文件下载到当前目录并将其另存为 `my_encrypted_file.pdf`。

1. 确认是否可以使用 Lambda 函数保护的密码 (`my-secret-password`) 打开下载的文件。

------

### 使用自动化脚本测试应用程序
<a name="file-processing-app-test-auto"></a>

在您的项目目录中创建以下文件：
+ `test_pdf_encrypt.py` – 可用于自动测试应用程序的测试脚本
+ `pytest.ini` – 测试脚本的配置文件

展开以下各部分，查看代码，继而详细了解每个文件所起的作用。

#### 自动化测试脚本
<a name="file-processing-app-test-script"></a>

将以下代码复制并粘贴到名为 `test_pdf_encrypt.py` 的文件。请务必替换占位符存储桶名称：
+ 在 `test_source_bucket_available` 函数中，将 `amzn-s3-demo-bucket` 替换为源存储桶的名称。
+ 在 `test_encrypted_file_in_bucket` 函数中，将 `amzn-s3-demo-bucket-encrypted` 替换为 `source-bucket-encrypted`，其中 `source-bucket>` 是源存储桶的名称。
+ 在 `cleanup` 函数中，将 `amzn-s3-demo-bucket` 替换为源存储桶的名称，并将 `amzn-s3-demo-bucket-encrypted` 替换为目标存储桶的名称。

```
import boto3
import json
import pytest
import time
import os

@pytest.fixture
def lambda_client():
    return boto3.client('lambda')
    
@pytest.fixture
def s3_client():
    return boto3.client('s3')

@pytest.fixture
def logs_client():
    return boto3.client('logs')

@pytest.fixture(scope='session')
def cleanup():
    # Create a new S3 client for cleanup
    s3_client = boto3.client('s3')

    yield
    # Cleanup code will be executed after all tests have finished

    # Delete test.pdf from the source bucket
    source_bucket = 'amzn-s3-demo-bucket'
    source_file_key = 'test.pdf'
    s3_client.delete_object(Bucket=source_bucket, Key=source_file_key)
    print(f"\nDeleted {source_file_key} from {source_bucket}")

    # Delete test_encrypted.pdf from the destination bucket
    destination_bucket = 'amzn-s3-demo-bucket-encrypted'
    destination_file_key = 'test_encrypted.pdf'
    s3_client.delete_object(Bucket=destination_bucket, Key=destination_file_key)
    print(f"Deleted {destination_file_key} from {destination_bucket}")
        

@pytest.mark.order(1)
def test_source_bucket_available(s3_client):
    s3_bucket_name = 'amzn-s3-demo-bucket'
    file_name = 'test.pdf'
    file_path = os.path.join(os.path.dirname(__file__), file_name)

    file_uploaded = False
    try:
        s3_client.upload_file(file_path, s3_bucket_name, file_name)
        file_uploaded = True
    except:
        print("Error: couldn't upload file")

    assert file_uploaded, "Could not upload file to S3 bucket"

    

@pytest.mark.order(2)
def test_lambda_invoked(logs_client):

    # Wait for a few seconds to make sure the logs are available
    time.sleep(5)

    # Get the latest log stream for the specified log group
    log_streams = logs_client.describe_log_streams(
        logGroupName='/aws/lambda/EncryptPDF',
        orderBy='LastEventTime',
        descending=True,
        limit=1
    )

    latest_log_stream_name = log_streams['logStreams'][0]['logStreamName']

    # Retrieve the log events from the latest log stream
    log_events = logs_client.get_log_events(
        logGroupName='/aws/lambda/EncryptPDF',
        logStreamName=latest_log_stream_name
    )

    success_found = False
    for event in log_events['events']:
        message = json.loads(event['message'])
        status = message.get('record', {}).get('status')
        if status == 'success':
            success_found = True
            break

    assert success_found, "Lambda function execution did not report 'success' status in logs."

@pytest.mark.order(3)
def test_encrypted_file_in_bucket(s3_client):
    # Specify the destination S3 bucket and the expected converted file key
    destination_bucket = 'amzn-s3-demo-bucket-encrypted'
    converted_file_key = 'test_encrypted.pdf'

    try:
        # Attempt to retrieve the metadata of the converted file from the destination S3 bucket
        s3_client.head_object(Bucket=destination_bucket, Key=converted_file_key)
    except s3_client.exceptions.ClientError as e:
        # If the file is not found, the test will fail
        pytest.fail(f"Converted file '{converted_file_key}' not found in the destination bucket: {str(e)}")

def test_cleanup(cleanup):
    # This test uses the cleanup fixture and will be executed last
    pass
```

自动化测试脚本将执行三个测试函数来确认应用程序运行是否正确：
+ 测试 `test_source_bucket_available` 通过将测试 PDF 文件上传到存储桶来确认源存储桶是否已成功创建。
+ 测试 `test_lambda_invoked` 通过询问函数的最新 CloudWatch Logs 日志流，来确认上传测试文件时，Lambda 函数是否已运行并报告成功。
+ 测试 `test_encrypted_file_in_bucket` 确认目标存储桶是否包含加密 `test_encrypted.pdf` 文件。

待这些测试运行完毕后，脚本会执行额外的清理步骤，以便从源存储桶和目标存储桶中删除 `test.pdf` 和 `test_encrypted.pdf` 文件。

与 AWS SAM 模板一样，此文件中指定的存储桶名称是占位符。在运行测试前，必须使用应用程序的真实存储桶名称编辑此文件。[使用自动化脚本测试应用程序](#file-processing-app-test-auto) 中将进一步介绍此步骤

#### 测试脚本配置文件
<a name="file-processing-app-test-config"></a>

将以下代码复制并粘贴到名为 `pytest.ini` 的文件。

```
[pytest]
markers =
    order: specify test execution order
```

这是为了指定 `test_pdf_encrypt.py` 脚本中测试运行的顺序。

要运行测试，请执行以下操作：

1. 确保已在本地环境中安装 `pytest` 模块。可以通过运行以下命令安装 `pytest`：

   ```
   pip install pytest
   ```

1. 将名为 `test.pdf` 的 PDF 文件保存在包含 `test_pdf_encrypt.py` 和 `pytest.ini` 文件的目录中。

1. 打开终端或 Shell 程序，从包含测试文件的目录中运行以下命令。

   ```
   pytest -s -v
   ```

   测试完成后，输出应与以下内容类似：

   ```
   ============================================================== test session starts =========================================================
   platform linux -- Python 3.12.2, pytest-7.2.2, pluggy-1.0.0 -- /usr/bin/python3
   cachedir: .pytest_cache
   hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/pdf_encrypt_app/.hypothesis/examples')
   Test order randomisation NOT enabled. Enable with --random-order or --random-order-bucket=<bucket_type>
   rootdir: /home/pdf_encrypt_app, configfile: pytest.ini
   plugins: anyio-3.7.1, hypothesis-6.70.0, localserver-0.7.1, random-order-1.1.0
   collected 4 items
   
   test_pdf_encrypt.py::test_source_bucket_available PASSED
   test_pdf_encrypt.py::test_lambda_invoked PASSED
   test_pdf_encrypt.py::test_encrypted_file_in_bucket PASSED
   test_pdf_encrypt.py::test_cleanup PASSED
   Deleted test.pdf from amzn-s3-demo-bucket
   Deleted test_encrypted.pdf from amzn-s3-demo-bucket-encrypted
   
   
   =============================================================== 4 passed in 7.32s ==========================================================
   ```

## 后续步骤
<a name="file-processing-app-next-steps"></a>

您已经创建了示例应用程序，现在可以在所提供代码的基础上创建其他类型的文件处理应用程序。修改 `lambda_function.py` 文件中的代码，为用例实现文件处理逻辑。

许多常见的文件处理用例都涉及图像处理。使用 Python 时，[pillow](https://pypi.org/project/pillow/) 等常用的图像处理库通常包含 C 或 C\$1\$1 组件。为了确保函数的部署包与 Lambda 执行环境兼容，请务必使用正确的源分发二进制文件。

使用 AWS SAM 来部署资源时，必须额外采取一些步骤，以便在部署包中包含正确的源分发。由于 AWS SAM 不会为与生成计算机不同的平台安装依赖项，因此，如果生成机器使用与 Lambda 执行环境不同的操作系统或架构，则在 `requirements.txt` 文件中指定正确的源分发（`.whl` 文件）将不适用。所以，应执行以下操作之一：
+ 运行 `sam build` 时使用 `--use-container` 选项。指定此选项时，AWS SAM 会下载与 Lambda 执行环境兼容的容器基础映像，并使用该映像在 Docker 容器中构建函数的部署包。要了解更多信息，请参阅 [Building a Lambda function inside of a provided container](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-build.html#using-sam-cli-build-options-container)。
+ 使用正确的源分发二进制文件自行构建函数的 .zip 部署包，并将 .zip 文件保存在 AWS SAM 模板中指定为 `CodeUri` 的目录中。要了解有关使用二进制分发文件为 Python 构建.zip 部署包的更多信息，请参阅[创建含依赖项的 .zip 部署包](python-package.md#python-package-create-dependencies)和[使用原生库创建 .zip 部署包](python-package.md#python-package-native-libraries)。

# 创建用于执行定期数据库维护的应用程序
<a name="scheduled-task-app"></a>

您可以使用 AWS Lambda 来替换自动系统备份、文件转换和维护任务等计划进程。在此示例中，您将创建无服务器应用程序，该应用程序通过删除旧条目对 DynamoDB 表执行定期维护。该应用程序按照 cron 计划，使用 EventBridge 调度器调用 Lambda 函数。调用时，该函数会在表中查询早于一年的项目，然后将其删除。该函数会将每个已删除的项目记录在 CloudWatch Logs 中。

要实现此示例，请先创建 DynamoDB 表，并在其中填充部分测试数据以供您进行函数查询。然后，创建具有 EventBridge 调度器触发器和 IAM 执行角色的 Python Lambda 函数，该执行角色可授予函数读取和删除表中项目的权限。

![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/ExampleApps/cron_app.png)


**提示**  
若您为 Lambda 全新用户，建议先阅读教程（[创建第一个 Lambda 函数](getting-started.md)），再创建此示例应用程序。

您可以使用 AWS 管理控制台 创建并配置资源来手动部署应用程序。您也可以使用 AWS Serverless Application Model（AWS SAM）来部署应用程序。AWS SAM 是基础设施即代码（IaC）工具。若借助 IaC，则不必手动创建资源，只需在代码中定义资源，就能自动部署这些资源。

若想在部署此示例应用程序之前，了解有关将 Lambda 与 IaC 结合使用的更多信息，请参阅[将 Lambda 与基础设施即代码（IaC）结合使用](foundation-iac.md)。

## 先决条件
<a name="scheduled-task-app-prereqs"></a>

在创建示例应用程序之前，确保已安装好所需的命令行工具和程序。
+ **Python**

  为了填充您为测试应用程序而创建的 DynamoDB 表，此示例使用 Python 脚本和 CSV 文件将数据写入表中。确保在计算机上已安装 Python 3.8 或更高版本。
+ **AWS SAM CLI**

  若要使用 AWS SAM 创建 DynamoDB 表和部署示例应用程序，则需要同时安装 AWS SAM CLI。请按照《AWS SAM User Guide》**中的 [installation instructions](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) 操作。
+ **AWS CLI**

  要使用提供的 Python 脚本填充测试表，您需要安装并配置 AWS CLI。由于脚本使用 适用于 Python (Boto3) 的 AWS SDK，它需要访问您的 AWS Identity and Access Management（IAM）证书。您还需要安装 AWS CLI 才能使用 AWS SAM 部署资源。要安装 CLI，请按照《AWS Command Line Interface User Guide》**中的 [installation instructions](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) 操作。
+ **Docker**

  要使用 AWS SAM 部署应用程序，还必须在生成计算机上安装 Docker。按照 Docker 文档网站上的 [Install Docker Engine](https://docs.docker.com/engine/install/) 中的说明操作。

## 下载示例应用程序文件
<a name="scheduled-task-app-download"></a>

要创建示例数据库和用于定期维护的应用程序，请在项目目录中创建以下文件：

**数据库文件示例**
+ `template.yaml`：可用于创建 DynamoDB 表的 AWS SAM 模板
+ `sample_data.csv`：包含要加载到表中的示例数据的 CSV 文件
+ `load_sample_data.py`：用于将 CSV 文件中的数据写入表中的 Python 脚本

**用于定期维护的应用程序文件**
+ `lambda_function.py`：执行数据库维护的 Lambda 函数的 Python 函数代码
+ `requirements.txt` – 定义 Python 函数代码所需的依赖项的清单文件
+ `template.yaml` – 可用于部署应用程序的 AWS SAM 模板

**测试文件**
+ `test_app.py`：Python 脚本，用于扫描表并通过输出所有超过一年的记录来确认函数是否成功运行

展开以下各部分，查看代码，继而详细了解每个文件在创建和测试应用程序中所起的作用。要在本地计算机上创建文件，请复制并粘贴以下代码。

### AWS SAM 模板（DynamoDB 表示例）
<a name="scheduled-task-app-table-yaml"></a>

将以下代码复制并粘贴到名为 `template.yaml` 的文件。

```
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for DynamoDB Table with Order_number as Partition Key and Date as Sort Key

Resources:
  MyDynamoDBTable:
    Type: AWS::DynamoDB::Table
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      TableName: MyOrderTable
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: Order_number
          AttributeType: S
        - AttributeName: Date
          AttributeType: S
      KeySchema:
        - AttributeName: Order_number
          KeyType: HASH
        - AttributeName: Date
          KeyType: RANGE
      SSESpecification:
        SSEEnabled: true
      GlobalSecondaryIndexes:
        - IndexName: Date-index
          KeySchema:
            - AttributeName: Date
              KeyType: HASH
          Projection:
            ProjectionType: ALL
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true

Outputs:
  TableName:
    Description: DynamoDB Table Name
    Value: !Ref MyDynamoDBTable
  TableArn:
    Description: DynamoDB Table ARN
    Value: !GetAtt MyDynamoDBTable.Arn
```

**注意**  
AWS SAM 模板使用的标准命名约定为 `template.yaml`。在此示例中，您有两个模板文件：其中一个用于创建示例数据库，另一个用于创建应用程序本身。将它们保存在项目文件夹中的单独子目录中。

此 AWS SAM 模板定义了您为测试应用程序而创建的 DynamoDB 表资源。该表使用的主键为 `Order_number`，排序键为 `Date`。为了让 Lambda 函数直接按日期查找项目，我们还定义了名为 `Date-index` 的[全局二级索引](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html)。

要了解有关使用该 `AWS::DynamoDB::Table` 资源创建和配置 DynamoDB 表的更多信息，请参阅《AWS CloudFormation User Guide》中的 [AWS::DynamoDB::Table](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html)。**

### 数据库数据文件示例
<a name="scheduled-task-app-csv-file"></a>

将以下代码复制并粘贴到名为 `sample_data.csv` 的文件。

```
Date,Order_number,CustomerName,ProductID,Quantity,TotalAmount
2023-09-01,ORD001,Alejandro Rosalez,PROD123,2,199.98
2023-09-01,ORD002,Akua Mansa,PROD456,1,49.99
2023-09-02,ORD003,Ana Carolina Silva,PROD789,3,149.97
2023-09-03,ORD004,Arnav Desai,PROD123,1,99.99
2023-10-01,ORD005,Carlos Salazar,PROD456,2,99.98
2023-10-02,ORD006,Diego Ramirez,PROD789,1,49.99
2023-10-03,ORD007,Efua Owusu,PROD123,4,399.96
2023-10-04,ORD008,John Stiles,PROD456,2,99.98
2023-10-05,ORD009,Jorge Souza,PROD789,3,149.97
2023-10-06,ORD010,Kwaku Mensah,PROD123,1,99.99
2023-11-01,ORD011,Li Juan,PROD456,5,249.95
2023-11-02,ORD012,Marcia Oliveria,PROD789,2,99.98
2023-11-03,ORD013,Maria Garcia,PROD123,3,299.97
2023-11-04,ORD014,Martha Rivera,PROD456,1,49.99
2023-11-05,ORD015,Mary Major,PROD789,4,199.96
2023-12-01,ORD016,Mateo Jackson,PROD123,2,199.99
2023-12-02,ORD017,Nikki Wolf,PROD456,3,149.97
2023-12-03,ORD018,Pat Candella,PROD789,1,49.99
2023-12-04,ORD019,Paulo Santos,PROD123,5,499.95
2023-12-05,ORD020,Richard Roe,PROD456,2,99.98
2024-01-01,ORD021,Saanvi Sarkar,PROD789,3,149.97
2024-01-02,ORD022,Shirley Rodriguez,PROD123,1,99.99
2024-01-03,ORD023,Sofia Martinez,PROD456,4,199.96
2024-01-04,ORD024,Terry Whitlock,PROD789,2,99.98
2024-01-05,ORD025,Wang Xiulan,PROD123,3,299.97
```

此文件包含部分测试数据示例，供以标准逗号分隔值（CSV）格式填充 DynamoDB 表。

### 用于加载示例数据的 Python 脚本
<a name="scheduled-task-app-load-script"></a>

将以下代码复制并粘贴到名为 `load_sample_data.py` 的文件。

```
import boto3
import csv
from decimal import Decimal

# Initialize the DynamoDB client
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('MyOrderTable') 
print("DDB client initialized.")

def load_data_from_csv(filename):
    with open(filename, 'r') as file:
        csv_reader = csv.DictReader(file)
        for row in csv_reader:
            item = {
                'Order_number': row['Order_number'],
                'Date': row['Date'],
                'CustomerName': row['CustomerName'],
                'ProductID': row['ProductID'],
                'Quantity': int(row['Quantity']),
                'TotalAmount': Decimal(str(row['TotalAmount']))
            }
            table.put_item(Item=item)
            print(f"Added item: {item['Order_number']} - {item['Date']}")

if __name__ == "__main__":
    load_data_from_csv('sample_data.csv')
    print("Data loading completed.")
```

此 Python 脚本首先使用 适用于 Python (Boto3) 的 AWS SDK 创建与 DynamoDB 表的连接。然后，它将迭代示例数据 CSV 文件中的各行，从该行创建项目，然后使用 boto3 SDK 将该项目写入 DynamoDB 表。

### Python 函数代码
<a name="scheduled-task-app-function-code"></a>

将以下代码复制并粘贴到名为 `lambda_function.py` 的文件。

```
import boto3
from datetime import datetime, timedelta
from boto3.dynamodb.conditions import Key, Attr
import logging

logger = logging.getLogger()
logger.setLevel("INFO")

def lambda_handler(event, context):
    # Initialize the DynamoDB client
    dynamodb = boto3.resource('dynamodb')
    
    # Specify the table name
    table_name = 'MyOrderTable'
    table = dynamodb.Table(table_name)
    
    # Get today's date
    today = datetime.now()
    
    # Calculate the date one year ago
    one_year_ago = (today - timedelta(days=365)).strftime('%Y-%m-%d')
    
    # Scan the table using a global secondary index
    response = table.scan(
        IndexName='Date-index',
        FilterExpression='#date < :one_year_ago',
        ExpressionAttributeNames={
            '#date': 'Date'
        },
        ExpressionAttributeValues={
            ':one_year_ago': one_year_ago
        }
    )
    
     # Delete old items
    with table.batch_writer() as batch:
        for item in response['Items']:
            Order_number = item['Order_number']
            batch.delete_item(
                Key={
                    'Order_number': Order_number,
                    'Date': item['Date']
                }
            )
            logger.info(f'deleted order number {Order_number}')
    
    # Check if there are more items to scan
    while 'LastEvaluatedKey' in response:
        response = table.scan(
            IndexName='DateIndex',
            FilterExpression='#date < :one_year_ago',
            ExpressionAttributeNames={
                '#date': 'Date'
            },
            ExpressionAttributeValues={
                ':one_year_ago': one_year_ago
            },
            ExclusiveStartKey=response['LastEvaluatedKey']
        )
        
        # Delete old items
        with table.batch_writer() as batch:
            for item in response['Items']:
                batch.delete_item(
                    Key={
                        'Order_number': item['Order_number'],
                        'Date': item['Date']
                    }
                )
    
    return {
        'statusCode': 200,
        'body': 'Cleanup completed successfully'
    }
```

Python 函数代码包含 Lambda 在调用函数时运行的[处理程序函数](python-handler.md) (`lambda_handler`)。

当 EventBridge 调度器调用函数时，它会使用 适用于 Python (Boto3) 的 AWS SDK 创建与要执行计划维护任务的 DynamoDB 表的连接。然后，它使用 Python `datetime` 库计算一年前的日期，然后扫描表中是否有早于该日期的项目，并将其删除。

请注意，来自 DynamoDB 查询和扫描操作的响应大小限制为最大 1 MB。如果响应大于 1 MB，则 DynamoDB 会对数据进行分页，并在响应中返回 `LastEvaluatedKey` 元素。为确保我们的函数将处理表中的所有记录，我们检查此密钥是否存在，并从上次评估的位置继续执行表扫描，直到扫描完整个表。

### `requirements.txt` 清单文件
<a name="scheduled-task-app-dependencies"></a>

将以下代码复制并粘贴到名为 `requirements.txt` 的文件。

```
boto3
```

在本示例中，函数代码只有一个不属于标准 Python 库的依赖项：即适用于 Python 的 SDK（Boto3），供函数用于扫描和删除来自 DynamoDB 表的项目。

**注意**  
适用于 Python 的 SDK（Boto3）的一个版本作为 Lambda 运行时的一部分包含在内，因此无需将 Boto3 添加到函数的部署包中，代码即可运行。不过，为了完全控制函数依赖项并避免可能出现的版本不一致问题，Python 的最佳实践是将所有函数依赖项包含在函数的部署包中。请参阅 [Python 中的运行时系统依赖项](python-package.md#python-package-dependencies)，了解更多信息。

### AWS SAM 模板（用于定期维护的应用程序）
<a name="scheduled-task-app-table-yaml"></a>

将以下代码复制并粘贴到名为 `template.yaml` 的文件。

```
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for Lambda function and EventBridge Scheduler rule

Resources:
  MyLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: ScheduledDBMaintenance
      CodeUri: ./
      Handler: lambda_function.lambda_handler
      Runtime: python3.11
      Architectures:
        - x86_64
      Events:
        ScheduleEvent:
          Type: ScheduleV2
          Properties:
            ScheduleExpression: cron(0 3 1 * ? *)
            Description: Run on the first day of every month at 03:00 AM
      Policies:
        - CloudWatchLogsFullAccess
        - Statement:
            - Effect: Allow
              Action:
                - dynamodb:Scan
                - dynamodb:BatchWriteItem
              Resource: !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/MyOrderTable'

  LambdaLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${MyLambdaFunction}
      RetentionInDays: 30

Outputs:
  LambdaFunctionName:
    Description: Lambda Function Name
    Value: !Ref MyLambdaFunction
  LambdaFunctionArn:
    Description: Lambda Function ARN
    Value: !GetAtt MyLambdaFunction.Arn
```

**注意**  
AWS SAM 模板使用的标准命名约定为 `template.yaml`。在此示例中，您有两个模板文件：其中一个用于创建示例数据库，另一个用于创建应用程序本身。将它们保存在项目文件夹中的单独子目录中。

此 AWS SAM 模板用于定义应用程序的资源。我们使用 `AWS::Serverless::Function` 资源定义 Lambda 函数。用于调用 Lambda 函数的 EventBridge 调度器计划和触发的创建都使用了此资源的 `Events` 属性，类型为 `ScheduleV2`。要了解有关在 AWS SAM 模板中定义 EventBridge 调度器的更多信息，请参阅《AWS Serverless Application Model Developer Guide》**中的 [ScheduleV2](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-schedulev2.html)。

除了 Lambda 函数和 EventBridge 调度器计划外，我们还为函数定义了 CloudWatch 日志组，用于向其发送已删除项目的记录。

### 测试脚本
<a name="scheduled-task-app-test-script"></a>

将以下代码复制并粘贴到名为 `test_app.py` 的文件。

```
import boto3
from datetime import datetime, timedelta
import json

# Initialize the DynamoDB client
dynamodb = boto3.resource('dynamodb')

# Specify your table name
table_name = 'YourTableName'
table = dynamodb.Table(table_name)

# Get the current date
current_date = datetime.now()

# Calculate the date one year ago
one_year_ago = current_date - timedelta(days=365)

# Convert the date to string format (assuming the date in DynamoDB is stored as a string)
one_year_ago_str = one_year_ago.strftime('%Y-%m-%d')

# Scan the table
response = table.scan(
    FilterExpression='#date < :one_year_ago',
    ExpressionAttributeNames={
        '#date': 'Date'
    },
    ExpressionAttributeValues={
        ':one_year_ago': one_year_ago_str
    }
)

# Process the results
old_records = response['Items']

# Continue scanning if we have more items (pagination)
while 'LastEvaluatedKey' in response:
    response = table.scan(
        FilterExpression='#date < :one_year_ago',
        ExpressionAttributeNames={
            '#date': 'Date'
        },
        ExpressionAttributeValues={
            ':one_year_ago': one_year_ago_str
        },
        ExclusiveStartKey=response['LastEvaluatedKey']
    )
    old_records.extend(response['Items'])

for record in old_records:
    print(json.dumps(record))

# The total number of old records should be zero.
print(f"Total number of old records: {len(old_records)}")
```

此测试脚本使用 适用于 Python (Boto3) 的 AWS SDK 创建与 DynamoDB 表的连接，并扫描超过一年的项目。为确认 Lambda 函数是否成功运行，在测试结束时，该函数会打印表中仍存在超过一年的记录数。如果 Lambda 函数成功运行，则表中的旧记录数应为零。

## 创建和填充示例 DynamoDB 表
<a name="scheduled-task-app-create-table"></a>

要测试用于定期维护的应用程序，您需要先创建 DynamoDB 表，然后在其中填充部分示例数据。您可以使用 AWS 管理控制台 手动创建表，也可以使用 AWS SAM 创建表。我们建议您使用 AWS SAM，通过使用 AWS CLI 命令快速创建和配置表。

------
#### [ Console ]

**创建 DynamoDB 表**

1. 打开 DynamoDB 控制台中 [Tables page](https://console.aws.amazon.com/dynamodbv2/home#tables)（表页面）。

1. 选择**创建表**。

1. 通过执行以下操作创建表：

   1. 在**表详细信息**下，在**表名称**中输入 **MyOrderTable**。

   1. 对于**分区键**，输入 **Order\$1number**，并将数据类型设置为**字符串**。

   1. 对于**排序键**，输入 **Date**，并将类型设置为**字符串**。

   1. 将**表设置**设置为**默认设置**，然后选择**创建表**。

1. 当表创建完成且其**状态**显示为**有效**时，请执行以下操作来创建全局二级索引（GSI）。应用程序将使用此 GSI 直接按日期搜索项目，以确定要删除的内容。

   1. 从表列表中选择 **MyOrderTable**。

   1. 选择**索引**选项卡。

   1. 在**全局二级索引**下，选择**创建索引**。

   1. 在**索引详细信息**下，对于**分区键**，输入 **Date**，并将**数据类型**设置为**字符串**。

   1. 对于 **Index name (索引名称)**，输入 **Date-index**。

   1. 将所有其他参数设置为其默认值，滚动到页面底部，然后选择**创建索引**。

------
#### [ AWS SAM ]

**创建 DynamoDB 表**

1. 导航到您为 DynamoDB 表保存 `template.yaml` 文件的文件夹。请注意，此示例使用两个 `template.yaml` 文件。确保将它们保存在单独的子文件夹中，并且您位于包含模板的正确文件夹中，以便创建 DynamoDB 表。

1. 运行如下命令。

   ```
   sam build
   ```

   此命令会收集您想要部署资源的构建构件，并将其以适当的格式放置在适当的位置进行部署。

1. 要创建 `template.yaml` 文件中指定的 DynamoDB 资源，请运行以下命令。

   ```
   sam deploy --guided
   ```

   使用 `--guided` 标志意味着 AWS SAM 将向您显示提示，以指导您完成部署过程。对于此部署，输入 **cron-app-test-db** 的 `Stack name`，然后使用 Enter 接受所有其他选项的默认值。

   当 AWS SAM 创建完 DynamoDB 资源后，您将看到以下消息。

   ```
   Successfully created/updated stack - cron-app-test-db in us-west-2
   ```

1. 此外，您还可以通过打开 DynamoDB 控制台的[表](https://console.aws.amazon.com/dynamodbv2/home#tables)页面来确认 DynamoDB 表已创建。您应该会看到名为 `MyOrderTable` 的表。

------

创建表后，接下来要添加部分示例数据来测试应用程序。您之前下载的 CSV 文件 (`sample_data.csv`) 包含许多示例条目，包括订单号、日期以及客户和订单信息。使用提供的 python 脚本 (`load_sample_data.py`) 将此数据添加到表中。

**将示例数据添加到表中**

1. 导航到含有 `sample_data.csv` 和 `load_sample_data.py` 文件的目录。如果这些文件位于不同的目录中，请进行移动，将文件保存在相同的位置。

1. 通过运行以下命令，创建 Python 虚拟环境，以运行脚本。我们建议您使用虚拟环境，因为在接下来的步骤中，您需要安装 适用于 Python (Boto3) 的 AWS SDK。

   ```
   python -m venv venv
   ```

1. 通过运行以下命令激活虚拟环境。

   ```
   source venv/bin/activate
   ```

1. 通过运行以下命令，将 SDK for Python（Boto3）安装到虚拟环境中。该脚本使用此库连接到 DynamoDB 表并添加项目。

   ```
   pip install boto3
   ```

1. 通过运行以下命令，运行脚本以填充表。

   ```
   python load_sample_data.py
   ```

   如果脚本成功运行，则它应在加载项目和报告 `Data loading completed` 时将每个项目打印到控制台。

1. 通过运行以下命令停用虚拟环境。

   ```
   deactivate
   ```

1. 您可以执行以下操作，以验证数据是否已加载到 DynamoDB 表：

   1. 打开 DynamoDB 控制台中的[浏览项目](https://console.aws.amazon.com/dynamodbv2/home#item-explorer)页面，然后选择表 (`MyOrderTable`)。

   1. 在**返回的项目**窗格中，您应该会看到脚本添加到表中 CSV 文件中的 25 个项目。

## 创建用于定期维护的应用程序
<a name="scheduled-task-app-create-app"></a>

您可以使用 AWS 管理控制台 或通过使用 AWS SAM 创建并部署此示例应用程序的资源。在生产环境中，建议使用 AWS SAM 等基础设施即代码（IaC）工具来重复部署无服务器应用程序，无需采用手动流程。

在本示例中，可按照控制台或说明了解如何单独配置每种 AWS 资源，或按照 AWS SAM 说明，使用 AWS CLI 命令快速部署应用程序。

------
#### [ Console ]

**使用 AWS 管理控制台 创建函数**

首先，创建包含基本起始代码的函数。然后，通过将此代码替换为自己的函数代码，方法是将代码直接复制并粘贴到 Lambda 代码编辑器中，或者将代码作为 `.zip` 包上传。对于此任务，我们建议复制并粘贴代码。

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

1. 选择 **Create function**（创建函数）。

1. 选择**从头开始创作**。

1. 在**基本信息**中，执行以下操作：

   1. 对于 **Function name（函数名称）**，请输入 `ScheduledDBMaintenance`。

   1. 对于**运行时**，请选择最新的 Python 版本。

   1. 对于**架构**，选择 **x86\$164**。

1. 选择**创建函数**。

1. 创建函数后，您可以使用提供的函数代码配置函数。

   1. 在**代码源**窗格中，将 Lambda 创建的 Hello world 代码替换为之前保存的 `lambda_function.py` 文件中的 Python 函数代码。

   1. 在**部署**部分，选择**部署**以更新函数的代码：  
![\[\]](http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/images/getting-started-tutorial/deploy-console.png)

**配置函数内存和超时（控制台）**

1. 选择函数的**配置**选项卡。

1. 在**常规配置**窗格中，选择**编辑**。

1. 将**内存**设置为 256 MB，并将**超时**设置为 15 秒。如果您正在处理包含许多记录的大型表，例如在生产环境中，则可以考虑将**超时**设置为更大数值。此举会让函数有更多时间扫描和清理数据库。

1. 选择**保存**。

**配置日志格式（控制台）**

您可以将 Lambda 函数配置为以非结构化文本或 JSON 格式输出日志。我们建议将 JSON 格式用于日志，以简化搜索和筛选日志数据的流程。要了解有关 Lambda 日志配置选项的更多信息，请参阅[为 Lambda 函数配置高级日志记录控件](monitoring-logs.md#monitoring-cloudwatchlogs-advanced)。

1. 选择函数的**配置**选项卡。

1. 选择**监控和操作工具**。

1. 在**日志记录配置**窗格中，选择**编辑**。

1. 对于**日志记录配置**，选择 **JSON**。

1. 选择**保存**。

**要设置 IAM 权限**

要向函数授予读取和删除 DynamoDB 项目所需的权限，您需要向函数的[执行](lambda-intro-execution-role.md)角色添加来定义必要权限的策略。

1. 打开**配置**选项卡，然后从左侧导航栏中选择**权限**。

1. 在**执行角色**下，选择角色名称。

1. 在 IAM 控制台中，选择**添加权限**，然后选择**创建内联策略**。

1. 使用 JSON 编辑器并输入以下策略：  
****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Effect": "Allow",
               "Action": [
                   "dynamodb:Scan",
                   "dynamodb:DeleteItem",
                   "dynamodb:BatchWriteItem"
               ],
               "Resource": "arn:aws:dynamodb:*:*:table/MyOrderTable"
           }
       ]
   }
   ```

1. 将策略命名为 **DynamoDBCleanupPolicy**，然后创建策略。

**将 EventBridge 调度器设置为触发器（控制台）**

1. 打开 [EventBridge 控制台](https://console.aws.amazon.com/events/home)。

1. 在左侧导航窗格中，选择**调度器**部分下的**调度器**。

1. 选择**创建计划**。

1. 通过执行以下操作配置计划：

   1. 在**计划名称**下，输入计划的名称（例如：**DynamoDBCleanupSchedule**）。

   1. 在**计划模式**下，选择**定期计划**。

   1. 对于**计划类型**，将默认值保留为**基于 Cron 的计划**，然后输入以下计划详细信息：
      + **分钟**：**0**
      + **小时**：**3**
      + **日**：**1**
      + **月**：**\$1**
      + **星期**：**?**
      + **年**：**\$1**

      评估后，此 cron 表达式会在每月第 1 天上午 03:00 运行。

   1. 对于**灵活时间窗口**下，选择**关闭**。

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

1. 通过执行以下操作，为 Lambda 函数配置触发器：

   1. 在**目标详细信息**窗格中，将**目标 API** 设置为**模板化目标**，然后选择 **AWS Lambda 调用**。

   1. 在**调用**下，从下拉列表中选择 Lambda 函数 (`ScheduledDBMaintenance`)。

   1. 将**有效载荷**留空，然后选择**下一步**。

   1. 向下滚动到**权限**，然后选择**为此计划创建新角色**。使用控制台创建新 EventBridge 调度器计划时，EventBridge 调度器会创建新策略，该策略具有调用函数所需的必要权限。有关管理计划权限的更多信息，请参阅《EventBridge Scheduler User Guide》中的 [Cron-based schedules](https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html#cron-based)。**

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

1. 查看设置，然后选择**创建计划**以完成计划和 Lambda 触发器的创建。

------
#### [ AWS SAM ]

**要部署应用程序，请使用 AWS SAM**

1. 导航到您为应用程序保存 `template.yaml` 文件的文件夹。请注意，此示例使用两个 `template.yaml` 文件。确保将它们保存在单独的子文件夹中，并且您位于包含模板的正确文件夹中，以便创建应用程序。

1. 将之前下载的 `lambda_function.py` 和 `requirements.txt` 文件复制到同一个文件夹。AWS SAM 模板中指定的代码位置是 `./`，即当前位置。尝试部署应用程序时，AWS SAM 将在此文件夹中搜索 Lambda 函数代码。

1. 运行如下命令。

   ```
   sam build --use-container
   ```

   此命令会收集您想要部署资源的构建构件，并将其以适当的格式放置在适当的位置进行部署。指定 `--use-container` 选项会在类似 Lambda 的 Docker 容器中构建函数。我们在这里使用它，您无需在本地计算机上安装 Python 3.12 即可进行构建。

1. 要创建 `template.yaml` 文件中指定的 Lambda 和 EventBridge 调度器资源，请运行以下命令。

   ```
   sam deploy --guided
   ```

   使用 `--guided` 标志意味着 AWS SAM 将向您显示提示，以指导您完成部署过程。对于此部署，输入 **cron-maintenance-app** 的 `Stack name`，然后使用 Enter 接受所有其他选项的默认值。

   当 AWS SAM 创建完 Lambda 和 EventBridge 调度器资源后，您将看到以下消息。

   ```
   Successfully created/updated stack - cron-maintenance-app in us-west-2
   ```

1. 此外，您还可以通过打开 Lambda 控制台的[函数](https://console.aws.amazon.com/lambda/home#/functions)页面，来确认系统已创建 Lambda 函数。您应该会看到名为 `ScheduledDBMaintenance` 的函数。

------

## 测试应用程序
<a name="scheduled-task-app-test-app"></a>

 要测试计划是否正确触发了函数，以及函数是否正确清理了数据库中的记录，您可以对计划进行临时修改，设置为在特定时间运行一次。然后，您可以再次运行 `sam deploy`，以将定期计划重置为每月运行一次。

**使用 AWS 管理控制台 运行应用程序。**

1. 导航回到 EventBridge 调度器控制台页面。

1. 选择计划，然后选择**编辑**。

1. 在**计划模式**部分的**定期**下，选择**一次性计划**。

1.  将调用时间设置为距现在起几分钟后，查看设置，然后选择**保存**。

 在计划运行并调用其目标之后，您可以运行 `test_app.py` 脚本来验证函数是否从 DynamoDB 表中成功删除了所有旧记录。

**使用 Python 脚本验证是否已删除旧记录**

1.  在命令行中，导航到保存 `test_app.py` 的文件夹。

1. 运行 脚本。

   ```
   python test_app.py
   ```

    如果成功删除，您将会看到以下输出。

   ```
   Total number of old records: 0
   ```

## 后续步骤
<a name="scheduled-task-app-next-steps"></a>

 现在，您可以修改 EventBridge 调度器以满足特定应用程序要求。EventBridge 调度器支持以下计划表达式：cron、速率和一次性计划。

 有关 EventBridge 调度器计划表达式的更多信息，请参阅[《EventBridge 调度器用户指南》](https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html)中的*计划类型*。《*IAM 用户指南*》中的[访问管理](https://docs.aws.amazon.com/IAM/latest/UserGuide/access.html) 

# 使用 Lambda 持久性函数创建订单处理系统
<a name="order-processing-app"></a>

**注意**  
需要：添加显示 API Gateway、持久性函数工作流程和支持服务（DynamoDB、EventBridge）的架构图

## 先决条件
<a name="order-processing-prerequisites"></a>
+ AWS CLI安装并配置了 
+ 需要：特定的持久性函数要求

## 创建源代码文件
<a name="order-processing-source"></a>

在您的项目目录中创建以下文件：
+ `lambda_function.py`：函数代码
+ `requirements.txt`：依赖项清单

### 函数代码
<a name="order-processing-function-code"></a>

```
# NEED: Verify correct imports
import boto3
import json

def lambda_handler(event, context):
    # NEED: Verify DurableContext syntax
    durable = context.durable
    
    try:
        # Validate and store order
        order = await durable.step('validate', async () => {
            return validate_order(event['order'])
        })
        
        # Process payment
        # NEED: Verify wait syntax
        await durable.wait(/* wait configuration */)
        
        # Additional steps
        # NEED: Complete implementation
        
    except Exception as e:
        # NEED: Error handling patterns
        raise e

def validate_order(order_data):
    # NEED: Implementation
    pass
```

### 要求文件
<a name="order-processing-requirements"></a>

```
# NEED: List of required packages
```

## 部署应用程序
<a name="order-processing-deploy"></a>

### 为订单创建 DynamoDB 表
<a name="order-processing-dynamodb"></a>

1. 打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 选择 **Create table** (创建表)

1. 对于**表名称**，输入 **Orders**

1. 对于**分区键**，输入 **orderId**

1. 将其他设置保留为默认值

1. 选择 **Create table** (创建表)

### 创建 Lambda 函数
<a name="order-processing-lambda"></a>

1. 在 [https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/) 中打开 Lambda 控制台

1. 选择**创建函数**

1. 选择**从头开始编写**

1. 对于**函数名称**，输入 **ProcessOrder**。

1. 对于**运行时**，请选择您的首选运行时

1. 需要：添加特定于持久性函数的配置

1. 选择**创建函数**

### 创建 API Gateway 端点
<a name="order-processing-apigateway"></a>

1. 在 [https://console.aws.amazon.com/apigateway/](https://console.aws.amazon.com/apigateway/) 中打开 API Gateway 控制台

1. 选择**创建 API**

1. 选择 **HTTP API**

1. 选择**构建**

1. 添加与 Lambda 函数的集成

1. 配置订单处理路由

1. 部署 API

## 测试应用程序
<a name="order-processing-test"></a>

提交测试订单：

```
{
    "orderId": "12345",
    "items": [
        {
            "productId": "ABC123",
            "quantity": 1
        }
    ]
}
```

需要：为持久性函数添加特定的监控指令

## 后续步骤
<a name="order-processing-next-steps"></a>

### 添加业务逻辑
<a name="order-processing-business-logic"></a>

实施库存管理：

```
async def check_inventory(order):
    # Add inventory check logic
    pass
```

添加价格计算：

```
async def calculate_total(order):
    # Add pricing logic
    pass
```

### 改进错误处理
<a name="order-processing-error-handling"></a>

添加补偿逻辑：

```
async def reverse_payment(order):
    # Add payment reversal logic
    pass
```

处理订单取消：

```
async def cancel_order(order):
    # Add cancellation logic
    pass
```

### 集成外部系统
<a name="order-processing-integrations"></a>

```
async def notify_shipping_provider(order):
    # Add shipping integration
    pass

async def send_customer_notification(order):
    # Add notification logic
    pass
```

### 增强监控
<a name="order-processing-monitoring"></a>
+ 创建 CloudWatch 控制面板
+ 为订单处理时间设置指标
+ 为延迟订单配置提醒