10、结账功能
在购物车界面点击去结账,要实现三件事情,三个功能:
- 订单表添加一条记录
- 订单详情表添加n条记录
- 购物车项表中需要删除对应的n条记录
10.1 OrderDAO 层
- 首先点击结账完成添加订单功能:
@Override
public void addOrderBean(OrderBean orderBean) {
int orderBeanId = super.executeUpdate("insert into t_order values(0,?,?,?,?,?)",orderBean.getOrderNo(),orderBean.getOrderDate(),orderBean.getOrderUser().getId(),orderBean.getOrderMoney(),orderBean.getOrderStatus());
orderBean.setId(orderBeanId);
}
思考: 此处为什么需要接受 executeUpdate 的返回值,然后设置到 OrderBean 中的 id 属性上?
- BaseDAO 中 executeUpdate 方法如果我们是 insert 操作,我们可以将自增列的 id 值进行一个返回,所以可以获得这个数据,以防以后将订单转化为一项一项的订单项的时候,获取到的订单 id 值为空;
- 所以 addOrderBean 这个方法要接收返回值,并且 set 到 userBean 的 Id 属性上。
if (insertFlag) {
rs = psmt.getGeneratedKeys();
if (rs.next()) {
return ((Long) rs.getLong(1)).intValue();
}
}
10.2 OrderItemDAO 层
- 添加订单项功能,一个订单可能会执行多次
public class OrderItemDAOImpl extends BaseDAO<OrderItem> implements OrderItemDAO {
@Override
public void addOrderItem(OrderItem orderItem) {
super.executeUpdate("insert into t_order_item values(0,?,?,?)",orderItem.getBook().getId(),orderItem.getBuyCount(),orderItem.getOrderBean().getId()) ;
}
}
10.3 CartItemDAO 层
- 删除特定的购物车项,一个订单也会执行多次,将购物车中的所有项目清空
@Override
public void delCartItem(CartItem cartItem) {
super.executeUpdate("delete from t_cart_item where id = ?" , cartItem.getId());
}
10.4 OrderService 层
- 增加订单项,调用 OrderDao 层的
addOrderBean(OrderBean orderBean)
方法,实现订单表中添加一条记录; - 增加 N 项订单详情,调用 OrderItemDao 层的
addOrderItem(OrderItem orderItem)
方法,对应当前用户的当前订单,生成多条订单详情项; - 删除购物车的 N 项,调用 CartItemDao 层的
delCartItem(CartItem cartItem)
方法,将购物车中的这 N 项都删除了;
public class OrderServiceImpl implements OrderService {
private OrderDAO orderDAO ;
private OrderItemDAO orderItemDAO ;
private CartItemDAO cartItemDAO ;
@Override
public void addOrderBean(OrderBean orderBean) {
//1) 订单表添加一条记录
//2) 订单详情表添加7条记录
//3) 购物车项表中需要删除对应的7条记录
//第1步:
orderDAO.addOrderBean(orderBean); //执行完这一步,orderBean中的id是有值的
//第2步:
//orderBean中的orderItemList是空的,此处我们应该根据用户的购物车中的购物车项去转换成一个一个的订单项
User currUser = orderBean.getOrderUser();
Map<Integer, CartItem> cartItemMap = currUser.getCart().getCartItemMap();
for(CartItem cartItem : cartItemMap.values()){
OrderItem orderItem = new OrderItem();
orderItem.setBook(cartItem.getBook());
orderItem.setBuyCount(cartItem.getBuyCount());
orderItem.setOrderBean(orderBean);
orderItemDAO.addOrderItem(orderItem);
}
//第3步:删除购物车每一项
for(CartItem cartItem : cartItemMap.values()){
cartItemDAO.delCartItem(cartItem);
}
}
}
解释:
- 形参只设置一个参数的话,用户信息可以从 session 作用域中获取,也可以直接放到形参中(就是发送请求的时候带上用户信息);
- 这里考虑如何将购物车和订单以及订单详情关联起来,需要用户这个中间表,所以这里传参数的时候也可以传一个User user;
- 对于订单详情的 UserBean 这一栏,我们在 OrderDAO 层的
addOrderItem(OrderItem orderItem)
方法中已经实现了属性设置;
10.5 OrderController 控制器
- 实现结账功能,新建一个订单项,要随机生成一个订单号,订单日期,绑定用户信息,总金额,订单状态;
- 用户点击这里的订单状态一般设置成 0 就可以了,之后等待管理员来操作;
public class OrderController {
private OrderService orderService ;
//结账功能,就是生成一个订单,将所有的数据从session中获取或者新建之后set上去
public String checkout(HttpSession session){
OrderBean orderBean = new OrderBean() ;
LocalDateTime localDateTime = LocalDateTime.now();
int year = localDateTime.getYear();
int month = localDateTime.getMonth().getValue();
int day = localDateTime.getDayOfMonth();
int hour = localDateTime.getHour();
int min = localDateTime.getMinute();
int sec = localDateTime.getSecond();
//订单编号是32位全球唯一随机数+年月日时分秒
orderBean.setOrderNo(UUID.randomUUID().toString()+"_"+year+month+day+hour+min+sec);
orderBean.setOrderDate(localDateTime);
User user =(User)session.getAttribute("currUser");
orderBean.setOrderUser(user);
orderBean.setOrderMoney(user.getCart().getTotalMoney());
orderBean.setOrderStatus(0);
orderService.addOrderBean(orderBean);
return "index" ;
}
}
解释:
- 新建订单,这里设置的订单编号是
32 位全球唯一随机数+年月日时分秒
,这里对于新时间日期 LocalDataTime 的创建方法,参考java8 — 新日期时间API篇;
10.6 修改 cart.html 页面
- 结账:
th:href="@{/order.do(operate='checkout')}"
,这边是因为 thymeleaf 表达式对于 url 地址后面赋值可以用小括号括起来,即(key='value')
的方式;
10.7 遇到的问题及解决方法
- orderBean 中的 orderItemList 是空的,我们没办法根据订单去生成订单详情项,此处我们应该根据用户的购物车中的购物车项去转换成一个一个的订单项;
解决方法:
- OrderService 层实现:即我们根据 orderBean 中的 orderUser 找到其中的 cart 属性,从 cart 购物车中找到 cartItemMap ,从中获取 cartItem 项,把它包装成订单项;
- 点击完去结账之后跳转到首页,但是首页上的购物车项的数字没有进行更新,应该清零,即和数据库重新进行一次交互;
- 但是我们当前所有静态页面放在 WEB-INF 文件夹下面,没办法通过重定向来访问,所以我们这里点击去结账之后跳转到结账界面:
解决方法:
- OrderController 层:最后跳转到结账界面,即
return "cart/checkout" ;
- (治标不治本,没找到解决方法,这里第一次是登陆的时候和数据库进行一次交互,设置了 user 中的购物车数量信息,第二次是在购物车的 index 方法中设置了 user 的购物车数量信息,但是总不能我每次想更新这个数字,就跳到登陆界面或者购物车界面吧,但是在其他控制器中加这个设置购物车数量信息也不太对,emmmmm,这个问题老师也没管,先不管它)
10.7 checkout 结账成功界面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>结算页面</title>
<link type="text/css" rel="stylesheet" th:href="@{/static/css/style.css}" >
<link rel="stylesheet" th:href="@{/static/css/minireset.css}" />
<link rel="stylesheet" th:href="@{/static/css/common.css}" />
<link rel="stylesheet" th:href="@{/static/css/cart.css}" />
<style type="text/css">
h1 {
text-align: center;
margin-top: 200px;
font-size: 26px;
}
.oid{
color: red;
font-weight: bolder;
}
</style>
</head>
<body>
<div class="header">
<div class="w">
<div class="header-left">
<a th:href="@{/index}">
<img th:src="@{/static/img/logo.gif}" alt=""/></a>
<span>我的购物车</span>
</div>
<div class="header-right">
<h3>欢迎<span th:text="${session.currUser.uname}">张总</span>光临尚硅谷书城</h3>
<div class="order"><a th:href="@{/order.do(operate='getOrderList')}">我的订单</a></div>
<div class="destory"><a href="index.html">注销</a></div>
<div class="gohome">
<a href="@{/page.do?operate=page&page=index}">返回</a>
</div>
</div>
</div>
</div>
<div id="main">
<h1>你的订单已结算,订单号为:<span class="oid" th:text="${session.OrderNo}">546845626455846</span></h1>
</div>
<div id="bottom">
<span>
尚硅谷书城.Copyright ©2015
</span>
</div>
</body>
</html>
11、查看我的订单列表
11.1 修改 cart.html 和 checkout.html 页面
- 对于购物车页面,右上角点击我的订单跳转到订单页面:
th:href="@{/order.do(operate='getOrderList')}"
; - 对于结账成功界面,右上角点击我的订单跳转到订单页面:
th:href="@{/order.do(operate='getOrderList')}"
;
11.2 OrderController 控制器
- 查看当前用户的订单列表功能,调用 orderService 层的
getOrderList(User user)
方法来查询订单列表功能; - 并且将订单列表和用户绑定起来,一对多关系。
//查看订单列表
public String getOrderList(HttpSession session){
User user =(User)session.getAttribute("currUser");
List<OrderBean> orderList = orderService.getOrderList(user);
user.setOrderList(orderList);
session.setAttribute("currUser",user);
return "order/order" ;
}
11.3 OrderService 层
- 获取某用户关联的订单信息功能,界面上要展示的信息如下,所以我们不光要获取数据库中的订单相关信息,还要获取订单数量信息;
- (方法1:修改数据库的表的方法)如果可以在数据库中的 t_order 表中添加一列 totalBookCount 列,这样的话违背了第三范式,这个图书的总数不直接依赖于 order 的 id,那么我们直接结账的时候在 OrderService 层的 addOrderBean 方法中,将 cart 中的 totalBookCount 这一项 set 进来就可以了。
- (方法2:我们没权限修改数据库的表)我们需要先将 t_order 和 t_order_item 这个表级联查到 buyCount 和 orderBean 列,然后将这张表作为一张新表,用聚合函数从中获取某一个 orderBean 的 buyCount 的总和作为订单数量。
使用 方法2 的实现:
- OrderService 层方法实现,除了调用 orderDAO 层的
getOrderList(User user)
获取数据库中所有的订单信息之外,还要调用 orderDAO 层的getOrderTotalBookCount(OrderBean orderBean)
获取所有的图书数量信息;
@Override
public List<OrderBean> getOrderList(User user) {
List<OrderBean> orderBeanList = orderDAO.getOrderList(user);
for (OrderBean orderBean: orderBeanList) {
Integer totalBookCount = orderDAO.getOrderTotalBookCount(orderBean);
orderBean.setTotalBookCount(totalBookCount);
}
return orderBeanList ;
}
11.4 OrderDAO 层
- 获取指定用户的订单列表,和获取所有的图书数量方法实现如下:
@Override
public List<OrderBean> getOrderList(User user) {
return executeQuery("SELECT * FROM t_order WHERE orderUser = ?",user.getId());
}
@Override
public Integer getOrderTotalBookCount(OrderBean orderBean) {
String sql = "SELECT SUM(t3.buyCount) AS totalBookCount , t3.orderBean FROM " +
"(" +
"SELECT t1.id , t2.buyCount , t2.orderBean FROM t_order t1 INNER JOIN t_order_item t2 " +
"ON t1.id = t2.orderBean WHERE t1.orderUser = ? " +
") t3 WHERE t3.orderBean = ? GROUP BY t3.orderBean" ;
return ((BigDecimal)executeComplexQuery(sql,orderBean.getOrderUser().getId(),orderBean.getId())[0]).intValue();
}
解释:
- 用户和订单有一对多的关系,所以我们可以在
user类
中新增orderList
属性,并实现get/set
方法; - 在多表链接+聚合函数查询出来的值是
BigDecimal
类型的,将类型强转成这个类之后再intValue()
获取它的数值就可以了。
11.5 修改 order.html 页面
- 将所有的引用路径改成
th:xxx
; - 遍历订单项:
th:each="orderBean : ${session.currUser.orderList}" th:object="${orderBean}"
- 查询订单号:
th:text="*{orderNo}"
- 订单日期:
th:text="*{orderDate}"
- 订单金额:
th:text="*{orderMoney}"
- 查询订单数量:
th:text="*{totalBookCount}"
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>我的订单</title>
<link rel="stylesheet" th:href="@{/static/css/minireset.css}"/>
<link rel="stylesheet" th:href="@{/static/css/common.css}"/>
<link rel="stylesheet" th:href="@{/static/css/cart.css}"/>
<link rel="stylesheet" th:href="@{/static/css/bookManger.css}"/>
<link rel="stylesheet" th:href="@{/static/css/orderManger.css}"/>
<base th:href="@{/}">
</head>
<body>
<div class="header">
<div class="w">
<div class="header-left">
<a href="../index.html">
<img th:src="@{/static/img/logo.gif}" alt=""/></a>
<h1>我的订单</h1>
</div>
<div class="header-right">
<h3>欢迎<span th:text="${session.currUser.uname}">张总</span>光临尚硅谷书城</h3>
<div class="order"><a th:href="@{/order.do(operate='getOrderList')}">我的订单</a></div>
<div class="destory"><a href="../index.html">注销</a></div>
<div class="gohome">
<a th:href="@{/page.do?operate=page&page=index}">返回</a>
</div>
</div>
</div>
</div>
<div class="list">
<div class="w">
<table>
<thead>
<tr>
<th>订单号</th>
<th>订单日期</th>
<th>订单金额</th>
<th>订单数量</th>
<th>订单状态</th>
<th>订单详情</th>
</tr>
</thead>
<tbody>
<tr th:each="orderBean : ${session.currUser.orderList}" th:object="${orderBean}">
<td th:text="*{orderNo}">12354456895</td>
<td th:text="*{orderDate}">2015.04.23</td>
<td th:text="*{orderMoney}">90.00</td>
<td th:text="*{totalBookCount}">88</td>
<td><a href="" class="send">等待发货</a></td>
<td><a href="">查看详情</a></td>
</tr>
</tbody>
</table>
<div class="footer">
<div class="footer-right">
<div>首页</div>
<div>上一页</div>
<ul>
<li class="active">1</li>
<li>2</li>
<li>3</li>
</ul>
<div>下一页</div>
<div>末页</div>
<span>共10页</span>
<span>30条记录</span>
<span>到第</span>
<input type="text"/>
<span>页</span>
<button>确定</button>
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="w">
<div class="top">
<ul>
<li>
<a href="">
<img th:src="static/img/bottom1.png" alt=""/>
<span>大咖级讲师亲自授课</span>
</a>
</li>
<li>
<a href="">
<img src="static/img/bottom.png" alt=""/>
<span>课程为学员成长持续赋能</span>
</a>
</li>
<li>
<a href="">
<img src="static/img/bottom2.png" alt=""/>
<span>学员真是情况大公开</span>
</a>
</li>
</ul>
</div>
<div class="content">
<dl>
<dt>关于尚硅谷</dt>
<dd>教育理念</dd>
<!-- <dd>名师团队</dd>
<dd>学员心声</dd> -->
</dl>
<dl>
<dt>资源下载</dt>
<dd>视频下载</dd>
<!-- <dd>资料下载</dd>
<dd>工具下载</dd> -->
</dl>
<dl>
<dt>加入我们</dt>
<dd>招聘岗位</dd>
<!-- <dd>岗位介绍</dd>
<dd>招贤纳师</dd> -->
</dl>
<dl>
<dt>联系我们</dt>
<dd>http://www.atguigu.com</dd>
<dd></dd>
</dl>
</div>
</div>
<div class="down">
尚硅谷书城.Copyright ©2015
</div>
</div>
</body>
</html>
11.6 遇到的问题及解决方法
点击去结账:
点击我的订单之后:
报错信息,其实主要就是一个空指针错误:
Caused by: java.lang.NullPointerException: Cannot load from object array because the return value of "com.atguigu.book.dao.impl.OrderDAOImpl.executeComplexQuery(String, Object[])" is null
原因就是查询图书数量的时候,这里执行复杂查询返回的是 BigDecimal ,一个对象属性,当图书的数量为 0 的时候返回的就是 null;
解决方法:
OrderController 对应的修改:
//结账功能,就是生成一个订单,将所有的数据从session中获取或者新建之后set上去
public String checkout(HttpSession session){
User user =(User)session.getAttribute("currUser");
if (user.getCart().getTotalBookCount() == 0){
return "index";
}
OrderBean orderBean = new OrderBean() ;
LocalDateTime localDateTime = LocalDateTime.now();
int year = localDateTime.getYear();
int month = localDateTime.getMonth().getValue();
int day = localDateTime.getDayOfMonth();
int hour = localDateTime.getHour();
int min = localDateTime.getMinute();
int sec = localDateTime.getSecond();
//订单编号是32位全球唯一随机数+年月日时分秒
String orderNo = UUID.randomUUID().toString()+"_"+year+month+day+hour+min+sec;
orderBean.setOrderNo(orderNo);
session.setAttribute("OrderNo",orderNo);
orderBean.setOrderDate(localDateTime);
orderBean.setOrderUser(user);
orderBean.setOrderMoney(user.getCart().getTotalMoney());
orderBean.setOrderStatus(0);
orderService.addOrderBean(orderBean);
return "cart/checkout" ;
}
解释:
- 首先是,对于购物车里面一本书都没有的情况下,这个时候也可以点击去结账,然后生成一个订单,但是这时候是不存在订单详情项的,这时候点击订单详情就会出现空指针异常,所以我们在请求端或者接收端要判断一下;
- 所以我们在开头判断如果购物车中图书数量为 0 ,那么我们点击去结账跳转到主界面(或者也可以从请求端改,弹出一个警示框);
- 这里单纯的使用
th:disabled="${}"
表达式是不可取的,因为 去结账 这个不是一个输入按钮,而是一个超链接,所以不能使用这个表达式来让它不可用。 - 其次是,将生成的订单编号放到
session
作用域中,也可以在形参上加 request 作用域,放到 request 作用域中(最好是这样,因为一个订单号一次请求有效,下次生成的是新的订单号了)。
12、编辑购物车
- 需求: 购物车详情页面我们点击
+
or-
会给服务端发送请求,用来改变当前这一项的数量,即修改 t_cart_item 中的 buyCount 数量;
12.1 修改 cart.html 页面
- 配合 js 实现动态功能 editCart 功能:
- - 号:
th:onclick="|editCart(${cartItem.id} , ${cartItem.buyCount-1})|"
,并且当
当前购物车图书数量已经变成 1 了,这个 - 号就不可用:th:disabled="${cartItem.buyCount == 1}"
, - + 号:
th:onclick="|editCart(${cartItem.id} , ${cartItem.buyCount+1})|"
<td>
<input class="count" value="-" th:onclick="|editCart(${cartItem.id},${cartItem.buyCount-1})|" th:disabled="${cartItem.buyCount == 1}"></input>
<input class="count-num" type="text" value="1" th:value="${cartItem.buyCount}"/>
<span class="count" th:onclick="|editCart(${cartItem.id} , ${cartItem.buyCount+1})|">+</span>
</td>
解释:
- 在 thymeleaf 表达式中,
||
代表字符串拼接,${}
表示里面有表达式; - 这里一开始用的的 span 块,那我们点击这个文字是不会发送任何请求的(也就是
th:disabled
不起作用),只能触发一些动态功能,为了让它变成一个输入,我们将这个标签变成input
。
12.2 CartController 控制器
- 实现编辑购物车中的图书数量功能,直接调用 cartItemService 层的
updateCartItem
方法;
//编辑购物车中的图书数量功能
public String editCart(Integer cartItemId , Integer buyCount){
cartItemService.updateCartItem(new CartItem(cartItemId , buyCount));
return "redirect:cart.do";
}
12.3 关于金额的精度问题
- 关于 Double 数据运算过程中精度调整,可以看Double数据运算过程中精度调整,对于 Double 类型的数据可能会出现如下情况;
解决方法:
- 我们需要使用 BigDecimal 类型来进行 Double 类型数据运算,就是乘法运算那里;
- BigDecimal 就是一种将 Double 类型的数据转换成字符串来运算,提供了加减乘除方法,最后可以再通过
.doubleValue()
转换成 Double 类型的数据。
12.3.1 CartItem 类
- 先来一个 Double xj 属性,记录每一个购物车项的金额,然后写它的 get 方法时,用 BigDecimal 类型来计算 数量*金额;
public Double getXj() {
BigDecimal bigDecimalPrice = new BigDecimal(""+getBook().getPrice());
BigDecimal bigDecimalBuyCount = new BigDecimal(""+buyCount);
BigDecimal bigDecimalXJ = bigDecimalPrice.multiply(bigDecimalBuyCount);
xj = bigDecimalXJ.doubleValue() ;
return xj;
}
12.3.2 Cart 类
- 计算购物车总金额的时候,即
getTotalMoney()
方法也要改成使用 BigDecimal 类型来计算;
public Double getTotalMoney() {
totalMoney = 0.0;
BigDecimal bigDecimalTotalMoney = new BigDecimal("" + totalMoney);
//如果购物车中有东西,将购物车每一项从map中取出来,根据cartItem确定数的价格和购买的数量
if (cartItemMap != null && cartItemMap.size() > 0) {
Set<Map.Entry<Integer, CartItem>> entries = cartItemMap.entrySet();//取出购物车中的所有键值对
for (Map.Entry<Integer, CartItem> cartItemEntry : entries) {
CartItem cartItem = cartItemEntry.getValue();//每一个购物车项
Double xj = cartItem.getXj();
bigDecimalTotalMoney = bigDecimalTotalMoney.add(new BigDecimal("" + xj));
}
}
return bigDecimalTotalMoney.doubleValue();
}
最终效果:
13、过滤器判断是否是合法用户
- 需求:因为我们有时候用户没有登录也可以访问主界面,这个时候 session 中就没有 user,很多功能显示会有问题;
- 除了主界面、登录、注册页面,其他的界面没有登陆的时候要跳转到登陆页面。
13.1 SessionFilter 过滤器
解决方法:(使用过滤器和白名单)
- 新建 SessionFilter , 用来判断 session 中是否保存了 currUser;
- 如果没有 currUser,表明当前不是一个登录合法的用户,应该跳转到登录页面让其登录;
@WebFilter(urlPatterns = {"*.do", "*.html"},
initParams = {
@WebInitParam(name = "bai",
value = "/page.do?operate=page&page=user/login," +
"/user.do?null," +
"/page.do?operate=page&page=user/regist," +
"/book.do?null")
})
public class SessionFilter implements Filter {
List<String> baiList = null;//放置所有的过滤器白名单
@Override
public void init(FilterConfig config) throws ServletException {
String bai = config.getInitParameter("bai");
String[] baiArr = bai.split(",");
baiList = Arrays.asList(baiArr);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//http://localhost:8080/page.do?operate=page&page=user/login
System.out.println("request.getRequestURI() = " + request.getRequestURI());// /page.do
System.out.println("request.getQueryString() = " + request.getQueryString());// operate=page&page=user/login
String uri = request.getRequestURI();
String queryString = request.getQueryString();
String str = uri + "?" + queryString;
//如果你请求的页面在白名单中
if (baiList.contains(str)) {
filterChain.doFilter(request, response);
} else {
HttpSession session = request.getSession();
Object currUserObj = session.getAttribute("currUser");
if (currUserObj == null) {
response.sendRedirect("page.do?operate=page&page=user/login");
} else {
filterChain.doFilter(request, response);
}
}
}
@Override
public void destroy() {}
}
解释:
- 在初始化的时候扫描注解,设置通行白名单;
doFilter
过滤方法中,如果请求在白名单中直接放行;- 如果不在白名单中,且当前 session 作用域中没有放置
currUser
键值对,也就是用户没有登录的情况下,非法操作跳转到登陆界面; - 过滤条件:当用户请求的是 index 页面、登录、注册页面是可以放行的,除了登录、注册的页面,还有点击注册、登录之后的请求页面也要放行,除此之外其他操作不放行。
- 添加过滤器之后,保证
CharacterEncodingFilter
字符过滤器还能生效的话,这个新的过滤器按字母排序要在 myssm 包之后,所以新建一个z字母开头的包; - 也就是过滤器的定义顺序,必须所有过滤器在字符过滤器之后,因为执行顺序是按照文件排序来执行的。即
SessionFilter
要在CharacterEncodingFilter
之后。