我最近在用 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 的默認安全策略。只有把可觀測性拉滿,問題才會乖乖現形。