电商平台的搭建(SpringMVC+SpringSecurity/Validation+Redis+MySQL+React)----购物车功能

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/linfujian1999/article/details/74613426

具体代码详见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实例)

<?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


附数据库表结构图片
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

猜你喜欢

转载自blog.csdn.net/linfujian1999/article/details/74613426