Happy 项目架构设计与技术实现
一、背景与目标
1. 项目背景
A. 业务场景
Happy 是一个开源的 AI 编程助手生态系统,允许用户从任何设备(手机、平板、电脑)远程控制运行在本地机器上的 AI 编程代理。该项目解决了传统 AI 编程工具受限于桌面环境的问题,实现了真正的"随时随地编码"体验。
该生态系统由三个独立但紧密协作的组件组成:
- happy:React Native 跨平台客户端,支持 iOS、Android 和 Web
- happy-cli:命令行工具,作为 AI 代理(Claude、Gemini、Codex)的包装器
- happy-server:后端同步服务,提供消息中继和用户认证
B. 痛点分析
传统 AI 编程助手存在以下限制:
- 平台限制:必须在桌面环境使用,移动设备无法参与
- 数据隐私:代码需要上传到云端服务器,存在泄露风险
- 订阅费用:商业 AI 编程工具通常需要付费订阅
- 离线限制:依赖网络连接,无法在离线环境使用
Happy 通过以下设计解决上述问题:
- AI 代理运行在用户控制的硬件上,完全本地化
- 端到端加密确保代码隐私,服务器无法解密
- 开源免费,使用用户自己的硬件和 API 配额
- 支持离线操作,同步网络恢复后自动合并
2. 设计目标
A. 功能目标
- 多平台客户端覆盖(iOS、Android、Web、macOS Desktop)
- 支持多种 AI 代理后端(Claude Code、Gemini、Codex)
- 实时会话同步和消息推送
- 端到端加密的代码存储
- 实时语音交互(实验功能)
B. 非功能目标
- 性能指标:WebSocket 延迟 < 100ms,消息同步 < 500ms
- 可用性:99.5% 以上(客户端依赖用户网络)
- 扩展性:支持水平扩展的服务器架构
- 安全性:端到端加密、零知识架构、公钥认证
二、总体设计
1. 设计原则
- 零知识架构:服务器存储加密数据但无法解密
- 端到端加密:所有敏感数据使用客户端派生的密钥加密
- 用户控制:AI 代理运行在用户硬件上,而非云端
- 开源透明:所有组件开源,代码可审计
- 跨平台优先:使用 React Native 实现多平台复用
2. 系统架构
graph TB
subgraph 客户端层
Mobile[移动端 Client<br/>React Native]
Web[Web Client<br/>React Native Web]
Desktop[Desktop Client<br/>Tauri]
end
subgraph CLI层
CLI[happy-cli<br/>命令行工具]
Claude[Claude Code SDK]
Gemini[Gemini ACP]
Codex[Codex MCP]
end
subgraph 服务器层
Server[Happy Server<br/>TypeScript + Fastify]
Socket[Socket.IO<br/>WebSocket]
API[HTTP REST API]
Auth[认证模块]
end
subgraph 存储层
PG[(PostgreSQL<br/>关系数据)]
Redis[(Redis<br/>缓存/PubSub)]
MinIO[(MinIO<br/>对象存储)]
end
Mobile <--> Server
Web <--> Server
Desktop <--> Server
CLI <--> Server
Server --> Socket
Server --> API
Server --> Auth
Server --> PG
Server --> Redis
Server --> MinIO
CLI --> Claude
CLI --> Gemini
CLI --> Codex3. 组件说明
3.1 客户端层(happy)
- 技术栈:React Native 0.81、Expo 54、TypeScript 5.9
- 样式系统:Unistyles(跨平台主题和断点)
- 路由:Expo Router v5 文件路由
- 状态管理:React Context + 自定义 reducer
- 实时通信:Socket.IO Client 4.8
- 加密:TweetNaCl(Ed25519 签名)、libsodium(AES-256-GCM)
3.2 CLI 层(happy-cli)
- 技术栈:Node.js 20、TypeScript 5、Ink(React Terminal UI)
- AI 集成:Claude Code SDK、Gemini ACP、Codex MCP
- 守护进程:后台服务管理多个会话
- MCP 服务器:统一的工具接口(ripgrep、difftastic、HTTP 代理)
3.3 服务器层(happy-server)
- 技术栈:Node.js 20、TypeScript 5.4、Fastify 5
- 实时通信:Socket.IO 4(WebSocket)
- 数据库:PostgreSQL 16 + Prisma 6
- 缓存:Redis 7(PubSub 分布式支持)
- 对象存储:MinIO(S3 兼容)
4. 交互流程
4.1 认证流程(QR 码挑战-响应)
sequenceDiagram
participant C as 移动端
participant S as Happy Server
participant CLI as happy-cli
C->>S: 请求认证 QR 码
S-->>C: 返回包含 challenge 的 QR 码
C->>CLI: 扫描 QR 码
Note over CLI: 验证签名
CLI->>S: 提交认证响应
S-->>CLI: 返回 JWT Token
CLI->>S: 建立 WebSocket 连接
S-->>C: 推送会话更新4.2 消息同步流程
sequenceDiagram
participant U as 用户
participant C as happy-cli
participant S as Happy Server
participant M as 移动端
U->>C: 输入指令
C->>C: AI 处理
C->>S: 发送加密消息
S->>S: 存储(无法解密)
S-->>M: WebSocket 推送
M->>M: 解密并显示三、详细设计
1. 核心模块
A. 同步引擎(happy/sync)
位于 sources/sync/sync.ts,共 2143 行代码,是客户端的核心同步引擎。
class Sync {
// 加密管理
encryption!: Encryption;
encryptionCache = new EncryptionCache();
// 同步锁(防重复请求)
sessionsSync: InvalidateSync;
messagesSync: Map<string, InvalidateSync>;
settingsSync: InvalidateSync;
// 加密密钥存储
sessionDataKeys: Map<string, Uint8Array>;
machineDataKeys: Map<string, Uint8Array>;
artifactDataKeys: Map<string, Uint8Array>;
// 主要方法
async create(credentials, encryption)
async restore(credentials, encryption)
onSessionVisible(sessionId)
subscribeToUpdates()
}InvalidateSync 模式:防重复请求的同步锁,确保同一资源不会同时发起多个同步请求。
B. 消息归约器(happy/sync/reducer)
处理来自 WebSocket 的原始消息,分为五个阶段:
- Phase 0:处理 AgentState 权限请求
- Phase 0.5:消息到事件转换
- Phase 1:处理用户和文本消息
- Phase 2:处理工具调用
- Phase 3:处理工具结果
- Phase 4:处理侧链消息
- Phase 5:处理模式切换事件
C. Agent Backend 抽象(happy-cli)
统一的 AI 代理接口,支持多种后端:
interface AgentBackend {
startSession(initialPrompt?: string): Promise<StartSessionResult>
stop(): Promise<void>
sendPrompt(sessionId, prompt): Promise<void>
cancel(sessionId): Promise<void>
onMessage(handler: (message: AgentMessage) => void): void
respondToPermission?(requestId: string, approved: boolean): Promise<void>
dispose(): Promise<void>
}D. 事件路由器(happy-server)
管理三种连接类型的事件分发:
type ClientConnection =
| SessionScopedConnection // { userId, sessionId }
| UserScopedConnection // { userId }
| MachineScopedConnection; // { userId, machineId }2. 关键流程
A. 正常会话创建流程
flowchart TD
A[CLI 启动] --> B[生成 Ed25519 密钥对]
B --> C[创建认证请求]
C --> D[显示 QR 码]
D --> E[移动端扫码]
E --> F{签名验证}
F -->|成功| G[交换 Token]
F -->|失败| H[返回错误]
G --> I[建立 WebSocket]
I --> J[创建会话]
J --> K[开始同步]B. 端到端加密流程
flowchart LR
A[用户生成密钥对] --> B[公钥注册到服务器]
B --> C[派生会话密钥 HKDF]
C --> D[AES-256-GCM 加密数据]
D --> E[服务器存储加密数据]
E --> F[客户端私钥解密]3. 数据存储
A. 数据模型(happy-server)
| 模型 | 描述 | 加密方式 |
|---|---|---|
| Account | 用户账户,公钥认证信息 | 明文 |
| Session | 用户会话,多设备同步 | 元数据明文,内容加密 |
| Machine | 设备注册和状态管理 | 明文 |
| SessionMessage | 会话消息 | 端到端加密 |
| Artifact | 加密的文件对象 | Header 和 Body 分别加密 |
| AccessKey | 访问密钥管理 | 明文 |
| UserRelationship | 用户社交关系 | 明文 |
| UserKVStore | 用户键值存储 | 加密 |
| UserFeedItem | 用户消息流 | 加密 |
B. 缓存策略
- 令牌缓存:认证令牌永久缓存
- 加密缓存:EncryptionCache 缓存派生密钥
- 消息去重:使用 Set 防止重复处理
- 状态缓存:InvalidateSync 模式防止重复请求
四、技术选型
1. 技术栈
A. 前端技术
| 技术 | 版本 | 用途 | 选择理由 |
|---|---|---|---|
| React Native | 0.81 | 跨平台框架 | 生态成熟,代码复用率高 |
| Expo | 54 | 开发工具链 | 降低原生开发门槛 |
| Unistyles | 3.0 | 样式系统 | 主题和断点支持完善 |
| Socket.IO | 4.8 | WebSocket 客户端 | 自动重连,事件驱动 |
| React Navigation | 6 | 路由导航 | Expo Router 深度集成 |
B. 后端技术
| 技术 | 版本 | 用途 | 选择理由 |
|---|---|---|---|
| Node.js | 20 | 运行时 | 与前端技术栈统一 |
| TypeScript | 5.4 | 开发语言 | 类型安全,代码可维护 |
| Fastify | 5 | Web 框架 | 高性能,插件生态完善 |
| Prisma | 6 | ORM | 类型安全,迁移管理方便 |
| Socket.IO | 4 | WebSocket 服务 | 与客户端协议一致 |
C. 基础设施
| 技术 | 版本 | 用途 | 选择理由 |
|---|---|---|---|
| PostgreSQL | 16 | 关系数据库 | ACID 支持,JSON 类型 |
| Redis | 7 | 缓存和 PubSub | 高性能,分布式支持 |
| MinIO | 最新 | 对象存储 | S3 兼容,可私有部署 |
| Docker | 最新 | 容器化 | 环境一致性,部署方便 |
2. 选型对比
A. 客户端框架
| 方案 | 优点 | 缺点 | 选择 |
|---|---|---|---|
| React Native | 生态成熟,代码复用高 | 性能略逊于原生 | ✅ 选择 |
| Flutter | 性能好,UI 一致 | 学习曲线陡峭 | ❌ |
| 原生开发 | 性能最佳 | 开发成本高,维护困难 | ❌ |
B. 后端框架
| 方案 | 优点 | 缺点 | 选择 |
|---|---|---|---|
| Fastify | 高性能,类型安全 | 生态不如 Express | ✅ 选择 |
| Express | 生态成熟 | 性能一般,类型支持弱 | ❌ |
| NestJS | 功能完善 | 过度设计,学习曲线陡 | ❌ |
C. 数据库
| 方案 | 优点 | 缺点 | 选择 |
|---|---|---|---|
| PostgreSQL | 功能强大,JSON 支持 | 资源占用较高 | ✅ 选择 |
| MySQL | 成熟稳定 | JSON 支持较弱 | ❌ |
| MongoDB | 灵活,Schema 自由 | 事务支持弱 | ❌ |
五、部署架构
1. 部署图
graph LR
LB[负载均衡<br/>Nginx] --> App1[App 实例 1<br/>:3005]
LB --> App2[App 实例 2<br/>:3005]
App1 --> PG[(PostgreSQL<br/>主从)]
App2 --> PG
App1 --> Redis[(Redis<br/>哨兵模式)]
App2 --> Redis
App1 --> MinIO[(MinIO<br/>分布式)]
App2 --> MinIO
Redis -.PubSub.-> Redis2. 容量规划
| 组件 | 配置 | 实例数 | 总资源 |
|---|---|---|---|
| App 服务器 | 4 核 8G | 3 | 12 核 24G |
| PostgreSQL | 8 核 32G | 2(主从) | 16 核 64G |
| Redis | 4 核 16G | 3(哨兵) | 12 核 48G |
| MinIO | 8 核 32G | 4(分布式) | 32 核 128G |
3. 高可用设计
- 应用层:多实例部署,负载均衡
- 数据库:主从复制,自动故障转移
- 缓存:Redis 哨兵模式,自动选主
- 对象存储:MinIO 分布式模式,纠删码
4. 降级熔断
- 服务降级:非核心功能(推送通知)可降级
- 熔断策略:错误率超过 5% 触发熔断
- 限流保护:每用户每分钟 100 次请求
六、安全设计
1. 认证授权
A. 公钥认证
使用 Ed25519 签名验证,无需密码:
// 1. 客户端生成密钥对
const keyPair = nacl.sign.keyPair();
// 2. 创建签名挑战
const challenge = randomBytes(32);
const signature = nacl.sign.detached(challenge, keyPair.secretKey);
// 3. 服务器验证签名
const isValid = nacl.sign.detached.verify(
challenge,
signature,
publicKey
);B. JWT Token
认证成功后颁发 JWT Token:
// 持久令牌(用户认证)
async createToken(userId: string, extras?: any): Promise<string>
// 临时令牌(OAuth,5 分钟 TTL)
async createGithubToken(userId: string): Promise<string>2. 数据加密
A. 端到端加密
- 密钥派生:使用 HKDF 从主密钥派生会话密钥
- 加密算法:AES-256-GCM
- 密钥存储:客户端本地安全存储
// 派生会话密钥
async deriveSessionKey(sessionId: string): Promise<Uint8Array> {
const info = new TextEncoder().encode(`session:${sessionId}`);
return hkdf(this.keyPair.secretKey, info, 32);
}
// 加密数据
encryptSessionData(data: string, key: Uint8Array): string {
const nonce = randomBytes(12);
const cipher = aes_gcm_encrypt(data, key, nonce);
return base64Encode(nonce + cipher);
}B. 加密范围
- 会话消息:完全加密
- 工件文件:Header 和 Body 分别加密
- 用户设置:敏感字段加密
- 键值存储:根据需求加密
3. 防护措施
- 重放攻击防护:挑战-响应机制,随机 Nonce
- 中间人攻击防护:HTTPS + 证书固定
- SQL 注入防护:Prisma 参数化查询
- XSS 防护:React 自动转义,Content Security Policy
七、监控告警
1. 监控指标
通过 Prometheus 收集以下指标:
A. 应用指标
- HTTP 请求总数和耗时
- WebSocket 连接数和事件数
- 数据库活跃连接数
- Redis 命令总数和耗时
B. 业务指标
- 活跃会话数
- 消息吞吐量
- 认证成功率
- 推送通知送达率
2. 告警规则
| 指标 | 阈值 | 级别 | 处理建议 |
|---|---|---|---|
| 错误率 | > 5% | P2 | 检查日志,定位错误源 |
| 响应时间 P99 | > 1s | P3 | 检查数据库慢查询 |
| WebSocket 断线 | > 10% | P2 | 检查网络和负载均衡 |
| 数据库连接 | > 80% | P1 | 扩容连接池或数据库 |
3. 日志规范
- 日志级别:DEBUG、INFO、WARN、ERROR
- 结构化日志:JSON 格式,包含 request_id
- 敏感信息:自动脱敏,不记录密钥和 Token
八、技术亮点
1. 零知识架构
服务器只存储加密数据,即使服务器被攻破也无法获取用户代码内容。这是通过客户端派生密钥并仅在客户端加密解密实现的。
2. InvalidateSync 模式
class InvalidateSync {
private invalidated = true;
private promise: Promise<void> | null = null;
invalidate(): void {
this.invalidated = true;
}
async awaitQueue(): Promise<void> {
while (this.invalidated) {
this.invalidated = false;
this.promise = this.sync();
await this.promise;
}
}
async get(): Promise<T> {
await this.awaitQueue();
return this.cache;
}
}该模式确保:
- 同一资源不会同时发起多个同步请求
- 数据更新时自动重新同步
- 缓存失效后自动刷新
3. 多 AI 代理支持
通过统一的 AgentBackend 接口,支持多种 AI 编程代理:
- Claude Code:使用官方 SDK 直接集成
- Gemini:通过 Agent Client Protocol (ACP)
- Codex:通过 MCP stdio 协议
用户可通过命令轻松切换:
happy # 默认使用 Claude
happy gemini # 使用 Gemini
happy codex # 使用 Codex4. 跨平台样式系统
使用 Unistyles 实现真正的跨平台样式:
const styles = StyleSheet.create((theme, runtime) => ({
container: {
flex: 1,
backgroundColor: theme.colors.background,
paddingTop: runtime.insets.top, // 自动处理安全区域
},
responsive: {
padding: theme.margins.sm,
backgroundColor: {
[mq.only.width(0, 768)]: theme.colors.mobile,
[mq.only.width(768)]: theme.colors.desktop,
}
}
}));九、项目规模
1. 代码统计
| 项目 | 文件数 | 代码行数 | 主要语言 |
|---|---|---|---|
| happy | 407 | ~50,000 | TypeScript/TSX |
| happy-cli | 150+ | ~20,000 | TypeScript |
| happy-server | 100+ | ~15,000 | TypeScript |
2. 核心文件
| 文件 | 行数 | 说明 |
|---|---|---|
| sync/sync.ts | 2,143 | 主同步引擎 |
| sync/storage.ts | 53,952 | 本地状态管理 |
| sync/typesRaw.ts | 31,563 | 消息类型定义 |
| sync/settings.ts | 17,825 | 设置管理 |
十、未来规划
1. 短期计划
- 完善实时语音交互功能
- 支持更多 AI 代理后端
- 优化消息同步性能
- 增加 GitHub 集成
2. 长期愿景
- 支持团队协作功能
- 插件系统扩展能力
- 本地 LLM 支持
- 更多平台支持(Windows Desktop)