From 9f60273e21e04439e9c9795ecea71509345eaeae Mon Sep 17 00:00:00 2001 From: Sutra Zhou Date: Tue, 29 May 2018 02:58:40 +0800 Subject: [PATCH 1/3] Implement WXA custom message related APIs. --- .../com/foxinmy/weixin4j/wxa/WeixinProxy.java | 7 ++ .../wxa/api/CustomMessageAdapters.java | 38 ++++++++++ .../weixin4j/wxa/api/CustomMessageApi.java | 69 +++++++++++++++++ .../wxa/model/custommessage/Command.java | 34 +++++++++ .../model/custommessage/CustomMessage.java | 45 +++++++++++ .../wxa/model/custommessage/Image.java | 24 ++++++ .../wxa/model/custommessage/Link.java | 74 +++++++++++++++++++ .../model/custommessage/MiniProgramPage.java | 64 ++++++++++++++++ .../wxa/model/custommessage/Text.java | 24 ++++++ .../wxa/model/custommessage/package-info.java | 6 ++ .../weixin4j/wxa/api/weixin.properties | 7 ++ .../wxa/api/CustomMessageAdaptersTest.java | 24 ++++++ .../wxa/model/custommessage/CommandTest.java | 15 ++++ 13 files changed, 431 insertions(+) create mode 100644 weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java create mode 100644 weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageApi.java create mode 100644 weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Command.java create mode 100644 weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/CustomMessage.java create mode 100644 weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Image.java create mode 100644 weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Link.java create mode 100644 weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/MiniProgramPage.java create mode 100644 weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Text.java create mode 100644 weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/package-info.java create mode 100644 weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdaptersTest.java create mode 100644 weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/model/custommessage/CommandTest.java diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WeixinProxy.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WeixinProxy.java index d86f10e0..256e0221 100644 --- a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WeixinProxy.java +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WeixinProxy.java @@ -7,6 +7,7 @@ import com.foxinmy.weixin4j.model.WeixinAccount; import com.foxinmy.weixin4j.mp.token.WeixinTokenCreator; import com.foxinmy.weixin4j.token.TokenCreator; import com.foxinmy.weixin4j.token.TokenManager; +import com.foxinmy.weixin4j.wxa.api.CustomMessageApi; import com.foxinmy.weixin4j.wxa.api.LoginApi; import com.foxinmy.weixin4j.wxa.api.QrCodeApi; import com.foxinmy.weixin4j.wxa.api.TemplateApi; @@ -23,6 +24,7 @@ public class WeixinProxy { private final QrCodeApi qrCodeApi; private final TemplateApi templateApi; private final TemplateMessageApi templateMessageApi; + private final CustomMessageApi customMessageApi; public WeixinProxy( WeixinAccount weixinAccount @@ -70,6 +72,7 @@ public class WeixinProxy { this.qrCodeApi = new QrCodeApi(tokenManager); this.templateApi = new TemplateApi(tokenManager); this.templateMessageApi = new TemplateMessageApi(tokenManager); + this.customMessageApi = new CustomMessageApi(tokenManager); } public LoginApi getLoginApi() { @@ -88,4 +91,8 @@ public class WeixinProxy { return templateMessageApi; } + public CustomMessageApi getCustomMessageApi() { + return customMessageApi; + } + } diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java new file mode 100644 index 00000000..421949d0 --- /dev/null +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java @@ -0,0 +1,38 @@ +package com.foxinmy.weixin4j.wxa.api; + +import java.util.HashMap; +import java.util.Map; + +import com.foxinmy.weixin4j.tuple.NotifyTuple; +import com.foxinmy.weixin4j.wxa.model.custommessage.Command; +import com.foxinmy.weixin4j.wxa.model.custommessage.CustomMessage; + +/** + * Adapters for {@link CustomMessageApi}. + * + * @since 1.8 + */ +final class CustomMessageAdapters { + + private CustomMessageAdapters() { + } + + public static Map toMap(CustomMessage customMessage) { + final NotifyTuple tuple = customMessage.getTuple(); + final String msgType = tuple.getMessageType(); + + final Map params = new HashMap(); + params.put("touser", customMessage.getToUser()); + params.put("msgtype", msgType); + params.put(msgType, customMessage.getTuple()); + + return params; + } + + public static Map toMap(String toUser, Command command) { + final Map params = new HashMap(2); + params.put("touser", toUser); + params.put("command", command.toString()); + return params; + } +} diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageApi.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageApi.java new file mode 100644 index 00000000..9764b936 --- /dev/null +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageApi.java @@ -0,0 +1,69 @@ +package com.foxinmy.weixin4j.wxa.api; + +import java.util.Map; + +import com.foxinmy.weixin4j.exception.WeixinException; +import com.foxinmy.weixin4j.token.TokenManager; +import com.foxinmy.weixin4j.wxa.model.custommessage.Command; +import com.foxinmy.weixin4j.wxa.model.custommessage.CustomMessage; + +/** + * 客服消息。 + * + * @since 1.8 + */ +public class CustomMessageApi extends TokenManagerApi { + + public CustomMessageApi(TokenManager tokenManager) { + super(tokenManager); + } + + /** + * 发送客服消息。 + * + *

+ * 当用户和小程序客服产生特定动作的交互时(具体动作列表请见下方说明), + * 微信将会把消息数据推送给开发者,开发者可以在一段时间内(目前修改为48小时)调用客服接口, + * 通过POST一个JSON数据包来发送消息给普通用户。 + * 此接口主要用于客服等有人工消息处理环节的功能,方便开发者为用户提供更加优质的服务。 + *

+ * + * @param customMessage the {@link CustomMessage}. + * @throws WeixinException indicates getting access token failed, or sending custom message failed. + * @see 发送客服消息 + */ + public void sendCustomMessage(final CustomMessage customMessage) + throws WeixinException { + final Map params = CustomMessageAdapters.toMap(customMessage); + final WxaApiResult r = this.post("message_custom_send", + params, WxaApiResult.TYPE_REFERENCE); + r.checkErrCode(); + } + + /** + * 客服输入状态。 + * + *

开发者可通过调用“客服输入状态接口”,返回客服当前输入状态给用户。

+ * + *
    + *
  1. 此接口需要客服消息接口权限。
  2. + *
  3. 如果不满足发送客服消息的触发条件,则无法下发输入状态。
  4. + *
  5. 下发输入状态,需要客服之前30秒内跟用户有过消息交互。
  6. + *
  7. 在输入状态中(持续15s),不可重复下发输入态。
  8. + *
  9. 在输入状态中,如果向用户下发消息,会同时取消输入状态。
  10. + *
+ * + * @param toUser 普通用户(openid) + * @param command "Typing":对用户下发“正在输入"状态;"CancelTyping":取消对用户的”正在输入"状态 + * @throws WeixinException indicates getting access token failed, or sending typing status failed. + * @see 客服输入状态 + */ + public void typingCustomMessage(String toUser, Command command) + throws WeixinException { + final Map params = CustomMessageAdapters.toMap(toUser, command); + final WxaApiResult r = this.post("message_custom_typing", + params, WxaApiResult.TYPE_REFERENCE); + r.checkErrCode(); + } + +} diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Command.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Command.java new file mode 100644 index 00000000..4652fd1c --- /dev/null +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Command.java @@ -0,0 +1,34 @@ +package com.foxinmy.weixin4j.wxa.model.custommessage; + +import com.foxinmy.weixin4j.wxa.api.CustomMessageApi; + +/** + * 客服输入状态命令。 + * + * @see CustomMessageApi#typingCustomMessage(String, Command) + * @since 1.8 + */ +public enum Command { + + /** + * 对用户下发“正在输入"状态。 + */ + TYPING("Typing"), + + /** + * 取消对用户的”正在输入"状态。 + */ + CANCEL_TYPING("CancelTyping"); + + private final String value; + + Command(final String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + +} diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/CustomMessage.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/CustomMessage.java new file mode 100644 index 00000000..75acae00 --- /dev/null +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/CustomMessage.java @@ -0,0 +1,45 @@ +package com.foxinmy.weixin4j.wxa.model.custommessage; + +import java.io.Serializable; + +import com.foxinmy.weixin4j.tuple.NotifyTuple; + +/** + * 客服消息。 + * + * @since 1.8 + */ +public class CustomMessage implements Serializable { + + private static final long serialVersionUID = 2018052901L; + + private String toUser; + + private NotifyTuple tuple; + + public String getToUser() { + return toUser; + } + + public void setToUser(String toUser) { + this.toUser = toUser; + } + + public NotifyTuple getTuple() { + return tuple; + } + + /** + * The {@link NotifyTuple}. + * + * @param tuple the tuple. + * @see Text + * @see Image + * @see Link + * @see MiniProgramPage + */ + public void setTuple(NotifyTuple tuple) { + this.tuple = tuple; + } + +} diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Image.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Image.java new file mode 100644 index 00000000..5b244068 --- /dev/null +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Image.java @@ -0,0 +1,24 @@ +package com.foxinmy.weixin4j.wxa.model.custommessage; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 图片消息。 + * + * @since 1.8 + */ +public class Image extends com.foxinmy.weixin4j.tuple.Image { + + private static final long serialVersionUID = 2018052901L; + + public Image(String mediaId) { + super(mediaId); + } + + @Override + @JSONField(serialize = false) + public String getMessageType() { + return super.getMessageType(); + } + +} diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Link.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Link.java new file mode 100644 index 00000000..99ca833d --- /dev/null +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Link.java @@ -0,0 +1,74 @@ +package com.foxinmy.weixin4j.wxa.model.custommessage; + +import com.alibaba.fastjson.annotation.JSONField; +import com.foxinmy.weixin4j.tuple.NotifyTuple; + +/** + * 图文链接。 + * + * @since 1.8 + */ +public class Link implements NotifyTuple { + + private static final long serialVersionUID = 2018052901L; + + private String title; + private String description; + private String url; + private String thumbUrl; + + public Link() { + } + + public Link( + String title, + String description, + String url, + String thumbUrl + ) { + this.title = title; + this.description = description; + this.url = url; + this.thumbUrl = thumbUrl; + } + + @Override + @JSONField(serialize = false) + public String getMessageType() { + return "link"; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @JSONField(name = "thumb_url") + public String getThumbUrl() { + return thumbUrl; + } + + public void setThumbUrl(String thumbUrl) { + this.thumbUrl = thumbUrl; + } + +} \ No newline at end of file diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/MiniProgramPage.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/MiniProgramPage.java new file mode 100644 index 00000000..7f0d9cfa --- /dev/null +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/MiniProgramPage.java @@ -0,0 +1,64 @@ +package com.foxinmy.weixin4j.wxa.model.custommessage; + +import com.alibaba.fastjson.annotation.JSONField; +import com.foxinmy.weixin4j.tuple.NotifyTuple; + +/** + * 小程序卡片。 + * + * @since 1.8 + */ +public class MiniProgramPage implements NotifyTuple { + + private static final long serialVersionUID = 2018052901L; + + private String title; + private String pagePath; + private String thumbMediaId; + + public MiniProgramPage() { + } + + public MiniProgramPage( + String title, + String pagePath, + String thumbMediaId + ) { + this.title = title; + this.pagePath = pagePath; + this.thumbMediaId = thumbMediaId; + } + + @Override + @JSONField(serialize = false) + public String getMessageType() { + return "miniprogrampage"; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + @JSONField(name = "pagepath") + public String getPagePath() { + return pagePath; + } + + public void setPagePath(String pagePath) { + this.pagePath = pagePath; + } + + @JSONField(name = "thumb_media_id") + public String getThumbMediaId() { + return thumbMediaId; + } + + public void setThumbMediaId(String thumbMediaId) { + this.thumbMediaId = thumbMediaId; + } + +} \ No newline at end of file diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Text.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Text.java new file mode 100644 index 00000000..89e56a06 --- /dev/null +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/Text.java @@ -0,0 +1,24 @@ +package com.foxinmy.weixin4j.wxa.model.custommessage; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * 文本消息。 + * + * @since 1.8 + */ +public class Text extends com.foxinmy.weixin4j.tuple.Text { + + private static final long serialVersionUID = 2018052901L; + + public Text(String content) { + super(content); + } + + @Override + @JSONField(serialize = false) + public String getMessageType() { + return super.getMessageType(); + } + +} diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/package-info.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/package-info.java new file mode 100644 index 00000000..de3940d3 --- /dev/null +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/custommessage/package-info.java @@ -0,0 +1,6 @@ +/** + * Models for {@link com.foxinmy.weixin4j.wxa.api.CustomMessageApi}. + * + * @since 1.8 + */ +package com.foxinmy.weixin4j.wxa.model.custommessage; diff --git a/weixin4j-wxa/src/main/resources/com/foxinmy/weixin4j/wxa/api/weixin.properties b/weixin4j-wxa/src/main/resources/com/foxinmy/weixin4j/wxa/api/weixin.properties index a5842379..8f43d7e8 100644 --- a/weixin4j-wxa/src/main/resources/com/foxinmy/weixin4j/wxa/api/weixin.properties +++ b/weixin4j-wxa/src/main/resources/com/foxinmy/weixin4j/wxa/api/weixin.properties @@ -36,3 +36,10 @@ wxopen_template_del={api_cgi_url}/wxopen/template/del?access_token=%s # \u53d1\u9001\u6a21\u7248\u6d88\u606f wxopen_template_message_send={api_cgi_url}/message/wxopen/template/send?access_token=%s + + +# \u53d1\u9001\u5ba2\u670d\u6d88\u606f +message_custom_send={api_cgi_url}/message/custom/send?access_token=% + +# \u5ba2\u670d\u8f93\u5165\u72b6\u6001 +message_custom_typing={api_cgi_url}/message/custom/typing?access_token=% diff --git a/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdaptersTest.java b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdaptersTest.java new file mode 100644 index 00000000..8501cedf --- /dev/null +++ b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdaptersTest.java @@ -0,0 +1,24 @@ +package com.foxinmy.weixin4j.wxa.api; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.alibaba.fastjson.JSON; +import com.foxinmy.weixin4j.wxa.model.custommessage.CustomMessage; +import com.foxinmy.weixin4j.wxa.model.custommessage.Text; + +public class CustomMessageAdaptersTest { + + @Test + public void testToMap() { + CustomMessage customMessage = new CustomMessage(); + customMessage.setToUser("OPENID"); + customMessage.setTuple(new Text("Hello World")); + + String json = JSON.toJSONString(CustomMessageAdapters.toMap(customMessage)); + System.out.println(json); + assertEquals("{\"touser\":\"OPENID\",\"text\":{\"content\":\"Hello World\"},\"msgtype\":\"text\"}", json); + } + +} diff --git a/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/model/custommessage/CommandTest.java b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/model/custommessage/CommandTest.java new file mode 100644 index 00000000..0cece616 --- /dev/null +++ b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/model/custommessage/CommandTest.java @@ -0,0 +1,15 @@ +package com.foxinmy.weixin4j.wxa.model.custommessage; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class CommandTest { + + @Test + public void testToString() { + assertEquals("Typing", Command.TYPING.toString()); + assertEquals("CancelTyping", Command.CANCEL_TYPING.toString()); + } + +} From aad62d7739b9a54c261c0ca83c5e8ad01d4aa844 Mon Sep 17 00:00:00 2001 From: Sutra Zhou Date: Tue, 29 May 2018 15:04:47 +0800 Subject: [PATCH 2/3] Specify Map initialCapacity. --- .../com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java index 421949d0..f5ac35be 100644 --- a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java @@ -21,7 +21,7 @@ final class CustomMessageAdapters { final NotifyTuple tuple = customMessage.getTuple(); final String msgType = tuple.getMessageType(); - final Map params = new HashMap(); + final Map params = new HashMap(3); params.put("touser", customMessage.getToUser()); params.put("msgtype", msgType); params.put(msgType, customMessage.getTuple()); From 138fadb68bc8f45af926676644c2c36b6a2dba67 Mon Sep 17 00:00:00 2001 From: Sutra Zhou Date: Tue, 29 May 2018 15:11:21 +0800 Subject: [PATCH 3/3] Fix CustomMessageAdapters. --- .../foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java | 2 +- .../weixin4j/wxa/api/CustomMessageAdaptersTest.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java index f5ac35be..41c0f167 100644 --- a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdapters.java @@ -24,7 +24,7 @@ final class CustomMessageAdapters { final Map params = new HashMap(3); params.put("touser", customMessage.getToUser()); params.put("msgtype", msgType); - params.put(msgType, customMessage.getTuple()); + params.put(msgType, tuple); return params; } diff --git a/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdaptersTest.java b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdaptersTest.java index 8501cedf..a8528e80 100644 --- a/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdaptersTest.java +++ b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdaptersTest.java @@ -1,6 +1,6 @@ package com.foxinmy.weixin4j.wxa.api; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -17,8 +17,9 @@ public class CustomMessageAdaptersTest { customMessage.setTuple(new Text("Hello World")); String json = JSON.toJSONString(CustomMessageAdapters.toMap(customMessage)); - System.out.println(json); - assertEquals("{\"touser\":\"OPENID\",\"text\":{\"content\":\"Hello World\"},\"msgtype\":\"text\"}", json); + assertTrue(json.contains("\"touser\":\"OPENID\"")); + assertTrue(json.contains("\"msgtype\":\"text\"")); + assertTrue(json.contains("\"text\":{\"content\":\"Hello World\"}")); } }