jeecgboot/JeecgBoot · error · JeecgBootException

500

500

Error message

请查看应用key和应用秘钥是否正确,组织ID是否匹配

What it means

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.

Source

Thrown at jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/ThirdAppDingtalkServiceImpl.java:1104

                actionCardMessage.setUserid_list(dtUserIds);
                return JdtMessageAPI.sendActionCardMessage(actionCardMessage, accessToken);
            }
        }
        return null;
    }

    /**
     * OAuth2登录,成功返回登录的SysUser,失败返回null
     */
    public SysUser oauth2Login(String authCode,Integer tenantId) {
        this.tenantIzExist(tenantId);
        // 代码逻辑说明: [QQYUN-3440]新建企业微信和钉钉配置表,通过租户模式隔离------------
        SysThirdAppConfig dtConfig = configMapper.getThirdConfigByThirdType(tenantId, MessageTypeEnum.DD.getType());
        // 1. 根据免登授权码获取用户 AccessToken
        String userAccessToken = JdtOauth2API.getUserAccessToken(dtConfig.getClientId(), dtConfig.getClientSecret(), authCode);
        if (userAccessToken == null) {
            log.error("oauth2Login userAccessToken is null");
            throw new JeecgBootException("请查看应用key和应用秘钥是否正确,组织ID是否匹配");
        }
        // 2. 根据用户 AccessToken 获取当前用户的基本信息(不包括userId)
        ContactUser contactUser = JdtOauth2API.getContactUsers("me", userAccessToken);
        if (contactUser == null) {
            log.error("oauth2Login contactUser is null");
            throw new JeecgBootException("获取钉钉用户信息失败");
        }
        String unionId = contactUser.getUnionId();
        // 3. 根据获取到的 unionId 换取用户 userId
        String accessToken = this.getTenantAccessToken(dtConfig);
        if (accessToken == null) {
            log.error("oauth2Login accessToken is null");
            throw new JeecgBootException("请查看应用key和应用秘钥是否正确,组织ID是否匹配");
        }
        Response<String> getUserIdRes = JdtUserAPI.getUseridByUnionid(unionId, accessToken);
        if (!getUserIdRes.isSuccess()) {
            log.error("oauth2Login getUseridByUnionid failed: " + JSON.toJSONString(getUserIdRes));
            throw new JeecgBootException("获取钉钉用户信息失败");

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. Verify the clientId/clientSecret in sys_third_app_config for the given tenantId and thirdType='dd' against the DingTalk open-platform app page.
  2. Ensure the authCode is fresh and single-use — do not cache/replay it; initiate the OAuth flow again.
  3. Confirm the corp/org ID in the config matches the corp that issued the authCode (cross-corp authCodes fail).
  4. Check the DingTalk app is published/enabled and has the required scopes (contact, auth).
  5. Enable DingTalk SDK debug logging to see the underlying error response from the token endpoint.

Example fix

// before: token-exchange failure surfaces as a vague message
String userAccessToken = JdtOauth2API.getUserAccessToken(dtConfig.getClientId(), dtConfig.getClientSecret(), authCode);
if (userAccessToken == null) {
    log.error("oauth2Login userAccessToken is null");
    throw new JeecgBootException("请查看应用key和应用秘钥是否正确,组织ID是否匹配");
}
// after: validate config presence before the call and log the SDK error
if (dtConfig == null || oConvertUtils.isEmpty(dtConfig.getClientId()) || oConvertUtils.isEmpty(dtConfig.getClientSecret())) {
    throw new JeecgBootException("钉钉应用配置缺失,请检查租户 " + tenantId + " 的 sys_third_app_config");
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the DingTalk config exists for this tenant before OAuth
SysThirdAppConfig cfg = configMapper.getThirdConfigByThirdType(tenantId, MessageTypeEnum.DD.getType());
if (cfg == null || oConvertUtils.isEmpty(cfg.getClientId()) || oConvertUtils.isEmpty(cfg.getClientSecret())) {
    throw new JeecgBootException("钉钉应用配置缺失 (tenantId=" + tenantId + ")");
}
// also ensure authCode is fresh and single-use before passing it in

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14). Data as JSON: /api/errors/f0f3b645f74f6234. Report an issue: GitHub.