您好,登錄后才能下訂單哦!
這篇文章主要介紹“OpenAPI開發怎么動態的添加接口”,在日常操作中,相信很多人在OpenAPI開發怎么動態的添加接口問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”OpenAPI開發怎么動態的添加接口”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
在如何動態的處理接口的返回數據 里提到了我們的業務場景:服務A對接了服務B,服務C等服務的一些接口,然后由服務A統一暴露接口給到外部用戶使用。
其中有個需求是:服務A可以動態的接入服務B/C的接口,對外暴露,并無需重啟服務A,即支持API接口的動態添加。
傳統的業務開發,使用 springboot 的話,會把服務需要暴露的 API 接口寫在 controller 層里,然后調用 service 層的接口方法,在實現層 implement 該 service 接口方法的具體實現函數。
controller層
@RestController @RequestMapping({"/v1"}) @Slf4j public class HelloController { @Autowired private HelloService helloService; @PostMapping(path = {"/hello"}) public String hello() { return Optional.ofNullable(helloService.hello()) .map(ret -> new ResponseEntity<>(ret, HttpStatus.OK)) .orElseThrow(() -> new MMException("something wrong")); } }
service層
public interface HelloService { String hello(); }
實現層
@Service public class HelloServiceImpl implements HelloService { @Override public String hello(){ return "hello world"; } }
我們可以看到,在 controller 層 API 接口的 subpath 是寫好的,構建部署之后,服務具有的 API 接口列表就固定了。如果需要新增 API 接口,就需要重新在 controller 里寫代碼,編譯構建,再部署上線。這樣效率很低,而且每次部署,都會影響到線上服務,也不安全。
對于 OpenAPI 的業務場景來說,其實是不關心接入的 API subpath 具體是什么,只關心能不能通過 subpath 找到對應的需要轉發的服務。
那么,在 controller 層,就可以不根據具體的 subpath 來匹配對應的服務,而是通過請求的方法get、post、put + 通配符的方式來分類接收外部請求,然后利用 AOP切面 和 Threadlocal 來處理和傳遞 subpath 攜帶的信息,給到實現層,最終在實現層分發請求到各個業務服務的 API 接口。
分為幾個組成部分:
http method: 請求方法,get、post、put、delete等
http scheme: http or https
OpenAPI統一域名: 外部訪問 OpenAPI 的統一域名
資源訪問類型: 訪問的資源,api、web等
OpenAPI版本號: OpenAPI 服務自身的版本號
內部服務的名稱: OpenAPI 對接的內部服務名稱(標識)
內部服務的path: 對接內部服務API的 subpath
泛化的 controller 層實現 以Get、Post請求為例:
@RestController @RequestMapping({"/v1"}) @Slf4j @ServicePath public class OpenApiController { @Autowired private OpenApiService openApiService; @ApiOperation("OpenAPI POST接收") @PostMapping(path = {"/**"}) public ResponseEntity<ReturnBase> filterPost(@Validated @RequestBody(required = false) Map<String, Object> reqMap) { return Optional.ofNullable(openApiService.filter(reqMap, null)) .map(ret -> new ResponseEntity<>(ret, HttpStatus.OK)) .orElseThrow(() -> new MMException("error.openapi.filter", ReturnEnum.C_GENERAL_BUSINESS_ERROR.getMsgCode())); } @ApiOperation("OpenAPI GET接收") @GetMapping(path = {"/**"}) public ResponseEntity<ReturnBase> filterGet(@RequestParam(required = false) MultiValueMap<String, String> params) { return Optional.ofNullable(openApiService.filter(null, params)) .map(ret -> new ResponseEntity<>(ret, HttpStatus.OK)) .orElseThrow(() -> new MMException("error.openapi.filter", ReturnEnum.C_GENERAL_BUSINESS_ERROR.getMsgCode())); } }
service層和service實現層
public interface OpenApiService { ReturnBase filter(Map<String, Object> reqBodyMap, MultiValueMap<String, String> reqGetParamsMap); } @Service @Slf4j public class OpenApiServiceImpl implements OpenApiService { @Override public ReturnBase filter(Map<String, Object> reqBodyMap, MultiValueMap<String, String> reqGetParamsMap) { String svcName = (String) OpenapiThreadlocal.getServiceParams().get(BizConstant.SVC_NAME); String svcPathPublic = (String) OpenapiThreadlocal.getServiceParams().get(BizConstant.SVC_PATH_PUBLIC); return doBizHandler(svcName, svcPathPublic); } }
AOP切面和注解
@Aspect @Component @Slf4j @Order(1) public class ServicePathAspect { static final Pattern PATTERN = Pattern.compile("/v\\d+(/.+)"); @Resource private CustomProperty customProperty; @Pointcut("@within(org.xxx.annotation.ServicePath)") public void servicePathOnClass() {} @Pointcut("@annotation(org.xxx.annotation.ServicePath)") public void servicePathOnMethod() { } @Before(value = "servicePathOnClass() || servicePathOnMethod()") public void before() { HttpServletRequest hsr = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String reqUri = hsr.getRequestURI(); String httpMethod = hsr.getMethod(); if (StrUtil.isEmpty(reqUri)) { log.error("request uri is empty"); throw new MMException(ReturnEnum.A_PARAM_VALIDATION_ERROR); } Matcher matcher = PATTERN.matcher(reqUri); String servicePath = ""; while (matcher.find()) { servicePath = matcher.group(1); } if (StrUtil.isEmpty(servicePath)) { log.error("can't parse service path from {}", reqUri); throw new MMException(ReturnEnum.A_PARAM_VALIDATION_ERROR); } String[] split = servicePath.split("\\/"); if (split.length < 3) { log.error("api format error: {}", servicePath); throw new MMException(ReturnEnum.A_PARAM_VALIDATION_ERROR); } String serviceName = split[1]; servicePath = servicePath.substring(serviceName.length() + 1); Map<String, Object> map = Maps.newHashMap(); map.put(BizConstant.SVC_NAME, serviceName); map.put(BizConstant.SVC_PATH_PUBLIC, servicePath); map.put(BizConstant.START_TIMESTAMP, start); OpenapiThreadlocal.addServiceParams(map); } } @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ServicePath { }
Threadlocal工具
public class OpenapiThreadlocal { private final static ThreadLocal<Map<String,Object>> SERVICE_PARAMS_HOLDER = new ThreadLocal<>(); public static void addServiceParams(Map<String, Object> svcParamsMap) { SERVICE_PARAMS_HOLDER.set(svcParamsMap); } public static Map<String, Object> getServiceParams() { return SERVICE_PARAMS_HOLDER.get(); } public static void removeServiceParams() { SERVICE_PARAMS_HOLDER.remove(); } }
到此,關于“OpenAPI開發怎么動態的添加接口”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。