Astro 网站接入 OneSend 询盘教程:Cloudflare D1 备份小白版
这是一份给新手看的完整教程,用来给已经做好的 Astro 外贸网站接入 OneSend 询盘,并使用 Cloudflare D1 做备份,防止丢单。
真实工作流程通常是:
网站页面已经做好
-> 已经有 inquiry / contact / quote 表单
-> 再把这个表单接到 OneSend
-> 再配置 Cloudflare D1 备份
所以本文档的重点不是让你重新做一个询盘页,而是教你如何检查和改造现有表单。
这套方案的目标不是“只要 Cloudflare D1 有记录就算成功”,而是双层保障:
第一层:Cloudflare D1 能查到询盘备份
第二层:OneSend / 网站询盘后台也能查到同一条询盘
只有两边都能查到,才说明完整链路真正跑通。
你最终要实现的效果是:
买家填写询盘表单
-> 提交到你自己网站的 /f/项目名
-> Cloudflare Pages Function 接收
-> 先保存一份到 Cloudflare D1
-> 再转发到 OneSend
-> 买家跳转到 thank-you 页面
一句话理解:
表单不要直接交给 OneSend。先交给自己网站的 Cloudflare Function,让它先备份,再转发。
换句话说:
D1 是保险柜,负责防丢单。
OneSend / 网站后台是正式业务系统,负责销售查看和跟进询盘。
1. 你需要准备什么
开始前,你需要有:
- 一个 Astro 网站项目
- 网站里已经有询盘表单,或者已经确定要用哪个表单收询盘
- 网站部署在 Cloudflare Pages
- 一个 OneSend 项目路径,例如
newsite - 一个 Cloudflare D1 数据库
- 一个 Cloudflare Turnstile site key
如果你已经有其他网站接入过 OneSend,并且 D1 表已经建好,那么新网站通常只需要:
- 复用同一个 D1 数据库
- 在新站 Cloudflare Pages 里绑定 D1
- 新表单使用新的项目名
- Turnstile 允许新域名
2. 先理解几个名字
Astro
Astro 是你的网站框架。询盘页可能是:
src/pages/inquiry.astro
也可能是:
src/pages/contact.astro
src/pages/contact-us.astro
src/pages/quote.astro
具体用哪个页面不重要,重要的是找到真正的表单,并把它的 action 改成 /f/项目名。
OneSend
OneSend 是接收询盘的后端服务。你的表单最终会转发到:
https://api.oneuie.me/f/项目名
例如项目名叫 newsite,最终地址就是:
https://api.oneuie.me/f/newsite
Cloudflare Pages Function
Cloudflare Pages Function 是部署在 Cloudflare 边缘上的小后端。
它放在项目根目录:
functions/f/[[path]].js
它的作用是:
- 接收表单
- 写入 D1 备份
- 转发给 OneSend
- 处理失败兜底
Cloudflare D1
D1 是 Cloudflare 的数据库。这里用来保存询盘备份。
即使 OneSend 临时失败,只要 D1 写入成功,询盘数据就不会丢。
Turnstile
Turnstile 是 Cloudflare 的人机验证,用来减少垃圾询盘。
前端表单会生成:
cf-turnstile-response
Cloudflare Function 会把它转成 OneSend 后端需要的:
turnstile_token
3. 第一步:创建 D1 表
如果你是第一次配置 D1,需要在 Cloudflare D1 控制台执行:
CREATE TABLE IF NOT EXISTS submissions_backup (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project TEXT NOT NULL,
data TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
如果你之前其他网站已经执行过,并且新网站绑定的是同一个 D1 数据库,就不用再执行。
重复执行也不会删数据,因为这里用了:
IF NOT EXISTS
意思是:如果表不存在就创建,如果已经存在就不动。
4. 第二步:在 Astro 项目里创建 Function
在 Astro 项目根目录创建这个文件:
functions/f/[[path]].js
注意:是项目根目录,不是 src/ 里面。
目录结构应该像这样:
your-astro-site/
src/
pages/
inquiry.astro
functions/
f/
[[path]].js
package.json
然后把下面代码放进去:
export async function onRequestPost(context) {
const { request, env, params } = context;
const url = new URL(request.url);
const projectSlug = params.path && params.path.length > 0 ? params.path[0] : "contact";
const vpsUrl = "https://api.oneuie.me" + url.pathname + url.search;
let formData = null;
let redirectTarget = request.headers.get("Referer") || "/";
let backupSaved = false;
try {
formData = await request.formData();
redirectTarget = formData.get("REDIRECT_URL") || redirectTarget;
if (env.DB && typeof env.DB.prepare === "function") {
try {
const entry = {};
for (const [k, v] of formData.entries()) {
if (typeof v === "string") entry[k] = v;
}
entry.received_at = new Date().toISOString();
entry.source_url = request.headers.get("Referer") || "";
entry.cf_ray = request.headers.get("CF-Ray") || "";
await env.DB.prepare("INSERT INTO submissions_backup (project, data) VALUES (?, ?)")
.bind(projectSlug, JSON.stringify(entry))
.run();
backupSaved = true;
} catch (e) {
console.error("D1 backup failed:", e.message);
}
} else {
console.error("D1 binding DB is missing.");
}
const headers = new Headers();
["accept", "user-agent", "referer", "origin"].forEach((h) => {
const val = request.headers.get(h);
if (val) headers.set(h, val);
});
headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
const forwardBody = new URLSearchParams();
for (const [k, v] of formData.entries()) {
if (typeof v === "string") forwardBody.append(k, v);
}
const turnstileToken = formData.get("cf-turnstile-response");
if (typeof turnstileToken === "string" && turnstileToken) {
forwardBody.set("turnstile_token", turnstileToken);
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 6000);
const vpsResponse = await fetch(vpsUrl, {
method: "POST",
body: forwardBody,
headers,
signal: controller.signal,
redirect: "manual",
});
clearTimeout(timeout);
if (vpsResponse.ok || (vpsResponse.status >= 300 && vpsResponse.status < 400)) {
return vpsResponse;
}
throw new Error(`VPS returned ${vpsResponse.status}`);
} catch (err) {
console.error("Form proxy failed:", err.message);
if (backupSaved) {
return Response.redirect(new URL(redirectTarget, request.url).href, 302);
}
return new Response("Submission could not be saved. Please go back and try again.", {
status: 503,
headers: {
"content-type": "text/plain; charset=utf-8",
"x-onesend-backup": "failed",
},
});
}
}
这段代码不用每个网站都改。通常只要 OneSend 域名还是:
https://api.oneuie.me
就可以直接复用。
5. 第三步:找到并改造现有询盘表单
网站做好以后,通常已经有询盘页或联系页。先找到实际收询盘的表单文件,常见位置有:
src/pages/inquiry.astro
src/pages/contact.astro
src/pages/contact-us.astro
src/pages/quote.astro
src/components/ContactForm.astro
你不需要重做页面,只需要检查和改造表单的关键属性。
需要改的第一件事:form action
假设 OneSend 项目名是 newsite,现有表单应该改成:
<form method="POST" action="/f/newsite">
不要写成:
<form method="POST" action="https://api.oneuie.me/f/newsite">
原因是:直接提交到 OneSend 会绕过 Cloudflare Function,D1 就没有备份。
需要加的第二件事:成功跳转字段
在表单里面加:
<input type="hidden" name="REDIRECT_URL" value="/thank-you" />
如果你的感谢页不是 /thank-you,就改成你自己的成功页路径。
需要加的第三件事:Turnstile
在表单提交按钮前加:
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div
class="cf-turnstile"
data-sitekey="你的 Turnstile site key"
data-response-field-name="cf-turnstile-response"
></div>
需要保留或补齐的字段
OneSend 常用字段建议保留这些名字:
<input type="text" name="name" required />
<input type="email" name="email" required />
<input type="text" name="tel" />
<input type="text" name="add" />
<input type="text" name="product" />
<textarea name="message" required></textarea>
如果你原来的表单字段更多,可以保留。Function 会把字符串字段一起备份到 D1,并转发给 OneSend。
最小可用表单示例
下面不是要求你重新做页面,只是给你对照检查用:
---
const projectSlug = "newsite";
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Inquiry</title>
</head>
<body>
<main>
<h1>Request a Quote</h1>
<form method="POST" action={`/f/${projectSlug}`}>
<input type="hidden" name="REDIRECT_URL" value="/thank-you" />
<div style="display:none;">
<label>Keep blank</label>
<input type="text" name="honeypot" />
</div>
<p>
<label>
Product
<input type="text" name="product" />
</label>
</p>
<p>
<label>
Name *
<input type="text" name="name" required />
</label>
</p>
<p>
<label>
Email *
<input type="email" name="email" required />
</label>
</p>
<p>
<label>
Phone
<input type="text" name="tel" />
</label>
</p>
<p>
<label>
Country
<input type="text" name="add" />
</label>
</p>
<p>
<label>
Requirements *
<textarea
name="message"
required
placeholder="Product type, capacity, liquid type, fittings, quantity, destination country..."
></textarea>
</label>
</p>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div
class="cf-turnstile"
data-sitekey="你的 Turnstile site key"
data-response-field-name="cf-turnstile-response"
></div>
<button type="submit">Submit Inquiry</button>
</form>
</main>
</body>
</html>
如果你使用这个示例,把这里换成你自己的 OneSend 项目名:
const projectSlug = "newsite";
例如项目名是 bancy,就写:
const projectSlug = "bancy";
这样表单会提交到:
/f/bancy
Function 会自动转发到:
https://api.oneuie.me/f/bancy
6. 第四步:确认感谢页存在
确认网站里有成功提交后的感谢页,例如:
src/pages/thank-you.astro
如果已经有,就不用新建。只要确保表单里的:
<input type="hidden" name="REDIRECT_URL" value="/thank-you" />
和实际感谢页路径一致。
如果还没有感谢页,可以用下面这个最小版本:
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex" />
<title>Thank You</title>
</head>
<body>
<main>
<h1>Thank you</h1>
<p>We have received your inquiry and will get back to you soon.</p>
<a href="/">Return to homepage</a>
</main>
</body>
</html>
为什么要有感谢页?
- 告诉客户提交成功
- 方便后续做转化统计
- 避免用户重复提交
7. 第五步:配置 Cloudflare Pages D1 绑定
部署到 Cloudflare Pages 后,进入你的项目后台:
Cloudflare Pages
-> 选择你的网站
-> Settings
-> Functions
-> D1 database bindings
添加绑定:
Variable name: DB
D1 database: 选择你的询盘备份数据库
必须注意:
变量名必须叫 DB
因为 Function 代码里写的是:
env.DB
如果你写成 DATABASE、D1、MY_DB,代码就找不到数据库。
还要注意生产环境和预览环境:
- Production 生产环境要绑定
- Preview 预览环境如果要测试,也要绑定
8. 第六步:配置 Turnstile
进入 Cloudflare Turnstile 后台:
Cloudflare
-> Turnstile
-> 选择你的 Widget
-> Settings
-> Permitted domains
把新网站域名加进去,例如:
example.com
www.example.com
然后把 Turnstile site key 放到表单里:
<div
class="cf-turnstile"
data-sitekey="你的 Turnstile site key"
data-response-field-name="cf-turnstile-response"
></div>
注意:
- site key 可以放在前端代码里
- secret key 不要放在前端代码里
- OneSend 后端如果已经配置好 secret,新网站一般只需要把域名加入允许列表
9. 第七步:部署前检查
在本地执行:
npm run build
确认没有报错。
然后检查这些东西是否存在:
functions/f/[[path]].js
现有询盘表单页面或表单组件
thank-you 成功页
检查表单:
<form method="POST" action="/f/项目名">
不要写成:
<form method="POST" action="https://api.oneuie.me/f/项目名">
直接提交到 OneSend 会绕过 D1 备份,不推荐。
10. 第八步:本地测试分两种情况
本地测试要分清楚你是在“看页面”,还是在“测试表单提交”。
情况 1:只是看页面
如果你只是想看页面有没有做好,例如:
/inquiry
/thank-you
可以用普通 Astro 命令:
npm run dev
或者先构建再预览:
npm run build
npm run preview
这种方式通常不需要绑定 Cloudflare D1。
原因是:
- 你只是看静态页面和样式
functions/f/[[path]].js不一定会按 Cloudflare Pages Function 的方式执行env.DB是 Cloudflare 环境里的绑定,普通 Astro 预览不会自动提供
情况 2:要本地测试表单提交
如果你想本地测试:
POST /f/项目名
也就是想让表单真的经过 Cloudflare Pages Function,可以用 Wrangler:
npm run build
npx wrangler pages dev dist
如果你的 wrangler.toml 里已经配置了 D1:
[[d1_databases]]
binding = "DB"
database_name = "你的数据库名称"
database_id = "你的数据库 ID"
wrangler pages dev dist 通常会读取这个绑定。
也可以显式传入 D1:
npx wrangler pages dev dist --d1 DB=你的数据库ID
注意:本地 D1 也需要有 submissions_backup 表。否则即使绑定了 DB,插入数据也会失败。
本地建表 SQL 仍然是:
CREATE TABLE IF NOT EXISTS submissions_backup (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project TEXT NOT NULL,
data TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
本地没有 D1 会怎样
当前 Function 逻辑是:
有 env.DB:先写 D1 备份,再转发 OneSend
没有 env.DB:打印 D1 binding DB is missing.,然后继续尝试转发 OneSend
所以 D1 是询盘备份保险,不是页面渲染必需品。
但是正式上线时,仍然建议绑定 D1。否则 OneSend 或网络短暂异常时,询盘没有备份,容易丢单。
11. 第九步:上线后测试
网站部署到 Cloudflare Pages 后,打开:
https://你的域名/inquiry
提交一条测试询盘,例如:
Name: Test
Email: test@example.com
Product: Test Product
Message: This is a test inquiry.
正常情况应该是:
- 页面跳转到
/thank-you - D1 数据库有一条新记录
- OneSend 后台也有一条新询盘
这三个条件要同时满足。尤其是第 2 和第 3 条:
D1 有记录 + OneSend 有记录 = 双层保障成功
如果只有 D1 有记录,说明询盘没有丢,但正式业务系统可能没有收到,需要继续排查 OneSend。
如果只有 OneSend 有记录,说明询盘进了业务系统,但没有备份成功,D1 防丢单保险没有生效。
12. 查询 D1 是否收到询盘
在 Cloudflare D1 控制台执行:
SELECT id, project, data, created_at
FROM submissions_backup
ORDER BY id DESC
LIMIT 10;
你应该能看到:
project = 你的项目名
data = 一整段 JSON 表单数据
如果 project 是 newsite,说明 /f/newsite 路径是对的。
如果 project 是 contact,说明你的表单可能提交到了:
/f/contact
D1 查询只是第一层检查。看到 D1 有记录以后,还要继续去 OneSend / 网站询盘后台查同一条测试询盘。
13. OneSend 后台检查
确认 OneSend 后台也收到同一条测试询盘。
如果 D1 有记录,但 OneSend 没收到,说明:
- 表单没有丢
- Cloudflare 已经备份成功
- 问题在 OneSend 项目路径、Turnstile 校验、API 转发或 OneSend 后台配置
这时可以先从 D1 的 data 字段里手动补单。
如果 D1 和 OneSend 都有记录,才算正式通过测试。
建议测试时在 message 里写一个明显的标记,例如:
TEST 2026-07-02 newsite inquiry
这样你可以在 D1 的 data 里查到,也可以在 OneSend / 网站后台里查到同一条记录。
14. 推荐表单字段
基础字段:
| 字段名 | 含义 | 是否建议必填 |
|---|---|---|
name |
客户姓名 | 是 |
email |
客户邮箱 | 是 |
tel |
电话或 WhatsApp | 否 |
add |
国家或地区 | 否 |
product |
感兴趣产品 | 否 |
message |
需求说明 | 是 |
REDIRECT_URL |
成功跳转地址 | 是 |
honeypot |
反垃圾字段 | 否 |
cf-turnstile-response |
Turnstile token | 自动生成 |
工业品 B2B 网站建议在提示语里引导客户提供:
- 产品类型
- 容量
- 尺寸或可用安装空间
- 液体类型
- 应用场景
- 材料要求
- 进出口尺寸
- 阀门类型
- 接头类型
- 数量
- 目的国家
- 包装要求
- 图纸、照片或项目现场条件
这样销售收到的不是模糊的 “price?”,而是可以直接报价的项目需求。
15. 常见问题
问题 1:提交后显示 503
可能原因:
- D1 没有绑定
- D1 binding 名字不是
DB - D1 表没有创建
- Cloudflare Function 没有生效
优先检查:
Cloudflare Pages -> Settings -> Functions -> D1 database bindings
确认变量名是:
DB
问题 2:D1 有记录,但 OneSend 没有
说明表单已经被 Cloudflare 收到并备份了。
继续检查:
- OneSend 是否配置了这个项目名
- 表单 action 是否是
/f/正确项目名 - Turnstile 域名是否加入允许列表
- OneSend 后端是否正常
问题 3:OneSend 有记录,但 D1 没有
通常说明表单直接提交到了 OneSend,没有经过 Cloudflare Function。
检查表单 action 是否写错:
错误写法:
action="https://api.oneuie.me/f/newsite"
正确写法:
action="/f/newsite"
问题 4:Turnstile 显示异常
检查:
- 表单里是否加载了 Turnstile 脚本
data-sitekey是否正确- Cloudflare Turnstile 后台是否添加了当前域名
- 本地测试时域名可能不在允许列表,建议以上线域名测试
问题 5:提交成功但没有跳转感谢页
检查表单里是否有:
<input type="hidden" name="REDIRECT_URL" value="/thank-you" />
还要确认:
src/pages/thank-you.astro
这个页面真实存在。
16. 新网站接入清单
每次新网站接入 OneSend,可以照这个清单做:
- 添加
functions/f/[[path]].js - 找到网站现有 inquiry / contact / quote 表单
- 确认
/thank-you或其他成功页存在 - 表单
method="POST" - 表单
action="/f/项目名" - 表单包含
REDIRECT_URL - 表单包含 Turnstile 组件
- Cloudflare Pages 绑定 D1,变量名为
DB - D1 里有
submissions_backup表 - Turnstile 后台加入新域名
- OneSend 后台确认项目名可用
- 上线后提交测试询盘
- D1 有记录
- OneSend 有记录
- 页面能跳转 thank-you
17. 最简版本
记住这个就够了:
1. 表单提交到 /f/项目名
2. functions/f/[[path]].js 接收
3. env.DB 写入 D1 备份
4. 转发到 https://api.oneuie.me/f/项目名
5. 成功后跳转 /thank-you
核心检查点:
表单 action 不能是 OneSend 完整域名
D1 binding 必须叫 DB
Turnstile 域名必须加白
OneSend 项目名必须对应