diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/Result.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/Result.java index a396fd1..02bd8a1 100644 --- a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/Result.java +++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/Result.java @@ -3,7 +3,6 @@ package com.aivfo.el.starter.base; import com.aivfo.el.starter.base.utils.StringPool; import com.aivfo.el.starter.base.utils.StringUtils; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.swagger.annotations.ApiModelProperty; import lombok.Getter; import lombok.NoArgsConstructor; @@ -41,7 +40,6 @@ import java.io.Serializable; @ToString @NoArgsConstructor @SuppressWarnings("all") -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public abstract class Result implements Serializable { /** diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/pom.xml b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/pom.xml index 31aff68..212cf09 100644 --- a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/pom.xml +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/pom.xml @@ -11,9 +11,33 @@ aivfo-doc-spring-boot-autoconfigure - - 11 - 11 - + + + org.springframework + spring-context + + + org.springframework.boot + spring-boot + + + org.springframework.boot + spring-boot-autoconfigure + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter + + + com.aivfo + aivfo-doc-spring-boot-core + 1.0.0-SNAPSHOT + + \ No newline at end of file diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/knife4j/Knife4jAutoConfiguration.java b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/knife4j/Knife4jAutoConfiguration.java new file mode 100644 index 0000000..052d3ca --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/knife4j/Knife4jAutoConfiguration.java @@ -0,0 +1,147 @@ +package com.aivfo.doc.autoconfigure.knife4j; + +import com.aivfo.doc.core.common.schema.AivfoOperationModelsProviderPlugin; +import com.aivfo.doc.core.common.schema.AivfoOperationResponseMessagePlugin; +import com.aivfo.doc.core.knife4j.EnableKnife4j; +import com.github.xiaoymin.knife4j.core.extend.OpenApiExtendSetting; +import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver; +import com.github.xiaoymin.knife4j.spring.filter.ProductionSecurityFilter; +import com.github.xiaoymin.knife4j.spring.filter.SecurityBasicAuthFilter; +import com.github.xiaoymin.knife4j.spring.model.MarkdownFiles; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.DispatcherServlet; +import springfox.documentation.swagger2.web.Swagger2ControllerWebMvc; + +import javax.servlet.Servlet; + +/** + * @author: wangyl + * @date: 2023/5/16 + * @description: Knife4j 基础自动配置类 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(value = { + EnableKnife4j.class, + Servlet.class, + DispatcherServlet.class, + Swagger2ControllerWebMvc.class +}) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@EnableConfigurationProperties(value = {Knife4jProperties.class}) +@ComponentScan(basePackages = "com.github.xiaoymin.knife4j.spring.plugin") +@Slf4j +public class Knife4jAutoConfiguration { + + /** + * 配置Cors + * + * @return cors filter + * @since 2.0.4 + */ + @Bean + @ConditionalOnMissingBean(CorsFilter.class) + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsConfiguration.setAllowCredentials(true); + corsConfiguration.addAllowedOrigin("*"); + corsConfiguration.addAllowedHeader("*"); + corsConfiguration.addAllowedMethod("*"); + corsConfiguration.setMaxAge(10000L); + source.registerCorsConfiguration("/**", corsConfiguration); + return new CorsFilter(source); + } + + /** + * Markdown resolver + * + * @param knife4jProperties knife 4 j properties + * @return the open api extension resolver + * @since 1.4.0 + */ + @Bean(initMethod = "start") + @ConditionalOnMissingBean(OpenApiExtensionResolver.class) + @ConditionalOnProperty(name = "knife4j.enable", havingValue = "true") + public OpenApiExtensionResolver markdownResolver(Knife4jProperties knife4jProperties) { + OpenApiExtendSetting setting = knife4jProperties.getSetting(); + if (setting == null) { + setting = new OpenApiExtendSetting(); + } + return new OpenApiExtensionResolver(setting, knife4jProperties.getDocuments()); + } + + /** + * 初始化自定义Markdown特性 + * + * @param knife4jProperties 配置文件 + * @return markdownFiles markdown files + * @since 1.4.0 + */ + @Bean(initMethod = "init") + public MarkdownFiles markdownFiles(@NotNull Knife4jProperties knife4jProperties) { + return new MarkdownFiles(knife4jProperties.getMarkdowns() == null ? "" : knife4jProperties.getMarkdowns()); + } + + /** + * Security basic auth filter + * + * @param knife4jProperties knife 4 j properties + * @return the security basic auth filter + * @since 1.4.0 + */ + @Bean + public SecurityBasicAuthFilter securityBasicAuthFilter(@NotNull Knife4jProperties knife4jProperties) { + + return new SecurityBasicAuthFilter(knife4jProperties.getBasic().isEnable(), + knife4jProperties.getBasic().getUsername(), + knife4jProperties.getBasic().getPassword()); + } + + /** + * Production security filter + * + * @return the production security filter + * @since 1.4.0 + */ + @Bean + public ProductionSecurityFilter productionSecurityFilter() { + return new ProductionSecurityFilter(false); + } + + /** + * Mvc operation response message plugin + * + * @return the mvc operation response message plugin + * @since 1.7.0 + */ + @Bean + @ConditionalOnMissingBean + public AivfoOperationResponseMessagePlugin mvcOperationResponseMessagePlugin() { + return new AivfoOperationResponseMessagePlugin(null); + } + + /** + * Mvc operation models provider plugin + * + * @return the mvc operation models provider plugin + * @since 1.7.0 + */ + @Bean + @ConditionalOnMissingBean + public AivfoOperationModelsProviderPlugin mvcOperationModelsProviderPlugin() { + return new AivfoOperationModelsProviderPlugin(null); + } + +} diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/knife4j/Knife4jProperties.java b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/knife4j/Knife4jProperties.java new file mode 100644 index 0000000..ba0410d --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/knife4j/Knife4jProperties.java @@ -0,0 +1,62 @@ +package com.aivfo.doc.autoconfigure.knife4j; + +import com.github.xiaoymin.knife4j.core.extend.OpenApiExtendSetting; +import com.github.xiaoymin.knife4j.core.model.MarkdownProperty; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +@Data +@ConfigurationProperties(prefix = Knife4jProperties.PREFIX) +public class Knife4jProperties { + /** + * PREFIX + */ + public static final String PREFIX = "aivfo.doc.knife4j"; + /** + * 是否开启BasicHttp验证 + */ + private Knife4jHttpBasic basic = new Knife4jHttpBasic(); + /** + * markdown 路径 + */ + private String markdowns; + /** + * 是否开启Knife4j增强模式 + */ + private boolean enable = false; + /** + * 是否开启默认跨域 + */ + private boolean cors = false; + /** + * 是否生产环境 + */ + private boolean production = false; + /** + * 个性化配置 + */ + private OpenApiExtendSetting setting; + /** + * 分组文档集合 + */ + private List documents; + + @Data + public static class Knife4jHttpBasic { + + /** + * basic 是否开启,默认为false + */ + private boolean enable = false; + /** + * basic 用户名 + */ + private String username = "aivfo"; + /** + * basic 密码 + */ + private String password = "aivfo2017"; + } +} diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/swagger/SwaggerDocAutoConfiguration.java b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/swagger/SwaggerDocAutoConfiguration.java new file mode 100644 index 0000000..b89bc1c --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/swagger/SwaggerDocAutoConfiguration.java @@ -0,0 +1,443 @@ +package com.aivfo.doc.autoconfigure.swagger; + + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.autoconfigure.condition.*; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.DispatcherServlet; +import springfox.documentation.builders.*; +import springfox.documentation.schema.ModelRef; +import springfox.documentation.service.*; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.ApiSelectorBuilder; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger.web.ApiKeyVehicle; +import springfox.documentation.swagger.web.UiConfiguration; +import springfox.documentation.swagger.web.UiConfigurationBuilder; +import springfox.documentation.swagger2.configuration.Swagger2DocumentationWebMvcConfiguration; +import springfox.documentation.swagger2.web.Swagger2ControllerWebMvc; + +import javax.servlet.Servlet; +import java.util.*; +import java.util.stream.Collectors; + +import static com.google.common.collect.Lists.newArrayList; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(value = {Servlet.class, DispatcherServlet.class, Swagger2ControllerWebMvc.class}) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@Import(value = {Swagger2DocumentationWebMvcConfiguration.class}) +@ConditionalOnProperty(name = SwaggerDocProperties.PREFIX + ".enabled", matchIfMissing = true) +@EnableConfigurationProperties(SwaggerDocProperties.class) +public class SwaggerDocAutoConfiguration implements BeanFactoryAware { + + /** + * Swagger properties + */ + private final SwaggerDocProperties swaggerProperties; + /** + * Basic auth + */ + private static final String BASIC_AUTH = "BasicAuth"; + /** + * None + */ + private static final String NONE = "None"; + /** + * Bean factory + */ + private BeanFactory beanFactory; + + /** + * Instantiates a new Swagger auto configuration. + * + * @param swaggerProperties the swagger properties + * @since 1.4.0 + */ + @Contract(pure = true) + public SwaggerDocAutoConfiguration(@NotNull SwaggerDocProperties swaggerProperties) { + this.swaggerProperties = swaggerProperties; + // 默认排除 /actuator 接口文档 + swaggerProperties.getExcludePath().add("/actuator/**"); + swaggerProperties.getExcludePath().add("/error"); + } + + /** + * Ui configuration ui configuration. + * + * @param swaggerProperties the swagger properties + * @return the ui configuration + * @since 1.4.0 + */ + @Bean + public UiConfiguration uiConfiguration(@NotNull SwaggerDocProperties swaggerProperties) { + return UiConfigurationBuilder.builder() + .deepLinking(swaggerProperties.getUiConfig().getDeepLinking()) + .defaultModelExpandDepth(swaggerProperties.getUiConfig().getDefaultModelExpandDepth()) + .defaultModelRendering(swaggerProperties.getUiConfig().getDefaultModelRendering()) + .defaultModelsExpandDepth(swaggerProperties.getUiConfig().getDefaultModelsExpandDepth()) + .displayOperationId(swaggerProperties.getUiConfig().getDisplayOperationId()) + .displayRequestDuration(swaggerProperties.getUiConfig().getDisplayRequestDuration()) + .docExpansion(swaggerProperties.getUiConfig().getDocExpansion()) + .maxDisplayedTags(swaggerProperties.getUiConfig().getMaxDisplayedTags()) + .operationsSorter(swaggerProperties.getUiConfig().getOperationsSorter()) + .showExtensions(swaggerProperties.getUiConfig().getShowExtensions()) + .tagsSorter(swaggerProperties.getUiConfig().getTagsSorter()) + .validatorUrl(swaggerProperties.getUiConfig().getValidatorUrl()) + .build(); + } + + /** + * Create rest api list. + * + * @param swaggerProperties the swagger properties + * @return the list + * @since 1.4.0 + */ + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(UiConfiguration.class) + @ConditionalOnProperty(name = SwaggerDocProperties.PREFIX + ".enabled", matchIfMissing = true) + public List createRestApi(@NotNull SwaggerDocProperties swaggerProperties) { + ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) this.beanFactory; + List docketList = new LinkedList<>(); + + // 没有分组 + if (swaggerProperties.getDocket().size() == 0) { + SwaggerDocProperties.DocketInfo docketInfo = new SwaggerDocProperties.DocketInfo() + .setTitle(swaggerProperties.getTitle()) + .setDescription(swaggerProperties.getDescription()) + .setVersion(StringUtils.isEmpty(swaggerProperties.getVersion()) + ? "1.0" + : swaggerProperties.getVersion()) + .setLicense(swaggerProperties.getLicense()) + .setLicenseUrl(swaggerProperties.getLicenseUrl()) + .setBasePackage(swaggerProperties.getBasePackage()) + .setContact(new SwaggerDocProperties.Contact(swaggerProperties.getContact().getName(), + swaggerProperties.getContact().getUrl(), + swaggerProperties.getContact().getEmail())) + .setTermsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl()); + + swaggerProperties.getDocket().put("wyl", docketInfo); + } + + // 分组创建 + return this.buildDockets(swaggerProperties, configurableBeanFactory, docketList); + } + + /** + * Build dockets list + * + * @param swaggerProperties swagger properties + * @param configurableBeanFactory configurable bean factory + * @param docketList docket list + * @return the list + * @since 1.4.0 + */ + @Contract("_, _, _ -> param3") + private List buildDockets(@NotNull SwaggerDocProperties swaggerProperties, + ConfigurableBeanFactory configurableBeanFactory, + List docketList) { + for (String groupName : swaggerProperties.getDocket().keySet()) { + SwaggerDocProperties.DocketInfo docketInfo = swaggerProperties.getDocket().get(groupName); + + ApiInfo apiInfo = new ApiInfoBuilder() + .title(docketInfo.getTitle().isEmpty() ? swaggerProperties.getTitle() : docketInfo.getTitle()) + .description(docketInfo.getDescription().isEmpty() ? swaggerProperties.getDescription() : docketInfo.getDescription()) + .version(docketInfo.getVersion().isEmpty() ? StringUtils.isEmpty(swaggerProperties.getVersion()) + ? "1.0" + : swaggerProperties.getVersion() + : docketInfo.getVersion()) + .license(docketInfo.getLicense().isEmpty() ? swaggerProperties.getLicense() : docketInfo.getLicense()) + .licenseUrl(docketInfo.getLicenseUrl().isEmpty() ? swaggerProperties.getLicenseUrl() : docketInfo.getLicenseUrl()) + .contact( + new Contact( + docketInfo.getContact().getName().isEmpty() + ? swaggerProperties.getContact().getName() + : docketInfo.getContact().getName(), + docketInfo.getContact().getUrl().isEmpty() + ? swaggerProperties.getContact().getUrl() + : docketInfo.getContact().getUrl(), + docketInfo.getContact().getEmail().isEmpty() + ? swaggerProperties.getContact().getEmail() + : docketInfo.getContact().getEmail() + ) + ) + .termsOfServiceUrl(docketInfo.getTermsOfServiceUrl().isEmpty() + ? swaggerProperties.getTermsOfServiceUrl() + : docketInfo.getTermsOfServiceUrl()) + .build(); + + Docket docketForBuilder = new Docket(DocumentationType.SWAGGER_2) + .host(swaggerProperties.getHost()) + .apiInfo(apiInfo) + .securityContexts(Collections.singletonList(this.securityContext())) + + .globalOperationParameters(this.assemblyGlobalOperationParameters( + swaggerProperties.getGlobalOperationParameters(), + docketInfo.getGlobalOperationParameters())); + + this.processAuthAndResponse(swaggerProperties, docketForBuilder); + + Docket docket = this.processPath( + docketForBuilder + .groupName(groupName) + .select() + .apis(RequestHandlerSelectors.basePackage(docketInfo.getBasePackage()))) + .build(); + + // ignoredParameterTypes + Class[] array = new Class[docketInfo.getIgnoredParameterTypes().size()]; + Class[] ignoredParameterTypes = docketInfo.getIgnoredParameterTypes().toArray(array); + docket.ignoredParameterTypes(ignoredParameterTypes); + + // 如果是 dubbo 应用, 会导致存在相同的 bean name, 因此这里添加一个前缀 + configurableBeanFactory.registerSingleton("SwaggerDoc@" + groupName, docket); + docketList.add(docket); + } + return docketList; + } + + + /** + * Process path * + * + * @param apiSelectorBuilder api selector builder + * @return the api selector builder + * @since 1.4.0 + */ + private ApiSelectorBuilder processPath(ApiSelectorBuilder apiSelectorBuilder) { + // base-path 处理 当没有配置任何 path 的时候,解析 /** + if (this.swaggerProperties.getBasePath().isEmpty()) { + this.swaggerProperties.getBasePath().add("/**"); + } + + for (String base : this.swaggerProperties.getBasePath()) { + apiSelectorBuilder.paths(PathSelectors.ant(base)); + } + + for (String exclude : this.swaggerProperties.getExcludePath()) { + apiSelectorBuilder.paths(PathSelectors.ant(exclude).negate()); + } + return apiSelectorBuilder; + } + + /** + * 配置默认的全局鉴权策略的开关,以及通过正则表达式进行匹配; 默认 ^.*$ 匹配所有 URL + * 其中 securityReferences 为配置启用的鉴权策略 + * + * @return security context + * @since 1.4.0 + */ + private SecurityContext securityContext() { + return SecurityContext.builder() + .securityReferences(this.defaultAuth()) + .forPaths(PathSelectors.regex(this.swaggerProperties.getAuthorization().getAuthRegex())) + .build(); + } + + /** + * Build global operation parameters from swagger properties list + * + * @param globalOperationParameters global operation parameters + * @return the list + * @since 1.4.0 + */ + @NotNull + private List buildGlobalOperationParametersFromSwaggerProperties( + List globalOperationParameters) { + List parameters = new ArrayList<>(); + + if (Objects.isNull(globalOperationParameters)) { + return parameters; + } + for (SwaggerDocProperties.GlobalOperationParameter globalOperationParameter : globalOperationParameters) { + parameters.add(new ParameterBuilder() + .name(globalOperationParameter.getName()) + .description(globalOperationParameter.getDescription()) + .modelRef(new ModelRef(globalOperationParameter.getModelRef())) + .parameterType(globalOperationParameter.getParameterType()) + .required(Boolean.parseBoolean(globalOperationParameter.getRequired())) + .build()); + } + return parameters; + } + + /** + * Process auth and response * + * + * @param swaggerProperties swagger properties + * @param docketForBuilder docket for builder + * @since 1.4.0 + */ + private void processAuthAndResponse(@NotNull SwaggerDocProperties swaggerProperties, Docket docketForBuilder) { + if (BASIC_AUTH.equalsIgnoreCase(swaggerProperties.getAuthorization().getType())) { + docketForBuilder.securitySchemes(Collections.singletonList(this.basicAuth())); + } else if (!NONE.equalsIgnoreCase(swaggerProperties.getAuthorization().getType())) { + docketForBuilder.securitySchemes(Collections.singletonList(this.apiKey())); + } + + // 全局响应消息 + if (!swaggerProperties.isApplyDefaultResponseMessages()) { + this.buildGlobalResponseMessage(swaggerProperties, docketForBuilder); + } + } + + /** + * 局部参数按照 name 覆盖局部参数 + * + * @param globalOperationParameters global operation parameters + * @param docketOperationParameters docket operation parameters + * @return list list + * @since 1.4.0 + */ + @NotNull + private List assemblyGlobalOperationParameters( + List globalOperationParameters, + List docketOperationParameters) { + + if (Objects.isNull(docketOperationParameters) || docketOperationParameters.isEmpty()) { + return this.buildGlobalOperationParametersFromSwaggerProperties(globalOperationParameters); + } + + Set docketNames = docketOperationParameters.stream() + .map(SwaggerDocProperties.GlobalOperationParameter::getName) + .collect(Collectors.toSet()); + + List resultOperationParameters = newArrayList(); + + if (Objects.nonNull(globalOperationParameters)) { + for (SwaggerDocProperties.GlobalOperationParameter parameter : globalOperationParameters) { + if (!docketNames.contains(parameter.getName())) { + resultOperationParameters.add(parameter); + } + } + } + + resultOperationParameters.addAll(docketOperationParameters); + return this.buildGlobalOperationParametersFromSwaggerProperties(resultOperationParameters); + } + + /** + * 配置默认的全局鉴权策略; 其中返回的 SecurityReference 中,reference 即为 ApiKey 对象里面的 name,保持一致才能开启全局鉴权 + * + * @return list list + * @since 1.4.0 + */ + @NotNull + private @Unmodifiable List defaultAuth() { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + return Collections.singletonList(SecurityReference.builder() + .reference(this.swaggerProperties.getAuthorization().getName()) + .scopes(authorizationScopes).build()); + } + + /** + * 配置基于 BasicAuth 的鉴权对象 + * + * @return basic auth + * @since 1.4.0 + */ + @NotNull + @Contract(" -> new") + private BasicAuth basicAuth() { + return new BasicAuth(this.swaggerProperties.getAuthorization().getName()); + } + + /** + * 配置基于 ApiKey 的鉴权对象 + * + * @return api key + * @since 1.4.0 + */ + @NotNull + @Contract(" -> new") + private ApiKey apiKey() { + return new ApiKey(this.swaggerProperties.getAuthorization().getName(), + this.swaggerProperties.getAuthorization().getKeyName(), + ApiKeyVehicle.HEADER.getValue()); + } + + /** + * 设置全局响应消息 + * + * @param swaggerProperties swaggerProperties 支持 POST,GET,PUT,PATCH,DELETE,HEAD,OPTIONS,TRACE + * @param docketForBuilder swagger docket builder + * @since 1.4.0 + */ + private void buildGlobalResponseMessage(@NotNull SwaggerDocProperties swaggerProperties, @NotNull Docket docketForBuilder) { + + SwaggerDocProperties.GlobalResponseMessage globalResponseMessages = swaggerProperties.getGlobalResponseMessage(); + + /* POST,GET,PUT,PATCH,DELETE,HEAD,OPTIONS,TRACE 响应消息体 **/ + List postResponseMessages = this.getResponseMessageList(globalResponseMessages.getPost()); + List getResponseMessages = this.getResponseMessageList(globalResponseMessages.getGet()); + List putResponseMessages = this.getResponseMessageList(globalResponseMessages.getPut()); + List patchResponseMessages = this.getResponseMessageList(globalResponseMessages.getPatch()); + List deleteResponseMessages = this.getResponseMessageList(globalResponseMessages.getDelete()); + List headResponseMessages = this.getResponseMessageList(globalResponseMessages.getHead()); + List optionsResponseMessages = this.getResponseMessageList(globalResponseMessages.getOptions()); + List trackResponseMessages = this.getResponseMessageList(globalResponseMessages.getTrace()); + + docketForBuilder.useDefaultResponseMessages(swaggerProperties.isApplyDefaultResponseMessages()) + .globalResponseMessage(RequestMethod.POST, postResponseMessages) + .globalResponseMessage(RequestMethod.GET, getResponseMessages) + .globalResponseMessage(RequestMethod.PUT, putResponseMessages) + .globalResponseMessage(RequestMethod.PATCH, patchResponseMessages) + .globalResponseMessage(RequestMethod.DELETE, deleteResponseMessages) + .globalResponseMessage(RequestMethod.HEAD, headResponseMessages) + .globalResponseMessage(RequestMethod.OPTIONS, optionsResponseMessages) + .globalResponseMessage(RequestMethod.TRACE, trackResponseMessages); + } + + /** + * 获取返回消息体列表 + * + * @param globalResponseMessageBodyList 全局 Code 消息返回集合 + * @return response message list + * @since 1.4.0 + */ + @NotNull + private List getResponseMessageList( + @NotNull List globalResponseMessageBodyList) { + List responseMessages = new ArrayList<>(); + for (SwaggerDocProperties.GlobalResponseMessageBody globalResponseMessageBody : globalResponseMessageBodyList) { + ResponseMessageBuilder responseMessageBuilder = new ResponseMessageBuilder(); + responseMessageBuilder.code(globalResponseMessageBody.getCode()).message(globalResponseMessageBody.getMessage()); + + if (!StringUtils.isEmpty(globalResponseMessageBody.getModelRef())) { + responseMessageBuilder.responseModel(new ModelRef(globalResponseMessageBody.getModelRef())); + } + responseMessages.add(responseMessageBuilder.build()); + } + + return responseMessages; + } + + /** + * Sets bean factory * + * + * @param beanFactory bean factory + * @throws BeansException beans exception + * @since 1.4.0 + */ + @Override + public void setBeanFactory(@NotNull BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + +} diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/swagger/SwaggerDocProperties.java b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/swagger/SwaggerDocProperties.java new file mode 100644 index 0000000..4f619ae --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/java/com/aivfo/doc/autoconfigure/swagger/SwaggerDocProperties.java @@ -0,0 +1,383 @@ +package com.aivfo.doc.autoconfigure.swagger; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.springframework.boot.context.properties.ConfigurationProperties; +import springfox.documentation.swagger.web.DocExpansion; +import springfox.documentation.swagger.web.ModelRendering; +import springfox.documentation.swagger.web.OperationsSorter; +import springfox.documentation.swagger.web.TagsSorter; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + *

Company: 成都返空汇网络技术有限公司

+ *

Description:

+ * + * @author dong4j + * @version 1.2.3 + * @email "mailto:dongshijie@fkhwl.com" + * @date 2020.01.27 14:54 + * @since 1.0.0 + */ +@Data +@ConfigurationProperties(prefix = SwaggerDocProperties.PREFIX) +public class SwaggerDocProperties { + /** + * PREFIX + */ + public static final String PREFIX = "aivfo.doc.swagger"; + /** + * 是否开启 swagger + */ + private boolean enabled; + /** + * 标题 + */ + private String title = ""; + /** + * 描述 + */ + private String description = ""; + /** + * 版本 + */ + private String version = ""; + /** + * 许可证 + */ + private String license = ""; + /** + * 许可证 URL + */ + private String licenseUrl = ""; + /** + * 服务条款 URL + */ + private String termsOfServiceUrl = ""; + /** + * 忽略的参数类型 + */ + private List> ignoredParameterTypes = new ArrayList<>(); + /** + * swagger 会解析的包路径 + */ + private String basePackage = "com.aivfo.doc"; + /** + * swagger 会解析的 url 规则 + */ + private List basePath = new ArrayList<>(); + /** + * 在 basePath 基础上需要排除的 url 规则 + */ + private List excludePath = new ArrayList<>(); + /** + * 分组文档 + */ + private Map docket = new LinkedHashMap<>(); + /** + * host 信息 + */ + private String host = ""; + /** + * 全局参数配置 + */ + private List globalOperationParameters; + /** + * Contact + */ + private Contact contact = new Contact(); + /** + * 页面功能配置 + */ + private UiConfig uiConfig = new UiConfig(); + /** + * 是否使用默认预定义的响应消息 ,默认 true + */ + private boolean applyDefaultResponseMessages = Boolean.TRUE; + /** + * 全局响应消息 + */ + private GlobalResponseMessage globalResponseMessage; + /** + * 全局统一鉴权配置 + */ + private Authorization authorization = new Authorization(); + + @Data + @NoArgsConstructor + public static class GlobalOperationParameter { + /** + * 参数名 + */ + private String name; + /** + * 描述信息 + */ + private String description; + /** + * 指定参数类型 + */ + private String modelRef; + /** + * 参数放在哪个地方:header,query,path,body.form + */ + private String parameterType; + /** + * 参数是否必须传 + */ + private String required; + + } + + @Data + @NoArgsConstructor + @Accessors(chain = true) + @AllArgsConstructor + public static class DocketInfo { + /** + * 标题 + */ + private String title = ""; + /** + * 描述 + */ + private String description = ""; + /** + * 版本 + */ + private String version = ""; + /** + * 许可证 + */ + private String license = ""; + /** + * 许可证 URL + */ + private String licenseUrl = ""; + /** + * 服务条款 URL + */ + private String termsOfServiceUrl = ""; + /** + * Contact + */ + private Contact contact = new Contact(); + /** + * swagger 会解析的包路径 + */ + private String basePackage = ""; + /** + * swagger 会解析的 url 规则 + */ + private List basePath = new ArrayList<>(); + /** + * 在 basePath 基础上需要排除的 url 规则 + */ + private List excludePath = new ArrayList<>(); + /** + * Global operation parameters + */ + private List globalOperationParameters; + /** + * 忽略的参数类型 + */ + private List> ignoredParameterTypes = new ArrayList<>(); + + } + + /** + * The type Contact. + */ + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Contact { + /** + * 联系人 + */ + private String name = ""; + /** + * 联系人 url + */ + private String url = ""; + /** + * 联系人 email + */ + private String email = ""; + + } + + /** + * The type Global response message. + */ + @Data + @NoArgsConstructor + public static class GlobalResponseMessage { + /** + * POST 响应消息体 + */ + List post = new ArrayList<>(); + /** + * GET 响应消息体 + */ + List get = new ArrayList<>(); + /** + * PUT 响应消息体 + */ + List put = new ArrayList<>(); + /** + * PATCH 响应消息体 + */ + List patch = new ArrayList<>(); + /** + * DELETE 响应消息体 + */ + List delete = new ArrayList<>(); + /** + * HEAD 响应消息体 + */ + List head = new ArrayList<>(); + /** + * OPTIONS 响应消息体 + */ + List options = new ArrayList<>(); + /** + * TRACE 响应消息体 + */ + List trace = new ArrayList<>(); + + } + + /** + * The type Global response message body. + */ + @Data + @NoArgsConstructor + public static class GlobalResponseMessageBody { + /** + * 响应码 + */ + private int code; + /** + * 响应消息 + */ + private String message; + /** + * 响应体 + */ + private String modelRef; + + } + + /** + * The type Ui config. + */ + @Data + @NoArgsConstructor + public static class UiConfig { + /** + * Api sorter + */ + private String apiSorter = "alpha"; + /** + * 是否启用 json 编辑器 + */ + private Boolean jsonEditor = false; + /** + * 是否显示请求头信息 + */ + private Boolean showRequestHeaders = true; + /** + * 支持页面提交的请求类型 + */ + private String submitMethods = "get,post,put,delete,patch"; + /** + * 请求超时时间 + */ + private Long requestTimeout = 10000L; + /** + * Deep linking + */ + private Boolean deepLinking; + /** + * Display operation id + */ + private Boolean displayOperationId; + /** + * Default models expand depth + */ + private Integer defaultModelsExpandDepth; + /** + * Default model expand depth + */ + private Integer defaultModelExpandDepth; + /** + * Default model rendering + */ + private ModelRendering defaultModelRendering; + /** + * 是否显示请求耗时,默认 false + */ + private Boolean displayRequestDuration = true; + /** + * 可选 none | list + */ + private DocExpansion docExpansion; + /** + * Boolean=false OR String + */ + private Object filter; + /** + * Max displayed tags + */ + private Integer maxDisplayedTags; + /** + * Operations sorter + */ + private OperationsSorter operationsSorter; + /** + * Show extensions + */ + private Boolean showExtensions; + /** + * Tags sorter + */ + private TagsSorter tagsSorter; + /** + * Network + */ + private String validatorUrl; + } + + /** + * securitySchemes 支持方式之一 ApiKey + */ + @Data + @NoArgsConstructor + public static class Authorization { + /** + * 鉴权策略 ID,对应 SecurityReferences ID + */ + private String name = "Authorization"; + /** + * 鉴权策略,可选 ApiKey | BasicAuth | None,默认 ApiKey + */ + private String type = "BasicAuth"; + /** + * 鉴权传递的 Header 参数 + */ + private String keyName = "X-Client-Token"; + /** + * 需要开启鉴权 URL 的正则 + */ + private String authRegex = "^.*$"; + } + +} + diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..1d7361d --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.aivfo.doc.autoconfigure.knife4j.Knife4jAutoConfiguration,\ + com.aivfo.doc.autoconfigure.swagger.SwaggerDocAutoConfiguration diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/pom.xml b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/pom.xml new file mode 100644 index 0000000..f29cfa9 --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/pom.xml @@ -0,0 +1,25 @@ + + + + aivfo-doc-spring-boot + com.aivfo + 1.0.0-SNAPSHOT + + 4.0.0 + + aivfo-doc-spring-boot-core + + + com.github.xiaoymin + knife4j-spring + 2.0.9 + + + com.github.xiaoymin + knife4j-spring-ui + 2.0.9 + + + \ No newline at end of file diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/src/main/java/com/aivfo/doc/core/common/schema/AivfoOperationModelsProviderPlugin.java b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/src/main/java/com/aivfo/doc/core/common/schema/AivfoOperationModelsProviderPlugin.java new file mode 100644 index 0000000..aced5b4 --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/src/main/java/com/aivfo/doc/core/common/schema/AivfoOperationModelsProviderPlugin.java @@ -0,0 +1,114 @@ +package com.aivfo.doc.core.common.schema; + +import com.aivfo.el.starter.base.Result; +import com.fasterxml.classmate.ResolvedType; +import com.fasterxml.classmate.TypeResolver; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import springfox.documentation.schema.Types; +import springfox.documentation.service.ResolvedMethodParameter; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.OperationModelsProviderPlugin; +import springfox.documentation.spi.service.contexts.RequestMappingContext; +import springfox.documentation.swagger.common.SwaggerPluginSupport; + +import javax.annotation.Resource; +import java.lang.annotation.Annotation; + +/** + * 包装返回值为 Result + */ +@Order(Ordered.HIGHEST_PRECEDENCE + 10) +public class AivfoOperationModelsProviderPlugin implements OperationModelsProviderPlugin { + /** + * Annotation + */ + private final Class filterAnnotationClass; + /** + * Type resolver + */ + @Resource + private TypeResolver typeResolver; + + /** + * Fkh operation models provider plugin + * + * @param filterAnnotationClass filterAnnotationClass + * @since 1.7.0 + */ + public AivfoOperationModelsProviderPlugin(Class filterAnnotationClass) { + this.filterAnnotationClass = filterAnnotationClass; + } + + /** + * Apply + * + * @param context context + * @since 1.7.0 + */ + @Override + public void apply(RequestMappingContext context) { + if (this.findAnnotationIfNotNull(context)) { + this.addInputParams(context); + this.addReturnType(context); + } + } + + /** + * Find annotation if not null + * + * @param context context + * @return the boolean + * @since 1.7.0 + */ + public boolean findAnnotationIfNotNull(RequestMappingContext context) { + if (null == this.filterAnnotationClass) { + return true; + } + return context.findAnnotation(filterAnnotationClass).isPresent(); + } + + /** + * Add return type + * + * @param context context + * @since 1.7.0 + */ + private void addReturnType(RequestMappingContext context) { + ResolvedType returnType = context.getReturnType(); + // 兼容,如果方法返回值本身写的 Result 就不包装了 + if (Result.class.equals(returnType.getErasedType())) { + return; + } + if (Types.isVoid(returnType)) { + returnType = this.typeResolver.resolve(Void.class); + } + ResolvedType packageType = context.alternateFor(this.typeResolver.resolve(Result.class, returnType)); + context.operationModelsBuilder().addReturn(packageType); + } + + /** + * Add input params + * + * @param context context + * @since 1.7.0 + */ + private void addInputParams(RequestMappingContext context) { + for (ResolvedMethodParameter parameter : context.getParameters()) { + ResolvedType modelType = context.alternateFor(parameter.getParameterType()); + context.operationModelsBuilder().addInputParam(modelType); + } + } + + /** + * Supports + * + * @param delimiter delimiter + * @return the boolean + * @since 1.7.0 + */ + @Override + public boolean supports(DocumentationType delimiter) { + return SwaggerPluginSupport.pluginDoesApply(delimiter); + } +} diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/src/main/java/com/aivfo/doc/core/common/schema/AivfoOperationResponseMessagePlugin.java b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/src/main/java/com/aivfo/doc/core/common/schema/AivfoOperationResponseMessagePlugin.java new file mode 100644 index 0000000..1fc6573 --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/src/main/java/com/aivfo/doc/core/common/schema/AivfoOperationResponseMessagePlugin.java @@ -0,0 +1,119 @@ +package com.aivfo.doc.core.common.schema; + +import com.aivfo.el.starter.base.Result; +import com.aivfo.el.starter.base.utils.StringPool; +import com.fasterxml.classmate.ResolvedType; +import com.fasterxml.classmate.TypeResolver; +import com.google.common.collect.Sets; +import org.springframework.core.annotation.Order; +import springfox.documentation.builders.ResponseMessageBuilder; +import springfox.documentation.schema.*; +import springfox.documentation.service.ResponseMessage; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.schema.contexts.ModelContext; +import springfox.documentation.spi.service.OperationBuilderPlugin; +import springfox.documentation.spi.service.contexts.OperationContext; +import springfox.documentation.spring.web.readers.operation.ResponseMessagesReader; +import springfox.documentation.swagger.common.SwaggerPluginSupport; + +import javax.annotation.Resource; +import java.lang.annotation.Annotation; +import java.util.Optional; + +/** + *

Description: 最后一位执行,修改200状态码显示的 schema

+ */ +@Order +public class AivfoOperationResponseMessagePlugin implements OperationBuilderPlugin { + /** + * Annotation + */ + private final Class filterAnnotationClass; + /** + * Name extractor + */ + @Resource + private TypeNameExtractor nameExtractor; + /** + * Type resolver + */ + @Resource + private TypeResolver typeResolver; + + /** + * Fkh operation models provider plugin + * + * @param filterAnnotationClass filterAnnotationClass + * @since 1.7.0 + */ + public AivfoOperationResponseMessagePlugin(Class filterAnnotationClass) { + this.filterAnnotationClass = filterAnnotationClass; + } + + /** + * Apply + * + * @param operationContext operation context + * @since 1.7.0 + */ + @Override + public void apply(OperationContext operationContext) { + if (this.findAnnotationIfNotNull(operationContext)) { + ResolvedType returnType = operationContext.getReturnType(); + // 兼容,如果方法返回值本身写的 Result 就不包装了 + if (Result.class.equals(returnType.getErasedType())) { + return; + } + if (Types.isVoid(returnType)) { + returnType = this.typeResolver.resolve(Void.class); + } + ResolvedType packageType = operationContext.alternateFor(this.typeResolver.resolve(Result.class, returnType)); + ModelContext modelContext = ModelContext.returnValue(StringPool.EMPTY, + operationContext.getGroupName(), + packageType, + Optional.empty(), + operationContext.getDocumentationType(), + operationContext.getAlternateTypeProvider(), + operationContext.getGenericsNamingStrategy(), + operationContext.getIgnorableParameterTypes()); + + int httpStatusCode = ResponseMessagesReader.httpStatusCode(operationContext); + String message = ResponseMessagesReader.message(operationContext); + ModelReference modelRef = + ResolvedTypes.modelRefFactory(modelContext, new JacksonEnumTypeDeterminer(), this.nameExtractor).apply(packageType); + ResponseMessage built = new ResponseMessageBuilder() + .code(httpStatusCode) + .message(message) + .responseModel(modelRef) + .build(); + + operationContext.operationBuilder().responseMessages(Sets.newHashSet(built)); + } + } + + /** + * Find annotation if not null + * + * @param context context + * @return the boolean + * @since 1.7.0 + */ + public boolean findAnnotationIfNotNull(OperationContext context) { + if (null == this.filterAnnotationClass) { + return true; + } + return context.findAnnotation(this.filterAnnotationClass).isPresent(); + } + + /** + * Supports + * + * @param delimiter delimiter + * @return the boolean + * @since 1.7.0 + */ + @Override + public boolean supports(DocumentationType delimiter) { + return SwaggerPluginSupport.pluginDoesApply(delimiter); + } +} diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/src/main/java/com/aivfo/doc/core/knife4j/EnableKnife4j.java b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/src/main/java/com/aivfo/doc/core/knife4j/EnableKnife4j.java new file mode 100644 index 0000000..adb1cb9 --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-core/src/main/java/com/aivfo/doc/core/knife4j/EnableKnife4j.java @@ -0,0 +1,18 @@ +package com.aivfo.doc.core.knife4j; + +import java.lang.annotation.*; + +/*** + * Enable Knife4j enhanced annotation and use @EnableSwagger2 annotation together. + * + * inlude: + *
    + *
  • Interface sorting
  • + *
  • Interface document download (word)
  • + *
+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.TYPE}) +@Documented +public @interface EnableKnife4j { +} diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/pom.xml b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/pom.xml index 9f44ed2..a466260 100644 --- a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/pom.xml +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/pom.xml @@ -15,5 +15,20 @@ 11 11 + + + com.aivfo + aivfo-doc-spring-boot-autoconfigure + 1.0.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + \ No newline at end of file diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/src/test/java/com/aivfo/doc/SampleKnife4jApplication.java b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/src/test/java/com/aivfo/doc/SampleKnife4jApplication.java new file mode 100644 index 0000000..b4ec311 --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/src/test/java/com/aivfo/doc/SampleKnife4jApplication.java @@ -0,0 +1,23 @@ +package com.aivfo.doc; + +import com.aivfo.doc.core.knife4j.EnableKnife4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + *

Company: 成都返空汇网络技术有限公司

+ *

Description:

+ * + * @author dong4j + * @version 1.4.0 + * @email "mailto:dongshijie@fkhwl.com" + * @date 2020.05.08 16:16 + * @since 1.4.0 + */ +@EnableKnife4j +@SpringBootApplication +public class SampleKnife4jApplication { + public static void main(String[] args) { + SpringApplication.run(SampleKnife4jApplication.class, args); + } +} diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/src/test/java/com/aivfo/doc/TestController.java b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/src/test/java/com/aivfo/doc/TestController.java new file mode 100644 index 0000000..f516df1 --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/src/test/java/com/aivfo/doc/TestController.java @@ -0,0 +1,134 @@ +package com.aivfo.doc; + +import com.aivfo.el.start.core.api.R; +import com.aivfo.el.start.core.exception.BaseException; +import com.aivfo.el.starter.base.Result; +import io.swagger.annotations.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import javax.validation.constraints.*; +import java.util.Date; + +/** + *

Company: 成都返空汇网络技术有限公司

+ *

Description:

+ * + * @author dong4j + * @version 1.0.0 + * @email "mailto:dongshijie@fkhwl.com" + * @date 2020.01.27 18:19 + * @since 1.4.0 + */ +@Api(tags = "swagger test api") +@RestController +public class TestController { + /** + * Find by id result. + * + * @param id the id + * @return the result + * @since 1.4.0 + */ + @GetMapping("/user/{id}") + @ApiOperation(value = "获取用户详情", notes = "xxx", produces = MediaType.APPLICATION_JSON_VALUE) + @ApiResponses( { + @ApiResponse(code = 2000, message = "操作成功", response = R.class), + @ApiResponse(code = 6000, message = "参数校验失败", response = R.class), + @ApiResponse(code = 4000, message = "用户不存在", response = R.class) + }) + public Result findById(@Valid @NotNull @Size(min = 1) @PathVariable("id") Long id) { + return R.succeed(); + } + + /** + * Save result. + * + * @param user the user + * @return the result + * @since 1.4.0 + */ + @PostMapping("/user") + @ApiOperation(value = "创建新用户", notes = "传入一个 User 实体", produces = MediaType.APPLICATION_JSON_VALUE) + public Result save(@Valid @RequestBody User user) { + return R.succeed(); + } + + /** + * Update result. + * + * @param user the user + * @return the result + * @since 1.4.0 + */ + @PutMapping("/user") + @ApiOperation(value = "修改用户信息", produces = MediaType.APPLICATION_JSON_VALUE) + public Result update(@Valid @RequestBody User user) { + return R.succeed(); + } + + /** + * Delete by id result. + * + * @param id the id + * @return the result + * @since 1.4.0 + */ + @DeleteMapping("/user/{id}") + @ApiOperation(value = "删除用户", produces = MediaType.APPLICATION_JSON_VALUE) + public Result deleteById(@Valid @NotNull @Size(min = 1) @PathVariable("id") Long id) { + if(id>10L) + { + throw new BaseException("123"); + } + return R.succeed("delete user : " + id); + } + + /** + * List. + * + * @param pageIndex the page index + * @param pageSize the page size + * @return the list + * @since 1.4.0 + */ + @GetMapping("/user") + @ApiOperation(value = "用户列表", produces = MediaType.APPLICATION_JSON_VALUE) + public Result list(@ApiParam("查看第几页") @RequestParam(value = "pageIndex", required = false) Integer pageIndex, + @ApiParam("每页多少条") @RequestParam(value = "pageSize", required = false) Integer pageSize) { + return R.succeed(); + } + + /** + *

Company: 成都返空汇网络技术有限公司

+ *

Description:

+ * + * @author dong4j + * @version 1.4.0 + * @email "mailto:dongshijie@fkhwl.com" + * @date 2020.05.08 17:27 + * @since 1.4.0 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + private static class User { + /** Username */ + @NotBlank(message = "用户名不能为空") + private String username; + /** Version */ + private String version; + /** Date */ + private Date date; + /** Age */ + @Max(150) + @Min(1) + private Integer age; + } +} diff --git a/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/src/test/resources/application.yml b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/src/test/resources/application.yml new file mode 100644 index 0000000..b36fdfc --- /dev/null +++ b/module/aivfo-doc-spring-boot/aivfo-doc-spring-boot-start/src/test/resources/application.yml @@ -0,0 +1,9 @@ +aivfo: + doc: + swagger: + title: swagger-plus test + description: 描述信息 + version: 2022.1.1-SNAPSHOT + license: xxxx + license-url: xxxxx + base-package: com.aivfo.doc diff --git a/module/aivfo-doc-spring-boot/pom.xml b/module/aivfo-doc-spring-boot/pom.xml index 886ffa9..b646a3d 100644 --- a/module/aivfo-doc-spring-boot/pom.xml +++ b/module/aivfo-doc-spring-boot/pom.xml @@ -14,5 +14,6 @@ aivfo-doc-spring-boot-autoconfigure aivfo-doc-spring-boot-start + aivfo-doc-spring-boot-core \ No newline at end of file