#1046 企业微信增加支持最新添加的任务卡片消息

This commit is contained in:
Jeff 2019-05-17 11:21:57 +08:00 committed by Binary Wang
parent 70a7781ed3
commit e9e7f6e46b
15 changed files with 420 additions and 32 deletions

View File

@ -88,6 +88,11 @@ public class WxConsts {
* 小程序卡片(要求小程序与公众号已关联)
*/
public static final String MINIPROGRAMPAGE = "miniprogrampage";
/**
* 任务卡片消息
*/
public static final String TASKCARD = "taskcard";
}
/**

View File

@ -84,6 +84,11 @@ public class WxCpConsts {
*/
public static final String LOCATION_SELECT = "location_select";
/**
* 任务卡片事件推送.
*/
public static final String TASKCARD_CLICK = "taskcard_click";
}
/**

View File

@ -301,6 +301,13 @@ public interface WxCpService {
*/
WxCpChatService getChatService();
/**
* 获取任务卡片服务
*
* @return 任务卡片服务
*/
WxCpTaskCardService getTaskCardService();
WxCpAgentService getAgentService();
WxCpOAService getOAService();

View File

@ -0,0 +1,30 @@
package me.chanjar.weixin.cp.api;
import me.chanjar.weixin.common.error.WxErrorException;
import java.util.List;
/**
* <pre>
* 任务卡片管理接口.
* Created by Jeff on 2019-05-16.
* </pre>
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @date 2019-05-16
*/
public interface WxCpTaskCardService {
/**
* <pre>
* 更新任务卡片消息状态
* 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/91579
*
* 注意: 这个方法使用WxCpConfigStorage里的agentId
* </pre>
*
* @param userIds 企业的成员ID列表
* @param taskId 任务卡片ID
* @param clickedKey 已点击按钮的Key
*/
void update(List<String> userIds, String taskId, String clickedKey) throws WxErrorException;
}

View File

@ -46,6 +46,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
private WxCpTagService tagService = new WxCpTagServiceImpl(this);
private WxCpAgentService agentService = new WxCpAgentServiceImpl(this);
private WxCpOAService oaService = new WxCpOAServiceImpl(this);
private WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this);
/**
* 全局的是否正在刷新access token的锁
@ -392,6 +393,11 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
return oaService;
}
@Override
public WxCpTaskCardService getTaskCardService() {
return taskCardService;
}
@Override
public RequestHttp<?, ?> getRequestHttp() {
return this;

View File

@ -0,0 +1,39 @@
package me.chanjar.weixin.cp.api.impl;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.WxCpTaskCardService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <pre>
* 任务卡片管理接口.
* Created by Jeff on 2019-05-16.
* </pre>
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @date 2019-05-16
*/
@RequiredArgsConstructor
public class WxCpTaskCardServiceImpl implements WxCpTaskCardService {
private final WxCpService mainService;
@Override
public void update(List<String> userIds, String taskId, String clickedKey) throws WxErrorException {
Integer agentId = this.mainService.getWxCpConfigStorage().getAgentId();
Map<String, Object> data = new HashMap<>(4);
data.put("userids", userIds);
data.put("agentid", agentId);
data.put("task_id", taskId);
data.put("clicked_key", clickedKey);
String url = "https://qyapi.weixin.qq.com/cgi-bin/message/update_taskcard";
this.mainService.post(url, WxGsonBuilder.create().toJson(data));
}
}

View File

@ -1,26 +1,18 @@
package me.chanjar.weixin.cp.bean;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import lombok.Data;
import me.chanjar.weixin.common.api.WxConsts.KefuMsgType;
import me.chanjar.weixin.cp.bean.article.MpnewsArticle;
import me.chanjar.weixin.cp.bean.article.NewArticle;
import me.chanjar.weixin.cp.bean.messagebuilder.FileBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.ImageBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.MarkdownMsgBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.MpnewsBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.NewsBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.TextBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.TextCardBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.VideoBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.VoiceBuilder;
import me.chanjar.weixin.cp.bean.messagebuilder.*;
import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 消息.
@ -49,6 +41,12 @@ public class WxCpMessage implements Serializable {
private List<NewArticle> articles = new ArrayList<>();
private List<MpnewsArticle> mpnewsArticles = new ArrayList<>();
/**
* 任务卡片特有的属性
*/
private String taskId;
private List<TaskCardButton> taskButtons = new ArrayList<>();
/**
* 获得文本消息builder.
*/
@ -112,6 +110,13 @@ public class WxCpMessage implements Serializable {
return new FileBuilder();
}
/**
* 获得任务卡片消息builder.
*/
public static TaskCardBuilder TASKCARD() {
return new TaskCardBuilder();
}
/**
* <pre>
@ -124,6 +129,7 @@ public class WxCpMessage implements Serializable {
* {@link KefuMsgType#NEWS}
* {@link KefuMsgType#MPNEWS}
* {@link KefuMsgType#MARKDOWN}
* {@link KefuMsgType#TASKCARD}
* </pre>
*
* @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
}

View File

@ -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;
/**
* <pre>
* 更新任务卡片消息状态的返回类
* 参考文档https://work.weixin.qq.com/api/doc#90000/90135/91579
* Created by Jeff on 2019-05-16.
* </pre>
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @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<String> invalidUsers;
public static WxCpTaskCardUpdateResult fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpTaskCardUpdateResult.class);
}
}

View File

@ -1,18 +1,8 @@
package me.chanjar.weixin.cp.bean;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import org.apache.commons.io.IOUtils;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
@ -22,6 +12,15 @@ import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import me.chanjar.weixin.cp.util.xml.XStreamTransformer;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* <pre>
@ -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

View File

@ -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;
/**
* <pre>
* 任务卡片消息Builder
* 用法: WxCustomMessage m = WxCustomMessage.TASKCARD().title(...)....toUser(...).build();
* </pre>
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @date 2019-05-16
*/
public class TaskCardBuilder extends BaseBuilder<TaskCardBuilder> {
private String title;
private String description;
private String url;
private String taskId;
/**
* 按钮个数为1~2个
*/
private List<TaskCardButton> 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<TaskCardButton> 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;
}
}

View File

@ -0,0 +1,23 @@
package me.chanjar.weixin.cp.bean.taskcard;
import lombok.Builder;
import lombok.Data;
/**
* <pre>
* 任务卡片按钮
* Created by Jeff on 2019-05-16.
* </pre>
*
* @author <a href="https://github.com/domainname">Jeff</a>
* @date 2019-05-16
*/
@Data
@Builder
public class TaskCardButton {
private String key;
private String name;
private String replaceName;
private String color;
private Boolean bold;
}

View File

@ -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 <a href="https://github.com/domainname">Jeff</a>
* @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");
}
}

View File

@ -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}]}}");
}
}

View File

@ -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 = "<xml>" +
"<ToUserName><![CDATA[toUser]]></ToUserName>" +
"<FromUserName><![CDATA[FromUser]]></FromUserName>" +
"<CreateTime>123456789</CreateTime>" +
"<MsgType><![CDATA[event]]></MsgType>" +
"<Event><![CDATA[taskcard_click]]></Event>" +
"<EventKey><![CDATA[key111]]></EventKey>" +
"<TaskId><![CDATA[taskid111]]></TaskId >" +
"<AgentID>1</AgentID>" +
"</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");
}
}

View File

@ -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}
* </pre>
*
*/