代碼地址:https://github.com/yangbajing/scala-applications/tree/master/email-serverjava
應用功能是實現一個基於隊列的郵件發送服務,每一個郵件發送者(使用smtp協議)做爲一個sender
。多個sender
能夠在同一個組(group)
中,每一個組中的sender
將串行發送郵件。git
郵件內容能夠經過REST API
提交,以可使用JMS
發佈到ActiveMQ
中,郵件服務器將從中讀取郵件內容。github
配置:apache
推薦在啓動時使用-Dapplication.file=/usr/app/etc/emailserver/application.conf
指定配置文件json
emailserver { server { interface = "0.0.0.0" port = 9999 } emails { email1 { userName = "email1@email.com" password = "" smtp = "" smtpPort = 0 ssl = true # 同一個組內的郵件會串行發送,此key可忽略 group = "1" } email2 { userName = "email2@email.com" password = "" smtp = "" smtpPort = 0 ssl = true group = "2" } } activemq { url = "tcp://127.0.0.1:61616" emailQueueName = "EmailQueue" } }
emails
定義郵件發送者(可用於stmp
服務進行郵件發送的郵箱信息)。能夠定義多個郵件發送者,但每一個郵件發送者的key
不能相同。好比:email1
和email2
服務器
activemq
定義了ActiveMQ
服務的鏈接參數。併發
編譯與運行:app
# 編譯 ./sbt assembly # 運行 java -Dapplication.file=/usr/app/etc/emailserver/application.conf -jar target/scala-2.11/email-server.jar
測試REST服務:框架
# 查詢存在的郵箱發送者: curl http://localhost:9999/email/users # 發送測試郵件 curl -v -XPOST -H "Content-Type: application/json" \ -d '{"sender":"Info@email.cn", "subject":"測試郵件","to":["user1@email.cn", "user2@email.cn"],"content":"測試郵件內容咯~"}' \ http://localhost:9999/email/send
使用JMS發送郵件:curl
安裝activemq
Downloads:http://mirrors.hust.edu.cn/apache/activemq/5.11.1/apache-activemq-5.11.1-bin.tar.gz tar zxf ~/Downloads/apache-activemq-5.11.1-bin.tar.gz cd apache-activemq-5.11.1/ ./bin/activemq-admin start
activemq
管理控制檯地址:http://localhost:8161/admin/,帳號:admin
,密碼:admin
。
JMS TCP地址:tcp://localhost:61616
生產測試郵件
修改EmailProducers.scala
的activeMqUrl
及mapMessage
參數,運行EmailProducers
生產一個郵件發送請求。
Akka Http
是一個完整的server
和client
端HTTP開發棧,基於akka-actor
他akka-stream
。它不是一個WEB
框架,而是提供了能夠構建Http
服務的工具包。
Akka Http
有一套很直觀的DSL
來定義路由,天然的造成了一個樹型的路由結構。如Routes:
pathPrefix("email") { path("send") { post { entity(as[JsValue].map(_.as[SendEmail])) { sendEmail => onComplete(emailService.sendEmail(sendEmail)) { case Success(value) => value match { case Right(msg) => complete(msg) case Left(msg) => complete(StatusCodes.Forbidden, msg) } case Failure(e) => complete(StatusCodes.InternalServerError, "SendEmail an error occurred: " + e.getMessage) } } } } ~ path("users") { get { onComplete(emailService.getEmailSenders) { case Success(emailSenders) => complete(Json.toJson(emailSenders)) case Failure(e) => complete(StatusCodes.InternalServerError, "An error occurred: " + e.getMessage) } } } }
path
-> post
-> entity
-> complete
式的函數嵌套,很直觀的定義出了聲明式的樹型REST URI
結構,井井有條、邏輯清晰。entity
函數用於解析Http Body
,將其映射成但願的數據類型,可自定義映射方法。
onComplete
函數用在返回類型是一個Future[T]
時,提供了快捷的方式把一個Future[T]
類型的響應轉換到complete
。
郵件的發送採用了串行發送的方式,這個模式恰好契合Actor
默認郵箱的FIFO
處理形式。把收到的郵件發送請求告訴一個actor
,actor
再從郵箱裏取出,並組裝成XXXXEmail
(郵件發送使用了commons-email)後發送出去。
首先,程序將收到的郵件發送請求交給EmailMaster
,EmailMaster
再根據郵件發送者(鏈接SMTP的郵箱用戶名)來決定將這個發送請求交給哪個具體的EmailGroupActor
。
這裏,程序對郵件發送者(簡稱:sender
)作了一個分組。由於對於使用相同smtp
郵件發送服務提供的sender
,程序中最後對此類的sender
作串行發送。而對於不一樣smtp
郵件發送服務提供的sender
,咱們能夠併發的發送郵件。這個能夠經過在定義配置文件的時候指定特定sender
屬於的郵件發送組。
emails { email1 { userName = "email1@email.com" password = "" smtp = "" smtpPort = 0 ssl = true # 同一個組內的郵件會串行發送,此key可忽略 group = "1" } }
ActiveMQ
鏈接ActiveMQ
使用了JMS
協議,這是一個Java EE
標準實現的消息隊列。代碼在:MQConsumerService。
在JMS
裏,郵件使用MapMessage
消息發送,程序使用case match
來匹配指望的消息格式。
val listener = new MessageListener { override def onMessage(message: Message): Unit = message match { case msg: MapMessage => { val subject = msg.getString("subject") val sender = msg.getString("sender") val content = msg.getString("content") val to = msg.getString("to").split(";") val mimeType = Option(msg.getString("mimeType")).map(MimeType.withName).getOrElse(MimeType.Text) val sendEmail = SendEmail(sender, subject, to, content, None, mimeType) emailService.sendEmail(sendEmail).onComplete(result => logger.debug(result.toString)) }
本文簡單的演示了Akka Http
構建一個REST
服務,並支持鏈接JMS Server
來獲取發送郵件消息。
演示了文件郵件和HTML
格式郵件的發送。
接下來能夠添加對郵件附件的支持,這個功能能夠留給讀者去實現。