写点什么

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

  • 2025-06-10
    福建
  • 本文字数:17907 字

    阅读完需:约 59 分钟

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)商品属性数据维护


//新增/编辑规格请求入参@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)获取前台类目下的商品列表


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(); } ...}
复制代码


文章转载自:东阳马生架构

原文链接:https://www.cnblogs.com/mjunz/p/18921436

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2025-04-01 加入

还未添加个人简介

评论

发布
暂无评论
商品中心—B端建品和C端缓存的技术文档(二)_架构_电子尖叫食人鱼_InfoQ写作社区