基於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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM