手撕商城體系之產商品系統

陳國利發表於2023-04-02

繼續接前文手撕商城系統架構設計與實現

本文主要講解商城體系下產商品系統的設計。商城系統可以拆分成多個業務中臺和多個應用服務。

1、產商品系統業務架構

產商品系統作為商城重要的基礎資訊組成部分,主要劃分為產品資訊和商品資訊,產品資訊保持最原始的產品基礎屬性和內容,商品資訊則根據不同的售賣策略、營銷價格屬性或SKU進行組裝而成。

因此商品源於產品而不同於產品。簡單概括來說,商品是營銷屬性的產品。

2、產商品關鍵內容資訊

產商品中心關鍵內容包括:產品資訊、商品資訊、目錄管理、標籤資訊、產品字典庫、產品分類、產品屬性、價格版本管理、商品SKU組合。

手撕商城體系之產商品系統

產品資訊應該包括產品基本資訊,產品價格(與報價系統產品價格統一庫),產品工藝術屬性資訊。

3、產商品系統邊界

產商品系統與其他系統關係

手撕商城體系之產商品系統

訂單系統與產商品系統呼叫關係

手撕商城體系之產商品系統

4、產商品結構模型設計

手撕商城體系之產商品系統

手撕商城體系之產商品系統

 

5、關鍵程式碼片斷

@RestController
@RequestMapping("/customer-api/v1/collect")
public class GoodsCollectController {

    /**
     * 分頁獲取當前會員的收藏列表
     *
     * @return 收藏列表
     */
    @RestApi(module = "商品收藏-C端", name = "分頁獲取當前會員的收藏列表", logabled = true)
    @PostMapping("/page")
    public DataResponse<CustomizePage<GoodsCollectVO>> pageCurrentMemberGoodsCollect(@RequestBody @Valid GoodsCollectQry qry) {
        CustomizePage<MallGoodsCollectE> goodsCollectE = MallGoodsCollectE.queryInstance().pageCurrentMemberGoodsCollect(qry);
        return DataResponse.of(GoodsCollectVOConverter.convert(goodsCollectE));
    }

    /**
     * 加入收藏
     *
     * @param cmd 商品id
     * @return 收藏列表
     */
    @RestApi(module = "商品收藏-C端", name = "加入收藏", logabled = true)
    @PostMapping("/add")
    public DataResponse<Boolean> addGoodsCollect(@RequestBody @Valid AddGoodsCollectCmd cmd) {
        return DataResponse.of(MallGoodsCollectE.queryInstance().addGoodsCollect(cmd));
    }

    /**
     * 取消收藏
     *
     * @param id 收藏id
     * @return 操作結果
     */
    @RestApi(module = "商品收藏-C端", name = "取消收藏", logabled = true)
    @DeleteMapping("/{id}")
    public DataResponse<Boolean> deleteGoodsCollect(@PathVariable Long id) {
        return DataResponse.of(MallGoodsCollectE.queryInstance().deleteGoodsCollect(id));
    }

    /**
     * 根據 商品id 查詢當前商品收藏情況
     *
     * @param cmd 商品id
     * @return 當前商品收藏情況
     */
    @RestApi(module = "商品收藏-C端", name = "根據 商品id 查詢當前商品收藏情況", logabled = true)
    @PostMapping("/getGoodsCollect")
    public DataResponse<GetGoodsCollectVO> getGoodsCollect(@RequestBody GetGoodsCollectCmd cmd) {
        MallGoodsCollectE mallGoodsCollectE = MallGoodsCollectE.queryInstance().getGoodsCollect(cmd);
        return DataResponse.of(BeanToolkit.instance().copy(mallGoodsCollectE, GetGoodsCollectVO.class));
    }
}
@Slf4j
@Service
public class GoodsService {
    @Autowired
    private GoodsSkuRpcService goodsSkuRpcService;
    @Autowired
    private GoodsGateway goodsGateway;

    /**
     * 查詢商品詳情
     */
    public Map<String, SpuApiCO> mapSkuCO(List<String> skuIds) {
        if (CollUtil.isEmpty(skuIds)) {
            return Collections.emptyMap();
        }
        DataResponse<Map<String, SpuApiCO>> dataResponse = goodsSkuRpcService.mapByIds(skuIds);
        return ResponseUtil.resultValidate(dataResponse);
    }

    /**
     * 批次更新商品庫存
     */
    public void updateInventory(List<UpdateInventoryDTO> dtoList) {
        goodsGateway.updateInventory(dtoList);
    }

    /**
     * 獲取商品供應商集合
     */
    public Map<String, SupplierDTO> mapSupplierCO() {
        return goodsGateway.mapSupplierCO();
    }

    /**
     * 計算商品購買價格
     *
     * @param skuCO       商品資訊
     * @param count       購買數量
     * @param memberLevel 會員等級
     * @param region      購買區域
     * @return 購買價格
     */
    public CalcPayPriceDTO calcPayPrice(SkuCO skuCO, Integer count, Integer memberLevel, String region) {
        //萬
        BigDecimal tenThousand = BigDecimal.valueOf(10000);
        //該方法的中的價格單位為分
        //商品原價,原積分
        Long price = BigDecimalUtils.yuan2Penny(skuCO.getPrice());
        Long integral = skuCO.getIntegral();

        //需支付價格,積分,運費
        Long goodsTotalPrice = price;
        Long goodsTotalIntegral = integral;
        Long freight = 0L;

        // 1、計算會員等級差異化
        DiffPriceOption levelDifference = skuCO.getLevelDifference();
        if (levelDifference.enabled()) {
            DiffPriceTmpl.DiffPriceForLevel diffPriceForLevel = levelDifference.getTmpl().getDiffs().stream()
                    .filter(tmpl -> tmpl.getLevel().equals(memberLevel))
                    .findFirst()
                    .get();

            if (DiffPriceMode.PERCENTAGE_DISCOUNT.getValue().equals(levelDifference.getTmpl().getMode())) {
                // 1.1、結算比例調整
                Long percent = diffPriceForLevel.getPercent().multiply(BigDecimal.valueOf(100)).longValue();
                goodsTotalPrice = BigDecimal.valueOf(price * percent).divide(tenThousand, RoundingMode.HALF_UP).longValue();
                // 積分不足1取1
                BigDecimal integralDecimal = BigDecimal.valueOf(integral * percent);
                goodsTotalIntegral = integralDecimal.compareTo(tenThousand) > 0 ?
                        integralDecimal.divide(tenThousand, RoundingMode.HALF_UP).longValue()
                        : integralDecimal.divide(tenThousand, RoundingMode.UP).longValue();
            } else if (DiffPriceMode.EXTRA_PAYMENT.getValue().equals(levelDifference.getTmpl().getMode())) {
                // 1.2、需額外支付
                if (diffPriceForLevel.getExtraPrice() != null) {
                    Long extraPrice = BigDecimalUtils.yuan2Penny(diffPriceForLevel.getExtraPrice());
                    goodsTotalPrice = (price + extraPrice);
                }
                if (diffPriceForLevel.getExtraIntegral() != null) {
                    goodsTotalIntegral = (integral + diffPriceForLevel.getExtraIntegral());
                }
            } else {
                throw new ServiceException("價格結算失敗");
            }
        }
        // 購物車結算時,收貨地址還沒選,選了再計算
        if (StringUtil.isNotEmpty(region)) {
            // 2、計算運費
            ShippingCostOption freeShippingRange = skuCO.getFreeShippingRange();
            if (freeShippingRange.enabled()) {
                UCRegionCacheCO customerRegion = MtdsBaseUCRegionCacheUtils.getUCRegionCacheCOById(region);
                Optional<ShippingCostTmpl.RegionalCost> regionalCostOptional = freeShippingRange.getTmpl().getRegionalCosts().stream()
                        .filter(tmpl -> customerRegion.getPids().contains(tmpl.getRegionId()))
                        .findFirst();

                if (regionalCostOptional.isPresent()) {
                    ShippingCostTmpl.RegionalCost regionalCost = regionalCostOptional.get();
                    // 2.1 滿足包郵條件
                    if (regionalCost.getFreeEnabled() == 1 && count >= regionalCost.getFreeQty()) {
                        freight = 0L;
                    } else {
                        // 2.2 計算運費
                        if (count <= regionalCost.getBaseQty()) {
                            freight = freight + BigDecimalUtils.yuan2Penny(regionalCost.getBasePrice());
                        } else {
                            freight = freight + BigDecimalUtils.yuan2Penny(regionalCost.getBasePrice());

                            int increaseCount = (count - regionalCost.getBaseQty());
                            long extraFreight = BigDecimalUtils.yuan2Penny(regionalCost.getIncreasePrice())
                                    * increaseCount;
                            freight = freight + (extraFreight);
                        }
                    }
                }
            }
        }

        //支付金額
        Long payPrice = (goodsTotalPrice * count) + freight;
        return CalcPayPriceDTO.builder()
                .skuId(Long.valueOf(skuCO.getId()))
                .oldGoodsTotalPrice(price * count)
                .goodsTotalPrice(goodsTotalPrice * count)
                .payPrice(payPrice)
                .freight(freight)
                .oldGoodsTotalIntegral(integral * count)
                .goodsTotalIntegral(goodsTotalIntegral * count)
                .build();
    }
}
@Slf4j
@Component
public class GoodsGatewayImpl implements GoodsGateway {
    @Autowired
    private GoodsSkuRpcService goodsSkuRpcService;
    @Autowired
    private GoodsSupplierRpcService supplierRpcService;

    @Override
    public void updateInventory(List<UpdateInventoryDTO> dtoList) {
        List<SkuIncrementCmd> skuIncrementCmds = BeanToolkit.instance().copyList(dtoList, SkuIncrementCmd.class);
        Response response = goodsSkuRpcService.increment(skuIncrementCmds);
        if (!response.getStatus()) {
            throw new RpcErrorException(response.getMessage(), "商品");
        }
    }

    @Override
    public Map<String, SupplierDTO> mapSupplierCO() {
        DataResponse<List<SupplierCO>> response = supplierRpcService.listAll();
        List<SupplierCO> supplierCOS = ResponseUtil.resultValidate(response);
        if (CollUtil.isEmpty(supplierCOS)) {
            return Collections.emptyMap();
        }

        List<SupplierDTO> supplierDTOS = BeanToolkit.instance().copyList(supplierCOS, SupplierDTO.class);
        return supplierDTOS.stream().collect(Collectors.toMap(SupplierDTO::getId, Function.identity()));
    }
}

 

相關文章