12 KiB
赵府智家系统架构
前端(v0.1.35)
Vue 3 单文件组件负责应用外壳、家庭卡片、设备目录和网络图,Vite 打包,Bootstrap 5 提供布局与控件样式。frontend/src/composables/bridge.js 接收已鉴权的状态快照;web/vue-bridge.js 封装现有业务动作。视频、家谱、记事等旧业务控制器暂按依赖顺序加载,只管理各自的兼容容器,不能覆盖 Vue 管理的节点。
生产请求 / 返回预构建的 web/ui/index.html,仅允许读取哈希命名的 JS/CSS;源码与构建产物一起提交,生产主机不运行构建工具。网络状态接口只检查当前管理员授权范围内的已登记设备,30 秒缓存,区别节点在线、端口可达与子网路由可见。
部署与职责
系统运行于自己的 Ubuntu 小电脑,提供局域网和 Tailscale 网页入口。第一阶段是视觉模块。管理程序、SQLite 和 MediaMTX 在同一主机运行,使用独立服务用户;网页只访问管理程序,设备凭据与媒体管理接口留在服务端。
摄像头与录像机继续在其原有网络工作。实时画面可直接从摄像头取流,也可由录像机提供;读取哪个位置的历史录像必须显式标明。录像机原有录像回放需要设备型号对应的检索、回放适配器。
对象关系
管理归属(家庭 / 办公场所 / 工厂,独立权限)
└─ 自定义区域树(parentId,不限制层数,禁止循环及跨归属父节点)
└─ 空间档案(名称、区域类别及可选地址)
├─ 地区:省、市、区县、街道
├─ 地址:小区或园区、楼栋、单元、楼层、门牌
├─ 所属:家庭或单位
├─ 摄像头对象(稳定 ID、品牌、型号、序列号、安装位置)
│ ├─ 镜头通道 1 → 主码流 / 子码流
│ └─ 镜头通道 2 → 主码流 / 子码流
└─ 录像机对象(地址、接入驱动、账号、空间引用)
└─ 其他设备档案(灯、手机、冰箱或自定类型,暂不含控制驱动)
每个镜头通道分别关联:
实时来源:直接摄像头 / 录像机
录像来源:本机 / 录像机(可独立于实时来源)
接入层
当前 RTSP 负责实时视频,ONVIF 负责读取设备身份、媒体配置、独立视频源及主子码流地址。不同品牌使用相同对象结构,路径和能力由设备返回值或对应驱动确定;不按品牌名字保证所有型号兼容。当前品牌驱动只提供路径模板,自动摄像头配置读取支持 ONVIF Media v1。
主码流用于清晰预览和可选本机录像。界面只显示主码流与子码流两个选项,默认主码流。子码流预览由每路子码流经 FFmpeg 转为 H.264 / AAC;MediaMTX 的 runOnDemand 仅在观看时启动受限分辨率的转码进程,发布权限限定为回环地址的兼容预览路径。高清兼容预览读取原始主码流,经 Intel VAAPI 完成解码、1080p 缩放和 H.264 编码,最高 15 帧/秒;无可用硬件时明确失败,不自动启动多路软件高清转换。原始编码直出不作为网页预览选项,原始主码流继续用于录像及转码输入。摄像机参数与云台按设备开放的 ONVIF 能力设置,不自动导入不存在的通道,不把服务可达当成画面接通。
管理 API
所有数据接口(除会话状态与健康检查)需要账号 Cookie 会话,并依据家庭、角色和空间核验访问权限。写接口接受 JSON,检查同源请求。
| 接口 | 用途 |
|---|---|
| GET /api/session | 初始化、登录状态 |
| POST /api/setup、/api/login、/api/logout | 管理员会话 |
| GET /api/state | 对象、镜头实际码流状态、存储状态 |
| POST /api/sites | 保存结构化空间档案 |
| POST /api/assets | 保存实体摄像头对象 |
| POST /api/recorders | 保存录像机与连接凭据 |
| POST /api/cameras | 保存镜头通道(早期接口名保留) |
| POST /api/onvif | 只读设备身份、视频源与码流配置 |
| POST /api/discover | 有界局域网 RTSP 探测 |
| POST /api/storage | 本机录像容量与保留策略 |
| GET /api/recordings | 本机录像时段 |
| GET /media/live/... | 登录鉴权后的 HLS 代理 |
| GET /media/playback | 本机录像回放、下载代理 |
当前交付与后续
本阶段实现独立运行、空间建模、三类视觉对象、实时视频接入和本机可选录像。摄像头真实接入结果由部署记录和主机运行状态说明。
下一阶段可加入具体录像机的历史回放适配器;随后扩展其他设备类型和联动规则。PTZ 已支持方向控制、停止和预置位;语音对讲、AI 检测目前没有实现,不作为已具备的能力展示。
配置写入 SQLite 并生成运行时媒体配置,录像数据独立存储。当前为单机模式;升级前备份整个数据目录。凭据和录像不包含在公开源码中。
身份与家谱
identity.py 管理账号、scrypt 密码散列、旧账户迁移、会话撤销和空间授权。genealogy.py 管理独立人物、亲子/配偶边和并发修订号。账号通过 personId 可选关联人物;祖先无需账号。households.py 管理家庭归属、负责人、交接记录与请求范围。每个家庭有独立家谱与修订号,按账号另授予 read/edit 权限。管理员与家庭成员的角色不由家谱辈分决定。
| API | 权限 |
|---|---|
| GET/POST /api/users | 管理员 |
| GET /api/family | 家谱查看或编辑 |
| POST /api/family/person | 家谱编辑 |
| POST /api/family/link | 家谱编辑 |
| GET /api/state | 按空间过滤,个人账号不返回设备连接凭据与配置 |
| GET /media/live/*、/media/playback、/api/recordings | 登录并具备对应空间权限 |
人物与关系修订在同一数据库事务中保存;修改关系时迭代检查亲子图,拒绝循环。前端 kinship.js 通过最短关系路径提供阅读称呼,未知长幼与复杂旁系明确显示不确定性或关系链。家谱页面不启动摄像头播放;返回实时画面时重新建立所需流。
家庭边界与管理连续性
账号 → 一个管理归属(家庭 / 办公场所 / 工厂)→ 多级区域 → 设备对象 / 镜头。人物、亲属关系、记事和历史版本也属于同一管理归属。兼容已有接口,数据库仍使用 households、familyId 命名。
X-Household-Id仅供超级管理员选择工作家庭;其余账号只能使用自己的家庭。所有写操作均核验所属家庭与关联对象,不能通过 body 迁移归属。- API 状态与账号列表按家庭过滤。媒体 URL 不依赖前端过滤,每个清单、分片与下载请求均核验摄像头归属和空间授权;超级管理员是明确的特权例外。
- 平台级扫描、存储策略、审计只允许超级管理员。重复接入已属于其他家庭的已知设备 IP 会被拒绝。
/api/households供超级管理员创建、家庭管理员修改本家庭名称和备注。/api/households/leadership仅允许当前家主或超级管理员办理家主及备用负责人安排。- 家主、备用负责人必须是本家庭已启用的家庭管理员且不能相同。备用负责人预先持有管理权限,意外发生不影响设备管理;身份交接需要有权限的账号明确操作、填写原因、检查修订号。
- 任内负责人不能被直接停用、降级;先交接,再调整账号。交接后原家主保留家庭管理员,必要时由新家主或超级管理员处理其账号。
- 家谱人物的去世日期、账号长期未登录、亲属称谓均不触发权限变化。
- 首次迁移只回填
familyId,不修改凭据或设备路径;旧历史快照也加归属。旧版本没有此隔离能力,多家庭运行后禁止只回退代码。
设备分类与区域授权
inventory.py 保存通用设备档案以及所有人、负责人、标签。视频设备继续使用原 assets、recorders、cameras 结构,不迁移凭据、不修改录像配置。前端 device-model.js 将三类档案统一为只读目录,按每个归属保存的维度顺序分组。修改分组只改变展示,不移动设备或放宽权限。
区域以 parentId 邻接表表示,创建/移动时在写锁内检查父节点归属、有效性与祖先循环。父链和后代集合均迭代计算,避免递归栈形成层数限制。用户的 includeSubspaces 默认关闭;开启后按当前区域树实时扩展 siteIds。修改该授权开关会撤销已有会话。人员归属是文本档案,不参与权限计算。
新增接口:POST /api/devices 保存通用设备(要求当前归属管理员);POST /api/device-view 保存当前归属默认分组顺序。统一状态的 devices 数组也经过归属和成员区域过滤。
场所、设备上下文与传承(v0.1.24)
steward.js 管理入口和当前设备上下文。根入口是归属列表;进入归属后才展示内部导航。deviceCameras 按摄像头 assetId 或录像机关联字段 recorderId / archiveRecorderId 选择已有授权通道,不推断不存在的镜头。通用设备没有视频页签。前端上下文缩小显示范围,后端的家庭与区域授权始终独立执行。
heritage.py 通过 GET /api/heritage 和 POST /api/heritage 维护 heritage_entries,kind 为 culture 或 asset,只允许 home 类归属。写入在全局写锁内检查归属、编辑权限、不可变类别和每条档案的 revision。关联设备需要同归属,普通编辑者还须具备该设备区域权限。文字统一转义;外链限定无嵌入凭据的 HTTP/HTTPS,浏览器用 noopener/noreferrer 打开,不由服务器抓取。
heritageAccess 独立于 familyAccess,普通账号默认 none,read 只能读,edit 可创建、编辑及归档;管理员限定于其管理归属,超级管理员可明确切换归属。权限改变撤销现有会话。新表无默认真实资料;旧账户未补写该字段也按 none 处理。
前端 heritage.js 用独立请求代次防止晚返回的数据覆盖其他页面;退出、切换家庭清空资料和表单。更新使用修订号防并发,归档可恢复;目前保留当前版本及最近修改人、时间,不保存该档案的逐次历史快照。所有人、保管人、意向接收人均是档案文本,不是认证标识,不参与权限或所有权计算。
当前架构边界:非超级管理员一账号一归属;财产记录与系统角色交接是独立流程;通用设备登记不代表支持控制;录像机既有历史录像仍需品牌适配。未来多归属成员表与正式交接流程须先完成明确授权模型,再扩展数据结构。
发布设置、个人偏好与备份(v0.1.25)
appearance.py 将平台配置保存在 settings.appearance,个人偏好保存在 settings 的 preferences:<user-id> 键;修改账号资料不会覆盖偏好。选项通过有限枚举校验,使用修订号阻止并发覆盖。公开 /api/branding 只返回允许公开的文案与图标 URL;完整配置 GET/POST /api/appearance 仅超级管理员。GET/POST /api/preferences 固定当前会话账号,不接受目标 userId。主题只修改 HTML data 属性,不保存任意 CSS 或脚本。
PNG 上传校验签名、尺寸、CRC、压缩数据长度、颜色模式及结束块;不允许 SVG、外链图片或附加尾部内容。Logo 与 favicon 是公开品牌图片;家庭 coverData 不进入状态接口,仅 /api/households/cover 在验证用户归属后按 no-store 返回。地级市只是展示索引,与 familyId 授权边界独立。
backups.py 通过独立 SQLite 连接复制运行数据库,关闭连接后校验及原子完成文件。备份只保留账号散列与业务数据,删除复制库内的会话,不改变原库会话。文件权限 0600;网络副本先临时复制再核验 SHA-256。实例随机前缀界定清理范围。配置、最近结果和最多 100 条结果历史保存在 settings,UI 仅显示最近 20 条;GET /api/backups、POST /api/backups/config、POST /api/backups/run 全部由后端强制超级管理员权限。
每日调度为应用进程内线程,使用北京时间和已尝试日期防重复,启动晚于当天设定时间会补尝试一次;不添加外部 cron。网络 IO 依赖挂载自身的超时和可用性;不会挂载网盘、保存网盘登录密码或声称云端同步已确认。服务实例必须单进程部署,跨进程任务互斥尚不支持。