netty服务器的基本骨架编写
This commit is contained in:
parent
5de9ece244
commit
3f5bfe9e71
@ -48,4 +48,8 @@
|
||||
|
||||
* 2015-04-19
|
||||
|
||||
+ 删除ActionMapping相关类
|
||||
+ 删除ActionMapping相关类
|
||||
|
||||
* 2015-05-07
|
||||
|
||||
+ 删除ResponseTuple接口
|
||||
@ -6,7 +6,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
/**
|
||||
* 图片对象
|
||||
* <p>
|
||||
* <font color="red">可用于「被动消息」「客服消息」「群发消息」</font>
|
||||
* <font color="red">可用于「客服消息」「群发消息」</font>
|
||||
* </p>
|
||||
*
|
||||
* @className Image
|
||||
@ -15,7 +15,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class Image implements ResponseTuple, MassTuple, NotifyTuple {
|
||||
public class Image implements MassTuple, NotifyTuple {
|
||||
|
||||
private static final long serialVersionUID = 6928681900960656161L;
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
/**
|
||||
* 音乐对象
|
||||
* <p>
|
||||
* <font color="red">可用于「被动消息」「客服消息」</font>
|
||||
* <font color="red">可用于「客服消息」</font>
|
||||
* </p>
|
||||
*
|
||||
* @className Music
|
||||
@ -15,7 +15,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class Music implements ResponseTuple, NotifyTuple {
|
||||
public class Music implements NotifyTuple {
|
||||
|
||||
private static final long serialVersionUID = -5952134916367253297L;
|
||||
|
||||
@ -23,7 +23,7 @@ public class Music implements ResponseTuple, NotifyTuple {
|
||||
public String getMessageType() {
|
||||
return "music";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 音乐标题
|
||||
*/
|
||||
|
||||
@ -9,7 +9,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
/**
|
||||
* 图文对象
|
||||
* <p>
|
||||
* <font color="red">可用于「被动消息」「客服消息」</font>
|
||||
* <font color="red">可用于「客服消息」</font>
|
||||
* </p>
|
||||
*
|
||||
* @className News
|
||||
@ -19,7 +19,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
* @see
|
||||
*/
|
||||
@XStreamAlias("Articles")
|
||||
public class News implements ResponseTuple, NotifyTuple {
|
||||
public class News implements NotifyTuple {
|
||||
|
||||
private static final long serialVersionUID = 3348756809039388415L;
|
||||
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
package com.foxinmy.weixin4j.tuple;
|
||||
|
||||
/**
|
||||
* 被动消息元件
|
||||
*
|
||||
* @className ResponseTuple
|
||||
* @author jy
|
||||
* @date 2014年11月22日
|
||||
* @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.Music
|
||||
* @see com.foxinmy.weixin4j.tuple.News
|
||||
* @see com.foxinmy.weixin4j.tuple.Trans
|
||||
*/
|
||||
public interface ResponseTuple extends Tuple {
|
||||
|
||||
}
|
||||
@ -5,7 +5,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
/**
|
||||
* 文本对象
|
||||
* <p>
|
||||
* <font color="red">可用于「被动消息」「客服消息」「群发消息」</font>
|
||||
* <font color="red">可用于「客服消息」「群发消息」</font>
|
||||
* </p>
|
||||
*
|
||||
* @className Text
|
||||
@ -15,7 +15,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
* @see
|
||||
*/
|
||||
@XStreamAlias("Content")
|
||||
public class Text implements ResponseTuple, MassTuple, NotifyTuple {
|
||||
public class Text implements MassTuple, NotifyTuple {
|
||||
|
||||
private static final long serialVersionUID = 520050144519064503L;
|
||||
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
package com.foxinmy.weixin4j.tuple;
|
||||
|
||||
import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
|
||||
/**
|
||||
* 转移到客服
|
||||
* <p>
|
||||
* <font color="red">可用于「被动消息」</font>
|
||||
* </p>
|
||||
*
|
||||
* @className Trans
|
||||
* @author jy
|
||||
* @date 2014年11月21日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
@XStreamAlias("TransInfo")
|
||||
public class Trans implements ResponseTuple {
|
||||
|
||||
private static final long serialVersionUID = -214711609286629729L;
|
||||
|
||||
@Override
|
||||
public String getMessageType() {
|
||||
return "transfer_customer_service";
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定会话接入的客服账号
|
||||
*/
|
||||
@XStreamAlias("KfAccount")
|
||||
private String kfAccount;
|
||||
|
||||
public Trans() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public Trans(String kfAccount) {
|
||||
this.kfAccount = kfAccount;
|
||||
}
|
||||
|
||||
public String getKfAccount() {
|
||||
return kfAccount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Trans [kfAccount=" + kfAccount + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,7 +7,7 @@ import com.thoughtworks.xstream.annotations.XStreamOmitField;
|
||||
/**
|
||||
* 视频对象
|
||||
* <p>
|
||||
* <font color="red">可用于「被动消息」「客服消息」</font>
|
||||
* <font color="red">可用于「客服消息」</font>
|
||||
* </p>
|
||||
*
|
||||
* @className Video
|
||||
@ -16,7 +16,7 @@ import com.thoughtworks.xstream.annotations.XStreamOmitField;
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class Video implements ResponseTuple, NotifyTuple {
|
||||
public class Video implements NotifyTuple {
|
||||
|
||||
private static final long serialVersionUID = 2167437425244069128L;
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
package com.foxinmy.weixin4j.tuple;
|
||||
|
||||
|
||||
/**
|
||||
* 语音对象
|
||||
* <p>
|
||||
* <font color="red">可用于「被动消息」「客服消息」「群发消息」</font>
|
||||
* <font color="red">可用于「客服消息」「群发消息」</font>
|
||||
* </p>
|
||||
*
|
||||
* @className Voice
|
||||
@ -13,7 +12,7 @@ package com.foxinmy.weixin4j.tuple;
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class Voice extends Image implements ResponseTuple, NotifyTuple {
|
||||
public class Voice extends Image implements NotifyTuple {
|
||||
|
||||
private static final long serialVersionUID = 8853054484809101524L;
|
||||
|
||||
|
||||
@ -59,7 +59,7 @@ public class MenuTest extends TokenTest {
|
||||
|
||||
Button b1 = new Button("我要订餐", "ORDERING", ButtonType.click);
|
||||
btnList.add(b1);
|
||||
Button b2 = new Button("查询订单", "http://802.canyi.net/order/list", ButtonType.view);
|
||||
Button b2 = new Button("查询订单", "http://182.254.188.133:8082/order/list", ButtonType.view);
|
||||
btnList.add(b2);
|
||||
Button b3 = new Button("最新资讯", "NEWS", ButtonType.click);
|
||||
btnList.add(b3);
|
||||
|
||||
@ -30,7 +30,7 @@ public class TagApi extends QyApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建标签(创建的标签属于管理组;默认为未加锁状态)
|
||||
* 创建标签(创建的标签属于管理组;默认为加锁状态。)
|
||||
*
|
||||
* @param tagName
|
||||
* 标签名称
|
||||
@ -69,7 +69,7 @@ public class TagApi extends QyApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除标签(管理组必须是指定标签的创建者 并且标签的成员列表为空)
|
||||
* 删除标签(管理组必须是指定标签的创建者,并且标签的成员列表为空。)
|
||||
*
|
||||
* @param tagId
|
||||
* 标签ID
|
||||
@ -105,7 +105,7 @@ public class TagApi extends QyApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签成员(管理组须拥有“获取标签成员”的接口权限,标签须对管理组可见;返回列表仅包含管理组管辖范围的成员)
|
||||
* 获取标签成员(管理组须拥有“获取标签成员”的接口权限,返回列表仅包含管理组管辖范围的成员。)
|
||||
*
|
||||
* @param tagId
|
||||
* 标签ID
|
||||
@ -125,7 +125,7 @@ public class TagApi extends QyApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增标签成员(标签对管理组可见且未加锁,成员属于管理组管辖范围)<br>
|
||||
* 新增标签成员(标签对管理组可见且未加锁,成员属于管理组管辖范围。)<br>
|
||||
* <font color="red">若部分userid非法,则在text中体现</font>
|
||||
*
|
||||
* @param tagId
|
||||
@ -144,7 +144,7 @@ public class TagApi extends QyApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除标签成员(标签对管理组可见且未加锁,成员属于管理组管辖范围)<br>
|
||||
* 删除标签成员(标签对管理组未加锁,成员属于管理组管辖范围)<br>
|
||||
* <font color="red">若部分userid非法,则在text中体现</font>
|
||||
*
|
||||
* @param tagId
|
||||
|
||||
@ -49,4 +49,9 @@ public class Callback implements Serializable {
|
||||
return aesKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Callback [url=" + url + ", token=" + token + ", aesKey="
|
||||
+ aesKey + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ public class Party implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -2567893218591084288L;
|
||||
/**
|
||||
* 部门ID
|
||||
* 部门ID,指定时必须大于1,不指定时则自动生成.
|
||||
*/
|
||||
private int id;
|
||||
/**
|
||||
|
||||
@ -16,4 +16,8 @@
|
||||
|
||||
+ 新增客服创建、关闭、转接会话事件
|
||||
|
||||
+ 新增deploy.xml远程部署ant脚本
|
||||
+ 新增deploy.xml远程部署ant脚本
|
||||
|
||||
* 2015-05-07
|
||||
|
||||
+ 完成基本骨架
|
||||
@ -44,5 +44,27 @@
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -5,7 +5,7 @@ import java.io.Serializable;
|
||||
import com.foxinmy.weixin4j.type.EncryptType;
|
||||
|
||||
/**
|
||||
* 微信的被动消息
|
||||
* 请求消息
|
||||
*
|
||||
* @className HttpWeixinMessage
|
||||
* @author jy
|
||||
@ -14,14 +14,10 @@ import com.foxinmy.weixin4j.type.EncryptType;
|
||||
* @see
|
||||
*/
|
||||
public class HttpWeixinMessage implements Serializable {
|
||||
|
||||
|
||||
private static final long serialVersionUID = -9157395300510879866L;
|
||||
|
||||
// 以下字段是加密方式为「安全模式」时的参数
|
||||
/**
|
||||
* 公众号的唯一ID
|
||||
*/
|
||||
private String toUserName;
|
||||
/**
|
||||
* 加密后的内容
|
||||
*/
|
||||
@ -51,9 +47,9 @@ public class HttpWeixinMessage implements Serializable {
|
||||
*/
|
||||
private String signature;
|
||||
/**
|
||||
* 设置的token
|
||||
* AES模式下消息签名
|
||||
*/
|
||||
private String token;
|
||||
private String msgSignature;
|
||||
/**
|
||||
* xml消息明文主体
|
||||
*/
|
||||
@ -63,14 +59,6 @@ public class HttpWeixinMessage implements Serializable {
|
||||
*/
|
||||
private String method;
|
||||
|
||||
public String getToUserName() {
|
||||
return toUserName;
|
||||
}
|
||||
|
||||
public void setToUserName(String toUserName) {
|
||||
this.toUserName = toUserName;
|
||||
}
|
||||
|
||||
public String getEncryptContent() {
|
||||
return encryptContent;
|
||||
}
|
||||
@ -119,12 +107,12 @@ public class HttpWeixinMessage implements Serializable {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
public String getMsgSignature() {
|
||||
return msgSignature;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
public void setMsgSignature(String msgSignature) {
|
||||
this.msgSignature = msgSignature;
|
||||
}
|
||||
|
||||
public String getOriginalContent() {
|
||||
@ -145,11 +133,10 @@ public class HttpWeixinMessage implements Serializable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HttpWeixinMessage [toUserName=" + toUserName
|
||||
+ ", encryptContent=" + encryptContent + ", encryptType="
|
||||
+ encryptType + ", echoStr=" + echoStr + ", timeStamp="
|
||||
+ timeStamp + ", nonce=" + nonce + ", signature=" + signature
|
||||
+ ", token=" + token + ", originalContent=" + originalContent
|
||||
+ ", method=" + method + "]";
|
||||
return "HttpWeixinMessage [encryptContent=" + encryptContent
|
||||
+ ", encryptType=" + encryptType + ", echoStr=" + echoStr
|
||||
+ ", timeStamp=" + timeStamp + ", nonce=" + nonce
|
||||
+ ", signature=" + signature + ", originalContent="
|
||||
+ originalContent + ", method=" + method + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
package com.foxinmy.weixin4j.message;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.foxinmy.weixin4j.response.WeixinResponse;
|
||||
|
||||
public class ResponseMessage implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -2822272237544040042L;
|
||||
|
||||
private WeixinMessage message;
|
||||
private WeixinResponse response;
|
||||
|
||||
public ResponseMessage(WeixinMessage message, WeixinResponse response) {
|
||||
this.message = message;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public WeixinMessage getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public WeixinResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public String toXml() {
|
||||
StringBuilder xmlContent = new StringBuilder();
|
||||
xmlContent.append("<xml>");
|
||||
xmlContent.append(String.format(
|
||||
"<ToUserName><![CDATA[%s]]></ToUserName>",
|
||||
message.getFromUserName()));
|
||||
xmlContent.append(String.format(
|
||||
"<FromUserName><![CDATA[%s]]></FromUserName>",
|
||||
message.getToUserName()));
|
||||
xmlContent.append(String.format(
|
||||
"<CreateTime><![CDATA[%d]]></CreateTime>",
|
||||
System.currentTimeMillis() / 1000l));
|
||||
xmlContent.append(String.format("<MsgType><![CDATA[%s]]></MsgType>",
|
||||
response.getMsgType()));
|
||||
xmlContent.append(response.toContent());
|
||||
xmlContent.append("</xml>");
|
||||
return xmlContent.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResponseMessage [message=" + message + ", response=" + response
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
package com.foxinmy.weixin4j.message;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* 被动消息
|
||||
*
|
||||
* @className WeixinMessage
|
||||
* @author jy
|
||||
* @date 2015年5月6日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
@XmlRootElement(name = "xml")
|
||||
public class WeixinMessage implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 7761192742840031607L;
|
||||
|
||||
/**
|
||||
* 开发者微信号
|
||||
*/
|
||||
private String toUserName;
|
||||
/**
|
||||
* 发送方账号 即用户的openid
|
||||
*/
|
||||
private String fromUserName;
|
||||
/**
|
||||
* 消息创建时间 系统毫秒数
|
||||
*/
|
||||
private long createTime;
|
||||
/**
|
||||
* 消息类型
|
||||
*
|
||||
*/
|
||||
private String msgType;
|
||||
/**
|
||||
* 消息ID 可用于排重
|
||||
*/
|
||||
private long msgId;
|
||||
|
||||
public String getToUserName() {
|
||||
return toUserName;
|
||||
}
|
||||
|
||||
@XmlElement(name = "ToUserName")
|
||||
public void setToUserName(String toUserName) {
|
||||
this.toUserName = toUserName;
|
||||
}
|
||||
|
||||
public String getFromUserName() {
|
||||
return fromUserName;
|
||||
}
|
||||
|
||||
@XmlElement(name = "FromUserName")
|
||||
public void setFromUserName(String fromUserName) {
|
||||
this.fromUserName = fromUserName;
|
||||
}
|
||||
|
||||
public long getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
@XmlElement(name = "CreateTime")
|
||||
public void setCreateTime(long createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public String getMsgType() {
|
||||
return msgType;
|
||||
}
|
||||
|
||||
@XmlElement(name = "MsgType")
|
||||
public void setMsgType(String msgType) {
|
||||
this.msgType = msgType;
|
||||
}
|
||||
|
||||
public long getMsgId() {
|
||||
return msgId;
|
||||
}
|
||||
|
||||
@XmlElement(name = "MsgId")
|
||||
public void setMsgId(long msgId) {
|
||||
this.msgId = msgId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof WeixinMessage) {
|
||||
return ((WeixinMessage) obj).getMsgId() == msgId
|
||||
&& ((WeixinMessage) obj).getCreateTime() == createTime;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return " toUserName=" + toUserName + ", fromUserName=" + fromUserName
|
||||
+ ", createTime=" + createTime + ", msgType=" + msgType
|
||||
+ ", msgId=" + msgId;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.foxinmy.weixin4j.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* aes & token
|
||||
*
|
||||
* @className AesToken
|
||||
* @author jy
|
||||
* @date 2015年5月6日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class AesToken implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6001008896414323534L;
|
||||
|
||||
/**
|
||||
* 账号ID
|
||||
*/
|
||||
private String appid;
|
||||
/**
|
||||
* 开发者的token
|
||||
*/
|
||||
private String token;
|
||||
/**
|
||||
* 安全模式下的加密密钥
|
||||
*/
|
||||
private String aesKey;
|
||||
|
||||
public AesToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public AesToken(String appid, String token, String aesKey) {
|
||||
this.appid = appid;
|
||||
this.token = token;
|
||||
this.aesKey = aesKey;
|
||||
}
|
||||
|
||||
public String getAppid() {
|
||||
return appid;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getAesKey() {
|
||||
return aesKey;
|
||||
}
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
package com.foxinmy.weixin4j.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 微信账号信息
|
||||
*
|
||||
* @className WeixinAccount
|
||||
* @author jy
|
||||
* @date 2014年11月18日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class WeixinAccount implements Serializable {
|
||||
private static final long serialVersionUID = -6001008896414323534L;
|
||||
|
||||
/**
|
||||
* 唯一的身份标识
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* 调用接口的密钥
|
||||
*/
|
||||
private String secret;
|
||||
private String token;
|
||||
/**
|
||||
* 安全模式下的加密密钥
|
||||
*/
|
||||
private String encodingAesKey;
|
||||
|
||||
public WeixinAccount() {
|
||||
}
|
||||
|
||||
public WeixinAccount(String id, String secret) {
|
||||
this.id = id;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getEncodingAesKey() {
|
||||
return encodingAesKey;
|
||||
}
|
||||
|
||||
public void setEncodingAesKey(String encodingAesKey) {
|
||||
this.encodingAesKey = encodingAesKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "id=" + id + ", secret=" + secret + ", token=" + token
|
||||
+ ", encodingAesKey=" + encodingAesKey;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package com.foxinmy.weixin4j.response;
|
||||
|
||||
public class BlankResponse implements WeixinResponse {
|
||||
|
||||
@Override
|
||||
public String getMsgType() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toContent() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.foxinmy.weixin4j.response;
|
||||
|
||||
public class DebugResponse implements WeixinResponse {
|
||||
|
||||
@Override
|
||||
public String getMsgType() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toContent() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.foxinmy.weixin4j.response;
|
||||
|
||||
/**
|
||||
* 回复图片消息
|
||||
*
|
||||
* @className ImageResponse
|
||||
* @author jy
|
||||
* @date 2015年5月5日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class ImageResponse implements WeixinResponse {
|
||||
|
||||
/**
|
||||
* 通过上传多媒体文件,得到的id。
|
||||
*/
|
||||
private String mediaId;
|
||||
|
||||
public ImageResponse(String mediaId) {
|
||||
this.mediaId = mediaId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toContent() {
|
||||
return String.format(
|
||||
"<Image><MediaId><![CDATA[%s]]></MediaId></Image>", mediaId);
|
||||
}
|
||||
|
||||
public String getMediaId() {
|
||||
return mediaId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsgType() {
|
||||
return "image";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
package com.foxinmy.weixin4j.response;
|
||||
|
||||
/**
|
||||
* 回复音乐消息
|
||||
*
|
||||
* @className MusicResponse
|
||||
* @author jy
|
||||
* @date 2015年5月5日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class MusicResponse implements WeixinResponse {
|
||||
|
||||
/**
|
||||
* 缩略图的媒体id,通过上传多媒体文件,得到的id
|
||||
*/
|
||||
private String thumbMediaId;
|
||||
/**
|
||||
* 音乐标题
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 音乐描述
|
||||
*/
|
||||
private String desc;
|
||||
/**
|
||||
* 音乐链接
|
||||
*/
|
||||
private String musicUrl;
|
||||
/**
|
||||
* 高质量音乐链接,WIFI环境优先使用该链接播放音乐
|
||||
*/
|
||||
private String hqMusicUrl;
|
||||
|
||||
public MusicResponse(String thumbMediaId) {
|
||||
this.thumbMediaId = thumbMediaId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toContent() {
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append("<Music>");
|
||||
content.append(String.format(
|
||||
"<ThumbMediaId><![CDATA[%s]]></ThumbMediaId>", thumbMediaId));
|
||||
content.append(String.format("<Title><![CDATA[%s]]></Title>",
|
||||
title != null ? title : ""));
|
||||
content.append(String.format(
|
||||
"<Description><![CDATA[%s]]></Description>",
|
||||
desc != null ? desc : ""));
|
||||
content.append(String.format("<MusicUrl><![CDATA[%s]]></MusicUrl>",
|
||||
musicUrl != null ? musicUrl : ""));
|
||||
content.append(String.format("<HQMusicUrl><![CDATA[%s]]></HQMusicUrl>",
|
||||
hqMusicUrl != null ? hqMusicUrl : ""));
|
||||
content.append("</Music>");
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
public String getThumbMediaId() {
|
||||
return thumbMediaId;
|
||||
}
|
||||
|
||||
public String getMusicUrl() {
|
||||
return musicUrl;
|
||||
}
|
||||
|
||||
public void setMusicUrl(String musicUrl) {
|
||||
this.musicUrl = musicUrl;
|
||||
}
|
||||
|
||||
public String getHqMusicUrl() {
|
||||
return hqMusicUrl;
|
||||
}
|
||||
|
||||
public void setHqMusicUrl(String hqMusicUrl) {
|
||||
this.hqMusicUrl = hqMusicUrl;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsgType() {
|
||||
return "music";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,159 @@
|
||||
package com.foxinmy.weixin4j.response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 回复图文消息
|
||||
*
|
||||
* @className NewsResponse
|
||||
* @author jy
|
||||
* @date 2015年5月5日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class NewsResponse implements WeixinResponse {
|
||||
|
||||
/**
|
||||
* 图文集合
|
||||
*/
|
||||
private List<Article> articleList;
|
||||
|
||||
public NewsResponse(List<Article> articleList) {
|
||||
this.articleList = articleList;
|
||||
}
|
||||
|
||||
public NewsResponse(Article article) {
|
||||
this.articleList = new ArrayList<Article>();
|
||||
this.articleList.add(article);
|
||||
}
|
||||
|
||||
public void pushArticle(Article article) {
|
||||
articleList.add(article);
|
||||
}
|
||||
|
||||
public void pushFirstArticle(Article article) {
|
||||
articleList.add(0, article);
|
||||
}
|
||||
|
||||
public void pushLastArticle(Article article) {
|
||||
articleList.add(articleList.size(), article);
|
||||
}
|
||||
|
||||
public Article removeLastArticle() {
|
||||
return articleList.remove(articleList.size() - 1);
|
||||
}
|
||||
|
||||
public Article removeFirstArticle() {
|
||||
return articleList.remove(0);
|
||||
}
|
||||
|
||||
public List<Article> getArticleList() {
|
||||
return articleList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toContent() {
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append(String.format("<ArticleCount>%d</ArticleCount>",
|
||||
articleList.size()));
|
||||
content.append("<Articles>");
|
||||
for (Article article : articleList) {
|
||||
content.append("<item>");
|
||||
content.append(String.format("<Title><![CDATA[%s]]></Title>",
|
||||
article.getTitle() != null ? article.getTitle() : ""));
|
||||
content.append(String.format(
|
||||
"<Description><![CDATA[%s]]></Description>",
|
||||
article.getDesc() != null ? article.getDesc() : ""));
|
||||
content.append(String.format("<Url><![CDATA[%s]]></Url>",
|
||||
article.getUrl() != null ? article.getUrl() : ""));
|
||||
content.append(String.format("<PicUrl><![CDATA[%s]]></PicUrl>",
|
||||
article.getPicUrl() != null ? article.getPicUrl() : ""));
|
||||
content.append("</item>");
|
||||
}
|
||||
content.append("</Articles>");
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsgType() {
|
||||
return "news";
|
||||
}
|
||||
|
||||
/**
|
||||
* 图文消息对象
|
||||
*
|
||||
* @className Article
|
||||
* @author jy
|
||||
* @date 2015年5月5日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class Article {
|
||||
/**
|
||||
* 图文消息标题
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 图文消息描述
|
||||
*/
|
||||
private String desc;
|
||||
/**
|
||||
* 点击图文消息跳转链接
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
|
||||
*/
|
||||
private String picUrl;
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getPicUrl() {
|
||||
return picUrl;
|
||||
}
|
||||
|
||||
public void setPicUrl(String picUrl) {
|
||||
this.picUrl = picUrl;
|
||||
}
|
||||
|
||||
public Article() {
|
||||
|
||||
}
|
||||
|
||||
public Article(String title, String desc, String url, String picUrl) {
|
||||
this.title = title;
|
||||
this.desc = desc;
|
||||
this.url = url;
|
||||
this.picUrl = picUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Article [title=" + title + ", desc=" + desc + ", url="
|
||||
+ url + ", picUrl=" + picUrl + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.foxinmy.weixin4j.response;
|
||||
|
||||
/**
|
||||
* 回复文本消息
|
||||
*
|
||||
* @className TextResponse
|
||||
* @author jy
|
||||
* @date 2015年5月5日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class TextResponse implements WeixinResponse {
|
||||
|
||||
/**
|
||||
* 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)
|
||||
*/
|
||||
private String content;
|
||||
|
||||
public TextResponse(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toContent() {
|
||||
return String.format("<Content><![CDATA[%s]]></Content>", content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsgType() {
|
||||
return "text";
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.foxinmy.weixin4j.response;
|
||||
|
||||
/**
|
||||
* 消息转移到客服
|
||||
*
|
||||
* @className TransferCustomerResponse
|
||||
* @author jy
|
||||
* @date 2015年5月5日
|
||||
* @since JDK 1.7
|
||||
* @see <a
|
||||
* href="http://mp.weixin.qq.com/wiki/5/ae230189c9bd07a6b221f48619aeef35.html">转移消息到多客服</a>
|
||||
*/
|
||||
public class TransferCustomerResponse implements WeixinResponse {
|
||||
|
||||
/**
|
||||
* 指定会话接入的客服账号
|
||||
*/
|
||||
private String kfAccount;
|
||||
|
||||
public String getKfAccount() {
|
||||
return kfAccount;
|
||||
}
|
||||
|
||||
public void setKfAccount(String kfAccount) {
|
||||
this.kfAccount = kfAccount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toContent() {
|
||||
StringBuilder content = new StringBuilder();
|
||||
if (kfAccount != null) {
|
||||
content.append(String
|
||||
.format("<TransInfo><KfAccount><![CDATA[%s]]></KfAccount></TransInfo>",
|
||||
kfAccount));
|
||||
}
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsgType() {
|
||||
return "transfer_customer_service";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
package com.foxinmy.weixin4j.response;
|
||||
|
||||
/**
|
||||
* 回复视频消息
|
||||
*
|
||||
* @className VideoResponse
|
||||
* @author jy
|
||||
* @date 2015年5月5日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class VideoResponse implements WeixinResponse {
|
||||
|
||||
/**
|
||||
* 通过上传多媒体文件,得到的id
|
||||
*/
|
||||
private String mediaId;
|
||||
/**
|
||||
* 视频消息标题
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 视频消息描述
|
||||
*/
|
||||
private String desc;
|
||||
|
||||
public VideoResponse(String mediaId) {
|
||||
this.mediaId = mediaId;
|
||||
}
|
||||
|
||||
public VideoResponse(String mediaId, String title, String desc) {
|
||||
this.mediaId = mediaId;
|
||||
this.title = title;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toContent() {
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append("<Video>");
|
||||
content.append(String.format("<MediaId><![CDATA[%s]]></MediaId>",
|
||||
mediaId));
|
||||
content.append(String.format("<Title><![CDATA[%s]]></Title>",
|
||||
title != null ? title : ""));
|
||||
content.append(String.format(
|
||||
"<Description><![CDATA[%s]]></Description>",
|
||||
desc != null ? desc : ""));
|
||||
content.append("</Video>");
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
public String getMediaId() {
|
||||
return mediaId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsgType() {
|
||||
return "video";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.foxinmy.weixin4j.response;
|
||||
|
||||
/**
|
||||
* 回复语音消息
|
||||
*
|
||||
* @className VoiceResponse
|
||||
* @author jy
|
||||
* @date 2015年5月5日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class VoiceResponse implements WeixinResponse {
|
||||
|
||||
/**
|
||||
* 通过上传多媒体文件,得到的id
|
||||
*/
|
||||
private String mediaId;
|
||||
|
||||
public VoiceResponse(String mediaId) {
|
||||
this.mediaId = mediaId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toContent() {
|
||||
return String.format(
|
||||
"<Voice><MediaId><![CDATA[%s]]></MediaId></Voice>", mediaId);
|
||||
}
|
||||
|
||||
public String getMediaId() {
|
||||
return mediaId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsgType() {
|
||||
return "voice";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.foxinmy.weixin4j.response;
|
||||
|
||||
/**
|
||||
* 微信被动消息回复
|
||||
*
|
||||
* @className WeixinResponse
|
||||
* @author jy
|
||||
* @date 2015年5月5日
|
||||
* @since JDK 1.7
|
||||
* @see TextResponse
|
||||
* @see ImageResponse
|
||||
* @see MusicResponse
|
||||
* @see VoiceResponse
|
||||
* @see VideoResponse
|
||||
* @see NewsResponse
|
||||
* @see TransferCustomerResponse
|
||||
* @see BlankResponse
|
||||
* @see DebugResponse
|
||||
* @see <a href=
|
||||
* "http://mp.weixin.qq.com/wiki/9/2c15b20a16019ae613d413e30cac8ea1.html">订阅号、服务号的被动响应消息</a>
|
||||
* @see <a
|
||||
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E8%A2%AB%E5%8A%A8%E5%93%8D%E5%BA%94%E6%B6%88%E6%81%AF">企业号的被动响应消息</a>
|
||||
*/
|
||||
public interface WeixinResponse {
|
||||
/**
|
||||
* 消息类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getMsgType();
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String toContent();
|
||||
}
|
||||
@ -12,8 +12,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.foxinmy.weixin4j.message.HttpWeixinMessage;
|
||||
import com.foxinmy.weixin4j.model.AesToken;
|
||||
import com.foxinmy.weixin4j.type.EncryptType;
|
||||
import com.foxinmy.weixin4j.util.Consts;
|
||||
import com.foxinmy.weixin4j.util.MessageUtil;
|
||||
import com.foxinmy.weixin4j.xml.EncryptMessageHandler;
|
||||
|
||||
/**
|
||||
* 微信消息解码类
|
||||
@ -29,14 +32,17 @@ public class WeixinMessageDecoder extends
|
||||
MessageToMessageDecoder<FullHttpRequest> {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private AesToken aesToken;
|
||||
|
||||
public WeixinMessageDecoder(AesToken aesToken) {
|
||||
this.aesToken = aesToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, FullHttpRequest req,
|
||||
List<Object> out) throws Exception {
|
||||
List<Object> out) throws RuntimeException {
|
||||
String content = req.content().toString(Consts.UTF_8);
|
||||
HttpWeixinMessage message = new HttpWeixinMessage();
|
||||
if (!content.isEmpty()) {
|
||||
// TODO
|
||||
}
|
||||
message.setMethod(req.getMethod().name());
|
||||
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.getUri(),
|
||||
true);
|
||||
@ -60,13 +66,24 @@ public class WeixinMessageDecoder extends
|
||||
String signature = parameters.containsKey("signature") ? parameters
|
||||
.get("signature").get(0) : "";
|
||||
message.setSignature(signature);
|
||||
String msgSignature = parameters.containsKey("msg_signature") ? parameters
|
||||
.get("msg_signature").get(0) : "";
|
||||
message.setMsgSignature(msgSignature);
|
||||
|
||||
message.setOriginalContent(content);
|
||||
if (message.getEncryptType() == EncryptType.AES) {
|
||||
/*WeixinAccount mpAccount = ConfigUtil.getWeixinAccount();
|
||||
message.setOriginalContent(MessageUtil.aesDecrypt(
|
||||
mpAccount.getId(), mpAccount.getEncodingAesKey(),
|
||||
message.getEncryptContent()));*/
|
||||
if (!content.isEmpty()) {
|
||||
if (message.getEncryptType() == EncryptType.AES) {
|
||||
if (aesToken.getAesKey() == null || aesToken.getAppid() == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"AESEncodingKey or AppId not be null in AES mode");
|
||||
}
|
||||
String encryptContent = EncryptMessageHandler.parser(content);
|
||||
message.setEncryptContent(encryptContent);
|
||||
message.setOriginalContent(MessageUtil.aesDecrypt(
|
||||
aesToken.getAppid(), aesToken.getAesKey(),
|
||||
encryptContent));
|
||||
} else {
|
||||
message.setOriginalContent(content);
|
||||
}
|
||||
}
|
||||
out.add(message);
|
||||
}
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
package com.foxinmy.weixin4j.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.foxinmy.weixin4j.message.ResponseMessage;
|
||||
import com.foxinmy.weixin4j.model.AesToken;
|
||||
import com.foxinmy.weixin4j.util.Consts;
|
||||
import com.foxinmy.weixin4j.util.HttpUtil;
|
||||
import com.foxinmy.weixin4j.util.MessageUtil;
|
||||
import com.foxinmy.weixin4j.util.RandomUtil;
|
||||
|
||||
/**
|
||||
* 微信消息编码类
|
||||
*
|
||||
* @className WeixinMessageEncoder
|
||||
* @author jy
|
||||
* @date 2014年11月13日
|
||||
* @since JDK 1.7
|
||||
* @see <a
|
||||
* href="http://mp.weixin.qq.com/wiki/0/61c3a8b9d50ac74f18bdf2e54ddfc4e0.html">加密接入指引</a>
|
||||
*/
|
||||
public class WeixinMessageEncoder extends
|
||||
MessageToMessageEncoder<ResponseMessage> {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
private final AesToken aesToken;
|
||||
|
||||
public WeixinMessageEncoder(AesToken aesToken) {
|
||||
this.aesToken = aesToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ResponseMessage response,
|
||||
List<Object> out) throws RuntimeException {
|
||||
if (aesToken.getAesKey() == null || aesToken.getAppid() == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"AESEncodingKey or AppId not be null in AES mode");
|
||||
}
|
||||
String nonce = RandomUtil.generateString(32);
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000l);
|
||||
String encrtypt = MessageUtil.aesEncrypt(aesToken.getAppid(),
|
||||
aesToken.getAesKey(), response.toXml());
|
||||
String msgSignature = MessageUtil.signature(aesToken.getToken(), nonce,
|
||||
timestamp, encrtypt);
|
||||
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append("<xml>");
|
||||
content.append(String.format("<Nonce><![CDATA[%s]]></Nonce>", nonce));
|
||||
content.append(String.format("<TimeStamp><![CDATA[%s]]></TimeStamp>",
|
||||
timestamp));
|
||||
content.append(String.format(
|
||||
"<MsgSignature><![CDATA[%s]]></MsgSignature>", msgSignature));
|
||||
content.append(String.format("<Encrypt><![CDATA[%s]]></Encrypt>",
|
||||
encrtypt));
|
||||
content.append("</xml>");
|
||||
|
||||
out.add(HttpUtil.createWeixinMessageResponse(content.toString(),
|
||||
Consts.CONTENTTYPE$APPLICATION_XML));
|
||||
|
||||
log.info("\n=================aes encrtypt out=================");
|
||||
log.info("{}", content);
|
||||
}
|
||||
}
|
||||
@ -2,11 +2,29 @@ package com.foxinmy.weixin4j.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.foxinmy.weixin4j.message.HttpWeixinMessage;
|
||||
import com.foxinmy.weixin4j.message.ResponseMessage;
|
||||
import com.foxinmy.weixin4j.message.WeixinMessage;
|
||||
import com.foxinmy.weixin4j.model.AesToken;
|
||||
import com.foxinmy.weixin4j.response.TextResponse;
|
||||
import com.foxinmy.weixin4j.type.EncryptType;
|
||||
import com.foxinmy.weixin4j.util.Consts;
|
||||
import com.foxinmy.weixin4j.util.HttpUtil;
|
||||
import com.foxinmy.weixin4j.util.MessageUtil;
|
||||
|
||||
/**
|
||||
* 微信被动消息处理类
|
||||
@ -19,9 +37,16 @@ import com.foxinmy.weixin4j.message.HttpWeixinMessage;
|
||||
*/
|
||||
public class WeixinMessageHandler extends
|
||||
SimpleChannelInboundHandler<HttpWeixinMessage> {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final AesToken aesToken;
|
||||
private final JAXBContext jaxbContext;
|
||||
|
||||
public WeixinMessageHandler(AesToken aesToken) throws JAXBException {
|
||||
this.aesToken = aesToken;
|
||||
jaxbContext = JAXBContext.newInstance(WeixinMessage.class);
|
||||
}
|
||||
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
ctx.flush();
|
||||
}
|
||||
@ -36,53 +61,65 @@ public class WeixinMessageHandler extends
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx,
|
||||
HttpWeixinMessage httpMessage) throws Exception {
|
||||
|
||||
String xmlContent = httpMessage.getOriginalContent();
|
||||
|
||||
|
||||
/*
|
||||
log.info("\n=================message in=================\n{}",
|
||||
httpMessage);
|
||||
boolean isGet = httpMessage.getMethod().equals(HttpMethod.GET.name());
|
||||
boolean validate = false;
|
||||
if (isGet || httpMessage.getEncryptType() == EncryptType.RAW) {
|
||||
validate = MessageUtil.signature(httpMessage.getToken(),
|
||||
if (httpMessage.getMethod().equals(HttpMethod.GET.name())) {
|
||||
if (MessageUtil.signature(aesToken.getToken(),
|
||||
httpMessage.getTimeStamp(), httpMessage.getNonce()).equals(
|
||||
httpMessage.getSignature());
|
||||
if (isGet && validate) {
|
||||
httpMessage.getSignature())) {
|
||||
ctx.write(HttpUtil.createWeixinMessageResponse(
|
||||
httpMessage.getEchoStr(), ContentType.TEXT_PLAIN));
|
||||
httpMessage.getEchoStr(), Consts.CONTENTTYPE$TEXT_PLAIN));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
validate = MessageUtil.signature(httpMessage.getToken(),
|
||||
httpMessage.getTimeStamp(), httpMessage.getNonce(),
|
||||
httpMessage.getEncryptContent()).equals(
|
||||
httpMessage.getSignature());
|
||||
}
|
||||
if (!validate) {
|
||||
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.FORBIDDEN));
|
||||
return;
|
||||
}
|
||||
if (action == null) {
|
||||
} else if (httpMessage.getMethod().equals(HttpMethod.POST.name())) {
|
||||
if (!MessageUtil.signature(aesToken.getToken(),
|
||||
httpMessage.getTimeStamp(), httpMessage.getNonce()).equals(
|
||||
httpMessage.getSignature())) {
|
||||
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.FORBIDDEN));
|
||||
return;
|
||||
}
|
||||
if (httpMessage.getEncryptType() == EncryptType.AES) {
|
||||
if (!MessageUtil.signature(aesToken.getToken(),
|
||||
httpMessage.getTimeStamp(), httpMessage.getNonce(),
|
||||
httpMessage.getEncryptContent()).equals(
|
||||
httpMessage.getMsgSignature())) {
|
||||
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.FORBIDDEN));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.NOT_FOUND));
|
||||
HttpResponseStatus.METHOD_NOT_ALLOWED));
|
||||
return;
|
||||
}
|
||||
ResponseMessage response = action.execute(xmlContent);
|
||||
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
|
||||
WeixinMessage weixinMessage = (WeixinMessage) jaxbUnmarshaller
|
||||
.unmarshal(new ByteArrayInputStream(httpMessage
|
||||
.getOriginalContent().getBytes(Consts.UTF_8)));
|
||||
/*
|
||||
* if (action == null) { ctx.write(new
|
||||
* DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
|
||||
* HttpResponseStatus.NOT_FOUND)); return; }
|
||||
*/
|
||||
ResponseMessage response = new ResponseMessage(weixinMessage,
|
||||
new TextResponse("Hello World!"));
|
||||
log.info("\n=================message out=================\n{}",
|
||||
response);
|
||||
if (response == null) {
|
||||
ctx.write(HttpUtil.createWeixinMessageResponse("",
|
||||
ContentType.TEXT_PLAIN));
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* if (response == null) {
|
||||
* ctx.write(HttpUtil.createWeixinMessageResponse(Consts.SUCCESS,
|
||||
* Consts.CONTENTTYPE$TEXT_PLAIN)); return; }
|
||||
*/
|
||||
if (httpMessage.getEncryptType() == EncryptType.RAW) {
|
||||
ctx.write(HttpUtil.createWeixinMessageResponse(response.toXml(),
|
||||
null));
|
||||
Consts.CONTENTTYPE$APPLICATION_XML));
|
||||
} else {
|
||||
ctx.write(response);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,15 +6,26 @@ import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
|
||||
import com.foxinmy.weixin4j.model.AesToken;
|
||||
|
||||
public class WeixinServerInitializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
private final AesToken aesToken;
|
||||
|
||||
public WeixinServerInitializer(AesToken aesToken) {
|
||||
if (aesToken == null) {
|
||||
throw new IllegalArgumentException("AesToken not be null.");
|
||||
}
|
||||
this.aesToken = aesToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel channel) throws Exception {
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
pipeline.addLast(new HttpObjectAggregator(65536));
|
||||
pipeline.addLast(new WeixinMessageDecoder());
|
||||
//pipeline.addLast(new WeixinMessageEncoder());
|
||||
pipeline.addLast(new WeixinMessageHandler());
|
||||
pipeline.addLast(new WeixinMessageDecoder(aesToken));
|
||||
pipeline.addLast(new WeixinMessageEncoder(aesToken));
|
||||
pipeline.addLast(new WeixinMessageHandler(aesToken));
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,8 +8,7 @@ import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import com.foxinmy.weixin4j.model.AesToken;
|
||||
import com.foxinmy.weixin4j.server.WeixinServerInitializer;
|
||||
|
||||
/**
|
||||
@ -23,16 +22,48 @@ import com.foxinmy.weixin4j.server.WeixinServerInitializer;
|
||||
*/
|
||||
public final class WeixinServerBootstrap {
|
||||
|
||||
private final static int port;
|
||||
private final static int workerThreads;
|
||||
static {
|
||||
ResourceBundle netty = ResourceBundle.getBundle("netty");
|
||||
port = Integer.parseInt(netty.getString("port"));
|
||||
workerThreads = Integer.parseInt(netty.getString("workerThreads"));
|
||||
/**
|
||||
* 默认boss线程数,一般设置为cpu的核数
|
||||
*/
|
||||
public final static int DEFAULT_BOSSTHREADS = 1;
|
||||
/**
|
||||
* 默认worker线程数
|
||||
*/
|
||||
public final static int DEFAULT_WORKERTHREADS = 20;
|
||||
/**
|
||||
* 默认服务启动端口
|
||||
*/
|
||||
public final static int DEFAULT_SERVERPORT = 30000;
|
||||
|
||||
/**
|
||||
* 明文模式
|
||||
*
|
||||
* @param token
|
||||
* 开发者填写的token
|
||||
*/
|
||||
public static void startup(String token) {
|
||||
startup(DEFAULT_BOSSTHREADS, DEFAULT_WORKERTHREADS, DEFAULT_SERVERPORT,
|
||||
new AesToken(token));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
||||
/**
|
||||
* 兼容模式 & 密文模式
|
||||
*
|
||||
* @param appid
|
||||
* 公众号的唯一ID
|
||||
* @param token
|
||||
* 开发者填写的token
|
||||
* @param aesKey
|
||||
* 消息加密的密钥
|
||||
*/
|
||||
public static void startup(String appid, String token, String aesKey) {
|
||||
startup(DEFAULT_BOSSTHREADS, DEFAULT_WORKERTHREADS, DEFAULT_SERVERPORT,
|
||||
new AesToken(appid, token, aesKey));
|
||||
}
|
||||
|
||||
public static void startup(int bossThreads, int workerThreads,
|
||||
int serverPort, AesToken aesToken) {
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup(bossThreads);
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup(workerThreads);
|
||||
try {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
@ -40,9 +71,9 @@ public final class WeixinServerBootstrap {
|
||||
b.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.handler(new LoggingHandler())
|
||||
.childHandler(new WeixinServerInitializer());
|
||||
Channel ch = b.bind(port).sync().channel();
|
||||
System.err.println("weixin server startup OK:" + port);
|
||||
.childHandler(new WeixinServerInitializer(aesToken));
|
||||
Channel ch = b.bind(serverPort).sync().channel();
|
||||
System.err.println("weixin server startup OK:" + serverPort);
|
||||
ch.closeFuture().sync();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
public final class Base64 {
|
||||
|
||||
public static byte[] decodeBase64(final String content) {
|
||||
byte[] data = StringUtil.getBytesUtf8(content);
|
||||
ByteBuf des = io.netty.handler.codec.base64.Base64.decode(Unpooled
|
||||
.copiedBuffer(data));
|
||||
if (des.hasArray()) {
|
||||
return des.array();
|
||||
} else {
|
||||
byte[] desArray = new byte[des.readableBytes()];
|
||||
des.readBytes(desArray);
|
||||
return desArray;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] encodeBase64(final byte[] bytes) {
|
||||
ByteBuf des = io.netty.handler.codec.base64.Base64.encode(Unpooled
|
||||
.copiedBuffer(bytes));
|
||||
if (des.hasArray()) {
|
||||
return des.array();
|
||||
} else {
|
||||
byte[] desArray = new byte[des.readableBytes()];
|
||||
des.readBytes(desArray);
|
||||
return desArray;
|
||||
}
|
||||
}
|
||||
|
||||
public static String encodeBase64String(final byte[] bytes) {
|
||||
byte[] data = encodeBase64(bytes);
|
||||
return HexUtil.encodeHexString(data);
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,9 @@ public final class Consts {
|
||||
public static final String TLS = "TLS";
|
||||
public static final String X509 = "X.509";
|
||||
public static final String AES = "AES";
|
||||
public static final String MD5 = "MD5";
|
||||
public static final String SHA = "SHA";
|
||||
public static final String SHA1 = "SHA-1";
|
||||
public static final String CONTENTTYPE$APPLICATION_XML = "application/xml";
|
||||
|
||||
public static final String CONTENTTYPE$TEXT_PLAIN = "text/plain";
|
||||
}
|
||||
|
||||
@ -3,78 +3,58 @@ package com.foxinmy.weixin4j.util;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* 签名工具类
|
||||
*
|
||||
* @className DigestUtil
|
||||
* @author jy
|
||||
* @date 2015年5月6日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public final class DigestUtil {
|
||||
|
||||
public static String SHA1(String decript) {
|
||||
private static MessageDigest getDigest(final String algorithm) {
|
||||
try {
|
||||
MessageDigest digest = java.security.MessageDigest
|
||||
.getInstance("SHA-1");
|
||||
digest.update(decript.getBytes());
|
||||
byte messageDigest[] = digest.digest();
|
||||
// Create Hex String
|
||||
StringBuffer hexString = new StringBuffer();
|
||||
// 字节数组转换为 十六进制 数
|
||||
for (int i = 0; i < messageDigest.length; i++) {
|
||||
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
|
||||
if (shaHex.length() < 2) {
|
||||
hexString.append(0);
|
||||
}
|
||||
hexString.append(shaHex);
|
||||
}
|
||||
return hexString.toString();
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
return MessageDigest.getInstance(algorithm);
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String SHA(String decript) {
|
||||
try {
|
||||
MessageDigest digest = java.security.MessageDigest
|
||||
.getInstance("SHA");
|
||||
digest.update(decript.getBytes());
|
||||
byte messageDigest[] = digest.digest();
|
||||
// Create Hex String
|
||||
StringBuffer hexString = new StringBuffer();
|
||||
// 字节数组转换为 十六进制 数
|
||||
for (int i = 0; i < messageDigest.length; i++) {
|
||||
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
|
||||
if (shaHex.length() < 2) {
|
||||
hexString.append(0);
|
||||
}
|
||||
hexString.append(shaHex);
|
||||
}
|
||||
return hexString.toString();
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
/**
|
||||
* SHA1签名
|
||||
*
|
||||
* @param content
|
||||
* 待签名字符串
|
||||
* @return 签名后的字符串
|
||||
*/
|
||||
public static String SHA1(String content) {
|
||||
byte[] data = StringUtil.getBytesUtf8(content);
|
||||
return HexUtil.encodeHexString(getDigest(Consts.SHA1).digest(data));
|
||||
}
|
||||
|
||||
public static String MD5(String input) {
|
||||
try {
|
||||
// 获得MD5摘要算法的 MessageDigest 对象
|
||||
MessageDigest mdInst = MessageDigest.getInstance("MD5");
|
||||
// 使用指定的字节更新摘要
|
||||
mdInst.update(input.getBytes());
|
||||
// 获得密文
|
||||
byte[] md = mdInst.digest();
|
||||
// 把密文转换成十六进制的字符串形式
|
||||
StringBuffer hexString = new StringBuffer();
|
||||
// 字节数组转换为 十六进制 数
|
||||
for (int i = 0; i < md.length; i++) {
|
||||
String shaHex = Integer.toHexString(md[i] & 0xFF);
|
||||
if (shaHex.length() < 2) {
|
||||
hexString.append(0);
|
||||
}
|
||||
hexString.append(shaHex);
|
||||
}
|
||||
return hexString.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
/**
|
||||
* SHA签名
|
||||
*
|
||||
* @param content
|
||||
* 待签名字符串
|
||||
* @return 签名后的字符串
|
||||
*/
|
||||
public static String SHA(String content) {
|
||||
byte[] data = StringUtil.getBytesUtf8(content);
|
||||
return HexUtil.encodeHexString(getDigest(Consts.SHA).digest(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* MD5签名
|
||||
*
|
||||
* @param content
|
||||
* 待签名字符串
|
||||
* @return 签名后的字符串
|
||||
*/
|
||||
public static String MD5(String content) {
|
||||
byte[] data = StringUtil.getBytesUtf8(content);
|
||||
return HexUtil.encodeHexString(getDigest(Consts.MD5).digest(data));
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
public final class HexUtil {
|
||||
/**
|
||||
* Used to build output as Hex
|
||||
*/
|
||||
private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
/**
|
||||
* Used to build output as Hex
|
||||
*/
|
||||
private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
|
||||
private static char[] encodeHex(final byte[] data, final char[] toDigits) {
|
||||
final int l = data.length;
|
||||
final char[] out = new char[l << 1];
|
||||
// two characters form the hex value.
|
||||
for (int i = 0, j = 0; i < l; i++) {
|
||||
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
|
||||
out[j++] = toDigits[0x0F & data[i]];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static String encodeHexString(final byte[] data) {
|
||||
return new String(encodeHex(data, true));
|
||||
}
|
||||
|
||||
public static char[] encodeHex(final byte[] data, final boolean toLowerCase) {
|
||||
return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* 消息工具类
|
||||
*
|
||||
* @className MessageUtil
|
||||
* @author jy
|
||||
* @date 2014年10月31日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public final class MessageUtil {
|
||||
|
||||
/**
|
||||
* 验证微信签名
|
||||
*
|
||||
* @param signature
|
||||
* 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数
|
||||
* @return 开发者通过检验signature对请求进行相关校验。若确认此次GET请求来自微信服务器
|
||||
* 请原样返回echostr参数内容,则接入生效 成为开发者成功,否则接入失败
|
||||
* @see <a
|
||||
* href="http://mp.weixin.qq.com/wiki/0/61c3a8b9d50ac74f18bdf2e54ddfc4e0.html">接入指南</a>
|
||||
*/
|
||||
public static String signature(String... para) {
|
||||
Arrays.sort(para);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String str : para) {
|
||||
sb.append(str);
|
||||
}
|
||||
return DigestUtil.SHA1(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对xml消息加密
|
||||
*
|
||||
* @param appId
|
||||
* 应用ID
|
||||
* @param encodingAesKey
|
||||
* 加密密钥
|
||||
* @param xmlContent
|
||||
* 原始消息体
|
||||
* @return aes加密后的消息体
|
||||
* @throws WeixinException
|
||||
*/
|
||||
public static String aesEncrypt(String appId, String encodingAesKey,
|
||||
String xmlContent) throws RuntimeException {
|
||||
byte[] randomBytes = StringUtil.getBytesUtf8(RandomUtil
|
||||
.generateString(16));
|
||||
byte[] xmlBytes = StringUtil.getBytesUtf8(xmlContent);
|
||||
int xmlLength = xmlBytes.length;
|
||||
byte[] orderBytes = new byte[4];
|
||||
orderBytes[3] = (byte) (xmlLength & 0xFF);
|
||||
orderBytes[2] = (byte) (xmlLength >> 8 & 0xFF);
|
||||
orderBytes[1] = (byte) (xmlLength >> 16 & 0xFF);
|
||||
orderBytes[0] = (byte) (xmlLength >> 24 & 0xFF);
|
||||
byte[] appidBytes = StringUtil.getBytesUtf8(appId);
|
||||
int byteLength = randomBytes.length + xmlLength + orderBytes.length
|
||||
+ appidBytes.length;
|
||||
// ... + pad: 使用自定义的填充方式对明文进行补位填充
|
||||
byte[] padBytes = PKCS7Encoder.encode(byteLength);
|
||||
// random + endian + xml + appid + pad 获得最终的字节流
|
||||
byte[] unencrypted = new byte[byteLength + padBytes.length];
|
||||
byteLength = 0;
|
||||
// src:源数组;srcPos:源数组要复制的起始位置;dest:目的数组;destPos:目的数组放置的起始位置;length:复制的长度
|
||||
System.arraycopy(randomBytes, 0, unencrypted, byteLength,
|
||||
randomBytes.length);
|
||||
byteLength += randomBytes.length;
|
||||
System.arraycopy(orderBytes, 0, unencrypted, byteLength,
|
||||
orderBytes.length);
|
||||
byteLength += orderBytes.length;
|
||||
System.arraycopy(xmlBytes, 0, unencrypted, byteLength, xmlBytes.length);
|
||||
byteLength += xmlBytes.length;
|
||||
System.arraycopy(appidBytes, 0, unencrypted, byteLength,
|
||||
appidBytes.length);
|
||||
byteLength += appidBytes.length;
|
||||
System.arraycopy(padBytes, 0, unencrypted, byteLength, padBytes.length);
|
||||
try {
|
||||
byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=");
|
||||
// 设置加密模式为AES的CBC模式
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(aesKey, Consts.AES);
|
||||
IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
|
||||
// 加密
|
||||
byte[] encrypted = cipher.doFinal(unencrypted);
|
||||
// 使用BASE64对加密后的字符串进行编码
|
||||
return Base64.encodeBase64String(encrypted);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("-40006,AES加密失败:", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对AES消息解密
|
||||
*
|
||||
* @param appId
|
||||
* @param encodingAesKey
|
||||
* aes加密的密钥
|
||||
* @param encryptContent
|
||||
* 加密的消息体
|
||||
* @return 解密后的字符
|
||||
* @throws WeixinException
|
||||
*/
|
||||
public static String aesDecrypt(String appId, String encodingAesKey,
|
||||
String encryptContent) throws RuntimeException {
|
||||
byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=");
|
||||
byte[] original;
|
||||
try {
|
||||
// 设置解密模式为AES的CBC模式
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec key_spec = new SecretKeySpec(aesKey, Consts.AES);
|
||||
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey,
|
||||
0, 16));
|
||||
cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
|
||||
// 使用BASE64对密文进行解码
|
||||
byte[] encrypted = Base64.decodeBase64(encryptContent);
|
||||
// 解密
|
||||
original = cipher.doFinal(encrypted);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("-40007,AES解密失败", e);
|
||||
}
|
||||
String xmlContent, fromAppId;
|
||||
try {
|
||||
// 去除补位字符
|
||||
byte[] bytes = PKCS7Encoder.decode(original);
|
||||
// 获取表示xml长度的字节数组
|
||||
byte[] lengthByte = Arrays.copyOfRange(bytes, 16, 20);
|
||||
// 获取xml消息主体的长度(byte[]2int)
|
||||
// http://my.oschina.net/u/169390/blog/97495
|
||||
int xmlLength = lengthByte[3] & 0xff | (lengthByte[2] & 0xff) << 8
|
||||
| (lengthByte[1] & 0xff) << 16
|
||||
| (lengthByte[0] & 0xff) << 24;
|
||||
xmlContent = StringUtil.newStringUtf8(Arrays.copyOfRange(bytes, 20,
|
||||
20 + xmlLength));
|
||||
fromAppId = StringUtil.newStringUtf8(Arrays.copyOfRange(bytes,
|
||||
20 + xmlLength, bytes.length));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("-40008,公众平台发送的xml不合法", e);
|
||||
}
|
||||
// 校验appId是否一致
|
||||
if (!fromAppId.trim().equals(appId)) {
|
||||
throw new RuntimeException("-40005,校验AppID失败");
|
||||
}
|
||||
return xmlContent;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.foxinmy.weixin4j.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public final class StringUtil {
|
||||
|
||||
private static byte[] getBytes(final String content, final Charset charset) {
|
||||
if (content == null) {
|
||||
return null;
|
||||
}
|
||||
return content.getBytes(charset);
|
||||
}
|
||||
|
||||
private static String newString(final byte[] bytes, final Charset charset) {
|
||||
return bytes == null ? null : new String(bytes, charset);
|
||||
}
|
||||
|
||||
public static byte[] getBytesUtf8(final String content) {
|
||||
return getBytes(content, Consts.UTF_8);
|
||||
}
|
||||
|
||||
public static String newStringUtf8(final byte[] bytes) {
|
||||
return newString(bytes, Consts.UTF_8);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package com.foxinmy.weixin4j.xml;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
import org.xml.sax.helpers.XMLReaderFactory;
|
||||
|
||||
import com.foxinmy.weixin4j.util.Consts;
|
||||
|
||||
public class EncryptMessageHandler extends DefaultHandler {
|
||||
|
||||
private String encryptContent;
|
||||
|
||||
private String currentQName;
|
||||
|
||||
@Override
|
||||
public void startDocument() throws SAXException {
|
||||
encryptContent = null;
|
||||
currentQName = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName,
|
||||
Attributes attributes) throws SAXException {
|
||||
this.currentQName = qName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName)
|
||||
throws SAXException {
|
||||
this.currentQName = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length)
|
||||
throws SAXException {
|
||||
if (currentQName.equalsIgnoreCase("encrypt")) {
|
||||
this.encryptContent = new String(ch, start, length);
|
||||
}
|
||||
}
|
||||
|
||||
public String getEncryptContent() {
|
||||
return encryptContent;
|
||||
}
|
||||
|
||||
public static String parser(String xmlContent) throws RuntimeException {
|
||||
EncryptMessageHandler messageHandler = new EncryptMessageHandler();
|
||||
try {
|
||||
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
|
||||
xmlReader.setContentHandler(messageHandler);
|
||||
xmlReader.parse(new InputSource(new ByteArrayInputStream(xmlContent
|
||||
.getBytes(Consts.UTF_8))));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (SAXException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return messageHandler.getEncryptContent();
|
||||
}
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- For assistance related to logback-translator or configuration -->
|
||||
<!-- files in general, please contact the logback user mailing list -->
|
||||
<!-- at http://www.qos.ch/mailman/listinfo/logback-user -->
|
||||
<!-- -->
|
||||
<!-- For professional support please see -->
|
||||
<!-- http://www.qos.ch/shop/products/professionalSupport -->
|
||||
<!-- -->
|
||||
<configuration>
|
||||
|
||||
<!-- 控制台输出日志 -->
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 文件输出指定项目日志 -->
|
||||
<appender name="file"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!--See http://logback.qos.ch/manual/appenders.html#RollingFileAppender -->
|
||||
<!--and http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy -->
|
||||
<!--for further documentation -->
|
||||
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>/tmp/weixin/log/server/weixin.server.%d{yyyy-MM-dd}.log
|
||||
</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 异步输出指定项目日志 -->
|
||||
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
|
||||
<queueSize>512</queueSize>
|
||||
<!-- 添加附加的appender,最多只能添加一个 -->
|
||||
<appender-ref ref="file" />
|
||||
</appender>
|
||||
|
||||
<logger name="org.apache" level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</logger>
|
||||
|
||||
<logger name="org.springframework" level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</logger>
|
||||
|
||||
<logger name="com.foxinmy.weixin4j" level="INFO">
|
||||
<appender-ref ref="async" />
|
||||
</logger>
|
||||
|
||||
</configuration>
|
||||
@ -1,4 +0,0 @@
|
||||
# netty\u76d1\u542c\u7aef\u53e3
|
||||
port=8080
|
||||
# \u5de5\u4f5c\u8fdb\u7a0b\u6570
|
||||
workerThreads=20
|
||||
@ -0,0 +1,68 @@
|
||||
package com.foxinmy.weixin4j.server.test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AesMsgTest extends MessagePush {
|
||||
StringBuilder xmlSb = new StringBuilder();
|
||||
|
||||
@Test
|
||||
public void testValidate() throws IOException {
|
||||
String echostr = "2143641595566077626";
|
||||
String para = "?signature=0d2366aedb4f3531cfa4297c1e4ea7eece2311d9&echostr="
|
||||
+ echostr + "×tamp=1415951914&nonce=165976363";
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
String response = get(para);
|
||||
Assert.assertEquals(echostr, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testType1() throws IOException {
|
||||
String para = "?signature=6dd806a20a314723e78bc58742a1b98a7adfd151×tamp=1415979366&nonce=1865915590";
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[gh_248c6f91d64f]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[oyFLst1bqtuTcxK-ojF8hOGtLQao]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>1415979365</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[CLICK]]></Event>");
|
||||
xmlSb.append("<EventKey><![CDATA[CHECKIN]]></EventKey>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(para, xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.err.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testType2() throws IOException {
|
||||
String para = "/?signature=b4343f727d6e9b1072f6f72d28b0d0cf38986dce×tamp=1430926116&nonce=1801492986&encrypt_type=aes&msg_signature=af1868ffe3058db89643c6c546e49cd40d717ac9";
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[gh_248c6f91d64f]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[oyFLst1bqtuTcxK-ojF8hOGtLQao]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>1415980001</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[CLICK]]></Event>");
|
||||
xmlSb.append("<EventKey><![CDATA[CHECKIN]]></EventKey>");
|
||||
xmlSb.append("<Encrypt><![CDATA[TDlZ8S/dhpElqvh4Nd4esnSk6Z6jIF65S3OIXiDWdwRMw+B7uzRcoJDZcETYw3b5ZE1eB9zyt4qHyJdjiBazUeN/ZkG6PVPHaprE0q0yBs2YCGLesAsbQ8rFyMtDazsOzug7pu/PM3RI886PnJ1QAcnPDT7xj4lRmKasWziZ3Ta45FH4vNkaozKNluzpUpq43PGzlOfa1ITvx22ScBcWmdaKnEBFAH/sGwpJ6IaKzHo5DDGyD0gmQ/QNiSh/swMzZewKG6VU0rRWms1rMXOXgP5ttxH0d7wIYMeqbXwwqRh4lh93CxVs2pJGrxTpI48ZnytWSSBBWfMSgchdxvHGd4auBnYWPizIoeQjb7/zJkXnnfru8/TiSQHXujLQuETV78I1jbvNeDJ7dMVsYbpANzfNa5VtJSZCorucCHgK15k=]]></Encrypt>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(para, xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.err.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testType3() throws IOException {
|
||||
String para = "?signature=ad05f836772d1cbba1ff2edb7be4b9c9fb3a43d5×tamp=1415980001&nonce=1803216865&encrypt_type=aes&msg_signature=c0d38e9ca00548f7142627ec2908a4fe8481025e";
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[gh_248c6f91d64f]]></ToUserName>");
|
||||
xmlSb.append("<Encrypt><![CDATA[R6VQIWDR14XgSRLm25zc7V/WJYqK15gsUiMh0u/5GTMZME6jGtHkyfVN079ZPL065b+ZDq3TnoFKKtjtZlzcodY6Fm8+EujvtbTdVMMFSwdo8AwqVViAn09+DDfqPaNvbHUSiYsL3qlxArs1MH6APRUHFo7MU/piY1x2stJc8+kv28xtF+K8Aou0RuPO7PeQ18Zu/GkLnYNiI1E7UG31UYfOgVKcRjeE0PXa18iF5LBS8G/ce/l+/pH/DJWUBw5uXaqSOlo21tctlgLXu3bYUUkIu8tT49QwhHvRZILtO9icoyCNuTA7iTcHIdlAe1bD1S0ncmopIQCGmoU2/AXC2CCi6trONf3EPNKKKfDeQYHadnVZOg6kTX2cnYmHZLviYeLzjCKFSqSNkimoSKQ/Dcutpsq1D82NCwiExUZW4oo=]]></Encrypt>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(para, xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.err.println(response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,177 @@
|
||||
package com.foxinmy.weixin4j.server.test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 接收事件消息格式测试
|
||||
*
|
||||
* @author jy.hu
|
||||
* @date 2014年3月24日
|
||||
* @since JDK 1.7
|
||||
*/
|
||||
public class EventMsgTest extends MessagePush {
|
||||
|
||||
private StringBuilder xmlSb = new StringBuilder();
|
||||
|
||||
/***************** event message *********************/
|
||||
@Test
|
||||
public void scribe() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[subscribe]]></Event>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scan() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[SCAN]]></Event>");
|
||||
xmlSb.append("<EventKey><![CDATA[SCENE_VALUE]]></EventKey>");
|
||||
xmlSb.append("<Ticket><![CDATA[TICKET]]></Ticket>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scan_scribe() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml><ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[subscribe]]></Event>");
|
||||
xmlSb.append("<EventKey><![CDATA[qrscene_123123]]></EventKey>");
|
||||
xmlSb.append("<Ticket><![CDATA[TICKET]]></Ticket>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void location() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[fromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[LOCATION]]></Event>");
|
||||
xmlSb.append("<Latitude>23.137466</Latitude>");
|
||||
xmlSb.append("<Longitude>113.352425</Longitude>");
|
||||
xmlSb.append("<Precision>119.385040</Precision>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void menu_click() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[CLICK]]></Event>");
|
||||
xmlSb.append("<EventKey><![CDATA[EVENTKEY]]></EventKey>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void menu_link() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[VIEW]]></Event>");
|
||||
xmlSb.append("<EventKey><![CDATA[www.qq.com]]></EventKey>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void menu_scan() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[scancode_waitmsg]]></Event>");
|
||||
xmlSb.append("<EventKey><![CDATA[www.qq.com]]></EventKey>");
|
||||
xmlSb.append("<ScanCodeInfo><ScanType><![CDATA[qrcode]]></ScanType>");
|
||||
xmlSb.append("<ScanResult><![CDATA[1]]></ScanResult>");
|
||||
xmlSb.append("</ScanCodeInfo>");
|
||||
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void menu_photo() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[pic_weixin]]></Event>");
|
||||
xmlSb.append("<EventKey><![CDATA[www.qq.com]]></EventKey>");
|
||||
xmlSb.append("<SendPicsInfo><Count>1</Count>");
|
||||
xmlSb.append("<PicList><item><PicMd5Sum><![CDATA[1b5f7c23b5bf75682a53e7b6d163e185]]></PicMd5Sum>");
|
||||
xmlSb.append("</item></PicList></SendPicsInfo>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void menu_location() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[event]]></MsgType>");
|
||||
xmlSb.append("<Event><![CDATA[location_select]]></Event>");
|
||||
xmlSb.append("<EventKey><![CDATA[www.qq.com]]></EventKey>");
|
||||
xmlSb.append("<SendLocationInfo><Location_X><![CDATA[23]]></Location_X>");
|
||||
xmlSb.append("<Location_Y><![CDATA[113]]></Location_Y>");
|
||||
xmlSb.append("<Scale><![CDATA[15]]></Scale>");
|
||||
xmlSb.append("<Label><![CDATA[ 广州市海珠区客村艺苑路 106号]]></Label>");
|
||||
xmlSb.append("<Poiname><![CDATA[]]></Poiname></SendLocationInfo>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,142 @@
|
||||
package com.foxinmy.weixin4j.server.test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 接受一般消息格式测试
|
||||
*
|
||||
* @author jy.hu
|
||||
* @date 2014年3月24日
|
||||
* @since JDK 1.7
|
||||
*/
|
||||
public class InMsgTest extends MessagePush {
|
||||
|
||||
private StringBuilder xmlSb = new StringBuilder();
|
||||
|
||||
/***************** common message *********************/
|
||||
|
||||
@Test
|
||||
public void text() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[fromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>1348831860</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[text]]></MsgType>");
|
||||
xmlSb.append("<Content><![CDATA[this is a test]]></Content>");
|
||||
xmlSb.append("<MsgId>1234567890123456</MsgId>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void image() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[image]]></MsgType>");
|
||||
xmlSb.append("<PicUrl><![CDATA[this is a url]]></PicUrl>");
|
||||
xmlSb.append("<MediaId><![CDATA[media_id]]></MediaId>");
|
||||
xmlSb.append("<MsgId>1234567890123456</MsgId>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void voice() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[voice]]></MsgType>");
|
||||
xmlSb.append("<MediaId><![CDATA[media_id]]></MediaId>");
|
||||
xmlSb.append("<Format><![CDATA[Format]]></Format>");
|
||||
xmlSb.append("<MsgId>1234567890123456</MsgId>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void re_voice() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[voice]]></MsgType>");
|
||||
xmlSb.append("<MediaId><![CDATA[media_id]]></MediaId>");
|
||||
xmlSb.append("<Format><![CDATA[Format]]></Format>");
|
||||
xmlSb.append("<Recognition><![CDATA[腾讯微信团队]]></Recognition>");
|
||||
xmlSb.append("<MsgId>1234567890123456</MsgId>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void video() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[video]]></MsgType>");
|
||||
xmlSb.append("<MediaId><![CDATA[media_id]]></MediaId>");
|
||||
xmlSb.append("<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>");
|
||||
xmlSb.append("<MsgId>1234567890123456</MsgId>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void location() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[location]]></MsgType>");
|
||||
xmlSb.append("<Location_X>23.134521</Location_X>");
|
||||
xmlSb.append("<Location_Y>113.358803</Location_Y>");
|
||||
xmlSb.append("<Scale>20</Scale>");
|
||||
xmlSb.append("<Label><![CDATA[位置信息]]></Label>");
|
||||
xmlSb.append("<MsgId>1234567890123456</MsgId>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void link() throws IOException {
|
||||
xmlSb.delete(0, xmlSb.length());
|
||||
xmlSb.append("<xml>");
|
||||
xmlSb.append("<ToUserName><![CDATA[toUser]]></ToUserName>");
|
||||
xmlSb.append("<FromUserName><![CDATA[FromUser]]></FromUserName>");
|
||||
xmlSb.append("<CreateTime>123456789</CreateTime>");
|
||||
xmlSb.append("<MsgType><![CDATA[link]]></MsgType>");
|
||||
xmlSb.append("<Title><![CDATA[公众平台官网链接]]></Title>");
|
||||
xmlSb.append("<Description><![CDATA[公众平台官网链接]]></Description>");
|
||||
xmlSb.append("<Url><![CDATA[url]]></Url>");
|
||||
xmlSb.append("<MsgId>1234567890123456</MsgId>");
|
||||
xmlSb.append("</xml>");
|
||||
String response = push(xmlSb.toString());
|
||||
Assert.assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package com.foxinmy.weixin4j.server.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
public class MessagePush {
|
||||
|
||||
private final String server = "http://localhost:30000";
|
||||
private final HttpClient httpClient;
|
||||
private final HttpPost httpPost;
|
||||
private final HttpGet httpGet;
|
||||
|
||||
public MessagePush() {
|
||||
httpClient = new DefaultHttpClient();
|
||||
httpPost = new HttpPost();
|
||||
httpPost.setURI(URI.create(server));
|
||||
|
||||
httpGet = new HttpGet();
|
||||
httpGet.setURI(URI.create(server));
|
||||
}
|
||||
|
||||
public String get(String para) throws IOException {
|
||||
httpGet.setURI(URI.create(server + para));
|
||||
HttpResponse httpResponse = httpClient.execute(httpGet);
|
||||
return entity(httpResponse);
|
||||
}
|
||||
|
||||
public String push(String xml) throws IOException {
|
||||
return push("", xml);
|
||||
}
|
||||
|
||||
public String push(String para, String xml) throws IOException {
|
||||
httpPost.setURI(URI.create(server + para));
|
||||
httpPost.setEntity(new StringEntity(xml, StandardCharsets.UTF_8));
|
||||
HttpResponse httpResponse = httpClient.execute(httpPost);
|
||||
return entity(httpResponse);
|
||||
}
|
||||
|
||||
private String entity(HttpResponse httpResponse) throws IOException {
|
||||
StatusLine statusLine = httpResponse.getStatusLine();
|
||||
|
||||
int status = statusLine.getStatusCode();
|
||||
if (status != HttpStatus.SC_OK) {
|
||||
throw new IOException(Integer.toString(status) + "request fail");
|
||||
}
|
||||
if (status == HttpStatus.SC_MOVED_PERMANENTLY
|
||||
|| status == HttpStatus.SC_MOVED_TEMPORARILY) {
|
||||
throw new IOException(Integer.toString(status) + "uri moved");
|
||||
}
|
||||
return EntityUtils.toString(httpResponse.getEntity(),
|
||||
StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.foxinmy.weixin4j.server.test;
|
||||
|
||||
import com.foxinmy.weixin4j.startup.WeixinServerBootstrap;
|
||||
|
||||
/**
|
||||
* 服务启动
|
||||
*
|
||||
* @className ServerStarup
|
||||
* @author jy
|
||||
* @date 2015年5月7日
|
||||
* @since JDK 1.7
|
||||
* @see
|
||||
*/
|
||||
public class ServerStarup {
|
||||
public static void main(String[] args) {
|
||||
String appid = "";
|
||||
String token = "";
|
||||
String aesKey = "";
|
||||
WeixinServerBootstrap.startup(appid, token, aesKey);
|
||||
//WeixinServerBootstrap.startup(token);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user