Files
memind/docs/architecture/page-data-api-public-access-plan-20260708.md
T
john 6b0c633a75 feat(page-data): complete Phase 4-5, ops UI, and publish integration
Add visitor roles, row-level scope, owner ops APIs, MySQL policy index,
Turnstile captcha, browser client SDK, publish-panel dataset binding,
acceptance tests, and usage documentation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 14:52:49 +08:00

12 KiB
Raw Blame History

Page Data API 与公开页面数据访问方案

日期: 2026-07-08

状态: 已实现(Phase 1–5 + 产品层);以代码与 docs/page-data-api-usage.md 为准。

1. 背景

MindSpace 已有用户私有数据空间能力:

  • 每个用户工作区有一个 .mindspace/private-data.sqlite
  • Agent 通过 private_data_infoprivate_data_schemaprivate_data_queryprivate_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。

一句话:

Agent 可以建模和写 SQLHTML 只能调用受限 dataset API。

3. 非目标

第一阶段不做以下事情:

  • 不让公开页面直接执行 SQL。
  • 不让公开页面创建表、删表、改表结构。
  • 不把用户私有数据迁入现有 MySQL 主业务表。
  • 不把 Page Data API 绑死到当前 session 结构。
  • 不开放跨用户任意查询。
  • 不默认允许公开页面读取 owner 的全部 SQLite。
  • 不默认开放 hard delete。

4. 建议架构

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 或受控查询。

示例:

{
  "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 的系统表中,例如:

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 草案

登录用户页面:

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

公开页面:

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、聚合结果或白名单字段。
  • 默认关闭 updatedelete

风险控制:

  • 不要求访问者登录。
  • 不应该展示敏感字段。
  • 表单提交必须有限流和大小限制。

7.2 简单令牌或密码访问

适合:

  • 小团队共享台账。
  • 临时活动登记。
  • 内部共享清单。
  • 不想让访问者注册,但需要阻止完全公开访问的轻量协作页。

默认权限:

  • 可以允许 read
  • 可以允许 insert
  • 可以允许 update 白名单字段。
  • 可以允许 soft_delete
  • 不默认允许 hard delete。

建议流程:

访问者打开页面
  -> 输入页面密码
  -> 后端校验密码 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。它由用户构建或发布页面时选择的访问模式和数据能力生成。

示例:

{
  "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_atversion
  • 自动写入 updated_at 和访问者标识。

Delete

第一阶段只允许 soft delete。

推荐语义:

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_hashuser_agent_hashpageDataSessionId
  • 公开 update/delete 记录操作日志。
  • 对频繁失败密码尝试做限流。
  • 对公开表单提交可选验证码或 Turnstile。

12. 构建期用户体验

Agent 或页面构建器应在创建数据页面时询问:

这个页面谁可以使用?

1. 完全公开
   任何人打开链接即可访问

2. 口令访问
   访问者输入页面口令后使用

3. 登录访问
   访问者需要注册或登录后使用

然后选择数据能力:

这个页面可以对数据做什么?

- 读取数据
- 新增数据
- 修改数据
- 删除数据

系统内部把选择转换成 Page Access Policy 和 dataset scope。用户不直接选择 SQL 权限。

13. 建议落地顺序

Phase 1: 私有登录页读取与提交

  • UserDataSpaceService
  • Page Data API 支持登录用户访问自己的 dataset。
  • 支持 GET rowsPOST rowsGET 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. 推荐默认策略

默认策略如下:

完全公开:
  insert 可选开启
  read 默认关闭
  update/delete 默认关闭

口令访问:
  read/insert 可选开启
  update 可选开启并限制字段
  delete 仅 soft delete

登录访问:
  可按角色开放 read/insert/update/soft delete
  hard delete 仍默认关闭

整体原则:

SQLite 是用户私有主库。
Agent 有受控 SQL 能力。
页面只有 dataset API 能力。
公开页面按 Page Access Policy 授权,不按 owner 权限授权。