# IStatisticsReview.java

    package cloud.wconnect.rateshop.service.reviewanalysis;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationDateTimeDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.rateshop.cons.reviewanalysis.ReviewAnalysisCons;
    import cloud.wconnect.rateshop.entity.reviewanalysis.Review;
    import cloud.wconnect.rateshop.pojo.dto.reviewanalysis.GroupStatisticsReviewDTO;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.lang.Nullable;
    import org.springframework.util.ReflectionUtils;
    import org.springframework.util.StringUtils;

    import java.lang.reflect.Method;
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.DayOfWeek;
    import java.time.LocalDate;
    import java.time.temporal.TemporalAdjusters;
    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;

    /**
     * 统计接口
     *
     * @author lichen
     * @date 2024/12/27
     */
    public interface IStatisticsReview {

        /**
         * 判断是否支持此统计类型
         *
         * @param type 统计类型
         * @return boolean
         * @author lichen
         * @date 2024/12/27
         */
        boolean supports(int type);

        /**
         * 统计数据
         *
         * @param propertyDTO         资产DTO
         * @param reviews             评论数据集合
         * @param statisticsDimension 统计维度 1-月 2-周 3-日
         * @return java.util.List<cloud.wconnect.rateshop.pojo.dto.reviewanalysis.StatisticsReviewDTO>
         * @author lichen
         * @date 2024/12/27
         */
        List<GroupStatisticsReviewDTO> statistics(PropertyDTO propertyDTO, List<Review> reviews, @Nullable Integer statisticsDimension);

        /**
         * 计算（评论总次数、平均得分、平均入住天数）
         *
         * @param reviews 评论数据
         * @return cloud.wconnect.rateshop.pojo.dto.reviewanalysis.StatisticsReviewDTO
         * @author lichen
         * @date 2024/12/28
         */
        static GroupStatisticsReviewDTO compute(List<Review> reviews) {
            if (CollectionUtils.isEmpty(reviews)) {
                return GroupStatisticsReviewDTO.builder()
                        .avgScore(new BigDecimal("0"))
                        .reviews(0)
                        .avgNights(new BigDecimal("0"))
                        .build();
            } else {
                //评论总数
                int totalNum = reviews.size();
                //总分
                Integer totalScore = reviews.stream().map(Review::getScore).reduce(Integer::sum).orElse(0);
                //总入住天数
                Integer totalNights = reviews.stream().map(Review::getNights).reduce(Integer::sum).orElse(0);
                //平均得分
                BigDecimal avgScore = new BigDecimal(totalScore).divide(new BigDecimal(totalNum), BaseConstant.DATA_PRECISION, RoundingMode.HALF_UP);
                //平均入住天数
                BigDecimal avgNights = new BigDecimal(totalNights).divide(new BigDecimal(totalNum), BaseConstant.DATA_PRECISION, RoundingMode.HALF_UP);

                return GroupStatisticsReviewDTO.builder()
                        .avgScore(avgScore)
                        .reviews(totalNum)
                        .avgNights(avgNights)
                        .build();
            }
        }

        /**
         * 先根据日期（日、周、月）、再根据指定分组字段统计评论数据
         *
         * @param propertyDTO         资产DTO
         * @param reviews             评论数据
         * @param secondGroupField    第二层分组字段
         * @param statisticsDimension 统计维度 1-月 2-周 3-日
         * @return java.util.List<cloud.wconnect.rateshop.pojo.dto.reviewanalysis.StatisticsReviewDTO>
         * @author lichen
         * @date 2024/12/28
         */
        static List<GroupStatisticsReviewDTO> statisticsByDateAndGroupField(PropertyDTO propertyDTO, List<Review> reviews,
                                                                            String secondGroupField, Integer statisticsDimension) {
            InternationalizationDateTimeDTO dateTime = propertyDTO.getInternationalization().getDateTime();
            //获取周起始日
            DayOfWeek startOfWeek = DateTimeUtils.getDayOfWeek(dateTime.getStartOfWeek());

            //第一层分组：根据统计维度和评论日期进行分组
            Map<String, List<Review>> date2Reviews = reviews.stream().collect(Collectors.groupingBy(item -> {
                String reviewDate = item.getReviewDate();
                if (ReviewAnalysisCons.DAY == statisticsDimension) {
                    return reviewDate;
                } else if (ReviewAnalysisCons.MONTH == statisticsDimension) {
                    return reviewDate.substring(0, 6);
                } else {
                    LocalDate localDate = DateTimeUtils.strToLocalDate(reviewDate, BaseConstant.DATE_FORMAT_YYYYMMDD);
                    //获取当前日期所属的周
                    Map<String, Integer> weekNumber = DateTimeUtils.getWeekNumber(localDate, startOfWeek);
                    return weekNumber.get("year").toString() + weekNumber.get("week").toString();
                }
            }));
            //第一层分组字段的set方法
            Method firstGroupSetMethod = getSetMethod(GroupStatisticsReviewDTO.class, "date");

            //第二层分组字段的get方法
            Method getMethod = getGetMethod(Review.class, secondGroupField);
            //第二层分组字段的set方法
            Method setMethod = getSetMethod(GroupStatisticsReviewDTO.class, secondGroupField);

            //返回集合
            ArrayList<GroupStatisticsReviewDTO> resultList = Lists.newArrayList();

            for (Map.Entry<String, List<Review>> firstEntry : date2Reviews.entrySet()) {
                String date = firstEntry.getKey();
                //按照日期分组后的评论集合
                List<Review> firstReviews = firstEntry.getValue();

                //统计维度内的开始日期、结束日期
                String startDate = "";
                String endDate = "";
                if (ReviewAnalysisCons.WEEK == statisticsDimension) {
                    //获取周的开始日期和结束日期
                    int year = Integer.parseInt(date.substring(0, 4));
                    int week = Integer.parseInt(date.substring(4));
                    LocalDate startDateOfNWeek = DateTimeUtils.getStartDateOfNWeek(year, week, startOfWeek);
                    startDate = DateTimeUtils.localDateToStr(startDateOfNWeek, BaseConstant.DATE_FORMAT_YYYYMMDD);
                    endDate = DateTimeUtils.localDateToStr(startDateOfNWeek.plusDays(6), BaseConstant.DATE_FORMAT_YYYYMMDD);
                } else if (ReviewAnalysisCons.MONTH == statisticsDimension) {
                    startDate = date + "01";

                    //当月最后一天
                    LocalDate localDate = DateTimeUtils.strToLocalDate(date + "01", BaseConstant.DATE_FORMAT_YYYYMMDD);
                    LocalDate lastDayOfMonth = localDate.with(TemporalAdjusters.lastDayOfMonth());
                    endDate = DateTimeUtils.localDateToStr(lastDayOfMonth, BaseConstant.DATE_FORMAT_YYYYMMDD);
                }

                //按照第二层分组字段，进行分组
                Map<Object, List<Review>> groupFileld2Reviews = firstReviews.stream().collect(Collectors.groupingBy(item -> ReflectionUtils.invokeMethod(getMethod, item)));
                for (Map.Entry<Object, List<Review>> secondEntry : groupFileld2Reviews.entrySet()) {
                    Object secondKey = secondEntry.getKey();
                    List<Review> secondList = secondEntry.getValue();

                    GroupStatisticsReviewDTO groupStatisticsReviewDTO = compute(secondList);
                    //设置分组字段
                    ReflectionUtils.invokeMethod(firstGroupSetMethod, groupStatisticsReviewDTO, date);
                    ReflectionUtils.invokeMethod(setMethod, groupStatisticsReviewDTO, secondKey);
                    groupStatisticsReviewDTO.setStartDate(startDate);
                    groupStatisticsReviewDTO.setEndDate(endDate);
                    resultList.add(groupStatisticsReviewDTO);
                }
            }
            //根据时间正序排列
            resultList.sort(Comparator.comparing(GroupStatisticsReviewDTO::getDate));
            return resultList;
        }

        /**
         * 根据分组字段统计评论数据
         *
         * @param reviews    评论数据
         * @param groupField 分组字段
         * @return java.util.List<cloud.wconnect.rateshop.pojo.dto.reviewanalysis.StatisticsReviewDTO>
         * @author lichen
         * @date 2024/12/28
         */
        static List<GroupStatisticsReviewDTO> statisticsByGroupField(List<Review> reviews, String groupField) {
            //分组字段的get方法
            Method getMethod = getGetMethod(Review.class, groupField);
            //分组字段的set方法
            Method setMethod = getSetMethod(GroupStatisticsReviewDTO.class, groupField);

            Map<Object, List<Review>> groupFileld2Reviews = reviews.stream().collect(Collectors.groupingBy(item -> ReflectionUtils.invokeMethod(getMethod, item)));

            ArrayList<GroupStatisticsReviewDTO> resultList = Lists.newArrayListWithCapacity(groupFileld2Reviews.size());

            for (Map.Entry<Object, List<Review>> entry : groupFileld2Reviews.entrySet()) {
                Object groupKey = entry.getKey();
                List<Review> list = entry.getValue();

                GroupStatisticsReviewDTO groupStatisticsReviewDTO = compute(list);
                //设置分组字段
                ReflectionUtils.invokeMethod(setMethod, groupStatisticsReviewDTO, groupKey);
                resultList.add(groupStatisticsReviewDTO);
            }
            return resultList;
        }

        /**
         * 根据分组字段统计评论数据
         *
         * @param reviews          评论数据
         * @param firstGroupField  第一个分组字段
         * @param secondGroupField 第二个分组字段
         * @return java.util.List<cloud.wconnect.rateshop.pojo.dto.reviewanalysis.StatisticsReviewDTO>
         * @author lichen
         * @date 2024/12/28
         */
        static List<GroupStatisticsReviewDTO> statisticsByGroupField(List<Review> reviews, String firstGroupField, String secondGroupField) {
            //第一个分组字段的set/get方法
            Method firstSetMethod = getSetMethod(GroupStatisticsReviewDTO.class, firstGroupField);
            Method firstGetMethod = getGetMethod(Review.class, firstGroupField);
            //第二个分组字段的set/get方法
            Method secondSetMethod = getSetMethod(GroupStatisticsReviewDTO.class, secondGroupField);
            Method secondGetMethod = getGetMethod(Review.class, secondGroupField);

            //按照第一层分组字段分组
            Map<Object, List<Review>> firstGroupFileld2Reviews = reviews.stream().collect(Collectors.groupingBy(item -> ReflectionUtils.invokeMethod(firstGetMethod, item)));

            ArrayList<GroupStatisticsReviewDTO> resultList = Lists.newArrayListWithCapacity(firstGroupFileld2Reviews.size());

            for (Map.Entry<Object, List<Review>> firstEntry : firstGroupFileld2Reviews.entrySet()) {
                Object firstGroupKey = firstEntry.getKey();
                List<Review> firstList = firstEntry.getValue();

                //按照第二层分组字段分组
                Map<Object, List<Review>> secondGroupFileld2Reviews = firstList.stream().collect(Collectors.groupingBy(item -> ReflectionUtils.invokeMethod(secondGetMethod, item)));

                for (Map.Entry<Object, List<Review>> secondEntry : secondGroupFileld2Reviews.entrySet()) {
                    Object secondGroupKey = secondEntry.getKey();
                    List<Review> secondList = secondEntry.getValue();
                    //计算
                    GroupStatisticsReviewDTO groupStatisticsReviewDTO = compute(secondList);
                    //设置分组字段
                    ReflectionUtils.invokeMethod(firstSetMethod, groupStatisticsReviewDTO, firstGroupKey);
                    ReflectionUtils.invokeMethod(secondSetMethod, groupStatisticsReviewDTO, secondGroupKey);
                    resultList.add(groupStatisticsReviewDTO);
                }
            }
            return resultList;
        }

        /**
         * 获取指定类的get方法
         *
         * @param clazz 类
         * @param field 属性名称
         * @return java.lang.reflect.Method
         * @author lichen
         * @date 2024/12/28
         */
        static Method getGetMethod(Class<?> clazz, String field) {
            String getMethodName = "get" + StringUtils.capitalize(field);
            return ReflectionUtils.findMethod(clazz, getMethodName);
        }

        /**
         * 获取指定类的set方法
         *
         * @param clazz 类
         * @param field 属性名称
         * @return java.lang.reflect.Method
         * @author lichen
         * @date 2024/12/28
         */
        static Method getSetMethod(Class<?> clazz, String field) {
            String setMethodName = "set" + StringUtils.capitalize(field);
            //field的类型
            Class<?> clazzByField = ReflectionUtils.findField(clazz, field).getType();
            return ReflectionUtils.findMethod(clazz, setMethodName, clazzByField);
        }
    }