193 lines
13 KiB
Markdown
193 lines
13 KiB
Markdown
# 🌧️ 雨晴Token 二开开发手册
|
||
|
||
> 本文档面向在本地 fork/二开 `RainyToken`(CATMIAOZHI/Rainytoken)的开发者。
|
||
> 记录本机已就绪的构建环境、项目结构地图、五个常见二开方向的落地清单、测试与发布流程、以及本仓库强制协作约定。
|
||
> 仓库路径:`D:\work-space\deepseek_harness\雨晴token`
|
||
|
||
---
|
||
|
||
## 1. 环境现状(本机已就绪)
|
||
|
||
| 组件 | 位置 / 版本 | 状态 |
|
||
|---|---|---|
|
||
| JDK | `C:\Users\lxy\.jdks\openjdk-22.0.1`(另有 `corretto-17.0.11` 备用) | ✅ |
|
||
| Android SDK | `D:\Android-SDK`(cmdline-tools + platform-tools + platform android-35 + build-tools 35.0.0) | ✅ |
|
||
| Gradle | wrapper **9.1.0**(`gradle/wrapper/gradle-wrapper.properties`,首次自动下载) | ✅ |
|
||
| AGP / Kotlin | 9.0.0 / 2.3.10(`gradle/libs.versions.toml`) | 项目自带 |
|
||
| SDK 路径配置 | `D:\work-space\deepseek_harness\雨晴token\local.properties` → `sdk.dir=D:/Android-SDK` | ✅ |
|
||
| Android Studio | `D:\Android-Studio`(Narwhal 2025.1.1,`bin/studio64.exe`;桌面 + 开始菜单均建快捷方式「Android Studio」);用户环境变量 `ANDROID_HOME`/`ANDROID_SDK_ROOT` 已指向 `D:\Android-SDK` | ✅ |
|
||
| Git | 全局代理 `127.0.0.1:12000` 失效;**直连可用**。拉取/推送请用 `git -c http.proxy= -c https.proxy= ...` | ⚠️ |
|
||
|
||
> ⚠️ 若换了机器/别人接手:删掉 `local.properties`,自己写 `sdk.dir=你的SDK路径`;SDK 组件与 CI 保持一致(`platforms;android-35` + `build-tools;35.0.0`)。
|
||
|
||
---
|
||
|
||
## 2. 构建与验证
|
||
|
||
```bash
|
||
# 单元测试(CI 第一步)
|
||
./gradlew testDebugUnitTest
|
||
|
||
# Debug APK(日常二开验证用这个)
|
||
./gradlew assembleDebug
|
||
# 产物: app/build/outputs/apk/debug/app-debug.apk
|
||
|
||
# Lint
|
||
./gradlew lintDebug
|
||
|
||
# Release APK(本机必须有 release.jks + 环境变量,见 §7;否则报错属正常防御)
|
||
# 想临时本机验证 Release 编译: 设 CI=true 会自动 fallback 到 debug keystore
|
||
# set CI=true && ./gradlew assembleRelease
|
||
```
|
||
|
||
**常见坑**
|
||
- `java.net.ConnectException` 拉依赖:确认没走失效代理(本机直连可用,无需代理)。
|
||
- `SDK location not found`:检查 `local.properties` 的 `sdk.dir`。
|
||
- `Release KEYSTORE_PASSWORD ... not set`:这是有意的防御(见 `app/build.gradle.kts` signingConfigs),别绕过。
|
||
- `Your project path contains non-ASCII characters`(Windows / AGP 9):目录名含中文(如本仓库 `雨晴token`)AGP 默认硬失败。已在本项目 `gradle.properties` 加 `android.overridePathCheck=true` 绕过(**该行属本地改动**,对 repo 而言是未提交 diff,勿顺手提交;要提交需经 owners 同意)。新机器照此处理。
|
||
|
||
---
|
||
|
||
## 3. 项目结构地图
|
||
|
||
```
|
||
app/src/main/java/com/rainy/token/
|
||
├── data/
|
||
│ ├── cache/ BalanceCache(DataStore 余额缓存)
|
||
│ ├── local/ UsageCache + Room(UsageRecordEntity/UsageDao/UsageDatabase/ChartBucket)、SecureStorage(Keystore AES-256-GCM 密文存储)、ChartSettingsStore
|
||
│ ├── remote/ DeepSeekApi(Retrofit 接口)
|
||
│ ├── debug/ DebugLog(App 内调试日志)
|
||
│ └── repository/ 各服务 R/E(OpenCodeGo / CommandCodeGo(+Usage) / Codex / Ollama / DeepSeek)+ CredentialRepository / RepositoryError / RefreshWriteSession / RetryHelper
|
||
├── domain/
|
||
│ ├── model/ ServiceBalance(统一余额模型)、Credential(密封类三种)、TriggerSummary
|
||
│ ├── service/ ServiceType 枚举、ServiceConfigProvider(FetchMethod + 每个服务的 URL/单位)
|
||
│ └── usecase/ RefreshBalanceUseCase(余额,五个服务 when 分发)、SyncUsageUseCase、SyncCommandCodeUsageUseCase
|
||
├── ui/
|
||
│ ├── RainyTokenNavHost.kt Navigation 路由
|
||
│ ├── dashboard/ 仪表盘 / 图表 / 详情(ViewModel 按 ServiceType.entries 自动生成卡片)
|
||
│ ├── heatmap/ Token 活动热力图
|
||
│ ├── servicedetail/ 服务详情(57KB,最大改动区之一)
|
||
│ ├── settings/ Settings / CredentialEdit(凭据表单由 FetchMethod 分发)
|
||
│ ├── webview/ WebView 登录 / Codex OAuth(PKCE)
|
||
│ ├── widget/ OpenCodeGo 桌面小组件
|
||
│ ├── components/ ServiceIcon / StatusChip / FormatUtils / UiText
|
||
│ └── theme/ Color.kt / Theme.kt / Type.kt(雨晴粉品牌)
|
||
└── di/ NetworkModule(Retrofit/OkHttp/Repository @Provides)、StorageModule(DataStore)
|
||
```
|
||
|
||
关键扩展点速查:
|
||
- 新增服务入口:**一个枚举 + 一个配置文件**(`ServiceType.kt` + `ServiceConfigProvider.kt`),仪表盘卡片由 `DashboardViewModel` 遍历 `ServiceType.entries` 自动生成。
|
||
- 统一数据模型:`ServiceBalance`,所有 Repository 最终都返回它。
|
||
- 统一错误:`RepositoryError` 密封类(InvalidCredential / RateLimited / Network / ServerError / ParseError…)。
|
||
- 凭据:`Credential` 密封类(ApiKey / Session / Codex)+ `SecureStorage` 加密落盘。
|
||
|
||
---
|
||
|
||
## 4. 二开方向指南
|
||
|
||
### A. 新增/替换 AI 服务商(如 Claude、Gemini、GLM、Kimi…)
|
||
|
||
> 如果只是"换一个 REST API 余额查询",改动清单如下(按序):
|
||
|
||
1. `domain/service/ServiceType.kt` —— 追加枚举项 `Xxx(displayName, storageKey)`。**storageKey 要稳定**(枚举名不可用于索引,改名会丢凭据)。
|
||
2. `domain/service/ServiceConfigProvider.kt` —— 追加 `ServiceConfig`(选 `FetchMethod.REST_API` 或 `WEBVIEW_SCRAPER`;`displayUnit` 如 `¥`/`$`/`Credits`)。
|
||
3. `data/remote/` —— REST 服务新建 Retrofit API 接口(参照 `DeepSeekApi.kt`);页面抓取服务参照 `OllamaRepository` 的 OkHttp + HTML 解析。
|
||
4. `data/repository/` —— 新建 `XxxRepository`:实现 `fetchBalance(): ServiceBalance`(可复用 `CredentialRepository` + `BalanceCache`),HTTP 错误映射到 `RepositoryError`。
|
||
5. `di/NetworkModule.kt` —— 用 `@Provides @Singleton` 显式提供(**不要用 `@Inject constructor`**,代码注释明确说明 KSP 2.x 误报隐患)。
|
||
6. `domain/usecase/RefreshBalanceUseCase.kt` —— `when (service)` 加分支(注意这个类写死了五个服务,新增必改)。
|
||
7. UI:
|
||
- `ui/components/ServiceIcon.kt` —— 加图标分支。
|
||
- `ui/dashboard/DashboardViewModel.kt` —— 卡片默认自动出现(它遍历 `ServiceType.entries` 生成卡片);**注意**:其中的 OPENCODE_GO 特判(:139)只用于刷新桌面小组件,不是"直达详情"。
|
||
- 若新服务要"直达详情/直达用法明细"或定制卡片主体,改动点在 `ui/dashboard/DashboardScreen.kt` 的 `when (card.service)` 跳转分支,与 `ui/dashboard/ServiceBalanceCards.kt` 的 `BalanceMainArea` 卡片渲染分支(参照现有 OPENCODE_GO 卡片直达;`ServiceBalanceCards` 有 `else` 通用兜底,新服务不配自定义主体也会正常渲染)。
|
||
- `ui/settings/CredentialEditViewModel.kt` —— REST_API 服务自动走 API Key 表单;Cookie 类自动走 Session 表单;特殊形态(如 Codex OAuth PKCE)需自建流程。
|
||
8. 本地化:`res/values*/strings.xml` —— 默认 `values/` 为英文(app_name=RainyToken),中文在 `values-b+zh+Hans` / `values-b+zh+Hant`(app_name=雨晴Token),**没有 `values-en` 目录**。
|
||
9. 测试:参照 `app/src/test/.../OllamaRepositoryTest.kt` 写单测(含 HTML/JSON 解析样例)。
|
||
|
||
> 若目标是"替换"某个服务(如把 OpenCodeGo 换成别家),先删除对应枚举/配置/Repository/UseCase 分支,再按上面加新的。
|
||
|
||
### B. UI / 品牌换皮(不改功能)
|
||
|
||
| 想改的东西 | 改哪里 |
|
||
|---|---|
|
||
| 主题主色(雨晴粉) | `ui/theme/Color.kt`(`StrawberryPink` 系)/ `Theme.kt` |
|
||
| 排版 | `ui/theme/Type.kt` |
|
||
| App 名称 | `res/values*/strings.xml` 的 `app_name`(含小组件 `widget_name`) |
|
||
| 桌面图标 | `res/mipmap-anydpi-v26/ic_launcher*.xml` + `drawable/ic_launcher_*` |
|
||
| 启动背景/词语 | `drawable/ic_rainy_wordmark.xml`、`RainyBackground.kt` |
|
||
| 仪表盘卡片 | `ui/dashboard/DashboardScreen.kt`(38KB 主战场) |
|
||
| 服务详情页 | `ui/servicedetail/ServiceDetailScreen.kt`(57KB,最大) |
|
||
|
||
> 换皮不影响业务逻辑,纯 Compose/资源改动,风险低。改完跑 `assembleDebug` + 截图自查。
|
||
|
||
### C. 改包名 + 独立发布自己的版本
|
||
|
||
1. `app/build.gradle.kts`:`namespace`、`applicationId` 改成 `com.你的域名.xxx`;`versionName/versionCode` 另起(如 1.0.0/1)。**严禁只改 applicationId 不改 namespace**(会连带所有 import 断裂)。
|
||
2. 全量重命名目录:`app/src/main/java/com/rainy/token/...` → 新包路径(Android Studio 右键 Refactor → Rename Package 最稳)。
|
||
3. `ui/theme/Type.kt`/`Theme.kt` 主题名、`strings.xml` app_name、图标 —— 换皮步骤同 §B。
|
||
4. `src/test/java/...` 包路径同步重命名;`AndroidManifest.xml` 内类引用是相对名(`.MainActivity`),重命名目录即自动跟随。
|
||
5. MIT License 保留:**必须保留原作者版权声明**(`LICENSE` 文件 + README 出处),不得删除/篡改署名。
|
||
6. 独立仓库:本地新建 remote(未授权不 push);CI/Release 用的 4 个 secrets(`KEYSTORE_BASE64` / `KEYSTORE_PASSWORD` / `KEYSTORE_ALIAS` / `KEY_PASSWORD`,见 `.github/workflows/release.yml`)在你自己仓库的 Settings → Secrets and variables 配置。
|
||
|
||
### D. 架构升级 / 大版本重构
|
||
|
||
当前版本基线:AGP 9.0.0 / Kotlin 2.3.10 / Gradle 9.1.0 / compileSdk 35 / minSdk 31 / targetSdk 35。可考虑方向:
|
||
- **依赖升级**:`gradle/libs.versions.toml` 里 compose-bom 2024.10.01、Kotlin 2.3.10 等,升级前看 [AGP release notes](https://developer.android.com/build/releases/gradle-plugin) 与 [compose-bom mapping](https://developer.android.com/develop/ui/compose/bom/bom-mapping)。
|
||
- **模块化**:把 `data` / `domain` / `ui` 拆成 Gradle module,便于多人并行 + 复用。
|
||
- **跨平台 Desktop(可选)**:项目现在是纯 Android(Compose UI + Android-only 依赖 retrofit/okhttp 大多已跨平台化,但 DataStore/Keystore/Widget/WebView 都有 Android 专属部分),迁移 KMP 工作量大,建议先评估改桌面版的真实收益(用户可在 Windows 部署一个桌面临时版,直接复用 `domain` + `data` 代码,换 Compose Desktop UI + 本地文件存凭据)。
|
||
- **测试基建**:现有仅单测(JUnit),可补 Robolectric UI 测试或增加 Repository 集成测试。
|
||
|
||
> 重构类改动务必走 §6 的审计流程,分批小步提交。
|
||
|
||
---
|
||
|
||
## 5. 测试
|
||
|
||
```bash
|
||
./gradlew testDebugUnitTest
|
||
# 报告: app/build/reports/tests/testDebugUnitTest/index.html
|
||
```
|
||
|
||
现有测试资产(`app/src/test/`):
|
||
- Repository 解析测试:Codex / CommandCodeGo(Plan映射) / CommandCodeUsage / Ollama / OpenCodeGo / Credential / RefreshWriteSession
|
||
- UI 逻辑测试:FormatUtils / FormatCodexPrimaryLabel / Heatmap(AllViews, Insights, Stats) / ServiceDetailViewModel
|
||
|
||
---
|
||
|
||
## 6. 发布流程(原仓库约定)
|
||
|
||
CI:`main` 推代码 → test → debug APK / release(debug-signed) APK。
|
||
Release:打 **轻量 tag** `v*` → `release.yml` 用 Secrets 解码 `release.jks` 签名出正式包 → 建 GitHub Release 附件。
|
||
|
||
本地"发布"流程(若沿用):
|
||
1. `app/build.gradle.kts` bump `versionCode`/`versionName`
|
||
2. 独立 commit:`release: bump to vX.Y.Z`
|
||
3. 打 tag:`git tag vX.Y.Z`(轻量)
|
||
4. push main + tag(**需用户授权**)
|
||
|
||
**签名密钥**:正式构建必须在项目根放 `release.jks`,并注入 `KEYSTORE_PASSWORD / KEYSTORE_ALIAS / KEY_PASSWORD` 环境变量;无密钥时本机 Release 会主动报错(防御设计,勿绕过)。
|
||
|
||
---
|
||
|
||
## 7. 协作约定(taste.md — 强制)
|
||
|
||
本项目由水晴喵/雨晴喵维护,本仓库及 fork 二开均须遵守:
|
||
|
||
1. **修改后独立审计(强制)**:完成任何代码修改后,派发独立 subagent 审计:
|
||
- 无阻断问题(编译失败、逻辑错误、数据流断裂、回归风险)
|
||
- 无影响用户体验的问题(UI 展示错误、交互异常、数值显示错误、异常路径处理)
|
||
2. **修复后复审**:若审计发现并修复问题,必须**复用同一个 subagent**(续接其 task_id)复审,确认解决且无新问题。
|
||
3. **审计通过才收尾**:推送 / 发版 / 宣告完成前必须已有审计通过结论。
|
||
4. **禁推送**:未经用户明确允许,不得 commit/push。
|
||
5. **实测优先**:下结论前用真实凭据/日志/接口实测,不臆断(字段单位、数值含义、异常原因)。
|
||
6. **凭据安全**:用户 cookie / API Key / token 仅用于实测,不写入代码、不提交、不泄露。
|
||
7. **子代理路径**:派发 subagent 时,prompt 里所有工作区文件路径一律用**完整绝对路径**(如 `D:\work-space\deepseek_harness\雨晴token\app\src\...`),子代理没有工作区附着上下文。
|
||
8. **改前理解**:修改任何文件前先理解相关代码及上下文,遵循项目既有惯例,不搞发明创造。
|
||
|
||
---
|
||
|
||
## 8. 备忘
|
||
|
||
- Git 拉取/推送绕过失效代理:`git -c http.proxy= -c https.proxy= push/pull/fetch ...`
|
||
- 当前 HEAD:`eecf549`(`main`,v1.6.3 基线)
|
||
- 本项目是雨晴系列第 4 个成员(RainyLLM / RainyScanner / Rainy2FA / RainyToken),MIT License
|