示例工程教程后端【免费下载链接】aws-doc-sdk-examplesWelcome to the AWS Code Examples Repository. This repo contains code examples used in the AWS documentation, AWS SDK Developer Guides, and more. For more information, see the Readme.md file below.项目地址https://gitcode.com/gh_mirrors/aw/aws-doc-sdk-examples点击查看免费下载本教程以 AWS 官方代码示例仓库aws-doc-sdk-examples中的javav2/usecases/creating_workflows_stepfunctions用例为主线完整演示如何用AWS SDK for Java V2与AWS Step Functions搭建一个处理组织支持工单Support Ticket的无服务器工作流。工作流的每一步由 Java 编写的AWS Lambda 函数实现并串联Amazon DynamoDB工单数据落库与Amazon SES邮件通知两项服务。读完本文你将掌握定义 Amazon States LanguageASL状态机、用 Lambda Java API 编写可被 Step Functions 调用的RequestHandler实现、构建与部署 Lambda 函数、在状态机中装配函数 ARN以及从前到后驱动并验证一条完整的无服务器业务流。教程目标与适用场景Lambda 是 AWS 提供的计算服务允许你无需预置或管理服务器即可运行代码Step Functions 则负责把这些代码编排成多步骤的有状态工作流。本文示例的业务场景是一个组织收到新的支持工单后系统需要依次完成登记工单 → 分配受理人 → 邮件通知受理人三个动作。该用例同时展示了三个非常实用的技能点读取传入工作流的数据从 Step Functions 传入的 JSON 事件中解析出工单 ID在步骤之间传递数据每个 Lambda 函数handleRequest的返回值会成为下一个步骤的输入从工作流中调用 AWS 服务通过 DynamoDB 增强型客户端持久化工单记录通过 SES 发送原始邮件Raw Email。前置条件与资源准备开始前需要准备以下环境与本仓库 README.md 中声明的 Prerequisites 一致一个 AWS 账号一款 Java IDE本教程使用 IntelliJ IDEAJava JDK 17仓库pom.xml中maven.compiler.source/target均设置为 17Maven 3.6 或更高版本。注意事项本用例涉及的 AWS 服务均属于 AWS 免费套餐覆盖范围但代码并未在所有 AWS 区域经过测试部分服务仅在某些区域可用运行本教程代码可能产生 AWS 费用教程结束后务必终止所创建的所有资源避免持续扣费。需要创建的 AWS 资源在编写代码之前需要先创建一张名为Case的 DynamoDB 表其主键Partition Key名为Id。PersistCase源码注释对此有明确要求Prior to running this code example, create a table named Case with a PK named id见 PersistCase.java。表结构除主键id外还需承载name、email、registrationDate字段对应源码中DynamoDbBean注解的Case内部类。理解工作流从工单到邮件通知的四个步骤本教程要创建的工作流如下图的 Step Functions 可视化流程图所示它由四个步骤组成Start—— 启动工作流Open Case—— 接收一个支持工单 ID 值并将其传入工作流Assign Case—— 将工单分配给某位员工并把数据存入 DynamoDB 表Send Email—— 使用 Amazon SES 向受理员工发送邮件告知有新工单End—— 结束工作流。工作流可以在步骤之间传递数据。例如Open Case步骤处理传入的工单 ID 后会把这个值传给Assign Case步骤后者的输出员工邮箱又作为Send Email步骤的输入。后续章节会展示如何在 Lambda 函数中编写业务逻辑来读取和处理这些数据值。创建执行 Lambda 所需的 IAM 角色本教程需要创建两个 IAM 角色lambda-support—— 用于执行invokeLambda 函数workflow-support—— 用于让 AWS Step Functions 可以调用invoke工作流中的 Lambda 函数。由于本教程会使用 DynamoDB 与 Amazon SES 服务lambda-support角色必须附加允许从 Lambda 函数中调用这两个服务的策略。创建 IAM 角色的步骤打开 AWS Management Console在搜索框中输入IAM并进入 IAM 控制台在左侧导航窗格选择Roles在 Roles 页面选择Create Role选择AWS service然后选择Lambda即信任实体为 Lambda 服务选择Permissions搜索AWSLambdaBasicExecutionRole并勾选该托管策略允许 Lambda 向 CloudWatch Logs 写入日志这正是LambdaLogger输出的前提选择Next: Tags选择Review将角色命名为lambda-support选择Create role点击lambda-support进入角色概览页选择Attach Policies搜索AmazonDynamoDBFullAccess并选择Attach policy授予 Lambda 读写Case表的权限搜索AmazonSESFullAccess并选择Attach policy授予发送邮件的权限重复上述流程创建workflow-support但第 3 步不再选择Lambda而是选择Step Functions作为信任实体该角色无需执行第 1113 步附加策略。使用 Step Functions 定义无服务器工作流Step Functions 使用Amazon States LanguageASL——一种基于 JSON 的状态机描述语言——来定义工作流中的每一步。定义完 ASL 文档后Step Functions 控制台会自动提供工作流的可视化表示。下图同时展示了本教程使用的 ASL 文档与对应的可视化工作流创建工作流的步骤打开 AWS Step Functions 控制台本教程使用us-west-2区域源码中也统一使用Region.US_WEST_2选择Create State Machine选择Write your workflow in code在Type区域选择Standard标准类型适用于需要审计与长时间运行的工作流在代码编辑器中粘贴以下 Amazon States Language 文档{ Comment: A simple AWS Step Functions state machine that automates a call center support session., StartAt: Open Case, States: { Open Case: { Type: Task, Resource: arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME, Next: Assign Case }, Assign Case: { Type: Task, Resource: arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME, Next: Send Email }, Send Email: { Type: Task, Resource: arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME, End: true } } }提示此时三个Resource字段中的REGION、ACCOUNT_ID、FUNCTION_NAME都是占位符控制台会提示 Lambda 资源相关的错误暂时无需担心后续章节会把这些值替换为真实函数 ARN。选择Next在名称字段输入SupportStateMachine在Permission区域选择Choose an existing role选择之前创建的workflow-support角色选择Create state machine看到成功创建状态机的提示后即完成。状态机如何传递数据Step Functions 会把上一次状态State的输出作为下一次状态的输入。在这个 ASL 文档中Open Case步骤结束后会把Handler函数的返回值工单 ID传递给Assign CaseAssign Case把Handler2的返回值受理员工邮箱传递给Send Email。理解这条数据链路是后续编写handleRequest方法返回值的核心依据。创建 IntelliJ 项目 LambdaFunctions在 IntelliJ IDEA 中选择File → New → Project在New Project对话框中选择Maven然后选择NextGroupId输入LambdaFunctionsArtifactId输入LambdaFunctions选择Next选择Finish。创建完成后将得到名为LambdaFunctions的 Maven 项目。请确认项目的pom.xml与本仓库中的 pom.xml 保持一致。添加 POM 依赖仓库中的 pom.xml 是本用例的依赖蓝本其核心配置可归纳如下项目基础配置groupId/artifactId均为LambdaFunctions版本1.0-SNAPSHOT打包方式为jar通过java.version17/java.version将编译源与目标版本锁定为 Java 17通过dependencyManagement引入AWS SDK BOM版本 2.21.20统一管理所有software.amazon.awssdk依赖版本。核心依赖及其用途依赖版本用途com.amazonaws:aws-lambda-java-core1.2.1提供RequestHandler、Context、LambdaLogger等 Lambda Java 运行时 APIcom.google.code.gson:gson2.8.9Handler中将环境变量、Context、事件 JSON 序列化为日志输出software.amazon.awssdk:dynamodb-enhanced由 BOM 管理PersistCase中的DynamoDbEnhancedClient、TableSchema、DynamoDbBean注解software.amazon.awssdk:dynamodb由 BOM 管理底层DynamoDbClientsoftware.amazon.awssdk:ses2.10.41SendMessage中的SesClient与SendRawEmailRequestjavax.mail:javax.mail-api/com.sun.mail:javax.mail1.5.5构造 MIME 邮件消息MimeMessage、MimeMultipartcom.googlecode.json-simple:json-simple1.1.1JSON 解析辅助org.apache.logging.log4j:log4j-api2.10.0日志 API打包插件关键build段配置了三个插件maven-compiler-plugin3.8.1按 Java 17 编译maven-surefire-plugin2.22.2执行单元测试maven-shade-plugin3.2.2在package阶段将项目连同全部依赖打进同一个胖 JAR这是后续能直接把单个 JAR 上传为 Lambda 函数的关键——Lambda 运行环境只会加载你上传的部署包所有第三方依赖必须包含在包内。用 Lambda Java API 编写工作流步骤使用 Lambda Java 运行时 API 编写函数的方式是实现com.amazonaws.services.lambda.runtime.RequestHandler接口把步骤被调用时要执行的业务逻辑放在handleRequest方法中方法的返回值会传给工作流中的下一个步骤。本示例共有三个工作流步骤对应三个 Java 类另有PersistCase与SendMessage两个工具类分别调用 DynamoDB 与 SES 服务。五个类均位于example包下源码路径与本仓库结构完全一致类文件路径工作流中的角色Handlersrc/main/java/example/Handler.java第一步读取传入的工单 IDHandler2src/main/java/example/Handler2.java第二步分配工单给员工并写入数据库Handler3src/main/java/example/Handler3.java第三步发送通知邮件PersistCasesrc/main/java/example/PersistCase.java调用 DynamoDB API 存储工单数据SendMessagesrc/main/java/example/SendMessage.java调用 SES API 发送邮件五个文件在 metadata.yaml 中均标记为lambda服务相关代码说明它们是一组完整的 Lambda 部署单元。Handler读取工单 IDHandler实现RequestHandlerMapString, String, String泛型参数表明它接收一个键值对形式的 JSON 事件返回字符串。它利用LambdaLogger把环境变量、Context 和事件数据记录到 Amazon CloudWatch Logs然后从事件中取出inputCaseID字段并返回// Handler value: example.Handler public class Handler implements RequestHandlerMapString, String, String { Override public String handleRequest(MapString, String event, Context context) { LambdaLogger logger context.getLogger(); Gson gson new GsonBuilder().create(); // log execution details logger.log(ENVIRONMENT VARIABLES: gson.toJson(System.getenv())); logger.log(CONTEXT: gson.toJson(context)); // process event logger.log(EVENT Data: gson.toJson(event)); String myCaseID event.get(inputCaseID); logger.log(CaseId44: myCaseID); return myCaseID; } }完整源码见 Handler.java。注意event.get(inputCaseID)读取的键名inputCaseID必须与最终执行工作流时传入的 JSON 字段名一致。Handler2分配工单并落库Handler2实现RequestHandlerString, String输入直接是字符串上一步返回的工单 ID。它用一段简单的随机逻辑在两位员工之间分配工单Math.random() 0.5时分配给 Tom Blue否则分配给 Sarah White然后通过PersistCase.putRecord把工单写入 DynamoDB 表Case最后返回被分配员工的邮箱地址给下一步// Handler value: example.Handler public class Handler2 implements RequestHandlerString, String { Override public String handleRequest(String event, Context context) { PersistCase perCase new PersistCase(); LambdaLogger logger context.getLogger(); String val event; logger.log(CASE is about to be assigned val); // Create very simple logic to assign case to an employee int tmp (Math.random() 0.5) ? 1 : 2; logger.log(TMP IS tmp); String emailEmp ; if (tmp 1) { // assign to tblue emailEmp tbluenoServer.com; perCase.putRecord(val, Tom Blue, emailEmp); } else { // assign to swhite emailEmp swhitenoServer.com; perCase.putRecord(val, Sarah White, emailEmp); } logger.log(emailEmp IS emailEmp); // return email - used in the next step return emailEmp; } }完整源码见 Handler2.java。这里的两个邮箱tbluenoServer.com、swhitenoServer.com是教程中的占位地址实际运行时需要替换为已验证的收件地址见下文 SES 说明。Handler3发送通知邮件Handler3同样实现RequestHandlerString, String接收上一步返回的员工邮箱创建SendMessage对象并调用sendMessage(email)发送通知邮件// Handler value: example.Handler public class Handler3 implements RequestHandlerString, String { Override public String handleRequest(String event, Context context) { LambdaLogger logger context.getLogger(); String email event; // log execution details logger.log(Email value email); SendMessage msg new SendMessage(); try { msg.sendMessage(email); } catch (IOException e) { e.printStackTrace(); } return ; } }完整源码见 Handler3.java。由于这是工作流的终态步骤返回值仅为空字符串不再传递给后续步骤。PersistCase用 DynamoDB 增强型客户端写入PersistCase展示了 AWS SDK for Java V2 中DynamoDB Enhanced Client增强型客户端的典型用法先用DynamoDbClient.builder().region(Region.US_WEST_2).build()构建底层客户端再用DynamoDbEnhancedClient.builder().dynamoDbClient(ddb).build()包装成增强型客户端最后通过TableSchema.fromBean(Case.class)把 Java Bean 映射到表Casepublic class PersistCase { // Puts an item into a DynamoDB table public void putRecord(String caseId, String employeeName, String email) { // Create a DynamoDbClient object Region region Region.US_WEST_2; DynamoDbClient ddb DynamoDbClient.builder() .region(region) .build(); // Create a DynamoDbEnhancedClient and use the DynamoDbClient object DynamoDbEnhancedClient enhancedClient DynamoDbEnhancedClient.builder() .dynamoDbClient(ddb) .build(); try { // Create a DynamoDbTable object DynamoDbTableCase caseTable enhancedClient.table(Case, TableSchema.fromBean(Case.class)); // Create an Instant object LocalDate localDate LocalDate.parse(2020-04-07); LocalDateTime localDateTime localDate.atStartOfDay(); Instant instant localDateTime.toInstant(ZoneOffset.UTC); // Populate the table Case caseRecord new Case(); caseRecord.setName(employeeName); caseRecord.setId(caseId); caseRecord.setEmail(email); caseRecord.setRegistrationDate(instant); // Put the case data into a DynamoDB table caseTable.putItem(caseRecord); } catch (DynamoDbException e) { System.err.println(e.getMessage()); System.exit(1); } System.out.println(done); } // Create the Case table to track open cases created in the workflow DynamoDbBean public static class Case { private String id; private String name; private String email; private Instant regDate; DynamoDbPartitionKey public String getId() { return this.id; }; public void setId(String id) { this.id id; } DynamoDbSortKey public String getName() { return this.name; } public void setName(String name) { this.name name; } public String getEmail() { return this.email; } public void setEmail(String email) { this.email email; } public Instant getRegistrationDate() { return regDate; } public void setRegistrationDate(Instant registrationDate) { this.regDate registrationDate; } } }完整源码见 PersistCase.java。需要关注的设计要点DynamoDbBean标记Case为可映射的 BeanDynamoDbPartitionKey标注id为主键DynamoDbSortKey标注name为排序键——这要求建表时对应的键属性必须匹配registrationDate使用Instant类型存储时间戳写入的是一个固定解析日期2020-04-07转换出的 UTCInstant实际项目可替换为Instant.now()所有字段均提供配套的 getter/setter这是 Enhanced Client Bean 映射的硬性要求。SendMessage用 SES 发送原始邮件SendMessage使用 Amazon SES API 发送邮件。与普通文本发送不同它通过javax.mail库手工构造MIME 邮件同时包含纯文本与 HTML 两种正文以multipart/alternative组织再序列化为原始字节最后通过SendRawEmailRequest提交给 SES 的sendRawEmail接口public class SendMessage { public void sendMessage(String email) throws IOException { // Sender String sender SPECIFY an EMAIL ADDRESS; // REPLACE WITH AN EMAIL ADDRESS String subject New Case; // The email body for recipients with non-HTML email clients. String bodyText Hello,\r\n You are assigned a new case; // The HTML body of the email. String bodyHTML html head/head body h1Hello!/h1 pPlease check the database for new ticket assigned to you./p /body /html; Region region Region.US_WEST_2; SesClient client SesClient.builder() .region(region) .build(); try { send(client, sender, email, subject, bodyText, bodyHTML); } catch (IOException | MessagingException e) { e.printStackTrace(); } } public static void send(SesClient client, String sender, String recipient, String subject, String bodyText, String bodyHTML) throws AddressException, MessagingException, IOException { Session session Session.getDefaultInstance(new Properties()); // Create a new MimeMessage object. MimeMessage message new MimeMessage(session); // Add subject, from and to lines. message.setSubject(subject, UTF-8); message.setFrom(new InternetAddress(sender)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipient)); // Create a multipart/alternative child container. MimeMultipart msgBody new MimeMultipart(alternative); // Create a wrapper for the HTML and text parts. MimeBodyPart wrap new MimeBodyPart(); // Define the text part. MimeBodyPart textPart new MimeBodyPart(); textPart.setContent(bodyText, text/plain; charsetUTF-8); // Define the HTML part. MimeBodyPart htmlPart new MimeBodyPart(); htmlPart.setContent(bodyHTML, text/html; charsetUTF-8); // Add the text and HTML parts to the child container. msgBody.addBodyPart(textPart); msgBody.addBodyPart(htmlPart); // Add the child container to the wrapper object. wrap.setContent(msgBody); // Create a multipart/mixed parent container. MimeMultipart msg new MimeMultipart(mixed); // Add the parent container to the message. message.setContent(msg); // Add the multipart/alternative part to the message. msg.addBodyPart(wrap); try { System.out.println(Attempting to send an email through Amazon SES using the AWS SDK for Java...); ByteArrayOutputStream outputStream new ByteArrayOutputStream(); message.writeTo(outputStream); ByteBuffer buf ByteBuffer.wrap(outputStream.toByteArray()); byte[] arr new byte[buf.remaining()]; buf.get(arr); SdkBytes data SdkBytes.fromByteArray(arr); RawMessage rawMessage RawMessage.builder() .data(data) .build(); SendRawEmailRequest rawEmailRequest SendRawEmailRequest.builder() .rawMessage(rawMessage) .build(); client.sendRawEmail(rawEmailRequest); } catch (SesException e) { System.err.println(e.awsErrorDetails().errorMessage()); System.exit(1); } } }完整源码见 SendMessage.java。使用前必须注意通过 SES 发送邮件的发件地址sender与收件地址都必须是已验证的邮箱地址——代码中的SPECIFY an EMAIL ADDRESS是占位符需要替换为你在 SES 控制台中完成验证的真实邮箱否则sendRawEmail会抛出验证异常。发件地址若不替换SesException会在运行时被捕获并打印错误信息后调用System.exit(1)退出进程。打包项目为 JAR在项目根目录pom.xml所在目录执行以下 Maven 命令mvn package在maven-shade-plugin的作用下项目会连同全部依赖被打包成一个可部署的胖 JAR。产物位于项目的target子目录中。这个 JAR 就是后续上传到 Lambda 的部署包。部署 Lambda 函数打开 Lambda 控制台为本用例的三个步骤创建三个同名函数选择Create Function选择Author from scratch从头创作在Basic information区域名称输入TicStep1Runtime选择Java 8Java 运行时兼容 JDK 17 编译产物在权限区域选择Use an existing role然后选择之前创建的lambda-support角色选择Create function在Code entry type中选择Upload a .zip or .jar file选择Upload浏览并选中上一步生成的 JAR 文件在Handler字段填入函数的完整限定名格式为包名.类名::方法名例如example.Handler::handleRequestexample.Handler指定包与类::后跟方法名选择Save对Handler2、Handler3类重复上述过程对应函数命名为TicStep2、TicStep3。完成后的三个函数TicStep1、TicStep2、TicStep3将在 ASL 文档中分别对应Open Case、Assign Case、Send Email三个状态。其中 Handler 字段的配置可对照源码顶部的注释例如 Handler.java 中写明的// Handler value: example.Handler。将 Lambda 函数接入状态机打开 Lambda 控制台在函数概览页右上角可以查看每个函数的Amazon Resource NameARN。复制TicStep1的 ARN粘贴到 Step Functions 控制台中 ASL 文档的Open Case状态的Resource字段替换原来的arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME占位符。依次对Assign Case与Send Email状态执行同样的操作分别填入TicStep2、TicStep3的 ARN。这一步正是把用 AWS SDK for Java 创建的 Lambda 函数接入 Step Functions 工作流的关键装配动作——状态机的Resource字段本质上就是被调用函数的 ARN。执行工作流并验证结果Step Functions 的每次执行Execution会接收一段 JSON 作为输入。对本工作流传入如下 JSON 即可启动一次工单处理{ inputCaseID: 001 }执行步骤在 Step Functions 控制台选择Start execution在Input区域粘贴上述 JSON观察工作流可视化每个步骤完成时节点会变为绿色如果某个步骤变为红色说明该步骤执行出错可以点击该步骤并在右侧查看对应的日志与异常信息工作流结束后打开 DynamoDB 控制台扫描Case表即可看到本次执行写入的工单记录。上图为多次执行后Case表的数据快照记录以id为主键包含email、name与registrationDate字段其中name字段同时充当排序键registrationDate为 UTC 时间戳。这些字段与PersistCase.Case的 Bean 属性一一对应可以直观验证 Lambda 函数向 DynamoDB 的写入结果。调试建议Handler会把环境变量、Context 与事件数据整体输出到 CloudWatch Logs排查输入事件格式不符类问题时优先查看这些日志步骤变红时Step Functions 控制台右侧会显示 Lambda 抛出的异常栈与日志流链接SES 相关步骤最常见的问题是邮箱未验证请核对发件/收件地址是否已在 SES 控制台完成验证。清理资源教程结束后请务必清理以下资源以避免持续扣费三个 Lambda 函数TicStep1、TicStep2、TicStep3、Step Functions 状态机SupportStateMachine、DynamoDB 表Case以及两个 IAM 角色lambda-support、workflow-support。源码文件索引本用例的完整源码与配置均位于仓库javav2/usecases/creating_workflows_stepfunctions/目录下可直接对照阅读README.md本教程的原始说明文档pom.xmlMaven 依赖与打包配置Java 17、AWS SDK BOM 2.21.20、shade 插件Handler.java第一步 Lambda读取工单 IDHandler2.java第二步 Lambda分配工单并写入 DynamoDBHandler3.java第三步 Lambda发送通知邮件PersistCase.javaDynamoDB 增强型客户端写入实现SendMessage.javaSES 原始邮件发送实现metadata.yaml示例元数据标记各文件对应的 AWS 服务。仓库中javav2/usecases/目录还收录了其他多服务组合用例本教程的实现思路Lambda 承载步骤 Step Functions 编排 DynamoDB 持久化 SES 通知可直接迁移到类似的多服务无服务器场景中。赞分享示例工程教程后端【免费下载链接】aws-doc-sdk-examplesWelcome to the AWS Code Examples Repository. This repo contains code examples used in the AWS documentation, AWS SDK Developer Guides, and more. For more information, see the Readme.md file below.项目地址https://gitcode.com/gh_mirrors/aw/aws-doc-sdk-examples点击查看免费下载相关推荐使用 AWS SDK for Java V2 构建 Step Functions 无服务器工作流自动修改 Amazon Redshift 数据使用 AWS SDK for Java V2 构建 Step Functions 无服务器工作流自动修改 Amazon Redshift 数据 本篇技术指南基示例工程教程后端如何使用AWS Step Functions和Java SDK构建高效ETL工作流完整指南如何使用AWS Step Functions和Java SDK构建高效ETL工作流完整指南 AWS Step Functions是一项强大的无服务器工作流服务示例工程教程后端AWS Step Functions 结合 AWS SDK for Java 2 构建 ETL 工作流完整实战解析aws-doc-sdk-examplesAWS Step Functions 结合 AWS SDK for Java 2 构建 ETL 工作流完整实战解析aws doc sdk examples示例工程教程后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
