released 1.4

This commit is contained in:
jinyu 2015-04-29 13:18:48 +08:00
parent 784d1f33ea
commit ce83591d35
12 changed files with 25 additions and 273 deletions

View File

@ -259,4 +259,8 @@
+ <font color="red">调整聚合方式,去除原先的weixin4j-mp和weixin4j-qy模块,相应的api模块直接继承weixin4j父模块</font>
+ **weixin4j-base**: <font color="red">删除ActionMapping相关类</font>
+ **weixin4j-base**: <font color="red">删除ActionMapping相关类</font>
* 2015-04-29
+ <font color="red">released 1.4</font>

View File

@ -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
<dependency>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j-mp-api</artifactId>
<version>1.3</version>
<artifactId>weixin4j-mp</artifactId>
<version>1.4</version>
</dependency>
微信企业号API
<dependency>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j-qy-api</artifactId>
<version>1.3</version>
<artifactId>weixin4j-qy</artifactId>
<version>1.4</version>
</dependency>
微信被动消息服务器
`正在重构中..`
以上依赖如果出现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)
----------------------

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j</artifactId>
<version>1.3</version>
<version>1.4</version>
<packaging>pom</packaging>
<name>weixin4j</name>
<url>https://github.com/foxinmy/weixin4j</url>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j</artifactId>
<version>1.3</version>
<version>1.4</version>
</parent>
<artifactId>weixin4j-base</artifactId>
<name>weixin4j-base</name>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j</artifactId>
<version>1.3</version>
<version>1.4</version>
</parent>
<artifactId>weixin4j-mp</artifactId>
<name>weixin4j-mp</name>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j</artifactId>
<version>1.3</version>
<version>1.4</version>
</parent>
<artifactId>weixin4j-qy</artifactId>
<name>weixin4j-qy</name>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j</artifactId>
<version>1.3</version>
<version>1.4</version>
</parent>
<artifactId>weixin4j-server</artifactId>
<name>weixin4j-server</name>

View File

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

View File

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

View File

@ -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 <a
* href="http://mp.weixin.qq.com/wiki/0/61c3a8b9d50ac74f18bdf2e54ddfc4e0.html">接入指南</a>
*/
public static String signature(String... para) {
Arrays.sort(para);
StringBuilder sb = new StringBuilder();
for (String str : para) {
sb.append(str);
}
return DigestUtil.SHA1(sb.toString());
}
/**
* 对xml消息加密
*
* @param appId
* 应用ID
* @param encodingAesKey
* 加密密钥
* @param xmlContent
* 原始消息体
* @return aes加密后的消息体
* @throws WeixinException
*/
public static String aesEncrypt(String appId, String encodingAesKey,
String xmlContent) throws RuntimeException {
byte[] randomBytes = 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;
}
}

View File

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

View File

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