likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 48

写在最前

如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。

源码地址(后端):gitee.com/csps/mingyu…

源码地址(前端):gitee.com/csps/mingyu…

文档地址:gitee.com/csps/mingyu…

迁移配置

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-从零搭建微服务-接口文档(二)

小结

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

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

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

转载自:https://juejin.cn/post/7247785683959824440
评论
请登录