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

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

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

异步工作的任务
一个最简单的实现方法是在Web应用的某个地方定义如下的页面:

用户点击”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

1 object 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

1 package controllers
2  
3 import models.PickList
4 import play.api.mvc.{Action, Controller}
5  
6 import scala.concurrent.{ExecutionContext,future}
7  
8 object 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

1 import play.api.GlobalSettings
2 import akka.actor.{Actor,Props}
3 import models.Warehouse
4 import play.api.libs.concurrent.Akka
5 import play.api.templates.Html
6 import play.api.libs.concurrent.Execution.Implicits.defaultContext
7  
8 object 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 一个可能的实现:

1 class 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函数库构建一个定时执行的任务。

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

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

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

1 package controllers
2 import play.api.mvc.{Action, Controller}
3 import concurrent.{ExecutionContext, Future}
4 object 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 Web开发教程(19): 任务–启动一些进程,布布扣,bubuko.com

时间: 2024-08-13 16:11:36

Play Framework Web开发教程(19): 任务–启动一些进程的相关文章

Play Framework Web开发教程(16): 处理HTTP请求和响应

设计应用的一个方面是规划HTTP请求的URL规范,超链接,HTTP表单以及可能的公用API接口.在Play这是通过路由配置,然后在控制器中实现相应的接口.Play应用中的路由配置可以Controller类构成了MVC框架中的控制层,如下图所示:在Play应用中Controller为定义了HTTP接口的Scala类,而你的路由配置决定了给定的HTTP请求调用哪个Controller中定义的方法,这些Controller中的方法称为Action(动作),因此Play 的MVC框架也称为基于"动作&q

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

和你编写代码类似,你编写的页面也可以由多个小的片段组合而成,这些小的片段本身也可以由更小的片段构成.这些小片段通常是可以在其它页面重复使用的:有些部分可以用在所有页面,而有些部分是某些页面特定的.本篇介绍如何使用这些可重用的小模板来构成整个页面.Includes到目前为止的例子,我们只显示了HTML的片段,没有实例显示整个页面.下面我们给出完整的显现产品列表的代码和模板: 1 def catalog() = Action { 2     val products = ProductDAO.lis

Twisted web开发教程

最近在网上看到一篇twisted web开发文章,将它实践了一下,twisted 提供基本的url路由 和 控制器,模板与模型需要外部扩展 1.目录浏览 2.get请求 3.url路由 4.接受带参数的get请求 5.获取和过期session 获取session 手动设置session过期 再获取一次看是否过期 5-1.session实现一个页面计数器 5.2 移除session 6.get与post请求,表单编写

推荐使用Tiny Framework web开发UI组件

TINY FRAMEWORK 基于组件化的J2EE开发框架,from:http://www.tinygroup.org/ 名字 Tiny名称的来历 取名Tiny是取其微不足道,微小之意. Tiny的构建者认为,一个J2EE开发框架是非常复杂的,只有把框架分解成非常细小.可控的部分,并且对每个细小.可控的部分都有一个最优解或相对最优解, 那么整个方案也就可以非常不错的落地. 策略 Tiny框架的构建策略 Think big, start small, scale fast. 想法要宏伟,但是要从小

谷歌浏览器web开发教程之开始篇:使用sublime

你的代码编辑器是主要的开发工具:你使用它去编辑和保存代码段.你可以通过学习编辑器快捷键和以及安装关键插件来好而快的写出代码. 目录 安装sublime文本编辑器 为什么使用包管理器? 安装插件 摘要 选择一个编辑器,定制快捷键,并安装插件来帮助你写出更好的代码. 利用软件包管理器,使其更容易发现.安装和更新插件. 安装插件,有助于提高你编辑代码的效率:从本指南中的建议的插件开始. 安装sublime编辑器 Sublime是一个伟大的编辑器,拥有很多强大的,可定制的功能,这增添了你编写代码的乐趣.

android开发教程之开机启动服务service示例

个例子实现的功能是:1,安装程序后看的一个Activity程序界面,里面有个按钮,点击按钮就会启动一个Service服务,此时在设置程序管理里面会看的有个Activity和一个Service服务运行2,如果手机关机重启,会触发你的程序里面的Service服务,当然,手机启动后是看不到你的程序界面.好比手机里面自带的闹钟功能,手机重启看不到闹钟设置界面只是启动服务,时间到了,闹钟就好响铃提醒. 程序代码是: 首先要有一个用于开机启动的Activity,给你们的按钮设置OnClickListener

WEB开发教程--ASP中静态数组与动态数组的用法

在ASP中,数组是具有相同名字的一组变量,数组中包含多个元素,由不同的下标值区分数组的各个元素.在VBScript中,数组有两种类型:静态数组和动态数组. 1.静态数组 静态数组在编译时开辟内存区,数组大小在运行时不可改变. 定义一个一维数组mmArray(3) Dim mmArray(3) mmArray(0)=1 mmArray(1)=3 mmArray(2)=5 mmArray(3)=7 其中mmArray是数组名,数组的下界为0,上界为3,数组元素从mmArray(0)到mmArray(

SpringBoot整合WEB开发--(八)启动任务系统

简介: 有一些特殊的任务需要在系统启动时执行,例如配置文件的加载,数据库初始化等操作,如果没有使用SpringBoot,这些问题可以在Listener中解决.SpringBoot提供了两种解决方案:CommandLineRunner和ApplicationRunner,这两个差别主要体现在参数上. 1.CommandLineRunner SpringBoot项目在启动时会遍历所有的CommandLineRunner的实现类并调用其中的run方法,如果整个系统中有多个CommandLineRunn

DuiVision开发教程(19)-菜单

DuiVision菜单类是CDuiMenu,有两种显示的位置,一种是在窗口顶部某个按钮点击后可以下拉一个菜单,另一种是托盘图标的右键菜单. 窗口中的菜单定义方式是xml文件中设置某个按钮的action属性,以menu:开头,后面是菜单的XML文件名或XML定义名,例如下面这样定义: <imgbtn name="button.menu" pos="-110,0,-77,29" skin="IDB_BT_MENU" tip="菜单&q