weixin4j-qy:新增客服消息

This commit is contained in:
jinyu 2015-11-20 14:36:09 +08:00
parent 4a194c0f6c
commit 3b1abd1a27
14 changed files with 284 additions and 58 deletions

View File

@ -492,3 +492,7 @@
+ weixin4j-[mp|qy]:version upgrade to 1.6.3
+ weixin4j-server:version upgrade to 1.1.3
* 2015-11-20
+ weixin4j-qy:新增客服消息

View File

@ -638,6 +638,10 @@
<desc>too many group now, no need to add new</desc>
<text>组数量过多</text>
</error>
<error>
<code>45022</code>
<text>应用名字长度不合法合法长度为2-16个字</text>
</error>
<error>
<code>45024</code>
<text>账号数量超过上限</text>
@ -1058,6 +1062,70 @@
<code>86222</code>
<text>不允许消息服务访问的API</text>
</error>
<error>
<code>86304</code>
<text>不合法的消息类型</text>
</error>
<error>
<code>86305</code>
<text>客服服务未启用</text>
</error>
<error>
<code>86306</code>
<text>缺少发送人</text>
</error>
<error>
<code>86307</code>
<text>缺少发送人类型</text>
</error>
<error>
<code>86308</code>
<text>缺少发送人id</text>
</error>
<error>
<code>86309</code>
<text>缺少接收人</text>
</error>
<error>
<code>86310</code>
<text>缺少接收人类型</text>
</error>
<error>
<code>86311</code>
<text>缺少接收人id</text>
</error>
<error>
<code>86312</code>
<text>缺少消息类型</text>
</error>
<error>
<code>86313</code>
<text>缺少客服发送人或接收人类型必须有一个为kf</text>
</error>
<error>
<code>86314</code>
<text>客服不唯一发送人或接收人类型必须只有一个为kf</text>
</error>
<error>
<code>86315</code>
<text>不合法的发送人类型</text>
</error>
<error>
<code>86316</code>
<text>不合法的发送人id。Userid不存在、openid不存在、kf不存在</text>
</error>
<error>
<code>86317</code>
<text>不合法的接收人类型</text>
</error>
<error>
<code>86318</code>
<text>不合法的接收人id。Userid不存在、openid不存在、kf不存在</text>
</error>
<error>
<code>86319</code>
<text>不合法的客服kf不在客服列表中</text>
</error>
<error>
<code>90001</code>
<text>未开通摇一摇周边</text>

View File

@ -34,7 +34,7 @@ public class Order extends ApiResult {
/**
* 交易状态
*
* @see com.foxinmy.weixin4j.mp.type.TradeState
* @see com.foxinmy.weixin4j.type.TradeState
*/
@XmlElement(name = "trade_state")
@JSONField(name = "trade_state")
@ -54,7 +54,7 @@ public class Order extends ApiResult {
/**
* 交易类型
*
* @see com.foxinmy.weixin4j.mp.type.TradeType
* @see com.foxinmy.weixin4j.type.TradeType
*/
@XmlElement(name = "trade_type")
@JSONField(name = "trade_type")

View File

@ -34,20 +34,12 @@ public class MenuTest extends TokenTest {
@Test
public void create() throws WeixinException {
btnList = new ArrayList<Button>();
String domain = "http://dianzhang.canyi.net";
btnList.add(new Button("立即下单", domain, ButtonType.view));
Button b1 = new Button("会员中心", "", ButtonType.click);
b1.pushSub(new Button("我的信息", "U:INFO", ButtonType.click));
b1.pushSub(new Button("修改信息", "U:UP:INFO", ButtonType.click));
btnList.add(b1);
btnList.add(new Button("个人中心", domain + "/user", ButtonType.view));
Button b2 = new Button("最新兼职", "PART:NEWEST", ButtonType.click);
btnList.add(b2);
Button b3 = new Button("功能", "", ButtonType.click);
b3.pushSub(new Button("附近兼职", "PART:NEAR", ButtonType.click));
b3.pushSub(new Button("搜索兼职", "PART:SO", ButtonType.click));
b3.pushSub(new Button("公交查询", "BUS:SO", ButtonType.click));
btnList.add(b3);
btnList.add(new Button("小哥介绍", domain, ButtonType.view));
JsonResult result = menuApi.createMenu(btnList);
Assert.assertEquals(0, result.getCode());
@ -75,6 +67,14 @@ public class MenuTest extends TokenTest {
System.out.println(btn);
}
Assert.assertEquals(3, btnList.size());
// Button [name=我的门店, type=view,
// content=http://dianzhang.canyi.net/setting/index, subs=[]]
// Button [name=每日签到, type=click, content=CHECKIN, subs=[]]
// Button [name=今日订单, type=null, content=null, subs=[Button [name=今日订单,
// type=view, content=http://dianzhang.canyi.net/order/index, subs=[]],
// Button [name=营业统计, type=view,
// content=http://dianzhang.canyi.net/stats/index, subs=[]]]]
}
@Test

View File

@ -98,35 +98,4 @@ public class XmlstreamTest {
sb.toString(),
com.foxinmy.weixin4j.payment.mch.RefundRecord.class));
}
/*
* public static String errorXml() { StringBuffer xml = new StringBuffer();
* String url =
* "http://qydev.weixin.qq.com/wiki/index.php?title=%E5%85%A8%E5%B1%80%E8%BF%94%E5%9B%9E%E7%A0%81%E8%AF%B4%E6%98%8E"
* ; try { Document doc = Jsoup.parse(new URL(url), 5000); Elements eles =
* doc.getElementsByTag("tr"); for (Element ele : eles) {
* xml.append("<error>"); xml.append("<code>").append(ele.child(0).text())
* .append("</code>"); xml.append("<text>").append(ele.child(1).text())
* .append("</text>"); xml.append("</error>"); } } catch (Exception e) {
* e.printStackTrace(); } return xml.toString(); }
*/
public static void main(String[] args) throws Exception {
// map2xml();
// xml2map();
// xml2order();
// System.err.println(xml2refundRecordV2());
// xml2refundRecordV3();
// object2xmlWithoutRootElement();
/*
* RefundRecord refundRecord = xml2refundRecordV2();
* System.err.println(refundRecord); String sign =
* refundRecord.getSign(); refundRecord.setSign(null); String validSign
* = PayUtil.paysignMd5(refundRecord, "paysignkey");
* System.err.println("sign=" + sign + ",validSign=" + validSign);
* System.err.println(ListsuffixResultSerializer
* .serializeToXML(refundRecord));
*/
// System.out.println(errorXml());
}
}

View File

@ -121,3 +121,7 @@
* 2015-11-09
+ version upgrade to 1.6.3
* 2015-11-20
+ 新增客服消息

View File

@ -26,6 +26,7 @@ import com.foxinmy.weixin4j.qy.api.QyApi;
import com.foxinmy.weixin4j.qy.api.TagApi;
import com.foxinmy.weixin4j.qy.api.UserApi;
import com.foxinmy.weixin4j.qy.message.ChatMessage;
import com.foxinmy.weixin4j.qy.message.CustomeMessage;
import com.foxinmy.weixin4j.qy.message.NotifyMessage;
import com.foxinmy.weixin4j.qy.model.AgentInfo;
import com.foxinmy.weixin4j.qy.model.AgentOverview;
@ -173,6 +174,28 @@ public class WeixinProxy {
return notifyApi.sendNotifyMessage(message);
}
/**
* 发送客服消息
*
* @param message
* 客服消息对象
* @return 发送结果
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%9C%8D%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E">客服接口说明</a>
* @see com.foxinmy.weixin4j.qy.api.NotifyApi
* @see com.foxinmy.weixin4j.tuple.Text
* @see com.foxinmy.weixin4j.tuple.Image
* @see com.foxinmy.weixin4j.tuple.Voice
* @see com.foxinmy.weixin4j.tuple.Video
* @see com.foxinmy.weixin4j.tuple.File
* @see com.foxinmy.weixin4j.qy.message.CustomeMessage
* @throws WeixinException
*/
public JsonResult sendCustomeMessage(CustomeMessage message)
throws WeixinException {
return notifyApi.sendCustomeMessage(message);
}
/**
* 自定义菜单(管理员须拥有应用的管理权限 并且应用必须设置在回调模式)
*

View File

@ -5,8 +5,10 @@ import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.weixin.JsonResult;
import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.qy.message.CustomeMessage;
import com.foxinmy.weixin4j.qy.message.NotifyMessage;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.tuple.NotifyTuple;
@ -47,7 +49,7 @@ public class NotifyApi extends QyApi {
* </p>
*
* @param message
* 客服消息对象
* 普通消息对象
* @return 如果无权限则本次发送失败如果收件人不存在或未关注发送仍然执行两种情况下均返回无效的部分</br> { "errcode":
* 0, "errmsg": "ok", "invaliduser": "UserID1",
* "invalidparty":"PartyID1", "invalidtag":"TagID1" }
@ -86,4 +88,35 @@ public class NotifyApi extends QyApi {
return response.getAsJson();
}
/**
* 发送客服消息
*
* @param message
* 客服消息对象
* @return 发送结果
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%9C%8D%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E">客服接口说明</a>
* @see com.foxinmy.weixin4j.tuple.Text
* @see com.foxinmy.weixin4j.tuple.Image
* @see com.foxinmy.weixin4j.tuple.Voice
* @see com.foxinmy.weixin4j.tuple.Video
* @see com.foxinmy.weixin4j.tuple.File
* @see com.foxinmy.weixin4j.qy.message.CustomeMessage
* @throws WeixinException
*/
public JsonResult sendCustomeMessage(CustomeMessage message)
throws WeixinException {
NotifyTuple tuple = message.getTuple();
String msgtype = tuple.getMessageType();
JSONObject obj = (JSONObject) JSON.toJSON(message);
obj.put("msgtype", msgtype);
obj.put(msgtype, tuple);
String message_kf_send_uri = getRequestUri("message_kf_send_uri");
Token token = tokenHolder.getToken();
WeixinResponse response = weixinExecutor.post(
String.format(message_kf_send_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
}

View File

@ -57,8 +57,10 @@ menu_create_uri={api_base_url}/menu/create?access_token=%s&agentid=%d
menu_delete_uri={api_base_url}/menu/delete?access_token=%s&agentid=%d
# \u67e5\u8be2\u83dc\u5355
menu_get_uri={api_base_url}/menu/get?access_token=%s&agentid=%d
# \u53d1\u9001\u6d88\u606f
# \u53d1\u9001\u666e\u901a\u6d88\u606f
message_send_uri={api_base_url}/message/send?access_token=%s
# \u53d1\u9001\u5ba2\u670d\u6d88\u606f
message_kf_send_uri={api_base_url}/kf/send?access_token=%s
# \u83b7\u53d6\u5fae\u4fe1IP\u5730\u5740
getcallbackip_uri={api_base_url}/getcallbackip?access_token=%s
# \u83b7\u53d6\u4f01\u4e1a\u53f7\u5e94\u7528\u4fe1\u606f

View File

@ -0,0 +1,113 @@
package com.foxinmy.weixin4j.qy.message;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.tuple.NotifyTuple;
/**
* 客服消息对象
*
* @className CustomeMessage
* @author jy
* @date 2015年11月20日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.tuple.Text
* @see com.foxinmy.weixin4j.tuple.Image
* @see com.foxinmy.weixin4j.tuple.Voice
* @see com.foxinmy.weixin4j.tuple.Video
* @see com.foxinmy.weixin4j.tuple.File
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E4%BC%81%E4%B8%9A%E5%AE%A2%E6%9C%8D%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E">客服消息</a>
*/
public class CustomeMessage implements Serializable {
private static final long serialVersionUID = -3620361273175868681L;
private CustomeTarget sender;
private CustomeTarget receiver;
/**
* 消息对象
*/
@JSONField(serialize = false)
private NotifyTuple tuple;
public CustomeMessage(CustomeTarget sender, CustomeTarget receiver,
NotifyTuple tuple) {
this.sender = sender;
this.receiver = receiver;
this.tuple = tuple;
}
public CustomeTarget getSender() {
return sender;
}
public CustomeTarget getReceiver() {
return receiver;
}
public NotifyTuple getTuple() {
return tuple;
}
/**
* 用户类型
*
* @className CustomeIdType
* @author jy
* @date 2015年11月20日
* @since JDK 1.7
* @see
*/
public enum CustomeIdType {
/**
* 客服
*/
kf,
/**
* 企业员工userid
*/
userid,
/**
* 公众号openid
*/
openid
}
/**
* 消息对象
*
* @className CustomeTarget
* @author jy
* @date 2015年11月20日
* @since JDK 1.7
* @see
*/
public static class CustomeTarget implements Serializable {
private static final long serialVersionUID = 1L;
private CustomeIdType type;
private String id;
public CustomeTarget(CustomeIdType type, String id) {
this.type = type;
this.id = id;
}
public CustomeIdType getType() {
return type;
}
public String getId() {
return id;
}
}
@Override
public String toString() {
return "CustomeMessage [sender=" + sender + ", receiver=" + receiver
+ ", tuple=" + tuple + "]";
}
}

View File

@ -7,7 +7,7 @@ import com.foxinmy.weixin4j.qy.model.IdParameter;
import com.foxinmy.weixin4j.tuple.NotifyTuple;
/**
* 客服消息对象
* 消息提醒对象
*
* @className NotifyMessage
* @author jy

View File

@ -23,7 +23,7 @@ public class ChatReceiver implements Serializable {
@XmlElement(name = "id")
private String targetId;
/**
* 群聊|单聊
* 群聊|单聊|userid|openid
*/
@XmlElement(name = "type")
private String chatType;

View File

@ -159,16 +159,16 @@ public final class WeixinServerBootstrap {
* 默认端口启动服务
*
*/
public void startup() throws WeixinException {
startup(DEFAULT_SERVERPORT);
public Channel startup() throws WeixinException {
return startup(DEFAULT_SERVERPORT);
}
/**
* 指定端口启动服务
*
*/
public void startup(int serverPort) throws WeixinException {
startup(DEFAULT_BOSSTHREADS, DEFAULT_WORKERTHREADS, serverPort);
public Channel startup(int serverPort) throws WeixinException {
return startup(DEFAULT_BOSSTHREADS, DEFAULT_WORKERTHREADS, serverPort);
}
/**
@ -180,9 +180,10 @@ public final class WeixinServerBootstrap {
* worker线程数
* @param serverPort
* 服务启动端口
* @return
* @throws WeixinException
*/
public void startup(int bossThreads, int workerThreads, int serverPort)
public Channel startup(int bossThreads, int workerThreads, int serverPort)
throws WeixinException {
messageDispatcher.setMessageHandlerList(messageHandlerList);
messageDispatcher.setMessageInterceptorList(messageInterceptorList);
@ -200,7 +201,7 @@ public final class WeixinServerBootstrap {
messageDispatcher));
Channel ch = b.bind(serverPort).sync().channel();
logger.info("weixin4j server startup OK:{}", serverPort);
ch.closeFuture().sync();
return ch.closeFuture().channel();
} catch (WeixinException e) {
throw e;
} catch (InterruptedException e) {

View File

@ -13,5 +13,14 @@ public enum AgentType {
/**
* 聊天应用
*/
chat
chat,
// 企业客服回调
/**
* 企业号内部客服客户为企业号通讯录成员
*/
kf_internal,
/**
* 企业号外部客服客户为服务号openid
*/
kf_external
}