diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java index 557d1fc1..9af7811c 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java @@ -88,6 +88,11 @@ public class WxConsts { * 小程序卡片(要求小程序与公众号已关联) */ public static final String MINIPROGRAMPAGE = "miniprogrampage"; + + /** + * 任务卡片消息 + */ + public static final String TASKCARD = "taskcard"; } /** diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/WxCpConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/WxCpConsts.java index 168bcf74..87892ecc 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/WxCpConsts.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/WxCpConsts.java @@ -84,6 +84,11 @@ public class WxCpConsts { */ public static final String LOCATION_SELECT = "location_select"; + /** + * 任务卡片事件推送. + */ + public static final String TASKCARD_CLICK = "taskcard_click"; + } /** diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java index 732c1449..364723db 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java @@ -301,6 +301,13 @@ public interface WxCpService { */ WxCpChatService getChatService(); + /** + * 获取任务卡片服务 + * + * @return 任务卡片服务 + */ + WxCpTaskCardService getTaskCardService(); + WxCpAgentService getAgentService(); WxCpOAService getOAService(); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTaskCardService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTaskCardService.java new file mode 100644 index 00000000..038c2dd3 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTaskCardService.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; + +import java.util.List; + +/** + *
+ * 任务卡片管理接口. + * Created by Jeff on 2019-05-16. + *+ * + * @author Jeff + * @date 2019-05-16 + */ +public interface WxCpTaskCardService { + /** + *
+ * 更新任务卡片消息状态 + * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/91579 + * + * 注意: 这个方法使用WxCpConfigStorage里的agentId + *+ * + * @param userIds 企业的成员ID列表 + * @param taskId 任务卡片ID + * @param clickedKey 已点击按钮的Key + */ + void update(List
+ * 任务卡片管理接口. + * Created by Jeff on 2019-05-16. + *+ * + * @author Jeff + * @date 2019-05-16 + */ +@RequiredArgsConstructor +public class WxCpTaskCardServiceImpl implements WxCpTaskCardService { + private final WxCpService mainService; + + @Override + public void update(List
@@ -124,6 +129,7 @@ public class WxCpMessage implements Serializable { * {@link KefuMsgType#NEWS} * {@link KefuMsgType#MPNEWS} * {@link KefuMsgType#MARKDOWN} + * {@link KefuMsgType#TASKCARD} ** * @param msgType 消息类型 @@ -249,6 +255,42 @@ public class WxCpMessage implements Serializable { messageJson.add("mpnews", newsJsonObject); break; } + case KefuMsgType.TASKCARD: { + JsonObject text = new JsonObject(); + text.addProperty("title", this.getTitle()); + text.addProperty("description", this.getDescription()); + + if (StringUtils.isNotBlank(this.getUrl())) { + text.addProperty("url", this.getUrl()); + } + + text.addProperty("task_id", this.getTaskId()); + + JsonArray buttonJsonArray = new JsonArray(); + for (TaskCardButton button : this.getTaskButtons()) { + JsonObject buttonJson = new JsonObject(); + buttonJson.addProperty("key", button.getKey()); + buttonJson.addProperty("name", button.getName()); + + if (StringUtils.isNotBlank(button.getReplaceName())) { + buttonJson.addProperty("replace_name", button.getReplaceName()); + } + + if (StringUtils.isNotBlank(button.getColor())) { + buttonJson.addProperty("color", button.getColor()); + } + + if (button.getBold() != null) { + buttonJson.addProperty("is_bold", button.getBold()); + } + + buttonJsonArray.add(buttonJson); + } + text.add("btn", buttonJsonArray); + + messageJson.add("taskcard", text); + break; + } default: { // do nothing } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTaskCardUpdateResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTaskCardUpdateResult.java new file mode 100644 index 00000000..c86b255b --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTaskCardUpdateResult.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + *
+ * 更新任务卡片消息状态的返回类 + * 参考文档:https://work.weixin.qq.com/api/doc#90000/90135/91579 + * Created by Jeff on 2019-05-16. + *+ * + * @author Jeff + * @date 2019-05-16 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WxCpTaskCardUpdateResult implements Serializable { + + @SerializedName("errcode") + private Integer errcode; + + @SerializedName("errmsg") + private String errmsg; + + /** + * 用户列表 + */ + @SerializedName("invaliduser") + private List
@@ -157,6 +156,10 @@ public class WxCpXmlMessage implements Serializable { @XStreamConverter(value = XStreamCDataConverter.class) private String recognition; + @XStreamAlias("TaskId") + @XStreamConverter(value = XStreamCDataConverter.class) + private String taskId; + /** * 通讯录变更事件. * 请参考常量 me.chanjar.weixin.cp.WxCpConsts.ContactChangeType diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TaskCardBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TaskCardBuilder.java new file mode 100644 index 00000000..3c2d7792 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TaskCardBuilder.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.cp.bean.messagebuilder; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.cp.bean.WxCpMessage; +import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton; + +import java.util.List; + +/** + ** */+ * 任务卡片消息Builder + * 用法: WxCustomMessage m = WxCustomMessage.TASKCARD().title(...)....toUser(...).build(); + *+ * + * @author Jeff + * @date 2019-05-16 + */ +public class TaskCardBuilder extends BaseBuilder{ + private String title; + private String description; + private String url; + private String taskId; + /** + * 按钮个数为1~2个 + */ + private List buttons; + + public TaskCardBuilder() { + this.msgType = WxConsts.KefuMsgType.TASKCARD; + } + + public TaskCardBuilder title(String title) { + this.title = title; + return this; + } + + public TaskCardBuilder description(String description) { + this.description = description; + return this; + } + + public TaskCardBuilder url(String url) { + this.url = url; + return this; + } + + public TaskCardBuilder taskId(String taskId) { + this.taskId = taskId; + return this; + } + + public TaskCardBuilder buttons(List buttons) { + this.buttons = buttons; + return this; + } + + @Override + public WxCpMessage build() { + WxCpMessage m = super.build(); + m.setSafe(null); + m.setTitle(this.title); + m.setDescription(this.description); + m.setUrl(this.url); + m.setTaskId(this.taskId); + m.setTaskButtons(this.buttons); + return m; + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/taskcard/TaskCardButton.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/taskcard/TaskCardButton.java new file mode 100644 index 00000000..0ffe49ce --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/taskcard/TaskCardButton.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.cp.bean.taskcard; + +import lombok.Builder; +import lombok.Data; + +/** + * + * 任务卡片按钮 + * Created by Jeff on 2019-05-16. + *+ * + * @author Jeff + * @date 2019-05-16 + */ +@Data +@Builder +public class TaskCardButton { + private String key; + private String name; + private String replaceName; + private String color; + private Boolean bold; +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImplTest.java new file mode 100644 index 00000000..be387548 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImplTest.java @@ -0,0 +1,65 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.inject.Inject; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.ApiTestModule; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.WxCpMessage; +import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; +import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.util.Arrays; + +import static org.testng.Assert.assertNotNull; + +/** + * 测试任务卡片服务 + * + * @author Jeff + * @date 2019-05-16 + */ +@Guice(modules = ApiTestModule.class) +public class WxCpTaskCardServiceImplTest { + + @Inject + private WxCpService wxCpService; + + @Test + public void testSendTaskCard() throws WxErrorException { + TaskCardButton btn1 = TaskCardButton.builder() + .key("key1") + .name("同意") + .replaceName("已同意") + .bold(true) + .build(); + TaskCardButton btn2 = TaskCardButton.builder() + .key("key2") + .name("拒绝") + .replaceName("已拒绝") + .color("red") + .build(); + WxCpMessage message = WxCpMessage.TASKCARD() + .toUser("jeff|mr.t") + .title("有一个待审批的请求") + .description("申请:购买图书\n金额:100 元") + .taskId("task_1") + .url("http://www.qq.com") + .buttons(Arrays.asList(btn1, btn2)) + .build(); + + WxCpMessageSendResult messageSendResult = this.wxCpService.messageSend(message); + assertNotNull(messageSendResult); + System.out.println(messageSendResult); + System.out.println(messageSendResult.getInvalidPartyList()); + System.out.println(messageSendResult.getInvalidUserList()); + System.out.println(messageSendResult.getInvalidTagList()); + } + + @Test + public void testUpdate() throws Exception { + wxCpService.getTaskCardService().update(Arrays.asList("jeff", "mr.t"), "task_1", "key1"); + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java index 344d1e82..c5421175 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java @@ -1,9 +1,11 @@ package me.chanjar.weixin.cp.bean; -import org.testng.annotations.*; - import me.chanjar.weixin.cp.bean.article.MpnewsArticle; import me.chanjar.weixin.cp.bean.article.NewArticle; +import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton; +import org.testng.annotations.Test; + +import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; @@ -67,7 +69,7 @@ public class WxCpMessageTest { WxCpMessage reply = WxCpMessage.NEWS().toUser("OPENID").addArticle(article1).addArticle(article2).build(); assertThat(reply.toJson()) - .isEqualTo( "{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"news\":{\"articles\":" + + .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"news\":{\"articles\":" + "[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}," + "{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}," + "\"safe\":\"0\"}"); @@ -97,7 +99,7 @@ public class WxCpMessageTest { WxCpMessage reply = WxCpMessage.MPNEWS().toUser("OPENID").addArticle(article1, article2).build(); assertThat(reply.toJson()) - .isEqualTo( "{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"mpnews\":{\"articles\":" + + .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"mpnews\":{\"articles\":" + "[{\"title\":\"Happy Day\",\"thumb_media_id\":\"thumb\",\"author\":\"aaaaaa\"," + "\"content_source_url\":\"nice url\",\"content\":\"hahaha\",\"digest\":\"digest\",\"show_cover_pic\":\"heihei\"}" + ",{\"title\":\"Happy Day\",\"thumb_media_id\":\"thumb\",\"author\":\"aaaaaa\"," + @@ -112,4 +114,30 @@ public class WxCpMessageTest { .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"mpnews\":{\"media_id\":\"mmm\"},\"safe\":\"0\"}"); } + public void testTaskCardBuilder() { + TaskCardButton button1 = TaskCardButton.builder() + .key("yes") + .name("批准") + .replaceName("已批准") + .color("blue") + .bold(true) + .build(); + TaskCardButton button2 = TaskCardButton.builder() + .key("yes") + .name("拒绝") + .replaceName("已拒绝") + .color("red") + .bold(false) + .build(); + WxCpMessage reply = WxCpMessage.TASKCARD().toUser("OPENID") + .title("任务卡片") + .description("有一条待处理任务") + .url("http://www.qq.com") + .taskId("task_123") + .buttons(Arrays.asList(button1, button2)) + .build(); + assertThat(reply.toJson()) + .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"taskcard\",\"taskcard\":{\"title\":\"任务卡片\",\"description\":\"有一条待处理任务\",\"url\":\"http://www.qq.com\",\"task_id\":\"task_123\",\"btn\":[{\"key\":\"yes\",\"name\":\"批准\",\"replace_name\":\"已批准\",\"color\":\"blue\",\"is_bold\":true},{\"key\":\"yes\",\"name\":\"拒绝\",\"replace_name\":\"已拒绝\",\"color\":\"red\",\"is_bold\":false}]}}"); + } + } diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java index aaf40324..89c0396e 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java @@ -1,9 +1,11 @@ package me.chanjar.weixin.cp.bean; import me.chanjar.weixin.common.api.WxConsts; -import org.testng.annotations.*; +import org.testng.annotations.Test; -import static org.testng.Assert.*; +import static me.chanjar.weixin.cp.WxCpConsts.EventType.TASKCARD_CLICK; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; @Test public class WxCpXmlMessageTest { @@ -149,4 +151,26 @@ public class WxCpXmlMessageTest { assertEquals(wxMessage.getExtAttrs().getItems().get(0).getName(), "爱好"); } + + public void testTaskCardEvent() { + String xml = "" + + " "; + WxCpXmlMessage wxMessage = WxCpXmlMessage.fromXml(xml); + assertEquals(wxMessage.getToUserName(), "toUser"); + assertEquals(wxMessage.getFromUserName(), "FromUser"); + assertEquals(wxMessage.getCreateTime(), Long.valueOf(123456789L)); + assertEquals(wxMessage.getMsgType(), WxConsts.XmlMsgType.EVENT); + assertEquals(wxMessage.getAgentId(), Integer.valueOf(1)); + assertEquals(wxMessage.getEvent(), TASKCARD_CLICK); + assertEquals(wxMessage.getEventKey(), "key111"); + assertEquals(wxMessage.getTaskId(), "taskid111"); + } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java index 92c298a6..560add60 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/kefu/WxMpKefuMessage.java @@ -109,6 +109,7 @@ public class WxMpKefuMessage implements Serializable { * {@link WxConsts.KefuMsgType#MPNEWS} * {@link WxConsts.KefuMsgType#WXCARD} * {@link WxConsts.KefuMsgType#MINIPROGRAMPAGE} + * {@link WxConsts.KefuMsgType#TASKCARD} *" + + " " + + " 123456789 " + + "" + + " " + + " " + + " " + + " 1 " + + "