طريقة جديدة لاستخدام البريد الإلكتروني لتطبيقات الدعم: برنامج تعليمي من AWS
نشرت: 2022-03-11قد لا يكون البريد الإلكتروني رائعًا مثل منصات الاتصال الأخرى ولكن العمل معه يمكن أن يظل ممتعًا. تم تكليفي مؤخرًا بتنفيذ المراسلة في تطبيق جوال. كان المصيد الوحيد هو أن الاتصال الفعلي يجب أن يكون عبر البريد الإلكتروني. أردنا أن يتمكن مستخدمو التطبيق من التواصل مع فريق دعم مثلما ترسل رسالة نصية. يحتاج أعضاء فريق الدعم إلى تلقي هذه الرسائل عبر البريد الإلكتروني ، ويحتاجون أيضًا إلى أن يكونوا قادرين على الرد على المستخدم الأصلي. بالنسبة إلى المستخدم النهائي ، كل ما يحتاج إليه هو المظهر والعمل تمامًا مثل أي تطبيق مراسلة حديث آخر.
في هذه المقالة ، سوف نلقي نظرة على كيفية تنفيذ خدمة مماثلة لتلك المذكورة أعلاه باستخدام Java وعدد قليل من خدمات الويب من Amazon. ستحتاج إلى حساب AWS صالح ، واسم مجال ، والوصول إلى Java IDE المفضل لديك.
البنية التحتية
قبل أن نكتب أي رمز ، سنقوم بإعداد خدمات AWS المطلوبة لتوجيه البريد الإلكتروني واستهلاكه. سنستخدم SES لإرسال واستهلاك رسائل البريد الإلكتروني و SNS + SQS لتوجيه الرسائل الواردة.
كل شيء يبدأ هنا مع SES. ابدأ بتسجيل الدخول إلى حساب AWS الخاص بك والانتقال إلى وحدة تحكم SES.
قبل أن نبدأ ، ستحتاج إلى اسم مجال تم التحقق منه يمكنك إرسال رسائل بريد إلكتروني منه.
سيكون هذا هو تطبيق المجال الذي سيرسل المستخدمون رسائل بريد إلكتروني منه وسيرد أعضاء الدعم عليه. يعد التحقق من ملكية النطاق باستخدام SES عملية مباشرة ، ويمكن العثور على مزيد من المعلومات هنا.
إذا كانت هذه هي المرة الأولى التي تستخدم فيها SES ، أو لم تطلب حدًا للإرسال ، فسيتم وضع الحماية في حسابك. هذا يعني أنك لن تكون قادرًا على إرسال بريد إلكتروني إلى العناوين التي لم يتم التحقق منها مع AWS. قد يتسبب هذا في حدوث خطأ لاحقًا في البرنامج التعليمي ، عندما نرسل بريدًا إلكترونيًا إلى مكتب المساعدة الخيالي الخاص بنا. لتجنب ذلك ، يمكنك التحقق من أي عنوان بريد إلكتروني تخطط لاستخدامه كمكتب المساعدة الخاص بك في وحدة تحكم SES في علامة التبويب عناوين البريد الإلكتروني.
بمجرد أن يكون لديك مجال تم التحقق منه ، يمكننا إنشاء مجموعة قواعد. انتقل إلى علامة التبويب "مجموعات القواعد" في وحدة تحكم SES وأنشئ قاعدة استلام جديدة.
ستكون الخطوة الأولى عند إنشاء قاعدة استلام هي تحديد المستلم.
تسمح لك عوامل تصفية المستلمين بتحديد رسائل البريد الإلكتروني التي سيستهلكها SES ، وكيفية معالجة كل رسالة واردة. يحتاج المستلم الذي نحدده هنا إلى مطابقة المجال ونمط العنوان الذي يتم إرسال رسائل مستخدم التطبيق إليه عبر البريد الإلكتروني. تتمثل أبسط حالة هنا في إضافة مستلم للمجال الذي تحققنا منه مسبقًا ، في حالتنا example.com . سيؤدي هذا إلى تكوين SES لتطبيق قاعدتنا على جميع رسائل البريد الإلكتروني المرسلة إلى example.com . (على سبيل المثال ، [email protected] ، [email protected]).
لإنشاء قاعدة لنطاقنا بالكامل ، سنضيف مستلمًا لـ example.com .
من الممكن أيضًا مطابقة أنماط العنوان. هذا مفيد إذا كنت تريد توجيه الرسائل الواردة إلى قوائم انتظار SQS مختلفة.
لنفترض أن لدينا قائمة انتظار A وقائمة انتظار B. يمكننا إضافة مستلمين: [email protected] و [email protected] . إذا أردنا إدراج رسالة في قائمة الانتظار أ ، فسنرسل بريدًا إلكترونيًا إلى [email protected]. سيتطابق جزء من هذا مع مستلم [email protected] . كل شيء بين + و @ هو بيانات مستخدم عشوائية ، ولن يؤثر على مطابقة عنوان SES. للإدراج في قائمة الانتظار B ، ما عليك سوى استبدال a بـ b.
بعد تحديد المستلمين ، فإن الخطوة التالية هي تكوين الإجراء الذي سينفذه SES بعد استهلاك بريد إلكتروني جديد. نريد في النهاية أن ينتهي الأمر بهذه الأشياء في SQS ، ولكن لا يمكن حاليًا الانتقال مباشرة من SES إلى SQS. لسد الفجوة ، نحتاج إلى استخدام SNS. حدد إجراء SNS وأنشئ موضوعًا جديدًا. سنقوم في النهاية بتكوين هذا الموضوع لإدراج الرسائل في SQS.
حدد إنشاء موضوع SNS ومنحه اسمًا.
بعد إنشاء الموضوع ، نحتاج إلى تحديد ترميز الرسالة. سأستخدم Base64 من أجل الحفاظ على الأحرف الخاصة. سيؤثر الترميز الذي تختاره على كيفية فك تشفير الرسائل عندما نستهلكها في خدمتنا.
بمجرد تعيين القاعدة ، نحتاج فقط إلى تسميتها.
ستكون الخطوة التالية هي تكوين SQS و SNS ، لذلك نحتاج إلى التوجه إلى وحدة تحكم SQS وإنشاء قائمة انتظار جديدة.
لتبسيط الأمور ، أستخدم نفس اسم موضوع SNS الخاص بنا.
بعد أن نحدد قائمة الانتظار الخاصة بنا ، سنحتاج إلى تعديل سياسة الوصول الخاصة بها. نريد فقط منح إذن موضوع SNS للإدراج. يمكننا تحقيق ذلك عن طريق إضافة شرط يطابق موضوع SNS الخاص بنا.
يجب ملء حقل القيمة بـ ARN لموضوع SNS الذي تقوم SES بالإخطار به.
بعد إعداد SQS ، حان الوقت لرحلة أخرى إلى وحدة تحكم SNS لتكوين موضوعك لإدراج الإشعارات في قائمة انتظار SQS الجديدة اللامعة.
في وحدة تحكم SNS ، حدد الموضوع الذي تقوم SES بإخطاره. من هناك ، قم بإنشاء اشتراك جديد. يجب أن يكون بروتوكول الاشتراك هو Amazon SQS ، ويجب أن تكون الوجهة هي ARN لقائمة انتظار SQS التي أنشأتها للتو.
بعد كل ذلك ، يجب إعداد جانب 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>
إرسال الدعم بالبريد الإلكتروني نيابة عن المستخدم
أولاً ، دعنا نحدد فولًا لإرسال بريد إلكتروني إلى مكتب الدعم نيابة عن المستخدم. ستكون مهمة هذه الوحدة هي معالجة رسالة واردة من معرّف مستخدم ، وإرسال هذه الرسالة بالبريد الإلكتروني إلى عنوان البريد الإلكتروني لمكتب الدعم المحدد مسبقًا.
لنبدأ بتحديد الواجهة.
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 } }
دعنا نضيف أيضًا AWS SDK إلى pom الخاص بنا ، وسنستخدم عميل SES لإرسال رسائل البريد الإلكتروني الخاصة بنا:
<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk</artifactId> <version>1.11.5</version> </dependency>
أول شيء يتعين علينا القيام به هو إنشاء عنوان بريد إلكتروني لإرسال رسالة مستخدمنا منه. سيلعب العنوان الذي ننشئه دورًا مهمًا في الجانب المستهلك من خدمتنا. يجب أن يحتوي على معلومات كافية لتوجيه رد مكتب المساعدة إلى المستخدم الأصلي.
لتحقيق ذلك ، سنقوم بتضمين معرف المستخدم الأصلي في عنوان البريد الإلكتروني الذي تم إنشاؤه لدينا. للحفاظ على نظافة الأشياء ، سننشئ كائنًا يحتوي على معرف المستخدم ونستخدم سلسلة JSON المشفرة من Base64 كعنوان بريد إلكتروني.
لنقم بإنشاء وحدة برامج جديدة مسؤولة عن تحويل معرف المستخدم إلى عنوان بريد إلكتروني.
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 وترميز Base64 لتمثيل JSON. يجب أن تبدو النسخة النهائية من طريقة 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 SES المضمن في 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 );
نحن نستخدم DefaultAWSCredentialsProviderChain لإدارة بيانات الاعتماد لنا ، وستبحث هذه الفئة عن بيانات اعتماد AWS على النحو المحدد هنا.
سنستخدم مفتاح وصول AWS مزودًا بإمكانية الوصول إلى SES وفي النهاية SQS. لمزيد من المعلومات تحقق من الوثائق من أمازون.
ستكون الخطوة التالية هي تحديث طريقة messageSupport لدينا لإرسال الدعم عبر البريد الإلكتروني باستخدام AWS SDK. تجعل 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. تأكد من أن SUPPORT_EMAIL_ADDRESS المحدد في SupportBeanSesImpl يشير إلى عنوان بريد إلكتروني تملكه. إذا كان حساب SES الخاص بك في وضع الحماية ، فيجب أيضًا التحقق من هذا العنوان. يمكن التحقق من عناوين البريد الإلكتروني في وحدة تحكم SES ضمن قسم عناوين البريد الإلكتروني.
@Test public void emailSupport() throws JsonProcessingException { supportBean.messageSupport(1, "Hello World!"); }
بعد تشغيل هذا ، سترى رسالة تظهر في بريدك الوارد. والأفضل من ذلك ، قم بالرد على الرسالة وتحقق من قائمة انتظار SQS التي قمنا بإعدادها مسبقًا. يجب أن تشاهد حمولة تحتوي على ردك.
تستهلك الردود من SQS
ستكون الخطوة الأخيرة هي قراءة رسائل البريد الإلكتروني من SQS ، وتحليل رسالة البريد الإلكتروني ، ومعرفة معرف المستخدم الذي يجب إعادة توجيه الرد إليه.
للاستماع إلى رسائل SQS الجديدة ، سنستخدم Spring Cloud AWS messaging SDK. سيسمح لنا ذلك بتهيئة مستمع رسائل SQS عبر التعليقات التوضيحية ، وبالتالي تجنب قدرًا كبيرًا من التعليمات البرمجية المعيارية.

أولا ، التبعيات المطلوبة.
أضف تبعية Spring Cloud للمراسلة:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-aws-messaging</artifactId> </dependency>
وأضف 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. لحسن الحظ ، لا نحتاج إلى الكثير من التكوين على الإطلاق ، لذا سيكون تعريف الفول الخاص بنا خفيفًا جدًا. ستتمثل النقطة الرئيسية لهذا الملف في تمكين مستمعي قائمة الانتظار المدفوعة بالتعليقات التوضيحية ، وهذا سيسمح لنا بتعليق طريقة باعتبارها SqsListener.
قم بإنشاء ملف XML جديد باسم aws-config.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 />
. نحن نحدد أيضًا منطقة افتراضية. هذا ليس ضروريًا ، ولكن القيام بذلك سيسمح لنا بالرجوع إلى قائمة انتظار SQS بالاسم بدلاً من عنوان URL. نحن لا نحدد أي بيانات اعتماد لـ AWS ، من خلال حذفها ، فإن Spring سيكون افتراضيًا إلى DefaultAWSCredentialsProviderChain ، وهو نفس المزود الذي استخدمناه سابقًا في مجموعة SES الخاصة بنا. يمكن العثور على مزيد من المعلومات في مستندات Spring Cloud AWS.
لاستخدام تكوين XML هذا في تطبيق Spring Boot الخاص بنا ، نحتاج إلى استيراده بشكل صريح. توجه إلى فئة 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 المنفذ ويبدأ في استطلاع SQS لنا. في كل مرة يتم العثور على رسالة جديدة ، سيتم استدعاء طريقتنا المشروحة مع محتويات الرسالة. اختياريًا ، يمكن تكوين Spring Cloud لتنظيم الرسائل الواردة ، مما يمنحك القدرة على العمل مع كائنات مكتوبة قوية داخل مستمع قائمة الانتظار. بالإضافة إلى ذلك ، لديك القدرة على إدخال رأس واحد أو خريطة لجميع الرؤوس التي تم إرجاعها من استدعاء AWS الأساسي.
يمكننا استخدام اسم قائمة الانتظار المنطقي هنا نظرًا لأننا حددنا المنطقة مسبقًا في aws-config.xml ، إذا أردنا حذف أننا سنكون قادرين على استبدال القيمة بعنوان URL المؤهل بالكامل لـ SQS. نحن أيضًا نحدد سياسة الحذف ، وهذا سيؤدي إلى تكوين Spring لحذف الرسالة الواردة من SQS إذا تم استيفاء شرطها. هناك العديد من السياسات المحددة في SqsMessageDeletionPolicy ، نحن نقوم بتكوين Spring لحذف رسالتنا إذا تم تنفيذ طريقة consumeSqsMessage الخاصة بنا بنجاح.
نقوم أيضًا بحقن رؤوس SQS المرتجعة في طريقتنا باستخدامHeaders ، وستحتوي الخريطة المحقونة على بيانات وصفية متعلقة بقائمة الانتظار والحمولة المستلمة. يتم حقن نص الرسالة باستخدامNotificationMessage. يدعم Spring التنظيم باستخدام جاكسون ، أو عبر محول نص الرسالة المخصص. من أجل الراحة ، سنقوم فقط بحقن سلسلة JSON الخام والعمل معها باستخدام فئة JSONObject المضمنة في AWS SDK.
ستحتوي الحمولة المسترجعة من SQS على الكثير من البيانات. ألق نظرة على JSONObject لتتعرف على الحمولة المرتجعة. تحتوي حمولتنا على بيانات من كل خدمة AWS تم تمريرها من خلالها ، و SES ، و SNS ، وأخيراً SQS. من أجل هذا البرنامج التعليمي ، نحن نهتم بشيئين فقط: قائمة عناوين البريد الإلكتروني التي تم إرسالها إليها ونص البريد الإلكتروني. لنبدأ بتحليل رسائل البريد الإلكتروني.
//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); }
نظرًا لأنه في العالم الحقيقي ، قد يتضمن مكتب المساعدة الخاص بنا أكثر من مجرد المرسل الأصلي في رده أو ردها ، فسنريد التحقق من العنوان قبل تحليل معرف المستخدم. سيعطي هذا مكتب الدعم لدينا القدرة على إرسال رسائل إلى عدة مستخدمين في نفس الوقت بالإضافة إلى القدرة على تضمين مستخدمين من خارج التطبيق.
دعنا نعود إلى واجهة 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 الخاصة بنا. إذا نجح ذلك ، فإننا نتحقق بعد ذلك للتأكد من ملء معرف المستخدم المطلوب. إذا نجح كل هذا ، فيمكننا افتراض وجود تطابق بأمان.
عد إلى EmailSqsListener الخاص بنا وقم بحقن UserEmailBean المحدث حديثًا.
private final UserEmailBean userEmailBean; @Autowired public EmailSqsListener(UserEmailBean userEmailBean) { this.userEmailBean = userEmailBean; }
سنقوم الآن بتحديث طريقة consumeSqs. أولاً ، دعنا نحلل نص الرسالة الإلكترونية:
//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. نحتاج إلى طريقة لإعادة معرف المستخدم من بريد إلكتروني. عد إلى واجهة 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);
سيكون الهدف من هذه الطريقة هو إرجاع معرف المستخدم من عنوان منسق. سيكون التنفيذ مشابهًا لطريقة التحقق الخاصة بنا. دعنا نتوجه إلى 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; } }
عد الآن إلى EmailSqsListener لدينا وعملية التحديث البريد الإلكتروني لاستخدام هذه الطريقة الجديدة.
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 ، بتجميع محلل بريد إلكتروني مرخص من معهد ماساتشوستس للتكنولوجيا في Java استنادًا إلى إحدى مكتبات GitHub. سنستخدم هذه المكتبة الرائعة.
أولاً ، دعنا نحدث 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. سنحتاج إلى تحليل البريد الإلكتروني الخام إلى javax.mail MimeMessage قبل تمريره إلى EmailReplyParser. أضف تبعية المشاع.
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.4</version> </dependency>
الآن يمكننا العودة إلى EmailSqsListener لدينا وإنهاء عملية البريد الإلكتروني. في هذه المرحلة ، لدينا معرف المستخدم الأصلي وجسم البريد الإلكتروني الأولي. الشيء الوحيد المتبقي هو تحليل الرد.
لتحقيق ذلك ، سنستخدم مزيجًا من javax.mail و 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); }
يتم إحتوائه
وهذا كل شيء! لدينا الآن كل ما نحتاجه لتقديم استجابة للمستخدم الأصلي!
في هذه المقالة ، رأينا كيف يمكن استخدام Amazon Web Services لتنظيم خطوط الأنابيب المعقدة. على الرغم من أنه في هذه المقالة ، تم تصميم خط الأنابيب حول رسائل البريد الإلكتروني ؛ يمكن الاستفادة من هذه الأدوات نفسها لتصميم أنظمة أكثر تعقيدًا ، حيث لا داعي للقلق بشأن صيانة البنية التحتية ويمكنك التركيز على الجوانب الممتعة لهندسة البرمجيات بدلاً من ذلك.