mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-23 00:21:28 +00:00
feat(storage): migrate config, dedup and delivery state to D1
- Move oversized queue payloads from KV to R2 (PAYLOAD binding, webhooks/YYYY/MM/DD/*.json, KV queue:payload:* fallback)
- Persist routes/groups to D1 (d1_routes/d1_groups) with memory -> KV -> D1 three-tier cache, seeded from legacy KV config keys
- Move webhook dedup (dedup_keys), delivery state (delivery_state) and message tracking (message_tracking) to D1 via canUseD1 probe with automatic KV fallback
- Batch send_logs inserts (recordSendBatch) and add group_id/ts index
- Add storage-prune scheduled task for expired dedup/state/tracking rows
- Add TTL to invite:group:{id} index and audit all ephemeral KV keys
- Add D1 indexes for the new tables
- Sync AGENTS.md, README.md/zh and docs/ (en/zh) with the new storage layout
This commit is contained in:
parent
2e1b0f022e
commit
25ebae4ae5
46 changed files with 1450 additions and 117 deletions
64
server/lib/storage/payload.ts
Normal file
64
server/lib/storage/payload.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import type { Env } from "../types";
|
||||
|
||||
const PAYLOAD_PREFIX = "webhooks/";
|
||||
const DEFAULT_TTL_SECONDS = 3600;
|
||||
|
||||
export interface PayloadStore {
|
||||
put(payload: string, ttl?: number): Promise<string>;
|
||||
get(key: string): Promise<string | null>;
|
||||
delete(key: string): Promise<void>;
|
||||
}
|
||||
|
||||
function generateKey(): string {
|
||||
const bytes = new Uint8Array(16);
|
||||
crypto.getRandomValues(bytes);
|
||||
const hex = Array.from(bytes)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
const now = new Date();
|
||||
const y = now.getUTCFullYear();
|
||||
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
|
||||
const d = String(now.getUTCDate()).padStart(2, "0");
|
||||
return `${PAYLOAD_PREFIX}${y}/${m}/${d}/${hex}.json`;
|
||||
}
|
||||
|
||||
export function r2PayloadStore(env: Env): PayloadStore {
|
||||
const bucket = env.PAYLOAD;
|
||||
if (!bucket) {
|
||||
return {
|
||||
async put(_payload: string): Promise<string> {
|
||||
throw new Error("R2 binding is not configured");
|
||||
},
|
||||
async get(): Promise<null> {
|
||||
throw new Error("R2 binding is not configured");
|
||||
},
|
||||
async delete(): Promise<void> {
|
||||
throw new Error("R2 binding is not configured");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
async put(payload: string, ttl?: number): Promise<string> {
|
||||
const key = generateKey();
|
||||
const expires = new Date(
|
||||
Date.now() + (ttl ?? DEFAULT_TTL_SECONDS) * 1000,
|
||||
);
|
||||
await bucket.put(key, payload, {
|
||||
httpMetadata: { contentType: "application/json" },
|
||||
customMetadata: { expires: expires.toISOString() },
|
||||
});
|
||||
return key;
|
||||
},
|
||||
|
||||
async get(key: string): Promise<string | null> {
|
||||
const obj = await bucket.get(key);
|
||||
if (!obj) return null;
|
||||
return obj.text();
|
||||
},
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
await bucket.delete(key);
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue