一、幂等键与约束function validKey(k: string): boolean { return /^[A-Za-z0-9_\-\.]{8,128}$/.test(k) } 二、请求哈希与存储import crypto from 'crypto' function requestHash(path: string, body: any): string { const s = path + ':' + JSON.stringify(body) return crypto.createHash('sha256').update(s).digest('hex') } type Entry = { key: string; hash: string; status: number; body: string; expireAt: number } class Store { map = new Map<string, Entry>() ttlSec: number constructor(ttlSec: number) { this.ttlSec = ttlSec } get(k: string): Entry | undefined { const e = this.map.get(k) if (!e) return undefined if (Date.now() > e.expireAt) { this.map.delete(k); return undefined } return e } set(k: string, e: Entry) { this.map.set(k, e) } } 三、并发锁与防重放class Lock { locks = new Set<string>() acquire(k: string): boolean { if (this.locks.has(k)) return false; this.locks.add(k); return true } release(k: string) { this.locks.delete(k) } } 四、路由中间件type Req = { headers: Record<string, string | undefined>; path: string; body: any } type Res = { status: (n: number) => Res; end: (b?: string) => void; setHeader: (k: string, v: string) => void } function idempotencyGuard(store: Store, lock: Lock) { return async function handler(req: Req, res: Res, next: Function) { const key = req.headers['idempotency-key'] || '' if (!validKey(key)) return res.status(400).end('invalid_idempotency_key') const h = requestHash(req.path, req.body) const cached = store.get(key) if (cached) { if (cached.hash !== h) return res.status(409).end('key_conflict') res.setHeader('Idempotency-Replayed', 'true') return res.status(cached.status).end(cached.body) } if (!lock.acquire(key)) return res.status(409).end('in_progress') try { ;(req as any).idemKey = key ;(req as any).idemHash = h next() } finally { lock.release(key) } } } function recordResponse(store: Store, req: Req, status: number, body: string) { const key = (req as any).idemKey as string const hash = (req as any).idemHash as string const ttlSec = 900 const expireAt = Date.now() + ttlSec * 1000 store.set(key, { key, hash, status, body, expireAt }) } 五、示例与验收键格式长度与字符约束通过;TTL≤900秒;冲突返回`409`。重复请求响应复用并带`Idempotency-Replayed`;并发锁避免重复执行。存储包含状态与响应体,过期后自动清理并拒绝旧键复用。

发表评论 取消回复