基于SSM框架大型分布式电商系统开发(15-16)

前言

单点登录解决方案CAS+购物车解决方案

第15章 单点登录解决方案-CAS

1.开源单点登录系统CAS入门

1.1 什么是单点登录

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
我们目前的系统存在诸多子系统,而这些子系统是分别部署在不同的服务器中,那么使用传统方式的session是无法解决的,我们需要使用相关的单点登录技术来解决。
在这里插入图片描述

1.2 什么是CAS

CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目。CAS 具有以下特点:
【1】开源的企业级单点登录解决方案。
【2】CAS Server 为需要独立部署的 Web 应用。
【3】CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。
从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。下图是 CAS 最基本的协议过程:
在这里插入图片描述
SSO单点登录访问流程主要有以下步骤:

  1. 访问服务:SSO客户端发送请求访问应用系统提供的服务资源。
  2. 定向认证:SSO客户端会重定向用户请求到SSO服务器。
  3. 用户认证:用户身份认证。
  4. 发放票据:SSO服务器会产生一个随机的Service Ticket。
  5. 验证票据:SSO服务器验证票据Service Ticket的合法性,验证通过后,允许客户端访问服务。
  6. 传输用户信息:SSO服务器验证票据通过后,传输用户认证结果信息给客户端。
1.3 CAS服务端部署

Cas服务端其实就是一个war包。
在资源\cas\source\cas-server-4.0.0-release\cas-server-4.0.0\modules目录下
cas-server-webapp-4.0.0.war 将其改名为cas.war放入tomcat目录下的webapps下。启动tomcat自动解压war包。浏览器输入http://localhost:8080/cas/login ,可看到登录页面
在这里插入图片描述
不要嫌弃这个页面丑,我们后期可以再提升它的颜值。暂时把注意力放在功能实现上。
这里有个固定的用户名和密码 casuser /Mellon
登录成功后会跳到登录成功的提示页面
在这里插入图片描述

1.4 CAS服务端配置
1.4.1端口修改

如果我们不希望用8080端口访问CAS, 可以修改端口
(1)修改TOMCAT的端口
打开tomcat 目录 conf\server.xml 找到下面的配置
在这里插入图片描述
将端口8080,改为9100
(2)修改CAS配置文件
修改cas的WEB-INF/cas.properties

server.name=http://localhost:9100
1.4.2去除https认证

CAS默认使用的是HTTPS协议,如果使用HTTPS协议需要SSL安全证书(需向特定的机构申请和购买) 。如果对安全要求不高或是在开发测试阶段,可使用HTTP协议。我们这里讲解通过修改配置,让CAS使用HTTP协议。
(1)修改cas的WEB-INF/deployerConfigContext.xml
找到下面的配置

<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient"/>

这里需要增加参数p:requireSecure=“false”,requireSecure属性意思为是否需要安全验证,即HTTPS,false为不采用
(2)修改cas的/WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml
找到下面配置

<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
      p:cookieSecure="true"
      p:cookieMaxAge="-1"
      p:cookieName="CASTGC"
      p:cookiePath="/cas" />

参数p:cookieSecure=“true”,同理为HTTPS验证相关,TRUE为采用HTTPS验证,FALSE为不采用https验证。
参数p:cookieMaxAge="-1",是COOKIE的最大生命周期,-1为无生命周期,即只在当前打开的窗口有效,关闭或重新打开其它窗口,仍会要求验证。可以根据需要修改为大于0的数字,比如3600等,意思是在3600秒内,打开任意窗口,都不需要验证。
我们这里将cookieSecure改为false , cookieMaxAge 改为3600
(3)修改cas的WEB-INF/spring-configuration/warnCookieGenerator.xml
找到下面配置

<bean id="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure="true"
p:cookieMaxAge="-1"
p:cookieName="CASPRIVACY"
p:cookiePath="/cas" />

我们这里将cookieSecure改为false , cookieMaxAge 改为3600

2.CAS服务端数据源设置

2.1需求分析

我们现在让用户名密码从我们的品优购的user表里做验证

2.2配置数据源

(1)修改cas服务端中web-inf下deployerConfigContext.xml ,添加如下配置

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  
			  p:driverClass="com.mysql.jdbc.Driver"  
			  p:jdbcUrl="jdbc:mysql://127.0.0.1:3306/pinyougoudb?characterEncoding=utf8"  
			  p:user="root"  
			  p:password="123456" /> 
<bean id="passwordEncoder" 
class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"  
		c:encodingAlgorithm="MD5"  
		p:characterEncoding="UTF-8" />  
<bean id="dbAuthHandler"  
		  class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"  
		  p:dataSource-ref="dataSource"  
		  p:sql="select password from tb_user where username = ?"  
		  p:passwordEncoder-ref="passwordEncoder"/>  

然后在配置文件开始部分找到如下配置

<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
        <constructor-arg>
            <map>               
                <entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
                <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />
            </map>
        </constructor-arg>      
        <property name="authenticationPolicy">
            <bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" />
        </property>
</bean>

其中

 <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />

一句是使用固定的用户名和密码,我们在下面可以看到这两个bean ,如果我们使用数据库认证用户名和密码,需要将这句注释掉。
添加下面这一句配置

<entry key-ref="dbAuthHandler" value-ref="primaryPrincipalResolver"/>

(2)将以下三个jar包放入webapps\cas\WEB-INF\lib下
在这里插入图片描述
(这三个jar包在资源\cas\jar目录下)
用数据库中的用户名和密码进行测试

3.CAS服务端界面改造

3.1需求分析

我们现在动手将CAS默认的登录页更改为自己的品优购登陆页

3.2改头换面
3.2.1拷贝资源

(1)将品优购的登陆页login.html拷贝到cas系统下WEB-INF\view\jsp\default\ui 目录下
(2)将css js等文件夹拷贝到 cas目录下
(3) 将原来的casLoginView.jsp 改名(可以为之后的修改操作做参照),将login.html改名为casLoginView.jsp

3.2.2修改页面

编辑casLoginView.jsp 内容
(1)添加指令

<%@ page pageEncoding="UTF-8" %>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

(2)修改form标签

<form:form method="post" id="fm1" commandName="${commandName}" htmlEscape="true" class="sui-form">
......
</form:form>

(3)修改用户名框

<form:input id="username" tabindex="1" 
	accesskey="${userNameAccessKey}" path="username" autocomplete="off" htmlEscape="true" 
	placeholder="邮箱/用户名/手机号" class="span2 input-xfat" />

(4)修改密码框

 <form:password  id="password" tabindex="2" path="password" 
      accesskey="${passwordAccessKey}" htmlEscape="true" autocomplete="off" 
	  placeholder="请输入密码" class="span2 input-xfat"   />

(5)修改登陆按钮

<input type="hidden" name="lt" value="${loginTicket}" />
<input type="hidden" name="execution" value="${flowExecutionKey}" />
<input type="hidden" name="_eventId" value="submit" />
<input class="sui-btn btn-block btn-xlarge btn-danger" accesskey="l" value="登陆" type="submit" />

修改后效果如下:
在这里插入图片描述

3.3错误提示

在表单内加入错误提示框

<form:errors path="*" id="msg" cssClass="errors" element="div" htmlEscape="false" />

测试:输入错误的用户名和密码,提示是英文。这个提示信息是在WEB-INF\classes目录下的messages.properties文件中

authenticationFailure.AccountNotFoundException=Invalid credentials.
authenticationFailure.FailedLoginException=Invalid credentials.

设置国际化为zn_CN ,修改cas-servlet.xml

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver" p:defaultLocale="zh_CN" />

我们需要将此信息拷贝到messages_zh_CN.properties下,并改为中文提示(转码)

authenticationFailure.AccountNotFoundException=\u7528\u6237\u4E0D\u5B58\u5728.
authenticationFailure.FailedLoginException=\u5BC6\u7801\u9519\u8BEF.

第一个是用户名不存在时的错误提示
第二个是密码错误的提示

4.品优购用户中心

4.1需求分析

用户中心实现单点登录。

4.2代码实现
4.2.1用户中心实现单点登录

(1)将用户中心相关的页面(home-开头的)拷贝至 pinnyougou-user-web
在这里插入图片描述
(2)pom.xml 引入springSecurity、cas客户端和springSecurity Cas整合包依赖。
(3)web.xml 添加spring-security过滤器设置首页为home-index.html

<welcome-file-list>
	<welcome-file>home-index.html</welcome-file>
</welcome-file-list>

(4)构建UserDetailsServiceImpl.java
在这里插入图片描述
(5)添加spring-security.xml,并做以下修改配置匿名访问资源

     <!-- 匿名访问资源 -->
	<http pattern="/css/**" security="none"></http>
	<http pattern="/js/**" security="none"></http>
	<http pattern="/image/**" security="none"></http>
	<http pattern="/plugins/**" security="none"></http>
	<http pattern="/register.html" security="none"></http>
	<http pattern="/user/add.do" security="none"></http>
	<http pattern="/user/sendCode.do" security="none"></http>

设置服务地址属性

<beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">    
     <beans:property name="service" value="http://localhost:9106/login/cas"/>
</beans:bean>  

设置认证类

<beans:bean id="userDetailsService" class="com.pinyougou.user.service.UserDetailServiceImpl"/>  
4.2.2页面显示用户名

(1)pinyougou-user-web创建LoginController.java

@RestController
@RequestMapping("/login")
public class LoginController {	
	@RequestMapping("/name")
	public Map showName(){
		String name = SecurityContextHolder.getContext().getAuthentication().getName();//得到登陆人账号
		Map map=new HashMap<>();
		map.put("loginName", name);
		return map;		
	}	
}

(2)创建loginService.js

//服务层
app.service('loginService',function($http){
	//读取列表数据绑定到表单中
	this.showName=function(){
		return $http.get('../login/name.do');		
	}
});

(3)创建indexController.js

//首页控制器
app.controller('indexController',function($scope,loginService){
	$scope.showName=function(){
		loginService.showName().success(
			function(response){				
				$scope.loginName=response.loginName;
			}
		);		
	}	
});

(4) 修改home-index.html 引入js

    <script type="text/javascript" src="plugins/angularjs/angular.min.js"></script>
	<script type="text/javascript" src="js/base.js"></script>	
	<script type="text/javascript" src="js/service/loginService.js"></script>
	<script type="text/javascript" src="js/controller/indexController.js"></script>

指令,调用方法查询登陆名

<body ng-app="pinyougou" ng-controller="indexController" ng-init="showName()">

显示用户名

 <span class="name">{{loginName}}</span>
4.2.3退出登录

设置退出登录后的跳转地址

<beans:bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">  
        <beans:constructor-arg value="http://localhost:9100/cas/logout?service=http://localhost:9103"/>  
       ........  
</beans:bean>  

退出登录后,跳转到网站首页

<span class="safe"> <a href="/logout/cas">退出登录 </a></span>

第16章 购物车解决方案

1.购物车需求分析与解决方案

1.1 需求分析

用户在商品详细页点击加入购物车,提交商品SKU编号和购买数量,添加到购物车。购物车展示页面如下:
在这里插入图片描述

1.2 实现思路

购物车数据的存储结构如下:
在这里插入图片描述
当用户在未登录的情况下,将此购物车存入cookies , 在用户登陆的情况下,将购物车数据存入redis 。如果用户登陆时,cookies中存在购物车,需要将cookies的购物车合并到redis中存储.

1.3 工程搭建

(1)创建工程pinyougou-cart-interface ,依赖pinyougou-pojo
(2)创建工程pinyougou-cart-service(WAR),依赖pinyougou-cart-interface 和pinyougou-common工程 和spring、 dubbox 等相关依赖, 添加web.xml 与spring配置文件(参照其他service工程) tomcat插件端口设置为9007 ,dubbo端口为20887
(3)创建工程pinyougou-cart-web ,依赖 pinyougou-cart-interface springsecurity 、CAS 等。添加web.xml 与spring配置文件(参照其他web工程)tomcat插件端口设置为9107 ,拷贝UserDetailServiceImpl.java , 拷贝页面资源
在这里插入图片描述
(4)将资源文件夹中 Cookie工具类拷贝到pinyougou-common工程中。需要在pinyougou-common工程引入servlet-api依赖

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>servlet-api</artifactId>
	<scope>provided</scope>
</dependency>
1.4购物车实体类

在pinyougou-pojo的com.pinyougou.pojogroup中创建购物车实体类

public class Cart implements Serializable{
	private String sellerId;//商家ID
	private String sellerName;//商家名称
	private List<TbOrderItem> orderItemList;//购物车明细
	//getter  and setter  ......
}

这个类是对每个商家的购物车进行的封装

2.Cookie存储购物车

2.1需求分析

使用cookie存储购物车数据。服务层负责逻辑,控制层负责读写cookie 。

2.2服务接口层

(1)服务层接口 pinyougou-cart-interface新建com.pinyougou.cart.service包,包下建立接口CartService

/**
 * 购物车服务接口 
 * @author Administrator
 *
 */
public interface CartService {
	/**
	 * 添加商品到购物车
	 * @param cartList
	 * @param itemId
	 * @param num
	 * @return
	 */
	public List<Cart> addGoodsToCartList(List<Cart> cartList,Long itemId,Integer num );
}
2.3服务实现层

实现思路:

//1.根据商品SKU ID查询SKU商品信息
//2.获取商家ID		
//3.根据商家ID判断购物车列表中是否存在该商家的购物车		
//4.如果购物车列表中不存在该商家的购物车
//4.1 新建购物车对象
//4.2 将新建的购物车对象添加到购物车列表		
//5.如果购物车列表中存在该商家的购物车		
// 查询购物车明细列表中是否存在该商品
//5.1. 如果没有,新增购物车明细		
//5.2. 如果有,在原购物车明细上添加数量,更改金额

代码实现 pinyougou-cart-service 工程创建CartServiceImpl.java

/**
 * 购物车服务实现类
 * @author Administrator
 *
 */
@Service
public class CartServiceImpl implements CartService {

	@Autowired
	private TbItemMapper itemMapper;
	
	@Override
	public List<Cart> addGoodsToCartList(List<Cart> cartList, Long itemId, Integer num) {
	
		//1.根据商品SKU ID查询SKU商品信息
		TbItem item = itemMapper.selectByPrimaryKey(itemId);
		if(item==null){
			throw new RuntimeException("商品不存在");
		}
		if(!item.getStatus().equals("1")){
			throw new RuntimeException("商品状态无效");
		} 		
		//2.获取商家ID		
		String sellerId = item.getSellerId();
		
		//3.根据商家ID判断购物车列表中是否存在该商家的购物车		
		Cart cart = searchCartBySellerId(cartList,sellerId);
		
		//4.如果购物车列表中不存在该商家的购物车
		if(cart==null){			
			//4.1 新建购物车对象 ,
			cart=new Cart();
			cart.setSellerId(sellerId);
			cart.setSellerName(item.getSeller());						
			TbOrderItem orderItem = createOrderItem(item,num);
			List orderItemList=new ArrayList();
            orderItemList.add(orderItem);
			cart.setOrderItemList(orderItemList);
			//4.2将购物车对象添加到购物车列表
			cartList.add(cart);   			
		}else{
			//5.如果购物车列表中存在该商家的购物车			
			// 判断购物车明细列表中是否存在该商品
			TbOrderItem orderItem = searchOrderItemByItemId(cart.getOrderItemList(),itemId);						
			if(orderItem==null){
				//5.1. 如果没有,新增购物车明细				
				orderItem=createOrderItem(item,num);
				cart.getOrderItemList().add(orderItem);
			}else{
				//5.2. 如果有,在原购物车明细上添加数量,更改金额
				orderItem.setNum(orderItem.getNum()+num);			
				orderItem.setTotalFee(new BigDecimal(orderItem.getNum()*orderItem.getPrice().doubleValue())  );
				//如果数量操作后小于等于0,则移除
				if(orderItem.getNum()<=0){
					cart.getOrderItemList().remove(orderItem);//移除购物车明细	
				}
				//如果移除后cart的明细数量为0,则将cart移除
				if(cart.getOrderItemList().size()==0){
					cartList.remove(cart);
				}
			}			
		}			
		return cartList;
	}
	
	
	/**
	 * 根据商家ID查询购物车对象
	 * @param cartList
	 * @param sellerId
	 * @return
	 */
	private Cart searchCartBySellerId(List<Cart> cartList, String sellerId){
		for(Cart cart:cartList){
			if(cart.getSellerId().equals(sellerId)){
				return cart;
			}		
		}
		return null;
	}
	
	/**
	 * 根据商品明细ID查询
	 * @param orderItemList
	 * @param itemId
	 * @return
	 */
	private TbOrderItem searchOrderItemByItemId(List<TbOrderItem> orderItemList ,Long itemId ){
		for(TbOrderItem orderItem :orderItemList){
			if(orderItem.getItemId().longValue()==itemId.longValue()){
				return orderItem;				
			}			
		}
		return null;
	}
	
	/**
	 * 创建订单明细
	 * @param item
	 * @param num
	 * @return
	 */
	private TbOrderItem createOrderItem(TbItem item,Integer num){
		if(num<=0){
			throw new RuntimeException("数量非法");
		}    		
		TbOrderItem orderItem=new TbOrderItem();
		orderItem.setGoodsId(item.getGoodsId());
		orderItem.setItemId(item.getId());
		orderItem.setNum(num);
		orderItem.setPicPath(item.getImage());
		orderItem.setPrice(item.getPrice());
		orderItem.setSellerId(item.getSellerId());
		orderItem.setTitle(item.getTitle());
		orderItem.setTotalFee(new BigDecimal(item.getPrice().doubleValue()*num));
		return orderItem;
	}
}
2.4后端控制层

实现思路:
(1)从cookie中取出购物车
(2)向购物车添加商品
(3)将购物车存入cookie
pinyougou-cart-web工程新建CartController.java

@RestController
@RequestMapping("/cart")
public class CartController {

	@Reference
	private CartService cartService;
	
	@Autowired
	private  HttpServletRequest request;
	
	@Autowired
	private  HttpServletResponse response;
	
	
	/**
	 * 购物车列表
	 * @param request
	 * @return
	 */
	@RequestMapping("/findCartList")
	public List<Cart> findCartList(){
		String cartListString = util.CookieUtil.getCookieValue(request, "cartList","UTF-8");
		if(cartListString==null || cartListString.equals("")){
			cartListString="[]";
		}
		List<Cart> cartList_cookie = JSON.parseArray(cartListString, Cart.class);
		return cartList_cookie;	
	}
	
	/**
	 * 添加商品到购物车
	 * @param request
	 * @param response
	 * @param itemId
	 * @param num
	 * @return
	 */
	@RequestMapping("/addGoodsToCartList")
	public Result addGoodsToCartList(Long itemId,Integer num){
		try {			
			List<Cart> cartList =findCartList();//获取购物车列表
			cartList = cartService.addGoodsToCartList(cartList, itemId, num);	
			util.CookieUtil.setCookie(request, response, "cartList", JSON.toJSONString(cartList),3600*24,"UTF-8");
			return new Result(true, "添加成功");
		} catch (Exception e) {
			e.printStackTrace();
			return new Result(false, "添加失败");
		}
	}	
}

浏览器测试:
查看购物车:http://localhost:9105/cart/findCartList.do
添加商品到购物车 :
http://localhost:9105/cart/addGoodsToCartList.do?itemId=1369280&num=100

3.购物车前端代码

3.1需求分析

实现购物车页面的展示与相关操作
在这里插入图片描述
可以实现购物车列表、数量的增减与移除以及合计数统计

3.2购物车列表
3.2.1前端服务层

pinyougou-cart-web增加cartService.js

//购物车服务层
app.service('cartService',function($http){
	//购物车列表
	this.findCartList=function(){
		return $http.get('cart/findCartList.do');		
	}
});
3.2.2前端控制层

pinyougou-cart-web增加cartController.js

//购物车控制层 
app.controller('cartController',function($scope,cartService){
	//查询购物车列表
	$scope.findCartList=function(){
		cartService.findCartList().success(
			function(response){
				$scope.cartList=response;
			}
		);		
	}
});
3.2.3页面

修改cart.html 引入js

<script type="text/javascript" src="plugins/angularjs/angular.min.js">  </script>
<script type="text/javascript" src="js/base.js">  </script>
<script type="text/javascript" src="js/service/cartService.js">  </script>
<script type="text/javascript" src="js/controller/cartController.js">  </script> 

添加相关指令,指定控制器,调用初始化方法

<body ng-app="pinyougou" ng-controller="cartController" ng-init="findCartList()">

循环显示购物车列表

<div class="cart-item-list" ng-repeat="cart in cartList">
	<div class="cart-shop">
		<input type="checkbox" name="" id="" value="" />
		<span class="shopname self">{{cart.sellerName}}【商家ID:{{cart.sellerId}}】</span>
	</div>
	<div class="cart-body">
		<div class="cart-list" ng-repeat="orderItem in cart.orderItemList">
			<ul class="goods-list yui3-g">
				<li class="yui3-u-1-24">
					<input type="checkbox" name="" id="" value="" />
				</li>
				<li class="yui3-u-11-24">
					<div class="good-item">
						<div class="item-img"><img src="{{orderItem.picPath}}" /></div>
						<div class="item-msg">
							{{orderItem.title}}
						</div>
					</div>
				</li>				
				<li class="yui3-u-1-8"><span class="price">{{orderItem.price.toFixed(2)}}</span></li>
				<li class="yui3-u-1-8">
					<a href="javascript:void(0)" class="increment mins">-</a>
					<input autocomplete="off" type="text" ng-model="orderItem.num"  minnum="1" class="itxt" />
					<a href="javascript:void(0)" class="increment plus">+</a>
				</li>
				<li class="yui3-u-1-8"><span class="sum">{{orderItem.totalFee.toFixed(2)}}</span></li>
				<li class="yui3-u-1-8">
					<a href="#none">删除</a><br />
					<a href="#none">移到我的关注</a>
				</li>
			</ul>
		</div>		
	</div>
</div>
3.3购物车数量增减与移除
3.3.1前端服务层

pinyougou-cart-web的 cartService.js

//添加商品到购物车
this.addGoodsToCartList=function(itemId,num){
	return $http.get('cart/addGoodsToCartList.do?itemId='+itemId+'&num='+num);
}
3.3.2前端控制层

pinyougou-cart-web的 cartController.js

//添加商品到购物车
	$scope.addGoodsToCartList=function(itemId,num){
		cartService.addGoodsToCartList(itemId,num).success(
			function(response){
				if(response.success){
					$scope.findCartList();//刷新列表
				}else{
					alert(response.message);//弹出错误提示
				}				
			}
		);
	}
3.3.3页面

修改pinyougou-cart-web的cart.html 实现数量增减

<li class="yui3-u-1-8">
	<a href="javascript:void(0)" ng-click="addGoodsToCartList(orderItem.itemId,-1)" class="increment mins">-</a>
	<input autocomplete="off" type="text" ng-model="orderItem.num"  minnum="1" class="itxt" />
	<a href="javascript:void(0)" ng-click="addGoodsToCartList(orderItem.itemId,1)"  class="increment plus">+</a> 
</li>

实现删除功能

<a href="#none" ng-click="addGoodsToCartList(orderItem.itemId,-orderItem.num)" >删除</a><br />
3.4合计数
3.4.1前端服务层

修改cartService.js

//求合计
	this.sum=function(cartList){		
		var totalValue={totalNum:0, totalMoney:0.00 };//合计实体
		for(var i=0;i<cartList.length;i++){
			var cart=cartList[i];
			for(var j=0;j<cart.orderItemList.length;j++){
				var orderItem=cart.orderItemList[j];//购物车明细
				totalValue.totalNum+=orderItem.num;
				totalValue.totalMoney+= orderItem.totalFee;
			}				
		}
		return totalValue;
	}
3.4.2前端控制层

修改cartController.js ,调用服务层方法

   //查询购物车列表
	$scope.findCartList=function(){
		cartService.findCartList().success(
			function(response){
				$scope.cartList=response;
				$scope.totalValue=cartService.sum($scope.cartList);//求合计数
			}
		);
	}
3.4.3页面
<div class="chosed">已选择<span>{{totalValue.totalNum}}</span>件商品</div>
		<div class="sumprice">
	    <span><em>总价(不含运费) :
</em><i class="summoney">¥{{totalValue.totalMoney}}</i></span>
</div>

4.Redis存储购物车

4.1需求分析

判断当前用户是否登陆,如果未登录采用Cookie存储,如果登录则采用Redis存储。登录后要进行Cookie购物车与Redis购物车的合并操作,并清除Cookie购物车。

4.2获取当前登录人账号
4.2.1配置文件

spring-security.xml 更改配置:
去掉

<http pattern="/cart/*.do" security="none"></http>

添加

<http use-expressions="false" entry-point-ref="casProcessingFilterEntryPoint">
    <intercept-url pattern="/cart/*.do" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
    <intercept-url pattern="/**" access="ROLE_USER"/>  
    <custom-filter position="CAS_FILTER" ref="casAuthenticationFilter" />  
    <custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>  
    <custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>  
</http>

access=“IS_AUTHENTICATED_ANONYMOUSLY” 用于设置资源可以在不登陆时可以访问。
此配置与 security="none"的区别在于当用户未登陆时获取登陆人账号的值为anonymousUser ,而security="none"的话,无论是否登陆都不能获取登录人账号的值。

4.2.2代码实现

在pinyougou-cart-web的findCartList和addGoodsToCartList方法中,获取用户名

//得到登陆人账号,判断当前是否有人登陆
String username = SecurityContextHolder.getContext().getAuthentication().getName();

测试:当用户未登陆时,username的值为anonymousUser

4.3远程购物车存取
4.3.1服务接口层

pinyougou-cart-interface中CartService.java定义方法

/**
	 * 从redis中查询购物车
	 * @param username
	 * @return
	 */
	public List<Cart> findCartListFromRedis(String username);
	
	/**
	 * 将购物车保存到redis
	 * @param username
	 * @param cartList
	 */
	public void saveCartListToRedis(String username,List<Cart> cartList);
4.3.2服务实现层

pinyougou-cart-service中CartServiceImpl.java实现方法

@Autowired
private RedisTemplate redisTemplate;
@Override
public List<Cart> findCartListFromRedis(String username) {
	System.out.println("从redis中提取购物车数据....."+username);
	List<Cart> cartList = (List<Cart>) redisTemplate.boundHashOps("cartList").get(username);
	if(cartList==null){
		cartList=new ArrayList();
	}
	return cartList;
}
@Override
public void saveCartListToRedis(String username, List<Cart> cartList) {
	System.out.println("向redis存入购物车数据....."+username);
	redisTemplate.boundHashOps("cartList").put(username, cartList);
}
4.3.3控制层

修改CartController.java的findCartList方法

/**
 * 购物车列表
 * @param request
 * @return
 */
@RequestMapping("/findCartList")
public List<Cart> findCartList(){
	String username = SecurityContextHolder.getContext().getAuthentication().getName(); 
	if(username.equals("anonymousUser")){//如果未登录
		//读取本地购物车//
		..........
		return cartList_cookie;
	}else{//如果已登录					
		List<Cart> cartList_redis =cartService.findCartListFromRedis(username);//从redis中提取				
		return cartList_redis;
	}			
}

修改addGoodsToCartList方法

/**
 * 添加商品到购物车
 * @param request
 * @param response
 * @param itemId
 * @param num
 * @return
 */
@RequestMapping("/addGoodsToCartList")
public Result addGoodsToCartList(Long itemId,Integer num){
	String username = SecurityContextHolder.getContext().getAuthentication().getName(); 
	System.out.println("当前登录用户:"+username);
	try {			
		List<Cart> cartList =findCartList();//获取购物车列表
		cartList = cartService.addGoodsToCartList(cartList, itemId, num);
		if(username.equals("anonymousUser")){ //如果是未登录,保存到cookie
			util.CookieUtil.setCookie(request, response, "cartList", JSON.toJSONString(cartList),3600*24 ,"UTF-8");
			System.out.println("向cookie存入数据");
		}else{//如果是已登录,保存到redis
			cartService.saveCartListToRedis(username, cartList);			
		}
		return new Result(true, "添加成功");
	}  catch (RuntimeException e) {
		e.printStackTrace();
		return new Result(false, e.getMessage());
	}catch (Exception e) {
		e.printStackTrace();
		return new Result(false, "添加失败");
	}
}

为避免调用远程服务超时,我们可以将过期时间改为6秒(默认为1秒)

@Reference(timeout=6000)
private CartService cartService;
4.3.4跳板页

(1)创建跳板页:pinyougou-cart-web 工程新建login.html ,页面添加脚本

<script type="text/javascript">
	location.href="cart.html";
</script>

(2)购物车页面链接到跳板页

请<a href="login.html">登录</a> 
4.4购物车合并
4.4.1服务接口层

pinyougou-cart-interface工程的CartService.java定义方法

/**
 * 合并购物车
 * @param cartList1
 * @param cartList2
 * @return
 */
public List<Cart> mergeCartList(List<Cart> cartList1,List<Cart> cartList2);
4.4.2服务实现层

pinyougou-cart-service工程CartServiceImpl.java实现方法

public List<Cart> mergeCartList(List<Cart> cartList1, List<Cart> cartList2) {
		System.out.println("合并购物车");
		for(Cart cart: cartList2){
			for(TbOrderItem orderItem:cart.getOrderItemList()){
				cartList1= addGoodsToCartList(cartList1,orderItem.getItemId(),orderItem.getNum());		
			}			
		}		
		return cartList1;
	}
4.4.3控制层

修改pinyougou-cart-web工程CartController类的findCartList方法

@RequestMapping("/findCartList")
public List<Cart> findCartList(){
	String username = SecurityContextHolder.getContext().getAuthentication().getName(); 
	String cartListString  = util.CookieUtil.getCookieValue(request, "cartList", "UTF-8");
	if(cartListString==null || cartListString.equals("")){
		cartListString="[]";
	}
	List<Cart> cartList_cookie = JSON.parseArray(cartListString, Cart.class);
	if(username.equals("anonymousUser")){//如果未登录			
		return cartList_cookie;			
	}else{
		List<Cart> cartList_redis =cartService.findCartListFromRedis(username);//从redis中提取	
		if(cartList_cookie.size()>0){//如果本地存在购物车
			//合并购物车
			cartList_redis=cartService.mergeCartList(cartList_redis, cartList_cookie);	
			//清除本地cookie的数据
			util.CookieUtil.deleteCookie(request, response, "cartList");
			//将合并后的数据存入redis 
			cartService.saveCartListToRedis(username, cartList_redis); 
		}			
		return cartList_redis;			
	}	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值