Nowy sposób korzystania z poczty e-mail w aplikacjach pomocy technicznej: samouczek AWS
Opublikowany: 2022-03-11Poczta e-mail może nie być tak fajna jak inne platformy komunikacyjne, ale praca z nią nadal może być fajna. Niedawno otrzymałem zadanie zaimplementowania wiadomości w aplikacji mobilnej. Jedynym haczykiem było to, że właściwa komunikacja musiała odbywać się przez e-mail. Chcieliśmy, aby użytkownicy aplikacji mogli komunikować się z zespołem pomocy, tak jak wysyła się wiadomość tekstową. Członkowie zespołu pomocy technicznej musieli otrzymywać te wiadomości za pośrednictwem poczty e-mail, a także musieli być w stanie odpowiedzieć użytkownikowi, z którego pochodzi. Dla użytkownika końcowego wszystko, co musi wyglądać i działać jak każda inna nowoczesna aplikacja do przesyłania wiadomości.
W tym artykule przyjrzymy się, jak zaimplementować usługę podobną do opisanej powyżej przy użyciu Javy i kilku usług internetowych Amazona. Będziesz potrzebować ważnego konta AWS, nazwy domeny i dostępu do swojego ulubionego środowiska Java IDE.
Infrastruktura
Zanim napiszemy jakikolwiek kod, skonfigurujemy wymagane usługi AWS do routingu i używania poczty e-mail. Będziemy używać SES do wysyłania i odbierania wiadomości e-mail oraz SNS+SQS do routingu wiadomości przychodzących.
Wszystko zaczyna się tutaj od SES. Zacznij od zalogowania się na swoje konto AWS i przejścia do konsoli SES.
Zanim zaczniemy, będziesz potrzebować zweryfikowanej nazwy domeny, z której możesz wysyłać e-maile.
Będzie to aplikacja domeny, z której użytkownicy aplikacji będą wysyłać wiadomości e-mail, a członkowie pomocy będą odpowiadać. Weryfikacja domeny za pomocą SES jest prostym procesem, a więcej informacji można znaleźć tutaj.
Jeśli korzystasz z SES po raz pierwszy lub nie zażądałeś limitu wysyłania, Twoje konto zostanie umieszczone w piaskownicy. Oznacza to, że nie będziesz w stanie wysyłać e-maili na adresy, które nie zostały zweryfikowane w AWS. Może to spowodować błąd w dalszej części samouczka, gdy wyślemy e-mail do naszego fikcyjnego help desku. Aby tego uniknąć, możesz zweryfikować dowolny adres e-mail, którego planujesz używać jako pomocy technicznej w konsoli SES w zakładce Adresy e-mail.
Gdy masz zweryfikowaną domenę, możemy utworzyć zestaw reguł. Przejdź do zakładki Rule Sets w konsoli SES i utwórz nową regułę paragonu.
Pierwszym krokiem podczas tworzenia reguły odbioru będzie zdefiniowanie odbiorcy.
Filtry odbiorców pozwolą Ci określić, jakie e-maile będą wykorzystywane przez SES oraz jak przetwarzać każdą przychodzącą wiadomość. Odbiorca, którego tutaj definiujemy, musi być zgodny z domeną i wzorcem adresu, od którego wysyłane są wiadomości e-mail użytkownika. Najprostszym przypadkiem byłoby tutaj dodanie odbiorcy dla domeny, którą wcześniej zweryfikowaliśmy, w naszym przypadku example.com . Spowoduje to skonfigurowanie SES tak, aby zastosował naszą regułę do wszystkich wiadomości e-mail wysyłanych na adres example.com . (np. [email protected], [email protected]).
Aby stworzyć regułę dla całej naszej domeny, dodamy odbiorcę na przykład example.com .
Możliwe jest również dopasowanie wzorców adresów. Jest to przydatne, jeśli chcesz kierować wiadomości przychodzące do różnych kolejek SQS.
Powiedzmy, że mamy kolejkę A i kolejkę B. Możemy dodać dwóch odbiorców: [email protected] i [email protected] . Jeśli chcemy wstawić wiadomość do kolejki A, wyślemy e-maila [email protected]. Część tego będzie pasować do naszego odbiorcy [email protected] . Wszystko między + i @ to dowolne dane użytkownika, nie wpłynie to na dopasowanie adresu SES. Aby wstawić do kolejki B, po prostu zamień a na b.
Po zdefiniowaniu odbiorców kolejnym krokiem jest skonfigurowanie akcji wykonywanej przez SES po odebraniu nowej wiadomości e-mail. Chcemy ostatecznie, aby trafiły one do SQS, jednak obecnie nie jest możliwe bezpośrednie przejście z SES do SQS. Aby wypełnić tę lukę, musimy użyć SNS. Wybierz akcję SNS i utwórz nowy temat. Ostatecznie skonfigurujemy ten temat, aby wstawiać wiadomości do SQS.
Wybierz utwórz temat SNS i nadaj mu nazwę.
Po utworzeniu tematu musimy wybrać kodowanie wiadomości. Użyję Base64, aby zachować znaki specjalne. Wybrane kodowanie będzie miało wpływ na sposób dekodowania wiadomości, gdy wykorzystujemy je w naszej usłudze.
Po ustawieniu reguły wystarczy ją nazwać.
Następnym krokiem będzie konfiguracja SQS i SNS, w tym celu musimy przejść do konsoli SQS i utworzyć nową kolejkę.
Aby uprościć sprawę, używam tej samej nazwy, co nasz temat SNS.
Po zdefiniowaniu naszej kolejki będziemy musieli dostosować jej politykę dostępu. Chcemy tylko udzielić pozwolenia na wstawianie naszego tematu SNS. Możemy to osiągnąć, dodając warunek pasujący do naszego tematu SNS arn.
Pole wartości powinno być wypełnione numerem ARN dla tematu SNS, o którym ma powiadamiać SES.
Po skonfigurowaniu SQS nadszedł czas na jeszcze jedną podróż z powrotem do konsoli SNS, aby skonfigurować temat i wstawić powiadomienia do nowej, błyszczącej kolejki SQS.
W konsoli SNS wybierz temat, którego dotyczy SES. Stamtąd utwórz nową subskrypcję. Protokołem subskrypcji powinien być Amazon SQS, a miejscem docelowym powinien być ARN właśnie wygenerowanej kolejki SQS.
Po tym wszystkim strona równania AWS powinna być skonfigurowana. Możemy przetestować naszą pracę, wysyłając do siebie e-maile. Wyślij e-mail do domeny skonfigurowanej z SES, a następnie przejdź do konsoli SQS i wybierz kolejkę. Powinien być widoczny ładunek zawierający Twój e-mail.
Usługa Java do obsługi wiadomości e-mail
Teraz czas na zabawną część! W tej sekcji stworzymy prosty mikroserwis zdolny do wysyłania wiadomości i przetwarzania przychodzących wiadomości e-mail. Pierwszym krokiem będzie zdefiniowanie interfejsu API, który wyśle e-mail do naszego działu pomocy technicznej w imieniu użytkownika.
Szybka uwaga. Skoncentrujemy się na składnikach logiki biznesowej tej usługi i nie będziemy definiować punktów końcowych REST ani warstwy trwałości.
Aby zbudować usługę Spring, użyjemy Spring Boot i Maven. Możemy użyć Spring Initializer do wygenerowania dla nas projektu, start.spring.io.
Na początek nasz pom.xml powinien wyglądać mniej więcej tak:
<?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>
Wsparcie przez e-mail w imieniu użytkownika
Najpierw zdefiniujmy fasolę do wysyłania e-maili do naszego działu pomocy technicznej w imieniu użytkownika. Zadaniem tego ziarna będzie przetworzenie wiadomości przychodzącej z identyfikatora użytkownika i przesłanie jej na nasz wcześniej zdefiniowany adres e-mail działu pomocy technicznej.
Zacznijmy od zdefiniowania interfejsu.
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); }
I pusta realizacja:
@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 } }
Dodajmy również AWS SDK do naszego pom, będziemy używać klienta SES do wysyłania naszych e-maili:
<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk</artifactId> <version>1.11.5</version> </dependency>
Pierwszą rzeczą, którą musimy zrobić, to wygenerować adres e-mail, z którego zostanie wysłana wiadomość naszego użytkownika. Wygenerowany przez nas adres będzie odgrywał kluczową rolę po stronie konsumenckiej naszej usługi. Musi zawierać wystarczającą ilość informacji, aby przekierować odpowiedź działu pomocy z powrotem do użytkownika, z którego pochodzi.
Aby to osiągnąć, w wygenerowanym przez nas adresie e-mail dołączymy pierwotny identyfikator użytkownika. Aby utrzymać porządek, utworzymy obiekt zawierający identyfikator użytkownika i użyjemy jego ciągu JSON zakodowanego w Base64 jako adresu e-mail.
Stwórzmy nowy bean odpowiedzialny za przekształcenie identyfikatora użytkownika w adres e-mail.
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); }
Zacznijmy naszą implementację od dodania wymaganych zgód i prostej klasy wewnętrznej, która pomoże nam zserializować nasz 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; } } }
Generowanie naszego adresu e-mail jest proste, wystarczy utworzyć obiekt UserDetails i zakodować w Base64 reprezentację JSON. Gotowa wersja naszej metody createAddressForUserID powinna wyglądać mniej więcej tak:
@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; }
Teraz możemy wrócić do SupportBeanSesImpl i zaktualizować go, aby używał nowego ziarna e-mail, które właśnie stworzyliśmy.
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); }
Do wysyłania e-maili użyjemy klienta AWS SES dołączonego do AWS SDK.
/** * SES client */ private final AmazonSimpleEmailService amazonSimpleEmailService = new AmazonSimpleEmailServiceClient( new DefaultAWSCredentialsProviderChain() //see http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html );
Używamy DefaultAWSCredentialsProviderChain do zarządzania poświadczeniami dla nas, ta klasa wyszuka poświadczenia AWS zgodnie z definicją tutaj.
Przejdziemy do klucza dostępu AWS z dostępem do SES i ostatecznie SQS. Aby uzyskać więcej informacji, zapoznaj się z dokumentacją Amazon.
Następnym krokiem będzie aktualizacja naszej metody messageSupport do obsługi poczty e-mail przy użyciu pakietu AWS SDK. SES SDK sprawia, że jest to prosty proces. Gotowa metoda powinna wyglądać mniej więcej tak:
@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); }
Aby to wypróbować, utwórz klasę testową i wstrzyknij SupportBean. Upewnij się, że SUPPORT_EMAIL_ADDRESS zdefiniowany w SupportBeanSesImpl wskazuje na Twój adres e-mail. Jeśli Twoje konto SES jest w trybie piaskownicy, ten adres również musi zostać zweryfikowany. Adresy e-mail można zweryfikować w konsoli SES w sekcji Adresy e-mail.
@Test public void emailSupport() throws JsonProcessingException { supportBean.messageSupport(1, "Hello World!"); }
Po uruchomieniu tego w skrzynce odbiorczej powinien pojawić się komunikat. Jeszcze lepiej odpowiedz na wiadomość i sprawdź kolejkę SQS, którą ustawiliśmy wcześniej. Powinieneś zobaczyć ładunek zawierający Twoją odpowiedź.
Zużywanie odpowiedzi z SQS
Ostatnim krokiem będzie odczytanie wiadomości e-mail z SQS, przeanalizowanie wiadomości e-mail i ustalenie, do jakiego identyfikatora użytkownika należy przekazać odpowiedź.
Aby nasłuchiwać nowych wiadomości SQS, użyjemy zestawu SDK do przesyłania wiadomości Spring Cloud AWS. Umożliwi nam to skonfigurowanie nasłuchiwania komunikatów SQS za pomocą adnotacji, a tym samym unikniemy znacznej części standardowego kodu.
Najpierw wymagane zależności.

Dodaj zależność przesyłania wiadomości Spring Cloud:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-aws-messaging</artifactId> </dependency>
I dodaj Spring Cloud AWS do swojego zarządzania zależnościami pom:
<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>
Obecnie Spring Cloud AWS nie obsługuje konfiguracji opartej na adnotacjach, więc będziemy musieli zdefiniować komponent bean XML. Na szczęście w ogóle nie potrzebujemy dużej konfiguracji, więc nasza definicja fasoli będzie dość jasna. Głównym celem tego pliku będzie włączenie detektorów kolejki opartych na adnotacjach, co pozwoli nam na adnotację metody jako SqsListener.
Utwórz nowy plik XML o nazwie aws-config.xml w folderze zasobów. Nasza definicja powinna wyglądać mniej więcej tak:
<?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>
Ważną częścią tego pliku jest <aws-messaging:annotation-driven-queue-listener />
. Definiujemy również region domyślny. Nie jest to konieczne, ale dzięki temu będziemy mogli odwoływać się do naszej kolejki SQS według nazwy zamiast adresu URL. Nie definiujemy żadnych poświadczeń AWS, pomijając je, Spring domyślnie ustawi się na DefaultAWSCredentialsProviderChain, tego samego dostawcę, którego używaliśmy wcześniej w naszym ziarno SES. Więcej informacji można znaleźć w dokumentacji Spring Cloud AWS.
Aby użyć tej konfiguracji XML w naszej aplikacji Spring Boot, musimy ją jawnie zaimportować. Przejdź do swojej klasy @SpringBootApplication i zaimportuj ją.
@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); } }
Teraz zdefiniujmy bean, który będzie obsługiwał przychodzące wiadomości SQS. Spring Cloud AWS pozwala nam to osiągnąć za pomocą jednej adnotacji!
/** * 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); } }
Magia tkwi w adnotacji @SqsListener. Dzięki temu Spring skonfiguruje Executor i rozpocznie dla nas sondowanie SQS. Za każdym razem, gdy zostanie znaleziona nowa wiadomość, nasza metoda z adnotacjami zostanie wywołana wraz z treścią wiadomości. Opcjonalnie Spring Cloud można skonfigurować do organizowania wiadomości przychodzących, co daje możliwość pracy z obiektami o silnym typie wewnątrz odbiornika kolejki. Dodatkowo masz możliwość wstrzyknięcia pojedynczego nagłówka lub mapy wszystkich nagłówków zwróconych z bazowego wywołania AWS.
Możemy tutaj użyć logicznej nazwy kolejki, ponieważ wcześniej zdefiniowaliśmy region w aws-config.xml, gdybyśmy chcieli pominąć, że bylibyśmy w stanie zastąpić wartość naszym w pełni kwalifikowanym adresem URL SQS. Definiujemy również politykę usuwania, która skonfiguruje Spring tak, aby usuwał przychodzącą wiadomość z SQS, jeśli jej warunek jest spełniony. Istnieje wiele zasad zdefiniowanych w SqsMessageDeletionPolicy, konfigurujemy Spring tak, aby usuwał naszą wiadomość, jeśli nasza metoda consumerSqsMessage zostanie pomyślnie wykonana.
Wstrzykujemy również zwrócone nagłówki SQS do naszej metody za pomocą @Headers, a wstrzyknięta mapa będzie zawierać metadane związane z otrzymaną kolejką i ładunkiem. Treść wiadomości jest wstrzykiwana za pomocą @NotificationMessage. Spring obsługuje marshalling za pomocą Jacksona lub za pomocą niestandardowego konwertera treści wiadomości. Ze względu na wygodę wstrzykniemy nieprzetworzony ciąg znaków JSON i będziemy z nim pracować przy użyciu klasy JSONObject dołączonej do zestawu AWS SDK.
Ładunek pobrany z SQS będzie zawierał dużo danych. Spójrz na JSONObject, aby zapoznać się ze zwróconym ładunkiem. Nasz ładunek zawiera dane z każdej usługi AWS, przez którą przeszedł, SES, SNS i wreszcie SQS. Na potrzeby tego samouczka tak naprawdę interesują nas tylko dwie rzeczy: lista adresów e-mail, na które zostało wysłane oraz treść wiadomości e-mail. Zacznijmy od przeanalizowania e-maili.
//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); }
Ponieważ w prawdziwym świecie nasz helpdesk może zawierać w swojej odpowiedzi więcej niż tylko oryginalnego nadawcę, będziemy chcieli zweryfikować adres, zanim przeanalizujemy identyfikator użytkownika. Dzięki temu nasze biuro pomocy będzie mogło wysyłać wiadomości do wielu użytkowników jednocześnie, a także uwzględniać użytkowników spoza aplikacji .
Wróćmy do naszego interfejsu UserEmailBean i dodajmy kolejną metodę.
/** * Returns true if the input email address matches our template * @param emailAddress Email to check * @return true if it matches */ boolean emailMatchesUserFormat(String emailAddress);
W UserEmailBeanJSONImpl, aby zaimplementować tę metodę, będziemy chcieli zrobić dwie rzeczy. Najpierw sprawdź, czy adres kończy się na naszą EMAIL_DOMAIN, a następnie sprawdź, czy możemy go rozmieścić.
@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("@")); }
Zdefiniowaliśmy dwie nowe metody, emailMatchesUserFormat, które właśnie dodaliśmy do naszego interfejsu, oraz prostą metodę narzędziową do dzielenia adresu e-mail na @. Nasza implementacja emailMatchesUserFormat działa, próbując zdekodować Base64 i przenieść część adresową z powrotem do naszej klasy pomocniczej UserDetails. Jeśli to się powiedzie, sprawdzamy, czy wprowadzono wymagany identyfikator użytkownika. Jeśli to wszystko się uda, możemy spokojnie założyć mecz.
Wróć do naszego EmailSqsListener i wstrzyknij świeżo zaktualizowany UserEmailBean.
private final UserEmailBean userEmailBean; @Autowired public EmailSqsListener(UserEmailBean userEmailBean) { this.userEmailBean = userEmailBean; }
Teraz zaktualizujemy metodę consumerSqsMethod. Najpierw przeanalizujmy treść wiadomości e-mail:
//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); }
Teraz utwórzmy nową metodę, która będzie przetwarzać adres e-mail i treść wiadomości e-mail.
private void processEmail(String emailAddress, String emailBody){ }
I na koniec zaktualizuj pętlę e-mail, aby wywołać tę metodę, jeśli znajdzie dopasowanie.
//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); } }
Zanim zaimplementujemy processEmail, musimy dodać jeszcze jedną metodę do naszego UserEmailBean. Potrzebujemy metody na zwrócenie identyfikatora użytkownika z wiadomości e-mail. Wróć do interfejsu UserEmailBean, aby dodać ostatnią metodę.
/** * 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);
Celem tej metody będzie zwrócenie identyfikatora użytkownika ze sformatowanego adresu. Wdrożenie będzie podobne do naszej metody weryfikacji. Przejdźmy do UserEmailBeanJSONImpl i wypełnij tę metodę.
@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; } }
Teraz wróć do naszego EmailSqsListener i zaktualizuj processEmail, aby użyć tej nowej metody.
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; } }
Świetnie! Teraz mamy prawie wszystko, czego potrzebujemy. Ostatnią rzeczą, jaką musimy zrobić, to przeanalizować odpowiedź z nieprzetworzonej wiadomości.
Analiza odpowiedzi z e-maili jest w rzeczywistości dość skomplikowanym zadaniem. Formaty wiadomości e-mail nie są ustandaryzowane, a różnice między różnymi klientami poczty e-mail mogą być ogromne. Surowa odpowiedź będzie również zawierać znacznie więcej niż odpowiedź i podpis. Oryginalna wiadomość najprawdopodobniej również zostanie dołączona. Inteligentni ludzie w Mailgun stworzyli świetny post na blogu wyjaśniający niektóre wyzwania. Otworzyli również swoje podejście oparte na uczeniu maszynowym do analizowania wiadomości e-mail, sprawdź to tutaj.
Biblioteka Mailgun jest napisana w Pythonie, więc w naszym samouczku użyjemy prostszego rozwiązania opartego na Javie. Użytkownik GitHub, edlio, ułożył w Javie licencjonowany przez MIT parser poczty e-mail oparty na jednej z bibliotek GitHub. Będziemy korzystać z tej wspaniałej biblioteki.
Najpierw zaktualizujmy nasz pom, użyjemy https://jitpack.io do pobrania EmailReplyParser.
<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
Teraz dodaj zależność GitHub.
<dependency> <groupId>com.github.edlio</groupId> <artifactId>EmailReplyParser</artifactId> <version>v1.0</version> </dependency>
Będziemy również korzystać z poczty e-mail Apache Commons. Będziemy musieli przeanalizować surową wiadomość e-mail w javax.mail MimeMessage przed przekazaniem jej do EmailReplyParser. Dodaj zależność commons.
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.4</version> </dependency>
Teraz możemy wrócić do naszego EmailSqsListener i zakończyć proces Email. W tym momencie mamy pierwotny identyfikator użytkownika i surową treść wiadomości e-mail. Pozostało tylko przeanalizować odpowiedź.
Aby to osiągnąć, użyjemy kombinacji javax.mail i EmailReplyParser edlio.
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); }
Zakończyć
I to wszystko! Mamy teraz wszystko, czego potrzebujemy, aby dostarczyć odpowiedź pierwotnemu użytkownikowi!
W tym artykule zobaczyliśmy, w jaki sposób Amazon Web Services można wykorzystać do orkiestracji złożonych potoków. Chociaż w tym artykule potok został zaprojektowany wokół wiadomości e-mail; te same narzędzia można wykorzystać do projektowania jeszcze bardziej złożonych systemów, w których nie trzeba się martwić o utrzymanie infrastruktury i można zamiast tego skupić się na zabawnych aspektach inżynierii oprogramowania.