Vue3 + SpringBoot 项目打包 Android App 完整实战记录
从零到一,将web项目(Vue3 + ElementPlus + SpringBoot)打包成可运行的 Android APK
技术选型:为什么选 Capacitor
在决定将 Vue3 项目打包成 Android App 时,主要对比了三个方案:
| 方案 | 推荐度 | 理由 |
|---|---|---|
| Capacitor | ⭐⭐⭐⭐⭐ | Vite 官方推荐,现代、活跃维护、配置简单、插件生态丰富 |
| Cordova | ⭐⭐⭐ | 老牌方案,配置繁琐,构建速度慢 |
| 原生 WebView | ⭐⭐ | 需要写 Java/Kotlin,维护成本高 |
结论:选用 Capacitor,完美兼容 Vite 构建产物。
2. 环境准备:JDK、Android SDK、Gradle
⚠️ 关键坑点:Android Gradle Plugin 8.x 要求 JDK 11+,JDK 8 会报错。
# 推荐使用 JDK 17 或 21(LTS 版本)
java -version
# openjdk version "17.0.xx" 或 "21.0.xx"2.2 Android SDK(命令行工具)
下载:https://developer.android.com/studio#command-line-tools-only
目录结构(关键!):
D:\AndroidSDK\ # SDK 根目录(ANDROID_HOME)
└── cmdline-tools\
└── latest\ # 版本子目录
├── bin\
│ └── sdkmanager.bat
├── lib\
└── ...环境变量:
ANDROID_HOME = D:\AndroidSDK
PATH += %ANDROID_HOME%\cmdline-tools\latest\bin
PATH += %ANDROID_HOME%\platform-tools安装必要组件:
sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
sdkmanager --licenses # 接受所有许可证2.3 Gradle Wrapper 镜像配置
修改 android/gradle/wrapper/gradle-wrapper.properties:
# 腾讯云镜像(加速下载)
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip3. 项目配置:前端构建与 Capacitor 初始化
3.1 安装 Capacitor
npm install @capacitor/core @capacitor/cli
npx cap init 若依管理 com.ruoyi.app
npx cap add android3.2 capacitor.config.ts 配置
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.test.app',
appName: '你APP的名称',
webDir: 'dist',
server: {
androidScheme: 'http' // ⚠️ 关键!避免混合内容拦截
}
};
export default config;3.3 Vite 构建配置调整
问题:vite-plugin-compression生成的 .gz 文件导致 Android 资源合并失败。
解决方案:修改 vite/plugins/index.js,注释掉 compression 插件:
// isBuild && vitePlugins.push(...createCompression(viteEnv))3.4 package.json 快捷命令
{
"scripts": {
"apk:sync": "npx cap copy android",
"apk:build": "cd android && gradlew.bat assembleDebug",
"apk": "npm run build:prod && npm run apk:sync && npm run apk:build"
}
}4. 打包实战:从源码到 APK
4.1 完整打包流程
# 1. 构建前端(生成 dist 目录)
npm run build:prod
# 2. 同步到 Android 项目
npx cap copy android
# 3. 打包 APK
cd android
.\gradlew.bat assembleDebug # 或使用快捷命令: npm run apk4.2 APK 输出路径
android/app/build/outputs/apk/debug/app-debug.apk5. Bug 修复全记录
5.1 Gradle 下载超时
报错:
Exception in thread "main" javax.net.ssl.SSLException: Read timed out原因:国内访问 services.gradle.org 被限速。
解决:修改 gradle-wrapper.properties,使用腾讯云镜像:
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip5.2 Java 版本不兼容
报错:
Dependency requires at least JVM runtime version 11. This build uses a Java 8 JVM.原因:Android Gradle Plugin 8.13.0 要求 JDK 11+。
解决:安装 JDK 17/21,配置环境变量:
set JAVA_HOME=C:\Program Files\Java\jdk-17
set PATH=%JAVA_HOME%\bin;%PATH%或在 android/gradle.properties 中指定:
org.gradle.java.home=C\:\\Program Files\\Java\\jdk-175.3 SDK location not found
报错:
SDK location not found. Define a valid SDK location with an ANDROID_HOME解决:在 android/local.properties 中配置:
sdk.dir=D\:\\AndroidSDK5.4 Android SDK 许可证问题
报错:
Warning: License for package Android SDK Build-Tools 35 not accepted.解决:
sdkmanager --licenses
# 一路输入 y 回车,或一键接受:
echo y | sdkmanager --licenses5.5 资源重复(.gz 文件)
报错:
Duplicate resources: public/index.html 和 public/index.html.gz原因:Vite 的 vite-plugin-compression 生成 .gz 文件,与原始文件冲突。
解决:注释掉 vite/plugins/index.js 中的 compression 插件:
// isBuild && vitePlugins.push(...createCompression(viteEnv))然后重新构建:
rm -rf dist
npm run build:prod5.6 Android 明文 HTTP 限制
报错:
Mixed Content: The page at 'https://localhost/...' was loaded over HTTPS,
but requested an insecure XMLHttpRequest endpoint 'http://...'原因:Android 9+ 默认禁止明文 HTTP 传输。
解决一:在 AndroidManifest.xml 中添加:
<application
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
...>
</application>解决二:创建 res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">43.138.249.67</domain>
</domain-config>
</network-security-config>5.7 混合内容拦截(页面走 HTTPS 导致 HTTP 请求被拦)
报错:
Mixed Content: The page at 'https://localhost/login' was loaded over HTTPS,
but requested 'http://xxx.xxx.xxx.xx:端口号/captchaImage'. This request has been blocked.原因:Capacitor 默认使用 https 作为 Android Scheme。
解决:修改 capacitor.config.ts:
const config: CapacitorConfig = {
// ...
server: {
androidScheme: 'http' // 强制页面走 HTTP
}
};修改后执行:
npx cap sync android
npm run apk5.8 CORS 预检失败(最关键的问题)
这是整个打包过程中最核心、最难啃的问题。
5.8.1 现象
Access to XMLHttpRequest at 'http://43.138.249.67:8080/captchaImage'
from origin 'http://localhost' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.奇怪的是:Network 面板显示状态码 200,但浏览器仍然拦截。
5.8.2 根本原因
浏览器发送跨域请求时,先发 OPTIONS 预检请求。如果 OPTIONS 返回 500(或缺少 CORS 头),即使 GET 返回 200,数据也被浏览器丢弃。
# 测试 OPTIONS 预检
#这里填入你自己的后端接口地址
curl -X OPTIONS http://123.456.78.9:8080/captchaImage \
-H "Origin: http://localhost" \
-H "Access-Control-Request-Method: GET" -v
# 结果:HTTP/1.1 500 Internal Server Error ❌5.8.3 解决过程
第一步:添加 @CrossOrigin 到 Controller
@RestController
@CrossOrigin(origins = "*", allowedHeaders = "*",
methods = {RequestMethod.GET, RequestMethod.OPTIONS})
public class CaptchaController {
// ...
}第二步:发现 ResourcesConfig 中已有 CorsFilter
@Configuration
public class ResourcesConfig {
@Bean
public CorsFilter corsFilter() {
// 配置 CORS
}
}与 SecurityConfig 中的 .cors() 配置冲突,导致异常。
第三步:统一 CORS 配置
删除 ResourcesConfig 中的 corsFilter(),在 SecurityConfig 中使用 allowedOriginPatterns:
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*"); // ⚠️ 用 Pattern,不是 Origin
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(3600L);
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}第四步:SecurityConfig 放行 OPTIONS
.authorizeHttpRequests((requests) -> {
requests.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 放行预检
.requestMatchers("/login", "/register", "/captchaImage").permitAll()
.anyRequest().authenticated();
})5.8.4 为什么用 allowedOriginPatterns 而不是 allowedOrigins?
// ❌ 报错:When allowCredentials is true, allowedOrigins cannot contain "*"
configuration.addAllowedOrigin("*");
configuration.setAllowCredentials(true);
// ✅ 正确
configuration.addAllowedOriginPattern("*");
configuration.setAllowCredentials(true);5.8.5 验证
curl -X OPTIONS http://123.456.78.9:8080/captchaImage \
-H "Origin: http://localhost" \
-H "Access-Control-Request-Method: GET" -v
# HTTP/1.1 200 OK ✅
# Access-Control-Allow-Origin: * ✅6. 数据持久化:Cookie → localStorage
6.1 问题
App 清理后台后重新打开,Token 丢失,需要重新登录。
原因:Capacitor WebView 在 App 被清理后台后,会清除所有 Cookie 和 SessionStorage。
6.2 解决方案
将 js-cookie 替换为 localStorage。
修改 utils/auth.js:
// 修改前(使用 Cookies)
import Cookies from 'js-cookie'
export function getToken() { return Cookies.get('Admin-Token') }
export function setToken(token) { return Cookies.set('Admin-Token', token) }
// 修改后(使用 localStorage)
export function getToken() { return localStorage.getItem('Admin-Token') }
export function setToken(token) { return localStorage.setItem('Admin-Token', token) }修改 app.js:
// 所有 Cookies.get/set 替换为 localStorage.getItem/setItem
localStorage.setItem('sidebarStatus', this.sidebar.opened ? '1' : '0')7. 总结与建议
7.1 核心经验
| 问题类型 | 关键点 |
|---|---|
| 网络 | usesCleartextTraffic="true" + androidScheme: 'http' |
| CORS | allowedOriginPatterns("*") + allowCredentials(true) + 放行 OPTIONS |
| 构建 | 禁用 .gz 压缩,使用国内镜像 |
| 存储 | 用 localStorage 替代 Cookie |
| 调试 | WebView.setWebContentsDebuggingEnabled(true) + chrome://inspect |
7.2 建议保留的配置
- capacitor.config.ts 中的 androidScheme: 'http'
- AndroidManifest.xml 中的 usesCleartextTraffic 和 networkSecurityConfig
- SecurityConfig 中的 corsFilter + allowedOriginPatterns
- package.json 中的 npm run apk 快捷命令
7.3 调试技巧
- Chrome 远程调试:chrome://inspect 查看 Network 请求
- curl 测试后端:验证 CORS 配置是否生效
- 查看后端日志:定位 Security 拦截原因
7.4 安全建议
- 正式发布前,注释掉 WebView.setWebContentsDebuggingEnabled(true)
- 敏感信息不要硬编码在代码中
- 使用签名密钥打包 Release 版本
📌 结语
从零开始,花了 3 天时间,经历了 Java 版本冲突、Gradle 被墙、Android 明文限制、CORS 预检失败、数据持久化等 8 个主要问题,最终成功将 Vue3 + SpringBoot 项目打包成可运行的 Android App。
这 3 天的经验,价值远超写 3 个月的业务代码。
技术不是为了炫耀,而是为了帮助别人解决问题。
记录时间:2026年8月
技术栈:Vue3 + ElementPlus + SpringBoot 3 + Capacitor 8