构建并验证订单 API

AWSBeginner
立即练习

介绍

订单团队需要接收 JSON 订单并返回计算总额。你将把健康检查、写入和读取路由连接到提供的 Lambda 应用,测试有效请求和被拒绝的请求,并在清理前检查存储的 DynamoDB 记录。

请先完成 通过 HTTP API 暴露 Lambda 函数 和 通过 Lambda 读取和写入 DynamoDB。这个全新环境提供应用、角色和独立的表。

认证考点

认证 考点 本实验练习
Solutions Architect – Associate (SAA-C03) 任务 2.1 API 请求验证、响应与持久化业务数据。
Developer – Associate (DVA-C02) 任务 1.1, 任务 3.2 API 请求验证、响应与持久化业务数据。
DevOps Engineer – Professional (DOP-C02) 任务 3.2 基础练习:API 请求验证、响应与持久化业务数据。

实验概览

概念图:HTTP 健康与订单路由共用 Lambda 后端;应用验证 JSON,并在 DynamoDB 中存储或读取有效订单。

准备订单后端

本步骤中,你将检查提供的请求约定,授予函数访问订单表的权限,并部署后端。应用以整数分计算金额:数量 × 250 + 100 分处理费。使用上方的 AWS View,比较请求与存储数据。

进入工作目录并读取应用:

cd /home/labex/project
cat app.py

handler 接收 HTTP 载荷格式 2.0。POST /orders 要求 JSON 恰好包含两个字段:一个长度为 2–40 个字符的小写 id,以字母开头,只使用字母、数字或连字符;以及一个 1 到 10 之间的整数 quantity。代码在 PutItem 前拒绝其他结构。GET /orders?id=... 使用 GetItem 读取存储记录。每个响应包含 HTTP 状态和 JSON 响应体。无需重写提供的应用。

读取准备好的表清单:

aws dynamodb list-tables --query TableNames

预期看到 labex-a02-orders 和 labex-a02-reference。只有订单表用于这个 API 的业务操作。将它的 ARN 读入 shell 变量:

TABLE_ARN=$(aws dynamodb describe-table \
  --table-name labex-a02-orders \
  --query Table.TableArn \
  --output text)
echo "$TABLE_ARN"

执行角色控制出站数据库访问。使用 here-document 创建策略文件。不带引号的 JSON 标记允许将 $TABLE_ARN 展开为刚读取的 ARN;其中不包含凭证:

cat > orders-policy.json <<JSON
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["dynamodb:PutItem", "dynamodb:GetItem"],
    "Resource": "$TABLE_ARN"
  }]
}
JSON

将这个具名内联策略附加到准备好的执行角色:

aws iam put-role-policy \
  --role-name labex-a02-execution \
  --policy-name OrdersData \
  --policy-document file://orders-policy.json

打包提供的代码,将 app.py 放在归档文件根目录:

zip orders.zip app.py

读取准备好的角色 ARN:

ROLE_ARN=$(aws iam get-role \
  --role-name labex-a02-execution \
  --query Role.Arn \
  --output text)
echo "$ROLE_ARN"

以十秒超时部署函数。应用从环境中读取 TABLE_NAME,使表选择与源代码分离:

aws lambda create-function \
  --function-name labex-a02-orders-api \
  --runtime python3.12 \
  --handler app.handler \
  --role "$ROLE_ARN" \
  --timeout 10 \
  --environment '{"Variables":{"TABLE_NAME":"labex-a02-orders"}}' \
  --zip-file fileb://orders.zip \
  --query '{Name:FunctionName,Handler:Handler,Table:Environment.Variables.TABLE_NAME}'

预期看到函数名称、app.handler 和 labex-a02-orders。在 AWS View 中检查 Function 卡片,并确认订单表仍有零个项目。部署代码和授予数据访问权限不会创建订单。

映射健康检查和订单路由

本步骤中,你将为三个路由配置一个集成,并授予每个路由调用函数的权限。

创建 HTTP API 并保存 ID:

API_ID=$(aws apigatewayv2 create-api \
  --name labex-a02 \
  --protocol-type HTTP \
  --query ApiId \
  --output text)
echo "$API_ID"
printf '%s\n' "$API_ID" > api-id.txt

创建名称为字面值 $default 的阶段,并启用自动部署:

aws apigatewayv2 create-stage \
  --api-id "$API_ID" \
  --stage-name '$default' \
  --auto-deploy \
  --query '{Stage:StageName,AutoDeploy:AutoDeploy}'

读取后端 ARN,创建载荷 2.0 集成。它的 POST 方法用于调用 Lambda;每个客户端路由仍有自己的 HTTP 方法:

FUNCTION_ARN=$(aws lambda get-function-configuration \
  --function-name labex-a02-orders-api \
  --query FunctionArn \
  --output text)
echo "$FUNCTION_ARN"
INTEGRATION_ID=$(aws apigatewayv2 create-integration \
  --api-id "$API_ID" \
  --integration-type AWS_PROXY \
  --integration-method POST \
  --integration-uri "$FUNCTION_ARN" \
  --payload-format-version 2.0 \
  --query IntegrationId \
  --output text)
echo "$INTEGRATION_ID"

将公开健康检查端点映射到这个集成:

aws apigatewayv2 create-route \
  --api-id "$API_ID" \
  --route-key 'GET /health' \
  --target "integrations/$INTEGRATION_ID" \
  --authorization-type NONE \
  --query RouteKey \
  --output text

映射 JSON 写入路由:

aws apigatewayv2 create-route \
  --api-id "$API_ID" \
  --route-key 'POST /orders' \
  --target "integrations/$INTEGRATION_ID" \
  --authorization-type NONE \
  --query RouteKey \
  --output text

映射存储订单的读取路由:

aws apigatewayv2 create-route \
  --api-id "$API_ID" \
  --route-key 'GET /orders' \
  --target "integrations/$INTEGRATION_ID" \
  --authorization-type NONE \
  --query RouteKey \
  --output text

执行角色的数据库策略不会允许 API Gateway 调用函数。读取账户 ID,再添加三个准确的调用授权。每条语句使用 API ID、字面值 $default、客户端方法和路由路径;\$ 在展开的 shell 字符串中保留美元符号:

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
echo "$ACCOUNT_ID"
aws lambda add-permission \
  --function-name labex-a02-orders-api \
  --statement-id ApiHealth \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-account "$ACCOUNT_ID" \
  --source-arn "arn:aws:execute-api:us-east-1:$ACCOUNT_ID:$API_ID/\$default/GET/health" \
  --query Statement \
  --output text
aws lambda add-permission \
  --function-name labex-a02-orders-api \
  --statement-id ApiWriteOrder \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-account "$ACCOUNT_ID" \
  --source-arn "arn:aws:execute-api:us-east-1:$ACCOUNT_ID:$API_ID/\$default/POST/orders" \
  --query Statement \
  --output text
aws lambda add-permission \
  --function-name labex-a02-orders-api \
  --statement-id ApiReadOrder \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-account "$ACCOUNT_ID" \
  --source-arn "arn:aws:execute-api:us-east-1:$ACCOUNT_ID:$API_ID/\$default/GET/orders" \
  --query Statement \
  --output text

使用生成的 ID 构造准备好的工作环境 API 地址。这个地址在工作环境内提供请求服务,不是 AWS 公共主机名:

API_URL="http://127.0.0.1:8081/api/$API_ID"
echo "$API_URL"

测试健康检查路由。curl -i 包含 HTTP 响应头:

curl -i "$API_URL/health"

预期得到 HTTP 200 和 {"healthy": true}。在 AWS View 中确认三个路由键都使用 NONE,阶段已启用 AutoDeploy,健康检查调用返回 200。订单表仍应为空,因为这个请求检查健康状态,不会存储订单。

三个路由共用一个处理程序;订单写入和读取使用订单表,健康检查不使用。

测试有效订单和被拒绝的请求

本步骤中,你将创建两个订单,读取存储结果,并检查失败请求在影响业务数据前是否被拒绝。

HTTP 请求由方法、路径、请求头和可选的请求体组成。-X POST 选择写入方法,-H 提供 Content-Type: application/json,--data 发送 JSON 字符串。创建数量为 2 的订单:

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{"id":"order-one","quantity":2}'

预期得到 HTTP 201 Created,响应体包含 order-one、数量 2 和 total_cents: 600(2 × 250 + 100)。状态告诉客户端订单已存储;响应体报告计算结果。

使用不同数量创建第二个订单:

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{"id":"order-two","quantity":5}'

预期得到 HTTP 201 和 total_cents: 1350。通过 API 使用查询参数读取两个订单:

curl -i "$API_URL/orders?id=order-one"
curl -i "$API_URL/orders?id=order-two"

两个响应都应为 HTTP 200,且 ID、数量和总额与对应的创建响应相同。读取处理程序获取 DynamoDB 状态,不会根据客户端提供的新数量重新计算总额。

使用 CLI 独立检查第一个存储项目:

aws dynamodb get-item \
  --table-name labex-a02-orders \
  --key '{"id":{"S":"order-one"}}' \
  --consistent-read \
  --query Item

带类型的项目包含 quantity: {"N":"2"} 和 total_cents: {"N":"600"}。在 AWS View 中,将两条实际表记录与 POST 和 GET 调用响应进行比较。

语法有效的 JSON 请求也可能违反业务约定。发送负数数量:

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{"id":"bad-negative","quantity":-1}'

预期得到 HTTP 400 Bad Request 和 Use an id and an integer quantity from 1 to 10。编码为 JSON 字符串的数字也会被拒绝:

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{"id":"bad-type","quantity":"2"}'

最后,测试格式错误的 JSON 请求体:

curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{not-json'

最后两个请求也返回 HTTP 400。它们实际的处理程序事件可能出现在 CloudWatch Logs 中,但表仍必须只包含两个有效订单。请求验证在提供的应用中、PutItem 之前执行;仅匹配 API 路由不会验证请求体。

有效的读取 ID 可能没有对应的存储订单。查询一个尚未创建的 ID:

curl -i "$API_URL/orders?id=missing-order"

预期得到 HTTP 404 和 Order not found。这与 400 无效请求不同:ID 格式符合要求,但项目不存在。查看上方的 AWS View,检查实际的 201、200、400 和 404 响应;展开某次调用的 Show logs,比较输入与结果。这项手动 UI 检查补充了对远端记录和实际执行的独立只读检查。

AWS View 中两个通过验证的订单和保留的参考数据; 这个示例显示失败请求之后只有两个有效订单记录,以及未改变的参考数据。

向下滚动到 DynamoDB tables 卡片,在自己的环境中比较这些字段。

这里的 400 响应检验应用校验:请求到达 Lambda,由函数在写入前拒绝输入。后续 JWT 拒绝会在 API Gateway 阻止请求,使其无法到达 Lambda。结合执行日志和存储记录,判断是哪一层边界拒绝了请求。

移除 API 和本实验的订单资源

本步骤中,你将删除 API 的资源,同时保留无关的参考表、日志和准备好的执行角色。

你的资源清单包括 api-id.txt 中的 API ID、labex-a02-orders-api、它的 /aws/lambda/labex-a02-orders-api 日志组、labex-a02-orders,以及 OrdersData 内联角色策略。先删除 API,包括它的路由、集成和默认阶段:

aws apigatewayv2 delete-api --api-id "$API_ID"

移除函数及其调用策略:

aws lambda delete-function --function-name labex-a02-orders-api

删除独立的函数日志组:

aws logs delete-log-group --log-group-name /aws/lambda/labex-a02-orders-api

删除本实验的订单表及其数据:

aws dynamodb delete-table \
  --table-name labex-a02-orders \
  --query TableDescription.TableName \
  --output text

仅移除你添加到准备好的角色中的数据策略:

aws iam delete-role-policy --role-name labex-a02-execution --policy-name OrdersData

成功读取资源清单以证明删除完成。身份验证或网络错误不能作为删除证据:

aws apigatewayv2 get-apis --query 'Items[].Name'
aws lambda list-functions --query 'Functions[].FunctionName'
aws dynamodb list-tables --query TableNames

前两个列表应为空;表列表应只包含 labex-a02-reference。确认它的原始项目仍然存在:

aws dynamodb get-item \
  --table-name labex-a02-reference \
  --key '{"id":{"S":"reference"}}' \
  --query Item

预期看到 message: {"S":"keep unchanged"}。角色仍应保留无关的日志策略:

aws iam list-role-policies --role-name labex-a02-execution --query PolicyNames

预期只有 FunctionLogs。在 AWS View 中,API、Function 和调用记录应消失,参考表和 INFO platform reference keep unchanged 仍然保留。保留这些准备好的资源;测试凭证和 VM 关闭在资源检查之后处理。

总结

你使用准确的数据库策略和 API 调用授权,将三个 HTTP 路由连接到提供的 Lambda 应用。你创建并读取了计算总额不同的订单,检查了实际 DynamoDB 记录,并区分了成功、无效和订单不存在的响应。随后,你移除了 API 及其数据、函数、日志和策略,同时保留了参考资源。

进一步阅读 AWS 官方资料:HTTP API 的 Lambda 事件与响应格式; HTTP API 路由与查询参数。