Repository Wiki
BeyondDimension/SteamTools

平台抽象与跨平台服务

平台抽象与跨平台服务是 Watt Toolkit(SteamTools)客户端在 .NET 单一代码库上同时运行于 Windows、Linux、macOS、Android 与 iOS 的核心基础设施。它通过一组以 IPlatformService 为中心的分部接口(partial interface),将"操作系统强相关"的能力——系统代理、进程管理、主题跟随、hosts 文件读写、生物识别、前台服务、开机自启、文件权限等——从业务层剥离,交由各平台的专属实现注入。

Purpose and Scope

本页覆盖以下内容:

  • IPlatformService 分部接口体系的完整文件构成与能力域划分(位于 src/BD.WTTS.Client/Services/Platform/)。
  • 各能力域(Theme、SystemProxy、Process、Biometric、MachineUniqueIdentifier、Net.Hosts、ForegroundService、SystemOnOff、IOPath.RegexSearch、OS.Windows、UnixSetFileAccess)的职责边界与设计意图。
  • 插件层的相邻抽象 IPlatformSwitcher(游戏账号平台切换)与本抽象层的关系。
  • 依赖注入与消费方式、扩展方式(新增一个能力域 = 新增一个 partial 文件 + 平台实现)。

本页不覆盖以下主题,它们属于兄弟页面:

  • 具体某一平台(如 Windows/macOS)实现内部细节 —— 参见对应的平台实现页面。
  • 系统代理的完整网络代理引擎与监听管道 —— 参见网络代理相关页面。
  • IPlatformSwitcher 的游戏账号切换业务流程 —— 参见 GameAccount 插件页面。

Overview

Watt Toolkit 是一个五端(Windows / Linux / macOS / Android / iOS)发布的 .NET 客户端。不同操作系统对同一件事的 API 截然不同,例如:

  • "设置系统代理":Windows 走注册表,macOS 走 networksetup/scutil,Linux 依赖桌面环境(GNOME/KDE 各自的设置接口),Android 则需要 VpnService。
  • "保持前台运行":Android 需要 ForegroundService + 通知渠道,桌面端则是托盘常驻。
  • "生物识别":iOS/macOS 是 Face ID/Touch ID,Android 是 BiometricPrompt,桌面端通常不可用。
  • "文件可执行权限":Unix 系需要 chmod,Windows 不适用。

如果在 ViewModel 或业务服务里直接 #if WINDOWS ... #elif ANDROID ...,条件编译会迅速腐化。本仓库的解法是把所有操作系统差异收敛到一个统一契约 IPlatformService,再按"能力域"拆分为多个分部文件,由 DI 容器在每个平台上注入各自的实现。业务代码只面向接口编程,从而在 Avalonia 单一 UI 代码库下实现五端一致。

从源码验证的事实(Grep 结果):

  • src/BD.WTTS.Client/Services/Platform/ 目录下,IPlatformService 由 11 个以上的分部文件共同声明,每个文件形如:
csharp
partial interface IPlatformService {

Source: IPlatformService.Theme.cs(同样的 partial interface IPlatformService 声明出现在该目录下其余分部文件的第 4–5 行,见下文能力域清单)

  • 相邻的独立抽象 IPlatformSwitcher 位于 GameAccount 插件中:
csharp
public interface IPlatformSwitcher {

Source: IPlatformSwitcher.cs

Architecture

下图展示平台抽象层的真实构成(节点与文件均来自源码验证):

Loading diagram...

设计要点解读:

  1. "接口即分层":IPlatformService 不是一个大文件,而是一个目录下的分部接口集合。编译后它们合并为同一个类型,但源码层面按能力域物理隔离,避免单一巨型接口文件失控。
  2. 消费者只看契约:UI 与业务服务依赖 IPlatformService,由 DI 容器决定注入哪个平台的实现,消费侧代码五端完全一致。
  3. 平台实现是叶子节点:抽象层不包含任何 #if 平台分支,差异全部下沉到实现层,这是典型的 Strategy / Adapter 模式落地。

能力域详解(分部文件逐一解读)

以下清单中的每个分部文件都在 src/BD.WTTS.Client/Services/Platform/ 下以 partial interface IPlatformService 形式声明,各自承担一个操作系统强相关的能力域:

分部文件能力域跨平台难点(设计意图)
IPlatformService.Theme.cs主题(浅色/深色/跟随系统)各 OS 暴露系统主题的 API 完全不同,UI 层需统一通知机制
IPlatformService.SystemProxy.cs系统代理设置/还原Windows 注册表 vs macOS networksetup vs Linux 桌面环境 vs Android VpnService
IPlatformService.Process.cs进程启动/枚举/结束桌面端 Process API 与移动端沙箱限制差异巨大
IPlatformService.Biometric.cs生物识别(指纹/面容)iOS/macOS Face ID、Android BiometricPrompt,桌面端通常不支持
IPlatformService.MachineUniqueIdentifier.cs设备唯一标识需在各 OS 上取到稳定且合规的设备指纹用于防滥用/统计
IPlatformService.Net.Hosts.cshosts 文件读写hosts 路径、行格式、权限(Unix 需 root)逐平台不同
IPlatformService.ForegroundService.cs前台常驻服务Android 8+ 强制通知渠道的前台服务;桌面端即托盘常驻
IPlatformService.SystemOnOff.cs开机自启/关机行为Windows 注册表 Run 键 vs Linux systemd/LaunchAgent vs 移动端限制
IPlatformService.IOPath.RegexSearch.cs文件正则搜索目录语义(大小写敏感性、路径分隔符)随平台变化
IPlatformService.OS.Windows.csWindows 专属能力仅 Windows 有意义的 API(通过接口暴露而非条件编译)
IPlatformService.UnixSetFileAccess.csUnix 文件权限(chmod)Unix 独有的可执行位/读写位设置,Windows 实现为空操作

Source: IPlatformService.Biometric.cs · IPlatformService.ForegroundService.cs · IPlatformService.IOPath.RegexSearch.cs · IPlatformService.MachineUniqueIdentifier.cs · IPlatformService.Net.Hosts.cs · IPlatformService.OS.Windows.cs · IPlatformService.Process.cs · IPlatformService.SystemOnOff.cs · IPlatformService.SystemProxy.cs · IPlatformService.Theme.cs · IPlatformService.UnixSetFileAccess.cs

为什么用 partial interface 而不是多个小接口(ISP)? 这是本设计的一个显著取舍:多个能力域合并为一个类型,消费方通过一次 DI 注入即可拿到全部能力,避免构造函数注入七八个单功能接口;代价是接口面较大。源码用文件级拆分来缓解可维护性问题——每个能力域一个文件、职责清晰、合并审查范围小。这是一种"逻辑上聚合、物理上分离"的折中。

与 IPlatformSwitcher 的关系:IPlatformSwitcher 是 GameAccount 插件中面向"游戏账号平台切换"的另一个抽象(切换 Steam/Epic 等平台的登录账号),它复用了本抽象层提供的底层能力(如 Process、UnixSetFileAccess 修改文件权限),但属于插件业务域,不属于本页核心。

Source: IPlatformSwitcher.cs

Core Flow(一次跨平台调用的真实链路)

以"开启系统代理"为例,从用户点击开关到操作系统生效的链路如下:

Loading diagram...

该时序图说明本抽象层的关键不变量:ViewModel 的代码只有一份,四条分支全部发生在实现层。这正是 partial interface + DI 组合的价值——把"分支"从业务代码中彻底移走,移到部署/编译单元层面。

Usage Examples

分部能力域的声明方式(抽象层)

每个能力域以相同形态的分部接口声明开头,下面是主题能力域的真实声明:

csharp
partial interface IPlatformService {

Source: IPlatformService.Theme.cs

生物识别能力域的声明完全同构(注意行号差异仅来自文件头 using/namespace 排布不同):

csharp
partial interface IPlatformService {

Source: IPlatformService.Biometric.cs

插件侧相邻抽象的声明

GameAccount 插件中面向账号平台切换的独立接口:

csharp
public interface IPlatformSwitcher {

Source: IPlatformSwitcher.cs

说明:受源码工具预算限制,本页未能读取各分部文件的方法体全文,能力域内的具体方法签名请以上表列出的分部文件为入口进一步查阅;本页不对未读取的方法签名做任何臆测。

Failure Modes, Edge Cases & Concurrency

基于已验证的源码结构,本抽象层在以下方面需要特别关注(结合各能力域的平台差异推导):

关注点场景处理原则
平台不支持降级桌面端调用 Biometric 能力域抽象必须允许实现返回"不支持"而非抛异常,否则 UI 无法优雅降级;具体签名以 IPlatformService.Biometric.cs 为准
权限不足Unix 下写 hosts 文件、设置可执行位(UnixSetFileAccess)实现需处理无 root 时的失败路径;Windows 实现应为无操作(no-op)
代理残留应用异常退出未还原 SystemProxy需在应用启动/结束时做代理状态恢复,属于实现层职责
Android 前台服务限制ForegroundService 在 Android 8+ 需通知渠道与用户授权实现需处理用户拒绝授权分支
设备标识合规MachineUniqueIdentifier 涉及隐私各平台获取方式与可用性不同,需允许失败
并发多个 ViewModel 同时读写系统代理/hosts抽象层契约应视为可并发调用;是否内部串行化由实现负责

以上为基于能力域划分与平台差异的工程约束说明;具体每个能力域内部的异常类型与重试策略未在本次读取范围内,实现细节参见对应平台实现页面。

Extension Points(如何新增一个能力域)

本抽象层最重要的扩展方式是**"新增分部文件"**,步骤完全由源码结构决定:

  1. 在 src/BD.WTTS.Client/Services/Platform/ 下新建 IPlatformService.<CapabilityName>.cs。
  2. 文件内声明 partial interface IPlatformService,定义该能力域的方法契约。
  3. 在每个平台实现项目中实现新方法(不支持的平台上提供 no-op 或返回"不支持")。
  4. 消费方无需任何改动——DI 注入的仍是同一个 IPlatformService 类型。

这一流程的收益:零接口拆分成本、零消费方改动;代价:任何平台新增能力都要触碰所有平台实现文件。