MongoDB聚合搜索Aggregation(时间,模糊匹配,最新值)SpringCloud JAVA

场景:一个设备的molde属性,该属性定义在物模型中,且使用属性通过上报的方式存在设备数据ThingData中,每次都会存一条并记录时间;通过点击传thingId,再通过id调用dubbo访问其他服务根据thingId获取到对应的productId,且productId关联物模型的id,以此拿到物模型定义中的property数组,其中每个property都有一个唯一标识,而属性上报也是根据唯一值来做判断对应的属性的;通过循环property列表拿到标识让后再ThingData文档中先筛选出符合的数据,然后还要根据场景来判断是否需要进行模糊匹配得到所有符合idenfier或value的存在thingData中的最新值;

!!!注意细节:mongo的映射关系,还有文档名必须正确还要注意层级关系!

需求1.多条件查询的难点:的双属性模糊匹配

listThingPropertiesData(String thingId, String keyWords, Integer pageSize, Integer pageNum) {
        ThingModelDTO thingModelData = getThingModelByThingId(thingId);

        // 遍历设备物模型的所有属性,每个属性读取最新值
        List propertiesData = new ArrayList<>();

        // 创建一个空的关键字条件
        Criteria keywordCriteria = new Criteria();

        // 如果有关键字,添加模糊匹配规则
        if (StringUtils.hasText(keyWords)) {
            keywordCriteria = keywordCriteria.orOperator(
                    Criteria.where("property.identifier").regex(keyWords, "i"), // 匹配标识符
                    Criteria.where("property.value").regex(keyWords, "i") // 匹配属性值
            );
        }

        for (ThingModelDTO.PropertyDTO propSpecDTO : propertiesSpec) {
            String propIdentifier = propSpecDTO.getIdentifier();
            // 第一步:构建基本查询条件,根据thingId和属性标识符查询
            Criteria baseCriteria = Criteria.where("thingId").is(thingId)
                    .and("property.identifier").is(propIdentifier);

            Aggregation aggregation = Aggregation.newAggregation(
                    Aggregation.match(baseCriteria),
                    // 如果有关键字,再执行模糊匹配
                    Aggregation.match(keywordCriteria),
                    Aggregation.sort(Sort.by(Sort.Order.desc("property.timestamp"))),
                    Aggregation.group("property.identifier")
                            .first("property.identifier").as("identifier")
                            .first("property.value").as("value")
                            .first("property.timestamp").as("timestamp")
            );

           try {
               AggregationResults aggregationResults = mongoTemplate.aggregate(
                       aggregation,"thingData", ThingData.PropertyData.class);
               List matchingData = aggregationResults.getMappedResults();
               propertiesData.addAll(matchingData);

               // 添加ThingModel中有但ThingData中没有的属性
               if (matchingData.isEmpty()) {
                   propertiesData.add(new ThingData.PropertyData("ThingModel:" + propSpecDTO.getIdentifier(), null, System.currentTimeMillis()));
               }
           }catch (Exception e) {
               log.debug("查询失败! 原因" + e);
               throw new ServiceException(ServiceException.ExceptionType.INTERNAL_FAILURE,
                       ExceptionInfoBuilder.build(ExceptionTemplate.INTERNAL_FAILURE_DATABASE,"聚合查询"));
           }
        }

        List propertyDataDTOs = ThingDataMapper.INSTANCE.toPropertyDTO(propertiesData);

        int startIndex = (pageNum - 1) * pageSize;
        int endIndex = Math.min(startIndex + pageSize, propertyDataDTOs.size());

        List paginatedList = propertyDataDTOs.stream()
                .skip(startIndex)
                .limit(endIndex - startIndex)
                .collect(Collectors.toList());

        return ResponseDTOBuilder.build(paginatedList, propertyDataDTOs.size(), pageSize, pageNum);
    }
需求2.:多条件查询难点:筛选只符合一个时间段内的最新数据;
listThingServicesData(String thingId, String status, Long startTime, Long endTime, Integer pageSize, Integer pageNum) throws ServiceException {

        // 遍历设备物模型的所有服务,每个服务读取最新值
        List servicesData = new ArrayList<>();
        for (ThingModelDTO.ServiceDTO serviceSpecDTO : servicesSpec) {
            String serviceIdentifier = serviceSpecDTO.getIdentifier();
            // 构建基本查询条件,根据thingId和服务标识符查询
            Criteria baseCriteria = Criteria.where("thingId").is(thingId)
                    .and("service.identifier").is(serviceIdentifier);

            // 创建一个空的关键字条件
            Criteria keywordCriteria = new Criteria();
            if (status != null && !status.isEmpty()) {
                keywordCriteria = keywordCriteria.and("service.status").is(status);
            }

            // 如果有时间范围,添加时间范围匹配规则
            if (startTime != null && endTime != null) {
                keywordCriteria = keywordCriteria.and("service.timestamp").gte(startTime).lte(endTime);
            } else if (startTime != null) {
                keywordCriteria = keywordCriteria.and("service.timestamp").gte(startTime);
            } else if (endTime != null) {
                keywordCriteria = keywordCriteria.and("service.timestamp").lte(endTime);
            }

            Aggregation aggregation = Aggregation.newAggregation(
                    Aggregation.match(baseCriteria),
                    Aggregation.match(keywordCriteria),
                    Aggregation.sort(Sort.by(Sort.Order.desc("service.timestamp"))),
                    Aggregation.group("service.identifier")
                            .first("service.identifier").as("identifier")
                            .first("service.status").as("status")
                            .first("service.serviceName").as("serviceName")
                            .first("service.callType").as("callType")
                            .first("service.inputParams").as("inputParams")
                            .first("service.outputParams").as("outputParams")
                            .first("service.timestamp").as("timestamp")
            );

            try{
                AggregationResults aggregationResults = mongoTemplate.aggregate(
                        aggregation, "thingData", ThingData.ServiceData.class);

                ThingData.ServiceData data = aggregationResults.getUniqueMappedResult();

                servicesData.add(Objects.requireNonNullElseGet(data, () -> new ThingData.ServiceData(serviceSpecDTO.getIdentifier(), serviceSpecDTO.getServiceName(),
                        null, serviceSpecDTO.getCallType(), serviceSpecDTO.getInputParams(), serviceSpecDTO.getOutputParams(), System.currentTimeMillis())));
            } catch (Exception e) {
                log.debug("聚合查询失败! 原因" + e);
                throw new ServiceException(ServiceException.ExceptionType.INTERNAL_FAILURE,
                        ExceptionInfoBuilder.build(ExceptionTemplate.INTERNAL_FAILURE_DATABASE,"聚合查询"));
            }

        }

        List serviceDataDTOs = ThingDataMapper.INSTANCE.toServiceDTO(servicesData);

        int startIndex = (pageNum - 1) * pageSize;
        int endIndex = Math.min(startIndex + pageSize, serviceDataDTOs.size());

        List paginatedList = serviceDataDTOs.stream()
                .skip(startIndex)
                .limit(endIndex - startIndex)
                .collect(Collectors.toList());

        return ResponseDTOBuilder.build(paginatedList, serviceDataDTOs.size(), pageSize, pageNum);
    }
需求3.非聚合实现的多条件复杂查询场景:
场景:product{attributes{nodetype;   而我需要进行设备的搜索,设备搜索根据nodetype筛选出一组多个产品,然后根据这组产品的id来得到这些产品下的所有设备;

核心如下:

 Query query = new Query();
        Criteria criteria = new Criteria();

        if (StringUtils.hasText(productId)) {
            // 如果提供了 productId,将其作为查询条件
            query.addCriteria(Criteria.where("product.id").is(productId));
        }

        if (StringUtils.hasText(nodeType)) {
            //获取符合 nodeType 的所有产品的 productId 列表
            Criteria nodeTypeCriteria = Criteria.where("attributes.nodeType").is(nodeType);
            List products = mongoTemplate.find(Query.query(nodeTypeCriteria), Product.class);

            if (!products.isEmpty()) {
                List orCriteriaList = new ArrayList<>();
                for (Product product : products) {
                    // 将ID添加到查询条件中
                    orCriteriaList.add(Criteria.where("product.id").is(product.getId()));
                }
                // 将 Criteria 列表组合成单个 $or 表达式
                criteria.orOperator(orCriteriaList.toArray(new Criteria[0]));
            }
        }


        if (StringUtils.hasText(thingStatus)) {
            // 如果提供了 thingStatus,将其作为查询条件
            query.addCriteria(Criteria.where("status").is(thingStatus));
        }

        // 如果没有提供关键字,只需执行分页查询,同时过滤掉已删除的设备
        query.addCriteria(Criteria.where("deletedAt").is(null));

        if (StringUtils.hasText(keyWords)) {
            // 如果提供了 keyWords,添加关键字搜索条件
            Pattern pattern = Pattern.compile(".*" + keyWords + ".*", Pattern.CASE_INSENSITIVE);
            criteria.orOperator(
                    Criteria.where("name").regex(pattern),
                    Criteria.where("id").regex(pattern)
            );
        }

        query.addCriteria(criteria);

        // 获取符合条件的设备总数
        long totalCount = mongoTemplate.count(query, Thing.class);

        int skip = (pageNum - 1) * pageSize;
        query.skip(skip).limit(pageSize);

        try {
            List things = mongoTemplate.find(query, Thing.class);
            return ResponseDTOBuilder.build(ThingMapper.INSTANCE.toDTO(things), totalCount, pageSize, pageNum);
        } catch (Exception e) {
            log.error("查询设备失败" + e);
            throw new ServiceException(
                    ServiceException.ExceptionType.INTERNAL_FAILURE,
                    ExceptionInfoBuilder.build(ExceptionTemplate.INTERNAL_FAILURE_DATABASE,"查询设备")
            );
        }

你可能感兴趣的:(java,数据库,mongodb,聚类,spring,cloud,dubbo,log4j)