xmeta 联机数据
所有请求都带这枚令牌。没连接时发出去会 401 —— 这也是要验的一条。
读
写
删
管理员专属
调试
这几条动的是「别人的」数据(公共板、改动记录、换昵称),默认拿 466 当那个人 —— 点一次上面的「初始化测试数据」会换成真实存在的 uid。 想亲眼看到「改别人没开放的字段 → 403」,得换个不是管理员的身份;测试台上做不到(你自己就是管理员), 这条在 e2e 测试里用三个令牌验过了。
造一批能跑通所有接口的样本:每人一条私有背包 + 一条公开角色池、别人一条开放的公共板、 我自己的世界 boss,外加几条改动日志。全部走上面这些公开接口,不碰数据库。 重复点会覆盖,不会越堆越多。
GET /api/kv/:scope → { "data": Row | null }
PUT /api/kv/:scope → { "data": Row, "created": true, "modified": true, "changed": ["tagInt1"] }
PATCH /api/kv/:scope → { "data": Row, "modified": true, "changed": ["tagInt1"] }
DELETE /api/kv/:scope/:uid → { "deleted": 1, "data": Row }
DELETE /api/kv/:scope?all=1 → { "deleted": 12, "scope": "package" }
GET /api/kv/:scope/list → { "items": [Row], "page": 1, "size": 20, "total": 3, "hasNext": false }
GET /api/kv/:scope/:uid/log → { "items": [Log], "page": 1, "size": 20, "total": 2, "hasNext": false }
POST /api/user/profiles → { "profiles": [ { "uid": 42, "nickname": "…", "avatar": "…" } ] }
GET 没建过回 data: null,不是报错 —— 别当成失败处理。PUT 是整行覆盖,没写的字段会被清空;日常改数据用 PATCH。PATCH 的 inc 也一样,新值直接就在 data 里,不用再查一次。modified 是 false、changed 是空数组,日志也不会记 —— 那不是出错,是「你提交的内容和原来一模一样」。DELETE /:uid 回带被删掉的那一行,客户端可以据此撤销。?all=1 删的是整个 scope(所有人的数据),必须显式带上。list / log 都带 hasNext,分页不用自己算 page * size < total。{
"uid": 42,
"scope": "package",
"isPublic": false,
"openEdit": ["tagInt1"],
"tagTinyint": 1,
"tagInt1": 100,
"tagInt2": 0,
"tagBigint": 0,
"text1": "我的背包",
"text2": null,
"textLong": null,
"extra": { "items": ["sword"], "gold": 1000 },
"expiresAt": "2026-10-17T12:00:00.000Z",
"createdAt": "2026-10-10T12:00:00.000Z",
"updatedAt": "2026-10-10T12:00:00.000Z"
}
uid / scope —— 这条数据归谁、属于哪个业务。isPublic —— false 时只有属主和管理员看得见。openEdit —— 别人能改哪些字段的白名单,空数组 = 只读。只收这 7 个:tagTinyint / tagInt1 / tagInt2 / tagBigint / text1 / text2 / textLong。写 extra、isPublic 之类会 400。tagTinyint / tagInt1 / tagInt2 / tagBigint —— 数字标签,给筛选用;也可以是「剩余血量」那种状态。text1 / text2 / textLong —— 上限 128 / 512 / 1024 字符。extra —— 任意 JSON,序列化后不超过 2048 字节;永远不在 openEdit 里,别人改不了。expiresAt —— 过期时刻。写的时候带 ttl(秒,最多 30 天)就从当前时刻重算;不带就不动。{
"id": 901,
"dataId": 77,
"actorUid": 42,
"action": "update",
"changed": ["tagInt1", "text1"],
"before": { "tagInt1": 100, "text1": "旧值" },
"after": { "tagInt1": 90, "text1": "新值" },
"createdAt": "2026-10-10T12:00:00.000Z"
}
actorUid —— 真正动手的人(管理员替玩家改时就是管理员)。action —— "create" 或 "update"。changed —— 这次动了哪几个字段,用的也是字段名。before / after —— 只含被改字段的前后值,不是整行快照。{ "error": { "code": "invalid_param", "message": "text1 超过 128 字" } }
code 判断,别按 message 匹配。quota_exceeded(409)—— 行数用满了:普通用户每个 toy 128 条,管理员 512 条。
字段名一律是驼峰(tagInt1、text1),请求和响应都一样。
写错名字不会被静默放过 —— 会 400,而且报错里会点出是哪个字段。