電商平臺的搭建(SpringMVC+SpringSecurity/Validation+Redis+MySQL+React)----購物車功能

承前之作,之前博客介紹了商城的登錄註冊功能,這一篇總結一下購物車的實現,其實技術的實現有多種方式,這裏只是其中之一,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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章