目录
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