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值并求和

相关推荐
懒惰蜗牛几秒前
Day63 | Java IO之NIO三件套--选择器(下)
java·nio·选择器·selector·半包粘包·tcp缓冲区
JavaGuide7 分钟前
美团2026届后端一二面(附详细参考答案)
java·后端
打工人你好9 分钟前
如何设计更安全的 VIP 权限体系
java·jvm·安全
L.EscaRC16 分钟前
Spring IOC核心原理与运用
java·spring·ioc
摇滚侠29 分钟前
2025最新 SpringCloud 教程,Nacos-总结,笔记19
java·笔记·spring cloud
在逃热干面33 分钟前
(笔记)获取终端输出保存到文件
java·笔记·spring
爱笑的眼睛1134 分钟前
深入理解MongoDB PyMongo API:从基础到高级实战
java·人工智能·python·ai
笃行客从不躺平44 分钟前
遇到大SQL怎么处理
java·开发语言·数据库·sql
q***87601 小时前
Spring Boot 整合 Keycloak
java·spring boot·后端
Billow_lamb1 小时前
Spring Boot2.x.x全局拦截器
java·spring boot·后端