فهرست منبع

#562 小程序增加代码管理相关 API

* 微信开放平台:1. WxOpenInRedisConfigStorage 支持 JedisPool/JedisSentinelPool 等 Pool<Jedis> 的子类;2. WxOpenInRedisConfigStorage 增加 keyPrefix 以支持可配置的前缀;

* 微信开放平台:增加小程序代码模板库管理

* 小程序:增加代码管理相关 API
Charming 7 سال پیش
والد
کامیت
0247486b13
18فایلهای تغییر یافته به همراه1027 افزوده شده و 14 حذف شده
  1. 140 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCodeService.java
  2. 7 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
  3. 148 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java
  4. 20 14
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
  5. 62 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCategory.java
  6. 37 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java
  7. 39 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequest.java
  8. 199 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeExtConfig.java
  9. 30 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequest.java
  10. 30 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistribution.java
  11. 7 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/package-info.java
  12. 28 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeCommitRequestGsonAdapter.java
  13. 50 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeVersionDistributionGsonAdapter.java
  14. 4 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java
  15. 156 0
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImplTest.java
  16. 25 0
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequestTest.java
  17. 30 0
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequestTest.java
  18. 15 0
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistributionTest.java

+ 140 - 0
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;
+}

+ 7 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java

@@ -136,6 +136,13 @@ public interface WxMaService {
   WxMaTemplateService getTemplateService();
 
   /**
+   * 返回代码操作相关的 API
+   *
+   * @return WxMaCodeService
+   */
+  WxMaCodeService getCodeService();
+
+  /**
    * 初始化http请求对象.
    */
   void initHttp();

+ 148 - 0
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);
+  }
+}

+ 20 - 14
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.SimpleGetRequestExecutor;
 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 class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
   public WxMaTemplateService getTemplateService() {
     return this.templateService;
   }
+
+  @Override
+  public WxMaCodeService getCodeService() {
+    return this.codeService;
+  }
 }

+ 62 - 0
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;
+}

+ 37 - 0
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);
+  }
+}

+ 39 - 0
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);
+  }
+}

+ 199 - 0
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;
+  }
+}

+ 30 - 0
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);
+  }
+}

+ 30 - 0
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);
+  }
+}

+ 7 - 0
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;

+ 28 - 0
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;
+  }
+}

+ 50 - 0
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;
+  }
+}

+ 4 - 0
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() {

+ 156 - 0
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 {
+
+  }
+}

+ 25 - 0
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\\\"}\"}");
+  }
+}

+ 30 - 0
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());
+  }
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 15 - 0
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistributionTest.java