Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion weixin-java-cp/INTELLIGENT_ROBOT.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ String fromUser = message.getFromUserName(); // 发送用户
// ...
```

对于智能机器人 API 模式的 JSON 回调消息,可使用 `WxCpIntelligentRobotMessage` 解析:

```java
WxCpIntelligentRobotMessage callbackMessage =
robotService.parseCallbackMessage(jsonBody);
String botId = callbackMessage.getAiBotId();
String userId = callbackMessage.getFrom().getUserid();
String msgType = callbackMessage.getMsgType();
```

### 删除智能机器人

```java
Expand Down Expand Up @@ -146,4 +156,4 @@ robotService.deleteRobot(robotId);
1. 需要确保企业微信应用具有智能机器人相关权限
2. 智能机器人功能可能需要特定的企业微信版本支持
3. 会话ID可以用于保持对话的连续性,提升用户体验
4. 机器人状态: 0表示停用,1表示启用
4. 机器人状态: 0表示停用,1表示启用
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,12 @@ public interface WxCpIntelligentRobotService {
*/
WxCpIntelligentRobotSendMessageResponse sendMessage(WxCpIntelligentRobotSendMessageRequest request) throws WxErrorException;

}
/**
* 解析智能机器人 API 模式回调消息.
*
* @param callbackMessageJson 回调消息JSON
* @return 解析后的回调消息对象
*/
WxCpIntelligentRobotMessage parseCallbackMessage(String callbackMessageJson);

}
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,9 @@ public WxCpIntelligentRobotSendMessageResponse sendMessage(WxCpIntelligentRobotS
return WxCpIntelligentRobotSendMessageResponse.fromJson(responseText);
}

}
@Override
public WxCpIntelligentRobotMessage parseCallbackMessage(String callbackMessageJson) {
return WxCpIntelligentRobotMessage.fromJson(callbackMessageJson);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package me.chanjar.weixin.cp.bean.intelligentrobot;

import com.google.gson.annotations.SerializedName;
import lombok.Data;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.io.Serializable;
import java.util.List;

/**
* 企业微信智能机器人回调消息.
*
* <p>官方文档: https://developer.work.weixin.qq.com/document/path/100719</p>
*/
@Data
public class WxCpIntelligentRobotMessage implements Serializable {
private static final long serialVersionUID = -1L;

/**
* 本次回调的唯一性标志.
*/
@SerializedName("msgid")
private String msgId;

/**
* 智能机器人id.
*/
@SerializedName("aibotid")
private String aiBotId;

/**
* 会话id,仅群聊类型时返回.
*/
@SerializedName("chatid")
private String chatId;

/**
* 会话类型,single/group.
*/
@SerializedName("chattype")
private String chatType;

/**
* 消息发送者.
*/
@SerializedName("from")
private From from;

/**
* 支持主动回复消息的临时url.
*/
@SerializedName("response_url")
private String responseUrl;

/**
* 消息类型.
*/
@SerializedName("msgtype")
private String msgType;

@SerializedName("text")
private Text text;

@SerializedName("image")
private Image image;

@SerializedName("mixed")
private Mixed mixed;

@SerializedName("voice")
private Voice voice;

@SerializedName("file")
private FileInfo file;

@SerializedName("video")
private Video video;

@SerializedName("quote")
private Quote quote;

@SerializedName("stream")
private Stream stream;

public static WxCpIntelligentRobotMessage fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpIntelligentRobotMessage.class);
}

public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}

@Data
public static class From implements Serializable {
private static final long serialVersionUID = -1L;

@SerializedName("userid")
private String userid;
}

@Data
public static class Text implements Serializable {
private static final long serialVersionUID = -1L;

@SerializedName("content")
private String content;
}

@Data
public static class Image implements Serializable {
private static final long serialVersionUID = -1L;

@SerializedName("url")
private String url;
Comment on lines +113 to +114
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Capture media aeskey in callback payloads

For image/file/video callbacks, WeCom sends the download URL together with an aeskey used to decrypt the downloaded media. These media payload classes only retain url, so fromJson() silently drops the key and callers receiving encrypted image, file, or video messages cannot use the SDK model to decrypt the media without reparsing the raw JSON.

Useful? React with 👍 / 👎.

}

@Data
public static class Voice implements Serializable {
private static final long serialVersionUID = -1L;

@SerializedName("content")
private String content;
}

@Data
public static class FileInfo implements Serializable {
private static final long serialVersionUID = -1L;

@SerializedName("url")
private String url;
}

@Data
public static class Video implements Serializable {
private static final long serialVersionUID = -1L;

@SerializedName("url")
private String url;
}

@Data
public static class Stream implements Serializable {
private static final long serialVersionUID = -1L;

@SerializedName("id")
private String id;
}

@Data
public static class Mixed implements Serializable {
private static final long serialVersionUID = -1L;

@SerializedName("msg_item")
private List<MixedItem> msgItem;
}

@Data
public static class MixedItem implements Serializable {
private static final long serialVersionUID = -1L;

@SerializedName("msgtype")
private String msgType;

@SerializedName("text")
private Text text;

@SerializedName("image")
private Image image;
}

@Data
public static class Quote implements Serializable {
private static final long serialVersionUID = -1L;

@SerializedName("msgtype")
private String msgType;

@SerializedName("text")
private Text text;

@SerializedName("image")
private Image image;

@SerializedName("mixed")
private Mixed mixed;

@SerializedName("voice")
private Voice voice;

@SerializedName("file")
private FileInfo file;

@SerializedName("video")
private Video video;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,15 @@ public void testSendMessageResponse() {
assert response.getSessionId().equals("session123");
assert response.getErrcode() == 0;
}
}

@Test
public void testParseCallbackMessage() {
String callbackJson = "{\"msgid\":\"msg_1\",\"aibotid\":\"bot_1\",\"chattype\":\"single\","
+ "\"from\":{\"userid\":\"user_1\"},\"msgtype\":\"text\",\"text\":{\"content\":\"hello\"}}";
WxCpIntelligentRobotMessage message = this.wxCpService.getIntelligentRobotService().parseCallbackMessage(callbackJson);
assert message.getMsgId().equals("msg_1");
assert message.getAiBotId().equals("bot_1");
assert message.getFrom().getUserid().equals("user_1");
assert message.getText().getContent().equals("hello");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package me.chanjar.weixin.cp.bean.intelligentrobot;

import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;

/**
* 智能机器人回调消息测试.
*/
public class WxCpIntelligentRobotMessageTest {

@Test
public void testFromJsonWithTextMessage() {
String json = "{"
+ "\"msgid\":\"msg_1\","
+ "\"aibotid\":\"bot_1\","
+ "\"chatid\":\"chat_1\","
+ "\"chattype\":\"group\","
+ "\"from\":{\"userid\":\"zhangsan\"},"
+ "\"response_url\":\"https://example.com/reply\","
+ "\"msgtype\":\"text\","
+ "\"text\":{\"content\":\"@robot hello\"}"
+ "}";

WxCpIntelligentRobotMessage message = WxCpIntelligentRobotMessage.fromJson(json);
assertEquals(message.getMsgId(), "msg_1");
assertEquals(message.getAiBotId(), "bot_1");
assertEquals(message.getChatId(), "chat_1");
assertEquals(message.getChatType(), "group");
assertNotNull(message.getFrom());
assertEquals(message.getFrom().getUserid(), "zhangsan");
assertEquals(message.getResponseUrl(), "https://example.com/reply");
assertEquals(message.getMsgType(), "text");
assertNotNull(message.getText());
assertEquals(message.getText().getContent(), "@robot hello");
assertNull(message.getMixed());
assertNull(message.getStream());
}

@Test
public void testFromJsonWithMixedAndQuote() {
String json = "{"
+ "\"msgid\":\"msg_2\","
+ "\"aibotid\":\"bot_2\","
+ "\"chattype\":\"single\","
+ "\"from\":{\"userid\":\"lisi\"},"
+ "\"msgtype\":\"mixed\","
+ "\"mixed\":{\"msg_item\":["
+ "{\"msgtype\":\"text\",\"text\":{\"content\":\"hello\"}},"
+ "{\"msgtype\":\"image\",\"image\":{\"url\":\"https://example.com/1.png\"}}"
+ "]},"
+ "\"quote\":{\"msgtype\":\"text\",\"text\":{\"content\":\"quoted\"}}"
+ "}";

WxCpIntelligentRobotMessage message = WxCpIntelligentRobotMessage.fromJson(json);
assertEquals(message.getMsgType(), "mixed");
assertNotNull(message.getMixed());
assertNotNull(message.getMixed().getMsgItem());
assertEquals(message.getMixed().getMsgItem().size(), 2);
assertEquals(message.getMixed().getMsgItem().get(0).getMsgType(), "text");
assertEquals(message.getMixed().getMsgItem().get(0).getText().getContent(), "hello");
assertEquals(message.getMixed().getMsgItem().get(1).getMsgType(), "image");
assertEquals(message.getMixed().getMsgItem().get(1).getImage().getUrl(), "https://example.com/1.png");
assertNotNull(message.getQuote());
assertEquals(message.getQuote().getMsgType(), "text");
assertEquals(message.getQuote().getText().getContent(), "quoted");

String serialized = message.toJson();
WxCpIntelligentRobotMessage deserialized = WxCpIntelligentRobotMessage.fromJson(serialized);
assertEquals(deserialized.getAiBotId(), "bot_2");
assertEquals(deserialized.getFrom().getUserid(), "lisi");
assertEquals(deserialized.getMixed().getMsgItem().size(), 2);
}
}