创建购物车项目jt-cart
创建好了,继续老套路,把依赖都配好
pom文件
父级pom文件中加入jt-cart的module
yml文件配置直接按照jt-sso的来就行,只改端口号即可。
有时候我们建立子模块的时候出现差错了,就会删除重新建立,但是吧,会发现再次创建的项目的
src/main/java不是Sources类型的,以及resources也不是指向Resources。 当我们进行手动点击后,会发现启动类无法加载,编译错误。配置也没问题,父子依赖也有。就是pom的依赖不过来。
解决方法,
当看到有那个打√的,原来被忽视了,取消√。然后应用并保存。重新加载maven
在jt-common中添加POJO对象
@TableName("tb_cart")
@Data
@Accessors(chain = true)
public class Cart extends BasePojo{
private Long id; //购物车ID
private Long userId; //用户Id
private Long itemId; //商品Id
private String itemTitle; //商品标题
private String itemImage; //商品图片
private Long itemPrice; //商品价格
private Integer num; //商品数量
}
页面url分析
当用户点击购物车按钮时,跳转购物车展现页面
编辑Controller(jt-web)页面的实现都是在jt-web中处理的,具体的业务是在jt-cart中实现的
创建CartContrller ,属于服务消费者
@Controller
@RequestMapping("/cart")
public class CartController {
@Reference(timeout=3000)
private DubboCartService cartService;
/**
* 1.根据用户信息获取购物车列表数据
*/
@RequestMapping("/show")
public String show(Model model) {
Long userId = 7L; //暂时写死
List<Cart> cartList = cartService.findCartListByUserId(userId);
Double totalPrice=0.0;
if(CollectionUtils.isNotEmpty(cartList)){
totalPrice= cartList.stream().collect(Collectors.summingDouble(Cart::getItemPrice));
}
model.addAttribute("totalPrice",totalPrice);
model.addAttribute("cartList", cartList);
return "/cart";
}
}
在jt-common的service中创建DubboCartService ,也可称之为服务注册中心
public interface DubboCartService {
List<Cart> findCartListByUserId(Long userId);
}
在jt-cart中创建实现类,也称之为服务提供者
@Service(timeout = 3000)
public class DubboCartServiceImpl implements DubboCartService {
@Autowired
private CartMapper cartMapper;
@Override
public List<Cart> findCartListByUserId(Long userId) {
QueryWrapper<Cart> queryWrapper = new QueryWrapper<Cart>();
queryWrapper.eq("user_id", userId);
return cartMapper.selectList(queryWrapper);
}
}
注意这里的@service注解是dubbo的注解。
CartMapper:
@Mapper
public interface CartMapper extends BaseMapper<Cart>{
}
大家不可误解这只是单纯的接口和实现。那是因为注解帮你很多事
我们看一下服务消费者的注解,@Reference这也是dubbo的,是用于发现服务用的
@Reference(timeout=3000)
private DubboCartService cartService;
CartMapper.xml,注意路径
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ly.mapper.CartMapper">
</mapper>
我们看一下效果,页面可到我的GitHub中档一下。页面使用的是thymeleaf模版引擎渲染的数据。可根据自己需要进行修改。
点击数量增减按钮,看一下请求
看一下js源码
编写Controller
@PostMapping("/update/num/{itemId}/{num}")
@ResponseBody
public SysResult updateNum(@PathVariable Long itemId,@PathVariable Integer num) {
Long userId = 7L;
Cart cart = new Cart();
cart.setUserId(userId).setItemId(itemId).setNum(num);
cartService.updateNum(cart);
return SysResult.success();
}
编辑Service
@Override
public void updateNum(Cart cart) {
Cart cartTemp = new Cart();
cartTemp.setNum(cart.getNum())
.setUpdated(new Date());
UpdateWrapper<Cart> updateWrapper = new UpdateWrapper<Cart>();
updateWrapper.eq("user_id", cart.getUserId())
.eq("item_id", cart.getItemId());
cartMapper.update(cartTemp, updateWrapper);
}
当用户点击删除按钮时,应该删除购物车记录,同时重定向到购物车列表页面.
编辑CartController:
@RequestMapping("/delete/{itemId}")
public String deleteCart(Cart cart) {
Long userId = 7L;
cart.setUserId(userId);
cartService.deleteCart(cart);
return "redirect:/cart/show";
}
编辑DubboCartServiceImpl:
@Override
@Transactional
public void deleteCart(Cart cart) {
QueryWrapper<Cart> queryWrapper = new QueryWrapper<Cart>(cart);
//根据对象中不为null的属性,充当where条件
cartMapper.delete(queryWrapper);
}
回到首页,点击商品,这里我做了修改。需要重新档一下index和item页面。
(一开始我做的时候使用的jsp,故引用了伪静态技术,所以前面做的时候没注意链接的后缀名.html)
进到详情页,点击加入购物车
看一下js,这里使用了thymeleaf的获取后端值的格式
Controller
/**
* 完成购物车新增
*/
@RequestMapping("/add/{itemId}")
public String saveCart(Cart cart) {
Long userId = 7L;
cart.setUserId(userId);
cartService.insertCart(cart);
return "redirect:/cart/show";
}
编辑service:
@Override
@Transactional
public void insertCart(Cart cart) {
QueryWrapper<Cart> queryWrapper = new QueryWrapper<Cart>();
queryWrapper.eq("user_id", cart.getUserId())
.eq("item_id", cart.getItemId());
Cart cartDB = cartMapper.selectOne(queryWrapper);
if (cartDB == null) {
cart.setCreated(new Date())
.setUpdated(cart.getCreated());
cartMapper.insert(cart);
} else {
int num = cart.getNum() + cartDB.getNum();
Cart cartTemp = new Cart();
cartTemp.setNum(num)
.setUpdated(new Date());
UpdateWrapper<Cart> upWrapper = new UpdateWrapper<>();
upWrapper.eq("id", cartDB.getId());
cartMapper.update(cartTemp, upWrapper);
}
}
业务说明
当用户在没有登录的条件下,不允许访问特殊的模块例如jt-cart/jt-order.应该跳转到用户登录页面.
思路:
拦截器工作原理
说明:servlet为了基于request对象和response对象满足用户不同的业务需求,开发了拦截器机制.
说明:拦截器构成要素=拦截之后实现业务+path(路径)
拦截器拦截时期
@Component
public class UserInterceptor implements HandlerInterceptor{
@Autowired(required = false)
private JedisCluster jedisCluster;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取用户cookie信息
Cookie[] cookies = request.getCookies();
String ticket = null;
if(cookies.length > 0) {
for (Cookie cookie : cookies) {
if("JT_TICKET".equals(cookie.getName())) {
ticket = cookie.getValue();
break;
}
}
}
//判断取值是否有效 不为null时校验信息
if(!StringUtils.isEmpty(ticket)) {
String userJSON = jedisCluster.get(ticket);
if(!StringUtils.isEmpty(userJSON)) {
//方式1:该方式公司中初级程序员必会 request
//request.setAttribute("JT_USER", user);
//方式2:使用ThreadLocal实现
User user = JsonUtil.toObject(userJSON, User.class);
UserThreadLocal.set(user);
return true;
}
}
//重定向到用户登录页面
response.sendRedirect("/user/login");
return false; //表示请求拦截
}
}
配置拦截器:
@Configuration
public class MvcConfigurer implements WebMvcConfigurer{
@Autowired
private UserInterceptor userInterceptor;
//开启匹配后缀型配置
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
}
//添加拦截器配制
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userInterceptor)
.addPathPatterns("/cart/**","/order/**");
//如果有多个拦截器,可以addInterceptor多次
}
}
创建UserThreadLocal :
public class UserThreadLocal {
/**
* 1.如果需要存储单个对象 写对象类型
* 2.如果需要保存多个数据 使用Map集合
*/
private static ThreadLocal<User> thread = new ThreadLocal<>();
public static void set(User user) {
thread.set(user);
}
public static User get() {
return thread.get();
}
public static void remove() {
thread.remove();
}
}
在上面中可以看到有俩种方式存储用户登录信息,一般情况下,普遍将用户登录信息放在session中,但我们这次不放在session中。放在线程里
名称:本地线程变量
特点:线程安全的
可以实现在同一个线程内数据共享!!!
看一下引用过程
我们上面在写购物车功能时,是将用户的id写死了,我们现在将其改为动态获取
修改,自行修改其他的值吧
Long userId = UserThreadLocal.get().getId();
重启服务,进行测试吧。
本节讲解了实现购物车的功能,以及用到了拦截器判断用户是否登陆,以及使用了ThreadLocal的方式存放用户信息,这也提高了安全性!!