商品中心—1.B端建品和C端緩存的技術文檔二

大綱

1.商品中心的專業術語

2.商品中心的基本業務系統

3.商品中心整體架構設計以及運行流程

4.商品B端—商品編碼生成邏輯

5.商品B端—商品核心數據模型

6.商品B端—轉換建品請求數據為商品模型數據

7.商品B端—商品建品時商品編號補全與審核配置

8.商品B端—商品審核前的草稿數據保存邏輯

9.商品B端—不需審核的建品流程持久化邏輯

10.商品B端—審核工單分頁列表和商品草稿查詢

11.商品B端—商品審核時的敏感字段diff計算邏輯

12.商品B端—對草稿中的商品進行審核的邏輯

13.商品B端—商品屬性+買手+品類的數據維護

14.商品C端—通用緩存讀寫組件的實現邏輯

15.商品C端—接口代碼實現邏輯

10.商品B端—審核工單分頁列表和商品草稿查詢

//審批服務
@DubboService(version = "1.0.0", interfaceClass = AuditApi.class, retries = 0)
public class AuditApiImpl implements AuditApi {@Autowiredprivate AuditService auditService;@Overridepublic 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());}}@Overridepublic 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 {...//獲取審核的代辦列表@Overridepublic PageResult<AuditInfoDTO> getTodoList(QueryTodoListRequest queryTodoListRequest) {//獲取用戶審核角色AuditorListConfigDO auditor = productAuditRepository.getAuditorRuleByUserId(queryTodoListRequest.getUserId());//返回待辦列表return productAuditRepository.pageResult(queryTodoListRequest, auditor);}//查詢草稿詳情信息@Overridepublic DraftDetailDTO getDraftDetail(QueryDraftRequest queryDraftRequest) {//草稿詳情信息DraftDetailDTO draftDetailDTO = productAuditRepository.getDraftDetail(queryDraftRequest.getTicketId());//構建需要比較不同的字段數據buildDiffChangeField(draftDetailDTO);return draftDetailDTO;}//構建需要比較不同的字段的數據private void buildDiffChangeField(DraftDetailDTO draftDetailDTO) {//草稿主表信息DraftMainDTO draftMainDTO = draftDetailDTO.getDraftMainDTO();//修改后的商品數據FullProductData fullProductData = JSON.parseObject(draftMainDTO.getFeatures(), FullProductData.class);//商品新增時,item版本號是0,草稿表中的版本號是item表中的版本號加1//所以此時判斷草稿表中的版本號是小于等于1表示新增數據if (draftMainDTO.getVersionId() <= 1) {buildAddDiff(fullProductData, draftDetailDTO);} else {buildUpdateDiff(fullProductData, draftDetailDTO);}}...
}//商品審核 資源管理
@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 {...//查詢草稿詳情信息@Overridepublic DraftDetailDTO getDraftDetail(QueryDraftRequest queryDraftRequest) {//草稿詳情信息DraftDetailDTO draftDetailDTO = productAuditRepository.getDraftDetail(queryDraftRequest.getTicketId());//構建需要比較不同的字段數據buildDiffChangeField(draftDetailDTO);return draftDetailDTO;}//構建需要比較不同的字段的數據private void buildDiffChangeField(DraftDetailDTO draftDetailDTO) {//草稿主表信息DraftMainDTO draftMainDTO = draftDetailDTO.getDraftMainDTO();//修改后的商品數據FullProductData fullProductData = JSON.parseObject(draftMainDTO.getFeatures(), FullProductData.class);//商品新增時,item版本號是0,草稿表中的版本號是item表中的版本號加1//所以此時判斷草稿表中的版本號是小于等于1表示新增數據if (draftMainDTO.getVersionId() <= 1) {buildAddDiff(fullProductData, draftDetailDTO);} else {buildUpdateDiff(fullProductData, draftDetailDTO);}}//填充新增的 商品差異變化信息private void buildAddDiff(FullProductData fullProductData, DraftDetailDTO draftDetailDTO) {//item信息ItemInfoDO itemInfoDO = fullProductData.getItemInfoDO();List<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 {@Autowiredprivate AuditService auditService;...@Overridepublic 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 {//工單idprivate Long ticketId;//審核狀態 1-通過 3-拒絕private Integer auditStatus;//拒絕原因private String rejectReason;//操作人private Integer operatorUser;
}@Service
public class AuditServiceImpl implements AuditService {...//執行審核@Transactional@Overridepublic ExecAuditDTO execAudit(AuditRequest auditRequest) {//驗證是否有可以審核,并填充審核信息AuditInfoDTO auditInfoDTO = productAuditRepository.checkAudit(auditRequest);//執行審核execGoodsAudit(auditRequest, auditInfoDTO);//處理審核的信息DB變更productAuditRepository.updateAudit(auditRequest, auditInfoDTO);return new ExecAuditDTO(Boolean.TRUE);}//商品審核private void execGoodsAudit(AuditRequest auditRequest, AuditInfoDTO auditInfoDTO) {DraftMainDTO draftMainDTO = auditInfoDTO.getDraftMainDTO();Integer ticketType = auditInfoDTO.getTicketType();//如果是審批通過,則需要更改正式表的數據if (Objects.equals(auditRequest.getAuditStatus(), AuditStatusEnum.PASS.getCode())) {FullProductData fullProductData = JSON.parseObject(draftMainDTO.getFeatures(), FullProductData.class);//建品審核if (Objects.equals(ticketType, AuditTypeEnum.GOODS.getCode())) {fullProductData.getItemInfoDO().setVersionId(draftMainDTO.getVersionId());//產品信息入庫;版本號小于等于1,表示新增,否則表示修改if (fullProductData.getItemInfoDO().getVersionId() <= 1) {productInfoRepository.saveItemInfo(fullProductData);} else {productInfoRepository.updateItemInfo(fullProductData);}} else if (Objects.equals(ticketType, AuditTypeEnum.PRICE.getCode())) {SkuInfoDO skuInfoDO = fullProductData.getSkuInfoDOList().get(0);productInfoRepository.saveRecord(skuInfoDO);}}}...
}//商品審核 資源管理
@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;@Datapublic static class AttributeKeyRequest implements Serializable {//屬性key編碼private String keyCode;//屬性key名稱private String keyName;//擴展字段private String features;//排序private Integer keySort;//刪除標記(1-有效,0-刪除)private Integer delFlag;}@Datapublic static class AttributeValueRequest implements Serializable {//屬性key編碼private String keyCode;//屬性value名稱private String valueName;//擴展字段private String features;//排序private Integer valueSort;//刪除標記(1-有效,0-刪除)private Integer delFlag;}
}//規格服務
@Service
public class AttributeServiceImpl implements AttributeService {@Resourceprivate AttributeRepository attributeRepository;//新增/編輯規格鍵值接口@Transactional(rollbackFor = Exception.class)@Overridepublic AttributeResultDTO saveAttribute(AttributeRequest attributeRequest) {//入參檢查this.checkAttributeRequestParam(attributeRequest);//保存規格信息attributeRepository.saveAttribute(attributeRequest);//返回結果return new AttributeResultDTO(Boolean.TRUE);}//入參檢查private void checkAttributeRequestParam(AttributeRequest attributeRequest) {ParamCheckUtil.checkObjectNonNull(attributeRequest);//規格鍵信息AttributeRequest.AttributeKeyRequest attributeKeyRequest = attributeRequest.getAttributeKeyRequest();ParamCheckUtil.checkObjectNonNull(attributeKeyRequest);//規格值信息List<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;//負責的品類IDprivate String categoryId;//刪除標記(1-有效,0-刪除)private Integer delFlag;//操作人@NotNull(message = "操作人[operateUser]不能為空")private Integer operateUser;
}//買手服務
@Service
public class BuyerServiceImpl implements BuyerService {@Resourceprivate BuyerRepository buyerRepository;@Overridepublic BuyerResultDTO saveBuyer(BuyerRequest buyerRequest) {//保存買手信息buyerRepository.saveOrUpdate(buyerRequest);//返回結果信息return new BuyerResultDTO(Boolean.TRUE);}@Overridepublic BuyerListDTO getBuyerInfo(QueryBuyerListRequest queryBuyerListRequest) {List<BuyerInfoDTO> buyerInfoDTOS = buyerRepository.listBuyerInfo(queryBuyerListRequest);//返回信息return new BuyerListDTO(buyerInfoDTOS);}@Overridepublic PageResult<BuyerInfoDTO> getBuyerInfoPage(QueryBuyerPageRequest queryBuyerPageRequest) {return buyerRepository.pageResult(queryBuyerPageRequest);}
}

(3)品類數據維護

//新增/編輯品類請求入參
@Data
public class CategoryRequest implements Serializable {//idprivate Long id;//品類名稱@NotNull(message = "品類名稱[categoryName]不能為空")private String categoryName;//父ID(一級類目父ID為0)private Integer parentId;//排序(正整數,數字越小越靠前)@NotNull(message = "排序[categorySort]不能為空")private Integer categorySort;//圖標iconprivate String icon;//目錄是否展示(1-是,0-否)private Integer showMark;//是否是末級類目@NotNull(message = "末級類目[lastFlag]不能為空")private Integer lastFlag;//渠道(1-每日生鮮、2-美團、3-餓了么、4-淘鮮達、5-招商銀行)@NotNull(message = "渠道[channel]不能為空")private Integer channel;//賣家類型(1-自營,2-POP)@NotNull(message = "賣家類型[sellerType]不能為空")private Integer sellerType;//擴展字段private String feature;//刪除標記(1-有效,0-刪除)private Integer delFlag;//操作人@NotNull(message = "操作人[operateUser]不能為空")private Integer operateUser;
}//商品品類信息
@Service
public class CategoryInfoServiceImpl implements CategoryInfoService {@Resourceprivate CategoryRepository categoryRepository;@Resourceprivate CategoryInfoConverter categoryInfoConverter;//查詢品類樹@Overridepublic List<CategoryInfoTreeDTO> selectTree(QueryCategoryRequest categoryQueryRequest) {return categoryInfoConverter.converterTreeList(categoryRepository.selectTree(categoryQueryRequest));}//查詢某個層級下的品類樹(默認不帶條件查詢父類)@Overridepublic List<CategoryInfoDTO> selectChild(QueryCategoryRequest categoryQueryRequest) {//查詢某個層級的品類樹List<CategoryInfoDO> categoryInfoList = categoryRepository.listBy(categoryQueryRequest);//返回查詢結果return categoryInfoConverter.converterList(categoryInfoList);}//保存/修改品類信息@Overridepublic CategoryResultDTO saveCategory(CategoryRequest categoryRequest) {//保存品類樹categoryRepository.saveOrUpdate(categoryRequest);//返回結果信息return new CategoryResultDTO(Boolean.TRUE);}//查詢品類信息列表@Overridepublic 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 {@Resourceprivate FrontCategoryCache frontCategoryStringSource;@Resourceprivate FrontCategoryConverter frontCategoryConverter;//基于通用緩存讀寫組件,去獲取前臺類目@Overridepublic 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> {@Resourceprivate 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 keyprotected String getRedisKey(String key) {return String.format(getPendingRedisKey(), key);}//獲取BO對象的Classprotected abstract Class<BO> getBOClass();//獲取待處理的Redis Keyprotected abstract String getPendingRedisKey();//關聯表字段值protected abstract Map<String, Object> getTableFieldsMap(String key);//獲取DB讀取對象protected abstract RedisStringDatabase<DO> getStringDatabase();//DO轉BOprotected abstract List<BO> convertDO2BO(Collection<DO> doList);...
}@Service("frontCategoryStringSource")
public class FrontCategoryCache extends AbstractRedisStringCache<FrontCategoryDO, FrontCategoryBO> {@Resourceprivate FrontCategoryStringDatabase frontCategoryStringDatabase;...//獲取BO對象的Class@Overrideprotected Class<FrontCategoryBO> getBOClass() {return FrontCategoryBO.class;}//獲取待處理的Redis Key@Overrideprotected String getPendingRedisKey() {return AbstractRedisKeyConstants.FRONT_CATEGORY_STRING;}@Overrideprotected RedisStringDatabase<FrontCategoryDO> getStringDatabase() {return frontCategoryStringDatabase;}//DO轉BO@Overrideprotected 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> {...//獲取表數據@Overridepublic 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 {@Resourceprivate RedisCache redisCache;@Resourceprivate 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;}}//查詢數據庫表的數據并賦值到Redispublic <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()) {//把空對象暫存到RedisredisCache.setex(redisKey, EMPTY_OBJECT_STRING, RedisKeyUtils.redisKeyRandomTime(INT_EXPIRED_ONE_DAY, TimeUnit.HOURS, NUMBER_24));log.warn("發生緩存穿透 redisKey={}", redisKey);return optional;}//把表數據對象存到RedisredisCache.setex(redisKey, JSON.toJSONString(optional.get()), RedisKeyUtils.redisKeyRandomTime(INT_EXPIRED_SEVEN_DAYS));log.info("表數據對象存到redis redisKey={}, data={}", redisKey, optional.get());return optional;} finally {redisLock.unlock(key);}}...
}

15.商品C端—接口代碼實現邏輯

(1)獲取前臺類目下的商品列表

(2)獲取商品信息和詳情接口

(1)獲取前臺類目下的商品列表

FrontCategoryRelationCache和SkuCollectCache這兩個緩存類,都繼承自抽象類AbstractRedisStringCache,并使用了通用緩存讀寫組件RedisReadWriteManager。

//商品前臺類目服務
@DubboService(version = "1.0.0", interfaceClass = FrontCategoryApi.class, retries = 0)
public class FrontCategoryApiImpl implements FrontCategoryApi {@Resourceprivate FrontCategoryRelationCache frontCategoryRelationCache;@Resourceprivate SkuCollectCache skuCollectCache;@Resourceprivate FrontCategoryConverter frontCategoryConverter;...//獲取前臺類目下的商品列表@Overridepublic JsonResult<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> {@Resourceprivate FrontCategoryRelationStringDatabase frontCategoryRelationStringDatabase;@Overrideprotected Class<FrontCategoryRelationBO> getBOClass() {return FrontCategoryRelationBO.class;}@Overrideprotected String getPendingRedisKey() {return AbstractRedisKeyConstants.FRONT_CATEGORY_ITEM_RELATION_SET;}@Overrideprotected RedisStringDatabase<FrontCategoryRelationDO> getStringDatabase() {return frontCategoryRelationStringDatabase;}...
}@Service("frontCategoryRelationStringDatabase")
public class FrontCategoryRelationStringDatabase extends AbstractRedisStringDatabase<FrontCategoryRelationDO> {...@Overridepublic 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> {@Resourceprivate 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 keyprotected String getRedisKey(String key) {return String.format(getPendingRedisKey(), key);}...
}

(2)獲取商品信息和詳情接口

ItemCollectCache和ProductDetailCache這兩個緩存類,都繼承自抽象類AbstractRedisStringCache,并使用了通用緩存讀寫組件RedisReadWriteManager。

@DubboService(version = "1.0.0", interfaceClass = ProductCollectApi.class, retries = 0)
public class ProductCollectApiImpl implements ProductCollectApi {@Resourceprivate ItemCollectCache itemCollectCache;@Resourceprivate ProductDetailCache productDetailCache;...//根據itemId或skuId獲取商品信息@Overridepublic JsonResult<Map<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獲取商品詳情@Overridepublic 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();}...
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/83071.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/83071.shtml
英文地址,請注明出處:http://en.pswp.cn/web/83071.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

網絡之交換機

定義與作用 交換機是一種為所連接的IT設備提供網絡通信的設備&#xff0c;主要作用是轉發傳輸數據&#xff0c;實現網絡設備之間的通信互聯&#xff0c;還能對網絡進行分段和隔離&#xff0c;劃分多個虛擬網段&#xff0c;提高網絡安全性&#xff0c;以及對不同端口、用戶和應用…

AI不會殺死創作,但會殺死平庸

作為一個敲了8年Java代碼的普通本科程序員&#xff0c;日常主要泡在會議后臺管理系統的開發里。從2023年底被朋友拽著試了第一把AI工具到現在&#xff0c;電腦手機上的AI軟件比外賣App還多——寫代碼的Copilot、畫時序圖的工具、聊天的ChatGPT、Deepseek&#xff0c;基本市面上…

Golang——8、協程和管道

協程和管道 1、協程1.1、進程、線程和協程1.2、goroutine的使用以及sync.WaitGroup1.3、啟動多個協程1.4、設置Golang并行運行的時候占用的cup數量1.5、goroutine統計素數 2、管道2.1、管道的操作2.2、協程和管道協同2.3、單向管道2.4、多路復用之select2.5、解決協程中出現的異…

深入理解Python內置模塊及第三方庫的使用與管理

Python 內置模塊與第三方庫 在 Python 編程中&#xff0c;模塊和庫是幫助開發者高效實現各種功能的基礎工具。Python 提供了豐富的內置模塊以及第三方庫&#xff0c;能夠支持從基礎的文件操作到復雜的數據分析和機器學習等任務。本篇文章將深入介紹 Python 的內置模塊與第三方…

二分查找-P2249 【深基13.例1】查找

文章目錄 參考代碼二分標準模板 題目來源-洛谷網 參考代碼 #include<bits/stdc.h> using namespace std; const int N 1e65; int m,n,a[N],b; int find(int t) {int l1,rn;while(l<r){int mid(lr)/2;//防止溢出 mid l (r-l) /2 ;if(a[mid]>t) rmid;//中間值比…

手寫muduo網絡庫(一):項目構建和時間戳、日志庫

引言 本文作為手寫 muduo 網絡庫系列開篇&#xff0c;聚焦項目基礎框架搭建與核心基礎工具模塊設計。通過解析 CMake 工程結構設計、目錄規劃原則&#xff0c;結合時間戳與日志系統的架構&#xff0c;為后續網絡庫開發奠定工程化基礎。文中附完整 CMake 配置示例及模塊代碼。 …

NLP學習路線圖(三十二): 模型壓縮與優化

一、 核心壓縮與優化技術詳解 1. 知識蒸餾:智慧的傳承(Knowledge Distillation, KD) 核心思想:“師授徒業”。訓練一個龐大、高性能但笨重的“教師模型”(Teacher Model),讓其指導訓練一個輕量級的“學生模型”(Student Model)。學生模型學習模仿教師模型的輸出行為(…

vue前端字典映射

1.界面展示 2.圖中狀態字段接收的數據如下 3.代碼轉換&#xff0c;添加計算屬性代碼 再在綁定屬性的地方做轉換 computed: {statusMap() {return {"-1": "已退號",1: "掛號",2: "接診",3: "已完診",};},},<m-input:spa…

基于 llama-factory進行模型微調

# GLM4-9B-chat Lora 微調. 介紹如何基于 llama-factory 框架&#xff0c;對 glm-4-9b-chat 模型進行 Lora 微調。Lora 是一種高效微調方法&#xff0c;深入了解其原理可參見博客&#xff1a;[知乎|深入淺出 Lora](https://zhuanlan.zhihu.com/p/650197598)。 ## 環境配置 在完…

不到 2 個月,OpenAI 火速用 Rust 重寫 AI 編程工具。尤雨溪也覺得 Rust 香!

一、OpenAI 用 Rust 重寫 Codex CLI OpenAI 已用 Rust 語言重寫了其 AI 命令行編程工具 Codex CLI&#xff0c;理由是此舉能提升性能和安全性&#xff0c;同時避免對 Node.js 的依賴。他們認為 Node.js “可能讓部分用戶感到沮喪或成為使用障礙”。 Codex 是一款實驗性編程代理…

Go 并發編程深度指南

Go 并發編程深度指南 Go 語言以其內置的并發原語而聞名&#xff0c;通過 goroutine 和 channel 提供了一種高效、安全的并發編程模型。本文將全面解析 Go 的并發機制及其實際應用。 核心概念&#xff1a;Goroutines 和 Channels 1. Goroutines (協程) Go 的輕量級線程實現&…

vue和uniapp聊天頁面右側滾動條自動到底部

1.vue右側滾動條自動到底部 <div ref"newMessage1"></div> <!-- 定義<div ref"newMessage1"></div>與<div v-for”item in list“>循環同級定義-->定義方法 scrollToBottomCenter(){this.$nextTick(() > {this.$re…

iOS 項目怎么構建穩定性保障機制?一次系統性防錯經驗分享(含 KeyMob 工具應用)

崩潰、內存飆升、后臺任務未釋放、頁面卡頓、日志丟失——穩定性問題&#xff0c;不一定會立刻崩&#xff0c;但一旦積累&#xff0c;就是“上線后救不回來的代價”。 穩定性保障不是某個工具的功能&#xff0c;而是一套貫穿開發、測試、上線全流程的“觀測分析防范”機制。 …

JMeter函數整理

"_csvRead"函數 csvRead函數是從外部讀取參數&#xff0c;csvRead函數可以從一個文件中讀取多個參數。 下面具體講一下如何使用csvread函數&#xff1a; 1.新建一個csv或者text文件&#xff0c;里面保存要讀取的參數&#xff0c;每個參數間用逗號相隔。每行表示每一組…

深入理解 React Hooks

在當今的 React 開發中,Hooks 已經成為構建函數組件的核心工具。自 React 16.8 版本引入以來,Hooks 徹底改變了開發者編寫 React 組件的方式,使得狀態管理和副作用處理變得更加簡潔和直觀。本文將全面介紹 React 提供的各種 Hooks,從基礎的 useState 和 useEffect,到高級的…

Doris-2:單虛擬機上非docker化安裝Doris實驗環境

Doris-2:單虛擬機上非docker化安裝Doris實驗環境 1.安裝1.1.環境說明1.2.基礎準備1.2.1.JDK1.2.2.操作系統配置(使用root或者有權賬戶)1.2.2.1.修改環境變量1.2.2.2.修改虛擬內存區域1.2.2.3.關閉swap1.2.2.4.關閉防火墻1.2.2.5.創建用戶和組1.3.安裝doris1.3.1.解壓1.3.2.配置…

C# SqlSugar:依賴注入與倉儲模式實踐

C# SqlSugar&#xff1a;依賴注入與倉儲模式實踐 在 C# 的應用開發中&#xff0c;數據庫操作是必不可少的環節。為了讓數據訪問層更加簡潔、高效且易于維護&#xff0c;許多開發者會選擇成熟的 ORM&#xff08;對象關系映射&#xff09;框架&#xff0c;SqlSugar 就是其中備受…

Razor編程中@Helper的用法大全

文章目錄 第一章&#xff1a;Helper基礎概念1.1 Helper的定義與作用1.2 Helper的基本語法結構1.3 Helper與HtmlHelper的區別 第二章&#xff1a;基礎Helper用法2.1 無參數Helper2.2 帶簡單參數的Helper2.3 帶默認值的參數2.4 使用模型作為參數 第三章&#xff1a;高級Helper用法…

Python-正則表達式(re 模塊)

目錄 一、re 模塊的使用過程二、正則表達式的字符匹配1. 匹配開頭結尾2. 匹配單個字符3. 匹配多個字符4. 匹配分組5. Python 代碼示例 三、re 模塊的函數1. 函數一覽表2. Python 代碼示例1&#xff09;search 與 finditer2&#xff09;findall3&#xff09;sub4&#xff09;spl…

前端知識導圖

前端知識導圖 參考&#xff1a;字節標準 前端知識導圖 通用基礎 1、編程語言 HTML CSS JS TS 2、計算機基礎 計算機網略 數據結構 算法&#xff1a;二分查找、十大排序、二叉樹先中后和層次遍歷、集合交并集、leetcode刷題經驗 編譯構建 webpack & vite 應用基礎 開…