企业微信机器人开发文档

摘要

如果想让机器人的回复更加灵活可控,使用个人或企业内部的数据资料等,那么就需要构建自己的企业微信机器人。文章列举了构建机器人的过程,如果只是想直接使用,可以直接下载代码构建即可。

前置准备

环境要求

  1. 企业微信管理员权限(用于创建API模式的智能机器人)
  2. 具备域名的可公开访问项目
  3. Java 9以下版本需要下载JCE无限制权限策略文件(我直接java17)

技术准备

引入官方代码

  1. 从企业微信开发者中心下载官方提供的加解密代码

  2. 将 com/qq/weixin/mp/aes 目录下的所有Java文件复制到您的项目中
  3. 确保相关导入没有问题,无编译错误

交互流程

URL验证接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    /**
* URL验证接口 (GET请求)
* 企业微信会调用此接口验证URL有效性
*/
@GetMapping("/push/wechat")
public String wechatGet(
@RequestParam("msg_signature") String msgSignature,
@RequestParam("timestamp") String timestamp,
@RequestParam("corpid") String corpid, // 企业id
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr) {
log.info("验证URL请求的corpid:{}, 签名: {}, 时间戳: {}, 随机数: {}",
corpid, msgSignature, timestamp, nonce);
try {
// 使用URL中的corpid初始化加解密工具
WXBizJsonMsgCrypt wxcpt = new WXBizJsonMsgCrypt(sToken, sEncodingAESKey, "");
// 验证URL并获取明文echostr
String sEchoStr = wxcpt.VerifyURL(msgSignature, timestamp, nonce, echostr);
log.info("验证URL成功, 返回明文: {}", sEchoStr);
return sEchoStr;
} catch (Exception e) {
log.error("验证URL失败", e);
return "error";
}
}

关键点说明
corpid参数:必须从URL参数中获取,不能使用固定值
返回值:必须返回解密后的明文echostr,不能包含其他内容
异常处理:必须捕获所有异常并记录日志,但对外返回简洁信息

接收消息接口实现

数据结构定义

org-language
1
2
3
4
5
6
@Data
public static class RobotData {
private String tousername; // 接收方企业微信ID
private String encrypt; // 加密的消息内容
private String agentid; // 应用ID
}

接口代码

这里代码如果看着比较复杂,可以直接下载tag:
v0.1版本,只是做了简单的文本回复测试。这里代码是已经打通百炼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* 接收消息接口 (POST请求)
* 企业微信会将用户发送给机器人的消息推送到此接口
* {
* "msgid": "CAIQ16HMjQYY/NGagIOAgAMgq4KM0AI=",
* "aibotid": "AIBOTID",
* "chatid": "CHATID",
* "chattype": "group",
* "from": {
* "userid": "USERID"
* },
* "msgtype": "text",
* "text": {
* "content": "@RobotA hello robot"
* }
* }
*
curl -X POST http://127.0.0.1:8080/wx/work/robot/push/wechat?msg_signature=65173e4c8284b36c27ea1565a12fe9b98da7b760&timestamp=1698044465&nonce=1698044465 -H "Content-Type: application/json" -d '{"encrypt":"1/eL5o4f0xYunv/GG/9AVW+UXnj3ZCntsFmkLcTW8Ekm8nenhQ2IcTzeZNQujFiXgjrEMAnxFX55pHnYZHxmlEl6K9k+gjLiS8k2gKn4hqmATH8WcQgJW+pahiqg3jH4WMuk6RSgZ6QpL4x21LgUB3SimB5DM4SkkrIDt+sWgM2L0JjJqK9vFio0txS12CW5GBjlpVhg1kcvJP9JZ9c/VYU0eJ9R8I3GfI377UlINYHgwwfFTyRIpeWz8gS9lybijk7vFcNrW+qyvRKOKZeT3Mi7OXuLu4MKuVLL8fDy45E="}'
*/
@PostMapping("/push/wechat")
public String wechatPost(
@RequestParam("msg_signature") String msgSignature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestBody RobotData postData) {

log.info("接收消息请求的签名: {}, 时间戳: {}, 随机数: {}", msgSignature, timestamp, nonce);
log.info("接收到的加密消息: {}", JSON.toJSONString(postData));

try {
// 加解密库要求传 receiveid 参数,企业自建智能机器人的使用场景里,receiveid直接传空字符串即可;。
WXBizJsonMsgCrypt wxcpt = new WXBizJsonMsgCrypt(sToken, sEncodingAESKey, "");

// 解密消息
String sMsg = wxcpt.DecryptMsg(msgSignature, timestamp, nonce, JSON.toJSONString(postData));
log.info("消息解密后内容: {}", sMsg);

// 解析消息内容
JSONObject json = new JSONObject(sMsg);
String msgType = json.getString("msgtype");
String chatType = json.getString("chattype"); // 聊天类型: single/group
log.info("消息类型: {}, 聊天类型: {}", msgType, chatType);

// 处理文本消息
if ("text".equals(msgType)) {
JSONObject textObj = json.getJSONObject("text");
String content = textObj.getString("content");
String userId = json.getJSONObject("from").getString("userid");

log.info("用户[{}]在[{}]聊天中发送消息: {}", userId, chatType, content);

// 构建回复消息
String replyMsg = replyMessageStream.reply(json);

log.info("加密前的回复消息: {}", replyMsg);
// 加密回复消息
String encryptMsg = wxcpt.EncryptMsg(replyMsg, timestamp, nonce);
log.info("加密后的回复消息: {}", encryptMsg);
return encryptMsg;
}else if("stream".equals(msgType)){
// 企业微信官方文档显示如果首次返回stream,则后续的stream消息会返回stream_id,且stream_id不变
JSONObject streamObj = json.getJSONObject("stream");
String streamId= streamObj.getString("id");
// 通过streamId获取数据栈,返回结果
String poll = streamMapRepository.poll(streamId);
String streamText = streamMapRepository.getStreamText(streamId);
log.info("streamId: {}, pull: {}, streamText: {}", streamId, poll, streamText);
String replyMsg;
if(!StringUtils.hasText(streamText)){
replyMsg = replyMessageStream.buildStreamMessage("你好呀!我是智能机器人,有什么可以帮您的吗? 当前时间是" + new Date(), streamId, true, null);
}else if("messageend".equals(poll)){
replyMsg = replyMessageStream.buildStreamMessage(streamText, streamId, true, null);
// 结束后清理数据
streamMapRepository.delete(streamId);
}else{
replyMsg = replyMessageStream.buildStreamMessage(streamText, streamId, false, null);
}
log.info("streamId: {}, 加密前的回复消息: {}", streamId, replyMsg);
// 加密回复消息
String encryptMsg = wxcpt.EncryptMsg(replyMsg, timestamp, nonce);
log.info("streamId: {}, 加密后的回复消息: {}", streamId, encryptMsg);
return encryptMsg;
}

} catch (Exception e) {
log.error("处理消息失败", e);
}

return "success";
}

消息格式说明

企业微信推送的消息格式示例:实际以官方最新内容为准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"msgid": "CAIQ16HMjQYY/NGagIOAgAMgq4KM0AI=",
"aibotid": "AIBOTID",
"chatid": "CHATID",
"chattype": "group",
"from": {
"userid": "USERID"
},
"msgtype": "text",
"text": {
"content": "@RobotA hello robot"
}
}

参数 说明
msgid 本次回调的唯一性标志,开发者需据此进行事件排重(可能因为网络等原因重复回调)
aibotid 智能机器人id
chatid 会话id,仅群聊类型时候返回
chattype 会话类型,single\group,分别表示:单聊\群聊
from 该事件触发者的信息
from.userid 操作者的userid
msgtype 消息类型,此时固定是text
text.content 消息内容

修改加解密工具类

修改 JsonParse 类的 extract 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 提取出 JSON 包中的加密消息
* @param jsontext 待提取的json字符串
* @return 提取出的加密消息字符串
* @throws AesException
*/
public static Object[] extract(String jsontext) throws AesException {
Object[] result = new Object[3];
try {
JSONObject json = new JSONObject(jsontext);
String encrypt_msg = json.getString("encrypt");
// 企业微信机器人消息格式中不包含tousername和agentid
result[0] = ""; // tousername
result[1] = encrypt_msg;
result[2] = ""; // agentid
return result;
} catch (Exception e) {
log.error("json解析出错!", e);
throw new AesException(AesException.ParseJsonError);
}
}

代码路径

如果有帮助,点点star https://github.com/zhaozhiwei1992/weixin-work-bot

配置与测试

应用部署

将应用部署到具备域名的服务器上并确保应用可通过HTTPS访问(企业微信要求回调URL必须是HTTPS)

机器人配置

  1. 在企业微信管理后台创建API模式的智能机器人
  2. 随机生成Token和EncodingAESKey,在application.yml中填入生成的Token和EncodingAESKey
  3. 设置回调URL为:https://您的域名/robot/push/wechat?corpid=$CORPID$

测试验证

URL验证测试:保存机器人配置时,企业微信会自动调用验证接口

消息接收测试:

在企业微信中单聊或群聊中@机器人发送消息,查看后台日志确认收到并正确处理消息,验证机器人是否能正确回复。

常见问题排查

  1. URL验证失败:检查Token、EncodingAESKey和corpid是否正确
  2. 解密失败:检查提取加密消息的方法是否正确修改
  3. 无法回复消息:检查回复消息的格式是否符合企业微信要求,回复msgtype一定要用stream,否则可能出现都访问通了,但是客户端收不到消息。
  4. HTTPS证书问题:确保证书有效且受信任

注意事项

性能考虑:消息处理应尽量高效,避免超时(企业微信默认超时时间为5秒)
安全考虑: 验证消息签名确保请求来自企业微信
对用户输入进行适当过滤和转义,防止注入攻击
错误处理:妥善处理所有异常,避免服务崩溃
日志记录:详细记录请求和响应信息,便于排查问题
通过以上步骤,您可以成功开发并部署一个企业微信智能机器人,实现接收消息和自动回复的功能。

参考

https://developer.work.weixin.qq.com/community/question/detail?content_id=16740110965903826290

https://blog.csdn.net/qq_52011411/article/details/150932697