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>
|
<version>${commonmark.version}</version>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package cc.ryanc.halo.config;
|
package cc.ryanc.halo.config;
|
||||||
|
|
||||||
import cc.ryanc.halo.config.properties.HaloProperties;
|
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.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.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Halo configuration.
|
* Halo configuration.
|
||||||
|
@ -13,4 +18,35 @@ import org.springframework.context.annotation.Configuration;
|
||||||
@EnableConfigurationProperties(HaloProperties.class)
|
@EnableConfigurationProperties(HaloProperties.class)
|
||||||
public class HaloConfiguration {
|
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;
|
package cc.ryanc.halo.config;
|
||||||
|
|
||||||
import cc.ryanc.halo.filter.CorsFilter;
|
import cc.ryanc.halo.config.properties.HaloProperties;
|
||||||
import cc.ryanc.halo.filter.LogFilter;
|
|
||||||
import cc.ryanc.halo.web.interceptor.ApiInterceptor;
|
import cc.ryanc.halo.web.interceptor.ApiInterceptor;
|
||||||
import cc.ryanc.halo.web.interceptor.InstallInterceptor;
|
import cc.ryanc.halo.web.interceptor.InstallInterceptor;
|
||||||
import cc.ryanc.halo.web.interceptor.LocaleInterceptor;
|
import cc.ryanc.halo.web.interceptor.LocaleInterceptor;
|
||||||
import cc.ryanc.halo.web.interceptor.LoginInterceptor;
|
import cc.ryanc.halo.web.interceptor.LoginInterceptor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.PropertySource;
|
import org.springframework.context.annotation.PropertySource;
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.web.servlet.LocaleResolver;
|
import org.springframework.web.servlet.LocaleResolver;
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
@ -51,6 +48,9 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||||
@Autowired
|
@Autowired
|
||||||
private LocaleInterceptor localeInterceptor;
|
private LocaleInterceptor localeInterceptor;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HaloProperties haloProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册拦截器
|
* 注册拦截器
|
||||||
*
|
*
|
||||||
|
@ -101,38 +101,14 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
|
||||||
.addResourceLocations("classpath:/static/halo-backend/images/favicon.ico");
|
.addResourceLocations("classpath:/static/halo-backend/images/favicon.ico");
|
||||||
registry.addResourceHandler("/backup/**")
|
registry.addResourceHandler("/backup/**")
|
||||||
.addResourceLocations("file:///" + System.getProperties().getProperty("user.home") + "/halo/backup/");
|
.addResourceLocations("file:///" + System.getProperties().getProperty("user.home") + "/halo/backup/");
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (!haloProperties.getDocDisabled()) {
|
||||||
* Creates a CorsFilter.
|
// If doc is enable
|
||||||
*
|
registry.addResourceHandler("swagger-ui.html")
|
||||||
* @return Cors filter registration bean
|
.addResourceLocations("classpath:/META-INF/resources/");
|
||||||
*/
|
registry.addResourceHandler("/webjars/**")
|
||||||
@Bean
|
.addResourceLocations("classpath:/META-INF/resources/webjars/");
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue