

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 建立無伺服器檔案處理應用程式
<a name="file-processing-app"></a>

Lambda 最常見的使用案例之一是執行檔案處理任務。例如，您可以使用 Lambda 函數從 HTML 檔案或影像自動建立 PDF 檔案，或在使用者上傳影像時建立縮圖。

在此範例中，您會建立這樣的應用程式：當 PDF 檔案上傳至 Amazon Simple Storage Service (Amazon S3) 儲存貯體時，該應用程式會自動加密 PDF 檔案。若要實作此應用程式，您需要建立下列資源：
+ 供使用者上傳 PDF 檔案 S3 儲存貯體
+ 使用 Python 編寫的 Lambda 函數，用於讀取上傳的檔案，並建立其受密碼保護的加密版本
+ 供 Lambda 儲存加密檔案的第二個 S3 儲存貯體

您也可以建立 AWS Identity and Access Management (IAM) 政策，授予 Lambda 函數在 S3 儲存貯體上執行讀取和寫入操作的許可。

![\[\]](http://docs.aws.amazon.com/zh_tw/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)，以及該處理常式調用以執行 PDF 加密的其他兩個函數，分別為 `encrypt_pdf` 和 `add_encrypted_suffix`。

當該函數被 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` 套件。

**注意**  
Lambda 執行時期包含適用於 Python 的 SDK (Boto3) 版本，因此程式碼無需將 Boto3 新增至函數的部署套件即可執行。不過，為了維持對函數相依項的完整控制，並避免版本不一致可能造成的問題，Python 的最佳實務是在函數的部署套件中包含所有函數相依項。如需進一步了解，請參閱[Python 中的執行期相依項](python-package.md#python-package-dependencies)。

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

您可以手動或使用 來建立和部署此範例應用程式的資源 AWS SAM。在生產環境中，我們建議您使用類似 的 IaC 工具 AWS SAM ，快速且重複地部署整個無伺服器應用程式，而無需使用手動程序。

### 手動部署資源
<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 主控台的[一般用途儲存貯體](https://console.aws.amazon.com/s3/buckets)頁面。

1. 選取最 AWS 區域 接近您地理位置的 。可使用螢幕頂端的下拉式清單來變更區域。  
![\[\]](http://docs.aws.amazon.com/zh_tw/lambda/latest/dg/images/console_region_select.png)

1. 選擇**建立儲存貯體**。

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>

執行角色是授予 Lambda 函數存取 AWS 服務 和資源許可的 IAM 角色。若要向函數提供 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 主控台中開啟 [角色頁面](https://console.aws.amazon.com/iam/home/roles)。

1. 選擇建**立角色**。

1. 在**信任的實體類型**欄位中，選取 **AWS 服務**；在**使用案例**欄位中，選取 **Lambda**。

1. 選擇**下一步**。

1. 執行下列操作，新增 `AmazonS3FullAccess` 受管政策：

   1. 在**許可政策**區段，於搜尋列輸入 **AmazonS3FullAccess**。

   1. 勾選政策旁的核取方塊。

   1. 選擇**下一步**。

1. 在**角色詳細資訊**區段，於**角色名稱**欄位輸入 **LambdaS3Role**。

1. 選擇**建立角色**。

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

**若要建立執行角色並連接 `AmazonS3FullAccess` 受管政策 (AWS CLI)**

1. 將下面的 JSON 儲存在名為 `trust-policy.json` 的檔案中。此信任政策允許 Lambda 透過授予服務主體呼叫 AWS Security Token Service (AWS STS) `AssumeRole`動作的許可來使用角色的`lambda.amazonaws.com`許可。  
****  

   ```
   {
     "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. 若要執行下列 CLI 命令，以連接 `AmazonS3FullAccess` 受管政策。

   ```
   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. 執行以下命令，在 `package` 目錄中安裝 `requirements.txt` 檔案中指定的相依項。

   ```
   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 中，使用您偏好的 zip 工具建立 `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. 開啟 Lambda 主控台中的[函數頁面](https://console.aws.amazon.com/lambda/home#/functions)。

1. 請確定您在 AWS 區域 建立 S3 儲存貯體的相同 中作業。可使用螢幕頂端的下拉式清單來變更區域。  
![\[\]](http://docs.aws.amazon.com/zh_tw/lambda/latest/dg/images/console_region_select.png)

1. 選擇 **Create function (建立函數)**。

1. 選擇 **Author from scratch** (從頭開始撰寫)。

1. 在**基本資訊**下，請執行下列動作：

   1. 針對**函數名稱**，請輸入 `EncryptPDF`。

   1. 針對**執行時期**，選擇 **Python 3.12**。

   1. 對於 **Architecture** (架構)，選擇 **x86\$164**。

1. 執行下列步驟，連接在上一個步驟建立的執行角色：

   1. 展開 **Change default execution role** (變更預設執行角色) 區段。

   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. 選擇 **Add trigger (新增觸發條件)**。

1. 選取 **S3**。

1. 在**儲存貯體**下，選取您的來源儲存貯體。

1. 在**事件類型**下，選取**所有物件建立事件**。

1. 在**遞迴調用**下，選取核取方塊，確認您了解不建議使用相同的 S3 儲存貯體進行輸入和輸出作業。您可以閱讀無伺服器園地中 [導致 Lambda 函數失控的遞迴模式](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 命令。請務必以您自己的 AWS 帳戶 ID 取代 `source-account` 參數，並使用您自己的來源儲存貯體名稱。

   ```
   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 儲存貯體名稱全域唯一，但是在使用資源型政策時，最佳實務是指定儲存貯體必須屬於您的帳戶。這是因為如果您刪除儲存貯體，另一個 可以使用相同的 Amazon Resource Name (ARN) AWS 帳戶 建立儲存貯體。

1. 將下面的 JSON 儲存在名為 `notification.json` 的檔案中。套用至來源儲存貯體時，此 JSON 會設定儲存貯體，以便在每次新增新物件時傳送通知至 Lambda 函數。將 AWS 帳戶 Lambda 函數 ARN 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` 的檔案中。取代預留位置儲存貯體名稱：
   + 對於來源儲存貯體，請以符合 [S3 儲存貯體命名規則](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)的任何名稱取代 `amzn-s3-demo-bucket`。
   + 對於目的地儲存貯體，請將取代 `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，即可進行建置。

   在建置過程中， 會在您以 範本中的 `CodeUri` 屬性指定的位置 AWS SAM 尋找 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 帳戶：
+ 名為 的 An CloudFormation [stack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-whatis-concepts.html#cfn-concepts-stacks) `sam-app`
+ 名稱為 `EncryptPDF` 的 Lambda 函數
+ 兩個 S3 儲存貯體，其中包含您在編輯`template.yaml` AWS SAM 範本檔案時選擇的名稱
+ 具有名稱格式為 `sam-app-EncryptPDFFunctionRole-2qGaapHFWOQ8` 的函數 IAM 執行角色

 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` 函數中，以 `source-bucket-encrypted` 取代 `amzn-s3-demo-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` 和 `test_pdf_encrypt.py` 的目錄中儲存名為 `pytest.ini` 的 PDF 檔案。

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 檔案儲存在範本中指定為 `CodeUri`的目錄中 AWS SAM 。若要進一步了解如何使用二進位分佈為 Python 建置 .zip 部署套件，請參閱[建立含相依項的 .zip 部署套件](python-package.md#python-package-create-dependencies)和[建立含原生程式庫的 .zip 部署套件](python-package.md#python-package-native-libraries)。