架构概述

本教程将构建一个完整的无服务器图片上传系统:

  • API Gateway: 提供 RESTful API 接口

  • Lambda: 处理业务逻辑(图片处理、验证)

  • S3: 存储图片文件

  • DynamoDB: 记录图片元数据

应用场景: 用户通过 API 上传图片,Lambda 自动处理并存储到 S3,同时在 DynamoDB 中记录上传信息。


架构流程图

客户端 → API Gateway → Lambda → S3 (存储图片)
                          ↓
                      DynamoDB (记录元数据)

第一步:创建 S3 存储桶

# 创建 S3 存储桶
aws s3 mb s3://my-image-upload-bucket-20251229 --region us-east-1
​
# 启用版本控制(可选)
aws s3api put-bucket-versioning \
  --bucket my-image-upload-bucket-20251229 \
  --versioning-configuration Status=Enabled

第二步:创建 DynamoDB 表

aws dynamodb create-table \
  --table-name ImageMetadata \
  --attribute-definitions \
    AttributeName=imageId,AttributeType=S \
  --key-schema \
    AttributeName=imageId,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --region us-east-1

表结构设计:

  • imageId: 主键,唯一标识

  • fileName: 原始文件名

  • s3Key: S3 对象键

  • uploadTime: 上传时间戳

  • fileSize: 文件大小

  • contentType: 文件类型


第三步:创建 IAM 角色

Lambda 需要访问 S3 和 DynamoDB 的权限。

创建信任策略文件 trust-policy.json:

cat > trust-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

创建权限策略文件 lambda-policy.json:

cat > lambda-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::my-image-upload-bucket-20251229/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:PutItem",
        "dynamodb:GetItem"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:*:table/ImageMetadata"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}
EOF

执行创建命令:

# 创建角色
aws iam create-role \
  --role-name ImageUploadLambdaRole \
  --assume-role-policy-document file://trust-policy.json
​
# 附加自定义策略
aws iam put-role-policy \
  --role-name ImageUploadLambdaRole \
  --policy-name ImageUploadPolicy \
  --policy-document file://lambda-policy.json

第四步:编写 Lambda 函数

创建 Lambda 函数代码 lambda_function.py:

cat > lambda_function.py << EOF
import json
import boto3
import base64
import uuid
from datetime import datetime
​
s3 = boto3.client('s3')
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('ImageMetadata')
​
BUCKET_NAME = 'my-image-upload-bucket-20251229'
​
def lambda_handler(event, context):
    try:
        # 解析请求体
        body = json.loads(event['body'])
        image_data = body['image']  # Base64 编码的图片
        file_name = body.get('fileName', 'unknown.jpg')
        content_type = body.get('contentType', 'image/jpeg')
        
        # 解码 Base64 图片
        image_bytes = base64.b64decode(image_data)
        file_size = len(image_bytes)
        
        # 生成唯一 ID 和 S3 键
        image_id = str(uuid.uuid4())
        s3_key = f"images/{image_id}/{file_name}"
        
        # 上传到 S3
        s3.put_object(
            Bucket=BUCKET_NAME,
            Key=s3_key,
            Body=image_bytes,
            ContentType=content_type
        )
        
        # 记录到 DynamoDB
        upload_time = datetime.utcnow().isoformat()
        table.put_item(
            Item={
                'imageId': image_id,
                'fileName': file_name,
                's3Key': s3_key,
                'uploadTime': upload_time,
                'fileSize': file_size,
                'contentType': content_type,
                'bucketName': BUCKET_NAME
            }
        )
        
        # 返回成功响应
        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
            },
            'body': json.dumps({
                'message': '图片上传成功',
                'imageId': image_id,
                's3Url': f"https://{BUCKET_NAME}.s3.amazonaws.com/{s3_key}",
                'fileSize': file_size
            })
        }
        
    except Exception as e:
        return {
            'statusCode': 500,
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
            },
            'body': json.dumps({
                'message': '上传失败',
                'error': str(e)
            })
        }
EOF

打包并部署:

# 打包代码
zip lambda_function.zip lambda_function.py
​
# 创建 Lambda 函数
aws lambda create-function \
  --function-name ImageUploadFunction \
  --runtime python3.11 \
  --role arn:aws:iam::654654569766:role/ImageUploadLambdaRole \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://lambda_function.zip \
  --timeout 30 \
  --memory-size 256 \
  --region us-east-1

第五步:创建 API Gateway

# 创建 REST API
aws apigateway create-rest-api \
  --name ImageUploadAPI \
  --description "图片上传 API" \
  --region us-east-1
​
# 获取 API ID(从上一步输出中获取)
API_ID=6tduhktb98
​
# 获取根资源 ID
ROOT_ID=$(aws apigateway get-resources \
  --rest-api-id $API_ID \
  --query 'items[0].id' \
  --output text)
​
# 创建 /upload 资源
RESOURCE_ID=$(aws apigateway create-resource \
  --rest-api-id $API_ID \
  --parent-id $ROOT_ID \
  --path-part upload \
  --query 'id' \
  --output text)
​
# 创建 POST 方法
aws apigateway put-method \
  --rest-api-id $API_ID \
  --resource-id $RESOURCE_ID \
  --http-method POST \
  --authorization-type NONE
​
# 集成 Lambda
LAMBDA_ARN="arn:aws:lambda:us-east-1:<YOUR_ACCOUNT_ID>:function:ImageUploadFunction"
​
aws apigateway put-integration \
  --rest-api-id $API_ID \
  --resource-id $RESOURCE_ID \
  --http-method POST \
  --type AWS_PROXY \
  --integration-http-method POST \
  --uri "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/$LAMBDA_ARN/invocations"
​
# 授权 API Gateway 调用 Lambda
aws lambda add-permission \
  --function-name ImageUploadFunction \
  --statement-id apigateway-invoke \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:us-east-1:<YOUR_ACCOUNT_ID>:$API_ID/*/*"
​
# 部署 API
aws apigateway create-deployment \
  --rest-api-id $API_ID \
  --stage-name prod

API 端点: https://<API_ID>.execute-api.us-east-1.amazonaws.com/prod/upload


第六步:测试 API

使用 curl 测试:

# 将图片转换为 Base64
IMAGE_BASE64=$(base64 -w 0 test-image.jpg)
​
#构造JSON(注意:手动确保没有非法字符)
cat > /tmp/upload-payload.json <<EOF
{
  "image": "$IMAGE_BASE64",
  "fileName": "test-image.jpg",
  "contentType": "image/jpeg"
}
EOF

# 发送请求
curl -X POST \
     -H "Content-Type: application/json" \
     --data-binary @/tmp/upload-payload.json \
     https://<API_ID>.execute-api.us-east-1.amazonaws.com/prod/upload

2025-12-29T09:58:08-bwzwyzju.png

使用 Python 测试:

import requests
import base64
​
# 读取图片
with open('test-image.jpg', 'rb') as f:
    image_data = base64.b64encode(f.read()).decode('utf-8')
​
# 发送请求
url = 'https://<API_ID>.execute-api.us-east-1.amazonaws.com/prod/upload'
payload = {
    'image': image_data,
    'fileName': 'test-image.jpg',
    'contentType': 'image/jpeg'
}
​
response = requests.post(url, json=payload)
print(response.json())

第七步:查询上传记录

2025-12-29T09:58:55-gbiehuvt.png

查询 DynamoDB:

aws dynamodb get-item \
  --table-name ImageMetadata \
  --key '{"imageId": {"S": "<your-image-id>"}}' \
  --region us-east-1
  
aws dynamodb get-item \
  --table-name ImageMetadata \
  --key '{"imageId": {"S": "f1732ea8-40d4-4d7c-af5f-8abe8d1b24bf"}}' \
  --region us-east-1

扫描所有记录:

aws dynamodb scan \
  --table-name ImageMetadata \
  --region us-east-1

2025-12-29T09:59:03-qburommf.png

优化建议

1. 添加图片格式验证

ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif']
if content_type not in ALLOWED_TYPES:
    return {'statusCode': 400, 'body': json.dumps({'error': '不支持的图片格式'})}

2. 添加文件大小限制

MAX_SIZE = 5 * 1024 * 1024  # 5MB
if file_size > MAX_SIZE:
    return {'statusCode': 400, 'body': json.dumps({'error': '文件过大'})}

3. 使用 S3 预签名 URL(更高效)

对于大文件,建议使用预签名 URL 直接上传到 S3:

def generate_presigned_url(file_name):
    s3_key = f"images/{uuid.uuid4()}/{file_name}"
    url = s3.generate_presigned_url(
        'put_object',
        Params={'Bucket': BUCKET_NAME, 'Key': s3_key},
        ExpiresIn=3600
    )
    return url, s3_key

4. 添加 CloudWatch 监控

aws cloudwatch put-metric-alarm \
  --alarm-name ImageUploadErrors \
  --alarm-description "Lambda 错误率过高" \
  --metric-name Errors \
  --namespace AWS/Lambda \
  --statistic Sum \
  --period 300 \
  --threshold 10 \
  --comparison-operator GreaterThanThreshold

5. 启用 S3 生命周期策略

aws s3api put-bucket-lifecycle-configuration \
  --bucket my-image-upload-bucket-20251229 \
  --lifecycle-configuration file://lifecycle.json

lifecycle.json:

{
  "Rules": [
    {
      "Id": "ArchiveOldImages",
      "Status": "Enabled",
      "Transitions": [
        {
          "Days": 90,
          "StorageClass": "GLACIER"
        }
      ]
    }
  ]
}

成本估算

基于每月 10,000 次上传(每张图片 1MB):

服务

用量

月成本(美元)

API Gateway

10,000 请求

$0.04

Lambda

10,000 次调用,256MB

$0.20

S3 存储

10GB

$0.23

DynamoDB

10,000 写入

$1.25

总计

~$1.72


安全最佳实践

  1. 启用 API 密钥或 Cognito 认证

  2. 配置 S3 存储桶策略,禁止公开访问

  3. 启用 CloudTrail 审计日志

  4. 使用 WAF 防护 API Gateway

  5. 加密 S3 对象(SSE-S3 或 SSE-KMS)


故障排查

Lambda 超时

  • 增加超时时间:--timeout 60

  • 检查网络配置(VPC)

S3 上传失败

  • 验证 IAM 权限

  • 检查存储桶名称和区域

DynamoDB 写入失败

  • 确认表名正确

  • 检查 IAM 角色权限


总结

本教程展示了如何构建一个完整的无服务器图片上传系统,涵盖:

  • ✅ API Gateway 配置 RESTful 接口

  • ✅ Lambda 处理业务逻辑

  • ✅ S3 存储图片文件

  • ✅ DynamoDB 记录元数据

  • ✅ IAM 权限配置

  • ✅ 测试和优化

这是一个典型的 AWS 无服务器架构模式,可扩展到文件管理、内容分发等多种场景。


扩展阅读