diff --git a/weixin4j-base/CHANGE.md b/weixin4j-base/CHANGE.md index 48f32b3e..4759d5c1 100644 --- a/weixin4j-base/CHANGE.md +++ b/weixin4j-base/CHANGE.md @@ -48,4 +48,8 @@ * 2015-04-19 - + 删除ActionMapping相关类 \ No newline at end of file + + 删除ActionMapping相关类 + +* 2015-05-07 + + + 删除ResponseTuple接口 \ No newline at end of file diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Image.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Image.java index 798388dc..c010ab74 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Image.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Image.java @@ -6,7 +6,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 图片对象 *

- * 可用于「被动消息」「客服消息」「群发消息」 + * 可用于「客服消息」「群发消息」 *

* * @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; diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Music.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Music.java index 004a9bf7..1332ad6c 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Music.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Music.java @@ -6,7 +6,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 音乐对象 *

- * 可用于「被动消息」「客服消息」 + * 可用于「客服消息」 *

* * @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"; } - + /** * 音乐标题 */ diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/News.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/News.java index a2314a0b..7c5d06b9 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/News.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/News.java @@ -9,7 +9,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 图文对象 *

- * 可用于「被动消息」「客服消息」 + * 可用于「客服消息」 *

* * @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; diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/ResponseTuple.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/ResponseTuple.java deleted file mode 100644 index b7a7d906..00000000 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/ResponseTuple.java +++ /dev/null @@ -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 { - -} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Text.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Text.java index c7d705a0..f86d4a1d 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Text.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Text.java @@ -5,7 +5,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 文本对象 *

- * 可用于「被动消息」「客服消息」「群发消息」 + * 可用于「客服消息」「群发消息」 *

* * @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; diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Trans.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Trans.java deleted file mode 100644 index 88eb115f..00000000 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Trans.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.foxinmy.weixin4j.tuple; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 转移到客服 - *

- * 可用于「被动消息」 - *

- * - * @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 + "]"; - } - -} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Video.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Video.java index a59cfe51..479acb08 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Video.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Video.java @@ -7,7 +7,7 @@ import com.thoughtworks.xstream.annotations.XStreamOmitField; /** * 视频对象 *

- * 可用于「被动消息」「客服消息」 + * 可用于「客服消息」 *

* * @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; diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Voice.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Voice.java index 578ad929..bdcb4e09 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Voice.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/tuple/Voice.java @@ -1,10 +1,9 @@ package com.foxinmy.weixin4j.tuple; - /** * 语音对象 *

- * 可用于「被动消息」「客服消息」「群发消息」 + * 可用于「客服消息」「群发消息」 *

* * @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; diff --git a/weixin4j-mp/src/test/java/com/foxinmy/weixin4j/mp/test/MenuTest.java b/weixin4j-mp/src/test/java/com/foxinmy/weixin4j/mp/test/MenuTest.java index 557d7184..f3caeab8 100644 --- a/weixin4j-mp/src/test/java/com/foxinmy/weixin4j/mp/test/MenuTest.java +++ b/weixin4j-mp/src/test/java/com/foxinmy/weixin4j/mp/test/MenuTest.java @@ -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); diff --git a/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/TagApi.java b/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/TagApi.java index 0736c440..31891702 100644 --- a/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/TagApi.java +++ b/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/TagApi.java @@ -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 { } /** - * 新增标签成员(标签对管理组可见且未加锁,成员属于管理组管辖范围)
+ * 新增标签成员(标签对管理组可见且未加锁,成员属于管理组管辖范围。)
* 若部分userid非法,则在text中体现 * * @param tagId @@ -144,7 +144,7 @@ public class TagApi extends QyApi { } /** - * 删除标签成员(标签对管理组可见且未加锁,成员属于管理组管辖范围)
+ * 删除标签成员(标签对管理组未加锁,成员属于管理组管辖范围)
* 若部分userid非法,则在text中体现 * * @param tagId diff --git a/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/model/Callback.java b/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/model/Callback.java index 70cfe3b2..f2205028 100644 --- a/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/model/Callback.java +++ b/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/model/Callback.java @@ -49,4 +49,9 @@ public class Callback implements Serializable { return aesKey; } + @Override + public String toString() { + return "Callback [url=" + url + ", token=" + token + ", aesKey=" + + aesKey + "]"; + } } diff --git a/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/model/Party.java b/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/model/Party.java index a83622a7..43ea9807 100644 --- a/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/model/Party.java +++ b/weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/model/Party.java @@ -16,7 +16,7 @@ public class Party implements Serializable { private static final long serialVersionUID = -2567893218591084288L; /** - * 部门ID + * 部门ID,指定时必须大于1,不指定时则自动生成. */ private int id; /** diff --git a/weixin4j-server/CHANGE.md b/weixin4j-server/CHANGE.md index f9950329..f7744eef 100644 --- a/weixin4j-server/CHANGE.md +++ b/weixin4j-server/CHANGE.md @@ -16,4 +16,8 @@ + 新增客服创建、关闭、转接会话事件 - + 新增deploy.xml远程部署ant脚本 \ No newline at end of file + + 新增deploy.xml远程部署ant脚本 + +* 2015-05-07 + + + 完成基本骨架 \ No newline at end of file diff --git a/weixin4j-server/pom.xml b/weixin4j-server/pom.xml index 4395f07a..85ff80a9 100644 --- a/weixin4j-server/pom.xml +++ b/weixin4j-server/pom.xml @@ -44,5 +44,27 @@ junit junit + + org.apache.httpcomponents + httpcore + ${httpclient.version} + test + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + commons-codec + commons-codec + + + org.apache.httpcomponents + httpcore + + + test + \ No newline at end of file diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/message/HttpWeixinMessage.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/message/HttpWeixinMessage.java index d1a9b504..c35dcd38 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/message/HttpWeixinMessage.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/message/HttpWeixinMessage.java @@ -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 + "]"; } } diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/message/ResponseMessage.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/message/ResponseMessage.java new file mode 100644 index 00000000..1d674927 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/message/ResponseMessage.java @@ -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(""); + xmlContent.append(String.format( + "", + message.getFromUserName())); + xmlContent.append(String.format( + "", + message.getToUserName())); + xmlContent.append(String.format( + "", + System.currentTimeMillis() / 1000l)); + xmlContent.append(String.format("", + response.getMsgType())); + xmlContent.append(response.toContent()); + xmlContent.append(""); + return xmlContent.toString(); + } + + @Override + public String toString() { + return "ResponseMessage [message=" + message + ", response=" + response + + "]"; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/message/WeixinMessage.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/message/WeixinMessage.java new file mode 100644 index 00000000..6071a6be --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/message/WeixinMessage.java @@ -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; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/model/AesToken.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/model/AesToken.java new file mode 100644 index 00000000..262dd3fe --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/model/AesToken.java @@ -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; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/model/WeixinAccount.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/model/WeixinAccount.java deleted file mode 100644 index 19a5f75e..00000000 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/model/WeixinAccount.java +++ /dev/null @@ -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; - } -} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/BlankResponse.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/BlankResponse.java new file mode 100644 index 00000000..f172bacc --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/BlankResponse.java @@ -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; + } + +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/DebugResponse.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/DebugResponse.java new file mode 100644 index 00000000..3cc3d89f --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/DebugResponse.java @@ -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; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/ImageResponse.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/ImageResponse.java new file mode 100644 index 00000000..b0023c58 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/ImageResponse.java @@ -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( + "", mediaId); + } + + public String getMediaId() { + return mediaId; + } + + @Override + public String getMsgType() { + return "image"; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/MusicResponse.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/MusicResponse.java new file mode 100644 index 00000000..f5088674 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/MusicResponse.java @@ -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(""); + content.append(String.format( + "", thumbMediaId)); + content.append(String.format("<![CDATA[%s]]>", + title != null ? title : "")); + content.append(String.format( + "", + desc != null ? desc : "")); + content.append(String.format("", + musicUrl != null ? musicUrl : "")); + content.append(String.format("", + hqMusicUrl != null ? hqMusicUrl : "")); + content.append(""); + 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"; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/NewsResponse.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/NewsResponse.java new file mode 100644 index 00000000..0dc71d24 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/NewsResponse.java @@ -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
articleList; + + public NewsResponse(List
articleList) { + this.articleList = articleList; + } + + public NewsResponse(Article article) { + this.articleList = new ArrayList
(); + 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
getArticleList() { + return articleList; + } + + @Override + public String toContent() { + StringBuilder content = new StringBuilder(); + content.append(String.format("%d", + articleList.size())); + content.append(""); + for (Article article : articleList) { + content.append(""); + content.append(String.format("<![CDATA[%s]]>", + article.getTitle() != null ? article.getTitle() : "")); + content.append(String.format( + "", + article.getDesc() != null ? article.getDesc() : "")); + content.append(String.format("", + article.getUrl() != null ? article.getUrl() : "")); + content.append(String.format("", + article.getPicUrl() != null ? article.getPicUrl() : "")); + content.append(""); + } + content.append(""); + 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 + "]"; + } + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/TextResponse.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/TextResponse.java new file mode 100644 index 00000000..a9a4c9c1 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/TextResponse.java @@ -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); + } + + @Override + public String getMsgType() { + return "text"; + } + + public String getContent() { + return content; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/TransferCustomerResponse.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/TransferCustomerResponse.java new file mode 100644 index 00000000..39971847 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/TransferCustomerResponse.java @@ -0,0 +1,43 @@ +package com.foxinmy.weixin4j.response; + +/** + * 消息转移到客服 + * + * @className TransferCustomerResponse + * @author jy + * @date 2015年5月5日 + * @since JDK 1.7 + * @see 转移消息到多客服 + */ +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("", + kfAccount)); + } + return content.toString(); + } + + @Override + public String getMsgType() { + return "transfer_customer_service"; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/VideoResponse.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/VideoResponse.java new file mode 100644 index 00000000..20f3ee1e --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/VideoResponse.java @@ -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(""); + 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"; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/VoiceResponse.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/VoiceResponse.java new file mode 100644 index 00000000..a09aa1b1 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/VoiceResponse.java @@ -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( + "", mediaId); + } + + public String getMediaId() { + return mediaId; + } + + @Override + public String getMsgType() { + return "voice"; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/WeixinResponse.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/WeixinResponse.java new file mode 100644 index 00000000..20b72c53 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/response/WeixinResponse.java @@ -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 订阅号、服务号的被动响应消息 + * @see 企业号的被动响应消息 + */ +public interface WeixinResponse { + /** + * 消息类型 + * + * @return + */ + public String getMsgType(); + + /** + * 消息内容 + * + * @return + */ + public String toContent(); +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageDecoder.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageDecoder.java index 59eff522..9366faf1 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageDecoder.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageDecoder.java @@ -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 { 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 out) throws Exception { + List 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); } diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageEncoder.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageEncoder.java new file mode 100644 index 00000000..fd9777b1 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageEncoder.java @@ -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 加密接入指引 + */ +public class WeixinMessageEncoder extends + MessageToMessageEncoder { + 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 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(""); + content.append(String.format("", nonce)); + content.append(String.format("", + timestamp)); + content.append(String.format( + "", msgSignature)); + content.append(String.format("", + encrtypt)); + content.append(""); + + out.add(HttpUtil.createWeixinMessageResponse(content.toString(), + Consts.CONTENTTYPE$APPLICATION_XML)); + + log.info("\n=================aes encrtypt out================="); + log.info("{}", content); + } +} \ No newline at end of file diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageHandler.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageHandler.java index 075ec7b4..c790d226 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageHandler.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageHandler.java @@ -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 { - 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); - }*/ + } } } diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinServerInitializer.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinServerInitializer.java index 8e0151bc..30b69db0 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinServerInitializer.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinServerInitializer.java @@ -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 { + 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)); } } diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/startup/WeixinServerBootstrap.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/startup/WeixinServerBootstrap.java index ed9bc5f9..702e2d9f 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/startup/WeixinServerBootstrap.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/startup/WeixinServerBootstrap.java @@ -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(); diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/Base64.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/Base64.java new file mode 100644 index 00000000..4d4cba7c --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/Base64.java @@ -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); + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/Consts.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/Consts.java index e9b3f739..07842477 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/Consts.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/Consts.java @@ -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"; } diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/DigestUtil.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/DigestUtil.java index 1090e849..dc3691d3 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/DigestUtil.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/DigestUtil.java @@ -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)); } } diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/HexUtil.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/HexUtil.java new file mode 100644 index 00000000..dff126f5 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/HexUtil.java @@ -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); + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/MessageUtil.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/MessageUtil.java new file mode 100644 index 00000000..8add46cc --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/MessageUtil.java @@ -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 接入指南 + */ + 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; + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/StringUtil.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/StringUtil.java new file mode 100644 index 00000000..dd7f7e69 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/StringUtil.java @@ -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); + } +} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/xml/EncryptMessageHandler.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/xml/EncryptMessageHandler.java new file mode 100644 index 00000000..98d79af9 --- /dev/null +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/xml/EncryptMessageHandler.java @@ -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(); + } +} diff --git a/weixin4j-server/src/main/resources/logback.xml b/weixin4j-server/src/main/resources/logback.xml deleted file mode 100644 index 550ca220..00000000 --- a/weixin4j-server/src/main/resources/logback.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - UTF-8 - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - %msg%n - - - - - - - - - - - - /tmp/weixin/log/server/weixin.server.%d{yyyy-MM-dd}.log - - 30 - - - - UTF-8 - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - 0 - - 512 - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/weixin4j-server/src/main/resources/netty.properties b/weixin4j-server/src/main/resources/netty.properties deleted file mode 100644 index ddecb819..00000000 --- a/weixin4j-server/src/main/resources/netty.properties +++ /dev/null @@ -1,4 +0,0 @@ -# netty\u76d1\u542c\u7aef\u53e3 -port=8080 -# \u5de5\u4f5c\u8fdb\u7a0b\u6570 -workerThreads=20 \ No newline at end of file diff --git a/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/AesMsgTest.java b/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/AesMsgTest.java new file mode 100644 index 00000000..16d7f53e --- /dev/null +++ b/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/AesMsgTest.java @@ -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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1415979365"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1415980001"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + String response = push(para, xmlSb.toString()); + Assert.assertNotNull(response); + System.err.println(response); + } +} diff --git a/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/EventMsgTest.java b/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/EventMsgTest.java new file mode 100644 index 00000000..d2bff32f --- /dev/null +++ b/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/EventMsgTest.java @@ -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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("23.137466"); + xmlSb.append("113.352425"); + xmlSb.append("119.385040"); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + String response = push(xmlSb.toString()); + Assert.assertNotNull(response); + System.out.println(response); + } +} diff --git a/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/InMsgTest.java b/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/InMsgTest.java new file mode 100644 index 00000000..52a4992d --- /dev/null +++ b/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/InMsgTest.java @@ -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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1348831860"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1234567890123456"); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1234567890123456"); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1234567890123456"); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1234567890123456"); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1234567890123456"); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append("23.134521"); + xmlSb.append("113.358803"); + xmlSb.append("20"); + xmlSb.append(""); + xmlSb.append("1234567890123456"); + xmlSb.append(""); + 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(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("123456789"); + xmlSb.append(""); + xmlSb.append("<![CDATA[公众平台官网链接]]>"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1234567890123456"); + xmlSb.append(""); + String response = push(xmlSb.toString()); + Assert.assertNotNull(response); + System.out.println(response); + } +} diff --git a/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/MessagePush.java b/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/MessagePush.java new file mode 100644 index 00000000..e8f4c416 --- /dev/null +++ b/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/MessagePush.java @@ -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); + } +} \ No newline at end of file diff --git a/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/ServerStarup.java b/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/ServerStarup.java new file mode 100644 index 00000000..b616c2bf --- /dev/null +++ b/weixin4j-server/src/test/java/com/foxinmy/weixin4j/server/test/ServerStarup.java @@ -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); + } +}