From 3e2db37446f0b90d358e222e7a16a756bb2b0efa Mon Sep 17 00:00:00 2001 From: songyinyin Date: Sun, 12 Feb 2023 22:56:45 +0800 Subject: [PATCH] feat: powerjob-remote http support spring-webmvc --- powerjob-remote/pom.xml | 1 + .../powerjob-remote-impl-http-spring/pom.xml | 41 +++++++++++ .../powerjob/remote/http/HttpProtocol.java | 17 +++++ .../remote/http/HttpSpringCSInitializer.java | 70 +++++++++++++++++++ .../spring/PowerjobCSMethodProcessor.java | 54 ++++++++++++++ .../http/spring/SpringMvcTransporter.java | 57 +++++++++++++++ .../remote/http/spring/SpringUtils.java | 30 ++++++++ .../remote/http/vertx/VertxTransporter.java | 6 +- powerjob-worker-spring-boot-starter/pom.xml | 15 ++++ .../PowerJobRemoteAutoConfiguration.java | 40 +++++++++++ .../main/resources/META-INF/spring.factories | 3 +- 11 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 powerjob-remote/powerjob-remote-impl-http-spring/pom.xml create mode 100644 powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/HttpProtocol.java create mode 100644 powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/HttpSpringCSInitializer.java create mode 100644 powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/PowerjobCSMethodProcessor.java create mode 100644 powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/SpringMvcTransporter.java create mode 100644 powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/SpringUtils.java create mode 100644 powerjob-worker-spring-boot-starter/src/main/java/tech/powerjob/worker/autoconfigure/PowerJobRemoteAutoConfiguration.java diff --git a/powerjob-remote/pom.xml b/powerjob-remote/pom.xml index 5dd8343f..ca3b2791 100644 --- a/powerjob-remote/pom.xml +++ b/powerjob-remote/pom.xml @@ -14,6 +14,7 @@ powerjob-remote-benchmark powerjob-remote-impl-http powerjob-remote-impl-akka + powerjob-remote-impl-http-spring powerjob-remote diff --git a/powerjob-remote/powerjob-remote-impl-http-spring/pom.xml b/powerjob-remote/powerjob-remote-impl-http-spring/pom.xml new file mode 100644 index 00000000..ac8d613d --- /dev/null +++ b/powerjob-remote/powerjob-remote-impl-http-spring/pom.xml @@ -0,0 +1,41 @@ + + + + powerjob-remote + tech.powerjob + 4.0.0 + + 4.0.0 + + powerjob-remote-impl-http-spring + 4.3.0 + + + 8 + 8 + UTF-8 + + 5.3.23 + 4.3.0 + + + + + + tech.powerjob + powerjob-remote-framework + ${powerjob-remote-framework.version} + + + + org.springframework + spring-webmvc + ${spring.version} + compile + + + + + \ No newline at end of file diff --git a/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/HttpProtocol.java b/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/HttpProtocol.java new file mode 100644 index 00000000..2e874652 --- /dev/null +++ b/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/HttpProtocol.java @@ -0,0 +1,17 @@ +package tech.powerjob.remote.http; + +import tech.powerjob.remote.framework.transporter.Protocol; + +/** + * HttpProtocol + * + * @author tjq + * @since 2022/12/31 + */ +public class HttpProtocol implements Protocol { + + @Override + public String name() { + return tech.powerjob.common.enums.Protocol.HTTP.name(); + } +} diff --git a/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/HttpSpringCSInitializer.java b/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/HttpSpringCSInitializer.java new file mode 100644 index 00000000..b82e9f7a --- /dev/null +++ b/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/HttpSpringCSInitializer.java @@ -0,0 +1,70 @@ +package tech.powerjob.remote.http; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.util.pattern.PathPatternParser; +import tech.powerjob.remote.framework.actor.ActorInfo; +import tech.powerjob.remote.framework.actor.HandlerInfo; +import tech.powerjob.remote.framework.cs.CSInitializer; +import tech.powerjob.remote.framework.cs.CSInitializerConfig; +import tech.powerjob.remote.framework.transporter.Transporter; +import tech.powerjob.remote.http.spring.SpringMvcTransporter; +import tech.powerjob.remote.http.spring.SpringUtils; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.List; + +/** + * @author songyinyin + * @since 2023/2/11 19:55 + */ +public class HttpSpringCSInitializer implements CSInitializer { + + private RequestMappingHandlerMapping requestMappingHandlerMapping; + + @Override + public String type() { + return tech.powerjob.common.enums.Protocol.HTTP.name(); + } + + @Override + public void init(CSInitializerConfig config) { + this.requestMappingHandlerMapping = (RequestMappingHandlerMapping) SpringUtils.getBean("requestMappingHandlerMapping"); + } + + @Override + public Transporter buildTransporter() { + return new SpringMvcTransporter(); + } + + @Override + public void bindHandlers(List actorInfos) { + for (ActorInfo actorInfo : actorInfos) { + for (HandlerInfo handlerInfo : actorInfo.getHandlerInfos()) { + + RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration(); + options.setPatternParser(new PathPatternParser()); + RequestMappingInfo mapping = RequestMappingInfo.paths(handlerInfo.getLocation().toPath()) + .methods(RequestMethod.POST) + // 处理请求的提交内容类型 +// .consumes(MediaType.APPLICATION_JSON_VALUE) + // 返回的内容类型 + .produces(MediaType.APPLICATION_JSON_VALUE) + .options(options) + .build(); + Method method = handlerInfo.getMethod(); + requestMappingHandlerMapping.registerMapping(mapping, actorInfo.getActor(), method); + } + } + } + + @Override + public void close() throws IOException { + + } + + +} diff --git a/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/PowerjobCSMethodProcessor.java b/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/PowerjobCSMethodProcessor.java new file mode 100644 index 00000000..7b9b62c3 --- /dev/null +++ b/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/PowerjobCSMethodProcessor.java @@ -0,0 +1,54 @@ +package tech.powerjob.remote.http.spring; + +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; +import tech.powerjob.common.exception.PowerJobException; +import tech.powerjob.remote.framework.actor.Handler; + +import java.util.List; + +/** + * 带有 @Handler 注解的,接收的请求参数 使用 json 解析,等同于Spring中,在请求参数前使用注解:@RequestBody + * + * @author songyinyin + * @see RequestResponseBodyMethodProcessor + * @since 2023/2/12 18:02 + */ +public class PowerjobCSMethodProcessor implements HandlerMethodArgumentResolver { + + private HandlerMethodArgumentResolver requestResponseBodyMethodProcessor; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasMethodAnnotation(Handler.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + if (requestResponseBodyMethodProcessor == null) { + RequestMappingHandlerAdapter requestMappingHandlerAdapter = SpringUtils.getBean(RequestMappingHandlerAdapter.class); + this.requestResponseBodyMethodProcessor = getRequestResponseBodyMethodProcessor(requestMappingHandlerAdapter.getArgumentResolvers()); + } + if (requestResponseBodyMethodProcessor == null) { + throw new PowerJobException("requestResponseBodyMethodProcessor is null"); + } + return requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory); + } + + private HandlerMethodArgumentResolver getRequestResponseBodyMethodProcessor(List resolvers) { + if (resolvers == null) { + return null; + } + for (HandlerMethodArgumentResolver resolver : resolvers) { + if (resolver instanceof RequestResponseBodyMethodProcessor) { + return resolver; + } + } + return null; + } +} diff --git a/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/SpringMvcTransporter.java b/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/SpringMvcTransporter.java new file mode 100644 index 00000000..ee226f19 --- /dev/null +++ b/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/SpringMvcTransporter.java @@ -0,0 +1,57 @@ +package tech.powerjob.remote.http.spring; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import tech.powerjob.common.PowerSerializable; +import tech.powerjob.remote.framework.base.RemotingException; +import tech.powerjob.remote.framework.base.URL; +import tech.powerjob.remote.framework.transporter.Protocol; +import tech.powerjob.remote.framework.transporter.Transporter; +import tech.powerjob.remote.http.HttpProtocol; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +/** + * spring-webmvc 使用 RestTemplate 发送http请求,后续兼容 spring-webflux 后,部分请求可以是非阻塞式的 + * + * @author songyinyin + * @since 2023/2/12 11:43 + */ +public class SpringMvcTransporter implements Transporter { + + private static final Protocol PROTOCOL = new HttpProtocol(); + + private final RestTemplate restTemplate = new RestTemplate(); + + @Override + public Protocol getProtocol() { + return PROTOCOL; + } + + @Override + public void tell(URL url, PowerSerializable request) { + String fullUrl = getFullUrl(url); + restTemplate.postForEntity(fullUrl, request, String.class); + } + + @Override + public CompletionStage ask(URL url, PowerSerializable request, Class clz) throws RemotingException { + String fullUrl = getFullUrl(url); + ResponseEntity responseEntity = restTemplate.postForEntity(fullUrl, request, clz); + // throw exception + final int statusCode = responseEntity.getStatusCodeValue(); + if (statusCode != HttpStatus.OK.value()) { + // CompletableFuture.get() 时会传递抛出该异常 + throw new RemotingException(String.format("request [url:%s] failed, status: %d, msg: %s", + fullUrl, statusCode, responseEntity.getBody() + )); + } + return CompletableFuture.completedFuture(responseEntity.getBody()); + } + + private String getFullUrl(URL url) { + return "http://" + url.getAddress().toFullAddress() + url.getLocation().toPath(); + } +} diff --git a/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/SpringUtils.java b/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/SpringUtils.java new file mode 100644 index 00000000..f30d899f --- /dev/null +++ b/powerjob-remote/powerjob-remote-impl-http-spring/src/main/java/tech/powerjob/remote/http/spring/SpringUtils.java @@ -0,0 +1,30 @@ +package tech.powerjob.remote.http.spring; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * Spring ApplicationContext 工具类 + * + * @author tjq + * @since 2020/4/7 + */ +public class SpringUtils implements ApplicationContextAware { + + private static ApplicationContext context; + + public static T getBean(Class clz) { + return context.getBean(clz); + } + + public static Object getBean(String beanName) { + return context.getBean(beanName); + } + + @Override + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + context = ctx; + } +} + diff --git a/powerjob-remote/powerjob-remote-impl-http/src/main/java/tech/powerjob/remote/http/vertx/VertxTransporter.java b/powerjob-remote/powerjob-remote-impl-http/src/main/java/tech/powerjob/remote/http/vertx/VertxTransporter.java index e4b545fc..38da9160 100644 --- a/powerjob-remote/powerjob-remote-impl-http/src/main/java/tech/powerjob/remote/http/vertx/VertxTransporter.java +++ b/powerjob-remote/powerjob-remote-impl-http/src/main/java/tech/powerjob/remote/http/vertx/VertxTransporter.java @@ -57,7 +57,11 @@ public class VertxTransporter implements Transporter { // 获取远程服务器的HTTP连接 Future httpClientRequestFuture = httpClient.request(requestOptions); // 转换 -> 发送请求获取响应 - Future responseFuture = httpClientRequestFuture.compose(httpClientRequest -> httpClientRequest.send(JsonObject.mapFrom(request).toBuffer())); + Future responseFuture = httpClientRequestFuture.compose(httpClientRequest -> + httpClientRequest + .putHeader("content-type", "application/json") + .send(JsonObject.mapFrom(request).toBuffer()) + ); return responseFuture.compose(httpClientResponse -> { // throw exception final int statusCode = httpClientResponse.statusCode(); diff --git a/powerjob-worker-spring-boot-starter/pom.xml b/powerjob-worker-spring-boot-starter/pom.xml index be0645ce..81af3a86 100644 --- a/powerjob-worker-spring-boot-starter/pom.xml +++ b/powerjob-worker-spring-boot-starter/pom.xml @@ -24,6 +24,21 @@ tech.powerjob powerjob-worker ${powerjob.worker.version} + + + tech.powerjob + powerjob-remote-impl-http + + + tech.powerjob + powerjob-remote-impl-akka + + + + + tech.powerjob + powerjob-remote-impl-http-spring + ${powerjob.worker.version} diff --git a/powerjob-worker-spring-boot-starter/src/main/java/tech/powerjob/worker/autoconfigure/PowerJobRemoteAutoConfiguration.java b/powerjob-worker-spring-boot-starter/src/main/java/tech/powerjob/worker/autoconfigure/PowerJobRemoteAutoConfiguration.java new file mode 100644 index 00000000..e0c5ab6c --- /dev/null +++ b/powerjob-worker-spring-boot-starter/src/main/java/tech/powerjob/worker/autoconfigure/PowerJobRemoteAutoConfiguration.java @@ -0,0 +1,40 @@ +package tech.powerjob.worker.autoconfigure; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import tech.powerjob.remote.http.HttpSpringCSInitializer; +import tech.powerjob.remote.http.spring.PowerjobCSMethodProcessor; +import tech.powerjob.remote.http.spring.SpringUtils; + +import java.util.List; + +/** + * @author songyinyin + * @since 2023/2/12 22:23 + */ +@Configuration +@AutoConfigureBefore(PowerJobAutoConfiguration.class) +@ConditionalOnClass(HttpSpringCSInitializer.class) +public class PowerJobRemoteAutoConfiguration implements WebMvcConfigurer { + + @Bean + public SpringUtils powerJobSpringUtils() { + return new SpringUtils(); + } + + @Bean + @ConditionalOnMissingBean + public PowerjobCSMethodProcessor powerjobCSMethodProcessor() { + return new PowerjobCSMethodProcessor(); + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(powerjobCSMethodProcessor()); + } +} diff --git a/powerjob-worker-spring-boot-starter/src/main/resources/META-INF/spring.factories b/powerjob-worker-spring-boot-starter/src/main/resources/META-INF/spring.factories index 611af95f..5e1bef33 100644 --- a/powerjob-worker-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ b/powerjob-worker-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -1,2 +1,3 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - tech.powerjob.worker.autoconfigure.PowerJobAutoConfiguration \ No newline at end of file + tech.powerjob.worker.autoconfigure.PowerJobAutoConfiguration,\ + tech.powerjob.worker.autoconfigure.PowerJobRemoteAutoConfiguration