diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WeixinProxy.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WeixinProxy.java
new file mode 100644
index 00000000..3095152d
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/WeixinProxy.java
@@ -0,0 +1,23 @@
+package com.foxinmy.weixin4j.wxa;
+
+import com.foxinmy.weixin4j.model.WeixinAccount;
+import com.foxinmy.weixin4j.wxa.api.LoginApi;
+
+public class WeixinProxy {
+
+ private final LoginApi loginApi;
+
+ public WeixinProxy(WeixinAccount weixinAccount) {
+ if (weixinAccount == null) {
+ throw new IllegalArgumentException(
+ "weixinAccount must not be empty");
+ }
+
+ this.loginApi = new LoginApi(weixinAccount);
+ }
+
+ public LoginApi getLoginApi() {
+ return loginApi;
+ }
+
+}
diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/LoginApi.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/LoginApi.java
new file mode 100644
index 00000000..cf8b2ac6
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/LoginApi.java
@@ -0,0 +1,38 @@
+package com.foxinmy.weixin4j.wxa.api;
+
+import com.alibaba.fastjson.TypeReference;
+import com.foxinmy.weixin4j.exception.WeixinException;
+import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
+import com.foxinmy.weixin4j.model.WeixinAccount;
+import com.foxinmy.weixin4j.wxa.model.Session;
+
+public class LoginApi extends WxaApi {
+
+ private final WeixinAccount weixinAccount;
+
+ public LoginApi(WeixinAccount weixinAccount) {
+ this.weixinAccount = weixinAccount;
+ }
+
+ public Session jscode2session(String jsCode) throws WeixinException {
+ return jscode2session(jsCode, "authorization_code");
+ }
+
+ /**
+ * 登录凭证校验
+ *
+ * @param jsCode 登录时获取的 code
+ * @param grantType 填写为 authorization_code
+ * @return the session.
+ * @throws WeixinException 发生错误时。比如 js_code 无效。
+ * @see 登录凭证校验
+ */
+ public Session jscode2session(String jsCode, String grantType) throws WeixinException {
+ String jscode2sessionUri = getRequestUri("sns_jscode2session",
+ weixinAccount.getId(), weixinAccount.getSecret(), jsCode, grantType);
+ WeixinResponse response = weixinExecutor.get(jscode2sessionUri);
+ return response.getAsObject(new TypeReference() {
+ });
+ }
+
+}
diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/WxaApi.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/WxaApi.java
new file mode 100644
index 00000000..03a2236e
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/WxaApi.java
@@ -0,0 +1,24 @@
+package com.foxinmy.weixin4j.wxa.api;
+
+import java.util.ResourceBundle;
+
+import com.foxinmy.weixin4j.api.BaseApi;
+
+public abstract class WxaApi extends BaseApi {
+
+ private static final ResourceBundle WEIXIN_BUNDLE;
+
+ static {
+ WEIXIN_BUNDLE = ResourceBundle
+ .getBundle("com/foxinmy/weixin4j/wxa/api/weixin");
+ }
+ @Override
+ protected ResourceBundle weixinBundle() {
+ return WEIXIN_BUNDLE;
+ }
+
+ protected String getRequestUri(String key, Object... args) {
+ return String.format(getRequestUri(key), args);
+ }
+
+}
diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/package-info.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/package-info.java
new file mode 100644
index 00000000..ac7a4ea3
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/package-info.java
@@ -0,0 +1 @@
+package com.foxinmy.weixin4j.wxa.api;
diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/weixin.properties b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/weixin.properties
new file mode 100644
index 00000000..e92081b2
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/api/weixin.properties
@@ -0,0 +1,8 @@
+# \u5fae\u4fe1\u516c\u4f17\u5e73\u53f0|\u5c0f\u7a0b\u5e8f\u6587\u6863\u8bf4\u660e
+# https://developers.weixin.qq.com/miniprogram/dev/api/
+# ----------------------------------------------------------------------------
+
+api_base_url=https://api.weixin.qq.com
+
+# \u767b\u5f55\u51ed\u8bc1\u6821\u9a8c
+sns_jscode2session={api_base_url}/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=%s
diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/Session.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/Session.java
new file mode 100644
index 00000000..ceeffdb5
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/Session.java
@@ -0,0 +1,37 @@
+package com.foxinmy.weixin4j.wxa.model;
+
+import java.io.Serializable;
+
+public class Session implements Serializable {
+
+ private static final long serialVersionUID = 2018051801L;
+
+ private String openId;
+ private String sessionKey;
+ private String unionId;
+
+ public String getOpenId() {
+ return openId;
+ }
+
+ public void setOpenId(String openId) {
+ this.openId = openId;
+ }
+
+ public String getSessionKey() {
+ return sessionKey;
+ }
+
+ public void setSessionKey(String sessionKey) {
+ this.sessionKey = sessionKey;
+ }
+
+ public String getUnionId() {
+ return unionId;
+ }
+
+ public void setUnionId(String unionId) {
+ this.unionId = unionId;
+ }
+
+}
diff --git a/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/package-info.java b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/package-info.java
new file mode 100644
index 00000000..bcb61d30
--- /dev/null
+++ b/weixin4j-wxa/src/main/java/com/foxinmy/weixin4j/wxa/model/package-info.java
@@ -0,0 +1 @@
+package com.foxinmy.weixin4j.wxa.model;
diff --git a/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/model/SessionTest.java b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/model/SessionTest.java
new file mode 100644
index 00000000..69e259f4
--- /dev/null
+++ b/weixin4j-wxa/src/test/java/com/foxinmy/weixin4j/wxa/model/SessionTest.java
@@ -0,0 +1,25 @@
+package com.foxinmy.weixin4j.wxa.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+import com.alibaba.fastjson.JSON;
+
+public class SessionTest {
+
+ @Test
+ public void test() {
+ Session session = JSON.parseObject("{\"openid\": \"OPENID\", \"session_key\": \"SESSIONKEY\"}", Session.class);
+ assertEquals("OPENID", session.getOpenId());
+ assertEquals("SESSIONKEY", session.getSessionKey());
+ assertNull(session.getUnionId());
+
+ session = JSON.parseObject("{\"openid\": \"OPENID\", \"session_key\": \"SESSIONKEY\", \"unionid\": \"UNIONID\"}", Session.class);
+ assertEquals("OPENID", session.getOpenId());
+ assertEquals("SESSIONKEY", session.getSessionKey());
+ assertEquals("UNIONID", session.getUnionId());
+ }
+
+}