mirror of https://github.com/halo-dev/halo
Add swagger support
parent
bf65f63627
commit
faa616d992
13
pom.xml
13
pom.xml
|
@ -190,6 +190,19 @@
|
|||
<version>${commonmark.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package cc.ryanc.halo.config;
|
||||
|
||||
import cc.ryanc.halo.config.properties.HaloProperties;
|
||||
import cc.ryanc.halo.filter.CorsFilter;
|
||||
import cc.ryanc.halo.filter.LogFilter;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* Halo configuration.
|
||||
|
@ -13,4 +18,35 @@ import org.springframework.context.annotation.Configuration;
|
|||
@EnableConfigurationProperties(HaloProperties.class)
|
||||
public class HaloConfiguration {
|
||||
|
||||
/**
|
||||
* Creates a CorsFilter.
|
||||
*
|
||||
* @return Cors filter registration bean
|
||||
*/
|
||||
@Bean
|
||||
FilterRegistrationBean<CorsFilter> corsFilter() {
|
||||
FilterRegistrationBean<CorsFilter> corsFilter = new FilterRegistrationBean<>();
|
||||
|
||||
corsFilter.setOrder(Ordered.HIGHEST_PRECEDENCE + 10);
|
||||
corsFilter.setFilter(new CorsFilter());
|
||||
corsFilter.addUrlPatterns("/api/*");
|
||||
|
||||
return corsFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LogFilter.
|
||||
*
|
||||
* @return Log filter registration bean
|
||||
*/
|
||||
@Bean
|
||||
FilterRegistrationBean<LogFilter> logFilter() {
|
||||
FilterRegistrationBean<LogFilter> logFilter = new FilterRegistrationBean<>();
|
||||
|
||||
logFilter.setOrder(Ordered.HIGHEST_PRECEDENCE + 9);
|
||||
logFilter.setFilter(new LogFilter());
|
||||
logFilter.addUrlPatterns("/api/*", "/admin/*");
|
||||
|
||||
return logFilter;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
package cc.ryanc.halo.config;
|
||||
|
||||
import cc.ryanc.halo.config.properties.HaloProperties;
|
||||
import com.fasterxml.classmate.TypeResolver;
|
||||
import io.swagger.models.auth.In;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import springfox.documentation.builders.*;
|
||||
import springfox.documentation.schema.AlternateTypeRule;
|
||||
import springfox.documentation.schema.AlternateTypeRuleConvention;
|
||||
import springfox.documentation.service.*;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger.web.SecurityConfiguration;
|
||||
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static springfox.documentation.schema.AlternateTypeRules.newRule;
|
||||
|
||||
/**
|
||||
* Swagger configuration.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@EnableSwagger2
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class SwaggerConfiguration {
|
||||
|
||||
private final HaloProperties haloProperties;
|
||||
|
||||
private final List<ResponseMessage> globalResponses = Arrays.asList(
|
||||
new ResponseMessageBuilder().code(200).message("Success").build(),
|
||||
new ResponseMessageBuilder().code(400).message("Bad request").build(),
|
||||
new ResponseMessageBuilder().code(401).message("Unauthorized").build(),
|
||||
new ResponseMessageBuilder().code(403).message("Forbidden").build(),
|
||||
new ResponseMessageBuilder().code(404).message("Not found").build(),
|
||||
new ResponseMessageBuilder().code(500).message("Internal server error").build());
|
||||
|
||||
public SwaggerConfiguration(HaloProperties haloProperties) {
|
||||
this.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket haloDefaultApi() {
|
||||
log.debug("Doc disabled: [{}]", haloProperties.getDocDisabled());
|
||||
return buildApiDocket("cc.ryanc.halo.default",
|
||||
"cc.ryanc.halo.web.controller.api",
|
||||
"/api/**")
|
||||
.enable(!haloProperties.getDocDisabled());
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityConfiguration security() {
|
||||
return SecurityConfigurationBuilder.builder()
|
||||
.clientId("halo-app-client-id")
|
||||
.clientSecret("halo-app-client-secret")
|
||||
.realm("halo-app-realm")
|
||||
.appName("halo-app")
|
||||
.scopeSeparator(",")
|
||||
.additionalQueryStringParams(null)
|
||||
.useBasicAuthenticationWithAccessCodeGrant(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Docket buildApiDocket(@NonNull String groupName, @NonNull String basePackage, @NonNull String antPattern) {
|
||||
Assert.hasText(groupName, "Group name must not be blank");
|
||||
Assert.hasText(basePackage, "Base package must not be blank");
|
||||
Assert.hasText(antPattern, "Ant pattern must not be blank");
|
||||
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName(groupName)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage(basePackage))
|
||||
.paths(PathSelectors.ant(antPattern))
|
||||
.build()
|
||||
.apiInfo(apiInfo())
|
||||
.securitySchemes(Arrays.asList(apiKeys()))
|
||||
.securityContexts(Arrays.asList(securityContext()))
|
||||
.useDefaultResponseMessages(false)
|
||||
.globalResponseMessage(RequestMethod.GET, globalResponses)
|
||||
.globalResponseMessage(RequestMethod.POST, globalResponses)
|
||||
.globalResponseMessage(RequestMethod.DELETE, globalResponses)
|
||||
.globalResponseMessage(RequestMethod.PUT, globalResponses)
|
||||
.directModelSubstitute(Temporal.class, String.class);
|
||||
}
|
||||
|
||||
private ApiKey apiKeys() {
|
||||
return new ApiKey("TOKEN ACCESS", HttpHeaders.AUTHORIZATION, In.HEADER.name());
|
||||
}
|
||||
|
||||
private SecurityContext securityContext() {
|
||||
return SecurityContext.builder()
|
||||
.securityReferences(defaultAuth())
|
||||
.forPaths(PathSelectors.regex("/api/.*"))
|
||||
.build();
|
||||
}
|
||||
|
||||
private List<SecurityReference> defaultAuth() {
|
||||
AuthorizationScope[] authorizationScopes = {new AuthorizationScope("global", "accessEverything")};
|
||||
return Arrays.asList(new SecurityReference("TOKEN ACCESS", authorizationScopes));
|
||||
}
|
||||
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("Halo API Documentation")
|
||||
.description("Documentation for Halo API")
|
||||
.version("v0.4.2")
|
||||
.termsOfServiceUrl("https://ryanc.cc/")
|
||||
.contact(new Contact("RYAN0UP", "https://ryanc.cc/", "i#ryanc.cc"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) {
|
||||
return new AlternateTypeRuleConvention() {
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AlternateTypeRule> rules() {
|
||||
return Arrays.asList(
|
||||
newRule(resolver.resolve(Pageable.class), resolver.resolve(pageableMixin())),
|
||||
newRule(resolver.resolve(Sort.class), resolver.resolve(sortMixin())));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* For controller parameter(like eg: HttpServletRequest, ModelView ...).
|
||||
*
|
||||
* @param clazz controller parameter class type must not be null
|
||||
* @return empty type
|
||||
*/
|
||||
private Type emptyMixin(Class<?> clazz) {
|
||||
Assert.notNull(clazz, "class type must not be null");
|
||||
|
||||
return new AlternateTypeBuilder()
|
||||
.fullyQualifiedClassName(String.format("%s.generated.%s", clazz.getPackage().getName(), clazz.getSimpleName()))
|
||||
.withProperties(Collections.emptyList())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Type sortMixin() {
|
||||
return new AlternateTypeBuilder()
|
||||
.fullyQualifiedClassName(String.format("%s.generated.%s", Sort.class.getPackage().getName(), Sort.class.getSimpleName()))
|
||||
.withProperties(Arrays.asList(property(String[].class, "sort")))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Type pageableMixin() {
|
||||
return new AlternateTypeBuilder()
|
||||
.fullyQualifiedClassName(String.format("%s.generated.%s", Pageable.class.getPackage().getName(), Pageable.class.getSimpleName()))
|
||||
.withProperties(Arrays.asList(property(Integer.class, "page"), property(Integer.class, "size"), property(String[].class, "sort")))
|
||||
.build();
|
||||
}
|
||||
|
||||
private AlternateTypePropertyBuilder property(Class<?> type, String name) {
|
||||
return new AlternateTypePropertyBuilder()
|
||||
.withName(name)
|
||||
.withType(type)
|
||||
.withCanRead(true)
|
||||
.withCanWrite(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +1,16 @@
|
|||
package cc.ryanc.halo.config;
|
||||
|
||||
import cc.ryanc.halo.filter.CorsFilter;
|
||||
import cc.ryanc.halo.filter.LogFilter;
|
||||
import cc.ryanc.halo.config.properties.HaloProperties;
|
||||
import cc.ryanc.halo.web.interceptor.ApiInterceptor;
|
||||
import cc.ryanc.halo.web.interceptor.InstallInterceptor;
|
||||
import cc.ryanc.halo.web.interceptor.LocaleInterceptor;
|
||||
import cc.ryanc.halo.web.interceptor.LoginInterceptor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
|
@ -51,6 +48,9 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
|||
@Autowired
|
||||
private LocaleInterceptor localeInterceptor;
|
||||
|
||||
@Autowired
|
||||
private HaloProperties haloProperties;
|
||||
|
||||
/**
|
||||
* 注册拦截器
|
||||
*
|
||||
|
@ -101,38 +101,14 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
|||
.addResourceLocations("classpath:/static/halo-backend/images/favicon.ico");
|
||||
registry.addResourceHandler("/backup/**")
|
||||
.addResourceLocations("file:///" + System.getProperties().getProperty("user.home") + "/halo/backup/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CorsFilter.
|
||||
*
|
||||
* @return Cors filter registration bean
|
||||
*/
|
||||
@Bean
|
||||
FilterRegistrationBean<CorsFilter> corsFilter() {
|
||||
FilterRegistrationBean<CorsFilter> corsFilter = new FilterRegistrationBean<>();
|
||||
|
||||
corsFilter.setOrder(Ordered.HIGHEST_PRECEDENCE + 10);
|
||||
corsFilter.setFilter(new CorsFilter());
|
||||
corsFilter.addUrlPatterns("/api/*");
|
||||
|
||||
return corsFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LogFilter.
|
||||
*
|
||||
* @return Log filter registration bean
|
||||
*/
|
||||
@Bean
|
||||
FilterRegistrationBean<LogFilter> logFilter() {
|
||||
FilterRegistrationBean<LogFilter> logFilter = new FilterRegistrationBean<>();
|
||||
|
||||
logFilter.setOrder(Ordered.HIGHEST_PRECEDENCE + 9);
|
||||
logFilter.setFilter(new LogFilter());
|
||||
logFilter.addUrlPatterns("/api/*", "/admin/*");
|
||||
|
||||
return logFilter;
|
||||
if (!haloProperties.getDocDisabled()) {
|
||||
// If doc is enable
|
||||
registry.addResourceHandler("swagger-ui.html")
|
||||
.addResourceLocations("classpath:/META-INF/resources/");
|
||||
registry.addResourceHandler("/webjars/**")
|
||||
.addResourceLocations("classpath:/META-INF/resources/webjars/");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue