1、状态管理
很多功能界面都需要验证登录状态(需要已登录),登录时把状态(存数据表示状态就可以,不用非要status状态码)记住
不适合AOP:面向方法名/对象角度过滤。
拦截器:面向url过滤
拦截器+session进行状态管理
去看登录方法
UserController
登录成功以后(要记住状态),获取到用户完整信息,存到session中。
后续要验证,直接在session中搜索user
退出以后要清除状态,调用session.invalidate()方法,让session自己销毁
登录后要在页面显示用户信息,显示登陆状态,提供方法从session获取用户信息,返回给页面处理
controller/Interceptor/LoginChecklnterceptor
登录检查拦截器,很多页面统一验证登录状态
- @Component受容器管理
- 实现HandlerInterceptor接口【HandlerInterceptor接口的三个方法,对应拦截器拦截的时机】
- 重写prehandle方法,返回布尔类型。参数的请求和响应,despachservelitxxx自动传入
- 从请求中获取session
- 从session中尝试获取用户。
- 若user为空没登陆,Interceptor返回false。在Interceptor1处拦截,不会执行controller
输出json代码解读未看
@Component
public class LoginCheckInterceptor implements HandlerInterceptor, ErrorCode {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(); //请求中获取session
User user = (User) session.getAttribute("loginUser");
if (user == null) {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
Map<Object, Object> data = new HashMap<>();
data.put("code", USER_NOT_LOGIN);
data.put("message", "用户未登录!");
ResponseModel model = new ResponseModel(ResponseModel.STATUS_FAILURE, data);
writer.write(JSONObject.toJSONString(model));
return false;
}
return true;
}
}
configuration/WebMvcConfiguration
具体对哪些请求拦截还不明朗,需要注册声明。所以自己写一个WebMvcConfiguration配置类。
- @Configuration配置类注解
- 实现WebMvcConfigurer接口
- 重写WebMvcConfigurer接口的addInterceptors(InterceptorRegistry registry)注册拦截器的(注册机)方法
- WebMvcConfigurer接口底层实例化了registry对象,我们只要把我们实例化的拦截器注入到这个bean下,把拦截器组件传给registry。
- 声明拦截器对哪些路径有效【可以有多个路径拦截】
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/order/create");
}
}
拦截器小结:
- 写一个LoginCheckInterceptor 实现 HandlerInterceptor接口。写方法,在合适时机作拦截处理
- 注册声明拦截哪些
2、跨域
跨域——在一个域名(页面)中,访问另一个域名/服务器的资源
一个路径的xxx.thml页面,调用了xxx.js,xxx.js中的ajax代码,访问了另一个路径
用Nginx代理
实际上没有跨域,要求前后端地址要规范,格式受限
标签
js标签,欺骗性,局限性。只能get请求
HTTP协议的跨域资源共享(CORS)
后端同意访问就行,加一段注解
@CrossOrigin(origins = "${nowcoder.web.path}", allowedHeaders = "*", allowCredentials = "true")
@CrossOrigin注解——声明服务端允许跨域
origins = "${nowcoder.web.path}"——哪些路径可以跨域。
nowcoder.web.path——application中配置的参数,值就是127.0.0.1:5500,表示这个端口可以跨域访问我
allowedHeaders = "*" ——跨域时可以读取header里的内容
allowCredentials = "true"——可以存/使用cookie
前端也加内容,表示允许带cookies
cookie&session
因为业务连续性,需要保存客户端状态,让服务端能够记住。
cookie
保存在浏览器,存json字符串,内存很小,不安全
session 保存在服务器,可存object,安全性强,但会加重服务器压力,默认半小时过期。依赖于cookie
- 首次访问首页,服务器生成session,把用户信息存【到session对象中】起来。此处为空的session对象。
- 返回【存的是sessionID】的cookie给浏览器保存
- 登录,浏览器带着cookie来,由sessionID获取到对应session对象,首次登录为空session对象,存入用户信息
- 再次访问/访问其他子页面,浏览器带着cookie来,由sessionID获取到对应session对象,读取用户信息
分布式场景
多个服务器
存到redis中的session设置过期时间
token
多客户端+分布式 场景
单点登录SSO
登陆一次可以访问多个网站
根域名相同
设置cookie的生效范围
域名不同
基于中间服务器颁发全局token验证
并且每个域名/子服务器/关联服务器,颁发局部token。
服务器间的验证,通过web.service
redirect(地址) 重定向,浏览器自动的行为
全局token失效时,会通知局部token注销
用户信息存到local.story/session.story比cookie安全
中间服务器登录以后:
- 会给客户端返回token(通过cookie实现?)
- 会要求客户端重定向到原来访问的地址
- 会给客户端创建cookie保存用户信息
每个服务器/网址背后都有对应的redis存各自的token
商品列表
用户表, 商品表:id,标题,价格,销量,主图路径,描述 商品库存表:id,库存id==映射商品id,库存值 活动表:id,name(活动名),开始介绍时间,商品id,活动价格
商品和库存分表,因为如果不分表(库存字段在商品表中),下单减库存要锁整行,后期高并发场景,要刷信息,你在读,不行。
锁库存表的行,不影响读商品表。
entity/Item、ItemStock:实体类。
- 写属性(字段),加注解给字段限制条件。
- 重写tostring方法
dao/ItemMapper、ItemStockMapper:dao层,接口文件
- 单纯定义(生成)增删改查方法【方法体内容在mapper.xml的sql文件中】
- 写增加销量方法,查询秒杀活动的商品
resources/mappers/ItemMapper.xml、ItemStockMapper.xml:mapper层,sql文件
<!-- 查询正在进行秒杀活动的商品 -->
<select id="selectOnPromotion" resultMap="BaseResultMap">
select id, title, price, sales, image_url, description
from item
where exists (
select 1 from promotion
where item_id = item.id
and start_time<=CURRENT_TIMESTAMP and end_time>=CURRENT_TIMESTAMP
)
</select>
<是小于<号
>是大于>号
service/ItemService:业务层。接口
只写要实现的业务/功能的方法。
查询所有参与秒杀活动的商品的方法,findItemsOnPromotion()
public interface ItemService {
List<Item> findItemsOnPromotion();
Item findItemById(int id);
boolean decreaseStock(int itemId, int amount);
void increaseSales(int itemId, int amount);
}
service/impl/ItemServiceImpl:业务层的实现类
写方法体内容
findItemsOnPromotion()方法,查询所有参与秒杀活动的商品,调用dao层的itemMapper的方法
但是不只要查items表,还要获取item_stock和promotion表的内容,怎么处理?
- 关联查询
- 缓存优化
- Java8流式语法
√
流式循环,循环遍历item,每次循环,由item查询库存+活动,填到item中。
-
把list转换成流,对流做处理,每次处理的是一个item的粒度。
-
获取itemId去查询库存+活动,存到item(用set方法)
-
最后把流转换成list对象
item实体类中加入了,库存、活动两引用类型的字段
public List<Item> findItemsOnPromotion() {
List<Item> items = itemMapper.selectOnPromotion();
return items.stream().map(item -> {
// 查库存
ItemStock stock = itemStockMapper.selectByItemId(item.getId());
item.setItemStock(stock);
// 查活动
Promotion promotion = promotionMapper.selectByItemId(item.getId());
if (promotion != null && promotion.getStatus() == 0) {
item.setPromotion(promotion);
}
return item;
}).collect(Collectors.toList());
}
itemController
商品详情,列表,P4的2h10min左右?
索引
用法
最左匹配原则
多个索引,是分层排序。
小结
- entity:实体类,如user
- dao:数据层,实体类相应的
接口,接口内只声明方法,userMapper - mapper(.xml):mapper层,sql(配置)文件,写接口方法的
sql语句,userMapper.xml - service:业务层,
接口,只声明业务方法,UserService - service/impl:业务层接口的实现类,写接口方法的
具体内容(方法体),UserServiceImpl
注:service/impl实现类的方法里,可调用dao层的接口