From 282699d95cbe604a51e7c48383c4ed81af6b6de2 Mon Sep 17 00:00:00 2001 From: "jy.hu" Date: Sat, 15 Nov 2014 20:45:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=8E=B7=E5=8F=96=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E6=9C=8D=E5=8A=A1=E5=99=A8IP=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E4=BB=A5=E5=8F=8A=E6=B6=88=E6=81=AFAES?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E8=A7=A3=E5=AF=86=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 25 ++- weixin4j-base/README.md | 6 +- .../com/foxinmy/weixin4j/model/Consts.java | 3 + .../foxinmy/weixin4j/model/WeixinAccount.java | 20 ++- .../com/foxinmy/weixin4j/msg/BaseMessage.java | 10 -- .../com/foxinmy/weixin4j/msg/TextMessage.java | 5 - .../foxinmy/weixin4j/type/MessageType.java | 2 +- .../com/foxinmy/weixin4j/util/ClassUtil.java | 74 +++++++- .../foxinmy/weixin4j/util/MessageUtil.java | 160 ++++++++++++++---- .../foxinmy/weixin4j/util/PKCS7Encoder.java | 72 ++++++++ weixin4j-mp/README.md | 18 +- weixin4j-mp/weixin4j-mp-api/README.md | 7 +- .../foxinmy/weixin4j/mp/api/HelperApi.java | 22 ++- .../foxinmy/weixin4j/mp/api/weixin.properties | 2 + .../weixin4j/mp/payment/PayAction.java | 3 +- .../foxinmy/weixin4j/mp/type/EncryptType.java | 5 + .../src/main/resources/weixin.properties | 1 + .../foxinmy/weixin4j/mp/test/HelpTest.java | 25 +++ .../weixin4j/mp/test/msg/AesMsgTest.java | 64 +++++++ .../weixin4j/mp/test/msg/MessagePush.java | 34 +++- .../weixin4j/mp/test/msg/OutMsgTest.java | 3 +- weixin4j-mp/weixin4j-mp-server/README.md | 9 +- weixin4j-mp/weixin4j-mp-server/pom.xml | 2 +- .../weixin4j-mp-server/src/main/assembly.xml | 1 + .../weixin4j/mp/action/AbstractAction.java | 11 +- .../weixin4j/mp/action/BlankAction.java | 10 +- .../weixin4j/mp/action/SignatureAction.java | 48 ------ .../weixin4j/mp/action/WeixinAction.java | 4 +- .../weixin4j/mp/model/HttpWeixinMessage.java | 133 +++++++++++++++ .../com/foxinmy/weixin4j/mp/server/README.md | 6 +- .../mp/server/WeixinMessageDecoder.java | 81 +++++++++ .../mp/server/WeixinMessageEncoder.java | 71 ++++++++ .../mp/server/WeixinServerHandler.java | 111 ++++++------ .../mp/server/WeixinServerInitializer.java | 2 + .../WeixinServerBootstrap.java} | 4 +- .../foxinmy/weixin4j/mp/util/HttpUtil.java | 42 +++++ .../src/main/resources/weixin.properties | 3 +- .../weixin4j-mp-server/src/main/startup.sh | 12 +- 38 files changed, 893 insertions(+), 218 deletions(-) create mode 100644 weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/PKCS7Encoder.java create mode 100644 weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/EncryptType.java create mode 100644 weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/HelpTest.java create mode 100644 weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/AesMsgTest.java delete mode 100644 weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/SignatureAction.java create mode 100644 weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/model/HttpWeixinMessage.java create mode 100644 weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinMessageDecoder.java create mode 100644 weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinMessageEncoder.java rename weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/{statrup/WeixinServiceBootstrap.java => startup/WeixinServerBootstrap.java} (94%) create mode 100644 weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/util/HttpUtil.java diff --git a/README.md b/README.md index b23c58fc..7361b5a9 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ weixin4j * 2014-11-03 - + **weixin-mp**: 分离为`weixin-mp-api`和`weixin-mp-server`两个工程 + + **weixin4j-mp**: 分离为`weixin-mp-api`和`weixin-mp-server`两个工程 - + **weixin-mp**: 加入支付模块 + + **weixin4j-mp**: 加入支付模块 * 2014-11-05 @@ -50,26 +50,35 @@ weixin4j * 2014-11-06 - + **weixin-base**: 删除`WeixinConfig`类只保留`WeixinAccount`类 + + **weixin4j-base**: 删除`WeixinConfig`类只保留`WeixinAccount`类 - + **weixin-mp**: 新增V3版本`退款接口` + + **weixin4j-mp**: 新增V3版本`退款接口` * 2014-11-08 - + **weixin-mp**: 新增V2版本`退款申请`、`退款查询`、`对账单下载`三个接口 + + **weixin4j-mp**: 新增V2版本`退款申请`、`退款查询`、`对账单下载`三个接口 + **weixin-mp**: 新增一个简单的`语义理解`接口 * 2014-11-11 - + **weixin-mp**: 自定义`assembly`将`weixin4j-base`工程也一起打包(`weixin4j-mp-api-full.jar`) + + **weixin4j-mp**: 自定义`assembly`将`weixin4j-base`工程也一起打包(`weixin4j-mp-api-full.jar`) + +* 2014-11-15 + + + **weixin4j-base**: 新增aes加密解密函数 + + + **weixin4j-mp**: 新增获取`微信服务器IP地址`接口 + + + **weixin4j-mp**: 解决`server工程`打包不能运行问题(`ClassUtil`无法获取jar包里面的类) + + + **weixin4j-mp**: 新增被动消息的`加密`以及回复消息的`解密` + 接下来 ------ * 公众号智能接口 -* 微信消息加密 - * 被扫支付 * 企业号API封装 diff --git a/weixin4j-base/README.md b/weixin4j-base/README.md index ce482a10..582ae674 100644 --- a/weixin4j-base/README.md +++ b/weixin4j-base/README.md @@ -21,4 +21,8 @@ weixin4j-base * 2014-11-06 - + 删除`WeixinConfig`类只保留`WeixinAccount`类 \ No newline at end of file + + 删除`WeixinConfig`类只保留`WeixinAccount`类 + +* 2014-11-15 + + + 新增`aes加密解密`函数 \ No newline at end of file diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Consts.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Consts.java index bb8e7ee8..cccebc3e 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Consts.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Consts.java @@ -8,4 +8,7 @@ public final class Consts { public static final String PKCS12 = "PKCS12"; public static final String TLS = "TLS"; public static final String X509 = "X.509"; + public static final String AES = "AES"; + public static final String PROTOCOL_FILE = "file"; + public static final String PROTOCOL_JAR = "jar"; } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/WeixinAccount.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/WeixinAccount.java index 20eb1198..7ec0afc8 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/WeixinAccount.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/WeixinAccount.java @@ -25,6 +25,8 @@ public class WeixinAccount implements Serializable { private String appSecret; // 公众号支付请求中用于加密的密钥 Key,可验证商户唯一身份,PaySignKey 对应于支付场景中的 appKey 值 private String paySignKey; + // 安全模式下的加密密钥 + private String encodingAesKey; // 财付通商户身份的标识 private String partnerId; // 财付通商户权限密钥Key @@ -75,6 +77,14 @@ public class WeixinAccount implements Serializable { this.appSecret = appSecret; } + public String getEncodingAesKey() { + return encodingAesKey; + } + + public void setEncodingAesKey(String encodingAesKey) { + this.encodingAesKey = encodingAesKey; + } + public String getPaySignKey() { return paySignKey; } @@ -195,10 +205,10 @@ public class WeixinAccount implements Serializable { public String toString() { return "WeixinAccount [token=" + token + ", openId=" + openId + ", appId=" + appId + ", appSecret=" + appSecret - + ", paySignKey=" + paySignKey + ", partnerId=" + partnerId - + ", partnerKey=" + partnerKey + ", mchId=" + mchId - + ", deviceInfo=" + deviceInfo + ", isAlive=" + isAlive - + ", isService=" + isService + ", isSubscribe=" + isSubscribe - + "]"; + + ", encodingAesKey=" + encodingAesKey + ", paySignKey=" + + paySignKey + ", partnerId=" + partnerId + ", partnerKey=" + + partnerKey + ", mchId=" + mchId + ", deviceInfo=" + + deviceInfo + ", isAlive=" + isAlive + ", isService=" + + isService + ", isSubscribe=" + isSubscribe + "]"; } } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/msg/BaseMessage.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/msg/BaseMessage.java index 4bbfef2b..fe296d8a 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/msg/BaseMessage.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/msg/BaseMessage.java @@ -56,16 +56,6 @@ public class BaseMessage extends BaseMsg { this.msgType = msgType; } - public BaseMessage(MessageType msgType, BaseMessage inMessage) { - this(msgType, inMessage.getFromUserName(), inMessage.getToUserName()); - } - - public BaseMessage(MessageType msgType, String toUserName, - String fromUserName) { - super(toUserName, fromUserName); - this.msgType = msgType; - } - public MessageType getMsgType() { return msgType; } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/msg/TextMessage.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/msg/TextMessage.java index 43fdcc94..e2385938 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/msg/TextMessage.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/msg/TextMessage.java @@ -25,11 +25,6 @@ public class TextMessage extends BaseMessage { super(MessageType.text); } - public TextMessage(String content, BaseMessage inMessage) { - super(MessageType.text, inMessage); - this.content = content; - } - @XStreamAlias("Content") private String content; // 消息内容 diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/type/MessageType.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/type/MessageType.java index 669569a1..cb1315a7 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/type/MessageType.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/type/MessageType.java @@ -20,7 +20,7 @@ public enum MessageType { text(TextMessage.class), image(ImageMessage.class), voice( VoiceMessage.class), video(VideoMessage.class), location( LocationMessage.class), link(LinkMessage.class), event( - EventMessage.class), signature(null); + EventMessage.class); private Class messageClass; MessageType(Class messageClass) { diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ClassUtil.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ClassUtil.java index aa98f92d..187dc600 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ClassUtil.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ClassUtil.java @@ -2,12 +2,21 @@ package com.foxinmy.weixin4j.util; import java.io.File; import java.io.FilenameFilter; +import java.io.IOException; +import java.net.JarURLConnection; import java.net.URL; +import java.util.Enumeration; import java.util.HashSet; import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import com.alibaba.fastjson.JSON; +import com.foxinmy.weixin4j.model.Consts; /** * 对class的获取 + * * @className ClassUtil * @author jy * @date 2014年10月31日 @@ -17,15 +26,28 @@ import java.util.Set; public class ClassUtil { public static Set> getClasses(Package _package) { - ClassLoader classLoader = Thread.currentThread() - .getContextClassLoader(); - String subPath = _package.getName().replace(".", File.separator); - URL fullPath = classLoader.getResource(subPath); - File dir = new File(fullPath.getPath()); - return findClasses(dir, _package.getName()); + String packageName = _package.getName(); + String packageFileName = packageName.replace(".", File.separator); + URL fullPath = Thread.currentThread().getContextClassLoader() + .getResource(packageFileName); + String protocol = fullPath.getProtocol(); + if (protocol.equals(Consts.PROTOCOL_FILE)) { + File dir = new File(fullPath.getPath()); + return findClassesByFile(dir, packageName); + } else if (protocol.equals(Consts.PROTOCOL_JAR)) { + try { + return findClassesByJar( + ((JarURLConnection) fullPath.openConnection()) + .getJarFile(), + packageFileName); + } catch (IOException e) { + ; + } + } + return null; } - private static Set> findClasses(File dir, String packageName) { + private static Set> findClassesByFile(File dir, String packageName) { Set> classes = new HashSet>(); File[] files = dir.listFiles(new FilenameFilter() { @Override @@ -35,7 +57,7 @@ public class ClassUtil { }); for (File file : files) { if (file.isDirectory()) { - classes.addAll(findClasses(file, + classes.addAll(findClassesByFile(file, packageName + "." + file.getName())); } else { try { @@ -48,9 +70,43 @@ public class ClassUtil { } catch (ClassNotFoundException e) { ; } - } } return classes; } + + private static Set> findClassesByJar(JarFile jar, + String packageName) { + Set> classes = new HashSet>(); + Enumeration jarEntries = jar.entries(); + while (jarEntries.hasMoreElements()) { + JarEntry jarEntry = jarEntries.nextElement(); + if (jarEntry.isDirectory()) { + continue; + } + String entryName = jarEntry.getName(); + if (!entryName.startsWith(packageName)) { + continue; + } + if (!entryName.endsWith(".class")) { + continue; + } + try { + Class clazz = Class.forName(entryName.replaceAll("/", ".") + .replace(".class", "")); + if (clazz.isInterface()) { + continue; + } + classes.add(clazz); + } catch (ClassNotFoundException e) { + ; + } + } + return classes; + } + + public static void main(String[] args) { + Package _package = JSON.class.getPackage(); + System.out.println(getClasses(_package)); + } } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/MessageUtil.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/MessageUtil.java index 443cb755..b001030a 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/MessageUtil.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/MessageUtil.java @@ -3,15 +3,20 @@ package com.foxinmy.weixin4j.util; import java.io.InputStream; import java.util.Arrays; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.io.SAXReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.foxinmy.weixin4j.exception.WeixinException; +import com.foxinmy.weixin4j.model.Consts; import com.foxinmy.weixin4j.msg.BaseMessage; import com.foxinmy.weixin4j.type.EventType; import com.foxinmy.weixin4j.type.MessageType; @@ -19,6 +24,7 @@ import com.foxinmy.weixin4j.xml.XStream; /** * 消息工具类 + * * @className MessageUtil * @author jy * @date 2014年10月31日 @@ -27,20 +33,9 @@ import com.foxinmy.weixin4j.xml.XStream; */ public class MessageUtil { - private final static Logger log = LoggerFactory - .getLogger(MessageUtil.class); - /** * 验证微信签名 * - * @param token - * 开发者填写的token - * @param echostr - * 随机字符串 - * @param timestamp - * 时间戳 - * @param nonce - * 随机数 * @param signature * 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数 * @return 开发者通过检验signature对请求进行相关校验。若确认此次GET请求来自微信服务器 @@ -48,35 +43,126 @@ public class MessageUtil { * @see 接入指南 */ - public static String signature(String token, String echostr, - String timestamp, String nonce, String signature) { - if (StringUtils.isBlank(token)) { - log.error("signature fail : token is null!"); - return null; + public static String signature(String... para) { + Arrays.sort(para); + StringBuilder sb = new StringBuilder(); + for (String str : para) { + sb.append(str); } - if (StringUtils.isBlank(echostr) || StringUtils.isBlank(timestamp) - || StringUtils.isBlank(nonce)) { - log.error("signature fail : invalid parameter!"); - return null; - } - String _signature = null; + return DigestUtils.sha1Hex(sb.toString()); + } + + /** + * 对xml消息加密 + * + * @param appId + * @param encodingAesKey + * 加密密钥 + * @param xmlContent + * 原始消息体 + * @return aes加密后的消息体 + * @throws WeixinException + */ + public static String aesEncrypt(String appId, String encodingAesKey, + String xmlContent) throws WeixinException { + byte[] randomBytes = RandomUtil.generateString(16).getBytes( + org.apache.http.Consts.UTF_8); + byte[] xmlBytes = xmlContent.getBytes(org.apache.http.Consts.UTF_8); + 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 = appId.getBytes(org.apache.http.Consts.UTF_8); + 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 { - String[] a = { token, timestamp, nonce }; - Arrays.sort(a); - StringBuilder sb = new StringBuilder(3); - for (String str : a) { - sb.append(str); - } - _signature = DigestUtils.sha1Hex(sb.toString()); + 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) { - log.error("signature error", e); + throw new WeixinException("-40006", "AES加密失败"); } - if (signature.equals(_signature)) { - return echostr; - } else { - log.error("signature fail : invalid signature!"); - return null; + } + + /** + * 对xml消息解密 + * + * @param appId + * @param encodingAesKey + * aes加密的密钥 + * @param encryptContent + * 加密的消息体 + * @return 解密后的xml + * @throws WeixinException + */ + public static String aesDecrypt(String appId, String encodingAesKey, + String encryptContent) throws WeixinException { + 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 WeixinException("-40007", "AES解密失败"); } + 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 = new String(Arrays.copyOfRange(bytes, 20, + 20 + xmlLength), org.apache.http.Consts.UTF_8); + fromAppId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, + bytes.length), org.apache.http.Consts.UTF_8); + } catch (Exception e) { + throw new WeixinException("-40008", "公众平台发送的xml不合法"); + } + // 校验appId是否一致 + if (!fromAppId.trim().equals(appId)) { + throw new WeixinException("-40005", "校验AppID失败"); + } + return xmlContent; } /** diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/PKCS7Encoder.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/PKCS7Encoder.java new file mode 100644 index 00000000..a56b2b0f --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/PKCS7Encoder.java @@ -0,0 +1,72 @@ +/** + * 对公众平台发送给公众账号的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package com.foxinmy.weixin4j.util; + +import java.util.Arrays; + +import org.apache.http.Consts; + +/** + * 提供基于PKCS7算法的加解密接口
+ * 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串). + *
    + *
  1. 第三方回复加密消息给公众平台
  2. + *
  3. 第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。
  4. + *
+ * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 + *
    + *
  1. 在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: + * http://www.oracle.com/technetwork/java/javase + * /downloads/jce-7-download-432124.html
  2. + *
  3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
  4. + *
  5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
  6. + *
  7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
  8. + *
+ */ +public class PKCS7Encoder { + private final static int BLOCK_SIZE = 32; + + /** + * 获得对明文进行补位填充的字节. + * + * @param count + * 需要进行填充补位操作的明文字节个数 + * @return 补齐用的字节数组 + */ + public static byte[] encode(int count) { + // 计算需要填充的位数 + int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); + if (amountToPad == 0) { + amountToPad = BLOCK_SIZE; + } + // 获得补位所用的字符 + byte target = (byte) (amountToPad & 0xFF); + char padChr = (char) target; + String tmp = new String(); + for (int index = 0; index < amountToPad; index++) { + tmp += padChr; + } + return tmp.getBytes(Consts.UTF_8); + } + + /** + * 删除解密后明文的补位字符 + * + * @param decrypted + * 解密后的明文 + * @return 删除补位字符后的明文 + */ + public static byte[] decode(byte[] decrypted) { + int pad = (int) decrypted[decrypted.length - 1]; + if (pad < 1 || pad > 32) { + pad = 0; + } + return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); + } +} \ No newline at end of file diff --git a/weixin4j-mp/README.md b/weixin4j-mp/README.md index 50112376..7c25e0b9 100644 --- a/weixin4j-mp/README.md +++ b/weixin4j-mp/README.md @@ -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`) \ No newline at end of file + + **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**: 新增被动消息的`加密`以及回复消息的`解密` \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-api/README.md b/weixin4j-mp/weixin4j-mp-api/README.md index 71ed137b..07698944 100644 --- a/weixin4j-mp/weixin4j-mp-api/README.md +++ b/weixin4j-mp/weixin4j-mp-api/README.md @@ -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`) \ No newline at end of file + + 自定义`assembly`将`weixin4j-base`工程也一起打包(`weixin4j-mp-api-full.jar`) + +* 2014-11-15 + + + 新增获取`微信服务器IP地址接口` \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/HelperApi.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/HelperApi.java index 1c74d7c0..b99ff6d5 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/HelperApi.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/HelperApi.java @@ -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() { }); } -} + + /** + * 获取微信服务器IP地址 + * + * @return IP地址 + * @see 获取IP地址 + * @throws WeixinException + */ + public List 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); + } +} \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/weixin.properties b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/weixin.properties index f2deb48b..8879ad91 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/weixin.properties +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/weixin.properties @@ -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 diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/PayAction.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/PayAction.java index 757a3574..0e9e329a 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/PayAction.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/PayAction.java @@ -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 <xml>
* <return_code>SUCCESS/FAIL</return_code>
* <return_msg>如非空,为错误 原因签名失败参数格式校验错误</return_msg>
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/EncryptType.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/EncryptType.java new file mode 100644 index 00000000..864dff41 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/EncryptType.java @@ -0,0 +1,5 @@ +package com.foxinmy.weixin4j.mp.type; + +public enum EncryptType { + RAW, AES +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/resources/weixin.properties b/weixin4j-mp/weixin4j-mp-api/src/main/resources/weixin.properties index 18671ff5..a2a4c960 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/resources/weixin.properties +++ b/weixin4j-mp/weixin4j-mp-api/src/main/resources/weixin.properties @@ -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",\ diff --git a/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/HelpTest.java b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/HelpTest.java new file mode 100644 index 00000000..0b2fba81 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/HelpTest.java @@ -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 ipList = helperApi.getcallbackip(); + Assert.assertFalse(ipList.isEmpty()); + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/AesMsgTest.java b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/AesMsgTest.java new file mode 100644 index 00000000..c391ec4b --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/AesMsgTest.java @@ -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×tamp=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×tamp=1415979366&nonce=1865915590"; + xmlSb.delete(0, xmlSb.length()); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1415979365"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + String response = push(para, xmlSb.toString()); + Assert.assertNotNull(response); + } + + @Test + public void testType2() throws WeixinException, IOException { + String para = "?signature=ad05f836772d1cbba1ff2edb7be4b9c9fb3a43d5×tamp=1415980001&nonce=1803216865&encrypt_type=raw&msg_signature=c0d38e9ca00548f7142627ec2908a4fe8481025e"; + xmlSb.delete(0, xmlSb.length()); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append("1415980001"); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + String response = push(para, xmlSb.toString()); + Assert.assertNotNull(response); + } + + @Test + public void testType3() throws WeixinException, IOException { + String para = "?signature=ad05f836772d1cbba1ff2edb7be4b9c9fb3a43d5×tamp=1415980001&nonce=1803216865&encrypt_type=aes&msg_signature=c0d38e9ca00548f7142627ec2908a4fe8481025e"; + xmlSb.delete(0, xmlSb.length()); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + xmlSb.append(""); + String response = push(para, xmlSb.toString()); + Assert.assertNotNull(response); + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/MessagePush.java b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/MessagePush.java index fd38a509..67700e2c 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/MessagePush.java +++ b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/MessagePush.java @@ -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); } -} +} \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/OutMsgTest.java b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/OutMsgTest.java index 1ec68236..8c1d6e4a 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/OutMsgTest.java +++ b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/msg/OutMsgTest.java @@ -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()); } diff --git a/weixin4j-mp/weixin4j-mp-server/README.md b/weixin4j-mp/weixin4j-mp-server/README.md index bddb3b5d..0aef6ae2 100644 --- a/weixin4j-mp/weixin4j-mp-server/README.md +++ b/weixin4j-mp/weixin4j-mp-server/README.md @@ -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`工程 \ No newline at end of file + + 得到`weixin-mp-server`工程 + +* 2014-11-15 + + + 解决`server工程`打包不能运行问题(`ClassUtil`无法获取jar包里面的类) + + + 新增被动消息的`加密`以及回复消息的`解密` \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-server/pom.xml b/weixin4j-mp/weixin4j-mp-server/pom.xml index 12774800..9b63f36d 100644 --- a/weixin4j-mp/weixin4j-mp-server/pom.xml +++ b/weixin4j-mp/weixin4j-mp-server/pom.xml @@ -13,7 +13,7 @@ https://github.com/foxinmy/weixin4j/tree/master/weixin4j-mp/weixin4j-server 微信公众号服务 - weixin4j-mp-server + weixin-mp-server org.apache.maven.plugins diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/assembly.xml b/weixin4j-mp/weixin4j-mp-server/src/main/assembly.xml index 20fa8e92..3b4d6a82 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/assembly.xml +++ b/weixin4j-mp/weixin4j-mp-server/src/main/assembly.xml @@ -6,6 +6,7 @@ zip + true true diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/AbstractAction.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/AbstractAction.java index 6bb26a1f..22b41619 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/AbstractAction.java +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/AbstractAction.java @@ -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 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 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 getGenericType() { Class clazz = null; Type type = getClass().getGenericSuperclass(); diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/BlankAction.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/BlankAction.java index d1bce24a..f64e8eb2 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/BlankAction.java +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/BlankAction.java @@ -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 extends AbstractAction { @Override - public TextResponse execute(M inMessage) { - return new TextResponse("", inMessage); + public BaseResponse execute(M inMessage) { + return null; } } diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/SignatureAction.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/SignatureAction.java deleted file mode 100644 index 8361a522..00000000 --- a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/SignatureAction.java +++ /dev/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> 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); - } -} diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/WeixinAction.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/WeixinAction.java index 983a9879..d6509bc2 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/WeixinAction.java +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/WeixinAction.java @@ -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; } diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/model/HttpWeixinMessage.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/model/HttpWeixinMessage.java new file mode 100644 index 00000000..d3715426 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/model/HttpWeixinMessage.java @@ -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 + "]"; + } +} diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/README.md b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/README.md index b14d4f53..03efe206 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/README.md +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/README.md @@ -1 +1,5 @@ -微信服务netty启动类 \ No newline at end of file +WeixinMessageDecoder:对微信消息进行解码 + +WeixinMessageEncoder:对微信消息进行编码 + +WeixinServerHandler:微信请求处理类 \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinMessageDecoder.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinMessageDecoder.java new file mode 100644 index 00000000..d6f575a3 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinMessageDecoder.java @@ -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 加密接入指引 + */ +public class WeixinMessageDecoder extends + MessageToMessageDecoder { + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Override + protected void decode(ChannelHandlerContext ctx, FullHttpRequest req, + List 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> 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); + } +} diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinMessageEncoder.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinMessageEncoder.java new file mode 100644 index 00000000..75bd9b32 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinMessageEncoder.java @@ -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 加密接入指引 + */ +public class WeixinMessageEncoder extends MessageToMessageEncoder { + 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 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 map = new HashMap(); + 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); + } +} \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinServerHandler.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinServerHandler.java index 36451068..0986577b 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinServerHandler.java +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinServerHandler.java @@ -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 { 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); + } + } } diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinServerInitializer.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinServerInitializer.java index bd116ae1..16e8258b 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinServerInitializer.java +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/server/WeixinServerInitializer.java @@ -22,6 +22,8 @@ public class WeixinServerInitializer extends ChannelInitializer { 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)); } } diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/statrup/WeixinServiceBootstrap.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/startup/WeixinServerBootstrap.java similarity index 94% rename from weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/statrup/WeixinServiceBootstrap.java rename to weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/startup/WeixinServerBootstrap.java index e5496173..caca53ae 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/statrup/WeixinServiceBootstrap.java +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/startup/WeixinServerBootstrap.java @@ -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; diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/util/HttpUtil.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/util/HttpUtil.java new file mode 100644 index 00000000..1534ecca --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/util/HttpUtil.java @@ -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; + } +} diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/resources/weixin.properties b/weixin4j-mp/weixin4j-mp-server/src/main/resources/weixin.properties index ba1eb132..f1b18e7f 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/resources/weixin.properties +++ b/weixin4j-mp/weixin4j-mp-server/src/main/resources/weixin.properties @@ -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 \ No newline at end of file +ca_file=/tmp/weixin/xxxxxx.p12 \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/startup.sh b/weixin4j-mp/weixin4j-mp-server/src/main/startup.sh index 56a51710..d55fbd85 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/startup.sh +++ b/weixin4j-mp/weixin4j-mp-server/src/main/startup.sh @@ -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 }