当用户在不登录的条件下,不允许访问购物车/订单等受限的系统.并且重定向到用户的登录页面.
问题:
1.如何校验用户是否登录? Cookie /Redis
2.如何拦截用户的请求呢? 拦截器设定.
说明:通过图中的分析 handler处理器负责Controller之后的所有的业务处理.
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/**");
}
}
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.检查是否用到Request/Response对象,如果需要使用则建议使用拦截器.
2.看具体业务功能. 具体业务具体分析.
当用户登录之后,点击购物车/订单模块时需要动态的获取userID.如何实现???
实现方式:
1).基于Request对象的方式实现数据传参
2).基于线程实现数据传参
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; //表示拦截
}
}
说明:如果利用Request对象的方式进行数据的传参,一般只能在Controller中进行动态的数据接收.
如果在业务执行过程中需要该数据,则通过参数的形式继续向下传递.导致接口方法中的参数个数较多.
虽然该写法没有任何的问题. 该操作是否可以优化???
名字: 本地线程变量
作用: 在当前线程内,实现数据的共享.
package com.jt.util;
import com.jt.pojo.User;
public class UserThreadLocal {
//1.定义本地线程变量!!!!!
private static ThreadLocal<User> threadLocal = new ThreadLocal<>();
//ThreadLocal
//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).将数据保存到ThreadLocal中
2).动态获取数据
问题: 利用Dubbo框架从Controller能否向Service利用ThreadLocal传递数据???
答案: 不可以
原因: ThreadLocal只适用与单个项目内使用.不适合多系统调用.
<!--添加依赖-->
<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>
1.当在购物车中点击去结算操作时应该跳转到订单的确认页面. order-cart.jsp
2.应该展现用户的全部的购物车信息. ${carts}
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";
}
}
1).html标签
<html>
<input type="text" name="name" />
<input type="text" name="age" />
</html>
2).Controller中的方法
public xxxx saveUser(String name,int age){
}
1).html标签
<html>
<input type="text" name="name" />
<input type="text" name="age" />
</html>
2).Controller中的方法
public xxxx saveUser(User user){ //名称需要和属性一致...
}
目的:该方法可以有效的解决重名参数提交的问题. 为对象的引用赋值.
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;
}
<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="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>
说明:利用Order对象封装了其他2张表的数据.,所以参数使用Order对象封装即可.
1).post提交
/**
* 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);
}
@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;
}
说明:根据orderId查询订单数据.,之后在success页面中展现数据
/**
* 实现订单的查询 根据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";
}
@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;
}