chaihongjun.me

vue3项目生产环境代理方式解决无法配置接口服务器或请求第三方API的cors跨域问题

网上一堆关于在开发环境下vue3配置vite.config.js文件解决cors跨域问题的文章。这篇文章主要介绍的是在生产环境遇到无法配置后端服务器或请求第三方API的情况下解决cors跨域的方法,当然也会涉及开发环境的配置。实际是通过前端可以控制的范围解决开发和生产环境下的cors跨域问题。

前置情况:

vue3+vite+axios开发网站项目

因为使用了axios,所以有基本的封装:

//src/request/index.js
import axios from 'axios'
import { ElMessage } from 'element-plus'

// 创建 axios 实例
const service = axios.create({
  baseURL: import.meta.env.DEV ? '/api' : 'https://maizuo.chaihongjun.me/api',
  timeout: 3000, // 请求超时时间
})
// 其他代码 ...
export default service

这里需要注意的是

baseURL: import.meta.env.DEV ? '/api' : 'https://maizuo.chaihongjun.me/api',

  1. 用于判断是开发还是其他环境,如果是开发环境,那么基础地址是"/api",如果是其他环境,这里基本等价于生产环境,那就是后面的地址。

  2. 这里基础地址baseURL要和后面的接口请求方法的地址做拼接,拼接后的地址(axiosInstance.baseURL+axiosInstance.url)将是完整的请求URL。

  3. 这里baseURL这么设置是方便后续的开发环境和生产环境进行转发请求。

  4. 这两个地址特征也需要注意,因为是本地开发环境,所以基础地址设置为一个绝对地址,而生产环境是要部署到最终服务器,所以必须有最终前端服务器的域名网址,这里的域名网址一定是你部署的前端服务器的域名网址,如这里的https://maizuo.chaihongjun.me。

接口请求方法文件:

//src/api/index.js
import service from '@/request/index.js'

//1. 城市列表
export const getCitiesInfo = () => {
  return service.get('/getCitiesInfo')
}
// ... 其他接口请求方法

因为是拼接,所以在开发环境下执行请求接口方法getCitiesInfo的时候,请求的地址就是/api/getCitiesInfo。生产环境请求的地址就是https://maizuo.chaihongjun.me/api/getCitiesInfo。

无论是开发还是生产环境,我们最终请求的地址是第三方的API,这意味着我们没办法在接口服务器端去处理cors跨域问题,实际是通过前端可以控制的范围解决开发和生产环境下的cors跨域问题。

开发环境的cors解决方式是通过配置vite的代理模式:

//vite.config.js
// ... 其他代码
export default defineConfig(({ command }) => ({
  plugins: [
    vue(),
    vueDevTools(),
    AutoImport({
      resolvers: [VantResolver()],
    }),
    Components({
      resolvers: [VantResolver()],
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    },
  },
  server: { //本地开发服务器
    proxy: command === 'serve' ? { //代理配置
      '/api': {
        target: 'https://api.iynn.cn/film/api/v1',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '') // 去掉 /api 前缀
      }
    } : {}
  },
  esbuild: {
    drop: ['console', 'debugger'],
  }
}))

代理配置那里,当请求地址是/api/getCitiesInfo的时候,因为这个地址是以/api开头,所以就被转发到target:https://api.iynn.cn/film/api/v1。于是变成了去请求https://api.iynn.cn/film/api/v1/api/getCitiesInfo。这个与真实的请求地址https://api.iynn.cn/film/api/v1/getCitiesInfo就差在路径中多了一个/api/。于是又通过rewrite: (path) => path.replace(/^\/api/, '')去以/api开头的这个掉多余的/api/,这样开发环境就请求到正确地址。

生产环境的转发依靠的是前端服务器完成(nginx):

server {

# ...其他配置
location /api { # 转发配置
    proxy_pass https://api.iynn.cn/film/api/v1/; # 一定要以"/" 结尾 
    proxy_set_header Host api.iynn.cn; #将请求头中的 Host 字段设置为目标服务器的域名,确保目标服务器能够正确识别请求
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # 设置 CORS 头
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';

    # 处理预检请求(OPTIONS)
    if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Allow-Origin' '*';
      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
      add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';
      add_header 'Access-Control-Max-Age' 1728000;
      add_header 'Content-Type' 'text/plain charset=UTF-8';
      add_header 'Content-Length' 0;
      return 204;
    }
    # 调试信息
    add_header X-Debug-Message "Proxied to API" always;
  }
 
  location / {  #解决vue项目其他页面404
    try_files $uri $uri/ /index.html;
  }
  
}

前端服务器匹配/api开头的请求,比如请求是https://maizuo.chaihongjun.me/api/getCitiesInfo,其中路径/api/getCitiesInfo这个就匹配到规则了,将这个请求转发到了代理目标服务器。

原始请求是https://maizuo.chaihongjun.me/api/getCitiesInfo,请求头Host:maizuo.chaihongjun.me。当nginx转发到https://api.iynn.cn/film/api/v1/,并且拼接剩余路径,就变成了https://api.iynn.cn/film/api/v1/getCitiesInfo。

这里重点是proxy_pass的规则:如果proxy_pass是以"/"结尾,则将匹配的部分"/api"替换为proxy_pass的路径部分("/film/api/v1/")。

所以原始路径"/api/getCitiesInfo" 中的"/api"经过替换变成了"/film/api/v1/getCitiesInfo"。proxy_set_header Host api.iynn.cn 的配置则是设置了新的请求头。

特别注意,如果proxy_pass 不是以"/"结尾,则会将匹配部分追加到proxy_pass路径后:

 location /api {
    proxy_pass https://api.iynn.cn/film/api/v1;
    proxy_set_header Host api.iynn.cn;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

代理之后的请求URL将是:https://api.iynn.cn/film/api/v1/api/getCitiesInfo


知识共享许可协议本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。作者:柴宏俊»