大纲
1.商品中心的专业术语
2.商品中心的基本业务系统
3.商品中心整体架构设计以及运行流程
4.商品B端---商品编码生成逻辑
5.商品B端---商品核心数据模型
6.商品B端---转换建品请求数据为商品模型数据
7.商品B端---商品建品时商品编号补全与审核配置
8.商品B端---商品审核前的草稿数据保存逻辑
9.商品B端---不需审核的建品流程持久化逻辑
10.商品B端---审核工单分页列表和商品草稿查询
11.商品B端---商品审核时的敏感字段diff计算逻辑
12.商品B端---对草稿中的商品进行审核的逻辑
13.商品B端---商品属性+买手+品类的数据维护
14.商品C端---通用缓存读写组件的实现逻辑
15.商品C端---接口代码实现逻辑
1.商品中心的专业术语
一.ITEM
商品售卖展示单位,仅⽤于销售时展示使⽤。
二.SKU
SKU是Stock Keeping Unit(库存单位),即库存进出计量的单位。可以是以件、盒、托盘等为单位,如:iPhoneX + ⿊⾊ + 256G。
三.SPU
SPU是Standard Product Unit标准化产品单元,是对某一类标准产品的共同特征属性的描述。SPU是商品信息聚合的最⼩单位,如:iPhoneX就是SPU。SPU的出现是为了满足在叶子类目下对商品进行进一步抽象的需求。比如手机就是叶子类目,虽然可以添加苹果手机或者华为手机这样的类目,但这样添加就比较麻烦了,可能会导致类目树就会变得非常庞大。所以SPU是一个介于叶子类目和商品之间的概念,是对类目的细化。因此SPU通常由"后台类目 + 关键属性"唯一确定。
四.CSPU
CSPU也就是子标准化产品单元,即SPU的细分,Child SPU。CSPU通常由"后台类目 + 关键属性 + 销售属性"唯一确定。比如手机类型下,品牌和型号这两个属性可以确定一个SPU,但还不能确定一个CSPU,需要额外的销售属性才能确定一个CSPU。以苹果手机为例,品牌是iPhone、型号是X、颜色为黑色、存储为256G,两个关键属性是品牌和型号,两个销售属性为颜色和存储。
五.运营品类
运营品类是⼀种抽象的概念,例如:运动裤、⼿机。每一个商品都会有所属的品类,比如iPhone X这个SPU会属于手机这个品类。不同的电商平台会对品类进行不同的划分,手机品类在有的平台是一级品类,在有的平台是电子产品品类的子品类。
六.前台类⽬
多级品类可以构成前台类⽬,例如:男T恤 + 男短裤可归属到男装类⽬。电商网站首页里,左侧都会有一颗类目树,这个类目树就是前台类目。
七.SKU规格
⽤来区分单品的主要指标。例如⼿机商品由颜⾊、内存两种规格区分单品,每个规格有多个可选值。从每个规格选出一个值,拼凑起来的组合就可以唯一确定一款商品SKU。颜色规格:白色、黑色、粉色、天蓝色;内存:128G、256G、512G。
八.原料商品
只采购不销售的商品,只有采购属性如包材或原材料,例如:吸管、开瓶器。
九.普通商品
⼜采购⼜销售的商品,有库存和销售属性。
十.组套商品
不采购只销售的商品,共享库存和销售属性,例如:原料商品 + 普通商品组合为⼀个商品。开瓶器是原料商品,红酒是普通商品,开瓶器 + 红酒就是一个组套商品。开瓶器不能单卖但需要采购,用户购买红酒时不用关注开瓶器,开瓶器会和红酒打包在一起进行展示和售卖。
十一.虚拟商品
不采购只销售,只有虚拟库存,只有销售属性,例如:会员卡、虚拟卡、购物卡、游戏点卡。这些虚拟商品没有必要去进行采购,用户支付后也不需要履约签收。用户完成对虚拟商品的支付后,商品直接可以展示在用户的会员中心里。
十二.售卖区
商品可以在哪⾥卖,售卖范围配置:按城市配置、按卖家组配置。有的商品只能在部分城市可以售卖,部分城市是没法售卖的。在某些区域里,商品的库存不好发货,可能会显示该区域无货。
仓库会分成两种:微仓和大仓,微仓就是微型的小仓库,大仓就是大型的大仓库。大仓可以辐射很大一片区域的发货,仓库容量很大,里面可以放很多商品。微仓也叫前置仓,在一个城市里,可以设置微仓。可以将该城市经常购买的,库存量消耗比较大的商品,放到多个微仓里。这样距离消费者就会更近一些,发货也可以更快一些。
十三.卖家类型
类型一:⾃营,类型二:POP。自营就是商品是由平台自己来采购、入仓、售卖,POP(Platform Open Plan)意思是平台开放计划,POP就是第三方卖家入驻平台开店售卖自己的商品。
十四.商品状态
可售:商品配置了售卖区并且状态为可售
可补:商品可售且微仓可补货状态
可采:商品可售且⼤仓可采货状态
准备上架:建品后为此状态,表示可采和可补
试销上架:上架状态,表示处于试销阶段
上架:正式上架售卖
预下架:售完不展示商品,表示不可采和可补
下架:不可采和不可补
停售:永久下架,已淘汰
十五.商品价格
商城价:⾮会员⽤户购买商品的价格
会员价:会员⽤户购买的价格
营销价:促销活动价
秒杀价:秒杀活动价格,⼀⼝价
2.商品中心的基本业务系统
(1)商品基础服务
(2)商品类型与采购销售之间的关系
(3)商品中心的业务系统
(1)商品基础服务
服务一: 提供从建品到下架期间可采可补可售管理的商品全流程服务
服务二: 对商品基本信息、品牌信息、运营品类、前台类⽬、仓配信息、标签信息、品控信息、销售信息、推⼴信息等进⾏精细化管理与运营
服务三: 通过权限收敛,可以很好把控并记录⽤户操作⾏为,使流程更加规范
服务四: 通过提效⼯具,业务⽅可以批量处理商品相关⼯作,降低⼈⼒成本
(2)商品类型与采购销售之间的关系
(3)商品中心的业务系统
商品中心的系统主要会分为两类:一个是面向B端,一个是面向C端。面向B端的系统,主要由公司运营来使用,对商品进行精细化管理。面向C端的系统,则会对C端用户提供各种商品浏览和查询的接口。
一.价格中心系统
商品价格管理,提供全流程价格管控和分析。⽀持功能:价格查询、价格设置、审核流程、历史价格查询与趋势分析等。
二.商品卖家系统
商品售卖⽅,这⾥将卖家定义为卖家树,⽤户可以定位到多个卖家。商品基于卖家售卖,⽤户在当前覆盖的区域内可浏览到相应卖家的商品。将多个卖家合并为⼀个⼤的卖家称为卖家组,也称为售卖区,售卖区之间的逻辑处理称为售卖区管理(可售区域)。
三.商品⽣命周期系统
商品的状态分为:准备上架、试销上架、上架、预下架、下架、停售。为了更好的管理商品,需要对商品进⾏⼀套⽣命周期管理。⽤于考核商品,降低滞销率、资⾦成本以及影响商品的可采可补逻辑。
四.商品库存系统
商品库存需要分卖家设置,卖家 + 商品 + 库存关系定位具体商品库存数量。
五.商品标签系统
需要打上特殊标签的商品,例如:爆款,后台进⾏标签 + 标签组 + 商品管理。
六.属性库系统
商品关联的属性,涉及四种属性:关键属性、销售属性、⾮关键属性、导购属性。
七.商品品控系统
把控商品质量,在商品⼊库前进⾏取样检测,给出质检报告。合格商品允许⼊库,不合格商品不允许⼊库。将可售卖商品关联上质检报告,展示给⽤户。
3.商品中心整体架构设计以及运行流程
(1)商品中心整体架构
(2)商品新建编辑流程
(1)商品中心整体架构
(2)商品新建编辑流程
kotlin
//商品服务
@DubboService(version = "1.0.0", interfaceClass = ProductApi.class, retries = 0)
public class ProductApiImpl implements ProductApi {
@Autowired
private ProductService productService;
//建品/编辑商品接口
@Override
public JsonResult<ProductDTO> product(ProductRequest request) {
try {
ProductDTO productDTO = productService.product(request);
return JsonResult.buildSuccess(productDTO);
} catch (ProductBizException e) {
log.error("biz error: request={}", JSON.toJSONString(request), e);
return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
} catch (Exception e) {
log.error("system error: request={}", JSON.toJSONString(request), e);
return JsonResult.buildError(e.getMessage());
}
}
...
}
4.商品B端---商品编码生成逻辑
java
//商品编码
@Service
public class ProductNoManagerImpl implements ProductNoManager {
//6位序列号
private static final int width = 6;
@Autowired
private ProductAutoNoMapper productAutoNoMapper;
//生成商品编码
@Override
public String generateProductNo(Integer sourceType) {
ProductTypeEnum productTypeEnum = ProductTypeEnum.getByCode(sourceType);
if (productTypeEnum == null) {
throw new ProductBizException(ProductErrorCodeEnum.PARAM_ERROR);
}
return getProductNo(productTypeEnum.getValue());
}
//获取组装后的商品编码,商品的prefixNo是100000
private String getProductNo(String prefixNo) {
//有一张ProductAutoNo表专门用于生成商品ID
//分库分表也可以利用此来实现基于数据库的内存缓存分段的发号器
ProductAutoNoDO productAutoNoDO = new ProductAutoNoDO();
productAutoNoMapper.insert(productAutoNoDO);
Long autoNo = productAutoNoDO.getId();//获取自增ID
return prefixNo + IDUtils.genId(autoNo, width);//数字混淆算法
}
}
5.商品B端---商品核心数据模型
typescript
//建品/编辑商品请求入参
@Data
public class ProductRequest implements Serializable {
//商品基本信息
private ItemBaseRequest itemBaseRequest;
//存储信息
private ItemStorageRequest itemStorageRequest;
//品控信息
private ShelfLifeRequest shelfLifeRequest;
//图文信息
private List<ItemVideoImgRequest> itemVideoImgRequestList;
//销售信息
private ItemSaleRequest itemSaleRequest;
//推广信息
private ItemPopularizeRequest itemPopularizeRequest;
//操作人
@NotNull(message = "操作人[operateUser]不能为空")
private Integer operatorUser;
//商品基本信息
@Data
public static class ItemBaseRequest implements Serializable {
//商品ID
private String itemId;
//商品名称
private String itemName;
//渠道(1-每日生鲜、2-美团、3-饿了么、4-淘鲜达、5-招商银行)
private Integer channel;
//卖家类型(1-自营、2-POP)
private Integer sellerType;
//商品状态
private Integer itemStatus;
//商品类型
private Integer itemType;
//品牌ID
private Integer brandId;
//产地ID
private Integer producingAreaId;
//成本价(单位:分)
private Integer basePrice;
//末级品类ID
private Integer lastCategoryId;
//一级品类ID
private Integer oneCategoryId;
//二级品类ID
private Integer twoCategoryId;
//三级品类ID
private Integer threeCategoryId;
}
//存储信息
@Data
public static class ItemStorageRequest implements Serializable {
//存储条件
private Integer storeConditionType;
//ITEM维度规格值(多个规格集合):key=颜色,value=蓝色;key=颜色,value=红色;key=内存,value=128g;key=内存,value=256g
private List<ProductSpcesValue> productSpcesValueList;
}
//规格信息
@Data
public static class ProductSpcesValue implements Serializable {
//规格关键字
private String key;
//规格值
private String value;
//排序
private Integer sort;
}
//品控信息
@Data
public static class ShelfLifeRequest implements Serializable {
//保质期(单位:小时)
private Integer shelfLife;
//Map<key=保质期类型,value=保质期时间(单位:小时)>:acceptLife 允收期,shelfLife 货架期
private Map<String, Integer> shelfLifeMap;
}
//图文信息
@Data
public static class ItemVideoImgRequest implements Serializable {
//内容类型(1-主图,2-轮播图、3-详情图、4-视频)
private Integer contentType;
//链接地址
private String contentUrl;
//排序(正整数,数字越小越靠前)
private Integer contentSort;
}
//销售信息
@Data
public static class ItemSaleRequest implements Serializable {
//sku信息
private List<SkuInfoRequest> skuInfoRequestList;
}
//sku信息
@Data
public static class SkuInfoRequest implements Serializable {
//商品itemId
private String itemId;
//商品skuId
private String skuId;
//商品SKU名称
private String skuName;
//商城价
private Integer basePrice;
//会员价
private Integer vipPrice;
//商品分级(ABC标签,运营归类处理)
private Integer skuGrade;
//69码,条形码
private String barCode;
//SKU维度规格值(单个):key=颜色,value=蓝色;key=内存,value=128g
private List<ProductSpcesValue> productSpcesValueList;
//sku匹配的spu信息
private Long cspuId;
}
//推广信息
@Data
public static class ItemPopularizeRequest implements Serializable {
//推荐语
private String recommend;
//亮点
private List<HighlightsRequest> highlightsRequestList;
//卖点
private List<SellingPointRequest> sellingPointRequestList;
//质检报告
private List<QualityControlRequest> qualityControlRequestList;
}
//亮点
@Data
public static class HighlightsRequest implements Serializable {
//亮点文案
private String highlights;
//排序(正整数,数字越小越靠前)
private Integer sort;
}
//卖点
@Data
public static class SellingPointRequest implements Serializable {
//卖点文案
private String sellingPoint;
//排序(正整数,数字越小越靠前)
private Integer sort;
}
//质检报告
@Data
public static class QualityControlRequest implements Serializable {
//商品skuId
private String skuId;
//质检报告名称
private String qcName;
//材料图片链接
private String qcImgUrl;
//排序(正整数,数字越小越靠前)
private Integer qcSort;
}
}
6.商品B端---转换建品请求数据为商品模型数据
前端的建品请求数据比较复杂,需要和后端的商品模型数据匹配起来,所以需要进行数据转换。这种数据转换,通常会用Builder模式来实现。
ini
@Service
public class ProductServiceImpl implements ProductService {
...
//建品/编辑商品
@Transactional(rollbackFor = Exception.class)
@Override
@ParamsValidate
public ProductDTO product(ProductRequest productRequest) {
//入参检查
checkProductRequestParam(productRequest);
//商品数据处理
ProductDTO productDTO = handleProduct(productRequest);
//返回商品信息
return productDTO;
}
//建品/编辑商品入参检查
private void checkProductRequestParam(ProductRequest productRequest) {
ParamCheckUtil.checkObjectNonNull(productRequest);
//商品基本信息
ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();
ParamCheckUtil.checkObjectNonNull(itemBaseRequest);
//存储信息
ProductRequest.ItemStorageRequest itemStorageRequest = productRequest.getItemStorageRequest();
ParamCheckUtil.checkObjectNonNull(itemStorageRequest);
//品控信息
ProductRequest.ShelfLifeRequest shelfLifeRequest = productRequest.getShelfLifeRequest();
ParamCheckUtil.checkObjectNonNull(shelfLifeRequest);
//图文信息
List<ProductRequest.ItemVideoImgRequest> itemVideoImgRequestList = productRequest.getItemVideoImgRequestList();
ParamCheckUtil.checkObjectNonNull(itemVideoImgRequestList);
//销售信息
ProductRequest.ItemSaleRequest itemSaleRequest = productRequest.getItemSaleRequest();
ParamCheckUtil.checkObjectNonNull(itemSaleRequest);
//推广信息
ProductRequest.ItemPopularizeRequest itemPopularizeRequest = productRequest.getItemPopularizeRequest();
ParamCheckUtil.checkObjectNonNull(itemPopularizeRequest);
}
//商品数据处理
private ProductDTO handleProduct(ProductRequest productRequest) {
//构建商品的全量信息
FullProductData fullProductData = buildProduct(productRequest);
//是否构建填充 itemId
Boolean createFlag = whetherBuildProductItemId(fullProductData);
//判断是否需要审核,根据配置的sku或者item或者品类信息,如果需要审核,只处理草稿表中的数据,正式表中的数据不动
if (productAuditRepository.needAudit(fullProductData, createFlag)) {
//需要审核,则正式表中的数据不变更,只新增草稿表记录
FullDraftData fullDraftData = buildDraft(fullProductData, AuditTypeEnum.GOODS.getCode());
//保存草稿信息
productAuditRepository.saveDraft(fullDraftData);
return new ProductDTO(null, null);
}
//如果不需要审核,则保存商品信息
this.saveOrUpdateDBProduct(fullProductData, createFlag);
//发送消息通知订阅方
sendUpdateProductMessage(fullProductData);
//返回商品返回结果
return new ProductDTO(fullProductData.getItemInfoDO().getItemId(), buildProductSkuIds(fullProductData));
}
//前端建品请求数据到后端商品数据模型的转换
private FullProductData buildProduct(ProductRequest productRequest) {
ProductBuilder productBuilder = new ProductBuilder(productRequest);
FullProductData fullProductData = productBuilder.buildItemInfo()
.buildItemShelfLife()
.buildItemVideoImgList()
.buildSkuInfoList()
.buildSkuBarCodeRelationList()
.buildCspuSkuRelation()
.buildAttributeExtend()
.buildQualityControl()
.build();
return fullProductData;
}
...
}
//全量商品数据
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FullProductData {
//ITEM信息
private ItemInfoDO itemInfoDO;
//保质期信息
private ItemShelfLifeDO itemShelfLifeDO;
//视频图片信息
private List<ItemVideoImgDO> itemVideoImgDOList;
//SKU信息
private List<SkuInfoDO> skuInfoDOList;
//69码关系
private List<SkuBarCodeRelationDO> skuBarCodeRelationDOList;
//CSPU与SKU关系
private List<CspuSkuRelationDO> cspuSkuRelationDOList;
//ITEM或SKU扩展属性
private AttributeExtendDO attributeExtendDO;
//品控信息
private List<QualityControlDO> qualityControlDOList;
public FullProductData(ItemInfoDO itemInfoDO, List<SkuInfoDO> skuInfoDOList) {
this.itemInfoDO = itemInfoDO;
this.skuInfoDOList = skuInfoDOList;
}
}
//全量商品数据
public class ProductBuilder {
//商品入参
private ProductRequest productRequest;
//全量商品数据
private FullProductData fullProductData;
public ProductBuilder(ProductRequest productRequest) {
this.productRequest = productRequest;
this.fullProductData = new FullProductData();
}
public ProductBuilder buildItemInfo() {
ItemInfoDO itemInfoDO = new ItemInfoDO();
ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();
itemInfoDO.setItemId(itemBaseRequest.getItemId());
itemInfoDO.setItemName(itemBaseRequest.getItemName());
...
fullProductData.setItemInfoDO(itemInfoDO);
return this;
}
public ProductBuilder buildItemShelfLife() {
ItemShelfLifeDO itemShelfLifeDO = new ItemShelfLifeDO();
ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();
ProductRequest.ShelfLifeRequest shelfLifeRequest = productRequest.getShelfLifeRequest();
itemShelfLifeDO.setItemId(itemBaseRequest.getItemId());
itemShelfLifeDO.setShelfLifeContent(JSON.toJSONString(shelfLifeRequest.getShelfLife()));
itemShelfLifeDO.setDelFlag(DelFlagEnum.EFFECTIVE.getCode());
...
fullProductData.setItemShelfLifeDO(itemShelfLifeDO);
return this;
}
public ProductBuilder buildItemVideoImgList() {
List<ItemVideoImgDO> itemVideoImgDOList = new ArrayList<>(16);
ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();
List<ProductRequest.ItemVideoImgRequest> itemVideoImgRequestList = productRequest.getItemVideoImgRequestList();
for (ProductRequest.ItemVideoImgRequest itemVideoImgRequest : itemVideoImgRequestList) {
ItemVideoImgDO itemVideoImgDO = new ItemVideoImgDO();
itemVideoImgDO.setItemId(itemBaseRequest.getItemId());
...
itemVideoImgDOList.add(itemVideoImgDO);
}
fullProductData.setItemVideoImgDOList(itemVideoImgDOList);
return this;
}
public ProductBuilder buildSkuInfoList() {
List<SkuInfoDO> skuInfoDOList = new ArrayList<>(16);
ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();
List<ProductRequest.SkuInfoRequest> skuInfoRequestList = productRequest.getItemSaleRequest().getSkuInfoRequestList();
for (ProductRequest.SkuInfoRequest skuInfoRequest : skuInfoRequestList) {
SkuInfoDO skuInfoDO = new SkuInfoDO();
skuInfoDO.setItemId(skuInfoRequest.getItemId());
skuInfoDO.setSkuId(skuInfoRequest.getSkuId());
skuInfoDO.setSkuName(skuInfoRequest.getSkuName());
...
skuInfoDOList.add(skuInfoDO);
}
fullProductData.setSkuInfoDOList(skuInfoDOList);
return this;
}
public ProductBuilder buildSkuBarCodeRelationList() {
List<SkuBarCodeRelationDO> skuBarCodeRelationDOList = new ArrayList<>(16);
List<ProductRequest.SkuInfoRequest> skuInfoRequestList = productRequest.getItemSaleRequest().getSkuInfoRequestList();
for (ProductRequest.SkuInfoRequest skuInfoRequest : skuInfoRequestList) {
SkuBarCodeRelationDO skuBarCodeRelationDO = new SkuBarCodeRelationDO();
skuBarCodeRelationDO.setSkuId(skuInfoRequest.getSkuId());
skuBarCodeRelationDO.setBarCode(skuInfoRequest.getBarCode());
...
skuBarCodeRelationDOList.add(skuBarCodeRelationDO);
}
fullProductData.setSkuBarCodeRelationDOList(skuBarCodeRelationDOList);
return this;
}
public ProductBuilder buildCspuSkuRelation() {
List<CspuSkuRelationDO> cspuSkuRelationDOList = new ArrayList<>(16);
ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();
List<ProductRequest.SkuInfoRequest> skuInfoRequestList = productRequest.getItemSaleRequest().getSkuInfoRequestList();
for (ProductRequest.SkuInfoRequest skuInfoRequest : skuInfoRequestList) {
CspuSkuRelationDO cspuSkuRelationDO = new CspuSkuRelationDO();
cspuSkuRelationDO.setSkuId(skuInfoRequest.getSkuId());
cspuSkuRelationDO.setCspuId(skuInfoRequest.getCspuId());
...
cspuSkuRelationDOList.add(cspuSkuRelationDO);
}
fullProductData.setCspuSkuRelationDOList(cspuSkuRelationDOList);
return this;
}
public ProductBuilder buildAttributeExtend() {
AttributeExtendDO attributeExtendDO = new AttributeExtendDO();
ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();
ProductRequest.ItemPopularizeRequest itemPopularizeRequest = productRequest.getItemPopularizeRequest();
attributeExtendDO.setParticipateId(itemBaseRequest.getItemId());
...
fullProductData.setAttributeExtendDO(attributeExtendDO);
return this;
}
public ProductBuilder buildQualityControl() {
List<QualityControlDO> qualityControlDOList = new ArrayList<>(16);
ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();
ProductRequest.ItemPopularizeRequest itemPopularizeRequest = productRequest.getItemPopularizeRequest();
List<ProductRequest.QualityControlRequest> qualityControlRequestList = itemPopularizeRequest.getQualityControlRequestList();
for (ProductRequest.QualityControlRequest qualityControlRequest : qualityControlRequestList) {
QualityControlDO qualityControlDO = new QualityControlDO();
qualityControlDO.setItemId(itemBaseRequest.getItemId());
qualityControlDO.setSkuId(qualityControlRequest.getSkuId());
...
qualityControlDOList.add(qualityControlDO);
}
fullProductData.setQualityControlDOList(qualityControlDOList);
return this;
}
public FullProductData build() {
return this.fullProductData;
}
}
7.商品B端---商品建品时商品编号补全与审核配置
scss
@Service
public class ProductServiceImpl implements ProductService {
...
//建品/编辑商品
@Transactional(rollbackFor = Exception.class)
@Override
@ParamsValidate
public ProductDTO product(ProductRequest productRequest) {
//入参检查
checkProductRequestParam(productRequest);
//商品数据处理
ProductDTO productDTO = handleProduct(productRequest);
//返回商品信息
return productDTO;
}
...
//商品数据处理
private ProductDTO handleProduct(ProductRequest productRequest) {
//构建商品的全量信息
FullProductData fullProductData = buildProduct(productRequest);
//是否构建填充itemId
Boolean createFlag = whetherBuildProductItemId(fullProductData);
//判断是否需要审核,根据配置的sku或者item或者品类信息,如果需要审核,只处理草稿表中的数据,正式表中的数据不动
if (productAuditRepository.needAudit(fullProductData, createFlag)) {
//需要审核,则正式表中的数据不变更,只新增草稿表记录
FullDraftData fullDraftData = buildDraft(fullProductData, AuditTypeEnum.GOODS.getCode());
//保存草稿信息
productAuditRepository.saveDraft(fullDraftData);
return new ProductDTO(null, null);
}
//如果不需要审核,则保存商品信息
this.saveOrUpdateDBProduct(fullProductData, createFlag);
//发送消息通知订阅方
sendUpdateProductMessage(fullProductData);
//返回商品返回结果
return new ProductDTO(fullProductData.getItemInfoDO().getItemId(), buildProductSkuIds(fullProductData));
}
//是否需要构建商品的ItemId
private Boolean whetherBuildProductItemId(FullProductData fullProductData) {
//ITEM信息
ItemInfoDO itemInfoDO = fullProductData.getItemInfoDO();
//新增
if (StringUtils.isEmpty(itemInfoDO.getItemId())) {
//保质期
ItemShelfLifeDO itemShelfLifeDO = fullProductData.getItemShelfLifeDO();
//生成Item的Id
String itemId = createItemId();
//赋值itemId
itemInfoDO.setItemId(itemId);
itemShelfLifeDO.setItemId(itemId);
//SKU信息
List<SkuInfoDO> skuInfoDOList = fullProductData.getSkuInfoDOList();
for (SkuInfoDO skuInfoDO : skuInfoDOList) {
//对每个SKU也生成ID
String skuId = productNoManager.generateProductNo(ProductTypeEnum.SKU.getCode());
skuInfoDO.setSkuId(skuId);
skuInfoDO.setItemId(itemId);
}
//视频图片
List<ItemVideoImgDO> itemVideoImgDOList = fullProductData.getItemVideoImgDOList();
for (ItemVideoImgDO itemVideoImgDO : itemVideoImgDOList) {
itemVideoImgDO.setItemId(itemId);
}
//属性扩展
AttributeExtendDO attributeExtendDO = fullProductData.getAttributeExtendDO();
attributeExtendDO.setParticipateId(itemInfoDO.getItemId());
attributeExtendDO.setParticipateType(ProductTypeEnum.ITEM.getCode());
return true;
}
return false;
}
//创建ItemId
private String createItemId() {
String itemId = productNoManager.generateProductNo(ProductTypeEnum.ITEM.getCode());
return itemId;
}
...
}
//商品审核 资源管理
@Repository
public class ProductAuditRepository {
...
//验证是否需要审核
public Boolean needAudit(FullProductData fullProductData, Boolean createFlag) {
ItemInfoDO itemInfoDO = fullProductData.getItemInfoDO();
Integer count = 0;
if (!createFlag) {
//1.首先判断 商品审核内容配置表 中是否有对应的skuId
List<String> skuIds = fullProductData.getSkuInfoDOList().stream().map(SkuInfoDO::getSkuId).collect(Collectors.toList());
count = countByCustomIds(skuIds, AuditCustomTypeEnum.SKU);
if (count > 0) {
return true;
}
//2.是否有对应的item
count = countByCustomIds(Collections.singletonList(itemInfoDO.getItemId()), AuditCustomTypeEnum.ITEM);
if (count > 0) {
return true;
}
}
//3.验证是否有对应的categoryId
List<Integer> categoryIds = Arrays.asList(itemInfoDO.getFirstCategoryId(), itemInfoDO.getSecondCategoryId(), itemInfoDO.getThirdCategoryId());
count = countByCustomIds(categoryIds, AuditCustomTypeEnum.CATEGORY);
//当商品审核内容配置表中有相应的品类数据,则需要审核,否则不需要审核
return count > 0;
}
...
}
8.商品B端---商品审核前的草稿数据保存逻辑
scss
@Service
public class ProductServiceImpl implements ProductService {
...
//建品/编辑商品
@Transactional(rollbackFor = Exception.class)
@Override
@ParamsValidate
public ProductDTO product(ProductRequest productRequest) {
//入参检查
checkProductRequestParam(productRequest);
//商品数据处理
ProductDTO productDTO = handleProduct(productRequest);
//返回商品信息
return productDTO;
}
...
//商品数据处理
private ProductDTO handleProduct(ProductRequest productRequest) {
//构建商品的全量信息
FullProductData fullProductData = buildProduct(productRequest);
//是否构建填充itemId
Boolean createFlag = whetherBuildProductItemId(fullProductData);
//判断是否需要审核,根据配置的sku或者item或者品类信息,如果需要审核,只处理草稿表中的数据,正式表中的数据不动
if (productAuditRepository.needAudit(fullProductData, createFlag)) {
//需要审核,则正式表中的数据不变更,只新增草稿表记录
FullDraftData fullDraftData = buildDraft(fullProductData, AuditTypeEnum.GOODS.getCode());
//保存草稿信息
productAuditRepository.saveDraft(fullDraftData);
return new ProductDTO(null, null);
}
//如果不需要审核,则保存商品信息
this.saveOrUpdateDBProduct(fullProductData, createFlag);
//发送消息通知订阅方
sendUpdateProductMessage(fullProductData);
//返回商品返回结果
return new ProductDTO(fullProductData.getItemInfoDO().getItemId(), buildProductSkuIds(fullProductData));
}
...
//根据商品数据构建商品草稿数据
private FullDraftData buildDraft(FullProductData fullProductData, Integer auditType) {
ProductDraftBuilder productDraftBuilder = new ProductDraftBuilder(fullProductData);
FullDraftData fullDraftData = productDraftBuilder.buildDraftMain(auditType)
.buildDraftImgList()
.build();
return fullDraftData;
}
...
}
//商品审核 资源管理
@Repository
public class ProductAuditRepository {
...
//保存草稿信息
public void saveDraft(FullDraftData fullDraftData) {
//1.保存工单信息
AuditInfoDO auditInfoDO = saveAudit(fullDraftData);
//2.保存工单审核历史信息
saveAuditHistory(auditInfoDO);
//3.保存草稿信息
saveDraftMain(fullDraftData, auditInfoDO.getId());
//4.保存草稿图片信息
saveDraftImgBatch(fullDraftData);
}
//保存工单信息
private AuditInfoDO saveAudit(FullDraftData fullDraftData) {
AuditInfoDO auditInfoDO = auditConverter.converterDO(fullDraftData.getDraftMainDO());
auditInfoDO.initCommon();
int count = auditInfoMapper.insert(auditInfoDO);
if (count <= 0) {
throw new ProductBizException(AuditExceptionCode.AUDIT_SQL.getErrorCode(), "保存工单失败");
}
return auditInfoDO;
}
//保存工单审核历史信息
private void saveAuditHistory(AuditInfoDO auditInfoDO) {
AuditHistoryDO auditHistoryDO = auditConverter.converterHistoryDO(auditInfoDO);
int count = this.auditHistoryMapper.insert(auditHistoryDO);
if (count <= 0) {
throw new ProductBizException(AuditExceptionCode.AUDIT_SQL.getErrorCode(), "保存工单审核历史信息失败");
}
}
//保存草稿信息
private void saveDraftMain(FullDraftData fullDraftData, Long auditId) {
DraftMainDO draftMainDO = fullDraftData.getDraftMainDO();
draftMainDO.setTicketId(auditId);
int count = draftMainMapper.insert(draftMainDO);
if (count <= 0) {
throw new ProductBizException(AuditExceptionCode.AUDIT_SQL.getErrorCode(), "保存草稿审核信息失败");
}
}
//保存草稿图片信息
private void saveDraftImgBatch(FullDraftData fullDraftData) {
List<DraftImgDO> draftImgDOS = fullDraftData.getDraftImgDOS();
if (!CollectionUtils.isEmpty(draftImgDOS)) {
for (DraftImgDO draftImgDO : draftImgDOS) {
draftImgDO.setDraftId(fullDraftData.getDraftMainDO().getId());
}
draftImgMapper.saveBatch(draftImgDOS);
}
}
...
}
9.商品B端---不需审核的建品流程持久化逻辑
scss
@Service
public class ProductServiceImpl implements ProductService {
...
//商品数据处理
private ProductDTO handleProduct(ProductRequest productRequest) {
//构建商品的全量信息
FullProductData fullProductData = buildProduct(productRequest);
//是否构建填充itemId
Boolean createFlag = whetherBuildProductItemId(fullProductData);
//判断是否需要审核,根据配置的sku或者item或者品类信息,如果需要审核,只处理草稿表中的数据,正式表中的数据不动
if (productAuditRepository.needAudit(fullProductData, createFlag)) {
//需要审核,则正式表中的数据不变更,只新增草稿表记录
FullDraftData fullDraftData = buildDraft(fullProductData, AuditTypeEnum.GOODS.getCode());
//保存草稿信息
productAuditRepository.saveDraft(fullDraftData);
return new ProductDTO(null, null);
}
//如果不需要审核,则保存商品信息
this.saveOrUpdateDBProduct(fullProductData, createFlag);
//发送消息通知订阅方
sendUpdateProductMessage(fullProductData);
//返回商品返回结果
return new ProductDTO(fullProductData.getItemInfoDO().getItemId(), buildProductSkuIds(fullProductData));
}
//新增或者修改商品相关信息
@Override
public void saveOrUpdateDBProduct(FullProductData fullProductData, Boolean createFlag) {
if (createFlag) {
//新增
productInfoRepository.saveItemInfo(fullProductData);
} else {
//修改
productInfoRepository.updateItemInfo(fullProductData);
}
}
...
}
//商品 资源管理
@Repository
public class ProductInfoRepository {
...
//保存商品的明细信息
public void saveItemInfo(FullProductData fullProductData) {
//保存商品Item的信息
saveItemInfo(fullProductData.getItemInfoDO());
//保存商品保质期信息
saveItemShelfLife(fullProductData.getItemShelfLifeDO());
//批量保存商品图片视频信息
saveBatchVideoImg(fullProductData.getItemVideoImgDOList());
//批量保存商品sku信息
saveBatchSkuInfo(fullProductData.getSkuInfoDOList());
//批量保存69码信息
saveBatchSkuBarCodeRelation(fullProductData.getSkuBarCodeRelationDOList());
//批量保存CSPU与SKU关系
saveBatchCspuSkuRelation(fullProductData.getCspuSkuRelationDOList());
//批量保存品控信息
saveBatchQualityControl(fullProductData.getQualityControlDOList());
//保存ITEM或SKU扩展属性
saveAttributeExtend(fullProductData.getAttributeExtendDO());
}
//修改商品的明细信息
public void updateItemInfo(FullProductData fullProductData) {
//更新商品item信息
updateItemInfo(fullProductData.getItemInfoDO());
//更新商品保质期信息
updateItemShelfLife(fullProductData.getItemShelfLifeDO());
//更新商品扩展信息
updateAttributeExtend(fullProductData.getAttributeExtendDO());
//更新商品的视频图片信息
batchUpdateVideoImg(fullProductData.getItemVideoImgDOList());
//批量更新商品sku信息
batchUpdateSkuInfo(fullProductData.getSkuInfoDOList());
//批量更新商品的69规格
batchUpdateSkuBarCodeRelation(fullProductData.getSkuBarCodeRelationDOList());
//批量更新 CSPU与SKU关系
batchUpdateCspuSkuRelation(fullProductData.getCspuSkuRelationDOList());
//批量更新品控信息
batchUpdateQualityControl(fullProductData.getQualityControlDOList());
}
...
//保存商品Item的信息
private void saveItemInfo(ItemInfoDO itemInfoDO) {
int count = itemInfoMapper.insert(itemInfoDO);
if (count <= 0) {
throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);
}
}
//保存商品保质期信息
private void saveItemShelfLife(ItemShelfLifeDO itemShelfLifeDO) {
int count = itemShelfLifeMapper.insert(itemShelfLifeDO);
if (count <= 0) {
throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);
}
}
//批量保存商品图片视频信息
private void saveBatchVideoImg(List<ItemVideoImgDO> itemVideoImgDOList) {
int count = itemVideoImgMapper.saveBatch(itemVideoImgDOList);
if (count <= 0) {
throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);
}
}
//批量保存商品sku信息
private void saveBatchSkuInfo(List<SkuInfoDO> skuInfoDOList) {
int count = skuInfoMapper.saveBatch(skuInfoDOList);
if (count <= 0) {
throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);
}
}
//批量保存69码信息
private void saveBatchSkuBarCodeRelation(List<SkuBarCodeRelationDO> skuBarCodeRelationDOList) {
int count = skuBarCodeRelationMapper.saveBatch(skuBarCodeRelationDOList);
if (count <= 0) {
throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);
}
}
//批量保存CSPU与SKU关系
private void saveBatchCspuSkuRelation(List<CspuSkuRelationDO> cspuSkuRelationDOList) {
int count = cspuSkuRelationMapper.saveBatch(cspuSkuRelationDOList);
if (count <= 0) {
throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);
}
}
//批量保存品控信息
private void saveBatchQualityControl(List<QualityControlDO> qualityControlDOList) {
int count = qualityControlMapper.saveBatch(qualityControlDOList);
if (count <= 0) {
throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);
}
}
//保存 ITEM或SKU扩展属性
private void saveAttributeExtend(AttributeExtendDO attributeExtendDO) {
int count = attributeExtendMapper.insert(attributeExtendDO);
if (count <= 0) {
throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);
}
}
...
}
10.商品B端---审核工单分页列表和商品草稿查询
scss
//审批服务
@DubboService(version = "1.0.0", interfaceClass = AuditApi.class, retries = 0)
public class AuditApiImpl implements AuditApi {
@Autowired
private AuditService auditService;
@Override
public JsonResult<PageResult<AuditInfoDTO>> getTodoList(QueryTodoListRequest request) {
try {
//审核工单分页列表
PageResult<AuditInfoDTO> todoList = auditService.getTodoList(request);
return JsonResult.buildSuccess(todoList);
} catch (ProductBizException e) {
log.error("biz error: request={}", JSON.toJSONString(request), e);
return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
} catch (Exception e) {
log.error("system error: request={}", JSON.toJSONString(request), e);
return JsonResult.buildError(e.getMessage());
}
}
@Override
public JsonResult<DraftDetailDTO> getDraftDetail(QueryDraftRequest request) {
try {
//商品草稿查询
DraftDetailDTO draftDetailDTO = auditService.getDraftDetail(request);
return JsonResult.buildSuccess(draftDetailDTO);
} catch (ProductBizException e) {
log.error("biz error: request={}", JSON.toJSONString(request), e);
return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
} catch (Exception e) {
log.error("system error: request={}", JSON.toJSONString(request), e);
return JsonResult.buildError(e.getMessage());
}
}
...
}
@Service
public class AuditServiceImpl implements AuditService {
...
//获取审核的代办列表
@Override
public PageResult<AuditInfoDTO> getTodoList(QueryTodoListRequest queryTodoListRequest) {
//获取用户审核角色
AuditorListConfigDO auditor = productAuditRepository.getAuditorRuleByUserId(queryTodoListRequest.getUserId());
//返回待办列表
return productAuditRepository.pageResult(queryTodoListRequest, auditor);
}
//查询草稿详情信息
@Override
public DraftDetailDTO getDraftDetail(QueryDraftRequest queryDraftRequest) {
//草稿详情信息
DraftDetailDTO draftDetailDTO = productAuditRepository.getDraftDetail(queryDraftRequest.getTicketId());
//构建需要比较不同的字段数据
buildDiffChangeField(draftDetailDTO);
return draftDetailDTO;
}
//构建需要比较不同的字段的数据
private void buildDiffChangeField(DraftDetailDTO draftDetailDTO) {
//草稿主表信息
DraftMainDTO draftMainDTO = draftDetailDTO.getDraftMainDTO();
//修改后的商品数据
FullProductData fullProductData = JSON.parseObject(draftMainDTO.getFeatures(), FullProductData.class);
//商品新增时,item版本号是0,草稿表中的版本号是item表中的版本号加1
//所以此时判断草稿表中的版本号是小于等于1表示新增数据
if (draftMainDTO.getVersionId() <= 1) {
buildAddDiff(fullProductData, draftDetailDTO);
} else {
buildUpdateDiff(fullProductData, draftDetailDTO);
}
}
...
}
//商品审核 资源管理
@Repository
public class ProductAuditRepository {
...
//获取用户审核角色
public AuditorListConfigDO getAuditorRuleByUserId(Integer userId) {
LambdaQueryWrapper<AuditorListConfigDO> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(AuditorListConfigDO::getAuditorId, userId);
AuditorListConfigDO auditorListConfigDO = auditorListConfigMapper.selectOne(queryWrapper);
//判断是否查询到对应的权限信息
if (Objects.isNull(auditorListConfigDO)) {
throw new ProductBizException(AuditExceptionCode.USER_AUDIT_RULE_NULL);
}
return auditorListConfigDO;
}
//获取用户可审核的详细列表
public PageResult<AuditInfoDTO> pageResult(QueryTodoListRequest queryTodoListRequest, AuditorListConfigDO auditor) {
LambdaQueryWrapper<AuditInfoDO> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(AuditInfoDO::getTicketStatus, AuditStatusEnum.UNAUDITED.getCode());
Page<AuditInfoDO> page = new Page<>(queryTodoListRequest.getPageNum(), queryTodoListRequest.getPageSize());
Integer auditorRole = auditor.getAuditorRole();
//不是拥有所有审核权限,则增加限定条件,指定是建品审核或者是价格审核
if (!Objects.equals(AuditorRoleEnum.ADMIN.getCode(), auditorRole)) {
queryWrapper.eq(AuditInfoDO::getTicketType, auditorRole);
}
//根据角色查询待办列表
return auditConverter.converterPageResult(auditInfoMapper.selectPage(page, queryWrapper));
}
//查询草稿明细信息
public DraftDetailDTO getDraftDetail(Long ticketId) {
//1.查询草稿主表信息
DraftMainDTO draftMainDTO = auditConverter.convertDTO(getByTicketId(ticketId));
//2.查询草稿图片列表信息
List<DraftImgDTO> draftImgDTOS = getByDraft(draftMainDTO);
//返回草稿的主体信息
return new DraftDetailDTO(draftMainDTO, draftImgDTOS);
}
...
}
11.商品B端---商品审核时的敏感字段diff计算逻辑
审核时需要把Item和SKU的敏感字段的diff值显示出来,方便审核员审核。
scss
@Service
public class AuditServiceImpl implements AuditService {
...
//查询草稿详情信息
@Override
public DraftDetailDTO getDraftDetail(QueryDraftRequest queryDraftRequest) {
//草稿详情信息
DraftDetailDTO draftDetailDTO = productAuditRepository.getDraftDetail(queryDraftRequest.getTicketId());
//构建需要比较不同的字段数据
buildDiffChangeField(draftDetailDTO);
return draftDetailDTO;
}
//构建需要比较不同的字段的数据
private void buildDiffChangeField(DraftDetailDTO draftDetailDTO) {
//草稿主表信息
DraftMainDTO draftMainDTO = draftDetailDTO.getDraftMainDTO();
//修改后的商品数据
FullProductData fullProductData = JSON.parseObject(draftMainDTO.getFeatures(), FullProductData.class);
//商品新增时,item版本号是0,草稿表中的版本号是item表中的版本号加1
//所以此时判断草稿表中的版本号是小于等于1表示新增数据
if (draftMainDTO.getVersionId() <= 1) {
buildAddDiff(fullProductData, draftDetailDTO);
} else {
buildUpdateDiff(fullProductData, draftDetailDTO);
}
}
//填充新增的 商品差异变化信息
private void buildAddDiff(FullProductData fullProductData, DraftDetailDTO draftDetailDTO) {
//item信息
ItemInfoDO itemInfoDO = fullProductData.getItemInfoDO();
List<DiffValue> itemDiffValues = DiffFieldUtil.buildDiffField(itemInfoDO, null, itemDiffFields);
//skuList diff 存放Map集合
Map<String, List<DiffValue>> skuDiffFieldsMap = null;
//sku信息
List<SkuInfoDO> skuInfoDOList = fullProductData.getSkuInfoDOList();
if (!CollectionUtils.isEmpty(skuInfoDOList)) {
skuDiffFieldsMap = new HashMap<>(skuInfoDOList.size());
for (SkuInfoDO skuInfoDO : skuInfoDOList) {
List<DiffValue> skuDiffValues = DiffFieldUtil.buildDiffField(skuInfoDO, null, skuDiffFields);
if (!CollectionUtils.isEmpty(skuDiffValues)) {
skuDiffFieldsMap.put(skuInfoDO.getSkuId(), skuDiffValues);
}
}
}
//填充商品数据变更的差异信息
buildDiffInfo(itemDiffValues, skuDiffFieldsMap, draftDetailDTO);
}
//填充商品数据变更的差异信息
private void buildDiffInfo(List<DiffValue> itemDiffValues, Map<String, List<DiffValue>> skuDiffFieldsMap, DraftDetailDTO draftDetailDTO) {
//item变更字段
if (!CollectionUtils.isEmpty(itemDiffValues)) {
draftDetailDTO.setItemDiffFields(itemDiffValues);
}
//sku变更字段
if (!CollectionUtils.isEmpty(skuDiffFieldsMap)) {
draftDetailDTO.setSkuDiffFields(skuDiffFieldsMap);
}
}
//填充修改的 商品差异变化信息
private void buildUpdateDiff(FullProductData fullProductData, DraftDetailDTO draftDetailDTO) {
//item信息
ItemInfoDO itemInfoDO = fullProductData.getItemInfoDO();
//先查询修改前itemInfoDO和修改前的skuInfoDOList,再比较变更值
ItemInfoDO oldItemInfoDO = productInfoRepository.getItemByItemId(itemInfoDO.getItemId());
List<DiffValue> itemDiffValues = DiffFieldUtil.buildDiffField(itemInfoDO, oldItemInfoDO, itemDiffFields);
List<SkuInfoDO> oldSkuInfoDOList = productInfoRepository.listSkuByItemId(itemInfoDO.getItemId());
List<SkuInfoDO> skuInfoDOList = fullProductData.getSkuInfoDOList();
List<DiffValue> skuDiffValues;
//skuList diff 存放Map集合
Map<String, List<DiffValue>> skuDiffFieldsMap = new HashMap<>();
//旧的商品集合转换
Map<String, SkuInfoDO> oldMap = oldSkuInfoDOList.stream().collect(Collectors.toMap(SkuInfoDO::getSkuId, e -> e));
for (SkuInfoDO skuInfoDO : skuInfoDOList) {
if (oldMap.containsKey(skuInfoDO.getSkuId())) {
SkuInfoDO oldSkuInfoDO = oldMap.get(skuInfoDO.getSkuId());
skuDiffValues = DiffFieldUtil.buildDiffField(skuInfoDO, oldSkuInfoDO, skuDiffFields);
if (!CollectionUtils.isEmpty(skuDiffValues)) {
skuDiffFieldsMap.put(skuInfoDO.getSkuId(), skuDiffValues);
}
}
}
//填充修改的商品信息
buildDiffInfo(itemDiffValues, skuDiffFieldsMap, draftDetailDTO);
}
...
}
public class DiffFieldUtil {
public static List<DiffValue> buildDiffField(Object newObj, Object oldObj, List<String> diffFields) {
//oldObj为null表示新增,如果newObj与oldObj类型不同,则不处理
if (!Objects.isNull(oldObj) && !newObj.getClass().equals(oldObj.getClass())) {
return null;
}
List<DiffValue> diffValues = new ArrayList<>();
Field[] newObjFields = newObj.getClass().getDeclaredFields();
Field[] oldObjFields = null;
if (!Objects.isNull(oldObj)) {
oldObjFields = oldObj.getClass().getDeclaredFields();
}
for (int i = 0; i < newObjFields.length; i++) {
Field newObjField = newObjFields[i];
//需要比较当前字段
String fieldName = newObjField.getName();
if (diffFields.contains(fieldName)) {
try {
Object newValue = newObjField.get(fieldName);
if (Objects.isNull(oldObjFields) || !Objects.equals(oldObjFields[i].get(fieldName), newValue)) {
DiffValue diffValue = new DiffValue();
diffValue.setField(fieldName);
diffValue.setOldValue(Objects.isNull(oldObjFields) ? null : oldObjFields[i].get(fieldName));
diffValue.setNewValue(newValue);
diffValues.add(diffValue);
}
} catch (IllegalAccessException e) {
log.error("获取字段值失败", e);
}
}
}
return diffValues;
}
}
12.商品B端---对草稿中的商品进行审核的逻辑
scss
//审批服务
@DubboService(version = "1.0.0", interfaceClass = AuditApi.class, retries = 0)
public class AuditApiImpl implements AuditApi {
@Autowired
private AuditService auditService;
...
@Override
public JsonResult<ExecAuditDTO> execAudit(AuditRequest request) {
try {
ExecAuditDTO execAuditDTO = auditService.execAudit(request);
return JsonResult.buildSuccess(execAuditDTO);
} catch (ProductBizException e) {
log.error("biz error: request={}", JSON.toJSONString(request), e);
return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
} catch (Exception e) {
log.error("system error: request={}", JSON.toJSONString(request), e);
return JsonResult.buildError(e.getMessage());
}
}
}
//审核请求入参
@Data
public class AuditRequest extends BaseEntity implements Serializable {
//工单id
private Long ticketId;
//审核状态 1-通过 3-拒绝
private Integer auditStatus;
//拒绝原因
private String rejectReason;
//操作人
private Integer operatorUser;
}
@Service
public class AuditServiceImpl implements AuditService {
...
//执行审核
@Transactional
@Override
public ExecAuditDTO execAudit(AuditRequest auditRequest) {
//验证是否有可以审核,并填充审核信息
AuditInfoDTO auditInfoDTO = productAuditRepository.checkAudit(auditRequest);
//执行审核
execGoodsAudit(auditRequest, auditInfoDTO);
//处理审核的信息DB变更
productAuditRepository.updateAudit(auditRequest, auditInfoDTO);
return new ExecAuditDTO(Boolean.TRUE);
}
//商品审核
private void execGoodsAudit(AuditRequest auditRequest, AuditInfoDTO auditInfoDTO) {
DraftMainDTO draftMainDTO = auditInfoDTO.getDraftMainDTO();
Integer ticketType = auditInfoDTO.getTicketType();
//如果是审批通过,则需要更改正式表的数据
if (Objects.equals(auditRequest.getAuditStatus(), AuditStatusEnum.PASS.getCode())) {
FullProductData fullProductData = JSON.parseObject(draftMainDTO.getFeatures(), FullProductData.class);
//建品审核
if (Objects.equals(ticketType, AuditTypeEnum.GOODS.getCode())) {
fullProductData.getItemInfoDO().setVersionId(draftMainDTO.getVersionId());
//产品信息入库;版本号小于等于1,表示新增,否则表示修改
if (fullProductData.getItemInfoDO().getVersionId() <= 1) {
productInfoRepository.saveItemInfo(fullProductData);
} else {
productInfoRepository.updateItemInfo(fullProductData);
}
} else if (Objects.equals(ticketType, AuditTypeEnum.PRICE.getCode())) {
SkuInfoDO skuInfoDO = fullProductData.getSkuInfoDOList().get(0);
productInfoRepository.saveRecord(skuInfoDO);
}
}
}
...
}
//商品审核 资源管理
@Repository
public class ProductAuditRepository {
...
//验证是否可审核,并返回审核对象
public AuditInfoDTO checkAudit(AuditRequest auditRequest) {
Long ticketId = auditRequest.getTicketId();
//查询审核工单
AuditInfoDO auditInfoDO = auditInfoMapper.selectById(ticketId);
if (Objects.isNull(auditInfoDO)) {
throw new ProductBizException(AuditExceptionCode.USER_AUDIT_INFO_NULL);
}
AuditInfoDTO auditInfoDTO = auditConverter.convertAuditDTO(auditInfoDO);
//获取审核工单的详情
DraftMainDO draftMainDO = getByTicketId(ticketId);
if (Objects.isNull(draftMainDO)) {
throw new ProductBizException(AuditExceptionCode.USER_AUDIT_INFO_NULL.getErrorCode(), "审核工单详情信息不存在");
}
//验证权限是否满足
AuditorListConfigDO auditorListConfigDO = getAuditorRuleByUserId(auditRequest.getOperatorUser());
if (Objects.isNull(auditorListConfigDO)) {
throw new ProductBizException(AuditExceptionCode.USER_AUDIT_RULE_NULL);
}
//不是超级审核权限,并且拥有的审核权限与审核类型不一致
if (!Objects.equals(AuditorRoleEnum.ADMIN.getCode(), auditorListConfigDO.getAuditorRole())
&& !Objects.equals(draftMainDO.getTicketType(), auditorListConfigDO.getAuditorRole())) {
throw new ProductBizException(ProductErrorCodeEnum.AUDIT_ERROR);
}
auditInfoDTO.setDraftMainDTO(auditConverter.convertDTO(draftMainDO));
return auditInfoDTO;
}
//修改审核信息
public void updateAudit(AuditRequest auditRequest, AuditInfoDTO auditInfoDTO) {
DraftMainDTO draftMainDTO = auditInfoDTO.getDraftMainDTO();
//软删除草稿表数据
deleteDraftMain(draftMainDTO);
//修改审核表信息
updateAudit(auditInfoDTO, auditRequest);
//新增审核历史记录
saveAuditHistory(auditRequest);
}
//逻辑删除草稿表数据
private void deleteDraftMain(DraftMainDTO draftMainDTO) {
DraftMainDO draftMainDO = auditConverter.converterDO(draftMainDTO);
draftMainDO.setDelFlag(DelFlagEnum.DISABLED.getCode());
//草稿表数据删除
int count = draftMainMapper.updateById(draftMainDO);
if (count <= 0) {
throw new ProductBizException(AuditExceptionCode.AUDIT_SQL);
}
}
//修改审核表信息
private void updateAudit(AuditInfoDTO auditInfoDTO, AuditRequest auditRequest) {
AuditInfoDO auditInfoDO = auditConverter.convertAuditDO(auditInfoDTO);
auditInfoDO.setTicketStatus(auditRequest.getAuditStatus());
auditInfoDO.setUpdateUser(auditRequest.getOperatorUser());
auditInfoDO.setUpdateTime(new Date());
int count = this.auditInfoMapper.updateById(auditInfoDO);
if (count <= 0) {
throw new ProductBizException(AuditExceptionCode.AUDIT_SQL);
}
}
//新增审核历史记录
private void saveAuditHistory(AuditRequest auditRequest) {
AuditHistoryDO auditHistoryDO = auditConverter.converterHistoryDO(auditRequest);
auditHistoryDO.initCommon();
int count = this.auditHistoryMapper.insert(auditHistoryDO);
if (count <= 0) {
throw new ProductBizException(AuditExceptionCode.AUDIT_SQL);
}
}
...
}
13.商品B端---商品属性 + 买手 + 品类的数据维护
(1)商品属性数据维护
(2)买手数据维护
(3)品类数据维护
(1)商品属性数据维护
typescript
//新增/编辑规格请求入参
@Data
public class AttributeRequest implements Serializable {
//规格键信息
private AttributeKeyRequest attributeKeyRequest;
//规格值信息
private List<AttributeValueRequest> attributeValueRequests;
//操作人
@NotNull(message = "操作人[operateUser]不能为空")
private Integer operateUser;
@Data
public static class AttributeKeyRequest implements Serializable {
//属性key编码
private String keyCode;
//属性key名称
private String keyName;
//扩展字段
private String features;
//排序
private Integer keySort;
//删除标记(1-有效,0-删除)
private Integer delFlag;
}
@Data
public static class AttributeValueRequest implements Serializable {
//属性key编码
private String keyCode;
//属性value名称
private String valueName;
//扩展字段
private String features;
//排序
private Integer valueSort;
//删除标记(1-有效,0-删除)
private Integer delFlag;
}
}
//规格服务
@Service
public class AttributeServiceImpl implements AttributeService {
@Resource
private AttributeRepository attributeRepository;
//新增/编辑规格键值接口
@Transactional(rollbackFor = Exception.class)
@Override
public AttributeResultDTO saveAttribute(AttributeRequest attributeRequest) {
//入参检查
this.checkAttributeRequestParam(attributeRequest);
//保存规格信息
attributeRepository.saveAttribute(attributeRequest);
//返回结果
return new AttributeResultDTO(Boolean.TRUE);
}
//入参检查
private void checkAttributeRequestParam(AttributeRequest attributeRequest) {
ParamCheckUtil.checkObjectNonNull(attributeRequest);
//规格键信息
AttributeRequest.AttributeKeyRequest attributeKeyRequest = attributeRequest.getAttributeKeyRequest();
ParamCheckUtil.checkObjectNonNull(attributeKeyRequest);
//规格值信息
List<AttributeRequest.AttributeValueRequest> attributeValueRequests = attributeRequest.getAttributeValueRequests();
ParamCheckUtil.checkCollectionNonEmpty(attributeValueRequests);
}
...
}
(2)买手数据维护
typescript
//新增/编辑买手请求入参
@Data
public class BuyerRequest implements Serializable {
private Long id;
//真实姓名
private String realName;
//花名
private String roster;
//买手图像
private String imageUrl;
//介绍
private String description;
//负责的品类ID
private String categoryId;
//删除标记(1-有效,0-删除)
private Integer delFlag;
//操作人
@NotNull(message = "操作人[operateUser]不能为空")
private Integer operateUser;
}
//买手服务
@Service
public class BuyerServiceImpl implements BuyerService {
@Resource
private BuyerRepository buyerRepository;
@Override
public BuyerResultDTO saveBuyer(BuyerRequest buyerRequest) {
//保存买手信息
buyerRepository.saveOrUpdate(buyerRequest);
//返回结果信息
return new BuyerResultDTO(Boolean.TRUE);
}
@Override
public BuyerListDTO getBuyerInfo(QueryBuyerListRequest queryBuyerListRequest) {
List<BuyerInfoDTO> buyerInfoDTOS = buyerRepository.listBuyerInfo(queryBuyerListRequest);
//返回信息
return new BuyerListDTO(buyerInfoDTOS);
}
@Override
public PageResult<BuyerInfoDTO> getBuyerInfoPage(QueryBuyerPageRequest queryBuyerPageRequest) {
return buyerRepository.pageResult(queryBuyerPageRequest);
}
}
(3)品类数据维护
typescript
//新增/编辑品类请求入参
@Data
public class CategoryRequest implements Serializable {
//id
private Long id;
//品类名称
@NotNull(message = "品类名称[categoryName]不能为空")
private String categoryName;
//父ID(一级类目父ID为0)
private Integer parentId;
//排序(正整数,数字越小越靠前)
@NotNull(message = "排序[categorySort]不能为空")
private Integer categorySort;
//图标icon
private String icon;
//目录是否展示(1-是,0-否)
private Integer showMark;
//是否是末级类目
@NotNull(message = "末级类目[lastFlag]不能为空")
private Integer lastFlag;
//渠道(1-每日生鲜、2-美团、3-饿了么、4-淘鲜达、5-招商银行)
@NotNull(message = "渠道[channel]不能为空")
private Integer channel;
//卖家类型(1-自营,2-POP)
@NotNull(message = "卖家类型[sellerType]不能为空")
private Integer sellerType;
//扩展字段
private String feature;
//删除标记(1-有效,0-删除)
private Integer delFlag;
//操作人
@NotNull(message = "操作人[operateUser]不能为空")
private Integer operateUser;
}
//商品品类信息
@Service
public class CategoryInfoServiceImpl implements CategoryInfoService {
@Resource
private CategoryRepository categoryRepository;
@Resource
private CategoryInfoConverter categoryInfoConverter;
//查询品类树
@Override
public List<CategoryInfoTreeDTO> selectTree(QueryCategoryRequest categoryQueryRequest) {
return categoryInfoConverter.converterTreeList(categoryRepository.selectTree(categoryQueryRequest));
}
//查询某个层级下的品类树(默认不带条件查询父类)
@Override
public List<CategoryInfoDTO> selectChild(QueryCategoryRequest categoryQueryRequest) {
//查询某个层级的品类树
List<CategoryInfoDO> categoryInfoList = categoryRepository.listBy(categoryQueryRequest);
//返回查询结果
return categoryInfoConverter.converterList(categoryInfoList);
}
//保存/修改品类信息
@Override
public CategoryResultDTO saveCategory(CategoryRequest categoryRequest) {
//保存品类树
categoryRepository.saveOrUpdate(categoryRequest);
//返回结果信息
return new CategoryResultDTO(Boolean.TRUE);
}
//查询品类信息列表
@Override
public List<CategoryInfoDTO> selectListByLike(QueryCategoryListRequest categoryListRequest) {
return categoryInfoConverter.converterList(categoryRepository.selectListByLike(categoryListRequest));
}
}
14.商品C端---通用缓存读写组件的实现逻辑
下面以获取前台类目为例,去说明先读缓存再读DB的通用缓存读写组件的逻辑。
FrontCategoryCache继承自Redis缓存抽象类AbstractRedisStringCache,这个抽象类中会有一个模版方法listRedisStringData(),该方法可以根据关键字来批量获取数据,并且会调用通用缓存读写组件的listRedisStringDataByCache()方法。
其中,listRedisStringDataByCache()方法需要传入两个方法:一个是获取Redis的key的方法,一个是从DB查询数据的方法。
scss
//商品前台类目服务
@DubboService(version = "1.0.0", interfaceClass = FrontCategoryApi.class, retries = 0)
public class FrontCategoryApiImpl implements FrontCategoryApi {
@Resource
private FrontCategoryCache frontCategoryStringSource;
@Resource
private FrontCategoryConverter frontCategoryConverter;
//基于通用缓存读写组件,去获取前台类目
@Override
public JsonResult<List<FrontCategoryDTO>> getFrontCategory(FrontCategoryQuery frontCategoryQuery) {
//入参校验
checkParams(frontCategoryQuery);
List<String> frontCategoryIdList = Arrays.asList(String.valueOf(frontCategoryQuery.getFrontCategoryId()));
//基于通用缓存读写组件,先读缓存再读DB来获取前台类目
Optional<List<FrontCategoryBO>> optional = frontCategoryStringSource.listRedisStringData(frontCategoryIdList);
if (!optional.isPresent()) {
JsonResult.buildSuccess();
}
List<FrontCategoryDTO> frontCategoryDTOList = frontCategoryConverter.converterFrontCategoryList(optional.get());
return JsonResult.buildSuccess(frontCategoryDTOList);
}
...
}
//Redis(String)缓存抽象类:<DO>是数据对象、<BO>是缓存对象
public abstract class AbstractRedisStringCache<DO, BO> {
@Resource
private RedisReadWriteManager redisReadWriteManager;
...
//根据关键字批量获取数据
public Optional<List<BO>> listRedisStringData(List<String> keyList) {
if (CollectionUtils.isEmpty(keyList)) {
return Optional.empty();
}
//下面会调用通用缓存读写组件RedisReadWriteManager的listRedisStringDataByCache()方法
//getBOClass()需要子类实现
//getPendingRedisKey()也需要子类实现
//最后的匿名函数中,也使用了多个需要子类实现的方法:getTableFieldsMap()、getStringDatabase()、convertDO2BO()
Optional<List<BO>> boListOpt = redisReadWriteManager.listRedisStringDataByCache(keyList, getBOClass(), this::getRedisKey, (key) -> {
Map<String, Object> tableFieldsMap = getTableFieldsMap(key);
Optional<DO> doOpt;
try {
doOpt = getStringDatabase().getTableData(tableFieldsMap, queryType());
} catch (Exception e) {
log.error("根据关键字批量获取数据出现异常 key={},paramMap={}", key, tableFieldsMap, e);
return Optional.empty();
}
if (!doOpt.isPresent()) {
return Optional.empty();
}
List<BO> boList = convertDO2BO(Arrays.asList(doOpt.get()));
if (CollectionUtils.isEmpty(boList)) {
return Optional.empty();
}
return Optional.of(boList.get(0));
});
return boListOpt;
}
//获取Redis key
protected String getRedisKey(String key) {
return String.format(getPendingRedisKey(), key);
}
//获取BO对象的Class
protected abstract Class<BO> getBOClass();
//获取待处理的Redis Key
protected abstract String getPendingRedisKey();
//关联表字段值
protected abstract Map<String, Object> getTableFieldsMap(String key);
//获取DB读取对象
protected abstract RedisStringDatabase<DO> getStringDatabase();
//DO转BO
protected abstract List<BO> convertDO2BO(Collection<DO> doList);
...
}
@Service("frontCategoryStringSource")
public class FrontCategoryCache extends AbstractRedisStringCache<FrontCategoryDO, FrontCategoryBO> {
@Resource
private FrontCategoryStringDatabase frontCategoryStringDatabase;
...
//获取BO对象的Class
@Override
protected Class<FrontCategoryBO> getBOClass() {
return FrontCategoryBO.class;
}
//获取待处理的Redis Key
@Override
protected String getPendingRedisKey() {
return AbstractRedisKeyConstants.FRONT_CATEGORY_STRING;
}
@Override
protected RedisStringDatabase<FrontCategoryDO> getStringDatabase() {
return frontCategoryStringDatabase;
}
//DO转BO
@Override
protected List<FrontCategoryBO> convertDO2BO(Collection<FrontCategoryDO> frontCategoryDOList) {
if (CollectionUtils.isEmpty(frontCategoryDOList)) {
return null;
}
List<FrontCategoryBO> result = Lists.newArrayList();
for (FrontCategoryDO frontCategoryDO : frontCategoryDOList) {
FrontCategoryBO frontCategoryBO = new FrontCategoryBO();
BeanUtils.copyProperties(frontCategoryDO, frontCategoryBO);
result.add(frontCategoryBO);
}
return result;
}
...
}
@Service("frontCategoryStringDatabase")
public class FrontCategoryStringDatabase extends AbstractRedisStringDatabase<FrontCategoryDO> {
...
//获取表数据
@Override
public Optional<FrontCategoryDO> getTableData(Map<String, Object> tableFieldsMap, String queryType) {
if (tableFieldsMap.containsKey(ID)) {
QueryWrapper<FrontCategoryDO> queryWrapper = new QueryWrapper<>();
queryWrapper.in("ID", Sets.newHashSet(Integer.valueOf(tableFieldsMap.get(ID).toString())));
List<FrontCategoryDO> frontCategoryDOList = frontCategoryMapper.selectList(queryWrapper);
if (!CollectionUtils.isEmpty(frontCategoryDOList)) {
FrontCategoryDO doBase = frontCategoryDOList.get(0);
if (Objects.equals(DelFlagEnum.EFFECTIVE.getCode(), doBase.getDelFlag())) {
return Optional.of(doBase);
}
}
return Optional.empty();
}
throw new UnsupportedOperationException();
}
...
}
//通用缓存读写组件
@Service
public class RedisReadWriteManager {
@Resource
private RedisCache redisCache;
@Resource
private RedisLock redisLock;
...
//批量获取缓存数据
//@param keyList 关键字列表
//@param clazz 需要将缓存JSON转换的对象
//@param getRedisKeyFunction 获取Redis key的方法
//@param getDbFuction 获取数据源对象的方法
//@return java.util.Optional<java.util.List<T>>
public <T> Optional<List<T>> listRedisStringDataByCache(List<String> keyList, Class<T> clazz,
Function<String, String> getRedisKeyFunction, Function<String, Optional<T>> getDbFuction) {
try {
List<T> list = Lists.newArrayList();
List<String> pendingKeyList = keyList.stream().distinct().collect(toList());
List<String> redisKeyList = pendingKeyList.stream().map(getRedisKeyFunction).distinct().collect(toList());
List<String> cacheList = redisCache.mget(redisKeyList);
for (int i = 0; i < cacheList.size(); i++) {
String cache = cacheList.get(i);
//过滤无效缓存
if (EMPTY_OBJECT_STRING.equals(cache)) {
continue;
}
if (StringUtils.isNotBlank(cache)) {
T t = JSON.parseObject(cache, clazz);
list.add(t);
continue;
}
//缓存没有则读库
Optional<T> optional = getRedisStringDataByDb(pendingKeyList.get(i), getRedisKeyFunction, getDbFuction);
if (optional.isPresent()) {
list.add(optional.get());
}
}
return CollectionUtils.isEmpty(list) ? Optional.empty() : Optional.of(list);
} catch (Exception e) {
log.error("批量获取缓存数据异常 keyList={},clazz={}", keyList, clazz, e);
throw e;
}
}
//查询数据库表的数据并赋值到Redis
public <T> Optional<T> getRedisStringDataByDb(String key, Function<String, String> getRedisKeyFunction, Function<String, Optional<T>> getDbFuction) {
if (StringUtils.isEmpty(key) || Objects.isNull(getDbFuction)) {
return Optional.empty();
}
try {
//使用分布式锁
if (!redisLock.lock(key)) {
return Optional.empty();
}
String redisKey = getRedisKeyFunction.apply(key);
Optional<T> optional = getDbFuction.apply(key);
if (!optional.isPresent()) {
//把空对象暂存到Redis
redisCache.setex(redisKey, EMPTY_OBJECT_STRING, RedisKeyUtils.redisKeyRandomTime(INT_EXPIRED_ONE_DAY, TimeUnit.HOURS, NUMBER_24));
log.warn("发生缓存穿透 redisKey={}", redisKey);
return optional;
}
//把表数据对象存到Redis
redisCache.setex(redisKey, JSON.toJSONString(optional.get()), RedisKeyUtils.redisKeyRandomTime(INT_EXPIRED_SEVEN_DAYS));
log.info("表数据对象存到redis redisKey={}, data={}", redisKey, optional.get());
return optional;
} finally {
redisLock.unlock(key);
}
}
...
}
15.商品C端---接口代码实现逻辑
(1)获取前台类目下的商品列表
(2)获取商品信息和详情接口
(1)获取前台类目下的商品列表
FrontCategoryRelationCache和SkuCollectCache这两个缓存类,都继承自抽象类AbstractRedisStringCache,并使用了通用缓存读写组件RedisReadWriteManager。
kotlin
//商品前台类目服务
@DubboService(version = "1.0.0", interfaceClass = FrontCategoryApi.class, retries = 0)
public class FrontCategoryApiImpl implements FrontCategoryApi {
@Resource
private FrontCategoryRelationCache frontCategoryRelationCache;
@Resource
private SkuCollectCache skuCollectCache;
@Resource
private FrontCategoryConverter frontCategoryConverter;
...
//获取前台类目下的商品列表
@Override
public JsonResult<FrontCategorySkuRelationDTO> getFrontCategorySkuList(FrontCategoryQuery frontCategoryQuery) {
//入参校验
checkParams(frontCategoryQuery);
List<String> frontCategoryIdList = Arrays.asList(String.valueOf(frontCategoryQuery.getFrontCategoryId()));
//查询前端类目下关联的商品sku信息
Optional<List<FrontCategoryRelationBO>> optiona = frontCategoryRelationCache.listRedisStringData(frontCategoryIdList);
if (!optiona.isPresent()) {
JsonResult.buildSuccess();
}
//填充商品的sku信息
List<FrontCategoryRelationBO> frontCategoryRelationBOS = optiona.get();
List<String> skuIdList = frontCategoryRelationBOS.stream().map(FrontCategoryRelationBO::getParticipateId).collect(Collectors.toList());
Optional<List<SkuInfoBO>> optional = skuCollectCache.listRedisStringData(skuIdList);
if (!optional.isPresent()) {
JsonResult.buildSuccess();
}
List<Object> skuList = frontCategoryConverter.converterObjectList(optional.get());
return JsonResult.buildSuccess(new FrontCategorySkuRelationDTO(skuList));
}
...
}
@Service("frontCategoryRelationCache")
public class FrontCategoryRelationCache extends AbstractRedisStringCache<FrontCategoryRelationDO, FrontCategoryRelationBO> {
@Resource
private FrontCategoryRelationStringDatabase frontCategoryRelationStringDatabase;
@Override
protected Class<FrontCategoryRelationBO> getBOClass() {
return FrontCategoryRelationBO.class;
}
@Override
protected String getPendingRedisKey() {
return AbstractRedisKeyConstants.FRONT_CATEGORY_ITEM_RELATION_SET;
}
@Override
protected RedisStringDatabase<FrontCategoryRelationDO> getStringDatabase() {
return frontCategoryRelationStringDatabase;
}
...
}
@Service("frontCategoryRelationStringDatabase")
public class FrontCategoryRelationStringDatabase extends AbstractRedisStringDatabase<FrontCategoryRelationDO> {
...
@Override
public Optional<FrontCategoryRelationDO> getTableData(Map<String, Object> tableFieldsMap, String queryType) {
if (tableFieldsMap.containsKey(FRONT_CATEGORY_ID)) {
List<FrontCategoryRelationDO> frontCategoryDOList = frontCategoryMapper.queryFrontCategoryList(Arrays.asList(Long.valueOf(tableFieldsMap.get(FRONT_CATEGORY_ID).toString())));
if (!CollectionUtils.isEmpty(frontCategoryDOList)) {
FrontCategoryRelationDO doBase = frontCategoryDOList.get(0);
if (Objects.equals(DelFlagEnum.EFFECTIVE.getCode(), doBase.getDelFlag())) {
return Optional.of(doBase);
}
}
return Optional.empty();
}
throw new UnsupportedOperationException();
}
...
}
//Redis(string)缓存抽象类:<DO>数据对象、<BO>缓存对象
public abstract class AbstractRedisStringCache<DO, BO> {
@Resource
private RedisReadWriteManager redisReadWriteManager;
...
//根据关键字批量获取数据
public Optional<List<BO>> listRedisStringData(List<String> keyList) {
if (CollectionUtils.isEmpty(keyList)) {
return Optional.empty();
}
//下面会调用通用缓存读写组件RedisReadWriteManager的listRedisStringDataByCache()方法
//getBOClass()需要子类实现
//getPendingRedisKey()也需要子类实现
//最后的匿名函数中,也使用了多个需要子类实现的方法:getTableFieldsMap()、getStringDatabase()、convertDO2BO()
Optional<List<BO>> boListOpt = redisReadWriteManager.listRedisStringDataByCache(keyList, getBOClass(), this::getRedisKey, (key) -> {
Map<String, Object> tableFieldsMap = getTableFieldsMap(key);
Optional<DO> doOpt;
try {
doOpt = getStringDatabase().getTableData(tableFieldsMap, queryType());
} catch (Exception e) {
log.error("根据关键字批量获取数据出现异常 key={},paramMap={}", key, tableFieldsMap, e);
return Optional.empty();
}
if (!doOpt.isPresent()) {
return Optional.empty();
}
List<BO> boList = convertDO2BO(Arrays.asList(doOpt.get()));
if (CollectionUtils.isEmpty(boList)) {
return Optional.empty();
}
return Optional.of(boList.get(0));
});
return boListOpt;
}
//获取Redis key
protected String getRedisKey(String key) {
return String.format(getPendingRedisKey(), key);
}
...
}
(2)获取商品信息和详情接口
ItemCollectCache和ProductDetailCache这两个缓存类,都继承自抽象类AbstractRedisStringCache,并使用了通用缓存读写组件RedisReadWriteManager。
scss
@DubboService(version = "1.0.0", interfaceClass = ProductCollectApi.class, retries = 0)
public class ProductCollectApiImpl implements ProductCollectApi {
@Resource
private ItemCollectCache itemCollectCache;
@Resource
private ProductDetailCache productDetailCache;
...
//根据itemId或skuId获取商品信息
@Override
public JsonResult<Map<String, ProductCollectDTO>> getProductCollect(ProductCollectQuery productCollectQuery) {
if (Objects.isNull(productCollectQuery) || CollectionUtils.isEmpty(productCollectQuery.getProductIdList())) {
return JsonResult.buildError(ProductErrorCodeEnum.PARAM_ERROR.getErrorCode(), ProductErrorCodeEnum.PARAM_ERROR.getErrorMsg());
}
if (productCollectQuery.getProductIdList().size() > BaseConstants.LIMIT_100) {
return JsonResult.buildError(ProductErrorCodeEnum.PRODUCT_LIMIT_ERROR.getErrorCode(), ProductErrorCodeEnum.PRODUCT_LIMIT_ERROR.getErrorMsg());
}
Set<String> productIdSet = Sets.newHashSet(productCollectQuery.getProductIdList());
Set<String> itemIdSet = productIdSet.stream().filter(NumberUtils::isItem).collect(Collectors.toSet());
List<ItemInfoBO> itemInfoBOList = Lists.newArrayList();
if (!CollectionUtils.isEmpty(itemIdSet)) {
Optional<List<ItemInfoBO>> itemOptional = itemCollectCache.listRedisStringData(Lists.newArrayList(itemIdSet));
if (itemOptional.isPresent()) {
itemInfoBOList = itemOptional.get();
}
}
//获取sku相关信息
ProductBO productBO = buildSkuInfoList(productCollectQuery, itemInfoBOList);
return JsonResult.buildSuccess(buildProductCollect(productBO.getItemInfoBOList(), productBO.getSkuInfoBOList(), productBO.getPriceBOList()));
}
//根据skuId获取商品详情
@Override
public JsonResult<ProductDetailDTO> getProductDetail(ProductDetailQuery productDetailQuery) {
if (Objects.isNull(productDetailQuery) || Objects.isNull(productDetailQuery.getSkuId())) {
return JsonResult.buildError(ProductErrorCodeEnum.PARAM_ERROR.getErrorCode(), ProductErrorCodeEnum.PARAM_ERROR.getErrorMsg());
}
List<String> productIdList = Arrays.asList(productDetailQuery.getSkuId());
Optional<List<ProductDetailBO>> optional = productDetailCache.listRedisStringData(productIdList);
if (optional.isPresent()) {
List<ProductDetailBO> productDetailBOS = optional.get();
ProductDetailDTO productDetailDTO = productDetailConverter.converterDetail(productDetailBOS.get(0));
return JsonResult.buildSuccess(productDetailDTO);
}
return JsonResult.buildSuccess();
}
...
}