提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
【项目实战】秒杀场景超高含金量项目,3000 QPS 高并发秒杀系统实现
视频链接见文章底部
一、项目导入
1. 导入代码
2. 阅读代码
前后端分离的跨域问题?(CORS)
比如80端口 请求9000端口
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
CorsConfiguration config = new CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedOrigin("*");
// #允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(18000L);
// 允许提交请求的方法,*表示全部允许
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
跨域 --> 客户端需要询问服务端是否允许跨域
在发起api请求前,会构造一个预请求。疫预请求的方法是options类型
且请求参数,请求体都不存在。预请求的作用是请求到服务端,如果服务端允许跨域,就会通过预请求携带对应的响应头信息回来。
如果浏览器检测到响应头中有允许当前页面跨域,就会发起真实请求
cors:是一套机制,用于浏览器校验跨域请求。(同源策略)
基本理念:只要服务器明确表示允许则,校验通过。明确拒绝或没有表示,百不通过
微服务中如何获得用户真实IP?
用户---->网关---->微服务
网关可以拿到用户ip!
public class CommonFilter implements GlobalFilter {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/**
* pre拦截逻辑
* 在请求去到微服务之前,做了两个处理
* 1.把客户端真实IP通过请求同的方式传递给微服务
* 2.在请求头中添加FEIGN_REQUEST的请求头,值为0,标记请求不是Feign调用,而是客户端调用
*/
ServerHttpRequest request =exchange.getRequest().mutate().
header(CommonConstants.REAL_IP,exchange.getRequest().getRemoteAddress().getHostString()).
header(CommonConstants.FEIGN_REQUEST_KEY,CommonConstants.FEIGN_REQUEST_FALSE).build();
全局过滤器,因为微服务的请求是网关转发过来的,getRequest().getRemoteAddress()获取到的是网关ip。
登录流程(jwt)
第0步:拿着token查
第一步:拿到用户信息,先到redis中查,查不到到数据库中查,并且存到redis中。
第二步:如果查到的信息为空或者查出的数据密码不符,往mq中发送消息(登录失败),抛出异常,提示密码或者用户名错误
第三步:密码符合,查出用户信息,创建token
private String createToken(UserInfo userInfo) {
//token创建
String token = UUID.randomUUID().toString().replace("-", "");
//把user对象存储到redis中
CommonRedisKey redisKey = CommonRedisKey.USER_TOKEN;
redisTemplate.opsForValue().set(redisKey.getRealKey(token), JSON.toJSONString(userInfo), redisKey.getExpireTime(), redisKey.getUnit());
return token;
}
过期时间是30分钟,如何保证用户体验?
在过滤器中
/**
* post拦截逻辑
* 在请求执行完微服务之后,需要刷新token在redis的时间
* 判断token不为空 && Redis还存在这个token对于的key,这时候需要延长Redis中对应key的有效时间.
*/
String token,redisKey;
if(!StringUtils.isEmpty(token =exchange.getRequest().getHeaders().getFirst(CommonConstants.TOKEN_NAME))
&& redisTemplate.hasKey(redisKey = CommonRedisKey.USER_TOKEN.getRealKey(token))){
redisTemplate.expire(redisKey, CommonRedisKey.USER_TOKEN.getExpireTime(), CommonRedisKey.USER_TOKEN.getUnit());
}
二、秒杀功能实现
1. 秒杀列表功能展示
秒杀产品列表查询
数据库设计
商品表t_product
id | bigint | 主键id |
---|---|---|
product_name | varchar | 产品名称 |
product_title | varchar | 产品标题 |
product_img | varchar | 产品图片链接 |
product_detail | longtext | 产品详细介绍 |
product_price | decimal | 产品价格 |
秒杀商品表t_seckill_product |
id product_id(对应t_product_id) seckill_price(价格) intergral(积分) stock_count(库存) start_date(开始日期) time(秒杀开始场次) |
bigint bigint decimal decimal int date int |
---|
秒杀商品SeckillProductVo(t_product + t_seckill_product)
productName productTitle productImg productDetail productPrice currentCount; |
---|
逻辑:
Result<List<SeckillProductVo>> selectTodayListByTime(@RequestParam("time") Integer time)
得到秒杀商品t_seckill_product_id集合
Result<List<Product>> selectByIdList(@RequestParam("ids") List<Long> idList);
得到id的商品集合
整体思想图
商品详情页
@RequestMapping("/find")
public Result<SeckillProductVo> findById(Integer time, Long seckillId) {
return Result.success(seckillProductService.selectByIdAndTime(seckillId, time));
}
@Cacheable(key = "'selectByIdAndTime:' + #time + ':' + #seckillId")
public SeckillProductVo selectByIdAndTime(Long seckillId, Integer time) {
SeckillProduct seckillProduct = seckillProductMapper.selectByIdAndTime(seckillId, time);
Result<List<Product>> result = productFeignApi.selectByIdList(Collections.singletonList(seckillProduct.getProductId()));
if (result.hasError() || result.getData() == null || result.getData().size() == 0) {
throw new BusinessException(n