012-从零搭建微服务-接口文档(二)

写在最前

如果这个项目让你有所收获,记得 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 控制是否开启接口文档密码校验,通过 usernamepassword 配置登录接口文档的用户名与密码

swagger:
  basic:
    # 是否开启接口文档密码校验
    enabled: true
    username: mingyue
    password: mingyue

启动测试

打开 swagger-ui: http://mingyue-gateway:9100/swagger-ui.html,会弹出登录框,输入 Nacos 中配置的用户名密码登录即可,查看是否配置成功!

012-从零搭建微服务-接口文档(二)_第1张图片

小结

Swagger 接口文档基础功能已经可以使用,但仍有很多很多需要做的地方,比如:

  1. Authorize 功能,也就是 Token 还未使用;
  2. 基于 Openapi 结构体接入第三方工具,如:ApifoxPostman等。为什么有 Swagger-UI ,要接入第三方工具?其实 Swagger-UI 很不好用,哈哈哈~;
  3. 导出接口文档,如PDF等文本格式;
  4. 。。。

山高路远,但仍要脚踏实地,收回来!下一篇我们先控制接口访问,必须携带有效 Token 才可以交互接口!

你可能感兴趣的:(MingYue微服务,微服务,java,架构)