springboot篇】二十一. 基於springboot電商項目 十 購物車模塊和自定義註解校驗登錄狀況

springboot項目

中國加油,武漢加油!

篇幅較長,配合目錄觀看

案例準備

  1. 本案例基於springboot篇】二十一. 基於springboot電商項目 九 修改密碼,登錄和註銷

1. 購物車模塊環境搭建

1.1 新建數據庫表t_cart

DROP TABLE IF EXISTS `t_cart`;
CREATE TABLE `t_cart` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `gid` int(10) DEFAULT NULL,
  `num` int(10) DEFAULT NULL,
  `uid` int(10) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `sub_total` decimal(10,0) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1.2 shop-entity定義Cart

package com.wpj.entity;

import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_cart")
public class Cart implements Serializable{
    @TableId(type = IdType.AUTO)
    private Integer id;
    private Integer gid; // 商品id
    private Integer num; // 商品數量
    private Integer uid; // 用戶id
    @TableField(value = "create_time")
    private Date createTime; // 創建
    @TableField(value = "sub_total")
    private BigDecimal subTotal; // 商品小計
    @TableField(exist = false)
    private Goods goods; // 商品信息
}

1.3 shop-mapper編寫Mapper接口

package com.wpj.mapper;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.wpj.entity.Cart;

public interface ICartMapper extends BaseMapper<Cart> {
}

1.4 shop-service編寫Servic接口

package com.wpj.service;

import com.wpj.entity.Cart;

public interface ICartService extends IBaseService<Cart> {
}

1.5 shop-service-iml新建cart-service(module-springboot)

1.6 cart-service導包

<dependency>
 <groupId>com.wpj</groupId>
    <artifactId>shop-service-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.wpj</groupId>
    <artifactId>shop-mapper</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.wpj</groupId>
    <artifactId>shop-common</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>0.2.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.8</version>
</dependency>

1.7 編寫yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/nz1904-springboot-shop
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
mybatis-plus:
  type-aliases-package: com.wpj.entity
  mapper-locations: classpath:/mapper/*.xml
dubbo:
  application:
    name: cart-service
  registry:
    address: zookeeper://192.168.59.100:2181
  protocol:
    port: -1

1.8 cart-service編寫ServiceImpl實現類

package com.wpj.service.impl;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.wpj.entity.Cart;
import com.wpj.mapper.ICartMapper;
import com.wpj.service.ICartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CartServiceImpl extends BaseServiceImpl<Cart> implements ICartService {

    @Autowired
    private ICartMapper cartMapper;

    @Override
    public BaseMapper<Cart> getMapper() {
        return cartMapper;
    }
}

1.9 shop-web新建shop-cart(module-springboot)

  1. Spring Boot DevTools
  2. Spring Web
  3. Thymeleaf
  4. Spring Data Redis(Access+Driver)

1.10 shop-cart編寫Controller

package com.wpj.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/cart")
public class CartController {

    @RequestMapping("/addCart")
    @ResponseBody
    public String addCart(Integer gid, Integer num) {
        System.out.println("gid = " + gid + ", num = " + num);
        return "ok";
    }
}

1.11 編寫yml

server:
  port: 8085
spring:
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
  redis:
    host: 192.168.59.100
    password: admin
dubbo:
  application:
    name: shop-cart
  registry:
    address: zookeeper://192.168.59.100:2181
  consumer:
    check:  false
    retries: 3
    timeout: 10000

1.12 修改程序入口

package com.wpj;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(scanBasePackages = "com.wpj", exclude = DataSourceAutoConfiguration.class)
public class ShopCartApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShopCartApplication.class, args);
    }
}

1.13 啓動程序入口Test

在這裏插入圖片描述

2. 自定義註解校驗登錄狀況

2.1 shop-common導包

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.2</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.24.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.1.8.RELEASE</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.wpj</groupId>
    <artifactId>shop-entity</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.1.10.RELEASE</version>
</dependency>

2.2 編寫IsLogin註解

package com.wpj.common.aop;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface IsLogin {
    // 默認不強制注入user
    boolean mustUser() default false;
}

2.3 編寫IsLoginAOP

package com.wpj.common.aop;

import com.wpj.common.constant.Constants;
import com.wpj.entity.User;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class IsLoginAOP {

    @Autowired
    private RedisTemplate redisTemplate;

    @Around("@annotation(IsLogin)")
    public Object handler(ProceedingJoinPoint point) {
        // 添加了兩個依賴 servlet-api 和 spring-web
        // 獲取HttpServletRequest對象(和Servlet容器解耦的情況下獲取ServletAPI)
        ServletRequestAttributes requestAttributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        // 根據req對象獲取Cookie
        String loginToken = ""; // 登錄用戶的憑證
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
            for(int i=0;i<cookies.length;i++){
                Cookie cookie = cookies[i];
                if(Constants.LOGIN_TOKEN.equals(cookie.getName())){
                    loginToken = cookie.getValue();
                    break;
                }
            }
        }
        // 再從Redis中查詢登錄用戶
        User user = null; // 當前登錄用戶
        if(!StringUtils.isEmpty(loginToken)){
            user = (User) redisTemplate.opsForValue().get(loginToken);
        }
        // 判斷用戶是否登錄,如果沒有登錄,再判斷mustUser是否爲true
        if(user == null) {
            // 獲取目標方法
            MethodSignature methodSignature = (MethodSignature)point.getSignature();
            // 獲取方法上面的IsLogin的註解
            IsLogin annotation = methodSignature.getMethod().getAnnotation(IsLogin.class);
            // 判斷user是否是必須的
            if(annotation.mustUser()){
                return "redirect:http://localhost:8084/toLogin";
            }
        }
        // 把user傳遞Controller中的形參
        Object[] args = point.getArgs();// 獲取目標方法的參數
        for(int i =0;i<args.length;i++){
            if(args[i]!= null && args[i].getClass() == User.class){ // 判斷當前的參數是否是user類型
                args[i]=user; // 替換
                break;
            }
        }
        Object proceed = null;
        try {
            // 調用Controller,把我們處理過的參數傳遞過去
            proceed = point.proceed(args);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return proceed;
    }
}

2.4 shop-cart編寫Contrller

package com.wpj.controller;

import com.wpj.common.aop.IsLogin;
import com.wpj.entity.Cart;
import com.wpj.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


@Controller
@RequestMapping("/cart")
public class CartController {

    @RequestMapping(value = "/addCart")
    // @ResponseBody
    @IsLogin(mustUser = true)
    public String addCart(String callback) {
        System.out.println("CartController.addCart");
        String result = "ok";
        return callback == null?result+"":callback+"('"+result+"')";
    }
}

2.5 啓動程序入口測試

  1. 注意清楚cookie達到沒有登錄情況下
  2. 調用addCart就會跳轉到login頁面
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述

3. 購物車模塊-加入購物車

3.1 shop-cart導包

<dependency>
    <groupId>com.wpj</groupId>
    <artifactId>shop-common</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.wpj</groupId>
    <artifactId>shop-service-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>0.2.0</version>
</dependency>

3.2 Service添加方法

package com.wpj.service;

import com.wpj.entity.Cart;
import com.wpj.entity.User;

public interface ICartService extends IBaseService<Cart> {
    int addCart(Cart cart, User user, String cartToken);
}

3.3 ServiceImpl重寫方法

package com.wpj.service.impl;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.wpj.entity.Cart;
import com.wpj.entity.User;
import com.wpj.mapper.ICartMapper;
import com.wpj.service.ICartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class CartServiceImpl extends BaseServiceImpl<Cart> implements ICartService {

    @Autowired
    private ICartMapper cartMapper;

    @Autowired
    private RedisTemplate redisTemplate;
    
    @Override
    public BaseMapper<Cart> getMapper() {
        return cartMapper;
    }

    @Override
    public int addCart(Cart cart, User user, String cartToken) {

        cart.setCreateTime(new Date());
        // 判斷用戶是否登錄
        if (user != null) {
            cart.setUid(user.getId());
            cartMapper.insert(cart); // 添加到數據庫
            return 1;
        } else {
            redisTemplate.opsForList().rightPush(cartToken, cart);
            return 1;
        }
    }
}

3.2 shop-cart完善Controller的addCart方法

package com.wpj.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.wpj.common.aop.IsLogin;
import com.wpj.common.constant.Constants;
import com.wpj.entity.Cart;
import com.wpj.entity.User;
import com.wpj.service.ICartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

@Controller
@RequestMapping("/cart")
public class CartController {

    @Autowired
    private RedisTemplate redisTemplate;

    @Reference
    private ICartService cartService;

    /**
     * 登錄了放到數據庫中
     * 未登錄放到Redis
     *      1)key放到cookie中
     * @param callback
     * @return
     */
    @RequestMapping(value = "/addCart")
    @ResponseBody
    @IsLogin
    public String addCart(String callback, Cart cart, User user,
                          @CookieValue(name = Constants.CART_TOKEN,required = false) String cartToken,
                          HttpServletResponse resp){

       // 判斷用戶瀏覽器中是否有cartToken
        if(StringUtils.isEmpty(cartToken)){
            cartToken= UUID.randomUUID().toString();
            Cookie cookie = new Cookie(Constants.CART_TOKEN,cartToken);
            cookie.setMaxAge(60*60*24*5);
            cookie.setPath("/");
            cookie.setHttpOnly(false);

            resp.addCookie(cookie);
        }
        int result = cartService.addCart(cart,user,cartToken);

        return callback == null?result+"":callback+"('"+result+"')";
    }
}

3.3 cart-service修改ServiceImpl的@Service

在這裏插入圖片描述

3.4 配置yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/nz1904-springboot-shop
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
  redis:
    host: 192.168.59.100
    password: admin
mybatis-plus:
  type-aliases-package: com.wpj.entity
  mapper-locations: classpath:/mapper/*.xml
dubbo:
  application:
    name: cart-service
  registry:
    address: zookeeper://192.168.59.100:2181
  protocol:
    port: -1

3.4 啓動程序入口Test

3.4.1 未登錄狀態下

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

3.4.2 登錄狀態下

在這裏插入圖片描述在這裏插入圖片描述

4. 購物車模塊-我的購物車

4.1 shop-service-api添加方法

List<Cart> getUserCartList(User user, String cartToken);

4.2 cart-service重寫方法

@Reference
private IGoodService goodService;

@Override
public List<Cart> getUserCartList(User user, String cartToken) {
    // 查詢用戶購物車
    List<Cart> cartList = new ArrayList<Cart>();
    if (user != null) {
        EntityWrapper wrapper = new EntityWrapper();
        wrapper.eq("uid", user.getId());
        cartList = cartMapper.selectList(wrapper);
    } else {
        cartList = redisTemplate.opsForList().range(cartToken, 0, -1);
    }
    // 查詢購物車中每個商品的信息
    for (Cart cart : cartList) {
        Goods goods = goodService.selectById(cart.getGid());
        cart.setGoods(goods);
        BigDecimal gprice = goods.getGprice();
        Integer num = cart.getNum();
        BigDecimal subTotal = gprice.multiply(BigDecimal.valueOf(num));// 價格*num
        cart.setSubTotal(subTotal); // 小計
    }
    // 去重
    Set<Integer> set = new HashSet<>(); // set目的是爲了判斷是否有重複
    Map<Integer, Cart> map = new HashMap<>(); // Map<商品Id,購車>
    for (Cart cart : cartList) {
        if (set.add(cart.getGoods().getId())) {
            map.put(cart.getGoods().getId(), cart);
        } else {
            Cart mapCart = map.get(cart.getGoods().getId());
            mapCart.setNum(mapCart.getNum() + cart.getNum());
            mapCart.setSubTotal(mapCart.getSubTotal().add(cart.getSubTotal()));
        }
    }
    // 清空集合
    cartList.clear();
    // 把集合中的商品放到list裏面
    Set<Map.Entry<Integer, Cart>> entries = map.entrySet();
    for (Map.Entry<Integer, Cart> entry : entries) {
        cartList.add(entry.getValue());
    }
    return cartList;
}

4.3 shop-cart引入靜態資源

4.4 編寫cartList.html

4.5 shop-cart添加方法

@RequestMapping(value = "/getUserCartList")
@IsLogin
public String getUserCartList(User user, @CookieValue(name = Constants.CART_TOKEN,required = false) String cartToken, ModelMap map){
    List<Cart> cartList = cartService.getUserCartList(user,cartToken);
    map.put("cartList",cartList);
    return "cartList";
}

4.6 修改shop-search的searchGoodList.html

在這裏插入圖片描述

4.7 啓動程序入口測試

在這裏插入圖片描述
在這裏插入圖片描述

5 購物車模塊-設置購物車數量

5.1 修改shop-search的searchGoodsList.html

<script type="text/javascript">
    $(function(){
        $.ajax({
            url:"http://localhost:8085/cart/getUserCartCount",
            dataType:"JSONP",
            jsonpCallback:"callback1",
            success:function(data){
                $("#showCart").text(data);
            }
        })
    })
</script>
<li><a href="http://localhost:8085/cart/getUserCartList">我的購物車<span id="showCart"></span></a></li>

5.2 shop-cart的Controller編寫方法

@RequestMapping(value = "/getUserCartCount")
@IsLogin
@ResponseBody
public String getUserCartCount(String callback,User user, @CookieValue(name = Constants.CART_TOKEN,required = false) String cartToken){
    Integer count = 0;
    List<Cart> cartList = cartService.getUserCartList(user,cartToken);
    for(Cart cart:cartList){
        count+=cart.getNum();
    }
    return callback == null?count+"":callback+"('"+count+"')";
}

6. 購物車模塊-購物車合併

6.1 shop-service-api添加方法

int mergeCart(User user, String cartToken);

6.2 cart-service重寫方法

 @Override
public int mergeCart(User user, String cartToken) {
	// 從reids中查詢
	List<Cart> cartList = redisTemplate.opsForList().range(cartToken, 0, -1);
	for (Cart cart : cartList) {
	    cart.setUid(user.getId());
	    cartMapper.insert(cart);
	}
	// 清空redis
	redisTemplate.delete(cartToken);
	return 1;
}

6.3 shop-sso修改Controller的login方法

@Reference
private ICartService cartService;

@RequestMapping(value = "/login")
@ResponseBody
public ResultEntity login(String username, String password, String returnUrl,HttpServletResponse resp, @CookieValue(name = Constants.CART_TOKEN,required = false) String cartToken){
    // 根據用戶名查詢對象
    User user = userService.selectByUsername(username);
    if(user != null){
        // 密碼的比對
        if(PasswordUtils.checkpw(password,user.getPassword())){
            // 購車的合併
            if(!StringUtils.isEmpty(cartToken)){
                int result = cartService.mergeCart(user,cartToken);
            }
            // 把登陸用戶放道redis中
            redisTemplate.opsForValue().set(username,user,5,TimeUnit.DAYS);
            // 把用戶的憑證放道cookie
            Cookie cookie = new Cookie(Constants.LOGIN_TOKEN,username);
            cookie.setMaxAge(60*60*24*5);
            cookie.setHttpOnly(false); // 是否允許js訪問
            cookie.setPath("/"); // 同一個域名下面可以訪問
//                cookie.setDomain(); // 正則表達式,解決一級域名的cookie跨域
//                cookie.setSecure(true); // 必須是加密(https)的請求才可以攜帶cookie
            // 寫到cookie
            resp.addCookie(cookie);
            if(StringUtils.isEmpty(returnUrl)){
                System.out.println(returnUrl);
                returnUrl = "http://localhost:8081/";
            }
            return ResultEntity.SUCCESS(returnUrl);
        }else{
            return ResultEntity.FALL("用戶名或密碼錯誤");
        }

    }else{
        return ResultEntity.FALL("用戶名不存在");
    }
}

7. 購物車模塊-提交訂單

7.1 shop-cart修改cartList.html

在這裏插入圖片描述

7.2 shop-web修改Controller的getUserCartList方法

 @RequestMapping(value = "/getUserCartList")
@IsLogin
public String getUserCartList(User user, @CookieValue(name = Constants.CART_TOKEN,required = false) String cartToken, ModelMap map){
    List<Cart> cartList = cartService.getUserCartList(user,cartToken);
    BigDecimal totalPrice = new BigDecimal(0);
    for(Cart cart:cartList){
        totalPrice= totalPrice.add(cart.getSubTotal());
    }
    map.put("cartList",cartList);
    map.put("totalPrice",totalPrice);
    return "cartList";
}

7.3 啓動程序入口測試

在這裏插入圖片描述

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