一、参数与校验function validCursor(c: string): boolean { return /^[A-Za-z0-9_\-\.]{1,128}$/.test(c) } function validPageSize(n: number, max: number): boolean { return Number.isInteger(n) && n > 0 && n <= max } 二、游标与查询构造type Page = { items: any[]; nextCursor?: string } function decodeCursor(c: string | undefined): { id?: string } { if (!c) return {} if (!validCursor(c)) return {} return { id: c } } function buildQuery(base: string, cursor: { id?: string }, limit: number): string { const parts: string[] = [] if (cursor.id) parts.push(`id > '${cursor.id}'`) const where = parts.length ? 'WHERE ' + parts.join(' AND ') : '' return `${base} ${where} ORDER BY id ASC LIMIT ${limit}` } 三、响应头与Backpressuretype Res = { setHeader: (k: string, v: string) => void; end: (b?: string) => void } function sendPage(res: Res, page: Page, size: number, max: number) { res.setHeader('X-Page-Size', String(size)) res.setHeader('X-Max-Page-Size', String(max)) if (page.nextCursor) res.setHeader('X-Next-Cursor', page.nextCursor) if (!page.nextCursor) res.setHeader('X-End', 'true') res.end(JSON.stringify(page)) } 四、速率协同class RateGate { windowMs: number; max: number; hits = new Map<string, number[]>(); constructor(windowMs: number, max: number) { this.windowMs = windowMs; this.max = max } allow(key: string): boolean { const now = Date.now(); const arr = (this.hits.get(key) || []).filter(t => now - t < this.windowMs); if (arr.length >= this.max) return false; arr.push(now); this.hits.set(key, arr); return true } } 五、整合示例type Req = { query: Record<string, string | undefined>; headers: Record<string, string | undefined> } function handleList(req: Req, res: Res, maxSize = 100, gate = new RateGate(1000, 5)) { const key = (req.headers['x-user-id'] || 'anon') + ':' + (req.headers['x-tenant-id'] || 'anon') if (!gate.allow(key)) { res.setHeader('Retry-After', '1'); return res.end(JSON.stringify({ error: 'rate_limited' })) } const size = Number(req.query['size'] || '20') const cursorStr = req.query['cursor'] || undefined if (!validPageSize(size, maxSize)) return res.end(JSON.stringify({ error: 'invalid_size' })) const cursor = decodeCursor(cursorStr) const sql = buildQuery('SELECT id, name FROM items', cursor, size) const items = Array.from({ length: size }, (_, i) => ({ id: (cursor.id || '0') + '_' + i, name: 'item' + i })) const nextCursor = items.length ? String(items[items.length - 1].id) : undefined sendPage(res, { items, nextCursor }, size, maxSize) } 六、验收清单游标格式与页大小参数校验通过;最大页大小强制;响应头包含`X-Next-Cursor/X-Max-Page-Size`。速率限制每Key每窗口内请求数上限;Backpressure通过页大小与速率协同实现。查询构造包含`ORDER BY id ASC`与`LIMIT`;避免过深`offset`导致资源耗尽。

发表评论 取消回复