CGB2004-京淘項目Day20

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("定時任務執行成功!!!!");
	}
}
相關文章
相關標籤/搜索