[Web] 接口文档工具: Swagger

目录

0 引言

1 springboot 整合 swagger2 ,并配置密码登录认证

依赖引入

  • spring-boot.version : 2.3.12.RELEASE
  • swagger.version : 1.5.14
  • springfox-swagger2.version : 2.8.0
<!-- swagger | start -->
<!-- springfox-swagger2 : 内部依赖/自动引入 swagger-annotations / swagger-models -->
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<exclusions>
		<exclusion>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
		</exclusion>
	</exclusions>
	<version>${springfox-swagger2.version}</version>
</dependency>

<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger-ui</artifactId>
	<version>${springfox-swagger2.version}</version>
</dependency>

<dependency>
	<groupId>io.swagger</groupId>
	<artifactId>swagger-annotations</artifactId>
	<version>${swagger.version}</version>
</dependency>

<dependency>
	<groupId>io.swagger</groupId>
	<artifactId>swagger-models</artifactId>
	<version>${swagger.version}</version>
</dependency>
<!-- swagger | end -->


<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<version>${spring-boot.version}</version>
	<exclusions>
		<exclusion>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-api</artifactId>
		</exclusion>
		<!-- 去掉默认日志配置logback -->
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-logging</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<!-- 模板引擎 -->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
	<version>${spring-boot.version}</version>
</dependency>

配置文件: application.yml

# swagger-doc
swagger:
  # requestHander 的包路径
  package: cn.xxx.yyy.app
  title: project API
  description: project API
  version: 1.0
  # 登录认证的账户密码
  username: ${SWAGGER_USERNAME:admin}
  password: ${SWAGGER_PASSWORD:admin}
  contactAuthor: jack
  contactUrl: localhost:${server.port}/swagger-ui.html
  contactEmail: 1111@qq.com

spring:
  resources:
    # 静态资源的路径
    static-locations: classpath:/static
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    encoding: UTF-8 #编码
    suffix: .html #模板后缀
    mode: HTML #模板

注:需配置静态路径的目录(spring.resources.static-locations)、模板引擎的策略(spring.thymeleaf)

启用 Swagger2

方式1:@EnableSwagger2 注解

  • @EnableSwagger2springfox提供的一个注解,代表swagger2相关技术开启。启用后,会自动扫描当前类所在包,及子包中所有类型的swagger相关注解,做swagger文档的定制
//@EnableFeignClients // [feign] Feign 客户端调用第三方接口,本工程暂不涉及
@EnableDiscoveryClient(autoRegister = true) // [nacos] 注册中心 | feign 整合 nacos 需要启用此注解(服务提供端 + Feign 客户端) | true: 开启服务自动注册功能,项目启动后能在 nacos 的web端界面看到服务的相关信息,且具备拉取服务信息的功能(前提是 nacos.discovery.enabled 不为 false)

@EnableSwagger2 // [swagger] 开启swagger2
@Api(tags = "yyyy app 启动模块") // [swagger] 作用范围: 模块类 | tags = "模块说明"

@MapperScan("cn.xxx.yyy.app") // [mybatis]
@ServletComponentScan(basePackages = "cn.xxx.yyy.app")// 扫描 Servlet 包,例如: ApplicationRequestListener
@SpringBootApplication // [spring-boot]
//@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@RestController // // [spring-web(mvc / webflux)]
//@RequestMapping("/") // [spring-web(mvc / webflux)]
@ComponentScan(basePackages = {"cn.xxx.yyy"}) // [spring-context]
public class xxxxApplication {
   ...
}

方式2:配置类 : Swagger2

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.List;
//@EnableSwagger2 此处,也可利用注解
@Configuration
public class Swagger2Configuration implements WebMvcConfigurer {
    /**
     * 是否启用 swagger ui
     * @note 方法2 : `springfox.documentation.enabled=true/false`
     */
    @Value("${SWAGGER_UI_ENABLE:true}") //@Value : 支持从本地或远端NACOS配置中心 的 application/bootstrap.* 、环境变量中读取配置项
    private Boolean enable;

    private static final String splitor = ";";

    //可以获取application.yml 配置的参数
    @Autowired
    public Environment env;

    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public Docket api() {
        //添加 head 参数 start
        ParameterBuilder tokenPar = new ParameterBuilder();
        List<Parameter> params = new ArrayList<Parameter>();
        //这里配置 访问jwt令牌请求头,不需要的不加
        /**
         params.add(
         new ParameterBuilder()
         .name("authorization")
         .description("令牌")
         .modelRef(new ModelRef("string"))
         .parameterType("header")
         .required(false)
         .build()
         );
         **/

        log.info("swagger-ui-enable: {}", enable);

        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                //.apis(RequestHandlerSelectors.any())
                .apis(RequestHandlerSelectors.basePackage(env.getProperty("swagger.package")))
                //.apis(basePackage("com.tyzn.controller.api"  + splitor + "com.tyzn.controller.backApi"))
                .paths(PathSelectors.any())
                //.paths(Predicates.not(PathSelectors.regex("/error.*")))//过滤掉 error* 页面
                //.paths(Predicates.or(PathSelectors.regex("/error.*"), PathSelectors.regex("/admin/.*") ) ) //过滤掉 error* 页面或 /admin/ 路径的请求
                .build()
                .globalOperationParameters(params)
                .apiInfo(apiInfo())
                .enable(enable); // 设置是否启动 Swagger | 若为 false,在浏览器将无法访问
    }

    private ApiInfo apiInfo() {
        ApiInfoBuilder ab = new ApiInfoBuilder();
        ab.title(env.getProperty("swagger.title"));
        ab.description(env.getProperty("swagger.description"));
        ab.version(env.getProperty("swagger.version"));
        ab.contact(new Contact(env.getProperty("swagger.contactAuthor"), env.getProperty("swagger.contactUrl"), env.getProperty("swagger.contactEmail")));
        //ab.contact(new Contact("Jack", "localhost:8080/swagger-ui.html", "xxxx@qq.com"));
        return ab.build();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    public static Predicate<RequestHandler> basePackage(final String basePackage) {
        return input -> declaringClass(input).transform(handlerPackage(basePackage)).or(true);
    }

    private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {
        return input -> {
            // 循环判断匹配
            for (String strPackage : basePackage.split(splitor)) {
                boolean isMatch = input.getPackage().getName().startsWith(strPackage);
                if (isMatch) {
                    return true;
                }
            }
            return false;
        };
    }

    private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
        return Optional.fromNullable(input.declaringClass());
    }
}

登录过滤器 : SwaggerFilter


import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @author 闲走天涯
 * @version V1.0
 * @Title: Swagger2
 * @Description: 登录过滤器
 * @date 2021-08-05
 */
@Component
public class SwaggerFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        String requestURI = req.getRequestURI();
        //swagger-security
        if (requestURI.contains("swagger-ui")) {
            //获取 session
            HttpSession session = req.getSession();
            //判断 swagger 是否已经登录
            Object swaggerLogin = session.getAttribute("swaggerLogin");
            if (swaggerLogin == null || StringUtils.isEmpty(swaggerLogin.toString()) || !Boolean.parseBoolean(swaggerLogin.toString())) {
                String basePath = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + req.getContextPath() + "/";
                String fullPath = basePath + "swagger/toLogin";
                //重定向登录页面
                res.sendRedirect(fullPath);
            } else {
                chain.doFilter(req, res);
            }
        } else {
            chain.doFilter(req, res);
        }
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
    }
}

控制器 : SwaggerController

  • SwaggerController : swagger 登录控制层-登录接口

import cn.xxx.common.entity.SwaggerAccount;
import cn.xxx.model.dto.XResponse;
import com.alibaba.cloud.commons.lang.StringUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

@Controller // 应该用@RestController, 如果是 @Controller,会认为你返回的一个 modelandview 要解析,即会报错"TemplateInputException: Error resolving template 无法解析页面"
@RequestMapping(value = "/swagger")
@Slf4j
public class SwaggerController {

    @Value("${swagger.username}")
    private String swaggerUserName;

    @Value("${swagger.password}")
    private String swaggerPassword;

    @PostConstruct
    public void init(){
        log.info("swaggerUserName: {}", swaggerUserName);
        log.info("swaggerPassword: {}", swaggerPassword);
    }

    @GetMapping(value = {"toLogin", ""})
    public String toLogin(HttpServletRequest request) {
        Object swaggerLogin = request.getSession().getAttribute("swaggerLogin");
        if (swaggerLogin != null && StringUtils.isNotBlank(swaggerLogin.toString()) && Boolean.parseBoolean(swaggerLogin.toString())) {
            return "redirect:/swagger-ui.html";
        }
        return "swaggerLogin";
    }

    @PostMapping(value = "/login")
    @ResponseBody
    public ResponseEntity<XResponse> login(@RequestBody SwaggerAccount swaggerAccount, HttpServletRequest request) {
        log.info("swaggerAccount : {} , config.username : {}, config.password : {}", swaggerAccount, swaggerUserName, swaggerPassword);
        if (
            StringUtils.isNotBlank(swaggerAccount.getUsername()) && StringUtils.isNotBlank(swaggerAccount.getPassword())
            && swaggerUserName.equals(swaggerAccount.getUsername()) && swaggerPassword.equals(swaggerAccount.getPassword())
        ) {
            request.getSession().setAttribute("swaggerLogin", true);
            return ResponseEntity.ok( XResponse.success("ok") );
        }
        return ResponseEntity.ok( XResponse.failure("fail") );
    }
}
  • SwaggerAccount
import lombok.*;

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class SwaggerAccount {
    private String username;

    private String password;
}
  • XResponse
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

@ApiModel(value = "公共响应对象", description = "公共响应对象") // [swagger] 作用范围: 模型类,如VO、BO
public class XResponse<T> {
    private static final long serialVersionUID = 1L;

    /**
     * 状态码
     */
    @ApiModelProperty(
            value = "响应状态",
            example = "true",
            hidden = false
    ) // [swagger] 作用范围: 类的方法、属性上 / hidden=true 可隐藏该属性 / value = "属性说明"
    private Boolean status;

    /**
     * 返回对象
     */
    @ApiModelProperty(value = "业务数据")
    private T data;

    /**
     * 错误码
     */
    @ApiModelProperty(value = "错误码")
    private String errorCode;

    /**
     * 错误信息
     */
    @ApiModelProperty(value = "错误信息", example = "error message")
    private String errorMsg;

    @ApiModelProperty(value = "traceId", example = "trace id")
    private String traceId;

    public XResponse() {

    }

    public XResponse(Boolean status, T data, String errorCode, String errorMsg) {
        this.status = status;
        this.data = data;
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public static XResponse create(Boolean status, Object data, String errorCode,
                                        String errorMsg) {
        return new XResponse(status, data, errorCode, errorMsg);
    }

    public static XResponse success() {
        return create(true, null, null, null);
    }

    public static <T> XResponse success(T obj) {
        return create(true, obj, null, null);
    }

    public static XResponse failure() {
        return create(false, null, null, null);
    }

    public static XResponse failure(String errorCode) {
        return create(false, null, errorCode, null);
    }

    public static XResponse failure(String errorCode, String errorMsg) {
        return create(false, null, errorCode, errorMsg);
    }

    public Boolean getStatus() {
        return status;
    }

    public T getData() {
        return data;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setStatus(Boolean status) {
        this.status = status;
    }

    public void setData(T data) {
        this.data = data;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public String getTraceId() {
        return traceId;
    }

    public void setTraceId(String traceId) {
        this.traceId = traceId;
    }

    @Override
    public String toString() {
        return "XResponse{" +
                "status=" + status +
                ", data=" + data +
                ", errorCode='" + errorCode + '\'' +
                ", errorMsg='" + errorMsg + '\'' +
                ", traceId='" + traceId + '\'' +
                '}';
    }
}

登录页面 : swagger-login.html

  • /resources/templates/swagger-login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <!-- https://www.runoob.com/html/html5-intro.html -->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Login</title>
    <style>
        .login {
            width: 100%;
            height: 600px;
            text-align: center;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-flow: column;
        }

        .body > div {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
        }

        .body > div > input {
            margin-left: 10px;
        }
    </style>
</head>
<body>
<div class="login">
    <h2 class="title">欢迎使用 SWAGGER </h2>
    <div class="body">
        <div>
            <span>账号:</span>
            <input type="text" id="username">
        </div>
        <div>
            <span>密码:</span>
            <input type="password" id="password">
        </div>
    </div>

    <button type="button" id="logBtn">登陆</button>

</div>

<!-- 对应文件在服务器的物理存储路径: /resources/static/js/jquery-3.7.1.js -->
<script src="../js/jquery-3.7.1.js"></script>
<!--<script src="https://code.jquery.com/jquery-3.7.1.js"></script>-->
<script>
    $('#logBtn').click(function () {
        let username = $("#username").val();
        let password = $("#username").val();
        if (username === '' && password === '') {
            alert('username and password must be not empty!');
            return false;
        }
        $.ajax({
            url: '/swagger/login',
            //data: { username: username, password: password },
            data : JSON.stringify({ username: username, password: password }),
            type: 'post',
            //contentType: 'application/x-www-form-urlencoded'
            contentType: 'application/json',
            //dataType: 'json',
            success: function (result) {
                if (result.status) {
                    location.href = '/swagger-ui.html';
                } else {
                    alert(result.errorCode + ':' + result.errorMsg);
                }
            }
            , error : function (result) {
                console.log("error")
            }
        });
    });
</script>
</body>
</html>

测试验证

方式1:浏览器

方式2:curl

/ # curl http://127.0.0.1:9527/swagger-ui.html -v
*   Trying 127.0.0.1:9527...
* Connected to 127.0.0.1 (127.0.0.1) port 9527
> GET /swagger-ui.html HTTP/1.1
> Host: 127.0.0.1:9527
> User-Agent: curl/8.9.0
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 302 
< Set-Cookie: JSESSIONID=98961270D1A3A14EEA856FF0D292C268; Path=/; HttpOnly
< Location: http://127.0.0.1:9527/swagger/toLogin
< Content-Length: 0
< Date: Thu, 01 Aug 2024 09:24:02 GMT
< 
* Connection #0 to host 127.0.0.1 left intact

302(重定向) 到 /swagger/toLogin ,说明生效了。

X 参考文献

"控制台日志输出告警,页面按F12打开swagger控制台"/","/csrf"两个404接口" => 在 2.9.x 版本中有,暂时还没有找到好的解决方案,回退到 2.8.0 版本可以解决

posted @   千千寰宇  阅读(64)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示