造个轮子:手把手教你开发企业微信机器人SDK,让机器人定时提醒你喝水

前言

最近在阅读的时候,看到一篇用企业微信机器人实现定时提醒功能的文章,顿时有了兴趣,于是乎自己动手实操一遍,期间也遇到了一些问题,最后也是顺利地完成了一个简单的SDK的开发,结合自己的经验,写了这样一篇 SDK 开发文章。

什么是SDK?

SDK(Software Development Kit)即 软件开发工具包 ,就是帮助我们开发出软件的工具集合,除了代码之外,一般还要搭配文档、示例等。

一般 SDK 都是需要 引入 到项目中使用的。比如学 Java 的朋友最早接触的 JDK,就是用来开发 Java 软件的工具包,使用时需要编写 类似 import java.util.* 的语法来引入。此外,大部分的 SDK,都是需要通过人工或项目管理工具,将其文件下载到指定路径才能引入。

使用SDK的好处?

比如公司有许多系统都需要实现文件上传的功能

显然我们不需要给每个系统都去开发文件上传,而是只要有一个团队编写一套通用的文件上传SDK,然后让需要实现同样功能的系统去引入即可。可以大大提高开发效率。

因此,编写SDK也就是写一个功能、造一个轮子。好的轮子可以减少一些项目在相同功能上的差异,也可以省去每个系统都去开发同样功能的时间。

上手开发

学习开发第三方应用时,一定要先完整阅读官方文档,比如企业微信群机器人配置文档。

官方网址:

developer.work.weixin.qq.com/document/pa...

设计SDK结构

在查阅企微机器人文档后,了解到企业微信机器人支持发送多种类型的消息,包括文本、 Markdown 、图片、图文、文件、语音和模块卡片等,文档中对每一种类型的请求参数和字段含义都做了详尽的解释。

由于每种消息最终都是要转换成 JSON 格式作为 HTTP 请求的参数的,所以我们可以设计一个基础的消息类(Message)来存放公共参数,然后定义各种不同的具体消息类来集成它(比如文本消息 TextMessage、Markdown 消息 MarkdownMessage 等)。

为了简化开发者使用 SDK 来发送消息,定义统一的 MessageSender 类,在类中提供发送消息的方法(比如发送文本消息 sendText),可以接受 Message 并发送到企业微信服务器。

最终,客户端只需调用统一的消息发送方法即可。SDK 的整体结构如下图所示:

开发SDK

做好设计之后,接下来就可以开始开发 SDK 了。

步骤如下:

  1. 获取 webhook
  2. 创建 SDK 项目
  3. 编写代码
  4. SDK 打包
  5. 调用 SDK

1. 获取webhook

首先,必须在企业微信群聊中创建一个企业微信机器人,并获取机器人的 webhook。

webhook 是一个 url 地址,用于接受我们开发者自己服务器的请求,从而控制企业微信机器人。后续所有的开发过程,都需要通过 webhook 才可以实现。

复制并保存好这个 Webhook 地址,注意不要泄露该地址!

2、创建SDK项目

SDK 通常是一个很干净的项目,此处我们使用 Maven 来构建一个空的项目,并在 pom.xml 文件中配置项目信息。

需要特别注意的是,既然我们正在创建一个 SDK,这意味着它将被更多的开发者使用。因此,在配置 groupId 和 artifactId 时,我们应当遵循以下规范:

  1. groupId:它是项目组织或项目开发者的唯一标识符,其实际对应的是 main 目录下的 Java 目录结构。
  2. artifactId:它是项目的唯一标识符,对应的是项目名称,即项目的根目录名称。通常,它应当为纯小写,并且多个词之间使用中划线(-)隔开。
  3. version:它指定了项目的当前版本。其中,SNAPSHOT 表示该项目仍在开发中,是一个不稳定的版本。
xml 复制代码
<groupId>com.xhl</groupId>
<artifactId>wxsdk</artifactId>
<version>1.0-SNAPSHOT</version>

所以此处我们把项目只作为 Spring Boot 的 starter,需要在 pom.xml 文件中引入依赖:

xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

为了让我们的项目更加易用,我们还要能做到让开发者通过配置文件来传入配置(比如 webhook),而不是通过硬编码重复配置各种信息。

所以此处我们把项目只作为 Spring Boot 的 starter,需要在 pom.xml 文件中引入依赖:

xml 复制代码
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-autoconfigure</artifactId>
   <version>3.1.2</version>
</dependency>
​
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-configuration-processor -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <version>3.1.2</version>
</dependency>

最后,我们还需要添加一个配置,配置项<skip>true<skip>表示跳过执行该插件的默认行为:

xml 复制代码
 <build>
   <plugins>
          <plugin>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-maven-plugin</artifactId>
              <version>1.5.4.RELEASE</version>
              <configuration>
                  <skip>true</skip>
              </configuration>
          </plugin>
   </plugins>
</build>

这样,一个 SDK 项目的初始依赖就配置好了。

3、编写配置类

现在我们就可以按照之前设计的结构开发了。

首先,我们要写一个配置类,用来接受开发者在配置文件中写入的 webhook。

同时,我们可以在配置类中,将需要被调用的 MessageSender 对象 Bean 自动注入到 IOC 容器中,不用让开发者自己 new 对象了。

示例代码:

java 复制代码
@Configuration
@ConfigurationProperties(prefix = "wechatwork-bot")
@ComponentScan
@Data
public class WebhookConfig {
  private String webhook;
  @Bean
  public RtxRobotMessageSender rtxRobotMessageSender(){
    return new RtxRobotMessageSender(webhook);
  }
}

接下来,为了让 Spring Boot 项目在启动时能自动识别并应用配置类,需要把配置类写入到 resources/META-INF/spring.factories 文件中,示例代码如下:

properties 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xhl.rtxrobot.config.WebhookConfig

4、编写消息类

接下来,我们要按照官方文档的请求参数把几种类型的消息对象编写好。

由于每个消息类都有一个固定的字段 msgtype,所以我们定义一个基类 Message,方便后续将不同类型的消息传入统一的方法:

java 复制代码
public class Message {
  /**
  * 消息类型
  */
  String msgtype;
}

接下来编写具体的消息类,比如纯文本类型消息 TextMessage,示例代码如下:

java 复制代码
@Data
public class TextMessage extends Message implements Serializable {
  
  private static final long serialVersionUID = 1L;
  /**
  * 消息内容
  */
  private String content;
  
  /**
  * 被提及者userId列表
  */
  private List<String> mentionedList;
  
  /**
  * 被提及者电话号码列表
  */
  private List<String> mentionedMobileList;
​
  /**
  * 提及全体
  */
  private boolean mentionAll = false;
  
  public TextMessage(String content, List<String> mentionedList, List<String> mentionedMobileList, boolean mentionAll) {
    this.content = content;
    this.mentionedList = mentionedList;
    this.mentionedMobileList = mentionedMobileList;
    this.mentionAll = mentionAll;
    
    if (mentionAll) {
      if (CollUtil.isNotEmpty(mentionedList) || CollUtil.isNotEmpty(mentionedMobileList)) {
        if (CollUtil.isNotEmpty(mentionedList)) {
          this.mentionedList.add("@all");
        } else {
          this.mentionedList = CollUtil.newArrayList("@all");
        }
      } else {
        this.mentionedList = CollUtil.newArrayList("@all");
      }
    }
  }
  
  public JSONObject toJson() {
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("msgtype", "text");
    JSONObject textObject = new JSONObject();
    textObject.put("content", content);
    textObject.put("mentioned_list", mentionedList);
    textObject.put("mentioned_mobile_list", mentionedMobileList);
    jsonObject.put("text", textObject);
    jsonObject.put("mentionAll",mentionAll);
    return jsonObject;
  }
}

上面的代码中,有个代码优化小细节,官方文档是使用 "@all" 字符串来表示 @全体成员的,但 "@all" 是一个魔法值,为了简化调用,我们将其封装为 mentionAll 布尔类型字段,并且在构造函数中自动转换为实际请求需要的字段。

  1. 构造方法:
  2. TextMessage(String content, List<String> mentionedList, List<String> mentionedMobileList, Boolean mentionAll):带有多个参数的构造方法,用于设置所有成员变量的值。如果 mentionAll 为 true,还会将 @all 添加到 mentionedList 中。
  3. TextMessage(String content):带有单个参数的构造方法,用于设置 content 成员变量的值,并将其他成员变量设置为默认值。

这里记得打上@Data注解,我就是忘记打上注解结果JSON序列化失败了。

toJson()方法是按照官方文档的格式,在初始化对象的时候别忘记使用这个方法转成json格式与官网要求的对应。

5、编写消息发送类

接下来,我们将编写一个消息发送类。在这个类中,定义了用于发送各种类型消息的方法,并且所有的方法都会依赖调用底层的 send 方法。send 方法的作用是通过向企微机器人的 webhook 地址发送请求,从而驱动企微机器人发送消息。

以下是示例代码,有很多编码细节:

java 复制代码
@Data
public class RtxRobotMessageSender {
  private final String webhook;
  
  public WebhookConfig webhookConfig;
  
  public RtxRobotMessageSender(String webhook) {
    this.webhook = webhook;
  }
  
  /**
  * 支持自定义消息发送
  */
  public void sendMessage(Message message) throws Exception {
    if (message instanceof TextMessage) {
      TextMessage textMessage = (TextMessage) message;
      send(textMessage.toJson());
    }
    //        } else if (message instanceof MarkdownMessage) {
    //            MarkdownMessage markdownMessage = (MarkdownMessage) message;
    //            send(markdownMessage);
    //        }
    else {
      throw new RuntimeException("Unsupported message type");
    }
  }
  
  /**
  * 发送文本(简化调用)
  */
  public void sendText(String content) throws Exception {
    sendText(content, null, null, true);
  }
  
  /**
  * 严格调用
  * @param content
  * @param mentionedList
  * @param mentionedMobileList
  * @param mentionAll
  * @throws Exception
  */
  
  public void sendText(String content, List<String> mentionedList, List<String> mentionedMobileList,boolean mentionAll) throws Exception {
    TextMessage textMessage = new TextMessage(content, mentionedList, mentionedMobileList, mentionAll);
    JSONObject jsonMessage = textMessage.toJson();
    send(jsonMessage);
  }
  
  /**
  * 发送消息的公共依赖底层代码
  */
  private void send(JSONObject message) throws Exception {
    String webhook = this.webhook;
    
    Object messageJsonObject = JSONUtil.toJsonPrettyStr(message);
    log.info("发送消息:{}", messageJsonObject);
    
    // 未传入配置,降级为从配置文件中寻找
    if (StrUtil.isBlank(this.webhook)) {
      try {
        webhook = webhookConfig.getWebhook();
      } catch (Exception e) {
        log.error("没有找到配置项中的webhook,请检查:1.是否在application.yml中填写webhook 2.是否在spring环境下运行");
        throw new RuntimeException(e);
      }
    }
    // 创建 OkHttpClient 实例
    OkHttpClient client = new OkHttpClient();
    RequestBody body = RequestBody.create(MediaType.get("application/json; charset=utf-8"),(String) messageJsonObject);
    // 构建请求对象
    Request request = new Request.Builder().url(webhook).post(body).build();
    try (Response response = client.newCall(request).execute()) {
      if (response.isSuccessful()) {
        log.info("消息发送成功");
      } else {
        log.error("消息发送失败,响应码:{}", response.code());
        throw new Exception("消息发送失败,响应码:" + response.code());
      }
    } catch (IOException e) {
      log.error("发送消息时发生错误:" + e);
      throw new Exception("发送消息时发生错误", e);
    }
  }
}

6、SDK打包

接下来就可以对 SDK 进行打包,然后本地使用或者上传到远程仓库了。

SDK 的打包非常简单,通过 Maven 的 install 命令即可,SDK 的 jar 包就会被导入到你的本地仓库中。

在打包前建议先执行 clean 来清理垃圾文件。

7、调用SDK

最后我们来调用自己写的 SDK,首先将你的 SDK 作为依赖引入到项目中,比如我们的接水提醒应用。

引入代码如下:

xml 复制代码
<dependency>
  <groupId>com.xhl</groupId>
    <artifactId>wxrobot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

然后将之前复制的 webhook 写入到 Spring Boot 的配置文件中

yaml 复制代码
wechatwork-bot:
​
  webhook: 你的webhook地址

随后你就可以用依赖注入的方式得到一个消息发送者对象了:

java 复制代码
@Resource
​
public RtxRobotMessageSender rtxRobotMessageSender;

使用定时任务提醒喝水

别忘了打上@EnableScheduling注解

java 复制代码
@Component
​
public class WaterReminderTask {
​
  @Resource
  
  public RtxRobotMessageSender rtxRobotMessageSender;
  
  @Scheduled(cron = "0 0 9,15,22 * * ?") // 每天早上9点、下午3点和晚上9点触发任务
  
  public void remindToDrinkWater() throws Exception {
    
    String message = "小火龙提醒你:记得喝水啦!"; // 提示喝水的消息内容
    
    // 调用消息发送方法发送消息
    sendReminderMessage(message);
  }
  
  private void sendReminderMessage(String message) throws Exception {
    
    // 调用消息发送的方法,具体实现根据您的需求进行修改
    rtxRobotMessageSender.sendText(message);
  }
}
相关推荐
一只鱼^_27 分钟前
牛客周赛 Round 99
java·数据结构·c++·算法·贪心算法·动态规划·近邻算法
程序员爱钓鱼29 分钟前
Go语言实战案例-字符串反转
后端·google·go
玩代码1 小时前
CompletableFuture 详解
java·开发语言·高并发·线程
人生在勤,不索何获-白大侠2 小时前
day21——特殊文件:XML、Properties、以及日志框架
xml·java·开发语言
free-9d5 小时前
NodeJs后端常用三方库汇总
后端·node.js
Dcs5 小时前
用不到 1000 行 Go 实现 BaaS,Pennybase 是怎么做到的?
java
Cyanto6 小时前
Spring注解IoC与JUnit整合实战
java·开发语言·spring·mybatis
qq_433888936 小时前
Junit多线程的坑
java·spring·junit
gadiaola7 小时前
【SSM面试篇】Spring、SpringMVC、SpringBoot、Mybatis高频八股汇总
java·spring boot·spring·面试·mybatis
写不出来就跑路7 小时前
WebClient与HTTPInterface远程调用对比
java·开发语言·后端·spring·springboot