document
API test

商品上架

POST
http://localhost:88/api/product/spuinfo/:spuiId/up

Request Parameters

parameter
type
description
required
spuiId
long
示例:36
required

Response Parameters

parameter
type
description
required
msg
string
示例:success
required
code
int
示例:0
required

Description or Example

# BUG解决 ## feign异常 >**`feign.FeignException$InternalServerError: status 500 reading WareSkuService#getStockStatusBySkuIds(List)`** >因为如果传递一个`json`数组过去, 必须放在请求体, 所以必须发`post`请求, 但是, 如果写`@RequestMapping`, 默认发的是`get`请求, 因此会报错, 所以, 需要明确的写`@PostMapping` ## 获取data数据失败问题(史前巨坑) > 必须传参`TypeReference`否则无法解决 > 必须使用老师的方式, 泛型无法解决 > _**总结, 老师怎么写, 就怎么写!!!**_ ## 上架商品的注意事项 > 上架商品的时候, 一定要指定文档的ID, 否则会出现重复上架带来的数据冗余问题 # 核心代码 ```java /** * 商品上传 * @param spuId * @return */ @RequestMapping("/{spuId}/up") public R productStatusUp(@PathVariable("spuId") Long spuId) { boolean isSuccess = skuInfoService.up(spuId); return isSuccess ? R.ok() : R.error(ErrorCodeEnum.PRODUCT_UP_FAILURE.getCode(), ErrorCodeEnum.PRODUCT_UP_FAILURE.getMessage()); } ``` ```java @Override public boolean up(Long spuId) { List<SkuInfoEntity> skuInfoEntities = this.getSkusBySpuId(spuId); // 1. 通过spuId获取所有的Sku信息 // 获取SPU下所有的规格参数, 不要放在循环里面查询, 没有必要发送冗余请求 List<ProductAttrValueEntity> baseAttrs = productAttrValueService.getBaseAttrBySpuId(spuId); // SPU下规格参数 List<SkuESModel.Attr> attrs = null; // 避免空指针异常 if (baseAttrs != null && !baseAttrs.isEmpty()) { // 获取所有的的可查询的规格参数id List<Long> baseAttrIds = baseAttrs.stream().map(ProductAttrValueEntity::getAttrId).collect(Collectors.toList()); baseAttrIds = attrService.filterQuickSearchBaseAttrIds(baseAttrIds); // 封装成一个set集合 Set<Long> set = new HashSet<>(baseAttrIds); attrs = baseAttrs.stream().map(baseAttr -> { SkuESModel.Attr attr = new SkuESModel.Attr(); BeanUtils.copyProperties(baseAttr, attr); return attr; }).filter(attr -> set.contains(attr.getAttrId())) .collect(Collectors.toList()); } // 避免空指针异常 if (skuInfoEntities != null && !skuInfoEntities.isEmpty()) { // 获取所有的skuId List<Long> skuIds = skuInfoEntities.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList()); // 批量查询这些sku是否还有库存 R info = wareSkuService.getStockStatusBySkuIds(skuIds); if (info.getCode() != 0) { log.error("库存信息查询失败"); throw new RuntimeException("库存信息查询失败"); } TypeReference<List<SkuStockTo>> typeReference = new TypeReference<List<SkuStockTo>>() { }; List<SkuStockTo> stockTos = info.getData(typeReference); // 库存情况 Map<Long, Boolean> skuStockMap = null; if (stockTos != null && !stockTos.isEmpty()) { // 将查询出来的库存情况封装成map集合 skuStockMap = stockTos.stream().collect(Collectors.toMap(SkuStockTo::getSkuId, SkuStockTo::getHasStock)); } List<SkuESModel.Attr> finalAttrs = attrs; Map<Long, Boolean> finalSkuStockMap = skuStockMap; List<SkuESModel> skuESModels = skuInfoEntities.stream().map(skuInfoEntity -> { SkuESModel skuESModel = new SkuESModel(); // 创建一个最终封装的对象 BeanUtils.copyProperties(skuInfoEntity, skuESModel); skuESModel.setSkuImg(skuInfoEntity.getSkuDefaultImg()); // 因为价格在es是以keyword存储的, 以ascii码比较, 因此需要规定统一的长度, 前面补0, 最终可以正常获取范围 BigDecimal price = skuInfoEntity.getPrice(); // 默认长度为6, 不足则补 String finalPrice = NumberUtil.decimalFormat("000000.00", price); skuESModel.setSkuPrice(finalPrice); // 获取品牌相关的信息 BrandEntity brandEntity = brandService.getById(skuESModel.getBrandId()); skuESModel.setBrandName(brandEntity.getName()) .setBrandImg(brandEntity.getLogo()); // 获取分类信息 CategoryEntity categoryEntity = categoryService.getById(skuESModel.getCatalogId()); skuESModel.setCatalogName(categoryEntity.getName()); // 封装规格参数信息 // todo: 后端管理热度评分需求的完善, 这里默认设置为0 skuESModel.setAttrs(finalAttrs).setHotScore(0L); // 获取库存相关的信息 if (finalSkuStockMap != null && !finalSkuStockMap.isEmpty()) skuESModel.setHasStock(finalSkuStockMap.get(skuESModel.getSkuId())); return skuESModel; }).collect(Collectors.toList()); // 将数据保存在es中 R booleanR = esClientService.indexProduct(skuESModels); TypeReference<Boolean> typeReference1 = new TypeReference<Boolean>() { }; Boolean isSuccess = booleanR.getData(typeReference1); if (!isSuccess) { return false; } // 更新状态与时间, 先封装更新对象 SpuInfoEntity spuInfoEntity = new SpuInfoEntity().setId(spuId) .setPublishStatus(ProductConstant.SpuPublishStatus.PUBLISH_STATUS_UP.getCode()) .setUpdateTime(new Date()); spuInfoService.updateById(spuInfoEntity); } // TODO: 需要开启feign的重试机制, 重试机制默认不开启 return true; // 没有数据, 默认成功 } @Override public List<SkuInfoEntity> getSkusBySpuId(Long spuId) { LambdaQueryWrapper<SkuInfoEntity> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SkuInfoEntity::getSpuId, spuId); return this.list(queryWrapper); } ``` ```java @Override public List<Long> filterQuickSearchBaseAttrIds(List<Long> baseAttrIds) { if (baseAttrIds != null && !baseAttrIds.isEmpty()) { LambdaQueryWrapper<AttrEntity> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(AttrEntity::getAttrId, baseAttrIds); queryWrapper.eq(AttrEntity::getSearchType, ProductConstant.AttrSearch.QUICK_SEARCH_TYPE.getCode()); return this.list(queryWrapper).stream().map(AttrEntity::getAttrId).collect(Collectors.toList()); } return null; } ``` ```java @FeignClient("bitmall-ware") public interface WareSkuService { @PostMapping("/ware/waresku/getStockStatus") R getStockStatusBySkuIds(@RequestBody List<Long> skuIds); } ``` ```java @Override public List<SkuStockTo> getSkuStockStatusBySkuIds(List<Long> skuIds) { // 当skuId不为空时 if (skuIds != null && !skuIds.isEmpty()) { List<SkuStockTo> tos = skuIds.stream().map(skuId -> { Integer stock = wareSkuDao.getStockBySkuId(skuId); stock = stock == null ? 0 : stock; // 对空判断, 避免空指针异常 return new SkuStockTo().setSkuId(skuId).setHasStock(stock > 0); }).collect(Collectors.toList()); return tos; } return null; } ``` ```java @FeignClient("bitmall-search") public interface ESClientService { /** * 商品商家 */ @PostMapping("/es/index/product/up") R indexProduct(@RequestBody List<SkuESModel> skuESModels); } ``` ```java public Boolean indexProduct(List<SkuESModel> skuESModels) throws IOException { if (skuESModels != null && !skuESModels.isEmpty()) { // 创建一个批量操作的请求 BulkRequest bulkRequest = new BulkRequest(); skuESModels.forEach(skuESModel -> { IndexRequest indexRequest = new IndexRequest(ElasticSearchConstant.PRODUCT_INDEX); // 索引文档请求 String skuEsModelJson = JSON.toJSONString(skuESModel); indexRequest.id(skuESModel.getSkuId().toString()); // id设定 为skuId, 避免重复上架带来的数据冗余问题 indexRequest.source(skuEsModelJson, XContentType.JSON); // 封装好一个批量请求 bulkRequest.add(indexRequest); // 添加到批量操作中 }); BulkResponse itemResponses = client.bulk(bulkRequest, SearchConfig.COMMENT_OPTIONS); if (itemResponses.hasFailures()) { // 如果存在错误 log.error(itemResponses.buildFailureMessage()); // 输出错误日志 return false; } } return true; // 如果没有数据, 不需要保存, 默认成功 } ``` ```java // R对象 public<T> T getData(TypeReference<T> typeReference) { Object data = this.get("data"); if (data != null) { String jsonString = JSON.toJSONString(data); return JSON.parseObject(jsonString, typeReference); } return null; } public R setData(Object data) { this.put("data", data); return this; } ``` # 扩展知识 ## 这里是否需要事务? > 开门见山的说, 这里不需要事务 > 1. 前面的操作均以查询为主, 没有批量操作 > 2. ES的批量存储操作是相互独立的, 不能用事务 > 3. 而且存储ES后才会更改数据库记录状态, 如果上述过程失败了, 状态也不会改变, 完全没有影响, 不需要数据库事务的回滚 ## 为什么一次性将SKU所有的存储状态返回? > 这里涉及到微服务调用, 多次微服务延迟很高, 效率低 ## 序列化问题 > 如果一个被序列化的类想要`HAS A`另一个类, 那么这个类也需要被序列化, 否则无法存储, 在当前项目中, `R`继承于`HashMap`, 如果这里设定了泛型, 仍然无法将数据存储进去, 是一个巨坑的点 ## 无法直接调用构造器怎么办 > 思路和创建集合对象的时候同时往里面添加元素类似, 因为无法直接使用构造器(可能是受保护或私有), 因此, 我们可以转换思路, `new`匿名内部类, 该匿名内部类理论上和原类一点区别都没, 因此, 可以采取该方式破解 ## 重复上架是否存在问题 > 重复上架是没有任何问题的, 因为ES索引文档, 即使索引多次, 仅仅是更新, 并不会有冗余记录, 没有任何影响! ## Feign的源码流程 [流程图](https://www.processon.com/embed/64c31d4cc07d99075d8e4c45)