商品中心—1.B端建品和C端缓存
大纲(16930字)
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)商品新建编辑流程

//商品服务@DubboService(version = \"1.0.0\", interfaceClass = ProductApi.class, retries = 0)public class ProductApiImpl implements ProductApi {@Autowiredprivate ProductService productService;//建品/编辑商品接口@Overridepublic JsonResult 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端—商品编码生成逻辑
//商品编码@Servicepublic class ProductNoManagerImpl implements ProductNoManager {//6位序列号private static final int width = 6;@Autowiredprivate ProductAutoNoMapper productAutoNoMapper;//生成商品编码@Overridepublic String generateProductNo(Integer sourceType) {ProductTypeEnum productTypeEnum = ProductTypeEnum.getByCode(sourceType);if (productTypeEnum == null) {throw new ProductBizException(ProductErrorCodeEnum.PARAM_ERROR);}return getProductNo(productTypeEnum.getValue());}//获取组装后的商品编码,商品的prefixNo是100000private String getProductNo(String prefixNo) {//有一张ProductAutoNo表专门用于生成商品ID//分库分表也可以利用此来实现基于数据库的内存缓存分段的发号器ProductAutoNoDO productAutoNoDO = new ProductAutoNoDO();productAutoNoMapper.insert(productAutoNoDO);Long autoNo = productAutoNoDO.getId();//获取自增IDreturn prefixNo + IDUtils.genId(autoNo, width);//数字混淆算法}}
5.商品B端—商品核心数据模型
//建品/编辑商品请求入参@Datapublic class ProductRequest implements Serializable {//商品基本信息private ItemBaseRequest itemBaseRequest;//存储信息private ItemStorageRequest itemStorageRequest;//品控信息private ShelfLifeRequest shelfLifeRequest;//图文信息private List itemVideoImgRequestList;//销售信息private ItemSaleRequest itemSaleRequest;//推广信息private ItemPopularizeRequest itemPopularizeRequest;//操作人@NotNull(message = \"操作人[operateUser]不能为空\")private Integer operatorUser;//商品基本信息@Datapublic static class ItemBaseRequest implements Serializable {//商品IDprivate 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;//品牌IDprivate Integer brandId;//产地IDprivate Integer producingAreaId;//成本价(单位:分)private Integer basePrice;//末级品类IDprivate Integer lastCategoryId;//一级品类IDprivate Integer oneCategoryId;//二级品类IDprivate Integer twoCategoryId;//三级品类IDprivate Integer threeCategoryId;}//存储信息@Datapublic static class ItemStorageRequest implements Serializable {//存储条件private Integer storeConditionType;//ITEM维度规格值(多个规格集合):key=颜色,value=蓝色;key=颜色,value=红色;key=内存,value=128g;key=内存,value=256gprivate List productSpcesValueList;}//规格信息@Datapublic static class ProductSpcesValue implements Serializable {//规格关键字private String key;//规格值private String value;//排序private Integer sort;}//品控信息@Datapublic static class ShelfLifeRequest implements Serializable {//保质期(单位:小时)private Integer shelfLife;//Map:acceptLife 允收期,shelfLife 货架期private Map shelfLifeMap;}//图文信息@Datapublic static class ItemVideoImgRequest implements Serializable {//内容类型(1-主图,2-轮播图、3-详情图、4-视频)private Integer contentType;//链接地址private String contentUrl;//排序(正整数,数字越小越靠前)private Integer contentSort;}//销售信息@Datapublic static class ItemSaleRequest implements Serializable {//sku信息private List skuInfoRequestList;}//sku信息@Datapublic static class SkuInfoRequest implements Serializable {//商品itemIdprivate String itemId;//商品skuIdprivate 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=128gprivate List productSpcesValueList;//sku匹配的spu信息private Long cspuId;}//推广信息@Datapublic static class ItemPopularizeRequest implements Serializable {//推荐语private String recommend;//亮点private List highlightsRequestList;//卖点private List sellingPointRequestList;//质检报告private List qualityControlRequestList;}//亮点@Datapublic static class HighlightsRequest implements Serializable {//亮点文案private String highlights;//排序(正整数,数字越小越靠前)private Integer sort;}//卖点@Datapublic static class SellingPointRequest implements Serializable {//卖点文案private String sellingPoint;//排序(正整数,数字越小越靠前)private Integer sort;}//质检报告@Datapublic static class QualityControlRequest implements Serializable {//商品skuIdprivate String skuId;//质检报告名称private String qcName;//材料图片链接private String qcImgUrl;//排序(正整数,数字越小越靠前)private Integer qcSort;}}
6.商品B端—转换建品请求数据为商品模型数据
前端的建品请求数据比较复杂,需要和后端的商品模型数据匹配起来,所以需要进行数据转换。这种数据转换,通常会用Builder模式来实现。
@Servicepublic class ProductServiceImpl implements ProductService {...//建品/编辑商品@Transactional(rollbackFor = Exception.class)@Override@ParamsValidatepublic 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 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);//是否构建填充 itemIdBoolean 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@AllArgsConstructorpublic class FullProductData {//ITEM信息private ItemInfoDO itemInfoDO;//保质期信息private ItemShelfLifeDO itemShelfLifeDO;//视频图片信息private List itemVideoImgDOList;//SKU信息private List skuInfoDOList;//69码关系private List skuBarCodeRelationDOList;//CSPU与SKU关系private List cspuSkuRelationDOList;//ITEM或SKU扩展属性private AttributeExtendDO attributeExtendDO;//品控信息private List qualityControlDOList;public FullProductData(ItemInfoDO itemInfoDO, List 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 itemVideoImgDOList = new ArrayList(16);ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();List 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 skuInfoDOList = new ArrayList(16);ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();List 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 skuBarCodeRelationDOList = new ArrayList(16);List 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 cspuSkuRelationDOList = new ArrayList(16);ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();List 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 qualityControlDOList = new ArrayList(16);ProductRequest.ItemBaseRequest itemBaseRequest = productRequest.getItemBaseRequest();ProductRequest.ItemPopularizeRequest itemPopularizeRequest = productRequest.getItemPopularizeRequest();List 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端—商品建品时商品编号补全与审核配置
@Servicepublic class ProductServiceImpl implements ProductService {...//建品/编辑商品@Transactional(rollbackFor = Exception.class)@Override@ParamsValidatepublic ProductDTO product(ProductRequest productRequest) {//入参检查checkProductRequestParam(productRequest);//商品数据处理ProductDTO productDTO = handleProduct(productRequest);//返回商品信息return productDTO;}...//商品数据处理private ProductDTO handleProduct(ProductRequest productRequest) {//构建商品的全量信息FullProductData fullProductData = buildProduct(productRequest);//是否构建填充itemIdBoolean 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));}//是否需要构建商品的ItemIdprivate Boolean whetherBuildProductItemId(FullProductData fullProductData) {//ITEM信息ItemInfoDO itemInfoDO = fullProductData.getItemInfoDO();//新增if (StringUtils.isEmpty(itemInfoDO.getItemId())) {//保质期ItemShelfLifeDO itemShelfLifeDO = fullProductData.getItemShelfLifeDO();//生成Item的IdString itemId = createItemId();//赋值itemIditemInfoDO.setItemId(itemId);itemShelfLifeDO.setItemId(itemId);//SKU信息List skuInfoDOList = fullProductData.getSkuInfoDOList();for (SkuInfoDO skuInfoDO : skuInfoDOList) {//对每个SKU也生成IDString skuId = productNoManager.generateProductNo(ProductTypeEnum.SKU.getCode());skuInfoDO.setSkuId(skuId);skuInfoDO.setItemId(itemId);}//视频图片List 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;}//创建ItemIdprivate String createItemId() {String itemId = productNoManager.generateProductNo(ProductTypeEnum.ITEM.getCode());return itemId;}...}//商品审核 资源管理@Repositorypublic class ProductAuditRepository {...//验证是否需要审核public Boolean needAudit(FullProductData fullProductData, Boolean createFlag) {ItemInfoDO itemInfoDO = fullProductData.getItemInfoDO();Integer count = 0;if (!createFlag) {//1.首先判断 商品审核内容配置表 中是否有对应的skuIdList skuIds = fullProductData.getSkuInfoDOList().stream().map(SkuInfoDO::getSkuId).collect(Collectors.toList());count = countByCustomIds(skuIds, AuditCustomTypeEnum.SKU);if (count > 0) {return true;}//2.是否有对应的itemcount = countByCustomIds(Collections.singletonList(itemInfoDO.getItemId()), AuditCustomTypeEnum.ITEM);if (count > 0) {return true;}}//3.验证是否有对应的categoryIdList categoryIds = Arrays.asList(itemInfoDO.getFirstCategoryId(), itemInfoDO.getSecondCategoryId(), itemInfoDO.getThirdCategoryId());count = countByCustomIds(categoryIds, AuditCustomTypeEnum.CATEGORY);//当商品审核内容配置表中有相应的品类数据,则需要审核,否则不需要审核return count > 0;}...}
8.商品B端—商品审核前的草稿数据保存逻辑
@Servicepublic class ProductServiceImpl implements ProductService {...//建品/编辑商品@Transactional(rollbackFor = Exception.class)@Override@ParamsValidatepublic ProductDTO product(ProductRequest productRequest) {//入参检查checkProductRequestParam(productRequest);//商品数据处理ProductDTO productDTO = handleProduct(productRequest);//返回商品信息return productDTO;}...//商品数据处理private ProductDTO handleProduct(ProductRequest productRequest) {//构建商品的全量信息FullProductData fullProductData = buildProduct(productRequest);//是否构建填充itemIdBoolean 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;}...}//商品审核 资源管理@Repositorypublic 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 draftImgDOS = fullDraftData.getDraftImgDOS();if (!CollectionUtils.isEmpty(draftImgDOS)) {for (DraftImgDO draftImgDO : draftImgDOS) {draftImgDO.setDraftId(fullDraftData.getDraftMainDO().getId());}draftImgMapper.saveBatch(draftImgDOS);}}...}
9.商品B端—不需审核的建品流程持久化逻辑
@Servicepublic class ProductServiceImpl implements ProductService {...//商品数据处理private ProductDTO handleProduct(ProductRequest productRequest) {//构建商品的全量信息FullProductData fullProductData = buildProduct(productRequest);//是否构建填充itemIdBoolean 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));}//新增或者修改商品相关信息@Overridepublic void saveOrUpdateDBProduct(FullProductData fullProductData, Boolean createFlag) {if (createFlag) {//新增productInfoRepository.saveItemInfo(fullProductData);} else {//修改productInfoRepository.updateItemInfo(fullProductData);}}...}//商品 资源管理@Repositorypublic 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 itemVideoImgDOList) {int count = itemVideoImgMapper.saveBatch(itemVideoImgDOList);if (count <= 0) {throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);}}//批量保存商品sku信息private void saveBatchSkuInfo(List skuInfoDOList) {int count = skuInfoMapper.saveBatch(skuInfoDOList);if (count <= 0) {throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);}}//批量保存69码信息private void saveBatchSkuBarCodeRelation(List skuBarCodeRelationDOList) {int count = skuBarCodeRelationMapper.saveBatch(skuBarCodeRelationDOList);if (count <= 0) {throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);}}//批量保存CSPU与SKU关系private void saveBatchCspuSkuRelation(List cspuSkuRelationDOList) {int count = cspuSkuRelationMapper.saveBatch(cspuSkuRelationDOList);if (count <= 0) {throw new BaseBizException(ProductExceptionCode.PRODUCT_SQL);}}//批量保存品控信息private void saveBatchQualityControl(List 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端—审核工单分页列表和商品草稿查询
//审批服务@DubboService(version = \"1.0.0\", interfaceClass = AuditApi.class, retries = 0)public class AuditApiImpl implements AuditApi {@Autowiredprivate AuditService auditService;@Overridepublic JsonResult<PageResult> getTodoList(QueryTodoListRequest request) {try {//审核工单分页列表PageResult 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());}}@Overridepublic JsonResult 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());}}...}@Servicepublic class AuditServiceImpl implements AuditService {...//获取审核的代办列表@Overridepublic PageResult getTodoList(QueryTodoListRequest queryTodoListRequest) {//获取用户审核角色AuditorListConfigDO auditor = productAuditRepository.getAuditorRuleByUserId(queryTodoListRequest.getUserId());//返回待办列表return productAuditRepository.pageResult(queryTodoListRequest, auditor);}//查询草稿详情信息@Overridepublic 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);}}...}//商品审核 资源管理@Repositorypublic class ProductAuditRepository {...//获取用户审核角色public AuditorListConfigDO getAuditorRuleByUserId(Integer userId) {LambdaQueryWrapper 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 pageResult(QueryTodoListRequest queryTodoListRequest, AuditorListConfigDO auditor) {LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery();queryWrapper.eq(AuditInfoDO::getTicketStatus, AuditStatusEnum.UNAUDITED.getCode());Page 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 draftImgDTOS = getByDraft(draftMainDTO);//返回草稿的主体信息return new DraftDetailDTO(draftMainDTO, draftImgDTOS);}...}
11.商品B端—商品审核时的敏感字段diff计算逻辑
审核时需要把Item和SKU的敏感字段的diff值显示出来,方便审核员审核。
@Servicepublic class AuditServiceImpl implements AuditService {...//查询草稿详情信息@Overridepublic 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 itemDiffValues = DiffFieldUtil.buildDiffField(itemInfoDO, null, itemDiffFields);//skuList diff 存放Map集合Map<String, List> skuDiffFieldsMap = null;//sku信息List skuInfoDOList = fullProductData.getSkuInfoDOList();if (!CollectionUtils.isEmpty(skuInfoDOList)) {skuDiffFieldsMap = new HashMap(skuInfoDOList.size());for (SkuInfoDO skuInfoDO : skuInfoDOList) {List skuDiffValues = DiffFieldUtil.buildDiffField(skuInfoDO, null, skuDiffFields);if (!CollectionUtils.isEmpty(skuDiffValues)) {skuDiffFieldsMap.put(skuInfoDO.getSkuId(), skuDiffValues);}}}//填充商品数据变更的差异信息buildDiffInfo(itemDiffValues, skuDiffFieldsMap, draftDetailDTO);}//填充商品数据变更的差异信息private void buildDiffInfo(List itemDiffValues, Map<String, List> 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 itemDiffValues = DiffFieldUtil.buildDiffField(itemInfoDO, oldItemInfoDO, itemDiffFields);List oldSkuInfoDOList = productInfoRepository.listSkuByItemId(itemInfoDO.getItemId());List skuInfoDOList = fullProductData.getSkuInfoDOList();List skuDiffValues;//skuList diff 存放Map集合Map<String, List> skuDiffFieldsMap = new HashMap();//旧的商品集合转换Map 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 buildDiffField(Object newObj, Object oldObj, List diffFields) {//oldObj为null表示新增,如果newObj与oldObj类型不同,则不处理if (!Objects.isNull(oldObj) && !newObj.getClass().equals(oldObj.getClass())) {return null;}List 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端—对草稿中的商品进行审核的逻辑
//审批服务@DubboService(version = \"1.0.0\", interfaceClass = AuditApi.class, retries = 0)public class AuditApiImpl implements AuditApi {@Autowiredprivate AuditService auditService;...@Overridepublic JsonResult 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());}}}//审核请求入参@Datapublic class AuditRequest extends BaseEntity implements Serializable {//工单idprivate Long ticketId;//审核状态 1-通过 3-拒绝private Integer auditStatus;//拒绝原因private String rejectReason;//操作人private Integer operatorUser;}@Servicepublic class AuditServiceImpl implements AuditService {...//执行审核@Transactional@Overridepublic 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);}}}...}//商品审核 资源管理@Repositorypublic 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)商品属性数据维护
//新增/编辑规格请求入参@Datapublic class AttributeRequest implements Serializable {//规格键信息private AttributeKeyRequest attributeKeyRequest;//规格值信息private List attributeValueRequests;//操作人@NotNull(message = \"操作人[operateUser]不能为空\")private Integer operateUser;@Datapublic static class AttributeKeyRequest implements Serializable {//属性key编码private String keyCode;//属性key名称private String keyName;//扩展字段private String features;//排序private Integer keySort;//删除标记(1-有效,0-删除)private Integer delFlag;}@Datapublic static class AttributeValueRequest implements Serializable {//属性key编码private String keyCode;//属性value名称private String valueName;//扩展字段private String features;//排序private Integer valueSort;//删除标记(1-有效,0-删除)private Integer delFlag;}}//规格服务@Servicepublic class AttributeServiceImpl implements AttributeService {@Resourceprivate AttributeRepository attributeRepository;//新增/编辑规格键值接口@Transactional(rollbackFor = Exception.class)@Overridepublic 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 attributeValueRequests = attributeRequest.getAttributeValueRequests();ParamCheckUtil.checkCollectionNonEmpty(attributeValueRequests);}...}
(2)买手数据维护
//新增/编辑买手请求入参@Datapublic class BuyerRequest implements Serializable {private Long id;//真实姓名private String realName;//花名private String roster;//买手图像private String imageUrl;//介绍private String description;//负责的品类IDprivate String categoryId;//删除标记(1-有效,0-删除)private Integer delFlag;//操作人@NotNull(message = \"操作人[operateUser]不能为空\")private Integer operateUser;}//买手服务@Servicepublic class BuyerServiceImpl implements BuyerService {@Resourceprivate BuyerRepository buyerRepository;@Overridepublic BuyerResultDTO saveBuyer(BuyerRequest buyerRequest) {//保存买手信息buyerRepository.saveOrUpdate(buyerRequest);//返回结果信息return new BuyerResultDTO(Boolean.TRUE);}@Overridepublic BuyerListDTO getBuyerInfo(QueryBuyerListRequest queryBuyerListRequest) {List buyerInfoDTOS = buyerRepository.listBuyerInfo(queryBuyerListRequest);//返回信息return new BuyerListDTO(buyerInfoDTOS);}@Overridepublic PageResult getBuyerInfoPage(QueryBuyerPageRequest queryBuyerPageRequest) {return buyerRepository.pageResult(queryBuyerPageRequest);}}
(3)品类数据维护
//新增/编辑品类请求入参@Datapublic class CategoryRequest implements Serializable {//idprivate Long id;//品类名称@NotNull(message = \"品类名称[categoryName]不能为空\")private String categoryName;//父ID(一级类目父ID为0)private Integer parentId;//排序(正整数,数字越小越靠前)@NotNull(message = \"排序[categorySort]不能为空\")private Integer categorySort;//图标iconprivate 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;}//商品品类信息@Servicepublic class CategoryInfoServiceImpl implements CategoryInfoService {@Resourceprivate CategoryRepository categoryRepository;@Resourceprivate CategoryInfoConverter categoryInfoConverter;//查询品类树@Overridepublic List selectTree(QueryCategoryRequest categoryQueryRequest) {return categoryInfoConverter.converterTreeList(categoryRepository.selectTree(categoryQueryRequest));}//查询某个层级下的品类树(默认不带条件查询父类)@Overridepublic List selectChild(QueryCategoryRequest categoryQueryRequest) {//查询某个层级的品类树List categoryInfoList = categoryRepository.listBy(categoryQueryRequest);//返回查询结果return categoryInfoConverter.converterList(categoryInfoList);}//保存/修改品类信息@Overridepublic CategoryResultDTO saveCategory(CategoryRequest categoryRequest) {//保存品类树categoryRepository.saveOrUpdate(categoryRequest);//返回结果信息return new CategoryResultDTO(Boolean.TRUE);}//查询品类信息列表@Overridepublic List selectListByLike(QueryCategoryListRequest categoryListRequest) {return categoryInfoConverter.converterList(categoryRepository.selectListByLike(categoryListRequest));}}
14.商品C端—通用缓存读写组件的实现逻辑
下面以获取前台类目为例,去说明先读缓存再读DB的通用缓存读写组件的逻辑。
FrontCategoryCache继承自Redis缓存抽象类AbstractRedisStringCache,这个抽象类中会有一个模版方法listRedisStringData(),该方法可以根据关键字来批量获取数据,并且会调用通用缓存读写组件的listRedisStringDataByCache()方法。
其中,listRedisStringDataByCache()方法需要传入两个方法:一个是获取Redis的key的方法,一个是从DB查询数据的方法。
//商品前台类目服务@DubboService(version = \"1.0.0\", interfaceClass = FrontCategoryApi.class, retries = 0)public class FrontCategoryApiImpl implements FrontCategoryApi {@Resourceprivate FrontCategoryCache frontCategoryStringSource;@Resourceprivate FrontCategoryConverter frontCategoryConverter;//基于通用缓存读写组件,去获取前台类目@Overridepublic JsonResult<List> getFrontCategory(FrontCategoryQuery frontCategoryQuery) {//入参校验checkParams(frontCategoryQuery);List frontCategoryIdList = Arrays.asList(String.valueOf(frontCategoryQuery.getFrontCategoryId()));//基于通用缓存读写组件,先读缓存再读DB来获取前台类目Optional<List> optional = frontCategoryStringSource.listRedisStringData(frontCategoryIdList);if (!optional.isPresent()) {JsonResult.buildSuccess();}List frontCategoryDTOList = frontCategoryConverter.converterFrontCategoryList(optional.get());return JsonResult.buildSuccess(frontCategoryDTOList);}...}//Redis(String)缓存抽象类:是数据对象、是缓存对象public abstract class AbstractRedisStringCache {@Resourceprivate RedisReadWriteManager redisReadWriteManager;...//根据关键字批量获取数据public Optional<List> listRedisStringData(List keyList) {if (CollectionUtils.isEmpty(keyList)) {return Optional.empty();}//下面会调用通用缓存读写组件RedisReadWriteManager的listRedisStringDataByCache()方法//getBOClass()需要子类实现//getPendingRedisKey()也需要子类实现//最后的匿名函数中,也使用了多个需要子类实现的方法:getTableFieldsMap()、getStringDatabase()、convertDO2BO()Optional<List> boListOpt = redisReadWriteManager.listRedisStringDataByCache(keyList, getBOClass(), this::getRedisKey, (key) -> {Map tableFieldsMap = getTableFieldsMap(key);Optional 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 boList = convertDO2BO(Arrays.asList(doOpt.get()));if (CollectionUtils.isEmpty(boList)) {return Optional.empty();}return Optional.of(boList.get(0));});return boListOpt;}//获取Redis keyprotected String getRedisKey(String key) {return String.format(getPendingRedisKey(), key);}//获取BO对象的Classprotected abstract Class getBOClass();//获取待处理的Redis Keyprotected abstract String getPendingRedisKey();//关联表字段值protected abstract Map getTableFieldsMap(String key);//获取DB读取对象protected abstract RedisStringDatabase getStringDatabase();//DO转BOprotected abstract List convertDO2BO(Collection doList);...}@Service(\"frontCategoryStringSource\")public class FrontCategoryCache extends AbstractRedisStringCache {@Resourceprivate FrontCategoryStringDatabase frontCategoryStringDatabase;...//获取BO对象的Class@Overrideprotected Class getBOClass() {return FrontCategoryBO.class;}//获取待处理的Redis Key@Overrideprotected String getPendingRedisKey() {return AbstractRedisKeyConstants.FRONT_CATEGORY_STRING;}@Overrideprotected RedisStringDatabase getStringDatabase() {return frontCategoryStringDatabase;}//DO转BO@Overrideprotected List convertDO2BO(Collection frontCategoryDOList) {if (CollectionUtils.isEmpty(frontCategoryDOList)) {return null;}List 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 {...//获取表数据@Overridepublic Optional getTableData(Map tableFieldsMap, String queryType) {if (tableFieldsMap.containsKey(ID)) {QueryWrapper queryWrapper = new QueryWrapper();queryWrapper.in(\"ID\", Sets.newHashSet(Integer.valueOf(tableFieldsMap.get(ID).toString())));List 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();}...}//通用缓存读写组件@Servicepublic class RedisReadWriteManager {@Resourceprivate RedisCache redisCache;@Resourceprivate RedisLock redisLock;...//批量获取缓存数据//@param keyList 关键字列表//@param clazz 需要将缓存JSON转换的对象//@param getRedisKeyFunction 获取Redis key的方法//@param getDbFuction 获取数据源对象的方法//@return java.util.Optional<java.util.List>public Optional<List> listRedisStringDataByCache(List keyList, Class clazz,Function getRedisKeyFunction, Function<String, Optional> getDbFuction) {try {List list = Lists.newArrayList();List pendingKeyList = keyList.stream().distinct().collect(toList());List redisKeyList = pendingKeyList.stream().map(getRedisKeyFunction).distinct().collect(toList());List 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 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;}}//查询数据库表的数据并赋值到Redispublic Optional getRedisStringDataByDb(String key, Function getRedisKeyFunction, Function<String, Optional> 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 optional = getDbFuction.apply(key);if (!optional.isPresent()) {//把空对象暂存到RedisredisCache.setex(redisKey, EMPTY_OBJECT_STRING, RedisKeyUtils.redisKeyRandomTime(INT_EXPIRED_ONE_DAY, TimeUnit.HOURS, NUMBER_24));log.warn(\"发生缓存穿透 redisKey={}\", redisKey);return optional;}//把表数据对象存到RedisredisCache.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。
//商品前台类目服务@DubboService(version = \"1.0.0\", interfaceClass = FrontCategoryApi.class, retries = 0)public class FrontCategoryApiImpl implements FrontCategoryApi {@Resourceprivate FrontCategoryRelationCache frontCategoryRelationCache;@Resourceprivate SkuCollectCache skuCollectCache;@Resourceprivate FrontCategoryConverter frontCategoryConverter;...//获取前台类目下的商品列表@Overridepublic JsonResult getFrontCategorySkuList(FrontCategoryQuery frontCategoryQuery) {//入参校验checkParams(frontCategoryQuery);List frontCategoryIdList = Arrays.asList(String.valueOf(frontCategoryQuery.getFrontCategoryId()));//查询前端类目下关联的商品sku信息Optional<List> optiona = frontCategoryRelationCache.listRedisStringData(frontCategoryIdList);if (!optiona.isPresent()) {JsonResult.buildSuccess();}//填充商品的sku信息List frontCategoryRelationBOS = optiona.get();List skuIdList = frontCategoryRelationBOS.stream().map(FrontCategoryRelationBO::getParticipateId).collect(Collectors.toList());Optional<List> optional = skuCollectCache.listRedisStringData(skuIdList);if (!optional.isPresent()) {JsonResult.buildSuccess();}List skuList = frontCategoryConverter.converterObjectList(optional.get());return JsonResult.buildSuccess(new FrontCategorySkuRelationDTO(skuList));}...}@Service(\"frontCategoryRelationCache\")public class FrontCategoryRelationCache extends AbstractRedisStringCache {@Resourceprivate FrontCategoryRelationStringDatabase frontCategoryRelationStringDatabase;@Overrideprotected Class getBOClass() {return FrontCategoryRelationBO.class;}@Overrideprotected String getPendingRedisKey() {return AbstractRedisKeyConstants.FRONT_CATEGORY_ITEM_RELATION_SET;}@Overrideprotected RedisStringDatabase getStringDatabase() {return frontCategoryRelationStringDatabase;}...}@Service(\"frontCategoryRelationStringDatabase\")public class FrontCategoryRelationStringDatabase extends AbstractRedisStringDatabase {...@Overridepublic Optional getTableData(Map tableFieldsMap, String queryType) {if (tableFieldsMap.containsKey(FRONT_CATEGORY_ID)) {List 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)缓存抽象类:数据对象、缓存对象public abstract class AbstractRedisStringCache {@Resourceprivate RedisReadWriteManager redisReadWriteManager;...//根据关键字批量获取数据public Optional<List> listRedisStringData(List keyList) {if (CollectionUtils.isEmpty(keyList)) {return Optional.empty();}//下面会调用通用缓存读写组件RedisReadWriteManager的listRedisStringDataByCache()方法//getBOClass()需要子类实现//getPendingRedisKey()也需要子类实现//最后的匿名函数中,也使用了多个需要子类实现的方法:getTableFieldsMap()、getStringDatabase()、convertDO2BO()Optional<List> boListOpt = redisReadWriteManager.listRedisStringDataByCache(keyList, getBOClass(), this::getRedisKey, (key) -> {Map tableFieldsMap = getTableFieldsMap(key);Optional 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 boList = convertDO2BO(Arrays.asList(doOpt.get()));if (CollectionUtils.isEmpty(boList)) {return Optional.empty();}return Optional.of(boList.get(0));});return boListOpt;}//获取Redis keyprotected String getRedisKey(String key) {return String.format(getPendingRedisKey(), key);}...}
(2)获取商品信息和详情接口
ItemCollectCache和ProductDetailCache这两个缓存类,都继承自抽象类AbstractRedisStringCache,并使用了通用缓存读写组件RedisReadWriteManager。
@DubboService(version = \"1.0.0\", interfaceClass = ProductCollectApi.class, retries = 0)public class ProductCollectApiImpl implements ProductCollectApi {@Resourceprivate ItemCollectCache itemCollectCache;@Resourceprivate ProductDetailCache productDetailCache;...//根据itemId或skuId获取商品信息@Overridepublic JsonResult<Map> 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 productIdSet = Sets.newHashSet(productCollectQuery.getProductIdList());Set itemIdSet = productIdSet.stream().filter(NumberUtils::isItem).collect(Collectors.toSet());List itemInfoBOList = Lists.newArrayList();if (!CollectionUtils.isEmpty(itemIdSet)) {Optional<List> 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获取商品详情@Overridepublic JsonResult getProductDetail(ProductDetailQuery productDetailQuery) {if (Objects.isNull(productDetailQuery) || Objects.isNull(productDetailQuery.getSkuId())) {return JsonResult.buildError(ProductErrorCodeEnum.PARAM_ERROR.getErrorCode(), ProductErrorCodeEnum.PARAM_ERROR.getErrorMsg());}List productIdList = Arrays.asList(productDetailQuery.getSkuId());Optional<List> optional = productDetailCache.listRedisStringData(productIdList);if (optional.isPresent()) {List productDetailBOS = optional.get();ProductDetailDTO productDetailDTO = productDetailConverter.converterDetail(productDetailBOS.get(0));return JsonResult.buildSuccess(productDetailDTO);}return JsonResult.buildSuccess();}...}


