第一次用UniApp开发H5页面时,我兴冲冲地写好了接口请求代码,结果浏览器控制台突然跳出一行刺眼的红色报错:"Access-Control-Allow-Origin"。这个场景相信很多开发者都遇到过,特别是在前后端分离的开发模式下,跨域问题就像一道必须跨过的门槛。
跨域问题本质上是一个安全机制。浏览器出于安全考虑,会阻止不同源(协议、域名、端口任一不同)的请求响应。举个例子,如果你的H5页面运行在http://localhost:8080,而接口服务部署在http://api.example.com,这就构成了跨域。
在UniApp的H5开发中,这个问题尤为突出。因为开发阶段我们通常用本地服务器调试,而接口可能部署在测试环境或生产环境。这时候就需要代理配置来绕过浏览器的同源策略限制。代理的工作原理很简单:让同域的服务器帮你转发请求到目标服务器,这样浏览器就不会拦截了。
manifest.json是UniApp项目的核心配置文件,它提供了H5开发服务器的代理配置选项。我推荐先在HBuilderX中打开manifest.json文件,切换到"源码视图"模式,这样可以直接编辑JSON内容。
找到或添加h5配置项,在其中加入devServer配置。这里有个小技巧:如果你不确定配置位置,可以直接在JSON顶层添加"h5"字段。完整的配置示例如下:
json复制"h5": {
"devServer": {
"disableHostCheck": true,
"proxy": {
"/api": {
"target": "http://your-api-domain.com",
"changeOrigin": true,
"secure": false,
"ws": false,
"pathRewrite": {
"^/api": ""
}
}
}
}
}
每个配置参数都有其特定作用:
我在实际项目中发现,当接口返回404时,很可能是pathRewrite配置有问题。这时候可以先用简单的配置测试,确认代理生效后再调整重写规则。
虽然manifest.json方案很方便,但在某些场景下,我们可能更习惯使用vue.config.js的方式。这个文件需要手动创建在项目根目录(与src同级),UniApp会自动识别它。
创建vue.config.js后,加入以下配置:
javascript复制module.exports = {
devServer: {
disableHostCheck: true,
proxy: {
'/api': {
target: 'http://your-api-domain.com',
changeOrigin: true,
secure: false,
ws: false,
pathRewrite: {
'^/api': ''
}
}
}
}
}
两种配置方式看起来很相似,但有几个关键区别:
我遇到过一个典型场景:项目需要同时配置代理和自定义webpack loader。这时候就必须使用vue.config.js,因为manifest.json不支持loader配置。
在实际项目中,我分别测试了两种配置方式的性能差异。结果发现,在代理转发效率上几乎没有区别,因为底层都是基于webpack-dev-server实现的。主要的区别在于:
根据我的经验,给出以下选择建议:
有个常见的误区是同时使用两种配置,这可能导致代理规则混乱。我建议始终只选择一种方式,并在团队内统一规范。
配置好代理后,我们需要一个健壮的请求封装。这是我的常用方案:
javascript复制// request.js
const BaseHost = process.env.NODE_ENV === 'development' ? '/api' : 'http://prod-api.com'
export default function request(options) {
return new Promise((resolve, reject) => {
uni.request({
url: BaseHost + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
'Content-Type': 'application/json',
'X-Token': uni.getStorageSync('token'),
...options.headers
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data)
} else {
reject(res)
}
},
fail: (err) => {
reject(err)
}
})
})
}
在实际项目中,我还加入了以下增强功能:
这些优化使请求层更加健壮,特别是在弱网环境下表现更好。一个典型的错误处理增强版如下:
javascript复制function withRetry(fn, retries = 3) {
return async function(...args) {
let lastError
for (let i = 0; i < retries; i++) {
try {
return await fn(...args)
} catch (error) {
lastError = error
if (error.statusCode === 502) {
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
continue
}
throw error
}
}
throw lastError
}
}
这是最常见的问题,我总结了一套排查流程:
当代理配置正确但接口返回404时:
如果配置后还是出现跨域错误:
在最近的一个电商项目中,我遇到了一个典型的代理配置挑战。项目需要同时对接三个不同的后端服务:用户服务、商品服务和订单服务。每个服务的API前缀和地址都不同。
最终的解决方案是在vue.config.js中配置多个代理规则:
javascript复制module.exports = {
devServer: {
proxy: {
'/user-api': {
target: 'http://user-service.com',
pathRewrite: { '^/user-api': '' }
},
'/product-api': {
target: 'http://product-service.com',
pathRewrite: { '^/product-api': '' }
},
'/order-api': {
target: 'http://order-service.com',
pathRewrite: { '^/order-api': '' }
}
}
}
}
对应的请求封装也需要适配多前缀:
javascript复制const SERVICE_PREFIX = {
user: '/user-api',
product: '/product-api',
order: '/order-api'
}
function getApiUrl(service, path) {
return process.env.NODE_ENV === 'development'
? `${SERVICE_PREFIX[service]}${path}`
: `${SERVICE_HOST[service]}${path}`
}
这个方案完美解决了多服务对接的问题,而且保持了开发和生产环境的一致性。上线后运行稳定,证明了代理配置方案的可靠性。