商品中心—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 {    @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端---商品编码生成逻辑

复制代码
//商品编码@Servicepublic 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端---商品核心数据模型

复制代码
//建品/编辑商品请求入参@Datapublic 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模式来实现。

复制代码
@Servicepublic 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@AllArgsConstructorpublic 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端---商品建品时商品编号补全与审核配置

复制代码
@Servicepublic 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;    }    ...}
//商品审核 资源管理@Repositorypublic 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端---商品审核前的草稿数据保存逻辑

​​​​​​​

复制代码
@Servicepublic 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;    }    ...}
//商品审核 资源管理@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<DraftImgDO> 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);        //是否构建填充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);        }    }    ...}
//商品 资源管理@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<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());        }    }    ...}
@Servicepublic 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);        }    }    ...}
//商品审核 资源管理@Repositorypublic 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值显示出来,方便审核员审核。

复制代码
@Servicepublic 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());        }    }}
//审核请求入参@Datapublic class AuditRequest extends BaseEntity implements Serializable {    //工单id    private Long ticketId;    //审核状态 1-通过 3-拒绝    private Integer auditStatus;    //拒绝原因    private String rejectReason;    //操作人    private Integer operatorUser;}
@Servicepublic 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);            }        }    }    ...}
//商品审核 资源管理@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<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;    }}
//规格服务@Servicepublic 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)买手数据维护

复制代码
//新增/编辑买手请求入参@Datapublic 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;}
//买手服务@Servicepublic 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)品类数据维护

复制代码
//新增/编辑品类请求入参@Datapublic 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;}
//商品品类信息@Servicepublic 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();    }    ...}
//通用缓存读写组件@Servicepublic 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();    }    ...}
相关推荐
paopaokaka_luck1 小时前
基于SpringBoot+Uniapp的健身饮食小程序(协同过滤算法、地图组件)
前端·javascript·vue.js·spring boot·后端·小程序·uni-app
Villiam_AY1 小时前
Redis 缓存机制详解:原理、问题与最佳实践
开发语言·redis·后端
UQWRJ1 小时前
菜鸟教程R语言一二章阅读笔记
开发语言·笔记·r语言
岁忧3 小时前
macOS配置 GO语言环境
开发语言·macos·golang
朝朝又沐沐4 小时前
算法竞赛阶段二-数据结构(36)数据结构双向链表模拟实现
开发语言·数据结构·c++·算法·链表
魔尔助理顾问4 小时前
系统整理Python的循环语句和常用方法
开发语言·后端·python
Ares-Wang4 小时前
JavaScript》》JS》 Var、Let、Const 大总结
开发语言·前端·javascript
程序视点4 小时前
Java BigDecimal详解:小数精确计算、使用方法与常见问题解决方案
java·后端
你的人类朋友5 小时前
❤️‍🔥微服务的拆分策略
后端·微服务·架构
遇见尚硅谷5 小时前
C语言:*p++与p++有何区别
c语言·开发语言·笔记·学习·算法