一、幂等键与约束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`;并发锁避免重复执行。存储包含状态与响应体,过期后自动清理并拒绝旧键复用。

发表评论 取消回复