Commit 8ac650dd by jianan

idP

parent e488d737
package com.yd.user.api.client;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yd.common.exception.BusinessException;
import com.yd.user.api.config.CasdoorProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Casdoor 管理 API 客户端:以应用 clientId/clientSecret 的 Basic Auth 调用,
* 身份为应用 owner。仅封装查询与创建两类原子操作。
*/
@Slf4j
@Component
public class CasdoorClient {
private final CasdoorProperties properties;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
public CasdoorClient(CasdoorProperties properties) {
this.properties = properties;
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
this.restTemplate = new RestTemplate(factory);
}
/**
* 按组织 + 手机号查询用户 subject。
*
* @return Casdoor 用户 id(sub);不存在返回 null
*/
public String findSubByPhone(String phone) {
String url = String.format("%s/api/get-user?owner=%s&phone=%s",
properties.getEndpoint(), properties.getOrganization(), phone);
JsonNode root = exchange(url, HttpMethod.GET, null);
JsonNode data = root.path("data");
if (data.isMissingNode() || data.isNull()) {
return null;
}
return data.path("id").asText(null);
}
/**
* 创建用户,创建后按手机号查回 subject。
*/
public String createUserAndGetSub(String username, String displayName, String phone, String password) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("owner", properties.getOrganization());
body.put("name", username);
body.put("displayName", displayName);
body.put("phone", phone);
body.put("countryCode", "CN");
body.put("password", password);
String url = properties.getEndpoint() + "/api/add-user";
JsonNode root = exchange(url, HttpMethod.POST, body);
if (!"ok".equalsIgnoreCase(root.path("status").asText())) {
throw new BusinessException("Casdoor 创建用户失败:" + root.path("msg").asText());
}
String sub = findSubByPhone(phone);
if (StringUtils.isBlank(sub)) {
throw new BusinessException("Casdoor 创建用户成功但查回 subject 为空,phone=" + phone);
}
return sub;
}
private JsonNode exchange(String url, HttpMethod method, Object body) {
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(properties.getClientId(), properties.getClientSecret(), StandardCharsets.UTF_8);
if (body != null) {
headers.setContentType(MediaType.APPLICATION_JSON);
}
try {
String response = restTemplate.exchange(
url, method, new HttpEntity<>(body, headers), String.class).getBody();
return objectMapper.readTree(response);
} catch (Exception e) {
log.error("调用 Casdoor 管理 API 异常 url={} method={}", url, method, e);
throw new BusinessException("调用 Casdoor 失败:" + e.getMessage());
}
}
}
package com.yd.user.api.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* Casdoor 统一身份提供方配置。
* 凭据通过 Nacos(yd-user-api.yml)注入,不入库、不进 git。
*/
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "casdoor")
public class CasdoorProperties {
/** Casdoor 站点地址,如 https://auth.anjibao.cn */
private String endpoint;
/** 管理 API 鉴权用的应用 clientId */
private String clientId;
/** 管理 API 鉴权用的应用 clientSecret */
private String clientSecret;
/** 转介人所属 Casdoor 组织(owner) */
private String organization;
}
...@@ -115,4 +115,12 @@ public class ApiClientUserController implements ApiClientUserFeignClient { ...@@ -115,4 +115,12 @@ public class ApiClientUserController implements ApiClientUserFeignClient {
return apiClientUserService.getByIdpSub(tenantBizId, idpSub); return apiClientUserService.getByIdpSub(tenantBizId, idpSub);
} }
/**
* 为客户端用户开通统一身份
*/
@Override
public Result<String> provisionIdp(String clientUserBizId) {
return apiClientUserService.provisionIdp(clientUserBizId);
}
} }
...@@ -26,6 +26,9 @@ public interface ApiClientUserService { ...@@ -26,6 +26,9 @@ public interface ApiClientUserService {
Result<ApiClientUserIdpLoginResponse> getByIdpSub(String tenantBizId, String idpSub); Result<ApiClientUserIdpLoginResponse> getByIdpSub(String tenantBizId, String idpSub);
/** 为客户端用户开通统一身份(幂等),返回 Casdoor subject。 */
Result<String> provisionIdp(String clientUserBizId);
Result saveClientUserList(List<String> userBizIdList, Result saveClientUserList(List<String> userBizIdList,
String tenantBizId, String tenantBizId,
String projectBizId); String projectBizId);
......
...@@ -12,6 +12,7 @@ import com.yd.common.result.Result; ...@@ -12,6 +12,7 @@ import com.yd.common.result.Result;
import com.yd.common.utils.RandomStringGenerator; import com.yd.common.utils.RandomStringGenerator;
import com.yd.csf.feign.client.agentreferralrelation.ApiAgentReferralRelationFeignClient; import com.yd.csf.feign.client.agentreferralrelation.ApiAgentReferralRelationFeignClient;
import com.yd.csf.feign.request.agentreferralrelation.ApiAgentReferralRelationSaveRequest; import com.yd.csf.feign.request.agentreferralrelation.ApiAgentReferralRelationSaveRequest;
import com.yd.user.api.client.CasdoorClient;
import com.yd.user.api.service.ApiClientUserService; import com.yd.user.api.service.ApiClientUserService;
import com.yd.user.feign.request.clientuser.*; import com.yd.user.feign.request.clientuser.*;
import com.yd.user.feign.response.clientuser.ApiClientUserDetailResponse; import com.yd.user.feign.response.clientuser.ApiClientUserDetailResponse;
...@@ -46,6 +47,9 @@ public class ApiClientUserServiceImpl implements ApiClientUserService { ...@@ -46,6 +47,9 @@ public class ApiClientUserServiceImpl implements ApiClientUserService {
@Autowired @Autowired
private ApiAgentReferralRelationFeignClient apiAgentReferralRelationFeignClient; private ApiAgentReferralRelationFeignClient apiAgentReferralRelationFeignClient;
@Autowired
private CasdoorClient casdoorClient;
/** /**
* 分页查询-客户端用户信息 * 分页查询-客户端用户信息
* @param request * @param request
...@@ -192,6 +196,60 @@ public class ApiClientUserServiceImpl implements ApiClientUserService { ...@@ -192,6 +196,60 @@ public class ApiClientUserServiceImpl implements ApiClientUserService {
} }
/** /**
* 开通统一身份(幂等):
* 已有 idp_sub 直接返回;否则按手机号查 Casdoor,查无则创建,回写 idp_sub。
*/
@Override
public Result<String> provisionIdp(String clientUserBizId) {
ClientUser clientUser = iClientUserService.queryOne(clientUserBizId);
if (Objects.isNull(clientUser)) {
throw new BusinessException("客户端用户信息不存在");
}
if (StringUtils.isNotBlank(clientUser.getIdpSub())) {
return Result.success(clientUser.getIdpSub());
}
String phone = normalizePhone(clientUser.getPhone());
if (StringUtils.isBlank(phone)) {
throw new BusinessException("客户端用户手机号为空,无法开通统一身份");
}
String sub = casdoorClient.findSubByPhone(phone);
if (StringUtils.isBlank(sub)) {
String initialPassword = buildInitialPassword(clientUser, phone);
// Casdoor 登录名用业务ID,保证唯一可溯源;用户实际以手机号登录
sub = casdoorClient.createUserAndGetSub(
clientUser.getClientUserBizId(), clientUser.getName(), phone, initialPassword);
}
clientUser.setIdpSub(sub);
iClientUserService.saveOrUpdate(clientUser);
return Result.success(sub);
}
/** 手机号规范化:去非数字、去国家码,得到 11 位本地号码。 */
private String normalizePhone(String phone) {
if (StringUtils.isBlank(phone)) {
return null;
}
String digits = phone.replaceAll("\\D", "");
if (digits.startsWith("86") && digits.length() == 13) {
digits = digits.substring(2);
}
return digits;
}
/** 规则初始密码:证件号后六位;无证件或不足六位时用手机号后六位。 */
private String buildInitialPassword(ClientUser clientUser, String phone) {
String idCard = clientUser.getIdCardNumber();
if (StringUtils.isNotBlank(idCard) && idCard.length() >= 6) {
return idCard.substring(idCard.length() - 6);
}
if (phone.length() >= 6) {
return phone.substring(phone.length() - 6);
}
throw new BusinessException("无法生成初始密码:证件号与手机号均不足六位");
}
/**
* 同步sys_user系统用户,批量更新客户端用户表数据 * 同步sys_user系统用户,批量更新客户端用户表数据
* @param userBizIdList * @param userBizIdList
* @param tenantBizId * @param tenantBizId
......
...@@ -17,11 +17,12 @@ import java.util.List; ...@@ -17,11 +17,12 @@ import java.util.List;
/** /**
* 客户端用户信息Feign客户端 * 客户端用户信息Feign客户端
*/ */
@FeignClient(name = "yd-user-api",path = "/user/api/clientUser",fallbackFactory = ApiClientUserFeignFallbackFactory.class) @FeignClient(name = "yd-user-api", path = "/user/api/clientUser", fallbackFactory = ApiClientUserFeignFallbackFactory.class)
public interface ApiClientUserFeignClient { public interface ApiClientUserFeignClient {
/** /**
* 分页查询-客户端用户信息 * 分页查询-客户端用户信息
*
* @param request * @param request
* @return * @return
*/ */
...@@ -31,6 +32,7 @@ public interface ApiClientUserFeignClient { ...@@ -31,6 +32,7 @@ public interface ApiClientUserFeignClient {
/** /**
* 详情-客户端用户信息 * 详情-客户端用户信息
*
* @param clientUserBizId * @param clientUserBizId
* @return * @return
*/ */
...@@ -41,6 +43,7 @@ public interface ApiClientUserFeignClient { ...@@ -41,6 +43,7 @@ public interface ApiClientUserFeignClient {
/** /**
* 根据条件查询客户端用户业务ID列表 * 根据条件查询客户端用户业务ID列表
*
* @param tenantBizId * @param tenantBizId
* @param tenantBizId * @param tenantBizId
* @return * @return
...@@ -51,6 +54,7 @@ public interface ApiClientUserFeignClient { ...@@ -51,6 +54,7 @@ public interface ApiClientUserFeignClient {
/** /**
* 查询-客户端用户详情 body入参 * 查询-客户端用户详情 body入参
*
* @return * @return
*/ */
@PostMapping("/body/detail") @PostMapping("/body/detail")
...@@ -58,6 +62,7 @@ public interface ApiClientUserFeignClient { ...@@ -58,6 +62,7 @@ public interface ApiClientUserFeignClient {
/** /**
* 保存-客户端用户信息(新增或者编辑) * 保存-客户端用户信息(新增或者编辑)
*
* @return * @return
*/ */
@PostMapping("/save") @PostMapping("/save")
...@@ -65,6 +70,7 @@ public interface ApiClientUserFeignClient { ...@@ -65,6 +70,7 @@ public interface ApiClientUserFeignClient {
/** /**
* 编辑-客户端用户审批状态 * 编辑-客户端用户审批状态
*
* @return * @return
*/ */
@PutMapping("/edit/approvalStatus") @PutMapping("/edit/approvalStatus")
...@@ -72,6 +78,7 @@ public interface ApiClientUserFeignClient { ...@@ -72,6 +78,7 @@ public interface ApiClientUserFeignClient {
/** /**
* 编辑-客户端用户openId * 编辑-客户端用户openId
*
* @return * @return
*/ */
@PutMapping("/edit/openId") @PutMapping("/edit/openId")
...@@ -80,6 +87,7 @@ public interface ApiClientUserFeignClient { ...@@ -80,6 +87,7 @@ public interface ApiClientUserFeignClient {
/** /**
* 多条件查询客户端用户列表信息 * 多条件查询客户端用户列表信息
*
* @return * @return
*/ */
@PostMapping("/condition/list") @PostMapping("/condition/list")
...@@ -97,4 +105,15 @@ public interface ApiClientUserFeignClient { ...@@ -97,4 +105,15 @@ public interface ApiClientUserFeignClient {
Result<ApiClientUserIdpLoginResponse> getByIdpSub( Result<ApiClientUserIdpLoginResponse> getByIdpSub(
@NotBlank(message = "租户业务ID不能为空") @RequestParam(value = "tenantBizId") String tenantBizId, @NotBlank(message = "租户业务ID不能为空") @RequestParam(value = "tenantBizId") String tenantBizId,
@NotBlank(message = "IdP subject不能为空") @RequestParam(value = "idpSub") String idpSub); @NotBlank(message = "IdP subject不能为空") @RequestParam(value = "idpSub") String idpSub);
/**
* 为已存在的客户端用户开通统一身份:按手机号查/建 Casdoor 用户,回写 idp_sub(幂等)。
*
* @param clientUserBizId 客户端用户业务ID
* @return Casdoor subject
*/
@PostMapping("/provisionIdp")
@Operation(summary = "为客户端用户开通统一身份")
Result<String> provisionIdp(
@NotBlank(message = "客户端用户表唯一业务ID不能为空") @RequestParam(value = "clientUserBizId") String clientUserBizId);
} }
...@@ -65,6 +65,12 @@ public class ApiClientUserFeignFallbackFactory implements FallbackFactory<ApiCli ...@@ -65,6 +65,12 @@ public class ApiClientUserFeignFallbackFactory implements FallbackFactory<ApiCli
log.error("按IdP subject解析登录身份失败 tenantBizId={} idpSub={}", tenantBizId, idpSub, cause); log.error("按IdP subject解析登录身份失败 tenantBizId={} idpSub={}", tenantBizId, idpSub, cause);
return null; return null;
} }
@Override
public Result<String> provisionIdp(String clientUserBizId) {
log.error("为客户端用户开通统一身份失败 clientUserBizId={}", clientUserBizId, cause);
return null;
}
}; };
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment