diff --git a/.gitignore b/.gitignore
index 1d3be1be..dd217e55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,9 +12,9 @@ hs_err_pid*
*~
# eclipse ignore
-*.settings/*
-/.project
-/.classpath
+.settings/
+.project
+.classpath
/.tomcatplugin
# idea ignore
@@ -22,7 +22,7 @@ hs_err_pid*
*.iml
# maven ignore
-target/*
+target/
# other ignore
*.log
diff --git a/README.md b/README.md
index a9347797..d47a3810 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,11 @@ weixin4j
* **weixin4j-qy[1.7.8]**
`企业号API封装`
-
+
+* **weixin4j-wxa[1.7.9]**
+
+ `小程序 API 封装`
+
* **weixin4j-server[1.1.8]**
`netty服务器&消息分发`
diff --git a/pom.xml b/pom.xml
index a586f9e7..11cfda02 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,7 @@
weixin4j-base
weixin4j-mp
weixin4j-qy
+ weixin4j-wxa
weixin4j-server
weixin4j-example
weixin4j-serverX
diff --git a/weixin4j-wxa/pom.xml b/weixin4j-wxa/pom.xml
new file mode 100644
index 00000000..1a552d0d
--- /dev/null
+++ b/weixin4j-wxa/pom.xml
@@ -0,0 +1,49 @@
+
+
+ 4.0.0
+
+ com.foxinmy
+ weixin4j
+ 1.7.9
+
+ weixin4j-wxa
+ weixin4j-wxa
+ https://github.com/foxinmy/weixin4j/tree/master/weixin4j-wxa
+ 微信小程序 API 支持
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.foxinmy
+ weixin4j-base
+ ${project.version}
+
+
+ commons-codec
+ commons-codec
+ 1.10
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ 1.55
+
+
+ junit
+ junit
+
+
+ ch.qos.logback
+ logback-core
+ 1.1.8
+ test
+
+
+
\ No newline at end of file
diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/AESUtils.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/AESUtils.java
new file mode 100644
index 00000000..4b445b66
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/AESUtils.java
@@ -0,0 +1,58 @@
+package com.foxinmy.weixin4j.wxa;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Key;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+final class AESUtils {
+
+ private static boolean initialized = false;
+
+ private AESUtils() {
+ }
+
+ /**
+ * AES解密
+ *
+ * @param content 密文
+ * @return 明文
+ * @throws InvalidAlgorithmParameterException
+ * @throws NoSuchProviderException
+ */
+ static byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte)
+ throws InvalidAlgorithmParameterException {
+ initialize();
+ try {
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
+ Key sKeySpec = new SecretKeySpec(keyByte, "AES");
+
+ cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte)); // 初始化
+ byte[] result = cipher.doFinal(content);
+ return result;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void initialize() {
+ if (initialized)
+ return;
+ Security.addProvider(new BouncyCastleProvider());
+ initialized = true;
+ }
+
+ private static AlgorithmParameters generateIV(byte[] iv) throws Exception {
+ AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
+ params.init(new IvParameterSpec(iv));
+ return params;
+ }
+
+}
diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WXBizDataCrypt.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WXBizDataCrypt.java
new file mode 100644
index 00000000..d65e4329
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WXBizDataCrypt.java
@@ -0,0 +1,48 @@
+package com.foxinmy.weixin4j.wxa;
+
+import java.nio.charset.Charset;
+import java.security.InvalidAlgorithmParameterException;
+
+import org.apache.commons.codec.binary.Base64;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * @see 加密数据解密算法
+ */
+public class WXBizDataCrypt {
+
+ private final String appid;
+
+ private final String sessionKey;
+
+ public WXBizDataCrypt(String appid, String sessionKey) {
+ this.appid = appid;
+ this.sessionKey = sessionKey;
+ }
+
+ public JSONObject decryptData(final String encryptedData, final String iv) {
+ final byte[] aesKey = Base64.decodeBase64(sessionKey);
+ final byte[] aesCipher = Base64.decodeBase64(encryptedData);
+ final byte[] aesIV = Base64.decodeBase64(iv);
+
+ final byte[] resultByte;
+ try {
+ resultByte = AESUtils.decrypt(aesCipher, aesKey, aesIV);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new RuntimeException(e);
+ }
+
+ final String decryptedText = new String(resultByte, Charset.forName("UTF-8"));
+ final JSONObject decrypted = JSON.parseObject(decryptedText);
+
+ final String appId = decrypted.getJSONObject("watermark").getString("appid");
+ if (!appId.equals(this.appid)) {
+ throw new IllegalArgumentException("Invalid Buffer");
+ }
+
+ return decrypted;
+ }
+
+}
diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/package-info.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/package-info.java
new file mode 100644
index 00000000..6a4bfcc5
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * WeChat Mini Program support library.
+ */
+package com.foxinmy.weixin4j.wxa;
diff --git a/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/WXBizDataCryptTest.java b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/WXBizDataCryptTest.java
new file mode 100644
index 00000000..8743e9dc
--- /dev/null
+++ b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/WXBizDataCryptTest.java
@@ -0,0 +1,55 @@
+package com.foxinmy.weixin4j.wxa;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.alibaba.fastjson.JSONObject;
+
+public class WXBizDataCryptTest {
+
+ @Test
+ public void testDecryptData() {
+ String appId = "wx4f4bc4dec97d474b";
+ String sessionKey = "tiihtNczf5v6AKRyjwEUhQ==";
+
+ WXBizDataCrypt biz = new WXBizDataCrypt(appId, sessionKey);
+
+ String encryptedData
+ = "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZM"
+ + "QmRzooG2xrDcvSnxIMXFufNstNGTyaGS"
+ + "9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+"
+ + "3hVbJSRgv+4lGOETKUQz6OYStslQ142d"
+ + "NCuabNPGBzlooOmB231qMM85d2/fV6Ch"
+ + "evvXvQP8Hkue1poOFtnEtpyxVLW1zAo6"
+ + "/1Xx1COxFvrc2d7UL/lmHInNlxuacJXw"
+ + "u0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn"
+ + "/Hz7saL8xz+W//FRAUid1OksQaQx4CMs"
+ + "8LOddcQhULW4ucetDf96JcR3g0gfRK4P"
+ + "C7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB"
+ + "6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns"
+ + "/8wR2SiRS7MNACwTyrGvt9ts8p12PKFd"
+ + "lqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYV"
+ + "oKlaRv85IfVunYzO0IKXsyl7JCUjCpoG"
+ + "20f0a04COwfneQAGGwd5oa+T8yO5hzuy"
+ + "Db/XcxxmK01EpqOyuxINew==";
+ String iv = "r7BXXKkLb8qrSNn05n0qiA==";
+
+ JSONObject data = biz.decryptData(encryptedData, iv);
+
+ assertEquals("CN", data.getString("country"));
+ assertEquals("ocMvos6NjeKLIBqg5Mr9QjxrP1FA", data.getString("unionId"));
+ assertEquals(1, data.getIntValue("gender"));
+ assertEquals("Guangdong", data.getString("province"));
+ assertEquals("Guangzhou", data.getString("city"));
+ assertEquals("http://wx.qlogo.cn/mmopen/vi_32/aSKcBBPpibyKNicHNTMM0qJVh8Kjgiak2AHWr8MHM4WgMEm7GFhsf8OYrySdbvAMvTsw3mo8ibKicsnfN5pRjl1p8HQ/0", data.getString("avatarUrl"));
+ assertEquals("oGZUI0egBJY1zhBYw2KhdUfwVJJE", data.getString("openId"));
+ assertEquals("Band", data.getString("nickName"));
+ assertEquals("zh_CN", data.getString("language"));
+
+ JSONObject watermark = data.getJSONObject("watermark");
+ assertEquals("wx4f4bc4dec97d474b", watermark.getString("appid"));
+ assertEquals(1477314187L, watermark.getLongValue("timestamp"));
+ }
+
+}