1 购物车订单删除
1.1页面分析
1.2 jt-web CartController
@Reference(check=false)
private DubboCatService cartService;
/**
* 删除页面 cart/delete/${cart.itemId}.html
* 请求参数:itemId/userId
* 返回值结果:重定向到系统首页
* */
@RequestMapping("/delete/{itemId}")
public String doDeleteCard(Cart cart){
Long userId = 7L;
cart.setUserId(userId);
cartService.deleteCart(cart);
return "redirect:/cart/show.html";
}
1.3 jt-cart DubboCartService
//删除购物车商品信息
@Override
public void deleteCart(Cart cart) {
cartMapper.delete(new QueryWrapper<>(cart));
}
1.4购物车权限控制
1.4.1 需求说明
当用户在没有登陆的条件下不允许访问敏感业务,购物车操作/订单操作等 使用拦截器机制
过滤器:过滤url请求,一般不做业务处理
拦截器:在服务器工作前进行拦截,或者服务器返回数据后进行拦截进行数据处理
一共三个拦截:服务器执行之前,服务器返回数据之后,用户拿到数据之前
早期在Xml进行配置,springBoot都是用配置类
1.4.2创建包结构
添加拦截器
package com.jt.interceptor;
import com.jt.util.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import redis.clients.jedis.JedisCluster;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component//将拦截器交给spring容器管理
public class UserInterceptor implements HandlerInterceptor {
private static final String JT_TICKET="JT_TICKET";
@Autowired
private JedisCluster jedisCluster;
/**
* 返回值说明:
* 1.false 表示拦截 一般都要配合重定向使用
* 2.true 表示放行
* 如何实现业务:
* 判断用户是否登陆???Cookie数据 检查redis中的数据
* 重定向到系统登陆页面
* @param request
* @param response
* @param handler
* throws Exception
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.检验Cookie中是否有结果
Cookie cookie= CookieUtil.getCookie(request, JT_TICKET);
//2.校验Cookie是否有效
if(cookie!=null){
String ticket=cookie.getValue();
if(StringUtils.hasLength(ticket)){
//校验redis中是否有结果
if(jedisCluster.exists(ticket)){
//用户存在,直接返回true
return true;
}
}
//没有结果,cookie中数据有误,需要删除cookie
CookieUtil.addCookie(response, JT_TICKET, "", "*", "jt.com", 0);
}
//3.如果数据为空,重定向到系统首页
response.sendRedirect("/user/login.html");
return false; //表示拦截
}
}
添加拦截器配置
//添加拦截器配置
@Autowired
private UserInterceptor userInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userInterceptor)
.addPathPatterns("/cart/**","/order/**");
}
1.5 动态获取userId
在拦截其中获取用户信息
package com.jt.interceptor;
import com.jt.pojo.User;
import com.jt.util.CookieUtil;
import com.jt.util.ObjectMapperUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import redis.clients.jedis.JedisCluster;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component//将拦截器交给spring容器管理
public class UserInterceptor implements HandlerInterceptor {
private static final String JT_TICKET="JT_TICKET";
@Autowired
private JedisCluster jedisCluster;
/**
* 返回值说明:
* 1.false 表示拦截 一般都要配合重定向使用
* 2.true 表示放行
* 如何实现业务:
* 判断用户是否登陆???Cookie数据 检查redis中的数据
* 重定向到系统登陆页面
* @param request
* @param response
* @param handler
* throws Exception
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.检验Cookie中是否有结果
Cookie cookie= CookieUtil.getCookie(request, JT_TICKET);
//2.校验Cookie是否有效
if(cookie!=null){
String ticket=cookie.getValue();
if(StringUtils.hasLength(ticket)){
//校验redis中是否有结果
if(jedisCluster.exists(ticket)){
//获取user信息
String json=jedisCluster.get(ticket);
User user= ObjectMapperUtil.toObject(json,User.class);
//获取到的对象如何传输?????request
request.setAttribute("JT_USER", user);
//用户存在,直接返回true
return true;
}
}
//没有结果,cookie中数据有误,需要删除cookie
CookieUtil.addCookie(response, JT_TICKET, "", "*", "jt.com", 0);
}
//3.如果数据为空,重定向到系统首页
response.sendRedirect("/user/login.html");
return false; //表示拦截
}
//为了防止内存泄漏,将多余数据删除
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
request.removeAttribute("JT_USER");
}
}
2 ThreadLocal介绍
本地线程变量
2.1工作原理
作用: 在线程内部(一个线程)实现数据的共享
request 对象只有在@controller表示的类中才能获取
threadLocal没有线程安全问题 作用 : 在线程内部实现数据共享
2.2工具API编辑
jt-web中
package com.jt.util;
import com.jt.pojo.User;
public class UserThreadLocal {
private static ThreadLocal<User> threadLocal=new ThreadLocal<User>();
public static void setUser(User user){
threadLocal.set(user);
}
public static User getUser(){
return threadLocal.get();
}
public static void remove(){
threadLocal.remove();
}
}
微服务中Dubbo开启另一个线程执行任务
思考题: JT-WEB中ThreadLocal.set(xxx),问 在jt-cart中的service能否执行ThreadLocal.get()???
A. 不能
由于内存的泄漏,所以对加入ThreadLocal中数据要进行删除
重构拦截器工具API
package com.jt.interceptor;
import com.jt.pojo.User;
import com.jt.util.CookieUtil;
import com.jt.util.ObjectMapperUtil;
import com.jt.util.UserThreadLocal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import redis.clients.jedis.JedisCluster;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component//将拦截器交给spring容器管理
public class UserInterceptor implements HandlerInterceptor {
private static final String JT_TICKET="JT_TICKET";
@Autowired
private JedisCluster jedisCluster;
/**
* 返回值说明:
* 1.false 表示拦截 一般都要配合重定向使用
* 2.true 表示放行
* 如何实现业务:
* 判断用户是否登陆???Cookie数据 检查redis中的数据
* 重定向到系统登陆页面
* @param request
* @param response
* @param handler
* throws Exception
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.检验Cookie中是否有结果
Cookie cookie= CookieUtil.getCookie(request, JT_TICKET);
//2.校验Cookie是否有效
if(cookie!=null){
String ticket=cookie.getValue();
if(StringUtils.hasLength(ticket)){
//校验redis中是否有结果
if(jedisCluster.exists(ticket)){
//获取user信息
String json=jedisCluster.get(ticket);
User user= ObjectMapperUtil.toObject(json,User.class);
//获取到的对象如何传输?????request
request.setAttribute("JT_USER", user);
UserThreadLocal.setUser(user);
//用户存在,直接返回true
return true;
}
}
//没有结果,cookie中数据有误,需要删除cookie
CookieUtil.addCookie(response, JT_TICKET, "", "*", "jt.com", 0);
}
//3.如果数据为空,重定向到系统首页
response.sendRedirect("/user/login.html");
return false; //表示拦截
}
//为了防止内存泄漏,将多余数据删除
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除request对象
request.removeAttribute("JT_USER");
//移除threadLocal数据
UserThreadLocal.remove();
}
}
对购物车所有功能中的user进行动态获取
package com.jt.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.pojo.Cart;
import com.jt.pojo.User;
import com.jt.service.DubboCatService;
import com.jt.util.UserThreadLocal;
import com.jt.vo.SysResult;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@Controller
@RequestMapping("/cart")
public class CartController {
@Reference(check=false)
private DubboCatService cartService;
/**
* 删除页面 cart/delete/${cart.itemId}.html
* 请求参数:itemId/userId
* 返回值结果:重定向到系统首页
* */
@RequestMapping("/delete/{itemId}")
public String doDeleteCard(Cart cart){
Long userId= UserThreadLocal.getUser().getId();
cart.setUserId(userId);
cartService.deleteCart(cart);
return "redirect:/cart/show.html";
}
/**
*
* */
@RequestMapping("/add/{itemId}")
public String saveCart(Cart cart){
Long userId= UserThreadLocal.getUser().getId();
cart.setUserId(userId);
cartService.saveCart(cart);
return "redirect:/cart/show.html";
}
/**
* 购物车商品数量更新操作
* 业务需求: 实现商品数量的更新操作
* url:http://www.jt.com/cart/update/num/1474391990/10
* 参数: userId=7/itemId/num
* 返回值: void
*
* 用法: 如果restFul参数名称与对象的属性名称一致,则可以使用对象的方式接收
* */
@RequestMapping("/update/num/{itemId}/{num}")
@ResponseBody//1.将数据用json返回 2.表示ajax请求结束
public void doupdateCartNum(Cart cart){
Long userId= UserThreadLocal.getUser().getId();
cart.setUserId(userId);
cartService.updateCartNum(cart);
}
/**
* 展现购物车列表信息
* http://www.jt.com/cart/show.html
* 参数: 必须获取userId=7L
* 返回值:cart.jsp
* 页面取值参数${cartList}
* */
@RequestMapping("/show")
public String doFindCart(Model model, HttpServletRequest request){
// User user=(User)request.getAttribute("JT_USER");
Long userId= UserThreadLocal.getUser().getId(); //暂时写死,后期维护
List<Cart> cartList=cartService.findCartList(userId);
model.addAttribute("cartList", cartList);
return "cart";
}
}
3 商品订单的实现
3.1订单确认页面的跳转
3.1.1 需求的分析:
页面跳转: order-cart.jsp页面,需要展现商品数据信息,同时有收件人地址信息.
3.1.2jt-web新建OrderController
package com.jt.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.pojo.Cart;
import com.jt.service.DubboCatService;
import com.jt.util.UserThreadLocal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("/order")
public class OrderController {
@Reference(check = false)
private DubboCatService cartsrvice;
/**
* 1.实现订单确认页面跳转
* url地址: http://www.jt.com/order/create.html
* 参数说明: 获取userId
* 返回值结果: 订单确认页面 order-cart.jsp
* 页面取值方式: ${carts}
*/
@RequestMapping("/create")
public String doFindOrderCart(Model model){
long userId= UserThreadLocal.getUser().getId();;
List<Cart> cartList=cartsrvice.findCartList(userId);
model.addAttribute("carts", cartList);
return "order-cart";
}
}
测试结果
3.2 创建订单项目
3.2.1订单模块表设计
3.2.2在jt-common中导入pojo对象
package com.jt.pojo;
import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@TableName("tb_order")
@Data
@Accessors(chain=true)
public class Order extends BasePojo{
@TableField(exist=false) //入库操作忽略该字段
private OrderShipping orderShipping;
//封装订单商品信息 一对多
@TableField(exist=false) //入库操作忽略该字段
private List<OrderItem> orderItems;
@TableId
private String orderId;
private String payment;
private Integer paymentType;
private String postFee;
private Integer status;
private Date paymentTime;
private Date consignTime;
private Date endTime;
private Date closeTime;
private String shippingName;
private String shippingCode;
private Long userId;
private String buyerMessage;
private String buyerNick;
private Integer buyerRate;
}
package com.jt.pojo;
import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@TableName("tb_order_item")
@Data
@Accessors(chain=true)
public class OrderItem extends BasePojo{
private String itemId;
private String orderId;
private Integer num;
private String title;
private Long price;
private Long totalFee;
private String picPath;
}
package com.jt.pojo;
import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@TableName("tb_order_shipping")
@Data
@Accessors(chain=true)
public class OrderShipping extends BasePojo{
@TableId
private String orderId;
private String receiverName;
private String receiverPhone;
private String receiverMobile;
private String receiverState;
private String receiverCity;
private String receiverDistrict;
private String receiverAddress;
private String receiverZip;
}
3.3定义服务提供者jt-order
server:
port: 8095
servlet:
context-path: /
spring:
datasource:
#引入druid数据源
#type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3307/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
#url: jdbc:mysql://192.168.126.129:8066/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
#mybatis-plush配置
mybatis-plus:
type-aliases-package: com.jt.pojo
mapper-locations: classpath:/mybatis/mappers/*.xml
configuration:
map-underscore-to-camel-case: true
#关于Dubbo配置
dubbo:
scan:
basePackages: com.jt #指定dubbo的包路径
application: #应用名称
name: provider-order #一个接口对应一个服务名称
registry:
address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
protocol: #指定协议
name: dubbo #使用dubbo协议(tcp-ip) web-controller直接调用sso-Service
port: 20883 #每一个服务都有自己特定的端口 不能重复.
logging:
level:
com.jt.mapper: debug
<!--添加依赖-->
<dependencies>
<dependency>
<groupId>com.jt</groupId>
<artifactId>jt-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.4SpringMVC参数传递说明
3.4.1简单参数赋值
<input type="text" name="name" value="二郎神">
<input type="text" name="age" value="18">
public void doFindUser(String name,Integer age){
}
3.4.2对象赋值
<input type="text" name="name" value="二郎神">
<input type="text" name="age" value="18">
<input type="text" name="sex" value="男">
public void doFindUser(User user){
}
3.4.3对象引用赋值
<input type="text" name="name" value="二郎神">
<input type="text" name="age" value="18">
<input type="text" name="name" value="哮天犬">
<input type="text" name="age" value="15">
问题:如果页面中有重名属性
解决方案:利用对象的引用封装
class Dog{
private String name;
private Integer age;
}
class User{
private String name;
private Integer age;
private Dog dog;
}
<input type="text" name="name" value="二郎神">
<input type="text" name="age" value="18">
<input type="text" name="dog.name" value="哮天犬">
<input type="text" name="dog.age" value="15">
public void doGetUser(User user){
}
给list集合传递参数:
class Dog{
private String name;
private Integer age;
}
class User{
private String name;
private Integer age;
private Dog dog;
private List<String> hobbys;
}
<input type="text" name="name" value="二郎神">
<input type="text" name="age" value="18">
<input type="text" name="dog.name" value="哮天犬">
<input type="text" name="dog.age" value="15">
<input type="text" name="hobbys[0]" value="敲代码">
<input type="text" name="hobbys[1]" value="学java">
3.5订单入库的实现
3.5.1 业务分析
1)url
2).页面参数分析
3).页面结构分析
4).POJO对象赋值
5).页面JS分析
表单数据序列化:
OrderController
@Reference(check = false)
private DubboOrderService orderService;
@RequestMapping("/submit")
@ResponseBody
public SysResult doSubmit(Order order){
long userId=UserThreadLocal.getUser().getId();
order.setUserId(userId);
String orderId=orderService.saveOrder(order);
if(StringUtils.hasLength(orderId))
return SysResult.success(orderId);
return SysResult.fail();
}
DubboOrderServiceImpl
package com.jt.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.jt.mapper.OrderItemMapper;
import com.jt.mapper.OrderMapper;
import com.jt.mapper.OrderShippingMapper;
import com.jt.pojo.Order;
import com.jt.pojo.OrderItem;
import com.jt.pojo.OrderShipping;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@Service(timeout=3000)
public class DubboOrderServiceImpl implements DubboOrderService{
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private OrderShippingMapper orderShippingMapper;
@Override
@Transactional
public String saveOrder(Order order) {
//字符串拼接
String orderId=order.getUserId()+""+System.currentTimeMillis();
//1.完成订单入库操作
order.setOrderId(orderId).setStatus(1);//未付款状态
orderMapper.insert(order);
//2.订单物流入库
OrderShipping orderShipping=order.getOrderShipping();
orderShipping.setOrderId(orderId);
orderShippingMapper.insert(orderShipping);
//3.订单商品的入库
List<OrderItem> lists=order.getOrderItems();
for (OrderItem orderItem : lists) {
orderItem.setOrderId(orderId);
orderItemMapper.insert(orderItem);
}
return orderId;
}
}
3.6订单的查询
3.6.1页面分析
编辑OrderController
/**
* 实现订单页面的查询
* http://www.jt.com/order/success.html?id=71608193982133
*页面取值:order对象及封装的两个属性
* */
@RequestMapping("/success")
public String doSuccess(@RequestParam("id") String orderId,Model model){//将id赋值给orderId
Order order=orderService.findOrderById(orderId);
model.addAttribute("order",order);
return "success";
}
编辑OrderService
@Override
public Order findOrderById(String orderId) {
//查询订单商品信息
QueryWrapper<OrderItem> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("order_id", orderId);
List<OrderItem> orderItemLists=orderItemMapper.selectList(queryWrapper);
//查询物流信息
OrderShipping orderShipping=orderShippingMapper.selectById(orderId);
//查询订单信息
Order order=orderMapper.selectById(orderId);
//数据包装
order.setOrderItems(orderItemLists).setOrderShipping(orderShipping);
return order;
}
测试效果:
项目的梳理: