{"record":{"id":"f0f3b645f74f6234","repo":"jeecgboot/JeecgBoot","slug":"500-f0f3b6","errorCode":"500","errorMessage":"请查看应用key和应用秘钥是否正确，组织ID是否匹配","messagePattern":"请查看应用key和应用秘钥是否正确，组织ID是否匹配","errorType":"exception","errorClass":"JeecgBootException","httpStatus":null,"severity":"error","filePath":"jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/ThirdAppDingtalkServiceImpl.java","lineNumber":1104,"sourceCode":"                actionCardMessage.setUserid_list(dtUserIds);\n                return JdtMessageAPI.sendActionCardMessage(actionCardMessage, accessToken);\n            }\n        }\n        return null;\n    }\n\n    /**\n     * OAuth2登录，成功返回登录的SysUser，失败返回null\n     */\n    public SysUser oauth2Login(String authCode,Integer tenantId) {\n        this.tenantIzExist(tenantId);\n        // 代码逻辑说明: [QQYUN-3440]新建企业微信和钉钉配置表，通过租户模式隔离------------\n        SysThirdAppConfig dtConfig = configMapper.getThirdConfigByThirdType(tenantId, MessageTypeEnum.DD.getType());\n        // 1. 根据免登授权码获取用户 AccessToken\n        String userAccessToken = JdtOauth2API.getUserAccessToken(dtConfig.getClientId(), dtConfig.getClientSecret(), authCode);\n        if (userAccessToken == null) {\n            log.error(\"oauth2Login userAccessToken is null\");\n            throw new JeecgBootException(\"请查看应用key和应用秘钥是否正确，组织ID是否匹配\");\n        }\n        // 2. 根据用户 AccessToken 获取当前用户的基本信息（不包括userId）\n        ContactUser contactUser = JdtOauth2API.getContactUsers(\"me\", userAccessToken);\n        if (contactUser == null) {\n            log.error(\"oauth2Login contactUser is null\");\n            throw new JeecgBootException(\"获取钉钉用户信息失败\");\n        }\n        String unionId = contactUser.getUnionId();\n        // 3. 根据获取到的 unionId 换取用户 userId\n        String accessToken = this.getTenantAccessToken(dtConfig);\n        if (accessToken == null) {\n            log.error(\"oauth2Login accessToken is null\");\n            throw new JeecgBootException(\"请查看应用key和应用秘钥是否正确，组织ID是否匹配\");\n        }\n        Response<String> getUserIdRes = JdtUserAPI.getUseridByUnionid(unionId, accessToken);\n        if (!getUserIdRes.isSuccess()) {\n            log.error(\"oauth2Login getUseridByUnionid failed: \" + JSON.toJSONString(getUserIdRes));\n            throw new JeecgBootException(\"获取钉钉用户信息失败\");","sourceCodeStart":1086,"sourceCodeEnd":1122,"githubUrl":"https://github.com/jeecgboot/JeecgBoot/blob/96fb33f5ec68516da0b0147da06b2eb0419e063a/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/ThirdAppDingtalkServiceImpl.java#L1086-L1122","documentation":"Thrown by ThirdAppDingtalkServiceImpl.oauth2Login when JdtOauth2API.getUserAccessToken(clientId, clientSecret, authCode) returns null. The clientId/clientSecret come from SysThirdAppConfig (queried per tenant by configMapper.getThirdConfigByThirdType), and the authCode is the DingTalk免登授权码. A null token means the OAuth2 token exchange at DingTalk failed — almost always a credential or authCode problem.","triggerScenarios":"Wrong app key/secret for the corp in SysThirdAppConfig; authCode already used (DingTalk authCodes are single-use) or expired; org ID in the config doesn't match the corp that issued the authCode; app disabled in DingTalk admin console.","commonSituations":"Copied config from another environment without updating key/secret; clock skew causing authCode rejection; the user switched corps but the config still points at the old one; tenantId resolves to a config row with stale credentials.","solutions":["Verify the clientId/clientSecret in sys_third_app_config for the given tenantId and thirdType='dd' against the DingTalk open-platform app page.","Ensure the authCode is fresh and single-use — do not cache/replay it; initiate the OAuth flow again.","Confirm the corp/org ID in the config matches the corp that issued the authCode (cross-corp authCodes fail).","Check the DingTalk app is published/enabled and has the required scopes (contact, auth).","Enable DingTalk SDK debug logging to see the underlying error response from the token endpoint."],"exampleFix":"// before: token-exchange failure surfaces as a vague message\nString userAccessToken = JdtOauth2API.getUserAccessToken(dtConfig.getClientId(), dtConfig.getClientSecret(), authCode);\nif (userAccessToken == null) {\n    log.error(\"oauth2Login userAccessToken is null\");\n    throw new JeecgBootException(\"请查看应用key和应用秘钥是否正确，组织ID是否匹配\");\n}\n// after: validate config presence before the call and log the SDK error\nif (dtConfig == null || oConvertUtils.isEmpty(dtConfig.getClientId()) || oConvertUtils.isEmpty(dtConfig.getClientSecret())) {\n    throw new JeecgBootException(\"钉钉应用配置缺失，请检查租户 \" + tenantId + \" 的 sys_third_app_config\");\n}","handlingStrategy":"validation","validationCode":"// Validate the DingTalk config exists for this tenant before OAuth\nSysThirdAppConfig cfg = configMapper.getThirdConfigByThirdType(tenantId, MessageTypeEnum.DD.getType());\nif (cfg == null || oConvertUtils.isEmpty(cfg.getClientId()) || oConvertUtils.isEmpty(cfg.getClientSecret())) {\n    throw new JeecgBootException(\"钉钉应用配置缺失 (tenantId=\" + tenantId + \")\");\n}\n// also ensure authCode is fresh and single-use before passing it in","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep clientId/clientSecret in sys_third_app_config aligned with the DingTalk app page per tenant.","Never reuse an authCode — it is single-use; initiate the OAuth flow fresh each login.","Verify the org ID matches the corp issuing the authCode."],"tags":["dingtalk","oauth2","third-party-login","configuration","jeecg-boot"],"backgroundTag":null,"analyzedSha":"96fb33f5ec68516da0b0147da06b2eb0419e063a","analyzedAt":"2026-08-14T00:04:16.786Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}