用户数据日志的同步
1. 建立中间表对象
这里我使用SsoUser来进行演示
| 属性 | 注释 |
|---|---|
| userId | 用户ID |
| userCode | 用户编码 |
| userNum | 警号ID |
| idCard | 身份证ID |
假设就只有这个4个值的封装成为的对象,用来接受其他Api请求端给我们传过来的参数。
2.编写controller【前端控制器层】
@ApiOperation(value = "001-通过身份证获取用户信息", notes = "001-通过身份证获取用户信息")
@PostResource(value = "/user/get", name = "001-通过身份证获取用户信息")
public RwResponse<SsoUser> getUserByIdCard(@RequestParam(name = "idcard") String idcard) {
SsoUser ssoUser = sysUserApplicationService.getSsoUserByIdCard(idcard);
return RwResponse.build(ssoUser);
}
其中,@PostResource是一个自定义注解,用于标记某个方法为一个RESTful API的POST请求
name可以用于生成API文档或在日志中记录请求的详细信息,方便调试和维护。
RwResponse是一个封装好的响应体,这里不是本次的重点。
3.编写service 【业务层】
(1) 外层的方法API
public SsoUser getSsoUserByIdCard(String idcard) {
SsoUser ssoUser = ssoUserApi.getSsoUserByIdCard(idcard);
if (ssoUser == null) {
throw new ServiceException(CommCode.DATA_NOT_FOUND, "未找到身份证号[" + idcard + "]的用户信息!");
}
String deptCode = DeptLocalCahce.getDeptCodeByCodeInOtherSys(ssoUser.getOrgNum());
if (StringUtils.isBlank(deptCode)) {
deptCode = DeptLocalCahce.getDeptCodeByDeptName(ssoUser.getOrgName());
}
ssoUser.setDeptCode(StringUtils.isBlank(deptCode) ? "0101" : deptCode);
return ssoUser;
}
在这一层中,封装了大量调用其他方法API的接口,但是还是有几个方面可以展开来说说。
在DeptLocalCache中,如果无法通过orgNum来获取到用户的部门的信息的话,则通过另外的一个属性来来获取deptCode,然后进行再次的判断,如果仍然为空,则通过赋值默认值的方式进行赋值。最后将对象进行返回。
(2) getSsoUserByIdCard()
UserInfoQueryParam param = UserInfoQueryParam.builder()
.idCard(idCard)
.build();
List<SsoUser> list = this.getUserInfo(param);
list = list.stream().filter(item -> YesOrNoEnum.no(item.getIsDeleted())).collect(Collectors.toList());
if (CollectionUtil.isEmpty(list)) {
return null;
}
return list.stream().max(Comparator.comparing(SsoUser::getUpdatedSeq)).get();
在这个方法中,我们封装了查询的请求体,然后将得到的数据以流的方式进行过滤然后得到需要的数据,这里的思路则是,将每个对象中已经删除的对象进行标记并且抛弃,然后把剩下的再次合并成为一个集合,然后再进一步进行选择,而后面的一个比较容器中,目的是为了挑选出一个最大更新频率的用户,并封装成为SsoUser进行返回。
(3) getSsoUserByIdCard()
public List<SsoUser> getUserInfo(UserInfoQueryParam param) {
HttpRequest request = HttpUtil.createPost(ssoUserUrl + "/aa/bb/.userInfo");
request.method(Method.POST);
request.headerMap(this.getHeaders().toSingleValueMap(), true);
request.body(JSON.toJSONString(param), ContentType.JSON.getValue());
log.info("调用统一用户接口:{},入参:{}", "/aa/bb/.userInfo", JSON.toJSON(param).toString());
HttpResponse response = request.execute();
List<SsoUser> result = null;
if (response.getStatus() == 200) {
JSONObject jsonObject = JSON.parseObject(response.body());
JSONObject payloadObject = jsonObject.getJSONObject("payload");
if (payloadObject != null && payloadObject.containsKey("data")) {
result = JSONObject.parseArray(payloadObject.getString("data"), SsoUser.class);
}
} else {
return Collections.EMPTY_LIST;
}
return result == null ? Collections.EMPTY_LIST : result;
在这个方法中,就是一个请求url的基本套路,包括参数、参数请求头的相关转换【Json对象】和设置。然后在响应处理中,如果状体码为200时,则对json对象进行处理,提取payload对象中的data字段,并返回泛型集合,反之则返回空集合
(4) getHeaders()
private MultiValueMap<String, String> getHeaders() {
SsoToken ssoToken = ssoAuthApi.getToken();
log.info("调用统一认证接口获取token:{}", ssoToken.getAccessToken());
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add("Authorization", Joiner.on(" ").join("Bearer", ssoToken.getAccessToken()));
headers.add("Content-Type", ContentType.JSON.getValue());
return headers;
}
在这个方法中,将获取到的token,先记录到日志文件中去,然后新建立一个Map集合,并指定类型,然后在对获取到token进行加工和封装
(5) getToken()
public SsoToken getToken() {
if (StringUtils.isBlank(this.clientId) || StringUtils.isBlank(this.clientSecret)) {
throw new ServiceException(CommCode.ERROR, "缺少配置[sso.client-id]或[sso.client-secret]");
}
MultiValueMap<String, String> headers = new HttpHeaders();
String clientBase64 = Base64.encode(Joiner.on(":").join(clientId, clientSecret));
headers.put("Authorization", Collections.singletonList("Basic " + clientBase64));
headers.add("Content-Type", ContentType.FORM_URLENCODED.getValue());
SsoToken ssoToken = ssoAuthApi.getToken("client_credentials", headers);
if (ssoToken == null || StringUtils.isBlank(ssoToken.getAccessToken())) {
throw new ServiceException(CommCode.USER_LOGIN_TOKEN_EMPTY, "获取token失败!");
}
return ssoToken;
}
这段代码中,首先检查id和secret是否为空,如果有任一个为空,则抛出异常体现缺少配置,紧接着创建请求头,对id和secret进行编码,并且设置到Authorization中。添加Content-Type,值为application/x-www-form-urlencoded。如果获取成功,则返回SsoToken对象
(6) getToken(args…)
@FeignClient(value = "gz-sso-auth", url = "${sso.auth.url}")
public interface ISsoAuthApi {
@PostMapping("/sso/oauth2/token")
SsoToken getToken(@RequestParam("grant_type") String grantType
, @RequestHeader MultiValueMap<String, String> headers);
}
- 在这个代码中,使用到了Feign客户端,而他是一个Spring Cloud Feign 提供的注解,用于声明一个Feign客户端,他是一个声明式的Web服务客服端,使得我们撰写HTTP变得更简单。
- value则是指定服务的名称,Feign客户端会通过这个名称在服务注册与发现组件中查找对应的服务实例 。url是指从配置文件中读取URL,通常在application.properties或application.yml文件中进行配置。
关系总结
- 目的相同:
两个代码片段的目的都是与远程服务进行通信并获取数据。 - 实现方式不同:
Feign 客户端:使用 Spring Cloud Feign 客户端,通过声明式接口简化了 HTTP 请求的构建和发送过程。
手动构建 HTTP 请求:使用 HttpUtil 手动构建和发送 HTTP 请求,提供了更多的控制和灵活性。 - 应用场景:
Feign 客户端:适用于微服务架构中,服务之间的调用较为频繁且规则固定的情况。
手动构建 HTTP 请求:适用于需要更多自定义配置或复杂请求处理的情况。
代码逻辑对比 - 请求构建:
Feign 客户端:通过注解和方法参数自动构建请求。
手动构建 HTTP 请求:需要手动设置请求方法、请求头和请求体。 - 请求发送:
Feign 客户端:通过调用接口方法自动发送请求。
手动构建 HTTP 请求:需要调用 execute 方法发送请求。 - 响应处理:
Feign 客户端:直接返回响应对象,通常是一个 POJO。
手动构建 HTTP 请求:需要手动解析响应体,提取所需数据。
结论
这两个代码片段虽然实现方式不同,但都实现了与远程服务的通信。选择哪种方式取决于具体的应用场景和需求。Feign 客户端适用于简单的、规则固定的服务调用,而手动构建 HTTP 请求则提供了更多的灵活性和控制。
2233

被折叠的 条评论
为什么被折叠?



