diff --git a/CHANGE.md b/CHANGE.md index bc90aa86..ad6fec0d 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -259,4 +259,8 @@ + 调整聚合方式,去除原先的weixin4j-mp和weixin4j-qy模块,相应的api模块直接继承weixin4j父模块 - + **weixin4j-base**: 删除ActionMapping相关类 \ No newline at end of file + + **weixin4j-base**: 删除ActionMapping相关类 + +* 2015-04-29 + + + released 1.4 \ No newline at end of file diff --git a/README.md b/README.md index 8179096c..c10376e1 100644 --- a/README.md +++ b/README.md @@ -22,29 +22,33 @@ weixin4j 项目说明 ------- -+ `weixin4j`包含「微信公众平台」和「微信企业号」的API封装以及一个半成品的netty服务实现. ++ `weixin4j`包含「微信公众平台」和「微信企业号」的API封装. + API的成功调用依赖于正确的appid等数据,填写格式说明见API工程下的README.md文件. -+ 如需使用netty服务,可以在相应的action中实现自己的具体业务,打包后启动服务即可. ++ netty服务正在重构中 -如何获取API +如何获取 ---------- -###1.maven依赖(1.3,2015-04-04 released) +###1.maven依赖(1.4,2015-04-29 released) 微信公众平台API com.foxinmy - weixin4j-mp-api - 1.3 + weixin4j-mp + 1.4 微信企业号API com.foxinmy - weixin4j-qy-api - 1.3 + weixin4j-qy + 1.4 +微信被动消息服务器 + + `正在重构中..` + 以上依赖如果出现Missing artifact错误 请尝试在eclipse里这么做 + 进入 Window > Show View > Other > Maven Repositories 展开 Global Repositories 在group或者central上右键执行`update index` 操作 @@ -58,11 +62,7 @@ https://github.com/foxinmy/weixin4j/releases ###3.从源码打包 -`git clone`&`mvn package -Prelease`,到相应的target目录下将`weixin4j-[mp|qy]-full`包或者`weixin4j-base`和`weixin4j-[mp|qy]`引入到自己的工程. - -如何获取netty部分 ---------------- -正在构思中... +`git clone`&`mvn package -Prelease` [更新LOG](./CHANGE.md) ---------------------- diff --git a/pom.xml b/pom.xml index 9bd1cfea..9ca26b4f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.foxinmy weixin4j - 1.3 + 1.4 pom weixin4j https://github.com/foxinmy/weixin4j diff --git a/weixin4j-base/pom.xml b/weixin4j-base/pom.xml index f6d44151..a48fd843 100644 --- a/weixin4j-base/pom.xml +++ b/weixin4j-base/pom.xml @@ -4,7 +4,7 @@ com.foxinmy weixin4j - 1.3 + 1.4 weixin4j-base weixin4j-base diff --git a/weixin4j-mp/pom.xml b/weixin4j-mp/pom.xml index dc96f5a8..8680ece3 100644 --- a/weixin4j-mp/pom.xml +++ b/weixin4j-mp/pom.xml @@ -4,7 +4,7 @@ com.foxinmy weixin4j - 1.3 + 1.4 weixin4j-mp weixin4j-mp diff --git a/weixin4j-qy/pom.xml b/weixin4j-qy/pom.xml index ad56ebf4..bbc124f4 100644 --- a/weixin4j-qy/pom.xml +++ b/weixin4j-qy/pom.xml @@ -4,7 +4,7 @@ com.foxinmy weixin4j - 1.3 + 1.4 weixin4j-qy weixin4j-qy diff --git a/weixin4j-server/pom.xml b/weixin4j-server/pom.xml index d94321e5..4395f07a 100644 --- a/weixin4j-server/pom.xml +++ b/weixin4j-server/pom.xml @@ -5,7 +5,7 @@ com.foxinmy weixin4j - 1.3 + 1.4 weixin4j-server weixin4j-server diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageDecoder.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageDecoder.java index 36e43e1b..59eff522 100644 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageDecoder.java +++ b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/server/WeixinMessageDecoder.java @@ -12,11 +12,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.foxinmy.weixin4j.message.HttpWeixinMessage; -import com.foxinmy.weixin4j.model.WeixinAccount; import com.foxinmy.weixin4j.type.EncryptType; -import com.foxinmy.weixin4j.util.ConfigUtil; import com.foxinmy.weixin4j.util.Consts; -import com.foxinmy.weixin4j.util.MessageUtil; /** * 微信消息解码类 @@ -66,10 +63,10 @@ public class WeixinMessageDecoder extends message.setOriginalContent(content); if (message.getEncryptType() == EncryptType.AES) { - WeixinAccount mpAccount = ConfigUtil.getWeixinAccount(); + /*WeixinAccount mpAccount = ConfigUtil.getWeixinAccount(); message.setOriginalContent(MessageUtil.aesDecrypt( mpAccount.getId(), mpAccount.getEncodingAesKey(), - message.getEncryptContent())); + message.getEncryptContent()));*/ } out.add(message); } diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/ConfigUtil.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/ConfigUtil.java deleted file mode 100644 index da4de144..00000000 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/ConfigUtil.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.foxinmy.weixin4j.util; - -import java.io.File; -import java.util.ResourceBundle; -import java.util.Set; - -import com.alibaba.fastjson.JSON; -import com.foxinmy.weixin4j.model.WeixinAccount; - -/** - * 商户配置工具类 - * - * @className ConfigUtil - * @author jy - * @date 2014年10月31日 - * @since JDK 1.7 - * @see - */ -public class ConfigUtil { - private final static String CLASSPATH_PREFIX = "classpath:"; - private final static String CLASSPATH_VALUE; - private final static ResourceBundle weixinBundle; - static { - weixinBundle = ResourceBundle.getBundle("weixin"); - Set keySet = weixinBundle.keySet(); - File file = null; - CLASSPATH_VALUE = Thread.currentThread().getContextClassLoader() - .getResource("").getPath(); - for (String key : keySet) { - if (!key.endsWith("_path")) { - continue; - } - file = new File(getValue(key).replaceFirst(CLASSPATH_PREFIX, - CLASSPATH_VALUE)); - if (!file.exists() && !file.mkdirs()) { - System.err.append(String.format("%s create fail.%n", - file.getAbsolutePath())); - } - } - } - - /** - * 获取weixin.properties文件中的key值 - * - * @param key - * @return - */ - public static String getValue(String key) { - return weixinBundle.getString(key); - } - - /** - * 判断属性是否存在[classpath:]如果存在则拼接项目路径后返回 一般用于文件的绝对路径获取 - * - * @param key - * @return - */ - public static String getClassPathValue(String key) { - return new File(getValue(key).replaceFirst(CLASSPATH_PREFIX, - CLASSPATH_VALUE)).getPath(); - } - - public static WeixinAccount getWeixinAccount() { - String text = getValue("account"); - return JSON.parseObject(text, WeixinAccount.class); - } -} diff --git a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/MessageUtil.java b/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/MessageUtil.java deleted file mode 100644 index a87833cc..00000000 --- a/weixin4j-server/src/main/java/com/foxinmy/weixin4j/util/MessageUtil.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.foxinmy.weixin4j.util; - -import io.netty.handler.codec.base64.Base64; -import io.netty.handler.codec.base64.Base64Encoder; - -import java.util.Arrays; - -import javax.crypto.Cipher; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -/** - * 消息工具类 - * - * @className MessageUtil - * @author jy - * @date 2014年10月31日 - * @since JDK 1.7 - * @see - */ -public class MessageUtil { - - /** - * 验证微信签名 - * - * @param signature - * 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数 - * @return 开发者通过检验signature对请求进行相关校验。若确认此次GET请求来自微信服务器 - * 请原样返回echostr参数内容,则接入生效 成为开发者成功,否则接入失败 - * @see 接入指南 - */ - public static String signature(String... para) { - Arrays.sort(para); - StringBuilder sb = new StringBuilder(); - for (String str : para) { - sb.append(str); - } - return DigestUtil.SHA1(sb.toString()); - } - - /** - * 对xml消息加密 - * - * @param appId - * 应用ID - * @param encodingAesKey - * 加密密钥 - * @param xmlContent - * 原始消息体 - * @return aes加密后的消息体 - * @throws WeixinException - */ - public static String aesEncrypt(String appId, String encodingAesKey, - String xmlContent) throws RuntimeException { - byte[] randomBytes = RandomUtil.generateString(16).getBytes( - Consts.UTF_8); - byte[] xmlBytes = xmlContent.getBytes(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(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 { - byte[] aesKey = Base64.de(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.(encrypted); - } catch (Exception e) { - throw new RuntimeException("-40006,AES加密失败:", e); - } - } - - /** - * 对AES消息解密 - * - * @param appId - * @param encodingAesKey - * aes加密的密钥 - * @param encryptContent - * 加密的消息体 - * @return 解密后的字符 - * @throws WeixinException - */ - public static String aesDecrypt(String appId, String encodingAesKey, - String encryptContent) throws RuntimeException { - byte[] aesKey = Base64..decodeBase64(encodingAesKey + "="); - byte[] original; - try { - // 设置解密模式为AES的CBC模式 - Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); - SecretKeySpec key_spec = new SecretKeySpec(aesKey, Consts.AES); - IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, - 0, 16)); - cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); - // 使用BASE64对密文进行解码 - byte[] encrypted = Base64.decodeBase64(encryptContent); - // 解密 - original = cipher.doFinal(encrypted); - } catch (Exception e) { - throw new RuntimeException("-40007,AES解密失败:", e); - } - String xmlContent, fromAppId; - try { - // 去除补位字符 - byte[] bytes = PKCS7Encoder.decode(original); - // 获取表示xml长度的字节数组 - byte[] lengthByte = Arrays.copyOfRange(bytes, 16, 20); - // 获取xml消息主体的长度(byte[]2int) - // http://my.oschina.net/u/169390/blog/97495 - int xmlLength = lengthByte[3] & 0xff | (lengthByte[2] & 0xff) << 8 - | (lengthByte[1] & 0xff) << 16 - | (lengthByte[0] & 0xff) << 24; - xmlContent = new String(Arrays.copyOfRange(bytes, 20, - 20 + xmlLength), Consts.UTF_8); - fromAppId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, - bytes.length), Consts.UTF_8); - } catch (Exception e) { - throw new RuntimeException("-40008,公众平台发送的xml不合法:" + e.getMessage()); - } - // 校验appId是否一致 - if (!fromAppId.trim().equals(appId)) { - throw new RuntimeException("-40005,校验AppID失败"); - } - return xmlContent; - } -} diff --git a/weixin4j-server/src/main/resources/Copy of weixin.properties b/weixin4j-server/src/main/resources/Copy of weixin.properties deleted file mode 100644 index a6dc84e1..00000000 --- a/weixin4j-server/src/main/resources/Copy of weixin.properties +++ /dev/null @@ -1,22 +0,0 @@ -# \u516c\u4f17\u53f7\u4fe1\u606f -account={"id":"appid","secret":"appsecret",\ -"token":"\u5f00\u653e\u8005\u7684token",\ -"encodingAesKey":"\u516c\u4f17\u53f7\u8bbe\u7f6e\u4e86\u52a0\u5bc6\u65b9\u5f0f\u4e14\u4e3a\u300c\u5b89\u5168\u6a21\u5f0f\u300d\u65f6\u9700\u8981\u586b\u5165",\ -"mchId":"V3.x\u7248\u672c\u4e0b\u7684\u5fae\u4fe1\u5546\u6237\u53f7 \u670d\u52a1\u53f7\u652f\u4ed8\u65f6\u9700\u8981\u586b\u5165",\ -"version":\u6570\u5b57\u7c7b\u578b(2\u6216\u80053):\u5fae\u4fe1\u652f\u4ed8\u7684\u7248\u672c,\u5927\u6982\u57282014-09-14\u4e4b\u524d\u7533\u8bf7\u5e76\u4e14\u901a\u8fc7\u7684\u516c\u4f17\u53f7\u4e3aV2,\u5728\u8fd9\u4e4b\u540e\u5219\u4e3aV3 \u670d\u52a1\u53f7\u652f\u4ed8\u65f6\u9700\u8981\u586b\u5165,\ -"partnerId":"V2\u7248\u672c\u4e0b\u7684\u8d22\u4ed8\u901a\u7684\u5546\u6237\u53f7 \u670d\u52a1\u53f7\u652f\u4ed8\u65f6\u9700\u8981\u586b\u5165",\ -"partnerKey":"V2\u7248\u672c\u4e0b\u7684\u8d22\u4ed8\u901a\u5546\u6237\u6743\u9650\u5bc6\u94a5Key \u670d\u52a1\u53f7\u652f\u4ed8\u65f6\u9700\u8981\u586b\u5165",\ -"paySignKey":"\u5fae\u4fe1\u652f\u4ed8\u4e2d\u8c03\u7528API\u7684\u5bc6\u94a5 \u670d\u52a1\u53f7\u652f\u4ed8\u65f6\u9700\u8981\u586b\u5165"} - -# \u4f7f\u7528FileTokenHolder\u65f6token\u7684\u5b58\u653e\u8def\u5f84 -token_path=/tmp/weixin/token -# \u4e8c\u7ef4\u7801\u4fdd\u5b58\u8def\u5f84 -qr_path=/tmp/weixin/qr -# \u5a92\u4f53\u6587\u4ef6\u4fdd\u5b58\u8def\u5f84 -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 (V2\u7248\u672c\u540e\u7f00\u4e3a*.pfx,V3\u7248\u672c\u540e\u7f00\u4e3a*.p12) -ca_file=/tmp/weixin/xxxxx.p12 -# classpath\u8def\u5f84\u4e0b\u53ef\u4ee5\u8fd9\u4e48\u5199 -# ca_file=classpath:xxxxx.pfx \ No newline at end of file diff --git a/weixin4j-server/src/main/resources/weixin.properties b/weixin4j-server/src/main/resources/weixin.properties deleted file mode 100644 index 92076983..00000000 --- a/weixin4j-server/src/main/resources/weixin.properties +++ /dev/null @@ -1,5 +0,0 @@ -# \u6d4b\u8bd5\u4e4b\u7528 \u6b63\u5f0f\u73af\u5883\u4e0bcopy\u4e00\u4efd\u5230classpath -# \u516c\u4f17\u53f7\u4fe1\u606f -account={"id":"wx4ab8f8de58159a57","secret":"1d4eb0f4bf556aaed539f30ed05ca795",\ -"token":"\u5f00\u653e\u8005\u7684token",\ -"encodingAesKey":"\u516c\u4f17\u53f7\u8bbe\u7f6e\u4e86\u52a0\u5bc6\u65b9\u5f0f\u4e14\u4e3a\u300c\u5b89\u5168\u6a21\u5f0f\u300d\u65f6\u9700\u8981\u586b\u5165"} \ No newline at end of file