一、参数与校验
```ts
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 }
```
二、游标与查询构造
```ts
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}`
}
```
三、响应头与Backpressure
```ts
type 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))
}
```
四、速率协同
```ts
class RateGate { windowMs: number; max: number; hits = new Map(); 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 } }
```
五、整合示例
```ts
type Req = { query: Record; headers: Record }
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`导致资源耗尽。
发表评论 取消回复