浏览代码

tsl_data v1.1 添加了自己实现的基于POI和Graphics2D的截图工具类

lifuquan 2 年之前
父节点
当前提交
5271de1f1f
共有 23 个文件被更改,包括 1035 次插入119 次删除
  1. 3 1
      dingtalk_auto/src/main/java/com/nokia/common/aspose/AsposeUtil.java
  2. 40 0
      dingtalk_auto/src/main/java/com/nokia/common/dingtalk/DingTalkUtil.java
  3. 34 30
      dingtalk_auto/src/main/java/com/nokia/dingtalk_auto/service/TslTaskService.java
  4. 3 3
      dingtalk_auto/src/main/resources/application-dev.properties
  5. 14 3
      dingtalk_auto/src/main/resources/application.properties
  6. 37 4
      dingtalk_auto/src/test/java/com/nokia/dingtalk_auto/service/task/TslTaskServiceTest.java
  7. 3 3
      doc/开发文档/tsl_data/部署环境接口测试.md
  8. 2 0
      doc/开发文档/开发文档.md
  9. 2 3
      tsl_data/pom.xml
  10. 5 0
      tsl_data/src/main/java/com/nokia/common/excel/entity/AlignmentEnum.java
  11. 58 0
      tsl_data/src/main/java/com/nokia/common/excel/entity/CellInfo.java
  12. 109 0
      tsl_data/src/main/java/com/nokia/common/excel/entity/CellRect.java
  13. 33 0
      tsl_data/src/main/java/com/nokia/common/excel/entity/Gradient.java
  14. 48 0
      tsl_data/src/main/java/com/nokia/common/excel/entity/ThreeColorGradient.java
  15. 27 0
      tsl_data/src/main/java/com/nokia/common/excel/entity/TwoColorGradient.java
  16. 460 0
      tsl_data/src/main/java/com/nokia/common/excel/poi/PoiUtil.java
  17. 6 0
      tsl_data/src/main/java/com/nokia/tsl_data/dao/TslDao.java
  18. 42 28
      tsl_data/src/main/java/com/nokia/tsl_data/service/TslDataService.java
  19. 10 11
      tsl_data/src/main/java/com/nokia/tsl_data/service/TslReportService.java
  20. 65 5
      tsl_data/src/main/java/com/nokia/tsl_data/service/TslTaskService.java
  21. 0 8
      tsl_data/src/main/java/com/nokia/tsl_data/service/exception/TslTaskException.java
  22. 31 17
      tsl_data/src/main/resources/mapper/TslDao.xml
  23. 3 3
      tsl_data/src/test/java/com/nokia/tsl_data/TslDataApplicationTest.java

+ 3 - 1
dingtalk_auto/src/main/java/com/nokia/common/aspose/AsposeUtil.java

@@ -1,5 +1,7 @@
 package com.nokia.common.aspose;
 
+import java.io.FileOutputStream;
+
 import com.aspose.cells.ImageOrPrintOptions;
 import com.aspose.cells.ImageType;
 import com.aspose.cells.SheetRender;
@@ -40,7 +42,7 @@ public class AsposeUtil {
         options.setImageType(ImageType.PNG);
         SheetRender sheetRender = new SheetRender(worksheet, options);
         for (int i = 0; i < sheetRender.getPageCount(); i++) {
-            sheetRender.toImage(i, imgPath);
+            sheetRender.toImage(i, new FileOutputStream(imgPath));
         }
     }
 

+ 40 - 0
dingtalk_auto/src/main/java/com/nokia/common/dingtalk/DingTalkUtil.java

@@ -206,6 +206,46 @@ public class DingTalkUtil {
         }
     }
 
+    /**
+     * 使用钉钉机器人发送文字消息
+     * 
+     * @param msg
+     * @throws DingTalkApiException
+     */
+    public void sendMarkdownMsgWithRobot(String msg) throws DingTalkApiException {
+        String url = "https://api.dingtalk.com/v1.0/robot/groupMessages/send";
+
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+        httpHeaders.set("x-acs-dingtalk-access-token", getApiToken());
+
+        Map<String, Object> msgParam = new HashMap<>();
+        msgParam.put("text", msg);
+
+        Map<String, Object> request = new HashMap<>();
+        try {
+            request.put("msgParam", objectMapper.writeValueAsString(msgParam));
+            request.put("msgKey", "sampleMarkdown");
+            request.put("openConversationId", conversationId);
+            request.put("robotCode", appKey);
+
+            HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(request, httpHeaders);
+
+            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
+
+            if (responseEntity.getStatusCode().is2xxSuccessful()) {
+                JsonNode node = objectMapper.readTree(responseEntity.getBody());
+                if (node.get("processQueryKey") != null) {
+                    return;
+                } else {
+                    throw new DingTalkApiException("api返回格式有误--" + responseEntity.getBody());
+                }
+            }
+        } catch (JsonProcessingException e) {
+            throw new DingTalkApiException("入参格式错误--" + msgParam);
+        }
+    }
+
     /*
      * 获取api access token
      * 钉钉的access token的有效期是2个小时,并且对获取token的接口有熔断机制

+ 34 - 30
dingtalk_auto/src/main/java/com/nokia/dingtalk_auto/service/TslTaskService.java

@@ -25,6 +25,8 @@ import com.nokia.common.dingtalk.DingTalkUtil;
 import com.nokia.common.dingtalk.exception.DingTalkApiException;
 import com.nokia.common.jsch.SftpUtil;
 
+import lombok.Getter;
+
 /**
  * 向钉钉群发送投诉清单各地市投诉率
  */
@@ -65,7 +67,9 @@ public class TslTaskService {
     @Resource
     private ObjectMapper objectMapper;
 
+    @Getter
     private DingTalkUtil dingTalkUtil;
+    @Getter
     private boolean isDingTalkUtilInited = false;
 
     // 完成指定日期的发送任务
@@ -80,7 +84,7 @@ public class TslTaskService {
         if (!isDingTalkUtilInited) {
             initDingTalkUtil();
         }
-        dingTalkUtil.sendTextMsgWithRobot(message);
+        dingTalkUtil.sendMarkdownMsgWithRobot(message);
     }
 
     public void sendDingTalkMsg(String day) throws DingTalkApiException {
@@ -94,13 +98,14 @@ public class TslTaskService {
                 String mediaId = dingTalkUtil.upload(file.getAbsolutePath(), "image");
                 dingTalkUtil.sendImageMsgWithRobot(mediaId);
             } else if (file.getName().toLowerCase().endsWith(".xlsx")) {
+                // TODO 需要确认一下仅发送正常文件且只发送1个
                 String mediaId = dingTalkUtil.upload(file.getAbsolutePath(), "file");
                 dingTalkUtil.sendFileMsgWithRobot(mediaId, file.getName());
             }
         }
     }
 
-    private void initDingTalkUtil() {
+    public void initDingTalkUtil() {
         if (!isDingTalkUtilInited) {
             dingTalkUtil = new DingTalkUtil(appKey, appSecret, openConversationId, restTemplate,
                     redisTemplate, objectMapper);
@@ -111,13 +116,6 @@ public class TslTaskService {
     /**
      * 截图,组织一段话
      * 
-     * 2023年4月截至23日移动网投诉情况统计:
-     * 管理端-移网质量类:投诉率:石家庄、承德、雄安、张家口、沧州、衡水、唐山未达到目标值,石家庄、雄安、承德排名靠后;重复投诉率:唐山、沧州、廊坊增长较快。
-     * 客户端-移网网络体验:投诉问题解决满意率:雄安、张家口、唐山较低,与达标值差距较大;投诉问题解决率:雄安、张家口、唐山较低,与达标值差距较大;
-     * 投诉问题响应率:雄安、张家口、石家庄较低,与达标值差距较大。投诉处理时长、超时工单概况:
-     * 超时工单:雄安、石家庄、张家口分公司超时工单占比较高,石家庄、唐山、邯郸超时工单数量较多。
-     * 平均处理时长:本月相对较长的地市为雄安、石家庄、张家口;与3月比雄安时长增幅较大。
-     * 
      * @param day
      * @throws Exception
      */
@@ -130,10 +128,10 @@ public class TslTaskService {
         int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
         stringBuffer.append(calendar.get(Calendar.YEAR))
                 .append("年")
-                .append(calendar.get(Calendar.MONTH))
+                .append(calendar.get(Calendar.MONTH + 1))
                 .append("月截至")
                 .append(dayOfMonth)
-                .append("日移动网投诉情况统计:管理端-移网质量类:投诉率:");
+                .append("日移动网投诉情况统计:\n\n管理端-移网质量类:投诉率:<font color=#FF0000>");
         File file = new File(localFile);
         if (!file.exists()) {
             throw new Exception(String.format("文件--%s--不存在", localFile));
@@ -166,20 +164,26 @@ public class TslTaskService {
         }
         stringBuffer.deleteCharAt(stringBuffer.length() - 1);
         stringBuffer2.deleteCharAt(stringBuffer2.length() - 1);
-        stringBuffer.append("未达到目标值,")
+        stringBuffer.append("</font>未达到目标值,<font color=#FF0000>")
                 .append(stringBuffer2.toString())
-                .append("排名靠后;重复投诉率:");
+                .append("</font>排名靠后");
 
-        // 截图2
-        worksheet = worksheets.get("管理端-重复投诉率");
-        AsposeUtil.screenshot(localPath + day + "-2.png", worksheet, "A1:G16");
-        cells = worksheet.getCells();
-        stringBuffer.append(cells.get(3, 0).getStringValue())
-                .append("、")
-                .append(cells.get(4, 0).getStringValue())
-                .append("、")
-                .append(cells.get(5, 0).getStringValue())
-                .append("增长较快。客户端-移网网络体验:投诉问题解决满意率:");
+        // 1号忽略重复投诉率
+        if (day.endsWith("01")) {
+            stringBuffer.append("。\n\n客户端-移网网络体验:投诉问题解决满意率:<font color=#FF0000>");
+        } else {
+            stringBuffer.append(";重复投诉率:<font color=#FF0000>");
+            // 截图2 重复投诉率
+            worksheet = worksheets.get("管理端-重复投诉率");
+            AsposeUtil.screenshot(localPath + day + "-2.png", worksheet, "A1:G16");
+            cells = worksheet.getCells();
+            stringBuffer.append(cells.get(3, 0).getStringValue())
+                    .append("、")
+                    .append(cells.get(4, 0).getStringValue())
+                    .append("、")
+                    .append(cells.get(5, 0).getStringValue())
+                    .append("</font>增长较快。\n\n客户端-移网网络体验:投诉问题解决满意率:<font color=#FF0000>");
+        }
 
         // 截图5
         worksheet = worksheets.get("客户端-投诉问题解决满意度");
@@ -190,7 +194,7 @@ public class TslTaskService {
                 .append(cells.get(12, 0).getStringValue())
                 .append("、")
                 .append(cells.get(11, 0).getStringValue())
-                .append("较低,与达标值差距较大;投诉问题解决率:");
+                .append("</font>较低,与达标值差距较大;投诉问题解决率:<font color=#FF0000>");
 
         // 截图6
         worksheet = worksheets.get("客户端-投诉问题解决率");
@@ -201,7 +205,7 @@ public class TslTaskService {
                 .append(cells.get(12, 0).getStringValue())
                 .append("、")
                 .append(cells.get(11, 0).getStringValue())
-                .append("较低,与达标值差距较大;投诉问题响应率:");
+                .append("</font>较低,与达标值差距较大;投诉问题响应率:<font color=#FF0000>");
 
         // 截图7
         worksheet = worksheets.get("客户端-投诉问题响应率");
@@ -212,7 +216,7 @@ public class TslTaskService {
                 .append(cells.get(12, 0).getStringValue())
                 .append("、")
                 .append(cells.get(11, 0).getStringValue())
-                .append("较低,与达标值差距较大。投诉处理时长、超时工单概况:超时工单:");
+                .append("</font>较低,与达标值差距较大。\n\n投诉处理时长、超时工单概况:超时工单:<font color=#FF0000>");
 
         // 截图3 4
         worksheet = worksheets.get("投诉处理时长、超时工单概况");
@@ -224,7 +228,7 @@ public class TslTaskService {
                 .append(cells.get(3, 0).getStringValue())
                 .append("、")
                 .append(cells.get(4, 0).getStringValue())
-                .append("分公司超时工单占比较高,");
+                .append("</font>分公司超时工单占比较高,<font color=#FF0000>");
         list = new ArrayList<>();
         for (int i = 2; i < 14; i++) {
             List<Object> list2 = new ArrayList<>();
@@ -236,7 +240,7 @@ public class TslTaskService {
         stringBuffer.append(list.get(0).get(1).toString()).append("、")
                 .append(list.get(1).get(1).toString()).append("、")
                 .append(list.get(2).get(1).toString())
-                .append("超时工单数量较多。平均处理时长:本月相对较长的地市为");
+                .append("</font>超时工单数量较多。平均处理时长:本月相对较长的地市为<font color=#FF0000>");
 
         list = new ArrayList<>();
         for (int i = 2; i < 14; i++) {
@@ -249,8 +253,8 @@ public class TslTaskService {
         stringBuffer.append(list.get(0).get(1).toString()).append("、")
                 .append(list.get(1).get(1).toString()).append("、")
                 .append(list.get(2).get(1).toString())
-                .append(";与").append(cells.get(1, 7).getStringValue()).append("比")
-                .append(cells.get(2, 6).getStringValue()).append("时长增幅较大。");
+                .append("</font>;与").append(cells.get(1, 7).getStringValue()).append("比<font color=#FF0000>")
+                .append(cells.get(2, 6).getStringValue()).append("</font>时长增幅较大。");
 
         workbook.dispose();
         return stringBuffer.toString();

+ 3 - 3
dingtalk_auto/src/main/resources/application-dev.properties

@@ -3,6 +3,6 @@
 spring.redis.host=127.0.0.1
 spring.redis.port=6379
 
-tslTask.dingTalk.appKey=dingothmdq6opv6hjrm5
-tslTask.dingTalk.appSecret=SeoyAwUnzFIFY4j4CX089HJ0i-pj1BIzByB3AZcnbCQaq94lZvazFpfEGGQwPznc
-tslTask.dingTalk.openConversationId=cidcWmmFwduUTDB3G0vPNOldQ==
+# tslTask.dingTalk.appKey=dingothmdq6opv6hjrm5
+# tslTask.dingTalk.appSecret=SeoyAwUnzFIFY4j4CX089HJ0i-pj1BIzByB3AZcnbCQaq94lZvazFpfEGGQwPznc
+# tslTask.dingTalk.openConversationId=cidcWmmFwduUTDB3G0vPNOldQ==

+ 14 - 3
dingtalk_auto/src/main/resources/application.properties

@@ -8,6 +8,17 @@ tslTask.remoteDir=/data/report_auto/output
 tslTask.remoteHost=133.96.94.105
 tslTask.localDir=./download
 
-tslTask.dingTalk.appKey=dingothmdq6opv6hjrm5
-tslTask.dingTalk.appSecret=SeoyAwUnzFIFY4j4CX089HJ0i-pj1BIzByB3AZcnbCQaq94lZvazFpfEGGQwPznc
-tslTask.dingTalk.openConversationId=cidcWmmFwduUTDB3G0vPNOldQ==
+# 李福全测试群
+# tslTask.dingTalk.appKey=dingothmdq6opv6hjrm5
+# tslTask.dingTalk.appSecret=SeoyAwUnzFIFY4j4CX089HJ0i-pj1BIzByB3AZcnbCQaq94lZvazFpfEGGQwPznc
+# tslTask.dingTalk.openConversationId=cidcWmmFwduUTDB3G0vPNOldQ==
+
+# 王玉龙测试群
+# tslTask.dingTalk.appKey=dingzj6ozs4wfzxthzen
+# tslTask.dingTalk.appSecret=f5tIy_k8jjIPdwBT0Uns9cSEbP47vTZcqmkAwMnH0cD_GM4cu26Zt18vjRbylBbz
+# tslTask.dingTalk.openConversationId=cidlDlGNd6lQ++tNqDI8kX3Xw==
+
+# 河北联通移动网络优化工作群
+tslTask.dingTalk.appKey=dingzj6ozs4wfzxthzen
+tslTask.dingTalk.appSecret=f5tIy_k8jjIPdwBT0Uns9cSEbP47vTZcqmkAwMnH0cD_GM4cu26Zt18vjRbylBbz
+tslTask.dingTalk.openConversationId=cide4nWtey1XgBmy7VFcsu3rA==

+ 37 - 4
dingtalk_auto/src/test/java/com/nokia/dingtalk_auto/service/task/TslTaskServiceTest.java

@@ -1,8 +1,13 @@
 package com.nokia.dingtalk_auto.service.task;
 
+import java.io.File;
+
+import javax.annotation.Resource;
+
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.redis.core.RedisTemplate;
 
 import com.nokia.common.dingtalk.exception.DingTalkApiException;
 import com.nokia.dingtalk_auto.service.TslTaskService;
@@ -12,11 +17,39 @@ public class TslTaskServiceTest {
     @Autowired
     private TslTaskService tslTaskService;
 
+    @Resource
+    private RedisTemplate<String, Object> redisTemplate;
+
     @Test
     void test() throws Exception, DingTalkApiException {
-        // tslTaskService.download("20230426");
-        // System.out.println(tslTaskService.getFilesToSend("20230426"));
-        // tslTaskService.sendDingTalkMsg("20230426");
-        tslTaskService.runTask("20230425");
+        test2("20230501");
+    }
+
+    void test3(String day) throws Exception {
+        // 1. 下载文件
+        tslTaskService.download(day);
+        // 2. 生成要发送的内容
+        String message = tslTaskService.getFilesToSend(day);
+        System.out.println(message);
+    }
+
+    void test2(String day) throws Exception {
+        // 1. 下载文件
+        tslTaskService.download(day);
+        // 2. 生成要发送的内容
+        String message = tslTaskService.getFilesToSend(day);
+        System.out.println(message);
+        if (!tslTaskService.isDingTalkUtilInited()) {
+            tslTaskService.initDingTalkUtil();
+        }
+        String localPath = "./download/" + day;
+        File[] files = new File(localPath).listFiles();
+        for (File file : files) {
+            if (file.getName().toLowerCase().endsWith(".xlsx")) {
+                String mediaId = tslTaskService.getDingTalkUtil().upload(file.getAbsolutePath(), "file");
+                tslTaskService.getDingTalkUtil().sendFileMsgWithRobot(mediaId, file.getName());
+            }
+        }
+        tslTaskService.getDingTalkUtil().sendMarkdownMsgWithRobot(message);
     }
 }

+ 3 - 3
doc/开发文档/tsl_data/部署环境接口测试.md

@@ -5,10 +5,10 @@ nohup java -jar tsl_data-1.0-exec.jar >output.log 2>&1 &
 ## 数据手动入库
 
 ```http
-POST http://192.168.10.7:29100/tsl/task/warhouse HTTP/1.1
+POST http://192.168.10.7:29100/tsl/task/warahouse HTTP/1.1
 Content-Type:application/json
 
-20230425
+20230427
 ```
 
 ## 手动生成报表
@@ -17,7 +17,7 @@ Content-Type:application/json
 POST http://192.168.10.7:29100/tsl/task/report/generate HTTP/1.1
 Content-Type:application/json
 
-20230425
+20230501
 ```
 
 ## 定时任务

+ 2 - 0
doc/开发文档/开发文档.md

@@ -54,3 +54,5 @@ Content-Type:application/json
 #### conversationId
 
 唯一标识一个群
+
+<https://open-dev.dingtalk.com/apiExplorer?spm=ding_open_doc.document.0.0.afb839b7W85NCP#/jsapi?api=biz.chat.chooseConversationByCorpId>

+ 2 - 3
tsl_data/pom.xml

@@ -13,7 +13,7 @@
 
     <groupId>com.nokia</groupId>
     <artifactId>tsl_data</artifactId>
-    <version>1.0</version>
+    <version>1.1</version>
 
     <packaging>jar</packaging>
 
@@ -27,8 +27,7 @@
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <!-- 指定maven-compiler-plugin的配置属性开始 -->
         <!-- 编译时的编码 -->
-        <maven.compiler.encoding>
-            UTF-8</maven.compiler.encoding>
+        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
         <!-- 指定编译的版本 -->
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>

+ 5 - 0
tsl_data/src/main/java/com/nokia/common/excel/entity/AlignmentEnum.java

@@ -0,0 +1,5 @@
+package com.nokia.common.excel.entity;
+
+public enum AlignmentEnum {
+    CENTER, LEFT, RIGHT;
+}

+ 58 - 0
tsl_data/src/main/java/com/nokia/common/excel/entity/CellInfo.java

@@ -0,0 +1,58 @@
+package com.nokia.common.excel.entity;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.GraphicsEnvironment;
+
+import lombok.Data;
+
+/**
+ * excel一个单元格要截图的关键信息
+ * 
+ * 一个单元格可能是一个合并单元格
+ * 
+ * 包含字体(字体类型,字体大小)、背景色、字体颜色、是否包含边框等信息、单元格的大小和位置信息
+ */
+@Data
+public class CellInfo {
+    // x,y是单元格的左上角坐标
+    private int x;
+    private int y;
+    // width,height表示单元格的大小
+    private int width;
+    private int height;
+    // 背景色 默认为空
+    private Color backgroundColor = null;
+    // 是否存在边框 默认存在宽度1像素的黑色边框
+    private boolean isBordered = true;
+    private int borderWidth = 1;
+    private Color borderColor = Color.BLACK;
+    // 单元格值的字符串表示--时间按统一格式,其他数字按照excel格式
+    private String text;
+    // 字体 默认字体 微软雅黑 普通 11号字
+    private Font font = Font.decode("微软雅黑-PLAIN-11");
+    // 是否加粗
+    private boolean isBold = false;
+    // 文字颜色
+    private Color textColor = Color.BLACK;
+    // 文字对齐方向
+    private AlignmentEnum alignment = AlignmentEnum.CENTER;
+
+    /**
+     * 通过字符串配置字体
+     * 
+     * @param fontName 空格或-间隔的字符串 fontname-style-pointsize fontname pointsize
+     *                 fontname style
+     */
+    public void setFont(String fontName) {
+        String familyName = fontName.trim().split("-")[0].split(" ")[0];
+        // 所有允许的familyName
+        String[] availableFontFamilyNames = GraphicsEnvironment.getLocalGraphicsEnvironment()
+                .getAvailableFontFamilyNames();
+        for (int i = 0; i < availableFontFamilyNames.length; i++) {
+            if (availableFontFamilyNames[i].equalsIgnoreCase(familyName)) {
+                font = Font.decode(fontName);
+            }
+        }
+    }
+}

+ 109 - 0
tsl_data/src/main/java/com/nokia/common/excel/entity/CellRect.java

@@ -0,0 +1,109 @@
+package com.nokia.common.excel.entity;
+
+import java.awt.Color;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import lombok.Data;
+
+/**
+ * Excel上一个矩形区域的全部信息
+ */
+@Data
+public class CellRect {
+
+    // 总宽度
+    private int totalWidth;
+    // 总高度
+    private int totalHeight;
+    // 边框像素数
+    private int margin = 5;
+
+    // excel上的起止位置信息
+    private int startCol;
+    private int endCol;
+    private int startRow;
+    private int endRow;
+
+    // 背景色,默认白色
+    private Color backgroundColor = Color.WHITE;
+    // 统一字体
+    private String fontFamily = null;
+    private int fontSizeMin = 11;
+
+    // 范围内的全部单元格信息CellInfo
+    private List<CellInfo> cellInfos;
+
+    // 识别Rect区域的正则表达式
+    private static final Pattern PATTERN = Pattern.compile("^[A-Z]+\\d+:[A-Z]+\\d+$");
+
+    public static CellRect parse(String rect) {
+        boolean matches = PATTERN.matcher(rect).matches();
+        if (!matches) {
+            throw new RuntimeException(String.format("输入的%s格式错误,正确的示例A1:AA33,表示第0-32行,0-26列", rect));
+        }
+        String[] split = rect.split(":");
+        CellRect cellRect = new CellRect();
+        cellRect.setStartCol(getCol(split[0]));
+        cellRect.setStartRow(getRow(split[0]));
+        cellRect.setEndCol(getCol(split[1]));
+        cellRect.setEndRow(getRow(split[1]));
+        return cellRect;
+    }
+
+    /**
+     * 26进制字符串转10进制数
+     * 注意,AA = 1*26+1 所以是无0的
+     * 
+     * @param cell
+     * @return
+     */
+    private static int getCol(String cell) {
+        int result = 0;
+        // 位数
+        for (int i = 0; i < cell.length(); i++) {
+            int a = cell.charAt(i);
+            // 不是A-Z的字符直接忽略
+            if (a > 90 || a < 65)
+                continue;
+            // 发现1个A-Z的字符 位数+1
+            result = result * 26 + a - 64;
+        }
+        return result - 1;
+    }
+
+    /**
+     * 字符串转10进制数
+     * 
+     * @param cell
+     * @return
+     */
+    private static int getRow(String cell) {
+        int result = 0;
+        for (int i = 0; i < cell.length(); i++) {
+            int c = cell.charAt(i);
+            // 不是0-9
+            if (c < 48 || c > 57)
+                continue;
+            result = result * 10 + c - 48;
+        }
+        return result - 1;
+    }
+
+    /**
+     * 从列号转为列名 如26转为AA
+     * 
+     * @param colNum
+     * @return
+     */
+    public static String getColumnName(int colNum) {
+        StringBuffer stringBuffer = new StringBuffer();
+        int realCol = colNum + 1;
+        while (realCol > 26) {
+            stringBuffer.append((char) (realCol / 26 + 64));
+            realCol %= 26;
+        }
+        stringBuffer.append((char) (realCol + 64));
+        return stringBuffer.toString();
+    }
+}

+ 33 - 0
tsl_data/src/main/java/com/nokia/common/excel/entity/Gradient.java

@@ -0,0 +1,33 @@
+package com.nokia.common.excel.entity;
+
+import java.awt.Color;
+
+/**
+ * 渐进色
+ */
+public interface Gradient {
+    /**
+     * 针对执行的数值输入d,计算对应的颜色
+     * 
+     * @param d
+     * @return
+     */
+    Color getColor(double d);
+
+    public static Color parse(String ARGBHex) {
+        int a = Integer.parseInt(ARGBHex.substring(0, 2), 16);
+        int r = Integer.parseInt(ARGBHex.substring(2, 4), 16);
+        int g = Integer.parseInt(ARGBHex.substring(4, 6), 16);
+        int b = Integer.parseInt(ARGBHex.substring(6, 8), 16);
+        return new Color(r, g, b, a);
+    }
+
+    public static Color parse(byte[] argb) {
+        // 需要把byte(无符号整数0-255)转成int
+        return new Color(argb[1] & 0xff, argb[2] & 0xff, argb[3] & 0xff, argb[0] & 0xff);
+    }
+
+    public static Color parse(short[] rgb) {
+        return new Color(rgb[0], rgb[1], rgb[2]);
+    }
+}

+ 48 - 0
tsl_data/src/main/java/com/nokia/common/excel/entity/ThreeColorGradient.java

@@ -0,0 +1,48 @@
+package com.nokia.common.excel.entity;
+
+import java.awt.Color;
+
+import lombok.Data;
+
+/**
+ * 三色渐变色
+ */
+@Data
+public class ThreeColorGradient {
+
+    // 三种颜色 默认三色 FF63BE7B--FFFFEB84--FFF8696B 红/黄/绿
+    private Color startColor = new Color(99, 190, 123, 255);
+    private Color middleColor = new Color(255, 235, 132, 255);
+    private Color endColor = new Color(248, 105, 107, 255);
+
+    // 开始数、中位数、截止数
+    private double startValue;
+    private double middleValue;
+    private double endValue;
+
+    /**
+     * 根据输入值返回一个颜色
+     * 
+     * @param d
+     * @return
+     */
+    public Color getColor(double d) {
+        if ((d >= startValue && d < middleValue) || (d <= startValue && d > middleValue)) {
+            // 值在中位数和satartValue之间,计算起始颜色和中间颜色之间的渐进色
+            double t = middleValue == startValue ? 0 : (d - startValue) / (middleValue - startValue);
+            double red = (1 - t) * startColor.getRed() + t * middleColor.getRed();
+            double green = (1 - t) * startColor.getGreen() + t * middleColor.getGreen();
+            double blue = (1 - t) * startColor.getBlue() + t * middleColor.getBlue();
+            double opacity = (1 - t) * startColor.getAlpha() + t * middleColor.getAlpha();
+            return new Color((int) red, (int) green, (int) blue, (int) opacity);
+        } else {
+            // 值在中位数和endValue之间,计算中间亚瑟和结束颜色之间的渐进色
+            double t = middleValue == endValue ? 0 : (d - endValue) / (middleValue - endValue);
+            double red = (1 - t) * endColor.getRed() + t * middleColor.getRed();
+            double green = (1 - t) * endColor.getGreen() + t * middleColor.getGreen();
+            double blue = (1 - t) * endColor.getBlue() + t * middleColor.getBlue();
+            double opacity = (1 - t) * endColor.getAlpha() + t * middleColor.getAlpha();
+            return new Color((int) red, (int) green, (int) blue, (int) opacity);
+        }
+    }
+}

+ 27 - 0
tsl_data/src/main/java/com/nokia/common/excel/entity/TwoColorGradient.java

@@ -0,0 +1,27 @@
+package com.nokia.common.excel.entity;
+
+import java.awt.Color;
+
+/**
+ * 双色渐进色
+ */
+public class TwoColorGradient implements Gradient {
+
+    // 两种颜色 默认双色 FF63BE7B--FFF8696B 红/绿
+    private Color startColor = new Color(99, 190, 123, 255);
+    private Color endColor = new Color(248, 105, 107, 255);
+
+    // 开始数、截止数
+    private double startValue;
+    private double endValue;
+
+    @Override
+    public Color getColor(double d) {
+        double t = (d - startValue) / (endValue - startValue);
+        double red = (1 - t) * startColor.getRed() + t * endColor.getRed();
+        double green = (1 - t) * startColor.getGreen() + t * endColor.getGreen();
+        double blue = (1 - t) * startColor.getBlue() + t * endColor.getBlue();
+        double opacity = (1 - t) * startColor.getAlpha() + t * endColor.getAlpha();
+        return new Color((int) red, (int) green, (int) blue, (int) opacity);
+    }
+}

+ 460 - 0
tsl_data/src/main/java/com/nokia/common/excel/poi/PoiUtil.java

@@ -0,0 +1,460 @@
+package com.nokia.common.excel.poi;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.poi.hssf.usermodel.HSSFFont;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellValue;
+import org.apache.poi.ss.usermodel.Color;
+import org.apache.poi.ss.usermodel.ColorScaleFormatting;
+import org.apache.poi.ss.usermodel.ConditionalFormatting;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.SheetConditionalFormatting;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFColor;
+import org.apache.poi.xssf.usermodel.XSSFFont;
+import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import com.nokia.common.excel.entity.CellInfo;
+import com.nokia.common.excel.entity.CellRect;
+import com.nokia.common.excel.entity.Gradient;
+import com.nokia.common.excel.entity.ThreeColorGradient;
+
+/**
+ * POI的封装
+ */
+public class PoiUtil {
+
+    private static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    private static ThreeColorGradient threeColorGradient;
+
+    /**
+     * 截图rect指定的区域
+     * 
+     * @param sheet
+     * @param rect
+     * @return
+     */
+    public static BufferedImage screenShot(Sheet sheet, String rect) {
+        return screenShot(sheet, CellRect.parse(rect));
+    }
+
+    /**
+     * 使用指定的字体截图
+     * 
+     * @param sheet
+     * @param rect
+     * @param fontFamily
+     * @return
+     */
+    public static BufferedImage screenShot(Sheet sheet, String rect, String fontFamily) {
+        CellRect cellRect = CellRect.parse(rect);
+        cellRect.setFontFamily(fontFamily);
+        return screenShot(sheet, cellRect);
+    }
+
+    /**
+     * 截取由startCol、endCol、startRow、endRow指定的Sheet中矩形区域所包含的单元格到BufferedImage
+     * 
+     * @param sheet
+     * @param startCol
+     * @param endCol
+     * @param startRow
+     * @param endRow
+     * @return
+     */
+    public static BufferedImage screenShot(Sheet sheet, int startCol, int endCol, int startRow, int endRow) {
+        CellRect cellRect = new CellRect();
+        cellRect.setStartCol(startCol);
+        cellRect.setEndCol(endCol);
+        cellRect.setStartRow(startRow);
+        cellRect.setEndRow(endRow);
+        return screenShot(sheet, cellRect);
+    }
+
+    /**
+     * 截取由CellRect实例指定的Sheet中矩形区域所包含的单元格到BufferedImage
+     * 
+     * @param sheet
+     * @param cellRect
+     * @return java.awt.image.BufferedImage
+     */
+    public static BufferedImage screenShot(Sheet sheet, CellRect cellRect) {
+        // 从excel读取截图范围内的信息
+        readCellRect(sheet, cellRect);
+        int imageWidth = cellRect.getTotalWidth();
+        int imageHeight = cellRect.getTotalHeight();
+        // 创建image
+        BufferedImage image = new BufferedImage(imageWidth, imageHeight,
+                BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2d = image.createGraphics();
+        // 平滑字体
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        // 图片背景色
+        g2d.setColor(cellRect.getBackgroundColor());
+        g2d.fillRect(0, 0, imageWidth, imageHeight);
+        // 遍历CellInfo,将单元格输出到图片
+        for (CellInfo cellInfo : cellRect.getCellInfos()) {
+            // 背景色
+            if (cellInfo.getBackgroundColor() != null) {
+                g2d.setColor(cellInfo.getBackgroundColor());
+                g2d.fillRect(cellInfo.getX(), cellInfo.getY(), cellInfo.getWidth(), cellInfo.getHeight());
+            }
+            // 边框
+            if (cellInfo.isBordered()) {
+                g2d.setColor(cellInfo.getBorderColor());
+                g2d.setStroke(new BasicStroke(cellInfo.getBorderWidth()));
+                g2d.drawRect(cellInfo.getX(), cellInfo.getY(), cellInfo.getWidth(), cellInfo.getHeight());
+            }
+            // 写入值
+            g2d.setColor(cellInfo.getTextColor());
+            java.awt.Font font = cellInfo.getFont();
+            String text = cellInfo.getText();
+            int stringWidth = g2d.getFontMetrics(font).stringWidth(text);
+            g2d.setFont(font);
+            g2d.drawString(text, cellInfo.getX() + (cellInfo.getWidth() - stringWidth) / 2,
+                    cellInfo.getY() + (cellInfo.getHeight() - font.getSize()) / 2 + font.getSize());
+        }
+        g2d.dispose();
+        return image;
+    }
+
+    /**
+     * 读取sheet中由cellRect指定的矩形区域内容,更新cellRect对象
+     * 
+     * @param sheet
+     * @param cellRect
+     */
+    public static void readCellRect(Sheet sheet, CellRect cellRect) {
+        List<CellInfo> cellInfos = new ArrayList<>();
+        cellRect.setCellInfos(cellInfos);
+        // 种树原理,树比树空多1, 0-34有35个树空,需要种36棵树
+        int[] colPixPos = new int[cellRect.getEndCol() - cellRect.getStartCol() + 2];
+        int[] rowPixPos = new int[cellRect.getEndRow() - cellRect.getStartRow() + 2];
+        colPixPos[0] = cellRect.getMargin();
+        rowPixPos[0] = cellRect.getMargin();
+        for (int i = cellRect.getStartCol(); i <= cellRect.getEndCol(); i++) {
+            colPixPos[i + 1 - cellRect.getStartCol()] = (int) sheet.getColumnWidthInPixels(i) * 115 / 100
+                    + colPixPos[i - cellRect.getStartCol()];
+        }
+        Row row;
+        Cell cell;
+        CellStyle cellStyle;
+        // 获取sheet中的合并单元格
+        List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
+        // 获取sheet中的条件背景色
+        Map<String, ThreeColorGradient> conditionalBackgroundColor = getConditionalBackgroundColor(sheet);
+        for (int i = cellRect.getStartRow(); i <= cellRect.getEndRow(); i++) {
+            row = sheet.getRow(i);
+            if (row == null) {
+                throw new RuntimeException(String.format("readCellRectd调用出错, Row-%s 为空", i));
+            }
+            rowPixPos[i + 1 - cellRect.getStartRow()] = (int) row.getHeightInPoints() * 96 / 72
+                    + rowPixPos[i - cellRect.getStartRow()];
+            for (int j = cellRect.getStartCol(); j <= cellRect.getEndCol(); j++) {
+                // 行号i 列号j
+                // 判断是否为合并单元格
+                int[] inMerged = isInMerged(i, j, mergedRegions);
+                if (inMerged[0] == 0 && inMerged[1] == 0) {
+                    // 是合并单元格且不是第一个单元格,可以忽略,跳过单元格
+                    continue;
+                }
+                cell = row.getCell(j);
+                // 其他情况都不能忽略
+                CellInfo cellInfo = new CellInfo();
+                cellInfos.add(cellInfo);
+                // 截图的时候应该按照原格式显示,比如保留的小数位
+                cellInfo.setText(readCellValueAsString(cell));
+                // 判断单元格是否包含条件背景色
+                threeColorGradient = conditionalBackgroundColor.get(i + "-" + j);
+                if (threeColorGradient != null) {
+                    cellInfo.setBackgroundColor(threeColorGradient.getColor(row.getCell(j).getNumericCellValue()));
+                }
+                // 检查背景色
+                cellStyle = cell.getCellStyle();
+                // excel填充一般都是前景色
+                Color fillForegroundColorColor = cellStyle.getFillForegroundColorColor();
+                if (fillForegroundColorColor != null) {
+                    cellInfo.setBackgroundColor(colorCodec(fillForegroundColorColor));
+                }
+                // 是否有边框,只检查左边框,只要不是NONE,截图就设置为全边框
+                if (cellStyle.getBorderLeft().equals(BorderStyle.NONE)) {
+                    cellInfo.setBordered(false);
+                }
+                // 字体相关
+                Font fontAt = sheet.getWorkbook().getFontAt(cellStyle.getFontIndex());
+                // 字体颜色
+                cellInfo.setTextColor(fontColorCodec(fontAt, sheet.getWorkbook()));
+                StringBuffer stringBuffer = new StringBuffer();
+                // fontfamily
+                if (cellRect.getFontFamily() != null) {
+                    stringBuffer.append(cellRect.getFontFamily());
+                } else {
+                    stringBuffer.append(fontAt.getFontName());
+                }
+                // 加粗还是正常
+                if (fontAt.getBold()) {
+                    stringBuffer.append("-BOLD-");
+                } else {
+                    stringBuffer.append("-PLAIN-");
+                }
+                // 字体大小
+                int fontSize = fontAt.getFontHeightInPoints();
+                if (fontSize < cellRect.getFontSizeMin()) {
+                    stringBuffer.append(cellRect.getFontSizeMin());
+                } else {
+                    stringBuffer.append(fontSize);
+                }
+                cellInfo.setFont(stringBuffer.toString());
+                // 坐标
+                cellInfo.setX(colPixPos[j - cellRect.getStartCol()]);
+                cellInfo.setY(rowPixPos[i - cellRect.getStartRow()]);
+                // 计算单元格宽度和高度
+                cellInfo.setWidth(colPixPos[j + 1 - cellRect.getStartCol()] - colPixPos[j - cellRect.getStartCol()]);
+                cellInfo.setHeight(rowPixPos[i + 1 - cellRect.getStartRow()] - rowPixPos[i - cellRect.getStartRow()]);
+                // 合并单元格需要重新计算宽度和高度
+                if (inMerged[0] != -1 && inMerged[1] != -1) {
+                    cellInfo.setWidth(colPixPos[inMerged[1] + 1 - cellRect.getStartCol()]
+                            - colPixPos[j - cellRect.getStartCol()]);
+                    cellInfo.setHeight(rowPixPos[inMerged[0] + 1 - cellRect.getStartRow()]
+                            - rowPixPos[i - cellRect.getStartRow()]);
+                }
+            }
+        }
+        cellRect.setTotalWidth(colPixPos[colPixPos.length - 1] + cellRect.getMargin() + 1);
+        cellRect.setTotalHeight(rowPixPos[rowPixPos.length - 1] + cellRect.getMargin() + 1);
+    }
+
+    private static java.awt.Color fontColorCodec(Font fontAt, Workbook workbook) {
+        if (fontAt instanceof XSSFFont) {
+            XSSFColor xssfColor = ((XSSFFont) fontAt).getXSSFColor();
+            if (xssfColor != null) {
+                return Gradient.parse(xssfColor.getARGB());
+            }
+            return java.awt.Color.BLACK;
+        } else if (fontAt instanceof HSSFFont) {
+            // 未测试
+            return Gradient.parse(((HSSFFont) fontAt).getHSSFColor((HSSFWorkbook) workbook).getTriplet());
+        }
+        throw new RuntimeException("excel文件格式错误:既不是07格式也不是03格式");
+    }
+
+    /**
+     * 颜色转化
+     * 
+     * @param fillForegroundColorColor
+     * @return
+     */
+    private static java.awt.Color colorCodec(Color fillForegroundColorColor) {
+        try {
+            return Gradient.parse(XSSFColor.toXSSFColor(fillForegroundColorColor).getARGB());
+        } catch (IllegalArgumentException e) {
+            return Gradient.parse(HSSFColor.toHSSFColor(fillForegroundColorColor).getTriplet());
+        }
+    }
+
+    /**
+     * 读取单元格到字符串格式,按excel可见格式格式化字符串
+     * 
+     * @param cell
+     * @return null 表示有问题
+     */
+    public static String readCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        } else {
+            switch (cell.getCellType()) {
+                case STRING:
+                    return cell.getStringCellValue();
+                case NUMERIC: // 数字
+                    if (DateUtil.isCellDateFormatted(cell)) {
+                        // 日期格式,采用固定方式返回
+                        return dateFormat.format(cell.getDateCellValue());
+                    }
+                    // 数字格式,使用excel的方式格式化 示例:百分比 0.00% 保留1位小数 0.0
+                    String dataFormatString = cell.getCellStyle().getDataFormatString();
+                    if (dataFormatString.equalsIgnoreCase("general")) {
+                        return new DecimalFormat().format(cell.getNumericCellValue());
+                    } else {
+                        return new DecimalFormat(dataFormatString).format(cell.getNumericCellValue());
+                    }
+                case FORMULA: // 公式
+                    Workbook workbook = cell.getSheet().getWorkbook();
+                    FormulaEvaluator evaluator;
+                    CellValue cellValue;
+                    if (workbook instanceof HSSFWorkbook) {
+                        evaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);
+                        cellValue = evaluator.evaluate(cell);
+                    } else if (workbook instanceof XSSFWorkbook) {
+                        evaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook);
+                        cellValue = evaluator.evaluate(cell);
+                    } else {
+                        throw new RuntimeException(String.format("单元格%d行%d列FormulaEvaluator创建失败, 无法识别workbook的类型",
+                                cell.getRowIndex(), cell.getColumnIndex()));
+                    }
+                    switch (cellValue.getCellType()) {
+                        case STRING:
+                            return cellValue.getStringValue();
+                        case NUMERIC:
+                            if (DateUtil.isCellDateFormatted(cell)) {
+                                // 日期格式,采用固定方式返回
+                                return dateFormat.format(cell.getDateCellValue());
+                            }
+                            // 数字格式,使用excel的方式格式化 示例:百分比 0.00% 保留1位小数 0.0
+                            String dataFormatString2 = cell.getCellStyle().getDataFormatString();
+                            if (dataFormatString2.equalsIgnoreCase("general")) {
+                                return new DecimalFormat().format(cell.getNumericCellValue());
+                            } else {
+                                return new DecimalFormat(dataFormatString2).format(cell.getNumericCellValue());
+                            }
+                        default:
+                            return cellValue.formatAsString();
+                    }
+                case BLANK:
+                    return "";
+                case BOOLEAN:
+                    return String.valueOf(cell.getBooleanCellValue());
+                case ERROR:
+                    throw new RuntimeException(
+                            String.format("单元格%d行%d列上有错误", cell.getRowIndex(), cell.getColumnIndex()));
+                case _NONE:
+                    return null;
+                default:
+                    return null;
+            }
+        }
+    }
+
+    /**
+     * 判断单元格是否为合并单元格
+     * 
+     * @param row
+     * @param col
+     * @param mergedRegions
+     * @return {-1. -1} 表示不是合并单元格, 是合并单元格且是第一个单元格返回{lastRow, lastCol},
+     *         是合并且不是第一个单元格返回{0, 0}
+     */
+    public static int[] isInMerged(int row, int col, List<CellRangeAddress> mergedRegions) {
+        int[] isInMergedStatus = { -1, -1 };
+        for (CellRangeAddress cellRangeAddress : mergedRegions) {
+            if (row == cellRangeAddress.getFirstRow() && col == cellRangeAddress.getFirstColumn()) {
+                isInMergedStatus[0] = cellRangeAddress.getLastRow();
+                isInMergedStatus[1] = cellRangeAddress.getLastColumn();
+                return isInMergedStatus;
+            }
+            if (row >= cellRangeAddress.getFirstRow() && row <= cellRangeAddress.getLastRow()) {
+                if (col >= cellRangeAddress.getFirstColumn() && col <= cellRangeAddress.getLastColumn()) {
+                    isInMergedStatus[0] = 0;
+                    isInMergedStatus[1] = 0;
+                    return isInMergedStatus;
+                }
+            }
+        }
+        return isInMergedStatus;
+    }
+
+    /**
+     * 读取sheet中的条件背景颜色
+     * 当前仅支持三色渐进色条件背景
+     * 
+     * @param sheet
+     * @return
+     */
+    private static Map<String, ThreeColorGradient> getConditionalBackgroundColor(Sheet sheet) {
+        SheetConditionalFormatting sheetConditionalFormatting = sheet.getSheetConditionalFormatting();
+        int numConditionalFormattings = sheetConditionalFormatting.getNumConditionalFormattings();
+        Row row;
+        Map<String, ThreeColorGradient> map = new HashMap<>();
+        // 遍历所有条件格式
+        for (int i = 0; i < numConditionalFormattings; i++) {
+            ConditionalFormatting conditionalFormattingAt = sheetConditionalFormatting.getConditionalFormattingAt(i);
+            CellRangeAddress[] formattingRanges = conditionalFormattingAt.getFormattingRanges();
+            // 遍历1项条件格式的所有区域
+            for (CellRangeAddress cellRangeAddress : formattingRanges) {
+                int firstColumn = cellRangeAddress.getFirstColumn();
+                int firstRow = cellRangeAddress.getFirstRow();
+                int lastColumn = cellRangeAddress.getLastColumn();
+                int lastRow = cellRangeAddress.getLastRow();
+                List<Double> values = new ArrayList<>();
+                ThreeColorGradient threeColorGradient = new ThreeColorGradient();
+                // 遍历条件格式包含的所有单元格,获取取值
+                for (int j = firstRow; j <= lastRow; j++) {
+                    row = sheet.getRow(j);
+                    for (int k = firstColumn; k <= lastColumn; k++) {
+                        map.put(j + "-" + k, threeColorGradient);
+                        values.add(row.getCell(k).getNumericCellValue());
+                    }
+                }
+                // 排序
+                values.sort((o1, o2) -> Double.compare(o1, o2));
+                threeColorGradient.setStartValue(values.get(0));
+                threeColorGradient.setEndValue(values.get(values.size() - 1));
+                threeColorGradient.setMiddleValue(values.get(values.size() / 2));
+                // 渐进色颜色
+                for (int j = 0; j < conditionalFormattingAt.getNumberOfRules(); j++) {
+                    ColorScaleFormatting colorScaleFormatting = conditionalFormattingAt.getRule(j)
+                            .getColorScaleFormatting();
+                    if (colorScaleFormatting != null) {
+                        Color[] colors = colorScaleFormatting.getColors();
+                        threeColorGradient.setStartColor(colorCodec(colors[0]));
+                        threeColorGradient.setMiddleColor(colorCodec(colors[1]));
+                        threeColorGradient.setEndColor(colorCodec(colors[2]));
+                    }
+                }
+            }
+        }
+        return map;
+    }
+
+    // 复制单元格内容
+    public static void copyCellValue(Cell sourceCell, Cell targetCell) {
+        if (sourceCell == null) {
+            targetCell.setCellValue("");
+        } else {
+            switch (sourceCell.getCellType()) {
+                case BLANK:
+                    targetCell.setCellValue("");
+                    break;
+                case BOOLEAN:
+                    targetCell.setCellValue(sourceCell.getBooleanCellValue());
+                    break;
+                case ERROR:
+                    targetCell.setCellErrorValue(sourceCell.getErrorCellValue());
+                    break;
+                case FORMULA:
+                    targetCell.setCellFormula(sourceCell.getCellFormula());
+                    break;
+                case NUMERIC:
+                    targetCell.setCellValue(sourceCell.getNumericCellValue());
+                    break;
+                case STRING:
+                    targetCell.setCellValue(sourceCell.getStringCellValue());
+                    break;
+                default:
+                    targetCell.setCellValue("");
+                    break;
+            }
+        }
+    }
+}

+ 6 - 0
tsl_data/src/main/java/com/nokia/tsl_data/dao/TslDao.java

@@ -43,6 +43,12 @@ public interface TslDao {
      */
     List<Map<String, Object>> selectOldTsDurationForMonth(String monthId);
 
+    /**
+     * 
+     * @param monthId
+     */
+    void insertOldTsDurationForMonth(String monthId);
+
     /**
      * 存入历史处理时长数据
      * 

+ 42 - 28
tsl_data/src/main/java/com/nokia/tsl_data/service/TslDataService.java

@@ -1,6 +1,5 @@
 package com.nokia.tsl_data.service;
 
-import java.math.BigDecimal;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
@@ -94,7 +93,7 @@ public class TslDataService {
             // 达标值
             list1.add(2, satisfiedCompliance);
             // 差距
-            list1.add(3, ((BigDecimal) list1.get(1)).doubleValue() - (double) list1.get(2));
+            list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
         }
         // 逆序排序
         list0.sort((o1, o2) -> Double.compare((double) o2.get(3), (double) o1.get(3)));
@@ -108,7 +107,7 @@ public class TslDataService {
         // 达标值
         list1.add(2, satisfiedCompliance);
         // 差距
-        list1.add(3, ((BigDecimal) list1.get(1)).doubleValue() - (double) list1.get(2));
+        list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
 
         // 解决率
         list0 = new ArrayList<>();
@@ -124,7 +123,7 @@ public class TslDataService {
             // 达标值
             list1.add(2, resolutionCompliance);
             // 差距
-            list1.add(3, ((BigDecimal) list1.get(1)).doubleValue() - (double) list1.get(2));
+            list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
         }
         // 逆序排序
         list0.sort((o1, o2) -> Double.compare((double) o2.get(3), (double) o1.get(3)));
@@ -138,7 +137,7 @@ public class TslDataService {
         // 达标值
         list1.add(2, resolutionCompliance);
         // 差距
-        list1.add(3, ((BigDecimal) list1.get(1)).doubleValue() - (double) list1.get(2));
+        list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
 
         // 响应率
         list0 = new ArrayList<>();
@@ -153,7 +152,7 @@ public class TslDataService {
             // 达标值
             list1.add(2, responseCompliance);
             // 差距
-            list1.add(3, ((BigDecimal) list1.get(1)).doubleValue() - (double) list1.get(2));
+            list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
         }
         // 逆序排序
         list0.sort((o1, o2) -> Double.compare((double) o2.get(3), (double) o1.get(3)));
@@ -165,7 +164,7 @@ public class TslDataService {
         // 达标值
         list1.add(2, responseCompliance);
         // 差距
-        list1.add(3, ((BigDecimal) list1.get(1)).doubleValue() - (double) list1.get(2));
+        list1.add(3, ((double) list1.get(1)) - (double) list1.get(2));
 
         return result;
     }
@@ -185,20 +184,26 @@ public class TslDataService {
         List<List<Object>> result = new ArrayList<>();
         // 各地市数据
         for (String area : areas) {
+            // 地市 工单数 超时工单数 超时工单占比
             List<Object> list = new ArrayList<>(4);
             result.add(list);
-            // 地市
+
             list.add(0, area);
-            // 工单数
-            list.add(1, dayMap.get(area).get("total_num"));
-            // 超时工单数
-            list.add(2, dayMap.get(area).get("timeout_num"));
-            // 超时工单占比
-            list.add(3, dayMap.get(area).get("timeout_ratio"));
+            // 获取该地市的数据--可能为null
+            Map<String, Object> map = dayMap.get(area);
+            if (map == null) {
+                list.add(1, 0L);
+                list.add(2, 0L);
+                list.add(3, 0.0);
+            } else {
+                list.add(1, map.get("total_num"));
+                list.add(2, map.get("timeout_num"));
+                list.add(3, map.get("timeout_ratio"));
+            }
         }
         // 逆序排序
         result.sort((o1, o2) -> {
-            return ((BigDecimal) o2.get(3)).compareTo(((BigDecimal) o1.get(3)));
+            return Double.compare((double) o2.get(3), (double) o1.get(3));
         });
         // 全省数据
         List<Object> list = new ArrayList<>();
@@ -231,6 +236,11 @@ public class TslDataService {
         calendar.add(Calendar.MONTH, -1);
         String oldMonthId = new SimpleDateFormat("yyyyMM").format(calendar.getTime());
         List<Map<String, Object>> oldData = tslDao.selectOldTsDurationForMonth(oldMonthId);
+        // 如果上月的平均处理时长数据不存在,需要插入
+        if (oldData.size() == 0) {
+            tslDao.insertOldTsDurationForMonth(oldMonthId);
+            oldData = tslDao.selectOldTsDurationForMonth(oldMonthId);
+        }
         List<Map<String, Object>> dayData = tslDao.selectTsDurationForDay(day);
         // 转化格式方便读取
         Map<String, Map<String, Object>> dayMap = new HashMap<>();
@@ -244,16 +254,19 @@ public class TslDataService {
         List<List<Object>> result = new ArrayList<>();
         // 各地市数据
         for (String area : areas) {
-            List<Object> list = new ArrayList<>();
+            // 地市 上月平均处理时长 当月平均处理时长 涨幅
+            List<Object> list = new ArrayList<>(4);
             result.add(list);
-            // 地市
             list.add(0, area);
-            // 上月平均处理时长
             list.add(1, preMap.get(area).get("avg_duration"));
-            // 当月平均处理时长
-            list.add(2, dayMap.get(area).get("avg_duration"));
-            // 涨幅
-            list.add(3, ((double) list.get(2)) - (double) list.get(1));
+            Map<String, Object> map = dayMap.get(area);
+            if (map == null) {
+                list.add(2, 0.0);
+                list.add(3, 0.0);
+            } else {
+                list.add(2, dayMap.get(area).get("avg_duration"));
+                list.add(3, ((double) list.get(2)) - (double) list.get(1));
+            }
         }
         // 逆序排序
         result.sort((o1, o2) -> Double.compare((double) o2.get(3), (double) o1.get(3)));
@@ -296,12 +309,12 @@ public class TslDataService {
             list.add(3, preMap.get(area).get("repeat_ratio"));
             list.add(4, dayMap.get(area).get("repeat_ratio"));
             // 增量
-            list.add(5, ((BigDecimal) list.get(2)).subtract((BigDecimal) list.get(1)));
-            list.add(6, ((BigDecimal) list.get(4)).subtract((BigDecimal) list.get(3)));
+            list.add(5, ((double) list.get(2)) - ((double) list.get(1)));
+            list.add(6, ((double) list.get(4)) - ((double) list.get(3)));
         }
         // 排序--逆序
         result.sort((o1, o2) -> {
-            return ((BigDecimal) o2.get(6)).compareTo(((BigDecimal) o1.get(6)));
+            return Double.compare((double) o2.get(6), (double) o1.get(6));
         });
         // 全省数据
         List<Object> list = new ArrayList<>(7);
@@ -314,8 +327,8 @@ public class TslDataService {
         list.add(3, preMap.get("全省").get("repeat_ratio"));
         list.add(4, dayMap.get("全省").get("repeat_ratio"));
         // 增量
-        list.add(5, ((BigDecimal) list.get(2)).subtract((BigDecimal) list.get(1)));
-        list.add(6, ((BigDecimal) list.get(4)).subtract((BigDecimal) list.get(3)));
+        list.add(5, ((double) list.get(2)) - ((double) list.get(1)));
+        list.add(6, ((double) list.get(4)) - ((double) list.get(3)));
         return result;
     }
 
@@ -416,11 +429,12 @@ public class TslDataService {
     private List<Map<String, Object>> getUserCount(String monthId, int stackLayerCount) {
         List<Map<String, Object>> result = tslDao.selectUserCountForMonth(monthId);
         if (stackLayerCount != 0) {
+            stackLayerCount -= 1;
             if (result == null || result.size() == 0) {
                 LocalDate localDate = LocalDate.parse(monthId + "01", DateTimeFormatter.ofPattern("yyyyMMdd"));
                 localDate = localDate.plusMonths(-1L);
                 String newMonthId = DateTimeFormatter.ofPattern("yyyyMM").format(localDate);
-                getUserCount(newMonthId);
+                return getUserCount(newMonthId, stackLayerCount);
             }
         }
         return result;

+ 10 - 11
tsl_data/src/main/java/com/nokia/tsl_data/service/TslReportService.java

@@ -2,7 +2,6 @@ package com.nokia.tsl_data.service;
 
 import java.io.FileOutputStream;
 import java.io.OutputStream;
-import java.math.BigDecimal;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -143,7 +142,7 @@ public class TslReportService {
             cell.setCellStyle(cellStyle1);
             // 投诉问题解决满意率
             cell = row.createCell(1);
-            cell.setCellValue(((BigDecimal) list.get(1)).doubleValue());
+            cell.setCellValue(((double) list.get(1)));
             cell.setCellStyle(cellStyle2);
             // 达标值
             cell = row.createCell(2);
@@ -189,7 +188,7 @@ public class TslReportService {
             cell.setCellStyle(cellStyle1);
             // 投诉问题解决率
             cell = row.createCell(1);
-            cell.setCellValue(((BigDecimal) list.get(1)).doubleValue());
+            cell.setCellValue(((double) list.get(1)));
             cell.setCellStyle(cellStyle2);
             // 达标值
             cell = row.createCell(2);
@@ -235,7 +234,7 @@ public class TslReportService {
             cell.setCellStyle(cellStyle1);
             // 投诉问题解决率
             cell = row.createCell(1);
-            cell.setCellValue(((BigDecimal) list.get(1)).doubleValue());
+            cell.setCellValue(((double) list.get(1)));
             cell.setCellStyle(cellStyle2);
             // 达标值
             cell = row.createCell(2);
@@ -362,7 +361,7 @@ public class TslReportService {
             cell.setCellStyle(cellStyle1);
             // 超时工单占比
             cell = row.createCell(3);
-            cell.setCellValue(((BigDecimal) list.get(3)).doubleValue());
+            cell.setCellValue(((double) list.get(3)));
             cell.setCellStyle(cellStyle2);
         }
         // 设置条件格式D3-D14
@@ -531,27 +530,27 @@ public class TslReportService {
             cell.setCellStyle(cellStyle1);
             // 前一天重复工单数
             cell = row.createCell(1);
-            cell.setCellValue(((BigDecimal) list.get(1)).intValue());
+            cell.setCellValue(((double) list.get(1)));
             cell.setCellStyle(cellStyle1);
             // 当天重复工单数
             cell = row.createCell(2);
-            cell.setCellValue(((BigDecimal) list.get(2)).intValue());
+            cell.setCellValue(((double) list.get(2)));
             cell.setCellStyle(cellStyle1);
             // 前一天重复投诉率
             cell = row.createCell(3);
-            cell.setCellValue(((BigDecimal) list.get(3)).doubleValue());
+            cell.setCellValue(((double) list.get(3)));
             cell.setCellStyle(cellStyle2);
             // 当前重复投诉率
             cell = row.createCell(4);
-            cell.setCellValue(((BigDecimal) list.get(4)).doubleValue());
+            cell.setCellValue(((double) list.get(4)));
             cell.setCellStyle(cellStyle2);
             // 重复工单增量
             cell = row.createCell(5);
-            cell.setCellValue(((BigDecimal) list.get(5)).intValue());
+            cell.setCellValue(((double) list.get(5)));
             cell.setCellStyle(cellStyle1);
             // 重复投诉率增量
             cell = row.createCell(6);
-            cell.setCellValue(((BigDecimal) list.get(6)).doubleValue());
+            cell.setCellValue(((double) list.get(6)));
             cell.setCellStyle(cellStyle2);
         }
 

+ 65 - 5
tsl_data/src/main/java/com/nokia/tsl_data/service/TslTaskService.java

@@ -1,17 +1,27 @@
 package com.nokia.tsl_data.service;
 
+import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
 import java.util.Date;
 
+import javax.imageio.ImageIO;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import com.nokia.common.excel.entity.CellRect;
+import com.nokia.common.excel.poi.PoiUtil;
 import com.nokia.tsl_data.dao.TslDao;
-import com.nokia.tsl_data.service.exception.TslTaskException;
 
 import lombok.extern.slf4j.Slf4j;
 
@@ -54,6 +64,7 @@ public class TslTaskService {
                 try {
                     dataWaraHouseTask(day);
                     reportGenerateTask(day);
+                    screenShotTask(day);
                 } catch (Exception e) {
                     log.error("定时任务出错--{}", e.getMessage());
                     e.printStackTrace();
@@ -67,9 +78,8 @@ public class TslTaskService {
      * 报表生成任务
      * 
      * @param day
-     * @throws TslTaskException
      */
-    public void reportGenerateTask(String day) throws TslTaskException {
+    public void reportGenerateTask(String day) {
         File file = new File(outputPath + day);
         if (!file.exists()) {
             file.mkdirs();
@@ -77,15 +87,65 @@ public class TslTaskService {
         String dayId = day.substring(0, 4) + "-" + day.substring(4, 6) + "-" + day.substring(6);
         int qualityCountForDay = tslDao.selectQualityCountForDay(dayId);
         if (qualityCountForDay == 0) {
-            throw new TslTaskException("he_d_high_quality表缺少数据");
+            throw new RuntimeException("he_d_high_quality表缺少数据");
         }
         int compCountForDay = tslDao.selectCompCountForDay(day);
         if (compCountForDay == 0) {
-            throw new TslTaskException("he_d_mobile_comp表缺少数据");
+            throw new RuntimeException("he_d_mobile_comp表缺少数据");
         }
         tslReportService.workbookToFile(day, file.getAbsolutePath() + "/" + outputFileName + day + ".xlsx");
     }
 
+    /**
+     * 截图任务
+     * 
+     * @param day
+     */
+    public void screenShotTask(String day) {
+        String screenShotPath = outputPath + day + "/";
+        File file = new File(outputPath + day + "/" + outputFileName + day + ".xlsx");
+        if (!file.exists()) {
+            throw new RuntimeException(String.format("无法截图,文件%s不存在", file.getAbsolutePath()));
+        }
+        try (Workbook workbook = WorkbookFactory.create(file);) {
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(new SimpleDateFormat("yyyyMMdd").parse(day));
+            int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+            BufferedImage screenShot;
+            // 截图1
+            String area = "A1:" + CellRect.getColumnName(dayOfMonth + 7) + "15";
+            screenShot = PoiUtil.screenShot(workbook.getSheet("管理端-移网质量类"), area, "微软雅黑");
+            ImageIO.write(screenShot, "png", new File(screenShotPath + day + "-投诉率.png"));
+
+            // 截图2
+            if (!day.endsWith("01")) {
+                screenShot = PoiUtil.screenShot(workbook.getSheet("管理端-重复投诉率"), "A1:G16", "微软雅黑");
+                ImageIO.write(screenShot, "png", new File(screenShotPath + day + "-重复投诉率.png"));
+            }
+
+            // 截图3 4
+            Sheet sheet = workbook.getSheet("投诉处理时长、超时工单概况");
+            screenShot = PoiUtil.screenShot(sheet, "A1:D15", "微软雅黑");
+            ImageIO.write(screenShot, "png", new File(screenShotPath + day + "-超时工单.png"));
+            screenShot = PoiUtil.screenShot(sheet, "G1:J14", "微软雅黑");
+            ImageIO.write(screenShot, "png", new File(screenShotPath + day + "-处理时长.png"));
+
+            // 截图5
+            screenShot = PoiUtil.screenShot(workbook.getSheet("客户端-投诉问题解决满意度"), "A1:D15", "微软雅黑");
+            ImageIO.write(screenShot, "png", new File(screenShotPath + day + "-满意率.png"));
+
+            // 截图6
+            screenShot = PoiUtil.screenShot(workbook.getSheet("客户端-投诉问题解决率"), "A1:D15", "微软雅黑");
+            ImageIO.write(screenShot, "png", new File(screenShotPath + day + "-解决率.png"));
+
+            // 截图7
+            screenShot = PoiUtil.screenShot(workbook.getSheet("客户端-投诉问题响应率"), "A1:D15", "微软雅黑");
+            ImageIO.write(screenShot, "png", new File(screenShotPath + day + "-响应率.png"));
+        } catch (EncryptedDocumentException | IOException | ParseException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
     /**
      * 数据入库任务
      * 

+ 0 - 8
tsl_data/src/main/java/com/nokia/tsl_data/service/exception/TslTaskException.java

@@ -1,8 +0,0 @@
-package com.nokia.tsl_data.service.exception;
-
-public class TslTaskException extends Exception {
-    
-    public TslTaskException(String msg) {
-        super(msg);
-    }
-}

+ 31 - 17
tsl_data/src/main/resources/mapper/TslDao.xml

@@ -9,10 +9,11 @@
     <select id="selectQualityCountForDay" resultType="int"> select count(1) from
         report_auto.he_d_high_quality where acct_date = #{day} </select>
 
+    <!-- 满意率 -->
     <select id="selectClientRatioForDay" resultType="Map">with t1 as (select businoareaname,
-        complaint_satisfied_list::numeric, complaint_satisfied_count::numeric,
-        complaint_resolution_list::numeric, complaint_resolution_count::numeric,
-        complaint_response_list::numeric, complaint_response_count::numeric from
+        complaint_satisfied_list::float8, complaint_satisfied_count::float8,
+        complaint_resolution_list::float8, complaint_resolution_count::float8,
+        complaint_response_list::float8, complaint_response_count::float8 from
         report_auto.he_d_high_quality hdhq where acct_date = #{day} and profes_dep = '网络质量' and
         big_type_name = '移网网络体验' and small_type_name = '--') select '全省' as businoareaname,
         sum(complaint_satisfied_list) / sum(complaint_satisfied_count) as complaint_satisfied,
@@ -26,6 +27,19 @@
     <select id="selectOldTsDurationForMonth" resultType="Map"> select city_name,avg_duration from
         report_auto.avg_duration where month_id = #{monthId} </select>
 
+    <!-- 从现有数据获取上月处理时长并插入数据表 -->
+    <insert id="insertOldTsDurationForMonth">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
+        report_auto.he_d_mobile_comp hdmc where month_id = #{month_id} and day_id::float8 &lt;=
+        extract('day' from to_timestamp(#{month_id}, 'YYYYMM') + interval '1 month' - interval '1
+        day')) insert into report_auto.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 </insert>
+
     <insert id="insertOldTsDuration" parameterType="Map"> INSERT INTO report_auto.avg_duration
         (month_id, city_name, avg_duration) VALUES(#{map.month_id}, #{map.city_name},
         #{map.avg_duration}); </insert>
@@ -36,9 +50,9 @@
         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
-        report_auto.he_d_mobile_comp hdmc where month_id = substring('20230420' from 1 for 6) and
-        day_id &lt;= substring('20230420' from 7 for 2)) select compl_area_local, avg(duration) as
-        avg_duration from t1 group by compl_area_local </select>
+        report_auto.he_d_mobile_comp hdmc where month_id = substring(#{day} from 1 for 6) and day_id
+        &lt;= substring(#{day} from 7 for 2)) select compl_area_local, avg(duration) as avg_duration
+        from t1 group by compl_area_local </select>
 
     <select id="selectTimeoutTsCountForDay" resultType="Map">with t1 as (select compl_area_local,
         is_timeout from report_auto.he_d_mobile_comp hdmc where month_id = substring(#{day} from 1
@@ -49,19 +63,19 @@
         compl_area_local, count(1) as timeout_num from t5), t8 as (select compl_area_local, count(1)
         as timeout_num from t5 group by compl_area_local), t9 as (select * from t7 union select *
         from t8) select t4.compl_area_local, t4.total_num, t9.timeout_num, t9.timeout_num /
-        t4.total_num::numeric as timeout_ratio from t4,t9 where t4.compl_area_local =
+        t4.total_num::float8 as timeout_ratio from t4,t9 where t4.compl_area_local =
         t9.compl_area_local</select>
 
-    <select id="selectRepeatTsCountForDay" resultType="Map"> with t1 as (select
-        compl_area_local,busi_no from report_auto.he_d_mobile_comp hdmc where month_id =
-        substring(#{day} from 1 for 6) and day_id &lt;= 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), t4 as (select compl_area_local, count(1) as distinct_num from t2
-        group by compl_area_local), t5 as (select t3.compl_area_local, t3.total_num, t3.total_num -
-        t4.distinct_num as repeat_num, (t3.total_num - t4.distinct_num)/t3.total_num::numeric as
-        repeat_ratio from T3, t4 where t3.compl_area_local = t4.compl_area_local) select '全省' as
-        compl_area_local, sum(total_num) as total_num, sum(repeat_num) as repeat_num,
-        sum(repeat_num) /sum(total_num)::numeric as repeat_ratio from t5 union select * from t5 </select>
+    <select id="selectRepeatTsCountForDay" resultType="Map"> with t1 as (select compl_area_local,
+        busi_no from report_auto.he_d_mobile_comp hdmc where month_id = substring(#{day} from 1 for
+        6) and day_id &lt;= 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), t4 as
+        (select compl_area_local, count(1) as distinct_num from t2 group by compl_area_local), t5 as
+        (select t3.compl_area_local, t3.total_num, t3.total_num - t4.distinct_num::float8 as
+        repeat_num, (t3.total_num - t4.distinct_num)/t3.total_num::float8 as repeat_ratio from T3,
+        t4 where t3.compl_area_local = t4.compl_area_local) select '全省' as compl_area_local,
+        sum(total_num) as total_num, sum(repeat_num) as repeat_num, sum(repeat_num)
+        /sum(total_num)::float8 as repeat_ratio from t5 union select * from t5 </select>
 
     <select id="selectCityTslForMonth" resultType="Map"> select compl_area_local, day_id, count(1)
         as num from report_auto.he_d_mobile_comp hdmc where month_id = substring(#{day} from 1 for

+ 3 - 3
tsl_data/src/test/java/com/nokia/tsl_data/TslDataApplicationTest.java

@@ -6,7 +6,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
 import com.nokia.tsl_data.service.TslTaskService;
-import com.nokia.tsl_data.service.exception.TslTaskException;
 
 @SpringBootTest
 public class TslDataApplicationTest {
@@ -15,7 +14,8 @@ public class TslDataApplicationTest {
     private TslTaskService taskService;
 
     @Test
-    void test() throws IOException, TslTaskException {
-        taskService.reportGenerateTask("20230425");
+    void test() throws IOException {
+        // taskService.reportGenerateTask("20230501");
+        taskService.screenShotTask("20230501");
     }
 }