lifuquan 1 年之前
父節點
當前提交
027205848f
共有 31 個文件被更改,包括 1275 次插入248 次删除
  1. 9 11
      doc/开发文档.md
  2. 9 1
      doc/接口测试.http
  3. 二進制
      doc/数据订阅/能力商店/河北_CEM高品质2日统计/河北_CEM高品质2日统计_HE_D_HIGH_QUALITY.xlsx
  4. 二進制
      doc/需求文档/输出样本/投诉清单各地市投诉率20230911.xlsx
  5. 6 0
      pom.xml
  6. 2 1
      src/main/java/com/nokia/tsl_data/config/PropertiesConfig.java
  7. 2 0
      src/main/java/com/nokia/tsl_data/config/TaskSchedulingConfig.java
  8. 1 1
      src/main/java/com/nokia/tsl_data/controller/DataWarehouseController.java
  9. 18 0
      src/main/java/com/nokia/tsl_data/controller/DemoController.java
  10. 35 0
      src/main/java/com/nokia/tsl_data/controller/TaskSchedulingController.java
  11. 34 0
      src/main/java/com/nokia/tsl_data/dao/AvgDurationMapper.java
  12. 46 3
      src/main/java/com/nokia/tsl_data/dao/HighQualityCountMapper.java
  13. 52 3
      src/main/java/com/nokia/tsl_data/dao/HighQualityDataMapper.java
  14. 3 0
      src/main/java/com/nokia/tsl_data/dao/HighQualityListDayMapper.java
  15. 7 30
      src/main/java/com/nokia/tsl_data/dao/MobileComplaintMapper.java
  16. 22 0
      src/main/java/com/nokia/tsl_data/dao/ScheduledTaskMapper.java
  17. 15 0
      src/main/java/com/nokia/tsl_data/dao/SysDataDictionaryRepository.java
  18. 1 1
      src/main/java/com/nokia/tsl_data/entity/HighQualityData.java
  19. 41 0
      src/main/java/com/nokia/tsl_data/entity/ScheduledTask.java
  20. 21 0
      src/main/java/com/nokia/tsl_data/entity/_enum/ScheduledType.java
  21. 56 0
      src/main/java/com/nokia/tsl_data/entity/pojo/RunnableTask.java
  22. 29 0
      src/main/java/com/nokia/tsl_data/entity/pojo/ScheduledParameter.java
  23. 15 0
      src/main/java/com/nokia/tsl_data/properties/CustomerRateTargetProperties.java
  24. 19 0
      src/main/java/com/nokia/tsl_data/service/DemoService.java
  25. 92 5
      src/main/java/com/nokia/tsl_data/service/HighQualityCountService.java
  26. 161 1
      src/main/java/com/nokia/tsl_data/service/HighQualityDataService.java
  27. 179 0
      src/main/java/com/nokia/tsl_data/service/SchedulingTaskService.java
  28. 16 22
      src/main/java/com/nokia/tsl_data/service/TslDataService.java
  29. 362 152
      src/main/java/com/nokia/tsl_data/service/TslReportService.java
  30. 17 0
      src/test/java/com/nokia/tsl_data/MainTest.java
  31. 5 17
      src/test/java/com/nokia/tsl_data/TslDataApplicationTest.java

+ 9 - 11
doc/开发文档.md

@@ -6,14 +6,12 @@
 
 2. 客户端-战略考核(地市级别) 每日数据 当前是从CEM的高品质统计中的统计值
 
-任务编排
-
-HighQualityCount 数据入库
-
-HighQualityList 数据入库
-
-MobileComplaint 数据入库
-
-生成excel文件
-
-截图
+## 任务编排
+
+1. 数据入库及数据同步
+   - HighQualityCount 数据入库 每日1次 建议1点45分以后执行
+   - HighQualityList 数据入库  每日1次 建议1点45分以后执行
+   - MobileComplaint 数据入库  每日1次 建议1点45分以后执行
+   - 工单流转数据同步
+ 
+2. 

+ 9 - 1
doc/接口测试.http

@@ -1,3 +1,11 @@
+### 实验
+POST http://127.0.0.1:22222/tsl_data/close
+
+### 列出已调度的任务
+POST http://127.0.0.1:22222/tsl_data/scheduling/task/list
+
+###
+
 ### 入库 mobile_complaint 数据
 POST http://192.168.10.7:22222/tsl_data/warehouse/mobile_complaint
 Content-Type: application/json
@@ -20,7 +28,7 @@ Content-Type: application/json
 POST http://192.168.10.7:22222/tsl_data/delete/high_quality_count
 Content-Type: application/json
 
-20231028
+20231105
 
 ### 入库 high_quality_list 数据
 POST http://192.168.10.7:22222/tsl_data/warehouse/high_quality_list

二進制
doc/数据订阅/能力商店/河北_CEM高品质2日统计/河北_CEM高品质2日统计_HE_D_HIGH_QUALITY.xlsx


二進制
doc/需求文档/输出样本/投诉清单各地市投诉率20230911.xlsx


+ 6 - 0
pom.xml

@@ -36,6 +36,12 @@
     </properties>
 
     <dependencies>
+        <!-- spring-context-holder-starter -->
+        <dependency>
+            <groupId>com.nokia</groupId>
+            <artifactId>spring-context-holder-starter</artifactId>
+            <version>1.0.0</version>
+        </dependency>
         <!-- push-message-starter -->
         <dependency>
             <groupId>com.nokia</groupId>

+ 2 - 1
src/main/java/com/nokia/tsl_data/config/PropertiesConfig.java

@@ -1,11 +1,12 @@
 package com.nokia.tsl_data.config;
 
+import com.nokia.tsl_data.properties.CustomerRateTargetProperties;
 import com.nokia.tsl_data.properties.DataWarehouseProperties;
 import com.nokia.tsl_data.properties.OutputProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
-@EnableConfigurationProperties({DataWarehouseProperties.class, OutputProperties.class})
+@EnableConfigurationProperties({DataWarehouseProperties.class, OutputProperties.class, CustomerRateTargetProperties.class})
 public class PropertiesConfig {
 }

+ 2 - 0
src/main/java/com/nokia/tsl_data/config/TaskSchedulingConfig.java

@@ -1,11 +1,13 @@
 package com.nokia.tsl_data.config;
 
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 
 /**
  * 任务调度线程配置
  */
+@Configuration
 public class TaskSchedulingConfig {
 
     private final int poolSize = 3;

+ 1 - 1
src/main/java/com/nokia/tsl_data/controller/DataWarehouseController.java

@@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RestController;
 
 @Slf4j
 @RestController
-@RequestMapping("tsl_data")
+@RequestMapping("tsl_data/source")
 public class DataWarehouseController {
 
     private final DataWarehouseService dataWarehouseService;

+ 18 - 0
src/main/java/com/nokia/tsl_data/controller/DemoController.java

@@ -0,0 +1,18 @@
+package com.nokia.tsl_data.controller;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+@RestController
+@RequestMapping("/demo")
+public class DemoController {
+
+    private final Map<String, Runnable> map = new ConcurrentHashMap<>();
+
+
+}

+ 35 - 0
src/main/java/com/nokia/tsl_data/controller/TaskSchedulingController.java

@@ -0,0 +1,35 @@
+package com.nokia.tsl_data.controller;
+
+import com.nokia.common.http.vo.R;
+import com.nokia.tsl_data.entity.ScheduledTask;
+import com.nokia.tsl_data.service.SchedulingTaskService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@Slf4j
+@RestController
+@RequestMapping("tsl_data/scheduling")
+public class TaskSchedulingController {
+
+    private final SchedulingTaskService schedulingTaskService;
+
+    public TaskSchedulingController(SchedulingTaskService schedulingTaskService) {
+        this.schedulingTaskService = schedulingTaskService;
+    }
+
+    @PostMapping("task/list")
+    public R listTasksScheduled() {
+        List<ScheduledTask> scheduledTasks = schedulingTaskService.listTasksScheduled();
+        return R.ok().data(scheduledTasks);
+    }
+
+    public R addTask(ScheduledTask scheduledTask) {
+        log.info("{}", scheduledTask);
+
+        return R.ok();
+    }
+}

+ 34 - 0
src/main/java/com/nokia/tsl_data/dao/AvgDurationMapper.java

@@ -0,0 +1,34 @@
+package com.nokia.tsl_data.dao;
+
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface AvgDurationMapper {
+
+    /**
+     * 插入上月的平均处理时长
+     */
+    @Insert("with t1 as (select compl_area_local, " +
+            "  case when proce_time != '' then (extract('epoch' from to_timestamp(proce_time, 'YYYY-MM-DD HH24:MI:SS')) " +
+            "            - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 " +
+            "       when is_online_complete = '是' then 0 " +
+            "       else (extract('epoch' from to_timestamp(end_time, 'YYYY-MM-DD HH24:MI:SS')) " +
+            "            - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 end as duration " +
+            "from tsl_data.high_quality_list_day hdmc where month_id = #{month_id} " +
+            "and day_id::float8 = extract('day' from to_timestamp(#{month_id}, 'YYYYMM') + interval '1 month' - interval '1 day')) " +
+            "insert into tsl_data.avg_duration (month_id, city_name, avg_duration) " +
+            "select #{month_id} as month_id, compl_area_local as city_name, avg(duration) as avg_duration " +
+            "from t1 group by compl_area_local ")
+    void insertOldTsDurationForMonth(String monthId);
+
+    /**
+     * 查询历史处理时长
+     */
+    @Select("select city_name, avg_duration from tsl_data.avg_duration where month_id = #{monthId}")
+    List<Map<String, Object>> selectOldTsDurationForMonth(String monthId);
+}

+ 46 - 3
src/main/java/com/nokia/tsl_data/dao/HighQualityCountMapper.java

@@ -22,9 +22,52 @@ public interface HighQualityCountMapper {
     int selectQualityCountForDay(String day);
 
     /**
-     * 客户端-投诉问题解决满意度
-     * 客户端-投诉问题解决率
-     * 客户端-投诉问题响应率
+     * 查询客户端地市响应率数据 参数day_id 20231105
+     */
+    @Select("select businoareaname                                                                           as city,\n" +
+            "       complaint_response_list::float8                                                          as numerator,\n" +
+            "       complaint_response_count::float8 + complaint::float8                                     as denominator,\n" +
+            "       complaint_response_list::float8 / (complaint_response_count::float8 + complaint::float8) as rate\n" +
+            "from tsl_data.high_quality_count_day\n" +
+            "where month_id = substring('20231105' from 1 for 6)\n" +
+            "  and day_id = substring('20231105' from 7 for 2)\n" +
+            "  and profes_dep = '网络质量'\n" +
+            "  and big_type_name = '移网网络体验'\n" +
+            "  and small_type_name = '--'")
+    List<Map<String, Object>> selectResponseOfCity(@Param("day_id") String day);
+
+    /**
+     * 查询客户端地市解决率 参数day_id 20231105
+     */
+    @Select("select businoareaname                                                                               as city,\n" +
+            "       complaint_resolution_list::float8                                                            as numerator,\n" +
+            "       complaint_resolution_count::float8 + complaint::float8                                       as denominator,\n" +
+            "       complaint_resolution_list::float8 / (complaint_resolution_count::float8 + complaint::float8) as rate\n" +
+            "from tsl_data.high_quality_count_day\n" +
+            "where month_id = substring(#{day_id} from 1 for 6)\n" +
+            "  and day_id = substring(#{day_id} from 7 for 2)\n" +
+            "  and profes_dep = '网络质量'\n" +
+            "  and big_type_name = '移网网络体验'\n" +
+            "  and small_type_name = '--'")
+    List<Map<String, Object>> selectResolutionOfCity(@Param("day_id") String day);
+
+    /**
+     * 查询客户端地市满意度 参数day_id 20231105
+     */
+    @Select("select businoareaname                                                                             as city,\n" +
+            "       complaint_satisfied_list::float8                                                           as numerator,\n" +
+            "       complaint_satisfied_count::float8 + complaint::float8                                      as denominator,\n" +
+            "       complaint_satisfied_list::float8 / (complaint_satisfied_count::float8 + complaint::float8) as rate\n" +
+            "from tsl_data.high_quality_count_day\n" +
+            "where month_id = substring(#{day_id} from 1 for 6)\n" +
+            "  and day_id = substring(#{day_id} from 7 for 2)\n" +
+            "  and profes_dep = '网络质量'\n" +
+            "  and big_type_name = '移网网络体验'\n" +
+            "  and small_type_name = '--' ")
+    List<Map<String, Object>> selectSatisfiedOfCity(@Param("day_id") String day);
+
+    /**
+     * 客户端-投诉问题解决满意度 客户端-投诉问题解决率 客户端-投诉问题响应率
      */
     @Select("with t1 as (select businoareaname, complaint_satisfied_list::float8, complaint_satisfied_count::float8, " +
             "complaint_resolution_list::float8, complaint_resolution_count::float8, complaint_response_list::float8, " +

+ 52 - 3
src/main/java/com/nokia/tsl_data/dao/HighQualityDataMapper.java

@@ -1,8 +1,6 @@
 package com.nokia.tsl_data.dao;
 
-import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-import org.apache.ibatis.annotations.Update;
+import org.apache.ibatis.annotations.*;
 
 import java.util.List;
 import java.util.Map;
@@ -10,6 +8,57 @@ import java.util.Map;
 @Mapper
 public interface HighQualityDataMapper {
 
+    /**
+     * 区县投诉问题解决率
+     */
+    @Select("select checked_region as region, count(cp_is_ok = '解决' or null)::float / count(1) as rate " +
+            "from tsl_data.high_quality_data hqd " +
+            "where day_id = #{day_id} and (cp_is_ok  in ('解决', '未解决') or no_visit_tag = '未回访') " +
+            "group by checked_region ")
+    List<Map<String, Object>> selectRateOfCpIsOkOfRegion(@Param("day_id") String day);
+
+    /**
+     * 区县投诉问题本月累计未解决工单
+     */
+    @Select("select checked_region as region, count(1) as count " +
+            "from tsl_data.high_quality_data hqd " +
+            "where day_id = '20231105' and (cp_is_ok  = '未解决' or no_visit_tag = '未回访') " +
+            "group by checked_region ")
+    List<Map<String, Object>> selectCountOfCpNotOkOfRegion(@Param("day_id") String day);
+
+    /**
+     * 区县投诉问题满意度 满意 包括 满意 不满意包括 一般和不满意
+     */
+    @Select("select checked_region as region, count(cp_satisfaction = '满意' or null)::float / count(1) as rate " +
+            "from tsl_data.high_quality_data hqd " +
+            "where day_id = #{day_id} and (cp_satisfaction  in ('满意', '一般', '不满意') or no_visit_tag = '未回访') " +
+            "group by checked_region ")
+    List<Map<String, Object>> selectRateOfCpSatisfactionOfRegion(@Param("day_id") String day);
+
+    /**
+     * 区县投诉问题本月累计不满意工单 不满意包括 一般和不满意
+     */
+    @Select("select checked_region as region, count(1) as count " +
+            "from tsl_data.high_quality_data hqd " +
+            "where day_id = #{day_id} and (cp_satisfaction in ('不满意', '一般') or no_visit_tag = '未回访') " +
+            "group by checked_region ")
+    List<Map<String, Object>> selectCountOfCpNotSatisfactionOfRegion(@Param("day_id") String day);
+
+    /**
+     * 区县投诉问题响应率
+     */
+    @Select("select checked_region as region, count(cp_timely_contact = '是' or null)::float / count(1) as rate " +
+            "from tsl_data.high_quality_data hqd where day_id = #{day_id} " +
+            "and (cp_timely_contact in ('是', '否') or no_visit_tag = '未回访') group by checked_region ")
+    List<Map<String, Object>> selectRateOfCpTimelyContactOfRegion(@Param("day_id") String day);
+
+    /**
+     * 获取本月累计未响应工单量,这里不会每个区县都有
+     */
+    @Select("select checked_region as region, count(1) as count from tsl_data.high_quality_data hqd " +
+            "where day_id = #{day_id} and (cp_timely_contact = '否' or no_visit_tag = '未回访') group by checked_region")
+    List<Map<String, Object>> selectCountOfCpNotTimelyContactOfRegion(@Param("day_id") String day);
+
     /**
      * 依据工单流转的信息更新HighQualityData的地市信息
      */

+ 3 - 0
src/main/java/com/nokia/tsl_data/dao/HighQualityListDayMapper.java

@@ -14,6 +14,9 @@ public interface HighQualityListDayMapper {
     @Select("select sheet_no from tsl_data.high_quality_list_day")
     List<String> findOrderOf(String day);
 
+    /**
+     * 从 tsl_data.high_quality_list_day 表获取符合条件数据
+     */
     @ResultMap("highQualityDataResultMap")
     @Select("select concat(month_id, day_id) as day_id, sheet_no, cp_satisfaction, cp_is_ok,"
             + " cp_timely_contact, cp_timely_contact, area_id, area_name, city_id, city_name,"

+ 7 - 30
src/main/java/com/nokia/tsl_data/dao/MobileComplaintMapper.java

@@ -10,14 +10,14 @@ public interface MobileComplaintMapper {
     /**
      * 统计某天入库工单数量
      */
-    @Select("select count(1) from tsl_data.high_quality_list_day " +
+    @Select("select count(1) from tsl_data.mobile_complaint_day " +
             "where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2)")
     int selectCompCountForDay(String day);
 
     /**
      * 超时工单统计
      */
-    @Select("with t1 as (select compl_area_local, is_timeout from tsl_data.high_quality_list_day hdmc " +
+    @Select("with t1 as (select compl_area_local, is_timeout from tsl_data.mobile_complaint_day " +
             " where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2)), " +
             "t2 as (select '全省' as compl_area_local, count(1) as total_num from t1), " +
             "t3 as (select compl_area_local, count(1) as total_num from t1 group by compl_area_local), " +
@@ -39,8 +39,7 @@ public interface MobileComplaintMapper {
             "     when is_online_complete = '是' then 0" +
             "     else (extract('epoch' from to_timestamp(end_time, 'YYYY-MM-DD HH24:MI:SS'))" +
             "          - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 " +
-            "     end as duration" +
-            "from tsl_data.high_quality_list_day hdmc " +
+            "     end as duration from tsl_data.mobile_complaint_day " +
             "where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2)) " +
             "select compl_area_local, avg(duration) as avg_duration from t1 group by compl_area_local")
     List<Map<String, Object>> selectTsDurationForDay(String day);
@@ -48,7 +47,7 @@ public interface MobileComplaintMapper {
     /**
      * 重复工单
      */
-    @Select("with t1 as (select compl_area_local, busi_no from tsl_data.high_quality_list_day hdmc " +
+    @Select("with t1 as (select compl_area_local, busi_no from tsl_data.mobile_complaint_day " +
             " where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2)), " +
             "t2 as (select distinct * from t1), " +
             "t3 as (select compl_area_local, count(1) as total_num from t1 group by compl_area_local), " +
@@ -64,7 +63,7 @@ public interface MobileComplaintMapper {
      * 投诉清单按日按地市计数
      */
     @Select("select compl_area_local, substring(acct_date from 7 for 2) as day_id, count(1) as num " +
-            " from tsl_data.high_quality_list_day hdmc where month_id = substring(#{day} from 1 for 6) " +
+            " from tsl_data.mobile_complaint_day where month_id = substring(#{day} from 1 for 6) " +
             " and day_id = substring(#{day} from 7 for 2) group by compl_area_local, substring(acct_date from 7 for 2) " +
             " order by compl_area_local, substring(acct_date from 7 for 2)")
     List<Map<String, Object>> selectCityTslForMonth(String day);
@@ -73,7 +72,7 @@ public interface MobileComplaintMapper {
      * 投诉清单全省计数
      */
     @Select("select substring(acct_date from 7 for 2) as day_id, count(1) as num " +
-            "from tsl_data.high_quality_list_day hdmc where month_id = substring(#{day} from 1 for 6) " +
+            "from tsl_data.mobile_complaint_day where month_id = substring(#{day} from 1 for 6) " +
             "and day_id = substring(#{day} from 7 for 2) group by substring(acct_date from 7 for 2) " +
             "order by substring(acct_date from 7 for 2)")
     List<Map<String, Object>> selectAllTslForMonth(String day);
@@ -81,7 +80,7 @@ public interface MobileComplaintMapper {
     /**
      * 投诉清单地市总数
      */
-    @Select("select compl_area_local, count(1) as num from tsl_data.high_quality_list_day hdmc " +
+    @Select("select compl_area_local, count(1) as num from tsl_data.mobile_complaint_day " +
             "where month_id = substring(#{day} from 1 for 6) and day_id = substring(#{day} from 7 for 2) " +
             " group by compl_area_local order by compl_area_local")
     List<Map<String, Object>> selectCityAllForMonth(String day);
@@ -102,26 +101,4 @@ public interface MobileComplaintMapper {
     default int deleteMobileCompForDay(String day) {
         return deleteMobileComplaintForMonthIdAndDayId(day.substring(0, 6), day.substring(6));
     }
-
-    /**
-     * 插入上月的平均处理时长
-     */
-    @Insert("with t1 as (select compl_area_local, " +
-            "  case when proce_time != '' then (extract('epoch' from to_timestamp(proce_time, 'YYYY-MM-DD HH24:MI:SS')) " +
-            "            - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 " +
-            "       when is_online_complete = '是' then 0 " +
-            "       else (extract('epoch' from to_timestamp(end_time, 'YYYY-MM-DD HH24:MI:SS')) " +
-            "            - extract('epoch' from to_timestamp(accept_time, 'YYYY-MM-DD HH24:MI:SS'))) / 3600 end as duration " +
-            "from tsl_data.high_quality_list_day hdmc where month_id = #{month_id} " +
-            "and day_id::float8 = extract('day' from to_timestamp(#{month_id}, 'YYYYMM') + interval '1 month' - interval '1 day')) " +
-            "insert into tsl_data.avg_duration (month_id, city_name, avg_duration) " +
-            "select #{month_id} as month_id, compl_area_local as city_name, avg(duration) as avg_duration " +
-            "from t1 group by compl_area_local ")
-    void insertOldTsDurationForMonth(String monthId);
-
-    /**
-     * 查询历史处理时长
-     */
-    @Select("select city_name, avg_duration from tsl_data.avg_duration where month_id = #{monthId}")
-    List<Map<String, Object>> selectOldTsDurationForMonth(String monthId);
 }

+ 22 - 0
src/main/java/com/nokia/tsl_data/dao/ScheduledTaskMapper.java

@@ -0,0 +1,22 @@
+package com.nokia.tsl_data.dao;
+
+import com.nokia.tsl_data.entity.ScheduledTask;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 存储任务调度数据
+ */
+@Mapper
+public interface ScheduledTaskMapper {
+
+    /**
+     * 查找启动的任务
+     * TODO sql
+     */
+    @Select("")
+    List<ScheduledTask> selectTasksIsOn();
+
+}

+ 15 - 0
src/main/java/com/nokia/tsl_data/dao/SysDataDictionaryRepository.java

@@ -50,4 +50,19 @@ public interface SysDataDictionaryRepository extends JpaRepository<SysDataDictio
         }
         return result;
     }
+
+    /**
+     * 获取全部区县的map key是区县的realName value是地市/区县组成的列表
+     */
+    default Map<String, List<Object>> findAllRegion() {
+        List<SysDataDictionary> regions = findByTypeOrderByOrd("high_quality_list_region");
+        Map<String, List<Object>> result = new HashMap<>();
+        for (SysDataDictionary region : regions) {
+            List<Object> objects = new ArrayList<>();
+            objects.add(region.getParent().getRealName());
+            objects.add(region.getRealName());
+            result.put(region.getRealName(), objects);
+        }
+        return result;
+    }
 }

+ 1 - 1
src/main/java/com/nokia/tsl_data/entity/HighQualityData.java

@@ -23,7 +23,7 @@ public class HighQualityData {
     private String dayId;
 
     // 工单号
-    @Column(name = "sheet_no", columnDefinition = "varchar(50)", nullable = false, unique = true)
+    @Column(name = "sheet_no", columnDefinition = "varchar(50)", nullable = false)
     private String sheetNo;
 
     // 测评-满意度

+ 41 - 0
src/main/java/com/nokia/tsl_data/entity/ScheduledTask.java

@@ -0,0 +1,41 @@
+package com.nokia.tsl_data.entity;
+
+import com.nokia.tsl_data.entity._enum.ScheduledType;
+import com.nokia.tsl_data.entity.pojo.ScheduledParameter;
+import lombok.Data;
+
+import java.time.Instant;
+
+/**
+ * 已注册的任务 对应表`scheduled_task`
+ */
+@Data
+public class ScheduledTask {
+
+    /**
+     * id 唯一
+     */
+    private Long id;
+
+    private String name;
+
+    /**
+     * 任务状态 true 启动 false 关闭
+     * 对于 定时调度和周期调度 启动和关闭表示
+     */
+    private Boolean status;
+
+    private String beanName;
+
+    private String methodName;
+
+    private String methodParameter;
+
+    private ScheduledType scheduledType;
+
+    private ScheduledParameter scheduledParameter;
+
+    private Instant createTime;
+
+    private Instant lastModify;
+}

+ 21 - 0
src/main/java/com/nokia/tsl_data/entity/_enum/ScheduledType.java

@@ -0,0 +1,21 @@
+package com.nokia.tsl_data.entity._enum;
+
+/**
+ * 任务调度类型
+ * IMMEDIATELY 立即调度
+ * ONCE 按指定的时间或者延时调度一次
+ * CRON 定时调度
+ * INTERVAL 周期调度
+ */
+public enum ScheduledType {
+    IMMEDIATELY, ONCE, CRON, INTERVAL;
+
+    public static ScheduledType ofValue(String value) {
+        for (ScheduledType type : values()) {
+            if (type.toString().equalsIgnoreCase(value)) {
+                return type;
+            }
+        }
+        throw new RuntimeException("不存在 " + value + " 对应的ScheduledType, 允许的type有:IMMEDIATELY、ONCE、CRON、INTERVAL");
+    }
+}

+ 56 - 0
src/main/java/com/nokia/tsl_data/entity/pojo/RunnableTask.java

@@ -0,0 +1,56 @@
+package com.nokia.tsl_data.entity.pojo;
+
+import com.nokia.common.spring.context.SpringContextHolder;
+import com.nokia.tsl_data.entity.ScheduledTask;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * 执行任务的实体类
+ */
+@Slf4j
+@Data
+public class RunnableTask implements Runnable{
+
+    private final SpringContextHolder springContextHolder;
+    private final ScheduledTask scheduledTask;
+
+    public RunnableTask(SpringContextHolder springContextHolder, ScheduledTask scheduledTask) {
+        this.springContextHolder = springContextHolder;
+        this.scheduledTask = scheduledTask;
+    }
+
+    @Override
+    public void run() {
+        try {
+            Object bean = springContextHolder.getBean(scheduledTask.getBeanName());
+            Method method;
+            if (!StringUtils.hasLength(scheduledTask.getMethodParameter())) {
+                method = bean.getClass().getDeclaredMethod(scheduledTask.getMethodName());
+                ReflectionUtils.makeAccessible(method);
+                method.invoke(bean);
+            } else {
+                method = bean.getClass().getDeclaredMethod(scheduledTask.getMethodName(), String.class);
+                method.invoke(bean, scheduledTask.getMethodParameter());
+            }
+        } catch (NoSuchBeanDefinitionException e) {
+            e.printStackTrace();
+            log.error("未找到任务--任务:{}", scheduledTask);
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+            log.error("未找到方法--任务:{}", scheduledTask);
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+            log.error("方法不允许访问--任务:{}", scheduledTask);
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+            log.error("方法执行出错--任务:{}", scheduledTask);
+        }
+    }
+}

+ 29 - 0
src/main/java/com/nokia/tsl_data/entity/pojo/ScheduledParameter.java

@@ -0,0 +1,29 @@
+package com.nokia.tsl_data.entity.pojo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 调度参数
+ */
+@Data
+public class ScheduledParameter {
+
+    /**
+     * 定时任务表达式
+     */
+    private String cronExpression;
+
+    /**
+     * 启动时间
+     */
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+    private Date startTime;
+
+    /**
+     * 以秒计算的数字
+     */
+    private Long periodOfSeconds;
+}

+ 15 - 0
src/main/java/com/nokia/tsl_data/properties/CustomerRateTargetProperties.java

@@ -0,0 +1,15 @@
+package com.nokia.tsl_data.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "customer.rate.target")
+public class CustomerRateTargetProperties {
+    // 满意度目标
+    private double satisfiedCompliance = 0.92;
+    // 解决率目标
+    private double resolutionCompliance = 0.85;
+    // 响应率目标
+    private double responseCompliance = 0.99;
+}

+ 19 - 0
src/main/java/com/nokia/tsl_data/service/DemoService.java

@@ -0,0 +1,19 @@
+package com.nokia.tsl_data.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class DemoService {
+
+    public void test() {
+        log.info("=======test start=======");
+        try {
+            Thread.sleep(1000 * 10L);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        log.info("=======test end=======");
+    }
+}

+ 92 - 5
src/main/java/com/nokia/tsl_data/service/HighQualityCountService.java

@@ -2,8 +2,10 @@ package com.nokia.tsl_data.service;
 
 import com.nokia.tsl_data.dao.HighQualityCountMapper;
 import com.nokia.tsl_data.dao.SysDataDictionaryRepository;
+import com.nokia.tsl_data.properties.CustomerRateTargetProperties;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -14,16 +16,101 @@ import java.util.Map;
 @Service
 public class HighQualityCountService {
 
-    private final HighQualityCountMapper highQualityCountDao;
+    private final HighQualityCountMapper highQualityCountMapper;
     private final SysDataDictionaryRepository sysDataDictionaryRepository;
+    private final CustomerRateTargetProperties customerRateTargetProperties;
 
-    public HighQualityCountService(HighQualityCountMapper highQualityCountDao, SysDataDictionaryRepository sysDataDictionaryRepository) {
-        this.highQualityCountDao = highQualityCountDao;
+    public HighQualityCountService(HighQualityCountMapper highQualityCountMapper, SysDataDictionaryRepository sysDataDictionaryRepository, CustomerRateTargetProperties customerRateTargetProperties) {
+        this.highQualityCountMapper = highQualityCountMapper;
         this.sysDataDictionaryRepository = sysDataDictionaryRepository;
+        this.customerRateTargetProperties = customerRateTargetProperties;
     }
 
     /**
-     *
+     * 客户端地市满意度 客户端地市解决率 客户端地市响应率
+     */
+    public Map<String, List<Object>> generateThreeRateOfCity(String day) {
+        // 全部地市
+        List<String> allCityName = sysDataDictionaryRepository.findAllCityName();
+        Map<String, List<Object>> result = new HashMap<>();
+        for (String cityName : allCityName) {
+            List<Object> list = new ArrayList<>();
+            list.add(cityName);
+            result.put(cityName, list);
+        }
+        List<Object> allCity = new ArrayList<>();
+        allCity.add("全省");
+        result.put("全省", allCity);
+        double numerator = 0, denominator = 0, rate;
+        // 客户端地市满意度
+        List<Map<String, Object>> satisfiedOfCity = highQualityCountMapper.selectSatisfiedOfCity(day);
+        for (Map<String, Object> map : satisfiedOfCity) {
+            String key = (String) map.get("city");
+            List<Object> list;
+            if (key.equals("雄安新区")) {
+                list = result.get("雄安");
+            } else {
+                list = result.get(key);
+            }
+            list.add(map.get("rate"));
+            numerator += (double) map.get("numerator");
+            denominator += (double) map.get("denominator");
+            list.add(customerRateTargetProperties.getSatisfiedCompliance());
+            list.add((double) map.get("rate") - customerRateTargetProperties.getSatisfiedCompliance());
+        }
+        rate = numerator / denominator;
+        allCity.add(rate);
+        allCity.add(customerRateTargetProperties.getSatisfiedCompliance());
+        allCity.add(rate - customerRateTargetProperties.getSatisfiedCompliance());
+        // 客户端地市解决率
+        numerator = 0;
+        denominator = 0;
+        List<Map<String, Object>> resolutionOfCity = highQualityCountMapper.selectResolutionOfCity(day);
+        for (Map<String, Object> map : resolutionOfCity) {
+            String key = (String) map.get("city");
+            List<Object> list;
+            if (key.equals("雄安新区")) {
+                list = result.get("雄安");
+            } else {
+                list = result.get(key);
+            }
+            list.add(map.get("rate"));
+            numerator += (double) map.get("numerator");
+            denominator += (double) map.get("denominator");
+            list.add(customerRateTargetProperties.getResolutionCompliance());
+            list.add((double) map.get("rate") - customerRateTargetProperties.getResolutionCompliance());
+        }
+        rate = numerator / denominator;
+        allCity.add(rate);
+        allCity.add(customerRateTargetProperties.getResolutionCompliance());
+        allCity.add(rate - customerRateTargetProperties.getResolutionCompliance());
+        // 客户端地市响应率
+        numerator = 0;
+        denominator = 0;
+        List<Map<String, Object>> responseOfCity = highQualityCountMapper.selectResponseOfCity(day);
+        for (Map<String, Object> map : responseOfCity) {
+            String key = (String) map.get("city");
+            List<Object> list;
+            if (key.equals("雄安新区")) {
+                list = result.get("雄安");
+            } else {
+                list = result.get(key);
+            }
+            list.add(map.get("rate"));
+            numerator += (double) map.get("numerator");
+            denominator += (double) map.get("denominator");
+            list.add(customerRateTargetProperties.getResponseCompliance());
+            list.add((double) map.get("rate") - customerRateTargetProperties.getResponseCompliance());
+        }
+        rate = numerator / denominator;
+        allCity.add(rate);
+        allCity.add(customerRateTargetProperties.getResponseCompliance());
+        allCity.add(rate - customerRateTargetProperties.getResponseCompliance());
+        return result;
+    }
+
+    /**
+     * 客户端地市和全省按天级的统计数据
      */
     public Map<String, int[]> getComplaintsForDay(String day) {
         // 预处理时间
@@ -42,7 +129,7 @@ public class HighQualityCountService {
             // 拼接一个表示天的字符串,天如果小于10要补0
             String newDay = i >= 10 ? monthId + i : monthId + "0" + i;
             // 这里有一个查询操作
-            List<Map<String, Object>> totalComplaintsForDay = highQualityCountDao
+            List<Map<String, Object>> totalComplaintsForDay = highQualityCountMapper
                     .selectTotalComplaintsForDay(newDay);
             if (totalComplaintsForDay.size() == 0) {
                 throw new RuntimeException(String.format("HighQualityCount 缺少 %s 账期数据", newDay));

+ 161 - 1
src/main/java/com/nokia/tsl_data/service/HighQualityDataService.java

@@ -3,11 +3,16 @@ package com.nokia.tsl_data.service;
 import com.nokia.tsl_data.dao.HighQualityDataMapper;
 import com.nokia.tsl_data.dao.HighQualityDataRepository;
 import com.nokia.tsl_data.dao.HighQualityListDayMapper;
+import com.nokia.tsl_data.dao.SysDataDictionaryRepository;
 import com.nokia.tsl_data.entity.HighQualityData;
+import com.nokia.tsl_data.properties.CustomerRateTargetProperties;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 根据高质量明细表生产数据
@@ -19,11 +24,15 @@ public class HighQualityDataService {
     private final HighQualityDataRepository highQualityDataRepository;
     private final HighQualityDataMapper highQualityDataMapper;
     private final HighQualityListDayMapper highQualityListDayMapper;
+    private final SysDataDictionaryRepository sysDataDictionaryRepository;
+    private final CustomerRateTargetProperties customerRateTargetProperties;
 
-    public HighQualityDataService(HighQualityDataRepository highQualityDataRepository, HighQualityDataMapper highQualityDataMapper, HighQualityListDayMapper highQualityListDayMapper) {
+    public HighQualityDataService(HighQualityDataRepository highQualityDataRepository, HighQualityDataMapper highQualityDataMapper, HighQualityListDayMapper highQualityListDayMapper, SysDataDictionaryRepository sysDataDictionaryRepository, CustomerRateTargetProperties customerRateTargetProperties) {
         this.highQualityDataRepository = highQualityDataRepository;
         this.highQualityDataMapper = highQualityDataMapper;
         this.highQualityListDayMapper = highQualityListDayMapper;
+        this.sysDataDictionaryRepository = sysDataDictionaryRepository;
+        this.customerRateTargetProperties = customerRateTargetProperties;
     }
 
     /**
@@ -51,4 +60,155 @@ public class HighQualityDataService {
         int countOfCityUpdated3 = highQualityDataMapper.updateHighQualityDataRegionFromDefault(day);
         log.debug("根据 默认规则 更新区县 {} 条...", countOfCityUpdated3);
     }
+
+    /**
+     * 计算区县投诉问题解决率
+     */
+    public List<List<Object>> generateCpIsOkOfRegion(String day) {
+        // 地市 区县 解决率 达标值 与达标值差距 本月累计不满意工单量
+        // 1. 获取全部的地市和区县
+        Map<String, List<Object>> result = sysDataDictionaryRepository.findAllRegion();
+        // 2. 解决率
+        List<Map<String, Object>> maps = highQualityDataMapper.selectRateOfCpIsOkOfRegion(day);
+        // 第一次遍历,将工单不为0的区县列出
+        for (Map<String, Object> map : maps) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 高品质详表存在的 区县{} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Double.parseDouble(map.get("rate").toString()));
+            }
+        }
+        // 第二次遍历,补充工单为0的区县,响应率默认100%
+        // 3. 满意度达标值 与达标值差距
+        for (List<Object> list : result.values()) {
+            if (list.size() == 2) {
+                list.add(1.0);
+            }
+            // 写入达标值
+            list.add(customerRateTargetProperties.getResolutionCompliance());
+            // 写入与达标值的差距
+            list.add((double)list.get(2) - (double)list.get(3));
+        }
+        // 4. 本月累计不满意工单量
+        List<Map<String, Object>> maps1 = highQualityDataMapper.selectCountOfCpNotOkOfRegion(day);
+        for (Map<String, Object> map : maps1) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 累计未解决工单量 存在的区县 {} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Integer.parseInt(map.get("count").toString()));
+            }
+        }
+        for (List<Object> list : result.values()) {
+            // 对于没有不满意工单的,直接补0
+            if (list.size() == 5) {
+                list.add(0);
+            }
+        }
+        List<List<Object>> list = new ArrayList<>(result.values());
+        list.sort(Comparator.comparingDouble(o -> (double) o.get(4)));
+        return list;
+    }
+
+    /**
+     * 计算区县投诉问题满意度
+     */
+    public List<List<Object>> generateCpSatisfactionOfRegion(String day) {
+        // 地市 区县 满意度 达标值 与达标值差距 本月累计不满意工单量
+        // 1. 获取全部的地市和区县
+        Map<String, List<Object>> result = sysDataDictionaryRepository.findAllRegion();
+        // 2. 满意度
+        List<Map<String, Object>> maps = highQualityDataMapper.selectRateOfCpSatisfactionOfRegion(day);
+        // 第一次遍历,将工单不为0的区县列出
+        for (Map<String, Object> map : maps) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 高品质详表存在的 区县{} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Double.parseDouble(map.get("rate").toString()));
+            }
+        }
+        // 第二次遍历,补充工单为0的区县,响应率默认100%
+        // 3. 满意度达标值 与达标值差距
+        for (List<Object> list : result.values()) {
+            if (list.size() == 2) {
+                list.add(1.0);
+            }
+            // 写入达标值
+            list.add(customerRateTargetProperties.getSatisfiedCompliance());
+            // 写入与达标值的差距
+            list.add((double)list.get(2) - (double)list.get(3));
+        }
+        // 4. 本月累计不满意工单量
+        List<Map<String, Object>> maps1 = highQualityDataMapper.selectCountOfCpNotSatisfactionOfRegion(day);
+        for (Map<String, Object> map : maps1) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 累计不满意工单量 存在的区县 {} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Integer.parseInt(map.get("count").toString()));
+            }
+        }
+        for (List<Object> list : result.values()) {
+            // 对于没有不满意工单的,直接补0
+            if (list.size() == 5) {
+                list.add(0);
+            }
+        }
+        List<List<Object>> list = new ArrayList<>(result.values());
+        list.sort(Comparator.comparingDouble(o -> (double) o.get(4)));
+        return list;
+    }
+
+    /**
+     * 计算区县投诉问题响应率
+     */
+    public List<List<Object>> generateCpTimelyContactOfRegion(String day) {
+        // 地市 区县 响应率 达标值 与达标值差距 本月累计未响应工单量
+        // 1. 获取全部的地市和区县
+        Map<String, List<Object>> result = sysDataDictionaryRepository.findAllRegion();
+        // 2. 响应率
+        List<Map<String, Object>> maps = highQualityDataMapper.selectRateOfCpTimelyContactOfRegion(day);
+        // 第一次遍历,将工单不为0的区县列出
+        for (Map<String, Object> map : maps) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 高品质详表存在的 区县{} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Double.parseDouble(map.get("rate").toString()));
+            }
+        }
+        // 第二次遍历,补充工单为0的区县,响应率默认100%
+        // 3. 响应率达标值 与达标值差距
+        for (List<Object> list : result.values()) {
+            if (list.size() == 2) {
+                list.add(1.0);
+            }
+            // 写入达标值和与达标值的差距
+            // 写入达标值
+            list.add(customerRateTargetProperties.getResponseCompliance());
+            // 写入与达标值的差距
+            list.add((double)list.get(2) - (double)list.get(3));
+        }
+        // 4. 本月累计未响应工单量
+        List<Map<String, Object>> maps1 = highQualityDataMapper.selectCountOfCpNotTimelyContactOfRegion(day);
+        for (Map<String, Object> map : maps1) {
+            List<Object> objects = result.get(map.get("region").toString());
+            if (objects == null) {
+                log.error("发现 累计未响应工单量 存在的区县 {} 在 字典 high_quality_list_region 中不存在...", map);
+            } else {
+                objects.add(Integer.parseInt(map.get("count").toString()));
+            }
+        }
+        for (List<Object> list : result.values()) {
+            // 对于没有未响应工单的,直接补0
+            if (list.size() == 5) {
+                list.add(0);
+            }
+        }
+        List<List<Object>> list = new ArrayList<>(result.values());
+        list.sort(Comparator.comparingDouble(o -> (double) o.get(4)));
+        return list;
+    }
 }

+ 179 - 0
src/main/java/com/nokia/tsl_data/service/SchedulingTaskService.java

@@ -0,0 +1,179 @@
+package com.nokia.tsl_data.service;
+
+import com.nokia.common.spring.context.SpringContextHolder;
+import com.nokia.tsl_data.dao.ScheduledTaskMapper;
+import com.nokia.tsl_data.entity.ScheduledTask;
+import com.nokia.tsl_data.entity.pojo.RunnableTask;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.Trigger;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.support.CronTrigger;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+
+/**
+ * 任务注册服务
+ * 1. 启动时初始化任务
+ * 2. 关闭时保存未完成任务
+ * 3. 任务调度
+ * 4. 任务取消
+ * 5. 任务列表
+ */
+@Slf4j
+@Service
+public class SchedulingTaskService {
+
+    // 使用id作为任务唯一标识
+    private final ConcurrentHashMap<ScheduledTask, ScheduledFuture<?>> tasksScheduled = new ConcurrentHashMap<>();
+    private final ThreadPoolTaskScheduler taskScheduler;
+    private final ScheduledTaskMapper scheduledTaskMapper;
+    private final SpringContextHolder springContextHolder;
+
+    public SchedulingTaskService(ThreadPoolTaskScheduler taskScheduler, ScheduledTaskMapper scheduledTaskMapper, SpringContextHolder springContextHolder) {
+        this.taskScheduler = taskScheduler;
+        this.scheduledTaskMapper = scheduledTaskMapper;
+        this.springContextHolder = springContextHolder;
+    }
+
+    /**
+     * 增加任务
+     */
+    public void addTask(ScheduledTask scheduledTask) {
+
+    }
+
+    /**
+     * 更新任务
+     */
+    public void updateTask(ScheduledTask scheduledTask) {
+
+    }
+
+    /**
+     * 删除任务
+     */
+    public void deleteTask(ScheduledTask scheduledTask) {
+
+    }
+
+    /**
+     * 调度任务
+     */
+    private void scheduleTask(ScheduledTask scheduledTask) {
+        if (tasksScheduled.containsKey(scheduledTask)) {
+            throw new RuntimeException("已存在相同任务,请勿重复调度...");
+        }
+        Runnable runnable = new RunnableTask(springContextHolder, scheduledTask);
+        if (scheduledTask.getStatus()) {
+            ScheduledFuture<?> future = null;
+            switch (scheduledTask.getScheduledType()) {
+                case CRON:
+                    // 调度定时任务
+                    Trigger trigger = new CronTrigger(scheduledTask.getScheduledParameter().getCronExpression());
+                    future = taskScheduler.schedule(runnable, trigger);
+                    break;
+                case INTERVAL:
+                    // 周期任务
+                    Date startTime = scheduledTask.getScheduledParameter().getStartTime();
+                    Duration period = Duration.ofSeconds(scheduledTask.getScheduledParameter().getPeriodOfSeconds());
+                    if (startTime == null || startTime.getTime() <= System.currentTimeMillis()) {
+                        //
+                        future = taskScheduler.scheduleAtFixedRate(runnable, period);
+                    } else {
+                        future = taskScheduler.scheduleAtFixedRate(runnable, startTime.toInstant(), period);
+                    }
+                    break;
+                case ONCE:
+                    // 单次任务
+                    Date startTimeForOnceTask = scheduledTask.getScheduledParameter().getStartTime();
+                    if (startTimeForOnceTask.getTime() >= System.currentTimeMillis()) {
+                        // 单次任务只有在启动时间大于等于当前时间时才启动调度
+                        future = taskScheduler.schedule(runnable, startTimeForOnceTask);
+                    } else {
+                        // 当启动时间早于当前时间时,直接报错
+                        throw new RuntimeException("单次任务(ONCE)调度时间不能早于当前时间...");
+                    }
+                    break;
+                case IMMEDIATELY:
+                    // 马上执行的任务
+                default:
+                    future = taskScheduler.schedule(runnable, Instant.now());
+                    break;
+            }
+            if (future != null) {
+                tasksScheduled.put(scheduledTask, future);
+            }
+        }
+    }
+
+    /**
+     * 列出已注册调度任务的列表
+     */
+    public List<ScheduledTask> listTasksScheduled() {
+        List<ScheduledTask> result = new ArrayList<>();
+        // 遍历所有正在调度的任务
+        for (Map.Entry<ScheduledTask, ScheduledFuture<?>> entry : tasksScheduled.entrySet()) {
+            ScheduledFuture<?> future = entry.getValue();
+            if (future.isDone()) {
+                tasksScheduled.remove(entry.getKey());
+            } else {
+                result.add(entry.getKey());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 取消任务
+     */
+    public void cancelTask(ScheduledTask scheduledTask) {
+        ScheduledFuture<?> future = tasksScheduled.remove(scheduledTask);
+        if (future != null) {
+            future.cancel(true);
+        }
+    }
+
+    /**
+     * 在启动时获取需要调度的任务并进行调度
+     */
+    @PostConstruct
+    public void postConstruct() {
+        log.info("启动时初始化任务...");
+        List<ScheduledTask> scheduledTasks = scheduledTaskMapper.selectTasksIsOn();
+        for (ScheduledTask scheduledTask : scheduledTasks) {
+            try {
+                scheduleTask(scheduledTask);
+            }catch (Exception e) {
+                log.info("初始化任务 {} 出错: {}", scheduledTask, e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 在退出程序前保存程序调度的状态
+     */
+    @PreDestroy
+    public void preDestroy() throws IOException {
+        // TODO
+        log.info("============preDestroy===========");
+        List<String> lines = new ArrayList<>();
+        lines.add("=======preDestroy========");
+        lines.add(String.valueOf(System.currentTimeMillis()));
+        log.info(Paths.get("./output/aaa.text").toAbsolutePath().toString());
+        Files.write(Paths.get("./output/aaa.text"), lines, StandardOpenOption.APPEND);
+    }
+}

+ 16 - 22
src/main/java/com/nokia/tsl_data/service/TslDataService.java

@@ -2,7 +2,7 @@ package com.nokia.tsl_data.service;
 
 import com.nokia.tsl_data.dao.*;
 import com.nokia.tsl_data.entity.TargetTsRatio;
-import org.springframework.beans.factory.annotation.Value;
+import com.nokia.tsl_data.properties.CustomerRateTargetProperties;
 import org.springframework.stereotype.Service;
 
 import java.text.ParseException;
@@ -19,30 +19,25 @@ public class TslDataService {
     private final MobileComplaintMapper mobileComplaintMapper;
     private final SysDataDictionaryRepository sysDataDictionaryRepository;
     private final HighQualityCountMapper highQualityCountMapper;
+    private final AvgDurationMapper avgDurationMapper;
     private final HighQualityCountService highQualityCountService;
     private final UserCountService userCountService;
     private final TargetTsRatioRepository targetTsRatioRepository;
+    private final CustomerRateTargetProperties customerRateTargetProperties;
 
-    @Value("${tslTask.compliance.satisfied:0.92}")
-    private double satisfiedCompliance;
-    @Value("${tslTask.compliance.resolution:0.85}")
-    private double resolutionCompliance;
-    @Value("${tslTask.compliance.response:0.99}")
-    private double responseCompliance;
-
-    public TslDataService(MobileComplaintMapper mobileComplaintMapper, SysDataDictionaryRepository sysDataDictionaryRepository, HighQualityCountMapper highQualityCountMapper, HighQualityCountService highQualityCountService, UserCountService userCountService, TargetTsRatioRepository targetTsRatioRepository) {
+    public TslDataService(MobileComplaintMapper mobileComplaintMapper, SysDataDictionaryRepository sysDataDictionaryRepository, HighQualityCountMapper highQualityCountMapper, AvgDurationMapper avgDurationMapper, HighQualityCountService highQualityCountService, UserCountService userCountService, TargetTsRatioRepository targetTsRatioRepository, CustomerRateTargetProperties customerRateTargetProperties) {
         this.mobileComplaintMapper = mobileComplaintMapper;
         this.sysDataDictionaryRepository = sysDataDictionaryRepository;
         this.highQualityCountMapper = highQualityCountMapper;
+        this.avgDurationMapper = avgDurationMapper;
         this.highQualityCountService = highQualityCountService;
         this.userCountService = userCountService;
         this.targetTsRatioRepository = targetTsRatioRepository;
+        this.customerRateTargetProperties = customerRateTargetProperties;
     }
 
     /**
-     * 客户端-投诉问题解决满意度
-     * 客户端-投诉问题解决率
-     * 客户端-投诉问题响应率
+     * 客户端-投诉问题解决满意度 客户端-投诉问题解决率 客户端-投诉问题响应率
      */
     public List<List<List<Object>>> getSheet4_6Data(String day) {
         String realDay = day.substring(0, 4) + "-" + day.substring(4, 6) + "-" + day.substring(6);
@@ -71,7 +66,7 @@ public class TslDataService {
             // 满意率
             list1.add(1, dataMap.get(area).get("complaint_satisfied"));
             // 达标值
-            list1.add(2, satisfiedCompliance);
+            list1.add(2, customerRateTargetProperties.getSatisfiedCompliance());
             // 差距
             list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
         }
@@ -85,7 +80,7 @@ public class TslDataService {
         // 满意率
         list1.add(1, dataMap.get("全省").get("complaint_satisfied"));
         // 达标值
-        list1.add(2, satisfiedCompliance);
+        list1.add(2, customerRateTargetProperties.getSatisfiedCompliance());
         // 差距
         list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
 
@@ -101,7 +96,7 @@ public class TslDataService {
             // 解决率
             list1.add(1, dataMap.get(area).get("complaint_resolution"));
             // 达标值
-            list1.add(2, resolutionCompliance);
+            list1.add(2, customerRateTargetProperties.getResolutionCompliance());
             // 差距
             list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
         }
@@ -115,7 +110,7 @@ public class TslDataService {
         // 解决率
         list1.add(1, dataMap.get("全省").get("complaint_resolution"));
         // 达标值
-        list1.add(2, resolutionCompliance);
+        list1.add(2, customerRateTargetProperties.getResolutionCompliance());
         // 差距
         list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
 
@@ -130,7 +125,7 @@ public class TslDataService {
             // 解决率
             list1.add(1, dataMap.get(area).get("complaint_response"));
             // 达标值
-            list1.add(2, responseCompliance);
+            list1.add(2, customerRateTargetProperties.getResponseCompliance());
             // 差距
             list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
         }
@@ -142,7 +137,7 @@ public class TslDataService {
         // 相应
         list1.add(1, dataMap.get("全省").get("complaint_response"));
         // 达标值
-        list1.add(2, responseCompliance);
+        list1.add(2, customerRateTargetProperties.getResponseCompliance());
         // 差距
         list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
 
@@ -207,11 +202,11 @@ public class TslDataService {
         // 获取上个月的时间
         calendar.add(Calendar.MONTH, -1);
         String oldMonthId = new SimpleDateFormat("yyyyMM").format(calendar.getTime());
-        List<Map<String, Object>> oldData = mobileComplaintMapper.selectOldTsDurationForMonth(oldMonthId);
+        List<Map<String, Object>> oldData = avgDurationMapper.selectOldTsDurationForMonth(oldMonthId);
         // 如果上月的平均处理时长数据不存在,需要插入
         if (oldData.size() == 0) {
-            mobileComplaintMapper.insertOldTsDurationForMonth(oldMonthId);
-            oldData = mobileComplaintMapper.selectOldTsDurationForMonth(oldMonthId);
+            avgDurationMapper.insertOldTsDurationForMonth(oldMonthId);
+            oldData = avgDurationMapper.selectOldTsDurationForMonth(oldMonthId);
         }
         List<Map<String, Object>> dayData = mobileComplaintMapper.selectTsDurationForDay(day);
         // 转化格式方便读取
@@ -446,7 +441,6 @@ public class TslDataService {
             double cj = ((double) list.get(size - 2)) - ((double) list.get(size - 1));
             list.add(cj);
         }
-
         return result;
     }
 }

+ 362 - 152
src/main/java/com/nokia/tsl_data/service/TslReportService.java

@@ -26,10 +26,7 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
-import java.util.Calendar;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 投诉报表数据生成
@@ -41,17 +38,24 @@ public class TslReportService {
     private final TslDataService tslDataService;
     private final OutputProperties outputProperties;
     private final HighQualityCountMapper highQualityCountMapper;
+    private final HighQualityCountService highQualityCountService;
+    private final HighQualityDataService highQualityDataService;
     private final MobileComplaintMapper mobileComplaintMapper;
     private final SysDataDictionaryRepository sysDataDictionaryRepository;
 
     private XSSFWorkbook workbook = null;
+    private XSSFCellStyle cellStyle1 = null;
+    private XSSFCellStyle cellStyle2 = null;
+    private XSSFCellStyle cellStyle3 = null;
 
     private static final DateFormat DAY_FORMAT = new SimpleDateFormat("yyyyMMdd");
 
-    public TslReportService(TslDataService tslDataService, OutputProperties outputProperties, HighQualityCountMapper highQualityCountMapper, MobileComplaintMapper mobileComplaintMapper, SysDataDictionaryRepository sysDataDictionaryRepository) {
+    public TslReportService(TslDataService tslDataService, OutputProperties outputProperties, HighQualityCountMapper highQualityCountMapper, HighQualityCountService highQualityCountService, HighQualityDataService highQualityDataService, MobileComplaintMapper mobileComplaintMapper, SysDataDictionaryRepository sysDataDictionaryRepository) {
         this.tslDataService = tslDataService;
         this.outputProperties = outputProperties;
         this.highQualityCountMapper = highQualityCountMapper;
+        this.highQualityCountService = highQualityCountService;
+        this.highQualityDataService = highQualityDataService;
         this.mobileComplaintMapper = mobileComplaintMapper;
         this.sysDataDictionaryRepository = sysDataDictionaryRepository;
     }
@@ -74,36 +78,33 @@ public class TslReportService {
             String area = "A1:" + CellRect.getColumnName(dayOfMonth + 7) + "15";
             screenShot = PoiUtil.screenShot(workbook.getSheet("管理端-移网质量类"), area, "微软雅黑");
             ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-1-投诉率.png").toFile());
-
-            // 新截图
+            // 截图2
             String area2 = "A1:" + CellRect.getColumnName(dayOfMonth + 7) + "15";
             screenShot = PoiUtil.screenShot(workbook.getSheet("客户端-战略考核"), area2, "微软雅黑");
             ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-2-客户端-战略考核.png").toFile());
-
-            // 截图2
+            // 截图3 每月1号不发送重复投诉率
             if (!day.endsWith("01")) {
                 screenShot = PoiUtil.screenShot(workbook.getSheet("管理端-重复投诉率"), "A1:G16", "微软雅黑");
                 ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-3-重复投诉率.png").toFile());
             }
-
-            // 截图3 4
+            // 截图4 5
             Sheet sheet = workbook.getSheet("投诉处理时长、超时工单概况");
             screenShot = PoiUtil.screenShot(sheet, "A1:D15", "微软雅黑");
             ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-4-超时工单.png").toFile());
             screenShot = PoiUtil.screenShot(sheet, "G1:J14", "微软雅黑");
             ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-5-处理时长.png").toFile());
-
-            // 截图5
-            screenShot = PoiUtil.screenShot(workbook.getSheet("客户端-投诉问题解决满意度"), "A1:D15", "微软雅黑");
-            ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-6-满意率.png").toFile());
-
             // 截图6
-            screenShot = PoiUtil.screenShot(workbook.getSheet("客户端-投诉问题解决率"), "A1:D15", "微软雅黑");
-            ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-7-解决率.png").toFile());
-
-            // 截图7
-            screenShot = PoiUtil.screenShot(workbook.getSheet("客户端-投诉问题响应率"), "A1:D15", "微软雅黑");
-            ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-8-响应率.png").toFile());
+            sheet = workbook.getSheet("客户端地市三率");
+            screenShot = PoiUtil.screenShot(sheet, "A1:J15", "微软雅黑");
+            ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-6-地市三率.png").toFile());
+            // 截图7 8 9 区县三率
+            sheet = workbook.getSheet("客户端区县三率");
+            screenShot = PoiUtil.screenShot(sheet, "A1:F32", "微软雅黑");
+            ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-7-区县响应率.png").toFile());
+            screenShot = PoiUtil.screenShot(sheet, "A1:F32", "微软雅黑");
+            ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-8-区县满意度.png").toFile());
+            screenShot = PoiUtil.screenShot(sheet, "A1:F32", "微软雅黑");
+            ImageIO.write(screenShot, "png", Paths.get(outputProperties.getOutputPath(), day, day + "-9-区县解决率.png").toFile());
         } catch (EncryptedDocumentException | IOException | ParseException e) {
             e.printStackTrace();
             throw new RuntimeException(e.getMessage());
@@ -137,7 +138,7 @@ public class TslReportService {
      */
     private void workbookToFile(String day, File file) {
         // 每次需要重置workbook
-        workbook = new XSSFWorkbook();
+        workbook = getWorkbook();
         // 按照顺序写入各个sheet
         // 管理端-移网质量类
         getSheet1(day);
@@ -148,7 +149,10 @@ public class TslReportService {
         // 投诉处理时长、超时工单概况
         getSheet3(day);
         // 客户端-投诉问题解决满意度 客户端-投诉问题解决率 客户端-投诉问题响应率
-        getSheet4_6(day);
+        // getSheet4_6(day);
+        getCityThreeRateSheet(day);
+        // 区县三率
+        getSheet7(day);
         try (OutputStream outputStream = new FileOutputStream(file)) {
             workbook.write(outputStream);
             workbook.close();
@@ -160,11 +164,9 @@ public class TslReportService {
     }
 
     /**
-     * 客户端-投诉问题解决满意度
-     * 客户端-投诉问题解决率
-     * 客户端-投诉问题响应率
+     * 客户端区县三率
      */
-    private void getSheet4_6(String day) {
+    private void getSheet7(String day) {
         // 计算时间常数
         Calendar calendar = Calendar.getInstance(Locale.CHINA);
         try {
@@ -173,167 +175,272 @@ public class TslReportService {
             log.error("时间字符串解析失败--{}", day);
         }
         int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
-        Sheet sheet4 = getWorkbook().createSheet("客户端-投诉问题解决满意度");
-        Sheet sheet5 = getWorkbook().createSheet("客户端-投诉问题解决率");
-        Sheet sheet6 = getWorkbook().createSheet("客户端-投诉问题响应率");
+        Sheet sheet7 = getWorkbook().createSheet("客户端区县三率");
         Row row;
         Cell cell;
         CellRangeAddress rangeAddress;
-        XSSFDataFormat dataFormat = getWorkbook().createDataFormat();
-        XSSFFont font = getWorkbook().createFont();
-        font.setFontName("微软雅黑");
-        font.setFontHeightInPoints((short) 10);
-        // 基本模式 微软雅黑 10号字 带全边框 水平居中
-        XSSFCellStyle baseStyle = getWorkbook().createCellStyle();
-        baseStyle.setFont(font);
-        baseStyle.setAlignment(HorizontalAlignment.CENTER);
-        baseStyle.setVerticalAlignment(VerticalAlignment.CENTER);
-        baseStyle.setBorderBottom(BorderStyle.THIN);
-        baseStyle.setBorderTop(BorderStyle.THIN);
-        baseStyle.setBorderLeft(BorderStyle.THIN);
-        baseStyle.setBorderRight(BorderStyle.THIN);
-        // 样式1 与base相同
-        XSSFCellStyle cellStyle1 = baseStyle.copy();
-        // 样式2 百分比 2位小数
-        XSSFCellStyle cellStyle2 = baseStyle.copy();
-        cellStyle2.setDataFormat(dataFormat.getFormat("0.00%"));
-        // 样式3 自动换行 背景色FFE7E6E6 FFAEAAAA
-        XSSFCellStyle cellStyle3 = baseStyle.copy();
-        // cellStyle3.setWrapText(true);
-        XSSFColor color = new XSSFColor();
-        color.setARGBHex("FFAEAAAA");
-        cellStyle3.setFillPattern(FillPatternType.SOLID_FOREGROUND);
-        cellStyle3.setFillForegroundColor(color);
 
-        List<List<List<Object>>> sheet4_6Data = tslDataService.getSheet4_6Data(day);
-
-        // 客户端-投诉问题解决满意度 第一行
-        row = sheet4.createRow(0);
+        // 获取响应率数据
+        List<List<Object>> timelyContactRateData = highQualityDataService.generateCpTimelyContactOfRegion(day);
+        // 第一行 区县投诉问题响应率(1-11)
+        row = sheet7.createRow(0);
         cell = row.createCell(0);
-        cell.setCellValue(String.format("投诉问题解决满意率(1-%s)", dayOfMonth));
+        cell.setCellValue(String.format("区县投诉问题响应率(1-%s)", dayOfMonth));
         cell.setCellStyle(cellStyle3);
-        // 合并单元格 A1 - D1
-        rangeAddress = new CellRangeAddress(0, 0, 0, 3);
-        addMergedRegion(sheet4, rangeAddress);
-        // 客户端-投诉问题解决满意度 第二行
-        row = sheet4.createRow(1);
+        // 合并单元格 A1-F1
+        rangeAddress = new CellRangeAddress(0, 0, 0, 5);
+        addMergedRegion(sheet7, rangeAddress);
+        // 第二行
+        row = sheet7.createRow(1);
         cell = row.createCell(0);
         cell.setCellValue("地市");
         cell.setCellStyle(cellStyle3);
         cell = row.createCell(1);
-        cell.setCellValue("满意率");
+        cell.setCellValue("区县");
         cell.setCellStyle(cellStyle3);
         cell = row.createCell(2);
-        cell.setCellValue("达标值");
+        cell.setCellValue("响应率");
         cell.setCellStyle(cellStyle3);
         cell = row.createCell(3);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(4);
         cell.setCellValue("与达标值差距");
         cell.setCellStyle(cellStyle3);
+        cell = row.createCell(5);
+        cell.setCellValue("累计未响应");
+        cell.setCellStyle(cellStyle3);
+        // 写入数据
         int rowNum = 2;
-        for (List<Object> list : sheet4_6Data.get(0)) {
-            row = sheet4.createRow(rowNum++);
+        for (List<Object> list : timelyContactRateData) {
+            row = sheet7.createRow(rowNum++);
             // 地市
             cell = row.createCell(0);
             cell.setCellValue(list.get(0).toString());
             cell.setCellStyle(cellStyle1);
-            // 投诉问题解决满意率
+            // 区县
             cell = row.createCell(1);
-            cell.setCellValue(((double) list.get(1)));
-            cell.setCellStyle(cellStyle2);
-            // 达标值
+            cell.setCellValue(list.get(1).toString());
+            cell.setCellStyle(cellStyle1);
+            // 响应率
             cell = row.createCell(2);
             cell.setCellValue((double) list.get(2));
             cell.setCellStyle(cellStyle2);
-            // 达标值差距
+            // 达标值
             cell = row.createCell(3);
-            cell.setCellValue(((double) list.get(3)));
+            cell.setCellValue((double)list.get(3));
             cell.setCellStyle(cellStyle2);
+            // 与达标值差距
+            cell = row.createCell(4);
+            cell.setCellValue((double)list.get(4));
+            cell.setCellStyle(cellStyle2);
+            // 本月累计未响应工单数
+            cell = row.createCell(5);
+            cell.setCellValue((int)list.get(5));
+            cell.setCellStyle(cellStyle1);
         }
-        // 设置条件格式D3-D14
-        rangeAddress = new CellRangeAddress(2, 13, 3, 3);
-        setConditionalFormatting2(sheet4, rangeAddress);
-
-        // 设置列宽 2048 1304 2048 2304
-        for (int i = 0; i < 4; i++) {
-            sheet4.setColumnWidth(i, 2848);
-        }
-
-        // 设置行高 15.0 15.0...
-        for (int i = 0; i < 15; i++) {
-            sheet4.getRow(i).setHeightInPoints(15.0F);
+        // 与达标值差距设置条件格式
+        rangeAddress = new CellRangeAddress(2, --rowNum, 4, 4);
+        setConditionalFormatting2(sheet7, rangeAddress);
+        // 设置列宽
+        sheet7.setColumnWidth(0, 2848);
+        sheet7.setColumnWidth(1, 2848);
+        sheet7.setColumnWidth(2, 2848);
+        sheet7.setColumnWidth(3, 2848);
+        sheet7.setColumnWidth(4, 2848);
+        sheet7.setColumnWidth(5, 2848);
+
+        // 获取和写入满意度数据
+        List<List<Object>> satisfactionData = highQualityDataService.generateCpSatisfactionOfRegion(day);
+        // 第一行 区县投诉问题满意度(1-11)
+        row = sheet7.getRow(0);
+        cell = row.createCell(7);
+        cell.setCellValue(String.format("区县投诉问题满意度(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 H1-M1
+        rangeAddress = new CellRangeAddress(0, 0, 7, 12);
+        addMergedRegion(sheet7, rangeAddress);
+        // 第二行
+        row = sheet7.getRow(1);
+        cell = row.createCell(7);
+        cell.setCellValue("地市");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(8);
+        cell.setCellValue("区县");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(9);
+        cell.setCellValue("满意度");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(10);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(11);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(12);
+        cell.setCellValue("累计不满意");
+        cell.setCellStyle(cellStyle3);
+        // 写入数据
+        rowNum = 2;
+        for (List<Object> list : satisfactionData) {
+            row = sheet7.getRow(rowNum++);
+            // 地市
+            cell = row.createCell(7);
+            cell.setCellValue(list.get(0).toString());
+            cell.setCellStyle(cellStyle1);
+            // 区县
+            cell = row.createCell(8);
+            cell.setCellValue(list.get(1).toString());
+            cell.setCellStyle(cellStyle1);
+            // 响应率
+            cell = row.createCell(9);
+            cell.setCellValue((double) list.get(2));
+            cell.setCellStyle(cellStyle2);
+            // 达标值
+            cell = row.createCell(10);
+            cell.setCellValue((double)list.get(3));
+            cell.setCellStyle(cellStyle2);
+            // 与达标值差距
+            cell = row.createCell(11);
+            cell.setCellValue((double)list.get(4));
+            cell.setCellStyle(cellStyle2);
+            // 本月累计未响应工单数
+            cell = row.createCell(12);
+            cell.setCellValue((int)list.get(5));
+            cell.setCellStyle(cellStyle1);
         }
-
-        // 客户端-投诉问题解决率
-        row = sheet5.createRow(0);
-        cell = row.createCell(0);
-        cell.setCellValue(String.format("投诉问题解决率(1-%s)", dayOfMonth));
+        // 与达标值差距设置条件格式
+        rangeAddress = new CellRangeAddress(2, --rowNum, 11, 11);
+        setConditionalFormatting2(sheet7, rangeAddress);
+        // 设置列宽
+        sheet7.setColumnWidth(7, 2848);
+        sheet7.setColumnWidth(8, 2848);
+        sheet7.setColumnWidth(9, 2848);
+        sheet7.setColumnWidth(10, 2848);
+        sheet7.setColumnWidth(11, 2848);
+        sheet7.setColumnWidth(12, 2848);
+
+        // 获取和写入解决率数据 14到19列 O-T
+        List<List<Object>> cpIsOkData = highQualityDataService.generateCpIsOkOfRegion(day);
+        // 第一行 区县投诉问题解决率(1-11)
+        row = sheet7.getRow(0);
+        cell = row.createCell(14);
+        cell.setCellValue(String.format("区县投诉问题解决率(1-%s)", dayOfMonth));
         cell.setCellStyle(cellStyle3);
-        // 合并单元格 A1 - D1
-        rangeAddress = new CellRangeAddress(0, 0, 0, 3);
-        addMergedRegion(sheet5, rangeAddress);
-        // 客户端-投诉问题解决率 第二行
-        row = sheet5.createRow(1);
-        cell = row.createCell(0);
+        // 合并单元格 O1-T1
+        rangeAddress = new CellRangeAddress(0, 0, 14, 19);
+        addMergedRegion(sheet7, rangeAddress);
+        // 第二行
+        row = sheet7.getRow(1);
+        cell = row.createCell(14);
         cell.setCellValue("地市");
         cell.setCellStyle(cellStyle3);
-        cell = row.createCell(1);
+        cell = row.createCell(15);
+        cell.setCellValue("区县");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(16);
         cell.setCellValue("解决率");
         cell.setCellStyle(cellStyle3);
-        cell = row.createCell(2);
+        cell = row.createCell(17);
         cell.setCellValue("达标值");
         cell.setCellStyle(cellStyle3);
-        cell = row.createCell(3);
+        cell = row.createCell(18);
         cell.setCellValue("与达标值差距");
         cell.setCellStyle(cellStyle3);
+        cell = row.createCell(19);
+        cell.setCellValue("累计未解决");
+        cell.setCellStyle(cellStyle3);
+        // 写入数据
         rowNum = 2;
-        for (List<Object> list : sheet4_6Data.get(1)) {
-            row = sheet5.createRow(rowNum++);
+        for (List<Object> list : cpIsOkData) {
+            row = sheet7.getRow(rowNum++);
             // 地市
-            cell = row.createCell(0);
+            cell = row.createCell(14);
             cell.setCellValue(list.get(0).toString());
             cell.setCellStyle(cellStyle1);
-            // 投诉问题解决率
-            cell = row.createCell(1);
-            cell.setCellValue(((double) list.get(1)));
+            // 区县
+            cell = row.createCell(15);
+            cell.setCellValue(list.get(1).toString());
+            cell.setCellStyle(cellStyle1);
+            // 响应率
+            cell = row.createCell(16);
+            cell.setCellValue((double) list.get(2));
             cell.setCellStyle(cellStyle2);
             // 达标值
-            cell = row.createCell(2);
-            cell.setCellValue((double) list.get(2));
+            cell = row.createCell(17);
+            cell.setCellValue((double)list.get(3));
             cell.setCellStyle(cellStyle2);
             // 与达标值差距
-            cell = row.createCell(3);
-            cell.setCellValue(((double) list.get(3)));
+            cell = row.createCell(18);
+            cell.setCellValue((double)list.get(4));
             cell.setCellStyle(cellStyle2);
+            // 本月累计未响应工单数
+            cell = row.createCell(19);
+            cell.setCellValue((int)list.get(5));
+            cell.setCellStyle(cellStyle1);
         }
-        // 设置条件格式D3-D14
-        rangeAddress = new CellRangeAddress(2, 13, 3, 3);
-        setConditionalFormatting2(sheet5, rangeAddress);
-
-        // 设置列宽 2048 1304 2048 2304
-        for (int i = 0; i < 4; i++) {
-            sheet5.setColumnWidth(i, 2848);
+        // 与达标值差距设置条件格式
+        rangeAddress = new CellRangeAddress(2, --rowNum, 18, 18);
+        setConditionalFormatting2(sheet7, rangeAddress);
+        // 设置列宽
+        sheet7.setColumnWidth(14, 2848);
+        sheet7.setColumnWidth(15, 2848);
+        sheet7.setColumnWidth(16, 2848);
+        sheet7.setColumnWidth(17, 2848);
+        sheet7.setColumnWidth(18, 2848);
+        sheet7.setColumnWidth(19, 2848);
+
+        // 设置行高
+        for (int i = 0; i < rowNum; i++) {
+            sheet7.getRow(i).setHeightInPoints(20.0F);
         }
+    }
 
-        // 设置行高 15.0 15.0...
-        for (int i = 0; i < 15; i++) {
-            sheet5.getRow(i).setHeightInPoints(15.0F);
+    private void getCityThreeRateSheet(String day) {
+        // 计算时间常数
+        Calendar calendar = Calendar.getInstance(Locale.CHINA);
+        try {
+            calendar.setTime(DAY_FORMAT.parse(day));
+        } catch (ParseException e) {
+            log.error("时间字符串解析失败--{}", day);
         }
-
-        // 客户端-投诉问题响应率
-        row = sheet6.createRow(0);
+        int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+        Sheet sheet = getWorkbook().createSheet("客户端地市三率");
+        Row row;
+        Cell cell;
+        CellRangeAddress rangeAddress;
+        // 获取区县三率数据
+        Map<String, List<Object>> dataMap = highQualityCountService.generateThreeRateOfCity(day);
+        // 第一行
+        row = sheet.createRow(0);
         cell = row.createCell(0);
+        cell.setCellValue("");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(1);
+        cell.setCellValue(String.format("投诉问题解决满意率(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 B1 - D1
+        rangeAddress = new CellRangeAddress(0, 0, 1, 3);
+        addMergedRegion(sheet, rangeAddress);
+        cell = row.createCell(4);
+        cell.setCellValue(String.format("投诉问题解决率(1-%s)", dayOfMonth));
+        cell.setCellStyle(cellStyle3);
+        // 合并单元格 E1 - G1
+        rangeAddress = new CellRangeAddress(0, 0, 4, 6);
+        addMergedRegion(sheet, rangeAddress);
+        cell = row.createCell(7);
         cell.setCellValue(String.format("投诉问题响应率(1-%s)", dayOfMonth));
         cell.setCellStyle(cellStyle3);
-        // 合并单元格 A1 - D1
-        rangeAddress = new CellRangeAddress(0, 0, 0, 3);
-        addMergedRegion(sheet6, rangeAddress);
-        // 客户端-投诉问题响应率 第二行
-        row = sheet6.createRow(1);
+        // 合并单元格 H1 - J1
+        rangeAddress = new CellRangeAddress(0, 0, 7, 9);
+        addMergedRegion(sheet, rangeAddress);
+        // 第二行
+        row = sheet.createRow(1);
         cell = row.createCell(0);
         cell.setCellValue("地市");
         cell.setCellStyle(cellStyle3);
         cell = row.createCell(1);
-        cell.setCellValue("响应率");
+        cell.setCellValue("满意度");
         cell.setCellStyle(cellStyle3);
         cell = row.createCell(2);
         cell.setCellValue("达标值");
@@ -341,38 +448,120 @@ public class TslReportService {
         cell = row.createCell(3);
         cell.setCellValue("与达标值差距");
         cell.setCellStyle(cellStyle3);
-        rowNum = 2;
-        for (List<Object> list : sheet4_6Data.get(2)) {
-            row = sheet6.createRow(rowNum++);
+        cell = row.createCell(4);
+        cell.setCellValue("解决率");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(5);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(6);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(7);
+        cell.setCellValue("响应率");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(8);
+        cell.setCellValue("达标值");
+        cell.setCellStyle(cellStyle3);
+        cell = row.createCell(9);
+        cell.setCellValue("与达标值差距");
+        cell.setCellStyle(cellStyle3);
+        // 数据行
+        int rowNum = 2;
+        for (String city : sysDataDictionaryRepository.findAllCityName()) {
+            List<Object> list = dataMap.get(city);
+            row = sheet.createRow(rowNum++);
             // 地市
             cell = row.createCell(0);
             cell.setCellValue(list.get(0).toString());
             cell.setCellStyle(cellStyle1);
-            // 投诉问题解决率
+            // 满意度
             cell = row.createCell(1);
-            cell.setCellValue(((double) list.get(1)));
+            cell.setCellValue((double) list.get(1));
             cell.setCellStyle(cellStyle2);
-            // 达标值
             cell = row.createCell(2);
             cell.setCellValue((double) list.get(2));
             cell.setCellStyle(cellStyle2);
-            // 与达标值差距
             cell = row.createCell(3);
-            cell.setCellValue(((double) list.get(3)));
+            cell.setCellValue((double) list.get(3));
+            cell.setCellStyle(cellStyle2);
+            // 解决率
+            cell = row.createCell(4);
+            cell.setCellValue((double) list.get(4));
+            cell.setCellStyle(cellStyle2);
+            cell = row.createCell(5);
+            cell.setCellValue((double) list.get(5));
+            cell.setCellStyle(cellStyle2);
+            cell = row.createCell(6);
+            cell.setCellValue((double) list.get(6));
+            cell.setCellStyle(cellStyle2);
+            // 响应率
+            cell = row.createCell(7);
+            cell.setCellValue((double) list.get(7));
+            cell.setCellStyle(cellStyle2);
+            cell = row.createCell(8);
+            cell.setCellValue((double) list.get(8));
+            cell.setCellStyle(cellStyle2);
+            cell = row.createCell(9);
+            cell.setCellValue((double) list.get(9));
             cell.setCellStyle(cellStyle2);
         }
+        // 设置条件格式
         // 设置条件格式D3-D14
         rangeAddress = new CellRangeAddress(2, 13, 3, 3);
-        setConditionalFormatting2(sheet6, rangeAddress);
+        setConditionalFormatting2(sheet, rangeAddress);
+        // 设置条件格式G3-G14
+        rangeAddress = new CellRangeAddress(2, 13, 6, 6);
+        setConditionalFormatting2(sheet, rangeAddress);
+        // 设置条件格式J3-J14
+        rangeAddress = new CellRangeAddress(2, 13, 9, 9);
+        setConditionalFormatting2(sheet, rangeAddress);
+        // 写入全省数据
+        List<Object> list = dataMap.get("全省");
+        row = sheet.createRow(rowNum);
+        // 地市
+        cell = row.createCell(0);
+        cell.setCellValue(list.get(0).toString());
+        cell.setCellStyle(cellStyle1);
+        // 满意度
+        cell = row.createCell(1);
+        cell.setCellValue((double) list.get(1));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(2);
+        cell.setCellValue((double) list.get(2));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(3);
+        cell.setCellValue((double) list.get(3));
+        cell.setCellStyle(cellStyle2);
+        // 解决率
+        cell = row.createCell(4);
+        cell.setCellValue((double) list.get(4));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(5);
+        cell.setCellValue((double) list.get(5));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(6);
+        cell.setCellValue((double) list.get(6));
+        cell.setCellStyle(cellStyle2);
+        // 响应率
+        cell = row.createCell(7);
+        cell.setCellValue((double) list.get(7));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(8);
+        cell.setCellValue((double) list.get(8));
+        cell.setCellStyle(cellStyle2);
+        cell = row.createCell(9);
+        cell.setCellValue((double) list.get(9));
+        cell.setCellStyle(cellStyle2);
 
-        // 设置列宽 2048 1304 2048 2304
-        for (int i = 0; i < 4; i++) {
-            sheet6.setColumnWidth(i, 2848);
+        // 设置列宽
+        for (int i = 0; i < 9; i++) {
+            sheet.setColumnWidth(i, 2848);
         }
 
         // 设置行高 15.0 15.0...
         for (int i = 0; i < 15; i++) {
-            sheet6.getRow(i).setHeightInPoints(15.0F);
+            sheet.getRow(i).setHeightInPoints(15.0F);
         }
     }
 
@@ -1028,9 +1217,36 @@ public class TslReportService {
         }
     }
 
+    /**
+     * 初始化表格对象以及
+     */
     private XSSFWorkbook getWorkbook() {
         if (workbook == null) {
             workbook = new XSSFWorkbook();
+            // 基本模式 微软雅黑 10号字 带全边框 水平居中
+            XSSFCellStyle baseStyle = getWorkbook().createCellStyle();
+            XSSFFont font = getWorkbook().createFont();
+            font.setFontName("微软雅黑");
+            font.setFontHeightInPoints((short) 10);
+            baseStyle.setFont(font);
+            baseStyle.setAlignment(HorizontalAlignment.CENTER);
+            baseStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            baseStyle.setBorderBottom(BorderStyle.THIN);
+            baseStyle.setBorderTop(BorderStyle.THIN);
+            baseStyle.setBorderLeft(BorderStyle.THIN);
+            baseStyle.setBorderRight(BorderStyle.THIN);
+            // cellStyle1 基本模式 微软雅黑 10号字 带全边框 水平居中
+            cellStyle1 = baseStyle.copy();
+            // cellStyle2 百分比 2位小数
+            cellStyle2 = baseStyle.copy();
+            XSSFDataFormat dataFormat = getWorkbook().createDataFormat();
+            cellStyle2.setDataFormat(dataFormat.getFormat("0.00%"));
+            // cellStyle3 自动换行 背景色 FFAEAAAA
+            cellStyle3 = baseStyle.copy();
+            XSSFColor color = new XSSFColor();
+            color.setARGBHex("FFAEAAAA");
+            cellStyle3.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            cellStyle3.setFillForegroundColor(color);
         }
         return this.workbook;
     }
@@ -1048,7 +1264,7 @@ public class TslReportService {
     }
 
     /**
-     * 条件格式 3色渐变
+     * 条件格式 3色渐变 数字小的为绿色,大的为红色
      * 绿 FF63BE7B 黄 FFFFEB84 红 FFF8696B
      */
     private void setConditionalFormatting(Sheet sheet, CellRangeAddress rangeAddress) {
@@ -1060,18 +1276,15 @@ public class TslReportService {
         colors[0].setARGBHex("FF63BE7B");
         colors[1].setARGBHex("FFFFEB84");
         colors[2].setARGBHex("FFF8696B");
-
         rule.getColorScaleFormatting().setColors(colors);
-
         CellRangeAddress[] cellRangeAddresses = new CellRangeAddress[]{
                 rangeAddress
         };
-
         conditionalFormatting.addConditionalFormatting(cellRangeAddresses, rule);
     }
 
     /**
-     * 条件格式 3色渐变
+     * 条件格式 3色渐变 数字小的为红色,大的为绿色
      * 绿 FF63BE7B 黄 FFFFEB84 红 FFF8696B
      */
     private void setConditionalFormatting2(Sheet sheet, CellRangeAddress rangeAddress) {
@@ -1083,13 +1296,10 @@ public class TslReportService {
         colors[0].setARGBHex("FFF8696B");
         colors[1].setARGBHex("FFFFEB84");
         colors[2].setARGBHex("FF63BE7B");
-
         rule.getColorScaleFormatting().setColors(colors);
-
         CellRangeAddress[] cellRangeAddresses = new CellRangeAddress[]{
                 rangeAddress
         };
-
         conditionalFormatting.addConditionalFormatting(cellRangeAddresses, rule);
     }
 }

+ 17 - 0
src/test/java/com/nokia/tsl_data/MainTest.java

@@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 
 import java.io.*;
+import java.nio.charset.StandardCharsets;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -105,4 +106,20 @@ public class MainTest {
         }).get();
         log.info("done!");
     }
+
+    @Test
+    void test2() throws IOException {
+        String filePath = "D:\\src\\DM_M_QT_VOLET_USER_PROV_202309_1170011851488288768.csv";
+        String filePath2 = "D:\\src\\DM_M_QT_VOLET_USER_PROV_202309_1170011851488288768-筛选后.csv";
+        CSVFormat csvFormat = CSVFormat.DEFAULT.builder().build();
+        CSVParser csvParser = csvFormat.parse(new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8));
+        CSVPrinter csvPrinter = csvFormat.print(new OutputStreamWriter(new FileOutputStream(filePath2), "gbk"));
+        for (CSVRecord record : csvParser.getRecords()) {
+            if ("1".equals(record.get(12))) {
+                csvPrinter.printRecord(record);
+            }
+        }
+        csvParser.close();
+        csvPrinter.close();
+    }
 }

+ 5 - 17
src/test/java/com/nokia/tsl_data/TslDataApplicationTest.java

@@ -1,7 +1,6 @@
 package com.nokia.tsl_data;
 
-import com.nokia.tsl_data.dao.HighQualityDataMapper;
-import com.nokia.tsl_data.service.DataWarehouseService;
+import com.nokia.tsl_data.service.TslReportService;
 import com.nokia.tsl_data.service.UserCountService;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -11,8 +10,6 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.util.List;
-import java.util.Map;
 
 @SpringBootTest
 class TslDataApplicationTest {
@@ -30,7 +27,7 @@ class TslDataApplicationTest {
                 .forEach(line -> {
                     String[] split = line.split("\t");
                     System.out.println(split[0] + Double.parseDouble(split[2]));
-                    userCountService.updateManagementUserCount("202310", split[0], Double.parseDouble(split[2]));
+                    userCountService.updateManagementUserCount("202311", split[0], Double.parseDouble(split[2]));
                 });
     }
 
@@ -44,24 +41,15 @@ class TslDataApplicationTest {
                 .forEach(line -> {
                     String[] split = line.split("\t");
                     System.out.println(split[0] + Double.parseDouble(split[1]));
-                    userCountService.updateCustomerUserCount("202310", split[0], Double.parseDouble(split[1]));
+                    userCountService.updateCustomerUserCount("202311", split[0], Double.parseDouble(split[1]));
                 });
     }
 
     @Autowired
-    private HighQualityDataMapper highQualityDataMapper;
+    private TslReportService tslReportService;
 
     @Test
     void test() {
-        List<Map<String, Object>> maps = highQualityDataMapper.selectRegionTimelyContactRate("20231014");
-        maps.forEach(System.out::println);
-    }
-
-    @Autowired
-    private DataWarehouseService dataWarehouseService;
-
-    @Test
-    void test3() {
-        dataWarehouseService.warehouseHighQualityCountDay("20231101");
+        tslReportService.screenShot("20231105");
     }
 }