商品中心—1.B端建品和C端缓存的技术文档二

发布于:2025-06-10 ⋅ 阅读:(17) ⋅ 点赞:(0)

大纲

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端—接口代码实现逻辑

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();
    }
    ...
}