10 KiB
ABox Manager 架构基线
1. 文档范围
本文记录已建立的模块边界、依赖方向、提供方路由和并行开发接口,覆盖 ABox(局域网 ABSDK)与本地体验两条连接路径。ABSDK 0630 的初始化、签名细节和会话清理等未确认内容不在本文中固化,协议观测证据见 docs/protocol/absdk-0630.md。
产品范围和固定分工以 docs/project_plan.md 为准,原始简要需求继续保留在 docs/requirement_spec.md。
2. 当前模块
abox-mgr/
├── android/
│ ├── app/ # Android 应用、提供方路由与依赖注入组合根
│ └── core/
│ ├── designsystem/ # Compose Material 3 设计系统基础
│ └── session/ # 提供方 profile、凭据 Keystore 持久化
├── domain/ # 纯 Kotlin 领域模型和公共端口
├── absdk-bridge/ # ABSDK 0630 防腐层(生产)
├── runtime/simulation/ # 本地体验模拟运行时(生产)
├── testing/fakes/ # 测试专用 Fake(仅测试依赖)
├── docs/
└── samples/ # 历史示例,仅供参考
依赖方向:
android/app ────────────────┬──> android/core/designsystem
├──> android/core/session ──> domain
├──> domain
├──> absdk-bridge ──> domain
└──> runtime/simulation ──> domain
android/core/designsystem ─────> Compose Material 3
testing/fakes(仅测试)────────> domain
domain ────────────────────────> Kotlin 与 Coroutines
domain 不依赖 Android、Compose、Room、ABSDK 或具体网络实现。业务 UI 只依赖领域端口和类型化结果。
3. 提供方与数据隔离
认证前应用只提供两种入口,两者在所有构建变体中始终可用:
- ABox:通过进程内唯一的真实
AbsdkBridge连接当前局域网中的智能家居网关,登录成功后进入应用; - 本地体验:由
:runtime:simulation直接建立模拟会话并进入应用,不请求凭据、不访问网络、不调用 ABSDK。
每个提供方 profile 由 ProviderProfileId 标识,所有会话、设备目录和设备操作都显式携带该标识。
约束:
- 任一时刻只启用一个提供方;
- 切换提供方前必须退出当前会话;
- 不同提供方的数据、凭据和设备状态不得互相混用;
- 本地体验不调用 ABSDK,也不启动本地 HTTP 服务;
- Release 不是特殊情况:debug 与 release 暴露相同的提供方和路由行为。
4. 公共领域端口
domain 当前提供以下最小端口:
SessionGateway:登录、观察会话、验证会话和退出;LocalSessionGateway:无凭据本地会话的标记端口,继承SessionGateway;ProviderProfileStore:提供方 profile、活动 profile 和登录偏好持久化;CredentialStore:自动登录凭据的加密存取;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 防腐层已在 :absdk-bridge 中完成同步阻塞调用的后台串行化、返回解析和错误映射。
6. Fake 边界
testing/fakes 提供会话、设备目录、插座、温湿度和红外 Fake。它们:
- 不使用网络、Android API、真实时间或随机数;
- 支持成功、失败、无数据、离线和 Token 无效场景;
- 对插座状态和设备目录按
ProviderProfileId隔离; - 记录红外学习、发送和下载调用;
- 可直接用于其他模块的 ViewModel 与 UI 测试。
Fake 只用于开发和测试,不能作为真实 ABox 或真实硬件验收证据。:testing:fakes 是测试专用模块,不得加入应用生产依赖。
ABSDK 不提供设备目录发现能力。生产 ABox 设备目录由 :android:core:devicecatalog 按当前 profile 持久化,来源是用户明确配置或导入的真实 DeviceName;它不从本地体验目录复制数据,也不把测试 Fake 绑定到生产组合根。Local profile 仍由 SimulationDeviceCatalog 提供独立的确定性目录。
7. 本地体验模拟运行时
:runtime:simulation 是生产 Kotlin/JVM 模块,为本地体验提供确定性实现:
SimulationSessionGateway:直接建立无凭据的本地会话;SimulationDeviceCatalog:内置的本地体验设备目录;SimulationSocketGateway、SimulationTemperatureHumidityGateway、SimulationInfraredGateway:固定行为的模拟设备操作。
模拟实现不暴露脚本化失败注入、调用记录或断言辅助;这些能力只存在于 :testing:fakes。
8. Android 组合根与提供方路由
android/app 是单 Activity Compose 应用,负责:
ABoxManagerApplication与 Hilt 组合根;MainActivity;- 根
NavHost与提供方选择、登录、认证后路由; - 默认英文和简体中文资源;
- 使用
android/core/designsystem的主题和基础组件。
组合根按端口绑定生产实现:
SessionGateway -> AbsdkBridge(进程内单例)
LocalSessionGateway -> SimulationSessionGateway
SocketGateway -> RoutingSocketGateway
TemperatureHumidityGateway -> RoutingTemperatureHumidityGateway
InfraredGateway -> RoutingInfraredGateway
AbsdkBridge 以 Hilt 单例提供并在所有构建变体中相同:会话与插座/温湿度/红外操作共享同一个 Bridge 实例,使互斥锁和 Bridge 本地会话与厂商 SDK 单例状态保持一致。三个 Routing 网关读取活动 profile 并按提供方分发——ABox 转发到共享的 AbsdkBridge 实现,本地体验转发到模拟实现。任何功能 UI 都不直接选择真实或 Fake 实现。
设备目录导航、插座与温湿度页面、红外页面和情景页面由固定模块负责人实现,不放入工程基础层。
9. 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,不复制厂商二进制;
- ABSDK 0630 没有设备目录能力,Bridge 因此也不提供设备目录或设备发现。
App 组合根已通过 Hilt 单例绑定该 Bridge,绑定处以 @CleartextAbsdkTransport 显式 opt-in 承认厂商 JAR 的硬编码明文 HTTP 风险;App 同时声明 INTERNET 权限并允许明文传输(usesCleartextTraffic),这是项目所有者对当前局域网集成约束的明确风险接受,不代表传输具备机密性或完整性。协议证据与限制记录在 docs/protocol/absdk-0630.md。
10. Provider Session
:android:core:session 负责提供方登录的持久化与 Android 安全边界:
ProviderProfileStore使用 DataStore 保存 profile、当前活动 profile、记住用户名和自动登录开关;已移除的提供方类型(如历史兼容后端值)被忽略,不会成为活动 profile;CredentialStore使用 Android Keystore 中的 AES-256-GCM 密钥加密自动登录凭据,DataStore 只保存带随机 IV 的密文;ProviderProfileId作为 GCM AAD,防止不同 profile 的密文互换;- 本地体验的会话由
:runtime:simulation的SimulationSessionGateway提供,不调用 HTTP 或 ABSDK。
纯 Kotlin SessionCoordinator 统一处理启动恢复、提供方选择、登录偏好、自动登录、Token 失效重登、退出和切换提供方。ABox 登录成功必须来自注入的真实 Bridge 返回 DomainResult.Success<Session>;无效凭据和网络错误停留在登录页并显示本地化错误。ABox 远端退出清除密码和 Session,但保留 profile 与记住的用户名以便再次显示登录页;本地体验退出清除本地会话和活动 profile,回到提供方选择。
两个提供方在所有构建变体中始终可用,不存在按 debug/release 区分的可用性开关。
登录完成后进入 authenticated 协作路由。设备中心、详情、红外、情景、自动化和活动记录由对应 Android 模块提供,并通过当前 profile 路由到真实或本地体验端口。
11. 后续边界
Room 与设备业务 DataStore 的具体 schema/key 只在对应数据所有权明确后创建。生产 ABox 设备目录由设备目录模块维护,不能回退到 FakeDeviceCatalog。历史示例中的 AsyncTask、明文密码存储和散落 SDK 调用不得进入正式架构;ABSDK 厂商 JAR 的明文局域网传输风险已明确记录并由所有构建变体共同承担。