10.6.4 管理物品

用户只有在登录后才可以操作管理物品模块,如果未登录用户单击页面上方的”管理物品”链接,则SpringAOP机制将负责提示用户登录。
权限检查的执行过程是、客户端调用前端处理方法,如果前端处理方法符合Spring AOP中指定的方法名,权限检查拦截器将检查目标方法的参数,如果调用方法时HttpSession中没有userId属性,则拦截器将返回一个Map对象,在该Map对象中封装了”您还没有登录,请先登录系统再执行操作”的错误提示信息。
当未登录用户单击”管理物品”链接(假如用户绕过了前端JS的权限检查)时,将看到如图10.13所示的提示框。
如果能通过权限检查,则用户单击”管理物品”链接时,主页面将会加载viewOwnerItem.html页面,该页面的代码如下。
程序清单:codes\10\auction\WebContent\viewOwnerItem.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script src="js/viewOwnerItem.js"></script>
<div class="container">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">您当前的拍卖物品:</h3>
<a id="addItem" ui-sref="addItem" style="margin-top: -24px"
role="button" class="btn btn-default btn-sm pull-right"
aria-label="添加"> <i class="glyphicon glyphicon-plus"></i>
</a>
</div>
<div class="panel-body">
<table class="table table-bordered">
<thead>
<tr align="center">
<th style="text-align: center;">物品名</th>
<th style="text-align: center;">物品种类</th>
<th style="text-align: center;">初始/最高价格</th>
<th style="text-align: center;">物品描述</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>

HTML页面代码只是定义了一个简单的.panel容器,该.panel容器内包含一个表格,用于装载jQuery从服务器端返回的数据。下面是该页面配套的viewOwnerItem.js文件代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$(function () {
// 添加物品,绑定添加物品的方法
$("#addItem").click(function () {
goPage("addItem.html");
});
$.get("auction/getItemsByOwner",
{},
function (data) {
// 如果data中包含错误提示,直接显示错误提示
if (data.error) {
$("#tip").html("<span style='color:red;'>" + data.error + "</span>");
$('#myModal').modal('show');
return;
}
// 遍历数据展示到表格中去
// 使用jQuery的方法来遍历JSON集合数据
$.each(data, function () {
// 把数据注入到表格的行中去
var tr = document.createElement("tr");
tr.align = "center";
$("<td/>").html(this.name).appendTo(tr);
$("<td/>").html(this.kind).appendTo(tr);
$("<td/>").html(this.maxPrice).appendTo(tr);
$("<td/>").html(this.remark).appendTo(tr);
$("tbody").append(tr);
})
});
// 取消对话框中两个按钮上绑定的事件处理函数
$("#sure").unbind("click");
$("#cancelBn").unbind("click");
})

上面的代码先为页面上ID为addItem的元素绑定事件处理函数,当用户单击该按钮时系统将会跳转到addItem.html页面。
接下来的粗体字代码使用jQueryget()方法向/auction/getItemByOwner发送请求,获取当前用户所拥有的拍卖物品。处于路径上的Spring MVC控制器方法将会返回当前用户的所有物品,这些物品被封装在JSON数据中。
如果用户成功登录,单击”管理物品”链接,将会看到如图10.14所示的界面。
从图10.14 可看到表格的左上角有一个"+"链接,单击该链接将会触发goPager()函数,该函数负责在页面上装载addItem.html页面。该静态页面的代码如下。
程序清单:codes\10\auction\WebContent\addItem.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<script src="js/addItem.js"></script>
<div class="container">
<div class="panel panel-info">
<div class="panel-heading">
<h4 class="panel-title">添加物品</h4>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">物品名:</label>
<div class="col-sm-4">
<input type="text" class="form-control"
id="name" name="name" minlength="2"
maxlength="10" required>
</div>
<label for="price" class="col-sm-2 control-label">起拍价格:</label>
<div class="col-sm-4">
<input type="number" min="0"
class="form-control" id="initPrice"
name="initPrice" min="0">
</div>
</div>
<div class="form-group">
<label for="author" class="col-sm-2 control-label">有效时间:</label>
<div class="col-sm-4">
<select class="form-control" name="avail"
id="avail">
<option value="1" selected="selected">一天</option>
<option value="2">二天</option>
<option value="3">三天</option>
<option value="4">四天</option>
<option value="5">五天</option>
<option value="7">一个星期</option>
<option value="30">一个月</option>
<option value="365">一年</option>
</select>
</div>
<label for="categoryId"
class="col-sm-2 control-label">物品种类:</label>
<div class="col-sm-4">
<select class="form-control" name="kind"
id="kind">
</select>
</div>
</div>
<div class="form-group">
<label for="price" class="col-sm-2 control-label">物品描述:</label>
<div class="col-sm-4">
<textarea type="text" class="form-control"
id="desc" name="desc" minlength="20"
required rows="4"></textarea>
</div>
<label for="categoryId"
class="col-sm-2 control-label">物品备注:</label>
<div class="col-sm-4">
<textarea type="text" class="form-control"
id="remark" name="remark" minlength="20"
required rows="4"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" id="addItem"
class="btn btn-success">添加</button>
<button id="cancel" role="button"
class="btn btn-danger">取消</button>
</div>
</div>
</form>
</div>
</div>
</div>

该页面只是一个简单的表单页面,表单中包含了用户需要输入的物品信息。当用户提交该表单时将会触发对应的JS处理函数。该JS脚本如下所示。
程序清单:codes\10\auction\WebContent\js\addItem.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 文档加载完成以后,给下拉框绑定切换事件 
$(function () {
$.get("auction/getAllKind",
{},
function (data) {
// 遍历数据展示到表格中去
// 使用jQuery的方法来遍历JSON集合数据
$.each(data, function () {
// 把数据注入到下拉列表中
$("<option/>").html(this.kindName).val(this.id).appendTo("#kind");
})
});
$(".form-horizontal").submit(function () {
$.post("auction/addItem",
$(".form-horizontal").serializeArray(),
function (data) {
// 添加物品成功
if (data == "success") {
// 添加物品成功了,先提示再跳转
$('#myModal').modal('show');
$("#tip").html("<span style='color:red;'>您添加物品成功了,请问要继续吗?</span>");
} else {
// 添加物品失败了
$('#myModal').modal('show');
$("#tip").html("<span style='color:red;'>添加物品失败了</span>");
}
});
return false;
});
// 取消对话框中两个按钮上绑定的事件处理函数
$("#sure").unbind("click");
$("#cancelBn").unbind("click");
// 为对话框中的"确定"按钮绑定单击事件处理函数
$("#sure").click(function () {
$('.form-horizontal').get(0).reset();
})
// 为对话框中的"取消"按钮绑定单击事件处理函数
$("#cancelBn").click(function () {
goPage("viewOwnerItem.html");
})
// 取消
$("#cancel").click(function () {
goPage("viewOwnerItem.html");
});
});

在添加物品时,应该先加载物品种类,因为添加物品时应指定物品种类。因此,JS 脚本先向auction/getAllKind 发送请求,该控制器方法将会返回系统所有的种类信息,系统通过jQuery 将这些种类信息加载并显示在页面的下拉列表中,表单页面允许用户通过下拉列表选择所添加物品的种类。用户进入添加物品的表单页面时可看到如图10.15所示的界面。
当提交表单时,粗体字代码调用jQuerypost方法向服务器发送异步请求,该请求将表单数据提交到服务器的auction/addItem,该URL负责处理添加物品的请求。
用户添加物品成功后即可看到如图10.16所示的提示信息。
如果用户单击提示框中的”确定”按钮,系统将停留在添加物品的表单页面,让用户可以继续添加物品;如果用户单击”取消”按钮,系统将返回物品列表页面,这样可以在添加物品成功后,立即在页面上看到新增的物品。

10.6.3 处理用户登录

该系统有一些功能只有登录用户才能使用。如果用户单击页面上方的”登录”链接,链接上的事件处理函数将会在目标页面上装载login.html页面,该页面的内容主要是一个表单—该页面不需要有完整的页面内容,只要有表单部分即可。下面是该页面的代码。
程序清单:codes\10\auction\WebContent\login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<script src="js/login.js"></script>
<div class="container">
<!-- 登录界面 -->
<div class="page-header">
<h1>用户登录</h1>
</div>
<form class="form-horizontal" method="post">
<!-- .form-group相当于.row对应响应式布局的一行 -->
<div class="form-group">
<div class="col-sm-6">
<input class="form-control" placeholder="用户名/邮箱"
type="text" id="loginUser" name="loginUser" required
minlength="3" maxlength="10" />
</div>
</div>
<div class="form-group">
<div class="col-sm-6">
<input class="form-control" placeholder="密码"
id="loginPass" type="password" name="loginPass"
required minlength="3" maxlength="10" />
</div>
</div>
<div class="form-group">
<div class="col-sm-6">
<div class="input-group">
<input class="form-control " id="vercode"
name="vercode" type="text" placeholder="验证码"
required pattern="\w{6}"> <span
class="input-group-addon" id="basic-addon2">
验证码:<img src="auth.jpg" name="d" id="d"
alt="验证码" />
</span>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-6">
<div class="btn-group btn-group-justified" role="group"
aria-label="...">
<div class="btn-group" role="group">
<button type="submit" id="loginBtn"
class="btn btn-success">
<span class="glyphicon glyphicon-log-in"></span>&nbsp;登录
</button>
</div>
</div>
</div>
</div>
</form>
</div>

这个页面就是一个简单的表单,当用户单击页面上的”登录”按钮时,系统会触发登录处理。登录处理由页面上加载的login.js负责。浏览该页面可以看到如图10.11所示的登录界面。

由图10.11可看到,系统登录使用了一个随机图形验证码。使用随机图形验证码是为了增加系统安全性,防止Cracker暴力破解。该图形验证码实质上就是一个Servlet,与普通的Servlet不同的是,该Servlet不生成文本内容,而是生成图形内容,其代码如下。
程序清单:codes\10\auction\src\org\crazyit\auction\web\AuthImg.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package org.crazyit.auction.web;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet(urlPatterns ={"/auth.jpg"})
public class AuthImg extends HttpServlet
{
private static final long serialVersionUID = 1L;
// 定义图形验证码中绘制字符的字体
private final Font mFont = new Font("Arial Black", Font.PLAIN, 16);
// 定义图形验证码的大小
private final int IMG_WIDTH = 100;
private final int IMG_HEIGTH = 18;
// 定义一个获取随机颜色的方法
private Color getRandColor(int fc, int bc)
{
Random random = new Random();
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
// 得到随机颜色
return new Color(r, g, b);
}
// 重写service方法,生成对客户端的响应
public void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
// 设置禁止缓存
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGTH,
BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200, 250));
// 填充背景色
g.fillRect(1, 1, IMG_WIDTH - 1, IMG_HEIGTH - 1);
// 为图形验证码绘制边框
g.setColor(new Color(102, 102, 102));
g.drawRect(0, 0, IMG_WIDTH - 1, IMG_HEIGTH - 1);
g.setColor(getRandColor(160, 200));
// 生成随机干扰线
for (int i = 0; i < 80; i++)
{
int x = random.nextInt(IMG_WIDTH - 1);
int y = random.nextInt(IMG_HEIGTH - 1);
int xl = random.nextInt(6) + 1;
int yl = random.nextInt(12) + 1;
g.drawLine(x, y, x + xl, y + yl);
}
g.setColor(getRandColor(160, 200));
// 生成随机干扰线
for (int i = 0; i < 80; i++)
{
int x = random.nextInt(IMG_WIDTH - 1);
int y = random.nextInt(IMG_HEIGTH - 1);
int xl = random.nextInt(12) + 1;
int yl = random.nextInt(6) + 1;
g.drawLine(x, y, x - xl, y - yl);
}
// 设置绘制字符的字体
g.setFont(mFont);
// 用于保存系统生成的随机字符串
String sRand = "";
for (int i = 0; i < 6; i++)
{
String tmp = getRandomChar();
sRand += tmp;
// 获取随机颜色
g.setColor(new Color(20 + random.nextInt(110),
20 + random.nextInt(110), 20 + random.nextInt(110)));
// 在图片上绘制系统生成的随机字符
g.drawString(tmp, 15 * i + 10, 15);
}
// 获取HttpSesssion对象
HttpSession session = request.getSession(true);
// 将随机字符串放入HttpSesssion对象中
session.setAttribute("rand", sRand);
g.dispose();
// 向输出流中输出图片
ImageIO.write(image, "JPEG", response.getOutputStream());
}
// 定义获取随机字符串方法
private String getRandomChar()
{
// 生成一个0、1、2的随机数字
int rand = (int) Math.round(Math.random() * 2);
long itmp = 0;
char ctmp = '\u0000';
switch (rand)
{
// 生成大写字母
case 1 :
itmp = Math.round(Math.random() * 25 + 65);
ctmp = (char) itmp;
return String.valueOf(ctmp);
// 生成小写字母
case 2 :
itmp = Math.round(Math.random() * 25 + 97);
ctmp = (char) itmp;
return String.valueOf(ctmp);
// 生成数字
default :
itmp = Math.round(Math.random() * 9);
return itmp + "";
}
}
}

编写完该Servlet后,使用Annotation将它的URL映射为/auth.jsp,这样即可在HTML页面中通过如下方式显示图形验证码:

1
<img name="d" src="auth.jsp" alt="验证码"/>

使用图形验证码与使用普通图片并没有太大的区别,唯一的区别是将原来指定普通图片的src属性改为指定图形验证码ServletURL
处理用户登录的login.js代码如下。
程序清单:codes\10\auction\WebContent\js\login.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 定义登录的JS
// 文档加载完成以后进行异步登录
$(function () {
// 取消对话框中两个按钮上绑定的事件处理函数
$("#sure").unbind("click");
$("#cancelBn").unbind("click");
$(".form-horizontal").submit(function () {
// 发起异步登录
$.post("auction/loginAjax", $(".form-horizontal").serializeArray()
, function (data) {
if (data.error) {
$("#tip").html("<span style='color:red;'>" + data.error + "</span>");
$('#myModal').modal('show');
return;
}
switch (data) {
case '-2': // 验证码不正确
$("#tip").html("<span style='color:red;'>您输入的验证码不正确,请重新输入</span>");
$("#vercode").val('');
break;
case '-1': // 用户名、密码不正确
$("#tip").html("<span style='color:red;'>您输入的用户名、密码不正确,请重新输入</span>");
$(".form-horizontal").get(0).reset(); // 重设表单
break;
case '1': // 用户名、密码正确
$("#tip").html("<span style='color:red;'>登录成功,您可以继续使用系统了</span>");
// 为对话框中的"确定"按钮绑定单击事件处理函数
$("#sure").click(function () {
goPage("home.html");
})
break;
}
// 显示对话框
$('#myModal').modal('show');
});
return false;
})
})

在代码的开始部分取消了对话框中两个按钮上绑定的事件处理函数,接下来粗体字代码使用jQuerypost()方法向auction/loginAjax提交异步请求,随后可处理用户的登录请求。

10.6.2 浏览所有流拍物品

浏览所有流拍物品需要调用前端处理组件的getFailItems()方法,调用该方法无须执行权限检查。所有用户都可以直接调用。当用户单击页面上方的”浏览流拍物品”链接时,将发送获取所有流拍物品的请求,对应的函数如下。
程序清单:codes\10\auction\WebContent\js\viewFail.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 异步加载所有的流拍物品 
$(function() {
$.get("auction/getFailItems", {}, function(data) {
// 遍历数据展示到表格中去
// 使用jQuery的方法来遍历JSON集合数据
$.each(data, function() {
// 把数据注入到表格的行中去
var tr = document.createElement("tr");
tr.align = "center";
$("<td/>").html(this.name).appendTo(tr);
$("<td/>").html(this.kind).appendTo(tr);
$("<td/>").html(this.maxPrice).appendTo(tr);
$("<td/>").html(this.remark).appendTo(tr);
$("tbody").append(tr);
})
});
})

在上面的代码中,使用jQueryget()方法向auction/getFailItems发送请求,该方法指定的回调函数将会负责迭代服务器端返回的JSON 数据,并将数据添加到页面上的<tbody>元素内。
查看流拍物品的HTML页面代码如下。
程序清单:codes\10\auction\WebContent\viewFail.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script src="js/viewFail.js"></script>
<div class="container">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">所有流拍的物品</h3>
</div>
<div class="panel-body">
<table class="table table-bordered table-hover">
<thead>
<tr align="center">
<th style="text-align: center;">物品名</th>
<th style="text-align: center;">物品种类</th>
<th style="text-align: center;">初始/最高价格</th>
<th style="text-align: center;">物品描述</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>

从上面的页面代码可以看出,该页面只包含一个Bootstrap表单,在该表单内包含一个空的<tbody>元素,用于装载、显示从服务器端返回的流拍物品。如果系统中包含流拍物品,将可看到如图10.10所示的界面。

10.6 前端整合开发

一旦完成了上面这些定义,就可以在浏览器端通过jQuery与前端控制器暴露的JSON接口交互,向前端JSON接口提交请求,获取前端JSON接口返回的数据。jQuery还可以将前端接口返回的数据动态更新到HTML页面上。

10.6.1 定义系统首页

系统首页主要包含三大块静态内容:

  1. 页面上方包含的导航条、
  2. 页面下方的页脚信息
  3. 对话框,其中,对话框主要用于为各子页面弹出提示信息。

该页面代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<!DOCTYPE html>
<html>
<head>
<meta name="author" content="Yeeku.H.Lee(CrazyIt.org)" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>电子拍卖系统</title>
<!-- 导入Bootstrap 3的CSS样式的样式 -->
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" />
<!-- 导入Bootrap 3的JS插件所依赖的jQuery库 -->
<script src="jquery/jquery-3.1.1.js"></script>
<!-- 导入Bootstrap 3的JS插件库 -->
<script src="bootstrap/js/bootstrap.min.js"></script>
<!-- 导入本页面的JS库 -->
<script src="js/index.js"></script>
</head>
<body>
<!-- 导航条 -->
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed"
data-toggle="collapse" data-target="#menu">
<span class="sr-only">Toggle navigation</span> <span
class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- 标题栏 -->
<a class="navbar-brand" href="#">
<div>
<img alt="图书管理" src="images/fklogo.gif"
style="width: 52px; height: 52px">&nbsp;<strong>电子拍卖系统</strong>
</div>
</a>
</div>
<div class="collapse navbar-collapse" id="menu">
<ul class="nav navbar-nav">
<!-- 导航链接 -->
<li><a href="#" onclick="goPage('login.html');">登录</a></li>
<li><a href="#" onclick="goPage('viewSucc.html');">查看竞得物品</a></li>
<li><a href="#" onclick="goPage('viewFail.html');">浏览流拍物品</a></li>
<li><a href="#"
onclick="goPage('viewCategory.html');">管理种类</a></li>
<li><a href="#"
onclick="goPage('viewOwnerItem.html');">管理物品</a></li>
<li><a href="#" onclick="goPage('viewInBid.html');">浏览拍卖物品</a></li>
<li><a href="#" onclick="goPage('viewBid.html');">查看自己竞标</a></li>
<li><a href="#" onclick="goPage('home.html');">返回首页</a></li>
</ul>
</div>
</nav>

<div style="height: 50px;"></div>
<!-- 主体内容 -->
<div class="container">
<!-- 动态展示 -->
<div id="data" style="margin-top: 10px;">
<!-- 此处用于动态的去展示各种信息 -->
</div>
<hr>
<!-- 页脚 -->
<footer>
<p align="center">
All Rights Reserved.&copy; <a
href="http://www.crazyit.org">http://www.crazyit.org</a><br />
版权所有 Copyright&copy;2010-2018 Yeeku.H.Lee <br />
如有任何问题和建议,请登录 <a href="http://www.crazyit.org">http://www.crazyit.org</a><br />
</p>
</footer>
</div>

<!-- 弹出框 -->
<div id="myModal" class="modal bs-example-modal-sm fade">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close"
data-dismiss="modal">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">提示</h4>
</div>
<div class="modal-body">
<p id="tip"></p>
</div>
<div class="modal-footer">
<button id="cancelBn" type="button"
class="btn btn-default" data-dismiss="modal">取消</button>
<button id="sure" type="button"
class="btn btn-primary" data-dismiss="modal">确定</button>
</div>
</div>
</div>
</div>

</body>
</html>

在该页面代码中,在<head>元素内导入了BootstrapCSS样式单和JS库,以及jQuery的JS库等,这样该页面就可使用BootstapjQuery了。
<body>元素的开头部分包含一个导航条,该导航条内每个导航链接都为onclick事件绑定了事件处理函数,用于控制当用户单击该页面时跳转到对应的内容页面。
中间的粗体字代码处包含一个ID为data<div>元素,该元素是一个容器,用于装载各功能页面。前面定义的goPage()函数会调用jQueryload()函数装载目标页面片段,这样被装载的页面片段只要更新需要更新的部分即可。
页面上的<footer>元素定义页脚部分。
页面的最后部分定义了一个对话框,这个对话框用于显示提示信息,它可以被多个目标页面调用。

10.5.3 处理前端权限控制

在本系统中有些页面需要用户登录才能处理,因此系统会对这些页面进行权限检查。因此,本系统额外定义了一个控制器。该控制器检查HttpSession中是否包含用户登录ID,如果包含登录的用户ID,则表明用户已经登录,否则提示用户登录系统。下面是用于权限检查的控制器类。
程序清单:codes\10\auction\src\org\crazyit\auction\controller\authority\AuthController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.crazyit.auction.controller.authority;

import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/auction")
public class AuthController
{
@RequestMapping(value = "/authLogin")
@ResponseBody
public String preHandle(HttpSession session) throws Exception
{
// 1.在请求拦截执行之前 ,判断请求是否有权限
// 取出HttpSession里的userId属性
Integer userId = (Integer) session.getAttribute("userId");
// 如果HttpSession里的userId属性为null,或小于等于0
if (userId == null || userId <= 0)
{
// 如果还未登录,抛出异常
return "false";
} else
{
return "true";
}
}
}

接下来为了让该控制器返回结果,程序会在请求页面之前先向该控制器发送请求,只有判断用户登录之后才能继续请求目标页面。下面goPage()函数负责页面跳转功能,该函数判断被跳转页面是否需要进行权限检查,该函数的代码如下。
程序清单:codes\10\auction\WebContent\js\index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义一个方法用于处理菜单功能 
function goPage(pager) {
// 如果是需要被拦截的功能先做登录校验
if (pager == "viewSucc.html" || pager == "viewOwnerItem.html"
|| pager == "viewBid.html" || pager == "addItem.html"
|| pager == "addBid.html") {
$.post("auction/authLogin", {}, function(data) {
if (data != "true") {
$('#myModal').modal('show');
$("#tip").html(
"<span style='color:red;'>您还没有登录,请先登录再执行该操作</span>");
} else {
// 将pager装载到ID为data的元素中显示
$("#data").load(pager);
history.pushState(null, null, pager);
}
});
} else {
// 将pager装载到ID为data的元素中显示
$("#data").load(pager);
history.pushState(null, null, pager);
}
}

正如上面代码所示所示,只要程序访问列出的那些页面,系统将会先判断用户是否登录,如果用户并未登录则会提示用户需要登录
前面介绍的只是浏览器端JS的权限控制,此外还需要在前端控制器中执行权限检查。本系统考虑使用Around Advice来进行权限控制:Around Advice检查调用者的HttpSession状态,如果HttpSession包含了用户登录信息,就允许调用者调用前端处理方法;否则返回提示信息,提示用户登录。
提示:Around AdviceAOP编程中Advice的一种,关于Spring AOPAround Advice的内容,请参考”疯狂Java体系”的《轻量级Java EE企业应用实战》。
Around Advice类的代码如下。
程序清单:codes\10\auction\src\org\crazyit\auction\controller\authority\AuthorityInterceptor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package org.crazyit.auction.controller.authority;

import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.aspectj.lang.ProceedingJoinPoint;

public class AuthorityInterceptor
{
// 进行权限检查的方法
public Object authority(ProceedingJoinPoint jp) throws Throwable
{
System.out.println("----------------------=====--------------------");
HttpSession sess = null;
// 获取被拦截方法的全部参数
Object[] args = jp.getArgs();
// 遍历被拦截方法的全部参数
for (int i = 0; i < args.length; i++)
{
// 找到HttpSession类型的参数
if (args[i] instanceof HttpSession)
sess = (HttpSession) args[i];
}
// 取出HttpSession里的userId属性
Integer userId = (Integer) sess.getAttribute("userId");
// 如果HttpSession里的userId属性为null,或小于等于0
if (userId == null || userId <= 0)
{
// 如果还未登录,抛出异常
Map<String, String> map = new HashMap<>();
map.put("error", "您还没有登录,必须先登录才能使用该功能");
return map;
}
return jp.proceed();
}
}

正如我们从上面代码所看到的,Around Advice 会检查浏览者的HttpSession 状态,如果HttpSession中的userIdnulluserId的值小于0,则系统返回一个Map对象,该对象包含了提示用户登录的信息。
定义了该Around Advice之后,还需要将它配置在SpringMVC控制器所在的容器中。
程序清单:codes\10\auction\WebContent\WEB-INF\springmvc-servlet.xml

粗体字代码指定在执行org.crazyit.auction.controller 包下任意类的系列方法之前,先调用authorityAspectauthority()方法,该方法用于进行权限控制:保证只有具有相应权限的浏览者才能调用前端处理方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 定义一个普通Bean实例,该Bean实例将进行权限控制 -->
<bean
id="authority"
class="org.crazyit.auction.controller.authority.AuthorityInterceptor" />

<aop:config>
<!-- 将authority转换成切面Bean -->
<aop:aspect ref="authority">
<!-- 定义一个Before增强处理,直接指定切入点表达式 以切面Bean中的authority()方法作为增强处理方法 -->
<aop:around
pointcut="execution(* org.crazyit.auction.controller.*.getItemByWiner(..))
or execution(* org.crazyit.auction.controller.*.getBidByUser(..))
or execution(* org.crazyit.auction.controller.*.getItemsByOwner(..))
or execution(* org.crazyit.auction.controller.*.addItem(..))
or execution(* org.crazyit.auction.controller.*.addBid(..))"
method="authority" />
</aop:aspect>
</aop:config>

10.5.2 开发Spring MVC控制器

本例的Spring MVC控制器会采用注解的方式工作,因此只要为这些控制器、控制器方法增加相应的注解修饰,这些控制器即可对外暴露JSON接口。下面是本例的Spring MVC控制器代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package org.crazyit.auction.controller;

import java.util.List;
import javax.servlet.http.HttpSession;
import org.crazyit.auction.business.ItemBean;
import org.crazyit.auction.domain.Bid;
import org.crazyit.auction.domain.Item;
import org.crazyit.auction.domain.Kind;
import org.crazyit.auction.service.AuctionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


@Controller
@RequestMapping("/auction")
public class AuctionController
{
// 该前端处理类所依赖的业务逻辑组件
@Autowired
private AuctionService auctionService;

// 通过赢取者获取物品的方法
@GetMapping("/getItemByWiner")
@ResponseBody
public Object getItemByWiner(HttpSession sess) throws Exception
{
// 从HttpSession中取出userId属性
Integer winerId = (Integer) sess.getAttribute("userId");
List<ItemBean> itembeans = auctionService.getItemByWiner(winerId);
return itembeans;
}

// 获取所有流拍物品的方法
@GetMapping("/getFailItems")
@ResponseBody
public Object getFailItems() throws Exception
{
return auctionService.getFailItems();
}

// 处理用户登录的方法
@PostMapping(value = "/loginAjax")
@ResponseBody
public Object validLogin(String loginUser, String loginPass, String vercode,
HttpSession sess) throws Exception
{
String rand = (String) sess.getAttribute("rand");
if (rand != null && !rand.equalsIgnoreCase(vercode))
{
return "-2";
}
int result = auctionService.validLogin(loginUser, loginPass);
if (result > 0)
{
sess.setAttribute("userId", result);
return "1";
}
return "-1";
}

// 获取用户竞价的方法
@GetMapping("/getBidByUser")
@ResponseBody
public Object getBidByUser(HttpSession sess) throws Exception
{
// 从HttpSession中取出userId属性
Integer userId = (Integer) sess.getAttribute("userId");
return auctionService.getBidByUser(userId);
}

@GetMapping("/getItemsByOwner")
@ResponseBody
// 根据属主获取物品的方法
public Object getItemsByOwner(HttpSession sess) throws Exception
{
// 从HttpSession中取出userId属性
Integer userId = (Integer) sess.getAttribute("userId");
return auctionService.getItemsByOwner(userId);
}

// 获取所有物品种类的方法
@GetMapping("/getAllKind")
@ResponseBody
public Object getAllKind() throws Exception
{
return auctionService.getAllKind();
}

// 添加物品的方法
@PostMapping("/addItem")
@ResponseBody
public Object addItem(String name, String desc, String remark,
double initPrice, int avail, int kind, HttpSession sess)
throws Exception
{
// 从HttpSession中取出userId属性
Integer userId = (Integer) sess.getAttribute("userId");
Item item = new Item();
item.setItemName(name);
item.setItemDesc(desc);
item.setItemRemark(remark);
item.setInitPrice(initPrice);
int rowNum = auctionService.addItem(item, avail, kind, userId);
return rowNum > 0 ? "success" : "error";
}

// 添加种类的方法
@PostMapping("/addKind")
@ResponseBody
public Object addKind(String name, String desc) throws Exception
{
Kind kind = new Kind();
kind.setKindName(name);
kind.setKindDesc(desc);
int rowNum = auctionService.addKind(kind);
return rowNum > 0 ? "success" : "error";
}

// 根据种类获取物品的方法
@PostMapping("/getItemsByKind")
@ResponseBody
public Object getItemsByKind(int kindId) throws Exception
{
return auctionService.getItemsByKind(kindId);
}
// 添加竞价记录的方法
@PostMapping("/addBid")
@ResponseBody
public Object addBid(int itemId, double bidPrice, HttpSession sess)
throws Exception
{
Integer userId = (Integer) sess.getAttribute("userId");
Bid bid = new Bid();
bid.setBidPrice(bidPrice);
int id = auctionService.addBid(itemId, bid, userId);
return id > 0 ? "success" : "error";
}
}

在该代码中先定义了一个AuctionController 类,并使用@RequestMapping("/auction")修饰该类,这意味着该控制器内的所有处理方法都位于/auction命名空间下。比如getItemByWiner()方法使用了@GetMapping("/getItemByWiner")修饰,这意味着该处理方法对应的URL/auction/getItemByWiner

10.5 开发前端JSON接口

本应用的后端Java EE部分会暴露JSON接口供前端调用,前端jQuery既可向JSON接口发送请求,也可通过JSON接口获取服务器端数据

10.5.1 初始化Spring容器

为了让Spring管理各组件,包括**DAO组件业务逻辑组件以及前端MVC控制器**,需要让Spring容器随Web应用的启动而被初始化。初始化Spring容器时,会将容器中所有Bean全部实例化。一旦Spring完成容器中Bean的创建,Spring容器中的控制器即可对外处理请求。
为了让Spring容器随Web应用的启动而被初始化,可以使用Listener配置。在web.xml文件中增加如下配置片段。

1
2
3
4
5
6
7
8
9
<!-- 配置Web应用启动时加载Spring容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/appCtx.xml,
/WEB-INF/daoCtx.xml</param-value>
</context-param>

由于本应用将**DAO组件业务逻辑组件**等分开配置,因此该配置片段指定了两个配置文件,两个配置文件都放在WEB-INF路径下,文件名分别是daoCtx.xmlappCtx.xml
增加了上面的配置片段后,Spring容器会随Web应用的启动而被初始化。初始化Spring容器时,其中的Bean也会随之被初始化。
此外,还需要在web.xml文件中配置Spring MVC的核心Servlet,该核心Servlet负责处理所有请求,对外暴露JSON操作接口,因此还需要在web.xml文件中配置Spring MVC的核心Servlet。下面是配置Spring MVC核心Servlet的代码。

1
2
3
4
5
6
7
8
9
10
<!-- 配置SpringMVC的核心Servlet: DispatcherServlet -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

在该配置片段中配置了Spring MVC 的核心Servlet:DispatcherServlet,并指定使用该Servlet处理所有请求。因此根据Spring MVC 的要求,该Servlet 会要求提供一份springmvc-servlet.xml配置文件,这份配置文件用于设置Spring框架如何获取控制器组件,以及如何处理静态HTMLJS图片等资源。下面是springmvc-servlet.xml配置文件的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?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:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 告诉Spring框架去哪些包下搜索控制器 -->
<context:component-scan
base-package="org.crazyit.auction.controller">
</context:component-scan>
<!-- 默认装配方案 -->
<mvc:annotation-driven />
<!-- 定义默认的Servlet处理Web应用目录下的静态资源 -->
<mvc:default-servlet-handler />

<!-- 定义一个普通Bean实例,该Bean实例将进行权限控制 -->
<bean
id="authority"
class="org.crazyit.auction.controller.authority.AuthorityInterceptor" />

<aop:config>
<!-- 将authority转换成切面Bean -->
<aop:aspect ref="authority">
<!-- 定义一个Before增强处理,直接指定切入点表达式 以切面Bean中的authority()方法作为增强处理方法 -->
<aop:around
pointcut="execution(* org.crazyit.auction.controller.*.getItemByWiner(..))
or execution(* org.crazyit.auction.controller.*.getBidByUser(..))
or execution(* org.crazyit.auction.controller.*.getItemsByOwner(..))
or execution(* org.crazyit.auction.controller.*.addItem(..))
or execution(* org.crazyit.auction.controller.*.addBid(..))"
method="authority" />
</aop:aspect>
</aop:config>

</beans>

从以上代码可以看出,Spring 将会在org.crazyit.auction.controller包及其子包下搜索控制器。

10.4.7 配置业务层组件

随着系统逐渐增大,系统中的各种组件越来越多,如果将系统中的全部组件都部署在同一个配置文件里,则必然导致配置文件非常庞大,难以维护。因此,我们推荐将系统中各种组件分模块、分层进行配置,从而提供更好的可维护性。
对于本系统,因为系统规模较小,故没有将系统中的组件分模块进行配置,但我们将业务逻辑组件和DAO组件分开在两个配置文件中进行管理。
因为业务逻辑组件涉及事务管理,所以在业务逻辑组件配置文件里配置了业务逻辑组件数据源事务管理器事务拦截器MailSenderMailMessage等组件。
具体的配置文件如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?xml version="1.0" encoding="utf-8"?>
<!-- Spring配置文件的根元素,并指定Schema信息 -->
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd">

<!-- 配置Hibernate的局部事务管理器,使用HibernateTransactionManager类 -->
<!-- 该类实现PlatformTransactionManager接口,是针对Hibernate的特定实现 -->
<!-- 配置HibernateTransactionManager时需要依注入SessionFactory的引用 -->
<bean
id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory" />

<!-- 定义JavaMailSenderImpl,它用于发送邮件 指定发送邮件的SMTP服务器地址, 指定登录邮箱的用户名、密码 -->
<bean
id="mailSender"
class="org.springframework.mail.javamail.JavaMailSenderImpl"
p:host="smtp.163.com"
p:username="spring_test"
p:password="123abc">
<property name="javaMailProperties">
<props>
<prop key="mail.smtp.auth">true</prop>
<prop key="mail.smtp.timeout">25000</prop>
</props>
</property>
</bean>

<!-- 定义SimpleMailMessage Bean,它代表了一份邮件 指定发件人地址,指定邮件标题 -->
<bean
id="mailMessage"
class="org.springframework.mail.SimpleMailMessage"
p:from="spring_test@163.com"
p:subject="竞价通知" />
<!-- 配置业务逻辑组件 , 为业务逻辑组件注入所需的DAO组件 -->
<bean
id="auctionService"
class="org.crazyit.auction.service.impl.AuctionServiceImpl"
p:userDao-ref="auctionUserDao"
p:bidDao-ref="bidDao"
p:itemDao-ref="itemDao"
p:kindDao-ref="kindDao"
p:stateDao-ref="stateDao"
p:mailSender-ref="mailSender"
p:message-ref="mailMessage" />

<!-- 定义执行任务调度的线程池 -->
<task:scheduler
id="myScheduler"
pool-size="20" />
<!-- 对指定Bean的指定方法执行实际的调度 -->
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled
ref="auctionService"
method="updateWiner"
fixed-delay="86400000" />
</task:scheduled-tasks>

<!-- 配置事务切面Bean,指定事务管理器 -->
<tx:advice
id="txAdvice"
transaction-manager="transactionManager">
<!-- 用于配置详细的事务语义 -->
<tx:attributes>
<!-- 所有以'get'开头的方法是read-only的 -->
<tx:method
name="get*"
read-only="true"
timeout="8" />
<!-- 其他方法使用默认的事务设置 -->
<tx:method
name="*"
timeout="5" />
</tx:attributes>
</tx:advice>

<aop:config>
<!-- 配置一个切入点,匹配指定包下所有以Impl结尾的类执行的所有方法 -->
<aop:pointcut
id="auctionPc"
expression="execution(* org.crazyit.auction.service.impl.*Impl.*(..))" />
<!-- 指定在leeService切入点应用txAdvice事务切面 -->
<aop:advisor
advice-ref="txAdvice"
pointcut-ref="auctionPc" />
</aop:config>
</beans>

各种组件依赖通过配置文件设置,由容器管理其依赖,从而实现系统的解耦。

10.4.6 事务管理

前面已经介绍过了,每个业务逻辑方法都应该在逻辑上是一个整体,具有逻辑不可分的特征,因此系统应该为每个业务逻辑方法增加事务控制
借助于Spring声明式事务管理,在业务逻辑组件的方法内无须编写事务管理代码,所有的事务管理都放在配置文件中
提示:通过Spring AOP的支持,声明式事务成为可能。业务逻辑方法与持久层API彻底分离,从而让系统的业务逻辑层真正与持久层分离。当系统的持久层需要改变时,业务逻辑组件无须任何改变。
即使采用Spring的声明式事务管理,依然有多种配置方式可以选择,通常推荐使用Spring提供的tx:aop:两个命名空间来配置事务管理,这也是本系统所采用的事务配置方式。
在使用tx:aop:两个命名空间配置事务管理时,<tx:advice>负责配置事务切面Bean,而<aop:config>则负责为事务切面Bean创建代理。这种配置方式其实是Spring AOP机制的一种应用。下一节将会详细介绍本应用的事务配置。
提示:如果读者需要了解关于Spring AOP的更多知识,请参阅”疯狂Java体系”的《轻量级Java EE企业应用实战》的第8章。

10.4.5 判断拍卖物品状态

当拍卖物品进入系统后,随着时间的流逝,拍卖物品超过了有效时间,拍卖结束,此时拍卖物品可能有两种状态:

  • 一种是被人成功赢取,
  • 另一种是流拍。

不管是哪一种状态,这种拍卖物品状态的改变都应该由系统自动完成:系统每隔一段时间,判断系统中正在拍卖的物品是否到了拍卖期限,并修改其拍卖状态。
为了修改拍卖物品的状态,业务逻辑组件提供了一个updateWiner方法,该方法将当前时间与物品的拍卖最后期限比较,如果最后期限不晚于当前时间,则修改该物品的状态为流拍或拍卖成功。如果拍卖成功,还需要修改该物品的赢取者。该方法的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 根据时间来修改物品的状态、赢取者
*/
public void updateWiner() throws AuctionException
{
System.out.println("-----------------" + new Date());
try
{
List<Item> itemList = itemDao.findByState(1);
for (int i = 0; i < itemList.size(); i++)
{
Item item = itemList.get(i);
if (!item.getEndtime().after(new Date()))
{
// 根据指定物品和最高竞价来查询用户
AuctionUser au = userDao.findByItemAndPrice(item.getId(),
item.getMaxPrice());
// 如果该物品的最高竞价者不为null
if (au != null)
{
// 将该竞价者设为赢取者
item.setWiner(au);
// 修改物品的状态成为“被赢取”
item.setItemState(stateDao.get(State.class, 2));
itemDao.save(item);
} else
{
// 设置该物品的状态为“流拍”
item.setItemState(stateDao.get(State.class, 3));
itemDao.save(item);
}
}
}
} catch (Exception ex)
{
log.debug(ex.getMessage());
throw new AuctionException("根据时间来修改物品的状态、赢取者出现异常,请重试");
}
}

该方法并不由客户端直接调用,而是由任务调度来执行。**Spring的任务调度机制将负责每隔一段时间自动调用该方法一次**,以判断拍卖物品的状态。
系统的任务调度将让updateWiner()方法每隔一段时间执行一次,这种任务调度也可借助于Spring的任务调度机制来完成。
Spring的任务调度可简单地通过task:命令空间下的如下三个元素进行配置。

  • task:scheduler:该元素用于配置一个执行任务调度的线程池。
  • task:scheduled-tasks:该元素用于开启任务调度,该元素需要通过scheduler属性指定使用哪个线程池。
  • task:scheduled:该元素用于指定调度属性,比如延迟多久开始调度,每隔多长时间调度一次等。

通过这三个元素即可在Spring中配置任务调度。下面是配置该任务调度的配置片段。

1
2
3
4
5
6
7
8
9
10
11
<!-- 定义执行任务调度的线程池 -->
<task:scheduler
id="myScheduler"
pool-size="20" />
<!-- 对指定Bean的指定方法执行实际的调度 -->
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled
ref="auctionService"
method="updateWiner"
fixed-delay="86400000" />
</task:scheduled-tasks>

通过如此配置,可以保证Spring 容器启动后,即建立任务调度,该任务调度每隔一段时间调用AuctionServiceupdateWiner()方法一次。
提示:如果读者需要了解更多关于Spring任务调度的知识,可以参考Spring项目的参考手册。