# InitMonthlyReportTask.java

    package cloud.wconnect.workperformance.businesstask.monthlyreport;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.service.PropertyInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.workperformance.entity.monthlyreport.MonthlyReport;
    import cloud.wconnect.workperformance.entity.monthlyreport.PropertyMonthlyReportSet;
    import cloud.wconnect.workperformance.mapper.monthlyreport.MonthlyReportMapper;
    import cloud.wconnect.workperformance.service.monthlyreport.IMonthlyReportService;
    import cloud.wconnect.workperformance.service.monthlyreport.IPropertyMonthlyReportSetService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;

    import java.time.LocalDateTime;
    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * 每月1号生成上个月的月报
     *
     * @author lichen
     * @date 2023/9/1
     */
    @Slf4j
    @Component
    @DS("monthly_report")
    public class InitMonthlyReportTask {

        @Autowired
        private PropertyInternalService propertyInternalService;
        @Autowired
        private IMonthlyReportService monthlyReportService;
        @Autowired
        private MonthlyReportMapper monthlyReportMapper;
        @Autowired
        private IPropertyMonthlyReportSetService propertyMonthlyReportSetService;
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        @Autowired
        private ObjectMapper objectMapper;

        private static final String prefixKey = "wconnect:task:init-monthly-report:";

        /**
         * 初始化月报，每月1号初始化上个月的月报
         *
         * @return void
         * @author lichen
         * @date 2023/9/1
         */
        @Scheduled(cron = "${schedule.cron.init-monthly-report}", zone = "${default.timezone}")
        public void initMonthlyReport() {
            String currentDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYMMDD));
            if (!preventConcurrency(prefixKey + currentDate)) {
                return;
            }

            //查询配置月报区域的资产信息
            List<PropertyMonthlyReportSet> propertySetList = propertyMonthlyReportSetService.list();
            if (CollectionUtils.isEmpty(propertySetList)) return;

            List<Integer> propertyIds = propertySetList.stream().map(PropertyMonthlyReportSet::getPropertyId).collect(Collectors.toList());
            List<PropertyDTO> propertyDTOS = propertyInternalService.getProperty(propertyIds).stream().filter(PropertyDTO::getStatus).toList();
            if (CollectionUtils.isEmpty(propertyDTOS)) return;

            //资产配置区域信息
            Map<Integer, String> propertyId2AreaIds = propertySetList.stream()
                    .collect(Collectors.toMap(PropertyMonthlyReportSet::getPropertyId, PropertyMonthlyReportSet::getAreaIds));
            if (propertyId2AreaIds.isEmpty()) return;

            //构建月报对象
            List<MonthlyReport> monthlyReports = propertyDTOS.stream()
                    .filter(propertyDTO -> StringUtils.isNotBlank(propertyId2AreaIds.get(propertyDTO.getId())))
                    .map(item -> {
                        //获取上个月的时间
                        ZonedDateTime lastMonthZonedDateTime = DateTimeUtils.dateNow(item.getTimezone()).minusMonths(1);
                        int year = lastMonthZonedDateTime.getYear();
                        int month = lastMonthZonedDateTime.getMonthValue();
                        return MonthlyReport.builder()
                                .tenantId(item.getTenantId())
                                .propertyId(item.getId())
                                .year(year)
                                .month(month)
                                .areaIds(propertyId2AreaIds.get(item.getId()))
                                .createId(0)
                                .createTime(DateTimeUtils.nowTimestamp())
                                .build();
                    }).toList();
            if (CollectionUtils.isEmpty(monthlyReports)) return;
            //查询已经存在的月报
            List<Integer> existMonthlyReportPropertyIds = monthlyReportMapper.listByPropertyIdAndYearAndMonth(monthlyReports)
                    .stream().map(MonthlyReport::getPropertyId).toList();

            //批量新增
            List<MonthlyReport> saveBatchList = monthlyReports.stream().filter(item -> !existMonthlyReportPropertyIds.contains(item.getPropertyId())).toList();
            monthlyReportService.saveBatch(saveBatchList);

            //初始化数据
            Map<Integer, PropertyDTO> propertyId2PropertyDTO = propertyDTOS.stream().collect(Collectors.toMap(PropertyDTO::getId, Function.identity()));
            for (MonthlyReport monthlyReport : saveBatchList) {
                PropertyDTO propertyDTO = propertyId2PropertyDTO.get(monthlyReport.getPropertyId());
                List<Integer> needInitDataAreaIds = JsonUtils.convertStringToList(objectMapper, monthlyReport.getAreaIds(), Integer.class);
                monthlyReportService.initAreaData(needInitDataAreaIds, Lists.newArrayList(monthlyReport), propertyDTO);
            }
        }

        /**
         * 校验是否并发执行
         *
         * @param key 缓存前缀
         * @return java.lang.Boolean
         * @author lichen
         * @date 2023/9/2
         */
        private Boolean preventConcurrency(String key) {
            return redisTemplate.opsForValue().setIfAbsent(key, 0, 1, TimeUnit.DAYS);
        }

    }