Rainytoken/docs/REDEV-GUIDE.md

193 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🌧️ 雨晴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