Uninote
Uninote
用户根目录
brdr
common
programming
docs
tests
后端试题
问题讨论

管理后台 API

管理前端位于相邻的 mynote-admin 项目。管理 API 和现有 /v1 同步 API 由同一个服务提供,共享 SQLite 和对象存储。

服务配置

使用 Node.js 24,并在部署前执行 npm ci 和 npm run build。

变量 用途
MYNOTE_ADMIN_USERNAME 独立管理员用户名
MYNOTE_ADMIN_PASSWORD_HASH scripts/admin-password-hash.ts 从 stdin 生成的 scrypt salt:hash
MYNOTE_ADMIN_ORIGIN 正式后台完整 origin,例如 https://admin.example.com,不带尾部斜杠

用户名与哈希必须同时配置;完全未配置时管理功能关闭,管理接口返回 503。配置错误会阻止服务启动。修改配置后必须重启同步服务。

生成哈希时从 stdin 输入至少 12 个字符的密码,不把密码放在命令参数中。管理前端 README 提供 zsh 隐藏输入和本地启动示例。

接口

方法 路径 返回
POST /admin/v1/login 接收 { username, password },返回 { username },设置会话 Cookie
GET /admin/v1/session { username }
POST /admin/v1/logout 撤销会话并清除 Cookie
GET /admin/v1/overview 与现有 store.metrics 相同的计数
GET /admin/v1/vip-plans { items: [{ id, name, quotaBytes, priceText }] },普通档位 ID 固定为 normal
POST /admin/v1/vip-plans 接收 { name, quotaMB, priceText },返回新档位,HTTP 201
PATCH /admin/v1/vip-plans/:id 接收 { name, quotaMB, priceText },返回更新后的档位
PATCH /admin/v1/accounts/:accountId/vip 接收 { vipPlanId, vipExpiresAt },返回账号 ID 和有效配额信息
PATCH /admin/v1/accounts/:accountId/quota 旧入口返回 409,提示改用 VIP 档位
DELETE /admin/v1/accounts/:accountId 永久删除账号及云端关联数据,返回 { id, deleted: true }
GET /admin/v1/accounts?page=1&pageSize=20 { items, total, page, pageSize }
GET /admin/v1/libraries?page=1&pageSize=20 { items, total, page, pageSize }
GET /admin/v1/libraries/:libraryId/versions?beforeRevision=... { library, items, hasMore }
GET /admin/v1/libraries/:libraryId/versions/:revision { library, version }

分页大小范围 1–100,页码范围 1–1000000,按创建时间倒序、ID 升序稳定排序。列表只返回明确列出的非凭据字段。设备计数包含已退出设备,存储对象为账号级对象记录数。

身份与安全边界

  • 普通用户 Bearer Token 无法访问管理 API;管理员 Cookie 无法访问用户同步 API。
  • 管理员配置与已有 /metrics 管理令牌独立。
  • 每个登录请求最多 4 KiB;每个连接来源每分钟最多 5 次尝试。
  • 管理写请求必须带匹配的 Origin,包括登录与退出。
  • 开发环境默认按请求 Host 和连接协议匹配 Origin;正式环境明确设置 MYNOTE_ADMIN_ORIGIN。
  • 反向代理保留 Host;未配置可信代理 IP 解析,因此代理后的登录限流按代理连接 IP 聚合。
  • 密码用 scrypt 哈希校验。会话随机生成,服务端只保存 token 摘要。
  • Cookie 为 HttpOnly、SameSite=Strict,路径为 /admin/v1,有效期 8 小时。
  • 原生 TLS 或配置 HTTPS origin 时设置 Secure。正式部署必须使用 HTTPS。
  • 会话与限流记录保存在进程内并有容量上限;重启后会话失效。当前用于单实例。
  • 删除账号需要管理员会话和匹配的 Origin,记录独立的 admin.account.delete 审计事件(管理员用户名与目标账号 ID)。

验证

node scripts/run.mts node --test test/hosted-sync-admin.test.js test/hosted-sync-http.test.js

覆盖管理员登录、Cookie 属性、越权隔离、Origin 校验、登录请求大小、字段白名单、分页边界、退出撤销、限流和未配置凭据。既有 HTTP 测试验证 client API。

列表统计与关联筛选

  • 账号列表包含 libraryCount(现有仓库数)、deviceCount(未撤销设备数)和 usedBytes(当前文件和每条非删除历史版本的原始字节总量,不去重,与配额接口一致)。无关联记录时返回 0。
  • 笔记库列表包含 accountEmail、fileCount(未删除文件数)和 versionCount(历史版本数)。
  • 两个列表均支持可选的 accountId 参数,按账号 ID 或邮箱的连续片段模糊匹配,ASCII 字母不区分大小写,去除首尾空白。
    • 笔记库列表匹配所属账号的 ID 或邮箱,账号列表匹配账号本身。
    • 筛选同时影响 items 和 total,再进行分页;无匹配返回空列表。
    • 空白关键词或超过 128 字符返回 400;%、_ 和引号按普通字符匹配。
  • 管理前端点击账号的仓库数量跳到 /libraries?accountId=...,点击仓库所属账号跳到 /accounts?accountId=...。筛选和分页保存在 URL 中,支持刷新、浏览器前进后退和复制链接。
  • 笔记库列表支持可选 q 关键词,同时匹配仓库名或笔记库 ID 的任意连续片段,ASCII 字母不区分大小写。
  • q 会去除首尾空白;空白关键词或超过 128 字符返回 400。%、_ 和引号按普通字符匹配。
  • q 可与 accountId 组合,筛选同时影响总数和分页;前端更新任一筛选条件时回到第一页,并保留另一条件。
  • 仓库对象可能在账号内共享,因此不将账号使用空间分摊成仓库占用空间。

管理端版本浏览

  • 点击管理前端的库名称或 ID 进入版本列表,按版本号倒序每页 20 条。
  • 可选 beforeRevision 必须为正安全整数;下一页使用当前页最后一个版本号,避免新写入导致重复分页。
  • 列表复用同步服务版本摘要,包含版本号、文件路径、动作、设备和创建时间,不读取文件内容。
  • 点击版本号或文件路径进入详情,复用同步服务的版本详情与对象解密逻辑。
  • version.before 和 version.after 分别为变更前后的文件;新增前、删除后返回 null。
  • 文本文件以纯文本展示,包括空文件;二进制文件以 base64 返回,管理前端提供下载。
  • 所有接口必须具有有效管理员 Cookie,普通用户 Token 无法访问。
  • 仓库所属账号从数据库查询,不接受客户端提供的账号身份;不存在的库或版本返回 404,非法 ID、版本号或分页游标返回 400。
  • 功能为只读;变更后需重新启动同步服务以加载新增管理路由。

永久删除账号

  • 账号 ID 必须通过标识符校验;不存在或已删除的账号返回成功,重复请求安全。
  • 元数据在单个事务中删除:账号、设备、会话、笔记库及删除标记、文件、版本、操作、收藏、设置、报告、原账号审计记录,以及该邮箱的验证码和发送记录。
  • 删除操作与同步上传、仓库删除共用串行队列。已失效的会话无法继续访问,排队上传和延迟报告不能重建账号数据。
  • 加密文件按账号对象索引逐个删除;其他账号即使文件内容相同也不受影响。
  • 文件清理失败返回错误,未完成的对象索引保留在 SQLite 中;重试相同 DELETE 地址继续清理,服务重启后也可重试。
  • 前端失败对话框保留账号 ID 和重试入口;关闭或刷新后,可使用原账号 ID 调用相同接口继续清理。
  • 删除不影响客户端本地文件、既有离线备份或已经建立的设备直连传输。
  • 更新后需重新启动同步服务,并重新构建、发布管理前端。

VIP 与空间配额

  • 普通档位固定名称为“普通用户”,默认 10 MiB;初始化一次后持久化,后台可编辑其配额。
  • VIP 档位名称去除首尾空白后必须为 1–128 字符且唯一;本期支持创建和编辑,不提供删除。
  • 档位增加 priceText 展示文本(不超过 128 字符,去除首尾空白),填写币种、价格及计费周期,如“¥30/月”。
  • 现有档位迁移后的价格为空;移动端提示联系管理员询价,后台编辑时补齐价格。未提供价格字段的更新保留已有值。
  • 登录用户可调用 GET /v1/vip-plans,返回 { items: [{ id, name, quotaBytes, priceText }] },仅包含 VIP 档位,按配额和名称排序;无需管理员 Cookie,不含账户或审计数据。
  • 移动端升级弹窗每次打开获取报价,并读取 /v1/quota 展示已用空间和总配额;不增加支付或价格计算功能。
  • quotaMB 使用 MiB(1 MiB = 1,048,576 字节),允许 0 和小数,换算后的字节数须为非负安全整数。
  • 指派 VIP 时 vipPlanId 为付费档位 ID,vipExpiresAt 为未来 UTC ISO 时间;普通用户使用 null 或 normal,到期时间须为空。
  • 服务器逐次请求计算有效档位;到期时间小于等于服务器当前时间立即回落普通档位,无需定时任务。
  • 账号列表和 /v1/quota 返回 usedBytes、quotaBytes、effectivePlan: { id, name, quotaBytes }、 vipPlan: { id, name } | null、vipExpiresAt、vipExpired、overQuota。
  • vipPlan 保留已分配的过期档位,effectivePlan 表示实际生效档位;overQuota 仅在用量严格大于配额时为 true。
  • 配额按当前文件和每条非删除历史版本的原始字节分别累计,相同内容及跨库引用不去重;删除墓碑不计量。删除当前文件保留历史占用。
  • 上传配额按操作后的净增量检查:上传替换当前文件并新增历史版本,重命名新增历史版本,删除移除当前文件占用。
    • 操作重试及无变化上传不重复计量;超额后仍允许不增加用量的操作。
    • 底层加密对象仍按内容寻址复用;现有数据在服务端升级重启后按新口径即时统计,无需重写对象。
  • 降额不删除任何数据。新增对象会令用量超过配额时返回 HTTP 413 / 账户存储配额不足。;重复对象、零字节对象和非增量操作仍允许。
  • 下载、克隆、历史读取不因超额而禁用。移动端上传受阻后保留队列并继续下载,提示部分上传未完成。
  • 管理写操作需要管理员 Cookie 和匹配的 Origin;与上传共用串行队列,修改和审计同事务提交。
  • admin.vip-plan.save、admin.account.vip 审计包含管理员身份和变更前后的值。
  • 旧 accounts.quota_bytes 留作历史记录,不再决定有效配额;原直接设置接口返回 409,不兼容独立账号配额覆盖。
  • 首次迁移时现有未分配 VIP 的账户统一使用普通档位;上线前应核对原有特殊配额账户并配置相应 VIP。
  • 部署先更新、重启 API,再构建发布 admin 和 mobile;数据库迁移自动执行,保留笔记、历史和账号数据。

全局系统配置

系统配置独立于账号同步设置;移动端启动时异步读取下面的公开客户端默认值。启动时增量创建 system_configs 表; 配置和图片 BLOB 一起存入 SQLite,因此既有数据库备份和恢复同时包含它们,不占用用户配额。

方法 路径 返回
GET /v1/system-configs 匿名读取 { configs: { 配置键: 字符串值 } },仅客户端公开白名单配置
GET /admin/v1/system-configs?page=1&pageSize=20 { items, total, page, pageSize },按 ID 倒序
POST /admin/v1/system-configs 接收 { key, value?, remark?, image? },201 返回配置
PATCH /admin/v1/system-configs/:id 接收需要修改的字段,200 返回配置
DELETE /admin/v1/system-configs/:id { id, deleted: true },重复删除成功
GET /admin/v1/system-config-images/:token 公开读取上传图片,无需登录
  • 管理配置列表返回字段固定为 { id, key, value, remark },不返回图片 BLOB。
  • 键必填,去除首尾空白后长度 1–128,区分大小写且唯一;冲突返回 409。
  • 值和备注默认为空字符串,分别最多 16,384 和 1,000 字符;非法类型或长度返回 400。
  • 页码范围 1–1000000,每页条数范围 1–100;编辑不存在的配置返回 404。
  • image 为 { mimeType, dataBase64 },Base64 不含 data URL 前缀,必须使用规范编码。
  • 图片最多 5 MiB,支持 PNG、JPEG、GIF、WebP;校验 MIME 和文件签名,拒绝 SVG。
  • 写请求正文最多 8 MiB;正文或解码图片超限返回 413。
  • 上传图片优先于同次提交的 value,自动将值设为 /admin/v1/system-config-images/:token。
  • 图片标识使用随机 UUID。图片返回正确 MIME、nosniff 和 no-store,可加后台域名构成完整公开地址。
  • 管理路由中仅精确匹配的图片 GET 可匿名访问;管理配置列表、写操作以及其他管理路由继续受管理员认证保护。
  • 替换图片、修改值为其他内容或删除配置,同一事务清除原图片;旧地址返回 404。
  • 修改键或备注、提交不变的值时保留原图片。失败时原配置和图片保持不变。
  • 写操作沿用 Origin 校验和串行队列,事务内记录 admin.system-config.create/update/delete 审计事件。
  • 审计仅记录管理员用户名和配置 ID,不包含配置值、键、备注或图片内容。
  • 图片和下述客户端白名单配置键是公开资源,不应包含敏感内容;其他配置仍只有管理员可读取。

上线需重新构建并重启同步服务,同时重新构建、发布管理前端,反向代理继续保留 /admin/v1/。 回归测试包含于 test/hosted-sync-admin.test.ts,沿用本文验证命令。

客户端启动配置

  • GET /v1/system-configs 无需管理员或账号登录,管理凭据未配置时也可读取,返回 Cache-Control: no-store。
  • 仅返回已存在的客户端白名单字符串值:
    • debug.https、debug.defaultServer、debug.defaultEmail、debug.defaultPass。
    • tips.ofDay:桌面端每日提示的 JSON 文本数组。
    • 缺项省略,无配置时返回 { configs: {} }。
  • 不返回配置 ID、备注、图片或其他配置键;写入仍通过受保护的管理接口完成。
  • 移动端所有 debug.* 仅在本机隐藏功能启用后生效;debug.https 默认 false,为 true 时验证证书;普通模式始终验证证书。
  • 默认服务器为 https://192.168.0.223:8787,默认邮箱与密码为空;配置异步读取失败不阻塞启动,登录表单下次打开时应用新默认值。
  • 默认密码属于匿名公开信息,应仅填测试账号;移动端不持久化配置或记录配置内容。

桌面端每日提示

  • 通过现有系统配置管理接口新增或修改 tips.ofDay,不使用图片字段。
  • value 填写 JSON 数组文本,例如 ["使用命令面板查找功能。","使用大纲跳转到标题。"]。
    • 配置值整体最多 16,384 字符;客户端同时限制最多 200 条,每条最多 4000 字符,不能仅含空白。
    • 正文可包含换行,按 JSON 字符串转义;桌面端仅显示纯文本,不执行 HTML。
    • [] 表示清空缓存并停止后续自动展示;删除该配置键表示不更新客户端已有缓存。
  • 桌面端向当前配置的同步服务器匿名读取,不发送账号令牌。
    • 启动和打开每日提示窗口时后台获取,不阻塞窗口恢复或展示。
    • 未完成、失败或返回无效内容时继续使用本机缓存;首次无缓存时使用内置离线示例。
    • 已打开窗口不切换正文,新内容下次打开时使用;“每天启动时显示”仍是本机设置。
  • 首次上线此接口变更需重启同步服务端和桌面应用;后续修改配置值无需重启。

account-data-storage

codemirror-migration

点赞(0) 阅读(31) 举报
目录
标题