{"record":{"id":"90bd010c8f3b263a","repo":"binarywang/WxJava","slug":"appid","errorCode":null,"errorMessage":"请确保微信视频号配置 appId 的唯一性","messagePattern":"请确保微信视频号配置 appId 的唯一性","errorType":"validation","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java","lineNumber":51,"sourceCode":"    Map<String, WxChannelSingleProperties> appsMap = wxChannelMultiProperties.getApps();\n    if (appsMap == null || appsMap.isEmpty()) {\n      log.warn(\"微信视频号应用参数未配置，通过 WxChannelMultiServices#getWxChannelService(\\\"tenantId\\\")获取实例将返回空\");\n      return new WxChannelMultiServicesImpl();\n    }\n    /**\n     * 校验 appId 是否唯一，避免使用 redis 缓存 token、ticket 时错乱。\n     *\n     * 查看 {@link me.chanjar.weixin.channel.config.impl.WxChannelRedisConfigImpl#setAppid(String)}\n     */\n    Collection<WxChannelSingleProperties> apps = appsMap.values();\n    if (apps.size() > 1) {\n      // 校验 appId 是否唯一\n      boolean multi = apps.stream()\n        // 没有 appId，如果不判断是否为空，这里会报 NPE 异常\n        .collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting()))\n        .entrySet().stream().anyMatch(e -> e.getValue() > 1);\n      if (multi) {\n        throw new RuntimeException(\"请确保微信视频号配置 appId 的唯一性\");\n      }\n    }\n    WxChannelMultiServicesImpl services = new WxChannelMultiServicesImpl();\n\n    Set<Map.Entry<String, WxChannelSingleProperties>> entries = appsMap.entrySet();\n    for (Map.Entry<String, WxChannelSingleProperties> entry : entries) {\n      String tenantId = entry.getKey();\n      WxChannelSingleProperties wxChannelSingleProperties = entry.getValue();\n      WxChannelDefaultConfigImpl storage = this.wxChannelConfigStorage(wxChannelMultiProperties);\n      this.configApp(storage, wxChannelSingleProperties);\n      this.configHttp(storage, wxChannelMultiProperties.getConfigStorage());\n      WxChannelService wxChannelService = this.wxChannelService(storage, wxChannelMultiProperties);\n      services.addWxChannelService(tenantId, wxChannelService);\n    }\n    return services;\n  }\n\n  /**","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/binarywang/WxJava/blob/1c43293a3c2c9d7e91304b6d037fb017f680d0c6/solon-plugins/wx-java-channel-multi-solon-plugin/src/main/java/com/binarywang/solon/wxjava/channel/configuration/services/AbstractWxChannelConfiguration.java#L33-L69","documentation":"Thrown by the Solon multi-account Channel (视频号) auto-configuration when two or more tenant entries resolve to the same appId. Uniqueness is enforced because a shared Redis token/ticket cache is keyed by appId, so duplicate appIds would cause tokens to overwrite each other and cross-tenant leakage. It is a plain RuntimeException, so it fails application startup / bean wiring.","triggerScenarios":"Configuring wx-java-channel-multi-solon-plugin with a `apps` map whose values contain two entries with the same `appId`. Also triggered when two entries both leave `appId` null, because null is collapsed to the literal key `0` and thus also counts as a duplicate.","commonSituations":"Copying a tenant block in YAML and forgetting to change appId; multi-tenant setups sharing one appId across environments; copy-paste of a config snippet where appId was templated out.","solutions":["Inspect every entry under `wxjava.channel.apps.*` and make each `appId` distinct.","Ensure no two tenants share the same real appId; if you genuinely need the same appId twice, you must run separate application instances or separate Redis namespaces.","Remove or fill in any entry with a blank/null appId.","Re-run startup; the exception is gone once the groupingBy check finds no count > 1."],"exampleFix":"// before\nwxjava:\n  channel:\n    apps:\n      tenant-a:\n        appId: wx123\n      tenant-b:\n        appId: wx123   # duplicate -> throws\n// after\nwxjava:\n  channel:\n    apps:\n      tenant-a:\n        appId: wx123\n      tenant-b:\n        appId: wx456   # distinct","handlingStrategy":"validation","validationCode":"// Run during startup, before the multi-service bean is built\nCollection<WxChannelSingleProperties> apps = appsMap.values();\nMap<Object, Long> counts = apps.stream()\n    .collect(Collectors.groupingBy(\n        c -> c.getAppId() == null ? \"<null>\" : c.getAppId(),\n        Collectors.counting()));\nList<Object> dupes = counts.entrySet().stream()\n    .filter(e -> e.getValue() > 1)\n    .map(Map.Entry::getKey)\n    .collect(Collectors.toList());\nif (!dupes.isEmpty()) {\n    throw new IllegalStateException(\"Duplicate channel appIds: \" + dupes);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Centralize tenant config in a single YAML source and lint appId uniqueness in CI.","Never template two tenant blocks from the same appId literal.","Treat a blank appId as a build failure, not a default."],"tags":["config","multi-tenant","channel","solon","startup"],"backgroundTag":null,"analyzedSha":"1c43293a3c2c9d7e91304b6d037fb017f680d0c6","analyzedAt":"2026-08-14T02:29:11.060Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}