## 变更记录

版本 | 日期 | 变更
--- | --- | ---
v0.2 | 2026-06-11 | 引入 `design/` 官方设计系统后修订：① 计费模型由"订阅制 + 每日免费 10 分钟"改为时长包（不过期）+ 每日免费试用 3 分钟；② 第八章 mockup 已被设计系统高保真稿取代，正式以 `design/ui_kits/` 为准；③ 数据表 plans/subscriptions 废弃，改时长账本。详细设计见配套文档：前端实现设计 v1.0 · 后端与数据架构 v1.0
v0.3 | 2026-06-11 | 新增两项能力并更新配套文档至 v1.1：① 客户端打点（响应时间等指标采集与批量上报，监控首字延迟等体验目标）；② 反馈问题（设置内提交文字 + 图片 + 诊断信息）。详见 前端实现设计 v1.1 第八/九章 · 后端与数据架构 v1.1 3.5/3.6
v0.1 | 2026-06-11 | 初版 MVP 前后端设计方案

## 目录

## 一、产品概述与 MVP 需求

一款付费语音输入法，核心卖点：大模型识别准确度 + 流式实时上屏 + 极简交互。

MVP 需求 | 说明
--- | ---
全平台覆盖 | Mac / Windows / iPhone / iPad / Android
大模型识别 | 使用云厂商大模型 ASR，识别准确率显著优于传统方案
极简交互 | 桌面端：焦点在输入框时按住全局快捷键说话；移动端：键盘上按住按键说话
流式识别 | 说话过程中识别结果实时显示，不等说完
商业化 | 付费软件：微信登录 + 微信支付购买时长包
性能 | 识别快、无可感知卡顿（首字延迟 P95 < 500ms）

## 二、关键技术决策

决策点 | 选择 | 理由
--- | --- | ---
ASR 引擎 | 国内云厂商大模型流式 ASR 首选：阿里云百炼 `gummy-realtime-v1` 备选：火山引擎豆包流式 ASR | 原生 WebSocket 流式、国内延迟低、自带标点/ITN/中英混说、按量计费免运维。通过 Provider 抽象接口接入，可随时切换
后端语言 | Go | 本质是 WS 流式中继 + 业务 CRUD，瓶颈在云端 ASR 而非自身代码，Rust 性能优势体现不出来；微信支付有官方 Go SDK（wechatpay-go）；goroutine 天然适合"每连接一个转发循环"；开发速度快
桌面端 | Tauri 2 + Rust | 体积小（~10MB）、常驻内存低；Rust 侧做全局快捷键/音频采集/文字注入等系统层；一套代码出 Mac + Windows
移动端形态 | 系统第三方输入法（键盘扩展） | iOS Keyboard Extension / Android InputMethodService，按住说话直接上屏到任意 App，体验最佳。iOS 麦克风限制为最高风险项（见风险章节）
移动端技术 | Swift / Kotlin 各自原生 | 协议简单（WS + JSON + PCM），不值得引入跨端框架的复杂度；输入法扩展本身必须原生

## 三、总体架构

    ┌─────────────┐   ┌─────────────┐   ┌──────────────────┐
    │ 桌面端 Tauri │   │ iOS/Android │   │                  │
    │ Mac/Windows │   │  键盘扩展    │   │   微信开放平台    │
    └──────┬──────┘   └──────┬──────┘   │ (OAuth/支付回调)  │
           │  WSS 音频流      │          └────────┬─────────┘
           ▼                 ▼                   │
    ┌──────────────────────────────────────────────────────┐
    │                  Go 后端（单体起步）                    │
    │  ┌─────────┐ ┌───────────┐ ┌─────────┐ ┌──────────┐  │
    │  │  auth   │ │asr-gateway│ │ billing │ │  quota   │  │
    │  │ 微信登录 │ │ WS 流式中继│ │ 微信支付 │ │ 用量/配额 │  │
    │  └─────────┘ └─────┬─────┘ └─────────┘ └──────────┘  │
    └────────────────────┼─────────────────────────────────┘
                         │ WSS（同区域部署，内网级延迟）
                         ▼
              ┌─────────────────────┐     ┌──────────────┐
              │ 阿里云百炼 Gummy     │     │ PostgreSQL   │
              │ （流式大模型 ASR）   │     │ + Redis      │
              └─────────────────────┘     └──────────────┘

为什么音频走后端中继而不是客户端直连云 ASR：

1. ASR 密钥不能下发到客户端；
2. 计费基于识别秒数，必须在服务端计量，否则配额可被绕过；
3. Provider 可切换、可灰度，客户端协议永远不变。

中继增加的延迟约 10–50ms（后端与 ASR 同区域部署），对体验无感。

## 四、后端设计（Go）

### 4.1 技术栈与部署

- gin（HTTP API）+ gorilla/websocket（流式网关）
- PostgreSQL（用户/订单/时长账本）+ Redis（配额计数、登录轮询状态）
- `wechatpay-go` 官方 SDK
- 部署：阿里云（与百炼同区域）、Docker、单体应用起步

### 4.2 模块划分

    server/
      cmd/server/main.go
      internal/
        auth/        微信 OAuth（扫码 + App 登录）、JWT 签发与中间件
        asr/         Provider 接口 + gummy / volcano 两个实现
        gateway/     WS /v1/asr/stream 处理器（核心）
        billing/     微信支付下单、回调验签、订单状态机
        quota/       用量计量（Redis INCR + 异步落库）、试用/余额校验
        user/        用户、设备 CRUD、时长包查询
        store/       gorm models + migrations
      pkg/protocol/  客户端协议常量与消息结构（5 端对齐的唯一真相源）

### 4.3 ASR Provider 抽象（可切换的关键）

    type Provider interface {
        // 建立一次流式识别会话
        StartSession(ctx context.Context, cfg SessionConfig) (Session, error)
    }
    type Session interface {
        SendAudio(pcm []byte) error   // 推 100ms 音频帧
        Results() <-chan Result       // {Text, IsFinal, BeginTime, EndTime}
        Close() error
    }

MVP 实现 `gummy` provider（DashScope WebSocket API，原生流式、自带标点和 ITN、中英混说）。

### 4.4 客户端 ↔ 后端流式协议（WSS `/v1/asr/stream`）

方向 / 类型 | 内容
--- | ---
上行 · 二进制帧 | 16kHz / 16bit / mono PCM，每 100ms 一帧（3200 字节）
上行 · 文本帧 | `{"type":"start","sample_rate":16000}` / `{"type":"stop"}`
下行 · partial | `{"type":"partial","text":"今天天气"}` — 增量结果，客户端实时刷新浮层
下行 · final | `{"type":"final","text":"今天天气不错。"}` — 句子定稿，客户端执行注入
下行 · error | `{"type":"error","code":"QUOTA_EXCEEDED","message":"..."}`

鉴权：连接时带 `Authorization: Bearer <JWT>`。连接为长连接 + ping/pong 心跳，客户端启动时即预连接，按下快捷键时零握手开销。

### 4.5 微信登录

端 | 方式 | 微信平台应用类型
--- | --- | ---
桌面端 | 扫码登录：后端生成带 state 的二维码页 → 手机微信扫码授权 → 回调 → 客户端轮询 `GET /v1/auth/qr/:state` 拿 JWT | 网站应用
移动端 | 主 App 集成微信 OpenSDK 拉起授权 → code 换 token | 移动应用

用 unionid 关联同一用户的多端身份（桌面扫码与手机 App 登录是两个不同 openid）。

### 4.6 微信支付与计费

- 桌面端：Native 支付（后端下单返回 code_url → 客户端展示二维码）
- 移动端：APP 支付（主 App 拉起微信收银台）
- 回调 `POST /v1/pay/notify`：验签 → 订单状态机（pending → paid）→ 时长入账
- 计费模型（v0.2）：时长包不过期（100 分钟 ¥9 / 500 分钟 ¥39 / 2000 分钟 ¥129）+ 每天免费试用 3 分钟；用量按识别音频秒数计、先扣试用再扣余额，用尽时网关下发 `QUOTA_EXCEEDED`。详见后端与数据架构 v1.0 第五章

### 4.7 核心数据表

`users`、`wechat_identities`、`duration_packs`、`orders`、`balance_ledger`（时长账本）、`trial_usage`（每日试用）、`asr_sessions`、`devices`

## 五、存储选型说明

一句话总结：PostgreSQL 管"钱和账"（强一致、持久），Redis 管"快和临时"（计数器、带过期时间的状态）。

### PostgreSQL — 持久化业务数据（不能丢的数据）

表 | 用途
--- | ---
users / wechat_identities | 用户账号，微信 openid/unionid 与用户的关联
duration_packs / orders | 时长包定义（服务端可配）、微信支付订单——支付回调验签后改状态，钱相关数据必须强一致、可审计
balance_ledger / trial_usage | 时长账本（只追加，余额 = 合计）与每日免费试用用量（180 秒/天，自然日重置）
asr_sessions | 每次识别会话的用量明细（时长、试用/余额扣减拆分），用于对账与数据分析

特点：写入频率低、绝对不能丢，且需要事务（"回调到账 → 订单置为已支付 → 时长入账"必须原子完成）。

### Redis — 高频、短生命周期的热数据

1. 实时配额计数：识别过程中每隔几秒累计用量并校验是否超额。每帧都查 PostgreSQL 既扛不住也拖慢链路，故用 Redis `INCR` 实时计数，后台异步批量落库到 balance_ledger / asr_sessions；
2. 扫码登录轮询状态：桌面端展示二维码后轮询"扫了没"，state 只活 2 分钟，天然适合带 TTL 的 key；
3. JWT 黑名单 / 会话踢出：退出登录、账号踢出立即生效。

MVP 若想再简化可暂不引入 Redis（配额放进程内存 + 定时落库），但多实例部署时会算不准，且阿里云托管 Redis 运维成本接近零，故直接采用。

## 六、桌面端设计（Tauri 2 + Rust）

### 6.1 核心交互流程（push-to-talk）

    按下快捷键 → 浮层弹出（光标附近）→ 采集音频流式上传
    → partial 结果实时显示在浮层 → 松开快捷键
    → final 注入焦点输入框 → 浮层消失

### 6.2 Rust 侧模块

模块 | 方案
--- | ---
全局快捷键 | `tauri-plugin-global-shortcut`；默认按住 Cmd/Ctrl + Shift + Space 说话（press 开始 / release 结束）。macOS 监听 Fn 键需 CGEventTap，二期再做
音频采集 | `cpal` 采集 + 重采样到 16kHz mono PCM，100ms 分帧
WebSocket | `tokio-tungstenite`，启动即预连接、断线指数退避重连
文字注入 macOS | 写剪贴板 → CGEvent 模拟 Cmd+V → 恢复剪贴板（兼容性最好）；需引导用户授予辅助功能权限，首次启动做权限引导页
文字注入 Windows | `SendInput` + `KEYEVENTF_UNICODE` 直接注入 Unicode，无需剪贴板
浮层窗口 | Tauri 无边框 / 置顶 / 透明 / 不抢焦点（关键：`focusable: false`，否则焦点离开目标输入框）小窗，显示实时文本 + 音量波形

上屏策略（MVP）：partial 只显示在浮层不注入，final 句子级一次性注入。避免"增量注入 + 退格修正"与目标应用输入状态冲突的复杂性，二期可选讯飞式逐字上屏。

### 6.3 前端 UI（Web 技术）

- 设置窗口：快捷键自定义、麦克风选择、登录状态、购买时长（Native 支付二维码）
- 浮层窗口：识别文本 + 波形动画
- 常驻系统托盘，开机自启

## 七、移动端设计（系统输入法扩展）

### 7.1 iOS / iPadOS（同一套代码）

- 主 App（SwiftUI）：微信登录、购买时长（微信 APP 支付）、输入法启用引导；
- Keyboard Extension（UIInputViewController）：键盘上一个大麦克风按钮，按住说话 → 浮动文本区实时显示 partial → 松开后 final 经 `textDocumentProxy.insertText` 上屏；
- 需开启完全访问权限（联网必需）；token 通过 App Group 与主 App 共享。

⚠️ 最高风险项：iOS 键盘扩展内麦克风录音受系统限制（历史上 App Extension 不可用 AVAudioSession 录音；讯飞/搜狗有键盘内语音但实现路径需验证）。M0 第一周必须做技术 spike；若不可行，降级方案为键盘按钮经 deep link 跳主 App 录音 → App Group 回传 → 键盘上屏（体验打折但可发布）。

### 7.2 Android

- 主 App（Kotlin + Compose）：登录、支付、启用引导；
- IME Service（`InputMethodService`）：自绘键盘 + 按住说话，`RECORD_AUDIO` 权限即可在 IME 内录音（无 iOS 式限制），`currentInputConnection.commitText` 上屏，成熟可控。

实施顺序：先 Android 后 iOS（风险低、验证快）。两端不引入跨端框架，Swift / Kotlin 各自原生实现协议（对齐 `server/pkg/protocol`）。

## 八、各终端简要设计（界面与交互）

v0.2 注：本章 mockup 为 v0.1 规划草稿，已被 `design/` 官方设计系统的高保真稿取代（计费已改时长包、视觉禁用渐变/emoji）。正式界面以 `design/ui_kits/` 演示与前端实现设计 v1.0 为准，本章仅作历史记录保留。

### 8.1 桌面端（Mac / Windows）

界面清单：① 识别浮层② 系统托盘菜单③ 设置窗口④ 登录/购买窗口

#### ① 识别浮层（核心界面，两态）

按下快捷键瞬间弹出在光标附近，不抢焦点；松开快捷键注入文字后淡出。

🎙️ 聆听中…
松开 ⌘⇧Space 完成输入
态 A：按下快捷键瞬间（不等首个结果，先给反馈）
帮我把这份周报整理一下，重点突出本周的
松开 ⌘⇧Space 完成输入
态 B：流式识别中（深色为 final，浅色为 partial 实时刷新）

#### ② 系统托盘菜单 + ③ 设置窗口

🎙️ dudu 语音输入已就绪
启用快捷键✓
设置…
本月已用128 / 600 分钟
账号：wang***（已订阅）
退出
托盘菜单：状态一目了然，入口极简
dudu 设置
说话快捷键⌘ ⇧ Space（按住说话）
麦克风MacBook Pro 麦克风 ▾
开机自启✅
登录状态微信已登录 · wang***
当前套餐年度会员（2027-06-10 到期）续费
设置窗口：单页搞定全部配置

#### ④ 登录 / 购买窗口

- 登录：窗口内展示微信二维码（后端 `/v1/auth/qr`），手机扫码授权后自动登录并关闭；
- 购买：选套餐（月/年）→ 展示微信 Native 支付二维码 → 支付完成自动刷新订阅状态；
- 首次启动流程：欢迎页 → 授予权限（macOS 辅助功能 + 麦克风）→ 微信登录 → 试一试（引导用户在示例输入框里按快捷键说一句话）。

### 8.2 移动端键盘扩展（iOS / Android 布局一致）

界面清单：① 键盘面板（核心）② 候选/partial 文本条③ 未登录/超额提示态

MVP 键盘策略：以语音为主键盘——大麦克风按键 + 一行常用功能（切回系统键盘、退格、回车、标点），不做完整 26 键（降低开发量，用户在语音和系统键盘间一键切换）。

微信 · 与张三聊天中
说点什么…▏
dudu 语音输入已订阅 ✓
🌐 切换键盘
，
。
⌫ 退格
↵ 发送
🎙️ 按住说话
态 A：默认态（大按键 + 一行功能键）
微信 · 与张三聊天中
好的，我马上把文件发给你▏
dudu 语音输入00:04
好的，我马上把文件发给你，大概十分钟之
🟢 松开完成 · 上滑取消
态 B：按住说话中（partial 实时显示，final 即时上屏）

#### 键盘交互细节

- 按住说话，松开上屏；上滑取消本次输入（微信语音的肌肉记忆）；
- 流式上屏策略：final 句子即时 `insertText` 上屏，partial 显示在键盘自带的文本条里（不直接写入宿主输入框，避免修正抖动）；
- 未登录态：麦克风按键置灰，点击提示"打开 dudu App 登录"（deep link 跳主 App）；
- 超额态：按键变橙色提示"今日免费额度已用完，升级会员"。

### 8.3 移动端主 App（iOS / Android）

界面清单：① 启用引导页② 登录页③ 套餐购买页④ 我的（用量/订阅管理）

页面 | 内容与交互
--- | ---
① 启用引导页 | 三步卡片式引导：添加键盘 →（iOS）开启完全访问 → 试一试（内置输入框练习）。每步带系统设置深链与动图示意
② 登录页 | 单按钮"微信一键登录"（拉起微信 OpenSDK），登录后 token 写入 App Group 供键盘扩展使用
③ 套餐购买页 | 免费版 vs 会员对比表 + 月/年价格卡片，点击拉起微信 APP 支付，支付回调后即时解锁
④ 我的 | 本月用量进度条、订阅到期时间、麦克风/键盘权限自检（异常时红点提示并跳设置）

### 8.4 iPad 适配要点

- 与 iPhone 同一个 Keyboard Extension target，键盘布局按 size class 自适应（横屏时麦克风键加宽、功能键拉开间距）；
- 支持悬浮键盘（floating keyboard）形态下的紧凑布局；
- 主 App 用 SwiftUI 自适应布局，无需单独开发。

## 九、延迟优化清单（贯穿各端）

1. 客户端 WS 启动即预连接 + 心跳保活，按键时零握手；
2. 100ms 帧直推，客户端不做缓冲聚合；
3. 后端与 ASR 同区域部署，中继 goroutine 零拷贝转发；
4. 浮层/键盘 UI 在按键瞬间先进入"聆听中"态，不等首个结果；
5. 埋点指标：按键 → 首个 partial（目标 P95 < 500ms）、松开 → final 注入（目标 < 300ms）。

## 十、仓库结构（monorepo）

    dudu/
      server/      Go 后端
      desktop/     Tauri 2（Mac + Windows）
      android/     Kotlin：主 App + IME Service
      ios/         Swift：主 App + Keyboard Extension
      docs/        协议文档、架构决策记录

## 十一、里程碑

阶段 | 内容 | 周期
--- | --- | ---
M0 风险验证 | ① iOS 键盘扩展录音 spike；② Gummy 流式接入 demo；③ macOS 注入 + 不抢焦点浮层 demo；④ 立即启动微信开放平台（网站应用 + 移动应用）和商户号申请（周期数周，是关键路径；输入法上架需软著） | 1 周
M1 核心链路 | 后端 auth / gateway / quota + 桌面端完整链路（快捷键 → 采集 → 流式 → 浮层 → 注入） | 2–3 周
M2 商业化 | 微信登录（扫码 + App）、微信支付、时长包与配额 | 2 周
M3 移动端 | 先 Android IME，后 iOS 键盘扩展 | 3–4 周
M4 打磨 | 断网重连、错误兜底、延迟调优、内测分发 | 持续

## 十二、风险与验证

### 主要风险

风险 | 等级 | 缓解措施
--- | --- | ---
iOS 键盘扩展麦克风限制 | 高 | M0 第一周 spike 验证；降级方案：deep link 跳主 App 录音回传
微信开放平台 / 商户号申请周期长 | 中 | 第一天就启动申请（需企业资质、软著），与开发并行
macOS 文字注入在部分应用不兼容 | 中 | 剪贴板 + 粘贴方案兼容面最广；M0 在微信/浏览器/IDE 实测
ASR 厂商限流 / 故障 | 低 | Provider 抽象保证可热切到火山引擎备选

### 验证方式

- 后端：`go test ./...`；测试脚本推送 wav 文件模拟音频流，断言收到 partial/final 序列；微信支付用沙箱/模拟回调测试订单状态机；
- 桌面端：本地起 server 真实说话验证全链路；在备忘录/浏览器/IDE 等不同应用验证注入兼容性；记录 P95 首字延迟；
- 移动端：Android 真机在微信/备忘录中切换到本输入法验证上屏；iOS TestFlight 验证完全访问权限流程；
- 计费：构造超额场景验证 `QUOTA_EXCEEDED` 下发与客户端提示。