|
| 1 | +# 微信开放平台小程序审核额度管理最佳实践 |
| 2 | + |
| 3 | +## 问题背景 |
| 4 | + |
| 5 | +在使用微信开放平台第三方服务为多个授权小程序提交审核时,需要注意以下重要限制: |
| 6 | + |
| 7 | +### 审核额度限制 |
| 8 | + |
| 9 | +- **默认额度**: 每个第三方平台账号每月默认有 **20 个** 审核额度 |
| 10 | +- **消耗规则**: 每次调用 `submitAudit()` 提交一个小程序审核,会消耗 **1 个** 审核额度 |
| 11 | +- **重置周期**: 额度每月初自动重置 |
| 12 | +- **不可返还**: 审核撤回(undoCodeAudit)不会返还已消耗的额度 |
| 13 | +- **增加额度**: 如需更多额度,需要联系微信开放平台客服申请 |
| 14 | + |
| 15 | +### 常见问题 |
| 16 | + |
| 17 | +**问题**: 开放平台是每个 appId 都要这样提交审核吗?额度不够吧? |
| 18 | + |
| 19 | +**回答**: 是的,每个授权的小程序(appId)都需要单独调用 `submitAudit()` 提交审核,每次提交会消耗 1 个审核额度。默认的 20 个额度对于管理大量小程序的第三方平台来说可能不够用,建议: |
| 20 | +1. 提交审核前先查询剩余额度 |
| 21 | +2. 合理规划审核计划,避免重复提交审核 |
| 22 | +3. 联系微信开放平台申请增加额度 |
| 23 | + |
| 24 | +## API 使用说明 |
| 25 | + |
| 26 | +### 1. 查询审核额度 |
| 27 | + |
| 28 | +```java |
| 29 | +// 查询当前审核额度 |
| 30 | +WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota(); |
| 31 | + |
| 32 | +System.out.println("当月剩余提交审核次数: " + quota.getRest()); // 剩余额度 |
| 33 | +System.out.println("当月提交审核额度上限: " + quota.getLimit()); // 总额度 |
| 34 | +System.out.println("剩余加急次数: " + quota.getSpeedupRest()); // 剩余加急次数 |
| 35 | +System.out.println("加急额度上限: " + quota.getSpeedupLimit()); // 加急额度上限 |
| 36 | +``` |
| 37 | + |
| 38 | +**返回字段说明**: |
| 39 | +- `rest`: 当月剩余提交审核次数 |
| 40 | +- `limit`: 当月提交审核额度上限(默认 20) |
| 41 | +- `speedupRest`: 剩余加急次数 |
| 42 | +- `speedupLimit`: 加急额度上限 |
| 43 | + |
| 44 | +### 2. 提交审核 |
| 45 | + |
| 46 | +```java |
| 47 | +// 构建审核项 |
| 48 | +WxMaCodeSubmitAuditItem item = new WxMaCodeSubmitAuditItem(); |
| 49 | +item.setAddress("index"); // 页面路径 |
| 50 | +item.setTag("工具"); // 标签 |
| 51 | +item.setFirstClass("工具"); // 一级类目 |
| 52 | +item.setSecondClass("效率"); // 二级类目 |
| 53 | +item.setTitle("首页"); // 页面标题 |
| 54 | + |
| 55 | +// 构建提交审核消息 |
| 56 | +WxOpenMaSubmitAuditMessage message = new WxOpenMaSubmitAuditMessage(); |
| 57 | +message.setItemList(Collections.singletonList(item)); |
| 58 | +message.setVersionDesc("版本描述"); |
| 59 | + |
| 60 | +// 提交审核 |
| 61 | +WxOpenMaSubmitAuditResult result = wxOpenMaService.submitAudit(message); |
| 62 | +System.out.println("审核ID: " + result.getAuditId()); |
| 63 | +``` |
| 64 | + |
| 65 | +## 最佳实践 |
| 66 | + |
| 67 | +### 方案一:单个小程序提交前检查额度 |
| 68 | + |
| 69 | +这是最基本的做法,适用于偶尔提交审核的场景。 |
| 70 | + |
| 71 | +```java |
| 72 | +import me.chanjar.weixin.open.api.WxOpenMaService; |
| 73 | +import me.chanjar.weixin.open.bean.message.WxOpenMaSubmitAuditMessage; |
| 74 | +import me.chanjar.weixin.open.bean.result.WxOpenMaQueryQuotaResult; |
| 75 | +import me.chanjar.weixin.open.bean.result.WxOpenMaSubmitAuditResult; |
| 76 | +import me.chanjar.weixin.common.error.WxErrorException; |
| 77 | + |
| 78 | +public class AuditSubmitter { |
| 79 | + |
| 80 | + /** |
| 81 | + * 提交审核前检查额度 |
| 82 | + */ |
| 83 | + public WxOpenMaSubmitAuditResult submitWithQuotaCheck( |
| 84 | + WxOpenMaService wxOpenMaService, |
| 85 | + WxOpenMaSubmitAuditMessage message) throws WxErrorException { |
| 86 | + |
| 87 | + // 1. 检查审核额度 |
| 88 | + WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota(); |
| 89 | + System.out.println("当前剩余审核额度: " + quota.getRest()); |
| 90 | + |
| 91 | + if (quota.getRest() <= 0) { |
| 92 | + throw new RuntimeException("审核额度不足,无法提交审核。剩余额度: " + quota.getRest()); |
| 93 | + } |
| 94 | + |
| 95 | + // 2. 提交审核 |
| 96 | + WxOpenMaSubmitAuditResult result = wxOpenMaService.submitAudit(message); |
| 97 | + System.out.println("提交审核成功,审核ID: " + result.getAuditId()); |
| 98 | + |
| 99 | + // 3. 再次查询额度(可选) |
| 100 | + quota = wxOpenMaService.queryQuota(); |
| 101 | + System.out.println("提交后剩余审核额度: " + quota.getRest()); |
| 102 | + |
| 103 | + return result; |
| 104 | + } |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +### 方案二:批量提交审核的额度管理 |
| 109 | + |
| 110 | +适用于需要同时为多个小程序提交审核的场景。 |
| 111 | + |
| 112 | +```java |
| 113 | +import me.chanjar.weixin.open.api.WxOpenComponentService; |
| 114 | +import me.chanjar.weixin.open.api.WxOpenMaService; |
| 115 | +import me.chanjar.weixin.open.bean.message.WxOpenMaSubmitAuditMessage; |
| 116 | +import me.chanjar.weixin.open.bean.result.WxOpenMaQueryQuotaResult; |
| 117 | +import me.chanjar.weixin.open.bean.result.WxOpenMaSubmitAuditResult; |
| 118 | +import me.chanjar.weixin.common.error.WxErrorException; |
| 119 | + |
| 120 | +import java.util.ArrayList; |
| 121 | +import java.util.List; |
| 122 | +import java.util.Map; |
| 123 | + |
| 124 | +public class BatchAuditSubmitter { |
| 125 | + |
| 126 | + /** |
| 127 | + * 批量提交审核结果 |
| 128 | + */ |
| 129 | + public static class BatchSubmitResult { |
| 130 | + private int successCount; // 成功数量 |
| 131 | + private int failCount; // 失败数量 |
| 132 | + private int skipCount; // 跳过数量(额度不足) |
| 133 | + private List<String> failedAppIds; // 失败的 appId |
| 134 | + |
| 135 | + // getters and setters... |
| 136 | + } |
| 137 | + |
| 138 | + /** |
| 139 | + * 批量提交审核,带额度检查 |
| 140 | + */ |
| 141 | + public BatchSubmitResult batchSubmitWithQuotaCheck( |
| 142 | + WxOpenComponentService wxOpenComponentService, |
| 143 | + Map<String, WxOpenMaSubmitAuditMessage> appIdToMessageMap) { |
| 144 | + |
| 145 | + BatchSubmitResult result = new BatchSubmitResult(); |
| 146 | + result.setFailedAppIds(new ArrayList<>()); |
| 147 | + |
| 148 | + // 基本参数校验:避免空指针和空集合导致的 NoSuchElementException |
| 149 | + if (appIdToMessageMap == null || appIdToMessageMap.isEmpty()) { |
| 150 | + System.err.println("错误:待提交的小程序列表为空,未执行任何审核提交"); |
| 151 | + return result; |
| 152 | + } |
| 153 | + |
| 154 | + try { |
| 155 | + // 1. 检查总体额度是否充足 |
| 156 | + // 使用任意一个已授权的小程序 appId 获取 WxMaService 来查询审核额度。 |
| 157 | + // 注意:审核额度是以第三方平台维度统计的,因此这里选择任意一个 appId 即可。 |
| 158 | + WxOpenMaQueryQuotaResult quota = wxOpenComponentService |
| 159 | + .getWxMaServiceByAppid(appIdToMessageMap.keySet().iterator().next()) |
| 160 | + .queryQuota(); |
| 161 | + |
| 162 | + System.out.println("=== 批量提交审核开始 ==="); |
| 163 | + System.out.println("待提交数量: " + appIdToMessageMap.size()); |
| 164 | + System.out.println("当前剩余审核额度: " + quota.getRest()); |
| 165 | + |
| 166 | + if (quota.getRest() < appIdToMessageMap.size()) { |
| 167 | + System.err.println("警告:审核额度不足!"); |
| 168 | + System.err.println(" 需要提交: " + appIdToMessageMap.size() + " 个"); |
| 169 | + System.err.println(" 剩余额度: " + quota.getRest()); |
| 170 | + System.err.println(" 缺少额度: " + (appIdToMessageMap.size() - quota.getRest())); |
| 171 | + System.err.println("将仅提交前 " + quota.getRest() + " 个小程序"); |
| 172 | + } |
| 173 | + |
| 174 | + // 2. 依次提交审核 |
| 175 | + int count = 0; |
| 176 | + for (Map.Entry<String, WxOpenMaSubmitAuditMessage> entry : appIdToMessageMap.entrySet()) { |
| 177 | + String appId = entry.getKey(); |
| 178 | + WxOpenMaSubmitAuditMessage message = entry.getValue(); |
| 179 | + |
| 180 | + // 检查是否还有额度 |
| 181 | + if (count >= quota.getRest()) { |
| 182 | + System.out.println("AppId: " + appId + " 跳过(额度不足)"); |
| 183 | + result.setSkipCount(result.getSkipCount() + 1); |
| 184 | + continue; |
| 185 | + } |
| 186 | + |
| 187 | + try { |
| 188 | + WxOpenMaService maService = wxOpenComponentService.getWxMaServiceByAppid(appId); |
| 189 | + WxOpenMaSubmitAuditResult submitResult = maService.submitAudit(message); |
| 190 | + |
| 191 | + System.out.println("AppId: " + appId + " 提交成功,审核ID: " + submitResult.getAuditId()); |
| 192 | + result.setSuccessCount(result.getSuccessCount() + 1); |
| 193 | + count++; |
| 194 | + |
| 195 | + } catch (WxErrorException e) { |
| 196 | + System.err.println("AppId: " + appId + " 提交失败: " + e.getMessage()); |
| 197 | + result.setFailCount(result.getFailCount() + 1); |
| 198 | + result.getFailedAppIds().add(appId); |
| 199 | + count++; |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + // 3. 输出统计信息 |
| 204 | + System.out.println("=== 批量提交审核完成 ==="); |
| 205 | + System.out.println("成功: " + result.getSuccessCount()); |
| 206 | + System.out.println("失败: " + result.getFailCount()); |
| 207 | + System.out.println("跳过: " + result.getSkipCount()); |
| 208 | + |
| 209 | + // 4. 查询剩余额度 |
| 210 | + quota = wxOpenComponentService |
| 211 | + .getWxMaServiceByAppid(appIdToMessageMap.keySet().iterator().next()) |
| 212 | + .queryQuota(); |
| 213 | + System.out.println("剩余额度: " + quota.getRest()); |
| 214 | + |
| 215 | + } catch (WxErrorException e) { |
| 216 | + System.err.println("批量提交审核失败: " + e.getMessage()); |
| 217 | + e.printStackTrace(); |
| 218 | + } |
| 219 | + |
| 220 | + return result; |
| 221 | + } |
| 222 | +} |
| 223 | +``` |
| 224 | + |
| 225 | +### 方案三:审核额度监控和告警 |
| 226 | + |
| 227 | +建议实现一个审核额度监控机制,及时发现额度不足的情况。 |
| 228 | + |
| 229 | +```java |
| 230 | +import me.chanjar.weixin.open.api.WxOpenMaService; |
| 231 | +import me.chanjar.weixin.open.bean.result.WxOpenMaQueryQuotaResult; |
| 232 | +import me.chanjar.weixin.common.error.WxErrorException; |
| 233 | + |
| 234 | +public class QuotaMonitor { |
| 235 | + |
| 236 | + /** |
| 237 | + * 检查审核额度并发出告警 |
| 238 | + */ |
| 239 | + public void checkAndAlert(WxOpenMaService wxOpenMaService) { |
| 240 | + try { |
| 241 | + WxOpenMaQueryQuotaResult quota = wxOpenMaService.queryQuota(); |
| 242 | + |
| 243 | + int rest = quota.getRest(); |
| 244 | + int limit = quota.getLimit(); |
| 245 | + double percentage = (double) rest / limit * 100; |
| 246 | + |
| 247 | + System.out.println("审核额度状态:"); |
| 248 | + System.out.println(" 剩余: " + rest + " / " + limit); |
| 249 | + System.out.println(" 使用率: " + String.format("%.1f", 100 - percentage) + "%"); |
| 250 | + |
| 251 | + // 根据剩余额度发出不同级别的告警 |
| 252 | + if (rest <= 0) { |
| 253 | + sendCriticalAlert("审核额度已用尽!无法提交新的审核。"); |
| 254 | + } else if (rest <= 3) { |
| 255 | + sendWarningAlert("审核额度严重不足!剩余额度: " + rest); |
| 256 | + } else if (percentage < 30) { |
| 257 | + sendInfoAlert("审核额度偏低,剩余: " + rest + " (" + String.format("%.1f", percentage) + "%)"); |
| 258 | + } |
| 259 | + |
| 260 | + } catch (WxErrorException e) { |
| 261 | + System.err.println("查询审核额度失败: " + e.getMessage()); |
| 262 | + } |
| 263 | + } |
| 264 | + |
| 265 | + private void sendCriticalAlert(String message) { |
| 266 | + // 发送紧急告警(如:发送邮件、短信、钉钉消息等) |
| 267 | + System.err.println("[严重] " + message); |
| 268 | + } |
| 269 | + |
| 270 | + private void sendWarningAlert(String message) { |
| 271 | + // 发送警告(如:发送邮件、企业微信消息等) |
| 272 | + System.out.println("[警告] " + message); |
| 273 | + } |
| 274 | + |
| 275 | + private void sendInfoAlert(String message) { |
| 276 | + // 发送普通提示 |
| 277 | + System.out.println("[提示] " + message); |
| 278 | + } |
| 279 | +} |
| 280 | +``` |
| 281 | + |
| 282 | +## 常见问题 FAQ |
| 283 | + |
| 284 | +### Q1: 审核额度什么时候重置? |
| 285 | +A: 审核额度在每月初自动重置为默认值(通常是 20 个)。 |
| 286 | + |
| 287 | +### Q2: 审核撤回会返还额度吗? |
| 288 | +A: 不会。调用 `undoCodeAudit()` 撤回审核不会返还已消耗的额度。 |
| 289 | + |
| 290 | +### Q3: 如何增加审核额度? |
| 291 | +A: 需要联系微信开放平台客服申请增加额度。具体联系方式请参考微信开放平台官方文档。 |
| 292 | + |
| 293 | +### Q4: 审核失败会消耗额度吗? |
| 294 | +A: 会。只要调用了 `submitAudit()` 接口提交审核,无论审核是否通过,都会消耗 1 个额度。 |
| 295 | + |
| 296 | +### Q5: 加急审核会额外消耗额度吗? |
| 297 | +A: 加急审核(`speedAudit()`)使用的是单独的加急额度(`speedupRest`),不会消耗普通审核额度。但加急审核的前提是已经提交了审核,所以提交审核时仍会消耗 1 个普通审核额度。 |
| 298 | + |
| 299 | +### Q6: 多个小程序共享审核额度吗? |
| 300 | +A: 是的。同一个第三方平台账号下,所有授权的小程序共享审核额度。每提交一个小程序审核,都会消耗该第三方平台的 1 个审核额度。 |
| 301 | + |
| 302 | +### Q7: 如何避免审核额度不足? |
| 303 | +A: 建议采取以下措施: |
| 304 | +- 在批量提交审核前,先调用 `queryQuota()` 检查剩余额度 |
| 305 | +- 实现审核额度监控和告警机制 |
| 306 | +- 合理规划审核计划,避免不必要的重复提交审核 |
| 307 | +- 提高代码质量,减少审核不通过的情况 |
| 308 | +- 联系微信开放平台申请增加额度 |
| 309 | + |
| 310 | +### Q8: 能否查询历史审核额度使用情况? |
| 311 | +A: 微信开放平台 API 目前只提供当前剩余额度查询,不提供历史使用记录。如需统计历史使用情况,需要自行记录每次调用 `submitAudit()` 的时间和次数。 |
| 312 | + |
| 313 | +## 相关文档 |
| 314 | + |
| 315 | +- [微信开放平台官方文档 - 查询额度](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/query_quota.html) |
| 316 | +- [微信开放平台官方文档 - 提交审核](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/submit_audit.html) |
| 317 | +- [微信开放平台官方文档 - 加急审核](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/speedup_audit.html) |
| 318 | + |
| 319 | +## 技术支持 |
| 320 | + |
| 321 | +如有问题,请提交 Issue 到 [WxJava GitHub 仓库](https://github.com/binarywang/WxJava/issues)。 |
0 commit comments