暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

K8s利用etcd定时备份集群结合钉钉机器人通知

在这里插入图片描述

如何通过脚本的方式进行K8s集群的备份

查看K8s中master节点中etcd集群的状态

    kubectl get pods -n kube-system | grep etcd
    image-20240913090214864

    由于使用的etcd服务是K8s搭建时自身携带的,并不是独立搭建的etcd集群信息。使用 K8s 搭建集群时,etcd
    是 Kubernetes 集成的一个重要组件因此需要查看此K8s中etcd的配置信息如何。执行下面的命令。

      kubectl -n kube-system get pods # 首先执行这个命令查看一下master节点中etcd服务的name信息
      image-20240913090804336

      拿到信息之后需要将   kubectl -n kube-system describe pod这个命令修改成上述pod的名称信息进行得到运行的日志,需要从中获取到想对应的密钥信息,此步不可缺少不然执行备份命令时候会出现备份文件停留在备份过程中状态。

        kubectl -n kube-system describe pod etcd-xiaolumaster1
        image-20240913091104803

        此处是上述命令执行结果中详细信息做一下展示,我在下面的信息中使用了「提取这个密钥信息」这个标注出了信息的位置在哪里。

          [root@master01 ~]# kubectl -n kube-system describe pod etcd-xiaolumaster1
          Name: etcd-xiaolumaster1
          Namespace: kube-system
          Priority: 2000001000
          Priority Class Name: system-node-critical
          Node: xiaolumaster1/192.168.65.10
          Start Time: Thu, 12 Sep 2024 15:58:19 +0800
          Labels: component=etcd
          tier=control-plane
          Annotations: kubeadm.kubernetes.io/etcd.advertise-client-urls: https://192.168.65.10:2379
          kubernetes.io/config.hash: 891d5c4b32621c9c00025c4596a9fa98
          kubernetes.io/config.mirror: 891d5c4b32621c9c00025c4596a9fa98
          kubernetes.io/config.seen: 2024-09-12T15:54:42.469300940+08:00
          kubernetes.io/config.source: file
          Status: Running
          SeccompProfile: RuntimeDefault
          IP: 192.168.65.10
          IPs:
          IP: 192.168.65.10
          Controlled By: Node/xiaolumaster1
          Containers:
          etcd:
          Container ID: containerd://53f915abc40b5adc16e3c6e722c73054fb6538620bb3a14755951a5d093f1658
          Image: registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.5.7-0
          Image ID: registry.cn-hangzhou.aliyuncs.com/google_containers/etcd@sha256:e85dab14e03d2468bedd3f908898982ed0ef2622d3764cc7746eb51555fae06e
          Port: <none>
          Host Port: <none>
          Command:
          etcd
          --advertise-client-urls=https://192.168.65.10:2379
          --cert-file=/etc/kubernetes/pki/etcd/server.crt ### 提取这个密钥信息
          --client-cert-auth=true
          --data-dir=/var/lib/etcd
          --experimental-initial-corrupt-check=true
          --experimental-watch-progress-notify-interval=5s
          --initial-advertise-peer-urls=https://192.168.65.10:2380
          --initial-cluster=xiaolumaster1=https://192.168.65.10:2380
          --key-file=/etc/kubernetes/pki/etcd/server.key ### 提取这个密钥信息
          --listen-client-urls=https://127.0.0.1:2379,https://192.168.65.10:2379
          --listen-metrics-urls=http://127.0.0.1:2381
          --listen-peer-urls=https://192.168.65.10:2380
          --name=xiaolumaster1
          --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
          --peer-client-cert-auth=true
          --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
          --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
          --snapshot-count=10000
          --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt ### 提取这个密钥信息 虽然上面有一个和这个一样的,但是不提取 --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt 这个的原因是 --trusted-ca-file:这个参数用于为 etcd 客户端连接提供 CA 证书,确保客户端能够验证服务器的身份。因此,这个 CA 证书是客户端与服务器通信时使用的。而--peer-trusted-ca-file:这个参数用于 etcd 集群内节点之间(peer-to-peer)的通信。它确保节点之间能够相互验证身份,使用的是集群内节点之间的 CA 证书。


          --peer-trusted-ca-file:这个参数用于 etcd 集群内节点之间(peer-to-peer)的通信。它确保节点之间能够相互验证身份,使用的是集群内节点之间的 CA 证书。
          State: Running
          Started: Thu, 12 Sep 2024 15:54:55 +0800
          Ready: True
          Restart Count: 0
          Requests:
          cpu: 100m
          memory: 100Mi
          Liveness: http-get http://127.0.0.1:2381/health%3Fexclude=NOSPACE&serializable=true delay=10s timeout=15s period=10s #success=1 #failure=8
          Startup: http-get http://127.0.0.1:2381/health%3Fserializable=false delay=10s timeout=15s period=10s #success=1 #failure=24
          Environment: <none>
          Mounts:
          etc/kubernetes/pki/etcd from etcd-certs (rw)
          var/lib/etcd from etcd-data (rw)
          Conditions:
          Type Status
          Initialized True
          Ready True
          ContainersReady True
          PodScheduled True
          Volumes:
          etcd-certs:
          Type: HostPath (bare host directory volume)
          Path: etc/kubernetes/pki/etcd
          HostPathType: DirectoryOrCreate
          etcd-data:
          Type: HostPath (bare host directory volume)
          Path: var/lib/etcd
          HostPathType: DirectoryOrCreate
          QoS Class: Burstable
          Node-Selectors: <none>
          Tolerations: :NoExecute op=Exists
          Events:
          Type Reason Age From Message
          ---- ------ ---- ---- -------
          Normal Killing 63m kubelet Stopping container etcd
          Normal Pulled 16m (x4 over 47h) kubelet Container image "registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.5.7-0" already present on machine
          Normal Created 16m (x4 over 47h) kubelet Created container etcd
          Normal Started 16m (x4 over 47h) kubelet Started container etcd

          编写备份的脚本shell文件

          文件名称  backup_etcd.sh

            #!/bin/bash
            #时间戳,用来区分不同备份
            timestamp=`date +%Y%m%d-%H%M%S`
            #备份到哪个文件夹
            back_dir="/opt/k8s"
            #etcd集群列表
            endpoints="https://192.168.65.10:2379"
            #etcd证书路径
            cert_file="/etc/kubernetes/pki/etcd/server.crt"
            #etcd证书的key路径
            key_file="/etc/kubernetes/pki/etcd/server.key"
            #ca证书路径
            cacert_file="/etc/kubernetes/pki/etcd/ca.crt"


            mkdir -p $back_dir
            ETCDCTL_API=3 etcdctl \
            --endpoints="${endpoints}" \
            --cert=$cert_file \
            --key=$key_file \
            --cacert=$cacert_file \
            snapshot save $back_dir/snapshot_$timestamp.db
            image-20240913092023027

            赋予文件执行权限并查看权限信息

            这边我使用的上述路径下的文件,所以后续跟上的是这个路径在命令中。

              chmod +x opt/K8sJK/backup_etcd.sh

              查看一下权限是否已经赋予

              image-20240913092559723

              这里给上面的权限信息做一下详细的解释

              1. 「文件类型与权限」: -rwxr-xr-x
                • -
                  :表示这是一个普通文件(不是目录)。
                • rwx
                  :文件所有者(528287
                  ,后面解释)拥有「读」「写」「执行」权限。
                • r-x
                  :文件所属的组(89939
                  )拥有「读」「执行」权限,但没有写权限。
                • r-x
                  :其他用户(即系统中的其他任何人)拥有「读」「执行」权限,但没有写权限。
              2. 「硬链接数量」: 1
                • 表示有 1 个硬链接指向这个文件。

              执行备份文件的命令进行备份的验证

                /opt/K8sJK/backup_etcd.sh
                image-20240913093018414

                在配置的备份文件位置查看备份的信息,时间也可以刚好对上就是刚才的备份文件。

                image-20240913093049167

                对接钉钉机器人进行报备

                创建一下自己的组织获取管理员身份

                钉钉机器人开发平台直达链接

                  https://open.dingtalk.com/document/tutorial/create-a-robot
                  image-20240913093942315

                  具体的配置信息展示

                  image-20240913094356442

                  梳理开发对接钉钉的SDK功能jar包

                  这里采用钉钉官方旧版本SDK基础之上进行二开,后续如果需要进行持续迭代升级会考虑尝试新版本SDK风格进行二开。这里需要说明的是这个只是一个钉钉官方的小demo,二开需要根据自己的需求信息适量的修改和原创。

                    https://github.com/open-dingtalk/org-mini-program-tutorial-java
                    image-20240913125038520

                    钉钉官方的参考文章地址:

                      https://open.dingtalk.com/document/tutorial/create-a-robot

                      修改pom文件中旧版SDK最新地址

                         <dependency>
                        <groupId>com.aliyun</groupId>
                        <artifactId>alibaba-dingtalk-service-sdk</artifactId>
                        <version>2.0.0</version>
                        </dependency>

                        定时任务与@回复消息的大体代码展示

                        定时备份etcd

                          /**
                          * Copyright © 2024年 integration-projects-maven. All rights reserved.
                          * ClassName EtcdBackupTask.java
                          * author 舒一笑 yixiaoshu88@163.com
                          * version 1.0.0
                          * Description etcd定时备份逻辑实现
                          * createTime 2024年09月11日 15:41:00
                          */
                          @Component
                          @Slf4j
                          public class EtcdBackupTask {


                          @Value("${dingtalk.webhook}")
                          private String dingTalkWebhook;


                          @Value("${shell.script.path}")
                          private String shellScriptPath;


                          @Value("${backup.file.path}")
                          private String backupFilePath;


                          private final RobotsController robotsController;


                          public EtcdBackupTask(RobotsController robotsController) {
                          this.robotsController = robotsController;
                          }


                          @Scheduled(fixedRate = 60000) 每1分钟执行一次备份
                          public void backupEtcd() {
                          log.info("Starting etcd backup process...");


                          try {
                          使用 ProcessBuilder 调用 shell 脚本
                          ProcessBuilder processBuilder = new ProcessBuilder("/bin/bash", shellScriptPath);
                          processBuilder.environment().remove("ETCDCTL_CERT"); 移除可能存在的环境变量
                          processBuilder.redirectErrorStream(true);
                          Process process = processBuilder.start();


                          捕获输出日志
                          String backupLog = captureProcessOutput(process);


                          等待进程完成,设置较长的超时时间,例如10分钟
                          boolean finished = process.waitFor(10, TimeUnit.MINUTES);


                          if (!finished) {
                          如果进程未在规定时间内完成,强制终止并发送警告消息
                          process.destroyForcibly();
                          sendDingTalkMessage("你好,Etcd 备份失败!备份进程超时。");
                          log.error("Etcd backup process timed out.");
                          return;
                          }


                          int exitCode = process.exitValue();


                          if (exitCode == 0) {
                          // String backupFileName = getBackupFileName(backupLog);
                          // log.info("Etcd backup succeeded. Backup file: " + backupFileName);
                          // sendDingTalkMessage("你好,Etcd 备份成功!备份文件已保存,文件名:" + backupFileName);
                          String backupFileName = getBackupFileName(backupLog);
                          log.info("Etcd backup succeeded. Backup file: " + backupFileName);


                          获取备份文件状态
                          String snapshotStatus = getSnapshotStatus(backupFileName);
                          log.info("Etcd backup snapshot status: \n" + snapshotStatus);


                          将备份文件名和状态发送到钉钉
                          sendDingTalkMessage("你好,Etcd 备份成功!备份文件已保存,文件名:" + backupFileName + "\n" + snapshotStatus);
                          清理一周前的备份文件
                          cleanOldBackups(backupFilePath);
                          } else {
                          log.error("Etcd backup failed with exit code: " + exitCode);
                          log.error("Etcd backup failed. Error log: \n" + backupLog);
                          sendDingTalkMessage("你好,Etcd 备份失败!错误信息:" + backupLog);
                          }
                          } catch (IOException e) {
                          log.error("IOException occurred during etcd backup process.", e);
                          sendDingTalkMessage("你好,Etcd 备份过程中发生IO异常:" + e.getMessage());
                          } catch (InterruptedException e) {
                          log.error("Backup process interrupted.", e);
                          Thread.currentThread().interrupt(); 恢复中断状态
                          sendDingTalkMessage("你好,Etcd 备份过程中发生中断:" + e.getMessage());
                          } catch (Exception e) {
                          log.error("An unexpected error occurred during etcd backup.", e);
                          sendDingTalkMessage("你好,Etcd 备份过程中发生异常:" + e.getMessage());
                          }
                          }


                          捕获命令的输出和错误流
                          private String captureProcessOutput(Process process) throws IOException {
                          BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                          BufferedReader outputStream = new BufferedReader(new InputStreamReader(process.getInputStream()));
                          StringBuilder logBuilder = new StringBuilder();


                          String line;
                          捕获错误输出
                          while ((line = errorStream.readLine()) != null) {
                          logBuilder.append("ERROR: ").append(line).append("\n");
                          }


                          捕获标准输出
                          while ((line = outputStream.readLine()) != null) {
                          logBuilder.append("OUTPUT: ").append(line).append("\n");
                          }


                          return logBuilder.toString();
                          }


                          private void sendDingTalkMessage(String message) {
                          robotsController.sendMessage(dingTalkWebhook, message);
                          }


                          从日志中提取备份文件名的逻辑,假设日志里有明确的文件路径
                          private String getBackupFileName(String backupLog) {
                          使用正则表达式提取 "Snapshot saved at" 后的文件路径
                          String regex = "Snapshot saved at (.+\\.db)"; 匹配最终保存的 ".db" 文件路径
                          Pattern pattern = Pattern.compile(regex);
                          Matcher matcher = pattern.matcher(backupLog);


                          if (matcher.find()) {
                          return matcher.group(1).trim(); 返回捕获的文件路径
                          }


                          return "unknown"; 如果未找到则返回 unknown
                          }


                          获取快照状态的方法
                          private String getSnapshotStatus(String backupFileName) throws IOException, InterruptedException {
                          执行 `etcdctl snapshot status` 命令
                          ProcessBuilder processBuilder = new ProcessBuilder("etcdctl", "--write-out=table", "snapshot", "status", backupFileName);
                          Process process = processBuilder.start();


                          String snapshotStatus = captureProcessOutput(process);
                          process.waitFor(1, TimeUnit.MINUTES);


                          清理掉 "OUTPUT:" 前缀,但保留行内容
                          StringBuilder cleanedOutput = new StringBuilder();
                          String[] lines = snapshotStatus.split("\n");
                          for (String line : lines) {
                          // 移除 "OUTPUT:" 前缀,但保留行的内容
                          if (line.startsWith("OUTPUT:")) {
                          cleanedOutput.append(line.replace("OUTPUT: ", "")).append("\n");
                          } else if (!line.contains("ERROR") && !line.contains("Deprecated")) {
                          cleanedOutput.append(line).append("\n");
                          }
                          }


                          return cleanedOutput.toString();
                          }


                          // public void cleanOldBackups(String backupDirectory) throws IOException {
                          // try (Stream<Path> files = Files.list(Paths.get(backupDirectory))) {
                          // files.forEach(file -> {
                          // try {
                          // BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class);
                          // LocalDateTime fileTime = LocalDateTime.ofInstant(attr.lastModifiedTime().toInstant(), ZoneId.systemDefault());
                          //
                          // // 检查文件是否超过7天
                          // if (fileTime.isBefore(LocalDateTime.now().minusDays(7))) {
                          // Files.delete(file);
                          // log.info("Deleted old backup file: " + file.getFileName());
                          // }
                          // } catch (IOException e) {
                          // log.error("Error while deleting old backup file: " + file.getFileName(), e);
                          // }
                          // });
                          // }
                          // }


                          public void cleanOldBackups(String backupDirectory) throws IOException {
                          // 存储删除的文件名
                          StringBuilder deletedFiles = new StringBuilder("当前保留规则为只保留10分钟内文件,删除的备份文件列表:\n");


                          // 删除过期文件
                          try (Stream<Path> files = Files.list(Paths.get(backupDirectory))) {
                          files.forEach(file -> {
                          try {
                          BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class);
                          LocalDateTime fileTime = LocalDateTime.ofInstant(attr.lastModifiedTime().toInstant(), ZoneId.systemDefault());


                          // 检查文件是否超过10分钟
                          if (fileTime.isBefore(LocalDateTime.now().minusMinutes(10))) {
                          Files.delete(file);
                          log.info("Deleted old backup file: " + file.getFileName());
                          deletedFiles.append(file.getFileName()).append("\n"); // 将删除的文件名添加到消息中
                          }
                          } catch (IOException e) {
                          log.error("Error while deleting old backup file: " + file.getFileName(), e);
                          }
                          });
                          }


                          // // 构建剩余文件列表
                          // StringBuilder remainingFiles = new StringBuilder("当前剩余的备份文件列表:\n");
                          // try (Stream<Path> remaining = Files.list(Paths.get(backupDirectory))) {
                          // remaining.forEach(file -> remainingFiles.append(file.getFileName()).append("\n"));
                          // }


                          // 如果有删除的文件,添加到钉钉消息中
                          if (deletedFiles.length() > "当前保留规则为只保留10分钟内文件,删除的备份文件列表:\n".length()) {
                          // 将删除文件和剩余文件列表集成到消息
                          // sendDingTalkMessage(deletedFiles.toString() + "\n" + remainingFiles.toString());
                          sendDingTalkMessage(deletedFiles.toString());
                          } else {
                          // 没有删除的文件,也要展示剩余文件
                          // sendDingTalkMessage("没有删除任何备份文件,所有备份文件都在10分钟以内。\n" + remainingFiles.toString());
                          sendDingTalkMessage("没有删除任何备份文件,所有备份文件都在10分钟以内。");
                          }
                          }
                          }

                          @回复消息代码展示

                            @RestController
                            @Slf4j
                            /**
                            * 实现了机器人的简单问答功能
                            */
                            public class RobotsController {


                            private static final String Hello = "你好";


                            @Autowired
                            private RobotsController robotsController;




                            @Value("${dingtalk.webhook}")
                            private String dingTalkWebhook;


                            @Value("${backup.file.path}")
                            private String backupFilePath;


                            @RequestMapping(value = "/robots", method = RequestMethod.POST)
                            public String helloRobots(@RequestBody(required = false) JSONObject json) throws ApiException {
                            System.out.println(json);
                            String content = json.getJSONObject("text").get("content").toString().replaceAll(" ", "");
                            // 获取用户手机号,用于发送@消息
                            // String mobile = getUserMobile(json.getString("senderStaffId"));
                            String sessionWebhook = json.getString("sessionWebhook");
                            DingTalkClient client = new DefaultDingTalkClient(sessionWebhook);


                            if (content.contains(Hello)) {
                            hello(client,backupFilePath);
                            } else {
                            learning(client);
                            }
                            return null;
                            }


                            /**
                            * 发送钉钉通知消息
                            */
                            public void sendMessage(String webhookUrl, String messageContent) {
                            DingTalkClient client = new DefaultDingTalkClient(webhookUrl);
                            try {
                            OapiRobotSendRequest request = new OapiRobotSendRequest();
                            request.setMsgtype("text");
                            OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
                            text.setContent(messageContent);
                            request.setText(text);
                            OapiRobotSendResponse response = client.execute(request);
                            System.out.println(response.getBody());
                            } catch (ApiException e) {
                            e.printStackTrace();
                            }
                            }


                            /**
                            * 回答其他
                            */
                            private void learning(DingTalkClient client) {
                            try {
                            OapiRobotSendRequest request = new OapiRobotSendRequest();
                            request.setMsgtype("text");
                            OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
                            text.setContent("我是问好机器人 ~");
                            request.setText(text);
                            // OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
                            // at.setAtMobiles(Arrays.asList(mobile));
                            // isAtAll类型如果不为Boolean,请升级至最新SDK
                            // at.setIsAtAll(false);
                            // request.setAt(at);
                            OapiRobotSendResponse response = client.execute(request);
                            System.out.println(response.getBody());
                            } catch (ApiException e) {
                            e.printStackTrace();
                            }
                            }


                            /**
                            * 回答你好
                            */
                            public void hello(DingTalkClient client,String backupDirectory) throws ApiException {
                            OapiRobotSendRequest request = new OapiRobotSendRequest();
                            request.setMsgtype("text");
                            OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
                            text.setContent("哈哈,稍等马上给你当前备份的剩余文件信息~");
                            request.setText(text);
                            // // OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
                            // // at.setAtMobiles(Arrays.asList(mobile));
                            // // isAtAll类型如果不为Boolean,请升级至最新SDK
                            // // at.setIsAtAll(false);
                            // // request.setAt(at);
                            OapiRobotSendResponse response = client.execute(request);
                            System.out.println(response.getBody());
                            // 构建剩余文件列表
                            StringBuilder remainingFiles = new StringBuilder("当前剩余的备份文件列表:\n");
                            try (Stream<Path> remaining = Files.list(Paths.get(backupDirectory))) {
                            remaining.forEach(file -> remainingFiles.append(file.getFileName()).append("\n"));
                            sendDingTalkMessage(remainingFiles.toString());
                            } catch (IOException e) {
                            throw new RuntimeException(e);
                            }
                            }


                            private void sendDingTalkMessage(String message) {
                            robotsController.sendMessage(dingTalkWebhook, message);
                            }
                            }

                            yml配置文件展示

                            image-20240913125711558

                            打包成jar部署在master节点服务器中

                            image-20240913125830953

                            在对应位置执行运行脚本

                              nohup java -jar dingtalk-1.0.0.jar > dingtalk.log 2>&1 &
                              image-20240913125948323

                              执行上述命令时候可以在运行位置的目录中得到log日志文件方便查看日志信息

                              image-20240913130035372

                              内网穿透搭建

                              本地搭建的内网穿透工具为开源工具ngrok,为什么使用这个因为这个免费版启动之后会得到一个免费的域名。下面是目前常见的内网穿透解决方案的对比。

                              image-20240913130331396

                              Linux中搭建的参考网址,这里的话就不过多介绍如何注册网站用于等信息,直接百度或者谷歌搜索ngrok之后按照官网的引导就可以创建好自己的账户。下面就是记录一下如何做一个免费域名的搭建步骤。

                                https://dashboard.ngrok.com/get-started/setup/linux
                                image-20240913130632609

                                这块只需要根据官网下面的指示进行操作就可以。「但是介绍一个下载这边取巧的技巧就是可以通过科学的手段进行将ngrok下载下来之后将下面的命令修改一下执行」,我这边是已经把这个ngrok下载之后放在了opt目录下才这么写~

                                  sudo tar -xvzf /opt/ngrok-v3-stable-linux-amd64.tgz -C /usr/local/bin
                                  image-20240914085404716
                                  image-20240914085015125

                                  执行token配置命令

                                    ngrok config add-authtoken 2lxsXmbcgyFXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

                                    执行内网穿透命令,这边执行8080是因为服务的端口我设置的也是8080,两者需要匹配上。

                                      ngrok http http://localhost:8080

                                      内网穿透命令执行成功之后就可以得到一个ngrok分配给你的带通用ssl认证的域名信息,但是这边需要注意一下的是,虽然可以在浏览器中去掉了不安全的告警信息,但是使用HTTP模式的钉钉机器人那边过不了,所以为了实现与机器人即时通讯的功能我们需要部署两个机器人(主要带ssl认证的域名穿透有点贵)。

                                      image-20240914085946226

                                      执行定时备份的服务进行备份

                                        nohup java -jar dingtalk-1.0.0.jar > dingtalk.log 2>&1 &

                                        使用上述命令可以得到一个运行的服务,然后也可以得到想要的日志文件

                                        image-20240914090302420

                                        钉钉机器人的配置

                                        需要在这个位置配置你的域名信息,然后这块由于是ngrok穿透得到的免费域名,上面说了就是过不了钉钉的ssl认证,因此在写的时候就是要把https中的s去掉

                                        image-20240914090632761

                                        完成上述配置之后需要在钉钉客户端中也一样配置机器人的信息,还有就是自己个性化的回复语不然会有报错

                                        image-20240914091112110

                                        如何这边不设置自定义的回复消息,就会出现下面的报错信息。

                                        image-20240914091343386

                                        好了到这里就配置部署好了基本的定时的钉钉消息机器人,给大家看一下效果

                                        image-20240914091430870

                                        即时聊天机器人配置

                                        下面就是给大家演示一下这边这个机器人如何配置,没错这边就是需要使用到钉钉机器人的另一个Stream流方式,从我下面的名字定义应该也能看出来。

                                        image-20240914091456621

                                        话不多说直接上干货

                                        钉钉Stream模式文章介绍与SDK获取

                                          https://open.dingtalk.com/document/resourcedownload/introduction-to-stream-mode?spm=a2q3p.21071111.0.0.267d65eeHyQGLW
                                            https://open.dingtalk.com/document/orgapp/stream?spm=ding_open_doc.document.0.0.21444169EsPvLY

                                            在上述官方的链接介绍中已经将基本操作说比较清楚了,这边有几点主要主要的我这里说明一下。如果你是要为公司组织开发一定要获取到开发者权限不然做不了。

                                            image-20240914092100734

                                            可以更多的参考一下,下面这篇文章的介绍。

                                              https://open.dingtalk.com/document/orgapp/the-application-robot-in-the-enterprise-sends-group-chat-messages?spm=ding_open_doc.document.0.0.21444169EsPvLY
                                              image-20240914092347065

                                              官方演示demo下载地址

                                                https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20240221/buuola/stream-robot-group-message-quick-start.zip?spm=ding_open_doc.document.0.0.1f621cc0qRNFho&file=stream-robot-group-message-quick-start.zip

                                                官方演示需要的依赖

                                                   <dependencies>
                                                  <dependency>
                                                  <groupId>com.dingtalk.open</groupId>
                                                  <artifactId>dingtalk-stream</artifactId>
                                                  <version>1.1.0</version>
                                                  </dependency>
                                                  <dependency>
                                                  <groupId>org.springframework.boot</groupId>
                                                  <artifactId>spring-boot-starter-web</artifactId>
                                                  <version>2.7.4</version>
                                                  </dependency>


                                                  <dependency>
                                                  <groupId>com.aliyun</groupId>
                                                  <artifactId>dingtalk</artifactId>
                                                  <version>2.0.76</version>
                                                  </dependency>


                                                  <dependency>
                                                  <groupId>org.projectlombok</groupId>
                                                  <artifactId>lombok</artifactId>
                                                  <version>1.18.26</version>
                                                  <optional>true</optional>
                                                  </dependency>


                                                  <dependency>
                                                  <groupId>com.alibaba</groupId>
                                                  <artifactId>fastjson</artifactId>
                                                  <version>1.2.68</version>
                                                  </dependency>
                                                  </dependencies>

                                                  官方演示的代码

                                                    import com.alibaba.fastjson.JSONObject;


                                                    import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest;
                                                    import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
                                                    import com.aliyun.dingtalkrobot_1_0.Client;
                                                    import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendHeaders;
                                                    import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendRequest;
                                                    import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendResponse;
                                                    import com.aliyun.tea.TeaException;
                                                    import com.aliyun.teaopenapi.models.Config;
                                                    import com.dingtalk.open.app.api.OpenDingTalkClient;
                                                    import com.dingtalk.open.app.api.OpenDingTalkStreamClientBuilder;
                                                    import com.dingtalk.open.app.api.callback.OpenDingTalkCallbackListener;
                                                    import com.dingtalk.open.app.api.security.AuthClientCredential;
                                                    import lombok.extern.slf4j.Slf4j;


                                                    import java.util.Objects;


                                                    @Slf4j
                                                    public class sendGroupMessage {


                                                    public static final String CLIENT_ID = "< your client id>";


                                                    public static final String CLIENT_SECRET = "<your client secret>";


                                                    public static void main(String[] args) throws Exception {
                                                    OpenDingTalkClient client = OpenDingTalkStreamClientBuilder
                                                    .custom()
                                                    .credential(new AuthClientCredential(CLIENT_ID, CLIENT_SECRET))
                                                    .registerCallbackListener("/v1.0/im/bot/messages/get", new RobotMsgCallbackConsumer())
                                                    .build();
                                                    client.start();
                                                    }


                                                    public static class RobotMsgCallbackConsumer implements OpenDingTalkCallbackListener<JSONObject, JSONObject> {


                                                    /*
                                                    * @param request
                                                    * @return
                                                    */
                                                    @Override
                                                    public JSONObject execute(JSONObject request) {
                                                    String userId = request.get("senderStaffId").toString();
                                                    String content = request.getJSONObject("text").getString("content");
                                                    String openConversationId = request.get("conversationId").toString();
                                                    String robotCode = request.get("robotCode").toString();
                                                    log.info("receive bot message from user={}, msg={},openConversationId={},robotCode={} ", userId, content, openConversationId, robotCode);


                                                    OrgGroupSendHeaders orgGroupSendHeaders = new OrgGroupSendHeaders();
                                                    orgGroupSendHeaders.setXAcsDingtalkAccessToken(getToken());


                                                    OrgGroupSendRequest orgGroupSendRequest = new OrgGroupSendRequest();
                                                    orgGroupSendRequest.setMsgKey("sampleText");
                                                    orgGroupSendRequest.setRobotCode(robotCode);


                                                    orgGroupSendRequest.setOpenConversationId(openConversationId);


                                                    JSONObject msgParam = new JSONObject();
                                                    msgParam.put("content", "java-getting-start say : " + "hello");
                                                    orgGroupSendRequest.setMsgParam(msgParam.toJSONString());
                                                    try {
                                                    Config config = new Config();
                                                    config.protocol = "https";
                                                    config.regionId = "central";
                                                    com.aliyun.dingtalkrobot_1_0.Client client = new Client(config);
                                                    OrgGroupSendResponse orgGroupSendResponse = client.orgGroupSendWithOptions(orgGroupSendRequest,
                                                    orgGroupSendHeaders, new com.aliyun.teautil.models.RuntimeOptions());
                                                    if (Objects.isNull(orgGroupSendResponse) || Objects.isNull(orgGroupSendResponse.getBody())) {
                                                    log.error("RobotGroupMessagesService_send orgGroupSendWithOptions return error, response={}",
                                                    orgGroupSendResponse);
                                                    return null;
                                                    }
                                                    return new JSONObject();
                                                    } catch (TeaException e) {
                                                    log.error("RobotGroupMessagesService_send orgGroupSendWithOptions throw TeaException, errCode={}, " +
                                                    "errorMessage={}", e.getCode(), e.getMessage(), e);
                                                    throw e;
                                                    } catch (Exception e) {
                                                    log.error("RobotGroupMessagesService_send orgGroupSendWithOptions throw Exception", e);
                                                    try {
                                                    throw e;
                                                    } catch (Exception ex) {
                                                    throw new RuntimeException(ex);
                                                    }
                                                    }
                                                    }




                                                    public static String getToken() {
                                                    GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest();
                                                    getAccessTokenRequest.setAppKey(CLIENT_ID);
                                                    getAccessTokenRequest.setAppSecret(CLIENT_SECRET);
                                                    Config config = new Config();
                                                    config.protocol = "https";
                                                    config.regionId = "central";
                                                    try {
                                                    com.aliyun.dingtalkoauth2_1_0.Client client = new com.aliyun.dingtalkoauth2_1_0.Client(config);
                                                    GetAccessTokenResponse accessToken = client.getAccessToken(getAccessTokenRequest);
                                                    return accessToken.getBody().getAccessToken();
                                                    } catch (Exception e) {
                                                    throw new RuntimeException(e);
                                                    }
                                                    }
                                                    }
                                                    }

                                                    按照上述官方的演示说明就可以得到基本的效果

                                                    image-20240914092609142

                                                    下面介绍一下交互我做出的修改

                                                    我在原先基础上将这边代码做了自身效果的修改,从而实现将我我@之后能拿到我想要的信息

                                                    image-20240914092714332
                                                      @Override
                                                      public JSONObject execute(JSONObject request) {
                                                      String userId = request.get("senderStaffId").toString();
                                                      String content = request.getJSONObject("text").getString("content");
                                                      String openConversationId = request.get("conversationId").toString();
                                                      String robotCode = request.get("robotCode").toString();
                                                      log.info("receive bot message from user={}, msg={},openConversationId={},robotCode={} ", userId, content, openConversationId, robotCode);


                                                      OrgGroupSendHeaders orgGroupSendHeaders = new OrgGroupSendHeaders();
                                                      orgGroupSendHeaders.setXAcsDingtalkAccessToken(getToken());


                                                      OrgGroupSendRequest orgGroupSendRequest = new OrgGroupSendRequest();
                                                      orgGroupSendRequest.setMsgKey("sampleText");
                                                      orgGroupSendRequest.setRobotCode(robotCode);
                                                      orgGroupSendRequest.setOpenConversationId(openConversationId);


                                                      // 构建剩余文件列表
                                                      StringBuilder remainingFiles = new StringBuilder("当前剩余的备份文件列表:\n");
                                                      try (Stream<Path> remaining = Files.list(Paths.get("/opt/k8s"))) {
                                                      remaining.forEach(file -> remainingFiles.append(file.getFileName()).append("\n"));
                                                      } catch (IOException e) {
                                                      throw new RuntimeException(e);
                                                      }


                                                      // 将生成的备份文件列表作为消息内容发送
                                                      JSONObject msgParam = new JSONObject();
                                                      msgParam.put("content", "钉钉机器人说: " + remainingFiles.toString());
                                                      orgGroupSendRequest.setMsgParam(msgParam.toJSONString());


                                                      try {
                                                      Config config = new Config();
                                                      config.protocol = "https";
                                                      config.regionId = "central";
                                                      Client client = new Client(config);
                                                      OrgGroupSendResponse orgGroupSendResponse = client.orgGroupSendWithOptions(orgGroupSendRequest,
                                                      orgGroupSendHeaders, new com.aliyun.teautil.models.RuntimeOptions());
                                                      if (Objects.isNull(orgGroupSendResponse) || Objects.isNull(orgGroupSendResponse.getBody())) {
                                                      log.error("RobotGroupMessagesService_send orgGroupSendWithOptions return error, response={}", orgGroupSendResponse);
                                                      return null;
                                                      }
                                                      return new JSONObject();
                                                      } catch (TeaException e) {
                                                      log.error("RobotGroupMessagesService_send orgGroupSendWithOptions throw TeaException, errCode={}, errorMessage={}", e.getCode(), e.getMessage(), e);
                                                      throw e;
                                                      } catch (Exception e) {
                                                      log.error("RobotGroupMessagesService_send orgGroupSendWithOptions throw Exception", e);
                                                      throw new RuntimeException(e);
                                                      }
                                                      }

                                                      这里需要注意一下的是如果说你希望@之后能拿到自己想要的效果那么需要在,这边也进行一下修改,提出一下上述demo中的演示效果

                                                      image-20240914092840487

                                                      配置文件中配置的app.appKey和 robot.code,两者是一样的东西

                                                      image-20240914093004398

                                                      我这边就是直接把我的逻辑类顺手写在了这里

                                                      image-20240914093101972
                                                      8e8fd4b22cdf60c5d9806681501c1db2

                                                      最后的话打包这边需要给官方的POM做一下修改不然打包不出可运行的jar

                                                        <?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>org.springframework.boot</groupId>
                                                        <artifactId>spring-boot-starter-parent</artifactId>
                                                        <version>2.4.2</version>
                                                        <relativePath/>
                                                        </parent>


                                                        <groupId>com.dingtalk</groupId>
                                                        <artifactId>stream-robot-group-message-quick-start</artifactId>
                                                        <version>1.0-SNAPSHOT</version>
                                                        <packaging>jar</packaging>


                                                        <properties>
                                                        <maven.compiler.source>8</maven.compiler.source>
                                                        <maven.compiler.target>8</maven.compiler.target>
                                                        </properties>


                                                        <dependencies>
                                                        <dependency>
                                                        <groupId>com.dingtalk.open</groupId>
                                                        <artifactId>dingtalk-stream</artifactId>
                                                        <version>1.1.0</version>
                                                        </dependency>
                                                        <dependency>
                                                        <groupId>org.springframework.boot</groupId>
                                                        <artifactId>spring-boot-starter-web</artifactId>
                                                        </dependency>


                                                        <dependency>
                                                        <groupId>org.slf4j</groupId>
                                                        <artifactId>slf4j-api</artifactId>
                                                        <version>1.7.7</version>
                                                        </dependency>
                                                        <dependency>
                                                        <groupId>org.projectlombok</groupId>
                                                        <artifactId>lombok</artifactId>
                                                        <version>1.18.26</version>
                                                        <optional>true</optional>
                                                        </dependency>
                                                        <dependency>
                                                        <groupId>com.aliyun</groupId>
                                                        <artifactId>dingtalk</artifactId>
                                                        <version>1.5.59</version>
                                                        </dependency>
                                                        <dependency>
                                                        <groupId>org.asynchttpclient</groupId>
                                                        <artifactId>async-http-client</artifactId>
                                                        <version>2.12.3</version>
                                                        <exclusions>
                                                        <exclusion>
                                                        <groupId>io.netty</groupId>
                                                        <artifactId>netty-common</artifactId>
                                                        </exclusion>
                                                        <exclusion>
                                                        <groupId>io.netty</groupId>
                                                        <artifactId>netty-resolver</artifactId>
                                                        </exclusion>
                                                        </exclusions>
                                                        </dependency>
                                                        <dependency>
                                                        <groupId>org.apache.httpcomponents</groupId>
                                                        <artifactId>httpclient</artifactId>
                                                        <version>4.5.12</version>
                                                        </dependency>
                                                        <dependency>
                                                        <groupId>org.apache.httpcomponents</groupId>
                                                        <artifactId>httpmime</artifactId>
                                                        <version>4.5.12</version>
                                                        </dependency>
                                                        <dependency>
                                                        <groupId>org.apache.httpcomponents</groupId>
                                                        <artifactId>httpcore</artifactId>
                                                        <version>4.4.13</version>
                                                        </dependency>
                                                        <dependency>
                                                        <groupId>com.aliyun</groupId>
                                                        <artifactId>alibaba-dingtalk-service-sdk</artifactId>
                                                        <version>2.0.0</version>
                                                        </dependency>
                                                        <dependency>
                                                        <groupId>com.alibaba</groupId>
                                                        <artifactId>fastjson</artifactId>
                                                        <version>1.2.68</version>
                                                        </dependency>
                                                        </dependencies>


                                                        <build>
                                                        <plugins>
                                                        <plugin>
                                                        <groupId>org.springframework.boot</groupId>
                                                        <artifactId>spring-boot-maven-plugin</artifactId>
                                                        <configuration>
                                                        <mainClass>org.example.Application</mainClass>
                                                        </configuration>
                                                        </plugin>
                                                        </plugins>
                                                        </build>
                                                        </project>


                                                        执行打包命令拿到可运行的jar上传到可以访问外网的机器之后跑起来就可以进行与机器人交流获取你想要的信息。当然这边你可以使用一下上面说的命令后台启动的方式。

                                                          nohup java -jar stream-robot-group-message-quick-start-1.0-SNAPSHOT.jar > stream.log 2>&1 &
                                                          image-20240914093314075

                                                          最后运气起来之后就可以@他愉快的交流啦。如果说还有哪些小问题没有解决可以看看钉钉官网的介绍,对了机器人这边一定需要修改之后发布上线才行。

                                                          最后附录几张配置的详情图片

                                                          image-20240914093604847
                                                          image-20240914093632853
                                                          image-20240914093706156
                                                          image-20240914093717290

                                                          写在最后

                                                          好了本次的介绍到这里就结束了,需要注意的是上述的解决方案可以解决没有ssl认证但又可以与机器人交流的功能效果,但是需要注意的是由于上述的两个机器人分别使用了Webhook和Stream两种。这两种钉钉官方是存在调用的限制的,如果是长期需要频繁稳定的使用需要开通钉钉的会员。不然额度这块不是很够。

                                                          image-20240914093958876


                                                          文章转载自舒一笑的架构笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                                          评论