spring mongoTemplate 统计

本文介绍如何在MongoDB中实现跨越两个月的短信发送统计,包括成功、失败及未知状态的数量计算,并通过Java 8 Stream API简化合并过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景介绍

上篇spring mongoTemplate 分表分页查询介绍了数据被分散到多个集合中怎么查,现在同时也迎来了怎么统计的问题,由于原来所有数据在mysql一张表所以不管怎么查,怎么统计都很方便,我查看了一下原来统计的代码,发现大佬就写了一行,当然sql不止一行,后来我改好后发现,卧槽!!!写了300多行。

统计计划

  1. 由于发出去的短信实时都有状态报告回来,上篇文章中提到的2个集合实时都会有数据变化,但我这边业务上认定超过7天不回来的状态报告就不会回来了,所以我这边统计也是统计近7天的结果。
  2. 由于统计前7填的数据,因此存在同月和跨月的情况,如果是跨越的话计算出来的结果还需要按字段合并,这里使用java8的stream的reduce很方便
  3. 会统计成功数、失败数、未知数以及成功率4个纬度,解释下:
    成功数:我放发送成功且第三方平台下发成功,分别两个字段记录
    失败数:我放发送失败或者第三方平台下发失败
    未知数:未接收到状态报告
    所以我们可以知道只有失败数量时两张表中都会存在的,因为,失败数需要查两边合并起来
    成功率:成功数/总数
    这个总数应当是当前统计的总数,而不是发送的总数,因为短信发送可能持续较长一段时间,所以成功率短时间也是一直变化的

好了,现在我们看一下被扩展了300多行的代码吧!

public void execute() {
        logger.info("---------------------------开始:短信发送结果回调统计任务---------------------------");
        //1.查询出7天内的统计结果
//        List<SendResultStatistics> resultList = sendResultStatisticsMapper.doSendResultStatistics();
        List<SendResultStatistics> resultList = statistics7DaySendSmslogFromMongodb();
        //2.遍历统计结果,根据taskId填充统计结果到对应数据中。
        if (CollectionUtils.isNotEmpty(resultList)){
            for(SendResultStatistics s : resultList) {
                String taskId = s.getTaskId();
                List<SmsLog> smsLogList = smsLogService.selectList(new EntityWrapper<SmsLog>().eq("tast_id", taskId));
                if (smsLogList != null && smsLogList.size() > 0) {
                    SmsLog smsLog = smsLogList.get(0);
                    smsLog.setSuccess(s.getSuccess() == null ? 0 : s.getSuccess());
                    smsLog.setUnknow(s.getUnknow() == null ? 0 : s.getUnknow());
                    smsLog.setFailed(s.getFailed() == null ? 0 : s.getFailed());
                    smsLog.setSuccessRate(getSuccessRate(s.getSuccess(),s.getTotal().intValue()));
                    smsLogService.updateById(smsLog);
                } else {
                    continue;
                }
            }
        }
        logger.info("---------------------------结束:短信发送结果回调统计任务---------------------------");
    }

    private Double getSuccessRate(Integer success,Integer total){
        if (success == null || total == null || total == 0) {
            return 0d;
        }
        double c = (double) success / (double) total;
        c = new BigDecimal(c).setScale(3, BigDecimal.ROUND_HALF_UP).doubleValue();
        return c;
    }

    /**
     * 从mongodb的短信发送全量日志和预日志中按任务号统计
     * @return
     */
    private List<SendResultStatistics> statistics7DaySendSmslogFromMongodb() {
        Calendar calendar = Calendar.getInstance();
        Date now = calendar.getTime();
        int nowMonth = calendar.get(Calendar.MONTH) + 1;
        calendar.add(Calendar.DATE, -7);
        Date sevenDaysAgo = calendar.getTime();
        int sevenDaysAgoMonth = calendar.get(Calendar.MONTH) + 1;
        List<SendResultStatistics> list;
        String waitCollectionName;
        String allCollectionName;
        Date start;
        Date end;
        if (nowMonth == sevenDaysAgoMonth){
            //同月
            start = sevenDaysAgo;
            end = now;
            waitCollectionName = getColletionName(now,0);
            allCollectionName = getColletionName(now,1);
            list = statisticsDataByCreateTimeBetween(end,start,waitCollectionName,allCollectionName);
        }else{
            //临月
            waitCollectionName = getColletionName(now,0);
            allCollectionName = getColletionName(now,1);
            calendar = Calendar.getInstance();
            calendar.setTime(now);
            calendar.set(Calendar.DAY_OF_MONTH,calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
            calendar.set(Calendar.HOUR_OF_DAY,0);
            calendar.set(Calendar.MINUTE,0);
            calendar.set(Calendar.SECOND,0);
            start = calendar.getTime();
            end = now;
            List<SendResultStatistics> list1 = statisticsDataByCreateTimeBetween(end,start,waitCollectionName,allCollectionName);
            calendar = Calendar.getInstance();
            calendar.setTime(now);
            calendar.set(Calendar.DAY_OF_MONTH,calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
            calendar.set(Calendar.HOUR_OF_DAY,23);
            calendar.set(Calendar.MINUTE,59);
            calendar.set(Calendar.SECOND,59);
            end = calendar.getTime();
            start = sevenDaysAgo;
            waitCollectionName = getColletionName(sevenDaysAgo,0);
            allCollectionName = getColletionName(sevenDaysAgo,1);
            List<SendResultStatistics> list2 = statisticsDataByCreateTimeBetween(end,start,waitCollectionName,allCollectionName);
            list = megerList(list1,list2);
        }
        return list;
    }

    private List<SendResultStatistics> megerList(List<SendResultStatistics> list1, List<SendResultStatistics> list2) {
        if (CollectionUtils.isEmpty(list1) && CollectionUtils.isEmpty(list2)){
            return new ArrayList<>();
        }else if (CollectionUtils.isNotEmpty(list1) && CollectionUtils.isEmpty(list2)){
            return list1;
        }else if (CollectionUtils.isEmpty(list1) && CollectionUtils.isNotEmpty(list2)){
            return list2;
        }else {
            // CollectionUtils.isNotEmpty(list1) && CollectionUtils.isNotEmpty(list2)
            list1.addAll(list2);
            return list1.stream()
                        .collect(Collectors.groupingBy(SendResultStatistics :: getTaskId))
                        .entrySet().stream().map(e -> {
                            if (e.getValue().size() == 1){
                                /**
                                 * 1.临月查询可能性:
                                 * 一般情况下一个任务发的一批短信日志都会在同一个月内
                                 * 2.合并success,falied,unknow三个集合可能性:
                                 * 由于各个集合查询mongodb集合不同,每个集合很有可能元素数量不一样,
                                 * 合并集合中任务号不一致则说明没有任务号重复的元素直接返回不用合并字段
                                 */
                                return e.getValue().get(0);
                            }else{
                                /**
                                 * 1.临月查询可能性:
                                 * 由于一个任务发的一批短信量的缘故,并发发短信会有先后顺序,
                                 * 极端情况可能出现短信创建时间前一批上0点前,后一批在零点后,
                                 * 月底时候有可能跨月,这是由于构建短信日志对象时创建时间是new的
                                 * 而不是取自该次任务的创建时间
                                 * 2.合并success,falied,unknow三个集合可能性:
                                 * 由于各个集合查询mongodb集合不同,每个集合很有可能元素数量不一样,
                                 * 但肯定有不同集合中任务号一致的,任务号一致的字段合并
                                 */
                                return e.getValue().stream().reduce(
                                        (x,y) -> new SendResultStatistics(
                                                x.getTaskId(),
                                                (x.getTotal() == null ? 0 : x.getTotal()) + (y.getTotal() == null ? 0 : y.getTotal()),
                                                (x.getSuccess() == null ? 0 : x.getSuccess()) + (y.getSuccess() == null ? 0 : y.getSuccess()),
                                                (x.getUnknow() == null ? 0 : x.getUnknow()) + (y.getUnknow() == null ? 0 : y.getUnknow()),
                                                (x.getFailed() == null ? 0 : x.getFailed()) + (y.getFailed() == null ? 0 : y.getFailed())
                                                )
                                ).orElse(new SendResultStatistics());
                            }
                        }).collect(Collectors.toList());
        }
    }

    private List<SendResultStatistics> statisticsDataByCreateTimeBetween(Date now, Date sevenDaysAgo,String waitCollectionName, String allCollectionName) {
        //预日志:未知数量
        Aggregation unknowAgg = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("createTime").gte(sevenDaysAgo).lte(now).and("state").is("0")),
                Aggregation.group(
                        Aggregation.fields().and("batchNum")
                ).count().as("unknow")
        );
        AggregationResults<Map> unknowResults = this.mongoTemplate.aggregate(unknowAgg,waitCollectionName,Map.class);
        List<SendResultStatistics> unknowList = null;
        if (unknowResults != null){
            unknowList = unknowResults.getMappedResults().stream().map(e -> {
                SendResultStatistics statistics = new SendResultStatistics();
                statistics.setTaskId(e.get("_id").toString());
                statistics.setUnknow((Integer) e.get("unknow"));
                return statistics;
            }).collect(Collectors.toList());
        }
        //全量日志:成功数量
        Aggregation successAgg = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("createTime").gte(sevenDaysAgo).lte(now).and("state").is("0").and("sentStatus").is("0")),
                Aggregation.group(
                            Aggregation.fields().and("batchNum")
                ).count().as("success")
        );
        AggregationResults<Map> successResults = this.mongoTemplate.aggregate(successAgg,allCollectionName,Map.class);
        List<SendResultStatistics> successList = null;
        if (successResults != null){
            successList = successResults.getMappedResults().stream().map(e -> {
                SendResultStatistics statistics = new SendResultStatistics();
                statistics.setTaskId(e.get("_id").toString());
                statistics.setSuccess((Integer) e.get("success"));
                return statistics;
            }).collect(Collectors.toList());
        }
        //预日志:失败数量
        Aggregation failedWaitAgg = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("createTime").gte(sevenDaysAgo).lte(now).and("state").is("1")),
                Aggregation.group(
                        Aggregation.fields().and("batchNum")
                ).count().as("failed")
        );
        AggregationResults<Map> failedWaitResults = this.mongoTemplate.aggregate(failedWaitAgg,waitCollectionName,Map.class);
        List<SendResultStatistics> failedWaitList = null;
        if (failedWaitResults != null){
            failedWaitList = failedWaitResults.getMappedResults().stream().map(e -> {
                SendResultStatistics statistics = new SendResultStatistics();
                statistics.setTaskId(e.get("_id").toString());
                statistics.setFailed((Integer) e.get("failed"));
                return statistics;
            }).collect(Collectors.toList());
        }
        //全量日志:失败数量
        Aggregation failedAllAgg = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("createTime").gte(sevenDaysAgo).lte(now).and("sentStatus").is("1")),
                Aggregation.group(
                        Aggregation.fields().and("batchNum")
                ).count().as("failed")
        );
        AggregationResults<Map> failedAllResults = this.mongoTemplate.aggregate(failedAllAgg,allCollectionName,Map.class);
        List<SendResultStatistics> failedAllList = null;
        if (failedAllResults != null){
            failedAllList = failedAllResults.getMappedResults().stream().map(e -> {
                SendResultStatistics statistics = new SendResultStatistics();
                statistics.setTaskId(e.get("_id").toString());
                statistics.setFailed((Integer) e.get("failed"));
                return statistics;
            }).collect(Collectors.toList());
        }
        //合并失败数量集合
        List<SendResultStatistics> failedList = null;
        if (CollectionUtils.isEmpty(failedWaitList) && CollectionUtils.isNotEmpty(failedAllList)){
            failedList = failedAllList;
        }
        if (CollectionUtils.isNotEmpty(failedWaitList) && CollectionUtils.isEmpty(failedAllList)){
            failedList = failedWaitList;
        }
        if (CollectionUtils.isNotEmpty(failedWaitList) && CollectionUtils.isNotEmpty(failedAllList)){
            failedWaitList.addAll(failedAllList);
            failedList = failedWaitList.stream().collect(Collectors.groupingBy(SendResultStatistics :: getTaskId,Collectors.summingInt(SendResultStatistics :: getFailed)))
                    .entrySet()
                    .stream()
                    .map(e -> new SendResultStatistics(e.getKey(), e.getValue()))
                    .collect(Collectors.toList());
        }
        //合并成功、未知、失败三个集合
        List<SendResultStatistics> result = new ArrayList<>();
        if (CollectionUtils.isEmpty(successList) && CollectionUtils.isEmpty(unknowList) && CollectionUtils.isEmpty(failedList)){
            return result;
        }
        if (CollectionUtils.isNotEmpty(successList)){
            result = successList;
            if (CollectionUtils.isNotEmpty(unknowList)){
                result = megerList(result,unknowList);
            }
            if (CollectionUtils.isNotEmpty(failedList)){
                result = megerList(result,failedList);
            }
        }else if (CollectionUtils.isNotEmpty(unknowList)){
            result = unknowList;
            if (CollectionUtils.isNotEmpty(successList)){
                result = megerList(result,successList);
            }
            if (CollectionUtils.isNotEmpty(failedList)){
                result = megerList(result,failedList);
            }
        }else{
            // failedList 不为空
            result = failedList;
            if (CollectionUtils.isNotEmpty(successList)){
                result = megerList(result,successList);
            }
            if (CollectionUtils.isNotEmpty(unknowList)){
                result = megerList(result,unknowList);
            }
        }
        if (CollectionUtils.isNotEmpty(result)) {
            result.forEach(e -> {
                int success = e.getSuccess() == null ? 0 : e.getSuccess();
                int unknow = e.getUnknow() == null ? 0 : e.getUnknow();
                int failed = e.getFailed() == null ? 0 : e.getFailed();
                e.setTotal(success + unknow + failed);
            });
        }
        return result;
    }
     private String getColletionName(Date date,int flag) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int nowYear = calendar.get(Calendar.YEAR);
        int nowMonth = calendar.get(Calendar.MONTH) + 1;
        String month = nowMonth > 9 ? String.valueOf(nowMonth) : "0" + nowMonth;
        String baseName = flag == 0 ? MarketConstant.WAIT_STATUS_REPORT_COLLECTION_NAME : MarketConstant.SEND_SMS_LOG_COLLECTION_NAME;
        return nowYear + "_" + baseName + "_" + month;
    }
MongoTemplate 统计MongoDB 数据库在 Spring Framework 中引入的一种灵活的查询方法。MongoTemplate 是一个 Spring Data MongoDB 模块中的核心类,它提供了简单、方便的 API 和对 MongoDB 的原生支持。 MongoTemplate 统计操作可以通过调用 count、distinct、group、mapReduce、aggregate 以及 GeoSpatial 统计方法来实现。其中,count 方法是 MongoDB 官方 API 提供的基础方法,可以统计指定集合下符合条件的文档数量。 distinct 方法则用于返回指定字段的唯一值,可以在聚合查询、多条件查询或者排序查询时使用,提高数据查询效率。 group 方法则将结果分组并返回聚合数据,可以按照需要指定 group、match、project 和 sort 等参数,实现复杂的聚合操作。 mapReduce 方法则将数据集映射和规约,用于处理大量数据并输出结果。 aggregate 方法是 MongoDB 官方推出的聚合计算框架,可以根据需要进行聚合操作,实现复杂的聚合计算,包括 $match(筛选)、$group(分组)、$project(投影)、$sort(排序)以及 $limit 等操作。 GeoSpatial 统计方法则用于处理几何空间数据,包括点、线、面和多面体等数据,可以实现附近的地点搜索、地点聚合和地点可视化等操作。 通过调用不同的方法实现不同的统计操作,MongoTemplate 实现了 MongoDB 数据库的灵活、高效和可视化的查询和统计功能,为开发人员提供了强大的数据分析能力和丰富的业务应用场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值