RuoYi-Vue3项目中Swagger接口测试404,端口问题解析排查

一 问题概述

版本:ruoyi前后端分离版,ruoyi版本3.9.0 前端Vue3 后端Spring Boot 2.5.15 本地测试环境

ruoyi界面中系统工具下的系统接口集成了Swagger,当对其页面上的接口进行请求测试时却发生了404报错。具体表现如下图

二 问题排查

1、与Vue2进行对比

1.1 接口地址对比

通过浏览器开发者工具查看接口地址信息 直接启动Vue2的前端,后端使用同样的代码再进行测试

当使用了Vue2的前端后,接口测试是成功的。

这里可以看到Vue3接口的地址为:http://localhost:8080/dev-api/test/user/1

但Vue2接口的地址为:http://localhost/dev-api/test/user/1

其中1为请求参数

一个是8080端口 一个是80端口。

1.2 接口真实地址

通过上述对比,Vue2是可以成功请求的,那么是不是Vue3的接口地址生成不对呢 先来看下后端真实的接口地址是什么 yaml配置

yml 复制代码
# Swagger配置
swagger:
  # 是否开启swagger
  enabled: true
  # 请求前缀
  pathMapping: /dev-api

swaggerConfig

java 复制代码
@Configuration
public class SwaggerConfig
{
    /** 系统基础配置 */
    @Autowired
    private RuoYiConfig ruoyiConfig;

    /** 是否开启swagger */
    @Value("${swagger.enabled}")
    private boolean enabled;

    /** 设置请求的统一前缀 */
    @Value("${swagger.pathMapping}")
    private String pathMapping;

    /**
     * 创建API
     */
    @Bean
    public Docket createRestApi()
    {
        return new Docket(DocumentationType.OAS_30)
                // 是否启用Swagger
                .enable(enabled)
                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
                .apiInfo(apiInfo())
                // 设置哪些接口暴露给Swagger展示
                .select()
                // 扫描所有有注解的api,用这种方式更灵活
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                // 扫描指定包中的swagger注解
                // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
                // 扫描所有 .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                /* 设置安全模式,swagger可以设置访问token */
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts())
                .pathMapping(pathMapping);
    }
    
    .......
    
}

TestController

java 复制代码
/**
 * swagger 用户测试方法
 * 
 * @author ruoyi
 */
@Api("用户信息管理")
@RestController
@RequestMapping("/test/user")
public class TestController extends BaseController
{
    private final static Map<Integer, UserEntity> users = new LinkedHashMap<Integer, UserEntity>();
    {
        users.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));
        users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
    }

    @ApiOperation("获取用户详细")
    @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
    @GetMapping("/{userId}")
    public R<UserEntity> getUser(@PathVariable Integer userId)
    {

        if (!users.isEmpty() && users.containsKey(userId))
        {
            return R.ok(users.get(userId));
        }
        else
        {
            return R.fail("用户不存在");
        }
    }
    
    ......
    
}

从代码中可以看出(后端应用端口为8080)获取用户详细信息接口的真实地址为:http://localhost:8080/test/user ,pathMapping里配置了 "/dev-api" swagger在生成所有接口路径时,前面都要加上 "/dev-api"

1.3 代理对比

为了解决跨域问题,Vue里会有代理配置,先来查看下Vue2中的配置 .env.development

# 复制代码
VUE_APP_TITLE = 若依管理系统

# 开发环境配置
ENV = 'development'

# 若依管理系统/开发环境
VUE_APP_BASE_API = '/dev-api'

# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true

vue.config.js

js 复制代码
......

const baseUrl = 'http://localhost:8080' // 后端接口

const port = process.env.port || process.env.npm_config_port || 80 // 端口

......

// webpack-dev-server 相关配置
  devServer: {
    host: '0.0.0.0',
    port: port,
    open: true,
    proxy: {
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
        target: baseUrl,
        changeOrigin: true,
        pathRewrite: {
          ['^' + process.env.VUE_APP_BASE_API]: ''
        }
      },
      // springdoc proxy
      '^/v3/api-docs/(.*)': {
        target: baseUrl,
        changeOrigin: true
      }
    },
    disableHostCheck: true
  }

......

这里看到其会拦截端口为80带有"/dev-api"的请求转向 http://localhost:8080 并去掉"/dev-api" 再来看Vue3中的代理配置 vite.config.js

js 复制代码
......

const baseUrl = 'http://localhost:8080'

......

// vite 相关配置
server: {
  port: 80,
  host: true,
  open: true,
  proxy: {
    // https://cn.vitejs.dev/config/#server-proxy
    '/dev-api': {
      target: baseUrl,
      changeOrigin: true,
      rewrite: (p) => p.replace(/^\/dev-api/, '')
    },
     // springdoc proxy
     '^/v3/api-docs/(.*)': {
      target: baseUrl,
      changeOrigin: true,
    }
  }
}

......

同样是会拦截端口为80带有"/dev-api"的请求转向 http://localhost:8080 并去掉"/dev-api"

Vue2中通过webpack-dev-server进行代理,Vue3通过vite代理

报错原因

Vue2的Swagger中接口地址为 http://localhost/dev-api/test/user/ ,当请求该地址时就会被webpack-dev-server拦截然后转向http://localhost:8080/test/user 这是没问题的,但是Vue3的Swagger中接口地址为 http://localhost:8080/dev-api/test/user/ 其并不会被vite拦截,因为端口不是80,而后端接口真实地址并不包含"/dev-api",所以就会产生404报错。

1.4 问题定位

1.那么为什么Vue3中swagger的接口的端口地址为8080而Vue2中却是80呢?

ruoyi前端打开swagger的地址是 http://localhost/dev-api/swagger-ui/index.html ,无论在Vue2还是Vue3中都会被拦截转发到 http://localhost:8080/swagger-ui/index.html 在Vue2中访问 http://localhost/dev-api/swagger-ui/index.html 查看Swagger界面的Servers为http://localhost:80

在Vue2中访问访问 http://localhost:8080/swagger-ui/index.html 查看Swagger界面的Servers为 http://localhost:8080

在Vue3中访问 http://localhost/dev-api/swagger-ui/index.html 查看Swagger界面的Servers为http://localhost:8080

在Vue3中访问 http://localhost:8080/swagger-ui/index.html 查看Swagger界面的Servers为http://localhost:8080

Vue3这里端口都是8080

这里的服务地址是怎么生成的呢?

Spring Boot接收到请求后会优先使用X-Forwarded-*头来推断服务器URL。如果没有X-Forwarded头,它会使用请求中的Host头。Swagger在生成页面时会读取这些信息。

2.那么这就来看一下请求中的请求头headers里的X-Forwarded-*与Host都有什么。

在后端TestController中新增一个方法来打印请求中的headers

java 复制代码
@ApiOperation("请求头打印")
@GetMapping("/debug/headers")
public Map<String, String> listAllHeaders(HttpServletRequest request) {
    Enumeration<String> headerNames = request.getHeaderNames();
    Map<String, String> headers = new HashMap<>();
    while (headerNames.hasMoreElements()) {
        String key = headerNames.nextElement();
        headers.put(key, request.getHeader(key));
    }
    return headers;
}

重启后端服务并访问 http://localhost/dev-api/test/user/debug/headers

注意这里需要有身份认证,要在请求的headers里加上Authorization

这里通过接口工具来请求,请求地址与headers如图

启动Vue2的前端代码并发送请求,查看结果

再换Vue3的代码启动并请求

可以看到Vue2的代理转发后会附带有X-Forwarded-*信息,其中"x-forwarded-host": "localhost"是转发前的地址,Spring Boot优先读取的就是它。而Vue3的代理转发后没有,说明vite的代理转发后没有添加X-Forwarded-*头信息。但是因为都设置了changeOrigin: true,Vue2和Vue3都更改了Host为localhost:8080,Spring Boot在Vue3这里读取不到x-forwarded-host就会读取Host,所以端口就会变成8080。

三 解决方案

1、后端解决方式

后端解决方式比较简单,可以把ruoyi-admin模块中yml的swagger配置的pathMapping值置空,这样swagger中接口地址就会变成 http://localhost:8080/test/user 也就是后端接口的真正地址,再请求就不会发生404的报错了。

2、前端解决方式

可以将Vue3中vite配置的changeOrigin改为false,这样就不会更改Host值,在springdoc读取的时候也会读取到原地址的Host。但这样有可能会对别的接口造成影响。 也可以给vite配置中加上添加X-Forwarded-*头信息,加上后的vite配置为

js 复制代码
// vite 相关配置
server: {
  port: 80,
  host: true,
  open: true,
  proxy: {
    // https://cn.vitejs.dev/config/#server-proxy
    '/dev-api': {
      target: baseUrl,
      changeOrigin: true,
      rewrite: (p) => p.replace(/^\/dev-api/, ''),
      configure: (proxy, options) => {
        proxy.on('proxyReq', (proxyReq, req, res) => {
          // 添加X-Forwarded-*头
          proxyReq.setHeader('X-Forwarded-Host', req.headers.host);
          proxyReq.setHeader('X-Forwarded-Proto', 'http');
          proxyReq.setHeader('X-Forwarded-For', req.socket.remoteAddress);
        });
      }
    },
     // springdoc proxy
     '^/v3/api-docs/(.*)': {
      target: baseUrl,
      changeOrigin: true,
    }
  }
}
相关推荐
yvvvy几秒前
前端必懂的 Cache 缓存机制详解
前端
北海几经夏16 分钟前
React自定义Hook
前端·react.js
龙在天20 分钟前
从代码到屏幕,浏览器渲染网页做了什么❓
前端
TimelessHaze21 分钟前
【performance面试考点】让面试官眼前一亮的performance性能优化
前端·性能优化·trae
yes or ok33 分钟前
前端工程师面试题-vue
前端·javascript·vue.js
我要成为前端高手1 小时前
给不支持摇树的三方库(phaser) tree-shake?
前端·javascript
Noxi_lumors1 小时前
VITE BALABALA require balabla not supported
前端·vite
周胜21 小时前
node-sass
前端
aloha_1 小时前
Windows 系统中,杀死占用某个端口(如 8080)的进程
前端
牧野星辰1 小时前
让el-table长个小脑袋,记住我的滚动位置
前端·javascript·element