Browse Source

:new: #3077【小程序】增加openApi管理的接口支持

水依寒 1 year ago
parent
commit
831aac31ba

+ 57 - 3
weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java

@@ -70,11 +70,10 @@ public enum WxMaErrorMsgEnum {
    * appid不正确,或者不符合绑定关系要求.
    * 对应操作:<code>sendUniformMessage</code>
    * 对应地址:
-   * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN
-   * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html
+   * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/clearQuota.html
    * </pre>
    */
-  CODE_40013(40013, "appid不正确,或者不符合绑定关系要求"),
+  CODE_40013(40013, "appid不正确/不合法(避免异常字符,注意大小写),或者不符合绑定关系要求"),
   /**
    * <pre>
    * template_id 不正确.
@@ -268,6 +267,50 @@ public enum WxMaErrorMsgEnum {
    */
   CODE_47504(47504, "activity_id 过期"),
   /**
+   * api 禁止清零调用次数,因为清零次数达到上限
+   *
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/clearQuota.html">参考文档</a>
+   */
+  CODE_48006(48006, "api 禁止清零调用次数,因为清零次数达到上限"),
+
+  /**
+   * rid不存在
+   *
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/getRidInfo.html">参考文档</a>
+   */
+  CODE_76001(76001, "rid不存在"),
+  /**
+   * rid为空或者格式错误
+   *
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/getRidInfo.html">参考文档</a>
+   */
+  CODE_76002(76002, "rid为空或者格式错误"),
+  /**
+   * 当前账号无权查询该rid,该rid属于其他账号调用所产生
+   *
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/getRidInfo.html">参考文档</a>
+   */
+  CODE_76003(76003, "当前账号无权查询该rid,该rid属于其他账号调用所产生"),
+  /**
+   * rid过期
+   *
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/getRidInfo.html">参考文档</a>
+   */
+  CODE_76004(76004, "rid过期,仅支持持续7天内的rid"),
+  /**
+   * cgi_path填错了
+   *
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/getApiQuota.html">参考文档</a>
+   */
+  CODE_76021(76021, "cgi_path填错了"),
+  /**
+   * 当前调用接口使用的token与api所属账号不符
+   *
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/getApiQuota.html">参考文档</a>
+   */
+  CODE_76022(76022, "当前调用接口使用的token与api所属账号不符,详情可看注意事项的说明"),
+
+  /**
    * 没有绑定开放平台帐号.
    */
   CODE_89002(89002, "没有绑定开放平台帐号"),
@@ -343,6 +386,17 @@ public enum WxMaErrorMsgEnum {
   CODE_91017(91017, "+号规则 不同类型关联名主体不一致"),
 
   CODE_40097(40097, "参数错误"),
+  /**
+   * 缺少 appid 参数
+   * <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/clearQuotaByAppSecret.html">参考文档</a>
+   */
+  CODE_41002(41002, "缺少 appid 参数"),
+  /**
+   * 缺少 secret 参数
+   * <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/clearQuotaByAppSecret.html">参考文档</a>
+   */
+  CODE_41004(41004, "缺少 secret 参数"),
+
 
   CODE_41006(41006, "media_id 不能为空"),
 

+ 65 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaOpenApiService.java

@@ -0,0 +1,65 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.openapi.WxMiniGetApiQuotaResult;
+import cn.binarywang.wx.miniapp.bean.openapi.WxMiniGetRidInfoResult;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * openApi管理
+ *
+ * @author shuiyihan12
+ * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/clearQuota.html">openApi管理 微信文档</a>
+ * @since 2023/7/7 17:07
+ */
+public interface WxMaOpenApiService {
+
+  /**
+   * 本接口用于清空公众号/小程序/第三方平台等接口的每日调用接口次数
+   * HTTP调用:https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=ACCESS_TOKEN
+   *
+   * @return 是否成功
+   * @throws WxErrorException the wx error exception
+   * @apiNote !!! 单小程序直接调用该方法 , 如多个appid调用此方法前请调用 {@link cn.binarywang.wx.miniapp.api.WxMaService#switchoverTo} 切换appid !!!
+   * @code wxMaService.getWxMaOpenApiService().clearQuota() //单个
+   * @code wxMaService.switchoverTo(" appid ").getWxMaOpenApiService().clearQuota() //多个
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/clearQuota.html">注意事项参考微信文档</a>
+   */
+  boolean clearQuota() throws WxErrorException;
+
+  /**
+   * 查询API调用额度
+   * HTTP调用:https://api.weixin.qq.com/cgi-bin/openapi/quota/get?access_token=ACCESS_TOKEN
+   *
+   * @param cgiPath api的请求地址,例如"/cgi-bin/message/custom/send";不要前缀“https://api.weixin.qq.com” ,也不要漏了"/",否则都会76003的报错
+   * @return 额度详情
+   * @throws WxErrorException 微信异常
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/getApiQuota.html">注意事项参考微信文档</a>
+   */
+  WxMiniGetApiQuotaResult getApiQuota(String cgiPath) throws WxErrorException;
+
+  /**
+   * 查询rid信息
+   * HTTP调用:https://api.weixin.qq.com/cgi-bin/openapi/rid/get?access_token=ACCESS_TOKEN
+   *
+   * @param rid 调用接口报错返回的rid
+   * @return 该rid对应的请求详情
+   * @throws WxErrorException 微信异常
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/getRidInfo.html">注意事项参考微信文档</a>
+   */
+  WxMiniGetRidInfoResult getRidInfo(String rid) throws WxErrorException;
+
+
+  /**
+   * 使用AppSecret重置 API 调用次数
+   * HTTP调用:https://api.weixin.qq.com/cgi-bin/clear_quota/v2
+   *
+   * @return 是否成功
+   * @throws WxErrorException 微信异常
+   * @apiNote !!! 单小程序直接调用该方法 , 如多个appid调用此方法前请调用 {@link cn.binarywang.wx.miniapp.api.WxMaService#switchoverTo} 切换appid!!!
+   * 参考示例
+   * @code wxMaService.getWxMaOpenApiService().clearQuotaByAppSecret() //单个
+   * @code wxMaService.switchoverTo(" appid ").getWxMaOpenApiService().clearQuotaByAppSecret() //多个
+   * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/openApi-mgnt/clearQuotaByAppSecret.html">注意事项参考微信文档</a>
+   */
+  boolean clearQuotaByAppSecret() throws WxErrorException;
+}

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

@@ -523,4 +523,10 @@ public interface WxMaService extends WxService {
    * @return getWxMaShopPayService
    */
   WxMaShopPayService getWxMaShopPayService();
+
+  /**
+   * 小程序openApi管理
+   * @return getWxMaOpenApiService
+   */
+  WxMaOpenApiService getWxMaOpenApiService();
 }

+ 6 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java

@@ -85,6 +85,7 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
   private final WxMaProductOrderService productOrderService = new WxMaProductOrderServiceImpl(this);
   private final WxMaShopCouponService wxMaShopCouponService = new WxMaShopCouponServiceImpl(this);
   private final WxMaShopPayService wxMaShopPayService = new WxMaShopPayServiceImpl(this);
+  private final WxMaOpenApiService wxMaOpenApiService = new WxMaOpenApiServiceImpl(this);
   private Map<String, WxMaConfig> configMap;
   private int retrySleepMillis = 1000;
   private int maxRetryTimes = 5;
@@ -634,4 +635,9 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
   public WxMaShopPayService getWxMaShopPayService() {
     return this.wxMaShopPayService;
   }
+
+  @Override
+  public WxMaOpenApiService getWxMaOpenApiService() {
+    return this.wxMaOpenApiService;
+  }
 }

+ 81 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOpenApiServiceImpl.java

@@ -0,0 +1,81 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaOpenApiService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.openapi.WxMiniGetApiQuotaResult;
+import cn.binarywang.wx.miniapp.bean.openapi.WxMiniGetRidInfoResult;
+import cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.json.GsonParser;
+
+import static me.chanjar.weixin.common.api.WxConsts.ERR_CODE;
+
+/**
+ * @author shuiyihan12
+ * @since 2023/7/7 17:08
+ */
+@RequiredArgsConstructor
+public class WxMaOpenApiServiceImpl implements WxMaOpenApiService {
+  private final WxMaService wxMaService;
+
+  private static final String QUOTA = "quota";
+  private static final String REQUEST = "request";
+
+
+  @Override
+  public boolean clearQuota() throws WxErrorException {
+    JsonObject params = new JsonObject();
+    params.addProperty("appid", this.wxMaService.getWxMaConfig().getAppid());
+    String responseContent = this.wxMaService.post(WxMaApiUrlConstants.OpenApi.CLEAR_QUOTA, params.toString());
+    parseErrorResponse(responseContent);
+    return true;
+  }
+
+  @Override
+  public WxMiniGetApiQuotaResult getApiQuota(String cgiPath) throws WxErrorException {
+    JsonObject params = new JsonObject();
+    params.addProperty("cgi_path", cgiPath);
+    String responseContent = this.wxMaService.post(WxMaApiUrlConstants.OpenApi.GET_API_QUOTA, params.toString());
+    parseErrorResponse(responseContent);
+    JsonObject response = GsonParser.parse(responseContent);
+    if (response.has(QUOTA)) {
+      return WxMaGsonBuilder.create().fromJson(response.getAsJsonObject(QUOTA), WxMiniGetApiQuotaResult.class);
+    }
+    return null;
+  }
+
+
+  @Override
+  public WxMiniGetRidInfoResult getRidInfo(String rid) throws WxErrorException {
+    JsonObject params = new JsonObject();
+    params.addProperty("rid", rid);
+    String responseContent = this.wxMaService.post(WxMaApiUrlConstants.OpenApi.GET_RID_INFO, params.toString());
+    parseErrorResponse(responseContent);
+    JsonObject response = GsonParser.parse(responseContent);
+    if (response.has(REQUEST)) {
+      return WxMaGsonBuilder.create().fromJson(response.getAsJsonObject(QUOTA), WxMiniGetRidInfoResult.class);
+    }
+    return null;
+  }
+
+  @Override
+  public boolean clearQuotaByAppSecret() throws WxErrorException {
+    String url = String.format(WxMaApiUrlConstants.OpenApi.CLEAR_QUOTA_BY_APP_SECRET, this.wxMaService.getWxMaConfig().getAppid(), this.wxMaService.getWxMaConfig().getSecret());
+    String responseContent = this.wxMaService.post(url, "");
+    parseErrorResponse(responseContent);
+    return true;
+  }
+
+
+  private void parseErrorResponse(String response) throws WxErrorException {
+    JsonObject jsonObject = GsonParser.parse(response);
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(response, WxType.MiniApp));
+    }
+  }
+}

+ 28 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/openapi/WxMiniGetApiQuotaResult.java

@@ -0,0 +1,28 @@
+package cn.binarywang.wx.miniapp.bean.openapi;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * 查询API调用额度 返回数据
+ *
+ * @author shuiyihan12
+ * @since 2023/7/10 16:52
+ */
+@Data
+public class WxMiniGetApiQuotaResult {
+
+  /**
+   * 当天该账号可调用该接口的次数
+   */
+  @SerializedName("daily_limit")
+  private Integer dailyLimit;
+  /**
+   * 当天已经调用的次数
+   */
+  private Integer used;
+  /**
+   * 当天剩余调用次数
+   */
+  private Integer remain;
+}

+ 44 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/openapi/WxMiniGetRidInfoResult.java

@@ -0,0 +1,44 @@
+package cn.binarywang.wx.miniapp.bean.openapi;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * 查询rid信息 返回数据
+ * @author shuiyihan12
+ * @since 2023/7/10 16:53
+ */
+@Data
+public class WxMiniGetRidInfoResult {
+
+  /**
+   * 发起请求的时间戳
+   */
+  @SerializedName("invoke_time")
+  private Integer invokeTime;
+  /**
+   * 请求毫秒级耗时
+   */
+  @SerializedName("cost_in_ms")
+  private Integer costInMs;
+  /**
+   * 请求的URL参数
+   */
+  @SerializedName("request_url")
+  private String requestUrl;
+  /**
+   * post请求的请求参数
+   */
+  @SerializedName("request_body")
+  private String requestBody;
+  /**
+   * 接口请求返回参数
+   */
+  @SerializedName("response_body")
+  private String responseBody;
+  /**
+   * 接口请求的客户端ip
+   */
+  @SerializedName("client_ip")
+  private String clientIp;
+}

+ 24 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java

@@ -10,6 +10,30 @@ import lombok.experimental.UtilityClass;
  */
 @UtilityClass
 public class WxMaApiUrlConstants {
+
+  /**
+   * openApi管理
+   */
+  public interface OpenApi {
+    /**
+     * 重置API调用次数
+     */
+    String CLEAR_QUOTA = "https://api.weixin.qq.com/cgi-bin/clear_quota";
+    /**
+     * 查询API调用额度
+     */
+    String GET_API_QUOTA = "https://api.weixin.qq.com/cgi-bin/openapi/quota/get";
+    /**
+     * 查询rid信息
+     */
+    String GET_RID_INFO = "https://api.weixin.qq.com/cgi-bin/openapi/rid/get";
+    /**
+     * 使用AppSecret重置 API 调用次数
+     */
+    String CLEAR_QUOTA_BY_APP_SECRET = "https://api.weixin.qq.com/cgi-bin/clear_quota/v2?appid=%s&appsecret=%s";
+
+  }
+
   public interface Analysis {
     String GET_DAILY_SUMMARY_TREND_URL = "https://api.weixin.qq.com/datacube/getweanalysisappiddailysummarytrend";
     String GET_DAILY_VISIT_TREND_URL = "https://api.weixin.qq.com/datacube/getweanalysisappiddailyvisittrend";

+ 48 - 0
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaOpenApiServiceImplTest.java

@@ -0,0 +1,48 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.openapi.WxMiniGetApiQuotaResult;
+import cn.binarywang.wx.miniapp.test.ApiTestModule;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.AssertJUnit.assertTrue;
+
+/**
+ * openApi管理测试
+ *
+ * @author shuiyihan12
+ * @since 2023/7/7 17:08
+ */
+@Test
+@Guice(modules = ApiTestModule.class)
+public class WxMaOpenApiServiceImplTest {
+
+  @Inject
+  private WxMaService wxMaService;
+
+
+  @Test
+  public void clearQuota() throws WxErrorException {
+    final boolean result = wxMaService.getWxMaOpenApiService().clearQuota();
+    assertTrue(result);
+  }
+
+  @Test
+  public void getApiQuota() throws WxErrorException {
+    String cgiPath = "/cgi-bin/openapi/quota/get";
+    final WxMiniGetApiQuotaResult apiQuota = wxMaService.getWxMaOpenApiService().getApiQuota(cgiPath);
+    assertNotNull(apiQuota);
+    System.out.println(new Gson().toJson(apiQuota));
+  }
+  @Test
+  public void clearQuotaByAppSecret() throws WxErrorException {
+    final boolean result = wxMaService.getWxMaOpenApiService().clearQuotaByAppSecret();
+    assertTrue(result);
+  }
+
+}

+ 3 - 1
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java

@@ -209,9 +209,11 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
   @Override
   public String getPayBaseUrl() {
     if (this.getConfig().isUseSandboxEnv()) {
+      if (StringUtils.isNotBlank(this.getConfig().getApiV3Key())) {
+        throw new WxRuntimeException("微信支付V3 目前不支持沙箱模式!");
+      }
       return this.getConfig().getPayBaseUrl() + "/xdc/apiv2sandbox";
     }
-
     return this.getConfig().getPayBaseUrl();
   }