Selaa lähdekoodia

v2.0上线版本

lifuquan 1 vuosi sitten
vanhempi
commit
2f3f0e01be

+ 2 - 1
.gitignore

@@ -1,2 +1,3 @@
 target
-download
+download
+log

+ 3 - 0
doc/bin/run.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+nohup java -jar /data/jar/dingtalk_auto/dingtalk_auto-exec.jar >/dev/null 2>&1 &

+ 5 - 0
doc/bin/stop.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+for i in $(ps -ef | grep dingtalk_auto-exec.jar | grep -v grep | awk '{print $2}'); do
+  kill -9 "$i"
+done

+ 24 - 2
doc/接口测试.md

@@ -1,5 +1,7 @@
 # 接口测试
 
+## 零流量低流量明细
+
 - 手动发送 零流量低流量明细
 
 ```http
@@ -12,8 +14,28 @@ Content-Type:application/json
 - 撤回消息 零流量低流量明细
 
 ```http
-POST http://127.0.0.1:11111/dingtalk-auto/task/zerotraffic/recall HTTP/1.1
+POST http://192.168.31.182:11111/dingtalk-auto/task/zerotraffic/recall HTTP/1.1
+Content-Type:application/json
+
+KqePh2Z4QbSmqGuyYYlzljpOfepO7lvxLS2wwNYTIo0=, fYZtLFxtlvbdiobWvDGHW9XHwdkkn7RjnGtNarvwtuw=, 80UGxVpUHm6nzLXq+R5dN01A1m+2sxCyaM4TAIo6B4k=, 2ZrmgiIkO5+XXuFbl8obPWq8Ka7inNxVGMwlIdPV3YI=, I9MleAVHv+pHw70kw7kIs2ryG6Csat09CD9l7E69Ix8=, zsxARKojgIsvm+qPSjdTaOYzX8zXEw9ggVRVqdMVKjw=, KqePh2Z4QbSmqGuyYYlzlgHVsF4VZmQdQ4Dd1vCkX9w=, 1OFLDT+WEirj6cZeAbHmfNETttjrMFo4J4sXC1Qr0os=, yi+33a29TRmWzDRlX0qf79Oq9CXfTMyS+Pnt+bT1xIo=, ydJUNE5kqkwEObQiHv1aXUhlpuHHdQarasYHhauUums=
+```
+
+## 投诉清单各地市投诉率
+
+- 手动发送 投诉清单各地市投诉率
+
+```http
+POST http://127.0.0.1:11111/dingtalk-auto/task/tsl/send HTTP/1.1
+Content-Type:application/json
+
+20230720
+```
+
+- 撤回消息 投诉清单各地市投诉率
+
+```http
+POST http://192.168.31.182:11111/dingtalk-auto/task/tsl/recall HTTP/1.1
 Content-Type:application/json
 
-9PiST3kl8lk2u1M2lejVheA1JJ2EI0JY8SPVEltsByc=
+KqePh2Z4QbSmqGuyYYlzljpOfepO7lvxLS2wwNYTIo0=, fYZtLFxtlvbdiobWvDGHW9XHwdkkn7RjnGtNarvwtuw=, 80UGxVpUHm6nzLXq+R5dN01A1m+2sxCyaM4TAIo6B4k=, 2ZrmgiIkO5+XXuFbl8obPWq8Ka7inNxVGMwlIdPV3YI=, I9MleAVHv+pHw70kw7kIs2ryG6Csat09CD9l7E69Ix8=, zsxARKojgIsvm+qPSjdTaOYzX8zXEw9ggVRVqdMVKjw=, KqePh2Z4QbSmqGuyYYlzlgHVsF4VZmQdQ4Dd1vCkX9w=, 1OFLDT+WEirj6cZeAbHmfNETttjrMFo4J4sXC1Qr0os=, yi+33a29TRmWzDRlX0qf79Oq9CXfTMyS+Pnt+bT1xIo=, ydJUNE5kqkwEObQiHv1aXUhlpuHHdQarasYHhauUums=
 ```

+ 12 - 0
pom.xml

@@ -31,6 +31,18 @@
     </properties>
 
     <dependencies>
+        <!-- poi -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>5.2.3</version>
+        </dependency>
+        <!-- poi 2007 -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>5.2.3</version>
+        </dependency>
         <!-- com.nokia.common -->
         <dependency>
             <groupId>com.nokia</groupId>

+ 40 - 0
src/main/java/com/nokia/dingtalk_auto/config/TsxtDingTalkUtilConfig.java

@@ -0,0 +1,40 @@
+package com.nokia.dingtalk_auto.config;
+
+import javax.annotation.Resource;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.web.client.RestTemplate;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nokia.common.dingtalk.DingtalkUtil;
+
+/**
+ * 投诉系统机器人
+ */
+@Configuration
+public class TsxtDingTalkUtilConfig {
+    @Resource
+    private RestTemplate restTemplate;
+    @Resource
+    private RedisTemplate<String, Object> redisTemplate;
+    @Resource
+    private ObjectMapper objectMapper;
+
+    @Value("${tsxt.dingtalk.appkey}")
+    private String appKey;
+    @Value("${tsxt.dingtalk.appSecret}")
+    private String appSecret;
+
+    @Bean("tsxtDingtalkUtil")
+    public DingtalkUtil tsxtDingtalkUtil() {
+        return new DingtalkUtil()
+                .setAppKey(appKey)
+                .setAppSecret(appSecret)
+                .setObjectMapper(objectMapper)
+                .setRestTemplate(restTemplate)
+                .setRedisTemplate(redisTemplate);
+    }
+}

+ 63 - 0
src/main/java/com/nokia/dingtalk_auto/controller/TslTaskController.java

@@ -0,0 +1,63 @@
+package com.nokia.dingtalk_auto.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.nokia.common.http.R;
+import com.nokia.dingtalk_auto.service.TslTaskService;
+
+/**
+ * 投诉清单各地市投诉率
+ */
+@RequestMapping("/dingtalk-auto/task/tsl")
+@RestController
+public class TslTaskController {
+
+    @Autowired
+    private TslTaskService tslTaskService;
+
+    /**
+     * 撤回消息
+     * 
+     * @param messageIds
+     * @return
+     */
+    @PostMapping("/recall")
+    public R recallMessage(@RequestBody String messageIds) {
+        try {
+            String[] split = messageIds.split(",");
+            List<String> messageIdList = new ArrayList<>();
+            for (String messageId : split) {
+                messageIdList.add(messageId.trim());
+            }
+            tslTaskService.recallMessage(messageIdList);
+            return R.ok().message("撤回成功");
+        } catch (Exception e) {
+            e.printStackTrace();
+            return R.error().message(e.getMessage());
+        }
+    }
+
+    /**
+     * 手动发送消息
+     * 
+     * @param day
+     * @return
+     */
+    @PostMapping("/send")
+    public R sendManually(@RequestBody String day) {
+        try {
+            tslTaskService.runTaskManually(day);
+            return R.ok().message("发送成功");
+        } catch (Exception e) {
+            e.printStackTrace();
+            return R.error().message(e.getMessage());
+        }
+    }
+}

+ 10 - 2
src/main/java/com/nokia/dingtalk_auto/controller/ZeroTrafficTaskController.java

@@ -1,5 +1,8 @@
 package com.nokia.dingtalk_auto.controller;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -26,9 +29,14 @@ public class ZeroTrafficTaskController {
      * @return
      */
     @PostMapping("/recall")
-    public R recallMessage(@RequestBody String messageId) {
+    public R recallMessage(@RequestBody String messageIds) {
         try {
-            zeroTrafficTaskService.recallMessage(messageId);
+            String[] split = messageIds.split(",");
+            List<String> messageIdList = new ArrayList<>();
+            for (String messageId : split) {
+                messageIdList.add(messageId.trim());
+            }
+            zeroTrafficTaskService.recallMessage(messageIdList);
             return R.ok().message("撤回成功");
         } catch (Exception e) {
             e.printStackTrace();

+ 2 - 2
src/main/java/com/nokia/dingtalk_auto/service/MessageService.java

@@ -10,7 +10,7 @@ import com.nokia.common.dingtalk.DingtalkUtil;
 @Service
 public class MessageService {
 
-    @Value("${netbrain.dingtalk.openConversationId}")
+    @Value("${dingtalk.message.openConversationId}")
     private String openConversationId;
 
     @Autowired
@@ -18,7 +18,7 @@ public class MessageService {
     private DingtalkUtil dingtalkUtil;
 
     public void message(String message) {
-        dingtalkUtil.SendText(openConversationId, message);
+        dingtalkUtil.sendText(openConversationId, message);
     }
 
     public void error(String errorMessage) {

+ 439 - 0
src/main/java/com/nokia/dingtalk_auto/service/TslTaskService.java

@@ -0,0 +1,439 @@
+package com.nokia.dingtalk_auto.service;
+
+import java.io.File;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.poi.ss.usermodel.Row;
+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.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.stereotype.Service;
+
+import com.nokia.common.dingtalk.DingtalkUtil;
+import com.nokia.common.ssh.SftpUtil;
+
+/**
+ * 投诉清单各地市投诉率
+ */
+@Service
+public class TslTaskService {
+
+    @Value("${tsxt.tsl.remoteDir}")
+    private String remoteDir;
+
+    @Value("${tsxt.tsl.localDir}")
+    private String localDir;
+
+    @Value("${tsxt.tsl.remoteHost}")
+    private String remoteHost;
+
+    @Value("${tsxt.tsl.remotePort}")
+    private int remotePort;
+
+    @Value("${tsxt.tsl.remoteUser}")
+    private String remoteUser;
+
+    @Value("${tsxt.tsl.remotePassword}")
+    private String remotePassword;
+
+    @Value("${tsxt.tsl.openConversationId}")
+    private String openConversationId;
+
+    @Autowired
+    @Qualifier("tsxtDingtalkUtil")
+    private DingtalkUtil dingtalkUtil;
+
+    @Autowired
+    private MessageService messageService;
+
+    @Autowired
+    private ThreadPoolTaskScheduler taskScheduler;
+
+    /**
+     * 定时任务
+     */
+    @Scheduled(cron = "0 0 14 * * *")
+    public void cronTask() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.DATE, -1);
+        DateFormat format = new SimpleDateFormat("yyyyMMdd");
+        String day = format.format(calendar.getTime());
+        // 30分钟检查一次
+        checkRemoteDirAndWaitForFileOk(day, 1000L * 60 * 30).thenRun(() -> {
+            download(day);
+        }).thenRun(() -> {
+            try {
+                sendDingTalkMsg(day);
+            } catch (Exception e) {
+                throw new RuntimeException(e.getMessage());
+            }
+        }).exceptionally(ex -> {
+            // 处理异常--发送提醒并重新抛出异常
+            messageService.error(ex.getMessage());
+            throw new RuntimeException(ex.getMessage());
+        }).join();
+    }
+
+    /**
+     * 手动发送
+     * 
+     * @param day 20230607 样式的表示日期的字符串
+     */
+    public void runTaskManually(String day) {
+        CompletableFuture.runAsync(() -> {
+            download(day);
+        }).thenRun(() -> {
+            try {
+                sendDingTalkMsg(day);
+            } catch (Exception e) {
+                throw new RuntimeException(e.getMessage());
+            }
+        }).exceptionally(ex -> {
+            // 处理异常--发送提醒并重新抛出异常
+            messageService.error(ex.getMessage());
+            throw new RuntimeException(ex.getMessage());
+        }).join();
+    }
+
+    /**
+     * 撤回消息
+     * 
+     * @param messageId
+     */
+    public void recallMessage(List<String> messageIdList) {
+        dingtalkUtil.recallMessage(openConversationId, messageIdList);
+    }
+
+    /**
+     * 撤回消息
+     * 
+     * @param messageId
+     */
+    public void recallMessage(String messageId) {
+        dingtalkUtil.recallMessage(openConversationId, messageId);
+    }
+
+    private void sendDingTalkMsg(String day) throws Exception {
+        String msg = getMessageToSend(day);
+        List<String> messageIds = new ArrayList<>();
+        String title = "投诉清单各地市投诉率" + day;
+        String messageId0 = dingtalkUtil.sendMarkdown(openConversationId, msg, title);
+        messageIds.add(messageId0);
+        String localPath = localDir + "/" + day;
+        File[] files = new File(localPath).listFiles();
+
+        // 分开发送,保证发送文件的顺序
+        for (File file : files) {
+            if (file.getName().toLowerCase().endsWith("-1-投诉率.png")) {
+                String mediaId = dingtalkUtil.upload(file.getAbsolutePath(), "image");
+                String messageId = dingtalkUtil.sendImage(openConversationId, mediaId);
+                messageIds.add(messageId);
+            }
+        }
+        for (File file : files) {
+            if (file.getName().toLowerCase().endsWith("-2-客户端-战略考核.png")) {
+                String mediaId = dingtalkUtil.upload(file.getAbsolutePath(), "image");
+                String messageId = dingtalkUtil.sendImage(openConversationId, mediaId);
+                messageIds.add(messageId);
+            }
+        }
+        for (File file : files) {
+            if (file.getName().toLowerCase().endsWith("-3-重复投诉率.png")) {
+                String mediaId = dingtalkUtil.upload(file.getAbsolutePath(), "image");
+                String messageId = dingtalkUtil.sendImage(openConversationId, mediaId);
+                messageIds.add(messageId);
+            }
+        }
+        for (File file : files) {
+            if (file.getName().toLowerCase().endsWith("-4-超时工单.png")) {
+                String mediaId = dingtalkUtil.upload(file.getAbsolutePath(), "image");
+                String messageId = dingtalkUtil.sendImage(openConversationId, mediaId);
+                messageIds.add(messageId);
+            }
+        }
+        for (File file : files) {
+            if (file.getName().toLowerCase().endsWith("-5-处理时长.png")) {
+                String mediaId = dingtalkUtil.upload(file.getAbsolutePath(), "image");
+                String messageId = dingtalkUtil.sendImage(openConversationId, mediaId);
+                messageIds.add(messageId);
+            }
+        }
+        for (File file : files) {
+            if (file.getName().toLowerCase().endsWith("-6-满意率.png")) {
+                String mediaId = dingtalkUtil.upload(file.getAbsolutePath(), "image");
+                String messageId = dingtalkUtil.sendImage(openConversationId, mediaId);
+                messageIds.add(messageId);
+            }
+        }
+        for (File file : files) {
+            if (file.getName().toLowerCase().endsWith("-7-解决率.png")) {
+                String mediaId = dingtalkUtil.upload(file.getAbsolutePath(), "image");
+                String messageId = dingtalkUtil.sendImage(openConversationId, mediaId);
+                messageIds.add(messageId);
+            }
+        }
+        for (File file : files) {
+            if (file.getName().toLowerCase().endsWith("-8-响应率.png")) {
+                String mediaId = dingtalkUtil.upload(file.getAbsolutePath(), "image");
+                String messageId = dingtalkUtil.sendImage(openConversationId, mediaId);
+                messageIds.add(messageId);
+            }
+        }
+        for (File file : files) {
+            if (file.getName().toLowerCase().equals("投诉清单各地市投诉率" + day + ".xlsx")) {
+                String mediaId = dingtalkUtil.upload(file.getAbsolutePath(), "file");
+                String messageId = dingtalkUtil.sendFile(openConversationId, mediaId, file.getName());
+                messageIds.add(messageId);
+            }
+        }
+        messageService.message(String.format("投诉清单各地市投诉率 %s 账期 完成发送 ==消息id: %s", day, messageIds));
+    }
+
+    /**
+     * 根据要求组织一段话
+     * 
+     * @param day
+     * @return
+     * @throws Exception
+     */
+    private String getMessageToSend(String day) throws Exception {
+        String localPath = localDir + "/" + day + "/";
+        String localFile = localPath + "投诉清单各地市投诉率" + day + ".xlsx";
+        StringBuffer stringBuffer = new StringBuffer();
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(new SimpleDateFormat("yyyyMMdd").parse(day));
+        int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+        stringBuffer.append(calendar.get(Calendar.YEAR))
+                .append("年")
+                .append(calendar.get(Calendar.MONTH) + 1)
+                .append("月截至")
+                .append(dayOfMonth)
+                .append("日移动网投诉情况统计:\n\n管理端-移网质量类:");
+        File file = new File(localFile);
+        if (!file.exists()) {
+            throw new Exception(String.format("文件--%s--不存在", localFile));
+        }
+        try (Workbook workbook = WorkbookFactory.create(file)) {
+            Sheet sheet = workbook.getSheet("管理端-移网质量类");
+            Row row;
+            List<List<Object>> list = new ArrayList<>();
+            List<List<Object>> list0 = new ArrayList<>();
+            for (int i = 2; i < 14; i++) {
+                row = sheet.getRow(i);
+                double value = row.getCell(dayOfMonth + 6).getNumericCellValue();
+                List<Object> list2 = new ArrayList<>();
+                list2.add(0, value);
+                list2.add(1, row.getCell(dayOfMonth + 7).getStringCellValue());
+                if (value > 0.0) {
+                    list.add(list2);
+                }
+                list0.add(list2);
+            }
+            // 排序
+            list.sort((o1, o2) -> Double.compare((double) o2.get(0), (double) o1.get(0)));
+            list0.sort((o1, o2) -> Double.compare((double) o2.get(0), (double) o1.get(0)));
+            if (list.size() == 0) {
+                stringBuffer.append("投诉率:12个地市均已达标,<font color=#FF0000>");
+            } else {
+                stringBuffer.append("投诉率:<font color=#FF0000>");
+                for (int i = 0; i < list.size(); i++) {
+                    stringBuffer.append(list.get(i).get(1).toString()).append("、");
+                }
+                stringBuffer.deleteCharAt(stringBuffer.length() - 1);
+                stringBuffer.append("</font>未达到目标值,<font color=#FF0000>");
+            }
+            StringBuffer stringBuffer2 = new StringBuffer();
+            for (int i = 0; i < 3; i++) {
+                stringBuffer2.append(list0.get(i).get(1).toString()).append("、");
+            }
+            stringBuffer2.deleteCharAt(stringBuffer2.length() - 1);
+            stringBuffer.append(stringBuffer2.toString()).append("</font>排名靠后");
+
+            // 1号忽略重复投诉率
+            if (day.endsWith("01")) {
+                stringBuffer.append("。\n\n客户端-移网网络体验:投诉问题解决满意率:<font color=#FF0000>");
+            } else {
+                stringBuffer.append(";重复投诉率:<font color=#FF0000>");
+                sheet = workbook.getSheet("管理端-重复投诉率");
+                stringBuffer.append(sheet.getRow(3).getCell(0).getStringCellValue()).append("、")
+                        .append(sheet.getRow(4).getCell(0).getStringCellValue()).append("、")
+                        .append(sheet.getRow(5).getCell(0).getStringCellValue())
+                        .append("</font>增长较快。\n\n客户端-移网网络体验:投诉问题解决满意率:<font color=#FF0000>");
+            }
+
+            sheet = workbook.getSheet("客户端-投诉问题解决满意度");
+            stringBuffer.append(sheet.getRow(13).getCell(0).getStringCellValue()).append("、")
+                    .append(sheet.getRow(12).getCell(0).getStringCellValue()).append("、")
+                    .append(sheet.getRow(11).getCell(0).getStringCellValue())
+                    .append("</font>较低,与达标值差距较大;投诉问题解决率:<font color=#FF0000>");
+
+            sheet = workbook.getSheet("客户端-投诉问题解决率");
+            stringBuffer.append(sheet.getRow(13).getCell(0).getStringCellValue()).append("、")
+                    .append(sheet.getRow(12).getCell(0).getStringCellValue()).append("、")
+                    .append(sheet.getRow(11).getCell(0).getStringCellValue())
+                    .append("</font>较低,与达标值差距较大;投诉问题响应率:<font color=#FF0000>");
+
+            sheet = workbook.getSheet("客户端-投诉问题响应率");
+            stringBuffer.append(sheet.getRow(13).getCell(0).getStringCellValue()).append("、")
+                    .append(sheet.getRow(12).getCell(0).getStringCellValue()).append("、")
+                    .append(sheet.getRow(11).getCell(0).getStringCellValue())
+                    .append("</font>较低,与达标值差距较大。\n\n投诉处理时长、超时工单概况:超时工单:<font color=#FF0000>");
+
+            sheet = workbook.getSheet("投诉处理时长、超时工单概况");
+            stringBuffer.append(sheet.getRow(2).getCell(0).getStringCellValue()).append("、")
+                    .append(sheet.getRow(3).getCell(0).getStringCellValue()).append("、")
+                    .append(sheet.getRow(4).getCell(0).getStringCellValue())
+                    .append("</font>分公司超时工单占比较高,<font color=#FF0000>");
+            list = new ArrayList<>();
+            for (int i = 2; i < 14; i++) {
+                List<Object> list2 = new ArrayList<>();
+                list.add(list2);
+                list2.add(0, sheet.getRow(i).getCell(2).getNumericCellValue());
+                list2.add(1, sheet.getRow(i).getCell(0).getStringCellValue());
+            }
+            list.sort((o1, o2) -> Double.compare((double) o2.get(0), (double) o1.get(0)));
+            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("</font>超时工单数量较多。平均处理时长:本月相对较长的地市为<font color=#FF0000>");
+
+            list = new ArrayList<>();
+            for (int i = 2; i < 14; i++) {
+                List<Object> list2 = new ArrayList<>();
+                list.add(list2);
+                list2.add(0, sheet.getRow(i).getCell(8).getNumericCellValue());
+                list2.add(1, sheet.getRow(i).getCell(6).getStringCellValue());
+            }
+            list.sort((o1, o2) -> Double.compare((double) o2.get(0), (double) o1.get(0)));
+            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("</font>;与")
+                    .append(sheet.getRow(1).getCell(7).getStringCellValue()).append("比<font color=#FF0000>")
+                    .append(sheet.getRow(2).getCell(6).getStringCellValue()).append("</font>时长增幅较大。");
+        }
+
+        return stringBuffer.toString();
+    }
+
+    /**
+     * 下载文件,根据给出的月份字符串拼接远端路径,把文件下载到本地。
+     * 
+     * @param day 20230622 样式的表示月份的字符串
+     */
+    private void download(String day) {
+        // 创建SftpUtil
+        SftpUtil sftpUtil = getSftpUtil();
+        String remotePath = remoteDir + day;
+        String localPath = localDir + day;
+        File localFile = new File(localPath);
+        if (!localFile.exists()) {
+            localFile.mkdirs();
+        }
+        try {
+            // 连接到sftp服务器
+            sftpUtil.connect();
+            List<String> files = sftpUtil.ls(remotePath);
+            for (String fileName : files) {
+                sftpUtil.get(remotePath + "/" + fileName, localPath + "/" + fileName);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e.getMessage());
+        } finally {
+            sftpUtil.disconnect();
+        }
+    }
+
+    /**
+     * 检查文件是否存在,如果不存在等待milliseconds毫秒后重新检查,直到文件已存在
+     * 
+     * @param day          20230719
+     * @param milliseconds 毫秒数
+     * @return
+     */
+    private CompletableFuture<Void> checkRemoteDirAndWaitForFileOk(String day, Long milliseconds) {
+        return CompletableFuture.runAsync(() -> {
+            checkRemoteDir(day);
+        }, taskScheduler).handle((result, ex) -> {
+            if (ex != null) {
+                String msg = ex.getMessage();
+                // 发现文件不存在
+                if (msg.endsWith("No such file")) {
+                    // 发送提醒
+                    messageService.error("投诉清单各地市投诉率账期 " + day + " 文件未具备,请提醒生产方进行生产。");
+                    // 等待一段时间
+                    sleep(milliseconds);
+                    // 重新检查
+                    return checkRemoteDirAndWaitForFileOk(day, milliseconds).join();
+                }
+                // 其他异常不进行重试
+                messageService.error("投诉清单各地市投诉率检查 " + day + " 账期文件时发生异常:" + msg);
+                // 重新抛出异常
+                throw new RuntimeException(ex.getMessage());
+            }
+            // 没有异常时直接返回结果
+            return result;
+        });
+    }
+
+    /**
+     * 等待一段时间
+     * 
+     * @param milliseconds 等待的毫秒数
+     * @return
+     */
+    private void sleep(Long milliseconds) {
+        try {
+            Thread.sleep(milliseconds);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 检查文件是否存在
+     * 
+     * @param month
+     * @return
+     * @throws Exception
+     */
+    private void checkRemoteDir(String month) {
+        SftpUtil sftpUtil = getSftpUtil();
+        String remotePath = remoteDir + month;
+        try {
+            // 连接到sftp服务器
+            sftpUtil.connect();
+            // ls如果路径不存在会触发RuntimeException,携带消息No such file
+            List<String> files = sftpUtil.ls(remotePath);
+            if (files.size() == 0) {
+                // 如果路径存在,但路径下没有文件,抛出RuntimeException,携带消息No such file
+                throw new RuntimeException("No such file");
+            }
+        } finally {
+            sftpUtil.disconnect();
+        }
+    }
+
+    /**
+     * 获取Sftp连接工具SftpUtil
+     * 
+     * @return
+     */
+    private SftpUtil getSftpUtil() {
+        // 创建SftpUtil
+        return new SftpUtil()
+                .setHost(remoteHost)
+                .setPort(remotePort)
+                .setUser(remoteUser)
+                .setPassword(remotePassword);
+    }
+}

+ 11 - 2
src/main/java/com/nokia/dingtalk_auto/service/ZeroTrafficTaskService.java

@@ -42,7 +42,7 @@ public class ZeroTrafficTaskService {
     @Value("${netbrain.zeroTraffic.localDir}")
     private String localDir;
 
-    @Value("${netbrain.dingtalk.openConversationId}")
+    @Value("${netbrain.zeroTraffic.openConversationId}")
     private String openConversationId;
 
     @Autowired
@@ -95,6 +95,15 @@ public class ZeroTrafficTaskService {
         }).join();
     }
 
+    /**
+     * 撤回消息
+     * 
+     * @param messageId
+     */
+    public void recallMessage(List<String> messageIdList) {
+        dingtalkUtil.recallMessage(openConversationId, messageIdList);
+    }
+
     /**
      * 撤回消息
      * 
@@ -171,7 +180,7 @@ public class ZeroTrafficTaskService {
             for (File file : files) {
                 if (file.getName().toLowerCase().endsWith(month + ".xlsx")) {
                     String mediaId = dingtalkUtil.upload(file.getAbsolutePath(), "file");
-                    String messageId = dingtalkUtil.SendFile(openConversationId, mediaId, file.getName());
+                    String messageId = dingtalkUtil.sendFile(openConversationId, mediaId, file.getName());
                     messageIds.add(messageId);
                 }
             }

+ 29 - 1
src/main/resources/application.yml

@@ -6,6 +6,11 @@ spring:
     host: 127.0.0.1
     port: 6379
 
+logging:
+  level:
+    com:
+      nokia: debug
+
 dingtalk:
   default:
     appkey: dingothmdq6opv6hjrm5
@@ -17,11 +22,34 @@ netbrain:
   dingtalk:
     appkey: dingothmdq6opv6hjrm5
     appSecret: SeoyAwUnzFIFY4j4CX089HJ0i-pj1BIzByB3AZcnbCQaq94lZvazFpfEGGQwPznc
-    openConversationId: cidcWmmFwduUTDB3G0vPNOldQ==
+  # 零流量报表 配置
   zeroTraffic:
+    openConversationId: cidcWmmFwduUTDB3G0vPNOldQ==
     remoteHost: 133.96.94.246
     remotePort: 22
     remoteUser: nokia
     remotePassword: Nokia*123
     remoteDir: /data/wangluodanao/program/timingFile/
     localDir: ./download/netbrain/zerotraffic/
+
+# 投诉系统配置
+tsxt:
+  dingtalk:
+    # 王玉龙的机器人
+    appkey: dingzj6ozs4wfzxthzen
+    appSecret: f5tIy_k8jjIPdwBT0Uns9cSEbP47vTZcqmkAwMnH0cD_GM4cu26Zt18vjRbylBbz
+    # 李福全的机器人
+    # appkey: dingothmdq6opv6hjrm5
+    # appSecret: SeoyAwUnzFIFY4j4CX089HJ0i-pj1BIzByB3AZcnbCQaq94lZvazFpfEGGQwPznc
+  # 投诉每日自动发送的日报表(省公司级别)配置
+  tsl:
+    # 王玉龙的群
+    openConversationId: cide4nWtey1XgBmy7VFcsu3rA==
+    # 李福全的群
+    # openConversationId: cidcWmmFwduUTDB3G0vPNOldQ==
+    remoteHost: 133.96.94.105
+    remotePort: 22
+    remoteUser: do
+    remotePassword: Richr00t
+    remoteDir: /data/report_auto/output/
+    localDir: ./download/tsxt/tsl/

+ 63 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <property name="PATH" value="./log" />
+    <!-- 用于在console输出日志 -->
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} %-5level [%thread] %X{traceId} %logger:%line %msg%n</Pattern>
+            <!-- 使用系统默认的编码方式 -->
+            <!-- <charset>UTF-8</charset> -->
+        </encoder>
+    </appender>
+    <!-- 用于写入日志文件 -->
+    <appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${PATH}/trace.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <!-- 每天切换日志文件 -->
+            <fileNamePattern>${PATH}/trace.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <!-- 单个文件最大值,超过会切换新的日志文件,此配置最大不超过20GB -->
+            <maxFileSize>10MB</maxFileSize>
+            <!-- 最多保存60天日志 -->
+            <maxHistory>60</maxHistory>
+            <!-- 最大占用20GB空间 -->
+            <totalSizeCap>20GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} %-5level [%thread] %X{traceId} %logger:%line %msg%n</Pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+    <!-- 错误日志写入文件 -->
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${PATH}/error.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <!-- 每天切换日志文件 -->
+            <fileNamePattern>${PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <!-- 单个文件最大值,超过会切换新的日志文件,此配置最大不超过20GB -->
+            <maxFileSize>10MB</maxFileSize>
+            <!-- 最多保存60天日志 -->
+            <maxHistory>60</maxHistory>
+            <!-- 最大占用20GB空间 -->
+            <totalSizeCap>20GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} %-5level [%thread] %X{traceId} %logger:%line %msg%n</Pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!-- 错误日志仅保存ERROR级别 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+    <root level="INFO">
+        <appender-ref ref="STDOUT" />
+    </root>
+    <root level="TRACE">
+        <appender-ref ref="TRACE_FILE" />
+    </root>
+    <root level="ERROR">
+        <appender-ref ref="ERROR_FILE" />
+    </root>
+</configuration>

+ 4 - 3
src/test/java/com/nokia/dingtalk_auto/DingtalkAutoApplicationTest.java

@@ -3,17 +3,18 @@ package com.nokia.dingtalk_auto;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
-import com.nokia.dingtalk_auto.service.ZeroTrafficTaskService;
+
+import com.nokia.dingtalk_auto.service.TslTaskService;
 
 @SpringBootTest
 public class DingtalkAutoApplicationTest {
 
     @Autowired
-    private ZeroTrafficTaskService zeroTrafficSendService;
+    private TslTaskService tslTaskService;
 
     @Test
     void test() throws Exception {
-        zeroTrafficSendService.cronTask();
+        tslTaskService.runTaskManually("20230718");
     }
 
 }