用 Claude Code 构建 Serverless Functions:Lambda 与 Workers 实战
用 Claude Code 构建可靠的 serverless functions:需求提示词、平台选择、环境变量、幂等、重试、测试和部署检查。
Serverless functions 指的是:不需要长期管理服务器,而是在事件或 HTTP 请求到来时短时间执行代码的机制。它适合 Webhook、小型 API、图片或 CSV 处理入口、边缘重定向等场景。但它不是“没有运维”的魔法;超时、重试、密钥、权限、日志和费用都需要提前设计。
Claude Code 的价值在于把处理函数、事件样例、测试、部署说明和审查清单放在同一个上下文里。安全的做法不是让 Claude Code 直接部署,而是先写清需求,再选择运行时和平台,本地复现事件,分离配置与密钥,设计幂等性,测试失败路径,最后再让人审查公开范围和成本。
建议同时打开官方文档:AWS Lambda Documentation、Lambda Node.js 文档、Cloudflare Workers development and testing 和 Workers get started guide。站内延伸阅读包括 AWS Lambda 指南、Cloudflare Workers 指南、API 开发指南 和 密钥管理指南。
先确定适用场景
Serverless functions 最适合短时间、事件驱动、可以安全重试的工作。
| 场景 | 为什么适合 | 可让 Claude Code 起草 | 人必须确认 |
|---|---|---|---|
| 支付或表单 Webhook | 一个请求对应一个事件 | 签名校验、事件 fixture、错误响应 | 密钥、重复通知、重放规则 |
| 图片压缩或 CSV 导入入口 | 重任务可交给存储或队列 | 输入校验、job ID、结构化日志 | 文件大小、超时、失败清理 |
| 内部 JSON API | 小端点不需要常驻服务器 | handler、测试、路由 | 认证、CORS、公开范围、限流 |
| 边缘跳转和缓存 | 可在离用户更近的位置响应 | Worker 路由、缓存头、发布说明 | 缓存清理、个人信息、SEO 影响 |
flowchart LR
A[写需求提示词] --> B[选择 Lambda 或 Workers]
B --> C[本地复现事件]
C --> D[分离环境变量和密钥]
D --> E[设计幂等与重试]
E --> F[运行测试]
F --> G[部署到 dev]
G --> H[检查日志和清理路径]
给 Claude Code 的需求提示词
不要只说“做一个 serverless API”。要给它输入、输出、失败方式、可编辑文件和人工确认点。
请用 Node.js 创建一个最小 serverless function。
目标:
- 处理 POST /orders,并返回订单已接收的响应
- 可以通过 node local-test.mjs 在本地运行
- 事件格式按 AWS Lambda HTTP API v2 处理
要求:
- 说明 index.mjs、events/create-order.json、local-test.mjs、index.test.mjs
- 缺少 idempotency-key 时返回 400
- 同一个 idempotency-key 重复请求时返回相同结果
- 区分 invalid JSON、invalid input、unsupported route
- 日志用 JSON,但不能输出密钥或个人信息
- 给出部署前检查清单
限制:
- 不使用外部 npm 包
- 生产幂等存储应替换为 DynamoDB、KV 或其他持久存储
- IAM、公开 URL 和会产生费用的资源必须人工确认
平台选择
如果函数需要 AWS 事件、IAM、S3、DynamoDB、SQS 或 EventBridge,优先考虑 AWS Lambda。如果核心是边缘 HTTP 处理、轻量鉴权、重定向、缓存策略或 KV/D1/R2 组合,Cloudflare Workers 更自然。Vercel Functions 适合和 Next.js 应用一起发布的 API 或 OG 图片生成,但本文示例聚焦在 Lambda 与 Workers。
| 判断点 | AWS Lambda | Cloudflare Workers |
|---|---|---|
| 适合任务 | AWS 集成、业务 API、异步任务 | 边缘 HTTP、路由、缓存、轻量 API |
| 本地开发 | Node.js、SAM、AWS CLI | Wrangler |
| 权限 | IAM role 和 policy | binding、secret、账号权限 |
| 常见风险 | IAM 过宽、VPC/NAT 成本、日志量 | binding 不一致、运行限制、KV 一致性 |
可本地运行的 Lambda 示例
先不要上 AWS。下面的 handler 没有外部依赖,能在本地验证 HTTP API 事件、idempotency-key 和重复请求行为。
// index.mjs
import crypto from "node:crypto";
const localIdempotencyStore = new Map();
function json(statusCode, body) {
return {
statusCode,
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
};
}
function readHeader(headers = {}, name) {
const target = name.toLowerCase();
const found = Object.entries(headers).find(([key]) => key.toLowerCase() === target);
return found?.[1];
}
function parseBody(event) {
if (!event.body) return {};
const raw = event.isBase64Encoded
? Buffer.from(event.body, "base64").toString("utf8")
: event.body;
return JSON.parse(raw);
}
export async function handler(event = {}, context = {}) {
const method = event.requestContext?.http?.method ?? event.httpMethod ?? "GET";
const path = event.rawPath ?? event.path ?? "/";
const requestId = context.awsRequestId ?? crypto.randomUUID();
console.log(JSON.stringify({ level: "info", message: "request.start", requestId, method, path }));
if (method !== "POST" || path !== "/orders") {
return json(404, { error: "not_found" });
}
const idempotencyKey = readHeader(event.headers, "idempotency-key");
if (!idempotencyKey) {
return json(400, { error: "idempotency_key_required" });
}
if (localIdempotencyStore.has(idempotencyKey)) {
return json(200, { ...localIdempotencyStore.get(idempotencyKey), replay: true });
}
let body;
try {
body = parseBody(event);
} catch {
return json(400, { error: "invalid_json" });
}
if (!Number.isFinite(body.amount) || body.amount <= 0 || typeof body.currency !== "string") {
return json(400, { error: "invalid_order" });
}
const accepted = {
orderId: crypto.randomUUID(),
status: "accepted",
amount: body.amount,
currency: body.currency,
};
localIdempotencyStore.set(idempotencyKey, accepted);
console.log(JSON.stringify({ level: "info", message: "order.accepted", requestId, orderId: accepted.orderId }));
return json(202, accepted);
}
{
"version": "2.0",
"routeKey": "POST /orders",
"rawPath": "/orders",
"headers": {
"content-type": "application/json",
"idempotency-key": "demo-key-001"
},
"requestContext": {
"http": {
"method": "POST",
"path": "/orders"
}
},
"body": "{\"amount\":3200,\"currency\":\"CNY\"}",
"isBase64Encoded": false
}
// local-test.mjs
import { readFile } from "node:fs/promises";
import { handler } from "./index.mjs";
const eventPath = process.argv[2] ?? "events/create-order.json";
const event = JSON.parse(await readFile(eventPath, "utf8"));
const first = await handler(event, { awsRequestId: "local-001" });
const second = await handler(event, { awsRequestId: "local-002" });
console.log("first:", first.statusCode, first.body);
console.log("second:", second.statusCode, second.body);
node local-test.mjs events/create-order.json
这里的 Map 只适合本地演示。生产环境必须使用 DynamoDB 条件写入、数据库唯一键、Cloudflare KV/D1 或其他持久存储,因为 Lambda 的内存不能作为真实状态来源。
测试失败路径
// index.test.mjs
import crypto from "node:crypto";
import test from "node:test";
import assert from "node:assert/strict";
import { handler } from "./index.mjs";
function event(overrides = {}) {
return {
rawPath: "/orders",
headers: { "idempotency-key": crypto.randomUUID() },
requestContext: { http: { method: "POST" } },
body: JSON.stringify({ amount: 1200, currency: "CNY" }),
isBase64Encoded: false,
...overrides,
};
}
test("requires idempotency-key", async () => {
const result = await handler(event({ headers: {} }), {});
assert.equal(result.statusCode, 400);
});
test("accepts a valid order", async () => {
const result = await handler(event(), {});
assert.equal(result.statusCode, 202);
assert.equal(JSON.parse(result.body).status, "accepted");
});
test("rejects invalid JSON", async () => {
const result = await handler(event({ body: "not-json" }), {});
assert.equal(result.statusCode, 400);
});
node --test index.test.mjs
Workers 版本
Workers 的入口是 fetch(request, env)。下面的示例把幂等结果放到 KV,把 Webhook 密钥放到 Worker secret。
// src/worker.js
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (request.method !== "POST" || url.pathname !== "/orders") {
return Response.json({ error: "not_found" }, { status: 404 });
}
if (request.headers.get("x-webhook-secret") !== env.WEBHOOK_SECRET) {
return Response.json({ error: "unauthorized" }, { status: 401 });
}
const idempotencyKey = request.headers.get("idempotency-key");
if (!idempotencyKey) {
return Response.json({ error: "idempotency_key_required" }, { status: 400 });
}
const existing = await env.IDEMPOTENCY_KV.get(idempotencyKey, "json");
if (existing) {
return Response.json({ ...existing, replay: true });
}
const body = await request.json();
if (!Number.isFinite(body.amount) || typeof body.currency !== "string") {
return Response.json({ error: "invalid_order" }, { status: 400 });
}
const accepted = {
orderId: crypto.randomUUID(),
status: "accepted",
amount: body.amount,
currency: body.currency,
};
await env.IDEMPOTENCY_KV.put(idempotencyKey, JSON.stringify(accepted), {
expirationTtl: 86400,
});
return Response.json(accepted, { status: 202 });
},
};
{
"name": "serverless-orders-worker",
"main": "src/worker.js",
"compatibility_date": "2026-06-01",
"kv_namespaces": [
{
"binding": "IDEMPOTENCY_KV",
"id": "replace_with_real_kv_namespace_id"
}
]
}
npm create cloudflare@latest serverless-orders-worker
cd serverless-orders-worker
npx wrangler kv namespace create IDEMPOTENCY_KV
npx wrangler secret put WEBHOOK_SECRET
npx wrangler dev
常见陷阱和部署清单
最大的陷阱是以为函数只会执行一次。Webhook 提供方、队列、异步 Lambda 事件和浏览器都可能重试。第二个陷阱是把密钥写进环境变量后又输出到日志。第三个陷阱是权限过宽,例如 Resource: "*"。第四个陷阱是公开了 API Gateway 或 Worker 路由,却没有认证、CORS、限流、日志保留和删除计划。
部署前至少确认这些项目:
| 检查项 | 内容 |
|---|---|
| 需求 | 输入、输出、负责人、失败响应已经写明 |
| 运行时 | Lambda Node.js runtime 或 Workers compatibility date 明确 |
| 本地证明 | fixture 和 node --test 通过 |
| 环境与密钥 | 配置和密钥分离,日志不输出真实值 |
| 幂等性 | 重试不会重复收费或重复创建 |
| 超时/重试 | 慢任务进入队列或持久 job |
| 可观测性 | JSON 日志、错误率、告警、日志保留已定义 |
| 清理 | 删除命令或控制台清理步骤已记录 |
zip function.zip index.mjs
aws lambda update-function-code \
--function-name serverless-orders-dev \
--zip-file fileb://function.zip
npx wrangler deploy
最后让 Claude Code 做发布前审查:
请审查这个 serverless function。
按 blocking、non-blocking、human confirmation 分类。
检查幂等性、timeout/retry、密钥泄露、IAM 或 binding 过宽、日志个人信息、
本地测试复现性、清理步骤、官方链接和内部链接。
ClaudeCodeLab 将这类流程整理成 Claude Code 模板和产品。如果团队需要围绕 AWS 权限、CLAUDE.md、审查提示词和部署审批建立流程,可以查看 Claude Code 咨询与培训。
实际试用后,收益最大的是先创建事件 fixture。Claude Code 很快,但只有在提示词里明确重试、密钥、日志和清理要求时,它才会生成可审查的 serverless function。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
从Obsidian到CLAUDE.md的Claude Code流程:不再反复解释上下文
把 Obsidian 工作笔记整理成 CLAUDE.md 运行说明,让 Claude Code 每次都带着正确上下文开始。
Claude Code 收入 CTA 路由:从文章分流到 PDF、Gumroad 与咨询
用 Claude Code 按读者意图把文章流量分到免费 PDF、Gumroad 教材或咨询入口。
Claude Code 团队交接规则: 把审查证据、权限、回滚和收入路径一起交付
面向团队的 Claude Code 交接格式: 证据、权限、回滚、免费 PDF、Gumroad 与咨询路径都要可审查。