netty服务器的基本骨架编写

This commit is contained in:
jinyu 2015-05-07 08:44:59 +08:00
parent 5de9ece244
commit 3f5bfe9e71
49 changed files with 1890 additions and 385 deletions

View File

@ -48,4 +48,8 @@
* 2015-04-19
+ 删除ActionMapping相关类
+ 删除ActionMapping相关类
* 2015-05-07
+ 删除ResponseTuple接口

View File

@ -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;

View File

@ -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";
}
/**
* 音乐标题
*/

View File

@ -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;

View File

@ -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 {
}

View File

@ -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;

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -49,4 +49,9 @@ public class Callback implements Serializable {
return aesKey;
}
@Override
public String toString() {
return "Callback [url=" + url + ", token=" + token + ", aesKey="
+ aesKey + "]";
}
}

View File

@ -16,7 +16,7 @@ public class Party implements Serializable {
private static final long serialVersionUID = -2567893218591084288L;
/**
* 部门ID
* 部门ID,指定时必须大于1,不指定时则自动生成.
*/
private int id;
/**

View File

@ -16,4 +16,8 @@
+ 新增客服创建、关闭、转接会话事件
+ 新增deploy.xml远程部署ant脚本
+ 新增deploy.xml远程部署ant脚本
* 2015-05-07
+ 完成基本骨架

View File

@ -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>

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

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

View File

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

View File

@ -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;
/**
* 图片链接支持JPGPNG格式较好的效果为大图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 + "]";
}
}
}

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}
}

View File

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

View File

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

View File

@ -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>

View File

@ -1,4 +0,0 @@
# netty\u76d1\u542c\u7aef\u53e3
port=8080
# \u5de5\u4f5c\u8fdb\u7a0b\u6570
workerThreads=20

View File

@ -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 + "&timestamp=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&timestamp=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&timestamp=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&timestamp=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);
}
}

View File

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

View File

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

View File

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

View File

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