docs: harness 工程文档

This commit is contained in:
AndyChen1024
2026-09-03 19:03:35 +08:00
parent 914daa19ac
commit 1b6f6d53ad
11 changed files with 388 additions and 0 deletions

67
AGENTS.md Normal file
View File

@@ -0,0 +1,67 @@
# 明缘进阶场 Android 客户端
「明缘进阶场」的 Android 客户端(`com.mingyuan.android_client`),对接 `wechat-backend` 提供的 auth / checkin / plan / course-vote / survey / feedback / profile API。当前处于**基础建设阶段**:多模块骨架已就绪,各 feature 为空占位,尚未接入真实后端。
## 快速开始
```bash
./gradlew :app:assembleDebug # 构建调试 APK
./gradlew test testDebugUnitTest # 运行单元测试(JVM + Android)
bash scripts/verify.sh # 完整验证(格式 / 测试 / 构建 / 架构检查)
```
## 架构概览
多模块 NIA 架构:`app` 是组合根(Hilt 入口 + 单一 `AppNavHost`),`feature/*` 承载各业务界面,`core/*` 提供共享能力。依赖方向严格单向,由 `verifyArchitecture` Gradle 任务强制执行(违规即失败)。详见 [docs/architecture.md](docs/architecture.md)。
## 目录结构
```
app/ 组合根:MingyuanApplication、MainActivity、AppNavHost
core/common 纯 Kotlin 工具(无 Android 依赖)
core/model 领域模型(纯 Kotlin)
core/domain UseCase + Repository 接口(纯 Kotlin)
core/data Repository 实现,编排 network/database/datastore
core/network Retrofit/OkHttp、DTO、序列化
core/database Room 实体 / DAO / 数据库
core/datastore DataStore 偏好
core/designsystem 主题 / 颜色 / 字体 / 通用组件
core/ui 共享 Compose UI + 导航辅助
feature/* 业务功能(auth/checkin/plan/course/survey/feedback/profile)
build-logic/ 约定插件(mcl.*)+ verifyArchitecture
docs/ 架构 / 规范 / 决策记录(见下方文档地图)
scripts/verify.sh 确定性验证脚本
```
## 关键约定
1. **依赖方向单向**:`app → 所有`;`feature/* → core(ui/designsystem/domain/model)`;`core:data → domain/model/network/database/datastore`;`core:model`/`core:domain` 保持纯 Kotlin、不依赖 Android。新增模块或跨层依赖必须同步更新 `build-logic` 的 `RootPlugin.kt` allow-list 与 `docs/architecture.md`。
2. **版本唯一事实源**:所有依赖版本在 [gradle/libs.versions.toml](gradle/libs.versions.toml),禁止在模块里硬编码版本号。
3. **模块配置走约定插件**:每个模块只声明 `mcl.*` 插件 + namespace,不要手写 `android {}` 模板代码。新增模块/插件流程见 [build-logic/convention/DESIGN_NOTES.md](build-logic/convention/DESIGN_NOTES.md)。
4. **确定性格式化**:`spotlessCheck` 必须通过(ktlint 规则在 [.editorconfig](.editorconfig));提交前用 `./gradlew spotlessApply` 修正。
5. **命名空间规则**:`com.mingyuan.android_client.<路径点分>`(如 `feature:checkin` → `com.mingyuan.android_client.feature.checkin`)。
6. **验证优先于声称完成**:任何"完成/通过"声明都必须以 `scripts/verify.sh` 的新鲜输出为证据。
7. **Commit 格式**:`type(scope): description`,一次提交一个变更集。
8. **新 feature 模式**:在 `feature/<name>/` 下用 `mcl.android.feature` 插件,暴露 `<Name>Screen` 组合函数 + `<NAME>_ROUTE` 路由常量 + `<Name>ViewModel`,并在 `app` 的 `AppNavHost` 注册路由。
## 文档地图
| 主题 | 文档 |
|------|------|
| 架构与模块依赖 | [docs/architecture.md](docs/architecture.md) |
| 编码规范 / 命名 / commit | [docs/conventions.md](docs/conventions.md) |
| 测试策略与命令 | [docs/testing.md](docs/testing.md) |
| 构建 / 签名 / 发布 | [docs/deployment.md](docs/deployment.md) |
| 工作状态与遗留项 | [docs/progress.md](docs/progress.md) |
| 架构决策记录(ADR) | [docs/decisions/](docs/decisions/) |
| 约定插件设计说明 | [build-logic/convention/DESIGN_NOTES.md](build-logic/convention/DESIGN_NOTES.md) |
| domain 层设计说明 | [core/domain/DESIGN_NOTES.md](core/domain/DESIGN_NOTES.md) |
## 常见任务
| 任务 | 做法 |
|------|------|
| 新增一个 feature 模块 | 建 `feature/<name>/` + `mcl.android.feature` + Screen/ViewModel,在 `RootPlugin.kt` 与 `settings.gradle.kts` 注册,更新 `docs/architecture.md` |
| 新增一个依赖 | 加进 `gradle/libs.versions.toml`,模块里用 `libs.xxx` 引用 |
| 修改模块依赖关系 | 改对应模块的 `build.gradle.kts`,跑 `./gradlew verifyArchitecture` 确认不违规 |
| 本地完整验证 | `bash scripts/verify.sh` |

30
README.md Normal file
View File

@@ -0,0 +1,30 @@
# 明缘进阶场 Android 客户端
「明缘进阶场」的 Android 客户端,与 `wechat-backend`(明缘进阶场 API)配套。采用 Kotlin + Jetpack Compose + Hilt + Retrofit + Room + DataStore + Navigation 的现代多模块架构,参考 Google Now in Android 官方示例。
## 项目状态
当前处于**基础建设阶段**:多模块骨架、约定插件、版本目录、验证脚本与 CI 均已就绪;各 feature 模块为空占位,尚未接入真实后端。详见 [docs/progress.md](docs/progress.md)。
## 快速开始
```bash
./gradlew :app:assembleDebug # 构建调试 APK
./gradlew test testDebugUnitTest # 运行单元测试
bash scripts/verify.sh # 完整验证
```
## 技术栈
- **UI**:Jetpack Compose + Material 3
- **架构**:多模块(app / core / feature)、MVVM、单向数据流(StateFlow)
- **依赖注入**:Hilt(KSP)
- **网络**:Retrofit + OkHttp + kotlinx-serialization
- **本地存储**:Room + DataStore
- **导航**:Navigation Compose
- **构建**:Gradle 约定插件(build-logic)+ 版本目录(version catalog)
- **质量**:Spotless/ktlint + Android Lint + 自定义架构检查
## 文档
架构、规范、测试、部署与决策记录见 [AGENTS.md](AGENTS.md) 的文档地图,或直接浏览 [docs/](docs/)。

69
docs/architecture.md Normal file
View File

@@ -0,0 +1,69 @@
# 架构
## 概述
本项目采用 Google Now in Android(NIA)风格的多模块架构,将应用按**职责边界**拆分为 `app`、`core/*`、`feature/*` 三类模块。核心目标是让依赖方向**严格单向**、让每一层**可独立编译与测试**、让领域逻辑**与 Android 框架解耦**。
## 模块结构
| 模块 | 类型 | 职责 |
|------|------|------|
| `app` | application + compose + hilt | 组合根:`@HiltAndroidApp` Application、`MainActivity`、单一 `AppNavHost` |
| `core:common` | 纯 JVM | 无 Android 依赖的通用工具 |
| `core:model` | 纯 JVM | 领域模型(跨层共享的不可变数据) |
| `core:domain` | 纯 JVM | UseCase + Repository 接口(业务规则,框架无关) |
| `core:data` | android library + hilt | Repository 实现,编排 network/database/datastore |
| `core:network` | android library + serialization | Retrofit/OkHttp、DTO、序列化 |
| `core:database` | android library + room | Room 实体 / DAO / 数据库 |
| `core:datastore` | android library | DataStore 偏好存储 |
| `core:designsystem` | android library + compose | 主题 / 颜色 / 字体 / 通用组件 |
| `core:ui` | android library + compose + hilt | 共享 Compose UI 与导航辅助 |
| `feature:*` | feature | 各业务功能(auth/checkin/plan/course/survey/feedback/profile) |
## 分层与依赖方向
依赖必须**单向向下**,杜绝环。完整 allow-list 编码在 `build-logic/convention/src/main/kotlin/RootPlugin.kt` 的 `verifyArchitecture` 任务中——任何越层依赖都会在 `./gradlew verifyArchitecture` 时失败。
```
app ────────────► 所有模块(组合根)
feature/* ──────► core:ui, core:designsystem, core:domain, core:model
core:data ──────► core:domain, core:model, core:network, core:database, core:datastore
core:network ───► core:common, core:model
core:database ──► core:model
core:datastore ─► core:model
core:ui ────────► core:designsystem, core:model
core:designsystem ► core:common
core:model, core:common ► (叶子,无项目依赖)
```
分层语义(自上而下):
- **UI 层**(`feature/*` + `core:ui` + `core:designsystem`):渲染界面、处理交互、持有 `ViewModel`。
- **领域层**(`core:domain` + `core:model`):业务规则(UseCase)、领域模型、Repository 接口。**纯 Kotlin,不依赖 Android**,可快速跑在 JVM 上测试。
- **数据层**(`core:data` + `core:network`/`database`/`datastore`):Repository 实现、网络与本地数据源,依赖领域层接口。
## 模块配置(约定插件)
每个模块的 `build.gradle.kts` 只声明 `mcl.*` 约定插件 + namespace,不手写模板。插件语义:
| 插件 | 适用 |
|------|------|
| `mcl.kotlin.library` | 纯 JVM 模块(common/model/domain) |
| `mcl.android.library` | Android 库(network/database/datastore/data) |
| `mcl.android.library.compose` | Android 库 + Compose(designsystem/ui) |
| `mcl.android.feature` | feature 模块(= library.compose + hilt + 导航/lifecycle 依赖) |
| `mcl.android.application.compose` | app(= application + compose) |
| `mcl.android.hilt` | Hilt(KSP) |
| `mcl.android.room` | Room(KSP) |
| `mcl.android.lint` | Android Lint 配置 |
## 新增一个模块
1. 建目录 + `build.gradle.kts`(选对 `mcl.*` 插件 + 设 namespace)+ 空 `AndroidManifest.xml`(仅 Android 模块)。
2. 在 `settings.gradle.kts` 里 `include(":...")`。
3. 在 `RootPlugin.kt` 的 allow-list 里加一条(模块路径 → 允许依赖的模块)。
4. 更新本文件的分层图。
## 组合根(app)
`app` 负责把各模块**装配**起来:`MingyuanApplication` 标注 `@HiltAndroidApp`;`AppNavHost` 用 Navigation Compose 把各 feature 的路由常量(`<NAME>_ROUTE`)与 Screen 组合成单一导航图。后续接入数据层时,`app` 会直接依赖 `core:data`(以及含 Hilt `@Module` 的 network/database/datastore)完成 DI 聚合。

36
docs/conventions.md Normal file
View File

@@ -0,0 +1,36 @@
# 编码规范
## Kotlin
- 遵循官方 Kotlin 风格:四空格缩进、类型 `UpperCamelCase`、成员 `lowerCamelCase`、常量 `SCREAMING_SNAKE_CASE`。
- 优先不可变:用 `val`、`data class` 的 `copy()`,避免可变共享状态。
- 严格空安全,不用 `!!` 除非有充分理由。
- 用密封类(sealed class)表达状态 / 事件 / 结果。
- 协程:数据操作在 `Dispatchers.IO`,UI 在 `Dispatchers.Main`;用 `StateFlow` 暴露 UI 状态(单向数据流)。
## Compose
- 屏幕组合函数命名 `<Feature>Screen`,接收 `modifier: Modifier = Modifier` 作为首个参数。
- `@Composable` 函数名用 `PascalCase`;被 `@Preview` 或测试使用的纯函数可豁免下划线命名(见 `.editorconfig`)。
- 复用组件放 `core:designsystem` 或 `core:ui`,不复制到 feature。
## 命名
- Activity/Application 以 `Activity`/`Application` 结尾;ViewModel 以 `ViewModel` 结尾;UseCase 以 `UseCase` 结尾;Repository 实现以 `RepositoryImpl` 结尾(接口在 `core:domain` 用 `Repository`)。
- 资源用 `snake_case`;字符串、颜色、尺寸统一在 `core:designsystem` 管理。
- 测试类以 `<Subject>Test` 结尾。
## 版本与依赖
- 依赖版本只写在 `gradle/libs.versions.toml`,模块里用 `libs.*` 访问器引用,禁止硬编码版本。
- 新增依赖先确认是否已有等价项,避免重复引入。
## Git
- Commit 格式 `type(scope): description`,type 如 `feat`/`fix`/`refactor`/`docs`/`build`/`test`/`chore`;一次提交一个变更集。
- 提交前跑 `./gradlew spotlessApply` 与 `bash scripts/verify.sh`。
## 文档
- 重要决策写入 `docs/decisions/`(ADR),行为与文档在同一次变更里一起更新。
- 模块级"为什么这样设计"写在该目录的 `DESIGN_NOTES.md`(即时上下文)。

View File

@@ -0,0 +1,22 @@
# ADR 0001:多模块 NIA 架构,feature 单模块(不做 api/impl 拆分)
- **状态**:已接受
- **日期**:2026-09-03
## 背景
项目从单模块 Compose 模板起步,需要确立可扩展、可测试、可维护的架构基础。
## 决策
采用 Google Now in Android(NIA)风格多模块架构:`app`(组合根)+ `core/*`(共享能力)+ `feature/*`(业务功能)。每个 feature 用**单模块**,不做 nowinandroid 的 `feature:*/api` + `feature:*/impl` 拆分。
## 理由
- 依赖方向单向、职责清晰,符合参考项目 `nowinandroid` 与 `app_device` 的共识。
- 团队规模小、feature 间共享少,api/impl 拆分会带来额外模块与样板,收益有限。
## 后果
- 约定插件 `mcl.android.feature` 预置 feature 通用依赖,新增 feature 只需一个插件声明。
- 若未来 feature 间需要稳定 API 边界,再引入 api/impl 拆分(成本可控)。

View File

@@ -0,0 +1,23 @@
# ADR 0002:约定插件 + 版本目录作为唯一配置事实源
- **状态**:已接受
- **日期**:2026-09-03
## 背景
多模块项目若在每个模块手写 `android {}` 模板,会出现配置漂移、版本不一致、样板重复。
## 决策
- **约定插件**(`build-logic/`,included build):提供 `mcl.*` 插件,集中编码 compileSdk/minSdk、Java/Kotlin target、Compose、Hilt、Room、Lint、Spotless 等配置。模块只声明插件 + namespace。
- **版本目录**(`gradle/libs.versions.toml`):所有依赖版本唯一事实源,主构建与 build-logic 共用。
## 理由
- 把"严谨性"编码进确定性规则(harness-engineering 的"严谨性转移"原则),而非依赖人工记忆。
- 一个版本改一处,全项目生效。
## 后果
- 新增/调整模块配置需改 build-logic;新增依赖需改版本目录。
- 好处:`verifyArchitecture` 等约束可挂在约定插件体系里统一执行。

View File

@@ -0,0 +1,23 @@
# ADR 0003:工具链与版本选型
- **状态**:已接受
- **日期**:2026-09-03
## 背景
需要为一套现代化 Android 技术栈锁定版本。项目原本是 AGP 9.4.0 + Kotlin 2.2.10 + Gradle 9.6.0。
## 决策
- **保留**:Gradle 9.6.0(wrapper)、AGP 9.4.0、compileSdk/targetSdk 37、minSdk 28、Compose BOM 2026.02.01。
- **对齐 nowinandroid 已验证矩阵**:Kotlin 2.3.0、KSP 2.3.4、Hilt 2.59、Room 2.8.3、Retrofit 2.11.0、OkHttp 4.12.0、kotlinx-coroutines 1.10.1、kotlinx-serialization 1.8.0、Navigation Compose 2.8.5、DataStore 1.2.0、Lifecycle 2.10.0、truth 1.4.4、turbine 1.2.0、spotless 8.3.0、ktlint 1.4.0。
## 理由
- 采用 AGP 9 内置 Kotlin(不显式应用 `org.jetbrains.kotlin.android`),与项目原有模板一致,且经 nowinandroid 验证可与 KSP/Hilt/Room 共存。
- Kotlin 升到 2.3.0 是为了直接复用 nowinandroid 已证明可用的 KSP 2.3.4 / Hilt 2.59 / Room 2.8.3 组合,避免为旧 Kotlin 猜测 KSP 补丁版本。
## 后果
- 全量构建已验证通过(`:app:assembleDebug`)。
- 若未来 AGP 升级改变内置 Kotlin 行为,需回归验证 KSP/Hilt/Room 兼容性。

View File

@@ -0,0 +1,22 @@
# ADR 0004:包名与显示名
- **状态**:已接受
- **日期**:2026-09-03
## 背景
项目原有包名 `com.mingyuan.android_client`(applicationId 与 namespace),显示名 `android-client`。
## 决策
- **保留包名** `com.mingyuan.android_client`(避免牵连改动),仅把显示名 `app_name` 改为「明缘进阶场」。
- 模块命名空间沿用 `com.mingyuan.android_client.<路径点分>`。
## 理由
- 重命名 applicationId/namespace 会牵连 manifest、包目录、DI 组件等;基础建设阶段优先把架构骨架跑通。
- 下划线命名虽不符合惯例,但不影响构建与运行,作为已知小问题保留。
## 后果
- 显示名已是「明缘进阶场」;包名中的下划线如需规范化,可在后续单独决策并一次性迁移。

27
docs/deployment.md Normal file
View File

@@ -0,0 +1,27 @@
# 构建与部署
## 构建
```bash
./gradlew :app:assembleDebug # 调试 APK(app/build/outputs/apk/debug/)
./gradlew :app:assembleRelease # 发布 APK(未启用混淆,签名见下)
./gradlew installDebug # 安装到已连接设备
```
## 签名
当前发布构建**未配置签名**,也**未启用 R8 混淆**(`minify` 关闭)。接入真实发布前需:
1. 生成/配置 keystore(`keystore.properties` 或环境变量)。
2. 在 `app/build.gradle.kts` 配置 `signingConfigs` 与 `release` buildType 的签名。
3. 需要时启用 R8(`isMinifyEnabled = true` + proguard 规则)。
## 环境
- JDK 17+(`settings.gradle.kts` 有版本检查)。
- Android SDK 路径见 `local.properties`(`sdk.dir`)。
- `compileSdk` 37 / `minSdk` 28 / `targetSdk` 37。
## CI
`.github/workflows/ci.yml` 在推送 GitHub 后自动运行 `spotlessCheck → test/testDebugUnitTest → :app:assembleDebug → verifyArchitecture → lintDebug`。

35
docs/progress.md Normal file
View File

@@ -0,0 +1,35 @@
# 工作状态
> 本文件是跨会话的工作状态快照。变更后请更新。
## 当前状态
- ✅ 基础建设:多模块骨架(9 core + 7 feature + app)、约定插件(`mcl.*`)、版本目录、`verifyArchitecture`、Spotless/Lint、验证脚本、CI、文档、DESIGN_NOTES。
- ✅ 验证通过:`./gradlew :app:assembleDebug`、`test`/`testDebugUnitTest`、`verifyArchitecture` 全绿。
- ⬜ 各 feature 为占位屏(`<Feature>Screen` + 空 `<Feature>ViewModel`),无业务逻辑。
## 已完成
- [x] build-logic 约定插件 + 版本目录 + 根配置
- [x] 17 个模块骨架 + app 组合根(Hilt + Navigation)
- [x] 主题迁入 `core:designsystem`(`MingyuanTheme`)
- [x] `verifyArchitecture` 架构检查任务
- [x] AGENTS.md / README / docs/* / DESIGN_NOTES
- [x] scripts/verify.sh + CI workflow
## 已阻塞
- 无。
## 下一步计划
1. 接入真实后端:`core:network` 定义 Retrofit API + DTO(对应 wechat-backend 的 `/api/v1/*`),`core:data` 实现 Repository,`app` 补齐 DI 聚合。
2. `core:database`/`core:datastore` 落地本地存储 schema。
3. 逐个 feature 实现业务逻辑(checkin/plan/course/survey/feedback/profile/auth)。
4. 发布前:签名 + R8 混淆 + 图标/品牌。
## 遗留项(暂缓)
- Roborazzi 截图测试、WorkManager、Gradle Managed Devices —— 有真实 UI/后台任务时再接入。
- `docs/api.md`、`docs/data-model.md` —— 后端接线时再建。
- app 尚未直接依赖 `core:network`/`core:database`/`core:datastore`;接入 DI 时补上(已在 `verifyArchitecture` allow-list 中放行)。

34
docs/testing.md Normal file
View File

@@ -0,0 +1,34 @@
# 测试
## 运行命令
```bash
./gradlew test # 纯 JVM 模块(core:model/domain/common)单元测试
./gradlew testDebugUnitTest # Android 模块单元测试(本地 JVM)
./gradlew connectedDebugAndroidTest # 仪器测试(需连接模拟器/真机)
```
## 目录与命名
- JVM 单元测试:`src/test/kotlin/`;Android 单元测试:`src/test/kotlin/`。
- 仪器测试:`src/androidTest/kotlin/`。
- 测试类命名 `<Subject>Test`;测试方法名用反引号描述意图(`fun \`invoke delegates to implementation\`()`)。
## 测什么
- **领域层优先**(`core:domain`):UseCase、纯逻辑、状态转换——用 `kotlinx-coroutines-test` 的 `runTest` + `truth` 断言,快速且稳定。
- **数据层**(`core:data`):Repository 用 fake 数据源(实现 `core:domain` 里的接口),不真打网络/数据库。
- **UI**:`@HiltViewModel` 的 `ViewModel` 用 `StateFlow` 断言状态;Composable 用 Compose UI test。
- 网络 / 数据库边界用接口隔离,用 fake/dummy 替换真实实现。
## 依赖
- `junit`:`@Test`
- `truth`:断言(更可读的 `assertThat(...)`)
- `turbine`:Flow 测试(需要时再加)
- `kotlinx-coroutines-test`:`runTest`
## 约定
- 不写"假测试"(无断言的空壳);每条测试验证一个明确行为。
- 提交 PR 时在描述里写清运行的命令与结果。