CGB2005-京淘20

1.实现京淘项目权限控制

1.1 需求分析

如果用户没有进行登录操作时,访问购物车/订单等敏感操作时将不允许访问,应该重定向到系统的登录页面.

知识点:
1. AOP: 对原有的方法进行扩展.在原有的基础之上提供了额外的操作. 业务控制的.Service层
2. 拦截器: 控制了程序的执行轨迹.满足条件时才会执行任务. 控制的request对象/response对象 控制用户的请求.
3. 数据传输. request对象 /ThreadLocal

1.2 关于拦截器说明

1.2.1 SpringMVC程序调用流程

在这里插入图片描述

1.2.2 拦截器工作原理

在这里插入图片描述

1.2.3 拦截器配置

@Configuration						  //配置web.xml配置文件
public class MvcConfigurer implements WebMvcConfigurer{
    
    
	
	//开启匹配后缀型配置,为了将来程序实现静态页面的跳转而准备的
	@Override
	public void configurePathMatch(PathMatchConfigurer configurer) {
    
    
		
		configurer.setUseSuffixPatternMatch(true);
	}

	//添加拦截器配置
	@Autowired
	private UserInterceptor userInterceptor;

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
    
    

		registry.addInterceptor(userInterceptor).addPathPatterns("/cart/**","/order/**");
	}
}

1.2.3 拦截器业务实现

package com.jt.interceptor;

import com.jt.pojo.User;
import com.jt.thread.UserThreadLocal;
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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//处理器拦截器
//一般在接口中定义default可以使得实现类不必重写所有的接口的方法.
@Component  //将拦截器交给Spring容器管理
public class UserInterceptor implements HandlerInterceptor {
    
    

    @Autowired
    private JedisCluster jedisCluster;

    /**
     * 返回值说明:
     *      boolean:  true   放行
     *             :  false  拦截  一般配合重定向的方式使用
     *
     * 业务说明:
     *      如果用户已经登录则程序放行.否则拦截
     * 判断依据:  1.是否有cookie 2.redis中是否有记录.
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    

        //1.动态获取JT_TICKET数据
        String ticket = CookieUtil.getCookieValue("JT_TICKET",request);
        if(!StringUtils.isEmpty(ticket)){
    
    
            //2.判断redis中是否有数据
            if(jedisCluster.exists(ticket)){
    
    
                //4.动态获取user信息.将用户信息交给request对象传递给下一级
                String userJSON = jedisCluster.get(ticket);
                User user = ObjectMapperUtil.toObject(userJSON,User.class);
                //当前的对象会携带当前的用户信息!!!!
                request.setAttribute("JT_USER",user);

                //2.利用ThreadLocal的方式动态传参
                UserThreadLocal.set(user);
                return true;    //表示放行
            }
            //3.将无效的cookie删除.
            CookieUtil.deleteCookie("JT_TICKET","/","jt.com",response);
        }

        //重定向到用户的登录页面.
        response.sendRedirect("/user/login.html");
        return false;
    }

    /**
     * 说明:利用拦截器方法清空数据
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    

        //1.删除Request对象的数据
        request.removeAttribute("JT_USER");

        //2.删除ThreadLocal中的数据
        UserThreadLocal.remove();
    }
}

1.3 ThreadLocal

1.3.1 ThreadLocal介绍

1.名词解释: 本地线程变量
2.作用: 在多线程条件下使用ThreadLocal可以实现现场内数据的共享.
在这里插入图片描述

1.3.2 ThreadLocal API

public class UserThreadLocal {
    
    

    //1.定义变量
    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();
    }
}

1.3.3 练习题

问: 在JT-WEB拦截器中使用ThreadLocal进行数据传参,
问题1:JT-WEB的Controller中能否获取数据? 可以
问题2:jt-cart的Service层中能否获取数据 不可以
因为:jt-web服务器与jt-cart的服务器的通讯是通过Dubbo框架的RPC实现的. RPC相当于开启了一个新的线程,所以无法通讯.

1.3.4 关于ThreadLocal总结

1.在同一个线程内可以使用ThreadLocal
2.“一般条件下” 在同一个tomcat内的线程为同一个线程.

2.订单项目实现

2.1 项目创建jt-order

2.1.1 创建项目

在这里插入图片描述

2.1.2 添加继承/依赖/插件

<parent>
        <artifactId>jt</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!--添加依赖项-->
    <dependencies>
        <dependency>
            <groupId>org.example</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>

2.1.3 编辑POM.xml配置文件

server:
  port: 8095
  servlet:
    context-path: /
spring:
  datasource:
    #driver-class-name: com.mysql.jdbc.Driver
    #如果需要项目发布,则数据库地址必须配置远程数据库
    url: jdbc:mysql://127.0.0.1:3306/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

#日志记录 输出数据库的日志信息.
logging:
  config: classpath:logging-config.xml
  level: 
    com.jt.mapper: debug
    
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 #每个服务都应该有自己特定的端口

2.2 订单确认页面跳转

2.2.1 页面分析

在这里插入图片描述
2).页面取值说明
在这里插入图片描述
当用户点击去结算时,应该跳转到订单确认页面 order-cart.jsp,应该展现用户的购物车相关信息.之后提交订单即可.

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.thread.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,timeout = 3000)
    private DubboCartService cartService;

    /**
     * 订单确认页面跳转
     * 1.url地址:http://www.jt.com/order/create.html
     * 2.参数说明:  暂时没有
     * 3.返回值:  order-cart.jsp页面
     * 4.页面的取值:  ${carts}
     */
    @RequestMapping("/create")
    public String findCartByUserId(Model model){
    
     //request域

        Long userId = UserThreadLocal.get().getId();
        List<Cart> cartList = cartService.findCartListByUserId(userId);
        model.addAttribute("carts",cartList);
        return "order-cart";
    }
}

2.2.3 页面效果展现

在这里插入图片描述

2.3 订单表说明

2.3.1 订单表

在这里插入图片描述

2.3.2 订单表注意事项

1.订单的ID号不是主键自增
2.订单状态信息由 1-6 注意状态说明
在这里插入图片描述

2.4 SpringMVC中参数格式说明

2.4.1 简单参数传值问题

说明:页面传参中 name属性必须与mvc中的数据保持一致.
1.页面信息

<input  type="text" name="name" value="二郎神"/>
<input  type="text" name="age" value="3500"/>

2.服务端接收

public String  saveUser(String name,Integer age){
    
    ....}

2.4.2 利用对象的方式接收参数

1.页面信息

<input  type="text" name="name" value="二郎神"/>
<input  type="text" name="age" value="3500"/>

2.服务端接收

public String  saveUser(User user){
    
    ....}

2.4.3 mvc为对象的引用赋值

问题:如何解决重名提交的问题???
方案: 可以将对象进行封装,之后采用对象引用赋值的方式实现该功能.
注意事项: 属性的名称必须一致,否则赋值失败.

1.页面信息

<input  type="text" name="name" value="二郎神"/>
<input  type="text" name="age" value="3500"/>
<input  type="text" name="dog.name" value="啸天犬"/>
<input  type="text" name="dog.age" value="5000"/>

2.服务端接收

public class User {
    
     
	private String name;
	private Integer age;
	private Dog dog;
}

public class Dog{
    
    
	private String name;
	private Integer age;
}
public String  saveUser(User user){
    
    ....}

2.4.4 订单对象定义

采用为对象引用赋值的操作. 但是该属性不属于数据库,所以需要额外的配置.
在这里插入图片描述

2.5 订单入库

2.5.1 业务说明

当用户点击提交订单时要求实现三张表同时入库业务功能,并且保证orderId的值是一致的.

1.页面URL请求
在这里插入图片描述
2.页面参数
在这里插入图片描述
3.页面JS分析

jQuery.ajax( {
    
    
			type : "POST",
			dataType : "json",
			url : "/order/submit",
			//key=value&key2=value2&....
			data : $("#orderForm").serialize(),
			cache : false,
			success : function(result) {
    
    
				if(result.status == 200){
    
    
					location.href = "/order/success.html?id="+result.data;
				}else{
    
    
					$("#submit_message").html("订单提交失败,请稍后重试...").show();
				}
			},
			error : function(error) {
    
    
				$("#submit_message").html("亲爱的用户请不要频繁点击, 请稍后重试...").show();
			}
		});

2.5.2 编辑OrderController

 /**
     * 完成订单业务提交
     * 1.url地址: /order/submit
     * 2.页面参数: 整个form表单
     * 3.返回值结果: SysResult对象(orderId). json数据
     */
    @RequestMapping("/submit")
    @ResponseBody
    public SysResult submit(Order order){
    
    
        Long userId = UserThreadLocal.get().getId();
        order.setUserId(userId);
        String orderId = orderService.saveOrder(order);
        if(StringUtils.isEmpty(orderId)){
    
    
            return SysResult.fail();
        }
        return SysResult.success(orderId);
    }

2.5.2 编辑OrderService

package com.jt.service;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.dubbo.config.annotation.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
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;

@Service
public class OrderServiceImpl implements DubboOrderService {
    
    
	
	@Autowired
	private OrderMapper orderMapper;
	@Autowired
	private OrderShippingMapper orderShippingMapper;
	@Autowired
	private OrderItemMapper orderItemMapper;


	@Override
	@Transactional	//控制事务
	public String saveOrder(Order order) {
    
    

		//1.准备订单Id号   大量的字符串拼接
		String orderId = ""+order.getUserId() + System.currentTimeMillis();

		//2.完成订单入库
		order.setOrderId(orderId).setStatus(1);
		orderMapper.insert(order);

		//3.完成订单物流入库
		OrderShipping orderShipping = order.getOrderShipping();
		orderShipping.setOrderId(orderId);
		orderShippingMapper.insert(orderShipping);

		//4.完成订单商品入库操作
		List<OrderItem> orderItemList = order.getOrderItems();
		//手动完成批量赋值的操作
		//insert into orderItem(....) values (xx,xxx,xxx),(xx,xx,xx),(xx,xx,xx);
		for (OrderItem orderItem : orderItemList){
    
    
			orderItem.setOrderId(orderId);
			orderItemMapper.insert(orderItem);
		}
		System.out.println("完成订单入库操作");

		return orderId;
	}
}

2.6 订单查询

2.6.1 业务分析

业务说明: 根据orderId号,检索订单的数据信息.要求利用Order对象将所有的数据一起返回.
在这里插入图片描述

2.6.2 编辑OrderController

/**
     * 实现订单的查询,返回order对象
     * url: http://www.jt.com/order/success.html?id=191600674802663
     * 参数: id 订单的编号
     * 返回值: success
     * 页面取值: ${order.orderId}
     */
    @RequestMapping("/success")
    public String findOrderById(String id,Model model){
    
    

        Order order = orderService.findOrderById(id);
        model.addAttribute("order",order);
        return "success";
    }

2.6.3 编辑OrderService

@Override
	public Order findOrderById(String id) {
    
    

		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);
		return order.setOrderShipping(orderShipping).setOrderItems(orderItems);
	}

2.6.4.页面效果展现

在这里插入图片描述

2.7 超时订单的处理

2.7.1 业务说明

说明:如果订单提交之后如果30分钟没有完成付款,则将订单状态改为6.表示订单交易关闭.
问题:如何实现每个订单30分钟超时呀???

思路1: 利用数据库的计时的函数每当order入库之后,可以添加一个函数30分钟之后修改状态.
该方法不友好, 100万的订单刚刚入库. 100万个监听的事件.
思路2: 利用消息队列的方式实现 redis 开启线程向redis中存储数据之后设定超时时间.当key一旦失效则修改数据库状态.
Redis主要做缓存使用. 但是不合适.
思路3:
开启单独的一个线程(异步),每隔1分钟查询一次数据库,修改超时的订单处理即可.

2.7.2 Quartz介绍

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。Quartz的最新版本为Quartz 2.3.2。
在这里插入图片描述
组件说明:
1. Job 是用户自定义的任务.
2. JobDetail 负责封装任务的工具API.如果任务需要被执行,则必须经过jobDetail封装.
3. 调度器: 负责时间监控,当任务的执行时间一到则交给触发器处理
4. 触发器: 当接收到调度器的命令则开启新的线程执行job…

2.7.3 导入jar包

 <!--添加Quartz的支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

2.7.4 编辑配置类

package com.jt.conf;

import com.jt.quartz.OrderQuartz;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrderQuartzConfig {
    
    

	/**
	 * 思想说明:
	 * 	如果需要执行定时任务需要考虑的问题
	 * 	1.任务多久执行一次.  1分钟执行一次
	 * 	2.定时任务应该执行什么
	 *
	 */
	//定义任务详情
	@Bean
	public JobDetail orderjobDetail() {
    
    
		//指定job的名称和持久化保存任务
		return JobBuilder
				.newJob(OrderQuartz.class)		//1.定义执行的任务
				.withIdentity("orderQuartz")	//2.任务指定名称
				.storeDurably()
				.build();
	}
	//定义触发器
	@Bean
	public Trigger orderTrigger() {
    
    
		/*SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule()
				.withIntervalInMinutes(1)	//定义时间周期
				.repeatForever();*/
		CronScheduleBuilder scheduleBuilder 
			= CronScheduleBuilder.cronSchedule("0 0/1 * * * ?");
		return TriggerBuilder
				.newTrigger()
				.forJob(orderjobDetail())	//执行的任务
				.withIdentity("orderQuartz")	//任务的名称
				.withSchedule(scheduleBuilder).build();
	}
}

2.7.5 编辑定时任务

package com.jt.quartz;

import java.util.Calendar;
import java.util.Date;

import com.jt.mapper.OrderMapper;
import com.jt.pojo.Order;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.dubbo.config.annotation.Reference;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;


//准备订单定时任务
@Component
public class OrderQuartz extends QuartzJobBean{
    
    

	@Autowired
	private OrderMapper orderMapper;

	/**
	 * 如果用户30分钟之内没有完成支付,则将订单的状态status由1改为6.
	 * 条件判断的依据:  now()-创建时间 > 30分钟   <==>  created < now()-30
	 *
	 * sql: update tb_order set status=6,updated=#{updated} where status=1 and created< #{timeOut}
	 * @param context
	 * @throws JobExecutionException
	 */
	@Override
	@Transactional
	protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    
    

		//1.利用java工具API完成计算
		Calendar calendar = Calendar.getInstance();  //获取当前的时间
		calendar.add(Calendar.MINUTE,-30);
		Date timeOut = calendar.getTime();	//获取超时时间

		Order order = new Order();
		order.setStatus(6);
		//order.setUpdated(new Date());
		UpdateWrapper<Order> updateWrapper = new UpdateWrapper<>();
		updateWrapper.eq("status", "1").lt("created",timeOut);
		orderMapper.update(order, updateWrapper);
		System.out.println("定时任务执行");
	}
}

猜你喜欢

转载自blog.csdn.net/qq_16804847/article/details/108702654