在这篇文章中,学习如何利用AWS云开发工具包(CDK)来构建和部署一个无服务器的AWS Lambda函数,该函数使用Twilio的可编程短信API向用户发送短信。我们将使用Java来定义AWS CDK应用程序和AWS Lambda处理程序,并使用Twilio的Java Helper Library来处理SMS API的所有组件。
前提条件
在本教程中,你需要在你的系统上设置Java,因为我们将使用它来定义我们的AWS CDK应用程序和AWS Lambda处理器。除此之外,你还需要安装AWS CLI和AWS CDK CLI,以便分别配置AWS凭证和构建你的CDK应用程序。最后,你将需要创建一个AWS账户来部署应用程序,以及一个Twilio账户和一个支持短信的电话号码来发送短信。请参考下面的列表来设置本教程所需的一切。
- 设置Java开发环境
- 安装AWS CLI
- 安装AWS CDK
- 如果你还没有AWS账户,请创建一个AWS账户。
- 安装Docker,将Lambda项目容器化。
- 拥有支持短信的电话号码的Twilio账户。如果你还没有,请注册一个免费的Twilio试用。参考这个指南,获得你的第一个Twilio电话号码。
构建CDK应用程序
创建一个新的AWS CDK项目
要创建一个新的AWS CDK项目,你需要在你的系统上安装CDK CLI。首先为我们的CDK项目创建一个新的目录,并导航到其中:
mkdir twilio-java-helper-cdk-lambda
cd twilio-java-helper-cdk-lambda
接下来,使用CDK CLI运行cdk init 命令,使用Typescript创建一个新的CDK项目。app 参数指定了我们将用于初始化项目的模板:
cdk init app --language java
执行上述命令可以创建一个新的CDK项目,其中有一个CDK栈。生成的项目是一个Java Maven项目。在IntelliJ IDE或你选择的其他IDE中打开它。
CDK支持多种语言,如TypeScript、Python、Java和C#。你可以选择使用你所熟悉的任何语言。
定义AWS Lambda函数
在本节中,我们将定义一个基于Java的AWS Lambda函数,它将使用Twilio Java Helper Library与SMS APIs进行交互。
为Lambda函数创建一个新的Maven项目
在CDK项目的根部创建一个lambda目录,方法是右击项目名称,选择新建>目录。选定lambda目录后,从工具栏上点击File > New project ,创建一个新的Maven项目,命名为twilio-java-helper-send-ms**。 为项目选择Java语言,使用JDK版本11。

点击 "创建 "创建该项目。当IDE提示你打开该项目时,选择新建窗口 。

在本节的剩余部分,我们将在新窗口中对刚刚创建的twilio-java-helper-send-ms项目进行操作。
为Maven项目定义一个组ID
按照惯例,Maven项目使用相反的域名作为组ID。根据你拥有的域名添加一个组ID。本教程使用com.maskaravivek 作为组ID。用它更新你的pom.xml文件:
<project
...
<groupId>com.maskaravivek</groupId>
...
</project>
在src/main/java 目录中创建com/maskaravivek 目录,以匹配项目的组 ID。
在 pom.xml 中添加依赖项
在pom.xml 文件中定义所需的依赖项。首先,添加AWS Java SDK和AWS Lambda Java核心库的依赖项。打开pom.xml,在<project> 标签中添加以下定义:
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-core</artifactId>
<version>1.12.213</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
由于我们将通过API Gateway暴露Lambda函数,该函数需要代理API Gateway的请求和响应。因此,也要在pom.xml文件的<dependencies> 部分中添加AWS Lambda Java Events库的依赖性:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.11.0</version>
</dependency>
我们还需要Gson库来处理JSON:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
最后,为Twilio Java Helper Library添加一个依赖项,以便与SMS APIs一起工作:
<dependency>
<groupId>com.twilio.sdk</groupId>
<artifactId>twilio</artifactId>
<version>8.30.0</version>
</dependency>
注意:添加完依赖项后,你需要同步maven依赖项,方法是右键点击项目,选择Maven > Reload项目。
定义请求和响应类
Lambda函数代理了来自API网关的HTTP请求和响应,因此我们需要为它们创建Java Bean类。
在src/main/java/com/maskaravivek 包中为 Lambda 输入创建一个SendSmsRequest.java类。在IntelliJ IDE中,你可以通过右击包名,选择新建>Java类来创建一个新的Java类。在这个文件中添加以下代码:
package com.maskaravivek;
public class SendSmsRequest {
private String phoneNumber;
private String message;
public SendSmsRequest(String phoneNumber, String message) {
this.phoneNumber = phoneNumber;
this.message = message;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getMessage() {
return message;
}
}
Lambda处理器使用请求类来解析来自APIGatewayV2HTTPEvent 事件的JSON输入body 。
同时,在src/main/java/com/maskaravivek 包中为 Lambda 输出创建一个SendSmsResponse.java类:
package com.maskaravivek;
public class SendSmsResponse {
private String messageSid;
public SendSmsResponse(String messageSid) {
this.messageSid = messageSid;
}
}
body 该类被Lambda处理程序用来设置APIGatewayV2HTTPResponse 。
定义Lambda处理程序
接下来,定义AWS Lambda处理程序,它接受一个包含接收方电话号码和消息的JSON输入有效载荷,并使用Twilio的可编程短信API来发送消息。
在src/main/java/com/maskaravivek包中创建一个TwilioJavaHelperSendSmsHandler.java 类,并在其中添加以下代码片段:
package com.maskaravivek;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.twilio.Twilio;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse;
import java.util.HashMap;
public class TwilioJavaHelperSendSmsHandler implements RequestHandler<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) {
}
}
注意,Lambda处理程序实现了handleRequest 方法,该方法将APIGatewayV2HTTPEvent 作为输入并返回APIGatewayV2HTTPResponse 。
接下来添加上述Lambda处理程序的实现:
public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) {
final String ACCOUNT_SID = System.getenv("TWILIO_ACCOUNT_SID");
final String AUTH_TOKEN = System.getenv("TWILIO_AUTH_TOKEN");
final String TWILIO_PHONE_NUMBER = System.getenv("TWILIO_PHONE_NUMBER");
context.getLogger().log("Input: " + event.getBody());
SendSmsRequest sendSmsRequest = gson.fromJson(event.getBody(), SendSmsRequest.class);
Twilio.init(ACCOUNT_SID, AUTH_TOKEN);
Message message = Message.creator(new PhoneNumber(sendSmsRequest.getPhoneNumber()),
new PhoneNumber(TWILIO_PHONE_NUMBER),
sendSmsRequest.getMessage()).create();
System.out.println(message.getSid());
SendSmsResponse sendSmsResponse = new SendSmsResponse(message.getSid());
APIGatewayV2HTTPResponse response = new APIGatewayV2HTTPResponse();
response.setIsBase64Encoded(false);
response.setStatusCode(200);
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/json");
response.setHeaders(headers);
response.setBody(gson.toJson(sendSmsResponse));
return response;
}
该处理程序使用Twilio Java辅助库中的Message.creator(...).create() 函数,向接收者的电话号码发送短信。
注意,处理程序从环境变量中获取TWILIO_ACCOUNT_SID 、TWILIO_AUTH_TOKEN 和TWILIO_PHONE_NUMBER 的值。在后面的章节中,我们将看到如何在定义Lambda函数时设置这些环境变量。
构建Maven项目
点击IDE中的Build > Build Project ,构建Maven项目,检查其编译是否没有错误**。**另外,你也可以使用mvn,用以下命令编译该应用程序:
mvn clean compile
确认项目编译成功后,您可以选择更新Lambda Maven项目的版本,方法是将pom.xml文件中的<version> 标签从快照修改为发布版本:
<version>1.0</version>
注意,更新到发布版本是完全可选的,但在部署时使用发布版本标签是个好做法。
定义AWS CDK栈
现在Lambda处理器已经定义好了,切换回CDK Maven项目,即IntelliJ IDE中的twilio-java-helper-cdk-lambda窗口。
添加CDK依赖项
在CDK项目的pom.xml文件中添加以下依赖项。该pom.xml文件将在你初始化项目时由CDK自动生成:
<dependencies>
<!-- Add after existing dependencies -->
<dependency>
<groupId>software.amazon.jsii</groupId>
<artifactId>jsii-runtime</artifactId>
<version>1.58.0</version>
</dependency>
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>apigatewayv2-alpha</artifactId>
<version>2.22.0-alpha.0</version>
</dependency>
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>apigatewayv2-integrations-alpha</artifactId>
<version>2.22.0-alpha.0</version>
</dependency>
</dependencies>
为AWS Lambda函数添加一个CDK构架
AWS CDK构造是云组件的构建块,它封装了使用一个或多个AWS服务的配置细节和胶合逻辑。AWS CDK为其大多数流行的AWS服务提供构造。
在本教程的开始,当我们使用app 模板生成CDK项目时,在src/main/java/com/myorg目录下生成了一个TwilioJavaHelperCdkLambdaStack.java文件。现在我们将为AWS Lambda函数和API Gateway HTTP APIs定义结构。首先,在src/main/java/com/myorg/TwilioJavaHelperCdkLambdaStack**.java文件中添加以下导入。
import software.amazon.awscdk.BundlingOptions;
import software.amazon.awscdk.CfnOutput;
import software.amazon.awscdk.CfnOutputProps;
import software.amazon.awscdk.DockerVolume;
import software.amazon.awscdk.Duration;
import software.amazon.awscdk.services.apigatewayv2.alpha.AddRoutesOptions;
import software.amazon.awscdk.services.apigatewayv2.alpha.HttpApi;
import software.amazon.awscdk.services.apigatewayv2.alpha.HttpApiProps;
import software.amazon.awscdk.services.apigatewayv2.alpha.HttpMethod;
import software.amazon.awscdk.services.apigatewayv2.alpha.PayloadFormatVersion;
import software.amazon.awscdk.services.apigatewayv2.integrations.alpha.HttpLambdaIntegration;
import software.amazon.awscdk.services.apigatewayv2.integrations.alpha.HttpLambdaIntegrationProps;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.lambda.Code;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.FunctionProps;
import software.amazon.awscdk.services.lambda.Runtime;
import software.amazon.awscdk.services.logs.RetentionDays;
import software.amazon.awscdk.services.s3.assets.AssetOptions;
import software.constructs.Construct;
import static java.util.Collections.singletonList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import static software.amazon.awscdk.BundlingOutput.ARCHIVED;
为了避免任何人工干预,我们将为CDK栈中的Lambda函数定义打包指令。我们将使用Code.fromAsset API以及Maven项目的捆绑指令。这些命令将构建Maven项目,并将jar文件复制到asset-output目录。这些命令将在使用Java 11的官方镜像创建的Docker容器内执行。
在TwilioJavaHelperLambdaStack.java文件 的构造函数中添加以下代码片段*。* 生成的类应该有两个构造函数,我们需要在构造函数中添加代码,其参数为StackProps:
public TwilioJavaHelperLambdaCdkStack(final Construct scope, final String id, final StackProps props) {
super(scope, id, props);
List<String> sendSmsFnPackagingInstructions = Arrays.asList(
"/bin/sh",
"-c",
"cd twilio-java-helper-send-sms " +
"&& mvn clean install " +
"&& cp /asset-input/twilio-java-helper-send-sms/target/twilio-java-helper-send-sms.jar /asset-output/"
);
BundlingOptions.Builder builderOptions = BundlingOptions.builder()
.command(sendSmsFnPackagingInstructions)
.image(Runtime.JAVA_11.getBundlingImage())
.volumes(singletonList(
// Mount local .m2 repo to avoid download all the dependencies again inside the container
DockerVolume.builder()
.hostPath(System.getProperty("user.home") + "/.m2/")
.containerPath("/root/.m2/")
.build()
))
.user("root")
.outputType(ARCHIVED);
}
接下来,定义AWS Lambda函数的CDK构造,就在你上面定义的代码之后。我们将把TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN 和TWILIO_PHONE_NUMBER 作为环境变量传递给 Lambda 函数。另外,如上所述,我们使用Code.fromAsset API来捆绑Lambda代码。它将压缩档案上传到一个临时的AWS S3桶,以便部署:
HashMap<String, String> environmentMap = new HashMap<>();
environmentMap.put("TWILIO_ACCOUNT_SID", System.getenv("TWILIO_ACCOUNT_SID"));
environmentMap.put("TWILIO_AUTH_TOKEN", System.getenv("TWILIO_AUTH_TOKEN"));
environmentMap.put("TWILIO_PHONE_NUMBER", System.getenv("TWILIO_PHONE_NUMBER"));
Function sendSmsFunction = new Function(this, "TwilioJavaHelperSendSms", FunctionProps.builder()
.runtime(Runtime.JAVA_11)
.code(Code.fromAsset("lambda/", AssetOptions.builder()
.bundling(builderOptions
.command(sendSmsFnPackagingInstructions)
.build())
.build()))
.handler("com.maskaravivek.TwilioJavaHelperSendSmsHandler")
.memorySize(1024)
.timeout(Duration.minutes(1))
.environment(environmentMap)
.logRetention(RetentionDays.ONE_WEEK)
.build());
注意,FunctionProps.builder() ,可以用来设置Lambda函数的各种属性,如内存、超时、运行时间和环境变量。
为API网关API添加CDK构造
现在我们已经定义了AWS Lambda函数,继续使用HTTP API暴露该函数。我们将使用AWS API Gateway来通过HTTP公开我们的API。用下面的结构来定义服务,你应该在上一节添加的代码下面添加,而且还是在同一个构造函数中:
HttpApi httpApi = new HttpApi(this, "twilio-java-helper-demo", HttpApiProps.builder()
.apiName("twilio-java-helper-demo")
.build());
对于这个构造的最后一节,我们将使用HttpLambdaIntegration ,将Lambda暴露为HTTP API,并将其暴露为/sendSms 端点上的一个POST 方法:
HttpLambdaIntegration httpLambdaIntegration = new HttpLambdaIntegration(
"this",
sendSmsFunction,
HttpLambdaIntegrationProps.builder()
.payloadFormatVersion(PayloadFormatVersion.VERSION_2_0)
.build()
);
httpApi.addRoutes(AddRoutesOptions.builder()
.path("/sendSms")
.methods(singletonList(HttpMethod.POST))
.integration(httpLambdaIntegration)
.build()
);
new CfnOutput(this, "HttApi", CfnOutputProps.builder()
.description("Url for Http Api")
.value(httpApi.getApiEndpoint())
.build());
部署CDK应用程序
现在我们已经在CDK栈中为我们的云应用程序定义了资源,继续把它部署到AWS账户中。在部署应用程序之前,将Twilio账户SID和Auth Token设置为环境变量。你可以从Twilio控制台获得这些凭证。除了凭证之外,还要把你的twilio电话号码设置为环境变量。如果你没有twilio的电话号码,你可以按照这个指南获得一个电话号码:
export TWILIO_ACCOUNT_SID=<account_sid>
export TWILIO_AUTH_TOKEN=<auth_token>
export TWILIO_PHONE_NUMBER=<your_twilio_phone_number>
部署CDK应用程序有三个步骤。在部署应用程序之前,请确保Docker正在运行,并且AWS凭证已在你的系统上配置好。
引导应用程序
第一步是引导应用程序。引导提供AWS CDK可能需要的资源,以部署你的应用程序。这些资源可能包括一个用于存储部署相关文件的S3桶和用于授予部署权限的IAM角色。在你的CDK应用程序的根目录下执行以下命令来引导堆栈:
cdk bootstrap
合成应用程序
第二步是通过运行cdk synth 命令来合成栈。该命令执行你的CDK应用程序,这将导致其中定义的资源被翻译成基于YAML的AWS CloudFormation模板。在你的CDK应用程序的根目录下运行以下命令来合成堆栈:
cdk synth
注意,如果你的CDK应用程序包含多个堆栈,那么你需要在执行synth 命令时指定堆栈的名称。我们不需要担心这个问题,因为我们的演示程序只包含一个栈。另外,synth 命令会捕捉堆栈中的逻辑错误,如果堆栈没有被正确定义,会抛出一个异常。
部署应用程序
一旦上述命令执行成功,我们就可以通过执行以下命令来部署应用程序了。你应该在本地配置了AWS凭证,以便deploy 命令能够成功执行。deploy 命令将寻找AWS访问密钥、秘密和区域,以自动将应用程序部署到相关的AWS账户:
cdk deploy
请注意,你可能会被提示确认将应用于你账户的IAM角色/政策变化。
该应用程序应成功部署,它应该输出我们部署的HTTP API的基本URL。保持这个URL,因为我们在测试应用程序时需要它。
测试应用程序
现在应用程序已经部署到AWS账户,我们可以进入有趣的部分--测试API并检查我们是否能够向指定的电话号码发送消息。
记住,如果你使用的是Twilio的试用账户,那么你只能向你的账户下验证过的号码发送消息。
我们的API访问一个POST JSON请求,其中有接收方的电话号码和信息。这里有一个请求对象的例子:
{
"phoneNumber": "+12222222222",
"message": "Hello from the other side"
}
你可以在你的终端尝试以下curl 请求,通过替换基本URL来测试API。用你自己的手机号码替换假的电话号码:
curl --location --request POST '<BASE_URL>/sendSms' \
--header 'Content-Type: application/json' \
--data-raw '{
"phoneNumber": "+12222222222",
"message": "Test message"
}'
在执行上述curl 请求时,你应该得到一个带有消息SID的JSON响应:
{
"messageSid": "SMf54164c8df7149239f95a3c8fcd11ba4"
}
你在请求中提供的号码应该在很短的时间内收到该消息。
总结
在这篇文章中,我们学习了如何使用AWS CDK来使用基础设施即代码(IaC)结构以编程方式部署一个Lambda函数。AWS CDK简化了使用熟悉的编程语言为你的应用程序提供云资源的过程。它允许你使用面向对象的技术,并使基础设施的代码可以测试。本教程演示了我们如何在AWS Lambda函数中使用Twilio Java辅助库并将其部署到AWS。Twilio Java辅助库让我们更容易与各种Twilio服务互动,并提供易于使用的API。借助Twilio Java助手库和AWS CDK的力量,你可以更快地构建你的服务,为你的用户提供丰富的体验。你可以在GitHub上查看本教程中使用的全部源代码。