第十章 · 交互版

Cloudflare Workers 详解不用自己买服务器,也能让代码在网上干活

这一章全是中文讲解。代码里的英文单词是固定写法,照着抄就行,每个关键地方都配了中文说明。带「运行」按钮的代码框,点一下就能在手机上看到真实结果。

1这一章学什么

一句话目标:知道 Workers 的模型——请求进来 → fetch 处理 → 返回 Response;会写最小结构、会取请求信息、会造响应、会用 env 绑定和 KV / D1,会写路由。

学完这一章,你就能看懂自己 tg-bot 那类 Worker 的骨架了:为什么它不用开服务器,为什么换个路径就能干不同的事,为什么改了绑定名就读不到数据。

2概念

① Workers 的模型,一句话说清

有人发请求进来比如有人打开你的网址,或 Telegram 发来一条消息
→
你的 fetch 函数处理你写的代码在这里干活:查数据、算结果
→
返回一个 Response把结果打包成一个「响应」发回去

没有服务器、没有端口。你不用租一台机器守在那里。CF(Cloudflare)把你的函数部署在全球的边缘节点上——离访客最近的地方——有人来请求,就近跑你的代码,跑完就结束。

② 最小结构(现代模块写法)

一个 Worker 最少长这样。记住两个入口:fetch 管「来请求了」,scheduled 管「到点了」。

export default {
  // 有请求进来时,跑这个函数
  async fetch(request, env, ctx) {
    return new Response("Hello!", { status: 200 });
  },

  // 定时触发:到点自动调用这个
  // 干刷新缓存、发提醒这类活
  async scheduled(event, env, ctx) {
    // 到点要做的事写在这里
  }
};

③ Request 能取什么

请求进来后,你能从 request 上拿到这些信息:

request.method —— 请求方法,是 GET(取东西)还是 POST(交东西)等。
request.url —— 完整网址字符串。先用 new URL(request.url) 把它拆开,下面几项都是拆开后取的。
url.pathname —— 路径部分,如 /api/user。路由判断靠它(第 ⑦ 点)。
url.searchParams.get("q") —— 查询参数的值。网址是 ?q=你好 时,这里取到的就是「你好」。
request.headers.get("Authorization") —— 某个请求头的值,常用来取对方带来的令牌。
await request.json() / .text() / .formData() —— 按格式读请求体:JSON 用第一个,纯文字用第二个,表单用第三个。前面加 await 是因为读取要等一下。
request.cf —— CF 附加的信息:访客的国家、IP 所在城市等。

④ Response 怎么造

处理完,就要造一个 Response(响应)返回。最常用的四种:

// 1)纯文本:最简单
return new Response("ok");

// 2)JSON:做接口(API)时的标准返回法
return new Response(
  JSON.stringify({ ok: true }),
  { headers: { "Content-Type": "application/json" } }
);

// 3)跳转:把访客送到另一个网址,302 是临时跳转
return Response.redirect("https://example.com", 302);

// 4)反向代理:把请求转发给上游地址,原样返回它的结果
return fetch("https://上游地址" + url.pathname);

⑤ env:绑定与密钥,你的数据都从这儿进

env 是第二个参数。你在 CF 后台绑定的东西,全部挂在它上面,代码里从这里取:

KV 命名空间 —— 想象成一个全网的「键 → 值」大本子。假设绑定名叫 MY_KV:

await env.MY_KV.get("键");          // 读
await env.MY_KV.put("键", "值");     // 写
await env.MY_KV.delete("键");        // 删
await env.MY_KV.get("键", "json");   // 直接按 JSON 读出来

D1 数据库 —— 正经的表格数据库,绑定名假设叫 DB:

// 查一行:? 是占位符,真正的值用 bind 填,防注入
await env.DB.prepare("SELECT * FROM t WHERE id=?")
  .bind(id).first();

// .all() 查多行;.run() 执行写操作(增、删、改)
await env.DB.prepare("SELECT * FROM t").all();

⑥ 你踩过的两个坑,务必记住

坑一:绑定名必须一字不差。代码里写 env.Senflare,后台绑的却是别的名字,那 env.Senflare 就是 undefined(空的、不存在),后面一读就出问题。名字对不上,程序不会自己去猜。

坑二:重传代码会冲掉绑定。用某些部署工具重新上传代码时,会把你在后台手动加的绑定一起冲掉。记住顺序:部署后先验证绑定还在,再验证功能。

⑦ 路由:一个 Worker 管多个路径

同一个 Worker,靠判断 url.pathname 给不同路径干不同的事,这就叫路由:

async fetch(request, env) {
  const url = new URL(request.url);

  if (url.pathname === "/") {
    return new Response("首页");
  }
  if (url.pathname === "/api/time") {
    return Response.json({ time: new Date().toISOString() });
  }
  // 都不匹配:返回 404,意思是「没这个页面」
  return new Response("Not Found", { status: 404 });
}

3动手试

下面三段是纯 JavaScript,浏览器里就能跑,点「运行」看真实结果。Workers 专属的部分(export default、env、KV)浏览器跑不了,放在后面只看不跑,并标了「在 Workers 里跑」。

试一试 1:拆网址 浏览器里能跑

把一个完整网址交给 new URL(...) 拆开,看路径和查询参数分别是什么。

const url = new URL("https://example.com/api/time?q=1");

console.log("完整网址 href:", url.href);
console.log("路径 pathname:", url.pathname);
console.log("查询参数 q:", url.searchParams.get("q"));
点上面的「运行」,结果会显示在这里。

试一试 2:模拟路由判断 浏览器里能跑

用一个普通函数模拟 fetch 里的路由逻辑,给它三条路径,看每条各返回什么。

// 模拟路由:只看路径,决定回什么
function route(pathname) {
  if (pathname === "/") return "首页";
  if (pathname === "/api/time") return "时间接口的数据";
  return "404 Not Found(没这个页面)";
}

console.log("/        →", route("/"));
console.log("/api/time →", route("/api/time"));
console.log("/nope    →", route("/nope"));
点上面的「运行」,结果会显示在这里。

试一试 3:造一个 JSON 响应 浏览器里能跑

Response 浏览器里也有。造好之后再把它的文本读出来,看看 JSON 响应长什么样。

const res = new Response(
  JSON.stringify({ ok: true }),
  { headers: { "Content-Type": "application/json" } }
);

console.log("状态码 status:", res.status);
console.log("内容类型:", res.headers.get("Content-Type"));
const text = await res.text();
console.log("响应文本:", text);
点上面的「运行」,结果会显示在这里。

只看不跑 · 示例 A:最小结构 在 Workers 里跑

export default 是把整个 Worker 交出去的写法,只有 Workers 环境认它,浏览器里跑不了,记住形状就行。

export default {
  async fetch(request, env, ctx) {
    return new Response("Hello!", { status: 200 });
  }
};

只看不跑 · 示例 B:KV 读写三行 在 Workers 里跑

env.MY_KV 是后台绑进来的,浏览器里没有这个东西,所以这段只能在 Workers 里跑。

await env.MY_KV.put("count", "1");       // 写:存进去
const v = await env.MY_KV.get("count");  // 读:取出来,v 是字符串 "1"
await env.MY_KV.delete("count");         // 删:抹掉

4练一练

共 5 题。点一下选项,立刻判对错:选对了会讲为什么对,选错了会讲错在哪。

已答 0 / 5答对 0 题

5适用案例

🤖

案例一:tg-bot 的骨架,就是这一章的模型

你的 Telegram 机器人就是一个 Worker:Telegram 把消息以请求的形式发进来,fetch 接住、处理、回话;每天的到期提醒、定时提醒,则靠 scheduled 到点自动跑。一个 fetch + 一个 scheduled,就是它的全部骨架。

🧭

案例二:一个 Worker 管好几条路径

不用为每个功能部署一个 Worker:/ 返回首页,/api/time 返回时间接口,其他没登记的路径统一回 404。判断的依据就是 url.pathname——上面「试一试 2」里模拟的正是这套逻辑。

⚠️

案例三:两个真踩过的坑

第一,绑定名一字不差:代码写 env.Senflare,后台就必须绑这个名,差一个字母就是 undefined。第二,重传冲绑定:部署工具重传代码可能冲掉后台手加的绑定。所以每次部署后,顺序永远是先验绑定还在不在,再验功能好不好。

下一章:模块文件格式 →