Description or Example
# Bug修复
## 异步任务的相关知识
> 总所周知, 异步任务需要遵循等待原则, 即需要等待所有的任务执行完毕才能返回, 但是, 这里有一个易错点, 即阶段传递, `suppleAsync()`, 这个通常是连接两个阶段的载体, 因此, 在最后等待的时候, 需要等待这两个阶段, 否则第二个阶段可能会没有完成
# 相关知识
## 只要选中的购物项?
> 因为购物车里面有多个购物项, 只有选中的才能进订单, 因此, 在获取购物项的时候需要对选中状态进行过滤, 仅选中选中项
## 为什么获取最新价格?
> 因为加入购物车的时间和下单的时间不一样, 加入购物车时, 数据被持久化在Redis, 价格是那个时间的, 当前时间的价格和那个时间的有所不同, 因此, 我们需要查询每件商品的最新价格
### 老师获取价格有什么致命的缺陷?以及修改方案?
> 老师的获取价格复用了之前的API, 即利用增强FOR, 遍历每一个订单项, 利用订单项的SkuId去查询最新的价格
> 这里最大的缺点就是, 每次远程微服务只能获取一个订单项的最新价格信息, 如果有N个订单项就要远程调用N次, 效率极低, 不建议使用
> 因此, 我们可以采取一次查询的方式, 即, 我们可以将全部SkuId收集起来, 一次性全部查完, 最后映射回去即可
## 为什么用Feign进行远程调用, 获取不了订单信息? 或者Feign的内部异常?
### 理论

> 因为Feing远程调用的时候, 本质也是一次请求, 因此, Feign在发送请求之前也会构造一次请求, 通过源码和DEBUG可知, 其构造请求默认是由一个个请求拦截器`RequestInterceptor`组成的, 因此默认情况下没有请求拦截器, 因此, 最后的请求头是空的
> 请求头为空 -> 没有Cookie信息 -> 没有会话信息 -> 没有登录信息 -> 获取不到在线购物车操作 -> 导致可能的Feign内部异常或查询不到记录
### 源码图
[源码图](https://www.processon.com/embed/64f661b9b011bd58abce4a57)
### 解决
```java
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request;
if (requestAttributes != null) { // 当这个值等于空的时候, 只可能出现在并发的场景, 如果为空, 默认认为目标微服务不需要的登陆信息
request = requestAttributes.getRequest();
String cookie = request.getHeader("Cookie");
requestTemplate.header("Cookie", cookie);
}
};
}
}
```
### 为什么修改过后还是会抛内部异常?
SQL导致的错误
### RequestContextHolder原理

> 这个类具有获取请求上下文的能力, 其根本就是底层是由ThreadLocal这个类支撑起来的, 在线程的ThreadLocalMap中存储了请求的信息, 因此可以共享请求上下文
> 注意的是, 不同线程下, 这些请求信息就无法共享了
## MyBatis复习
### 如何映射一个符合目的的MAP
> 没有特别好的方法, 只能将实体类查出来, 然后通过stream()流映射成一个MAP
# 改进
## 异步编排对代码的改进
> 因为这里有多个微服务的远程调用, 如果采取传统的前后顺序执行, 吞吐量和延迟都非常差, 因此可以采用异步编排
### 异步编排下无法获取数据的问题
> 由之前的知识点可知, RequestContextHolder的低等逻辑是通过ThreadLocal来实现当前线程内Request共享的, 但是, 不同的线程, 有不同的ThreadLocalMap, 因此, 当我们使用异步编排的时候, 会使用其他线程, 其他线程没有保存request信息, 因此没有COokie信息
> 没有Req -> 没有Cookie -> 最终封装请求Header为空 -> 获取离线操作hash -> 最终无法获取
> 因此, 我们需要在当前线程获取Req, 然后再在异步编排的代码内部赋值即可
## 关于Stream流的阐述
> stream流中的map可以对元素进行映射, 可以先确定的是, stream流最后收集的集合是一个新的集合, 但是, 是否是新的集合并没有什么关系, 重要的是集合中强引用指向的数据, 这里map我是基于原来的对象修改的, 所以, 这个对象并不会有新的, 就是在原来的基础上改, 而且改动的东西不同, 因此不可能有线程安全问题
# 核心源码
```java
@GetMapping("/toTrade")
public ModelAndView toTrade(ModelAndView mv) {
// 获取支付确认页数据
ConfirmVO confirmVO = orderConfirmService.getConfirmVOData();
mv.setViewName("confirm");
mv.addObject(confirmVO);
return mv;
}
```
```java
@Override
public ConfirmVO getConfirmVOData() {
// 0. 通过ThreadLocal获取用户相关信息
MemberVO memberVO = UserLoginInterceptor.USER_STATE.get();
Long memberId = memberVO.getId();
// 1. 创建目标对象
ConfirmVO confirmVO = new ConfirmVO();
RequestAttributes mainThreadReq = RequestContextHolder.getRequestAttributes();
// 补充: 异步创建防重令牌
CompletableFuture.runAsync(() -> {
String orderToken = UUID.randomUUID().toString().replace("-", "");
redisTemplate.opsForValue().set(OrderConstant.ORDER_TOKEN_PREFIX + memberId, orderToken);
confirmVO.setOrderToken(orderToken);
}, threadPoolExecutor);
// 任务1
CompletableFuture<Void> getAddressTask = CompletableFuture.runAsync(() -> {
// 2. 查询用户地址相关信息
List<MemberAddressVO> addresses = memberService.getMemberAllAddressesWithOrder(memberId);
confirmVO.setAddress(addresses);
}, threadPoolExecutor);
// 任务2
CompletableFuture<List<OrderItemVO>> getOrderItemsTask = CompletableFuture.supplyAsync(() -> {
RequestContextHolder.setRequestAttributes(mainThreadReq);
// 3. 查询用户的选中的订单项
List<OrderItemVO> orderItemVOS = cartService.getMemberAllOrderItemsWithOrder();
confirmVO.setItems(orderItemVOS);
return orderItemVOS;
}, threadPoolExecutor);
// 任务2 -> 3
CompletableFuture<Void> getNewestPriceTask = getOrderItemsTask.thenAcceptAsync(orderItems -> {
// 4. 更新订单项的价格
// 4.1 获取SkuId集合
List<Long> skuIds = orderItems.stream().map(OrderItemVO::getSkuId).collect(Collectors.toList());
// 4.2 获取价格映射
Map<Long, BigDecimal> currentPrice = productService.getCurrentPrice(skuIds);
// 4.3 映射最新价格
orderItems = orderItems.stream().map(orderItemVO
// 如果没有价格, 默认价格为0
-> orderItemVO.setPrice(currentPrice.getOrDefault(orderItemVO.getSkuId(), new BigDecimal(0))))
.collect(Collectors.toList());
}, threadPoolExecutor);
CompletableFuture<Void> getWeightTask = getOrderItemsTask.thenAcceptAsync(orderItems -> {
// 补充: 获取重量
List<Long> skuIds = orderItems.stream().map(OrderItemVO::getSkuId).collect(Collectors.toList());
Map<Long, Long> spuIdBySkuId = productService.getSpuIdBySkuId(skuIds); // SKU 和 SPU的映射关系
List<Long> spuIds = new ArrayList<>(spuIdBySkuId.values()); // 窄化类型转换成List, 方便操作
Map<Long, BigDecimal> weightBySpuId = productService.getWeightBySpuId(spuIds); // SPU 和 weight的映射关系
Map<Long, BigDecimal> skuAndWeight = new HashMap<>();
for (Map.Entry<Long, Long> skuSpu : spuIdBySkuId.entrySet()) {
Long skuId = skuSpu.getKey();
Long spuId = skuSpu.getValue();
BigDecimal weight = weightBySpuId.getOrDefault(spuId, new BigDecimal(0));
skuAndWeight.putIfAbsent(skuId, weight);
}
// 构建好了Sku 与 重量的映射关系
orderItems = orderItems.stream().map(orderItem -> orderItem.setWeight(skuAndWeight.get(orderItem.getSkuId()))).collect(Collectors.toList());
}, threadPoolExecutor);
CompletableFuture<Void> getStockTask = getOrderItemsTask.thenAcceptAsync(orderItems -> {
// 补充: 查询这些订单项是否有库存
List<Long> skuIds = orderItems.stream().map(OrderItemVO::getSkuId).collect(Collectors.toList());
R info = wareService.getStockStatusBySkuIds(skuIds);
List<SkuStockTo> stocks = info.getData(new TypeReference<List<SkuStockTo>>() {
});
Map<Long, Boolean> stockMap = stocks.stream().collect(Collectors.toMap(SkuStockTo::getSkuId, SkuStockTo::getHasStock));
confirmVO.setStocks(stockMap);
}, threadPoolExecutor);
// 任务0
// 5. 获取对应的优惠信息(京豆)
Integer integration = memberVO.getIntegration();
confirmVO.setIntegration(integration == null ? 0 : integration);
// 等待所有任务的完成
CompletableFuture.allOf(getOrderItemsTask, getAddressTask, getNewestPriceTask, getStockTask, getWeightTask).join();
return confirmVO;
}
```
```java
@FeignClient("bitmall-member")
public interface MemberService {
/**
* 获取用户收货地址相关信息
* @param memberId
* @return
*/
@RequestMapping("/member/memberreceiveaddress/{memberId}")
List<MemberAddressVO> getMemberAllAddressesWithOrder(@PathVariable Long memberId);
}
```
```java
@RequestMapping("/{memberId}")
public List<MemberReceiveAddressEntity> getMemberAllAddressesWithOrder(@PathVariable Long memberId) {
return memberReceiveAddressService.getMemberAllAddressesWithOrder(memberId);
}
```
```java
@Override
public List<MemberReceiveAddressEntity> getMemberAllAddressesWithOrder(Long memberId) {
LambdaQueryWrapper<MemberReceiveAddressEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(MemberReceiveAddressEntity::getMemberId, memberId);
return this.list(queryWrapper);
}
```
```java
@FeignClient("bitmall-cart")
public interface CartService {
@RequestMapping("/getMember/order")
@ResponseBody
List<OrderItemVO> getMemberAllOrderItemsWithOrder();
}
```
```java
@RequestMapping("/getMember/order")
@ResponseBody
public List<CartItemVO> getMemberAllOrderItemsWithOrder() {
List<CartItemVO> allCartItems = cartService.getAllCartItems();
// 过滤出选中的购物项*(只要选中的购物项)
// 有可能有些二货直接调用, 而且没有扽牢固, 会造成空指针异常, 因此加以判断
return allCartItems != null ? allCartItems.stream().filter(CartItemVO::getCheck).collect(Collectors.toList()) : null;
}
```
```java
/**
* 获取所有的购物项, 该方法可以自行决定在线或离线的购物车操作
* 即自动从在线购物车中取数据(因为订单只能在登录下完成)
*/
public List<CartItemVO> getAllCartItems() {
return getAllCartItems(getHashHandle());
}
```
```java
@FeignClient("bitmall-product")
public interface ProductService {
@RequestMapping("/product/skuinfo/skus/currentPrice")
Map<Long, BigDecimal> getCurrentPrice(@RequestParam List<Long> skuIds);
@RequestMapping("/product/skuinfo/getSpuId")
Map<Long, Long> getSpuIdBySkuId(@RequestParam List<Long> skuIds);
@RequestMapping("/product/spuinfo/weight")
Map<Long, BigDecimal> getWeightBySpuId(@RequestParam List<Long> spuIds);
}
```
```java
@RequestMapping("/skus/currentPrice")
public Map<Long, BigDecimal> getCurrentPrice(@RequestParam List<Long> skuIds) {
return skuInfoService.getCurrentPrice(skuIds);
}
```
```xml
<select id="getCurrentPrice" resultMap="skuInfoMap">
SELECT sku_id, price FROM pms_sku_info WHERE sku_id IN
<foreach collection="skuIds" item="skuId" open="(" close=")" separator=",">
#{skuId}
</foreach>
</select>
```
```java
@RequestMapping("/getSpuId")
public Map<Long, Long> getSpuIdBySkuId(@RequestParam List<Long> skuIds) {
return skuInfoService.getSpuIdBySkuId(skuIds);
}
```
```java
@Override
public Map<Long, Long> getSpuIdBySkuId(List<Long> skuIds) {
LambdaQueryWrapper<SkuInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(SkuInfoEntity::getSkuId, SkuInfoEntity::getSpuId).in(SkuInfoEntity::getSkuId, skuIds);
List<SkuInfoEntity> skuInfoEntities = this.list(queryWrapper);
if (skuInfoEntities != null && !skuInfoEntities.isEmpty()) {
return skuInfoEntities.stream().collect(Collectors.toMap(
SkuInfoEntity::getSkuId, SkuInfoEntity::getSpuId
));
}
return null;
}
```
```java
@RequestMapping("/weight")
public Map<Long, BigDecimal> getWeightBySpuId(@RequestParam List<Long> spuIds) {
return spuInfoService.getWeightBySpuIds(spuIds);
}
```
```java
@Override
public Map<Long, BigDecimal> getWeightBySpuIds(List<Long> spuIds) {
LambdaQueryWrapper<SpuInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(SpuInfoEntity::getId, SpuInfoEntity::getWeight).in(SpuInfoEntity::getId, spuIds);
List<SpuInfoEntity> spuInfoEntities = this.list(queryWrapper);
if (spuInfoEntities != null && !spuInfoEntities.isEmpty()) {
return spuInfoEntities.stream().collect(Collectors.toMap(
SpuInfoEntity::getId, SpuInfoEntity::getWeight
));
}
return null;
}
```
# 确认页面流程的补充
