SpringBoot电商项目之实现购物车功能

本文介绍了在SpringBoot电商项目中如何实现购物车功能,包括自定义参数解析器以简化用户信息登录检验,定义购物车对象ShopCar及其后台操作,商品详情页面展示,以及购物车的查询、新增、删除和修改功能的实现。同时,文章还解决了Mybatis-plus时间字段序列化问题。
摘要由CSDN通过智能技术生成

目录

一、自定义的参数解析器

在使用自定义的参数解析器之前的做法:

使用自定义的参数解析器之后的做法:

二、购物车后台

1.定义购物车对象ShopCar

 2.web层定义ShopCarController.java

三、商品详情页面实现

四、购物车查询以及新增的前台

五、购物车删除功能和修改功能

删除

修改


上节内容完成了首页以及登录的功能,登录之后弹出“OK”提示,忘记实现自动跳页面了,这里先继续简单完善一下,比较简单:

 login.js:

 再次去登录成功之后就会自动跳转回主页并且绑定了用户名:

OK,进入今日主题:

一、自定义的参数解析器

在使用自定义的参数解析器之前的做法:

shopCarController :

package com.ycx.spbootpro.controller;

import com.ycx.spbootpro.exception.BusinessException;
import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.service.IRedisService;
import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 杨总
 * @create 2022-11-07 18:28
 */
@RestController
@RequestMapping("/shopCar")
public class shopCarController {
    @Autowired
    private IRedisService redisService;

    /**
     * 使用参数解析器之前的做法 弊端:
     * 在每一个需要登录之后才能操作的功能,都需要做用户登录验证,即以下代码都需要再写一遍
     * @param token
     * @return
     */
    @RequestMapping("/check")
    public JsonResponseBody check(@CookieValue("token") String token){
       if(token==null)
           throw new BusinessException(JsonResponseStatus.TOKEN_EEROR);
            User user=redisService.getUserByToken(token);
       if(user==null)
           throw new BusinessException(JsonResponseStatus.TOKEN_EEROR);
            return new JsonResponseBody();
    }

}

 弊端:
     在每一个需要登录之后才能操作的功能,都需要做用户登录验证,即以下代码都需要再写一遍

运行时,点击加入购物车

会出现关于Mybatis-plus时间字段代码生成问题

 org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `java.time.LocalDateTime` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (byte[])"{"@class":"com.zking.testspbootpro.model.User","nickname":"小胖","password":"6502cbf0ac7d357831536b119ff26d28","salt":"7ceff545c6944e5cb7da355ae6243939","registerDate":{"month":"DECEMBER","year":2021,"dayOfMonth":11,"hour":2,"minute":36,"monthValue":12,"nano":0,"second":56,"dayOfWeek":"SATURDAY","dayOfYear":345,"chronology":{"@class":"java.time.chrono.IsoChronology","id":"ISO","calendarType":"iso8601"}},"lastLoginDate":null,"loginCount":0}"; line: 1, column: 172] (through reference chain: com.zking.testspbootpro.model.User["registerDate"]); nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.time.LocalDateTime` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (byte[])"{"@class":"com.zking.testspbootpro.model.User","nickname":"小胖","password":"6502cbf0ac7d357831536b119ff26d28","salt":"7ceff545c6944e5cb7da355ae6243939","registerDate":{"month":"DECEMBER","year":2021,"dayOfMonth":11,"hour":2,"minute":36,"monthValue":12,"nano":0,"second":56,"dayOfWeek":"SATURDAY","dayOfYear":345,"chronology":{"@class":"java.time.chrono.IsoChronology","id":"ISO","calendarType":"iso8601"}},"lastLoginDate":null,"loginCount":0}"; line: 1, column: 172] (through reference chain: com.zking.testspbootpro.model.User["registerDate"])

 出现上述错误,原因是使用了lastLoginDate,去User.java类,将其改成java.util.Date;

改完之后将redis中的数据以及cookie中的数据清空,再次登录测试;

 此外我们还要在User类里面添加一个id属性,因为后面有需要涉及到:

package com.ycx.spbootpro.model;

import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import java.io.Serializable;
import java.util.Date;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 * 用户信息表
 * </p>
 *
 * @author yangzong
 * @since 2022-11-05
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_user")
public class User implements Serializable {

    private Long id;

    /**
     * 昵称
     */
    private String nickname;

    /**
     * MD5(MD5(pass明文+固定salt)+salt)
     */
    private String password;

    /**
     * 随机salt
     */
    private String salt;

    /**
     * 注册时间
     */
    private Date registerDate;

    /**
     * 最后一次登录时间
     */
    private Date lastLoginDate;

    /**
     * 登录次数
     */
    private Integer loginCount;


}

 使用自定义的参数解析器之后的做法:

shopCarController更改之后 :

package com.ycx.spbootpro.controller;

import com.ycx.spbootpro.exception.BusinessException;
import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.service.IRedisService;
import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 杨总
 * @create 2022-11-07 18:28
 */
@RestController
@RequestMapping("/shopCar")
public class shopCarController {
    @Autowired
    private IRedisService redisService;


    @RequestMapping("/check")
    public JsonResponseBody check(User user){
        return new JsonResponseBody();
    }
}

UserArgumentResovler : 

package com.ycx.spbootpro.config;

import com.ycx.spbootpro.exception.BusinessException;
import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.service.IRedisService;
import com.ycx.spbootpro.utils.CookieUtils;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;

/**
 * @author 杨总
 * @create 2022-11-07 19:14
 *
 * 凡是实现HandlerMethodArgumentResolver接口的类都是参数解析器类
 */


@Component
public class UserArgumentResovler implements HandlerMethodArgumentResolver {
        @Autowired
        private IRedisService redisService;
    /**
     * supportsParameter方法的返回值,
     * true:则会调用下面resolveArgument
     * false:则不调用
     * @param methodParameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.getParameterType() == User.class;
    }


    /**
     * resolveArgument:具体的业务代码处理
     * @param methodParameter
     * @param modelAndViewContainer
     * @param nativeWebRequest
     * @param webDataBinderFactory
     * @return
     * @throws Exception
     */
    @Override
    public Object resolveArgument(MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest,
                                  WebDataBinderFactory webDataBinderFactory) throws Exception {
        HttpServletRequest request =(HttpServletRequest) nativeWebRequest.getNativeRequest();
        String token = CookieUtils.getCookieValue(request, "token");
        if(token==null)
            throw new BusinessException(JsonResponseStatus.TOKEN_EEROR);
        User user=redisService.getUserByToken(token);
        if(user==null)
            throw new BusinessException(JsonResponseStatus.TOKEN_EEROR);
        return user;
    }
}

WebConfig :

package com.ycx.spbootpro.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * @author 杨总
 * @create 2022-11-07 19:37
 *
 * WebMvcConfigurer添加之后,会覆盖application.yml中静态资源映射
 * mvc:
 *         static-path-pattern: /static/**
 */

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private UserArgumentResovler userArgumentResovler;

    /**
     * 将对应的解析器添加到配置中
     * 配置静态资源访问映射,使用了WebMvcConfigurer会覆盖原有的application.yml文件中的静态资源配置
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
    }

    /**
     * 添加自定义的参数解析器
     * @param resolvers
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(userArgumentResovler);
    }
}


测试:

 

 然后去进行登录之后,再点击去购物车:

目前是404,因为queryShopCar页面还没有编写

 但是此时redis已经存在用户的值了(如下图):

经过测试,我们会发现,凡是controller中的方法中包含参数User,都会进参数解析器UserArgumentResovler中的resolveArgument方法;这样一定程度下可以减少用户信息登录检验;

当然,我们也可以通过拦截器、过滤器、aop等方式,来解决这一类问题。

二、购物车后台

购物车明细

 ShopCarItem :

package com.ycx.spbootpro.model.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 杨总
 * @create 2022-11-07 22:01
 *
 * 购物车明细
 */

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ShopCarItem {

    private Long gid;//商品id
    private String goodsName;//名称
    private String goodsImg;//图片
    private BigDecimal goodsPrice;//价格
    private Integer quantity;//数量

    /**
     * 这是个虚拟方法,用于计算商品的小计
     * 公式:商品的单价*数量=小计
     * @return
     */
    public BigDecimal smalltotal(){
        BigDecimal num=new BigDecimal(quantity);
        return goodsPrice.multiply(num);
    }
}

1.定义购物车对象ShopCar

1.1购物车中商品集合
定义购物车商品详情对象ShopCarItem
商品ID/商品名称/商品单价/商品图片/商品数量/小计计算方法
1.2加入购物车
1.3删除购物车中指定商品
1.4更新购物车中商品数量
1.5清空购物车
1.6总价计算

package com.ycx.spbootpro.model.vo;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;

/**
 * @author 杨总
 * @create 2022-11-07 22:00
 *
 * vo:view object
 *
 * 购物车对象
 */
public class ShopCar {
    //    1.1购物车中商品集合
    private List<ShopCarItem> items=new ArrayList<>();

    public List<ShopCarItem> getItems() {
        return items;
    }

    public void setItems(List<ShopCarItem> items) {
        this.items = items;
    }

    //    1.2加入购物车(增加)
    public void add(ShopCarItem shopCarItem) {
        //循环遍历购物车集合
        for (ShopCarItem item : items) {
            //判断加入购物车中的商品ID与购物车中的商品ID是否一致
            if (item.getGid().equals(shopCarItem.getGid()+"")) {
                //获取购物车中原有商品的数量,再进行+1
                Integer num = item.getQuantity();
                item.setQuantity(num + 1);
//              item.setQuantity(item.getQuantity()+1);
                return;
            }
        }
        //加入购物车
        items.add(shopCarItem);
    }

    //    1.3删除购物车中指定商品(删除)
    public void delete(String gids) {
        //将gids分割后转换成List集合
        List<String> ids = Arrays.asList(gids.split(","));
        //获取商品集合迭代器对象
        ListIterator<ShopCarItem> it = items.listIterator();
        //循环遍历迭代器
        while (it.hasNext()) {
            //获取迭代器元素并移动下标
            ShopCarItem shopCarItem = it.next();
            //判断购物车中的商品ID是否在被删除商品的ID集合中
            if (ids.contains(shopCarItem.getGid() + "")) {
                //删除商品
                it.remove();
            }
        }
    }

    //    1.4更新购物车中商品数量(修改)
    public void update(ShopCarItem shopCarItem) {
        //循环遍历购物车集合
        for (ShopCarItem item : items) {
            //判断加入购物车中的商品ID与购物车中的商品ID是否一致
            if (item.getGid().equals(shopCarItem.getGid())) {
                //将更新的商品数量覆盖购物车中对应商品的数量
                item.setQuantity(shopCarItem.getQuantity());
                break;
            }
        }
    }

    //    1.5清空购物车
    public void clear() {
        items.clear();
    }

    //    1.6总价计算
    public BigDecimal total() {
        BigDecimal total = new BigDecimal(0);
        for (ShopCarItem item : items) {
            total = total.add(item.smalltotal());
        }
        return total;
    }

}

  2.web层定义ShopCarController.java

1) 从session中获取购物车对象ShopCar
注:根据当前登陆用户ID绑定购物车,确保一人一车
2) 加入购物车方法
3) 查询购物车商品方法
4) 删除购物车指定商品方法
5) 更新购物车商品数量方法

package com.ycx.spbootpro.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ycx.spbootpro.exception.BusinessException;
import com.ycx.spbootpro.model.Goods;
import com.ycx.spbootpro.model.User;
import com.ycx.spbootpro.model.vo.ShopCar;
import com.ycx.spbootpro.model.vo.ShopCarItem;
import com.ycx.spbootpro.service.IGoodsService;
import com.ycx.spbootpro.service.IRedisService;
import com.ycx.spbootpro.utils.JsonResponseBody;
import com.ycx.spbootpro.utils.JsonResponseStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * @author 杨总
 * @create 2022-11-07 18:28
 */
@Controller
@RequestMapping("/shopCar")
public class shopCarController {
    @Autowired
    private IRedisService redisService;

    @ResponseBody
    @RequestMapping("/check")
    public JsonResponseBody check(User user) {
        return new JsonResponseBody();
    }

    //    从session中获取购物车对象
    private ShopCar getShopCar(User user, HttpServletRequest request) {
        HttpSession session = request.getSession();
        ShopCar shopCar = (ShopCar) session.getAttribute(user.getId() + "_shopCar");
        if (shopCar == null) {
            shopCar = new ShopCar();
            session.setAt
  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酒醉猫(^・ェ・^)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值