同时支持springboot:3.0,springboot:2.0,使用gateway聚合springdoc,ui使用knife4j,解决由于nginx配置代理前缀导致的文档无法访问,不强依赖注册中心(nacos,zk,Eureka)
有帮助的话记得点个赞哟!!!
将所有依赖集成好作为一个本地包供其他项目使用
当使用springboot:3.0,请使用 springdoc:2.0.0+
当使用springboot:2.0,请使用 springdoc:1.6.0+(部分springdoc的包名替换即可)
<dependencies>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
import com.github.mpcloud.framework.core.consts.Constant;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* The type Swagger properties.
*
* @author : Milo
*/
@ConfigurationProperties("springdoc.info")
public class DocumentInfoProperties {
private static final String DEFAULT_OPEN_API_ROUTE_NAME = "open-api-route";
private static final String DEFAULT_GATEWAY_API_ROUTE_NAME = "open-api-gateway-route";
/**
* 文档标题
*/
private String title;
/**
* 文档描述
*/
private String description;
/**
* 项目version
*/
private String projectVersion;
/**
* 许可证
*/
private String license;
/**
* 许可证URL
*/
private String licenseUrl;
/**
* 项目负责人信息
*/
private final Contact contact = new Contact();
/**
* gateway
*/
private final Gateway gateway = new Gateway();
public String getTitle() {
return this.title;
}
public void setTitle(final String title) {
this.title = title;
}
public String getDescription() {
return this.description;
}
public void setDescription(final String description) {
this.description = description;
}
public String getProjectVersion() {
return this.projectVersion;
}
public void setProjectVersion(final String projectVersion) {
this.projectVersion = projectVersion;
}
public String getLicense() {
return this.license;
}
public void setLicense(final String license) {
this.license = license;
}
public String getLicenseUrl() {
return this.licenseUrl;
}
public void setLicenseUrl(final String licenseUrl) {
this.licenseUrl = licenseUrl;
}
public Contact getContact() {
return this.contact;
}
public Gateway getGateway() {
return gateway;
}
public static class Contact {
/**
* 联系人
**/
private String name;
/**
* 联系人url
**/
private String url;
/**
* 联系人email
**/
private String email;
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public String getUrl() {
return this.url;
}
public void setUrl(final String url) {
this.url = url;
}
public String getEmail() {
return this.email;
}
public void setEmail(final String email) {
this.email = email;
}
}
public static class Gateway {
/**
* api 前缀
*/
private String apiPathPrefix = Constant.URL_PREFIX;
/**
* 接口文档路由名称
*/
private String openApiRouteName = DEFAULT_OPEN_API_ROUTE_NAME;
/**
* 网关接口文档路由名称
*/
private String openApiGatewayApiRouteName = DEFAULT_GATEWAY_API_ROUTE_NAME;
public String getApiPathPrefix() {
return apiPathPrefix;
}
public void setApiPathPrefix(String apiPathPrefix) {
this.apiPathPrefix = apiPathPrefix;
}
public String getOpenApiRouteName() {
return openApiRouteName;
}
public void setOpenApiRouteName(String openApiRouteName) {
this.openApiRouteName = openApiRouteName;
}
public String getOpenApiGatewayApiRouteName() {
return openApiGatewayApiRouteName;
}
public void setOpenApiGatewayApiRouteName(String openApiGatewayApiRouteName) {
this.openApiGatewayApiRouteName = openApiGatewayApiRouteName;
}
}
}
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
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.Configuration;
/**
* @author milo
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(DocumentInfoProperties.class)
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true")
@ConditionalOnWebApplication
public class DocumentConfiguration {
@Bean
public OpenAPI openApi(final DocumentInfoProperties docInfo) {
return new OpenAPI().info(new Info()
.title(docInfo.getTitle())
.description(docInfo.getDescription())
.version(docInfo.getProjectVersion())
.contact(new Contact().name(docInfo.getContact().getName()).email(docInfo.getContact().getEmail()).url(docInfo.getContact().getUrl()))
.license(new License().name(docInfo.getLicense()).url(docInfo.getLicenseUrl()))
);
}
}
解决代理导致的接口文档请求不了的问题
如果nginx配置的以下内容,则需要如下类解决这个问题.
location /api{
proxy_pass http://localhost:8080;
rewrite "^/api/(.*)$" /$1 break;
proxy_set_header Host $host:$server_port;
}
复写原有地址前缀
import com.github.mpcloud.framework.core.utils.PathUtils;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.webflux.core.providers.SpringWebFluxProvider;
/**
* @author : Milo
*/
public class CustomizeSpringWebFluxProvider extends SpringWebFluxProvider {
@Override
public String findPathPrefix(SpringDocConfigProperties springDocConfigProperties) {
DocumentInfoProperties documentInfoProperties = applicationContext.getBean(DocumentInfoProperties.class);
return PathUtils.append(documentInfoProperties.getGateway().getApiPathPrefix() + super.findPathPrefix(springDocConfigProperties));
}
}
import com.github.mpcloud.framework.core.consts.Constant;
import com.github.mpcloud.framework.core.utils.PathUtils;
import com.github.mpcloud.framework.core.utils.spring.SpringContextHolder;
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties.SwaggerUrl;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigParameters;
import org.springdoc.core.providers.SpringWebProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import java.util.List;
import static org.springdoc.core.utils.Constants.SPRINGDOC_ENABLED;
import static org.springdoc.core.utils.Constants.SPRINGDOC_SWAGGER_UI_ENABLED;
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
/**
* @author : Milo
*/
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnProperty(name = { SPRINGDOC_ENABLED, SPRINGDOC_SWAGGER_UI_ENABLED }, matchIfMissing = true, havingValue = "true")
public class GatewayRouterGroupProviderConfiguration {
private final DiscoveryClient discoveryClient;
private final SpringDocConfigProperties springDocConfig;
private final DocumentInfoProperties documentInfo;
private final SwaggerUiConfigParameters swaggerUiConfig;
public GatewayRouterGroupProviderConfiguration(DiscoveryClient discoveryClient, SpringDocConfigProperties springDocConfig, DocumentInfoProperties documentInfo, SwaggerUiConfigParameters swaggerUiConfig) {
this.discoveryClient = discoveryClient;
this.springDocConfig = springDocConfig;
this.documentInfo = documentInfo;
this.swaggerUiConfig = swaggerUiConfig;
}
/**
* openapi 路由(重写服务名所在位置)
*
* @param builder 路由构造器
*
* @return 路由
*/
@Bean
public RouteLocator openApiRouteLocator(final RouteLocatorBuilder builder) {
return builder.routes()
.route(this.documentInfo.getGateway().getOpenApiRouteName(), route -> route
.path(this.springDocConfig.getApiDocs().getPath() + "/**")
.filters(filter -> filter.rewritePath(this.springDocConfig.getApiDocs().getPath() + "/(?<segment>.*)", "/$\{segment}" + this.springDocConfig.getApiDocs().getPath()))
.uri(Constant.HTTP_PREFIX + "localhost:" + SpringContextHolder.getServerPort())
)
.build();
}
/**
* 网关当前路由 (去掉服务名)
*
* @param builder 路由构造器
*
* @return 路由
*/
@Bean
public RouteLocator registrationRouteLocator(Registration registration, final RouteLocatorBuilder builder) {
return builder.routes()
.route(this.documentInfo.getGateway().getOpenApiRouteName(), route -> route
.path(DEFAULT_PATH_SEPARATOR + registration.getServiceId() + "/**")
.filters(filter -> filter.rewritePath(DEFAULT_PATH_SEPARATOR + registration.getServiceId() + "/(?<segment>.*)", "/$\{segment}"))
.uri(Constant.HTTP_PREFIX + "localhost:" + SpringContextHolder.getServerPort())
)
.build();
}
/**
* Spring web provider spring web provider.
*
* @return the spring web provider
*/
@Bean
SpringWebProvider springWebProvider() {
return new SpringGatewayProvider();
}
@EventListener(classes = { ApplicationReadyEvent.class, HeartbeatEvent.class, RefreshRoutesEvent.class })
public synchronized void discover() {
List<SwaggerUrl> swaggerUrls = this.discoveryClient.getServices()
.stream()
.map(instance -> {
final SwaggerUrl swaggerUrl = new SwaggerUrl();
swaggerUrl.setName(instance);
swaggerUrl.setDisplayName(instance);
swaggerUrl.setUrl(PathUtils.append(this.documentInfo.getGateway().getApiPathPrefix(), this.springDocConfig.getApiDocs().getPath(), instance));
return swaggerUrl;
})
.toList();
swaggerUiConfig.getUrls().clear();
swaggerUiConfig.getUrls().addAll(swaggerUrls);
}
}
<dependencies>
<!--上面的步骤所构建的包(这个是我本地的,请替换为自己写的)-->
<dependency>
<groupId>com.github.mpcloud</groupId>
<artifactId>framework-document</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-ui</artifactId>
</dependency>
</dependencies>
server:
port: 8080
spring:
application:
name: mpcloud-gateway
cloud:
nacos:
username: nacos
password: nacos
discovery:
server-addr: localhost:8848
namespace: mpcloud
cluster-name: shanghai
heart-beat-timeout: 40000
heart-beat-interval: 20000
ip-delete-timeout: 80000
config:
server-addr: localhost:8848
namespace: mpcloud
timeout: 3000
file-extension: yml
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
globalcors:
cors-configurations:
'[/**]':
allowedHeaders: '*'
allowedMethods: '*'
allowedOrigins: '*'
httpclient:
connect-timeout: 2000
response-timeout: 60s
main:
banner-mode: off
webflux:
multipart:
file-storage-directory: /tmp
max-in-memory-size: 12MB
format:
date-time: yyyy-MM-dd HH:mm:ss
date: yyyy-MM-dd
time: HH:mm:ss
springdoc:
api-docs:
enabled: true
groups:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
info:
title: 网关Api文档
description: 网关Api文档
project-version: 0.0.1
license: https://milo-xiaomeng.github.io
license-url: https://milo-xiaomeng.github.io
contact:
name: milo
url: http://localhost:8080
email: milo.xiaomeng@gmail.com
gateway:
# 如果Nginx设置了代理路径前缀,则此处位置必须为对应前缀 如果没有前缀,可以不设置或者为 /
# 如Nginx如下配置
# location /api{
# proxy_pass http://localhost:8080;
# rewrite "^/api/(.*)$" /$1 break;
# proxy_set_header Host $host:$server_port;
# }
api-path-prefix: /api
show-actuator: true
dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--上面的步骤所构建的包(这个是我本地的,请替换为自己写的)-->
<dependency>
<groupId>com.github.mpcloud</groupId>
<artifactId>framework-document</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
</dependency>
</dependencies>
server:
port: 8083
spring:
application:
name: mpcloud-monitoring
profiles:
active: dev
cloud:
nacos:
username: nacos
password: nacos
discovery:
server-addr: localhost:8848
namespace: mpcloud
cluster-name: shanghai
config:
server-addr: localhost:8848
namespace: mpcloud
timeout: 3000
file-extension: yml
springdoc:
api-docs:
enabled: true
groups:
enabled: true
path: /v3/api-docs
info:
title: 监控Api文档
description: 监控Api文档
project-version: 0.0.1
license: mpcloud.github.com
license-url: mpcloud.github.com
contact:
name: milo
url: http://localhost:8080
email: milo.xiaomeng@gmail.com
show-actuator: true

我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po