用户手册-让你的网站动起来
目录
你部署的静态网站跑在浏览器里,天生没有后端。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 覆盖即可。
推荐阅读
- 当 AI 学会读构建系统:ByteAsk 给 C++ Agent 的一记提醒2026-11-10
- 把 AI 当同事,而不是搜索框:我最后留下三件工具2026-11-10
- 唱歌的人跑了一半2026-11-10
评论