diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdDingtalkAppUserRaw.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdDingtalkAppUserRaw.java new file mode 100644 index 000000000..d9faf6137 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/ZzdDingtalkAppUserRaw.java @@ -0,0 +1,36 @@ +package com.bsd.pjcloud.thirdparty.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 浙政钉开放平台 {@code /rpc/oauth2/dingtalk_app_user.json} 返回的 data 段(文档字段名与线上实际 key 可能为驼峰或下划线)。 + *
+ * 由业务层从开放平台 JSON 解析填入,再统一映射为 {@link ZZDUserDTO}。 + */ +@Data +public class ZzdDingtalkAppUserRaw implements Serializable { + + private static final long serialVersionUID = 1L; + + private String lastName; + + private String nickNameCn; + + private String mobile; + + private Integer organizationId; + + private Long accountId; + + private String account; + + private String employeeCode; + + private String openid; + + private String realmId; + + private String organizationCode; +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyOAuth2Service.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyOAuth2Service.java index 8b8849c69..578f5e954 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyOAuth2Service.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPartyOAuth2Service.java @@ -455,7 +455,12 @@ public class ThirdPartyOAuth2Service { req.setAuth_code(authCode); OapiRpcOauth2DingtalkAppUserJsonResponse resp = postClient.post(req); zzdAssertSdkTopSuccess(resp, "dingtalk_app_user"); - JSONObject data = zzdSdkDingtalkAppUserContentData(resp.getContent()); + String rawContent = resp.getContent(); + log.info("浙政钉 dingtalk_app_user 原始响应 content={}", rawContent); + JSONObject bodyData = zzdSdkDingtalkAppUserContentData(rawContent); + log.info("浙政钉 dingtalk_app_user 解析后(展开嵌套前) data={}", bodyData != null ? bodyData.toJSONString() : "null"); + JSONObject data = zzdUnwrapAppUserPayload(bodyData); + log.info("浙政钉 dingtalk_app_user 展开嵌套后用户 JSON={}", data != null ? data.toJSONString() : "null"); ZZDUserDTO dto = mapOpenPlatformUserToZzdDto(data); if (StringUtils.hasText(zzdOpenPlatform.getTenantId()) && dto.getExt() != null && StringUtils.hasText(dto.getExt().getRealmId()) @@ -514,49 +519,148 @@ public class ThirdPartyOAuth2Service { return content; } + /** + * 部分网关/版本将用户信息放在 {@code user} 或 {@code result} 下,与文档顶层字段不一致时先展开。 + */ + private static JSONObject zzdUnwrapAppUserPayload(JSONObject root) { + if (root == null) { + return null; + } + String[] nestedKeys = {"user", "result", "data", "body"}; + for (String nk : nestedKeys) { + Object inner = root.get(nk); + if (inner instanceof JSONObject) { + JSONObject jo = (JSONObject) inner; + if (!jo.isEmpty()) { + return jo; + } + } + } + return root; + } + + private static String zzdPickString(JSONObject o, String... keys) { + if (o == null || keys == null) { + return null; + } + for (String k : keys) { + if (k == null || !o.containsKey(k)) { + continue; + } + Object val = o.get(k); + if (val == null) { + continue; + } + if (val instanceof String) { + String s = ((String) val).trim(); + if (StringUtils.hasText(s)) { + return s; + } + } else { + String s = val.toString().trim(); + if (StringUtils.hasText(s)) { + return s; + } + } + } + return null; + } + + private static Integer zzdPickIntObject(JSONObject o, String... keys) { + if (o == null) { + return null; + } + for (String k : keys) { + if (!o.containsKey(k)) { + continue; + } + Object v = o.get(k); + if (v instanceof Number) { + return ((Number) v).intValue(); + } + if (v != null) { + try { + return Integer.parseInt(v.toString().trim()); + } catch (NumberFormatException ignored) { + } + } + } + return null; + } + + private static Long zzdPickLongObject(JSONObject o, String... keys) { + if (o == null) { + return null; + } + for (String k : keys) { + if (!o.containsKey(k)) { + continue; + } + Object v = o.get(k); + if (v instanceof Number) { + return ((Number) v).longValue(); + } + if (v != null) { + try { + return Long.parseLong(v.toString().trim()); + } catch (NumberFormatException ignored) { + } + } + } + return null; + } + + /** + * 将开放平台 data JSON 解析为实体,兼容驼峰/下划线及常见别名,再映射为 {@link ZZDUserDTO}。 + */ + private static ZzdDingtalkAppUserRaw parseZzdDingtalkAppUserRaw(JSONObject data) { + ZzdDingtalkAppUserRaw raw = new ZzdDingtalkAppUserRaw(); + raw.setLastName(zzdPickString(data, "lastName", "last_name")); + raw.setNickNameCn(zzdPickString(data, "nickNameCn", "nick_name_cn", "nickName", "nick_name")); + raw.setMobile(zzdPickString(data, "mobile", "phone", "telephone")); + raw.setOrganizationId(zzdPickIntObject(data, "organizationId", "organization_id", "orgId")); + raw.setAccountId(zzdPickLongObject(data, "accountId", "account_id", "tenantUserId", "tenant_user_id")); + raw.setAccount(zzdPickString(data, "account", "loginId", "login_id", "loginid")); + String employeeCode = zzdPickString(data, "employeeCode", "employee_code", "empId", "emp_id", "jobNumber", "job_number", "staffId", "staff_id"); + if (!StringUtils.hasText(employeeCode)) { + employeeCode = raw.getAccount(); + } + raw.setEmployeeCode(employeeCode); + raw.setOpenid(zzdPickString(data, "openid", "openId", "open_id", "id")); + raw.setRealmId(zzdPickString(data, "realmId", "realm_id")); + raw.setOrganizationCode(zzdPickString(data, "organizationCode", "organization_code", "orgCode", "org_code")); + return raw; + } + private static ZZDUserDTO mapOpenPlatformUserToZzdDto(JSONObject data) { + ZzdDingtalkAppUserRaw raw = parseZzdDingtalkAppUserRaw(data); ZZDUserDTO dto = new ZZDUserDTO(); - String lastName = data.getString("lastName"); - String nickCn = data.getString("nickNameCn"); + String lastName = raw.getLastName(); + String nickCn = raw.getNickNameCn(); dto.setUsername(lastName != null ? lastName : nickCn); dto.setNickName(lastName != null ? lastName : nickCn); - dto.setMobile(data.getString("mobile")); + dto.setMobile(raw.getMobile()); dto.setRegionCode(null); - Object orgId = data.get("organizationId"); - if (orgId instanceof Number) { - dto.setOrganizationId(((Number) orgId).intValue()); + if (raw.getOrganizationId() != null) { + dto.setOrganizationId(raw.getOrganizationId()); } else { dto.setOrganizationId(0); } - Object accountId = data.get("accountId"); int userId = 0; - if (accountId instanceof Number) { - long lv = ((Number) accountId).longValue(); + if (raw.getAccountId() != null) { + long lv = raw.getAccountId(); if (lv <= Integer.MAX_VALUE && lv >= Integer.MIN_VALUE) { userId = (int) lv; } - } else if (accountId != null) { - try { - long lv = Long.parseLong(accountId.toString()); - if (lv <= Integer.MAX_VALUE && lv >= Integer.MIN_VALUE) { - userId = (int) lv; - } - } catch (NumberFormatException ignored) { - } } dto.setUserId(userId); ZZDUserDTO.Ext ext = dto.new Ext(); - String employeeCode = data.getString("employeeCode"); - if (!StringUtils.hasText(employeeCode)) { - employeeCode = data.getString("account"); - } - ext.setEmployeeCode(employeeCode); - ext.setOrganizationCode(data.getString("organizationCode")); + ext.setEmployeeCode(raw.getEmployeeCode()); + ext.setOrganizationCode(raw.getOrganizationCode()); ext.setNickNameCn(nickCn); - ext.setOpenid(data.getString("openid")); - Object realm = data.get("realmId"); - ext.setRealmId(realm == null ? null : realm.toString()); - ext.setMobile(data.getString("mobile")); + ext.setOpenid(raw.getOpenid()); + ext.setRealmId(raw.getRealmId()); + ext.setMobile(raw.getMobile()); dto.setExt(ext); return dto; }