全书目录

第五十五篇:Cloudflare Workers 完全指南 —— 边缘驿站网络宇宙

七、代码:一个最小但完整的边缘链路

1 分钟 264 字 第 957 / 962 个阅读单元

#1. 入口 Worker:接单、查开关、做协调、写库、投递异步任务

ts
import { DurableObject } from "cloudflare:workers";

export interface Env {
  FLAGS: KVNamespace;
  DB: D1Database;
  FILES: R2Bucket;
  ORDER_PIPELINE: Queue;
  STOCK_ROOM: DurableObjectNamespace<StockRoom>;
}

type OrderPayload = {
  orderId: string;
  sku: string;
  qty: number;
  note?: string;
};

export class StockRoom extends DurableObject<Env> {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.ctx.storage.sql.exec(`
      CREATE TABLE IF NOT EXISTS inventory (
        sku TEXT PRIMARY KEY,
        stock INTEGER NOT NULL
      )
    `);
  }

  async reserve(sku: string, qty: number) {
    const row = this.ctx.storage.sql
      .exec<{ stock: number }>("SELECT stock FROM inventory WHERE sku = ?", sku)
      .one();

    const stock = row?.stock ?? 0;
    if (stock < qty) {
      return { ok: false, reason: "OUT_OF_STOCK" };
    }

    this.ctx.storage.sql.exec(
      "UPDATE inventory SET stock = stock - ? WHERE sku = ?",
      qty,
      sku,
    );

    return { ok: true };
  }
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname !== "/orders" || request.method !== "POST") {
      return new Response("Not Found", { status: 404 });
    }

    const body = await request.json<OrderPayload>();
    const flashSale = (await env.FLAGS.get("flash-sale")) === "on";

    const stock = env.STOCK_ROOM.getByName(`sku:${body.sku}`);
    const reserved = await stock.reserve(body.sku, body.qty);

    if (!reserved.ok) {
      return Response.json(reserved, { status: 409 });
    }

    await env.DB.prepare(`
      INSERT INTO orders (id, sku, qty, note, flash_sale)
      VALUES (?, ?, ?, ?, ?)
    `)
      .bind(body.orderId, body.sku, body.qty, body.note ?? null, flashSale ? 1 : 0)
      .run();

    ctx.waitUntil(
      env.ORDER_PIPELINE.send({
        orderId: body.orderId,
        note: body.note ?? "",
      }),
    );

    return Response.json({ ok: true, orderId: body.orderId });
  },
} satisfies ExportedHandler<Env>;

这段代码里每个角色都很清楚:

  • Worker 是柜台
  • KV 是活动公告栏
  • Durable Object 是库存协调室
  • D1 是订单账本
  • Queue 是后处理传送带

#2. Queue Consumer:把慢活移出主请求

ts
export interface Env {
  FILES: R2Bucket;
  DB: D1Database;
  CF_ACCOUNT_ID: string;
  CF_AIG_GATEWAY: string;
  CF_AIG_TOKEN: string; // 假设已在 AI Gateway 中配置好上游 BYOK
}

type OrderJob = {
  orderId: string;
  note: string;
};

export default {
  async queue(batch: MessageBatch<OrderJob>, env: Env): Promise<void> {
    for (const msg of batch.messages) {
      const prompt =
        msg.body.note.trim() === ""
          ? "用户没有备注,请返回“无特殊要求”。"
          : `请把这段订单备注压缩成一句客服摘要:${msg.body.note}`;

      const aiResp = await fetch(
        `https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.CF_AIG_GATEWAY}/compat/chat/completions`,
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${env.CF_AIG_TOKEN}`,
          },
          body: JSON.stringify({
            model: "openai/gpt-5-mini",
            messages: [{ role: "user", content: prompt }],
          }),
        },
      );

      const aiData = await aiResp.json<any>();
      const summary = aiData.choices?.[0]?.message?.content ?? "摘要生成失败";

      await env.FILES.put(`orders/${msg.body.orderId}/summary.txt`, summary);

      await env.DB.prepare(`
        UPDATE orders SET note_summary = ? WHERE id = ?
      `)
        .bind(summary, msg.body.orderId)
        .run();

      msg.ack();
    }
  },
} satisfies ExportedHandler<Env>;

这段代码体现的就是一句话:

用户不该在柜台前等 AI 总结跑完。


#3. 一个最小的 AI Gateway SDK 写法

ts
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: env.CF_AIG_TOKEN,
  baseURL: `https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.CF_AIG_GATEWAY}/compat`,
});

const resp = await client.chat.completions.create({
  model: "openai/gpt-5-mini",
  messages: [{ role: "user", content: "把这段文本压缩成一句标题" }],
});

这段的重点不是 SDK,而是心智:

  • 你的业务代码对接的是 AI Gateway
  • AI Gateway 再帮你治理上游模型流量