OnePage 手册
OnePage 手册
用块式笔记
记录与分享
用块式笔记 记录与分享

用户手册-让你的网站动起来

目录

你部署的静态网站跑在浏览器里,天生没有后端。KV 数据空间给每个站点配了一个随站点走的键值数据库:前端 JS 按站点名直接调用接口,就能存取数据——注册、登录、计数、排行榜、留言、草稿,全都不需要自己买服务器。

接口一览(前缀:/api/site/<站点名>/kv)

方法路径作用
GET?prefix=xxx按前缀列出本站全部键值(做数据表/榜单筛选)
GET/<key>读取单个键(缺失返回 404)
PUT/POST/<key>写入,body 传 {"v": 值, "ttl": 秒}(ttl 可选)
DELETE/<key>删除键
POST/<key>/incr原子自增(计数器/积分),可加 field 对对象字段累加

所有接口只需站点名,不需要任何令牌或登录。值可以是字符串、数字、JSON 对象:写入时自动序列化存储,读取时自动还原为写入时的类型——存数字读回就是数字,存对象读回就是对象,前端拿到 d.v 即可直接使用,不需要自己 JSON.parse。纯文本字符串读回仍是字符串。

三步接入

第一步,写入数据(游戏结束后存最高分):

async function saveBest(score) {
  await fetch('/api/site/my-site/kv/best', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ v: score })
  });
}

第二步,读取数据(页面打开时恢复):

async function loadBest() {
  const r = await fetch('/api/site/my-site/kv/best');
  if (!r.ok) return 0;          // 404 = 还没存过
  const d = await r.json();
  return d.v;                    // 直接可用
}

第三步,原子上增(计数器最怕并发丢加,incr 服务端帮你保证):

await fetch('/api/site/my-site/kv/visits/incr', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ amount: 1 })
});

账号绑定排行榜(防伪造)

KV 是公开读写的,谁都能改榜单怎么办?平台提供了官方排行榜接口:提交成绩必须登录平台账号,名字由服务端从账号昵称写入、前端改不了,且每个账号只保留最高分。

接口作用
GET /api/site/<站点名>/me检测当前浏览器登录的平台账号(返回 logged / name)
GET /api/site/<站点名>/lb读取前 10 名
POST /api/site/<站点名>/lb提交成绩(body: {"score":123,"dist":456}),未登录返回 401

前端示例——检测登录并锁定昵称:

const me = await (await fetch('/api/site/my-site/me?t=' + Date.now())).json();
if (me.logged) { nameInput.value = me.name; nameInput.disabled = true; }

提交成绩(只有超过自己历史最高才上榜):

await fetch('/api/site/my-site/lb', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ score: myScore })
});
提示:登录状态检测只在页面加载时跑一次。若用户打开页面后才去登录,可加一个"重新检测"按钮再调一次 /me。接口响应均带 no-store,不会被浏览器缓存。

可以拿 KV 做什么

  • 访问计数器:incr visits,展示"本站已被访问 N 次"
  • 暗色模式记忆:存偏好,下次打开自动恢复
  • 草稿箱:文本框内容自动 PUT 到 kv/draft,断网也不丢
  • 点赞/投票:按内容 id 做 key,incr 计数
  • 游戏排行榜:注册/登录体系 + lb 接口,或纯 KV 自建前缀榜单

安全提醒

KV 按站点名隔离、读写公开:知道站点名的人可以读写该站数据。请勿明文存敏感信息(真实密码、身份证号等);需要真实鉴权的场景(如付费、管理员操作)请等待平台后续的服务端校验能力。

提示:数据按 key 原样覆盖,类型以最后一次写入为准。如果同一个 key 一会儿写数字一会儿写字符串,读回类型也会跟着变。排行榜等需要服务端鉴权的数据请用下方的 lb 接口,不要用公开 KV。

进阶能力:文件、AI、密钥与写保护

除了 KV,平台还提供 Blob 文件存储、AI 模型网关、服务端密钥和 KV 写保护。四项都在「编辑网站 → 站点服务」里开关,接口同样按站点名寻址,前端 JS 直接调用,不需要令牌。

Blob 文件存储(头像 / 图片 / 上传文件)

二进制文件不要塞进 KV。接口前缀 /api/site/<站点名>/blob,上传时把文件二进制直接作为请求体 PUT 上去(也可用 FormData 的 file 字段),返回一个可直接用于 <img src> 的 url。

方法路径作用
PUT/POST/<path>上传/覆盖文件,返回 { ok, k, size, url }
GET/<path>下载文件(图片等安全类型可 inline 展示)
GET?prefix=xxx列出文件(k / size / updated_at)
DELETE/<path>删除文件
const SITE = 'my-site';
// 上传:fileOrBlob 来自 <input type="file"> 或 canvas.toBlob()
async function upload(path, fileOrBlob) {
  const r = await fetch(`/api/site/${SITE}/blob/${path}`, {
    method: 'PUT', body: fileOrBlob
  });
  const d = await r.json();
  if (!r.ok) throw new Error(d.error);
  return d.url;                      // 直接 <img src="...">
}
// 读取 / 列表 / 删除
const blob = await fetch(`/api/site/${SITE}/blob/avatar/u1.png`);
const list = await (await fetch(`/api/site/${SITE}/blob?prefix=avatar/`)).json();
await fetch(`/api/site/${SITE}/blob/avatar/u1.png`, { method: 'DELETE' });
限制:单文件 10MB;只允许图片 / 文本 / PDF / 音视频 / zip 等白名单扩展名,HTML、SVG、JS 一律拒绝(防止上传网页窃取同源登录态);路径不接受 .. 与隐藏文件。写操作受下方「KV 写保护」开关统一约束。

AI 模型网关(让静态站直接对话大模型)

先去「编辑网站 → 站点服务」勾选「启用 AI 网关」,并在「服务端密钥」里填三个键:OPENAI_API_BASE、OPENAI_API_KEY、OPENAI_MODEL。填完保存即可,密钥只在服务端使用,前端永远接触不到。

接口 POST /api/site/<站点名>/ai/chat/completions,请求体就是 OpenAI 兼容格式(messages / temperature / max_tokens / stream 等),响应也原样透传,所以任何 OpenAI SDK 换个 baseURL 就能用。

const SITE = 'my-site';
// 非流式
async function ask(text) {
  const r = await fetch(`/api/site/${SITE}/ai/chat/completions`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ messages: [{ role: 'user', content: text }] })
  });
  if (!r.ok) throw new Error((await r.text()).slice(0, 200));  // 401=密钥无效 429=今日超额
  const d = await r.json();
  return d.choices[0].message.content;
}

// 流式(SSE,逐字上屏)
async function askStream(text, onDelta) {
  const r = await fetch(`/api/site/${SITE}/ai/chat/completions`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ messages: [{ role: 'user', content: text }], stream: true })
  });
  const reader = r.body.getReader(), dec = new TextDecoder();
  let buf = '';
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buf += dec.decode(value, { stream: true });
    const lines = buf.split('\n'); buf = lines.pop();
    for (const line of lines) {
      if (!line.startsWith('data: ')) continue;
      const p = line.slice(6).trim();
      if (p === '[DONE]') return;
      try { onDelta(JSON.parse(p).choices[0].delta.content || ''); } catch (e) {}
    }
  }
}
默认「AI 网关需登录」+ 每日 100 次调用上限(按站点计,响应头有 X-RateLimit-Remaining)。用平台密钥时会强制使用平台配置的模型;填自己的密钥后前端可指定 model。上游报错会原样透传状态码和错误体,方便定位。

服务端密钥 & KV 写保护

服务端密钥:编辑页里配置的键值对只存在服务器数据库,页面只显示键名、不回显明文,也没有任何 API 能读出值。典型用途就是给 AI 网关配 Key——不要把 Token 硬编码进前端仓库,那样会被人拿去刷额度。

KV 写保护:默认关闭(保持所有老站点行为不变)。在编辑页打开后,KV 的 PUT / DELETE / incr 以及 Blob 的上传删除都要求登录平台账号,未登录返回 401;GET 读依然公开。/me 会多返回一个 can_write 字段,前端可据此禁用保存按钮。

const me = await (await fetch('/api/site/my-site/me')).json();
// me = { ok, logged, uid, name, can_write }
if (!me.can_write) {
  saveBtn.disabled = true;
  saveBtn.textContent = '请先登录平台账号再保存';
}

能力自查

GET /api/site/<站点名>/config 返回本站所有开关与限额(不含任何密钥),前端可以据此决定要不要显示 AI 入口、写保护提示等。

const cfg = await (await fetch('/api/site/my-site/config')).json();
// cfg.kv_require_login / ai_enabled / ai_require_login / ai_daily_limit
// cfg.ai_used_today / blob_max_bytes / blob_allow_ext
if (cfg.ai_enabled) showAIChatButton();
平台默认模型已切换为 dots_webdev_1022,站点开启 AI 网关后无需另填密钥即可直接对话。如需使用自己的模型 / 密钥 / 更高额度,在「服务端密钥」里填 OPENAI_API_BASE、OPENAI_API_KEY、OPENAI_MODEL 覆盖即可。

评论

评论加载中…

推荐阅读