# Restful数据调用 Thu, Mar 10, 2022 10:21 AM 数据库数据,Restful数据,总线数据,我们统一抽象为数据持久层,存放在架构的基础设施中。 因此在APP(应用层), 不可以出现远程调用的业务代码出现。一下介绍二种远程调用的实现方式。 ## 没有官方SDK的情况 在方法只提供请求接口或者官方的SDK无法满足我们的需求的情况下,我们需要自定义远程调用的实现。框架集成了Spring Cloud Feign和Spring Boot Data。因此,我们实现的远程调用可以直接依赖Feign来实现,Feign集成了熔断器,已便于我们可以通过配置控制我们所有的数据请求。 ### 定义Infrastructure结构 ``` --restapi (spring boot data 对应的结构) +-fallback (发生熔断,备用处理方法) +-remote (远程调用接口的定义) +-restobject (远程调用的DTO结构) --serviceimpl (client层定义的服务接口的实现) ``` restapi与数据持久层内容对应,在我们的整体架构设计中,理解上,我们将数据库,远程调用,文件数据读取等关于数据持久化的逻辑抽象为我们架构的数据持久层。因此,在restapi就可以理解是我们的一种持久化数据。而在业务中,如果将业务和第三方数据分离,还需要service的存在。service存在的意义是屏蔽应用层对远程调用相关的具体业务实现。 remote中定义远程调用的接口,所有类,应该以Rest结尾,比如:xxxRest.java ``` java @FeignClient(path = "/", // name="ocr-tencent", url = "https://ocr.tencentcloudapi.com", configuration = TencentAuthorize.class, // 权限参数 fallbackFactory = TencentOcrFallback.class // 熔断回调 ) public interface TencentOcrRest { @GetMapping BizLicenseRO verifyBizLicense(BizLicensePS dto); // 识别企业营业执照 } ``` 在实际实现过程中,我们不需要对远程调用接口进行实现,只需要负责定义远程调用的接口。该部分在Spring Boot加载过程中,SpringCloudFeign会对接口的内容进行动态加载和实现。 fallback中定义远程调用如果发生熔断执行的备用方法,主要用户处理异常或者发生异常的返回值, 所有类必须以Fallback结尾,比如:xxxFallback.java ``` java @Log @Component public class TencentOcrFallback implements TencentOcrRest, FallbackFactory { @Override public TencentOcrRest create(Throwable cause) { log.log(Level.INFO, cause.getMessage(), cause); return this; } @Override public BizLicenseRO verifyBizLicense(BizLicensePS dto) { // TODO Auto-generated method stub return null; } } ``` 该方法可以自由定义,我们在这里定义了Fallback工厂,用于捕获发生调用异常的原因。如果不需要记录异常的原因,可以只继承调用的远程访问接口。 restobjct中定义远程访问的结构体(类) 接口的参数,以PS结尾,比如: xxxPS.java ``` java @Data @JsonInclude(Include.NON_NULL) public class BizLicensePS { @Data @JsonInclude(Include.NON_NULL) public static final class ImageConfig { @JsonProperty("RegNum") private Boolean regNum; @JsonProperty("Name") private Boolean name; @JsonProperty("Address") private Boolean address; } @JsonProperty("Action") private String action = "VerifyBizLicense"; @JsonProperty("Version") private String version = "2018-11-19"; //... } ``` 返回值以RO结尾,比如:xxxRO.java ``` java @Data @EqualsAndHashCode(callSuper = true) @JsonInclude(Include.NON_NULL) public class BizLicenseRO extends ErrorCode { @JsonProperty("Response") private Response response; @Data @JsonInclude(Include.NON_NULL) public static final class Response { @JsonProperty("ErrorCode") private Integer errorCode; @JsonProperty("RequestId") private String requestId; @JsonProperty("RegNo") private String regNo; @JsonProperty("VerifyRegNo") private String verifyRegNo; // TODO ... @JsonProperty("RegNumResult") private RegNumResult regNumResult; @JsonAnySetter @JsonAnyGetter private Map other = new LinkedMap<>(); } @Data @JsonInclude(Include.NON_NULL) public static final class RegNumResult { @JsonProperty("RegNum") private String regNum; @JsonProperty("Name") private String name; @JsonProperty("Address") private String address; } } ``` 所有的定义不是一成不变的,我们需要灵活运用,需要注意定义DTO的时候,优先名字即意义的原则。 ``` java @Data public class ErrorCode { private boolean success = true; @JsonAnySetter @JsonAnyGetter private Map other = new LinkedMap<>(); } ``` 需要注意的是,对于远程调用,架构中默认使用fasterxml作为序列化和反序列化工具。因此,默认注解为fasterxml的注解。也可以通过修改pom和config来更序列化和反序列化工具。具体可以参考Feign。 serviceimpl是屏蔽业务层对远程调用的感知。如果因为业务很简单,也可以通过业务层直接使用remote内容。但是如果业务比较负责,考虑到之后业务维护的简便性。因此我们推荐将远程访问的内容屏蔽。 ``` java @Log @Component public class OcrServiceImpl implements OcrServiceI { @Autowired private TencentOcrRest ocrRest; @Override public Result verifyIdCard(IdCardDTO dto) { return null; } @Override public Result verifyBizLicense(BizLicenseDTO dto) { BizLicensePS ps = ObjectConvertor.dto2do(dto, BizLicensePS.class); BizLicenseRO ro = ocrRest.verifyBizLicense(ps); if (!ro.isSuccess()) { log.info(ro.toString()); return Result.buildFailure("error", "这里需要处理异常"); // TODO 格式化发生的异常 } BizLicenseRES res = new BizLicenseRES(); res.setRegNo(ro.getResponse().getRegNo()); res.setVerifyRegNo(ro.getResponse().getVerifyRegNo()); return Result.of(res); } } ``` 遇到具体的业务场景,还是需要具体场景具体分析。 ## 具有官方SDK的情况 一般情况下,如果是我们平台封装的SDK, 基本可以做到开盒即用,比如平台短信发送SDK ``` java @Autowired private PlatformServiceI platformSvc; @GetMapping("/test1") @ResponseBody public Result test1() { CheckCaptchaDto dto = new CheckCaptchaDto(); dto.setCode("654321"); dto.setPhone("15140420103"); dto.setConfType("SMS-CHECK-CAPTCHA"); // dto.setConfCode("xh_sms_aliyun_01"); Optional res = platformSvc.sendCheckCaptcha(dto); if (!res.isPresent()) { return Result.buildFailure("test", "error"); } return Result.of(res.get()); } ``` 在平台中引入对应的pom后,即可注入对应的服务,然后再业务中直接使用短信发送服务。 如果是第三方SDK, 没有提供Spring Boot的注入功能,这个时候需要我们对SDK适配。比如承接上文,我们对腾讯OCR进行适配 ``` java @Component public class OcrServiceImpl implements OcrServiceI { @Value("${spring.service.ocr-tencent.secret-id}") private String secretId; @Value("${spring.service.ocr-tencent.secret-key}") private String secretKey; @Value("${spring.service.ocr-tencent.region:ap-shanghai}") private String region; private OcrClient getOcrClient() { // 实例化一个认证对象,入参需要传入腾讯云账户secretId,secretKey,此处还需注意密钥对的保密 // 密钥可前往https://console.cloud.tencent.com/cam/capi网站进行获取 Credential cred = new Credential(secretId, secretKey); HttpProfile httpProfile = new HttpProfile(); // 实例化一个http选项,可选的,没有特殊需求可以跳过 httpProfile.setEndpoint("ocr.tencentcloudapi.com"); ClientProfile clientProfile = new ClientProfile(); // 实例化一个client选项,可选的,没有特殊需求可以跳过 clientProfile.setHttpProfile(httpProfile); return new OcrClient(cred, region, clientProfile); // 实例化要请求产品的client对象,clientProfile是可选的 } @Override public Result verifyIdCard(IdCardDTO dto) { return null; } @Override public Result verifyBizLicense(BizLicenseDTO dto) { try{ OcrClient client = getOcrClient(); VerifyBizLicenseRequest req = new VerifyBizLicenseRequest(); req.setImageUrl(dto.getImageUrl()); req.setImageBase64(dto.getImageBase64()); VerifyBizLicenseResponse resp = client.VerifyBizLicense(req); BizLicenseRES res = new BizLicenseRES(); res.setRegNo(resp.getRegNo()); res.setVerifyRegNo(resp.getVerifyRegNo()); return Result.of(res); } catch (TencentCloudSDKException e) { return Result.buildFailure(e.getErrorCode(), "这里需要处理异常"); } } } ``` 这里看到,我们再serviceimpl中实现了client中定义的服务。这样,对于service的使用者,无需关注具体的实现。 以上的内容仅限Kratos框架推荐的实现方式,并不是必须的实现方式,再实际开发过程中,还是应该更加实际的开发场景进行实当的必要的修改。