refactor: support for custom api group in plugin controllers (#4065)

#### What type of PR is this?
/kind improvement
/area core
/milestone 2.7.x

#### What this PR does / why we need it:
插件的 Controllers 支持自定义 API Group
如:
```java
@RestController
@ApiVersion("fake.halo.run/v1")
@RequestMapping("/fake")
public class DemoController {
}
```
则生成路由为 `/apis/fake.halo.run/v1/fake`
如果没有 group 默认兼容以前的为 `/apis/api.plugin.halo.run/{version}/plugins/{pluginName}/**`
```java
@RestController
@ApiVersion("v1alpha1")
@RequestMapping("/fake")
public class DemoController {
}
```

#### Which issue(s) this PR fixes:
Fixes #4053

#### Does this PR introduce a user-facing change?
```release-note
插件的 Controllers 支持自定义 API Group
```
pull/4091/head
guqing 2023-06-26 22:01:57 +08:00 committed by GitHub
parent e13beb4cd1
commit ff7ab4e4f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 24 additions and 15 deletions

View File

@ -7,7 +7,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.pf4j.PluginRuntimeException;
import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.AopUtils;
import org.springframework.core.MethodIntrospector; import org.springframework.core.MethodIntrospector;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -117,8 +116,7 @@ public class PluginRequestMappingHandlerMapping extends RequestMappingHandlerMap
if (info != null) { if (info != null) {
ApiVersion apiVersion = handlerType.getAnnotation(ApiVersion.class); ApiVersion apiVersion = handlerType.getAnnotation(ApiVersion.class);
if (apiVersion == null) { if (apiVersion == null) {
throw new PluginRuntimeException( return info;
"The handler [" + handlerType + "] is missing @ApiVersion annotation.");
} }
info = RequestMappingInfo.paths(buildPrefix(pluginId, apiVersion.value())).build() info = RequestMappingInfo.paths(buildPrefix(pluginId, apiVersion.value())).build()
.combine(info); .combine(info);
@ -126,9 +124,14 @@ public class PluginRequestMappingHandlerMapping extends RequestMappingHandlerMap
return info; return info;
} }
protected String buildPrefix(String pluginId, String version) { protected String buildPrefix(String pluginName, String apiVersion) {
GroupVersion groupVersion = GroupVersion.parseAPIVersion(version); GroupVersion groupVersion = GroupVersion.parseAPIVersion(apiVersion);
if (StringUtils.hasText(groupVersion.group())) {
// apis/{group}/{version}
return String.format("/apis/%s/%s", groupVersion.group(), groupVersion.version());
}
// apis/api.plugin.halo.run/{version}/plugins/{pluginName}
return String.format("/apis/api.plugin.halo.run/%s/plugins/%s", groupVersion.version(), return String.format("/apis/api.plugin.halo.run/%s/plugins/%s", groupVersion.version(),
pluginId); pluginName);
} }
} }

View File

@ -1,7 +1,6 @@
package run.halo.app.plugin; package run.halo.app.plugin;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.springframework.mock.http.server.reactive.MockServerHttpRequest.get; import static org.springframework.mock.http.server.reactive.MockServerHttpRequest.get;
import static org.springframework.mock.http.server.reactive.MockServerHttpRequest.post; import static org.springframework.mock.http.server.reactive.MockServerHttpRequest.post;
@ -20,7 +19,6 @@ import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.pf4j.PluginRuntimeException;
import org.springframework.core.MethodIntrospector; import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@ -74,15 +72,14 @@ class PluginRequestMappingHandlerMappingTest {
} }
@Test @Test
public void shouldFailWhenMissingApiVersion() throws Exception { public void shouldKeepRawWhenMissingApiVersion() throws Exception {
Method method = AppleMissingApiVersionController.class.getMethod("getName"); Method method = AppleMissingApiVersionController.class.getMethod("getName");
assertThatThrownBy(() -> RequestMappingInfo info =
this.handlerMapping.getPluginMappingForMethod("fakePlugin", method, this.handlerMapping.getPluginMappingForMethod("fakePlugin", method,
AppleMissingApiVersionController.class)).isInstanceOf(PluginRuntimeException.class) AppleMissingApiVersionController.class);
.hasMessage(
"The handler [class run.halo.app.plugin" assertThat(info.getPatternsCondition().getPatterns())
+ ".PluginRequestMappingHandlerMappingTest$AppleMissingApiVersionController] " .isEqualTo(Collections.singleton(new PathPatternParser().parse("/apples")));
+ "is missing @ApiVersion annotation.");
} }
@Test @Test
@ -174,6 +171,15 @@ class PluginRequestMappingHandlerMappingTest {
Set.of(HttpMethod.GET, HttpMethod.HEAD))); Set.of(HttpMethod.GET, HttpMethod.HEAD)));
} }
@Test
void buildPrefix() {
String s = handlerMapping.buildPrefix("fakePlugin", "v1");
assertThat(s).isEqualTo("/apis/api.plugin.halo.run/v1/plugins/fakePlugin");
s = handlerMapping.buildPrefix("fakePlugin", "fake.halo.run/v1alpha1");
assertThat(s).isEqualTo("/apis/fake.halo.run/v1alpha1");
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> void assertError(Mono<Object> mono, final Class<T> exceptionClass, private <T> void assertError(Mono<Object> mono, final Class<T> exceptionClass,
final Consumer<T> consumer) { final Consumer<T> consumer) {