From ea25058db852b8942963013719b947131b82e0dc Mon Sep 17 00:00:00 2001 From: john Date: Wed, 8 Jul 2026 12:25:02 +0800 Subject: [PATCH] docs: add page data api access plan --- ...ge-data-api-public-access-plan-20260708.md | 452 ++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 docs/architecture/page-data-api-public-access-plan-20260708.md diff --git a/docs/architecture/page-data-api-public-access-plan-20260708.md b/docs/architecture/page-data-api-public-access-plan-20260708.md new file mode 100644 index 0000000..99b172e --- /dev/null +++ b/docs/architecture/page-data-api-public-access-plan-20260708.md @@ -0,0 +1,452 @@ +# Page Data API 与公开页面数据访问方案 + +日期: 2026-07-08 + +状态: Draft,仅开发设计;本文不代表已实现。 + +## 1. 背景 + +MindSpace 已有用户私有数据空间能力: + +- 每个用户工作区有一个 `.mindspace/private-data.sqlite`。 +- Agent 通过 `private_data_info`、`private_data_schema`、`private_data_query`、`private_data_execute` 使用该 SQLite。 +- 当前能力适合问卷、表单、清单、调研数据、台账和分析中间表。 + +现在需要补齐的是页面侧的数据访问能力: + +- 用户构建 HTML 页面时,可以让页面读取必要数据。 +- 页面可以提交表单、更新记录、删除记录。 +- 公开分享页面也可以在授权范围内读写后台数据。 +- 不碰现有 session 存储,不改现有平台 MySQL 主业务表,不把用户数据塞进现有 MySQL。 + +本文定义 Page Data API 的边界、公开页面访问模式、权限模型和建议落地顺序。 + +## 2. 核心结论 + +结构化业务数据以 per-user SQLite 作为主存储,JSON 只作为 API 载荷、导入导出和少量页面配置格式。 + +Page Data API 不直接暴露 SQL。HTML 页面只访问 dataset API;后端根据页面授权、dataset scope、字段白名单和动作权限,受控读写 owner 的 private SQLite。 + +一句话: + +```text +Agent 可以建模和写 SQL;HTML 只能调用受限 dataset API。 +``` + +## 3. 非目标 + +第一阶段不做以下事情: + +- 不让公开页面直接执行 SQL。 +- 不让公开页面创建表、删表、改表结构。 +- 不把用户私有数据迁入现有 MySQL 主业务表。 +- 不把 Page Data API 绑死到当前 session 结构。 +- 不开放跨用户任意查询。 +- 不默认允许公开页面读取 owner 的全部 SQLite。 +- 不默认开放 hard delete。 + +## 4. 建议架构 + +```text +Agent / Goose MCP + -> private_data_* tools + -> UserDataSpaceService + -> owner .mindspace/private-data.sqlite + +HTML Page + -> Page Data API + -> Page Access Policy + -> Dataset Scope + -> UserDataSpaceService + -> owner .mindspace/private-data.sqlite +``` + +建议先抽出 `UserDataSpaceService`,让 Agent MCP 和 Page Data API 复用同一个底层实现。 + +`UserDataSpaceService` 建议职责: + +- 定位用户 workspace 和 `.mindspace/private-data.sqlite`。 +- 创建或检查用户私有数据库。 +- 执行只读查询。 +- 执行 Agent 侧受控 SQL。 +- 执行页面侧结构化 insert/update/soft delete。 +- 维护大小限制、超时、行数限制和危险 SQL 防护。 +- 提供 dataset schema、统计、分页查询等上层能力。 + +## 5. Dataset 模型 + +页面不直接访问 table,而是访问 dataset。dataset 是页面可见的数据契约,可以映射到 SQLite table、view 或受控查询。 + +示例: + +```json +{ + "name": "registrations", + "table": "form_registrations", + "description": "活动报名数据", + "actions": ["read", "insert", "update", "soft_delete"], + "columns": { + "read": ["id", "name", "phone", "status", "note", "created_at"], + "insert": ["name", "phone", "status", "note"], + "update": ["status", "note"], + "soft_delete": ["id"] + }, + "limits": { + "maxRowsPerRead": 100, + "maxInsertBytes": 8192 + } +} +``` + +dataset 元数据可以先存在用户 SQLite 的系统表中,例如: + +```sql +CREATE TABLE IF NOT EXISTS __page_data_datasets ( + name TEXT PRIMARY KEY, + table_name TEXT NOT NULL, + config_json TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +); +``` + +该系统表属于用户私有数据空间,不需要新增平台 MySQL schema。后续如果需要统一管理公开发布状态,可以再引入平台侧只存 pageId、ownerUserId、scope hash、token hash 的轻量索引。 + +## 6. API Surface 草案 + +登录用户页面: + +```text +GET /api/page-data/:dataset +GET /api/page-data/:dataset/schema +GET /api/page-data/:dataset/stats +POST /api/page-data/:dataset/rows +PATCH /api/page-data/:dataset/rows/:id +DELETE /api/page-data/:dataset/rows/:id +``` + +公开页面: + +```text +POST /api/public/pages/:pageId/data-auth +GET /api/public/pages/:pageId/data/:dataset +GET /api/public/pages/:pageId/data/:dataset/schema +GET /api/public/pages/:pageId/data/:dataset/stats +POST /api/public/pages/:pageId/data/:dataset/rows +PATCH /api/public/pages/:pageId/data/:dataset/rows/:id +DELETE /api/public/pages/:pageId/data/:dataset/rows/:id +``` + +约束: + +- `GET` 只支持白名单过滤、分页、排序和预设统计。 +- `POST` 只允许插入 dataset 允许的字段。 +- `PATCH` 只允许更新 dataset 允许的字段。 +- `DELETE` 第一阶段只做 soft delete。 +- 所有公开 API 都必须校验 pageId、访问模式、token、dataset、action、columns 和 limits。 + +## 7. 页面访问模式 + +用户构建或发布页面时必须选择访问模式。访问模式决定页面如何获得 Page Data API 权限。 + +### 7.1 完全公开 + +适合: + +- 展示页。 +- 公开统计页。 +- 匿名表单。 +- 不需要追责的反馈收集。 + +默认权限: + +- 可以允许 `insert`。 +- 可以允许读取公开 view、聚合结果或白名单字段。 +- 默认关闭 `update` 和 `delete`。 + +风险控制: + +- 不要求访问者登录。 +- 不应该展示敏感字段。 +- 表单提交必须有限流和大小限制。 + +### 7.2 简单令牌或密码访问 + +适合: + +- 小团队共享台账。 +- 临时活动登记。 +- 内部共享清单。 +- 不想让访问者注册,但需要阻止完全公开访问的轻量协作页。 + +默认权限: + +- 可以允许 `read`。 +- 可以允许 `insert`。 +- 可以允许 `update` 白名单字段。 +- 可以允许 `soft_delete`。 +- 不默认允许 hard delete。 + +建议流程: + +```text +访问者打开页面 + -> 输入页面密码 + -> 后端校验密码 hash + -> 返回短期 pageDataSessionToken + -> 页面后续 CRUD 都带 token + -> 后端每次校验 token 和 dataset scope +``` + +注意: + +- 密码只证明访问者知道入口口令,不代表访问者拥有 owner 权限。 +- 密码不应写入 HTML。 +- token 要短期有效,并支持撤销、轮换和重置密码。 +- 如需审计,可要求访问者输入显示名,写入 `updated_by_label`。 + +### 7.3 注册登录访问 + +适合: + +- 长期小系统。 +- 敏感度更高的数据。 +- 需要准确追踪谁修改了数据。 +- 需要区分 owner、editor、viewer 或不同访问者数据隔离的页面。 + +默认权限: + +- 可以按登录用户、角色和行级规则控制 read/insert/update/soft delete。 +- 可以记录真实 `visitorUserId`。 +- 可以在后续阶段支持更细的 row policy。 + +第一阶段可以只保留模式定义,不急于做完整外部访问者账号体系。 + +## 8. Page Access Policy + +Page Data API 的授权来源是 Page Access Policy。它由用户构建或发布页面时选择的访问模式和数据能力生成。 + +示例: + +```json +{ + "pageId": "pub_abc", + "ownerUserId": "user_123", + "workspaceRef": "MindSpace/user_123", + "accessMode": "password", + "datasets": { + "registrations": { + "read": true, + "insert": true, + "update": true, + "softDelete": true, + "hardDelete": false, + "columns": { + "read": ["id", "name", "phone", "status", "note", "created_at"], + "insert": ["name", "phone", "status", "note"], + "update": ["status", "note"] + } + } + } +} +``` + +Policy 要点: + +- 页面只能访问 policy 显式声明的 dataset。 +- dataset 只能执行 policy 显式声明的 action。 +- action 只能使用 policy 显式声明的 columns。 +- 公开页面必须使用 pageId + token/session 访问,不能借用 owner 登录态。 +- policy 修改后要能立即收紧权限。 + +## 9. CRUD 语义 + +### Insert + +`insert` 是公开页面最安全的写入能力,适合表单和上报。 + +要求: + +- 字段白名单。 +- 类型校验。 +- 长度限制。 +- 频率限制。 +- 自动写入 `created_at`。 + +### Update + +`update` 适合共享清单和协作台账。 + +要求: + +- 只能改白名单字段。 +- 必须带稳定 row id。 +- 可选乐观锁字段,例如 `updated_at` 或 `version`。 +- 自动写入 `updated_at` 和访问者标识。 + +### Delete + +第一阶段只允许 soft delete。 + +推荐语义: + +```sql +UPDATE target_table +SET deleted_at = CURRENT_TIMESTAMP, + deleted_by = ? +WHERE id = ? +``` + +要求: + +- 查询默认过滤 `deleted_at IS NULL`。 +- owner 或 Agent 可以恢复。 +- hard delete 只保留给登录 owner 或后台维护操作。 + +## 10. 对现有服务的影响 + +按本文方案实施,对现有服务影响应控制在新增能力范围内: + +- 不改现有 session 存储模型。 +- 不改现有 MySQL 主业务表。 +- 不要求现有公开 HTML 立即迁移。 +- 不影响 Agent 现有 `private_data_*` 工具。 +- 新增 Page Data API 可作为独立 route 接入。 +- 底层复用 private SQLite 能力,减少重复存储逻辑。 + +需要注意的新增运行风险: + +- 公开写入会带来垃圾数据和刷接口风险。 +- 公开读如果 scope 配错,可能泄露 owner 私有数据。 +- SQLite 是 per-user 低到中等并发数据层,不适合作为高并发公共数据库。 +- 密码访问无法可靠证明真实操作者身份,只能作为轻量协作权限。 +- 删除和更新必须有审计和恢复策略。 + +## 11. 安全要求 + +必须满足: + +- 公开页面永远不能传 SQL。 +- 后端每次请求都校验 pageId、token、dataset、action、columns。 +- 默认关闭公开读,除非 dataset 明确允许。 +- 默认关闭公开 update/delete,除非访问模式和 dataset 都明确允许。 +- 公开 delete 默认 soft delete。 +- 密码只存 hash,不存明文。 +- token 可撤销、可轮换、可过期。 +- 公开接口有限流、请求体大小限制和行数限制。 +- 错误信息不能泄露 SQLite 路径、SQL 细节或 owner 内部信息。 + +建议满足: + +- 所有公开写入记录 `created_ip_hash`、`user_agent_hash`、`pageDataSessionId`。 +- 公开 update/delete 记录操作日志。 +- 对频繁失败密码尝试做限流。 +- 对公开表单提交可选验证码或 Turnstile。 + +## 12. 构建期用户体验 + +Agent 或页面构建器应在创建数据页面时询问: + +```text +这个页面谁可以使用? + +1. 完全公开 + 任何人打开链接即可访问 + +2. 口令访问 + 访问者输入页面口令后使用 + +3. 登录访问 + 访问者需要注册或登录后使用 +``` + +然后选择数据能力: + +```text +这个页面可以对数据做什么? + +- 读取数据 +- 新增数据 +- 修改数据 +- 删除数据 +``` + +系统内部把选择转换成 Page Access Policy 和 dataset scope。用户不直接选择 SQL 权限。 + +## 13. 建议落地顺序 + +### Phase 1: 私有登录页读取与提交 + +- 抽 `UserDataSpaceService`。 +- Page Data API 支持登录用户访问自己的 dataset。 +- 支持 `GET rows`、`POST rows`、`GET schema`。 +- Agent 继续用 `private_data_*` 建表和注册 dataset。 + +### Phase 2: 完全公开 insert + +- 支持公开页面 `pageId`。 +- 支持公开页面向授权 dataset `insert`。 +- 默认不开放公开读。 +- 加入限流、字段校验和大小限制。 + +### Phase 3: 口令访问 read/update/soft delete + +- 支持页面密码 hash。 +- 支持 `data-auth` 换短期 token。 +- 支持白名单 `read/update/soft_delete`。 +- 加入操作日志和恢复语义。 + +### Phase 4: 注册登录访问 + +- 引入访问者身份。 +- 支持 viewer/editor/owner。 +- 支持行级规则和更完整审计。 + +### Phase 5: 管理与运维 + +- 页面 owner 可查看 dataset、操作日志、最近提交。 +- 支持重置密码、撤销 token、关闭 dataset。 +- 支持导出 JSON/CSV。 +- 支持软删除恢复。 + +## 14. 第一版验收标准 + +第一版完成后,至少应能证明: + +- Agent 可以创建表和 dataset。 +- 登录用户页面可以读取 dataset。 +- 登录用户页面可以提交表单到 SQLite。 +- 公开页面在无密码模式下只能执行被授权的 insert。 +- 未授权 dataset/action/column 会被拒绝。 +- 公开页面无法传 SQL。 +- 公开页面无法读取未授权字段。 +- Page Data API 不影响现有 session 和 MySQL 主链路。 + +## 15. 推荐默认策略 + +默认策略如下: + +```text +完全公开: + insert 可选开启 + read 默认关闭 + update/delete 默认关闭 + +口令访问: + read/insert 可选开启 + update 可选开启并限制字段 + delete 仅 soft delete + +登录访问: + 可按角色开放 read/insert/update/soft delete + hard delete 仍默认关闭 +``` + +整体原则: + +```text +SQLite 是用户私有主库。 +Agent 有受控 SQL 能力。 +页面只有 dataset API 能力。 +公开页面按 Page Access Policy 授权,不按 owner 权限授权。 +```