Java中Stream应用场景示例-订单报表分组统计

场景

Java8新特性-Stream对集合进行操作的常用API:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/126070657

针对Stream的一个常用的业务场景:对订单表按照产品id分组,展示不用产品

的不用状态(已支付、已退款)状态下的订单笔数统计、金额统计等报表实现。

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

实现

首先订单实体的关键字段

复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("instant_orders")
public class InstantOrders implements Serializable{

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "主键id")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private String id;


    @ApiModelProperty(value = "订单状态(PENDING_PAYMENT-待付款 PAID-已支付 COMPLETED-已完成 CANCELLED-已取消 REFUNDING-退款中 REFUNDED-已退款 CLOSED-已关闭)")
    @TableField("order_status")
    private InstantOrderStatusEnum orderStatus;



    @ApiModelProperty(value = "支付状态(PENDING-待支付 PAID-已支付 REFUNDED-已退款)")
    @TableField("pay_status")
    private InstantOrderPayStatusEnum payStatus;


    @ApiModelProperty(value = "产品id")
    @TableField("product_id")
    private String productId;


    @ApiModelProperty(value = "加油升数")
    @TableField("liters")
    private BigDecimal liters;


    @ApiModelProperty(value = "实收价格")
    @TableField("reality_price")
    private BigDecimal realityPrice;


}

分组统计实现

1、首先查询出符合条件的订单数据

复制代码
        List<InstantOrders> list = new LambdaQueryChainWrapper<>(instantOrdersMapper)
                .eq(InstantOrders::getStoreId, shiftFinishDTO.getStoreId())
                .gt(InstantOrders::getCreateTime,shiftFinishDTO.getStartTime())
                .lt(InstantOrders::getCreateTime,shiftFinishDTO.getEndTime())
                .list();

2、判空处理后根据产品id分组并批量查询产品名称

复制代码
            Set<String> productIds = list.stream()
                    .map(InstantOrders::getProductId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());
            //批量获取产品名称映射
            Map<String, String> productNameMap = iProductService.batchGetProductNames(productIds);

3、批量根据产品id查询产品名称实现

复制代码
    public Map<String, String> batchGetProductNames(Collection<String> productIds) {
        if (CollectionUtils.isEmpty(productIds))  {
            return Collections.emptyMap();
        }

        // 使用MP的in查询
        List<Product> products = new LambdaQueryChainWrapper<>(productMapper)
                .select(Product::getId, Product::getProductName)
                .in(Product::getId, productIds)
                .eq(Product::getIsDelete, IsDeletedEnum.NOT_DELETED)
                .list();

        return products.stream()
                .collect(Collectors.toMap(
                        Product::getId,
                        Product::getProductName,
                        (oldVal, newVal) -> oldVal  // 重复key处理
                ));
    }

4、根据产品id进行分组统计

复制代码
           List<OilTradingDTO> OilTradingDTOs = list.stream()
                    .filter(order -> order.getProductId() != null)
                    .collect(Collectors.groupingBy(
                            InstantOrders::getProductId,
                            Collectors.collectingAndThen(
                                    Collectors.toList(),
                                    orderList -> {
                                        OilTradingDTO oilTradingDTO = new OilTradingDTO();
                                        // 设置产品名称
                                        oilTradingDTO.setProductName(
                                                productNameMap.getOrDefault(
                                                        orderList.get(0).getProductId(),
                                                        "未知产品"
                                                )
                                        );

                                        // 统计升数总和
                                        oilTradingDTO.setLiter(orderList.stream()
                                                .map(o -> o.getLiters() != null ? o.getLiters() : BigDecimal.ZERO)
                                                .reduce(BigDecimal.ZERO, BigDecimal::add));

                                        // 统计订单笔数
                                        oilTradingDTO.setOrderCount(orderList.size());

                                        // 统计实收金额(PAID状态)
                                        oilTradingDTO.setReceivedAmount(orderList.stream()
                                                .filter(o -> o.getPayStatus() == InstantOrderPayStatusEnum.PAID)
                                                .map(o -> o.getRealityPrice() != null ? o.getRealityPrice() : BigDecimal.ZERO)
                                                .reduce(BigDecimal.ZERO, BigDecimal::add));

                                        // 统计退款金额(REFUNDED状态)
                                        oilTradingDTO.setRefundAmount(orderList.stream()
                                                .filter(o -> o.getPayStatus() == InstantOrderPayStatusEnum.REFUNDED)
                                                .map(o -> o.getRealityPrice() != null ? o.getRealityPrice() : BigDecimal.ZERO)
                                                .reduce(BigDecimal.ZERO, BigDecimal::add));

                                        return oilTradingDTO;
                                    }
                            )
                    ))
                    .values()
                    .stream()
                    .collect(Collectors.toList());

其中用到的结果dto

复制代码
@Data
@Builder
@AllArgsConstructor
@Accessors(chain = true)
public class OilTradingDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("产品名称")
    private String productName;

    @ApiModelProperty(value = "升数(升)", example = "23.18")
    private BigDecimal liter;

    @ApiModelProperty(value = "订单笔数", example = "3")
    private Integer orderCount;

    @ApiModelProperty(value = "加油实收金额(元)", example = "200.50")
    private BigDecimal receivedAmount;

    @ApiModelProperty(value = "退款金额(元)", example = "200.50")
    private BigDecimal refundAmount;

    public OilTradingDTO(){
        this.liter = BigDecimal.ZERO;
        this.receivedAmount = BigDecimal.ZERO;
        this.refundAmount = BigDecimal.ZERO;
    }

}

统计逻辑细化讲解:

过滤操作

.filter(order -> order.getProductId() != null): 过滤掉productId为null的订单,确保后续分组操作不会因null值出错。

分组操作

复制代码
.collect(Collectors.groupingBy(
        InstantOrders::getProductId,
        Collectors.collectingAndThen(
                Collectors.toList(),
                orderList -> {
                    // 转换逻辑
                }
        )
))

Collectors.groupingBy(): 按产品ID分组

第一个参数InstantOrders::getProductId: 分组依据是订单的productId字段

第二个参数是下游收集器,这里使用collectingAndThen

Collectors.collectingAndThen(): 先执行第一个收集器,然后对结果应用转换函数

Collectors.toList(): 将相同产品ID的订单收集到列表中

orderList -> {...}: 对每个产品ID对应的订单列表进行转换

转换操作

这部分为每个产品ID创建并填充一个OilTradingDTO对象:

产品名称设置:

从productNameMap中查找产品名称,使用第一个订单的productId作为key

如果找不到则使用默认值"未知产品"

升数总和统计:

使用流处理所有订单的liters字段

处理null值(使用BigDecimal.ZERO替代)

使用reduce求和

订单笔数统计:

直接使用订单列表的size()

实收金额统计:

只处理支付状态为PAID的订单

同样处理null值并求和

退款金额统计:

只处理支付状态为REFUNDED的订单

处理null值并求和

相关推荐
程序员烧烤4 小时前
【Java基础14】函数式接口、lamba表达式、方法引用一网打尽(下)
java·开发语言
spencer_tseng4 小时前
pinyin4j-2.5.0.jar
java·jar·pinyin4j
ZhengEnCi4 小时前
J1B-为什么99%的人配置Java环境失败?大厂开发者5分钟搞定的JDK安装与环境配置完全指南
java
零雲4 小时前
java面试:有了解过kafka架构吗,可以详细讲一讲吗
java·面试·kafka
一行•坚书4 小时前
kafka服务端与客户端如何协作?生产者发送消息分区策略是什么?消费者组分区策略?集群与ACK机制?
java·后端·kafka
serve the people4 小时前
Prompt Composition with LangChain’s PipelinePromptTemplate
java·langchain·prompt
天天摸鱼的java工程师4 小时前
干掉系统卡顿!Excel异步导出完整实战方案(百万数据也不慌)
java·后端
心随雨下5 小时前
Java中将System.out内容写入Tomcat日志
java·开发语言·tomcat
-指短琴长-5 小时前
ProtoBuf速成【基于C++讲解】
android·java·c++