✨ Spring Boot 精粹:为寄售详情打造专属库存聚合 API !!!

✨ Spring Boot 精粹:为寄售详情打造专属库存聚合 API 📈

在许多业务场景中,除了查看详细数据列表,我们还需要快速了解关键的聚合指标。例如,在“寄售库存详情”管理中,实时掌握总库存数量和总库存价值至关重要。本文将详细阐述如何使用 Spring Boot 为 ConsignmentDetail(寄售库存详情)模块创建一个专属的 API (Application Programming Interface - 应用程序编程接口) 端点,用于高效计算和提供这些关键的库存聚合数据。

📜 内容焦点

方面描述关键组件/实现
核心需求获取指定寄售库存下的总库存数量 (totalStockCount) 和总库存价值 (totalStockValue)独立的 GET API 端点
数据载体 (DTO - Data Transfer Object)ConsignmentDetailStockAggregationDTO封装 totalStockCounttotalStockValue
Service 层核心getConsignmentDetailStockAggregates 方法负责业务逻辑计算
辅助方法 fillStockAndValueForDetail计算单个详情的 stockstockValue (代码复用)
Controller 层新增 GET /api/consignmentSettlement/details/stockAggregates 端点暴露聚合查询功能
数据准确性依赖 ConsignmentSummary 计算实时 stockConsignmentSummaryRepository.sumStockInCountBy...

🤔 为何需要专属聚合 API?

当我们需要快速了解一个业务集合的整体情况时(比如一个寄售批次的总库存和总价值),如果这些聚合数据与明细列表数据混在一起,或者需要客户端自行计算,会带来诸多不便:

  • 🐢 客户端计算缓慢/不准确:将大量明细数据传输到客户端再进行聚合,效率低下且容易出错。
  • ⚙️ 后端列表接口臃肿:如果列表接口被迫承担聚合计算,会使其职责不清,响应变慢。
  • 🔄 数据获取不灵活:有时我们只需要聚合数据,而不需要完整的明细列表。

因此,设计一个专门的、服务端的聚合 API 是更清晰、高效的解决方案。

💡 解决方案:精准打击,一步到位

我们将通过以下步骤构建这个专属的聚合 API:

  1. 定义 DTOConsignmentDetailStockAggregationDTO 用于清晰地返回聚合结果。
  2. Service 层实现
    • getConsignmentDetailStockAggregates 方法作为核心,负责从数据库获取所有相关详情,然后逐条计算其库存和价值,并进行累加。
    • fillStockAndValueForDetail 辅助方法,用于统一计算单个 ConsignmentDetailstock(依赖 ConsignmentSummary)和 stockValue
  3. Controller 层暴露端点:创建一个新的 GET 请求端点来调用 Service 层的聚合方法。

1. DTO 定义:ConsignmentDetailStockAggregationDTO 📊

这是我们聚合数据的标准输出格式:

// com.productQualification.api.dto.ConsignmentDetailStockAggregationDTO
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "ConsignmentDetailStockAggregationDTO", description = "寄售库存详情的库存相关聚合数据DTO")
public class ConsignmentDetailStockAggregationDTO {
    @ApiModelProperty(value = "总库存数量", example = "1500")
    private Integer totalStockCount;
    @ApiModelProperty(value = "总库存价值", example = "75000.00")
    private BigDecimal totalStockValue;
}

2. Service 层核心逻辑 🧠

// com.productQualification.api.service.consignmentSettlement.ConsignmentDetailService
@Service
public class ConsignmentDetailService {
    @Autowired
    private ConsignmentDetailRepository consignmentDetailRepository;
    @Autowired
    private ConsignmentSummaryRepository consignmentSummaryRepository; // 用于计算库存

    private static final Logger log = LoggerFactory.getLogger(ConsignmentDetailService.class);
    private static final List<Integer> STOCK_CALCULATION_STATUSES = Arrays.asList(1, 2, 3, 4, 5, 6); // 计算库存的状态

    /**
     * 辅助方法:为单个 ConsignmentDetail 实体填充 stock 和 stockValue。
     */
    private void fillStockAndValueForDetail(ConsignmentDetail detail) {
        if (detail == null) return;
        if (detail.getProductId() == null || detail.getAdminId() == null || detail.getConsignmentSettlementId() == null) {
            log.warn("跳过库存计算 (Detail ID: {}), 原因: productId, adminId, 或 consignmentSettlementId 缺失.", detail.getId());
            detail.setStock(0); detail.setStockValue(BigDecimal.ZERO); return;
        }
        Integer calculatedStock = consignmentSummaryRepository.sumStockInCountByConsignmentSettlementAndProductAndAdminAndStatusIn(
                detail.getConsignmentSettlementId(), detail.getProductId(), detail.getAdminId(), STOCK_CALCULATION_STATUSES
        );
        detail.setStock(calculatedStock != null ? calculatedStock : 0);
        if (detail.getStock() > 0 && detail.getSettlementPrice() != null) {
            detail.setStockValue(BigDecimal.valueOf(detail.getStock()).multiply(detail.getSettlementPrice()).setScale(2, BigDecimal.ROUND_HALF_UP));
        } else {
            detail.setStockValue(BigDecimal.ZERO);
        }
        log.trace("已填充库存信息 (Detail ID {}): stock={}, stockValue={}", detail.getId(), detail.getStock(), detail.getStockValue());
    }

    /**
     * 获取指定寄售库存ID和管理员ID下的库存聚合数据 (总库存数和总库存价值)。
     */
    @Transactional(readOnly = true)
    public ConsignmentDetailStockAggregationDTO getConsignmentDetailStockAggregates(
            Integer adminId, Integer consignmentSettlementId) {
        log.debug("开始计算库存聚合数据: adminId={}, consignmentSettlementId={}", adminId, consignmentSettlementId);
        // 1. 获取指定 consignmentSettlementId 和 adminId 下的所有 ConsignmentDetail 记录
        List<ConsignmentDetail> allDetails = consignmentDetailRepository.findAllByConsignmentSettlementIdAndAdminId(
                consignmentSettlementId, adminId
        );
        int totalStockCount = 0;
        BigDecimal totalStockValue = BigDecimal.ZERO;
        if (!CollectionUtils.isEmpty(allDetails)) {
            log.info("找到 {} 条寄售详情记录进行聚合计算 (ConsignmentSettlement ID: {})", allDetails.size(), consignmentSettlementId);
            // 2. 遍历所有详情记录
            for (ConsignmentDetail detail : allDetails) {
                // 3. 为每条详情记录填充其当前的 stock 和 stockValue
                fillStockAndValueForDetail(detail);
                // 4. 累加库存数量和库存价值
                totalStockCount += detail.getStock();
                if (detail.getStockValue() != null) { // 防御性编程
                    totalStockValue = totalStockValue.add(detail.getStockValue());
                }
            }
        } else {
            log.info("未找到寄售详情记录进行聚合计算 (ConsignmentSettlement ID: {})", consignmentSettlementId);
        }
        log.info("库存聚合计算完成 (ConsignmentSettlement ID {}): 总库存数量={}, 总库存价值={}",
                consignmentSettlementId, totalStockCount, totalStockValue);
        // 5. 返回包含聚合结果的 DTO
        return new ConsignmentDetailStockAggregationDTO(totalStockCount, totalStockValue);
    }
    // ... (其他与分页列表相关的 Service 方法,如 findPaginatedDetails,这里不再赘述其内部实现细节)
}

核心思路getConsignmentDetailStockAggregates 方法不关心分页,它获取指定条件下的 所有 ConsignmentDetail 记录,然后为每一条计算最新的 stockstockValue(通过调用 fillStockAndValueForDetail,该方法内部会查询 ConsignmentSummary),最后将这些值累加起来。

3. Controller 层暴露 API 端点 📡

// com.productQualification.api.controller.consignmentSettlement.ConsignmentSettlementController
@RestController
@RequestMapping("/api/consignmentSettlement")
public class ConsignmentSettlementController {
    @Autowired
    private ConsignmentDetailService consignmentDetailService;
    private static final Logger log = LoggerFactory.getLogger(ConsignmentSettlementController.class);
    private static final int CONSIGNMENT_TOOL_TYPE = 10; // 假设的常量

    /**
     * 获取指定寄售库存的详情库存聚合数据 (总库存数和总库存价值)。
     */
    @GetMapping("/details/stockAggregates") // 清晰的 GET 请求路径
    @ApiOperation("获取指定寄售库存的详情库存聚合数据")
    public BaseResult getConsignmentDetailStockAggregates(
            @EffectiveAdminId(toolType = CONSIGNMENT_TOOL_TYPE) Integer adminId, // 从会话或Token获取管理员ID
            @RequestParam @ApiParam(value = "寄售库存ID", required = true, example = "1") Integer consignmentSettlementId) {
        try {
            log.info("请求获取库存聚合数据: adminId={}, consignmentSettlementId={}", adminId, consignmentSettlementId);
            ConsignmentDetailStockAggregationDTO aggregates = consignmentDetailService.getConsignmentDetailStockAggregates(adminId, consignmentSettlementId);
            return BaseResult.success("查询聚合数据成功", aggregates);
        } catch (Exception e) {
            log.error("查询寄售库存详情聚合数据失败: adminId={}, csId={}, error={}", adminId, consignmentSettlementId, e.getMessage(), e);
            // 返回统一的错误结构
            return BaseResult.failure(BaseResult.FAILURE, "查询聚合数据失败:" + e.getMessage());
        }
    }

    // ... (其他 Controller 端点,例如用于获取分页列表的 POST /details/listByPage)
}

🌊 Service 层聚合计算流程图 (Mermaid Flowchart)

展示 getConsignmentDetailStockAggregates 方法的逻辑:

是 (空列表)
否 (有数据)
循环下一条
遍历完成
API 请求: GET /details/stockAggregates (携带 consignmentSettlementId, adminId)
Service: getConsignmentDetailStockAggregates(adminId, csId)
Repository: findAllByConsignmentSettlementIdAndAdminId(csId, adminId)
获取所有相关 ConsignmentDetail 列表
列表是否为空?
totalStockCount = 0, totalStockValue = 0
遍历每一条 ConsignmentDetail (detail)
Service: fillStockAndValueForDetail(detail)
内部查询 ConsignmentSummary 获取 stock
计算 stockValue (stock * settlementPrice)
累加: totalStockCount += detail.getStock()
累加: totalStockValue += detail.getStockValue()
创建 DTO: new ConsignmentDetailStockAggregationDTO(totalStockCount, totalStockValue)
Service 返回 DTO
Controller 返回 BaseResult (含 DTO)
API 响应客户端

🔄 时序图:一次聚合数据请求的旅程 (Mermaid Sequence Diagram)

客户端 "API网关/前端" "ConsignmentSettlementController" "ConsignmentDetailService" "ConsignmentDetailRepository" "ConsignmentSummaryRepository" 请求 GET /api/consignmentSettlement/details/stockAggregates?consignmentSettlementId=X 调用 getConsignmentDetailStockAggregates(adminId, X) getConsignmentDetailStockAggregates(adminId, X) findAllByConsignmentSettlementIdAndAdminId(X, adminId) 返回 List<ConsignmentDetail> allDetails 调用 fillStockAndValueForDetail(detail) 开始填充单个详情的库存和价值 sumStockInCountByConsignmentSettlementAndProductAndAdminAndStatusIn(...) 返回 calculatedStock detail.setStock(calculatedStock) detail.setStockValue(calculatedStock * detail.getSettlementPrice()) loop [对 allDetails 中的每个 detail] 汇总 totalStockCount 和 totalStockValue 返回 ConsignmentDetailStockAggregationDTO 封装 BaseResult.success(dto) 返回 BaseResult 响应聚合数据 客户端 "API网关/前端" "ConsignmentSettlementController" "ConsignmentDetailService" "ConsignmentDetailRepository" "ConsignmentSummaryRepository"

🌟 专属聚合 API 的优势

  • 🎯 目标明确:接口只做一件事——提供聚合数据。
  • 🚀 按需获取:客户端只在需要时才请求聚合信息,避免不必要的计算和传输。
  • 🧹 后端清爽:保持列表查询接口的轻量级,聚合逻辑独立封装。
  • 潜在性能更优:如果聚合逻辑复杂或数据量大,未来可以在 Repository 层使用 JPQL (Java Persistence Query Language - Java持久化查询语言) / SQL (Structured Query Language - 结构化查询语言) 直接进行数据库层面的聚合,性能会更好。当前 Service 层计算对于中小型数据量是可行的。
  • 🪜 可扩展性:未来如果需要其他类型的聚合,可以类似地添加新的专属 API。

📚 英文缩写全称及中文解释

  • API: Application Programming Interface - 应用程序编程接口
  • DTO: Data Transfer Object - 数据传输对象
  • JPA: Java Persistence API - Java持久化应用程序接口
  • SQL: Structured Query Language - 结构化查询语言
  • HTTP: Hypertext Transfer Protocol - 超文本传输协议
  • GET: Hypertext Transfer Protocol GET method - 超文本传输协议GET方法 (一种HTTP请求方法)

🧠 思维导图总结

在这里插入图片描述


通过实现这个专属的库存聚合 API,我们能够为前端提供一个快速、准确获取关键业务指标的方式,同时也优化了后端服务的结构。希望这篇聚焦于聚合功能的博客能帮助你!💪

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值