我将介绍几种常用的跨域解决方案:
- CORS (跨域资源共享)
后端设置:
// Node.js + Express
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://example.com'); // 允许特定域名
// res.header('Access-Control-Allow-Origin', '*'); // 允许所有域名
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Credentials', 'true'); // 允许携带凭证
// 处理预检请求
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
});
- 代理服务器
开发环境(Vite配置):
// vite.config.js
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
生产环境(Nginx配置):
# nginx.conf
server {
listen 80;
server_name example.com;
location /api/ {
proxy_pass http://api.example.com/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
- JSONP (仅支持GET请求)
前端实现:
// 封装JSONP函数
function jsonp(url, params = {}, callback = 'callback') {
return new Promise((resolve, reject) => {
// 创建script标签
const script = document.createElement('script');
// 生成唯一回调函数名
const callbackName = 'jsonp_' + Date.now();
// 创建全局回调函数
window[callbackName] = (data) => {
resolve(data);
document.body.removeChild(script);
delete window[callbackName];
};
// 拼接参数
const queryString = Object.keys(params)
.map(key => `${key}=${params[key]}`)
.join('&');
// 组装URL
script.src = `${url}${url.includes('?') ? '&' : '?'}${queryString}&${callback}=${callbackName}`;
// 处理错误
script.onerror = () => {
reject(new Error('JSONP request failed'));
document.body.removeChild(script);
delete window[callbackName];
};
// 添加到文档中
document.body.appendChild(script);
});
}
// 使用示例
jsonp('http://api.example.com/data', { id: 123 })
.then(data => console.log(data))
.catch(err => console.error(err));
- postMessage跨域通信
// 发送方
const receiver = window.open('http://example.com');
receiver.postMessage('Hello!', 'http://example.com');
// 接收方
window.addEventListener('message', (event) => {
// 验证来源
if (event.origin !== 'http://trusted-domain.com') return;
console.log('Received message:', event.data);
// 回复消息
event.source.postMessage('Message received!', event.origin);
}, false);
- WebSocket跨域通信
// 前端实现
class WebSocketClient {
constructor(url) {
this.url = url;
this.ws = null;
this.handlers = {};
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected to server');
this.trigger('connect');
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.trigger('message', data);
};
this.ws.onclose = () => {
console.log('Disconnected from server');
this.trigger('disconnect');
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
this.trigger('error', error);
};
}
send(data) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
on(event, callback) {
if (!this.handlers[event]) {
this.handlers[event] = [];
}
this.handlers[event].push(callback);
}
trigger(event, data) {
if (this.handlers[event]) {
this.handlers[event].forEach(callback => callback(data));
}
}
close() {
if (this.ws) {
this.ws.close();
}
}
}
// 使用示例
const ws = new WebSocketClient('ws://api.example.com');
ws.on('connect', () => {
console.log('Connected!');
ws.send({ type: 'greeting', content: 'Hello Server!' });
});
ws.on('message', (data) => {
console.log('Received:', data);
});
ws.connect();
- 子域名跨域
// 设置document.domain
// 主域名相同的情况下可用
document.domain = 'example.com';
- 前端请求封装
// 封装axios请求
import axios from 'axios';
const request = axios.create({
baseURL: process.env.VUE_APP_API_URL,
timeout: 5000,
withCredentials: true, // 允许跨域携带cookie
});
// 请求拦截器
request.interceptors.request.use(
config => {
// 添加token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
// 响应拦截器
request.interceptors.response.use(
response => response.data,
error => {
if (error.response) {
switch (error.response.status) {
case 401:
// 处理未授权
break;
case 403:
// 处理禁止访问
break;
case 404:
// 处理未找到
break;
case 500:
// 处理服务器错误
break;
}
}
return Promise.reject(error);
}
);
export default request;
选择合适的跨域解决方案需要考虑以下因素:
- 安全性要求
- CORS可以精确控制允许的域名和请求方法
- 代理服务器可以隐藏实际API地址
- 请求类型
- JSONP只支持GET请求
- CORS支持所有HTTP方法
- WebSocket支持双向通信
- 开发维护成本
- CORS需要后端配合
- 代理服务器需要额外配置
- JSONP实现简单但功能有限
- 性能考虑
- CORS直接通信性能最好
- 代理服务器可能增加延迟
- WebSocket适合实时通信
- 浏览器兼容性
- CORS被现代浏览器广泛支持
- JSONP有最好的兼容性
- WebSocket可能需要降级方案
推荐的最佳实践:
- 开发环境:使用开发服务器代理
- 生产环境:
- 首选CORS(如果可以控制后端)
- 其次使用Nginx代理
- 特殊场景考虑WebSocket或postMessage
- 老旧系统:考虑JSONP作为后备方案
评论区