Просмотр исходного кода

完成了对钉钉api的封装和对sftp的封装

lifuquan 1 год назад
Родитель
Сommit
8216dd6aaa

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+**/target/

+ 43 - 0
data_generation/pom.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.nokia</groupId>
+        <artifactId>report_auto</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>com.nokia</groupId>
+    <artifactId>data_generation</artifactId>
+    <version>1.0</version>
+
+    <packaging>jar</packaging>
+
+    <properties>
+        <!-- 跳过测试代码 -->
+        <skipTests>
+            true</skipTests>
+        <!-- 指定java的版本 -->
+        <java.version>1.8</java.version>
+        <!-- 文件拷贝时的编码 -->
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <!-- 指定maven-compiler-plugin的配置属性开始 -->
+        <!-- 编译时的编码 -->
+        <maven.compiler.encoding>
+            UTF-8</maven.compiler.encoding>
+        <!-- 指定编译的版本 -->
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
+        <!-- 指定maven-compiler-plugin的配置属性结束 -->
+    </properties>
+
+    <dependencies>
+        
+    </dependencies>
+</project>

+ 71 - 0
dingtalk_auto/pom.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.nokia</groupId>
+        <artifactId>report_auto</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>com.nokia</groupId>
+    <artifactId>dingtalk_auto</artifactId>
+    <version>1.0</version>
+
+    <packaging>jar</packaging>
+
+    <properties>
+        <!-- 跳过测试代码 -->
+        <skipTests>
+            true</skipTests>
+        <!-- 指定java的版本 -->
+        <java.version>1.8</java.version>
+        <!-- 文件拷贝时的编码 -->
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <!-- 指定maven-compiler-plugin的配置属性开始 -->
+        <!-- 编译时的编码 -->
+        <maven.compiler.encoding>
+            UTF-8</maven.compiler.encoding>
+        <!-- 指定编译的版本 -->
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
+        <!-- 指定maven-compiler-plugin的配置属性结束 -->
+    </properties>
+
+    <dependencies>
+        <!--ssh2 -->
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <version>0.1.54</version>
+        </dependency>
+        <!-- starter-redis -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <!--starter-web-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!--lombok-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!-- springboot-test -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

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

@@ -0,0 +1,198 @@
+package com.nokia.common.dingtalk;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nokia.common.dingtalk.exception.DingTalkApiException;
+
+import lombok.extern.slf4j.Slf4j;
+
+/*
+ * 钉钉API封装
+ * 采用RestTemplate调用API接口的方式进行封装
+ * 官方SDK分为新版与旧版,使用起来比较复杂
+ */
+@Slf4j
+public class DingTalkUtil {
+
+    private static final String API_ACCESS_TOKEN_REDIS_KEY = "API_DING_TALK_ACCESS_TOKEN";
+    private static final String OAPI_ACCESS_TOKEN_REDIS_KEY = "OAPI_DING_TALK_ACCESS_TOKEN";
+
+    private final String appKey;
+    private final String appSecret;
+    private final String conversationId;
+    private final RestTemplate restTemplate;
+    private final RedisTemplate<String, Object> redisTemplate;
+    private final ObjectMapper objectMapper;
+
+    public DingTalkUtil(String appKey, String appSecret, String conversationId, RestTemplate restTemplate,
+            RedisTemplate<String, Object> redisTemplate, ObjectMapper objectMapper) {
+        this.appKey = appKey;
+        this.appSecret = appSecret;
+        this.conversationId = conversationId;
+        this.restTemplate = restTemplate;
+        this.redisTemplate = redisTemplate;
+        this.objectMapper = objectMapper;
+    }
+
+    /*
+     * 上传文件,返回mediaid
+     */
+    public String upload(String filePath) throws DingTalkApiException {
+        String url = "https://oapi.dingtalk.com/media/upload?access_token={0}";
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
+
+        MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
+        FileSystemResource fileSystemResource = new FileSystemResource(filePath);
+        multiValueMap.add("media", fileSystemResource);
+        multiValueMap.add("type", "image");
+
+        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(multiValueMap, httpHeaders);
+
+        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class,
+                getOapiToken());
+        if (responseEntity.getStatusCode().is2xxSuccessful()) {
+            try {
+                JsonNode node = objectMapper.readTree(responseEntity.getBody());
+                int errcode = node.get("errcode").asInt(-1);
+                if (errcode != 0) {
+                    throw new DingTalkApiException(node.get("errmsg").asText());
+                } else {
+                    return node.get("media_id").asText();
+                }
+            } catch (JsonProcessingException e) {
+                throw new DingTalkApiException(e.getMessage());
+            }
+        } else {
+            throw new DingTalkApiException(
+                    "钉钉api调用失败--" + responseEntity.getStatusCodeValue() + "--" + responseEntity.getBody());
+        }
+    }
+
+    /*
+     * 使用钉钉机器人发送消息
+     */
+    public void sendMsgWithRobot(Object msgParam) 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> request = new HashMap<>();
+        try {
+            request.put("msgParam", objectMapper.writeValueAsString(msgParam));
+            request.put("msgKey", "sampleImageMsg");
+            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.toString());
+        }
+    }
+
+    /*
+     * 获取api access token
+     * 钉钉的access token的有效期是2个小时,并且对获取token的接口有熔断机制
+     * 这里使用redis缓存token,缓存有效期设置为90分钟。
+     * 优先从redis获取,redis中不存在的情况下从
+     */
+    public String getApiToken() throws DingTalkApiException {
+        Object token = redisTemplate.opsForValue().get(API_ACCESS_TOKEN_REDIS_KEY);
+        if (token == null) {
+            // 调用api获取accessToken
+            String url = "https://api.dingtalk.com/v1.0/oauth2/accessToken";
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+
+            Map<String, Object> request = new HashMap<>();
+            request.put("appKey", appKey);
+            request.put("appSecret", appSecret);
+            HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<Map<String, Object>>(request, headers);
+
+            // 发送请求
+            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
+            log.info("请求新的api accessToken--{}", responseEntity.getStatusCode());
+            if (responseEntity.getStatusCode().is2xxSuccessful()) {
+                try {
+                    JsonNode node = objectMapper.readTree(responseEntity.getBody());
+                    if (node.get("accessToken") != null) {
+                        // 添加到缓存,缓存时间比返回的时间短10秒
+                        token = node.get("accessToken").asText();
+                        redisTemplate.boundValueOps(API_ACCESS_TOKEN_REDIS_KEY)
+                                .set(token, Duration.ofSeconds(node.get("expireIn").asLong(3600L) - 10L));
+                    } else {
+                        throw new DingTalkApiException("api返回格式有误--" + responseEntity.getBody());
+                    }
+                } catch (JsonProcessingException e) {
+                    throw new DingTalkApiException(e.getMessage());
+                }
+            } else {
+                throw new DingTalkApiException(
+                        "钉钉api调用失败--" + responseEntity.getStatusCodeValue() + "--" + responseEntity.getBody());
+            }
+        }
+        return (String) token;
+    }
+
+    /*
+     * 获取oapi access token
+     */
+    public Object getOapiToken() throws DingTalkApiException {
+        Object token = redisTemplate.opsForValue().get(OAPI_ACCESS_TOKEN_REDIS_KEY);
+
+        if (token == null) {
+            String url = "https://oapi.dingtalk.com/gettoken?appkey={0}&appsecret={1}";
+
+            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, appKey, appSecret);
+            log.info("请求新的oapi accessToken--{}--{}", responseEntity.getStatusCode(), responseEntity.getBody());
+            if (responseEntity.getStatusCode().is2xxSuccessful()) {
+                try {
+                    JsonNode node = objectMapper.readTree(responseEntity.getBody());
+                    int code = node.get("errcode").asInt(-1);
+                    if (code == 0) {
+                        // 添加到缓存
+                        token = node.get("access_token").asText();
+                        redisTemplate.boundValueOps(OAPI_ACCESS_TOKEN_REDIS_KEY)
+                                .set(token, Duration.ofSeconds(node.get("expires_in").asLong(3600L) - 10L));
+                    } else {
+                        throw new DingTalkApiException(node.get("errmsg").asText());
+                    }
+                } catch (JsonProcessingException e) {
+                    throw new DingTalkApiException(e.getMessage());
+                }
+            } else {
+                throw new DingTalkApiException(
+                        "钉钉api调用失败--" + responseEntity.getStatusCodeValue() + "--" + responseEntity.getBody());
+            }
+        }
+        return (String) token;
+    }
+}

+ 8 - 0
dingtalk_auto/src/main/java/com/nokia/common/dingtalk/exception/DingTalkApiException.java

@@ -0,0 +1,8 @@
+package com.nokia.common.dingtalk.exception;
+
+public class DingTalkApiException extends Throwable {
+
+    public DingTalkApiException(String msg) {
+        super(msg);
+    }
+}

+ 126 - 0
dingtalk_auto/src/main/java/com/nokia/common/jsch/SftpUtil.java

@@ -0,0 +1,126 @@
+package com.nokia.common.jsch;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.SftpException;
+import com.jcraft.jsch.ChannelSftp.LsEntry;
+
+public class SftpUtil {
+
+    private JSch jSch = new JSch();
+    private Session session = null;
+    private ChannelSftp channelSftp = null;
+
+    private String host;
+    private int port = 22;
+    private String user;
+    private String password;
+
+    /**
+     * sftp工具
+     * 
+     * @param src 源路径,必须是一个文件路径
+     * @param dst 目的路径,也应该是一个文件路径
+     * @throws IOException
+     * @throws JSchException
+     * @throws SftpException
+     */
+    public void get(String src, String dst) throws IOException, JSchException, SftpException {
+        try (OutputStream outputStream = Files.newOutputStream(Paths.get(dst));) {
+            channelSftp.get(src, dst);
+        }
+    }
+
+    @SuppressWarnings("rawtypes")
+    public List<String> ls(String path) throws SftpException {
+        List<String> files = new ArrayList<>();
+        Vector ls = channelSftp.ls(path);
+        for (Object object : ls) {
+            String fileName = ((LsEntry) object).getFilename();
+            if (".".equals(fileName) || "..".equals(fileName)) {
+                continue;
+            }
+            files.add(fileName);
+        }
+        return files;
+    }
+
+    /**
+     * 建立连接通道
+     * 
+     * @throws JSchException
+     */
+    public void connect() throws JSchException {
+        // 仅在第一次使用时初始化
+        if (channelSftp == null) {
+            sessionConnect();
+            channelSftp = (ChannelSftp) session.openChannel("sftp");
+        }
+        if (!channelSftp.isConnected()) {
+            channelSftp.connect();
+        }
+    }
+
+    /**
+     * 断开连接
+     */
+    public void disconnect() {
+        if (channelSftp != null) {
+            channelSftp.disconnect();
+        }
+        if (session != null) {
+            session.disconnect();
+        }
+        channelSftp = null;
+        session = null;
+        jSch = null;
+    }
+
+    /**
+     * 建立会话
+     * 
+     * @throws JSchException
+     */
+    private void sessionConnect() throws JSchException {
+        // 仅在第一次使用时初始化
+        if (session == null) {
+            this.session = jSch.getSession(user, host, port);
+            this.session.setPassword(password);
+            this.session.setUserInfo(new UserInfoImpl());
+            this.session.setConfig("StrictHostKeyChecking", "no");
+        }
+        if (!session.isConnected()) {
+            session.connect();
+        }
+    }
+
+    public SftpUtil setHost(String host) {
+        this.host = host;
+        return this;
+    }
+
+    public SftpUtil setPort(int port) {
+        this.port = port;
+        return this;
+    }
+
+    public SftpUtil setUser(String user) {
+        this.user = user;
+        return this;
+    }
+
+    public SftpUtil setPassword(String password) {
+        this.password = password;
+        return this;
+    }
+}

+ 37 - 0
dingtalk_auto/src/main/java/com/nokia/common/jsch/UserInfoImpl.java

@@ -0,0 +1,37 @@
+package com.nokia.common.jsch;
+
+import com.jcraft.jsch.UserInfo;
+
+public class UserInfoImpl implements UserInfo {
+
+    @Override
+    public String getPassphrase() {
+        return null;
+    }
+
+    @Override
+    public String getPassword() {
+        return null;
+    }
+
+    @Override
+    public boolean promptPassphrase(String arg0) {
+        return false;
+    }
+
+    @Override
+    public boolean promptPassword(String arg0) {
+        return false;
+    }
+
+    @Override
+    public boolean promptYesNo(String arg0) {
+        return true;
+    }
+
+    @Override
+    public void showMessage(String arg0) {
+
+    }
+
+}

+ 15 - 0
dingtalk_auto/src/main/java/com/nokia/dingtalk_auto/DingTalkAutoApplication.java

@@ -0,0 +1,15 @@
+package com.nokia.dingtalk_auto;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/*
+ * 程序启动入口
+ */
+@SpringBootApplication
+public class DingTalkAutoApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(DingTalkAutoApplication.class, args);
+    }
+}

+ 14 - 0
dingtalk_auto/src/main/java/com/nokia/dingtalk_auto/config/RestTemplateConfig.java

@@ -0,0 +1,14 @@
+package com.nokia.dingtalk_auto.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestTemplateConfig {
+
+    @Bean
+    public RestTemplate restTemplate() {
+        return new RestTemplate();
+    }
+}

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

@@ -0,0 +1,8 @@
+package com.nokia.dingtalk_auto.service.task;
+
+/**
+ * 向钉钉群发送
+ */
+public class TslTaskService {
+    
+}

+ 8 - 0
dingtalk_auto/src/main/resources/application-dev.properties

@@ -0,0 +1,8 @@
+
+# Redis配置
+spring.redis.host=127.0.0.1
+spring.redis.port=6379
+
+dingTalk.appKey=dingothmdq6opv6hjrm5
+dingTalk.appSecret=SeoyAwUnzFIFY4j4CX089HJ0i-pj1BIzByB3AZcnbCQaq94lZvazFpfEGGQwPznc
+dingTalk.openConversationId=cidcWmmFwduUTDB3G0vPNOldQ==

+ 1 - 0
dingtalk_auto/src/main/resources/application-pro.properties

@@ -0,0 +1 @@
+

+ 5 - 0
dingtalk_auto/src/main/resources/application.properties

@@ -0,0 +1,5 @@
+spring.profiles.active=dev
+
+server.port=11111
+
+logging.level.com.nokia=debug

+ 25 - 0
dingtalk_auto/src/test/java/com/nokia/common/jsch/SftpUtilTest.java

@@ -0,0 +1,25 @@
+package com.nokia.common.jsch;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.SftpException;
+
+public class SftpUtilTest {
+    @Test
+    void testGet() throws IOException, JSchException, SftpException {
+        SftpUtil sftpUtil = new SftpUtil()
+                .setHost("133.96.94.105")
+                .setPort(22)
+                .setUser("do")
+                .setPassword("Richr00t");
+        // sftpUtil.get("/data/report_auto/test/1_客服投诉清单各地市投诉率情况.png", "D:\\aaa\\1_客服投诉清单各地市投诉率情况.png");
+        sftpUtil.connect();
+        List<String> ls = sftpUtil.ls("/data/report_auto/test");
+        sftpUtil.disconnect();
+        System.out.println(ls.toString());
+    }
+}

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

@@ -0,0 +1,54 @@
+# 开发文档
+
+## 钉钉API相关汇总
+
+[钉钉api地址](https://open.dingtalk.com/document/orgapp/api-overview)
+
+### 获取AccesstokenAPI
+
+- [获取企业内部应用的accessToken](https://open.dingtalk.com/document/orgapp/obtain-the-access_token-of-an-internal-app)
+
+```http
+POST https://api.dingtalk.com/v1.0/oauth2/accessToken HTTP/1.1
+Content-Type:application/json
+
+{
+  "appKey" : "dingothmdq6opv6hjrm5",
+  "appSecret" : "SeoyAwUnzFIFY4j4CX089HJ0i-pj1BIzByB3AZcnbCQaq94lZvazFpfEGGQwPznc"
+}
+```
+
+- [获取企业内部应用的access_token--旧版api](https://open.dingtalk.com/document/orgapp/obtain-orgapp-token)
+
+```http
+GET https://oapi.dingtalk.com/gettoken?appkey=dingothmdq6opv6hjrm5&appsecret=SeoyAwUnzFIFY4j4CX089HJ0i-pj1BIzByB3AZcnbCQaq94lZvazFpfEGGQwPznc
+```
+
+- [机器人发送群聊消息](https://open.dingtalk.com/document/orgapp/the-robot-sends-a-group-message)
+
+```http
+POST https://api.dingtalk.com/v1.0/robot/groupMessages/send HTTP/1.1
+x-acs-dingtalk-access-token:String
+Content-Type:application/json
+
+{
+  "msgParam" : "String",
+  "msgKey" : "String",
+  "openConversationId" : "String",
+  "robotCode" : "String",
+}
+```
+
+### 上传媒体文件
+
+[上传媒体文件](https://open.dingtalk.com/document/orgapp/upload-media-files)
+
+### 参数概念及获取方式
+
+#### appKey appSecret
+
+应用key 和Secret
+
+#### conversationId
+
+唯一标识一个群