承前之作,之前博客介紹了商城的登錄註冊功能,這一篇總結一下購物車的實現,其實技術的實現有多種方式,這裏只是其中之一,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實例)
<?xml version="1.0" encoding="UTF-8"?>
<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.entity</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">false</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- jedis pool配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<!-- <property name="testOnBorrow" value="${redis.testOnBorrow}" /> -->
</bean>
<!-- redis服務器配置 -->
<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<Serializable, Object> 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<Serializable> 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<Serializable, Object, Object> 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<Serializable, Object, Object> operations = redisTemplate.opsForHash();
result = operations.entries(key);
return result;
}
/*寫入緩存*/
public boolean set(String key,HashMap<String, String> value) {
boolean result = false;
try {
HashOperations<Serializable, String, String> operations = redisTemplate.opsForHash();
operations.putAll(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/*寫入緩存*/
public boolean set(String key, HashMap<String, Long> value, long expireTime) {
boolean result = false;
try {
HashOperations<Serializable, String, Long> 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<Serializable, Object, Object> 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