目录
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;
-
- /**
- *
- * 用户信息表
- *
- *
- * @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
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.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
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;
- }
-
- }
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.setAttribute(user.getId() + "_shopCar", shopCar);
- }
- return shopCar;
- }
-
- // 查询
- @RequestMapping("/queryShopCar")
- public ModelAndView queryShopCar(User user,
- HttpServletRequest request,
- HttpServletResponse resp) {
- ModelAndView mv = new ModelAndView();
- ShopCar shopCar = getShopCar(user, request);
- mv.addObject("shopCar", shopCar);
- mv.setViewName("cart.html");
- return mv;
- }
-
-
- @Autowired
- private IGoodsService goodsService;
-
- /**
- * 增加
- * @param user
- * @param request
- * @param resp
- * @param gid
- * @return
- */
- @ResponseBody
- @RequestMapping("/add")
- public JsonResponseBody add(User user,
- HttpServletRequest request,
- HttpServletResponse resp,
- Long gid) {
-
- ModelAndView mv = new ModelAndView();
- ShopCar shopCar = getShopCar(user, request);
- Goods goods = goodsService.getOne(new QueryWrapper<Goods>().eq("gid", gid));
- // 初始化商品详情ShopCarItem
- ShopCarItem item = new ShopCarItem();
- item.setQuantity(1);
- item.setGid(goods.getGid());
- item.setGoodsImg(goods.getGoodsImg());
- item.setGoodsName(goods.getGoodsName());
- item.setGoodsPrice(goods.getGoodsPrice());
- //加入购物车
- shopCar.add(item);
-
- return new JsonResponseBody<>();
- }
-
- /**
- * 修改
- * @param user
- * @param request
- * @param resp
- * @param shopCarItem
- * @return
- */
- @ResponseBody
- @RequestMapping("/update")
- public JsonResponseBody update(User user,
- HttpServletRequest request,
- HttpServletResponse resp,
- ShopCarItem shopCarItem) {
-
- ModelAndView mv = new ModelAndView();
- ShopCar shopCar = getShopCar(user, request);
- //加入购物车
- shopCar.update(shopCarItem);
- return new JsonResponseBody<>();
- }
-
-
- /**
- * 删除购物车中指定的商品
- * @param user 用户对象
- * @param gids 商品ID
- * @param request
- * @return
- */
- @RequestMapping("/delete")
- @ResponseBody
- public JsonResponseBody> delete(User user,
- HttpServletRequest request,
- HttpServletResponse resp,
- String gids){
- ModelAndView mv = new ModelAndView();
- //从session中获取购物车对象
- ShopCar shopCar = getShopCar(user, request);
- //更新商品数量
- shopCar.delete(gids);
- return new JsonResponseBody<>();
- }
-
- }
index.html修改如下:
- DOCTYPE html>
- <html>
- <head lang="en">
- <#include "common/head.html" />
- <link rel="stylesheet" type="text/css" href="css/public.css"/>
- <link rel="stylesheet" type="text/css" href="css/index.css" />
- head>
- <div>
-
- <#include "common/top.html" />
-
-
- <div class="block_home_slider">
- <div id="home_slider" class="flexslider">
- <ul class="slides">
- <li>
- <div class="slide">
- <img src="img/banner2.jpg"/>
- div>
- li>
- <li>
- <div class="slide">
- <img src="img/banner1.jpg"/>
- div>
- li>
- ul>
- div>
- div>
-
-
- <div class="thImg">
- <div class="clearfix">
- <a href="${ctx}/page/vase_proList.html"><img src="img/i1.jpg"/>a>
- <a href="${ctx}/page/proList.html"><img src="img/i2.jpg"/>a>
- <a href="#2"><img src="img/i3.jpg"/>a>
- div>
- div>
-
-
- <div class="news">
- <div class="wrapper">
- <h2><img src="img/ih1.jpg"/>h2>
- <div class="top clearfix">
- <a href="${ctx}/page/proDetail.html"><img src="img/n1.jpg"/><p>p>a>
- <a href="${ctx}/page/proDetail.html"><img src="img/n2.jpg"/><p>p>a>
- <a href="${ctx}/page/proDetail.html"><img src="img/n3.jpg"/><p>p>a>
- div>
- <div class="bott clearfix">
- <a href="${ctx}/page/proDetail.html"><img src="img/n4.jpg"/><p>p>a>
- <a href="${ctx}/page/proDetail.html"><img src="img/n5.jpg"/><p>p>a>
- <a href="${ctx}/page/proDetail.html"><img src="img/n6.jpg"/><p>p>a>
- div>
- <h2><img src="img/ih2.jpg"/>h2>
- <#if gt01?? && gt01?size gt 0>
-
- <#list gt01?keys as key>
- <div class="flower clearfix tran">
- <#list gt01[key] as g>
- <a href="${ctx}/goods/detail/${g.gid}" class="clearfix">
- <dl>
- <dt>
- <span class="abl">span>
- <img src="${g.goodsImg}"/>
- <span class="abr">span>
- dt>
- <dd>${g.goodsName}dd>
- <dd><span>¥ ${g.goodsPrice}span>dd>
- dl>
- a>
- #list>
- div>
- #list>
- #if>
-
-
- div>
- div>
-
-
- <a href="#" class="ad"><img src="img/ib1.jpg"/>a>
-
-
- <div class="people">
- <div class="wrapper">
- <h2><img src="img/ih3.jpg"/>h2>
- <#if gt07?? && gt07?size gt 0>
- <#list gt07?keys as key>
- <div class="pList clearfix tran">
- <#list gt07[key] as g>
- <a href="${ctx}/goods/detail/${g.gid}">
- <dl>
- <dt>
- <span class="abl">span>
- <img src="${g.goodsImg}"/>
- <span class="abr">span>
- dt>
- <dd>${g.goodsName}dd>
- <dd><span>¥ ${g.goodsPrice}span>dd>
- dl>
- a>
- #list>
- div>
- #list>
- #if>>
-
- div>
- div>
-
- <#include "common/footer.html"/>
-
- <script src="js/public.js" type="text/javascript" charset="utf-8">script>
- <script src="js/nav.js" type="text/javascript" charset="utf-8">script>
- <script src="js/jquery.flexslider-min.js" type="text/javascript" charset="utf-8">script>
- <script type="text/javascript">
- $(function() {
- $('#home_slider').flexslider({
- animation: 'slide',
- controlNav: true,
- directionNav: true,
- animationLoop: true,
- slideshow: true,
- slideshowSpeed:2000,
- useCSS: false
- });
-
- });
- script>
-