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..957a1190 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,11 @@ weixin4j
* **weixin4j-qy[1.7.8]**
`企业号API封装`
-
+
+* **weixin4j-wxa[1.8.0]**
+
+ `小程序 API 封装`
+
* **weixin4j-server[1.1.8]**
`netty服务器&消息分发`
diff --git a/pom.xml b/pom.xml
index cc598c8a..36b5e930 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.foxinmy
weixin4j
- 1.7.9
+ 1.8.0-SNAPSHOT
pom
weixin4j
https://github.com/foxinmy/weixin4j
@@ -43,6 +43,7 @@
weixin4j-base
weixin4j-mp
weixin4j-qy
+ weixin4j-wxa
weixin4j-server
weixin4j-example
weixin4j-serverX
diff --git a/weixin4j-base/pom.xml b/weixin4j-base/pom.xml
index 9bf3d98a..08eb0a5e 100644
--- a/weixin4j-base/pom.xml
+++ b/weixin4j-base/pom.xml
@@ -5,7 +5,7 @@
com.foxinmy
weixin4j
- 1.7.9
+ 1.8.0-SNAPSHOT
weixin4j-base
weixin4j-base
diff --git a/weixin4j-example/pom.xml b/weixin4j-example/pom.xml
index 986d9834..3b6eb45b 100644
--- a/weixin4j-example/pom.xml
+++ b/weixin4j-example/pom.xml
@@ -5,7 +5,7 @@
com.foxinmy
weixin4j
- 1.7.9
+ 1.8.0-SNAPSHOT
war
weixin4j-example
@@ -48,13 +48,13 @@
com.foxinmy
weixin4j-mp
- 1.7.9
+ 1.8.0-SNAPSHOT
com.foxinmy
weixin4j-qy
- 1.7.9
+ 1.8.0-SNAPSHOT
diff --git a/weixin4j-mp/pom.xml b/weixin4j-mp/pom.xml
index 0e0ee994..4db3abed 100644
--- a/weixin4j-mp/pom.xml
+++ b/weixin4j-mp/pom.xml
@@ -5,7 +5,7 @@
com.foxinmy
weixin4j
- 1.7.9
+ 1.8.0-SNAPSHOT
weixin4j-mp
weixin4j-mp
diff --git a/weixin4j-qy/pom.xml b/weixin4j-qy/pom.xml
index 798d274c..a69764c7 100644
--- a/weixin4j-qy/pom.xml
+++ b/weixin4j-qy/pom.xml
@@ -5,7 +5,7 @@
com.foxinmy
weixin4j
- 1.7.9
+ 1.8.0-SNAPSHOT
weixin4j-qy
weixin4j-qy
diff --git a/weixin4j-server/pom.xml b/weixin4j-server/pom.xml
index d15af0e3..966436bb 100644
--- a/weixin4j-server/pom.xml
+++ b/weixin4j-server/pom.xml
@@ -5,7 +5,7 @@
com.foxinmy
weixin4j
- 1.7.9
+ 1.8.0-SNAPSHOT
weixin4j-server
1.1.9
diff --git a/weixin4j-serverX/pom.xml b/weixin4j-serverX/pom.xml
index 2b9b7518..7a5d240f 100644
--- a/weixin4j-serverX/pom.xml
+++ b/weixin4j-serverX/pom.xml
@@ -6,7 +6,7 @@
com.foxinmy
weixin4j
- 1.7.9
+ 1.8.0-SNAPSHOT
weixin4j-serverX
0.0.1
diff --git a/weixin4j-wxa/pom.xml b/weixin4j-wxa/pom.xml
new file mode 100644
index 00000000..81c827b4
--- /dev/null
+++ b/weixin4j-wxa/pom.xml
@@ -0,0 +1,49 @@
+
+
+ 4.0.0
+
+ com.foxinmy
+ weixin4j
+ 1.8.0-SNAPSHOT
+
+ 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
+
+
+
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..748e9889
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/AESUtils.java
@@ -0,0 +1,60 @@
+package com.foxinmy.weixin4j.wxa;
+
+import java.security.AlgorithmParameters;
+import java.security.Key;
+import java.security.Security;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * @since 1.8
+ */
+final class AESUtils {
+
+ private static boolean initialized = false;
+
+ private AESUtils() {
+ }
+
+ /**
+ * AES解密
+ *
+ * @param content 密文
+ * @param keyByte key
+ * @param ivByte 初始向量
+ * @return 明文
+ */
+ static byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) {
+ 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 synchronized 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..6c2ffe7d
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WXBizDataCrypt.java
@@ -0,0 +1,51 @@
+package com.foxinmy.weixin4j.wxa;
+
+import java.nio.charset.Charset;
+
+import org.apache.commons.codec.binary.Base64;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * 对微信小程序用户加密数据的解密.
+ *
+ * @since 1.8
+ * @see 加密数据解密算法
+ */
+public class WXBizDataCrypt {
+
+ private final String appid;
+
+ private final String sessionKey;
+
+ public WXBizDataCrypt(String appid, String sessionKey) {
+ this.appid = appid;
+ this.sessionKey = sessionKey;
+ }
+
+ /**
+ * 解密微信小程序用户加密数据.
+ *
+ * @param encryptedData 加密的用户数据.
+ * @param iv 与用户数据一同返回的初始向量.
+ * @return 解密后的原文.
+ */
+ 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[] decryptedBytes = AESUtils.decrypt(aesCipher, aesKey, aesIV);
+ final String decryptedText = new String(decryptedBytes, 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..c3fc0794
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * WeChat Mini
+ * Program support library.
+ *
+ * @since 1.8
+ */
+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"));
+ }
+
+}