diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WXAFacade.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WXAFacade.java index 46f25a9b..483abe1e 100644 --- a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WXAFacade.java +++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WXAFacade.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 WXAFacade { private final QrCodeApi qrCodeApi; private final TemplateApi templateApi; private final TemplateMessageApi templateMessageApi; + private final CustomMessageApi customMessageApi; /** * Constructs {@link WXAFacade} using {@link FileCacheStorager}. @@ -81,6 +83,7 @@ public class WXAFacade { this.qrCodeApi = new QrCodeApi(tokenManager); this.templateApi = new TemplateApi(tokenManager); this.templateMessageApi = new TemplateMessageApi(tokenManager); + this.customMessageApi = new CustomMessageApi(tokenManager); } public LoginApi getLoginApi() { @@ -99,4 +102,8 @@ public class WXAFacade { 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..41c0f167 --- /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(3); + params.put("touser", customMessage.getToUser()); + params.put("msgtype", msgType); + params.put(msgType, tuple); + + 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..a8528e80 --- /dev/null +++ b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/api/CustomMessageAdaptersTest.java @@ -0,0 +1,25 @@ +package com.foxinmy.weixin4j.wxa.api; + +import static org.junit.Assert.assertTrue; + +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)); + assertTrue(json.contains("\"touser\":\"OPENID\"")); + assertTrue(json.contains("\"msgtype\":\"text\"")); + assertTrue(json.contains("\"text\":{\"content\":\"Hello World\"}")); + } + +} 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()); + } + +}