Files
abox-mgr/docs/architecture.md
T
2026-08-23 21:58:04 +08:00

7.1 KiB
Raw Blame History

ABox Manager 架构基线

1. 文档范围

本文记录第一轮工程基础已经建立的模块边界、依赖方向和并行开发接口。ABSDK 0630 的初始化、协议、签名、连接重定向和会话清理等未确认内容不在本文中固化。

产品范围和固定分工以 docs/project_plan.md 为准,原始简要需求继续保留在 docs/requirement_spec.md。

2. 当前模块

abox-mgr/
├── android/
│   ├── app/                 # Android 应用与依赖注入组合根
│   └── core/designsystem/   # Compose Material 3 设计系统基础
├── domain/                  # 纯 Kotlin 领域模型和公共端口
├── testing/fakes/           # 确定性的公共 Fake
├── docs/
└── samples/                 # 历史示例,仅供参考

依赖方向:

android/app ────────────────┬──> android/core/designsystem
                            ├──> domain
                            └──> testing/fakes ──> domain

android/core/designsystem ─────> Compose Material 3
domain ────────────────────────> Kotlin 与 Coroutines

domain 不依赖 Android、Compose、Room、ABSDK 或具体网络实现。业务 UI 只依赖领域端口和类型化结果。

3. 提供方与数据隔离

应用计划支持 ABox、自有兼容后端和本地体验三种提供方。每个连接配置由 ProviderProfileId 标识,所有会话、设备目录和设备操作都显式携带该标识。

约束:

  • 任一时刻只启用一个提供方;
  • 切换远端提供方前必须退出当前会话;
  • 不同连接配置的数据、凭据和设备状态不得互相混用;
  • 本地体验不调用 ABSDK,也不启动本地 HTTP 服务;
  • Fake 使用相同标识隔离规则,便于在没有真实环境时验证边界。

4. 公共领域端口

domain 当前提供以下最小端口:

  • SessionGateway:登录、观察会话、验证会话和退出;
  • DeviceCatalog:按提供方观察设备目录;
  • SocketGateway:查询和设置插座状态;
  • TemperatureHumidityGateway:读取温度和湿度;
  • InfraredGateway:发送、学习和下载红外码。

端口只覆盖需求和 ABSDK 0630 文档已经确认的行为。DeviceId 是 App 本地目录中的稳定记录标识;设备操作必须使用 ABSDK 0630 实际接受的 DeviceName。同一提供方配置和设备类型内,DeviceName 必须唯一,设备目录在添加、编辑和导入时负责强制该约束。设备目录的增删改与二维码导入、遥控器布局、情景动作和持久化模型将在对应模块提出后增量加入,不在公共层提前假定。

5. 结果与错误边界

所有端口返回 DomainResult<T>:

  • Success<T> 携带类型化领域值;
  • Failure 携带 DomainError;
  • DomainError.externalCode 可以保留外部 SDK 错误码用于诊断;
  • UI 不接触 ABRet、原始 Map、HTTP 表单或服务端数据库模型。

真实 ABSDK 适配层必须在后续 feat/absdk-bridge 分支中完成同步阻塞调用的后台串行化、返回解析和错误映射。

6. Fake 边界

testing/fakes 提供会话、设备目录、插座、温湿度和红外 Fake。它们:

  • 不使用网络、Android API、真实时间或随机数;
  • 支持成功、失败、无数据、离线和 Token 无效场景;
  • 对插座状态和设备目录按 ProviderProfileId 隔离;
  • 记录红外学习、发送和下载调用;
  • 可直接用于其他模块的 ViewModel 与 UI 测试。

Fake 只用于开发和测试,不能作为真实 ABox 或真实硬件验收证据。

7. Android 组合根

android/app 是单 Activity Compose 应用,负责:

  • ABoxManagerApplication 与 Hilt 组合根;
  • MainActivity;
  • 根 NavHost;
  • 仅在 debug 构建中将领域端口绑定到当前开发阶段的 Fake,release 构建不包含 Fake;
  • 默认英文和简体中文资源;
  • 使用 android/core/designsystem 的主题和基础组件。

设备目录导航、插座与温湿度页面、红外页面和情景页面由固定模块负责人实现,不放入工程基础层。

8. ABSDK Bridge

:absdk-bridge 是纯 Kotlin/JVM 防腐层,依赖 domain 并实现会话、插座、温湿度和红外 Gateway。该模块:

  • 只在 VendorAbsdkClient 中接触 ABSDK、ABRet 和 IrInfo;
  • 将所有同步调用移到后台并串行进入全局 SDK;
  • 将返回值和错误码转换为 DomainResult 和领域模型;
  • 不提供设备目录;
  • 不调用未确认的 setHostInfo;
  • logout 只清除 Bridge 本地会话,因为 JAR 没有公开 logout 或 Token setter;
  • 生产工厂始终返回进程内同一个 Bridge,使互斥锁和本地会话与厂商单例保持一致;
  • 真实工厂标记为 @CleartextAbsdkTransport,调用方必须显式 opt-in 承认厂商 JAR 的硬编码 HTTP 风险;
  • 当前直接引用归档 Sample 目录中的唯一一份 JAR,不复制厂商二进制。

Bridge 本轮尚未加入 android/app 依赖,App debug 仍绑定 Fake。提供方选择、安全凭据和真实 Bridge 绑定由后续 feat/provider-login 完成;在获批的传输控制方案明确前,不得在 App 中启用真实 Bridge。协议证据与限制记录在 docs/protocol/absdk-0630.md。

9. Provider Session

:android:core:session 负责提供方登录的持久化与 Android 安全边界:

  • ProviderProfileStore 使用 DataStore 保存 profile、当前活动 profile、记住用户名和自动登录开关;
  • CredentialStore 使用 Android Keystore 中的 AES-256-GCM 密钥加密自动登录凭据,DataStore 只保存带随机 IV 的密文;
  • ProviderProfileId 作为 GCM AAD,防止不同 profile 的密文互换;
  • LocalSessionGateway 只维护本地内存 Session,不调用 HTTP 或 ABSDK;
  • UnavailableRemoteSessionGateway 在远端入口未获批准时明确拒绝登录。

纯 Kotlin SessionCoordinator 统一处理启动恢复、提供方选择、登录偏好、自动登录、Token 失效重登、退出和切换提供方。远端退出清除密码和 Session,但保留 profile 与 remembered username;本地体验退出后回到提供方选择。

App 按构建变体声明远端可用性:debug 的 ABox 入口明确是开发模拟,release 的 ABox 因传输安全待确认而禁用;兼容后端在协议明确前始终禁用;本地体验始终可用。App 没有依赖或创建 AbsdkBridge,也没有对 @CleartextAbsdkTransport opt-in。

登录完成后进入 authenticated 协作路由。当前仅有占位页面,设备列表与主导航由 alZerNest 的设备导航模块接管。

10. 后续边界

后续 KremeCN 工作保持为独立短期分支:

  • 在获批传输控制明确后,单独评审真实 ABox Bridge 的 App 绑定;
  • feat/compatible-server:在协议与连接方式确认后建立服务端、CLI 和 Docker 基础。

Room 与设备业务 DataStore 的具体 schema/key 只在对应数据所有权明确后创建。历史示例中的 AsyncTask、明文密码存储、全局明文网络和散落 SDK 调用不得进入正式架构。