新增获取微信服务器IP地址接口以及消息AES加密解密实现

This commit is contained in:
jy.hu
2014-11-15 20:45:13 +08:00
parent 717b9c7abf
commit 282699d95c
38 changed files with 893 additions and 218 deletions
+13 -5
View File
@@ -48,18 +48,26 @@ weixin4j-mp
+ `weixin-mp`分离为`weixin-mp-api``weixin-mp-server`两个工程
+ **weixin-mp**: 新增`支付`模块
+ **weixin4j-mp**: 新增`支付`模块
* 2014-11-06
+ **weixin-mp-api**: 新增V3版本`退款接口`
+ **weixin4j-mp-api**: 新增V3版本`退款接口`
* 2014-11-08
+ **weixin-mp-api**: 新增V2版本`退款申请``退款查询``对账单下载`三个接口
+ **weixin4j-mp-api**: 新增V2版本`退款申请``退款查询``对账单下载`三个接口
+ **weixin-mp-api**: 新增一个简单的`语义理解`接口
+ **weixin4j-mp-api**: 新增一个简单的`语义理解`接口
* 2014-11-11
+ **weixin-mp-api**: 自定义`assembly``weixin4j-base`工程也一起打包(`weixin4j-mp-api-full.jar`)
+ **weixin4j-mp-api**: 自定义`assembly``weixin4j-base`工程也一起打包(`weixin4j-mp-api-full.jar`)
* 2014-11-15
+ **weixin4j-mp-api**: 新增获取`微信服务器IP地址接口`
+ **weixin4j-mp-server**: 解决`server工程`打包不能运行问题(`ClassUtil`无法获取jar包里面的类)
+ **weixin4j-mp-server**: 新增被动消息的`加密`以及回复消息的`解密`
+6 -1
View File
@@ -46,6 +46,7 @@ weixin.properties说明
> account={"appId":"appId","appSecret":"appSecret",
> "token":"开放者的token 非必须","openId":"公众号的openid 非必须",
> "encodingAesKey":"公众号设置了加密方式且为「安全模式」需要填入",
> "mchId":"V3.x版本下的微信商户号",
> "partnerId":"财付通的商户号","partnerKey":"财付通商户权限密钥Key",
> "version":"针对微信支付的版本号(2,3),如果不填则按照mchId非空与否来判断",
@@ -103,4 +104,8 @@ weixin.properties说明
* 2014-11-11
+ 自定义`assembly``weixin4j-base`工程也一起打包(`weixin4j-mp-api-full.jar`)
+ 自定义`assembly``weixin4j-base`工程也一起打包(`weixin4j-mp-api-full.jar`)
* 2014-11-15
+ 新增获取`微信服务器IP地址接口`
@@ -1,5 +1,8 @@
package com.foxinmy.weixin4j.mp.api;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException;
@@ -72,4 +75,21 @@ public class HelperApi extends BaseApi {
return response.getAsObject(new TypeReference<SemResult>() {
});
}
}
/**
* 获取微信服务器IP地址
*
* @return IP地址
* @see <a
* href="http://mp.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E5%BE%AE%E4%BF%A1%E6%9C%8D%E5%8A%A1%E5%99%A8IP%E5%9C%B0%E5%9D%80">获取IP地址</a>
* @throws WeixinException
*/
public List<String> getcallbackip() throws WeixinException {
String getcallbackip_uri = getRequestUri("getcallbackip_uri");
Token token = tokenHolder.getToken();
Response response = request.post(String.format(getcallbackip_uri,
token.getAccessToken()));
return JSON.parseArray(response.getAsJson().getString("ip_list"),
String.class);
}
}
@@ -72,6 +72,8 @@ updateremark_uri={api_cgi_url}/user/info/updateremark?access_token=%s
template_send_uri={api_cgi_url}/message/template/send?access_token=%s
# \u8bed\u4e49\u7406\u89e3
semantic_uri={api_base_url}/semantic/semproxy/search?access_token=%s
# \u5fae\u4fe1\u670d\u52a1\u5730\u5740
getcallbackip_uri={api_cgi_url}/getcallbackip?access_token=%s
# \u8ba2\u5355\u67e5\u8be2
orderquery_uri={api_base_url}/pay/orderquery?access_token=%s
@@ -49,7 +49,6 @@ public class PayAction {
// V3 支付
// 此处的openid为微信用户的openid
WeixinAccount weixinAccount = ConfigUtil.getWeixinAccount();
weixinAccount.setOpenId("用户的openId");
payPackage = new PayPackageV3(weixinAccount, "用户openid", "商品描述",
"系统内部订单号", 1d, "IP地址", TradeType.JSAPI);
// V2 支付
@@ -147,7 +146,7 @@ public class PayAction {
*
*
* @param inputStream
* 订单细腻
* 订单回调
* @return &ltxml&gt<br>
* &ltreturn_code&gtSUCCESS/FAIL&lt/return_code&gt<br>
* &ltreturn_msg&gt如非空,为错误 原因签名失败参数格式校验错误&lt/return_msg&gt<br>
@@ -0,0 +1,5 @@
package com.foxinmy.weixin4j.mp.type;
public enum EncryptType {
RAW, AES
}
@@ -2,6 +2,7 @@
# \u516c\u4f17\u53f7\u4fe1\u606f
account={"appId":"wx4ab8f8de58159a57","appSecret":"1d4eb0f4bf556aaed539f30ed05ca795",\
"token":"\u5f00\u653e\u8005\u7684token \u975e\u5fc5\u987b","openId":"\u516c\u4f17\u53f7\u7684openid \u975e\u5fc5\u987b",\
"encodingAesKey":"\u516c\u4f17\u53f7\u8bbe\u7f6e\u4e86\u52a0\u5bc6\u65b9\u5f0f\u4e14\u4e3a\u300c\u5b89\u5168\u6a21\u5f0f\u300d\u9700\u8981\u586b\u5165",\
"mchId":"V3.x\u7248\u672c\u4e0b\u7684\u5fae\u4fe1\u5546\u6237\u53f7",\
"version":3,\
"partnerId":"\u8d22\u4ed8\u901a\u7684\u5546\u6237\u53f7","partnerKey":"\u8d22\u4ed8\u901a\u5546\u6237\u6743\u9650\u5bc6\u94a5Key",\
@@ -0,0 +1,25 @@
package com.foxinmy.weixin4j.mp.test;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.mp.api.HelperApi;
public class HelpTest extends TokenTest {
private HelperApi helperApi;
@Before
public void init() {
helperApi = new HelperApi(tokenHolder);
}
@Test
public void getcallbackip() throws WeixinException {
List<String> ipList = helperApi.getcallbackip();
Assert.assertFalse(ipList.isEmpty());
}
}
@@ -0,0 +1,64 @@
package com.foxinmy.weixin4j.mp.test.msg;
import java.io.IOException;
import org.junit.Assert;
import org.junit.Test;
import com.foxinmy.weixin4j.exception.WeixinException;
public class AesMsgTest extends MessagePush {
StringBuilder xmlSb = new StringBuilder();
@Test
public void testValidate() throws WeixinException, IOException {
String para = "?signature=0d2366aedb4f3531cfa4297c1e4ea7eece2311d9&echostr=2143641595566077626&timestamp=1415951914&nonce=165976363";
xmlSb.delete(0, xmlSb.length());
String response = get(para);
Assert.assertEquals("2143641595566077626", response);
}
@Test
public void testType1() throws WeixinException, 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);
}
@Test
public void testType2() throws WeixinException, IOException {
String para = "?signature=ad05f836772d1cbba1ff2edb7be4b9c9fb3a43d5&timestamp=1415980001&nonce=1803216865&encrypt_type=raw&msg_signature=c0d38e9ca00548f7142627ec2908a4fe8481025e";
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("</xml>");
String response = push(para, xmlSb.toString());
Assert.assertNotNull(response);
}
@Test
public void testType3() throws WeixinException, 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);
}
}
@@ -3,12 +3,12 @@ package com.foxinmy.weixin4j.mp.test.msg;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ResourceBundle;
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;
@@ -18,20 +18,40 @@ import com.foxinmy.weixin4j.exception.WeixinException;
public class MessagePush {
private HttpClient httpClient;
private final String server = "http://localhost:8090";
private final HttpClient httpClient;
private final HttpPost httpPost;
private final HttpGet httpGet;
public MessagePush() {
this.httpClient = new DefaultHttpClient();
ResourceBundle config = ResourceBundle.getBundle("netty");
httpClient = new DefaultHttpClient();
httpPost = new HttpPost();
httpPost.setURI(URI.create(String.format("http://localhost:%s",
Integer.parseInt(config.getString("port")))));
httpPost.setURI(URI.create(server));
httpGet = new HttpGet();
httpGet.setURI(URI.create(server));
}
public String get(String para) throws WeixinException, IOException {
httpGet.setURI(URI.create(server + para));
HttpResponse httpResponse = httpClient.execute(httpGet);
return entity(httpResponse);
}
public String push(String xml) throws WeixinException, IOException {
return push("", xml);
}
public String push(String para, String xml) throws WeixinException,
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 WeixinException,
IOException {
StatusLine statusLine = httpResponse.getStatusLine();
int status = statusLine.getStatusCode();
@@ -45,4 +65,4 @@ public class MessagePush {
return EntityUtils.toString(httpResponse.getEntity(),
StandardCharsets.UTF_8);
}
}
}
@@ -7,6 +7,7 @@ import org.junit.Test;
import com.foxinmy.weixin4j.mp.response.ArticleResponse;
import com.foxinmy.weixin4j.mp.response.ImageResponse;
import com.foxinmy.weixin4j.mp.response.MusicResponse;
import com.foxinmy.weixin4j.mp.response.TextResponse;
import com.foxinmy.weixin4j.mp.response.VideoResponse;
import com.foxinmy.weixin4j.mp.response.VoiceResponse;
import com.foxinmy.weixin4j.msg.BaseMessage;
@@ -31,7 +32,7 @@ public class OutMsgTest {
@Test
public void text() throws DocumentException {
TextMessage message = new TextMessage("text", inMessage);
TextResponse message = new TextResponse("text", inMessage);
System.out.println(message.toXml());
}
+8 -1
View File
@@ -28,6 +28,7 @@ weixin4j-mp-server
> account={"appId":"appId","appSecret":"appSecret",
> "token":"开放者的token 非必须","openId":"公众号的openid 非必须",
> "encodingAesKey":"公众号设置了加密方式且为「安全模式」需要填入",
> "mchId":"V3.x版本下的微信商户号",
> "partnerId":"财付通的商户号","partnerKey":"财付通商户权限密钥Key",
> "version":"针对微信支付的版本号(目前可能为2,3),如果不填则按照mchId非空与否来做判断",
@@ -48,4 +49,10 @@ weixin4j-mp-server
-------
* 2014-11-03
+ 得到`weixin-mp-server`工程
+ 得到`weixin-mp-server`工程
* 2014-11-15
+ 解决`server工程`打包不能运行问题(`ClassUtil`无法获取jar包里面的类)
+ 新增被动消息的`加密`以及回复消息的`解密`
+1 -1
View File
@@ -13,7 +13,7 @@
<url>https://github.com/foxinmy/weixin4j/tree/master/weixin4j-mp/weixin4j-server</url>
<description>微信公众号服务</description>
<build>
<finalName>weixin4j-mp-server</finalName>
<finalName>weixin-mp-server</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -6,6 +6,7 @@
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<dependencySets>
<dependencySet>
<useProjectArtifact>true</useProjectArtifact>
@@ -17,28 +17,27 @@ import com.foxinmy.weixin4j.xml.XStream;
* @author jy
* @date 2014年10月12日
* @since JDK 1.7
* @see
* @see com.foxinmy.weixin4j.mp.action.WeixinAction
*/
@SuppressWarnings("unchecked")
public abstract class AbstractAction<M extends BaseMessage> implements
WeixinAction {
public abstract BaseResponse execute(M inMessage);
@SuppressWarnings("unchecked")
@Override
public String execute(String msg) throws DocumentException {
public BaseResponse execute(String msg) throws DocumentException {
BaseMessage message = MessageUtil.xml2msg(msg);
if (message == null) {
Class<M> messageClass = getGenericType();
XStream xstream = XStream.get();
xstream.processAnnotations(messageClass);
xstream.alias("xml", messageClass);
return execute(xstream.fromXML(msg, messageClass)).toXml();
return execute(xstream.fromXML(msg, messageClass));
}
return execute((M) message).toXml();
return execute((M) message);
}
@SuppressWarnings("unchecked")
private Class<M> getGenericType() {
Class<M> clazz = null;
Type type = getClass().getGenericSuperclass();
@@ -1,21 +1,21 @@
package com.foxinmy.weixin4j.mp.action;
import com.foxinmy.weixin4j.mp.response.TextResponse;
import com.foxinmy.weixin4j.mp.response.BaseResponse;
import com.foxinmy.weixin4j.msg.BaseMessage;
/**
* 输出空白消息
* 回复一个空字符串 而不是一个XML结构体中content字段的内容为空
*
* @className BlankAction
* @author jy.hu
* @date 2014年10月2日
* @since JDK 1.7
* @see
* @see com.foxinmy.weixin4j.mp.action.AbstractAction
*/
public class BlankAction<M extends BaseMessage> extends AbstractAction<M> {
@Override
public TextResponse execute(M inMessage) {
return new TextResponse("", inMessage);
public BaseResponse execute(M inMessage) {
return null;
}
}
@@ -1,48 +0,0 @@
package com.foxinmy.weixin4j.mp.action;
import io.netty.handler.codec.http.QueryStringDecoder;
import java.util.List;
import java.util.Map;
import org.dom4j.DocumentException;
import com.foxinmy.weixin4j.mp.mapping.Action;
import com.foxinmy.weixin4j.type.MessageType;
import com.foxinmy.weixin4j.util.ConfigUtil;
import com.foxinmy.weixin4j.util.MessageUtil;
/**
* 用于校验消息是否来自微信
*
* @className SignatureAction
* @author jy
* @date 2014年10月24日
* @since JDK 1.7
* @see
*/
@Action(msgType = MessageType.signature)
public class SignatureAction implements WeixinAction {
@Override
public String execute(String uri) throws DocumentException {
String[] paths = uri.split("\\?");
if (paths == null || paths.length < 2) {
return "";
}
QueryStringDecoder queryDecoder = new QueryStringDecoder(paths[1],
false);
Map<String, List<String>> parameters = queryDecoder.parameters();
String echostr = parameters.containsKey("echostr") ? parameters.get(
"echostr").get(0) : null;
String timestamp = parameters.containsKey("timestamp") ? parameters
.get("timestamp").get(0) : null;
String nonce = parameters.containsKey("nonce") ? parameters
.get("nonce").get(0) : null;
String signature = parameters.containsKey("signature") ? parameters
.get("signature").get(0) : null;
String token = ConfigUtil.getValue("app_token");
return MessageUtil.signature(token, echostr, timestamp, nonce,
signature);
}
}
@@ -2,6 +2,8 @@ package com.foxinmy.weixin4j.mp.action;
import org.dom4j.DocumentException;
import com.foxinmy.weixin4j.mp.response.BaseResponse;
/**
* 消息处理接口
*
@@ -14,5 +16,5 @@ import org.dom4j.DocumentException;
* @see com.foxinmy.weixin4j.mp.action.DebugAction
*/
public interface WeixinAction {
public String execute(String msg) throws DocumentException;
public BaseResponse execute(String msg) throws DocumentException;
}
@@ -0,0 +1,133 @@
package com.foxinmy.weixin4j.mp.model;
import io.netty.handler.codec.http.HttpMethod;
import java.io.Serializable;
import com.foxinmy.weixin4j.mp.type.EncryptType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("xml")
public class HttpWeixinMessage implements Serializable {
private static final long serialVersionUID = -9157395300510879866L;
// 以下字段是加密方式为「安全模式」时的参数
@XStreamAlias("ToUserName")
private String toUserName;
@XStreamAlias("Encrypt")
private String encryptContent;
private EncryptType encryptType;
private String msgSignature;
// 以下字段每次被动消息时都会带上
private String echoStr;
private String timeStamp;
private String nonce;
private String signature;
private String token;
// xml消息主体
private String xmlContent;
// request method
private HttpMethod method;
public String getToUserName() {
return toUserName;
}
public void setToUserName(String toUserName) {
this.toUserName = toUserName;
}
public String getEncryptContent() {
return encryptContent;
}
public void setEncryptContent(String encryptContent) {
this.encryptContent = encryptContent;
}
public EncryptType getEncryptType() {
return encryptType;
}
public void setEncryptType(EncryptType encryptType) {
this.encryptType = encryptType;
}
public String getMsgSignature() {
return msgSignature;
}
public void setMsgSignature(String msgSignature) {
this.msgSignature = msgSignature;
}
public String getEchoStr() {
return echoStr;
}
public void setEchoStr(String echoStr) {
this.echoStr = echoStr;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
public String getNonce() {
return nonce;
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getXmlContent() {
return xmlContent;
}
public void setXmlContent(String xmlContent) {
this.xmlContent = xmlContent;
}
public HttpMethod getMethod() {
return method;
}
public void setMethod(HttpMethod method) {
this.method = method;
}
@Override
public String toString() {
return "HttpMessage [toUserName=" + toUserName + ", encryptContent="
+ encryptContent + ", encryptType=" + encryptType
+ ", msgSignature=" + msgSignature + ", echoStr=" + echoStr
+ ", timeStamp=" + timeStamp + ", nonce=" + nonce
+ ", signature=" + signature + ", token=" + token
+ ", xmlContent=" + xmlContent + ", method=" + method + "]";
}
}
@@ -1 +1,5 @@
微信服务netty启动类
WeixinMessageDecoder:对微信消息进行解码
WeixinMessageEncoder:对微信消息进行编码
WeixinServerHandler:微信请求处理类
@@ -0,0 +1,81 @@
package com.foxinmy.weixin4j.mp.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.foxinmy.weixin4j.model.WeixinAccount;
import com.foxinmy.weixin4j.mp.model.HttpWeixinMessage;
import com.foxinmy.weixin4j.mp.type.EncryptType;
import com.foxinmy.weixin4j.util.ConfigUtil;
import com.foxinmy.weixin4j.util.MessageUtil;
import com.foxinmy.weixin4j.xml.XStream;
/**
* 微信消息解码类
*
* @className WeixinMessageDecoder
* @author jy
* @date 2014年11月13日
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E5%85%A5%E6%8C%87%E5%BC%95">加密接入指引</a>
*/
public class WeixinMessageDecoder extends
MessageToMessageDecoder<FullHttpRequest> {
private final Logger log = LoggerFactory.getLogger(getClass());
@Override
protected void decode(ChannelHandlerContext ctx, FullHttpRequest req,
List<Object> out) throws Exception {
WeixinAccount account = ConfigUtil.getWeixinAccount();
String xmlContent = req.content().toString(Consts.UTF_8);
HttpWeixinMessage message = new HttpWeixinMessage();
if (StringUtils.isNotBlank(xmlContent)) {
message = XStream.get(xmlContent, HttpWeixinMessage.class);
}
message.setMethod(req.getMethod());
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.getUri(),
true);
log.info("\n=================receive request=================");
log.info("{}", req.getMethod());
log.info("{}", req.getUri());
log.info("{}", xmlContent);
Map<String, List<String>> parameters = queryDecoder.parameters();
String encryptType = parameters.containsKey("encrypt_type") ? parameters
.get("encrypt_type").get(0) : EncryptType.RAW.name();
message.setEncryptType(EncryptType.valueOf(encryptType.toUpperCase()));
String msgSignature = parameters.containsKey("msg_signature") ? parameters
.get("msg_signature").get(0) : "";
message.setMsgSignature(msgSignature);
String echoStr = parameters.containsKey("echostr") ? parameters.get(
"echostr").get(0) : "";
message.setEchoStr(echoStr);
String timeStamp = parameters.containsKey("timestamp") ? parameters
.get("timestamp").get(0) : "";
message.setTimeStamp(timeStamp);
String nonce = parameters.containsKey("nonce") ? parameters
.get("nonce").get(0) : "";
message.setNonce(nonce);
String signature = parameters.containsKey("signature") ? parameters
.get("signature").get(0) : "";
message.setSignature(signature);
message.setXmlContent(xmlContent);
if (message.getEncryptType() == EncryptType.AES) {
message.setXmlContent(MessageUtil.aesDecrypt(account.getAppId(),
account.getEncodingAesKey(), message.getEncryptContent()));
}
message.setToken(account.getToken());
out.add(message);
}
}
@@ -0,0 +1,71 @@
package com.foxinmy.weixin4j.mp.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.HttpResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.foxinmy.weixin4j.model.WeixinAccount;
import com.foxinmy.weixin4j.mp.response.BaseResponse;
import com.foxinmy.weixin4j.mp.util.HttpUtil;
import com.foxinmy.weixin4j.util.ConfigUtil;
import com.foxinmy.weixin4j.util.DateUtil;
import com.foxinmy.weixin4j.util.MessageUtil;
import com.foxinmy.weixin4j.util.RandomUtil;
import com.foxinmy.weixin4j.xml.Map2ObjectConverter;
import com.foxinmy.weixin4j.xml.XStream;
import com.thoughtworks.xstream.core.ClassLoaderReference;
import com.thoughtworks.xstream.mapper.DefaultMapper;
/**
* 微信消息编码类
*
* @className WeixinMessageEncoder
* @author jy
* @date 2014年11月13日
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E5%85%A5%E6%8C%87%E5%BC%95">加密接入指引</a>
*/
public class WeixinMessageEncoder extends MessageToMessageEncoder<BaseResponse> {
private final Logger log = LoggerFactory.getLogger(getClass());
protected final static XStream mapXstream = XStream.get();
static {
mapXstream.alias("xml", Map.class);
mapXstream.registerConverter(new Map2ObjectConverter(new DefaultMapper(
new ClassLoaderReference(XStream.class.getClassLoader()))));
}
@Override
protected void encode(ChannelHandlerContext ctx, BaseResponse response,
List<Object> out) throws Exception {
WeixinAccount account = ConfigUtil.getWeixinAccount();
String xmlContent = response.toXml();
String nonce = RandomUtil.generateString(32);
String timestamp = DateUtil.timestamp2string();
String encrtypt = MessageUtil.aesEncrypt(account.getAppId(),
account.getEncodingAesKey(), xmlContent);
String msgSignature = MessageUtil.signature(account.getToken(), nonce,
timestamp, encrtypt);
Map<String, String> map = new HashMap<String, String>();
map.put("Encrypt", encrtypt);
map.put("MsgSignature", msgSignature);
map.put("TimeStamp", timestamp);
map.put("Nonce", nonce);
String content = mapXstream.toXML(map);
HttpResponse httpResponse = HttpUtil
.createWeixinMessageResponse(content);
out.add(httpResponse);
log.info("\n=================aes encrtypt out=================");
log.info("{}", map);
log.info("{}", content);
}
}
@@ -1,33 +1,26 @@
package com.foxinmy.weixin4j.mp.server;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import java.nio.charset.StandardCharsets;
import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.foxinmy.weixin4j.mp.action.WeixinAction;
import com.foxinmy.weixin4j.mp.mapping.ActionMapping;
import com.foxinmy.weixin4j.mp.model.HttpWeixinMessage;
import com.foxinmy.weixin4j.mp.response.BaseResponse;
import com.foxinmy.weixin4j.mp.type.EncryptType;
import com.foxinmy.weixin4j.mp.util.HttpUtil;
import com.foxinmy.weixin4j.util.MessageUtil;
public class WeixinServerHandler extends ChannelInboundHandlerAdapter {
public class WeixinServerHandler extends
SimpleChannelInboundHandler<HttpWeixinMessage> {
private final Logger log = LoggerFactory.getLogger(getClass());
@@ -37,49 +30,61 @@ public class WeixinServerHandler extends ChannelInboundHandlerAdapter {
this.actionMapping = actionMapping;
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws DocumentException {
if (msg instanceof FullHttpRequest) {
FullHttpRequest req = (FullHttpRequest) msg;
if (HttpHeaders.is100ContinueExpected(req)) {
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
return;
}
String xmlMsg = req.content().toString(StandardCharsets.UTF_8);
log.info("\n=================message in=================\n{}",
xmlMsg);
WeixinAction action = actionMapping.getAction(xmlMsg);
if (action == null) {
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.NOT_FOUND));
return;
}
String content = action.execute(xmlMsg);
log.info("\n=================message out=================\n{}",
content);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
OK, Unpooled.copiedBuffer(content, StandardCharsets.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain;charset=utf-8");
response.headers().set(CONTENT_LENGTH,
response.content().readableBytes());
if (!HttpHeaders.isKeepAlive(req)) {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
} else {
response.headers().set(CONNECTION, Values.KEEP_ALIVE);
ctx.write(response);
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx,
HttpWeixinMessage httpMessage) throws Exception {
log.info("\n=================message in=================\n{}",
httpMessage);
boolean validate = false;
if (httpMessage.getMethod() == HttpMethod.GET
|| httpMessage.getEncryptType() == EncryptType.RAW) {
validate = MessageUtil.signature(httpMessage.getToken(),
httpMessage.getTimeStamp(), httpMessage.getNonce()).equals(
httpMessage.getSignature());
if (httpMessage.getMethod() == HttpMethod.GET && validate) {
HttpResponse httpResponse = HttpUtil
.createWeixinMessageResponse(httpMessage.getEchoStr());
ctx.write(httpResponse);
return;
}
} else {
validate = MessageUtil.signature(httpMessage.getToken(),
httpMessage.getTimeStamp(), httpMessage.getNonce(),
httpMessage.getEncryptContent()).equals(
httpMessage.getMsgSignature());
}
if (!validate) {
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.FORBIDDEN));
return;
}
String xmlContent = httpMessage.getXmlContent();
WeixinAction action = actionMapping.getAction(xmlContent);
if (action == null) {
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.NOT_FOUND));
return;
}
BaseResponse response = action.execute(xmlContent);
log.info("\n=================message out=================\n{}",
response);
if (httpMessage.getEncryptType() == EncryptType.RAW) {
HttpResponse httpResponse = HttpUtil
.createWeixinMessageResponse(response.toXml());
ctx.write(httpResponse);
} else {
ctx.write(response);
}
}
}
@@ -22,6 +22,8 @@ public class WeixinServerInitializer extends ChannelInitializer<SocketChannel> {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65536));
pipeline.addLast(new WeixinMessageDecoder());
pipeline.addLast(new WeixinMessageEncoder());
pipeline.addLast(new WeixinServerHandler(actionMapping));
}
}
@@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.mp.statrup;
package com.foxinmy.weixin4j.mp.startup;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
@@ -21,7 +21,7 @@ import com.foxinmy.weixin4j.mp.server.WeixinServerInitializer;
* @since JDK 1.7
* @see
*/
public final class WeixinServiceBootstrap {
public final class WeixinServerBootstrap {
private final static int port;
private final static int workerThreads;
@@ -0,0 +1,42 @@
package com.foxinmy.weixin4j.mp.util;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.DATE;
import static io.netty.handler.codec.http.HttpHeaders.Names.SERVER;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.HttpResponse;
import java.util.Date;
import org.apache.http.Consts;
/**
* HTTP工具类
*
* @className HttpUtil
* @author jy
* @date 2014年11月15日
* @since JDK 1.7
* @see
*/
public class HttpUtil {
public static HttpResponse createWeixinMessageResponse(String content) {
FullHttpResponse httpResponse = new DefaultFullHttpResponse(HTTP_1_1,
OK, Unpooled.copiedBuffer(content, Consts.UTF_8));
httpResponse.headers().set(CONTENT_TYPE,
"application/xml;encoding=utf-8");
httpResponse.headers().set(CONTENT_LENGTH, content.getBytes().length);
httpResponse.headers().set(CONNECTION, Values.KEEP_ALIVE);
httpResponse.headers().set(DATE, new Date());
httpResponse.headers().set(SERVER, "netty4");
return httpResponse;
}
}
@@ -1,6 +1,7 @@
# \u516c\u4f17\u53f7\u4fe1\u606f
account={"appId":"wx4ab8f8de58159a57","appSecret":"1d4eb0f4bf556aaed539f30ed05ca795",\
"token":"\u5f00\u653e\u8005\u7684token \u975e\u5fc5\u987b","openId":"\u516c\u4f17\u53f7\u7684openid \u975e\u5fc5\u987b",\
"encodingAesKey":"\u516c\u4f17\u53f7\u8bbe\u7f6e\u4e86\u52a0\u5bc6\u65b9\u5f0f\u4e14\u4e3a\u300c\u5b89\u5168\u6a21\u5f0f\u300d\u9700\u8981\u586b\u5165",\
"mchId":"V3.x\u7248\u672c\u4e0b\u7684\u5fae\u4fe1\u5546\u6237\u53f7",\
"version":3,\
"partnerId":"\u8d22\u4ed8\u901a\u7684\u5546\u6237\u53f7","partnerKey":"\u8d22\u4ed8\u901a\u5546\u6237\u6743\u9650\u5bc6\u94a5Key",\
@@ -15,4 +16,4 @@ media_path=/tmp/weixin/media
# \u5bf9\u8d26\u5355\u4fdd\u5b58\u8def\u5f84
bill_path=/tmp/weixin/bill
# ca\u8bc1\u4e66\u5b58\u653e\u7684\u5b8c\u6574\u8def\u5f84
ca_file=/tmp/weixin/xxxxx.p12
ca_file=/tmp/weixin/xxxxxx.p12
@@ -6,10 +6,10 @@ JAVA_HOME="/usr/local/java/"
RUNNING_USER=root
#Run home
APP_HOME="/usr/local/weixin/weixin-service"
APP_HOME="/usr/local/weixin/weixin-mp-server"
#main class
APP_MAINCLASS=com.foxinmy.weixin4j.mp.startup.WeixinServiceBootstrap
APP_MAINCLASS=com.foxinmy.weixin4j.mp.startup.WeixinServerBootstrap
#classpath
CLASSPATH=$APP_HOME/classes
@@ -42,9 +42,9 @@ start() {
checkpid
if [ $psid -ne 0 ]; then
echo "================================"
echo "====================================================="
echo "warn: $APP_MAINCLASS already started! (pid=$psid)"
echo "================================"
echo "====================================================="
else
echo -n "Starting $APP_MAINCLASS ..."
# JAVA_CMD="nohup $JAVA_HOME/bin/java $JAVA_OPTS -classpath $CLASSPATH $APP_MAINCLASS >/dev/null 2>&1 &"
@@ -79,9 +79,9 @@ stop() {
stop
fi
else
echo "================================"
echo "====================================================="
echo "warn: $APP_MAINCLASS is not running"
echo "================================"
echo "====================================================="
fi
}