商品中心—1.B端建品和C端缓存的技术文档
大纲
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 { @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端—商品编码生成逻辑
//商品编码 @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端—商品核心数据模型
//建品/编辑商品请求入参 @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模式来实现。
@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端—商品建品时商品编号补全与审核配置
@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端—商品审核前的草稿数据保存逻辑
@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端—不需审核的建品流程持久化逻辑
@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端—审核工单分页列表和商品草稿查询
//审批服务 @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值显示出来,方便审核员审核。
@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端—对草稿中的商品进行审核的逻辑
//审批服务 @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)商品属性数据维护
//新增/编辑规格请求入参 @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)买手数据维护
//新增/编辑买手请求入参 @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)品类数据维护
//新增/编辑品类请求入参 @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查询数据的方法。
//商品前台类目服务 @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。
//商品前台类目服务 @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。
@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(); } ... }
详细介绍后端技术栈的基础内容,包括但不限于:MySQL原理和优化、Redis原理和应用、JVM和G1原理和优化、RocketMQ原理应用及源码、Kafka原理应用及源码、ElasticSearch原理应用及源码、JUC源码、Netty源码、zk源码、Dubbo源码、Spring源码、Spring Boot源码、SCA源码、分布式锁源码、分布式事务、分库分表和TiDB、大型商品系统、大型订单系统等