Loading...

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.zip

3. 项目配置:前端构建与 Capacitor 初始化

3.1 安装 Capacitor

npm install @capacitor/core @capacitor/cli
npx cap init 若依管理 com.ruoyi.app
npx cap add android

3.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 apk

4.2 APK 输出路径

android/app/build/outputs/apk/debug/app-debug.apk

5. 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.zip

5.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-17

5.3 SDK location not found

报错

SDK location not found. Define a valid SDK location with an ANDROID_HOME

解决:在 android/local.properties 中配置:

sdk.dir=D\:\\AndroidSDK

5.4 Android SDK 许可证问题

报错

Warning: License for package Android SDK Build-Tools 35 not accepted.

解决

sdkmanager --licenses
# 一路输入 y 回车,或一键接受:
echo y | sdkmanager --licenses

5.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:prod

5.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 apk

5.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'
CORSallowedOriginPatterns("*") + allowCredentials(true) + 放行 OPTIONS
构建禁用 .gz 压缩,使用国内镜像
存储用 localStorage 替代 Cookie
调试WebView.setWebContentsDebuggingEnabled(true) + chrome://inspect

7.2 建议保留的配置

  1. capacitor.config.ts 中的 androidScheme: 'http'
  2. AndroidManifest.xml 中的 usesCleartextTraffic 和 networkSecurityConfig
  3. SecurityConfig 中的 corsFilter + allowedOriginPatterns
  4. package.json 中的 npm run apk 快捷命令

7.3 调试技巧

  1. Chrome 远程调试:chrome://inspect 查看 Network 请求
  2. curl 测试后端:验证 CORS 配置是否生效
  3. 查看后端日志:定位 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

0

回到顶部