Lambda function ghi dữ liệu

Chúng ta sẽ tạo một hàm Lambda để ghi dữ liệu vào bảng DynamoDB và tải ảnh lên S3.

Chỉnh sửa SAM Template

Hàm này yêu cầu hai S3 bucket bổ sung:

  • BookImageShop - lưu trữ ảnh sách gốc được tải lên
  • BookImageResizeShop - lưu trữ ảnh đã resize để phục vụ frontend, với bucket policy công khai và cấu hình CORS
  1. Mở tệp template.yaml trong thư mục fcaj-book-shop.

  2. Thêm các tham số sau vào phần Parameters:

    bookImageShopBucketName:
      Type: String
      Default: book-image-shop-by-tranvix
    
    bookImageResizeShopBucketName:
      Type: String
      Default: book-image-resize-shop-by-tranvix
    

    Thêm Parameters

  3. Thêm các tài nguyên S3 bucket vào phần Resources:

    BookImageShop:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: !Ref bookImageShopBucketName
        PublicAccessBlockConfiguration:
          BlockPublicAcls: false
          BlockPublicPolicy: false
          IgnorePublicAcls: false
          RestrictPublicBuckets: false
    
    BookImageResizeShop:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: !Ref bookImageResizeShopBucketName
        PublicAccessBlockConfiguration:
          BlockPublicAcls: false
          BlockPublicPolicy: false
          IgnorePublicAcls: false
          RestrictPublicBuckets: false
        CorsConfiguration:
          CorsRules:
            - AllowedHeaders:
                - "*"
              AllowedMethods:
                - GET
                - PUT
                - POST
                - DELETE
              AllowedOrigins:
                - "*"
    

    Thêm S3 Buckets

  4. Thêm bucket policy cho resize bucket và hàm Lambda BookCreate:

    BookImageResizeShopPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref BookImageResizeShop
        PolicyDocument:
          Version: 2012-10-17
          Statement:
            - Action:
                - "s3:GetObject"
              Effect: Allow
              Principal: "*"
              Resource: !Join
                - ""
                - - "arn:aws:s3:::"
                  - !Ref BookImageResizeShop
                  - /*
    

    Thêm Bucket Policy

  5. Thêm tài nguyên Lambda function BookCreate:

    BookCreate:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: fcaj-book-shop/book_create
        Handler: book_create.lambda_handler
        Runtime: python3.13
        Environment:
          Variables:
            BUCKET_NAME: !Ref BookImageShop
            BUCKET_RESIZE_NAME: !Ref BookImageResizeShop
            TABLE_NAME: !Ref BooksTable
        FunctionName: book_create
        Architectures:
          - x86_64
        Policies:
          - Statement:
              - Sid: BookCreateItem
                Effect: Allow
                Action:
                  - dynamodb:PutItem
                  - s3:PutObject
                Resource:
                  - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${booksTableName}
                  - !Join
                    - ""
                    - - "arn:aws:s3:::"
                      - !Ref BookImageShop
                      - /*
    

    Thêm tài nguyên BookCreate

Tài nguyên này định nghĩa một Lambda function tên book_create với:

  • Runtime Python 3.13 trên kiến trúc x86_64
  • Biến môi trường cho BUCKET_NAME, BUCKET_RESIZE_NAMETABLE_NAME
  • Được cấp quyền dynamodb:PutItem trên bảng Books
  • Được cấp quyền s3:PutObject trên bucket BookImageShop

Tạo mã Lambda Function

  1. Tạo cấu trúc thư mục như sau:

    fcaj-book-shop
    ├── fcaj-book-shop
    │   ├── books_list
    │   │   └── books_list.py
    │   └── book_create
    │       ├── book_create.py
    │       └── requirements.txt
    └── template.yaml
    
    • Tạo thư mục fcaj-book-shop/book_create trong thư mục fcaj-book-shop.
    • Tạo tệp book_create.py và sao chép đoạn mã sau vào đó:
    import base64
    from multipart import MultipartParser
    from io import BytesIO
    import boto3
    import os
    
    BUCKET = os.environ['BUCKET_NAME']
    BUCKET_RESIZE = os.environ['BUCKET_RESIZE_NAME']
    TABLE = os.environ['TABLE_NAME']
    
    s3_client = boto3.client('s3')
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(TABLE)
    
    header_res = {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "OPTIONS,POST,GET,DELETE",
        "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
    }
    
    def parse_multipart(body, boundary):
        parser = MultipartParser(stream=body, boundary=boundary)
    
        fields = {}
        files = {}
    
        for part in parser:
            if part.filename:
                files[part.name] = {
                    'file-name': part.filename,
                    'content': part.raw
                }
    
            else:
                fields[part.name] = part.value
    
        return fields, files
    
    
    def upload_to_s3(key, bucket, body):
        try:
            s3_client.put_object(Key=key, Bucket=bucket, Body=body)
        except Exception as e:
            print(f"Error uploading image to S3: {e}")
            raise Exception(f'Error uploading image to S3: {e}')
    
    
    def create_new_item(table, item):
        try:
            table.put_item(Item=item)
        except Exception as e:
            print(f"Error creating new item in DynamoDB table: {e}")
            raise Exception(f'Error creating new item in DynamoDB table: {e}')
    
    
    def lambda_handler(event, context):
        content_type = event['headers'].get(
            'Content-Type', '') or event['headers'].get('content-type', '')
    
        try:
            body = event['body']
    
            if event['isBase64Encoded']:
                body = BytesIO(base64.b64decode(body))
    
            boundary = content_type.split("boundary=")[1]
    
            fields, files = parse_multipart(body, boundary)
    
            # Upload image to s3
            file_name = files['image']['file-name']
            file_content = files['image']['content']
    
            upload_to_s3(file_name, BUCKET, file_content)
            s3_url = f'https://{BUCKET_RESIZE}.s3.amazonaws.com/{file_name}'
    
            # Create new item in DB
            item = {
                'id': fields.get('id', 0),
                'rv_id': 0,
                'name': fields.get('name', ''),
                'author': fields.get('author', ''),
                'price': fields.get('price', ''),
                'category': fields.get('category', ''),
                'description': fields.get('description', ''),
                'image': s3_url
            }
    
            create_new_item(table, item)
    
            return {
                "statusCode": 200,
                "headers": header_res,
                "body": "Successfully created item!",
            }
    
        except Exception as e:
            print(f"Error processing form data: {e}")
            raise Exception(f'Error processing form data: {e}')
    

    Mã nguồn book_create.py

  2. Tạo tệp requirements.txt trong thư mục book_create với nội dung:

    multipart
    

    requirements.txt

Thư viện multipart được yêu cầu để phân tích dữ liệu multipart form (tải lên tệp) được gửi từ frontend. SAM sẽ cài đặt dependency này trong quá trình sam build.

Build và Deploy

  1. Chạy các lệnh sau để build và validate SAM template:

    sam validate
    sam build
    

    SAM Validate và Build

  2. Triển khai stack đã cập nhật:

    sam deploy
    

    Changeset sẽ hiển thị các tài nguyên mới được thêm:

    • BookCreateRole (AWS::IAM::Role)
    • BookCreate (AWS::Lambda::Function)
    • BookImageShop (AWS::S3::Bucket)
    • BookImageResizeShop (AWS::S3::Bucket)
    • BookImageResizeShopPolicy (AWS::S3::BucketPolicy)

    Nhập y để xác nhận triển khai.

    SAM Deploy - Changeset

  3. Chờ quá trình triển khai hoàn tất. Bạn sẽ thấy thông báo Successfully created/updated stack.

    Deploy hoàn tất

Kiểm tra trên AWS Console

  1. Mở Lambda console. Bạn sẽ thấy cả hai hàm books_listbook_create.

    Lambda Functions

  2. Nhấp vào hàm book_create. Xem phần Code source.

    Code Source

  3. Xem phần Function overview hiển thị sơ đồ function.

    Function Overview

  4. Nhấp vào tab Configuration, sau đó chọn Environment variables. Xác nhận 3 biến môi trường đã được thiết lập đúng:

    • BUCKET_NAME = book-image-shop-by-tranvix
    • BUCKET_RESIZE_NAME = book-image-resize-shop-by-tranvix
    • TABLE_NAME = Books

    Environment Variables

  5. Chọn Permissions từ menu bên trái. Nhấp vào tên execution role (ví dụ: fcaj-book-shop-BookCreateRole-...).

    Permissions - Execution Role

  6. Tại trang IAM Role, kiểm tra phần Permissions policies. Bạn sẽ thấy:

    • AWSLambdaBasicExecutionRole (AWS managed) - cho CloudWatch Logs
    • BookCreateRolePolicy0 (Customer inline) - policy tùy chỉnh mà chúng ta đã định nghĩa

    IAM Role Permissions

  7. Nhấp vào BookCreateRolePolicy0 để mở rộng. Xem chi tiết policy JSON cấp quyền dynamodb:PutItems3:PutObject.

    BookCreateRolePolicy0 JSON

Kiểm tra S3 Buckets

  1. Mở S3 console. Tìm kiếm book-image. Bạn sẽ thấy cả hai bucket đã được tạo:

    • book-image-resize-shop-by-tranvix
    • book-image-shop-by-tranvix

    S3 Buckets

  2. Nhấp vào book-image-resize-shop-by-tranvix. Chuyển sang tab Permissions. Xác nhận Bucket policy cho phép truy cập công khai s3:GetObject.

    Bucket Permissions

  3. Xem chi tiết Bucket policy JSON.

    Bucket Policy JSON

  4. Cuộn xuống phần Cross-origin resource sharing (CORS). Xác nhận cấu hình CORS cho phép tất cả origin và method (GET, PUT, POST, DELETE).

    Cấu hình CORS