Play Framework Web开发教程(19): 任务–启动一些进程

原创 2014年08月17日 21:44:14

有些时候,一个Web应用有需要在正常的请求-响应周期之外执行一些代码,比如一些常时间运行的后台任务,或者也是在请求-响应周期中执行,但无需用户交互。
比如我们回到之前的产品分类的例子,我们需要跟踪订单是否有人拣选,打包了和发货了。拣选货物涉及了某个人根据订单在仓库中查找订单中的物品,然后可以打包这些货品,交给物流发货。一个实现方法是生成新图所示的货品目录的物品拣选单(和HTML表单无关)。
20140817001

在过去很长的一段时间内,系统构架都假定这些任务都在Web应用外实现,比如在一些旧系统中的批量任务生成。 而今天的系统很多是为Web为中心的,或者是基本云服务的。使用这些架构意味着我们需要在Web应用中有能够调度和执行这些任务。
为了更好的说明问题,我们考虑这样一个系统,这个系统用来生成物品拣选单并把拣选单发给仓库管理员,我们假定我们需要使用批量处理,因为生成一个拣选任务比较费时,而且我们需要优化这些任务的顺序以使得访问不同仓库的时间最小。

异步工作的任务
一个最简单的实现方法是在Web应用的某个地方定义如下的页面:
20140817002
用户点击”Generate & Send Pick List”按钮触发这个任务。

拣选单中的每个项目都是一个从指定仓库中提取物品的准备单,我们可以使用如下View模板来显示这个拣选单,
文件app/views/pickList.scala.html

1@(warehouse: String, list: List[models.Preparation],
2        time: java.util.Date)
3 
4@main("Warehouse " + warehouse + " pick list for " + time){
5    <table>
6        <tr>
7            <th>Order #</th>
8            <th>Product EAN</th>
9            <th>Product description</th>
10            <th>Quantity</th>
11            <th>Location</th>
12        </tr>
13        @for((preparation,index) <- list.zipWithIndex){
14            <tr@(if (index %2 ==0 " class = 'odd'") >
15                <th>@preparation.orderNumber</th>
16                <th>@preparation.product.ean</th>
17                <th>@preparation.product.description</th>
18                <th>@preparation.quantity</th>
19                <th>@preparation.location</th>
20        </tr> }
21    </table>
22}

通常显现这个View模板的情况是在一个Controller的某个Action中显示这个页面,比如,你可能会在浏览器中预览这个PickList:
文件:app/controllers/PickLists.scala

1object PickLists extends Controller{
2  def preview (warehouse:String) = Action {
3    val pickList = PickList.find (warehouse)
4    val timestamp = new java.util.Date
5    Ok (views.html.pickList(warehouse,pickList,timestamp))
6 
7  }
8}

然而,我们希望在另外的进程中生成,显示和发送PickList,从而使得这个过程和Controller中给浏览器发送响应消息的工作独立开来。
我们首先可以使用Scala的futures 来异步执行一些代码
文件app/controllers/PickLists.scala

1package controllers
2 
3import models.PickList
4import play.api.mvc.{Action, Controller}
5 
6import scala.concurrent.{ExecutionContext,future}
7 
8object PickLists extends Controller{
9 ...
10 
11  def sendAsync (warehouse:String) = Action {
12    import ExecutionContext.Implicits.global
13    future {
14      val pickList = PickList.find (warehouse)
15      send(views.html.pickList(warehouse,pickList,new java.util.Date))
16    }
17    Redirect(routes.PickLists.index())
18 
19  }
20}

这里我们使用了scala.concurrent.future来封装了一些异步执行的代码,这意味着不管send执行需要多久的时间,这个Action会立马执行网页的重定向到routes.PickLists.index。这时send发生在另外的一个execution context中。

任务调度
根据我们仓库如何工作的不同,可能自动每隔半小时自动生成pick list更为有效。为了实现这个功能,我们需要每隔半小时触发这个任务而无需人工干预。Play没有直接支持任务调度,但Play集成了Akka(参见Akka教程),因此我们可以使用Akka来周期调度这个任务,我们无需用户界面,而是在Play应用开始时创建和调度一个actor.
文件:app/Global.scala

1import play.api.GlobalSettings
2import akka.actor.{Actor,Props}
3import models.Warehouse
4import play.api.libs.concurrent.Akka
5import play.api.templates.Html
6import play.api.libs.concurrent.Execution.Implicits.defaultContext
7 
8object Global extends GlobalSettings{
9  override def onStart(application:play.api.Application) {
10    import scala.concurrent.duration._
11    import play.api.Play.current
12 
13    for(warehouse <- Warehouse.find()){
14      val actor = Akka.system.actorOf(
15        Props(new PickListActor(warehouse))
16      )
17 
18      Akka.system.scheduler.schedule(0.seconds,30.minutes,actor,"send")
19    }
20  }
21}

这段代码中应用启动时为每个仓库创建一个Actor,然后我们利用Akka的调度器 API 创建一个每半小时执行的任务。下面为PickListActor 一个可能的实现:

1class PickListActor(warehouse: String) extends Actor {
2  def receive = {
3    case "send" => {
4      val pickList = PickList.find(warehouse)
5      val html = views.html.pickList(warehouse, pickList, new Date)
6      send(html)
7    }
8    case _ => play.api.Logger.warn("unsupported message type")
9  }
10  def send(html: Html) {
11    // ...
12  }
13}

其中send的具体实现无关紧要,重点是我们可以利用Akka函数库构建一个定时执行的任务。

异步返回结果和暂停的请求
之前我们介绍了在另外一个线程中执行一个较长运行的任务,那是一个不需要返回结果的情况,但有时我们需要异步返回的结果。
比如,我们需要显示一个仪表盘来显示当前订单量的数目–也就是给定仓库的需要拣选和发货的订单数,这意味着检查所有订单并返回订单数目。
比如,我们使用如下一个函数返回所有的订单:

1val backlog = models.Order.backlog(warehouse)

假定这个函数执行的时间比较长。我们使用一个异步操作来获取订单,暂停HTTP请求,在等待结果的同时可以处理其它请求,下面是一个可能的实现:
文件:pp/controllers/Dashboard.scala

1package controllers
2import play.api.mvc.{Action, Controller}
3import concurrent.{ExecutionContext, Future}
4object Dashboard extends Controller {
5  def backlog(warehouse: String) = Action {
6    import ExecutionContext.Implicits.global
7    val backlog = scala.concurrent.future {
8      models.Order.backlog(warehouse)
9    }
10    Async {
11      backlog.map(value => Ok(value))
12    }
13  }
14}

这里scala.concurrent.future 返回一个promise 来封装一个尚未有结果的对象,它的类型为Future[String],代表一个String类型的占位符。
关于Future它代表一个可能还没有结束的结束过程,我们在后面这详细介绍,而且本篇的例子不是个完整的例子,这里只需要了解Web编程中异步操作的基本概念就可以了。

play framework 添加启动任务、定时任务

前人总结了很多东西,真的受益良多,有一个详细的博客在这里,看完立马就明白,我这里只是做一个记录,方便日后查找 http://desert3.iteye.com/blog/1586708 Play ...
  • fhzaitian
  • fhzaitian
  • 2015年07月21日 09:55
  • 3263

play framework 2.5.3 学习和使用过程中的“坑”

play framework 2.5.3 学习和使用过程中的“坑” 最近项目需要,接触到了play, 使用过程中,遇到了一些坑, 记下来。 1. 版本 play 分1.x和 2.x 两个系列,...
  • XuYongshi02
  • XuYongshi02
  • 2016年05月12日 19:20
  • 2213

Play Framework Web开发教程(33): 结构化页面-组合使用模板

和你编写代码类似,你编写的页面也可以由多个小的片段组合而成,这些小的片段本身也可以由更小的片段构成。这些小片段通常是可以在其它页面重复使用的:有些部分可以用在所有页面,而有些部分是某些页面特定的。本篇...
  • mapdigit
  • mapdigit
  • 2014年10月07日 08:34
  • 14122

Play framework 1.2.3 Jobs定时任务、异步任务、引导任务、触发任务、关闭任务

Play framework是一个web应用程序,大部分的应用逻辑都是通过在Controllers中以响应HTTP请求的方式来完成的。 有时候你可能需要执行一些和HTTP请求无关的应用逻辑。这在...
  • maguanghui_2012
  • maguanghui_2012
  • 2017年02月06日 13:16
  • 844

Scala语言与Play框架入门教程

Scala在业界已日益成为主流的编程语言和开发工具,与Java一样在Web开发领域的发展尤其令开发者关注,因此本文选取Scala语言中当前两个主要Web框架(Play、Lift)中的一个较易上手的Pl...
  • qq_14926159
  • qq_14926159
  • 2016年09月19日 16:34
  • 4101

play框架2.5.6教程——设置你喜欢的IDE

用Play操作很简单,你甚至不需要一个复杂的IDE,因为Play会自动地编译和更新你对你的资源所做的修改。所以你可以使用一个简单的文本编辑器进行操作。 然而使用一个现代的Java或者Scala I...
  • Java_Mike
  • Java_Mike
  • 2016年09月11日 15:47
  • 3430

play framework & 安装 & 创建新项目 & play常用命令

Play framework使用Apache V2协议是一个Java & Scala的程序框架和执行容器,使得更加容易地创建Web应用程序。。  Play基于一个轻量级、无状态、界面对用户友好的...
  • a502817870
  • a502817870
  • 2014年07月08日 18:06
  • 1909

playframework1.2.x 入门(四) 编写第一个页面

本篇引导重点在于使用playframework实现一个网页,使用groovy模板等技术。本系列代码已经上传至github,有需要的可以自行下载上一篇我们已经建立了博客的数据模型data model,现...
  • joson793847469
  • joson793847469
  • 2016年08月02日 22:29
  • 822

playframework拦截器和热加载 源码浅析

playframework拦截器和热加载源码浅析
  • rain082900
  • rain082900
  • 2013年12月24日 10:47
  • 3106

play framework2启动、编译、测试、转换eclipse工程项目

启动项目利用play控制台进入play控制台,进入项目目录,并打开cmd命令行,再执行:$ activator进入项目后在执行:[my-first-app] $ run这样项目就启动啦。不进入play...
  • u013066244
  • u013066244
  • 2016年12月22日 15:21
  • 1575
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Play Framework Web开发教程(19): 任务–启动一些进程
举报原因:
原因补充:

(最多只允许输入30个字)