Python 官方文档:入门教程 => 点击学习
目录0 | 需求说明1 | 思路方案传统的api接口暴露方法泛化的方法2 | 具体实施OpenAPI 的 URL 规范代码实现0 | 需求说明 在如何动态的处理接口的返回数据 里提
在如何动态的处理接口的返回数据 里提到了我们的业务场景:服务A对接了服务B,服务C等服务的一些接口,然后由服务A统一暴露接口给到外部用户使用。
其中有个需求是:服务A可以动态的接入服务B/C的接口,对外暴露,并无需重启服务A,即支持API接口的动态添加。
传统的业务开发,使用 SpringBoot 的话,会把服务需要暴露的 API 接口写在 controller 层里,然后调用 service 层的接口方法,在实现层 implement 该 service 接口方法的具体实现函数。
@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"));
}
}
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 接口。
分为几个组成部分:
泛化的 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();
}
}
至此,服务A可以动态的接入服务B/C的接口,对外暴露,并无需重启服务A,即支持API接口的动态添加的业务需求实现完毕。
以上就是详解OpenAPI开发如何动态的添加接口实现的详细内容,更多关于OpenAPI动态添加接口的资料请关注编程网其它相关文章!
--结束END--
本文标题: 详解OpenAPI开发如何动态的添加接口实现
本文链接: https://www.lsjlt.com/news/209302.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-03-01
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0