具体代码详见github github
承前之作,之前博客介绍了商城的登录注册功能,这一篇总结一下购物车的实现,其实技术的实现有多种方式,这里只是其中之一,mark下。
购物车功能仿照京东模式,用户未登录时购物车的信息存入浏览器cookie中,若中途用户登录,则之前cookie中购物车信息以登陆用户名为key、商品id为filed、数量为value存入redis,并清空cookie;登陆后的添加均存入redis。这是本购物车的实现技术。
基本操作为:用户点击加入购物车,跳转至购物车页面显示购物车中所有商品信息:
先看代码controller:
package git.com.postgraduate.bookstore.controller;
@Controller
public class RedisCartController {
@Autowired
private PaperService paperService;
@Autowired
private CartService cartService;
@Autowired
private SecurityService securityService;
@RequestMapping(value={"/addcart/{code}/{quantity}"})
public String buyerCart(@PathVariable("code") Long code, @PathVariable("quantity") Integer quantity, HttpServletRequest request, HttpServletResponse response) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper om = new ObjectMapper();
om.setSerializationInclusion(Include.NON_NULL);
CartInfo cartInfo = null;
Paper paper = null;
//get cart from cookie
Cookie[] cookies = request.getCookies();
if(null != cookies && cookies.length > 0) {
for(Cookie cookie : cookies) {
if("BUYER_CART".equals(cookie.getName())) {
String decode = URLDecoder.decode(cookie.getValue(), "UTF-8");
cartInfo = om.readValue(decode, CartInfo.class);
break;
}
}
}
//if no cart in cookie create it
if(null == cartInfo)
cartInfo = new CartInfo();
//add current product into cart
if(null != code) {
paper = paperService.getPaper(code);
}
if(null != paper) {
ProductInfo productInfo = new ProductInfo(paper);
cartInfo.addProduct(productInfo, quantity);
cartInfo.setQuantity(cartInfo.getQuantityTotal());
cartInfo.setTotal(cartInfo.getAmountTotal());
}
//above login in or login out is the same
//below need judge
String userName = securityService.findLoggedUsername();
if(null != userName) {
//login in
//add cart to redis
cartService.insertCartToRedis(cartInfo, userName);
//clear cookie, set survive time=0 and destroy
Cookie cookie = new Cookie("BUYER_CART", null);
cookie.setPath("/");
cookie.setMaxAge(-0);
response.addCookie(cookie);
} else {
//not login in
//save cart into cookie
//convert object into json
Writer writer = new StringWriter();
om.writeValue(writer, cartInfo);
String encode = URLEncoder.encode(writer.toString(), "UTF-8");
Cookie cookie = new Cookie("BUYER_CART", encode);
//set cookie is public share
cookie.setPath("/");
//max exist time is 24h
cookie.setMaxAge(24*60*60);
//cookie is writed into browser
response.addCookie(cookie);
}
return "redirect:/toCart";
}
@RequestMapping("/singleupdate/{code}/{num}")
public @ResponseBody String updateAndReturn(
@PathVariable("code") Long code, @PathVariable("num") Integer quantity, HttpServletRequest request, HttpServletResponse response) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper om = new ObjectMapper();
om.setSerializationInclusion(Include.NON_NULL);
CartInfo cartInfo = null;
Paper paper = null;
//get cart from cookie
Cookie[] cookies = request.getCookies();
if(null != cookies && cookies.length > 0) {
for(Cookie cookie : cookies) {
if("BUYER_CART".equals(cookie.getName())) {
String decode = URLDecoder.decode(cookie.getValue(), "UTF-8");
cartInfo = om.readValue(decode, CartInfo.class);
break;
}
}
}
//if no cart in cookie create it
if(null == cartInfo)
cartInfo = new CartInfo();
//add product into cart to instead fronted product infor
if(null != code) {
paper = paperService.getPaper(code);
}
if(null != paper) {
ProductInfo productInfo = new ProductInfo(paper);
//remove existing productInfo
cartInfo.removeProduct(productInfo);
cartInfo.addProduct(productInfo, quantity);
cartInfo.setQuantity(cartInfo.getQuantityTotal());
cartInfo.setTotal(cartInfo.getAmountTotal());
}
//above login in or login out is the same
//below need judge
String userName = securityService.findLoggedUsername();
if(null != userName) {
//login in
//update line single to redis
cartService.updateCartToRedis(cartInfo.findLineByCode(code), userName);
//clear cookie, set survive time=0 and destroy
Cookie cookie = new Cookie("BUYER_CART", null);
cookie.setPath("/");
cookie.setMaxAge(-0);
response.addCookie(cookie);
} else {
//not login in
//save cart into cookie
//convert object into json
Writer writer = new StringWriter();
om.writeValue(writer, cartInfo);
String encode = URLEncoder.encode(writer.toString(), "UTF-8");
Cookie cookie = new Cookie("BUYER_CART", encode);
//set cookie is public share
cookie.setPath("/");
//max exist time is 24h
cookie.setMaxAge(24*60*60);
//cookie is writed into browser
response.addCookie(cookie);
}
return "success";
}
@RequestMapping("deleteproduct/{code}")
public @ResponseBody String deleteProduct(@PathVariable("code") Long code, HttpServletRequest request, HttpServletResponse response) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper om = new ObjectMapper();
om.setSerializationInclusion(Include.NON_NULL);
CartInfo cartInfo = null;
ProductInfo productInfo = null;
Paper paper = null;
//get cart from cookie
Cookie[] cookies = request.getCookies();
if(null != cookies && cookies.length > 0) {
for(Cookie cookie : cookies) {
if("BUYER_CART".equals(cookie.getName())) {
String decode = URLDecoder.decode(cookie.getValue(), "UTF-8");
cartInfo = om.readValue(decode, CartInfo.class);
break;
}
}
}
//if no cart in cookie create it
if(null == cartInfo)
cartInfo = new CartInfo();
//add product into cart to instead fronted product infor
if(null != code) {
paper = paperService.getPaper(code);
}
if(null != paper) {
productInfo = new ProductInfo(paper);
//remove existing productInfo
cartInfo.removeProduct(productInfo);
cartInfo.setQuantity(cartInfo.getQuantityTotal());
cartInfo.setTotal(cartInfo.getAmountTotal());
}
//above login in or login out is the same
//below need judge
String userName = securityService.findLoggedUsername();
if(null != userName) {
//login in
//delete line single to redis
cartService.deleteProductInfoToRedis(productInfo, userName);;
//clear cookie, set survive time=0 and destroy
Cookie cookie = new Cookie("BUYER_CART", null);
cookie.setPath("/");
cookie.setMaxAge(-0);
response.addCookie(cookie);
} else {
//not login in
//save cart into cookie
//convert object into json
Writer writer = new StringWriter();
om.writeValue(writer, cartInfo);
String encode = URLEncoder.encode(writer.toString(), "UTF-8");
Cookie cookie = new Cookie("BUYER_CART", encode);
//set cookie is public share
cookie.setPath("/");
//max exist time is 24h
cookie.setMaxAge(24*60*60);
//cookie is writed into browser
response.addCookie(cookie);
}
return "success";
}
}
添加购物车功能是@RequestMapping(value={“/addcart/{code}/{quantity}”})
这里前端传递过来的参数商品id,和数量,以pathvariable的形式过来。
1)首先从request将cookie中购物车信息取出来(若有),utf-8解码,将购物车中的json格式的信息还原到CartInfo对象(写入cookie则相反:将CartInfo对象转换成json格式的String,然后用utf-8编码后写入cookie);若request中无购物车信息,new一个CartInfo对象
2)根据商品code(id)从数据库中将新添加的商品取出,存入购物车CartInfo中,此时购物车中应该包括cookie中的原购物车商品(若有)和新添加的商品;
3)判断用户是否登陆
3.1用户登陆,则把购物车存入redis
cartService.insertCartToRedis(cartInfo, userName);
并清除cookie中购物车信息;
3.2用户未登录,则把购物车对象CartInfo转换成json格式的String,utf-8转码存入cookie;
4)重定向/toCart
package git.com.postgraduate.bookstore.controller;
@Controller
public class ToCartController {
@Autowired
private CartService cartService;
@Autowired
private SecurityService securityService;
@RequestMapping(value= "/toCart")
public String toCart(Model model, HttpServletRequest request, HttpServletResponse response) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper om = new ObjectMapper();
om.setSerializationInclusion(Include.NON_NULL);
CartInfo cartInfo = null;
//get cart from cookie
Cookie[] cookies = request.getCookies();
if(null != cookies && cookies.length>0) {
for(Cookie cookie : cookies) {
if("BUYER_CART".equals(cookie.getName())) {
String decode = URLDecoder.decode(cookie.getValue(), "UTF-8");
cartInfo = om.readValue(decode, CartInfo.class);
break;
}
}
}
//judge whether login in
String userName = securityService.findLoggedUsername();
if(null != userName) {
//login in
//if cart is not empty add cart to redis
if(null != cartInfo) {
cartService.insertCartToRedis(cartInfo, userName);
// destroy cookie as before
Cookie cookie = new Cookie("BUYER_CART", null);
cookie.setPath("/");
cookie.setMaxAge(-0);
response.addCookie(cookie);
}
// get cart from redis
cartInfo = cartService.selectCartInfoFromRedis(userName);
}
if(null == cartInfo) {
cartInfo = new CartInfo();
}
cartInfo.setQuantity(cartInfo.getQuantityTotal());
cartInfo.setTotal(cartInfo.getAmountTotal());
//return cart to html react to construct components
model.addAttribute("BUYER_CART", cartInfo);
return "cart";
}
}
这里和添加新商品到购物车类似,只是少了添加新商品的代码
依然是先从request中取出CartInfo,判断是否用户登录,若登录则将cookie中的CartInfo持久化到redis,若未登录,则不执行任何事情。这里主要防止用户未登录状态下添加了新商品到购物车,然后进行了登录。原则就是一旦登陆则购物车信息完全在redis中,
登录状态下,从redis中将CartInfo信息取出,未登录则还是request中cookie的CartInfo,
将CartInfo放入model ,并返回cart.jsp。显示购物车信息。
以上是购物车的大致保存方案,下面看一下redis是如何使用的:
先来看redis需要Spring框架container IOC所创建的bean(即dataredis-context.xml配置文件,在spring初始化时以此创建bean实例)
<beans>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list>
<value>git.com.postgraduate.bookstore.entityvalue>
list>
property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">updateprop>
<prop key="hibernate.dialect">${hibernate.dialect}prop>
<prop key="hibernate.show_sql">falseprop>
props>
property>
bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
bean>
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="poolConfig" ref="poolConfig" />
<property name="port" value="${redis.port}" />
<property name="hostName" value="${redis.host}" />
<property name="password" value="${redis.password}" />
<property name="timeout" value="${redis.timeout}" />
bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer">bean>
property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
property>
bean>
beans>
redis的配置是 jedis pool配置 以及 redis服务器配置 、redisTempalte配置
jedis pool配置主要是配置redis的连接池,最大活跃数及最长等待时间,不详述;
redis服务器配置配置redis的连接,包括连接的ip port username password,和mysql连接类似,这里还配置了连接池;
redisTempalte主要是通过spring-data-redis来进行对redis的操作,并将redis连接配置bean作为属性引用进来,注意这里要配置序列化方式(将key hashkey hashvalue设置为用org.springframework.data.redis.serializer.StringRedisSerializer序列化,因为我们存入的购物车信息是key=username;hashkey=商品id;hashvalue=商品数量)
来看看 redis具体的操作方法:
package git.com.postgraduate.bookstore.dao;
import java.io.Serializable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
public abstract class AbstractRedisDao {
@Autowired
protected RedisTemplate redisTemplate;
}
package git.com.postgraduate.bookstore.dao.impl;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Repository;
import git.com.postgraduate.bookstore.dao.AbstractRedisDao;
@Repository
public class RedisDaoImpl extends AbstractRedisDao {
/*批量删除对应的value*/
public void remove(String... keys) {
for(String key : keys)
remove(key);;
}
/*批量删除key*/
public void removePattern(String pattern) {
Set keys = redisTemplate.keys(pattern);
if(keys.size()>0)
redisTemplate.delete(keys);
}
/*删除对应的value*/
public void remove(String key) {
if(exists(key)) {
redisTemplate.delete(key);
}
}
/*删除map中的key-value*/
public void remove(String key, String field) {
BoundHashOperations operations = redisTemplate.boundHashOps(key);
operations.delete(field);
}
/*判断缓存中是否有对应的value*/
public boolean exists(String key) {
return redisTemplate.hasKey(key);
}
public boolean exists(String key, String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
/*读取缓存*/
public Object get(String key) {
Object result = null;
HashOperations operations = redisTemplate.opsForHash();
result = operations.entries(key);
return result;
}
/*写入缓存*/
public boolean set(String key,HashMap value) {
boolean result = false;
try {
HashOperations operations = redisTemplate.opsForHash();
operations.putAll(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/*写入缓存*/
public boolean set(String key, HashMap value, long expireTime) {
boolean result = false;
try {
HashOperations operations = redisTemplate.opsForHash();
operations.putAll(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/*以一个梯度增加*/
public void incrBy(String key,String field, Long nm) {
BoundHashOperations operations = redisTemplate.boundHashOps(key);
operations.increment(field, nm);
}
}
主要是通过redisTemplate来进行redis的操作,具体操作方法不再详述
当然上边是直接操作redis的Dao层,我们还要向上封装到service层
package git.com.postgraduate.bookstore.service;
@Service
public class CartServiceImpl implements CartService {
@Autowired
private PaperService paperService;
@Autowired
RedisDaoImpl redisDao;
public void insertCartToRedis(CartInfo cartInfo, String userName) {
List<CartLineInfo> items = cartInfo.getCartLines();
if(! items.isEmpty()) {
//redis key: username; field: code; value: amount
HashMap<String, String> map = new HashMap<String, String>();
for(CartLineInfo item : items) {
//if exist in redis ,increaby
if(redisDao.exists(userName, String.valueOf(item.getProductInfo().getCode()))) {
redisDao.incrBy(userName, String.valueOf(item.getProductInfo().getCode()), new Long(item.getQuantity()));
} else {
map.put(String.valueOf(item.getProductInfo().getCode()), String.valueOf(item.getQuantity()));
}
if(map.size()>0)
redisDao.set(userName, map);
}
}
}
public void updateCartToRedis(CartLineInfo line, String userName) {
if(line.getQuantity()>0) {
HashMap<String, String> map = new HashMap<String, String>();
if(redisDao.exists(userName, String.valueOf(line.getProductInfo().getCode()))) {
map.put(String.valueOf(line.getProductInfo().getCode()), String.valueOf(line.getQuantity()));
if(map.size()>0)
redisDao.set(userName, map);
}
}
}
public void deleteProductInfoToRedis(ProductInfo productInfo, String userName) {
if(redisDao.exists(userName, String.valueOf(productInfo.getCode()))) {
redisDao.remove(userName, String.valueOf(productInfo.getCode()));;
}
}
public CartInfo selectCartInfoFromRedis(String username) {
CartInfo cartInfo = new CartInfo();
//get all product redis has form of key=username; map(field=code;value=quantity)
HashMap<String, String> getAll = (HashMap<String, String>) redisDao.get(username);
Set<Entry<String, String>> entrySet = getAll.entrySet();
for(Entry<String, String> entry: entrySet) {
Paper paper = paperService.getPaper(Long.valueOf(entry.getKey()));
ProductInfo productInfo = new ProductInfo(paper);
//add to shoppingCart
cartInfo.addProduct(productInfo, Integer.parseInt(entry.getValue()));
}
return cartInfo;
}
@Override
public void deleteCartInfo(String userName) {
if(redisDao.exists(userName)) {
redisDao.remove(userName);
}
}
}
这里主要介绍了购物车的储存方案,并没有详细展开,细节部分详见github