写在最前
如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
源码地址(后端):https://gitee.com/csps/mingyue
源码地址(前端):https://gitee.com/csps/mingyue-ui
文档地址:https://gitee.com/csps/mingyue/wikisapplication-common.yml
迁移配置
mingyue-auth => application-common.yml
将 Sa-Token 配置放入公共配置中,方便 components.security-schemes.apiKey.name= ${sa-token.token-name} 引用
# Sa-Token 配置
sa-token:
# token名称 (同时也是 cookie 名称)
token-name: Authorization
# OAuth2.0 配置
oauth2:
is-code: true
is-implicit: true
is-password: true
is-client: true
优化 Swagger 配置
修改 SwaggerProperties
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.License;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import java.util.Map;
/**
* SwaggerProperties
*
* @author Strive
* @date 2023/6/22 11:00
*/
@Data
@ConfigurationProperties(prefix = "swagger")
public class SwaggerProperties {
/**
* 文档基本信息
*/
@NestedConfigurationProperty
private InfoProperties info = new InfoProperties();
/**
* 组件
*/
@NestedConfigurationProperty
private Components components = null;
/**
* 是否开启 swagger
*/
private Boolean enabled = true;
/**
* 网关
*/
private String gateway;
/**
* 服务转发配置
*/
private Map<String, String> services;
/**
* 文档的基础属性信息
*
* @see io.swagger.v3.oas.models.info.Info
*/
@Data
public static class InfoProperties {
/**
* 标题
*/
private String title = null;
/**
* 描述
*/
private String description = null;
/**
* 联系人信息
*/
@NestedConfigurationProperty
private Contact contact = null;
/**
* 许可证
*/
@NestedConfigurationProperty
private License license = null;
/**
* 版本
*/
private String version = null;
}
}
修改 SwaggerAutoConfiguration
删除 securityScheme() 方法,修改 springOpenAPI()
import com.csp.mingyue.doc.support.SwaggerProperties;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.servers.Server;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.SpringDocConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.context.annotation.Bean;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Swagger 配置
*
* @author Strive
*/
@RequiredArgsConstructor
@AutoConfiguration(before = SpringDocConfiguration.class)
@ConditionalOnProperty(name = "swagger.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(SwaggerProperties.class)
@ConditionalOnMissingClass("org.springframework.cloud.gateway.config.GatewayAutoConfiguration")
public class SwaggerAutoConfiguration {
private final SwaggerProperties swaggerProperties;
private final ServiceInstance serviceInstance;
@Bean
public OpenAPI springOpenAPI() {
OpenAPI openApi = new OpenAPI();
// 文档基本信息
SwaggerProperties.InfoProperties infoProperties = swaggerProperties.getInfo();
Info info = convertInfo(infoProperties);
openApi.info(info);
// 鉴权方式配置
openApi.components(swaggerProperties.getComponents());
Set<String> keySet = swaggerProperties.getComponents().getSecuritySchemes().keySet();
List<SecurityRequirement> list = new ArrayList<>();
SecurityRequirement securityRequirement = new SecurityRequirement();
keySet.forEach(securityRequirement::addList);
list.add(securityRequirement);
// servers 提供调用的接口地址前缀
List<Server> serverList = new ArrayList<>();
String path = swaggerProperties.getServices().get(serviceInstance.getServiceId());
serverList.add(new Server().url(swaggerProperties.getGateway() + "/" + path));
openApi.servers(serverList);
return openApi;
}
/**
* 装填文档的基础属性信息
* @param infoProperties
* @return io.swagger.v3.oas.models.info.Info
*/
private Info convertInfo(SwaggerProperties.InfoProperties infoProperties) {
Info info = new Info();
info.setTitle(infoProperties.getTitle());
info.setDescription(infoProperties.getDescription());
info.setContact(infoProperties.getContact());
info.setLicense(infoProperties.getLicense());
info.setVersion(infoProperties.getVersion());
return info;
}
}
优化 getSysUsers 接口
通过 getSysUsers 接口使用 Swagger 注解小小实战一下
接口类增加 @Tag(name = “用户管理模块”)
@Tag(name = "用户管理模块")
public class SysUserController {
接口增加 @Tag(name = “用户管理模块”)
@Operation(summary = "获取所有用户信息")
public R<List<SysUser>> getSysUsers() {
return R.ok(sysUserService.list());
}
响应类增加 @Schema(description = “用户实体类”)
@Schema(description = "用户实体类")
public class SysUser implements Serializable {
响应类字段增加 @Schema(description = “用户ID”)
@Schema(description = "用户ID")
private Long userId;
接口文档增加身份校验
升级 mingyue-gateway,支持接口文档增加身份校验
接口文档一般在开发环境使用,极其不推荐在生产使用,将接口文档暴露出来非常不安全。开发环境公司内部使用时可以直接使用,无须增加身份校验,如果暴露出去,还是增加一个身份校验比较好,安全些。
增加 SpringDocConfiguration 配置
import lombok.Data;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.SwaggerUiConfigParameters;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* SpringDoc Config
*
* @author Strive
* @date 2023-6-22
*/
@Configuration(proxyBeanMethods = false)
public class SpringDocConfiguration {
/**
* 当 swagger.enabled = true 向 Bean 容器中注册改对象
* @return
*/
@Bean
@Lazy(false)
@ConditionalOnProperty(name = "swagger.enabled", havingValue = "true", matchIfMissing = true)
public List<GroupedOpenApi> apis(SwaggerUiConfigParameters swaggerUiConfigParameters,
SwaggerDocProperties swaggerProperties) {
List<GroupedOpenApi> groups = new ArrayList<>();
// 读取配置服务,添加接口分组,以服务为纬度进行分组
for (String value : swaggerProperties.getServices().values()) {
swaggerUiConfigParameters.addGroup(value);
}
return groups;
}
@Data
@Component
@ConfigurationProperties("swagger")
public class SwaggerDocProperties {
/**
* 添加接口文档的服务信息
*/
private Map<String, String> services;
/**
* 认证参数
*/
private SwaggerBasic basic = new SwaggerBasic();
@Data
public class SwaggerBasic {
/**
* 是否开启 basic 认证
*/
private Boolean enabled;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
}
}
增加 SwaggerBasicGatewayFilter 过滤器
import com.csp.mingyue.gateway.config.SpringDocConfiguration;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
/**
* Swagger 开启 Basic 认证
*
* @author Strive
* @date 2023/6/22
*/
@Slf4j
@RequiredArgsConstructor
public class SwaggerBasicGatewayFilter implements GlobalFilter {
private static final String API_URI = "/v3/api-docs";
private static final String BASIC_PREFIX = "Basic ";
private final SpringDocConfiguration.SwaggerDocProperties swaggerProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (!request.getURI().getPath().contains(API_URI)) {
return chain.filter(exchange);
}
if (hasAuth(exchange)) {
return chain.filter(exchange);
}
else {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add(HttpHeaders.WWW_AUTHENTICATE, "Basic Realm=\"mingyue\"");
return response.setComplete();
}
}
/**
* 简单的basic认证
* @param exchange 上下文
* @return 是否有权限
*/
private boolean hasAuth(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String auth = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
log.info("Basic认证信息为:{}", auth);
if (!StringUtils.hasText(auth) || !auth.startsWith(BASIC_PREFIX)) {
return Boolean.FALSE;
}
String username = swaggerProperties.getBasic().getUsername();
String password = swaggerProperties.getBasic().getPassword();
String encodeToString = Base64Utils
.encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
return auth.equals(BASIC_PREFIX + encodeToString);
}
}
增加 GatewayConfiguration 配置
import com.csp.mingyue.gateway.filter.SwaggerBasicGatewayFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 网关配置
*
* @author Strive
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
public class GatewayConfiguration {
@Bean
@ConditionalOnProperty(name = "swagger.basic.enabled")
public SwaggerBasicGatewayFilter swaggerBasicGatewayFilter(
SpringDocConfiguration.SwaggerDocProperties swaggerProperties) {
return new SwaggerBasicGatewayFilter(swaggerProperties);
}
}
修改 Nacos mingyue-gateway.yml 配置
通过
enabled
控制是否开启接口文档密码校验,通过username
与password
配置登录接口文档的用户名与密码
swagger:
basic:
# 是否开启接口文档密码校验
enabled: true
username: mingyue
password: mingyue
启动测试
打开 swagger-ui: http://mingyue-gateway:9100/swagger-ui.html,会弹出登录框,输入 Nacos 中配置的用户名密码登录即可,查看是否配置成功!
小结
Swagger 接口文档基础功能已经可以使用,但仍有很多很多需要做的地方,比如:
- Authorize 功能,也就是 Token 还未使用;
- 基于
Openapi
结构体接入第三方工具,如:Apifox
、Postman
等。为什么有 Swagger-UI ,要接入第三方工具?其实 Swagger-UI 很不好用,哈哈哈~; - 导出接口文档,如PDF等文本格式;
- 。。。
山高路远,但仍要脚踏实地,收回来!下一篇我们先控制接口访问,必须携带有效 Token 才可以交互接口!