Play2 + ReactiveMongo 實現一個活動報名應用

  • Play 2: https://playframework.com
  • ReactiveMongo: http://reactivemongo.org

代碼在: http://git.oschina.net/socialcredits/social-credits-activity-service前端

公司要作一些活動,須要一個線上的活動報名應用。想着前幾天恰好看了下 ReactiveMongo ,以爲這個小應用正好練練手。react

這個活動應用的功能很是簡單:用戶在線填寫表單,提交表單,後臺存儲數據並向指定的專員發送郵箱通知。git

Play 項目

整個項目目錄結構以下:數據庫

├── app
│   ├── controllers
│   │   └── inapi
│   ├── utils
│   └── views
│       └── activity
├── conf
├── data
│   └── src
│       └── main
├── platform
│   └── src
│       └── main
├── project
├── static
│   └── src
│       └── site
└── util
    └── src
        ├── main

appconf都是 Play 的標準目錄,分別放置代碼文件和項目配置文件。app.views 包下的是Play的模板頁面文件。gulp

static 是用於放置前端源文件的,包括:jssass等,使用gulp編譯,並輸入到 public 目錄。api

platform 目錄放置一些業務代碼,好比:Service。sass

data 目錄是數據相關類的存放地,包括modeldomain和數據庫訪問代碼,一此數據類相關的隱式轉換代碼也放置在此。app

util 就是工具庫了,包括常量定義、配置文件讀取、枚舉等。dom

ReactiveMongo

connection mongo collection

使用 ReactiveMongo 鏈接數據庫須要先建立一個 MongoDrvier ,並調用 driver.connection 方法建立鏈接,進而經過 conn.db 方法獲取一個數據庫訪問。ide

MyDriver.scala

class MyDriver private() {
  val driver = new MongoDriver()

  def connection = driver.connection(List(Settings.mongo.host))

  private def db(implicit ex: ExecutionContext) = connection.db(Settings.mongo.dbName)

  def collActivity(implicit ex: ExecutionContext) = db.collection[BSONCollection]("activity")

  def collActivityRegistration(implicit ex: ExecutionContext) = db.collection[BSONCollection]("activityRegistration")
}

case class 與 BSON的轉換。

使用 Macros.handler 是最簡單的實現 case classBSON 轉換的方法,它用到了 scala macro。代碼如:implicit val __activityHandler = Macros.handler[Activity]

BSONImplicits

implicit object LocalDateTimeHandler extends BSONHandler[BSONDateTime, LocalDateTime] {
  override def read(bson: BSONDateTime): LocalDateTime =
    LocalDateTime.ofInstant(Instant.ofEpochMilli(bson.value), ZoneOffset.ofHours(8))
  
  override def write(t: LocalDateTime): BSONDateTime =
    BSONDateTime(t.toInstant(ZoneOffset.ofHours(8)).toEpochMilli)
}

implicit val __activityHandler = Macros.handler[Activity]

數據庫訪問

查找一個Activity使用 find() 方法獲取一個訪問數據庫遊標,再在遊標上調用 .one[Activity] 方法便可獲取一個 Activity 對象,以 Option[Activity]

ActivityRepo

def findOneById(id: BSONObjectID): Future[Option[Activity]] = {
  activityColl.find(BSONDocument("_id" -> id)).one[Activity]
}

發送郵件

郵箱發送使用了 commons-email ,發送郵件的代碼很是簡單。

EmailService

@Singleton
class EmailService {
  private val emailSenderActor = Akka.system.actorOf(Props[EmailServiceActor], "email-sender")

  def sendEmail(id: String, subject: String, content: String): Unit = {
    emailSenderActor ! SendEmail(id, subject, content)
  }
}

class EmailServiceActor extends Actor with StrictLogging {
  override def receive: Receive = {
    case SendEmail(id, subject, content) =>
      val email = new SimpleEmail()
      email.setHostName(Settings.email.hostName)
      email.setSmtpPort(Settings.email.portSsl)
      email.setSSLOnConnect(true)
      email.setAuthenticator(new DefaultAuthenticator(Settings.email.username, Settings.email.password))
      email.setFrom(Settings.email.from)
      email.setSubject(subject)
      email.setMsg(content)
      email.addTo(Settings.email.to: _*)
      logger.info(
        s"""id: $id
            |from: ${Settings.email.from}
            |to: ${Settings.email.to}
            |$subject
            |$content""".stripMargin)
      val result = email.send()
      logger.info(
        s"""id: $id
            |[result] $result""".stripMargin)
  }
}

程序中使用了一個 Actor 來對郵件發送動做作隊列化處理,感受有點小題大做。不過 Actor 默認郵箱是FIFO的,這個特性很適合發送郵件的隊列操做。

相關文章
相關標籤/搜索