1.京淘权限设计
1.1 业务说明
当用户在不登陆的条件下,不容许访问购物车/订单等受限的系统.而且重定向到用户的登陆页面.
问题:
1.如何校验用户是否登陆? Cookie /Redis
2.如何拦截用户的请求呢? 拦截器设定.
html
1.2 拦截器实现用户权限校验
1.2.1 SpringMVC调用原理图
说明:经过图中的分析 handler处理器负责Controller以后的全部的业务处理.
java
1.2.2 mvc拦截器执行的示意图
1.2.3编辑拦截器配置文件
package com.jt.config; import com.jt.handler.UserInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class MvcConfigurer implements WebMvcConfigurer{ //web项目中的web.xml配置文件 @Autowired private UserInterceptor userInterceptor; //开启匹配后缀型配置 xxxx.html xxxx.do xxxxx.action @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(true); } //添加拦截器配置 @Override public void addInterceptors(InterceptorRegistry registry) { //暂时只拦截购物车/订单模块的请求 /* 只拦截一级请求路径 /** 拦截多级请求路径 registry.addInterceptor(userInterceptor) .addPathPatterns("/cart/**","/order/**"); } }
1.2.4 定义拦截器
package com.jt.handler; 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.web.servlet.HandlerInterceptor; import redis.clients.jedis.JedisCluster; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; //拦截器的类(业务) 拦截器的配置文件(拦截什么请求) @Component public class UserInterceptor implements HandlerInterceptor { private static final String TICKET = "JT_TICKET"; private static final String JTUSER = "JT_USER"; @Autowired private JedisCluster jedisCluster; /** * 实现pre的方法 * 返回值说明: * return false 表示拦截 须要配合重定向一齐使用 * return ture 表示放行 * 需求1: 若是用户没有登陆,则重定向到系统登陆页面 * 判断条件: 如何判断用户是否登陆. 1.检查Cookie中是否有记录 2.Redis中是否有记录. */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Cookie cookie = CookieUtil.getCookieByName(request, TICKET); if(cookie != null){ //不为null.则表示用户可能登陆. String ticket = cookie.getValue(); //cookie中存储的是Redis的key ticket密钥 if(jedisCluster.exists(ticket)){ return true; }else{ //Cookie中的记录与Redis中的记录不一致.应该删除Cookie中的数据. CookieUtil.deleteCookie(TICKET, "/", "jt.com",response); } } response.sendRedirect("/user/login.html"); return false; //表示拦截 } }
1.2.5 拦截器与AOP使用场景说明
1.检查是否用到Request/Response对象,若是须要使用则建议使用拦截器.
2.看具体业务功能. 具体业务具体分析.
git
1.3 动态获取当前用户ID
1.3.1 业务描述
当用户登陆以后,点击购物车/订单模块时须要动态的获取userID.如何实现???
实现方式:
1).基于Request对象的方式实现数据传参
2).基于线程实现数据传参
web
1.3.2 利用Request实现数据传参
1.3.2.1 编辑拦截器
package com.jt.handler; 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.web.servlet.HandlerInterceptor; import redis.clients.jedis.JedisCluster; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; //拦截器的类(业务) 拦截器的配置文件(拦截什么请求) @Component public class UserInterceptor implements HandlerInterceptor { private static final String TICKET = "JT_TICKET"; private static final String JTUSER = "JT_USER"; @Autowired private JedisCluster jedisCluster; /** * 实现pre的方法 * 返回值说明: * return false 表示拦截 须要配合重定向一齐使用 * return ture 表示放行 * 需求1: 若是用户没有登陆,则重定向到系统登陆页面 * 判断条件: 如何判断用户是否登陆. 1.检查Cookie中是否有记录 2.Redis中是否有记录. */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Cookie cookie = CookieUtil.getCookieByName(request, TICKET); if(cookie != null){ //不为null.则表示用户可能登陆. String ticket = cookie.getValue(); //cookie中存储的是Redis的key ticket密钥 if(jedisCluster.exists(ticket)){ //若是redis中的数据存在,则说明用户已经登陆.能够放行请求. //获取真实的用户信息 String userJSON = jedisCluster.get(ticket); //将json转化为对象 User user = ObjectMapperUtil.toObject(userJSON, User.class); request.setAttribute(JTUSER, user); return true; }else{ //Cookie中的记录与Redis中的记录不一致.应该删除Cookie中的数据. CookieUtil.deleteCookie(TICKET, "/", "jt.com",response); } } response.sendRedirect("/user/login.html"); return false; //表示拦截 } }
1.3.2.2 编辑CartController
1.4 ThreadLocal介绍
1.4.1 业务说明
说明:若是利用Request对象的方式进行数据的传参,通常只能在Controller中进行动态的数据接收.
若是在业务执行过程当中须要该数据,则经过参数的形式继续向下传递.致使接口方法中的参数个数较多.
虽然该写法没有任何的问题. 该操做是否能够优化???
redis
1.4.2ThreadLocal说明
名字: 本地线程变量
做用: 在当前线程内,实现数据的共享.
spring
1.4.3 ThreadLocal工具API
package com.jt.util; import com.jt.pojo.User; public class UserThreadLocal { //1.定义本地线程变量!!!!! private static ThreadLocal<User> threadLocal = new ThreadLocal<>(); //ThreadLocal<Map> //2.定义数据新增的方法 public static void set(User user){ threadLocal.set(user); } //3.获取数据 public static User get(){ return threadLocal.get(); } //4.移除方法 使用threadLocal时切记将数据移除.不然极端条件下,容易产出内存泄露的问题 public static void remove(){ threadLocal.remove(); } //实现数据的移除 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserThreadLocal.remove(); } }
1.4.4 实现用户数据获取
1).将数据保存到ThreadLocal中
2).动态获取数据
json
1.4.5 关于Dubbo中的ThreadLocal说明
问题: 利用Dubbo框架从Controller可否向Service利用ThreadLocal传递数据???
答案: 不能够
缘由: ThreadLocal只适用与单个项目内使用.不适合多系统调用.
cookie
2.京淘订单业务实现
2.1 订单项目建立
2.1.1构建项目
2.1.2 添加继承/依赖/插件
<!--添加依赖--> <dependencies> <dependency> <groupId>com.jt</groupId> <artifactId>jt-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> <!-- maven项目指定的插件配置 该插件主要负责 maven项目相关操做 打包/test/clean/update等相关maven操做 注意事项:但凡是maven项目则必须添加 插件.不然未来项目部署必然出错 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
2.1.3 编辑POJO对象
说明:将课前资料中的pojo文件,导入项目
mvc
2.1.4 编辑OrderService接口
2.1.5 订单提供者建立
2.2 订单确认页面跳转
2.2.1 业务需求
1.当在购物车中点击去结算操做时应该跳转到订单的确认页面. order-cart.jsp
2.应该展示用户的所有的购物车信息. ${carts}
app
2.2.2 编辑OrderController
package com.jt.controller; import com.alibaba.dubbo.config.annotation.Reference; import com.jt.pojo.Cart; import com.jt.service.DubboCartService; 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; //1.业务名称与Controller对应便可 @Controller @RequestMapping("/order") public class OrderController { //2.添加接口 @Reference private DubboCartService cartService; /** * 跳转订单确认页面 * 1.url:http://www.jt.com/order/create.html * 2.参数: 没有参数 null * 3.返回值: order-cart * 4.页面取值 {carts} 须要查询购物车信息.,给页面返回 */ @RequestMapping("create") public String create(Model model){ Long userId = UserThreadLocal.get().getId(); List<Cart> cartList = cartService.findCartList(userId); model.addAttribute("carts",cartList); return "order-cart"; } }
2.2.3 页面效果展示
2.3 关于SpringMVC中页面赋值说明
2.3.1 简单参数传递
1).html标签
<html> <input type="text" name="name" /> <input type="text" name="age" /> </html>
2).Controller中的方法
public xxxx saveUser(String name,int age){ }
2.3.2 利用对象的方式接收参数
1).html标签
<html> <input type="text" name="name" /> <input type="text" name="age" /> </html>
2).Controller中的方法
public xxxx saveUser(User user){ //名称须要和属性一致... }
2.3.3 为对象的引用赋值
目的:该方法能够有效的解决重名参数提交的问题. 为对象的引用赋值.
1).html标签
<html> <input type="text" name="name" value="二郎神"/> <input type="text" name="age" value="2000"/> <input type="text" name="dog.name" value="哮天犬"/> <input type="text" name="dog.age" value="9000"/> </html>
2).Controller中的方法
public xxxx saveUser(User user){ //名称须要和属性一致... } public class User{ private Dog dog; //为对象添加一个引用 private String name; private Integer age; } public class Dog{ private String name; private Integer age; }
2.4 完成订单入库
2.4.1 页面表单说明
<form id="orderForm" class="hide"> <input type="hidden" name="paymentType" value="1"/> <c:forEach items="${carts}" var="cart" varStatus="status"> <c:set var="totalPrice" value="${ totalPrice + (cart.itemPrice * cart.num)}"/> <input type="hidden" name="orderItems[${status.index}].itemId" value="${cart.itemId}"/> <input type="hidden" name="orderItems[${status.index}].num" value="${cart.num }"/> <input type="hidden" name="orderItems[${status.index}].price" value="${cart.itemPrice}"/> <input type="hidden" name="orderItems[${status.index}].totalFee" value="${cart.itemPrice * cart.num}"/> <input type="hidden" name="orderItems[${status.index}].title" value="${cart.itemTitle}"/> <input type="hidden" name="orderItems[${status.index}].picPath" value="${cart.itemImage}"/> </c:forEach> <input type="hidden" name="payment" value="<fmt:formatNumber groupingUsed="false" maxFractionDigits="2" minFractionDigits="2" value="${totalPrice/100 }"/>"/> <input type="hidden" name="orderShipping.receiverName" value="陈晨"/> <input type="hidden" name="orderShipping.receiverMobile" value="13800807944"/> <input type="hidden" name="orderShipping.receiverState" value="北京"/> <input type="hidden" name="orderShipping.receiverCity" value="北京"/> <input type="hidden" name="orderShipping.receiverDistrict" value="海淀区"/> <input type="hidden" name="orderShipping.receiverAddress" value="清华大学"/> </form>
2.4.2 POJO说明
说明:利用Order对象封装了其余2张表的数据.,因此参数使用Order对象封装便可.
2.4.3 页面URL分析
1).post提交
2).参数提交
3).返回值结果
2.4.4 编辑OrderController
/** * 1.实现订单入库 * url:http://www.jt.com/order/submit * 参数: 整个form表单 利用order对象接收 * 返回值: SysResult对象 返回orderId * 业务: 订单入库时应该入库3张表记录. order orderShipping orderItems * orderId由登陆用户id+当前时间戳手动 拼接. * 而且要求三个对象的主键值相同. */ @RequestMapping("/submit") @ResponseBody public SysResult submit(Order order){ //利用拦截器的方式赋值. Long userId = UserThreadLocal.get().getId(); order.setUserId(userId); //1.完成订单入库,而且返回orderId String orderId = orderService.saveOrder(order); return SysResult.success(orderId); }
2.4.5 编辑OrderService
@Transactional @Override public String saveOrder(Order order) { //orderId由登陆用户id+当前时间戳手动拼接. String orderId = "" + order.getUserId() + System.currentTimeMillis(); //1.完成订单入库操做 order.setOrderId(orderId).setStatus(1); orderMapper.insert(order); System.out.println("订单入库成功!!!"); //2.完成订单商品入库 List<OrderItem> orderItems = order.getOrderItems(); //insert into tb_order_item values(xxxxxx),(xxxxxxx),(xxxxxxx); for (OrderItem orderItem : orderItems){ orderItem.setOrderId(orderId); orderItemMapper.insert(orderItem); } System.out.println("订单商品入库成功!!!!"); //3.完成订单物流入库 OrderShipping orderShipping = order.getOrderShipping(); orderShipping.setOrderId(orderId); orderShippingMapper.insert(orderShipping); System.out.println("订单物流入库成功!!!!"); return orderId; }
2.5 完成订单查询
2.5.1 业务分析
说明:根据orderId查询订单数据.,以后在success页面中展示数据
2.5.2 编辑OrderController
/** * 实现订单的查询 根据orderId * url地址: http://www.jt.com/order/success.html?id=71598258019985 * 参数: id=71598258019985 * 返回值: success页面 * 页面参数: ${order.orderId} */ @RequestMapping("/success") public String findOrderById(String id,Model model){ Order order = orderService.findOrderById(id); model.addAttribute("order",order); return "success"; }
2.5.3 编辑OrderService
@Override public Order findOrderById(String id) { //查询order信息 Order order = orderMapper.selectById(id); //查询订单物流 OrderShipping orderShipping = orderShippingMapper.selectById(id); //查询订单商品 QueryWrapper<OrderItem> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("order_id", id); List<OrderItem> orderItems = orderItemMapper.selectList(queryWrapper); //为订单模块赋值 order.setOrderItems(orderItems).setOrderShipping(orderShipping); return order; }
2.5 订单超时实现状态修改
2.5.1业务说明
说明:当订单入库以后,若是30分钟用户没有完成付款操做,则将订单的状态信息由1未付款改成6交易关闭.
如何实现: 单独开启一个线程,每隔1分钟查询一次是否有超时订单.
2.5.2Quartz框架说明
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它能够与J2EE与J2SE应用程序相结合也能够单独使用。Quartz能够用来建立简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs能够作成标准的Java组件或 EJBs。Quartz的最新版本为Quartz 2.3.2。
组件说明:
1.Job 用户自定义的任务.
2.JobDetail 将用户封装以后的结果.
3.调度器 负责任务的协调服务.
4.触发器 当接收调度器的指令后,开启线程执行任务.
2.5.3 引入jar包文件
<!--添加Quartz的支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
2.5.4 关于配置类说明
@Configuration public class OrderQuartzConfig { //定义任务详情 @Bean public JobDetail orderjobDetail() { //指定job的名称和持久化保存任务 return JobBuilder .newJob(OrderQuartz.class) //引入自定义的任务 .withIdentity("orderQuartz")//指定任务名称 .storeDurably() .build(); } //定义触发器 @Bean public Trigger orderTrigger() { /*SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInMinutes(1) //定义时间周期 .repeatForever();*/ //经过调度器,指定程序多久执行一次. //0 0/1 * * * ? 时间表达式 规定任务多久执行一次 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0 0/1 * * * ?"); return TriggerBuilder .newTrigger() .forJob(orderjobDetail()) .withIdentity("orderQuartz") .withSchedule(scheduleBuilder).build(); } }
2.5.5 执行定时任务
//准备订单定时任务 @Component public class OrderQuartz extends QuartzJobBean{ @Autowired private OrderMapper orderMapper; /** * 当规定的执行时间一到,触发器就会开启线程,执行指定的任务. * 业务需求: * 要求将超时订单关闭. 要求30分钟 status 由1改成6 * 如何定义超时: * now() - created > 30分钟 订单超时 * created < now -30 * Sql: * update tb_order set status = 6,updated = #{date} * where created < (now -30) and status = 1; * */ @Override @Transactional protected void executeInternal(JobExecutionContext context) throws JobExecutionException { //对时间进行计算 Calendar calendar = Calendar.getInstance(); //实例化对象 获取当前时间 calendar.add(Calendar.MINUTE, -30); Date timeOut = calendar.getTime(); Order entity = new Order(); entity.setStatus(6).setUpdated(new Date()); UpdateWrapper<Order> updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("status", 1) .lt("created",timeOut); orderMapper.update(entity, updateWrapper); System.out.println("定时任务执行成功!!!!"); } }