我们上文一起搭建了订单的服务工程和web工程。我们参考京东可以知道,京东在没有登录时就可以使用购物车,但是当要真正付款的时候,一定是要求登录的,也就是说由购物车列表页面直接跳转到登录页面去登录。这显然用到了拦截器的功能,本文我们便一起实现登录功能。
下图便是购物车列表页面,我们点击”去结算”,如果当前用户还没登录,是必须要先登录的。也就是说在展示订单确认页面之前,需要对用户身份进行认证,要求用户必须登录。
下面我们便按照以下业务逻辑来编写一个拦截器,拦截器是需要实现HandlerInterceptor接口的。我们在taotao-order-web工程的src/main/java目录下新建一个com.taotao.order.interceptor包并在该包下新建一个LoginInterceptor拦截器(实现HandlerInterceptor接口),如下图所示。
- 从cookie中取token
- 没有token,需要跳转到登录页面
- 有token,则调用sso系统的服务,根据token查询用户信息
- 如果查不到用户信息,则意味着用户登录已经过期,需要跳转到登录页面
- 查询到用户信息,则放行
为方便大家复制,现将LoginInterceptor拦截器的代码给出。
/**
* 判断用户身份的拦截器
* <p>Title: LoginInterceptor</p>
* <p>Description: </p>
* <p>Company: www.itcast.cn</p>
* @version 1.0
*/
public class LoginInterceptor implements HandlerInterceptor {
@Value("${COOKIE_TOKEN_KEY}")
private String COOKIE_TOKEN_KEY;
@Value("${SSO_URL}")
private String SSO_URL;
@Autowired
private UserLoginService userLoginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 先从cookie中取token
String token = CookieUtils.getCookieValue(request, COOKIE_TOKEN_KEY);
/*
* 如果没有token,直接跳转到sso系统的登录页面,而且还需要把当前请求的url
* 做为参数传递给sso系统,用户登录成功之后还要跳转回当前请求的页面,
* 因此要在请求url中添加一个回调地址。
*/
if (StringUtils.isBlank(token)) {
// 跳转到http://localhost:8088/page/login
String url = SSO_URL + "/page/login?redirect=" + request.getRequestURL().toString();
// 如何进行跳转呢?
response.sendRedirect(url);
// 拦截
return false;
}
// 如果有token,需要调用sso系统的服务,根据token查询用户信息
TaotaoResult result = userLoginService.getUserByToken(token);
TbUser user = null;
if (result != null && result.getStatus() == 200) {
user = (TbUser) result.getData();
}
// 如果查询不到用户,跳转到sso系统的登录页面
else {
// 跳转到http://localhost:8088/page/login
String url = SSO_URL + "/page/login?redirect=" + request.getRequestURL().toString();
// 如何进行跳转呢?
response.sendRedirect(url);
// 拦截
return false;
}
/*
* 现在拦截器里面能够查询出用户,已经查了一次,如果我们在OrderController里面再取
* 一遍,那么就重复了,所以我们在拦截器里面取出用户之后,直接将其传递给OrderController。
*/
// 把user对象放到request中,拦截器里面的request与OrderController里面的request是同一个
request.setAttribute("user", user);
// 如果查询到用户,则放行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
可以看到,我们在代码中用到了两个常量,常量我们要定义在配置文件当中,如下图所示。
preHandle方法中通过token获取用户信息,显然调用到了SSO系统的服务,因此我们要确保在taotao-order-web工程中依赖了taotao-sso-interface工程,如下图所示。
光依赖taotao-sso-interface工程还不行,我们还得在springmvc.xml配置文件中引用taotao-sso-service发布的dubbo服务,另外我们写的拦截器Spring容器是不知道的,我们得告诉Spring容器我们写了这个拦截器,于是需要在springmvc.xml文件中配置该拦截器,如下图所示。
为了方便大家复制,现将springmvc.xml配置文件的内容给出。
<?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:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!-- 加载属性文件 -->
<context:property-placeholder location="classpath:resource/resource.properties" />
<context:component-scan base-package="com.taotao.order.controller" />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 拦截器配置 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 去结算超链接,http://localhost:8089/order/order-cart.html -->
<mvc:mapping path="/order/**"/>
<bean class="com.taotao.order.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
<!-- 引用dubbo服务 -->
<dubbo:application name="taotao-order-web"/>
<dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/>
<dubbo:reference interface="com.taotao.sso.service.UserLoginService" id="userLoginService" />
</beans>
我们是要由购物车列表页面访问订单确认页面时触发拦截器的,因此我们要先处理购物车列表页面,如下图所示。
由于要访问订单确认页面,我们就要先把下图所示的淘淘商城订单系统静态页面添加到taotao-order-web工程当中。
记得将css、js、images放到webapp目录下,将jsp目录放到WEB-INF目录下,如下图所示。
我们要访问的订单页面是order-cart.jsp页面,如下图所示,这个页面中”cartList”是Controller返回的购物车列表,cart的属性一定要正确。
下面我们按照如下业务逻辑在taotao-order-web工程中编写一个OrderController:
- 购物车商品数据从cookie中取出来的,即可以在订单系统中取到cookie中的购物车商品数据。
- 根据用户id来查询配送地址列表,需要用户登录。现在数据库里面并没有收货地址列表这个表,所以只能是使用静态数据。
- 从数据库表中查询支付方式列表,现在数据库里面并没有支付方式列表这个表,所以只能是使用静态数据。
- 返回一个逻辑视图,展示订单确认页面。
该OrderController来响应购物车列表请求访问订单确认页面的请求,如下图所示。
为方便大家复制,现将该OrderController类的代码给出。
/**
* 订单确认页面Controller
* <p>Title: OrderController</p>
* <p>Description: </p>
* <p>Company: www.itcast.cn</p>
* @version 1.0
*/
@Controller
public class OrderController {
@Value("${COOKIE_CART_KEY}")
private String COOKIE_CART_KEY;
@RequestMapping("/order/order-cart")
public String showOrderCart(HttpServletRequest request) {
// 取购物车商品列表
List<TbItem> cartList = getCartList(request);
// 取用户id
// TODO
TbUser user = (TbUser) request.getAttribute("user");
System.out.println(user.getUsername());
// 根据用户id来查询收货地址列表,现在数据库里面没有收货地址列表这个表,所以只能是使用静态数据
// 从数据库中查询支付方式列表
// 传递给页面
request.setAttribute("cartList", cartList);
// 返回一个逻辑视图
return "order-cart";
}
// 从Cookie中取出购物车列表
private List<TbItem> getCartList(HttpServletRequest request) {
// 使用CookieUtils取购物车列表
String json = CookieUtils.getCookieValue(request, COOKIE_CART_KEY, true);
// 判断Cookie中是否有值
if (StringUtils.isBlank(json)) {
// 没有值就返回一个空List
return new ArrayList();
}
// 把json转换成List对象
List<TbItem> list = JsonUtils.jsonToList(json, TbItem.class);
return list;
}
}
上面代码中用到了常量COOKIE_CART_KEY,因此我们需要在配置文件中配置下这个常量,如下图所示。
下面我们先来测试下拦截器是否好使,我们要启动taotao-order-web工程,但是要先将taotao-order聚合工程打包到本地maven仓库中,然后我们就启动taotao-order-web工程,启动成功后,我们再点击本文第一张图中的”去结算”,就可以看到,页面跳转到了登录页面,如下图所示,说明我们的拦截器没问题。
我们输入用户名和密码进行登录,发现登录到淘淘商城首页了,这不是我们想要去的页面,应该到订单确认页面才对。
登录有没有成功在login.jsp页面当中有判断,如下图所示,可以看到,js代码首先会去尝试获取从Controller端传过来的回调地址,如果取到了回调地址,那么登录成功后会跳转到回调地址,如果没有取到回调地址,那么登录成功后直接访问的便是淘淘商城首页,在上图中之所以我登录成功后访问到的是淘淘商城首页就是因为我们没有在PageController类当中添加回调地址。
下面我们到taotao-sso-web工程的PageController类中简单做下修改,如下图所示,我们在showLogin方法中添加了两个参数,一个是回调地址url,另一个是用来给jsp页面添加属性的Model,回调地址url也不是凭空来的,它是由拦截器重定向时就指定好的。
为方便大家复制,现将PageController类的代码给出。
/**
* 登录注册页面展示Controller
* <p>Title: PageController</p>
* <p>Description: </p>
* <p>Company: www.itcast.cn</p>
* @version 1.0
*/
@Controller
public class PageController {
@RequestMapping("/page/register")
public String showRegister() {
return "register";
}
@RequestMapping("/page/login")
public String showLogin(String redirect, Model model) {
model.addAttribute("redirect", redirect);
return "login";
}
}
既然我们修改了taotao-sso-web工程,下面我们便重启taotao-sso-web工程,重启后,我们重新来测试一下。如果你是刚登录的话,cookie中已经有了你的登录信息而且token还没过期,要想达到用户未登录的情况,有两种方法,一是删除cookie中的TT_TOKEN,如下图所示,另一种方法就是等,等30分钟,30分钟后token过期。很显然,我们删除cookie中的TT_TOKEN比较靠谱。
删除了cookie中的token或token过期后,我们还是从购物车列表页面(本文第一张图)点击”去结算”,还是会让我们登录,登录后会提示登录成功,点击”确定”后,我们看到的是下图所示订单确认页面,而不再是淘淘商城首页了。
但是当前这个页面头部还是显示的是未登录状态,我们参考单点登录系统的代码,在taotao-order-web工程的js目录下找到base-v1.js,打开它修改最上面的两个方法。
修改的代码如下,这样从订单确认页面便可以点击”登录”或”注册”跳转到相应的页面。
function login() {
return location.href = "http://localhost:8088/page/login";
}
function regist() {
return location.href = "http://localhost:8088/page/register";
}
我们再修改下js目录下的taotao.js文件,其实这个文件我们只需要将端口修改为8088就可以了。
下面我们重启taotao-order-web工程,重启后,我们刷新订单确认页面,便可以看到头部有用户信息了。