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("定時任務執行成功!!!!"); } }