使用 Cloudflare Workers 制作简单的表单接收并存储到 D1 数据库
搭建网站通常会需要一个收集用户反馈的表单,在这里公开一个 ZeoSeven 所使用的表单接收并保存的 Worker ,可以复制粘贴即用,当然,弄清楚它的原理会更好些 ...
以 Cloudflare Workers 为计算平台,首先,需要在 Cloudflare 仪表板 dash.cloudflare.com 中创建一个输出 Hello World 的默认 Worker 。创建完成后在你的 Worker 配置页右上角通常会出现一个 “编辑代码” 的蓝色按钮,单击以开始编写,但想要开始编写可能要等待很长时间的浏览器端 VSCode 加载 ...
准备资源
1. 一个域名,因为默认的 *.workers.dev 在中国大陆无法连接
2. 创建好的新 Cloudflare Workers
3. 创建好的 D1 数据库,并绑定到 Worker ,绑定的变量名称使用 DB
4. D1 数据库新建一个 Forms 表,表内新建 1 个 data 列,并使用 text 类型
开始
需要使用 默认输出、异步处理和 fetch API 作为一个 “大框框” 来运行其中的 JavaScript 代码。
就像这样:
export default { async fetch(request, env) { // ... } };
在 async fetch 中获取请求的路径并使用 if 条件,以确保只有在特定条件下才能触发 Worker 并在不满足条件时返回 403 错误,当然也可以是其它错误 ... 提高安全性的同时还可以使用 if...else 链在 1 个 Worker 中执行不同的任务。
let path = new URL(request.url).pathname; if (request.method === 'POST' && path === '/') { // ... } else { return new Response('403 Forbidden' ,{ headers: { 'Content-Type': 'text/plain' }, status: 403 }); }
可根据你的前端表单请求方法 method 来设置 if 块条件中的 request.method 值。
if 块内的核心代码:一个获取 formData 后转换为 Object 并输出到自定义模板,再定义一个 NoneFormData 标识来处理空表单的情况,当然,记录一下表单提交时的北京时间通常被认为是更好的。
try { // 等待 formData 可用后获取 const FormData = await request.formData(); // 将表单数据转换为 Object const getFormData = Object.fromEntries(FormData.entries()); // 定义一个空字符串作为最终输出 let FormDataOutput = ''; // 定义一个空表单内容的标识 let NoneFormData = true; // 循环获取表单的 key 和 value for (const [key, value] of Object.entries(getFormData)) { // 条件判断:键的值需有值才保存,不需要可删除 if 块 if (value.trim() !== '') { // 既然有值了,则将空表单内容标识设置为 false NoneFormData = false; // 输出格式化后的内容 FormDataOutput += `${key}:${value}`; } } // 如果没有表单数据,则忽略,有实际表单,则执行存储 if (!NoneFormData) { // 创建一个新的 Date 时间并使用计算北京时间后输出到 FormDataOutput FormDataOutput = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60 * 1000 + 8 * 60 * 60 * 1000).toISOString().replace('Z', '').replace('T', ' ') + FormDataOutput; // 从 env.DB 变量获取 D1 数据库存储到 Forms 表中的 data 列并等待存储完成, SQL 语句中的表名和列名可根据实际 D1 数据库表列名自定义 await env.DB.prepare(`INSERT INTO "Forms" (data) VALUES (?)`).bind(FormDataOutput).run(); // 成功提示,可以返回内容或重定向 return new Response('200 OK' ,{ headers: { 'Content-Type': 'text/plain' }, status: 200 }); } } catch (err) { // 保存失败提示,通常返回错误信息 return new Response(`<pre>${err.toString()}</pre>` ,{ headers: { 'Content-Type': 'text/html' }, status: 500 }); }
如果你不想直接返回内容而是重定向,可以使用
// 永久重定向 return Response.redirect('https://example.org/success', 301); // 临时重定向 return Response.redirect('https://example.org/success', 302);
这样的整体实现了 1 个 Worker 多个功能,只要满足其 if...else 链的条件,比如说在接收表单的逻辑之后,判断 GET 路径是否为 /robots.txt 则返回 robots 内容:
if ( ... ) { ... } else if (request.method === 'GET' && path === '/robots.txt') { return new Response('User-agent: *\nDisallow: /', { headers: { 'Content-Type': 'text/plain' }, status: 200 }); }
最后,在 Worker 的 “设置” 选项页面,添加 1 个 “域和路由” 即可使用域或路径来触发 Worker ,如果是路由,则域名需要在 Cloudflare DNS 中为 已代理状态 ,比较推荐使用自定义域,使 Worker 以独立的方式更佳的工作,其实本篇中的实现对路径 path 进行了判断,如果要按照本篇直接使用,则应使用自定义域并且前端表单的请求方法 method 需要是 POST 。
扩展:前端表单示例,可以和此 Worker 配合使用,其中 https://example.org 应替换为你的 Worker 路由或自定义域。
<form action='https://example.org' method='POST'> <label>姓名:<input type='text' name='name' /></label> <label>邮箱:<input type='email' name='email' /></label> <label>电话:<input type='tel' name='tel' /></label> <label>留言:<textarea name='message'></textarea></label> <button type='submit'>提交</button> </form>