谷粒商城–商品上架–高级篇笔记二
1. 新增商品索引
PUT product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "keyword"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"saleCount": {
"type": "long"
},
"hasStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catalogId": {
"type": "long"
},
"brandName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"brandImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"catalogName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrs": {
"type": "nested",
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}
2. 上架接口
gulimall-product/src/main/java/site/zhourui/gulimall/product/controller/SpuInfoController.java
新增上架接口
/**
* 商品上架
* POST /product/spuinfo/{spuId}/up
*/
@PostMapping("/{spuId}/up")
// @RequiresPermissions("product:spuinfo:delete")
public R up(@PathVariable("spuId") Long spuId){
spuInfoService.up(spuId);
return R.ok();
}
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/SpuInfoService.java
新增接口
void up(Long spuId);
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/impl/SpuInfoServiceImpl.java
商品上架主要逻辑代码
@Autowired
BrandService brandService;
@Autowired
CategoryService categoryService;
@Autowired
WareFeignService wareFeignService;
@Autowired
SearchFeignService searchFeignService;
/**
* 商品上架【spu商品上架,会上架所有sku商品到es,以skuId作为文档id】
*/
@Override
public void up(Long spuId) {
// sku的属性是继承spu的,一个商品下不同的sku,其基本属性是相同的,销售属性不同
// TODO 4、查询所有sku可以被用来检索 的规格属性
// 1、根据spuId获取所有Attr,过滤掉不可检索的Attr
List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrlistforspu(spuId);
// 2、将结果包装成AttrId集合
List<Long> attrIds = baseAttrs.stream().map(attr -> {
return attr.getAttrId();
}).collect(Collectors.toList());
// 3、获取所有可检索的AttrId集合,包装成set
List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds);
HashSet<Long> idSet = new HashSet<>(searchAttrIds);
// 4、将所有Attr根据set过滤并包装成List<SkuEsModel.Attrs>,最终存入es
List<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter(item -> {
return idSet.contains(item.getAttrId());
}).map(item -> {
SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
BeanUtils.copyProperties(item, attrs);
return attrs;
}).collect(Collectors.toList());
// 6、查出当前spuId对应的所有sku商品信息
List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId);
// 7、包装所有skuId
List<Long> skuIds = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
// TODO 1、发送远程调用查询库存是否有
// 8、库存信息,如果为null,库存为true
Map<Long, Boolean> stockMap = null;
try {
// 9、远程调用,获取所有sku的库存信息
R r = wareFeignService.getSkusHasStock(skuIds);
// 10、返回数据泛型,受保护的构造器,使用匿名内部类
TypeReference<List<SkuHasStockTo>> typeReference = new TypeReference<List<SkuHasStockTo>>() {
};
// 11、封装,skuId为key,hasStock为值
stockMap = r.getData(typeReference).stream().collect(Collectors.toMap(SkuHasStockTo::getSkuId, item -> item.getHasStock()));
}catch (Exception e){
log.error("库存服务查询异常:原因{}", e);
}
Map<Long, Boolean> finalStockMap = stockMap;
// 12、封装每个sku的信息,List<SkuEsModel>保存到es
List<SkuEsModel> upProducts = skus.stream().map(sku -> {
SkuEsModel esModel = new SkuEsModel();
BeanUtils.copyProperties(sku, esModel);
// 单独处理的数据:
// skuPrice, skuImg, hasstock, hotScore,
esModel.setSkuPrice(sku.getPrice());
esModel.setSkuImg(sku.getSkuDefaultImg());
// 库存为null【远程调用失败,默认为true】
if (finalStockMap == null) {
esModel.setHasStock(true);
}else {
esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
}
// TODO 2、热度评分。0
esModel.setHotScore(0l);
// TODO 3、查询品牌和分类的名字
BrandEntity brand = brandService.getById(esModel.getBrandId());
esModel.setBrandName(brand.getName());
esModel.setBrandImg(brand.getLogo());
CategoryEntity category = categoryService.getById(esModel.getCatalogId());
esModel.setCatalogName(category.getName());
// 设置检索属性:冗余信息,spu规格,sku共用
esModel.setAttrs(attrsList);
return esModel;
}).collect(Collectors.toList());
// TODO 5、将数据发送给es进行保存:gulimall-search
R r = searchFeignService.productStatusUp(upProducts);
if (r.getCode() == 0) {
// 远程调用成功
// TODO 6、修改当前spu的状态 上架
baseMapper.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
}else {
// 远程调用失败
// TODO 7、重复调用?接口幂等性:重试机制?
//Feign调用流程
/**
* 1、构造请求数据,将对象转为json
* RequestTemplate template = buildTemplateFromArgs.create(argv);
* 2、发送请求进行执行:【执行成功会解码响应数据】
* excuteAndDecode(template)
* 3、执行请求会有重试机制
* while(true){
* try{
* excuteAndDecode(template)
* }catch() {
* try{
* // 默认重试5次,也有不重试
* retryer.continueOrPropagate(e);
* }catch() {
* throw ex;
* }
* continue;
* }
* }
*
*
*/
}
}
3.获取所有可检索的AttrId集合,包装成set
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/AttrService.java
List<Long> selectSearchAttrIds(List<Long> attrIds);
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/impl/AttrServiceImpl.java
@Override
public List<Long> selectSearchAttrIds(List<Long> attrIds) {
return baseMapper.selectSearchAttrIds(attrIds);
}
gulimall-product/src/main/java/site/zhourui/gulimall/product/dao/AttrDao.java
List<Long> selectSearchAttrIds(@Param("attrIds") List<Long> attrIds);
gulimall-product/src/main/resources/mapper/product/AttrDao.xml
<select id="selectSearchAttrIds" resultType="java.lang.Long">
SELECT attr_id FROM `pms_attr` WHERE attr_id in
<foreach collection="attrIds" item="id" separator=", " open="(" close=")">
#{id}
</foreach>
AND search_type = 1
</select>
4.新增SkuEsModel.Attrs实体类
gulimall-common/src/main/java/site/zhourui/common/to/es/SkuEsModel.java
package site.zhourui.common.to.es;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class SkuEsModel {
private Long skuId;
private Long spuId;
private String skuTitle;
private BigDecimal skuPrice;
private String skuImg;
private Long saleCount;
private Boolean hasStock;
private Long hotScore;
private Long brandId;
private Long catalogId;
private String brandName;
private String brandImg;
private String catalogName;
private List<Attrs> attrs;
@Data
public static class Attrs {
private Long attrId;
private String attrName;
private String attrValue;
}
}
5.查出当前spuId对应的所有sku商品信息
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/SkuInfoService.java
List<SkuInfoEntity> getSkusBySpuId(Long spuId);
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/impl/SkuInfoServiceImpl.java
@Override
public List<SkuInfoEntity> getSkusBySpuId(Long spuId) {
return this.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));
}
6.发送远程调用查询库存是否有
6.1 封装SkuHasStockTo对象
gulimall-common/src/main/java/site/zhourui/common/to/SkuHasStockTo.java
gulimall-ware/src/main/java/site/zhourui/gulimall/ware/vo/SkuHasStockVo.java
package site.zhourui.gulimall.ware.vo;
import lombok.Data;
@Data
public class SkuHasStockVo {
private Long skuId;
private Boolean hasStock;
}
6.1在gulimall-ware模块新增接口
gulimall-ware/src/main/java/site/zhourui/gulimall/ware/controller/WareSkuController.java
/**
* 查询sku是否有库存
*/
@PostMapping("/hasstock")
// @RequiresPermissions("ware:waresku:list")
public R getSkusHasStock(@RequestBody List<Long> skuIds){
// sku_id stock
List<SkuHasStockVo> vos = wareSkuService.getSkusHasStock(skuIds);
return R.ok().setData(vos);
}
gulimall-ware/src/main/java/site/zhourui/gulimall/ware/service/WareSkuService.java
/**
* 判断是否有库存
*/
List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds);
gulimall-ware/src/main/java/site/zhourui/gulimall/ware/service/impl/WareSkuServiceImpl.java
/**
* 检查sku 是否有库存
*/
@Override
public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
List<SkuHasStockVo> vos = skuIds.stream().map(skuId -> {
SkuHasStockVo vo = new SkuHasStockVo();
// 1、不止一个仓库有,多个仓库都有库存 sum
// 2、锁定库存是别人下单但是还没下完
Long count = baseMapper.getSkuStock(skuId);
vo.setSkuId(skuId);
vo.setHasStock(count == null ? false : count > 0);
return vo;
}).collect(Collectors.toList());
return vos;
}
6.2 新增远程调用接口
gulimall-product/src/main/java/site/zhourui/gulimall/product/feign/WareFeignService.java
package site.zhourui.gulimall.product.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import site.zhourui.common.utils.R;
import java.util.List;
@FeignClient("gulimall-ware")
public interface WareFeignService {
@PostMapping("/ware/waresku/hasstock")
R getSkusHasStock(@RequestBody List<Long> skuIds);
}
6.3 修改R
gulimall-common/src/main/java/site/zhourui/common/utils/R.java
新增
public <T> T getData(String key, TypeReference<T> typeReference) {
Object data = get(key);// 默认是map类型,springmvc做的
String jsonStr = JSON.toJSONString(data);
T t = JSON.parseObject(jsonStr, typeReference);
return t;
}
// 利用fastJson进行逆转
// 这里要声明泛型<T>,这个泛型只跟方法有关,跟类无关。
// 例如类上有个泛型,这里可以使用类上的泛型,就不用声明
public <T> T getData(TypeReference<T> typeReference) {
Object data = get("data");// 默认是map类型,springmvc做的
String jsonStr = JSON.toJSONString(data);
T t = JSON.parseObject(jsonStr, typeReference);
return t;
}
public R setData(Object data) {
put("data", data);
return this;
}
7. 将数据发送给es进行保存
7.1 新增上架异常
gulimall-common/src/main/java/site/zhourui/common/exception/BizCodeEnume.java
新增异常类型
UNKNOW_EXCEPTION(10000, "系统未知异常"),
VALID_EXCEPTION(10001, "参数格式验证失败"),
PRODUCT_UP_EXCEPTION(11000, "商品上架异常");
7.2 新增es常量
gulimall-common/src/main/java/site/zhourui/common/constant/EsConstant.java
package site.zhourui.common.constant;
public class EsConstant {
public static final String PRODUCT_INDEX = "gulimall_product"; // sku数据在es中的索引
public static final Integer PRODUCT_PAGESIZE = 8; // 检索es 显示数据条数
}
7.3 在gulimall-search模块新增接口
gulimall-search/src/main/java/site/zhourui/gilimall/search/controller/ElasticSaveController.java
package site.zhourui.gilimall.search.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import site.zhourui.common.exception.BizCodeEnume;
import site.zhourui.common.to.es.SkuEsModel;
import site.zhourui.common.utils.R;
import site.zhourui.gilimall.search.service.ProductSaveService;
import java.io.IOException;
import java.util.List;
@Slf4j
@RequestMapping("/search/save")
@RestController
public class ElasticSaveController {
@Autowired
ProductSaveService productSaveService;
// 上架商品
@PostMapping("/product")
public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels) {
boolean b = false;
try {
b = productSaveService.productStatusUp(skuEsModels);
} catch (IOException e) {
log.error("ElasticSaveController商品上架错误:{}", e);
return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());
}
if (b) {
return R.ok();
}else {
return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());
}
}
}
gulimall-search/src/main/java/site/zhourui/gilimall/search/service/ProductSaveService.java
package site.zhourui.gilimall.search.service;
import site.zhourui.common.to.es.SkuEsModel;
import java.io.IOException;
import java.util.List;
/**
* @author zr
* @date 2021/10/26 13:50
*/
public interface ProductSaveService {
boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException;
}
gulimall-search/src/main/java/site/zhourui/gilimall/search/service/impl/ProductSaveServiceImpl.java
package site.zhourui.gilimall.search.service.impl;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import site.zhourui.common.constant.EsConstant;
import site.zhourui.common.to.es.SkuEsModel;
import site.zhourui.gilimall.search.config.GulimallElasticSearchConfig;
import site.zhourui.gilimall.search.service.ProductSaveService;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service("productSaveService")
public class ProductSaveServiceImpl implements ProductSaveService {
@Autowired
RestHighLevelClient client;
/**
* 保存数据到es:
* 1、创建单个保存请求
* IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX)
* 2、请求绑定数据
* indexRequest.source(JSON.toJSONString(model), XContentType.JSON)
* 3、循环构建批量保存请求
* BulkRequest.add(indexRequest)
* 4、使用客户端发送批量保存请求
* client.bulk(bulkRequest, GulimallElasticSearch.COMMON_OPTIONS)
*/
@Override
public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
// 保存到ES
// 1、给es中建立索引、product,建立映射关系
// 2、给es中保存这些数据【使用bulk,不使用index一条条保存】
// BulkRequest bulkRequest, RequestOptions options
BulkRequest bulkRequest = new BulkRequest();
for (SkuEsModel model : skuEsModels) {
// 构造保存请求,设置索引+id
IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
indexRequest.id(model.getSkuId().toString());
String s = JSON.toJSONString(model);
// 绑定请求与数据
indexRequest.source(s, XContentType.JSON);
// 添加到批量保存
bulkRequest.add(indexRequest);
}
BulkResponse bulk = client.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
// TODO 如果批量错误
boolean b = bulk.hasFailures();
List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
return item.getId();
}).collect(Collectors.toList());
log.info("商品上架完成:{},返回数据:{}", collect, bulk);
return !b;// 因为b代表的是是否有异常
}
}
7.4 新增远程调用接口
gulimall-product/src/main/java/site/zhourui/gulimall/product/feign/SearchFeignService.java
package site.zhourui.gulimall.product.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import site.zhourui.common.to.es.SkuEsModel;
import site.zhourui.common.utils.R;
import java.util.List;
@FeignClient("gulimall-search")
public interface SearchFeignService {
// 上架商品
@PostMapping("/search/save/product")
R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels);
}
8. 商品状态枚举
gulimall-common/src/main/java/site/zhourui/common/constant/ProductConstant.java
新增
public enum StatusEnum {
NEW_SPU(0, "新建"),
SPU_UP(1, "商品上架"),
SPU_DOWN(2, "商品下架");
private int code;
private String msg;
StatusEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}