Skip to content

New Architecture interface design

hqy edited this page Aug 7, 2026 · 5 revisions

TimeFlow 时间管理 App 产品架构设计

版本:v3.10
日期:2026-08-07
适用范围:语音驱动的日程创建、提醒配置、本地提醒与云端备份恢复

0. 设计摘要

TimeFlow 是一款以语音为核心交互方式的时间管理 App。用户通过语音创建、查询、修改和删除时间日程、地点日程及其提醒配置;当信息缺失、存在多个匹配结果、涉及重复日程范围或需要二次确认时,系统通过语音反问和语音输出继续完成对话。日历视图只负责展示和状态反馈,不提供日程业务编辑表单。

系统将“云端确认写入”和“客户端运行执行”分离:语音操作经过多轮补全和用户确认后,由服务端业务层校验并写入云端数据库;当前客户端直接应用服务端返回的最终快照,首次安装、换设备或需要恢复时再通过 HTTP 全量同步本地数据。客户端本地数据库负责日常读取,时间和地点监听全部由客户端实现。断网时客户端可以查看本地数据并继续执行已经注册的提醒,但不能创建、修改或删除日程,也不能执行依赖服务端的 ASR、LLM 和 TTS。

0.1 第一性原则

原则 设计结论
用户真正要完成的是管理时间 日程、提醒、查询和处置是核心业务;日历只是展示载体
语音是主要交互方式 所有核心业务操作走语音,不维护重复的 GUI 表单流程
提醒必须不依赖云端实时在线 已同步日程由客户端本地监听和本地送达
云端服务集中处理高价值智能能力 ASR、LLM、TTS 在服务端统一调用和升级
业务写入必须有唯一权威 云端数据库负责确认后的日程与提醒写入,本地只应用云端确认快照
数据模型应服务于当前功能 每条日程最多一个提醒,提醒配置作为日程属性保存;不持久化登录会话,不提前引入画像、导航或复杂历史
复杂情况通过对话解决 缺失信息、目标歧义、重复范围和危险操作由语音追问确认

0.2 明确要做与不做

要做 不做
一次性、周期性和全天时间日程 独立的离线智能创建
地点日程和返回记录地点提醒 服务端持续监听所有日程
每条日程最多一个时间或地点提醒 独立的“离开地点时提醒”
低、中、高三级提醒强度 本期智能提醒规则引擎
语音创建、查询、修改、删除和确认 GUI 表单创建和编辑
账号、云端确认写入和单设备全量恢复 多设备实时同步、增量变更和过早引入天气、路线、通勤等上下文
本地时间与地点监听 本地 ASR、LLM、TTS

1. 产品功能模块划分

模块按完整用户能力划分。一个模块必须能够独立说明输入、处理和输出,不以某一个 DTO、WebSocket 消息或第三方 SDK 作为模块边界。第三方接口适配属于基础设施层,网关层只负责对外网络协议。

1.1 前端功能模块

模块 包含的小功能 输入 输出 边界
语音交互模块 麦克风采集、WebSocket 音频流、对话上下文、问题播放、TTS 播放、语音结果接收 用户语音、服务端语音消息 音频流、对话回答、业务结果 不执行 ASR、LLM、TTS 模型,不直接写云端数据库
本地日程模块 本地日程及其单一提醒配置存储、日历展示、快照应用、周期日程计算所需的本地数据读取 语音操作结果、同步结果 日程列表、日程详情、提醒配置、本地快照 不提供 GUI 编辑;每条日程最多保存一个提醒;不自行生成未被服务端确认的云端日程
本地监听与提醒执行模块 时间监听、地点距离计算、返回地点布防、延期、强度分级、弹窗、震动、屏幕展示和音频播放 本地日程、提醒配置、当前时间、定位数据、用户处置 本地提醒送达、延期结果、送达状态 不依赖服务端到点消息;提醒业务规则由客户端实现
账号与同步模块 账号创建或登录、访问凭证保存、首次安装或需要恢复时应用全量快照、提醒处置状态上传、离线只读状态 HTTP 账号响应、HTTP 全量快照响应、提醒状态响应、WebSocket 云端确认快照 本地数据恢复、处置状态同步、网络状态 不执行日程业务语义;不在离线时创建或修改日程

1.2 后端功能模块

模块 包含的小功能 输入 输出 边界
账号与同步服务 账号创建或登录、JWT 签发与校验、首次安装或需要恢复时的全量快照查询和提醒处置状态同步 HTTP 认证请求、提醒处置状态 访问令牌、云端完整快照、状态同步结果 不维护登录会话;不处理语音语义;不负责时间和地点监听
语音交互模块 接收和发送语音内容,处理音频流、ASR 转写和 TTS 音频输出 音频流、ASR/TTS 调用请求 转写文本、TTS 音频、语音传输错误 不负责多轮对话、意图理解、业务确认和日程持久化
对话管理模块 管理会话上下文,调用 LLM 完成意图理解、多轮追问、信息补全、指代消解和二次确认,输出结构化业务命令 ASR 转写文本、会话上下文、日程查询结果、用户确认语音 对话问题、已确认结构化业务命令、对话错误 不接收原始音频;不直接调用第三方语音接口;不直接持久化日程
日程数据管理模块 管理用户确认后的日程及其单一提醒配置,提供查询、创建、修改和删除能力 对话管理模块输出的查询请求和已确认结构化操作 包含提醒字段的日程数据、查询结果 不负责语音内容、多轮对话、本地监听和提醒送达

“语音交互模块”和“对话管理模块”是两个相邻的后端产品模块:前者处理语音内容,后者处理对话语义和会话流程。两者内部仍然保持技术职责分离:gateway/ 负责 WebSocket 会话、消息路由和协议转换;intelligence/ 负责 ASR/LLM/TTS 的智能能力编排、语音对话工作流、提示词和结构化结果处理;infrastructure/ 负责阿里云 ASR、OpenAI LLM、阿里云 TTS 等第三方接口适配。产品模块拆分不表示把协议处理和模型调用代码写在产品模块目录中。

1.3 前后端职责边界

能力 前端负责 后端负责
语音输入 录音、分片、上传 语音交互模块接收音频并完成 ASR
语义理解 保存对话 UI 状态、播放问题的音频 对话管理模块调用 LLM、补全字段、追问和消歧
日程变更 应用云端确认快照到本地数据库 校验业务命令并在云端事务写入
提醒监听 时间、地点、返回地点和延期逻辑 不参与到点判断
提醒送达 TTS 播放、屏幕、震动、强度策略 生成 TTS 音频并提供缓存/同步来源
数据可靠性 本地运行读取、离线提醒、首次安装或需要恢复时全量恢复 云端写入权威和账号隔离

2. 数据设计原则

2.1 数据归属

数据位置 作用 权威范围
客户端本地数据库 日常日程读取、日历展示、提醒监听和本地提醒状态 当前设备的运行读取权威,不接受独立业务写入
云端数据库 已确认日程与提醒写入、账号隔离和登录恢复 业务写入和备份数据的唯一权威
客户端安全存储 access token 和当前 account_id 不进入普通业务表
服务端内存 当前 WebSocket 会话和多轮对话短状态 仅限连接生命周期,不作为持久化数据

本地和云端不是两套独立业务数据。语音操作必须先由服务端业务层完成云端事务,再把持久化后的最终快照返回当前客户端;当前客户端直接应用该快照,不再通过 HTTP 重复拉取或上传。首次安装、换设备、账号切换或本地数据丢失时,客户端通过 HTTP 全量快照恢复本地数据;正常登录同一账号且本地数据完整时,直接使用本地数据。本期只支持单设备业务流程,不设计多设备实时同步、增量游标和客户端与云端的双向字段合并。

数据按语义分成三类:

数据类别 示例 写入与同步规则
账号级共享业务状态 日程标题、时间、地点、周期、删除状态、提醒类型和强度 必须先写云端,再返回客户端
设备级提醒运行状态 next_trigger_atsnoozed_untilgeofence_armed 只由当前设备维护,不上传云端
提醒最终处置状态 reminder_disposition_state=confirmed 本轮提醒最终确认后,通过 HTTP 一次性同步云端;延期和超时过程只保存在本地

本期离线不允许创建、修改或删除日程;提醒延期和超时属于本地监听过程,不产生等待上传的过程状态。只有本轮提醒最终确认后,客户端才上传最终处置状态。

2.2 时间与地点规则

类型 必填字段 可选字段 说明
time titlestart_timeis_all_day end_timerecurrence_rule、地点字段 一次性、周期性或全天时间日程
location title、有效地点坐标 地点名称 以到达地点为主要指向的日程

schedule_type 表示日程的触发维度;schedule_kind 表示日程是一次性还是周期性。schedule_kind=recurring 时必须使用 recurrence_ruleschedule_kind=once 时不使用周期规则。

所有时间保存为带时区的 UTC 时间;timezone 保留用户创建时的 IANA 时区,用于周期展开和语音表达。is_all_day=true 时,start_timeend_time 仅作为本地日期的起止边界保存:单日全天的 end_time 是下一本地日期的零点,多日全天的 end_time 是结束日期的下一日零点;客户端展示为全天,不将午夜表达为用户指定的时间。地点日程必须有经纬度,地点名称可以由地址解析结果补全。

2.3 默认创建规则

缺失信息 默认处理
日程类型 根据有效时间或地点字段推断;两者都不存在时必须追问
日程周期 未表达周期时按一次性处理
只有日期没有具体时分 设置 is_all_day=true,按本地日期边界保存,不追问具体开始时刻
时区 使用客户端当前 IANA 时区
时间提醒 非全天时间日程默认提前 15 分钟提醒;全天日程默认在开始日期前一天 09:00 提醒;用户明确不提醒时不创建
地点提醒 只有用户明确表达地点提醒或创建地点日程时才创建
提醒强度 使用中强度
修改未提及字段 保持原值,不使用默认值覆盖

3. 提醒模型与本地执行

3.1 提醒类型

类型 触发条件 必要数据
at_time 到达指定绝对时间 reminder_trigger_at
before_start 日程开始边界前指定分钟数 时间日程、reminder_offset_minutes
arrive_location 进入日程目标地点围栏 日程经纬度
return_to_recorded_location 创建时记录当前位置,离开范围后再次进入 记录点经纬度

不提供独立的离开地点提醒。返回记录地点提醒的客户端流程为:

创建提醒时保存当前位置
    ↓
当前位置在记录范围内:不触发
    ↓
检测到用户离开范围:geofence_armed = true
    ↓
再次进入记录范围:触发提醒并取消本轮监听

at_time 表示一个明确的绝对时间点,全天日程可以在用户明确指定提醒时刻时使用该类型。周期日程如果需要每次发生都提醒,应使用 before_start。全天日程的技术开始边界是开始日期本地 00:00,默认 reminder_offset_minutes=900,即在前一天本地 09:00 提醒;周期性全天日程对每次发生都按该规则计算。默认提醒时间已经过去时不补发,由语音交互询问用户是否重新指定提醒时间。本期不把一个绝对 reminder_trigger_at 自动复制到周期日程的所有发生实例。

3.2 强度与送达方式

提醒强度直接决定客户端的送达方式。下表中的“普通弹窗”是应用自定义的全局弹窗,不是系统通知。

强度 送达方式 适用语义
low 系统通知 非紧急提醒
medium 普通弹窗 + 短震动 默认提醒强度
high 普通弹窗 + 短震动 + TTS 用户明确要求高强度提醒

lowmedium 不依赖 TTS。high 的 TTS 音频不可用时,使用普通弹窗、短震动和本地提示音完成提醒。

3.3 延期与状态

本版先定义最小提醒状态,避免把提醒送达状态误当成日程完成状态:

状态 含义
pending 已配置,等待条件满足
confirmed 用户确认已处理本轮提醒,不表示日程业务完成
snoozed 用户选择延期,或提醒超时未操作,等待 snoozed_until

提醒尚未触发或本轮尚未处置时,reminder_disposition_state 为空;它不是提醒配置的启用状态。用户没有指定延期时间时,默认延期十分钟。延期和超时只由客户端更新本地 reminder_disposition_state=snoozedsnoozed_until 并重新注册本地监听,不向云端上传;本轮最终确认后才将 disposition_state=confirmed 发送到云端。confirmed 不等同于日程完成;“完成”动作暂不定义为待办事项完成,当前不纳入服务端和客户端的核心状态机。

4. 后端 HTTP 接口设计

HTTP 只承担账号认证、云端全量快照恢复和提醒处置状态同步,不提供日程业务 CRUD,也不接收客户端上传的日程变更。所有日程与提醒写入都由已认证 WebSocket 语音命令触发,并由后端业务层完成云端事务。

基础地址:https://<host>/v1

4.1 账号创建或登录

POST /auth/access

本期采用登录和注册一体化的简化流程,账号只使用用户自定义用户名和密码,不引入验证码、邮箱、手机号或独立的登录会话表。服务端根据 username 查询账号:账号不存在时创建账号并登录;账号已存在时校验密码,校验成功后登录。

请求:

{
  "username": "timeflow_user",
  "password": "strong-password"
}

处理规则:

  • 新账号:校验账号和密码格式,创建账号后签发 JWT。
  • 已有账号:校验密码后签发 JWT。
  • 已有账号但密码错误:返回认证错误,不创建新账号。
  • 数据库对 username 保持唯一约束,避免并发创建重复账号。

响应 200 OK

{
  "account_id": "acc_001",
  "access_token": "access-token",
  "expires_in": 3600
}

服务端签发包含 account_idexp 的无状态 JWT,不保存登录会话,也不提供 Refresh Token。客户端将 Access Token 保存到平台安全存储;Token 到期后重新登录。退出登录只删除客户端 Token,不调用服务端登出接口,本地日程不删除。本期不设计多设备同时编辑和数据冲突处理。

4.2 拉取云端全量快照

GET /schedule/snapshot 请求头:Authorization: Bearer <access_token>

客户端仅在首次安装、换设备、账号切换、本地数据丢失或用户主动恢复时调用该接口。正常登录同一账号且本地数据完整时不调用该接口,直接使用本地 SQLite 数据。客户端在本地安全存储中保留当前账号标识和本地数据初始化状态,用于判断是否需要恢复。服务端返回当前账号的全部日程;每条日程直接包含自己的单一提醒配置,reminder_type=null 表示没有提醒。

响应 200 OK

{
  "schedules": [
    {
      "id": "schedule_001",
      "schedule_type": "time",
      "schedule_kind": "once",
      "title": "项目评审",
      "is_all_day": false,
      "start_time": "2026-08-07T07:00:00Z",
      "end_time": null,
      "timezone": "Asia/Shanghai",
      "recurrence_rule": null,
      "location_name": "203会议室",
      "latitude": 31.2304,
      "longitude": 121.4737,
      "status": "active",
      "reminder_type": "before_start",
      "reminder_trigger_at": null,
      "reminder_offset_minutes": 15,
      "reminder_strength": "medium",
      "reminder_disposition_state": null,
      "occurrence_overrides": []
    }
  ]
}

客户端在一个本地事务中用该快照覆盖本地日程数据:快照中不存在的本地业务数据删除,快照中的日程及提醒字段插入或更新,事务提交后重新注册本地监听。当前发起语音操作的设备不需要在写入成功后调用该接口重复拉取,它直接应用 WebSocket 返回的云端持久化快照。

4.3 同步提醒处置状态

PUT /schedule/reminder-state

该接口只同步本轮提醒的最终处置结果,不创建新的日程,也不负责判断时间或地点条件。客户端在本地完成延期和超时处理后不调用该接口;只有用户最终确认本轮提醒后,才异步上传 confirmed 状态。

请求头:Authorization: Bearer <access_token>

最终确认请求:

{
  "schedule_id": "schedule_001",
  "disposition_state": "confirmed"
}

服务端校验账号和日程归属关系,将最终 disposition_state=confirmed 写入对应日程的 reminder_disposition_state 字段。接口采用“设置目标状态”语义:重复提交相同请求只返回当前状态,因此不需要 event_id。延期时间、超时次数和本地重新监听时间不进入该接口。

响应:

{
  "ok": true,
  "schedule_id": "schedule_001",
  "disposition_state": "confirmed",
  "updated_at": "2026-08-06T08:00:02Z"
}

客户端发生延期或超时时,只更新本地 reminder_disposition_state=snoozedsnoozed_until 和监听时间,不改变同步状态,也不调用该接口。用户最终确认后,客户端将 reminder_disposition_state=confirmed 写入本地并将 sync_status 标记为 pending;HTTP 请求成功后根据服务端响应保持 confirmed 并将 sync_status 改为 synced。服务端没有正常响应时,本地保持 pending 并在网络恢复后重试,但不阻塞本地监听。

5. 后端 WebSocket 接口设计

WebSocket 用于会话建立、流式音频、语音多轮交互、结构化业务结果和 TTS 音频。文本控制消息使用 JSON Text Frame,音频使用 Binary Frame。

连接地址:

wss://<host>/ws?device_id=<device_id>

连接后第一条消息完成访问令牌校验。device_id 只用于识别设备,真正的访问控制由 access_token 完成。

5.1 会话建立

客户端发送:session.hello

{
  "type": "session.hello",
  "request_id": "req_session_001",
  "payload": {
    "access_token": "access-token",
    "device_id": "device_001",
    "app_version": "2.0.0",
    "timezone": "Asia/Shanghai"
  }
}

服务端响应:session.ready

{
  "type": "session.ready",
  "request_id": "req_session_001",
  "ok": true,
  "payload": {
    "session_id": "ws_session_001",
    "server_time": "2026-08-06T03:00:00Z"
  }
}

未通过鉴权或协议版本不支持时返回 session.error 并关闭连接。服务端不通过 WebSocket 接收账号密码。

5.2 开始语音操作

客户端发送:voice.stream.start

{
  "type": "voice.stream.start",
  "request_id": "req_voice_001",
  "payload": {
    "conversation_id": null,
    "audio_format": "pcm_s16le",
    "sample_rate_hz": 16000,
    "channels": 1
  }
}

服务端响应:voice.stream.started

{
  "type": "voice.stream.started",
  "request_id": "req_voice_001",
  "ok": true,
  "payload": {
    "stream_id": "stream_001",
    "conversation_id": "conversation_001"
  }
}

客户端随后连续发送 Binary Frame。每个 Binary Frame 都是原始 PCM 音频分片,不额外包装 JSON;WebSocket 保证同一连接内消息顺序,服务端按接收顺序交给 ASR。

5.3 结束语音操作

客户端发送:voice.stream.end

{
  "type": "voice.stream.end",
  "request_id": "req_voice_001",
  "payload": {
    "stream_id": "stream_001"
  }
}

服务端先返回最终 ASR:voice.asr.completed

{
  "type": "voice.asr.completed",
  "request_id": "req_voice_001",
  "conversation_id": "conversation_001",
  "payload": {
    "transcript": "明天下午三点在203开会",
    "language": "zh",
    "duration_ms": 2100
  }
}

5.4 语音追问

当信息缺失、多个日程匹配、重复范围不明确或删除需要确认时,服务端发送:voice.dialogue.question

{
  "type": "voice.dialogue.question",
  "request_id": "req_voice_001",
  "conversation_id": "conversation_001",
  "payload": {
    "question_id": "question_001",
    "question_kind": "missing_field",
    "speech_text": "请问会议在哪里举行?",
    "required_response": "location",
    "candidates": []
  }
}

question_kind 取值:

使用场景
missing_field 创建或修改缺少必要字段
ambiguous_target 多个日程与用户表达匹配
recurrence_scope 周期日程修改或删除范围不明确
confirmation 删除、批量修改等需要二次确认

服务端同时通过 TTS 音频播放 speech_text。客户端不需要 GUI 表单补齐字段,用户直接再次发送 voice.stream.start,并携带上一次的 conversation_id

5.5 语音操作成功

LLM 结构化输出只是一条候选业务命令。服务端必须依次完成账号权限校验、业务规则校验、幂等检查和云端数据库事务;只有事务提交成功后,才能发送 voice.command.result,且返回内容必须来自持久化后的数据库快照。

服务端发送:voice.command.result

{
  "type": "voice.command.result",
  "message_id": "msg_001",
  "request_id": "req_voice_001",
  "conversation_id": "conversation_001",
  "payload": {
    "operation": "create_schedule",
    "status": "applied",
    "schedule": {
      "id": "schedule_001",
      "schedule_type": "time",
      "schedule_kind": "once",
      "title": "开会",
      "is_all_day": false,
      "start_time": "2026-08-07T07:00:00Z",
      "end_time": null,
      "timezone": "Asia/Shanghai",
      "recurrence_rule": null,
      "location_name": "203",
      "latitude": null,
      "longitude": null,
      "status": "active",
      "reminder_type": "before_start",
      "reminder_trigger_at": null,
      "reminder_offset_minutes": 15,
      "reminder_strength": "medium",
      "reminder_disposition_state": null,
      "revision": 13,
      "updated_at": "2026-08-06T03:01:00Z"
    },
    "occurrence_overrides": []
  }
}

客户端处理顺序:验证 status=applied 和实体 revision,在同一个本地事务中应用包含提醒字段的日程快照,提交成功后注册、更新或取消本地监听。事务成功后,客户端发送不承载业务数据的 message.ack

{
  "type": "message.ack",
  "message_id": "msg_001",
  "status": "applied"
}

该 ACK 只用于确认客户端已经应用会改变本地日程镜像的 voice.command.result,不表示用户确认日程,也不表示用户完成提醒处置。当前设备不再调用 HTTP 上传,也不需要重复拉取刚完成的写入;如果响应丢失或本地事务失败,客户端标记本地数据需要恢复,下一次满足恢复条件时通过 HTTP 全量快照重新恢复本地数据。ACK 本身不需要再次确认,音频 Binary Frame 也不逐帧发送 ACK。

5.6 语音查询成功

查询使用同一消息类型,operation=list_schedules

{
  "type": "voice.command.result",
  "message_id": "msg_002",
  "request_id": "req_voice_002",
  "conversation_id": "conversation_002",
  "payload": {
    "operation": "list_schedules",
    "status": "applied",
    "schedules": [
      {
        "id": "schedule_001",
        "title": "开会",
        "schedule_type": "time",
        "schedule_kind": "once",
        "is_all_day": false,
        "start_time": "2026-08-07T07:00:00Z",
        "location_name": "203",
        "status": "active",
        "reminder_type": "before_start",
        "reminder_trigger_at": null,
        "reminder_offset_minutes": 15,
        "reminder_strength": "medium",
        "reminder_disposition_state": null
      }
    ]
  }
}

服务端根据查询结果生成简短 TTS 汇总。客户端可以展示列表,但不要求用户通过点击完成下一步业务操作。

5.7 修改、删除和提醒操作

语音解析后使用以下 operation,不新增一套独立 HTTP CRUD:

operation 作用 关键处理
create_schedule 创建一次性或周期性日程 缺失字段追问;默认值只补缺失字段
update_schedule 修改日程字段 只修改用户明确提及的字段
delete_schedule 删除日程 必须确认目标;周期日程必须确认范围
create_reminder 创建提醒 写入日程的提醒字段;已有提醒时引导用户改为修改操作
list_reminders 查询提醒 返回目标日程的零个或一个提醒配置
update_reminder 修改时间、地点或强度 更新日程的提醒字段,保留未提及字段
delete_reminder 删除提醒 确认后清空日程的提醒字段
snooze_reminder 延期本轮提醒 未给时间时使用十分钟默认值;只在客户端更新本地延期状态,不写入云端

需要确认时先返回 status=needs_confirmationvoice.dialogue.question,用户的肯定或否定通过下一轮语音提交。确认之前不得修改本地或云端业务数据。

周期日程执行 delete_schedule 时,必须先通过语音确认 recurrence_scope

recurrence_scope 用户语义 数据处理 客户端处理
this_occurrence 仅删除本次 schedule_occurrence_overrides 新增 action=cancel,原周期日程保持不变 跳过当前实例并计算下一次触发时间
this_and_future 删除本次及以后 将原日程 RRULE 的 UNTIL 截止到当前实例之前最后一次保留的发生时间 保留历史实例,取消当前及后续监听
entire_series 删除整个系列 将原日程设为 status=deleted 并写入 deleted_at,日程内的提醒配置随之停止生效 删除本地日程并取消监听任务

如果 this_and_future 选中的实例是周期系列第一次发生,则按 entire_series 处理。三种删除都必须在云端写入成功后返回最终结果,再由客户端更新 SQLite 和本地监听状态。

5.8 TTS 音频输出

服务端在语音问题、命令结果摘要和本地提醒音频准备时调用 TTS。文本控制消息:voice.tts.start

{
  "type": "voice.tts.start",
  "conversation_id": "conversation_001",
  "audio_id": "audio_001",
  "payload": {
    "format": "wav",
    "sample_rate_hz": 24000,
    "purpose": "dialogue_question",
    "schedule_id": null,
    "audio_version": null
  }
}

purpose 可为 dialogue_questioncommand_resultreminder。当 purpose=reminder 时,服务端必须带上 schedule_idaudio_version,客户端以 schedule_id 将音频缓存到对应日程;缓存缺失或版本不匹配时,提醒执行模块使用本地兜底音频。随后发送音频 Binary Frame,结束时发送:

{
  "type": "voice.tts.end",
  "conversation_id": "conversation_001",
  "audio_id": "audio_001"
}

提醒 TTS 音频只用于 high 强度。本期文案只使用日程标题、开始时间和位置;预计通勤、路线、天气、关联日程、交互模式和停车位置均不进入当前 TTS 输入。TTS 生成失败时,客户端使用普通弹窗、短震动和本地提示音继续完成提醒。

5.9 语音错误

{
  "type": "voice.command.error",
  "request_id": "req_voice_003",
  "conversation_id": "conversation_003",
  "ok": false,
  "error": {
    "code": "MISSING_REQUIRED_INFORMATION",
    "message": "无法确定日程开始时间",
    "retryable": true
  }
}

错误码至少包括:

错误码 含义
AUDIO_INVALID 音频格式或音频流无效
SPEECH_RECOGNITION_FAILED ASR 失败
UNDERSTANDING_FAILED LLM 无法形成有效意图

6. 数据库设计

云端数据库使用 PostgreSQL,客户端数据库使用 SQLite。两端表结构保持可同步字段一致,客户端额外保存少量本地运行状态。云端使用 PostgreSQL 原生类型,客户端使用 SQLite 类型亲和性保存对应数据。

数据含义 PostgreSQL SQLite 说明
ID 和文本 VARCHAR(n) TEXT 统一使用带业务前缀的字符串 ID
带时区时间 TIMESTAMPTZ TEXT SQLite 保存 UTC 的 ISO-8601 文本
经纬度 NUMERIC(9,6) REAL 客户端用于距离计算
整数和版本 INTEGERBIGINT INTEGER SQLite 使用 INTEGER 亲和类型
布尔值 BOOLEAN INTEGER SQLite 使用 01

6.1 云端 PostgreSQL accounts

字段 类型 约束 用途
id varchar(64) PK 账号 ID
username varchar(255) UNIQUE, NOT NULL 用户自定义用户名
password_hash varchar(255) NOT NULL 密码哈希,不保存明文密码
created_at timestamptz NOT NULL 创建时间
updated_at timestamptz NOT NULL 账号更新时间

6.2 云端 PostgreSQL schedules

字段 类型 约束 用途
id varchar(64) PK 日程 ID,客户端和云端一致
account_id varchar(64) FK, NOT NULL 数据归属账号
schedule_type varchar(16) NOT NULL timelocation
schedule_kind varchar(16) NOT NULL, DEFAULT once oncerecurring;一次性或周期性
title varchar(255) NOT NULL 日程标题
is_all_day boolean NOT NULL 是否为全天日程;默认 false
start_time timestamptz NULL 时间日程开始时间
end_time timestamptz NULL 时间日程结束时间
timezone varchar(64) NOT NULL 周期展开和语音表达使用的 IANA 时区
recurrence_rule varchar(512) NULL RFC 5545 RRULE;周期性日程必填,一次性日程为空
location_name varchar(255) NULL 地点名称
latitude numeric(9,6) NULL 日程地点纬度
longitude numeric(9,6) NULL 日程地点经度
reminder_type varchar(32) NULL 单一提醒类型;NULL 表示未配置提醒
reminder_trigger_at timestamptz NULL at_time 的绝对提醒时间
reminder_offset_minutes integer NULL before_start 的开始前偏移量
reminder_strength varchar(16) NULL lowmediumhigh;有提醒时必填
reminder_disposition_state varchar(16) NULL 本轮最终处置状态;当前为 confirmed 或 NULL
status varchar(16) NOT NULL activedeleted
revision bigint NOT NULL 云端日程版本,用于识别快照数据的新旧
created_at timestamptz NOT NULL 创建时间
updated_at timestamptz NOT NULL 最后修改时间
deleted_at timestamptz NULL 软删除时间,供同步下发删除变更
约束:schedule_type=time 必须有 start_timeschedule_kind=recurring 必须为时间日程且必须有 recurrence_ruleschedule_kind=oncerecurrence_rule 必须为空;is_all_day=true 时必须为时间日程,必须有表示排他结束边界的 end_time,默认 before_start 提醒使用 reminder_offset_minutes=900schedule_type=location 必须有有效经纬度且 is_all_day=falsestatus=deleted 时保留整行一段同步窗口,不能立即物理删除。

提醒约束:每条日程最多一个提醒;reminder_type 为空时,所有 reminder_* 配置字段必须为空;有提醒时,reminder_strength 必填且必须为三级枚举。at_time 必须有 reminder_trigger_atbefore_start 必须有 reminder_offset_minutesarrive_locationreturn_to_recorded_location 必须复用当前日程的有效经纬度。提醒配置的增删改与日程更新使用同一事务,并递增日程 revisionreminder_disposition_state 只保存本轮最终确认结果;延期时间、超时次数和本地重新监听时间不写入云端。

6.3 云端 PostgreSQL schedule_occurrence_overrides

该表只记录周期日程中被单独修改或删除的发生实例,不保存正常展开的所有实例。周期日程正常触发、用户确认提醒或延期提醒时,都不会写入该表。

字段 类型 约束 用途
id varchar(64) PK 例外记录 ID
schedule_id varchar(64) FK, NOT NULL 原周期日程 ID
occurrence_start timestamptz NOT NULL 被处理实例原本的开始时间
action varchar(16) NOT NULL cancelreplace
replacement_schedule_id varchar(64) FK, NULL replace 时指向新建的一次性日程
created_at timestamptz NOT NULL 创建时间
updated_at timestamptz NOT NULL 更新时间

约束:同一 schedule_id + occurrence_start 只能存在一条例外;action=replace 时必须有 replacement_schedule_idaction=cancel 时必须为空。新增或修改例外时递增原周期日程的 revision,不为例外单独维护同步版本。

6.4 客户端 SQLite local_schedules

客户端镜像云端 schedules 的业务字段,并增加本地同步所需字段。

字段 类型 用途
id text PK 与云端 schedules.id 相同
account_id text 当前设备上的账号隔离
schedule_type text timelocation
schedule_kind text oncerecurring;默认 once
title text 日程标题
is_all_day integer 是否为全天日程,01
start_time text NULL UTC 时间
end_time text NULL UTC 时间
timezone text IANA 时区
recurrence_rule text NULL 周期规则
location_name text NULL 地点名称
latitude real NULL 纬度
longitude real NULL 经度
reminder_type text NULL 单一提醒类型;为空表示未配置提醒
reminder_trigger_at text NULL at_time 的绝对提醒时间
reminder_offset_minutes integer NULL before_start 的开始前偏移量
reminder_strength text NULL lowmediumhigh;有提醒时必填
reminder_disposition_state text NULL 本地提醒当前处置状态;最终确认时同步云端
next_trigger_at text NULL 本地计算的下一触发点
snoozed_until text NULL 本地延期到期时间,不上传云端
geofence_armed integer 返回地点是否已经完成离开布防
disposition_updated_at text NULL 最近一次本地处置时间
sync_status text pendingsynced;处置状态是否已获得服务端确认
status text activedeleted
cloud_revision integer 最近应用的云端版本
updated_at text 云端业务更新时间

客户端不生成服务端不存在的新日程。在线语音写入成功后,客户端只应用 WebSocket 返回的云端确认快照;满足恢复条件时应用 HTTP 全量快照。离线状态只读取 active 数据,不允许修改这张表中的业务字段。

reminder_* 配置字段来自云端确认快照;next_trigger_atsnoozed_untilgeofence_armed 属于当前设备的监听运行状态。reminder_disposition_state 在本地立即生效,延期或超时时保持本地 snoozed 状态;本轮最终确认后才通过 HTTP 异步同步云端。对于周期日程,进入下一次发生实例时客户端重置本地处置状态;云端最终状态只用于备份,不参与本地监听判断。sync_status 只有在最终状态获得服务端成功响应后才变为 synced,不建立单独事件表。reminder_type 为空时,日程不注册提醒监听。

6.5 客户端 SQLite local_schedule_occurrence_overrides

字段 类型 用途
id text PK 与云端例外记录相同
schedule_id text 原周期日程 ID
occurrence_start text 被处理实例原本的 UTC 开始时间
action text cancelreplace
replacement_schedule_id text NULL 替换后的一次性日程 ID

客户端展开周期日程时先计算 RRULE,再排除 cancelreplace 对应的原实例;replace 指向的一次性日程按普通日程展示和监听。

7. 典型使用流程

以下流程图使用“业务动作说明 + 伪代码函数”表示数据流转。client 表示客户端,gateway 表示服务端协议网关,intelligence 表示智能交互编排层,business 表示业务逻辑层中的日程数据管理服务。函数只用于说明模块之间的调用关系,不代表具体代码名称。

7.1 语音创建时间日程

用户场景:明天下午三点203开会

flowchart TD
    A["用户语音输入:明天下午三点203开会<br/>audio = client.record_voice()"] --> B["客户端流式上传音频<br/>stream_id = client.stream_audio(audio)"]
    B --> C["ASR 转写语音内容<br/>transcript = intelligence.transcribe_audio(stream_id)"]
    C --> D["LLM 解析日程草稿<br/>draft = intelligence.parse_schedule_command(transcript)"]
    D --> E{"信息是否完整?<br/>intelligence.has_missing_fields(draft)"}
    E -->|否| F["生成并播放追问<br/>question = intelligence.build_followup_question(draft)"]
    F --> G["用户语音补充<br/>reply = client.record_voice()"]
    G --> H["合并补充信息<br/>draft = intelligence.merge_followup_answer(draft, transcript)"]
    H --> I["用户语音确认<br/>command = intelligence.confirm_schedule_command(draft)"]
    E -->|是| I
    I --> J["云端事务写入日程和默认提醒字段<br/>snapshot = business.save_schedule(command)"]
    J --> K["返回持久化快照<br/>result = gateway.send_command_result(snapshot)"]
    K --> L["客户端写入本地 SQLite<br/>client.apply_schedule_snapshot(result.payload)"]
    L --> M["注册本地监听并确认已应用<br/>client.register_local_listener(result.payload)<br/>client.send_message_ack(result.message_id, 'applied')"]
Loading

7.2 修改周期日程

用户场景:把每周一项目例会改到十点

flowchart TD
    A["用户语音输入:把每周一项目例会改到十点<br/>audio = client.record_voice()"] --> B["ASR 转写<br/>transcript = intelligence.transcribe_audio(client.stream_audio(audio))"]
    B --> C["查询云端候选日程<br/>matches = business.query_schedules(transcript)"]
    C --> D["匹配目标日程和修改范围<br/>candidate = intelligence.match_schedule(transcript, matches)"]
    D --> E{"目标或范围是否明确?<br/>intelligence.need_disambiguation(candidate)"}
    E -->|否| F["语音追问修改范围<br/>question = intelligence.ask_schedule_scope(candidate)"]
    F --> G["用户语音回答并完成消歧<br/>candidate = intelligence.resolve_schedule_scope(candidate, transcript)"]
    E -->|是| H["用户语音确认修改"]
    G --> H
    H --> I["生成已确认修改命令<br/>command = intelligence.confirm_update_command(candidate, transcript)"]
    I --> J["云端事务更新日程<br/>snapshot = business.update_schedule(command)"]
    J --> K["返回新的持久化快照<br/>result = gateway.send_command_result(snapshot)"]
    K --> L["更新本地数据并重算监听<br/>client.apply_schedule_snapshot(result.payload)<br/>client.rebuild_local_listener(result.payload)<br/>client.send_message_ack(result.message_id, 'applied')"]
Loading

7.3 返回停车地点提醒

用户场景:用户在停车场停车后说:提醒我回来取车

flowchart TD
    A["用户在停车场说:提醒我回来取车<br/>audio = client.record_voice()"] --> B["记录停车位置并上传语音<br/>recorded_location = client.current_location()<br/>transcript = intelligence.transcribe_audio(client.stream_audio(audio))"]
    B --> C["生成带返回地点提醒字段的日程<br/>draft = intelligence.parse_location_reminder(transcript, recorded_location)<br/>reminder_type = return_to_recorded_location"]
    C --> D["用户语音确认提醒<br/>command = intelligence.confirm_reminder_command(draft)"]
    D --> E["云端保存日程及提醒字段<br/>snapshot = business.save_schedule(command)"]
    E --> F["返回持久化快照并应用本地数据<br/>result = gateway.send_command_result(snapshot)<br/>client.apply_schedule_snapshot(result.payload)<br/>client.send_message_ack(result.message_id, 'applied')"]
    F --> G["注册返回地点围栏监听<br/>schedule_id = result.payload.schedule.id<br/>client.arm_geofence(schedule_id)"]
    G --> H{"是否已离开记录地点?<br/>client.detect_leave_zone(schedule_id)"}
    H -->|否| I["继续等待定位变化<br/>client.wait_for_location_update()"]
    I --> H
    H -->|是| J["开启返回检测<br/>client.update_geofence_state(schedule_id, true)"]
    J --> K{"是否再次进入记录地点?<br/>client.detect_reenter_zone(schedule_id)"}
    K -->|否| L["继续等待返回位置<br/>client.wait_for_location_update()"]
    L --> K
    K -->|是| M["触发回来取车提醒<br/>client.deliver_reminder(schedule_id)"]
Loading

8. 一致性、离线与安全约束

  1. 账号登录后,客户端使用带过期时间的 JWT 访问 HTTP 和 WebSocket;device_id 只用于标识 WebSocket 客户端,不对应服务端登录会话。
  2. 日程和提醒业务变更只有在云端事务提交成功并返回 status=applied 后才算成功。
  3. 本地监听使用本地数据库快照;云端同步不会在提醒触发时阻塞本地提醒。
  4. 客户端在本地事务中完整应用 WebSocket 快照或登录后的 HTTP 全量快照;快照应用成功后才重新注册本地监听。
  5. 用户确认期间目标日程发生变化时,服务端返回 STALE_CONFIRMATION 并重新进行语音确认,不自动套用旧命令。
  6. 断网时只读本地日程和提醒配置,不允许语音变更,不调用 ASR、LLM 和 TTS。
  7. 设备完成提醒送达后记录 delivered,不自动改变日程状态。
  8. 用户延期或超时时,客户端只在本地更新 disposition_statesnoozed_until 和下一触发时间;本轮最终确认后,再通过 HTTP 异步同步 disposition_state=confirmed
  9. TTS 音频和语音转写不作为云端日程主数据保存;是否缓存音频由客户端缓存策略决定。
  10. WebSocket 只承载已认证会话;访问令牌不写入普通日志。
  11. 地点提醒只保存必要的目标坐标,不保存连续位置历史。

9. 实施优先级

P0:语音日程闭环

  1. 账号创建或登录和无状态 JWT 鉴权。
  2. WebSocket 音频流、ASR、LLM 创建日程。
  3. 缺失字段追问和语音确认。
  4. 云端日程事务写入和版本生成,提醒字段随日程保存。
  5. 本地日程数据库及其提醒字段,以及云端快照应用。
  6. 一次性时间提醒、到达地点提醒和三档强度送达。
  7. HTTP 登录后的全量快照恢复。

P1:完整日程操作

  1. 语音查询、修改、删除。
  2. 周期日程和修改/删除范围确认。
  3. 多目标匹配、指代消解。
  4. 单一提醒配置及其修改。
  5. 返回记录地点提醒和延期。

P2:体验完善

  1. TTS 个性化缓存和失效更新。
  2. 旧确认失效后的语音恢复流程。
  3. 全天日程的多日范围、跨时区和更复杂提醒策略评估与补充。
  4. 评估通勤、路线、天气等上下文是否真正产生产品价值;未形成明确触发场景前不接入。

10. 验收标准

目标 验收标准
语音优先 创建、查询、修改、删除和提醒配置均可通过语音完成,不依赖 GUI 表单
对话完整 缺失信息、多个匹配、周期范围和删除确认均能通过语音追问完成
本地提醒 断网或 WebSocket 断开时,已同步日程仍能按时间或地点触发
云端确认写入 LLM 输出不能直接落库;业务校验和云端事务成功后才向客户端返回 applied
数据同步 当前设备可应用 WebSocket 云端快照,登录或本地数据丢失后可通过 HTTP 全量快照恢复
提醒边界 每条日程最多一个提醒,地点返回提醒不会变成独立离开提醒
强度可控 低、中、高三档送达方式可区分,TTS 失败不阻塞高强度提醒
离线边界 离线只读和提醒,不支持语音操作、日程修改或 TTS
数据最小化 不保存位置历史、对话历史、TTS 历史和智能规则决策历史

附录 A:后端五层目录与工程协作约束

后端目录以依赖方向为约束:入口负责组装各层;网关只依赖业务用例和智能能力端口;业务与智能层依赖抽象端口;数据层实现数据库端口;基础设施层实现第三方接口和运行时端口。业务层和智能层不得反向依赖 WebSocket、HTTP、SQLAlchemy 或第三方 SDK。

backend/src/timeflow/
├── business/
│   ├── accounts/
│   ├── calendar/
│   └── sync/
├── data/
│   ├── models/
│   ├── repositories/
│   └── migrations/
├── gateway/
│   ├── http/
│   └── websocket/
├── infrastructure/
│   ├── config/
│   ├── database/
│   ├── security/
│   └── external/
│       ├── asr/
│       ├── llm/
│       └── tts/
├── intelligence/
│   ├── conversation/
│   ├── command_parser/
│   └── speech_synthesis/
└── main.py

A.1 business/ 业务逻辑层

**职责:承载可测试、可复用的产品业务规则、业务对象和业务服务编排。包括账号服务、日程数据管理服务和同步服务;只处理账号、日程、提醒配置及其状态的数据语义,不处理语音对话流程和客户端提醒执行。

允许包含:

  • 业务服务,例如创建日程、查询日程、修改周期范围、执行已确认删除、管理日程内提醒配置、同步提醒处置和查询全量快照。
  • 领域实体和值对象,例如 ScheduleScheduleAggregateRecurrenceRule 和提醒强度值对象。
  • 业务命令、结果 DTO 和端口接口,例如 ScheduleRepository
  • 必填字段、周期日程范围、单一提醒类型组合等业务校验。
  • 在确有产品业务需要时调用非 AI 类第三方能力;调用应限制在必要 API,并通过端口隔离供应商差异。

禁止包含:

  • FastAPI 路由、WebSocket 连接对象和 JSON 序列化代码。
  • SQLAlchemy Model、数据库 Session、文件系统和环境变量读取。
  • 阿里云 ASR、OpenAI LLM、阿里云 TTS 等 AI 相关 SDK 调用;这些能力由智能交互编排层通过端口使用,并由基础设施层适配具体供应商。
  • 具体的 ASR、LLM、TTS Prompt 和音频协议。
  • 时间轮询、地理定位采集和客户端提醒执行。

A.2 data/ 数据层

**职责:实现 PostgreSQL 云端数据库的持久化模型、Repository 和数据库迁移。

允许包含:

  • SQLAlchemy ORM Model 和 PostgreSQL 字段映射。
  • AccountRepositoryScheduleRepository 的实现。
  • 全量快照查询、版本和软删除记录读取。
  • Alembic 迁移脚本。

禁止包含:

  • 业务规则判断,例如“缺失地点就追问”。
  • WebSocket/HTTP 请求模型。
  • LLM 输出解析、Prompt 拼装和第三方网络请求。
  • 客户端 SQLite 数据库、客户端定位和客户端提醒状态。

A.3 gateway/ 网关层

**职责:将 HTTP 和 WebSocket 等外部网络协议适配为系统内部可调用的请求和消息。网关是网络输入输出边界,不拥有业务决策,也不直接适配第三方模型服务。

允许包含:

  • HTTP 路由、认证依赖、请求/响应模型和错误映射。
  • WebSocket 连接生命周期、文本帧与 Binary Frame 接收发送。
  • 网络协议字段到内部命令和结果的转换。

禁止包含:

  • 日程创建和提醒配置业务规则。
  • 直接拼接 SQL 或直接修改 ORM Model。
  • 在 Handler 中实现多轮对话、重复日程范围或提醒数量判断。
  • 时间监听、地点监听和本地提醒送达。

A.4 infrastructure/ 基础设施层

**职责:提供运行时通用能力和应用装配所需的技术组件,不表达产品业务。

允许包含:

  • 配置加载、环境变量、日志、异常追踪。
  • 数据库 Engine、Session 工厂和事务管理。
  • 密码哈希、令牌签发与校验、随机 ID 和时间时钟。
  • HTTP/WebSocket 客户端连接池、重试和超时的通用封装。
  • 阿里云 ASR、OpenAI LLM、阿里云 TTS 等第三方接口适配器。
  • 任务执行器、资源生命周期和健康检查。

禁止包含:

  • 创建日程、删除确认和提醒强度等业务规则。
  • ASR/LLM/TTS Prompt 和模型选择决策。
  • 任何具体的 WebSocket 消息处理流程。
  • 直接向客户端发送产品消息。

infrastructure/external/ 下的第三方适配器只实现 intelligence/ 定义的调用端口,负责供应商协议、鉴权、超时、重试和外部错误转换,不包含日程业务规则,也不直接处理客户端 WebSocket 消息。

A.5 intelligence/ 智能交互编排层

**职责:负责语音对话全流程的智能工作流编排:接收网关转换后的语音会话事件,协调 ASR 转写、对话上下文维护、LLM 意图理解、缺失信息追问、用户确认、业务命令执行以及结果摘要和 TTS 输出。该层负责决定当前对话下一步调用哪个能力和返回哪类结果,但不直接持久化业务数据。

允许包含:

  • 语音会话工作流和对话状态转换,例如开始接收、转写完成、追问、确认和命令完成。
  • ASR 流式转写编排、文本标准化和最终转写结果处理。
  • 对话上下文维护,包括当前问题、候选日程、用户确认和多轮对话状态。
  • LLM 日历意图解析:创建、查询、修改、删除日程和提醒。
  • 缺失字段识别、追问生成、指代消解和多结果消歧。
  • 调用日程数据管理模块查询上下文或执行已确认的结构化业务命令。
  • TTS 文案生成和 TTS 调用端口编排,并把文本或音频结果交给网关发送。
  • 受约束的结构化输出 Schema、对话结果和错误结果。

禁止包含:

  • 直接保存日程和提醒到数据库,必须通过日程数据管理模块执行。
  • 直接调用阿里云 ASR、OpenAI LLM 或阿里云 TTS SDK,必须通过基础设施端口调用。
  • 处理 WebSocket 连接生命周期、JSON/Binary Frame 编解码和具体网络协议。
  • 直接执行账号鉴权和同步事务。
  • 决定本地监听何时触发或替用户完成提醒处置。

A.6 main.py 应用组装入口

main.py 只负责创建应用和依赖注入:读取配置、创建 PostgreSQL 数据库会话、初始化 Repository、第三方客户端、业务用例和 HTTP/WebSocket 路由。它不实现业务逻辑,不解析 LLM 结果,不执行 SQL。

附录 B:前端目录与功能边界

前端是 Android 客户端应用,按产品功能组织代码。features/ 是主要功能边界;每个功能内部再按 domain/application/data/presentation/ 分层。前端负责本地数据读取、语音交互、本地时间与地点监听及提醒送达,不负责 ASR、LLM、TTS 的模型实现,也不直接操作云端数据库。

frontend/
├── src/
│   ├── app/
│   │   ├── AppRoot.tsx
│   │   ├── AppProviders.tsx
│   │   ├── composition/
│   │   │   └── createAppServices.ts
│   │   └── orchestration/
│   │       └── AppRuntime.ts
│   ├── contracts/
│   │   ├── auth.ts
│   │   ├── schedule.ts
│   │   ├── conversation.ts
│   │   ├── reminder.ts
│   │   ├── sync.ts
│   │   └── transport.ts
│   ├── features/
│   │   ├── auth/
│   │   │   ├── domain/
│   │   │   ├── application/
│   │   │   │   └── interfaces/
│   │   │   ├── data/
│   │   │   └── presentation/
│   │   ├── schedule/
│   │   │   ├── domain/
│   │   │   ├── application/
│   │   │   │   └── interfaces/
│   │   │   ├── data/
│   │   │   │   └── local/
│   │   │   └── presentation/
│   │   ├── assistant/
│   │   │   ├── domain/
│   │   │   ├── application/
│   │   │   │   └── interfaces/
│   │   │   ├── data/
│   │   │   │   └── websocket/
│   │   │   └── presentation/
│   │   ├── reminder/
│   │   │   ├── domain/
│   │   │   ├── application/
│   │   │   │   └── interfaces/
│   │   │   ├── data/
│   │   │   │   └── local/
│   │   │   └── presentation/
│   │   └── sync/
│   │       ├── domain/
│   │       ├── application/
│   │       │   └── interfaces/
│   │       └── data/
│   │           ├── http/
│   │           ├── websocket/
│   │           └── local/
│   ├── infrastructure/
│   │   ├── network/
│   │   ├── database/
│   │   ├── audio/
│   │   ├── location/
│   │   ├── notifications/
│   │   └── secure-storage/
│   ├── shared/
│   │   ├── ui/
│   │   ├── errors/
│   │   └── time/
│   └── types/
├── modules/
├── plugins/
├── tests/
│   ├── architecture/
│   ├── unit/
│   └── integration/
└── package.json

B.1 app/ 应用组装与运行时

**职责:负责客户端启动、依赖注入、全局 Provider 和跨功能生命周期协调。

**允许包含:应用根组件、服务实例创建、网络恢复后的同步触发和全局运行时管理。

**禁止包含:具体日程规则、提醒触发规则、语音语义解析和平台 API 的具体实现。

B.2 contracts/ 网络契约

**职责:定义 HTTP、WebSocket Text Frame 和 Binary Frame 对应的线上数据结构,包括认证、日程、会话、提醒、同步和传输消息。

**允许包含:请求与响应类型、消息类型、枚举和协议字段定义。

**禁止包含:日程业务模型、本地 SQLite 表模型、提醒计算规则和网络请求实现。网络契约中的提醒字段与日程快照保持一致,不代表客户端单独维护云端提醒表。

B.3 features/ 产品功能

features/ 按客户端可识别的完整产品能力组织,而不是按“所有页面”“所有 Repository”或“所有 API”集中组织。功能内部的依赖方向为:presentation -> application -> domaindata 实现 application/interfaces 定义的接口;presentation 不直接访问网络、SQLite 或平台 SDK。

features/auth/

负责登录、账号状态和访问令牌生命周期。domain/ 保存认证状态和纯规则,application/ 组织登录、退出和会话恢复,data/ 实现认证请求及安全存储适配,presentation/ 展示登录状态。该功能不处理日程、提醒和语音业务。

features/schedule/

负责本地日程副本的读取、日历展示和日程领域表示。提醒配置作为日程字段随快照保存,不在该功能下建立独立提醒表。domain/ 保存日程实体、值对象和纯规则,application/ 提供日程查询和日历刷新能力,data/local/ 读取 SQLite 日程副本,presentation/ 提供月历、列表和详情只读展示。该功能不提供 GUI 创建、修改、删除入口,也不处理语音理解。

features/assistant/

负责语音入口、会话交互和语音操作结果展示。domain/ 保存客户端会话状态,application/ 组织录音、消息发送和会话事件处理,data/websocket/ 将会话事件映射到 WebSocket 消息,presentation/ 提供麦克风、对话和确认信息展示。该功能不自行实现 ASR、LLM、TTS,不直接落库,也不解释结构化命令的业务含义。

features/reminder/

负责设备本地提醒的完整生命周期。domain/ 处理时间和地点条件、提醒强度、提醒实例、确认和延期规则;application/ 负责注册、重建、触发、送达、确认和延期;data/local/ 负责本地运行状态持久化及平台调度映射;presentation/ 负责自定义提醒弹窗以及确认、延期操作。该功能读取 local_schedules 中的提醒字段,不建立 local_reminders 表;不负责修改云端日程配置,不依赖服务端到点消息,也不负责 ASR、LLM、TTS。

features/sync/

负责客户端本地数据与云端快照的一致性。domain/ 保存同步状态、版本和本地重试规则,application/ 组织快照应用、ACK、恢复、重试和对账,data/http/ 映射全量快照和提醒最终处置接口,data/websocket/ 映射云端确认快照及 message.ackdata/local/ 实现 SQLite 事务和现有同步状态字段更新。该功能不判断提醒条件、不负责提醒送达、不理解语音命令。

B.4 infrastructure/ 客户端基础设施

**职责:提供不包含产品业务语义的 Android 和通用技术能力。

子目录 职责 边界
network/ HTTP/WS 底层连接、超时和网络状态 不决定业务重试策略,不解释 DTO 含义
database/ SQLite 连接、事务、迁移和通用驱动 不实现日程查询、同步决策和提醒规则
audio/ 录音、播放和音频设备封装 不生成 ASR/TTS 内容,不决定语音对话流程
location/ 权限、定位采样和系统围栏能力封装 不判断地点提醒业务条件
notifications/ 系统通知、自定义弹窗、震动、闹钟和本地提示音封装 不决定提醒强度和触发规则
secure-storage/ Token 等敏感信息安全存储 不处理账号业务规则

B.5 shared/types/ 和工程目录

  • shared/ui/ 只提供无业务语义的基础视觉组件;shared/errors/ 提供通用错误类型和展示映射;shared/time/ 提供时间格式化和时区工具。三者不承载日程或提醒业务规则。
  • types/ 只保存环境和第三方类型声明,不保存业务模型或线上协议结构。
  • modules/ 只保存确需自建的 Expo/Android 原生模块;普通 TypeScript 业务代码放在 src/
  • plugins/ 只保存 Expo 构建期配置插件,不实现运行时业务逻辑。
  • tests/architecture/ 验证功能与分层依赖边界,tests/unit/ 验证领域规则和应用操作,tests/integration/ 验证 SQLite、网络和平台实现的协作;后端测试仍归后端工程自身管理。

B.6 前端依赖边界

  1. app/ 可以组装所有功能,但不替代功能内部的应用层。
  2. contracts/ 可以被 features/*/data 和网关适配代码使用,但不能反向依赖功能领域模型。
  3. features/*/domain 只能依赖稳定的纯 TypeScript 类型和工具,不依赖 React、SQLite、HTTP、WebSocket 或 Android API。
  4. features/*/application 只能通过 interfaces/ 使用网络、数据库、音频、定位和通知能力;具体实现由 data/infrastructure/ 注入。
  5. features/*/data 负责数据映射和外部适配,不把业务判断下沉到 HTTP、WebSocket 或 SQLite 实现中。
  6. infrastructure/ 不得引用具体 feature 的业务对象;功能业务规则只能位于对应 feature 的 domain/application/
  7. 日程确认后的快照由 sync 应用到 schedulereminder 所需的本地数据;提醒触发后由 reminder 在本地完成送达和处置,最终确认状态再通过 sync 上传云端。

Clone this wiki locally