Files
home-automation/docs/current-system-inventory.md
T

24 KiB
Raw Blame History

当前系统盘点

本文档用于盘点当前 branch 上的 Go 实现,并将其作为后续 Python 重构的唯一事实基线。

范围与基线

系统概览

当前应用是一个单进程 Go HTTP 服务,具备以下特征:

  • 暴露少量 REST API
  • 使用本地 SQLite 做持久化
  • 调用 Home Assistant API 和 webhook
  • 通过 OAuth 和 REST API 集成 TickTick
  • 当前仍依赖 Notion 做 poo 记录同步
  • 内置一个每日执行的定时同步任务

进程启动后会先读取 YAML 配置文件,再初始化 Notion 与 TickTick 工具层、初始化各业务组件自己的 SQLite 数据库、注册路由、启动调度器,最后在配置的端口上提供 HTTP 服务。可参考 src/cmd/serve.gosrc/cmd/serve.go

API 盘点

GET /status

  • 路由定义:src/cmd/serve.go
  • 用途:基础存活检查
  • 请求参数:无
  • 请求体:无
  • 响应:纯文本 OK
  • 鉴权:当前代码中无鉴权
  • 调用方类型:通用健康检查,可能用于本地监控或 supervisor 级别的探活

GET /poo/latest

  • 路由定义:src/cmd/serve.go
  • 处理函数:pooRecorder.HandleNotifyLatestPoo
  • 用途:将最新一条 poo 状态重新发布到 Home Assistant 的 sensor state
  • 请求参数:无
  • 请求体:无
  • 响应:
    • 成功:空响应体,默认 HTTP 200
    • 失败:通过 http.Error(...) 返回文本错误信息
  • 鉴权:当前代码中无鉴权
  • 外部调用方:
    • 会被 POST /homeassistant/publish 间接触发,当 target=poo_recorderaction=get_latest 时,代码会通过内部 HTTP 请求访问 http://localhost:{port}/poo/latest,见 src/components/homeassistant/homeassistant.go
  • 副作用:

POST /poo/record

  • 路由定义:src/cmd/serve.go
  • 处理函数:pooRecorder.HandleRecordPoo
  • 用途:记录一条 poo 事件,同时镜像到 Notion、刷新 Home Assistant sensor,并可选触发一个 Home Assistant webhook
  • 请求体 JSON
    • status: string
    • latitude: string
    • longitude: string
  • 请求校验:
    • JSON decoder 开启了 DisallowUnknownFields
    • 如果配置里缺少 pooRecorder.tableId,请求会直接失败,虽然从纯本地 DB 角度看本来仍有可能写入成功
  • 响应:
    • 成功:空响应体,默认 HTTP 200
    • 请求错误:返回 decoder 错误文本,HTTP 400
    • 服务端错误:返回错误文本,HTTP 500
  • 鉴权:当前代码中无鉴权
  • 外部调用方:大概率是 Home Assistant、移动端 shortcut、或手工调用;代码中没有明确写死调用方
  • 副作用:
    • 向 SQLite poo_records 插入一条记录
    • 异步向 Notion 追加一行
    • 同步发布最新 Home Assistant sensor 状态
    • 如果存在 pooRecorder.webhookId,异步触发一个 Home Assistant webhook

POST /homeassistant/publish

  • 路由定义:src/cmd/serve.go
  • 处理函数:HomeAssistant.HandleHaMessage
  • 用途:接收自动化消息,根据 targetaction 做分发
  • 请求体 JSON
    • target: string
    • action: string
    • content: string
  • 请求校验:
    • JSON decoder 开启了 DisallowUnknownFields
  • 响应:
    • 成功路径:空响应体,默认 HTTP 200
    • 失败路径:通常为空响应体并返回 HTTP 500TickTick auth 回调是单独的接口,不在这里
  • 鉴权:当前代码中无鉴权
  • 外部调用方:设计意图上是给 Home Assistant automation 消息调用

当前代码支持的消息契约如下:

  • target=poo_recorder, action=get_latest

  • target=location_recorder, action=record

    • content 预期是一个 JSON 风格字符串,实际很可能使用单引号
    • 当前代码会用 strings.ReplaceAll(message.Content, "'", "\"") 做归一化
    • 然后转发到本地 POST /location/record
    • src/components/homeassistant/homeassistant.go
  • target=ticktick, action=create_action_task

    • content 预期可解析为:
      • action: string
      • due_hour: int
    • 当前代码会忽略调用方传来的 title 字段,而是把 action 映射为 TickTick task title
    • 到期时间的计算方式是:取 now + due_hour 后所在日期的“次日零点”,再转成 TickTick 使用的时间格式
    • 最终在配置指定的 TickTick project 中创建任务
    • src/components/homeassistant/homeassistant.go

不支持的 targetaction 会返回 HTTP 500,并打 warning 日志。相关测试在 src/components/homeassistant/homeassistant_test.go

POST /location/record

  • 路由定义:src/cmd/serve.go
  • 处理函数:locationRecorder.HandleRecordLocation
  • 用途:记录人的位置点,用于人生轨迹 / movement history
  • 请求体 JSON
    • person: string
    • latitude: string
    • longitude: string
    • altitude: string,从请求结构上看是可选,但代码里即使为空也会被解析成 0
  • 请求校验:
    • JSON decoder 开启了 DisallowUnknownFields
    • 数值解析错误会被忽略;如果 latitude / longitude / altitude 不是合法数字,当前实现会静默落成 0
  • 响应:
    • 成功:空响应体,默认 HTTP 200
    • 请求错误:返回 decoder 错误文本,HTTP 400
  • 鉴权:当前代码中无鉴权
  • 外部调用方:
    • 可被任意客户端直接调用
    • 也会被 POST /homeassistant/publish 间接触发
  • 副作用:
    • 向 SQLite location 表插入一条记录

GET /ticktick/auth/code

  • 路由定义:src/cmd/serve.go
  • 处理函数:TicktickUtilImpl.HandleAuthCode
  • 用途:TickTick OAuth redirect callback
  • Query 参数:
    • state
    • code
  • 响应:
    • 成功:纯文本 Authorization successful
    • 失败:纯文本错误信息,HTTP 400 或 500
  • 鉴权:
    • 通过 OAuth state 与进程内保存的 authState 做校验
    • 没有额外的 session 或用户级鉴权
  • 外部调用方:TickTick OAuth redirect
  • 副作用:
    • 用 authorization code 换取 access token
    • 通过 viper.WriteConfig()ticktick.token 写回 YAML 配置文件

外部集成盘点

TickTick

  • 在 Python 重构中的状态:应保留
  • 主要文件:
  • 当前职责:
    • 初始化 TickTick 鉴权状态
    • 当 token 缺失时启动 OAuth 授权
    • 接收 OAuth callback 并持久化 token
    • 读取 project 下的 tasks
    • 若不存在同名任务,则创建新任务
  • 连接方式:
    • OAuth authorization code flow
    • 调用 https://ticktick.com/oauth/token
    • 调用 https://api.ticktick.com/open/v1/...
  • 依赖的配置项:
    • ticktick.clientId
    • ticktick.clientSecret
    • ticktick.redirectUri
    • ticktick.token
  • 关键实现依赖:
    • 原生 net/http
    • viper,同时承担配置读取和配置回写
  • 迁移高风险点:
    • OAuth callback 的 state 只保存在进程内;如果服务在授权开始和回调完成之间重启,流程会断
    • token 直接写回 YAML 配置文件,虽然简单,但运维上比较脆弱
    • 去重逻辑只按 task title 精确匹配
    • due date 的计算语义是隐含在代码里的,重构前应先冻结
    • Init() 会在启动时积极检查配置,且在 token 缺失时打印手动授权 URL;Python 版需要明确是否仍要在启动阶段卡住这一流程

Home Assistant

  • 在 Python 重构中的状态:应保留
  • 主要文件:
  • 当前职责:
    • 接收来自 Home Assistant automations 的命令 envelope
    • 将命令转发给本地模块
    • 把 sensor state 发布回 Home Assistant
    • 在 poo 记录后触发 Home Assistant webhook
  • 连接方式:
    • 入站 webhook 风格 JSON 接口:POST /homeassistant/publish
    • 出站 REST 调用:Home Assistant /api/states/{entity_id}
    • 出站 webhook 调用:/api/webhook/{webhook_id}
    • 出站调用使用 bearer token
  • 依赖的配置项:
    • homeassistant.ip
    • homeassistant.port
    • homeassistant.authToken
    • homeassistant.actionTaskProjectId
    • pooRecorder.webhookId
    • pooRecorder.sensorEntityName
    • pooRecorder.sensorFriendlyName
  • 关键实现依赖:
    • 原生 net/http
    • 通过 localhost:{port} 发起自调用,而不是直接走函数调用
  • 迁移高风险点:
    • 入站 /homeassistant/publish 当前没有鉴权
    • 当前命令 envelope 里的 content 是字符串,且常带单引号,现有客户端可能依赖这种非标准格式
    • 模块间当前是通过自调用 HTTP 和 1 秒 timeout 编排的
    • sensor 发布和 webhook 触发都属于强副作用行为,需要在兼容性测试里单独覆盖

Notion

  • 在 Python 重构中的状态:当前存在,但按已知目标不计划默认保留
  • 主要文件:
  • 当前职责:
    • 使用 config token 初始化 Notion client
    • 读取 / 写入 poo 记录对应的表格行
    • 每日做 SQLite 和 Notion 的双向同步
    • 提供一个反转 Notion 表顺序的辅助 CLI
  • 连接方式:
    • 通过 github.com/jomei/notionapi 调用 Notion API
    • token 鉴权
  • 依赖的配置项:
    • notion.token
    • pooRecorder.tableId
  • 关键实现依赖:
    • github.com/jomei/notionapi
  • 迁移高风险点:
    • 当前服务启动时如果缺少 notion.token 会直接退出,即便 Notion 并不是系统所有功能都需要的基础能力
    • POST /poo/record 当前要求 pooRecorder.tableId 存在,并会异步镜像到 Notion
    • 每日定时同步会同时改写 Notion 和 SQLite,若 Python 版移除这一行为,数据一致性预期会发生变化

数据库与 Schema 盘点

数据库类型

Poo recorder 数据库

  • 配置项:pooRecorder.dbPath
  • 默认路径:pooRecorder.db
  • migration 机制:
    • 手写 PRAGMA user_version
    • 当前有效版本可以认为是 1
    • 目前只实现了 0 -> 1
  • 表:
  • 字段:
    • timestamp TEXT PRIMARY KEY
    • status TEXT NOT NULL
    • latitude REAL NOT NULL
    • longitude REAL NOT NULL
  • 核心用途:
    • 用于查询最新 poo 状态并发布到 Home Assistant sensor
    • 作为本地 poo 历史的持久化来源
    • 作为 Notion 双向同步的本地数据源和数据汇
  • 明显核心字段:
    • timestamp
    • status
    • latitude
    • longitude
  • 可能属于历史包袱 / 后续需要再判断的点:
    • 当前实现与 Notion 表行结构高度耦合
    • 时间戳是字符串,格式为 2006-01-02T15:04Z07:00,不是带秒的完整 RFC3339
    • API 请求模型接受的经纬度是字符串,因此 Python 版的类型规范化要小心兼容

Location recorder 数据库

  • 配置项:locationRecorder.dbPath
  • 默认路径:location_recorder.db
  • migration 机制:
    • 手写 PRAGMA user_version
    • 当前版本 2
    • 已实现 migration
      • 0 -> 1:建表
      • 1 -> 2:把旧 datetime 字符串改写成 RFC3339 UTC
  • 表:
  • 字段:
    • person TEXT NOT NULL
    • datetime TEXT NOT NULL
    • latitude REAL NOT NULL
    • longitude REAL NOT NULL
    • altitude REAL
    • 主键 (person, datetime)
  • 核心用途:
    • 持久化人生轨迹 / 位置点记录
  • 明显核心字段:
    • person
    • datetime
    • latitude
    • longitude
  • 可能属于历史包袱 / 后续需要再判断的点:
    • altitude 在语义上是可选,但当前入站解析会把缺失和非法值一起压成 0
    • 当前没有查询 API,这张表目前主要承担“只写不读”的存储角色

跨模块数据库观察

  • 当前没有统一的共享 schema;每个组件各自打开自己的 SQLite 文件
  • 没有使用 ORM
  • 没有统一 migration 框架
  • 除主键外,没有看到额外索引
  • poo 的常规写入和异步 Notion 镜像之间没有事务保证,一致性更接近 best-effort

业务模块拆分

1. HTTP 外壳 / 应用启动层

  • 职责:
    • 读取配置
    • 设置日志级别
    • 管理 scheduler 生命周期
    • 注册路由
    • 处理优雅退出
  • 主要文件:src/cmd/serve.go
  • 依赖:
    • 所有业务模块
  • 迁移判断:
    • 这部分后续应成为 FastAPI 的 app 装配层
    • 可以在早期先迁为“薄壳”

2. Poo recorder

  • 职责:
    • 接收 poo 记录
    • 持久化本地 poo 历史
    • 向 Home Assistant 发布最新状态 sensor
    • 触发可选 Home Assistant webhook
    • 与 Notion 做同步
  • 主要文件:src/components/pooRecorder/pooRecorder.go
  • 依赖:
    • SQLite
    • Home Assistant util
    • Notion util
    • scheduler
  • 迁移判断:
    • 这是功能上重要、但耦合也最重的模块
    • 适合在设计上先拆成:
      • poo API / service
      • Home Assistant 发布适配层
      • legacy Notion sync adapter

3. Location recorder

  • 职责:
    • 接收位置更新
    • 持久化人生轨迹点
  • 主要文件:src/components/locationRecorder/locationRecorder.go
  • 依赖:
    • SQLite
  • 迁移判断:
    • 相对独立
    • 很适合作为优先迁移对象
    • 但需要先明确数值校验规则,因为当前实现会把非法数字静默压成 0

4. Home Assistant 命令路由层

  • 职责:
    • 接收命令 envelope
    • 根据 target/action 调度 poo、location、ticktick 行为
  • 主要文件:src/components/homeassistant/homeassistant.go
  • 依赖:
    • 本地服务端口
    • TickTick util
  • 迁移判断:
    • 它是外部 automations 的关键契约
    • 应在迁移早中期就被冻结和复刻

5. TickTick adapter

  • 职责:
    • OAuth callback
    • project / task REST 操作
    • 任务去重
  • 主要文件:src/util/ticktickutil/ticktickutil.go
  • 依赖:
    • TickTick API
    • 可写配置文件
  • 迁移判断:
    • 作为内部 adapter 相对独立
    • 复杂度中等,主要难点是 OAuth 和 token 持久化

6. Home Assistant 出站 client

7. Notion adapter 与辅助 CLI

8. 辅助 helper CLI

  • src/helper/poo_recorder_helper
    • 用于 Notion 表反转的运维辅助工具
  • src/helper/location_recorder
    • 当前基本还是脚手架,没有实质业务逻辑
  • 迁移判断:
    • 二者都不是后端重构的核心目标
    • poo_recorder_helper 应随着 Notion 一并视作 legacy

运行方式与部署形态

配置方式

  • 配置文件名:config.yaml
  • 搜索路径:
    • 当前工作目录
    • $HOME/.config/home-automation
  • 配置加载代码:src/cmd/serve.go
  • 开启了 viper.WatchConfig()

代码中实际出现的配置项包括:

  • port
  • logLevel
  • notion.token
  • ticktick.clientId
  • ticktick.clientSecret
  • ticktick.redirectUri
  • ticktick.token
  • homeassistant.ip
  • homeassistant.port
  • homeassistant.authToken
  • homeassistant.actionTaskProjectId
  • pooRecorder.tableId
  • pooRecorder.webhookId
  • pooRecorder.sensorEntityName
  • pooRecorder.sensorFriendlyName
  • pooRecorder.dbPath
  • locationRecorder.dbPath

进程模型

  • 单个二进制,通过 Cobra 子命令 serve 启动
  • 监听 SIGINT / SIGTERM 做优雅退出
  • scheduler 与 HTTP server 在同一进程内运行
  • 路由层使用 gorilla/mux

定时任务

被动接收式接口

  • 上面列出的入站 REST API
  • TickTick OAuth callback endpoint

运行时依赖的外部服务

  • Home Assistant HTTP API 与 webhook
  • TickTick OAuth 与 REST API
  • 当前 poo 流程仍依赖 Notion API
  • 本地可写文件系统,用于配置文件和 SQLite DB

当前本地 / 服务部署形态

容器化情况

  • 这一轮代码扫描中没有发现 Dockerfile 或 compose 文件
  • 当前部署形态是 supervisor-based,而不是 container-based

测试与文档现状

测试现状

  • 只发现一个测试文件:src/components/homeassistant/homeassistant_test.go
  • 当前测试覆盖:
    • 入站命令 JSON 解码
    • target/action 路由分发
    • 转发到 poo/location handler 的行为
    • TickTick task 创建委托
    • 错误日志和失败路径
  • 当前没有覆盖:
    • poo recorder 的 DB 行为
    • location recorder 的 DB 行为
    • TickTick OAuth 流程
    • Home Assistant 出站发布
    • Notion sync 逻辑
    • 启动与配置加载
    • scheduler 行为

本轮测试执行情况

  • 我尝试在 legacy/go-backend/src/ 下执行 go test ./...
  • 但当前会话环境里没有安装 go 命令,因此无法实际运行测试
  • 所以这轮关于测试的判断,基于静态阅读,而不是实际执行结果

文档现状

  • 仓库里的 README.md 基本只有标题和 badge,内容非常少
  • 没有用户可读的 API 文档
  • 没有 schema 文档
  • 没有 Home Assistant / TickTick 的契约说明文档
  • 没有关于配置项、OAuth 初始化、数据库文件位置的运维文档

后续最需要补齐的文档

  • Home Assistant 命令 envelope 与支持的 action
  • 各 API 的 request / response 契约
  • TickTick OAuth 初始化与 token 持久化方式
  • 数据库归属、用途与保留策略
  • Notion 下线 / 不迁移说明

对 Python 重构特别重要的事实

  • 当前 API 行为整体比较“轻响应、重副作用”,很多成功请求返回的都是空响应体
  • 当前所有入站 API 都没有看到鉴权
  • 当前系统本地真相来源是 SQLite,但 poo 数据还同时与 Notion 同步
  • notion.token 现在不是可选项,缺失时服务会在 initUtil() 阶段直接退出
  • Home Assistant 命令路由当前是通过本地 HTTP 自调用实现的,而不是直接服务层调用
  • TickTick callback 会改写应用本身使用的 YAML 配置文件