为 Worker 添加工单搜索

CloudflareBeginner
立即练习

介绍

支持 API 需要查找未关闭的工单,并安全地创建、更新和删除单条记录。你将把 Worker 连接到 D1,实现参数化 SQL 访问,并测试 API 如何处理缺失记录和无效输入。

HTTP 路由器已提供,因此本实验的重点是数据库集成。这个独立实验使用一个临时 Worker 和一个 D1 数据库,同时保留独立的本地数据。

请使用自己的学习账号和一台全新的 VM。初始化过程会先准备 Node.js 22.22.0,然后在 /home/labex/project/ticket-database 下运行 npm install,安装项目本地的 Wrangler 4.131.1 以及评测所需的依赖。直接依赖的版本已固定;安装过程会创建自己的 lockfile。初始化过程中不会登录云端,也不会执行涉及评测数据库的操作。在个人计算机上,请在项目目录中运行 npm install --save-dev wrangler@4.131.1,安装相同版本的 Wrangler。

本实验使用少量合成记录,符合 D1 免费额度。现有账号的使用量也会计入这些额度。你不需要购买域名。在确认资源已删除并完成注销之前,请保留这台 VM。

授权此 VM 并选择账号

在此步骤中,你会将这台全新的终端连接到自己的学习账号。仅登录 Dashboard 并不会授权这台 VM。D1 权限允许创建数据库、修改 SQL 和删除数据库;Workers 权限允许部署;KV 权限支持 Wrangler 清理资源时获取资源清单。授权前请查看实际的授权页面,包括 Background Access。

打开已准备好的项目并检查固定的 CLI 版本:

cd /home/labex/project/ticket-database
npx wrangler --version

预期输出为 4.131.1。开始设备授权;--device 会显示浏览器验证码,--browser=false 会由你自行决定是否打开浏览器:

npx wrangler login --device --browser=false --scopes account:read user:read d1:write workers_scripts:write workers_kv:write

在浏览器中打开终端显示的 URL,输入当前验证码,确认学习账号及权限,然后完成授权。等待终端确认成功。不要将密码或令牌粘贴到项目文件中。

npx wrangler whoami --json

检查 loggedIn: true,然后读取账号的 nameid,即使列表中只有一个账号也要读取。将目标账号的 ID 复制到下面的配置中。下面的 Shell 变量使用 6 个随机字节(12 个十六进制字符),避免与其他学习者的资源名称冲突。Here-document 会将 JSON 两行之间的内容写入文件;其中的 $RUN 会展开。

$schema 前的反斜杠会保留这个 JSON 键的原样;$RUN 仍会展开为本次运行的唯一名称。

RUN=labex-c04-d02-$(openssl rand -hex 6)
cat > wrangler.jsonc <<JSON
{
  "\$schema": "./node_modules/wrangler/config-schema.json",
  "name": "$RUN",
  "account_id": "YOUR_ACCOUNT_ID",
  "main": "src/index.js",
  "compatibility_date": "2026-09-15",
  "workers_dev": true,
  "preview_urls": false
}
JSON

运行这段命令前,先将 YOUR_ACCOUNT_ID 替换为实际账号 ID。保持此终端打开,以便 RUN 变量继续可用。name 用于标识本次运行;account_id 用于选择执行云端操作的账号。该文件是普通 JSON,同时也是有效的 JSONC。写入此文件不会部署 Worker。

准备独立的本地和远程数据

在此步骤中,你会创建 D1 连接,并填充熟悉的工单表。初始化过程已经在 src/index.js 中提供 HTTP 路由和输入验证;缺少的存储函数位于 src/store.js。这样可以将你的工作集中在 SQL 访问上。

创建一个临时云端数据库。--binding DB 为应用代码提供简短名称,--update-config 会将数据库的实际名称和 UUID 写入 wrangler.jsonc--use-remote=false 则保持开发过程使用本地数据库:

npx wrangler d1 create "$RUN-db" --binding DB --update-config --use-remote=false

读取创建出的名称和 ID,然后检查已保存的绑定:

cat wrangler.jsonc

DB 条目必须指向本次运行创建的数据库。绑定是代码与资源之间配置好的连接。它的 UUID 用于标识云端数据库,而 --local 使用的是这台 VM 中独立的 SQLite 数据库。运行 SQL 命令时,始终明确指定 --local--remote

cat schema.sql
npx wrangler d1 execute DB --local --file schema.sql
npx wrangler d1 execute DB --remote --file schema.sql

现在,本地和远程两个目标中都应有相同的两条种子工单。DB 是已提供处理程序通过 env.DB 接收的名称;它必须与配置中的绑定名称一致。

实现参数化 CRUD

在此步骤中,你将实现 CRUD:创建、读取、更新和删除。预处理语句会将 SQL 结构与输入分开。每个 ? 都是参数占位符;.bind(...) 按顺序提供参数值。即使用户输入看起来无害,也绝不要将其直接拼接到 SQL 中。

WHERE 用于限制受影响的记录。.all() 返回一个结果对象,其中的 results 字段是行数组。.first() 返回一行记录或 null。SQLite 的 RETURNING 子句可以直接返回已更改的行,无需再次查询。删除操作使用 .run(),其返回值中的 meta.changes 可用于判断记录是否实际存在。

写入存储模块:

cat > src/store.js <<'JS'
export async function list(db, status) {
  const query = status === null
    ? db.prepare('SELECT id, subject, status, source FROM tickets ORDER BY id')
    : db.prepare('SELECT id, subject, status, source FROM tickets WHERE status = ? ORDER BY id').bind(status);
  const { results } = await query.all();
  return results;
}
export async function get(db, id) {
  return db.prepare('SELECT id, subject, status, source FROM tickets WHERE id = ?').bind(id).first();
}
export async function create(db, subject) {
  return db.prepare("INSERT INTO tickets (subject, source) VALUES (?, 'api') RETURNING id, subject, status, source").bind(subject).first();
}
export async function update(db, id, status) {
  return db.prepare('UPDATE tickets SET status = ? WHERE id = ? RETURNING id, subject, status, source').bind(status, id).first();
}
export async function remove(db, id) {
  const result = await db.prepare('DELETE FROM tickets WHERE id = ?').bind(id).run();
  return result.meta.changes === 1;
}
JS

阅读 src/index.js,了解已提供的路由如何使用这些函数。缺失记录会转换为受控的 404 响应;格式错误的输入会转换为 400 响应;捕获到的数据库故障会转换为 503 响应,同时不会暴露 SQL 内部信息。

将本地服务器作为后台任务启动,以便继续使用当前终端:

npx wrangler dev --ip 0.0.0.0 > dev.log 2>&1 &

读取启动日志,并等待出现监听消息:

cat dev.log
curl -i http://localhost:8787/tickets?status=open

预期返回 HTTP 200,并且只包含工单 1。服务器使用的是本地数据库。记住终端显示的任务编号,以便清理。

在本地测试写入操作和被拒绝的输入

在此步骤中,你不仅要测试成功读取,还要测试其他行为。curl -i 会显示 HTTP 状态和响应头;-H 用于提供 JSON 内容类型;对于 POST 请求,-d 默认会发送请求体。

创建一个包含类似 SQL 标点符号的主题:

curl -i http://localhost:8787/tickets -H 'Content-Type: application/json' -d "{\"subject\":\"Printer ' OR 1=1 --\"}"

预期返回 201,并且主题内容保持为数据。将返回结果中的数字 id 复制到下面的 TICKET_ID;不要假设重复测试后 ID 仍然相同:

TICKET_ID=YOUR_RETURNED_ID
curl -i http://localhost:8787/tickets/$TICKET_ID
curl -i -X PATCH http://localhost:8787/tickets/$TICKET_ID -H 'Content-Type: application/json' -d '{"status":"closed"}'
curl -i -X DELETE http://localhost:8787/tickets/$TICKET_ID
curl -i http://localhost:8787/tickets/$TICKET_ID

预期依次得到:读取操作返回 200;更新操作返回包含 closed 的 200;删除操作返回没有响应体的 204;最后一次读取返回 404 和 {"error":"not_found"}。最初的两条工单必须保持不变。

发送格式错误的 JSON 和无效状态值:

curl -i http://localhost:8787/tickets -H 'Content-Type: application/json' -d '{'
curl -i -X PATCH http://localhost:8787/tickets/1 -H 'Content-Type: application/json' -d '{"status":"lost"}'

预期分别返回 HTTP 400,并包含 invalid_jsoninvalid_status。SQL 参数化可以防止输入被当作 SQL 执行,而应用层验证会拒绝超出业务规则的值。这两者解决的是不同的问题。

部署并测试绑定的数据库

在此步骤中,你将发布处理程序及其 D1 绑定。远程数据库已经填充数据;部署不会复制本地行。

npx wrangler deploy

将部署输出中的实际 https://...workers.dev URL 复制到 Shell 变量中。这是一个临时的合成 API,因此测试完成后要将其删除:

URL='YOUR_DEPLOYED_HTTPS_URL'
curl -i "$URL/tickets?status=open"

预期返回 200 和工单 1。如果全新部署暂时返回平台错误,请等待几秒,然后在一分钟内重复执行此读取操作。只有在状态和 JSON 都符合预期后才能继续;如果持续失败,需要进行调查。

在远程 API 上重复执行 CRUD 操作,并使用远程 API 自己返回的 ID:

curl -i "$URL/tickets" -H 'Content-Type: application/json' -d '{"subject":"Remote test"}'
TICKET_ID=YOUR_RETURNED_ID
curl -i -X PATCH "$URL/tickets/$TICKET_ID" -H 'Content-Type: application/json' -d '{"status":"closed"}'
curl -i -X DELETE "$URL/tickets/$TICKET_ID"
curl -i "$URL/tickets/$TICKET_ID"
curl -i "$URL/tickets"

预期依次返回 201、200、204、404,最后列出两条未改变的种子工单。在 Dashboard 中打开本次运行对应的准确 Worker,并进入其 Bindings 视图。确认 DB 指向你的数据库;点击数据库链接进行只读检查。已保存的本地绑定不能证明已部署的连接正确。

已部署 Worker 与 DB 绑定

此示例显示已部署的 Worker 通过 DB 连接到其 D1 数据库。随机后缀标识本次示例运行,你的资源名称会不同。表格中的 Value 链接会打开部署绑定所选的数据库。

删除临时资源

在此步骤中,你会在 VM 仍处于授权状态时,只删除本实验创建的资源。请先完成所有功能检查。在确认删除完成前,保留配置文件。

npx wrangler delete

确认提示中只有本次运行配置里的 Worker 名称。

npx wrangler d1 delete DB

检查提示内容,并确认其中只有本次运行创建的数据库。然后列出数据库:

npx wrangler d1 list --json

成功响应中不应再出现你记录的数据库名称和 UUID。其他资源可以保留。如果出现身份验证或网络错误,不能据此确认删除成功:先解决访问问题,再重新执行查询,然后继续操作。执行此步骤的验证时,必须保持登录状态。

同时停止本地开发任务。列出任务,并只终止你启动的 wrangler dev 任务(如果任务编号不同,请替换 %1):

jobs
kill %1

结束此 VM 的授权

在独立的删除检查通过后,再执行此步骤结束授权。注销会删除 Wrangler 在此 VM 上保存的授权信息;仅关闭 VM 并不能清理云端资源。

npx wrangler logout
npx wrangler whoami --json

预期显示 loggedIn: false。未认证的查询可能以非零状态退出;只有当结构化响应明确表示你已注销时,这种结果才符合预期。完成验证后,关闭实验环境。

总结

你练习了为 Worker 添加工单搜索。你检查了可观察的数据库结果,明确保留了所选账号和本地状态,并在注销前删除了临时资源。