我将介绍几种常用的跨域解决方案:

  1. 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();
  }
});
  1. 代理服务器

开发环境(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;
    }
}
  1. 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));
  1. 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);
  1. 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();
  1. 子域名跨域
// 设置document.domain
// 主域名相同的情况下可用
document.domain = 'example.com';
  1. 前端请求封装
// 封装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;

选择合适的跨域解决方案需要考虑以下因素:

  1. 安全性要求
  • CORS可以精确控制允许的域名和请求方法
  • 代理服务器可以隐藏实际API地址
  1. 请求类型
  • JSONP只支持GET请求
  • CORS支持所有HTTP方法
  • WebSocket支持双向通信
  1. 开发维护成本
  • CORS需要后端配合
  • 代理服务器需要额外配置
  • JSONP实现简单但功能有限
  1. 性能考虑
  • CORS直接通信性能最好
  • 代理服务器可能增加延迟
  • WebSocket适合实时通信
  1. 浏览器兼容性
  • CORS被现代浏览器广泛支持
  • JSONP有最好的兼容性
  • WebSocket可能需要降级方案

推荐的最佳实践:

  1. 开发环境:使用开发服务器代理
  2. 生产环境:
    • 首选CORS(如果可以控制后端)
    • 其次使用Nginx代理
    • 特殊场景考虑WebSocket或postMessage
  3. 老旧系统:考虑JSONP作为后备方案