From 46677de90f62ccdbbd48aaf4cb58b62b8e110608 Mon Sep 17 00:00:00 2001
From: Charming <CharmingOh@qq.com>
Date: Thu, 26 Apr 2018 11:40:42 +0800
Subject: [PATCH 1/4] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=BC=80=E6=94=BE?=
 =?UTF-8?q?=E5=B9=B3=E5=8F=B0=EF=BC=9A1.=20WxOpenInRedisConfigStorage=20?=
 =?UTF-8?q?=E6=94=AF=E6=8C=81=20JedisPool/JedisSentinelPool=20=E7=AD=89=20?=
 =?UTF-8?q?Pool<Jedis>=20=E7=9A=84=E5=AD=90=E7=B1=BB=EF=BC=9B2.=20WxOpenIn?=
 =?UTF-8?q?RedisConfigStorage=20=E5=A2=9E=E5=8A=A0=20keyPrefix=20=E4=BB=A5?=
 =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8F=AF=E9=85=8D=E7=BD=AE=E7=9A=84=E5=89=8D?=
 =?UTF-8?q?=E7=BC=80=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../api/impl/WxOpenInRedisConfigStorage.java  | 27 +++++++++++++++----
 1 file changed, 22 insertions(+), 5 deletions(-)

diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java
index eb2d5e5462..3745a15034 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java
@@ -1,7 +1,9 @@
 package me.chanjar.weixin.open.api.impl;
 
+import org.apache.commons.lang3.StringUtils;
 import redis.clients.jedis.Jedis;
 import redis.clients.jedis.JedisPool;
+import redis.clients.util.Pool;
 
 /**
  * @author <a href="https://github.com/007gzs">007</a>
@@ -15,7 +17,11 @@ public class WxOpenInRedisConfigStorage extends WxOpenInMemoryConfigStorage {
   private final static String JSAPI_TICKET_KEY = "wechat_jsapi_ticket:";
   private final static String CARD_API_TICKET_KEY = "wechat_card_api_ticket:";
 
-  protected final JedisPool jedisPool;
+  protected final Pool<Jedis> jedisPool;
+  /**
+   * redis 存储的 key 的前缀,可为空
+   */
+  private String keyPrefix;
   private String componentVerifyTicketKey;
   private String componentAccessTokenKey;
   private String authorizerRefreshTokenKey;
@@ -23,6 +29,15 @@ public class WxOpenInRedisConfigStorage extends WxOpenInMemoryConfigStorage {
   private String jsapiTicketKey;
   private String cardApiTicket;
 
+  public WxOpenInRedisConfigStorage(Pool<Jedis> jedisPool) {
+    this.jedisPool = jedisPool;
+  }
+
+  public WxOpenInRedisConfigStorage(Pool<Jedis> jedisPool, String keyPrefix) {
+    this.jedisPool = jedisPool;
+    this.keyPrefix = keyPrefix;
+  }
+
   public WxOpenInRedisConfigStorage(JedisPool jedisPool) {
     this.jedisPool = jedisPool;
   }
@@ -30,10 +45,12 @@ public WxOpenInRedisConfigStorage(JedisPool jedisPool) {
   @Override
   public void setComponentAppId(String componentAppId) {
     super.setComponentAppId(componentAppId);
-    this.componentVerifyTicketKey = COMPONENT_VERIFY_TICKET_KEY.concat(componentAppId);
-    this.componentAccessTokenKey = COMPONENT_ACCESS_TOKEN_KEY.concat(componentAppId);
-    this.authorizerRefreshTokenKey = AUTHORIZER_REFRESH_TOKEN_KEY.concat(componentAppId);
-    this.authorizerAccessTokenKey = AUTHORIZER_ACCESS_TOKEN_KEY.concat(componentAppId);
+    String prefix = StringUtils.isBlank(keyPrefix) ? "" :
+      (StringUtils.endsWith(keyPrefix, ":") ? keyPrefix : (keyPrefix + ":"));
+    componentVerifyTicketKey = prefix + COMPONENT_VERIFY_TICKET_KEY.concat(componentAppId);
+    componentAccessTokenKey = prefix + COMPONENT_ACCESS_TOKEN_KEY.concat(componentAppId);
+    authorizerRefreshTokenKey = prefix + AUTHORIZER_REFRESH_TOKEN_KEY.concat(componentAppId);
+    authorizerAccessTokenKey = prefix + AUTHORIZER_ACCESS_TOKEN_KEY.concat(componentAppId);
     this.jsapiTicketKey = JSAPI_TICKET_KEY.concat(componentAppId);
     this.cardApiTicket = CARD_API_TICKET_KEY.concat(componentAppId);
   }

From 14a60742159a41b1cda371d2e4fcad17c4b533e1 Mon Sep 17 00:00:00 2001
From: Charming <CharmingOh@qq.com>
Date: Thu, 26 Apr 2018 17:24:24 +0800
Subject: [PATCH 2/4] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=BC=80=E6=94=BE?=
 =?UTF-8?q?=E5=B9=B3=E5=8F=B0=EF=BC=9A=E5=A2=9E=E5=8A=A0=E5=B0=8F=E7=A8=8B?=
 =?UTF-8?q?=E5=BA=8F=E4=BB=A3=E7=A0=81=E6=A8=A1=E6=9D=BF=E5=BA=93=E7=AE=A1?=
 =?UTF-8?q?=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../open/api/WxOpenComponentService.java      | 49 ++++++++++++++++++-
 .../api/impl/WxOpenComponentServiceImpl.java  | 47 +++++++++++++++++-
 .../open/bean/WxOpenMaCodeTemplate.java       | 40 +++++++++++++++
 3 files changed, 133 insertions(+), 3 deletions(-)
 create mode 100644 weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/WxOpenMaCodeTemplate.java

diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java
index 9b07759051..35a438c0a2 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java
@@ -5,16 +5,18 @@
 import me.chanjar.weixin.common.exception.WxErrorException;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
+import me.chanjar.weixin.open.bean.WxOpenMaCodeTemplate;
 import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage;
 import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult;
 import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerOptionResult;
 import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult;
 
+import java.util.List;
+
 /**
  * @author <a href="https://github.com/007gzs">007</a>
  */
 public interface WxOpenComponentService {
-
   String API_COMPONENT_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";
   String API_CREATE_PREAUTHCODE_URL = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode";
   String API_QUERY_AUTH_URL = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth";
@@ -23,7 +25,6 @@ public interface WxOpenComponentService {
   String API_GET_AUTHORIZER_OPTION_URL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_option";
   String API_SET_AUTHORIZER_OPTION_URL = "https://api.weixin.qq.com/cgi-bin/component/api_set_authorizer_option";
 
-
   String COMPONENT_LOGIN_PAGE_URL = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=%s&pre_auth_code=%s&redirect_uri=%s";
   String CONNECT_OAUTH2_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s&component_appid=%s#wechat_redirect";
 
@@ -87,4 +88,48 @@ public interface WxOpenComponentService {
 
   WxMaJscode2SessionResult miniappJscode2Session(String appId, String jsCode) throws WxErrorException;
 
+  /**
+   * 代小程序实现业务
+   * <p>
+   * 小程序代码模版库管理:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1506504150_nMMh6&token=&lang=zh_CN
+   * access_token 为 component_access_token
+   */
+  String GET_TEMPLATE_DRAFT_LIST_URL = "https://api.weixin.qq.com/wxa/gettemplatedraftlist";
+  String GET_TEMPLATE_LIST_URL = "https://api.weixin.qq.com/wxa/gettemplatelist";
+  String ADD_TO_TEMPLATE_URL = "https://api.weixin.qq.com/wxa/addtotemplate";
+  String DELETE_TEMPLATE_URL = "https://api.weixin.qq.com/wxa/deletetemplate";
+
+  /**
+   * 获取草稿箱内的所有临时代码草稿
+   *
+   * @return 草稿箱代码模板列表(draftId)
+   * @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档
+   */
+  List<WxOpenMaCodeTemplate> getTemplateDraftList() throws WxErrorException;
+
+  /**
+   * 获取代码模版库中的所有小程序代码模版
+   *
+   * @return 小程序代码模版列表(templateId)
+   * @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档
+   */
+  List<WxOpenMaCodeTemplate> getTemplateList() throws WxErrorException;
+
+  /**
+   * 将草稿箱的草稿选为小程序代码模版
+   *
+   * @param draftId 草稿ID,本字段可通过“获取草稿箱内的所有临时代码草稿”接口获得
+   * @throws WxErrorException 操作失败时抛出,具体错误码请看此接口的注释文档
+   * @see #getTemplateDraftList
+   */
+  void addToTemplate(long draftId) throws WxErrorException;
+
+  /**
+   * 删除指定小程序代码模版
+   *
+   * @param templateId 要删除的模版ID
+   * @throws WxErrorException 操作失败时抛出,具体错误码请看此接口的注释文档
+   * @see #getTemplateList
+   */
+  void deleteTemplate(long templateId) throws WxErrorException;
 }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java
index 36dd286505..cb9062c7e3 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java
@@ -3,6 +3,8 @@
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.reflect.TypeToken;
 import me.chanjar.weixin.common.bean.result.WxError;
 import me.chanjar.weixin.common.exception.WxErrorException;
 import me.chanjar.weixin.common.util.crypto.SHA1;
@@ -15,6 +17,7 @@
 import me.chanjar.weixin.open.api.WxOpenService;
 import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken;
 import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken;
+import me.chanjar.weixin.open.bean.WxOpenMaCodeTemplate;
 import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo;
 import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage;
 import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult;
@@ -26,13 +29,14 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Map;
 
 /**
  * @author <a href="https://github.com/007gzs">007</a>
  */
 public class WxOpenComponentServiceImpl implements WxOpenComponentService {
-
+  private static final JsonParser JSON_PARSER = new JsonParser();
   private static final Map<String, WxMaService> WX_OPEN_MA_SERVICE_MAP = new Hashtable<>();
   private static final Map<String, WxMpService> WX_OPEN_MP_SERVICE_MAP = new Hashtable<>();
 
@@ -288,4 +292,45 @@ public WxMaJscode2SessionResult miniappJscode2Session(String appId, String jsCod
     return WxMaJscode2SessionResult.fromJson(responseContent);
   }
 
+  @Override
+  public List<WxOpenMaCodeTemplate> getTemplateDraftList() throws WxErrorException {
+    String responseContent = get(GET_TEMPLATE_DRAFT_LIST_URL);
+    JsonObject response = JSON_PARSER.parse(StringUtils.defaultString(responseContent, "{}")).getAsJsonObject();
+    boolean hasDraftList = response.has("draft_list");
+    if (hasDraftList) {
+      return WxOpenGsonBuilder.create().fromJson(response.getAsJsonArray("draft_list"),
+        new TypeToken<List<WxOpenMaCodeTemplate>>() {
+        }.getType());
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public List<WxOpenMaCodeTemplate> getTemplateList() throws WxErrorException {
+    String responseContent = get(GET_TEMPLATE_LIST_URL);
+    JsonObject response = JSON_PARSER.parse(StringUtils.defaultString(responseContent, "{}")).getAsJsonObject();
+    boolean hasDraftList = response.has("template_list");
+    if (hasDraftList) {
+      return WxOpenGsonBuilder.create().fromJson(response.getAsJsonArray("template_list"),
+        new TypeToken<List<WxOpenMaCodeTemplate>>() {
+        }.getType());
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public void addToTemplate(long draftId) throws WxErrorException {
+    JsonObject param = new JsonObject();
+    param.addProperty("draft_id", draftId);
+    post(ADD_TO_TEMPLATE_URL, param.toString());
+  }
+
+  @Override
+  public void deleteTemplate(long templateId) throws WxErrorException {
+    JsonObject param = new JsonObject();
+    param.addProperty("template_id", templateId);
+    post(DELETE_TEMPLATE_URL, param.toString());
+  }
 }
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/WxOpenMaCodeTemplate.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/WxOpenMaCodeTemplate.java
new file mode 100644
index 0000000000..be1bb9b138
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/WxOpenMaCodeTemplate.java
@@ -0,0 +1,40 @@
+package me.chanjar.weixin.open.bean;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 17:10
+ */
+@Data
+public class WxOpenMaCodeTemplate implements Serializable {
+  private static final long serialVersionUID = -3278116984473619010L;
+  /**
+   * 草稿id
+   */
+  @SerializedName(value = "draftId", alternate = "draft_id")
+  private Long draftId;
+  /**
+   * 模版id
+   */
+  @SerializedName(value = "templateId", alternate = "template_id")
+  private Long templateId;
+  /**
+   * 模版版本号,开发者自定义字段
+   */
+  @SerializedName(value = "userVersion", alternate = "user_version")
+  private String userVersion;
+  /**
+   * 模版描述 开发者自定义字段
+   */
+  @SerializedName(value = "userDesc", alternate = "user_desc")
+  private String userDesc;
+  /**
+   * 开发者上传草稿时间 / 被添加为模版的时间
+   */
+  @SerializedName(value = "createTime", alternate = "create_time")
+  private Long createTime;
+}

From 170307a77ecdf9321e6f749ac2a6d7de15adf88f Mon Sep 17 00:00:00 2001
From: Charming <CharmingOh@qq.com>
Date: Thu, 26 Apr 2018 20:26:15 +0800
Subject: [PATCH 3/4] =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=EF=BC=9A?=
 =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=A3=E7=A0=81=E7=AE=A1=E7=90=86=E7=9B=B8?=
 =?UTF-8?q?=E5=85=B3=20API?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../wx/miniapp/api/WxMaCodeService.java       | 140 ++++++++++++
 .../wx/miniapp/api/WxMaService.java           |   7 +
 .../miniapp/api/impl/WxMaCodeServiceImpl.java | 148 +++++++++++++
 .../wx/miniapp/api/impl/WxMaServiceImpl.java  |  34 +--
 .../wx/miniapp/bean/code/WxMaCategory.java    |  62 ++++++
 .../bean/code/WxMaCodeAuditStatus.java        |  37 ++++
 .../bean/code/WxMaCodeCommitRequest.java      |  39 ++++
 .../miniapp/bean/code/WxMaCodeExtConfig.java  | 199 ++++++++++++++++++
 .../bean/code/WxMaCodeSubmitAuditRequest.java |  30 +++
 .../code/WxMaCodeVersionDistribution.java     |  30 +++
 .../wx/miniapp/bean/code/package-info.java    |   7 +
 .../WxMaCodeCommitRequestGsonAdapter.java     |  28 +++
 ...xMaCodeVersionDistributionGsonAdapter.java |  50 +++++
 .../wx/miniapp/util/json/WxMaGsonBuilder.java |   4 +
 .../api/impl/WxMaCodeServiceImplTest.java     | 156 ++++++++++++++
 .../bean/code/WxMaCodeCommitRequestTest.java  |  25 +++
 .../code/WxMaCodeSubmitAuditRequestTest.java  |  30 +++
 .../code/WxMaCodeVersionDistributionTest.java |  15 ++
 18 files changed, 1027 insertions(+), 14 deletions(-)
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCodeService.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCategory.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequest.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeExtConfig.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequest.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistribution.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/package-info.java
 create mode 100755 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeCommitRequestGsonAdapter.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeVersionDistributionGsonAdapter.java
 create mode 100644 weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImplTest.java
 create mode 100644 weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequestTest.java
 create mode 100644 weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequestTest.java
 create mode 100644 weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistributionTest.java

diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCodeService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCodeService.java
new file mode 100644
index 0000000000..37e850eec3
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCodeService.java
@@ -0,0 +1,140 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.code.WxMaCategory;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeAuditStatus;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeSubmitAuditRequest;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
+import me.chanjar.weixin.common.exception.WxErrorException;
+
+import java.util.List;
+
+/**
+ * 小程序代码管理相关 API(大部分只能是第三方平台调用)
+ * 文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140610_Uavc4&token=&lang=zh_CN
+ *
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:43
+ */
+public interface WxMaCodeService {
+  /**
+   * 为授权的小程序帐号上传小程序代码
+   */
+  String COMMIT_URL = "https://api.weixin.qq.com/wxa/commit";
+  String GET_QRCODE_URL = "https://api.weixin.qq.com/wxa/get_qrcode";
+  String GET_CATEGORY_URL = "https://api.weixin.qq.com/wxa/get_category";
+  String GET_PAGE_URL = "https://api.weixin.qq.com/wxa/get_page";
+  String SUBMIT_AUDIT_URL = "https://api.weixin.qq.com/wxa/submit_audit";
+  String GET_AUDIT_STATUS_URL = "https://api.weixin.qq.com/wxa/get_auditstatus";
+  String GET_LATEST_AUDIT_STATUS_URL = "https://api.weixin.qq.com/wxa/get_latest_auditstatus";
+  String RELEASE_URL = "https://api.weixin.qq.com/wxa/release";
+  String CHANGE_VISIT_STATUS_URL = "https://api.weixin.qq.com/wxa/change_visitstatus";
+  String REVERT_CODE_RELEASE_URL = "https://api.weixin.qq.com/wxa/revertcoderelease";
+  String GET_SUPPORT_VERSION_URL = "https://api.weixin.qq.com/cgi-bin/wxopen/getweappsupportversion";
+  String SET_SUPPORT_VERSION_URL = "https://api.weixin.qq.com/cgi-bin/wxopen/setweappsupportversion";
+  String UNDO_CODE_AUDIT_URL = "https://api.weixin.qq.com/wxa/undocodeaudit";
+
+  /**
+   * 为授权的小程序帐号上传小程序代码(仅仅支持第三方开放平台)
+   *
+   * @param commitRequest 参数
+   * @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档
+   */
+  void commit(WxMaCodeCommitRequest commitRequest) throws WxErrorException;
+
+  /**
+   * 获取体验小程序的体验二维码
+   *
+   * @return 二维码 bytes
+   * @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档
+   */
+  byte[] getQrCode() throws WxErrorException;
+
+  /**
+   * 获取授权小程序帐号的可选类目
+   *
+   * @return List<WxMaCategory>
+   * @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档
+   */
+  List<WxMaCategory> getCategory() throws WxErrorException;
+
+  /**
+   * 获取小程序的第三方提交代码的页面配置(仅供第三方开发者代小程序调用)
+   *
+   * @return page_list 页面配置列表
+   * @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档
+   */
+  List<String> getPage() throws WxErrorException;
+
+  /**
+   * 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用)
+   *
+   * @param auditRequest 提交审核参数
+   * @return 审核编号
+   * @throws WxErrorException 提交失败时返回,具体错误码请看此接口的注释文档
+   */
+  long submitAudit(WxMaCodeSubmitAuditRequest auditRequest) throws WxErrorException;
+
+  /**
+   * 查询某个指定版本的审核状态(仅供第三方代小程序调用)
+   *
+   * @param auditId 提交审核时获得的审核id
+   * @return 审核状态
+   * @throws WxErrorException 查询失败时返回,具体错误码请看此接口的注释文档
+   */
+  WxMaCodeAuditStatus getAuditStatus(long auditId) throws WxErrorException;
+
+  /**
+   * 查询最新一次提交的审核状态(仅供第三方代小程序调用)
+   *
+   * @return 审核状态
+   * @throws WxErrorException 查询失败时返回,具体错误码请看此接口的注释文档
+   */
+  WxMaCodeAuditStatus getLatestAuditStatus() throws WxErrorException;
+
+  /**
+   * 发布已通过审核的小程序(仅供第三方代小程序调用)
+   *
+   * @throws WxErrorException 发布失败时抛出,具体错误码请看此接口的注释文档
+   */
+  void release() throws WxErrorException;
+
+  /**
+   * 修改小程序线上代码的可见状态(仅供第三方代小程序调用)
+   *
+   * @param action 设置可访问状态,发布后默认可访问,close为不可见,open为可见
+   * @throws WxErrorException 发布失败时抛出,具体错误码请看此接口的注释文档
+   */
+  void changeVisitStatus(String action) throws WxErrorException;
+
+  /**
+   * 小程序版本回退(仅供第三方代小程序调用)
+   *
+   * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档
+   */
+  void revertCodeRelease() throws WxErrorException;
+
+  /**
+   * 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用)
+   *
+   * @return 小程序版本分布信息
+   * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档
+   */
+  WxMaCodeVersionDistribution getSupportVersion() throws WxErrorException;
+
+  /**
+   * 设置最低基础库版本(仅供第三方代小程序调用)
+   *
+   * @param version 版本
+   * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档
+   */
+  void setSupportVersion(String version) throws WxErrorException;
+
+  /**
+   * 小程序审核撤回
+   * 单个帐号每天审核撤回次数最多不超过1次,一个月不超过10次
+   *
+   * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档
+   */
+  void undoCodeAudit() throws WxErrorException;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
index c437966ef8..000fda0b1e 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
@@ -135,6 +135,13 @@ public interface WxMaService {
    */
   WxMaTemplateService getTemplateService();
 
+  /**
+   * 返回代码操作相关的 API
+   *
+   * @return WxMaCodeService
+   */
+  WxMaCodeService getCodeService();
+
   /**
    * 初始化http请求对象.
    */
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java
new file mode 100644
index 0000000000..0cd40f6d21
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java
@@ -0,0 +1,148 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaCodeService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCategory;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeAuditStatus;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeSubmitAuditRequest;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.reflect.TypeToken;
+import me.chanjar.weixin.common.bean.result.WxError;
+import me.chanjar.weixin.common.exception.WxErrorException;
+import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestExecutor;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+/**
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 20:00
+ */
+public class WxMaCodeServiceImpl implements WxMaCodeService {
+  private static final JsonParser JSON_PARSER = new JsonParser();
+  private WxMaService wxMaService;
+
+  public WxMaCodeServiceImpl(WxMaService wxMaService) {
+    this.wxMaService = wxMaService;
+  }
+
+  @Override
+  public void commit(WxMaCodeCommitRequest commitRequest) throws WxErrorException {
+    this.wxMaService.post(COMMIT_URL, commitRequest.toJson());
+  }
+
+  @Override
+  public byte[] getQrCode() throws WxErrorException {
+    String appId = this.wxMaService.getWxMaConfig().getAppid();
+    Path qrCodeFilePath = null;
+    try {
+      RequestExecutor<File, String> executor = BaseMediaDownloadRequestExecutor
+        .create(this.wxMaService.getRequestHttp(), Files.createTempDirectory("weixin-java-tools-ma-" + appId).toFile());
+      qrCodeFilePath = this.wxMaService.execute(executor, GET_QRCODE_URL, null).toPath();
+      return Files.readAllBytes(qrCodeFilePath);
+    } catch (IOException e) {
+      throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e);
+    } finally {
+      if (qrCodeFilePath != null) {
+        try {
+          // 及时删除二维码文件,避免积压过多缓存文件
+          Files.delete(qrCodeFilePath);
+        } catch (Exception ignored) {
+        }
+      }
+    }
+  }
+
+  @Override
+  public List<WxMaCategory> getCategory() throws WxErrorException {
+    String responseContent = this.wxMaService.get(GET_CATEGORY_URL, null);
+    JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject();
+    boolean hasCategoryList = jsonObject.has("category_list");
+    if (hasCategoryList) {
+      return WxMaGsonBuilder.create().fromJson(jsonObject.getAsJsonArray("category_list"),
+        new TypeToken<List<WxMaCategory>>() {
+        }.getType());
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public List<String> getPage() throws WxErrorException {
+    String responseContent = this.wxMaService.get(GET_PAGE_URL, null);
+    JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject();
+    boolean hasPageList = jsonObject.has("page_list");
+    if (hasPageList) {
+      return WxMaGsonBuilder.create().fromJson(jsonObject.getAsJsonArray("page_list"),
+        new TypeToken<List<String>>() {
+        }.getType());
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public long submitAudit(WxMaCodeSubmitAuditRequest auditRequest) throws WxErrorException {
+    String responseContent = this.wxMaService.post(SUBMIT_AUDIT_URL, auditRequest.toJson());
+    JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject();
+    return GsonHelper.getLong(jsonObject, "auditid");
+  }
+
+  @Override
+  public WxMaCodeAuditStatus getAuditStatus(long auditId) throws WxErrorException {
+    JsonObject param = new JsonObject();
+    param.addProperty("auditid", auditId);
+    String responseContent = this.wxMaService.post(GET_AUDIT_STATUS_URL, param.toString());
+    return WxMaCodeAuditStatus.fromJson(responseContent);
+  }
+
+  @Override
+  public WxMaCodeAuditStatus getLatestAuditStatus() throws WxErrorException {
+    String responseContent = this.wxMaService.get(GET_LATEST_AUDIT_STATUS_URL, null);
+    return WxMaCodeAuditStatus.fromJson(responseContent);
+  }
+
+  @Override
+  public void release() throws WxErrorException {
+    this.wxMaService.post(RELEASE_URL, "{}");
+  }
+
+  @Override
+  public void changeVisitStatus(String action) throws WxErrorException {
+    JsonObject param = new JsonObject();
+    param.addProperty("action", action);
+    this.wxMaService.post(CHANGE_VISIT_STATUS_URL, param.toString());
+  }
+
+  @Override
+  public void revertCodeRelease() throws WxErrorException {
+    this.wxMaService.get(REVERT_CODE_RELEASE_URL, null);
+  }
+
+  @Override
+  public WxMaCodeVersionDistribution getSupportVersion() throws WxErrorException {
+    String responseContent = this.wxMaService.post(GET_SUPPORT_VERSION_URL, "{}");
+    return WxMaCodeVersionDistribution.fromJson(responseContent);
+  }
+
+  @Override
+  public void setSupportVersion(String version) throws WxErrorException {
+    JsonObject param = new JsonObject();
+    param.addProperty("version", version);
+    this.wxMaService.post(SET_SUPPORT_VERSION_URL, param.toString());
+  }
+
+  @Override
+  public void undoCodeAudit() throws WxErrorException {
+    this.wxMaService.get(UNDO_CODE_AUDIT_URL, null);
+  }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
index 1ec7801d16..d3be4a73e6 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
@@ -1,19 +1,6 @@
 package cn.binarywang.wx.miniapp.api.impl;
 
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.locks.Lock;
-
-import org.apache.http.HttpHost;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.BasicResponseHandler;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import cn.binarywang.wx.miniapp.api.WxMaCodeService;
 import cn.binarywang.wx.miniapp.api.WxMaMediaService;
 import cn.binarywang.wx.miniapp.api.WxMaMsgService;
 import cn.binarywang.wx.miniapp.api.WxMaQrcodeService;
@@ -35,6 +22,19 @@
 import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
+import org.apache.http.HttpHost;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.BasicResponseHandler;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
 
 /**
  * @author <a href="https://github.com/binarywang">Binary Wang</a>
@@ -51,6 +51,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
   private WxMaUserService userService = new WxMaUserServiceImpl(this);
   private WxMaQrcodeService qrCodeService = new WxMaQrcodeServiceImpl(this);
   private WxMaTemplateService templateService = new WxMaTemplateServiceImpl(this);
+  private WxMaCodeService codeService = new WxMaCodeServiceImpl(this);
 
   private int retrySleepMillis = 1000;
   private int maxRetryTimes = 5;
@@ -290,4 +291,9 @@ public WxMaQrcodeService getQrcodeService() {
   public WxMaTemplateService getTemplateService() {
     return this.templateService;
   }
+
+  @Override
+  public WxMaCodeService getCodeService() {
+    return this.codeService;
+  }
 }
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCategory.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCategory.java
new file mode 100644
index 0000000000..32195333eb
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCategory.java
@@ -0,0 +1,62 @@
+package cn.binarywang.wx.miniapp.bean.code;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 小程序帐号的可选类目,其中 address / tag / title 是提交审核会用到的
+ *
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:44
+ */
+@Data
+@Builder
+public class WxMaCategory implements Serializable {
+  private static final long serialVersionUID = -7663757440028175135L;
+  /**
+   * 一级类目名称
+   */
+  @SerializedName("first_class")
+  private String firstClass;
+  /**
+   * 二级类目名称
+   */
+  @SerializedName("second_class")
+  private String secondClass;
+  /**
+   * 三级类目名称
+   */
+  @SerializedName("third_class")
+  private String thirdClass;
+  /**
+   * 一级类目的ID编号
+   */
+  @SerializedName("first_id")
+  private Long firstId;
+  /**
+   * 二级类目的ID编号
+   */
+  @SerializedName("second_id")
+  private Long secondId;
+  /**
+   * 三级类目的ID编号
+   */
+  @SerializedName("third_id")
+  private Long thirdId;
+
+  /**
+   * 小程序的页面,可通过“获取小程序的第三方提交代码的页面配置”接口获得
+   */
+  private String address;
+  /**
+   * 小程序的标签,多个标签用空格分隔,标签不能多于10个,标签长度不超过20
+   */
+  private String tag;
+  /**
+   * 小程序页面的标题,标题长度不超过32
+   */
+  private String title;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java
new file mode 100644
index 0000000000..36c33eaf1a
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java
@@ -0,0 +1,37 @@
+package cn.binarywang.wx.miniapp.bean.code;
+
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 小程序代码审核状态
+ *
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:44:39
+ */
+@Data
+@Builder
+public class WxMaCodeAuditStatus implements Serializable {
+  private static final long serialVersionUID = 4655119308692217268L;
+  /**
+   * 审核 ID
+   */
+  @SerializedName(value = "auditId", alternate = {"auditid"})
+  private Long auditId;
+  /**
+   * 审核状态,其中0为审核成功,1为审核失败,2为审核中
+   */
+  private Integer status;
+  /**
+   * 当status=1,审核被拒绝时,返回的拒绝原因
+   */
+  private String reason;
+
+  public static WxMaCodeAuditStatus fromJson(String json) {
+    return WxMaGsonBuilder.create().fromJson(json, WxMaCodeAuditStatus.class);
+  }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequest.java
new file mode 100644
index 0000000000..ec64da59a3
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequest.java
@@ -0,0 +1,39 @@
+package cn.binarywang.wx.miniapp.bean.code;
+
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 微信代码请求上传参数
+ *
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:44:47
+ */
+@Data
+@Builder
+public class WxMaCodeCommitRequest implements Serializable {
+  private static final long serialVersionUID = 7495157056049312108L;
+  /**
+   * 代码库中的代码模版ID
+   */
+  private Long templateId;
+  /**
+   * 第三方自定义的配置
+   */
+  private WxMaCodeExtConfig extConfig;
+  /**
+   * 代码版本号,开发者可自定义
+   */
+  private String userVersion;
+  /**
+   * 代码描述,开发者可自定义
+   */
+  private String userDesc;
+
+  public String toJson() {
+    return WxMaGsonBuilder.create().toJson(this);
+  }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeExtConfig.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeExtConfig.java
new file mode 100644
index 0000000000..5121112392
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeExtConfig.java
@@ -0,0 +1,199 @@
+package cn.binarywang.wx.miniapp.bean.code;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 上传代码需要用到的第三方自定义的配置
+ *
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:44
+ */
+@Data
+@Builder
+public class WxMaCodeExtConfig implements Serializable {
+  private static final long serialVersionUID = -7666911367458178753L;
+  /**
+   * 配置 ext.json 是否生效
+   * 必填:是
+   */
+  private boolean extEnable;
+  /**
+   * 配置 extAppid
+   * 必填:是
+   */
+  private String extAppid;
+  /**
+   * 开发自定义的数据字段
+   * 必填:否
+   */
+  private Object ext;
+  /**
+   * 单独设置每个页面的 json
+   * 必填:否
+   * key: page 名称,如 pages/logs/logs
+   * value: page 配置
+   */
+  private Map<String, PageConfig> extPages;
+  /**
+   * 是否直接提交到待审核列表
+   * 必填:否
+   */
+  private Boolean directCommit;
+  /**
+   * 设置页面路径(同 app.json 相同的字段,填写会覆盖 app.json)
+   * 必填:否
+   */
+  private List<String> pages;
+  /**
+   * 设置默认页面的窗口表现(同 app.json 相同的字段,填写会覆盖 app.json)
+   * 必填:否
+   */
+  private PageConfig window;
+  /**
+   * 设置各种网络请求的超时时间(同 app.json 相同的字段,填写会覆盖 app.json)
+   * 必填:否
+   */
+  private NetworkTimeout networkTimeout;
+  /**
+   * 设置是否开启 debug 模式(同 app.json 相同的字段,填写会覆盖 app.json)
+   * 必填:否
+   */
+  private Boolean debug;
+
+  /**
+   * page.json 配置,页面配置
+   * 文档:https://mp.weixin.qq.com/debug/wxadoc/dev/framework/config.html
+   */
+  @Data
+  @Builder
+  public static class PageConfig {
+    /**
+     * 导航栏背景颜色,如"#000000" HexColor
+     * 默认:#000000
+     */
+    private String navigationBarBackgroundColor;
+    /**
+     * 导航栏标题颜色,仅支持 black/white
+     * 默认:white
+     */
+    private String navigationBarTextStyle;
+    /**
+     * 导航栏标题文字内容
+     */
+    private String navigationBarTitleText;
+    /**
+     * 窗口的背景色 HexColor
+     * 默认:#ffffff
+     */
+    private String backgroundColor;
+    /**
+     * 下拉背景字体、loading 图的样式,仅支持 dark/light
+     * 默认:dark
+     */
+    private String backgroundTextStyle;
+    /**
+     * 是否开启下拉刷新,详见页面相关事件处理函数
+     * 默认:false
+     */
+    private String enablePullDownRefresh;
+    /**
+     * 设置为 true 则页面整体不能上下滚动;只在 page.json 中有效,无法在 app.json 中设置该项
+     * 默认:false
+     */
+    private Boolean disableScroll;
+    /**
+     * 页面上拉触底事件触发时距页面底部距离,单位为px
+     * 默认:50
+     */
+    private Integer onReachBottomDistance;
+  }
+
+  /**
+   * tabBar 配置
+   */
+  @Data
+  @Builder
+  public static class TabBar {
+    /**
+     * HexColor, tab 上的文字默认颜色
+     */
+    private String color;
+    /**
+     * HexColor, tab 上的文字选中时的颜色
+     */
+    private String selectedColor;
+    /**
+     * HexColor, tab 的背景色
+     */
+    private String backgroundColor;
+    /**
+     * tabbar 上边框的颜色,仅支持 black/white
+     */
+    private String borderStyle;
+    /**
+     * tab 的列表,最少2个、最多5个 tab
+     */
+    private List<Item> list;
+    /**
+     * 可选值 bottom、top
+     */
+    private String position;
+
+    /**
+     * list item
+     */
+    @Data
+    @Builder
+    public static class Item {
+      /**
+       * 是	页面路径,必须在 pages 中先定义
+       */
+      private String pagePath;
+      /**
+       * tab 上按钮文字
+       */
+      private String text;
+      /**
+       * 图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px,当 postion 为 top 时,此参数无效,不支持网络图片
+       */
+      private String iconPath;
+      /**
+       * 选中时的图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px ,当 postion 为 top 时,此参数无效
+       */
+      private String selectedIconPath;
+    }
+  }
+
+  /**
+   * 各种网络请求的超时时间
+   */
+  @Data
+  @Builder
+  public static class NetworkTimeout {
+    /**
+     * wx.request的超时时间,单位毫秒,默认为:60000
+     * 必填:否
+     */
+    private Integer request;
+    /**
+     * wx.connectSocket的超时时间,单位毫秒,默认为:60000
+     * 必填:否
+     */
+    private Integer connectSocket;
+    /**
+     * wx.uploadFile的超时时间,单位毫秒,默认为:60000
+     * 必填:否
+     */
+    private Integer uploadFile;
+    /**
+     * wx.downloadFile的超时时间,单位毫秒,默认为:60000
+     * 必填:否
+     */
+    private Integer downloadFile;
+  }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequest.java
new file mode 100644
index 0000000000..5b784dbded
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequest.java
@@ -0,0 +1,30 @@
+package cn.binarywang.wx.miniapp.bean.code;
+
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 提交审核的请求
+ *
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:45
+ */
+@Data
+@Builder
+public class WxMaCodeSubmitAuditRequest implements Serializable {
+  private static final long serialVersionUID = 8854979405505241314L;
+  /**
+   * 提交审核项的一个列表(至少填写1项,至多填写5项)
+   */
+  @SerializedName("item_list")
+  private List<WxMaCategory> itemList;
+
+  public String toJson() {
+    return WxMaGsonBuilder.create().toJson(this);
+  }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistribution.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistribution.java
new file mode 100644
index 0000000000..4c0f09644f
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistribution.java
@@ -0,0 +1,30 @@
+package cn.binarywang.wx.miniapp.bean.code;
+
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * 小程序代码版本号分布
+ *
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:45
+ */
+@Data
+public class WxMaCodeVersionDistribution {
+  /**
+   * 当前版本
+   */
+  private String nowVersion;
+  /**
+   * 受影响用户占比
+   * key: version, 版本号
+   * value: percentage, 受影响比例
+   */
+  private Map<String, Float> uvInfo;
+
+  public static WxMaCodeVersionDistribution fromJson(String json) {
+    return WxMaGsonBuilder.create().fromJson(json, WxMaCodeVersionDistribution.class);
+  }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/package-info.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/package-info.java
new file mode 100644
index 0000000000..01f6496b99
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * 微信小程序代码管理相关的 bean
+ *
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:44
+ */
+package cn.binarywang.wx.miniapp.bean.code;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeCommitRequestGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeCommitRequestGsonAdapter.java
new file mode 100755
index 0000000000..410f86ca1f
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeCommitRequestGsonAdapter.java
@@ -0,0 +1,28 @@
+package cn.binarywang.wx.miniapp.util.json;
+
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import java.lang.reflect.Type;
+
+/**
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:47
+ */
+public class WxMaCodeCommitRequestGsonAdapter implements JsonSerializer<WxMaCodeCommitRequest> {
+
+  @Override
+  public JsonElement serialize(WxMaCodeCommitRequest request, Type typeOfSrc, JsonSerializationContext context) {
+    JsonObject requestJson = new JsonObject();
+    requestJson.addProperty("template_id", request.getTemplateId());
+    requestJson.addProperty("user_version", request.getUserVersion());
+    requestJson.addProperty("user_desc", request.getUserDesc());
+    if (request.getExtConfig() != null) {
+      requestJson.addProperty("ext_json", WxMaGsonBuilder.create().toJson(request.getExtConfig()));
+    }
+    return requestJson;
+  }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeVersionDistributionGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeVersionDistributionGsonAdapter.java
new file mode 100644
index 0000000000..027ca6a959
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeVersionDistributionGsonAdapter.java
@@ -0,0 +1,50 @@
+package cn.binarywang.wx.miniapp.util.json;
+
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+
+import java.lang.reflect.Type;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:47
+ */
+public class WxMaCodeVersionDistributionGsonAdapter implements JsonDeserializer<WxMaCodeVersionDistribution> {
+  @Override
+  public WxMaCodeVersionDistribution deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
+    if (json == null) {
+      return null;
+    }
+
+    WxMaCodeVersionDistribution distribution = new WxMaCodeVersionDistribution();
+    JsonObject object = json.getAsJsonObject();
+    distribution.setNowVersion(GsonHelper.getString(object, "now_version"));
+    distribution.setUvInfo(getAsMap(object.getAsJsonObject("uv_info"), "items"));
+    return distribution;
+  }
+
+  private Map<String, Float> getAsMap(JsonObject object, String memberName) {
+    JsonArray array = object.getAsJsonArray(memberName);
+    if (array != null && array.size() > 0) {
+      Map<String, Float> map = new LinkedHashMap<>(array.size());
+      for (JsonElement element : array) {
+        JsonObject elementObject = element.getAsJsonObject();
+        String version = GsonHelper.getString(elementObject, "version");
+        if (version != null) {
+          Float percentage = GsonHelper.getFloat(elementObject, "percentage");
+          map.put(version, percentage);
+        }
+      }
+      return map;
+    }
+    return null;
+  }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java
index 5a08a39a7e..a11e8e6c13 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java
@@ -1,6 +1,8 @@
 package cn.binarywang.wx.miniapp.util.json;
 
 import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 
@@ -13,6 +15,8 @@ public class WxMaGsonBuilder {
   static {
     INSTANCE.disableHtmlEscaping();
     INSTANCE.registerTypeAdapter(WxMaTemplateMessage.class, new WxMaTemplateMessageGsonAdapter());
+    INSTANCE.registerTypeAdapter(WxMaCodeCommitRequest.class, new WxMaCodeCommitRequestGsonAdapter());
+    INSTANCE.registerTypeAdapter(WxMaCodeVersionDistribution.class, new WxMaCodeVersionDistributionGsonAdapter());
   }
 
   public static Gson create() {
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImplTest.java
new file mode 100644
index 0000000000..34c788993b
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImplTest.java
@@ -0,0 +1,156 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaCodeService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCategory;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeAuditStatus;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeExtConfig;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeSubmitAuditRequest;
+import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
+import cn.binarywang.wx.miniapp.config.WxMaConfig;
+import cn.binarywang.wx.miniapp.test.ApiTestModule;
+import com.google.inject.Inject;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+/**
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 20:18
+ */
+@Test
+@Guice(modules = ApiTestModule.class)
+public class WxMaCodeServiceImplTest {
+  @Inject
+  private WxMaService wxService;
+  @Inject
+  private WxMaConfig wxMaConfig;
+
+  @Test
+  public void testGetCategory() throws Exception {
+    List<WxMaCategory> categories = wxService.getCodeService().getCategory();
+    System.out.println(String.valueOf(categories));
+  }
+
+  @Test
+  public void testCommit() throws Exception {
+    String themeColor = "#0074d9";
+    String themeFontColor = "#ffffff";
+
+    Map<String, Object> ext = new HashMap<>();
+    ext.put("appName", "xxx");
+    ext.put("verified", true);
+    ext.put("navigationBarBackgroundColor", themeColor);
+    ext.put("navigationBarTextStyle", themeFontColor);
+    ext.put("companyId", 4128);
+    ext.put("companyFullName", "xxx有限公司");
+
+    WxMaCodeService wxMaCodeService = wxService.getCodeService();
+    WxMaCodeCommitRequest commitRequest = WxMaCodeCommitRequest
+      .builder()
+      .templateId(6L)
+      .userVersion("v0.1.0")
+      .userDesc("init")
+      .extConfig(WxMaCodeExtConfig.builder()
+        .extAppid(wxMaConfig.getAppid())
+        .extEnable(true)
+        .ext(ext)
+        .window(
+          WxMaCodeExtConfig.PageConfig
+            .builder()
+            .navigationBarBackgroundColor(themeColor)
+            .navigationBarTextStyle(themeFontColor)
+            .build()
+        )
+        .build())
+      .build();
+    wxMaCodeService.commit(commitRequest);
+  }
+
+  @Test
+  public void testGetQrCode() throws Exception {
+    byte[] qrCode = wxService.getCodeService().getQrCode();
+    assertTrue(qrCode.length > 0);
+  }
+
+  @Test
+  public void testGetPage() throws Exception {
+    List<String> pageList = wxService.getCodeService().getPage();
+    System.out.println(String.valueOf(pageList));
+  }
+
+  @Test
+  public void testSubmitAudit() throws Exception {
+    WxMaCodeSubmitAuditRequest auditRequest = WxMaCodeSubmitAuditRequest
+      .builder()
+      .itemList(Arrays.asList(
+        WxMaCategory
+          .builder()
+          .address("pages/logs/logs")
+          .tag("工具 效率")
+          .firstClass("工具")
+          .firstId(287L)
+          .secondClass("效率")
+          .secondId(616L)
+          .title("日志")
+          .build()
+      )).build();
+    long auditId = wxService.getCodeService().submitAudit(auditRequest);
+    assertTrue(auditId > 0);
+    // 421937937
+    System.out.println(auditId);
+  }
+
+  @Test
+  public void testGetAuditStatus() throws Exception {
+    WxMaCodeAuditStatus auditStatus = wxService.getCodeService().getAuditStatus(421937937L);
+    System.out.println(auditStatus);
+    assertNotNull(auditStatus);
+  }
+
+  @Test
+  public void testGetLatestAuditStatus() throws Exception {
+    WxMaCodeAuditStatus auditStatus = wxService.getCodeService().getLatestAuditStatus();
+    System.out.println(auditStatus);
+    assertNotNull(auditStatus);
+  }
+
+  @Test
+  public void testRelease() throws Exception {
+    wxService.getCodeService().release();
+  }
+
+  @Test
+  public void testChangeVisitStatus() throws Exception {
+    wxService.getCodeService().changeVisitStatus("open");
+  }
+
+  @Test
+  public void testRevertCodeRelease() throws Exception {
+    wxService.getCodeService().revertCodeRelease();
+  }
+
+  @Test
+  public void testGetSupportVersion() throws Exception {
+    WxMaCodeVersionDistribution distribution = wxService.getCodeService().getSupportVersion();
+    System.out.println(distribution);
+  }
+
+  @Test
+  public void testSetSupportVersion() throws Exception {
+    wxService.getCodeService().setSupportVersion("1.2.0");
+  }
+
+  @Test
+  public void testUndoCodeAudit() throws Exception {
+
+  }
+}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequestTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequestTest.java
new file mode 100644
index 0000000000..36cf1c4840
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequestTest.java
@@ -0,0 +1,25 @@
+package cn.binarywang.wx.miniapp.bean.code;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+/**
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:54
+ */
+public class WxMaCodeCommitRequestTest {
+  @Test
+  public void testToJson() {
+    WxMaCodeCommitRequest commitRequest = WxMaCodeCommitRequest.builder()
+      .templateId(1L)
+      .userVersion("v0.1.0")
+      .userDesc("init")
+      .extConfig(WxMaCodeExtConfig.builder()
+        .extAppid("app123")
+        .extEnable(true)
+        .build())
+      .build();
+    assertEquals(commitRequest.toJson(), "{\"template_id\":1,\"user_version\":\"v0.1.0\",\"user_desc\":\"init\",\"ext_json\":\"{\\\"extEnable\\\":true,\\\"extAppid\\\":\\\"app123\\\"}\"}");
+  }
+}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequestTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequestTest.java
new file mode 100644
index 0000000000..1f962087dc
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequestTest.java
@@ -0,0 +1,30 @@
+package cn.binarywang.wx.miniapp.bean.code;
+
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+
+/**
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:55
+ */
+public class WxMaCodeSubmitAuditRequestTest {
+  @Test
+  public void testToJson() {
+    WxMaCodeSubmitAuditRequest request = WxMaCodeSubmitAuditRequest
+      .builder()
+      .itemList(Arrays.asList(
+        WxMaCategory
+          .builder()
+          .address("pages/logs/logs")
+          .tag("工具 效率")
+          .firstClass("工具")
+          .firstId(287L)
+          .secondClass("效率")
+          .secondId(616L)
+          .title("日志")
+          .build()
+      )).build();
+    System.out.println(request.toJson());
+  }
+}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistributionTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistributionTest.java
new file mode 100644
index 0000000000..da02972e04
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistributionTest.java
@@ -0,0 +1,15 @@
+package cn.binarywang.wx.miniapp.bean.code;
+
+import org.testng.annotations.Test;
+
+/**
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-26 19:58
+ */
+public class WxMaCodeVersionDistributionTest {
+  @Test
+  public void testFromJson() {
+    String json = "{\"errcode\":0,\"errmsg\":\"ok\",\"now_version\":\"1.2.0\",\"uv_info\":{\"items\":[{\"version\":\"0.0.0\",\"percentage\":0},{\"version\":\"1.0.0\",\"percentage\":0},{\"version\":\"1.0.1\",\"percentage\":0},{\"version\":\"1.1.0\",\"percentage\":0},{\"version\":\"1.1.1\",\"percentage\":0},{\"version\":\"1.2.0\",\"percentage\":0},{\"version\":\"1.2.1\",\"percentage\":0},{\"version\":\"1.2.2\",\"percentage\":0},{\"version\":\"1.2.3\",\"percentage\":0},{\"version\":\"1.2.4\",\"percentage\":0},{\"version\":\"1.2.5\",\"percentage\":0},{\"version\":\"1.2.6\",\"percentage\":0},{\"version\":\"1.3.0\",\"percentage\":0},{\"version\":\"1.4.0\",\"percentage\":0},{\"version\":\"1.4.1\",\"percentage\":0},{\"version\":\"1.4.2\",\"percentage\":0},{\"version\":\"1.4.3\",\"percentage\":0},{\"version\":\"1.4.4\",\"percentage\":0},{\"version\":\"1.5.0\",\"percentage\":0},{\"version\":\"1.5.1\",\"percentage\":0},{\"version\":\"1.5.2\",\"percentage\":0},{\"version\":\"1.5.3\",\"percentage\":0},{\"version\":\"1.5.4\",\"percentage\":0},{\"version\":\"1.5.5\",\"percentage\":0},{\"version\":\"1.5.6\",\"percentage\":0},{\"version\":\"1.5.7\",\"percentage\":0},{\"version\":\"1.5.8\",\"percentage\":0},{\"version\":\"1.6.0\",\"percentage\":0.0132},{\"version\":\"1.6.1\",\"percentage\":0.0132},{\"version\":\"1.6.2\",\"percentage\":0.0132},{\"version\":\"1.6.3\",\"percentage\":0.0132},{\"version\":\"1.6.4\",\"percentage\":0.0132},{\"version\":\"1.6.5\",\"percentage\":0.0132},{\"version\":\"1.6.6\",\"percentage\":0.0132},{\"version\":\"1.6.7\",\"percentage\":0.1711},{\"version\":\"1.6.8\",\"percentage\":0.1711},{\"version\":\"1.7.0\",\"percentage\":0.1842},{\"version\":\"1.7.1\",\"percentage\":0.25},{\"version\":\"1.7.2\",\"percentage\":0.5263},{\"version\":\"1.7.3\",\"percentage\":0.5263},{\"version\":\"1.7.4\",\"percentage\":0.6711}]}}\n";
+    System.out.println(WxMaCodeVersionDistribution.fromJson(json));
+  }
+}

From 2145a11ecb84f8f78255f5c82b018c6e4450fe48 Mon Sep 17 00:00:00 2001
From: Charming <CharmingOh@qq.com>
Date: Fri, 27 Apr 2018 15:50:18 +0800
Subject: [PATCH 4/4] =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=EF=BC=9A?=
 =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BF=AE=E6=94=B9=E6=9C=8D=E5=8A=A1=E5=99=A8?=
 =?UTF-8?q?=E5=9C=B0=E5=9D=80=E3=80=81=E6=88=90=E5=91=98=E7=AE=A1=E7=90=86?=
 =?UTF-8?q?=20API?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../wx/miniapp/api/WxMaService.java           |  7 ++
 .../wx/miniapp/api/WxMaSettingService.java    | 65 +++++++++++++++++++
 .../wx/miniapp/api/impl/WxMaServiceImpl.java  |  7 ++
 .../api/impl/WxMaSettingServiceImpl.java      | 48 ++++++++++++++
 .../wx/miniapp/bean/WxMaDomainAction.java     | 58 +++++++++++++++++
 .../api/impl/WxMaSettingServiceImplTest.java  | 54 +++++++++++++++
 6 files changed, 239 insertions(+)
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaSettingService.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImpl.java
 create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaDomainAction.java
 create mode 100644 weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImplTest.java

diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
index 000fda0b1e..9ba17f2bc5 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
@@ -142,6 +142,13 @@ public interface WxMaService {
    */
   WxMaCodeService getCodeService();
 
+  /**
+   * 小程序修改服务器地址、成员管理 API
+   *
+   * @return WxMaSettingService
+   */
+  WxMaSettingService getSettingService();
+
   /**
    * 初始化http请求对象.
    */
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaSettingService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaSettingService.java
new file mode 100644
index 0000000000..98870a9ca0
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaSettingService.java
@@ -0,0 +1,65 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.WxMaDomainAction;
+import me.chanjar.weixin.common.exception.WxErrorException;
+
+/**
+ * 小程序修改服务器地址、成员管理 API(大部分只能是第三方平台调用)
+ *
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-27 15:46
+ */
+public interface WxMaSettingService {
+  /**
+   * 修改服务器地址:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489138143_WPbOO&token=&lang=zh_CN
+   * access_token 为 authorizer_access_token
+   */
+  String MODIFY_DOMAIN_URL = "https://api.weixin.qq.com/wxa/modify_domain";
+  String SET_WEB_VIEW_DOMAIN_URL = "https://api.weixin.qq.com/wxa/setwebviewdomain";
+  /**
+   * 小程序成员管理:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140588_nVUgx&token=&lang=zh_CN
+   * access_token 为 authorizer_access_token
+   */
+  String BIND_TESTER_URL = "https://api.weixin.qq.com/wxa/bind_tester";
+  String UNBIND_TESTER_URL = "https://api.weixin.qq.com/wxa/unbind_tester";
+
+  /**
+   * 操作服务器域名
+   *
+   * @param domainAction 域名操作参数
+   *                     除了 webViewDomain,都是有效的
+   * @return 以下字段仅在 get 时返回完整字段
+   * @throws WxErrorException 操作失败时抛出,具体错误码请看文档
+   */
+  WxMaDomainAction modifyDomain(WxMaDomainAction domainAction) throws WxErrorException;
+
+  /**
+   * 设置小程序业务域名(仅供第三方代小程序调用)
+   * 授权给第三方的小程序,其业务域名只可以为第三方的服务器,
+   * 当小程序通过第三方发布代码上线后,小程序原先自己配置的业务域名将被删除,
+   * 只保留第三方平台的域名,所以第三方平台在代替小程序发布代码之前,需要调用接口为小程序添加业务域名。
+   * 提示:需要先将域名登记到第三方平台的小程序业务域名中,才可以调用接口进行配置。
+   *
+   * @param domainAction 域名操作参数
+   *                     只有 action 和 webViewDomain 是有效的
+   * @return 以下字段仅在 get 时返回完整字段
+   * @throws WxErrorException 操作失败时抛出,具体错误码请看文档
+   */
+  WxMaDomainAction setWebViewDomain(WxMaDomainAction domainAction) throws WxErrorException;
+
+  /**
+   * 绑定微信用户为小程序体验者
+   *
+   * @param wechatId 微信号
+   * @throws WxErrorException 失败时抛出,具体错误码请看文档
+   */
+  void bindTester(String wechatId) throws WxErrorException;
+
+  /**
+   * 解除绑定小程序的体验者
+   *
+   * @param wechatId 微信号
+   * @throws WxErrorException 失败时抛出,具体错误码请看文档
+   */
+  void unbindTester(String wechatId) throws WxErrorException;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
index d3be4a73e6..a4d9d515d7 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
@@ -5,6 +5,7 @@
 import cn.binarywang.wx.miniapp.api.WxMaMsgService;
 import cn.binarywang.wx.miniapp.api.WxMaQrcodeService;
 import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.WxMaSettingService;
 import cn.binarywang.wx.miniapp.api.WxMaTemplateService;
 import cn.binarywang.wx.miniapp.api.WxMaUserService;
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
@@ -52,6 +53,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
   private WxMaQrcodeService qrCodeService = new WxMaQrcodeServiceImpl(this);
   private WxMaTemplateService templateService = new WxMaTemplateServiceImpl(this);
   private WxMaCodeService codeService = new WxMaCodeServiceImpl(this);
+  private WxMaSettingService settingService = new WxMaSettingServiceImpl(this);
 
   private int retrySleepMillis = 1000;
   private int maxRetryTimes = 5;
@@ -296,4 +298,9 @@ public WxMaTemplateService getTemplateService() {
   public WxMaCodeService getCodeService() {
     return this.codeService;
   }
+
+  @Override
+  public WxMaSettingService getSettingService() {
+    return this.settingService;
+  }
 }
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImpl.java
new file mode 100644
index 0000000000..96ca6d21c2
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImpl.java
@@ -0,0 +1,48 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.WxMaSettingService;
+import cn.binarywang.wx.miniapp.bean.WxMaDomainAction;
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import me.chanjar.weixin.common.exception.WxErrorException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-27 15:46
+ */
+public class WxMaSettingServiceImpl implements WxMaSettingService {
+  private WxMaService wxMaService;
+
+  public WxMaSettingServiceImpl(WxMaService wxMaService) {
+    this.wxMaService = wxMaService;
+  }
+
+  @Override
+  public WxMaDomainAction modifyDomain(WxMaDomainAction domainAction) throws WxErrorException {
+    String responseContent = this.wxMaService.post(MODIFY_DOMAIN_URL, domainAction.toJson());
+    return WxMaDomainAction.fromJson(responseContent);
+  }
+
+  @Override
+  public WxMaDomainAction setWebViewDomain(WxMaDomainAction domainAction) throws WxErrorException {
+    String responseContent = this.wxMaService.post(SET_WEB_VIEW_DOMAIN_URL, domainAction.toJson());
+    return WxMaDomainAction.fromJson(responseContent);
+  }
+
+  @Override
+  public void bindTester(String wechatId) throws WxErrorException {
+    Map<String, Object> param = new HashMap<>(1);
+    param.put("wechatid", wechatId);
+    this.wxMaService.post(BIND_TESTER_URL, WxMaGsonBuilder.create().toJson(param));
+  }
+
+  @Override
+  public void unbindTester(String wechatId) throws WxErrorException {
+    Map<String, Object> param = new HashMap<>(1);
+    param.put("wechatid", wechatId);
+    this.wxMaService.post(UNBIND_TESTER_URL, WxMaGsonBuilder.create().toJson(param));
+  }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaDomainAction.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaDomainAction.java
new file mode 100644
index 0000000000..313f6cbed8
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaDomainAction.java
@@ -0,0 +1,58 @@
+package cn.binarywang.wx.miniapp.bean;
+
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 域名相关操作
+ *
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-27 15:45
+ */
+@Data
+@Builder
+public class WxMaDomainAction implements Serializable {
+  private static final long serialVersionUID = -2898601966852935708L;
+  /**
+   * add添加, delete删除, set覆盖, get获取。当参数是get时不需要填四个域名字段
+   */
+  private String action;
+  /**
+   * request合法域名,当action参数是get时不需要此字段。
+   */
+  @SerializedName("requestdomain")
+  private List<String> requestDomain;
+  /**
+   * socket合法域名,当action参数是get时不需要此字段。
+   */
+  @SerializedName("wsrequestdomain")
+  private List<String> wsRequestDomain;
+  /**
+   * uploadFile合法域名,当action参数是get时不需要此字段。
+   */
+  @SerializedName("uploaddomain")
+  private List<String> uploadDomain;
+  /**
+   * downloadFile合法域名,当action参数是get时不需要此字段。
+   */
+  @SerializedName("downloaddomain")
+  private List<String> downloadDomain;
+  /**
+   * 小程序业务域名,当action参数是get时不需要此字段。
+   */
+  @SerializedName("webviewdomain")
+  private List<String> webViewDomain;
+
+  public String toJson() {
+    return WxMaGsonBuilder.create().toJson(this);
+  }
+
+  public static WxMaDomainAction fromJson(String json) {
+    return WxMaGsonBuilder.create().fromJson(json, WxMaDomainAction.class);
+  }
+}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImplTest.java
new file mode 100644
index 0000000000..8da5f19208
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImplTest.java
@@ -0,0 +1,54 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.WxMaDomainAction;
+import cn.binarywang.wx.miniapp.test.ApiTestModule;
+import com.google.inject.Inject;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * @author <a href="https://github.com/charmingoh">Charming</a>
+ * @since 2018-04-27 15:38
+ */
+@Test
+@Guice(modules = ApiTestModule.class)
+public class WxMaSettingServiceImplTest {
+  @Inject
+  private WxMaService wxService;
+
+  @Test
+  public void testModifyDomain() throws Exception {
+    WxMaDomainAction domainAction = wxService.getSettingService().modifyDomain(WxMaDomainAction
+      .builder()
+      .action("get")
+      .build());
+    System.out.println(domainAction);
+    assertNotNull(domainAction);
+
+    domainAction.setAction("set");
+    WxMaDomainAction result = wxService.getSettingService().modifyDomain(domainAction);
+    System.out.println(result);
+  }
+
+  @Test
+  public void testBindTester() throws Exception {
+    wxService.getSettingService().bindTester("WeChatId");
+  }
+
+  @Test
+  public void testUnbindTester() throws Exception {
+    wxService.getSettingService().unbindTester("WeChatId");
+  }
+
+  @Test
+  public void testSetWebViewDomain() throws Exception {
+    WxMaDomainAction domainAction = wxService.getSettingService().setWebViewDomain(WxMaDomainAction
+      .builder()
+      .action("get")
+      .build());
+    System.out.println(domainAction);
+  }
+}