主题
开发代理(科普 + 命令)
一、为什么需要代理
前端跑在 localhost:3100,后端在 192.168.3.240:81。浏览器同源策略会拦截这种跨域请求。解决思路有三条路,复杂度递进:
浏览器 ──① 直发──> 后端地址(需后端开 CORS)
浏览器 ──② 代理──> localhost:3100/dev-api ──> 开发服务器转发 ──> 后端
浏览器 ──③ 免重启代理──> 改配置刷新即切换后端(无需重启 dev server)二、/dev-api 前缀是哪来的(先看这个)
/dev-api 不是后端真实路径,而是前端约定 + 代理规则 + 构建替换三者配合出来的"暗号":
- 前端写的是假路径:业务代码里请求写成
request('/dev-api/user/info'),/dev-api只是个约定前缀,用来标记"这条请求要走代理",不代表任何真实后端路径。 - 代理靠它识别要转发的请求:开发服务器代理规则
proxy: { "/dev-api": {...} }表示"所有以/dev-api开头的请求都拦下来转发给后端"——所以前端用的前缀必须和代理配置里的 key 一致,否则拦不到。 - 生产环境被构建替换:
/dev-api只存在于开发期。打包时(由vue.config.js的publicPath或.env的VUE_APP_BASE_API)会把/dev-api换成生产真实前缀(如/gov/server或空串同源),线上请求根本不带/dev-api。
结论:
/dev-api是开发期和代理之间的暗号,前后端 + 代理三处统一即可;换成/api、/proxy都行,只要一致。
三、方案一:浏览器直发(直接用后端地址)
前端代码里直接写后端地址,不经过开发服务器代理。
javascript
// 前端直接请求后端(无代理)
fetch("http://192.168.3.240:81/api/user/info")
.then(r => r.json())
.then(console.log);优势
- 零前端代理配置,单文件就能跑,最简单。
问题 / 劣势
- 开发/测试阶段会被 CORS 阻断:浏览器同源策略拦跨域,本地直接跑不通,除非后端显式开 CORS。
- 后端地址写死在前端:换环境(dev/test/prod)得改配置或重新构建,不如代理写相对路径
/dev-api灵活。 - 依赖后端开 CORS 放行:浏览器直发要能通,必须后端显式开启 CORS 头(增加后端配合成本);若后端图省事把
Access-Control-Allow-Origin设成*(允许任意源),接口会被任意网站跨域调用,比代理方案(前端不出现后端地址)更不可控。
四、方案二:开发服务器代理(proxy)
前端只写本地相对路径 /dev-api,由 webpack / vite 的开发服务器转发到真实后端,彻底绕过浏览器 CORS。
Vue CLI(vue.config.js)
javascript
module.exports = {
devServer: {
host: "0.0.0.0",
proxy: {
"/dev-api": {
target: "http://192.168.3.240:81", // 后端真实地址
changeOrigin: true, // 改 Host 头,让后端以为请求来自自己
pathRewrite: { "^/dev-api": "" } // 去掉前缀再转发
}
}
}
};Vite(vite.config.js)
javascript
export default {
server: {
host: "0.0.0.0",
proxy: {
"/server": {
target: "http://192.168.3.240:81",
changeOrigin: true,
ws: true // 支持 WebSocket
}
}
}
};优势
- 绕过 CORS,前端代码不出现后端真实地址(只写
/dev-api)。 - 换后端只改
target,环境切换干净。
问题 / 劣势
- 改
target/pathRewrite后需重启 dev server:webpack、vite 都在启动时一次性读取配置,没有运行时热更新(见方案三解决)。
五、方案三:免重启方案(动态读取 target,刷新即生效)
针对 Vue CLI(webpack),把后端地址抽到独立文件 .dev-proxy.js,用 http-proxy-middleware 的 router 函数 + 清除 require.cache,每次请求动态读取,改地址只需刷新浏览器,不用重启 dev server。
根目录 .dev-proxy.js(日常只改这一行)
javascript
module.exports = {
target: 'http://192.168.3.72/gov/server' // dev
// target: 'http://192.168.4.132:8080' // 陶璐
// target: 'http://192.168.3.172:8080' // 张数数
// target: 'http://192.168.4.178:8080' // 张明辉
// target: 'http://192.168.4.22:8080' // 柴培炀
}vue.config.js(一次写好,之后只动上面 .dev-proxy.js)
javascript
"use strict";
const path = require("path");
// 实时读取 .dev-proxy.js 的 target(每次清 require 缓存,改地址无需重启 dev server)
const PROXY_CONF_PATH = path.resolve(__dirname, ".dev-proxy.js");
function getProxyTarget() {
delete require.cache[require.resolve(PROXY_CONF_PATH)]; // 清缓存,强制重新读取
const conf = require(PROXY_CONF_PATH);
if (!conf || !conf.target) throw new Error(".dev-proxy.js 未配置 target");
return conf.target;
}
const port = process.env.port || process.env.npm_config_port || 90;
module.exports = {
devServer: {
host: "0.0.0.0",
port,
open: true,
before(app) {
app.get("/__proxy-target", (req, res) => res.json({ target: getProxyTarget() }));
const hpm = require("http-proxy-middleware");
const createProxy = hpm.createProxyMiddleware || hpm;
app.use(
createProxy("/dev-api", {
target: "http://0.0.0.0",
changeOrigin: true,
secure: false,
pathRewrite: { "^/dev-api": "" },
router: function () { // 每次请求动态返回目标,实现免重启切换
try { return getProxyTarget(); }
catch (e) {
console.log("[proxy] .dev-proxy.js 配置异常,拒绝转发:", e && e.message);
return "http://127.0.0.1:1"; // 故意不可达,触发 502,绝不偷偷回退
}
},
onProxyRes: function (proxyRes) { proxyRes.headers["x-proxy-target"] = getProxyTarget(); },
onError: function (err, req, res) {
let target = "";
try { target = getProxyTarget(); } catch (e) { target = "(.dev-proxy.js 配置异常:" + (e && e.message) + ")"; }
console.log("[proxy] 转发失败,当前 target:", target, "|", err && err.message);
if (res && !res.headersSent) {
res.writeHead(502, { "Content-Type": "application/json; charset=utf-8" });
res.end(JSON.stringify({ code: 502, msg: "代理不可达,当前 target:" + target }));
}
}
})
);
}
}
};优势
- 日常只改
.dev-proxy.js一行,刷新浏览器即切换后端,不用重启 dev server。 - 配置异常时拒绝转发并返回 502,不会偷偷连错地址。
问题 / 劣势
- 只对 Vue CLI(webpack)有效,Vite 没有这种动态
router机制,改 target 仍需重启。 - 配置比方案二复杂,新项目若用 Vite 直接用方案二即可。
结论:你们主力是 Ruoyi(Vue CLI),统一用本方案最省事;只有 Vite 新项目才退回方案二(接受重启)。
六、代理参数详解
| 参数 | 说明 | 示例值 |
|---|---|---|
target | 代理目标地址 | http://192.168.3.240:81 |
changeOrigin | 修改 Host 和 Referer | true |
pathRewrite | 路径重写(去掉前缀) | { '^/dev-api': '' } |
ws | 支持 WebSocket 代理 | true |
secure | 是否检查 SSL 证书 | false(自签证书时) |
七、三种转发方式对比
| 方式 | 原理 | 适用 | 前端是否见后端地址 |
|---|---|---|---|
| 浏览器直发 | 前端直接请求后端(需后端开 CORS) | 极简单页 / 后端已开 CORS | 是 |
| 开发代理(proxy) | 开发服务器转发,绕过浏览器限制 | 开发阶段首选 | 否 |
| nginx 反向代理 | 同源部署,生产标准做法 | 生产环境 | 否(同源) |