如何使用AWS Amplify、Lambda、API Gateway和DynamoDB部署静态Web应用
构建现代Web应用通常涉及复杂的设置和服务器管理,但其实不必如此。Amazon Web Services(AWS)提供了一套强大的"无服务器"服务,让您能够构建和部署应用而无需担心底层基础设施。这意味着AWS会为您处理所有繁重的服务器、扩展和维护工作。
在本教程中,我们将引导您使用几个关键的AWS无服务器服务构建一个简单但功能完整的Web应用。您将学习如何将前端(用户看到的界面)与强大的后端(处理数据的部分)连接起来,实现自动高效的扩展。
目录
- 我们将构建什么:无服务器求和计算器
- 核心AWS服务介绍
- 先决条件:需要准备什么
- 开始构建:如何构建我们的无服务器Web应用
- 如何测试应用:是否正常工作?
- 常见问题及解决方案
- 下一步:增强您的应用
- 结论
我们将构建什么:无服务器求和计算器
我们将创建一个简单的求和计算器Web应用。该应用将允许用户输入两个数字,将其发送到我们的AWS后端进行计算,存储结果,然后将求和结果返回给用户。
计算器的工作流程如下:
- 接收用户输入:在简单网页中输入两个数字
- 后端处理:这些数字将发送到运行在AWS Lambda上的代码,进行相加计算
- 数据存储:计算详情(两个数字、它们的和以及计算时间)将保存在名为DynamoDB的高速数据库中
- 显示结果:求和结果将返回到网页并显示给用户
这个项目是理解无服务器架构核心概念以及不同AWS服务如何协同工作创建动态Web应用的绝佳方式。
核心AWS服务介绍
在深入之前,让我们熟悉将要使用的主要AWS服务。将它们视为专业工具,每个都有特定职责,共同构建我们的应用。
AWS Lambda:想象一下有一个只在收到特定任务时才激活的小机器人。这就是Lambda!它是"无服务器计算"服务,意味着您不需要管理任何服务器。只需上传代码(本例中的计算器逻辑),Lambda只在需要时运行它。
为什么使用它:它处理我们的后端数学计算(数字相加)。当用户请求求和时,Lambda"唤醒",执行计算,然后返回休眠状态。这很高效且成本效益高,因为您只需为代码实际运行时间付费。
Amazon API Gateway:将其视为后端的门卫或接待员。当您的网页想要与Lambda函数通信时,它不会直接对话。而是将请求发送到API Gateway。
为什么使用它:API Gateway安全地接收来自网页的请求(如"请计算这两个数字的和"),然后告诉正确的Lambda函数唤醒并处理它。它充当后端的安全入口点,确保只有授权请求才能通过。
Amazon DynamoDB:这是我们高速、灵活的数据库。与传统数据库不同,DynamoDB是NoSQL数据库,非常适合快速高效处理大量数据而无需固定结构。
为什么使用它:我们将使用DynamoDB存储计算历史(输入的数字及其和)。它设计用于处理大量流量而不会减慢速度,非常适合需要快速存储和检索数据的Web应用。
AWS Amplify:Amplify就像您的Web和移动应用个人施工队。它简化了构建、部署和托管前端应用的过程,并与其他AWS服务无缝集成。
为什么使用它:我们将使用Amplify托管简单的HTML、CSS和JavaScript网页。它提供了将网站上线互联网的简单方法,为我们处理所有复杂的部署步骤。
先决条件:需要准备什么
要跟随本教程,您应该具备:
- AWS账户:这是访问所有AWS服务所必需的
- Python基础知识:我们的后端逻辑将使用Python编写
- REST API理解:了解API是什么以及RESTful API的工作原理会有帮助
- HTML/CSS/JavaScript熟悉度:我们的前端将使用这些标准Web技术构建
- NoSQL基础知识:虽然不是严格要求,但理解数据库中的键值对概念会有益
开始构建:如何构建我们的无服务器Web应用
我们将逐步构建应用,从数据库开始,然后是后端代码,通过API连接它们,最后部署前端。
步骤1:使用Amazon DynamoDB设置数据库
我们的计算器需要存储计算结果的地方。为此,我们将使用Amazon DynamoDB。
-
导航到DynamoDB控制台:
首先登录AWS管理控制台。在顶部搜索栏中输入"DynamoDB"并从结果中选择"DynamoDB"。这将带您进入DynamoDB仪表板。 -
创建新表:
在DynamoDB仪表板上,找到并点击"Create table"按钮。 -
配置表:
需要为表添加以下信息:- 表名:输入myTable
- 分区键:输入ID
分区键是DynamoDB中每个唯一项的主要标识符。将其视为每个记录的唯一ID号。它帮助DynamoDB快速查找和分布数据。
保留所有其他设置为默认值。
-
完成表创建:
点击页面底部的"Create table"按钮。DynamoDB现在将创建您的表,通常需要几秒钟。 -
重要:记下表ARN:
表创建后,点击列表中的名称(myTable)进入其详细信息页面。在"Summary"选项卡下,您将找到显示"ARN"的部分。这是在AWS中唯一标识DynamoDB表的标识符。
复制整个ARN并安全保存(如记事本)。稍后设置Lambda函数权限时需要它。
步骤2:使用AWS Lambda创建后端逻辑
现在我们有了数据库,让我们创建操作的核心:执行加法并保存结果的Lambda函数。
-
导航到Lambda控制台:
在AWS管理控制台搜索栏中输入"Lambda"并从结果中选择"Lambda"。这将带您进入Lambda仪表板。
然后点击仪表板上的"Create function"按钮创建新函数。 -
配置函数:
配置您的函数:- 选择"Author from scratch"
- 函数名:给函数起有意义的名字,例如SumCalculatorFunction
- 运行时:选择"Python 3.9"(或可用的最新Python 3.x运行时)
- 架构:保留为x86_64(默认)
- 权限:暂时保留默认执行角色
-
创建函数:
点击底部的"Create function"按钮创建函数。 -
编写Lambda函数代码:
函数创建后,您将进入其配置页面。向下滚动到"Code source"部分。这是编写或粘贴Python代码的地方。
您将看到默认的lambda_function.py文件。将其内容替换为以下Python代码:
import json
import boto3
import time
from botocore.exceptions import ClientError# 创建DynamoDB客户端
dynamodb = boto3.resource('dynamodb')
# 指定要交互的DynamoDB表
table = dynamodb.Table('myTable')def lambda_handler(event, context):# 从event数据中提取数字num1 = event.get('num1')num2 = event.get('num2')# 基本验证:检查是否提供了两个数字if num1 is None or num2 is None:return {'statusCode': 400,'body': json.dumps({'message': 'Both num1 and num2 are required'})}# 计算两个数字的和sum_result = num1 + num2# 为DynamoDB项生成唯一IDpartition_key = str(int(time.time() * 1000))sort_key = str(int(time.time()))# 准备要存储在DynamoDB表中的数据item = {'ID': partition_key,'Timestamp': sort_key,'num1': num1,'num2': num2,'sum': sum_result}# 尝试将项存储在DynamoDB表中try:table.put_item(Item=item)except ClientError as e:return {'statusCode': 500,'body': json.dumps({'message': f'Error storing data in DynamoDB: {e.response["Error"]["Message"]}'})}# 如果一切成功,返回成功消息和计算详情return {'statusCode': 200,'body': json.dumps({'message': 'Sum calculated and stored successfully','result': {'ID': partition_key,'Timestamp': sort_key,'num1': num1,'num2': num2,'sum': sum_result}})}
-
部署代码:
粘贴代码后,点击代码编辑器上方的"Deploy"按钮。这将保存更改并使其生效。 -
测试Lambda函数:
代码部署后,测试Lambda函数(可选但推荐)。点击"Deploy"按钮旁边的"Test"按钮。系统将提示配置测试事件。
选择"New event",对于"Event template",选择"hello-world"。将"Event JSON"框中的示例JSON替换为以下测试数据:{ "num1": 5, "num2": 10 }
给测试事件命名(例如testSum)。然后点击"Save"和"Test"。您应该看到"Status: Succeeded"消息,在"Execution result"选项卡中,您将看到statusCode: 200和计算的和。这确认了Lambda函数正常工作!
更新Lambda函数权限(IAM角色策略)
我们的Lambda函数当前具有默认权限,通常不包括访问DynamoDB的权限。我们需要明确授予它写入myTable的权限。我们将通过IAM角色策略实现。
-
导航到权限:
在Lambda函数配置页面上,点击"Configuration"选项卡。然后选择"Permissions"子选项卡。
您将看到带有"Role name"的"Execution role"部分。点击此"Role name"(它将是一个链接)。这将带您进入IAM控制台,特别是与Lambda函数关联的角色详细信息。 -
添加权限:
在IAM角色页面上,您将看到标题为"Permissions policies"的部分。点击"Add permissions"然后选择"Create inline policy"。 -
配置策略:
您将看到"Policy editor"。点击"Visual editor"选项卡(如果尚未选择)。- 服务:点击"Choose a service"并搜索选择"DynamoDB"
- 操作:在"Actions"部分,展开"Write"。搜索PutItem并选择复选框
- 资源:点击"Specific",点击"Add ARN",粘贴之前从步骤1复制的DynamoDB表ARN
-
审查并创建策略:
点击"Next: Review policy"。给策略起有意义的名字,例如DynamoDBPutItemPolicy。
审查策略JSON确保它授予对特定myTable ARN的dynamodb:PutItem权限。然后点击"Create policy"。
现在您的Lambda函数具有写入DynamoDB表所需的权限!
步骤3:使用Amazon API Gateway连接前端和后端
现在我们有了Lambda函数,我们需要让网页能够与之通信。这就是API Gateway的作用。
在AWS管理控制台搜索栏中输入"API Gateway"并从结果中选择"API Gateway"。
-
创建新API:
在API Gateway仪表板上,在"REST API"下,点击"Build"按钮。 -
配置API设置:
- 选择协议:选择REST
- 创建新API:选择New API
- API名称:给API起清晰的名字,例如SumCalculatorAPI
- 端点类型:选择Regional(默认)
然后点击"Create API"。
-
创建资源:
创建API后,您将进入其仪表板。从"Actions"下拉菜单中,选择"Create Resource"。- 资源名称:可以保留为calculator或任何有意义的名称
- 路径部分:保留为/(根路径)
然后点击"Create Resource"。
-
创建方法(POST):
在左导航面板中选择根资源(/),从"Actions"下拉菜单中,选择"Create Method"。
从出现的下拉列表中选择"POST"并点击旁边的复选标记按钮。
设置详情:- 集成类型:选择Lambda Function
- 使用Lambda代理集成:勾选此框
- Lambda区域:选择创建Lambda函数的AWS区域
- Lambda函数:开始输入Lambda函数名称并从下拉列表中选择
然后点击"Save"。可能会弹出确认添加Lambda权限的窗口。点击"OK"。
-
启用CORS:
这是关键步骤!我们的前端(将托管在AWS Amplify上,不同的域)需要权限与API Gateway端点通信。
仍然选择根资源(/),从"Actions"下拉菜单中,选择"Enable CORS"。将出现对话框。可以接受此简单应用的默认设置。确保POST在"Methods"下被选中,并且"Access-Control-Allow-Origin"设置为'*'。
然后点击"Enable CORS and replace existing CORS headers"。提示时点击"Yes, replace existing values"。 -
部署API:
对API Gateway方法的更改在API"部署"到"阶段"之前不会生效。从"Actions"下拉菜单中,选择"Deploy API"。- 部署阶段:选择"[New Stage]"
- 阶段名称:给阶段起名字,例如dev
- 阶段描述:(可选)添加描述
然后点击"Deploy"。
-
记下调用URL:
部署后,您将进入"Stages"视图。在左导航面板中点击新创建的阶段(例如dev)。
您将看到"Invoke URL"。这是API端点的公共URL。
复制整个调用URL并安全保存。这是我们的前端将用于向Lambda函数发送数字的URL!
步骤4:创建前端Web应用
现在我们的后端已准备好接收请求和处理计算,让我们构建用户将交互的简单网页。我们将使用基本的HTML、CSS和JavaScript。
您需要在计算机上的新文件夹中创建三个文件:index.html、app.js和style.css。
index.html(网页结构)
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Sum Calculator</title><link rel="stylesheet" href="style.css">
</head>
<body><h1>SUM CALCULATOR</h1><form id="calculatorForm"><label for="num1">Number 1:</label><input type="number" id="num1" required><label for="num2">Number 2:</label><input type="number" id="num2" required><button type="submit" id="calculateBtn"><span id="btnText">CALCULATE</span><span id="loadingSpinner" class="spinner d-none"></span> </button></form><div id="errorAlert" class="alert d-none"><strong style="color: red;">Error!</strong> <span id="errorMessage"></span></div><div id="resultSection" class="result-box d-none"><h2>Calculation Result:</h2><p><strong>Transaction ID:</strong> <span id="resultId"></span></p><p><strong>Timestamp:</strong> <span id="resultTimestamp"></span></p><p><strong>Numbers:</strong> <span id="resultNum1"></span> + <span id="resultNum2"></span></p><p><strong>Sum:</strong> <span id="resultSum"></span></p></div><script src="app.js"></script>
</body>
</html>
app.js(前端逻辑)
记住将
document.addEventListener('DOMContentLoaded', function() {const API_ENDPOINT = 'https://YOUR_API_GATEWAY_INVOKE_URL_HERE.execute-api.us-east-1.amazonaws.com/dev';const calculatorForm = document.getElementById('calculatorForm');const num1Input = document.getElementById('num1');const num2Input = document.getElementById('num2');const calculateBtn = document.getElementById('calculateBtn');const btnText = document.getElementById('btnText');const loadingSpinner = document.getElementById('loadingSpinner');const errorAlert = document.getElementById('errorAlert');const errorMessage = document.getElementById('errorMessage');const resultSection = document.getElementById('resultSection');const resultId = document.getElementById('resultId');const resultTimestamp = document.getElementById('resultTimestamp');const resultNum1 = document.getElementById('resultNum1');const resultNum2 = document.getElementById('resultNum2');const resultSum = document.getElementById('resultSum');calculatorForm.addEventListener('submit', function(event) {event.preventDefault();errorAlert.classList.add('d-none');resultSection.classList.add('d-none');const num1 = parseFloat(num1Input.value);const num2 = parseFloat(num2Input.value);if (isNaN(num1) || isNaN(num2)) {showError('Please enter valid numbers');return;}setLoadingState(true);const requestData = {num1: num1,num2: num2};fetch(API_ENDPOINT, {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify(requestData)}).then(response => {if (!response.ok) {return response.json().then(errData => {throw new Error(errData.message || 'Server error');});}return response.json();}).then(data => {setLoadingState(false);if (data.statusCode && data.statusCode === 200 && data.body) {try {const resultData = typeof data.body === 'string' ? JSON.parse(data.body) : data.body;displayResult(resultData);} catch (err) {showError('Error parsing the result: ' + err.message);}} else {showError('Unexpected API response format');}}).catch(error => {setLoadingState(false);showError(error.message || 'An error occurred while communicating with the API');});});function showError(message) {errorMessage.textContent = message;errorAlert.classList.remove('d-none');}function setLoadingState(isLoading) {if (isLoading) {btnText.textContent = 'Calculating...';loadingSpinner.classList.remove('d-none');calculateBtn.disabled = true;} else {btnText.textContent = 'CALCULATE';loadingSpinner.classList.add('d-none');calculateBtn.disabled = false;}}function displayResult(data) {if (!data.result) {showError('No result data in the API response');return;}const result = data.result;let timestampDisplay = result.Timestamp;if (result.Timestamp && !isNaN(result.Timestamp)) {const date = new Date(parseInt(result.Timestamp) * 1000);timestampDisplay = date.toLocaleString();}resultId.textContent = result.ID || 'N/A';resultTimestamp.textContent = timestampDisplay || 'N/A';resultNum1.textContent = result.num1;resultNum2.textContent = result.num2;resultSum.textContent = result.sum;resultSection.classList.remove('d-none');}
});
style.css(页面样式)
body {background-color: #222629;color: #FFFFFF;font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;padding: 20px;display: flex;flex-direction: column;align-items: center;min-height: 100vh;margin: 0;
}h1 {color: #86C232;margin-bottom: 30px;
}label {font-size: 18px;margin-right: 10px;display: inline-block;width: 80px;text-align: right;
}input[type="number"] {background-color: #333;border: 1px solid #444;color: #FFFFFF;padding: 8px 12px;font-size: 16px;border-radius: 5px;margin-bottom: 15px;width: 150px;outline: none;
}input[type="number"]:focus {border-color: #86C232;box-shadow: 0 0 0 3px rgba(134, 194, 50, 0.5);
}button {background-color: #86C232;border: none;color: #FFFFFF;font-size: 18px;font-weight: bold;padding: 10px 20px;border-radius: 5px;cursor: pointer;transition: background-color 0.3s ease;display: flex;align-items: center;justify-content: center;gap: 10px;margin-top: 20px;width: 180px;
}button:hover:not(:disabled) {background-color: #6a9b2b;
}button:disabled {background-color: #555;cursor: not-allowed;
}#calculatorForm {background-color: #333;padding: 30px;border-radius: 8px;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);display: flex;flex-direction: column;align-items: flex-start;margin-bottom: 30px;
}.spinner {border: 3px solid rgba(255, 255, 255, 0.3);border-top: 3px solid #FFFFFF;border-radius: 50%;width: 20px;height: 20px;animation: spin 1s linear infinite;
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}.alert {background-color: #330d0d;border: 1px solid #ff4d4d;color: #ff4d4d;padding: 10px 15px;border-radius: 5px;margin-top: 20px;width: 100%;max-width: 400px;text-align: center;
}.result-box {background-color: #333;border: 1px solid #86C232;padding: 20px;border-radius: 8px;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);width: 100%;max-width: 400px;margin-top: 20px;animation: fadeIn 0.5s ease-in-out;
}.result-box h2 {color: #86C232;margin-top: 0;border-bottom: 1px solid #444;padding-bottom: 10px;margin-bottom: 15px;
}.result-box p {margin-bottom: 8px;
}.result-box strong {color: #99EE33;
}.d-none {display: none !important;
}@keyframes fadeIn {from { opacity: 0; transform: translateY(10px); }to { opacity: 1; transform: translateY(0); }
}
步骤5:使用AWS Amplify部署前端
最后,让我们使用AWS Amplify将前端Web应用上线。Amplify使托管静态网站变得异常简单。
-
准备前端文件:
确保所有三个文件(index.html、app.js、style.css)在计算机上的单个文件夹中。
将此文件夹压缩为ZIP文件。确保index.html文件位于ZIP文件的根目录(不在ZIP内的另一个子文件夹中)。 -
导航到AWS Amplify控制台:
在AWS管理控制台搜索栏中输入"Amplify"并从结果中选择"AWS Amplify"。 -
无Git提供商部署:
在Amplify控制台上,您将看到连接到Git提供商的选项。为简化本教程,我们选择手动部署。
在"Deploy without Git provider"下,点击"Deploy"。 -
上传文件:
- 应用名称:给应用起名字,例如SumCalculatorWebApp
- 环境名称:可以使用dev或main
- 拖放或浏览文件:点击"drag and drop"区域或"Choose files"按钮
- 选择在步骤5.1中创建的ZIP文件
-
审查并部署:
现在点击"Save and Deploy"。Amplify将获取ZIP文件,提取其内容,并将静态网站部署到由AWS CloudFront提供的全球内容分发网络。这使您的网站在世界任何地方都能快速加载。此过程可能需要几分钟。 -
访问实时应用:
部署完成后,Amplify将提供"Domain" URL。点击此URL。这是您的实时公共Web应用!
如何测试应用:是否正常工作?
现在是最激动人心的部分——通过以下步骤测试我们完全无服务器的Web应用:
- 打开Amplify应用URL:导航到AWS Amplify提供的"Domain" URL
- 输入数字:在"Number 1"和"Number 2"输入字段中,输入任意两个数字(例如5和10)
- 点击"CALCULATE":点击"CALCULATE"按钮。您应该短暂看到按钮上显示"Calculating..."
- 观察结果:如果一切设置正确,您应该看到"Calculation Result"框出现在表单下方,显示交易ID、时间戳、数字及其和!
- 在DynamoDB中验证(可选):
- 返回DynamoDB控制台
- 点击myTable
- 点击"Explore table items"选项卡
- 您现在应该看到新条目,对应于在Web应用上执行的每个计算,存储了ID、时间戳、num1、num2和sum!
常见问题及解决方案
有时事情不会第一次就完美工作。以下是一些常见问题及解决方法:
CORS错误:
- 症状:Web浏览器开发者控制台显示错误,如"Access to fetch at '...' from origin '...' has been blocked by CORS policy"
- 解决方案:返回步骤3第5部分(启用CORS)并确保已为API启用CORS。确保POST被选中且Access-Control-Allow-Origin正确配置
Lambda超时:
- 症状:Web应用持续显示"Calculating..."或出现通用错误,在Lambda函数日志中看到"Task timed out"
- 解决方案:增加Lambda函数的"Timeout"设置
DynamoDB权限错误:
- 症状:Lambda函数执行失败,日志中看到错误如"User: ... is not authorized to perform: dynamodb:PutItem on resource: ..."
- 解决方案:确保正确添加了dynamodb:PutItem操作并指定了myTable的ARN作为资源
不正确的API Gateway调用URL:
- 症状:前端发出请求但立即失败或浏览器控制台中出现网络错误
- 解决方案:仔细检查app.js文件中的API_ENDPOINT常量是否与从API Gateway复制的调用URL完全一致
下一步:增强您的应用
这个计算器是一个很好的起点,但您可以显著扩展它:
- 添加身份验证:实现用户登录和注册,使只有授权用户才能使用计算器
- 实现错误处理:根据后端发送的内容显示特定错误消息,使前端更健壮
- 创建计算历史视图:扩展前端以获取和显示DynamoDB中存储的所有过去计算列表
- 添加输入验证:在前端和后端实现更健壮的验证以处理非数字输入或其他边缘情况
- 实现实时更新:使用AWS AppSync或带有API Gateway的WebSocket将新计算结果实时推送到前端,无需页面刷新
结论
恭喜!您已成功在AWS上构建并部署了功能完整的无服务器Web应用。您已经了解了如何利用强大的服务如AWS Lambda、Amazon API Gateway、Amazon DynamoDB和AWS Amplify来创建可扩展、成本效益高且维护量低的应用。
这种架构非常强大,因为它可以自动扩展以处理数千甚至数百万用户,而您无需管理单个服务器。请记住在完成实验后清理AWS资源以避免不必要的费用。
进一步学习资源
- AWS Lambda文档:深入了解无服务器函数
- Amazon DynamoDB开发指南:了解更多关于NoSQL数据库的知识
- Amazon API Gateway教程:探索构建各种类型的API
- AWS Amplify文档:发现构建和部署全栈应用的更多功能
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
公众号二维码