Fork me on GitHub

基于redis实现未登录购物车

 

  • springboot 工程
  • 主要说明购物车流程(故将登录用户信息保存至session)
  • 未登录时 将用户临时key 保存至cookie
  • 有不足之处 请大佬指点

 

项目源码: https://github.com/youxiu326/sb_shopping_cart

 

项目结构:

 

package com.youxiu326.common;

public class JsonResult {

    private String code;
    private String message;
    private Object data;

    public JsonResult() {
        this.code = "200";
        this.message = "操作成功";
    }

    public JsonResult success(String message){
        this.code = "200";
        this.message = message;
        return this;
    }

    public JsonResult error(String message){
        this.code = "400";
        this.message = message;
        return this;
    }

    public JsonResult error(){
        this.code = "400";
        this.message = "操作失败";
        return this;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}
JsonResult
package com.youxiu326.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import javax.annotation.PostConstruct;


/**
 * 防止redis  中文乱码
 */
@Configuration
public class RedisConfig {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @PostConstruct
    public void initRedisTemplate() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
    }

}
RedisConfig.java
package com.youxiu326.entity;

import java.io.Serializable;

/**
 * 用户
 */
public class Account implements Serializable {

    private String id = "youxiu326";

    private String code = "test";

    private String pwd = "123456";

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}
Account.java
package com.youxiu326.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * 购物车对象 一个购物车由n个CartItem组成
 */
public class ShoppingCart implements Serializable {

    public static final String unLoginKeyPrefix="TMP_";

    public static final String loginKeyPrefix="USER_";

    private String key="";

    private List<CartItem> cartItems = new ArrayList<>();//防止空指针

    public ShoppingCart(){}

    public ShoppingCart(String key) {
        this.key = key;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public List<CartItem> getCartItems() {
        return cartItems;
    }

    public void setCartItems(List<CartItem> cartItems) {
        this.cartItems = cartItems;
    }
}
ShoppingCart.java
package com.youxiu326.entity;

import java.io.InputStream;
import java.io.Serializable;
import java.util.Objects;

/**
 * 购物实体
 */
public class CartItem implements Serializable {

    private String code;

    private Integer quantity;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CartItem cartItem = (CartItem) o;
        return Objects.equals(code, cartItem.code);
    }

    @Override
    public int hashCode() {
        return Objects.hash(code);
    }
}
CartItem.java
# post
server.port=8888

# redis
spring.redis.host=youxiu326.xin
spring.redis.port=6379

# thymeleaf
spring.thymeleaf.cache=false
application.properties
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <base th:href="${#httpServletRequest.getContextPath()+'/'}">
    <meta charset="UTF-8">
    <title>测试未登陆基于redis实现购物车功能</title>
</head>
<body>
<h2>商品列表</h2>

<table>
    <tr>
        <td>商品编号</td>
        <td>商品数量</td>
    </tr>
    <tr  th:each="cart,iterStat : ${cartItems}">
        <th scope="row" th:text="${cart.code}">1</th>
        <td th:text="${cart.quantity}">quantity</td>
    <!--    <td >-->
    <!--        <img th:src="${cart.webLogo}"/>-->
    <!--    </td>-->
    <!--    <td th:text="${iterStat.index}">index</td>-->
    </tr>
</table>



<br/>


<form action="#" method="post">
    <table>
        <tr>
            <td>商品编号:</td>
            <td><input type="text" id="code" name="code" value="youxiu001"></td>
        </tr>
        <tr>
            <td>数量:</td>
            <td><input id="quantity" name="quantity" value="1"></td>
        </tr>
        <tr>
            <td colspan="1"><button type="button" onclick="add()">加购</button></td>
            <td colspan="1"><button type="button" onclick="remove()">减购</button></td>
        </tr>
    </table>
</form>

</body>

<script src="/jquery-1.11.3.min.js"></script>
<!--<script th:src="@{/jquery-1.11.3.min.js}"></script>-->
<script>
    function add(){
        $.ajax({
            type: 'POST',
            url: "/shopping/add",
            data: {"code":$("#code").val(),"quantity":$("#quantity").val()},
            // dataType: "json",
            success: function(response){
                if(response.code=="200"){
                    window.location.reload();
                }else{
                    alert(response.message);
                }
            },
            error:function(response){
                alert(response.message);
                console.log(response);
            }
        });
    }

     function remove(){
        $.ajax({
            type: 'POST',
            url: "/shopping/remove",
            data: {"code":$("#code").val(),"quantity":$("#quantity").val()},
            // dataType: "json",
            success: function(response){
                if(response.code=="200"){
                    window.location.reload();
                }else{
                    alert(response.message);
                }
            },
            error:function(response){
                alert("失败");
                console.log(response);
            }
        });
    }

</script>


</html>
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <base th:href="${#httpServletRequest.getContextPath()+'/'}">
    <meta charset="UTF-8">
    <title>登陆界面</title>
</head>
<body>

<div th:if="${session.account == null}">[未登陆]</div>
<div th:if="${session.account != null}">
    <div th:text="${session.account.code}"></div>
</div>

<form action="#" method="post">
    <table>
        <tr>
            <td>用户:</td>
            <td><input type="text" id="code" name="code" value="test"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input id="pwd" name="pwd" value="youxiu326"></td>
        </tr>
        <tr>
            <td colspan="1"><button type="button" onclick="login()">登陆</button></td>
            <td colspan="1"><button type="button" onclick="logout()">登出</button></td>
        </tr>
    </table>
</form>

<br/>

<a target="_blank" href="/shopping/index">去购物车页面</a>


</body>

<script src="/jquery-1.11.3.min.js"></script>
<!--<script th:src="@{/jquery-1.11.3.min.js}"></script>-->
<script>
    function login(){
        $.ajax({
            type: 'POST',
            url: "/login",
            data: {"code":$("#code").val(),"pwd":$("#pwd").val()},
            // dataType: "json",
            success: function(response){
                if(response.code=="200"){
                    alert(response.message);
                    window.location.reload();
                }else{
                    alert(response.message);
                }
            },
            error:function(response){
                alert(response.message);
                console.log(response);
            }
        });
    }

     function logout(){
        $.ajax({
            type: 'POST',
            url: "/logout",
            data: {"code":$("#code").val()},
            // dataType: "json",
            success: function(response){
                if(response.code=="200"){
                    alert(response.message);
                    window.location.reload();
                }else{
                    alert(response.message);
                }
            },
            error:function(response){
                alert("失败");
                console.log(response);
            }
        });
    }

</script>


</html>
login.html

 

package com.youxiu326.controller;

import com.youxiu326.common.JsonResult;
import com.youxiu326.entity.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

/**
 * 登陆接口
 */
@Controller
public class LoginCtrl {


    @GetMapping("/")
    public String toLogin(HttpServletRequest req){
        return "login";
    }


    @PostMapping("/login")
    @ResponseBody
    public JsonResult login(HttpServletRequest req,Account account){

        JsonResult result = new JsonResult();

        if (StringUtils.isBlank(account.getCode()) || StringUtils.isBlank(account.getPwd())){
            result.error("账户或密码为空");
            return result;
        }
        //创建登陆用户账户 【主要逻辑是购物车 登陆用户id 就是用户code】
        account.setId(account.getCode());
        //将用户保存至session
        req.getSession().setAttribute("account",account);

        return result.success("登陆成功");
    }

    @PostMapping("/logout")
    @ResponseBody
    public JsonResult logout(HttpServletRequest req,Account account){

        JsonResult result = new JsonResult();

        if (StringUtils.isBlank(account.getCode())){
            result.error("账户为空");
            return result;
        }
        req.getSession().removeAttribute("account");

        return result.success("登出成功");
    }

}
LoginCtrl.java

 

 

主要业务流程service:

package com.youxiu326.service.impl;

import com.youxiu326.common.JsonResult;
import com.youxiu326.entity.Account;
import com.youxiu326.entity.CartItem;
import com.youxiu326.entity.ShoppingCart;
import com.youxiu326.service.ShoppingCartService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.util.WebUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获得用户key
     *
     * 1.用户未登录情况下第一次进入购物车  -> 生成key 保存至cookie中
     * 2.用户未登录情况下第n进入购物车    -> 从cookie中取出key
     * 3.用户登录情况下                  -> 根据用户code生成key
     * 4.用户登录情况下并且cookie中存在key-> 从cookie取的的key从缓存取得购物车 合并至
     *  用户code生成key的购物车中去 ,这样后面才能根据用户code 取得正确的购物车
     *
     * @param req
     * @param resp
     * @param account
     * @return
     */
    @Override
    public String getKey(HttpServletRequest req, HttpServletResponse resp, Account account) {
        //https://github.com/youxiu326/sb_shiro_session.git

        String key = null;  //最终返回的key
        String tempKey = ""; //用来存储cookie中的临时key,

        Cookie cartCookie = WebUtils.getCookie(req, "shoopingCart");
        if(cartCookie!=null){
            //获取Cookie中的key
            key = cartCookie.getValue();
            tempKey = cartCookie.getValue();
        }
        if(StringUtils.isBlank(key)){
            key = ShoppingCart.unLoginKeyPrefix + UUID.randomUUID();
            if (account!=null)
                key = ShoppingCart.loginKeyPrefix + account.getId();
            Cookie cookie = new Cookie("shoopingCart",key);
            cookie.setMaxAge(-1);
            cookie.setPath("/");
            resp.addCookie(cookie);
        }else if (StringUtils.isNotBlank(key) && account!=null){//
            key = ShoppingCart.loginKeyPrefix + account.getId();
            if (tempKey.startsWith(ShoppingCart.unLoginKeyPrefix)){////1.满足cookie中取得的key 为未登录时的key
                //2.满足当前用户已经登录
                //3.合并未登录时用户所添加的购物车商品⑷
                mergeCart(tempKey,account);//
            }
        }
        return key;
    }

    /**
     * 合并购物车 返回最终购物车
     * @param tempKey
     */
    public ShoppingCart  mergeCart(String tempKey,Account account) {

        ShoppingCart loginCart = null;
        String loginkey = null;

        // 从redis取出用户缓存购物车数据
        HashOperations<String, String, ShoppingCart> vos = redisTemplate.opsForHash();
        ShoppingCart unLoginCart = vos.get("CACHE_SHOPPINGCART", tempKey);
        if (unLoginCart == null){
            unLoginCart = new ShoppingCart(tempKey);
        }
        if (account != null && tempKey.startsWith(ShoppingCart.unLoginKeyPrefix)) {////如果用户登录 并且 当前是未登录的key
            loginkey = ShoppingCart.loginKeyPrefix + account.getId();
            loginCart = mergeCart(loginkey, account);
            if (null != unLoginCart.getCartItems()) {//

                if (null != loginCart.getCartItems()) {
                    //满足未登录时的购物车不为空 并且 当前用户已经登录
                    //进行购物车合并
                    for (CartItem cv : unLoginCart.getCartItems()) {
                        long count = loginCart.getCartItems().stream().filter(it->it.getCode().equals(cv.getCode())).count();
                        if(count == 0 ){//没有重复的商品 则直接将商品加入购物车
                            loginCart.getCartItems().add(cv);
                        }else if(count == 1){//出现重复商品 修改数量
                            CartItem c = loginCart.getCartItems().stream().filter(it->it.getCode().equals(cv.getCode())).findFirst().orElse(null);
                            c.setQuantity(c.getQuantity()+1);
                        }
                    }
                } else {
                    //如果当前登录用户的购物车为空则 将未登录时的购物车合并
                    loginCart.setCartItems(unLoginCart.getCartItems());
                }
                unLoginCart = loginCart;
                //【删除临时key】
                vos.delete("CACHE_SHOPPINGCART",tempKey);
                //【将合并后的购物车数据 放入loginKey】//TMP_4369f86d-c026-4b1b-8fec-f3c69f6ffac5
                vos.put("CACHE_SHOPPINGCART",loginkey, unLoginCart);
            }
        }

        return unLoginCart;
    }

    /**
     * 添加购物车
     * @param req
     * @param resp
     * @param account 登陆用户信息
     * @param item  添加的购物车商品信息 包含商品code 商品加购数量
     * @return
     */
    public JsonResult addCart(HttpServletRequest req, HttpServletResponse resp,Account account,CartItem item){
        JsonResult result = new JsonResult();
        String key = getKey(req, resp,account);//得到最终key
        ShoppingCart cacheCart = mergeCart(key,account);//根据key取得最终购物车对象
        if(StringUtils.isNotBlank(item.getCode()) && item.getQuantity()>0){
            //TODO 进行一系列 商品上架 商品code是否正确 最大购买数量....
            if(false){
                return result.error();
            }
            long count = 0;
            if(null != cacheCart.getCartItems()) {
                count = cacheCart.getCartItems().stream().filter(it->it.getCode().equals(item.getCode())).count();
            }
            if (count==0){
                //之前购物车无该商品记录 则直接添加
                cacheCart.getCartItems().add(item);
            }else {
                //否则将同一商品数量相加
                CartItem c = cacheCart.getCartItems().stream().filter(it->it.getCode().equals(item.getCode())).findFirst().orElse(null);
                c.setQuantity(c.getQuantity()+item.getQuantity());

            }
        }
        //【将合并后的购物车数据 放入loginKey】
        HashOperations<String,String,ShoppingCart> vos = redisTemplate.opsForHash();
        vos.put("CACHE_SHOPPINGCART",key, cacheCart);
        result.setData(cacheCart);
        return result;
    }

    /**
     * 移除购物车
     * @param req
     * @param resp
     * @param account
     * @param item
     * @return
     */
    public JsonResult removeCart(HttpServletRequest req, HttpServletResponse resp,Account account,CartItem item){
        JsonResult result = new JsonResult();
        String key = getKey(req, resp,account);//得到最终key
        ShoppingCart cacheCart =  mergeCart(key , account);//根据key取得最终购物车对象
        if(cacheCart!=null && cacheCart.getCartItems()!=null && cacheCart.getCartItems().size()>0){////
            long count = cacheCart.getCartItems().stream().filter(it->it.getCode().equals(item.getCode())).count();
            if(count == 1 ){//
                CartItem ci = cacheCart.getCartItems().stream().filter(it->it.getCode().equals(item.getCode())).findFirst().orElse(null);
                if (ci.getQuantity()>item.getQuantity()){//
                    ci.setQuantity(ci.getQuantity()-item.getQuantity());
                }else if(ci.getQuantity()<=item.getQuantity()){
                    cacheCart.getCartItems().remove(ci);
                }
                //1.满足缓存购物车中必须有商品才能减购物车
                //2.满足缓存购物车中有该商品才能减购物车
                //3.判断此次要减数量是否大于缓存购物车中数量 进行移除还是数量相减操作
            }
            HashOperations<String,String,ShoppingCart> vos = redisTemplate.opsForHash();
            vos.put("CACHE_SHOPPINGCART",key, cacheCart);
        }
        result.setData(cacheCart);
        return result;
    }

    /**
     *  【场景:我加购了一双40码的鞋子到购物车 现在我想换成41码的鞋子】
     *  【例如:原商品code ABCDEFG40   ->  ABCDEFG41】
     *
     * @param req
     * @param resp
     * @param account
     * @param item 新购物商品
     * @param oldItem 原购物商品
     * @return
     */
    public String updateCart(HttpServletRequest req, HttpServletResponse resp,Account account,CartItem item,CartItem oldItem){

        //TODO 校验商品信息是否合法 是否上架 库存 最大购买数量....
        if(false){
            return null;
        }

        String key = getKey(req, resp,account);
        ShoppingCart cacheCart =  mergeCart(key , account);//TODO 待探讨
        cacheCart.getCartItems().remove(item);
        cacheCart.getCartItems().remove(oldItem);
        cacheCart.getCartItems().add(oldItem);
        HashOperations<String,String,ShoppingCart> vos = redisTemplate.opsForHash();
        vos.put("CACHE_SHOPPINGCART",key, cacheCart);
        return null;
    }

}

 

controller调用:

package com.youxiu326.controller;

import com.youxiu326.common.JsonResult;
import com.youxiu326.entity.Account;
import com.youxiu326.entity.CartItem;
import com.youxiu326.entity.ShoppingCart;
import com.youxiu326.service.ShoppingCartService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.WebUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * 购物车 Controller
 */
@Controller
@RequestMapping("/shopping")
public class ShoppingCartCtrl {

    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    private Account account;

    @Autowired
    private ShoppingCartService service;


    /**
     * 进入首页
     * @return
     */
    @GetMapping("/index")
    public String toIndex(HttpServletRequest req, HttpServletResponse resp, Model model){

        account = (Account) req.getSession().getAttribute("account");

        String key = service.getKey(req, resp, this.account);
        ShoppingCart cacheCart = service.mergeCart(key, this.account);
        model.addAttribute("cartItems",cacheCart.getCartItems());

        return "index";
    }

    @PostMapping("/add")
    @ResponseBody
    public JsonResult add(HttpServletRequest req, HttpServletResponse resp, CartItem cartItem){
        account = (Account) req.getSession().getAttribute("account");
        JsonResult result = service.addCart(req, resp, account, cartItem);
        return result;
    }

    @PostMapping("/remove")
    @ResponseBody
    public JsonResult remove(HttpServletRequest req, HttpServletResponse resp, CartItem cartItem){
        account = (Account) req.getSession().getAttribute("account");
        JsonResult result = service.removeCart(req, resp, account, cartItem);
        return result;
    }

    @PostMapping("/update")
    @ResponseBody
    public String update(HttpServletRequest req, HttpServletResponse resp){

        account = (Account) req.getSession().getAttribute("account");

        return "";
    }

}

 

演示效果:

 

项目源码: https://github.com/youxiu326/sb_shopping_cart

 

posted @ 2019-06-18 09:56  youxiu326  阅读(1899)  评论(0编辑  收藏  举报