我最近在用 Quasar + Capacitor 6 做一个 Android App,前端用的是 Vue3 + Quasar,打包交给 Capacitor 去跑在手机的 WebView 里,后端是 FastAPI 提供接口。开发模式下一切顺利,浏览器里访问接口没有任何问题,我甚至觉得打包也应该是轻轻松松。但真正把前端打成 APK 装到手机上之后,登录页却死活拿不到验证码。
开发模式下没问题,打包后却彻底失效,更麻烦的是装到手机里以后没法像在浏览器里那样直接看调试信息,前期只能靠猜。我第一反应是协议没对齐,于是没有犹豫,直接把 Capacitor 的配置改成了明确走 HTTP:在 capacitor.config
里把 Android 端的访问协议设成 http
,同时把混合内容也临时放开了——
{"appId": "org.capcitor.soil.app","appName": "soil","webDir": "www","server": {"androidScheme": "http"},"android": {"allowMixedContent": true}
}
我以为这一下就能跑了,重新打包装上去,结果还是老样子:验证码依然获取失败。到这一步我明白不能再靠猜,必须把真实的网络请求抓出来看。我换了个思路,从“能看见”入手,让 App 也能像浏览器一样被调试。于是改了 Android 入口,给 WebView 打开了远程调试开关。MainActivity.java
里加上 WebView.setWebContentsDebuggingEnabled(true)
,顺手把 Bundle
的导入也补齐:
package org.capcitor.soil.app; // ← 保持你项目的包名import android.os.Bundle; // ✅ 必须导入
import com.getcapacitor.BridgeActivity;
import android.webkit.WebView; // ✅ 开启 WebView 调试需要public class MainActivity extends BridgeActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// ✅ 开启 WebView 调试(Chrome → chrome://inspect)WebView.setWebContentsDebuggingEnabled(true);}
}
手机用数据线连上电脑,Chrome 里打开 chrome://inspect
,选中我的 WebView 页面,网络请求一览无余。很快我看到了关键线索:请求 http://192.168.209.35:9099/captchaImage
真发出去了,后端也确实回了 200 OK,但控制台下一行红字把真相挑明了——“Access to XMLHttpRequest from origin 'http://localhost' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header…”。这不是“服务器没回”,而是“浏览器(准确说是 WebView)把它拦了”。为什么开发模式没问题?因为开发时 Vite 代理把请求“同源化”了,CORS 的坑被 proxy 悄悄掩盖;等到 App 里直连后端,真实的跨域策略就原形毕露。
有了确凿的现场证据,定位就不再含糊。我意识到 Capacitor 的 Android WebView 默认把页面的 origin 视作 http://localhost
,而我后端的 CORS 白名单里一开始并没有这个精确的来源(更别说还写过 localhost:80
这种端口限定,完全对不上)。我回到后端,把 FastAPI 的 CORS 中间件按真实来源补齐,注意到如果用 allow_credentials=True
就不能配 "*"
,必须列出具体域名,于是把 Capacitor 的 http://localhost
、开发时的 http://localhost:9000
、以及我在局域网直开的前端地址都写了进去:
from fastapi.middleware.cors import CORSMiddlewareapp.add_middleware(CORSMiddleware,allow_origins=["http://localhost", # Capacitor WebView 的 origin"http://localhost:9000", # Vite/Quasar 开发服务器"http://192.168.209.35:9000" # 局域网访问前端时的 origin(如有)],allow_credentials=True,allow_methods=["*"],allow_headers=["*"],
)
后端重启,再回到 chrome://inspect
,我看见响应头里终于多出了那行关键的 access-control-allow-origin: http://localhost
。同一时间,App 里验证码图片顺利出现,登录流程也跟开发模式下一样顺滑。
回头梳理这段折腾,结论其实很朴素:我先把 Capacitor 的默认 HTTPS 行为“降”到了 HTTP(androidScheme: "http"
+ 临时放开的 allowMixedContent
),这一步解决了“混合内容”层面的阻断,但真正压在我身上的,是 CORS 配置没精准命中 WebView 的来源;只有把 http://localhost
加入白名单,并且符合带凭据请求的规范,链路才算真正打通。开发模式下之所以看不出问题,是因为代理把跨域细节给挡在了视线之外。
这一次最大的收获不是哪段配置,而是“看见”的能力:先让 App 能被调试,再用证据指导修改。以后再遇到“开发一切正常、打包就不行”的情况,我会第一时间把 WebView 调试开关打上,去网络面板确认请求的 URL、响应头和出错信息;如果临时需要 HTTP,我知道可以用 server.androidScheme: "http"
搭配 allowMixedContent: true
过渡,但长期还是会把接口升级成 HTTPS,回归 Capacitor 的默认安全策略。只有把可观测性拉满,问题才会乖乖现形。