13 KiB
🌧️ 雨晴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. 构建与验证
# 单元测试(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.ktssigningConfigs),别绕过。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 余额查询",改动清单如下(按序):
domain/service/ServiceType.kt—— 追加枚举项Xxx(displayName, storageKey)。storageKey 要稳定(枚举名不可用于索引,改名会丢凭据)。domain/service/ServiceConfigProvider.kt—— 追加ServiceConfig(选FetchMethod.REST_API或WEBVIEW_SCRAPER;displayUnit如¥/$/Credits)。data/remote/—— REST 服务新建 Retrofit API 接口(参照DeepSeekApi.kt);页面抓取服务参照OllamaRepository的 OkHttp + HTML 解析。data/repository/—— 新建XxxRepository:实现fetchBalance(): ServiceBalance(可复用CredentialRepository+BalanceCache),HTTP 错误映射到RepositoryError。di/NetworkModule.kt—— 用@Provides @Singleton显式提供(不要用@Inject constructor,代码注释明确说明 KSP 2.x 误报隐患)。domain/usecase/RefreshBalanceUseCase.kt——when (service)加分支(注意这个类写死了五个服务,新增必改)。- 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)需自建流程。
- 本地化:
res/values*/strings.xml—— 默认values/为英文(app_name=RainyToken),中文在values-b+zh+Hans/values-b+zh+Hant(app_name=雨晴Token),没有values-en目录。 - 测试:参照
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. 改包名 + 独立发布自己的版本
app/build.gradle.kts:namespace、applicationId改成com.你的域名.xxx;versionName/versionCode另起(如 1.0.0/1)。严禁只改 applicationId 不改 namespace(会连带所有 import 断裂)。- 全量重命名目录:
app/src/main/java/com/rainy/token/...→ 新包路径(Android Studio 右键 Refactor → Rename Package 最稳)。 ui/theme/Type.kt/Theme.kt主题名、strings.xmlapp_name、图标 —— 换皮步骤同 §B。src/test/java/...包路径同步重命名;AndroidManifest.xml内类引用是相对名(.MainActivity),重命名目录即自动跟随。- MIT License 保留:必须保留原作者版权声明(
LICENSE文件 + README 出处),不得删除/篡改署名。 - 独立仓库:本地新建 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 与 compose-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. 测试
./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 附件。
本地"发布"流程(若沿用):
app/build.gradle.ktsbumpversionCode/versionName- 独立 commit:
release: bump to vX.Y.Z - 打 tag:
git tag vX.Y.Z(轻量) - push main + tag(需用户授权)
签名密钥:正式构建必须在项目根放 release.jks,并注入 KEYSTORE_PASSWORD / KEYSTORE_ALIAS / KEY_PASSWORD 环境变量;无密钥时本机 Release 会主动报错(防御设计,勿绕过)。
7. 协作约定(taste.md — 强制)
本项目由水晴喵/雨晴喵维护,本仓库及 fork 二开均须遵守:
- 修改后独立审计(强制):完成任何代码修改后,派发独立 subagent 审计:
- 无阻断问题(编译失败、逻辑错误、数据流断裂、回归风险)
- 无影响用户体验的问题(UI 展示错误、交互异常、数值显示错误、异常路径处理)
- 修复后复审:若审计发现并修复问题,必须复用同一个 subagent(续接其 task_id)复审,确认解决且无新问题。
- 审计通过才收尾:推送 / 发版 / 宣告完成前必须已有审计通过结论。
- 禁推送:未经用户明确允许,不得 commit/push。
- 实测优先:下结论前用真实凭据/日志/接口实测,不臆断(字段单位、数值含义、异常原因)。
- 凭据安全:用户 cookie / API Key / token 仅用于实测,不写入代码、不提交、不泄露。
- 子代理路径:派发 subagent 时,prompt 里所有工作区文件路径一律用完整绝对路径(如
D:\work-space\deepseek_harness\雨晴token\app\src\...),子代理没有工作区附着上下文。 - 改前理解:修改任何文件前先理解相关代码及上下文,遵循项目既有惯例,不搞发明创造。
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