지원 앱에 이메일을 사용하는 새로운 방법: AWS 자습서
게시 됨: 2022-03-11이메일은 다른 커뮤니케이션 플랫폼만큼 멋지지 않을 수 있지만 이메일로 작업하는 것은 여전히 재미있습니다. 나는 최근에 모바일 앱에서 메시징을 구현하는 임무를 받았습니다. 유일한 문제는 실제 커뮤니케이션이 이메일을 통해 이루어져야 한다는 것이었습니다. 우리는 앱 사용자가 문자 메시지를 보내는 것처럼 지원 팀과 통신할 수 있기를 원했습니다. 지원 팀 구성원은 이메일을 통해 이러한 메시지를 수신하고 원래 사용자에게 응답할 수 있어야 했습니다. 최종 사용자에게 모든 것은 다른 최신 메시징 앱처럼 보이고 작동하는 데 필요했습니다.
이 기사에서는 Java와 소수의 Amazon 웹 서비스를 사용하여 위에서 설명한 것과 유사한 서비스를 구현하는 방법을 살펴보겠습니다. 유효한 AWS 계정, 도메인 이름 및 즐겨 사용하는 Java IDE에 대한 액세스 권한이 필요합니다.
인프라
코드를 작성하기 전에 이메일 라우팅 및 사용에 필요한 AWS 서비스를 설정합니다. 이메일을 보내고 사용하는 데 SES를 사용하고 수신 메시지 라우팅에 SNS+SQS를 사용할 것입니다.
모든 것은 SES에서 시작됩니다. 먼저 AWS 계정에 로그인하고 SES 콘솔로 이동합니다.
시작하기 전에 이메일을 보낼 수 있는 확인된 도메인 이름이 필요합니다.
이것은 사용자가 이메일 메시지를 보내고 지원 구성원이 회신할 도메인 앱입니다. SES로 도메인을 확인하는 것은 간단한 과정이며 여기에서 더 많은 정보를 찾을 수 있습니다.
SES를 처음 사용하거나 전송 제한을 요청하지 않은 경우 계정이 샌드박스 처리됩니다. 즉, AWS에서 확인되지 않은 주소로는 이메일을 보낼 수 없습니다. 이로 인해 튜토리얼 후반부에 가상의 헬프 데스크에 이메일을 보낼 때 오류가 발생할 수 있습니다. 이를 방지하려면 이메일 주소 탭의 SES 콘솔에서 헬프 데스크로 사용하려는 이메일 주소를 확인할 수 있습니다.
확인된 도메인이 있으면 규칙 세트를 생성할 수 있습니다. SES 콘솔에서 규칙 집합 탭으로 이동하여 새 수신 규칙을 생성합니다.
수신 규칙을 생성하는 첫 번째 단계는 수신자를 정의하는 것입니다.
수신자 필터를 사용하면 SES가 사용할 이메일과 각 수신 메시지를 처리하는 방법을 정의할 수 있습니다. 여기에서 정의하는 수신자는 사용자 메시지가 이메일로 전송되는 도메인 및 주소 패턴과 일치해야 합니다. 여기서 가장 간단한 경우는 이전에 확인한 도메인의 수신자를 추가하는 것입니다(예: example.com ). 이렇게 하면 example.com 으로 전송되는 모든 이메일에 규칙을 적용하도록 SES가 구성됩니다. (예: [email protected], [email protected]).
전체 도메인에 대한 규칙을 생성하려면 example.com 에 대한 수신자를 추가합니다.
주소 패턴을 일치시키는 것도 가능합니다. 이것은 들어오는 메시지를 다른 SQS 대기열로 라우팅하려는 경우에 유용합니다.
대기열 A와 대기열 B가 있다고 가정합니다. 두 명의 수신자를 추가할 수 있습니다: [email protected] 및 [email protected] . 대기열 A에 메시지를 삽입하려면 [email protected]으로 이메일을 보냅니다. 이 중 일부는 @example.com 수신자와 일치합니다. +와 @ 사이의 모든 것은 임의의 사용자 데이터이며 SES의 주소 일치에 영향을 미치지 않습니다. 큐 B에 삽입하려면 단순히 b로 바꾸십시오.
수신자를 정의한 후 다음 단계는 새 이메일을 사용한 후 SES가 수행할 작업을 구성하는 것입니다. 우리는 결국 이것이 SQS로 끝나기를 원하지만 현재 SES에서 SQS로 직접 이동할 수 없습니다. 그 격차를 좁히기 위해서는 SNS가 필요합니다. SNS 작업을 선택하고 새 주제를 만듭니다. 결국 SQS에 메시지를 삽입하도록 이 주제를 구성할 것입니다.
SNS 주제 생성을 선택하고 이름을 지정합니다.
주제가 생성되면 메시지 인코딩을 선택해야 합니다. 특수 문자를 보존하기 위해 Base64를 사용할 것입니다. 선택한 인코딩은 서비스에서 메시지를 사용할 때 메시지가 디코딩되는 방식에 영향을 미칩니다.
규칙이 설정되면 이름만 지정하면 됩니다.
다음 단계는 SQS 및 SNS를 구성하는 것이므로 SQS 콘솔로 이동하여 새 대기열을 생성해야 합니다.
간단하게 하기 위해 SNS 주제와 같은 이름을 사용하고 있습니다.
대기열을 정의한 후에는 액세스 정책을 조정해야 합니다. SNS 주제에 삽입 권한만 부여하려고 합니다. SNS 주제와 일치하는 조건을 추가하여 이를 달성할 수 있습니다.
값 필드는 SES가 알리는 SNS 주제에 대한 ARN으로 채워져야 합니다.
SQS가 설정되면 SNS 콘솔로 한 번 더 돌아가서 반짝이는 새 SQS 대기열에 알림을 삽입하도록 주제를 구성해야 합니다.
SNS 콘솔에서 SES가 알리는 주제를 선택합니다. 거기에서 새 구독을 만듭니다. 구독 프로토콜은 Amazon SQS여야 하고 대상은 방금 생성한 SQS 대기열의 ARN이어야 합니다.
그 후에 방정식의 AWS 측이 모두 설정되어야 합니다. 우리는 자신에게 이메일을 보내 작업을 테스트할 수 있습니다. SES로 구성된 도메인으로 이메일을 보낸 다음 SQS 콘솔로 이동하여 대기열을 선택하십시오. 이메일이 포함된 페이로드를 볼 수 있어야 합니다.
이메일을 다루는 자바 서비스
이제 재미있는 부분으로! 이 섹션에서는 메시지를 보내고 수신 이메일을 처리할 수 있는 간단한 마이크로서비스를 만들 것입니다. 첫 번째 단계는 사용자를 대신하여 지원 데스크에 이메일을 보낼 API를 정의하는 것입니다.
빠른 메모입니다. 우리는 이 서비스의 비즈니스 로직 구성 요소에 초점을 맞출 것이며 REST 끝점이나 지속성 계층을 정의하지 않을 것입니다.
Spring 서비스를 빌드하기 위해 Spring Boot와 Maven을 사용할 것입니다. Spring Initializer를 사용하여 start.spring.io라는 프로젝트를 생성할 수 있습니다.
시작하려면 pom.xml이 다음과 같아야 합니다.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.toptal.tutorials</groupId> <artifactId>email-processor</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>email-processor</name> <description>A simple "micro-service" for emailing support on behalf of a user and processing replies</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
사용자를 대신하여 지원 이메일 보내기
먼저 사용자를 대신하여 지원 데스크에 이메일을 보내기 위한 빈을 정의해 보겠습니다. 이 빈의 작업은 사용자 ID에서 들어오는 메시지를 처리하고 해당 메시지를 미리 정의된 지원 데스크 이메일 주소로 이메일로 보내는 것입니다.
인터페이스를 정의하는 것으로 시작하겠습니다.
public interface SupportBean { /** * Send a message to the application support desk on behalf of a user * @param fromUserId The ID of the originating user * @param message The message to send */ void messageSupport(long fromUserId, String message); }
그리고 빈 구현:
@Component public class SupportBeanSesImpl implements SupportBean { /** * Email address for our application help-desk * This is the destination address user support emails will be sent to */ private static final String SUPPORT_EMAIL_ADDRESS = "[email protected]"; @Override public void messageSupport(long fromUserId, String message) { //todo: send an email to our support address } }
pom에 AWS SDK도 추가해 보겠습니다. SES 클라이언트를 사용하여 이메일을 보낼 것입니다.
<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk</artifactId> <version>1.11.5</version> </dependency>
가장 먼저 해야 할 일은 사용자의 메시지를 보낼 이메일 주소를 생성하는 것입니다. 우리가 생성한 주소는 우리 서비스의 소비 측면에서 중요한 역할을 할 것입니다. 헬프 데스크의 응답을 원래 사용자에게 다시 라우팅하기에 충분한 정보를 포함해야 합니다.
이를 위해 생성된 이메일 주소에 원래 사용자 ID를 포함할 것입니다. 깔끔하게 유지하기 위해 사용자 ID가 포함된 객체를 만들고 이 객체의 Base64로 인코딩된 JSON 문자열을 이메일 주소로 사용할 것입니다.
사용자 ID를 이메일 주소로 바꾸는 역할을 하는 새 빈을 생성해 봅시다.
public interface UserEmailBean { /** * Returns a unique per user email address * @param userID Input user ID * @return An email address unique for the input userID */ String emailAddressForUserID(long userID); }
JSON 직렬화에 도움이 되는 간단한 내부 클래스와 필수 동의를 추가하여 구현을 시작하겠습니다.
@Component public class UserEmailBeanJSONImpl implements UserEmailBean { /** * The TLD for all our generated email addresses */ private static final String EMAIL_DOMAIN = "example.com"; /** * com.fasterxml.jackson.databind.ObjectMapper used to create a JSON object including our user ID */ private final ObjectMapper objectMapper = new ObjectMapper(); @Override public String emailAddressForUserID(long userID) { //todo: create the email address return null; } /** * Simple helper class we will serialize. * The JSON representation of this class will become our user email address */ private static class UserDetails{ private Long userID; public Long getUserID() { return userID; } public void setUserID(Long userID) { this.userID = userID; } } }
이메일 주소를 생성하는 것은 간단합니다. 우리가 해야 할 일은 UserDetails 개체를 만들고 JSON 표현을 Base64로 인코딩하는 것입니다. createAddressForUserID 메서드의 완성된 버전은 다음과 같아야 합니다.
@Override public String emailAddressForUserID(long userID) { UserDetails userDetails = new UserDetails(); userDetails.setUserID(userID); //create a JSON representation. String jsonString = objectMapper.writeValueAsString(userDetails); //Base64 encode it String base64String = Base64.getEncoder().encodeToString(jsonString.getBytes()); //create an email address out of it String emailAddress = base64String + "@" + EMAIL_DOMAIN; return emailAddress; }
이제 SupportBeanSesImpl로 돌아가 방금 만든 새 이메일 빈을 사용하도록 업데이트할 수 있습니다.
private final UserEmailBean userEmailBean; @Autowired public SupportBeanSesImpl(UserEmailBean userEmailBean) { this.userEmailBean = userEmailBean; } @Override public void messageSupport(long fromUserId, String message) throws JsonProcessingException { //user specific email String fromEmail = userEmailBean.emailAddressForUserID(fromUserId); }
이메일을 보내기 위해 AWS SDK에 포함된 AWS SES 클라이언트를 사용할 것입니다.
/** * SES client */ private final AmazonSimpleEmailService amazonSimpleEmailService = new AmazonSimpleEmailServiceClient( new DefaultAWSCredentialsProviderChain() //see http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html );
우리는 DefaultAWSCredentialsProviderChain을 활용하여 자격 증명을 관리하고 있으며, 이 클래스는 여기에 정의된 AWS 자격 증명을 검색합니다.
SES 및 결국 SQS에 대한 액세스 권한이 프로비저닝된 AWS 액세스 키로 이동합니다. 자세한 내용은 Amazon의 설명서를 확인하십시오.
다음 단계는 AWS SDK를 사용하여 이메일 지원으로 messageSupport 메서드를 업데이트하는 것입니다. SES SDK는 이를 간단한 프로세스로 만듭니다. 완성된 메서드는 다음과 같아야 합니다.
@Override public void messageSupport(long fromUserId, String message) throws JsonProcessingException { //User specific email String fromEmail = userEmailBean.emailAddressForUserID(fromUserId); //create the email Message supportMessage = new Message( new Content("New support request from userID " + fromUserId), //Email subject new Body().withText(new Content(message)) //Email body, this contains the user's message ); //create the send request SendEmailRequest supportEmailRequest = new SendEmailRequest( fromEmail, //From address, our user's generated email new Destination(Collections.singletonList(SUPPORT_EMAIL_ADDRESS)), //to address, our support email address supportMessage //Email body defined above ); //Send it off amazonSimpleEmailService.sendEmail(supportEmailRequest); }
그것을 시도하려면 테스트 클래스를 만들고 SupportBean을 주입하십시오. SupportBeanSesImpl에 정의된 SUPPORT_EMAIL_ADDRESS가 귀하가 소유한 이메일 주소를 가리키는지 확인하십시오. SES 계정이 샌드박스인 경우 이 주소도 확인해야 합니다. 이메일 주소는 SES 콘솔의 이메일 주소 섹션에서 확인할 수 있습니다.
@Test public void emailSupport() throws JsonProcessingException { supportBean.messageSupport(1, "Hello World!"); }
이것을 실행하면 받은 편지함에 메시지가 표시되어야 합니다. 더 나은 방법은 메시지에 회신하고 이전에 설정한 SQS 대기열을 확인하는 것입니다. 응답이 포함된 페이로드가 표시되어야 합니다.
SQS의 응답 사용
마지막 단계는 SQS에서 이메일을 읽고, 이메일 메시지를 구문 분석하고, 회신을 전달해야 하는 사용자 ID가 무엇인지 파악하는 것입니다.
새 SQS 메시지를 수신 대기하기 위해 Spring Cloud AWS 메시징 SDK를 사용할 것입니다. 이를 통해 주석을 통해 SQS 메시지 수신기를 구성할 수 있으므로 상당한 양의 상용구 코드를 피할 수 있습니다.
첫째, 필요한 종속성입니다.
Spring Cloud 메시징 종속성을 추가합니다.
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-aws-messaging</artifactId> </dependency>
그리고 pom 종속성 관리에 Spring Cloud AWS를 추가합니다.
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-aws</artifactId> <version>1.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
현재 Spring Cloud AWS는 주석 기반 구성을 지원하지 않으므로 XML Bean을 정의해야 합니다. 운 좋게도 많은 구성이 전혀 필요하지 않으므로 빈 정의는 매우 간단합니다. 이 파일의 요점은 주석 기반 큐 리스너를 활성화하는 것입니다. 이렇게 하면 메서드에 SqsListener로 주석을 달 수 있습니다.

리소스 폴더에 aws-config.xml이라는 새 XML 파일을 생성합니다. 정의는 다음과 같아야 합니다.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aws-context="http://www.springframework.org/schema/cloud/aws/context" xmlns:aws-messaging="http://www.springframework.org/schema/cloud/aws/messaging" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cloud/aws/context http://www.springframework.org/schema/cloud/aws/context/spring-cloud-aws-context.xsd http://www.springframework.org/schema/cloud/aws/messaging http://www.springframework.org/schema/cloud/aws/messaging/spring-cloud-aws-messaging.xsd"> <!--enable annotation driven queue listeners --> <aws-messaging:annotation-driven-queue-listener /> <!--define our region, this lets us reference queues by name instead of by URL. --> <aws-context:context-region region="us-east-1" /> </beans>
이 파일의 중요한 부분은 <aws-messaging:annotation-driven-queue-listener />
입니다. 우리는 또한 기본 지역을 정의하고 있습니다. 이것은 필수는 아니지만 그렇게 하면 URL 대신 이름으로 SQS 대기열을 참조할 수 있습니다. 우리는 AWS 자격 증명을 정의하지 않습니다. 이를 생략하면 이전에 SES 빈에서 사용한 것과 동일한 공급자인 DefaultAWSCredentialsProviderChain이 기본적으로 Spring이 됩니다. 자세한 정보는 Spring Cloud AWS 문서에서 찾을 수 있습니다.
Spring Boot 앱에서 이 XML 구성을 사용하려면 명시적으로 가져와야 합니다. @SpringBootApplication 클래스로 이동하여 가져옵니다.
@SpringBootApplication @ImportResource("classpath:aws-config.xml") //Explicit import for our AWS XML bean definition public class EmailProcessorApplication { public static void main(String[] args) { SpringApplication.run(EmailProcessorApplication.class, args); } }
이제 들어오는 SQS 메시지를 처리할 빈을 정의합시다. Spring Cloud AWS를 사용하면 단일 주석으로 이를 수행할 수 있습니다!
/** * Bean reasonable for polling SQS and processing new emails */ @Component public class EmailSqsListener { @SuppressWarnings("unused") //IntelliJ isn't quite smart enough to recognize methods marked with @SqsListener yet @SqsListener(value = "com-example-ses", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS) //Mark this method as a SQS listener //Since we already set up our region we can use the logical queue name here //Spring will automatically delete messages if this method executes successfully public void consumeSqsMessage(@Headers Map<String, String> headers, //Map of headers returned when requesting a message from SQS //This map will include things like the relieved time, count and message ID @NotificationMessage String rawJsonMessage //JSON string representation of our payload //Spring Cloud AWS supports marshaling here as well //For the sake of simplicity we will work with incoming messages as a JSON object ) throws Exception{ //com.amazonaws.util.json.JSONObject included with the AWS SDK JSONObject jsonSqsMessage = new JSONObject(rawJsonMessage); } }
여기서 마법은 @SqsListener 주석에 있습니다. 이를 통해 Spring은 Executor를 설정하고 SQS 폴링을 시작합니다. 새 메시지가 발견될 때마다 메시지 내용과 함께 주석이 달린 메서드가 호출됩니다. 선택적으로 Spring Cloud는 수신 메시지를 마샬링하도록 구성할 수 있으므로 대기열 수신기 내부에서 강력한 유형의 객체로 작업할 수 있습니다. 또한 기본 AWS 호출에서 반환된 모든 헤더의 맵 또는 단일 헤더를 삽입할 수 있습니다.
이전에 aws-config.xml에서 영역을 정의했기 때문에 여기에서 논리적 대기열 이름을 사용할 수 있습니다. 생략하려는 경우 정규화된 SQS URL로 값을 바꿀 수 있습니다. 우리는 또한 삭제 정책을 정의하고 있습니다. 이것은 조건이 충족되면 SQS에서 들어오는 메시지를 삭제하도록 Spring을 구성합니다. SqsMessageDeletionPolicy에 정의된 여러 정책이 있습니다. 우리는 consumerSqsMessage 메소드가 성공적으로 실행되면 메시지를 삭제하도록 Spring을 구성하고 있습니다.
또한 @Headers를 사용하여 반환된 SQS 헤더를 메서드에 주입하고 있으며 주입된 맵에는 수신된 페이로드 및 대기열과 관련된 메타데이터가 포함됩니다. 메시지 본문은 @NotificationMessage를 사용하여 주입됩니다. Spring은 Jackson을 사용하거나 사용자 정의 메시지 본문 변환기를 통해 마샬링을 지원합니다. 편의를 위해 원시 JSON 문자열을 삽입하고 AWS SDK에 포함된 JSONObject 클래스를 사용하여 작업하겠습니다.
SQS에서 검색된 페이로드에는 많은 데이터가 포함됩니다. 반환된 페이로드에 익숙해지려면 JSONObject를 살펴보십시오. 우리의 페이로드에는 SES, SNS 및 마지막으로 SQS를 통해 전달된 모든 AWS 서비스의 데이터가 포함됩니다. 이 튜토리얼을 위해 우리는 정말로 두 가지, 즉 이것이 전송된 이메일 주소 목록과 이메일 본문에만 관심을 둡니다. 이메일을 구문 분석하는 것으로 시작하겠습니다.
//Pull out the array containing all email addresses this was sent to JSONArray emailAddressArray = jsonSqsMessage.getJSONObject("mail").getJSONArray("destination"); for(int i = 0 ; i < emailAddressArray.length() ; i++){ String emailAddress = emailAddressArray.getString(i); }
현실 세계에서 헬프데스크는 답장에 원래 발신자 이상을 포함할 수 있으므로 사용자 ID를 구문 분석하기 전에 주소를 확인해야 합니다. 이렇게 하면 지원 데스크에서 동시에 여러 사용자에게 메시지를 보낼 수 있을 뿐만 아니라 앱이 아닌 사용자도 포함할 수 있습니다.
다시 UserEmailBean 인터페이스로 돌아가서 다른 메소드를 추가해 보겠습니다.
/** * Returns true if the input email address matches our template * @param emailAddress Email to check * @return true if it matches */ boolean emailMatchesUserFormat(String emailAddress);
UserEmailBeanJSONImpl에서 이 방법을 구현하기 위해 두 가지 작업을 수행하려고 합니다. 먼저 주소가 EMAIL_DOMAIN으로 끝나는지 확인한 다음 마샬링할 수 있는지 확인합니다.
@Override public boolean emailMatchesUserFormat(String emailAddress) { //not our address, return right away if(!emailAddress.endsWith("@" + EMAIL_DOMAIN)){ return false; } //We just care about the email part, not the domain part String emailPart = splitEmail(emailAddress); try { //Attempt to decode our email UserDetails userDetails = objectMapper.readValue(Base64.getDecoder().decode(emailPart), UserDetails.class); //We assume this email matches if the address is successfully decoded and marshaled return userDetails != null && userDetails.getUserID() != null; } catch (IllegalArgumentException | IOException e) { //The Base64 decoder will throw an IllegalArgumentException it the input string is not Base64 formatted //Jackson will throw an IOException if it can't read the string into the UserDetails class return false; } } /** * Splits an email address on @ * Returns everything before the @ * @param emailAddress Address to split * @return all parts before @. If no @ is found, the entire address will be returned */ private static String splitEmail(String emailAddress){ if(!emailAddress.contains("@")){ return emailAddress; } return emailAddress.substring(0, emailAddress.indexOf("@")); }
인터페이스에 방금 추가한 emailMatchesUserFormat과 @에서 이메일 주소를 분할하기 위한 간단한 유틸리티 메소드의 두 가지 새로운 메소드를 정의했습니다. 우리 emailMatchesUserFormat 구현은 Base64 디코딩을 시도하고 주소 부분을 다시 UserDetails 도우미 클래스로 마샬링하여 작동합니다. 성공하면 필수 사용자 ID가 채워졌는지 확인합니다. 이 모든 것이 해결된다면 우리는 안전하게 경기를 가정할 수 있습니다.
EmailSqsListener로 돌아가서 새로 업데이트된 UserEmailBean을 주입합니다.
private final UserEmailBean userEmailBean; @Autowired public EmailSqsListener(UserEmailBean userEmailBean) { this.userEmailBean = userEmailBean; }
이제 consumerSqsMethod를 업데이트하겠습니다. 먼저 이메일 본문을 구문 분석해 보겠습니다.
//Pull our content, remember the content will be Base64 encoded as per our SES settings String encodedContent = jsonSqsMessage.getString("content"); //Create a new String after decoding our body String decodedBody = new String( Base64.getDecoder().decode(encodedContent.getBytes()) ); for(int i = 0 ; i < emailAddressArray.length() ; i++){ String emailAddress = emailAddressArray.getString(i); }
이제 이메일 주소와 이메일 본문을 처리할 새 메서드를 만들어 보겠습니다.
private void processEmail(String emailAddress, String emailBody){ }
마지막으로 일치하는 항목을 찾으면 이 메서드를 호출하도록 전자 메일 루프를 업데이트합니다.
//Loop over all sent to addresses for(int i = 0 ; i < emailAddressArray.length() ; i++){ String emailAddress = emailAddressArray.getString(i); //If we find a match, process the email and method if(userEmailBean.emailMatchesUserFormat(emailAddress)){ processEmail(emailAddress, decodedBody); } }
processEmail을 구현하기 전에 UserEmailBean에 메소드를 하나 더 추가해야 합니다. 이메일에서 사용자 ID를 반환하는 방법이 필요합니다. UserEmailBean 인터페이스로 돌아가 마지막 메소드를 추가하십시오.
/** * Returns the userID from a formatted email address. * Returns null if no userID is found. * @param emailAddress Formatted email address, this address should be verified using {@link #emailMatchesUserFormat(String)} * @return The originating userID if found, null if not */ Long userIDFromEmail(String emailAddress);
이 방법의 목표는 형식이 지정된 주소에서 사용자 ID를 반환하는 것입니다. 구현은 검증 방법과 유사합니다. UserEmailBeanJSONImpl로 가서 이 메소드를 채우자.
@Override public Long userIDFromEmail(String emailAddress) { String emailPart = splitEmail(emailAddress); try { //Attempt to decode our email UserDetails userDetails = objectMapper.readValue(Base64.getDecoder().decode(emailPart), UserDetails.class); if(userDetails == null || userDetails.getUserID() == null){ //We couldn't find a userID return null; } //ID found, return it return userDetails.getUserID(); } catch (IllegalArgumentException | IOException e) { //The Base64 decoder will throw an IllegalArgumentException it the input string is not Base64 formatted //Jackson will throw an IOException if it can't read the string into the UserDetails class //Return null since we didn't find a userID return null; } }
이제 EmailSqListener로 돌아가서 이 새로운 방법을 사용하도록 processEmail을 업데이트하십시오.
private void processEmail(String emailAddress, String emailBody){ //Parse out the email address Long userID = userEmailBean.userIDFromEmail(emailAddress); if(userID == null){ //Whoops, we couldn't find a userID. Abort! return; } }
엄청난! 이제 우리는 필요한 거의 모든 것을 가지고 있습니다. 마지막으로 해야 할 일은 원시 메시지에서 응답을 구문 분석하는 것입니다.
이메일에서 답장을 분석하는 것은 실제로 상당히 복잡한 작업입니다. 이메일 메시지 형식은 표준화되어 있지 않으며 이메일 클라이언트마다 편차가 클 수 있습니다. 원시 응답에는 응답과 서명보다 훨씬 더 많은 것이 포함될 것입니다. 원본 메시지도 포함될 가능성이 큽니다. Mailgun의 똑똑한 사람들은 몇 가지 문제를 설명하는 훌륭한 블로그 게시물을 만들었습니다. 그들은 또한 이메일 구문 분석에 대한 기계 학습 기반 접근 방식을 오픈 소스로 제공합니다. 여기에서 확인하십시오.
Mailgun 라이브러리는 Python으로 작성되었으므로 자습서에서는 더 간단한 Java 기반 솔루션을 사용할 것입니다. GitHub 사용자 edlio는 GitHub 라이브러리 중 하나를 기반으로 Java로 MIT 라이선스 이메일 파서를 구성했습니다. 우리는 이 훌륭한 라이브러리를 사용할 것입니다.
먼저 pom을 업데이트하고 https://jitpack.io를 사용하여 EmailReplyParser를 가져올 것입니다.
<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
이제 GitHub 종속성을 추가합니다.
<dependency> <groupId>com.github.edlio</groupId> <artifactId>EmailReplyParser</artifactId> <version>v1.0</version> </dependency>
우리는 또한 Apache commons 이메일을 사용할 것입니다. EmailReplyParser에 전달하기 전에 원시 이메일을 javax.mail MimeMessage로 구문 분석해야 합니다. 커먼즈 종속성을 추가하십시오.
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.4</version> </dependency>
이제 EmailSqsListener로 돌아가서 processEmail을 완료할 수 있습니다. 이 시점에서 원래 사용자 ID와 원시 이메일 본문이 있습니다. 남은 일은 응답을 분석하는 것뿐입니다.
이를 수행하기 위해 javax.mail과 edlio의 EmailReplyParser를 조합하여 사용할 것입니다.
private void processEmail(String emailAddress, String emailBody) throws Exception { //Parse out the email address Long userID = userEmailBean.userIDFromEmail(emailAddress); if(userID == null){ //Whoops, we couldn't find a userID. Abort! return; } //Default javax.mail session Session session = Session.getDefaultInstance(new Properties()); //Create a new mimeMessage out of the raw email body MimeMessage mimeMessage = MimeMessageUtils.createMimeMessage( session, emailBody ); MimeMessageParser mimeMessageParser = new MimeMessageParser(mimeMessage); //Parse the message mimeMessageParser.parse(); //Parse out the reply for our message String replyText = EmailReplyParser.parseReply(mimeMessageParser.getPlainContent()); //Now we're done! //We have both the userID and the response! System.out.println("Processed reply for userID: " + userID + ". Reply: " + replyText); }
마무리
그리고 그게 다야! 이제 원래 사용자에게 응답을 전달하는 데 필요한 모든 것이 준비되었습니다!
이 기사에서는 Amazon Web Services를 사용하여 복잡한 파이프라인을 조정하는 방법을 살펴보았습니다. 이 기사에서 파이프라인은 이메일을 중심으로 설계되었습니다. 이러한 동일한 도구를 활용하여 인프라 유지 관리에 대해 걱정할 필요가 없고 대신 소프트웨어 엔지니어링의 재미있는 측면에 집중할 수 있는 훨씬 더 복잡한 시스템을 설계할 수 있습니다.