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,
    }
  }
}
相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax