浙政钉
This commit is contained in:
1 parent
4e5afdc272
commit
61460ce32f
6 files changed
+259
-8
No files matched your search
+7
@@ -5,5 +5,12 @@ import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UrlTokenDTO {
|
||||
/**
|
||||
* 旧数字政务/第三方网关下发的 uabaseToken(逐步淘汰)
|
||||
*/
|
||||
private String token;
|
||||
/**
|
||||
* 浙政钉 H5 官方 dd.getAuthCode 返回的临时码
|
||||
*/
|
||||
private String authCode;
|
||||
}
|
||||
+5
@@ -38,5 +38,10 @@ public class OAuth2ClientConfig {
|
||||
*/
|
||||
private String zzdShUserInfoUrl;
|
||||
|
||||
/**
|
||||
* 浙政钉官方 H5 authCode 换取用户信息(GET ?authCode=),响应体需与 {@code ApiResponse<ZZDUserDTO>} 一致
|
||||
*/
|
||||
private String zzdAuthCodeUserInfoUrl;
|
||||
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.bsd.pjcloud.thirdparty.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* 浙政钉开放平台(正式:openplatform-pro.ding.zj.gov.cn)企业内应用免登。
|
||||
* app-key / app-secret 请放在 Nacos 或环境变量,勿提交仓库。
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "zzd.open")
|
||||
public class ZzdOpenPlatformProperties {
|
||||
|
||||
/**
|
||||
* 为 false 时不走开放平台直连,仍使用 oauth2.client.zzd-auth-code-user-info-url 等旧网关配置
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
private String protocol = "https";
|
||||
|
||||
private String domain = "openplatform-pro.ding.zj.gov.cn";
|
||||
|
||||
/**
|
||||
* 租户 ID(与管理工作台源码中 realmId、前端 corpId 一致,用于核对日志)
|
||||
*/
|
||||
private String tenantId = "196729";
|
||||
|
||||
private String appKey = "";
|
||||
|
||||
private String appSecret = "";
|
||||
|
||||
public boolean isAppCredentialConfigured() {
|
||||
return StringUtils.hasText(appKey) && StringUtils.hasText(appSecret);
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -91,9 +91,10 @@ public class ThirdPartyOAth2Cotroller {
|
||||
@GlobalTransactional
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R zzdsso(@RequestBody UrlTokenDTO urlTokenDTO) {
|
||||
String authCode = urlTokenDTO.getAuthCode();
|
||||
String token = urlTokenDTO.getToken();
|
||||
if (StringUtils.isBlank(token)){
|
||||
return R.failed("token不能为空");
|
||||
if (StringUtils.isBlank(authCode) && StringUtils.isBlank(token)) {
|
||||
return R.failed("token与authCode不能均为空");
|
||||
}
|
||||
|
||||
//获取数字政务token通过浙政钉前端获取的token,调试阶段先写死
|
||||
@@ -123,7 +124,9 @@ public class ThirdPartyOAth2Cotroller {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
//暂时先注释,模拟一下就行
|
||||
ZZDUserDTO zzdUserDTO = thirdPartyOAuth2Service.zzdsso(token);
|
||||
ZZDUserDTO zzdUserDTO = StringUtils.isNotBlank(authCode)
|
||||
? thirdPartyOAuth2Service.zzdssoByAuthCode(authCode)
|
||||
: thirdPartyOAuth2Service.zzdsso(token);
|
||||
ZZDUserDTO.Ext ext = zzdUserDTO.getExt();
|
||||
String employeeCode = ext.getEmployeeCode();
|
||||
String mobile = zzdUserDTO.getMobile();
|
||||
|
||||
+191
-5
@@ -3,7 +3,9 @@ package com.bsd.pjcloud.thirdparty.service;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.bsd.pjcloud.admin.api.entity.SysUser;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.bsd.pjcloud.thirdparty.config.OAuth2ClientConfig;
|
||||
import com.bsd.pjcloud.thirdparty.config.ZzdOpenPlatformProperties;
|
||||
import com.bsd.pjcloud.thirdparty.constant.UserTypeEnum;
|
||||
import com.bsd.pjcloud.thirdparty.dto.*;
|
||||
import com.bsd.pjcloud.thirdparty.entity.ThirdPartyDeptEntity;
|
||||
@@ -27,6 +29,7 @@ import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.ResourceAccessException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -40,6 +43,11 @@ public class ThirdPartyOAuth2Service {
|
||||
|
||||
private static final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
private final Object zzdOpenTokenLock = new Object();
|
||||
|
||||
private volatile String zzdOpenCachedAppAccessToken;
|
||||
private volatile long zzdOpenCachedAppAccessTokenExpireMs;
|
||||
|
||||
@Autowired
|
||||
private OAuth2ClientConfig config;
|
||||
private final String baseUrl = "http://192.168.93.121:8880";
|
||||
@@ -55,6 +63,8 @@ public class ThirdPartyOAuth2Service {
|
||||
|
||||
private final ThirdPartyLogService thirdPartyLogService;
|
||||
|
||||
private final ZzdOpenPlatformProperties zzdOpenPlatform;
|
||||
|
||||
|
||||
public TokenDTO getAccessToken() {
|
||||
TokenDTO cachedToken = tokenCache.get("access_token");
|
||||
@@ -368,11 +378,173 @@ public class ThirdPartyOAuth2Service {
|
||||
|
||||
//使用token获取当前登录人的信息(浙政钉和诗画的在一起)
|
||||
// ZZDUserDTO zzdUser = getZZDUserInfo(zzdToken);
|
||||
ZZDUserDTO zzdUser = getZZDUserInfo(token,UserTypeEnum.ZZD.getType());
|
||||
ZZDUserDTO zzdUser = getZZDUserInfo(token, UserTypeEnum.ZZD.getType(), false);
|
||||
|
||||
return zzdUser;
|
||||
}
|
||||
|
||||
public ZZDUserDTO zzdssoByAuthCode(String authCode) {
|
||||
if (zzdOpenPlatform.isEnabled() && zzdOpenPlatform.isAppCredentialConfigured()) {
|
||||
return getZzdUserByOpenPlatformAuthCode(authCode);
|
||||
}
|
||||
return getZZDUserInfo(authCode, UserTypeEnum.ZZD.getType(), true);
|
||||
}
|
||||
|
||||
private String zzdOpenBaseUrl() {
|
||||
return zzdOpenPlatform.getProtocol() + "://" + zzdOpenPlatform.getDomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开放平台文档:POST /gettoken.json,表单 appkey、appsecret;返回 content.data.accessToken
|
||||
*/
|
||||
private String getZzdOpenAppAccessToken() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (zzdOpenCachedAppAccessToken != null && now < zzdOpenCachedAppAccessTokenExpireMs - 60_000L) {
|
||||
return zzdOpenCachedAppAccessToken;
|
||||
}
|
||||
synchronized (zzdOpenTokenLock) {
|
||||
now = System.currentTimeMillis();
|
||||
if (zzdOpenCachedAppAccessToken != null && now < zzdOpenCachedAppAccessTokenExpireMs - 60_000L) {
|
||||
return zzdOpenCachedAppAccessToken;
|
||||
}
|
||||
String url = zzdOpenBaseUrl() + "/gettoken.json";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||
body.add("appkey", zzdOpenPlatform.getAppKey());
|
||||
body.add("appsecret", zzdOpenPlatform.getAppSecret());
|
||||
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<String> response;
|
||||
try {
|
||||
response = restTemplate.postForEntity(url, entity, String.class);
|
||||
} catch (HttpStatusCodeException e) {
|
||||
log.error("浙政钉 gettoken HTTP异常 状态:{} 响应:{}", e.getRawStatusCode(), e.getResponseBodyAsString());
|
||||
throw new ThirdPartyException("浙政钉获取应用 access_token 失败", "ZZD_OPEN_TOKEN_HTTP");
|
||||
} catch (ResourceAccessException e) {
|
||||
log.error("浙政钉 gettoken 网络异常", e);
|
||||
throw new ThirdPartyException("浙政钉开放平台暂不可用", "ZZD_OPEN_TOKEN_NET");
|
||||
}
|
||||
String respBody = response.getBody();
|
||||
JSONObject data = parseZzdOpenContentData(respBody, url);
|
||||
String accessToken = data.getString("accessToken");
|
||||
if (!StringUtils.hasText(accessToken)) {
|
||||
throw new ThirdPartyException("浙政钉 gettoken 未返回 accessToken", "ZZD_OPEN_TOKEN_BAD");
|
||||
}
|
||||
int expiresIn = 7100;
|
||||
try {
|
||||
Object ex = data.get("expiresIn");
|
||||
if (ex != null) {
|
||||
expiresIn = Integer.parseInt(ex.toString());
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
zzdOpenCachedAppAccessToken = accessToken;
|
||||
zzdOpenCachedAppAccessTokenExpireMs = System.currentTimeMillis() + expiresIn * 1000L;
|
||||
return accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /rpc/oauth2/dingtalk_app_user.json,表单 access_token、auth_code
|
||||
*/
|
||||
private ZZDUserDTO getZzdUserByOpenPlatformAuthCode(String authCode) {
|
||||
String appToken = getZzdOpenAppAccessToken();
|
||||
String url = zzdOpenBaseUrl() + "/rpc/oauth2/dingtalk_app_user.json";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||
body.add("access_token", appToken);
|
||||
body.add("auth_code", authCode);
|
||||
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<String> response;
|
||||
try {
|
||||
response = restTemplate.postForEntity(url, entity, String.class);
|
||||
} catch (HttpStatusCodeException e) {
|
||||
log.error("浙政钉 dingtalk_app_user HTTP异常 状态:{} 响应:{}", e.getRawStatusCode(), e.getResponseBodyAsString());
|
||||
throw new ThirdPartyException("浙政钉获取用户失败", "ZZD_OPEN_USER_HTTP");
|
||||
} catch (ResourceAccessException e) {
|
||||
log.error("浙政钉 dingtalk_app_user 网络异常", e);
|
||||
throw new ThirdPartyException("浙政钉开放平台暂不可用", "ZZD_OPEN_USER_NET");
|
||||
}
|
||||
JSONObject data = parseZzdOpenContentData(response.getBody(), url);
|
||||
ZZDUserDTO dto = mapOpenPlatformUserToZzdDto(data);
|
||||
if (StringUtils.hasText(zzdOpenPlatform.getTenantId()) && dto.getExt() != null
|
||||
&& StringUtils.hasText(dto.getExt().getRealmId())
|
||||
&& !zzdOpenPlatform.getTenantId().equals(dto.getExt().getRealmId())) {
|
||||
log.warn("浙政钉用户 realmId={} 与配置 zzd.open.tenant-id={} 不一致", dto.getExt().getRealmId(), zzdOpenPlatform.getTenantId());
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
private static JSONObject parseZzdOpenContentData(String body, String requestUrl) {
|
||||
if (!StringUtils.hasText(body)) {
|
||||
throw new ThirdPartyException("浙政钉接口空响应: " + requestUrl, "ZZD_OPEN_EMPTY");
|
||||
}
|
||||
JSONObject root = JSON.parseObject(body);
|
||||
if (root == null || !root.getBooleanValue("success")) {
|
||||
throw new ThirdPartyException("浙政钉接口失败: " + body, "ZZD_OPEN_FAIL");
|
||||
}
|
||||
JSONObject content = root.getJSONObject("content");
|
||||
if (content == null) {
|
||||
throw new ThirdPartyException("浙政钉响应缺少 content: " + body, "ZZD_OPEN_BAD");
|
||||
}
|
||||
if (!content.getBooleanValue("success")) {
|
||||
throw new ThirdPartyException("浙政钉 content 失败: " + body, "ZZD_OPEN_FAIL");
|
||||
}
|
||||
JSONObject data = content.getJSONObject("data");
|
||||
if (data == null) {
|
||||
throw new ThirdPartyException("浙政钉响应缺少 data: " + body, "ZZD_OPEN_BAD");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private static ZZDUserDTO mapOpenPlatformUserToZzdDto(JSONObject data) {
|
||||
ZZDUserDTO dto = new ZZDUserDTO();
|
||||
String lastName = data.getString("lastName");
|
||||
String nickCn = data.getString("nickNameCn");
|
||||
dto.setUsername(lastName != null ? lastName : nickCn);
|
||||
dto.setNickName(lastName != null ? lastName : nickCn);
|
||||
dto.setMobile(data.getString("mobile"));
|
||||
dto.setRegionCode(null);
|
||||
Object orgId = data.get("organizationId");
|
||||
if (orgId instanceof Number) {
|
||||
dto.setOrganizationId(((Number) orgId).intValue());
|
||||
} else {
|
||||
dto.setOrganizationId(0);
|
||||
}
|
||||
Object accountId = data.get("accountId");
|
||||
int userId = 0;
|
||||
if (accountId instanceof Number) {
|
||||
long lv = ((Number) accountId).longValue();
|
||||
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.setNickNameCn(nickCn);
|
||||
ext.setOpenid(data.getString("openid"));
|
||||
Object realm = data.get("realmId");
|
||||
ext.setRealmId(realm == null ? null : realm.toString());
|
||||
ext.setMobile(data.getString("mobile"));
|
||||
dto.setExt(ext);
|
||||
return dto;
|
||||
}
|
||||
|
||||
|
||||
public String getZZDToken(String token) {
|
||||
String url = baseUrl+config.getZzdTokenUrl()+token;
|
||||
@@ -392,10 +564,24 @@ public class ThirdPartyOAuth2Service {
|
||||
* @return
|
||||
*/
|
||||
public ZZDUserDTO getZZDUserInfo(String zzdToken,String type) {
|
||||
return getZZDUserInfo(zzdToken, type, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zzdAuthCodeFlow true 时走 oauth2.client.zzd-auth-code-user-info-url,query 为 authCode
|
||||
*/
|
||||
public ZZDUserDTO getZZDUserInfo(String credential, String type, boolean zzdAuthCodeFlow) {
|
||||
|
||||
// 构建请求URL
|
||||
try {
|
||||
String url = baseUrl + config.getZzdShUserInfoUrl()+"?token=" + zzdToken;
|
||||
String requestPath = zzdAuthCodeFlow ? config.getZzdAuthCodeUserInfoUrl() : config.getZzdShUserInfoUrl();
|
||||
if (zzdAuthCodeFlow && !StringUtils.hasText(requestPath)) {
|
||||
throw new ThirdPartyException("未配置浙政钉 authCode 换取用户:请设置 zzd.open.app-key/app-secret(开放平台)或 oauth2.client.zzd-auth-code-user-info-url(网关)", "ZZD_AUTH_CODE_CONFIG");
|
||||
}
|
||||
String param = zzdAuthCodeFlow ? "authCode" : "token";
|
||||
String url = zzdAuthCodeFlow
|
||||
? baseUrl + requestPath + "?" + param + "=" + URLEncoder.encode(credential, StandardCharsets.UTF_8)
|
||||
: baseUrl + requestPath + "?" + param + "=" + credential;
|
||||
// 获取 Bearer Token
|
||||
String accessToken = getAccessToken().getAccess_token();
|
||||
|
||||
@@ -414,7 +600,7 @@ public class ThirdPartyOAuth2Service {
|
||||
|
||||
if (response.getBody().getCode().equals("-1")){
|
||||
log.error("获取浙政钉/诗画用户信息失败");
|
||||
log.error("token",zzdToken);
|
||||
log.error("token",credential);
|
||||
log.error("response",JSON.toJSONString(response.getBody()));
|
||||
ThirdPartyLogEntity thirdPartyLogEntity = new ThirdPartyLogEntity();
|
||||
if (type.equals(UserTypeEnum.ZZD.getType())){
|
||||
@@ -423,8 +609,8 @@ public class ThirdPartyOAuth2Service {
|
||||
thirdPartyLogEntity.setApiName(UserTypeEnum.SH.getDescription());
|
||||
}
|
||||
// thirdPartyLogEntity.setApiName("获取浙政钉/诗画用户信息失败");
|
||||
thirdPartyLogEntity.setRequestUrl( baseUrl + config.getZzdShUserInfoUrl());
|
||||
thirdPartyLogEntity.setRequestParameter(zzdToken);
|
||||
thirdPartyLogEntity.setRequestUrl( baseUrl + requestPath);
|
||||
thirdPartyLogEntity.setRequestParameter(credential);
|
||||
thirdPartyLogEntity.setResponseBody(JSON.toJSONString(response.getBody()));
|
||||
thirdPartyLogEntity.setErrorCode(response.getBody().getCode());
|
||||
thirdPartyLogEntity.setErrorMsg(response.getBody().getDescription());
|
||||
|
||||
@@ -40,6 +40,18 @@ oauth2:
|
||||
shjn_token_url: /prod-api/login/shpj
|
||||
# 浙证钉和诗画公用的获取用户信息接口
|
||||
zzd_sh_user_info_url: /prod-api/oauth2/apis/sso/v1/fetch_user_by_uabase_token
|
||||
# 浙政钉官方免登:前端 authCode 换用户信息(按实际网关/开放平台对接后填写,留空则 authCode 登录会报错)
|
||||
# zzd_auth_code_user_info_url: /prod-api/...
|
||||
|
||||
# 浙政钉开放平台直连(配置 app-key/app-secret 后 authCode 走 gettoken + dingtalk_app_user,无需 zzd_auth_code_user_info_url)
|
||||
zzd:
|
||||
open:
|
||||
enabled: true
|
||||
protocol: https
|
||||
domain: openplatform-pro.ding.zj.gov.cn
|
||||
tenant-id: "196729"
|
||||
app-key: ${ZZD_OPEN_APP_KEY:pjx_zhhqyth-wt2VrVa0olcqEBBJJb}
|
||||
app-secret: ${ZZD_OPEN_APP_SECRET:rT5zbpHO0L7raRHJtvlIs26N8hn53JDv1K1SNa7X}
|
||||
|
||||
# 正元智慧
|
||||
# https://gwyc.zj.gov.cn/API/%E7%9C%81%E5%B9%B3%E5%8F%B0API%E5%AF%B9%E6%8E%A5%E6%8E%A5%E5%8F%A3.html
|
||||
|
||||
Reference in new issue
Block a user