关于 cloudflare-worker-dev
面向使用 Cloudflare Workers 构建边缘 API 的开发者技能。介绍 Workers、KV、Durable Objects 等服务的选用、配置、部署和调试,并涵盖缓存、限流及常见实现陷阱。
主要功能
- 01Workers API 架构指导
- 02KV 缓存与存储模式
- 03Durable Objects 实时协调
- 04CORS 与 IP 限流处理
- 05Wrangler 部署与调试
- 06常见错误与反模式说明
适用场景
- 01构建 Cloudflare 边缘 API
- 02为请求添加 KV 缓存
- 03实现实时房间协调
- 04部署并排查 Worker 服务
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill cloudflare-worker-dev安装后在 Claude Code 中输入/cloudflare-worker-dev确认已加载。
附带文件.claude-plugin/plugin.json
详细介绍
Cloudflare Workers 开发
使用 Workers 构建高性能边缘 API,通过 KV 缓存,并借助 Durable Objects 实现实时协调。
核心架构
何时使用哪种服务
| 服务 | 使用场景 | 特性 |
|---|---|---|
| Workers | 请求处理、API 逻辑 | 无状态,CPU 时间 50ms(免费),30s(付费) |
| KV | 缓存、配置、会话 | 最终一致性,读取速度快 |
| Durable Objects | 实时功能、协调 | 强一致性,单线程 |
| R2 | 文件存储 | 兼容 S3,不收取出口流量费用 |
| D1 | 边缘 SQLite | 无服务器 SQL,适合读取 |
Worker 基础知识
Worker 基本结构
// src/index.ts
export interface Env {
MEETING_CACHE: KVNamespace;
RATE_LIMIT: KVNamespace;
API_KEY: string;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
// CORS handling
if (request.method === 'OPTIONS') {
return handleCORS();
}
try {
// Route handling
if (url.pathname === '/health') {
return json({ status: 'ok' });
}
if (url.pathname.startsWith('/api/')) {
return handleAPI(request, env, ctx);
}
return new Response('Not Found', { status: 404 });
} catch (error) {
console.error('Worker error:', error);
return json({ error: 'Internal error' }, 500);
}
},
// Cron trigger
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
ctx.waitUntil(runScheduledTask(env));
}
};
CORS 标头(必需)
const CORS_HEADERS = {
'Access-Control-Allow-Origin': '*', // Or specific origin
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
};
function handleCORS(): Response {
return new Response(null, { status: 204, headers: CORS_HEADERS });
}
function json(data: unknown, status = 200): Response {
return new Response(JSON.stringify(data), {
status,
headers: {
...CORS_HEADERS,
'Content-Type': 'application/json',
},
});
}
wrangler.toml 配置
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
# KV Namespaces
[[kv_namespaces]]
binding = "MEETING_CACHE"
id = "abc123..." # Production
preview_id = "def456..." # Dev
[[kv_namespaces]]
binding = "RATE_LIMIT"
id = "ghi789..."
# Environment variables
[vars]
CACHE_TTL = "86400"
RATE_LIMIT_REQUESTS = "100"
RATE_LIMIT_WINDOW = "3600"
# Secrets (set via `wrangler secret put`)
# API_KEY, DATABASE_URL, etc.
# Cron triggers
[triggers]
crons = ["0 */6 * * *"] # Every 6 hours
# Custom routes
# routes = [{ pattern = "api.example.com/*", zone_name = "example.com" }]
KV 存储模式
KV 基本操作
// Write with TTL
await env.CACHE.put('key', JSON.stringify(data), {
expirationTtl: 86400, // 24 hours in seconds
});
// Write with metadata
await env.CACHE.put('key', value, {
expirationTtl: 3600,
metadata: { createdAt: Date.now(), source: 'api' },
});
// Read
const value = await env.CACHE.get('key');
const parsed = await env.CACHE.get('key', 'json');
// Read with metadata
const { value, metadata } = await env.CACHE.getWithMetadata('key', 'json');
// Delete
await env.CACHE.delete('key');
// List keys
const { keys, cursor } = await env.CACHE.list({ prefix: 'meetings:' });
基于 Geohash 的缓存
import Geohash from 'latlon-geohash';
function getCacheKey(lat: number, lng: number, radius: number): string {
// 3-char geohash = ~150km cells, good for metro areas
const geohash = Geohash.encode(lat, lng, 3);
return `meetings:${geohash}:${radius}`;
}
async function getMeetingsWithCache(
lat: number,
lng: number,
radius: number,
env: Env
): Promise<{ data: Meeting[]; cached: boolean; geohash: string }> {
const geohash = Geohash.encode(lat, lng, 3);
const cacheKey = `meetings:${geohash}:${radius}`;
// Try cache first
const cached = await env.MEETING_CACHE.get(cacheKey, 'json');
if (cached) {
return { data: cached, cached: true, geohash };
}
// Fetch fresh data
const data = await fetchMeetings(lat, lng, radius);
// Cache in background (don't await)
env.ctx.waitUntil(
env.MEETING_CACHE.put(cacheKey, JSON.stringify(data), {
expirationTtl: 86400,
metadata: { cachedAt: Date.now(), geohash },
})
);
return { data, cached: false, geohash };
}
用于缓存调试的响应标头
function meetingsResponse(data: Meeting[], cached: boolean, geohash: string): Response {
return new Response(JSON.stringify(data), {
headers: {
...CORS_HEADERS,
'Content-Type': 'application/json',
'X-Cache': cached ? 'HIT' : 'MISS',
'X-Geohash': geohash,
'Cache-Control': 'public, max-age=3600',
},
});
}
速率限制
基于 IP 的速率限制
interface RateLimitConfig {
maxRequests: number;
windowSeconds: number;
}
async function checkRateLimit(
ip: string,
env: Env,
config: RateLimitConfig
): Promise<{ allowed: boolean; remaining: number; resetAt: number }> {
const key = `rate:${ip}`;
const now = Math.floor(Date.now() / 1000);
const windowStart = now - config.windowSeconds;
// Get current state
const stored = await env.RATE_LIMIT.get(key, 'json') as {
count: number;
windowStart: number;
} | null;
// New window or expired
if (!stored || stored.windowStart < windowStart) {
await env.RATE_LIMIT.put(key, JSON.stringify({
count: 1,
windowStart: now,
}), { expirationTtl: config.windowSeconds });
return {
allowed: true,
remaining: config.maxRequests - 1,
resetAt: now + config.windowSeconds,
};
}
// Within window
if (stored.count >= config.maxRequests) {
return {
allowed: false,
remaining: 0,
resetAt: stored.windowStart + config.windowSeconds,
};
}
// Increment
await env.RATE_LIMIT.put(key, JSON.stringify({
count: stored.count + 1,
windowStart: stored.windowStart,
}), { expirationTtl: config.windowSeconds });
return {
allowed: true,
remaining: config.maxRequests - stored.count - 1,
resetAt: stored.windowStart + config.windowSeconds,
};
}
// Usage in handler
async function handleAPI(request: Request, env: Env): Promise<Response> {
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
const rateLimit = await checkRateLimit(ip, env, {
maxRequests: parseInt(env.RATE_LIMIT_REQUESTS || '100'),
windowSeconds: parseInt(env.RATE_LIMIT_WINDOW || '3600'),
});
if (!rateLimit.allowed) {
return json({ error: 'Rate limit exceeded' }, 429, {
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': rateLimit.resetAt.toString(),
});
}
// ... handle request
}
Durable Objects(实时功能)
聊天室示例
// wrangler.toml
// [[durable_objects.bindings]]
// name = "CHAT_ROOMS"
// class_name = "ChatRoom"
// [[migrations]]
// tag = "v1"
// new_classes = ["ChatRoom"]
export class ChatRoom {
state: DurableObjectState;
sessions: WebSocket[] = [];
constructor(state: DurableObjectState) {
this.state = state;
}
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/websocket') {
if (request.headers.get('Upgrade') !== 'websocket') {
return new Response('Expected WebSocket', { status: 400 });
}
const [client, server] = Object.values(new WebSocketPair());
server.accept();
this.sessions.push(server);
server.addEventListener('message', (event) => {
this.broadcast(event.data as string, server);
});
server.addEventListener('close', () => {
this.sessions = this.sessions.filter(s => s !== server);
});
return new Response(null, { status: 101, webSocket: client });
}
return new Response('Not found', { status: 404 });
}
broadcast(message: string, exclude?: WebSocket) {
this.sessions.forEach(session => {
if (session !== exclude && session.readyState === WebSocket.OPEN) {
session.send(message);
}
});
}
}
// In main worker
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
if (url.pathname.startsWith('/room/')) {
const roomId = url.pathname.split('/')[2];
const id = env.CHAT_ROOMS.idFromName(roomId);
const room = env.CHAT_ROOMS.get(id);
return room.fetch(request);
}
}
};
部署与调试
命令
# Development
npx wrangler dev # Local dev server
npx wrangler dev --remote # Dev against real KV/DO
# Deployment
npx wrangler deploy # Deploy to production
npx wrangler deploy --env staging # Deploy to staging
# Secrets
npx wrangler secret put API_KEY # Set secret
npx wrangler secret list # List secrets
# KV Management
npx wrangler kv:key list --namespace-id=xxx
npx wrangler kv:key get --namespace-id=xxx "key"
npx wrangler kv:key delete --namespace-id=xxx "key"
# Logs
npx wrangler tail # Real-time logs
npx wrangler tail --format=pretty # Formatted output
错误代码
| 代码 | 含义 |
|---|---|
| 1101 | Worker 抛出异常 |
| 1102 | 超出 CPU 时间限制 |
| 1015 | 受到 Cloudflare 速率限制 |
| 524 | 源站超时(>100s) |
快速参考
// Get client IP
const ip = request.headers.get('CF-Connecting-IP');
// Get country
const country = request.cf?.country;
// Background task (won't block response)
ctx.waitUntil(doBackgroundWork());
// Streaming response
return new Response(readableStream, {
headers: { 'Content-Type': 'text/event-stream' }
});
// Proxy request
const response = await fetch(upstreamUrl, request);
return new Response(response.body, response);
反模式
❌ 在关键路径中等待 KV 写入
// ❌ ANTI-PATTERN: Blocks response on cache write
async function handler(request: Request, env: Env) {
const data = await fetchData();
await env.CACHE.put('key', data); // Unnecessary wait!
return json(data);
}
// ✅ CORRECT: Background write with waitUntil
async function handler(request: Request, env: Env, ctx: ExecutionContext) {
const data = await fetchData();
ctx.waitUntil(env.CACHE.put('key', data)); // Non-blocking
return json(data);
}
❌ 未处理 CORS
// ❌ ANTI-PATTERN: No preflight handling = broken browser requests
export default {
async fetch(request: Request) {
return json({ data: 'hello' }); // OPTIONS requests fail!
}
}
// ✅ CORRECT: Handle OPTIONS preflight
export default {
async fetch(request: Request) {
if (request.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: CORS_HEADERS });
}
return json({ data: 'hello' });
}
}
❌ 将密钥放在 wrangler.toml 中
# ❌ ANTI-PATTERN: Secrets in config (committed to git!)
[vars]
API_KEY = "sk-live-xxxxx"
# ✅ CORRECT: Use wrangler secret
# Run: npx wrangler secret put API_KEY
# Access: env.API_KEY
❌ 忽略 KV 的最终一致性
// ❌ ANTI-PATTERN: Read immediately after write
await env.KV.put('count', String(newCount));
const verify = await env.KV.get('count'); // May return old value!
// ✅ CORRECT: Trust write succeeded, or use Durable Objects for consistency
await env.KV.put('count', String(newCount));
return json({ count: newCount }); // Return what you wrote
❌ 调用外部 API 时不设置超时
// ❌ ANTI-PATTERN: External API can hang your worker
const data = await fetch('https://slow-api.com/data');
// ✅ CORRECT: Add timeout with AbortController
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const data = await fetch('https://slow-api.com/data', {
signal: controller.signal
});
} finally {
clearTimeout(timeout);
}
参考资料
详细指南请参阅 /references/:
kv-patterns.md- 高级 KV 使用模式durable-objects.md- 使用 DO 实现实时功能debugging.md- 常见问题排查
评论
支持 Markdown