diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 4cc24c8..fb4cb3a 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -204,3 +204,112 @@ env: - 工作流文件:`.github/workflows/docker-build-deploy.yml` - Dockerfile:`api/Dockerfile` - Docker Compose:`api/docker-compose.yml` + +--- + +# Android Release 工作流 + +## 概述 + +自动构建生产环境签名 APK 并发布到 GitHub Releases: + +1. **构建阶段**:编译 Release APK,使用生产签名配置 +2. **发布阶段**:创建 GitHub Release,将 APK 作为附件上传 + +## 工作流触发条件 + +- **Tag 推送**:推送 `v*` 格式的标签时自动触发(如 `v1.0.0`、`v1.2.3-beta`) +- **手动触发**:通过 GitHub Actions 界面手动触发,可选指定版本号 + +## 配置步骤 + +### 1. 配置 GitHub Secrets + +在 GitHub 仓库设置中添加以下 Secrets: + +| Secret | 说明 | 示例 | +|--------|------|------| +| `ANDROID_KEYSTORE_BASE64` | keystore 文件的 Base64 编码 | 见下方生成方法 | +| `ANDROID_KEY_ALIAS` | 密钥别名 | `suiyueshishu` | +| `ANDROID_KEY_PASSWORD` | 密钥密码 | | +| `ANDROID_STORE_PASSWORD` | keystore 密码 | | + +#### 生成 ANDROID_KEYSTORE_BASE64 + +```bash +# 在本地项目 app-android 目录下执行 +base64 -i release-keystore.jks | pbcopy # macOS,结果已复制到剪贴板 + +# Linux +base64 -w 0 release-keystore.jks +``` + +将输出内容粘贴到 GitHub Secrets 的 `ANDROID_KEYSTORE_BASE64` 中。 + +### 2. 版本号管理 + +- **Tag 触发**:从 tag 名自动提取版本号(如 `v1.0.0` → `versionName = "1.0.0"`) +- **手动触发**:使用输入的 `version_name`,或使用 `build.gradle.kts` 中的默认值 +- **versionCode**:自动使用 `GITHUB_RUN_NUMBER` 递增 + +### 3. 发布流程 + +#### 方式一:通过 Tag 自动发布 + +```bash +# 打标签并推送 +git tag v1.0.0 +git push origin v1.0.0 + +# 或者一步完成 +git tag v1.0.0 && git push origin v1.0.0 +``` + +工作流将自动: +1. 构建签名 Release APK +2. 上传 APK 为 GitHub Artifact +3. 创建 GitHub Release(附带 APK 和自动生成的更新日志) + +#### 方式二:手动触发 + +1. 进入仓库的 Actions 标签页 +2. 选择 "Android Release Build" 工作流 +3. 点击 "Run workflow" 按钮 +4. (可选)填写版本号 +5. 点击 "Run workflow" + +手动触发时仅构建 APK 并上传为 Artifact,不会创建 GitHub Release。 + +## 工作流执行流程 + +1. 检出代码(完整历史) +2. 确定版本号(Tag / 手动输入 / build.gradle.kts 默认值) +3. 设置 JDK 17 + Gradle 缓存 +4. 解码签名 keystore + 生成 keystore.properties +5. 覆盖 build.gradle.kts 中的版本号 +6. 执行 `./gradlew assembleRelease` +7. 上传 APK 为 Artifact(保留 30 天) +8. (Tag 触发时)生成 Release Notes 并创建 GitHub Release + +## APK 命名规则 + +APK 文件命名格式:`岁月史书_v{版本号}_release.apk` + +示例:`岁月史书_v1.0.0_release.apk` + +## 故障排查 + +### 1. 签名失败 + +- 检查 `ANDROID_KEYSTORE_BASE64` 是否正确生成(不能有换行或空格) +- 确认 `ANDROID_KEY_ALIAS`、`ANDROID_KEY_PASSWORD`、`ANDROID_STORE_PASSWORD` 与 keystore 匹配 + +### 2. 构建失败 + +- 检查 Actions 日志中的 Gradle 错误输出 +- 确认本地 `./gradlew assembleRelease` 可以成功 + +### 3. Release 创建失败 + +- 确认工作流有 `contents: write` 权限 +- 检查 Tag 名称是否以 `v` 开头 diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml new file mode 100644 index 0000000..a012c66 --- /dev/null +++ b/.github/workflows/android-release.yml @@ -0,0 +1,207 @@ +name: Android Release Build + +on: + push: + tags: + - 'v*' # 推送 v1.0.0 等标签时自动触发 + workflow_dispatch: # 支持手动触发 + inputs: + version_name: + description: '版本名(如 1.0.1),留空则使用 build.gradle.kts 中的默认值' + required: false + type: string + +env: + APP_NAME: 岁月史书 + +jobs: + build-release-apk: + name: Build Release APK + runs-on: ubuntu-latest + permissions: + contents: write # 需要 write 权限以创建 Release + + steps: + # ----------------------------------------------------------------------- + # 1. 检出代码 + # ----------------------------------------------------------------------- + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # 完整历史,用于生成 Release Notes + + # ----------------------------------------------------------------------- + # 2. 确定版本号 + # ----------------------------------------------------------------------- + - name: Determine version + id: version + run: | + if [ "${{ github.event_name }}" = "push" ] && [[ "${{ github.ref }}" == refs/tags/v* ]]; then + # Tag 触发:从 tag 名提取版本号(去掉 v 前缀) + VERSION_NAME="${GITHUB_REF#refs/tags/v}" + TAG_NAME="${GITHUB_REF#refs/tags/}" + elif [ -n "${{ github.event.inputs.version_name }}" ]; then + # 手动触发且提供了版本号 + VERSION_NAME="${{ github.event.inputs.version_name }}" + TAG_NAME="v${VERSION_NAME}" + else + # 手动触发但未提供版本号,从 build.gradle.kts 读取 + VERSION_NAME=$(grep 'versionName' app-android/app/build.gradle.kts | head -1 | sed 's/.*"\(.*\)".*/\1/') + TAG_NAME="v${VERSION_NAME}" + fi + + # versionCode 使用 GitHub Run Number 自增 + VERSION_CODE="${GITHUB_RUN_NUMBER}" + + echo "version_name=${VERSION_NAME}" >> $GITHUB_OUTPUT + echo "version_code=${VERSION_CODE}" >> $GITHUB_OUTPUT + echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT + echo "apk_name=${APP_NAME}_v${VERSION_NAME}_release.apk" >> $GITHUB_OUTPUT + + echo "版本名: ${VERSION_NAME}" + echo "版本号: ${VERSION_CODE}" + echo "Tag: ${TAG_NAME}" + + # ----------------------------------------------------------------------- + # 3. 设置 JDK + # ----------------------------------------------------------------------- + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + # ----------------------------------------------------------------------- + # 4. 设置 Gradle 缓存 + # ----------------------------------------------------------------------- + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + # ----------------------------------------------------------------------- + # 5. 解码签名文件 + # ----------------------------------------------------------------------- + - name: Decode keystore + working-directory: app-android + run: | + echo "解码 keystore 文件..." + echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > release-keystore.jks + + echo "生成 keystore.properties..." + cat > keystore.properties <> $GITHUB_OUTPUT + echo "apk_name=${FINAL_NAME}" >> $GITHUB_OUTPUT + + # ----------------------------------------------------------------------- + # 9. 上传 APK 为 Artifact(始终执行,手动触发时也可下载) + # ----------------------------------------------------------------------- + - name: Upload APK artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.apk.outputs.apk_name }} + path: app-android/${{ steps.apk.outputs.apk_path }} + retention-days: 30 + + # ----------------------------------------------------------------------- + # 10. 创建 GitHub Release(仅 Tag 触发时执行) + # ----------------------------------------------------------------------- + - name: Generate Release Notes + id: release_notes + if: startsWith(github.ref, 'refs/tags/v') + run: | + TAG_NAME="${{ steps.version.outputs.tag_name }}" + + # 获取上一个 tag + PREV_TAG=$(git tag --sort=-creatordate | grep '^v' | sed -n '2p') + + if [ -n "$PREV_TAG" ]; then + echo "生成 ${PREV_TAG}..${TAG_NAME} 之间的变更记录" + CHANGES=$(git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (%h)" --no-merges) + else + echo "首次发布,生成全部提交记录" + CHANGES=$(git log --pretty=format:"- %s (%h)" --no-merges -20) + fi + + # 写入多行输出 + { + echo "notes<> $GITHUB_OUTPUT + + - name: Create GitHub Release + if: startsWith(github.ref, 'refs/tags/v') + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.version.outputs.tag_name }} + name: "${{ env.APP_NAME }} ${{ steps.version.outputs.tag_name }}" + body: ${{ steps.release_notes.outputs.notes }} + draft: false + prerelease: false + files: app-android/${{ steps.apk.outputs.apk_path }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 99f5c8d..6a2ebeb 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ build/ dist/ *.egg-info/ +# 本地 ASR 模型缓存(Whisper 每次启动从该目录加载) +api/models/ +