鸿蒙 H5 环境下的 UniApp 跨域与存储兼容性问题排查
在跨端开发中,开发者常常会遇到不同平台、不同运行环境下的兼容性挑战。最近我在用 UniApp 开发一个系统时,就踩到了两个坑:
- 同域名下不同项目的 storage 数据无法读取
- 前端 JSONP 跨域在鸿蒙 H5 环境下完全失效
最后只能彻底调整思路,才算找到稳妥的解决方案。本文就来记录整个过程。
一、问题背景
项目中有两个系统:
- 系统 A(UniApp 开发):业务入口,需要获取
openId
来进行接口调用。 - 系统 B(同域名下的另一个项目,基于jQuery):在本地存储里已经保存了
openId
。
最初的思路是:系统 A 直接通过 uni.getStorage
读取系统 B 存储的 openId
,然后再通过 JSONP 请求后端接口来规避跨域。
在本地调试和常规浏览器里一切正常,但一旦发布成 H5 并在 鸿蒙系统 下运行,就遇到了两个严重问题。
二、遇到的问题
1. Storage 读取失败
原始代码很简单:
uni.getStorage({key: 'openId',success: function(res) {openId = res.data;}
});
在鸿蒙 H5 环境下,这段逻辑直接失效,拿不到任何数据。虽然两个项目属于同域,但鸿蒙的 WebView 存储机制对不同项目的隔离更严格,导致数据不可见。
2. JSONP 跨域彻底失效
为了请求后端接口,前端使用了 JSONP。iOS和安卓下使用都正常。
// JSONP请求方法
jsonpRequest(url, params, callback) {// 生成唯一的回调函数名const callbackName = 'jsonpCallback' + Date.now() + Math.floor(Math.random() * 10000)// 获取签名参数const mobile = uni.getStorageSync('mobile') || ''const token = uni.getStorageSync('token') || ''if (mobile && token) {const timestamp = Math.round(new Date().getTime() / 1000).toString()const randomStr = this.randomRange(10)const sign = this.hexMD5(timestamp + mobile + token + randomStr)// 添加签名参数params = Object.assign(params, {mobile: mobile,sign: sign,timestamp: timestamp,randomStr: randomStr})}// 添加callback参数params.callback = callbackName// 构建URLconst paramStr = Object.keys(params).map(key => `${key}=${encodeURIComponent(params[key])}`).join('&')const jsonpUrl = `${url}&${paramStr}`// 设置全局回调函数window[callbackName] = (data) => {callback(data)// 清理document.head.removeChild(script)delete window[callbackName]}// 创建script标签const script = document.createElement('script')script.src = jsonpUrlscript.onerror = () => {uni.showToast({title: '网络请求失败',icon: 'none'})// 确保在网络错误时也结束loading状态if (this.loading) {this.loading = falsethis.hasPermission = false}if (this.waitListLoading) {this.waitListLoading = false}document.head.removeChild(script)delete window[callbackName]}document.head.appendChild(script)
},
但是在鸿蒙 WebView 里,JSONP 的回调函数根本不执行,跨域彻底失败。换句话说,原本在浏览器里能跑通的方案,在鸿蒙上完全不可用。
三、原因分析
-
Storage 问题
- UniApp 的
uni.getStorage
仅能读取当前应用的本地存储。 - 不同项目(即使同域名)在鸿蒙 WebView 中被隔离,互不共享存储。
- UniApp 的
-
JSONP 跨域问题
- 鸿蒙系统对 H5 的安全限制更严格。
- JSONP 本质是
<script>
动态注入,在部分环境下会被拦截或不兼容。
四、解决方案
经过分析,我选择了完全放弃原有方案:
-
重新获取 openId
- 不再依赖另一个项目存储的
openId
。 - 在当前系统中直接通过
code
流程重新换取openId
,保证应用逻辑的独立性和可靠性。
- 不再依赖另一个项目存储的
-
使用服务端反向代理代替 JSONP
- 前端改回标准的
uni.request
。 - 在后端配置反向代理,由服务端转发请求到真实接口。
- 前端看到的始终是同域请求,从根本上解决跨域问题。
Nginx 配置示例:
location /api/ {client_max_body_size 300m;rewrite ^/api/(.*)$ /$1 break;proxy_pass https://backend.example.com;proxy_set_header Host fuwu.shiliyiyuan.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;proxy_ssl_server_name on;proxy_ssl_verify off;}
前端调用时就非常简洁:
uni.request({url: '/api/getOpenId',success: (res) => {console.log(res.data);} });
- 前端改回标准的
五、总结与经验
-
不要依赖跨项目的 Storage
- 不同 H5 构建产物就是不同的应用,不能指望共享存储。
- 在鸿蒙等新环境下,更是完全隔离。
-
避免 JSONP
- JSONP 兼容性差、安全性差,现代环境不推荐使用。
- 标准请求 + 服务端代理才是更稳妥的解决方案。
-
服务端代理是万能解法
- 对前端透明,兼容所有运行环境。
- 一旦搭好代理,所有跨域问题迎刃而解。
这次的实践让我更清楚地认识到:
跨平台开发时,兼容性要优先考虑,别抱着“能跑就行”的侥幸心理。与其前端绕路,不如服务端一次性解决。