# ReviewAnalysisController.java

    package cloud.wconnect.rateshop.controller.reviewanalysis;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.CommonFieldI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.InternalServerErrorException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.rateshop.cons.PrecisionCons;
    import cloud.wconnect.rateshop.cons.reviewanalysis.ReviewAnalysisCons;
    import cloud.wconnect.rateshop.entity.reviewanalysis.Review;
    import cloud.wconnect.rateshop.pojo.dto.reviewanalysis.GroupStatisticsReviewDTO;
    import cloud.wconnect.rateshop.pojo.dto.reviewanalysis.StatisticsReviewDTO;
    import cloud.wconnect.rateshop.pojo.dto.reviewanalysis.StatisticsReviewExcelDTO;
    import cloud.wconnect.rateshop.pojo.query.reviewanalysis.GroupStatisticsReviewQuery;
    import cloud.wconnect.rateshop.pojo.query.reviewanalysis.StatisticsReviewQuery;
    import cloud.wconnect.rateshop.service.reviewanalysis.IReviewService;
    import cloud.wconnect.rateshop.service.reviewanalysis.IStatisticsReview;
    import com.google.common.collect.Lists;
    import jakarta.servlet.http.HttpServletResponse;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.jxls.transform.poi.JxlsPoiTemplateFillerBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URLEncoder;
    import java.nio.charset.StandardCharsets;
    import java.text.MessageFormat;
    import java.util.*;

    /**
     * Review Analysis 控制层
     *
     * @author lichen
     * @date 2024/12/27
     */
    @RestController
    @RequestMapping("/review-analysis")
    public class ReviewAnalysisController {

        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private ApplicationContext applicationContext;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private IReviewService reviewService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 查询统计数据
         *
         * @param property 资产
         * @param query    查询对象
         * @return java.util.List<cloud.wconnect.rateshop.pojo.dto.reviewanalysis.StatisticsReviewDTO>
         * @author lichen
         * @date 2024/12/27
         */
        @ApiId(1315)
        @GetMapping("/statistics")
        public List<GroupStatisticsReviewDTO> getStatisticsInfo(String property, GroupStatisticsReviewQuery query) {
            //校验日期格式
            checkDateFormat(query.getStartDate(), query.getEndDate());

            //校验统计维度
            ArrayList<Integer> checkStatisticsDimensionTypeList = Lists.newArrayList(ReviewAnalysisCons.OVERVIEW_SCORE, ReviewAnalysisCons.OVERVIEW_SCORE_ROOM_TYPES,
                    ReviewAnalysisCons.REVIEWS_BY_ROOM_TYPES, ReviewAnalysisCons.REVIEWS_BY_NIGHTS, ReviewAnalysisCons.REVIEWS_BY_SCORES,
                    ReviewAnalysisCons.REVIEWS_BY_TRAVELLERS);
            if (checkStatisticsDimensionTypeList.contains(query.getType()) && Objects.isNull(query.getStatisticsDimension())) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("statisticsDimension"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The statisticsDimension cannot be null");
            }

            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            //查询评论数据
            List<Review> reviews = reviewService.listByDateRange(propertyId, query.getStartDate(), query.getEndDate());
            if (CollectionUtils.isEmpty(reviews)) {
                return Collections.emptyList();
            }

            //统计
            List<GroupStatisticsReviewDTO> groupStatisticsReviewDTOS = Collections.emptyList();
            Map<String, IStatisticsReview> beanMap = applicationContext.getBeansOfType(IStatisticsReview.class);
            for (IStatisticsReview bean : beanMap.values()) {
                if (bean.supports(query.getType())) {
                    groupStatisticsReviewDTOS = bean.statistics(propertyDTO, reviews, query.getStatisticsDimension());
                    break;
                }
            }
            return groupStatisticsReviewDTOS;
        }

        /**
         * 汇总信息
         *
         * @param property 资产
         * @param query    查询对象
         * @return cloud.wconnect.rateshop.pojo.dto.reviewanalysis.StatisticsReviewDTO
         * @author lichen
         * @date 2024/12/28
         */
        @ApiId(1316)
        @GetMapping("/summary")
        public StatisticsReviewDTO summary(String property, StatisticsReviewQuery query) {
            //校验日期格式
            checkDateFormat(query.getStartDate(), query.getEndDate());

            int propertyId = sessionContext.getPropertyId(property);

            //查询评论数据
            List<Review> reviews = reviewService.listByDateRange(propertyId, query.getStartDate(), query.getEndDate());

            //计算
            GroupStatisticsReviewDTO groupStatisticsReviewDTO = IStatisticsReview.compute(reviews);

            return beanMapper.map(groupStatisticsReviewDTO, StatisticsReviewDTO.class);
        }

        /**
         * 导出按日统计的评论数据
         *
         * @param property 资产
         * @param query    查询条件
         * @param response http响应对象
         * @return void
         * @author lichen
         * @date 2024/12/28
         */
        @ApiId(1317)
        @GetMapping("/export")
        public void export(String property, StatisticsReviewQuery query, HttpServletResponse response) {
            //校验日期格式
            checkDateFormat(query.getStartDate(), query.getEndDate());

            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            //查询评论数据
            List<Review> reviews = reviewService.listByDateRange(propertyId, query.getStartDate(), query.getEndDate());

            //统计数据
            List<GroupStatisticsReviewDTO> groupStatisticsReviewDTOS = Collections.emptyList();
            Map<String, IStatisticsReview> beanMap = applicationContext.getBeansOfType(IStatisticsReview.class);
            for (IStatisticsReview bean : beanMap.values()) {
                if (bean.supports(ReviewAnalysisCons.OVERVIEW_SCORE)) {
                    groupStatisticsReviewDTOS = bean.statistics(propertyDTO, reviews, ReviewAnalysisCons.DAY);
                    break;
                }
            }

            //国际化参数
            Locale locale = LocaleContextHolder.getLocale();
            String timezone = propertyDTO.getTimezone();
            InternationalizationDTO internationalization = propertyDTO.getInternationalization();
            String longDate = internationalization.getDateTime().getLongDate();
            String calculationRule = internationalization.getNumber().getCalculationRule();

            //国际化处理
            List<StatisticsReviewExcelDTO> statisticsReviewExcelDTOS = beanMapper.mapList(groupStatisticsReviewDTOS, StatisticsReviewExcelDTO.class, (dto, excelDto) -> {
                //日期(yyyyMMdd)
                String date = dto.getDate();
                //转化成时间戳
                long epochSecond = DateTimeUtils.dateStrToEpochSecond(date, BaseConstant.DATE_FORMAT_YYYYMMDD, timezone);
                //国际化处理
                String dateStr = InternationalizationUtils.formatDateTime(epochSecond, longDate, StringUtils.EMPTY, propertyDTO.getTimezone(), locale);
                excelDto.setDate(dateStr);

                String avgScoreStr = InternationalizationUtils.formatViewValue(dto.getAvgScore(), PrecisionCons.REVIEW_ANALYSIS_PRECISION, calculationRule, "");
                excelDto.setAvgScoreStr(avgScoreStr);

                String avgNightsStr = InternationalizationUtils.formatViewValue(dto.getAvgNights(), PrecisionCons.REVIEW_ANALYSIS_PRECISION, calculationRule, "");
                excelDto.setAvgNightsStr(avgNightsStr);
            });
            //按照日期倒序排列
            statisticsReviewExcelDTOS.sort(Comparator.comparing(StatisticsReviewExcelDTO::getDate).reversed());
            //模板名称
            String templateFileName = StringUtils.join("tmpl-xls/review_analysis_tmpl_", locale, ".xlsx");
            //导出Excel
            try (InputStream is = new ClassPathResource(templateFileName).getInputStream()) {
                HashMap<String, Object> data = new HashMap<>();
                data.put("list", statisticsReviewExcelDTOS);

                String fileName = property + "_review_analysis_" + query.getStartDate() + "-" + query.getEndDate() + ".xlsx";
                //对文件名进行URL编码
                String encodedFilename = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
                response.setContentType("application/vnd.ms-excel");
                response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
                // 设置 Content-Disposition 头
                String contentDisposition = "attachment; filename=\"" + encodedFilename + "\"; filename*=UTF-8''" + encodedFilename;
                response.setHeader("Content-Disposition", contentDisposition);
                JxlsPoiTemplateFillerBuilder.newInstance()
                        .withTemplate(is)
                        .buildAndFill(data, response::getOutputStream);
            } catch (IOException e) {
                throw new InternalServerErrorException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(),
                        MessageFormat.format("Export excel file error.propertyId:{0},startDate:{1},endDate:{2}",
                                propertyId, query.getStartDate(), query.getEndDate()), e);
            }
        }

        /**
         * 查询最后一次的更新时间
         *
         * @param property 资产
         * @return java.util.Map<java.lang.String, java.lang.Long>
         * @author lichen
         * @date 2025/1/18
         */
        @ApiId(1330)
        @GetMapping("last-time")
        public Map<String, Long> getLastTime(String property) {
            int propertyId = sessionContext.getPropertyId(property);
            Long lastTime = reviewService.getLastTime(propertyId);
            HashMap<String, Long> hashMap = new HashMap<>();
            hashMap.put("timestamp", lastTime);
            return hashMap;
        }

        /**
         * 校验日期格式
         *
         * @param startDate 开始日期
         * @param endDate   结束日期
         * @return void
         * @author lichen
         * @date 2024/12/28
         */
        private void checkDateFormat(String startDate, String endDate) {
            //校验日期格式
            try {
                DateTimeUtils.strToLocalDate(startDate, BaseConstant.DATE_FORMAT_YYYYMMDD);
                DateTimeUtils.strToLocalDate(endDate, BaseConstant.DATE_FORMAT_YYYYMMDD);
            } catch (Exception e) {
                LinkedHashMap<String, Boolean> map = new LinkedHashMap<>();
                map.put(CommonFieldI18nConstant.DATE, true);
                map.put(BaseConstant.DATE_FORMAT_YYYYMMDD, false);
                throw new BadRequestException(CommonErrorI18nConstant.FORMAT_INCORRECT,
                        i18nMessageUtils.getErrorParamMap(map),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The startDate or endDate Incorrect format, startDate:{0},endDate:{1}",
                                startDate, endDate), e);
            }
        }

    }