这篇文章主要介绍“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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。