📖 后端接口约定(本阶段未部署,供后端实现对接)
基础约定
Base URL:{APIBASE}(页内可配置,默认 https://t.aibiancheng.net)
鉴权:V67 登录态。管理接口需携带 Cookie 会话或 Authorization: Bearer (由 AIG SDK 注入);
扫码跳转 /go/:code 与计数 不鉴权、匿名访问。
统一错误:HTTP 非 2xx 时响应体 { "error": { "code": "STRING_CODE", "message": "人类可读" } }
短码规则:6 位 base62 [A-Za-z0-9],首字符为字母;服务端生成冲突重试 5 次;
自定义短码正则 ^[A-Za-z][A-Za-z0-9_-]{2,31}$
1. 活码 CRUD(需登录)
GET /api/v1/qrcodes
→ 200 { "items": [ { id, shortCode, target, type:"url"|"text", name, enabled,
expiresAt, createdAt, updatedAt, totalScans } ] }
POST /api/v1/qrcodes
请求 { name?, target, type:"url"|"text", shortCode?, expiresAt?, enabled? }
→ 201 { id, shortCode, target, type, name, enabled, expiresAt, createdAt, updatedAt }
错误 400 invalid_params / 401 unauthenticated / 409 shortcode_taken
GET /api/v1/qrcodes/:id → 200 单条详情(同上字段)
PUT /api/v1/qrcodes/:id
请求 { name?, target?, type?, enabled?, expiresAt? } (所有字段可选)
→ 200 更新后的完整对象
DELETE /api/v1/qrcodes/:id → 204 No Content
2. 扫码跳转(公开,免登录)
GET /go/:code
命中且 enabled && (expiresAt==null || expiresAt>now):
→ 302 Location:
服务端异步记录一条 scan 事件:{ ts, ua, ip, country, region, referer }
命中但 disabled:→ 302 /offline?code=xxxx (或 404 静态页)
命中但 expired: → 302 /expired?code=xxxx
未命中: → 404
3. 扫码统计(需登录)
GET /api/v1/qrcodes/:id/stats?from=YYYY-MM-DD&to=YYYY-MM-DD
→ 200 {
"total": 1234,
"byDay": [ { "date": "2026-10-01", "count": 23 }, ... ],
"byUA": { "mobile": 980, "desktop": 220, "tablet": 34 },
"byRegion":[ { "region": "湖南", "count": 310 }, ... ],
"recent": [ { "ts": 1728000000000, "ua": "Mobile",
"region": "湖南", "referer": "-" }, ... ]
}
错误码表
400 invalid_params 请求字段缺失/格式错误
401 unauthenticated 未登录或会话过期
403 forbidden 无权访问该活码(非本人)
404 not_found 活码/短码不存在
409 shortcode_taken 自定义短码已被占用
500 internal 服务端错误
前端调用示例(fetch)
const base = localStorage.getItem('qrcode-api-base') || 'https://t.aibiancheng.net';
const r = await fetch(base + '/api/v1/qrcodes', { credentials: 'include' });
// 浏览器自动带 Cookie;如需 Bearer:headers:{ Authorization:'Bearer '+AIG.state.token }