07_web · M6.1 最小可发布部署方案
07_web · M6.1 最小可发布部署方案
摘要
本方案定义 Xisound 官网(07_web/xisound-website)M6.1 阶段的最小可发布部署方案:本地 npm run dev 跑通完整留资闭环(表单提交 → SQLite 存储 → 邮件通知 / 企微推送)。
本文档不替用户拍板,而是对 3 项待定决策(B3 邮件服务 / B6 轻量 CRM / B7 企微 webhook)以及 2 项凭据(P1/P2)列出作用说明 + 每种做法的优劣对比,供用户后续决定。
用户已拍板项(直接写入):adapter @astrojs/node (standalone) · 数据库 SQLite · 账号/签名URL/部署 本轮不做。
本文档与既有规划的关系
AlgoDepartment/07_web/CLAUDE.md已规划 M6 全阶段(本地 + 阿里云 ECS + Vercel 海外)· 本文档聚焦 M6.1 子阶段(仅本地)AlgoDepartment/07_web/doc/M6-deployment-guide.md已有 M6 完整部署指南 · 本文档为其子章节的拍板依据- 本文档落地在 D3 架构级 · 补充 07_web/xisound-website 的实现细节将在 D4 · web-astro
1. 现状与目标
1.1 当前状态(2026-05-06 探索结果)
| 维度 | 现状 |
|---|---|
| 框架 | Astro 6.2.1 + Tailwind v4(@theme CSS) + TypeScript Strict |
| Node | >=22.12.0(已在 package.json engines 声明) |
| 包管理器 | npm + 淘宝镜像 https://registry.npmmirror.com |
| 输出 | output: 'static'(纯静态 · 35 页 build) |
| Adapter | 未安装(astro.config.mjs 注释预留 @astrojs/node mode: standalone) |
| API endpoint | src/pages/api/lead.ts 是 stub(prerender = true) · 仅占位 |
| DB | 无 · 无 better-sqlite3 依赖 |
| 邮件 / 企微 | 无代码 · 无凭据 |
.env.example |
不存在 · 需新建 |
data/ 目录 |
不存在 · 需新建(放 leads.db) |
| M5.8.1 | ✅ 已完成 · 0 client:* 指令 · 静态 build 可用 |
1.2 M6.1 目标
一句话目标
本地 npm run dev(端口 4321)能打开官网、在留资表单提交一条线索,三个副作用必须都跑通:
1. 写入 ./data/leads.db(SQLite)
2. 发送邮件到运营邮箱(若 B3 选了 M1-M3)
3. 推送消息到企微机器人(若 B7 选了 W1)
任一项因凭据缺失(P1/P2)→ 降级为 console.log 打印,但代码路径必须完整(不能跳过)。
1.3 不在 M6.1 范围内的项(M6.2-M6.4 延后)
| 延后项 | 延后到 |
|---|---|
| 账号系统(登录 / 注册 / session) | M6.2 |
| 下载签名 URL(白皮书 / SDK 等受保护资源) | M6.3 |
| 生产部署(阿里云 ECS / Vercel 海外 / Nginx / ICP) | M6.4 |
| PostgreSQL 升级 | M6.4 |
| Sitemap 动态化 / SSR ISR | M6.5+ |
2. 用户已拍板项(直接写入 M6.1)
| 决策 | 选择 | 含义 |
|---|---|---|
| adapter | ✅ @astrojs/node standalone |
最小心智负担 · 自带 HTTP server · 无需 Nginx · 部署即 node ./dist/server/entry.mjs |
| output 模式 | ✅ 'hybrid' |
大部分页面仍 SSG 预渲染 · 仅 /api/lead 等 endpoint 走 SSR |
| 数据库 | ✅ SQLite(文件 ./data/leads.db) |
零运维 · 文件即数据库 · M6.4 再升 PostgreSQL |
| 数据库 driver | ✅ better-sqlite3(推荐) |
同步 API · 性能最佳 · Node 原生扩展 · 无需 ORM 开销 |
| 账号系统 | ❌ 本轮不做 | 延后 M6.2 |
| 下载签名 URL | ❌ 本轮不做 | 延后 M6.3 |
| 生产部署 | ❌ 本轮不做 | 延后 M6.4 |
3. 【B3 · 邮件服务】作用说明 + 4 选项优劣对比
3.1 作用说明
邮件服务的作用:收到客户留资后,自动给运营邮箱发一封通知邮件,包含表单字段(姓名 / 公司 / 邮箱 / 电话 / 留言 / 来源页)。运营同事无需登录后台即可在手机收件箱看到新线索。
在 M6.1 里的位置:本地 npm run dev 时,表单提交 → SQLite 写入成功后 → 异步触发邮件发送(不阻塞响应)。
为什么不是必选:M6.1 最小闭环可以只存 SQLite + 企微推送(跳过邮件),后续再补。但推荐至少选一个,因为企微群推送对于不在群里的管理层不可见。
3.2 4 选项优劣对比
| 维度 | M1 · Resend | M2 · 阿里云 DirectMail | M3 · SMTP(nodemailer) | M4 · 暂不发邮件 |
|---|---|---|---|---|
| 适用地区 | 海外首选 · 国内可用但延迟 | 国内首选 · 阿里云生态 · 海外慢 | 全球通用 · 延迟取决于 SMTP 服务商 | 无 |
| 免费额度 | 3000 封/月 | 200 封/日(≈ 6000 封/月) | 看 SMTP 提供方(Gmail 500/日,QQ 无明确上限) | 无 |
| API 简洁度 | ⭐⭐⭐⭐⭐ 极简 · 3 行 axios POST | ⭐⭐⭐ SDK 调用 · 需签名鉴权 | ⭐⭐⭐⭐ nodemailer 5 行代码 | N/A |
| 凭据要求 | 1 个 API Key | AccessKey + AccessSecret + 域名备案 + 发件人授权 | SMTP host + port + user + password | 无 |
| 域名备案 | 无需(可先用 onboarding@resend.dev 测试) |
必须(国内 ICP 备案 + 阿里云邮件推送域名验证) | 按 SMTP 服务商要求(Gmail/QQ/163 无需) | 无 |
| 国内送达率 | 中等(依赖 DNS 路由) | ⭐⭐⭐⭐⭐ 极高(阿里云基础设施) | 不稳定(Gmail 可能被国内邮箱服务商标垃圾) | N/A |
| 国外送达率 | ⭐⭐⭐⭐⭐ 极高 | 中等偏低(国际路由) | 中等(看 SMTP 服务商) | N/A |
| 被标垃圾风险 | 低 · Resend 自动处理 SPF/DKIM/DMARC | 低 · 阿里云自动配置 | 高(Gmail/QQ 发来的邮件更易被拦) | N/A |
| 运维成本 | 极低 · 纯 SaaS | 低 · 阿里云控制台 | 中(自管 SMTP 账号) | 无 |
| 单价(超免费) | $20 / 月 / 50K 封 | ¥10 / 万封 | 免费(自建)/ 按 SMTP 服务商 | 无 |
| 合规性 | 海外服务 · 数据出境 | 国内服务 · 数据境内 | 看 SMTP 服务商所在地 | 无 |
| M6.1 推荐度 | ⭐⭐⭐⭐ 最快上手 · 无需备案 · 测试域名即用 | ⭐⭐⭐ 若域名已备案则最佳 · 否则等待备案耗时 | ⭐⭐ 最通用但易被拦 · 不推荐生产 | ⭐⭐ 最简 · 但丢失 "管理层可见" 能力 |
3.3 典型代码示例对比
M1 Resend(最简):
await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.RESEND_API_KEY}` },
body: JSON.stringify({ from: 'onboarding@resend.dev', to: OPS_EMAIL, subject, html })
});
M2 阿里云 DirectMail(中等复杂):需 @alicloud/dm20151123 SDK + AccessKey 签名,约 30 行。
M3 SMTP nodemailer(最经典):
const transport = nodemailer.createTransport({ host: 'smtp.qq.com', port: 465, secure: true, auth: { user, pass } });
await transport.sendMail({ from, to: OPS_EMAIL, subject, html });
M4 无邮件:只有 console.log('[M6.1-email-skipped]', leadData)。
3.4 决策建议矩阵
| 您的情况 | 建议 |
|---|---|
| 官网未备案 · 想快速跑通 | M1 Resend(测试域名即用) |
| 域名已备案 · 国内客户为主 | M2 阿里云 DirectMail |
| 已有公司 Gmail/QQ 企业邮箱 | M3 SMTP(但注意被标垃圾) |
| 只想看到企微群推送即可 | M4 暂不发邮件 |
4. 【B6 · 轻量 CRM 同步】作用说明 + 3 选项优劣对比
4.1 作用说明
CRM 同步的作用:除了 SQLite 存一份,是否同时写到一个"运营同事能直接看、能筛选、能导出"的外部表格?SQLite 文件对非技术同事不友好(需 DB Browser)。
在 M6.1 里的位置:这是可选功能,不影响 SQLite 主链路。M6.1 最小闭环可以只存 SQLite,后续再考虑外部同步。
4.2 3 选项优劣对比
| 维度 | C1 · 不同步(SQLite only) | C2 · 飞书多维表格 | C3 · 企微机器人推送即可 |
|---|---|---|---|
| 实现复杂度 | 0 · 无额外工作 | 高 · 需 OAuth / app_id / app_secret / table_id | 低 · 一个 Webhook URL 即可 |
| 运营查看方式 | 写个 /admin/leads 只读页(需做) 或 用 DB Browser |
飞书 APP 内查表 · 可排序筛选导出 | 企微群看消息(滚动刷过即消失) |
| 多人协作 | ❌ 只能本地 | ⭐⭐⭐⭐⭐ 飞书原生协作 | ⭐⭐ 群消息会被刷走 |
| 筛选/排序/导出 | ❌ 需自建 UI | ⭐⭐⭐⭐⭐ 飞书原生 | ❌ 无 |
| 历史追溯 | ⭐⭐⭐⭐⭐ SQLite 永久保留 | ⭐⭐⭐⭐⭐ 飞书永久保留 | ⭐⭐ 企微仅保留 7 天 |
| 凭据准备 | 无 | 需飞书开发者账号 + 自建应用 + 授权 + 表格预建 | 1 个 webhook URL |
| 国内可用性 | N/A | ⭐⭐⭐⭐ 飞书国内可用 | ⭐⭐⭐⭐⭐ 企微国内强 |
| 成本 | 免费 | 免费(飞书免费版) | 免费 |
| 数据主权 | ⭐⭐⭐⭐⭐ 本地文件 | 字节跳动服务器 | 腾讯服务器 |
| M6.1 推荐度 | ⭐⭐⭐⭐ 最简 · 无负担 | ⭐⭐⭐ 若已用飞书则最佳 | ⭐⭐⭐⭐ 运营通知 + 群协作 |
4.3 三选项并非互斥
| 组合 | 效果 |
|---|---|
| C1 only | 只存 SQLite · 最简 |
| C1 + C3 | SQLite + 企微推送(推荐 minimum) |
| C1 + C2 + C3 | 全覆盖(SQLite 主存 + 飞书看板 + 企微群通知) |
4.4 决策建议矩阵
| 您的情况 | 建议 |
|---|---|
| 早期 · 留资量小 · 只想跑通 | C1(SQLite only) |
| 公司用飞书 · 运营习惯用多维表格 | C2(配置一次,长期受益) |
| 公司用企微 · 不想引入飞书 | C3(企微推送 · 与 B7 同源) |
耦合提示
如果 B7 已选 W1(有企微 webhook),B6 选 C3 几乎零额外工作 —— 就是同一个 webhook 复用:发一条消息即可。
5. 【B7 · 企微机器人 Webhook】作用说明 + 3 选项优劣对比
5.1 作用说明
企微机器人的作用:每收到一条留资,在企微群里推一条消息(Markdown 格式),让群里的运营/销售立即看到。
在 M6.1 里的位置:独立副作用。即使未准备 webhook URL,代码也应写好 sendWeworkBot() 函数(URL 留空则 no-op · 控制台打印),M6.4 部署时填入 URL 即激活。
为什么重要:响应速度 >> 邮件(销售在群里 30 秒看到 vs 邮件 10 分钟才查)。
5.2 3 选项优劣对比
| 维度 | W1 · 已有 webhook URL | W2 · 暂无 · 写代码留空 no-op | W3 · 本轮完全跳过 |
|---|---|---|---|
| 当前准备工作 | 0 · 填到 .env.local 即用 |
0 · 不需凭据 | 0 · 不写代码 |
| M6.1 立即可用 | ✅ 可收到消息 | ❌ 仅控制台打印 | ❌ 完全无 |
| M6.4 迁移成本 | 0 · 改 .env.production |
0 · 改 .env 即激活 |
高 · 需重写 sendWeworkBot |
| 代码完整性 | ⭐⭐⭐⭐⭐ 最完整 | ⭐⭐⭐⭐⭐ 最完整(只是 URL 空) | ⭐⭐ 缺一个副作用 |
| 降级友好度 | N/A | ⭐⭐⭐⭐⭐ 完美 · 无侵入 | N/A |
| M6.1 推荐度 | ⭐⭐⭐⭐⭐ 最佳 | ⭐⭐⭐⭐ 足够 | ⭐⭐ 不推荐(代码不完整) |
5.3 创建企微机器人的步骤(若选 W1)
- 企业微信群 → 右上角 ⋯ → 添加群机器人 → 新建
- 配置名称 / 头像
- 复制 Webhook URL(形如
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx-xxx-xxx) - 填入
.env.local的WEWORK_WEBHOOK_URL
5.4 典型消息格式
{
"msgtype": "markdown",
"markdown": {
"content": "## 🎯 新线索\n- 姓名:张三\n- 公司:XX 车企\n- 邮箱:<zhang@xx.com>\n- 电话:13800000000\n- 留言:想了解 XiDSP 方案\n- 来源:<https://joysnd.com/products/xidsp>"
}
}
5.5 决策建议矩阵
| 您的情况 | 建议 |
|---|---|
| 公司用企微 · 知道 webhook 怎么建 | W1(10 分钟搞定) |
| 公司用企微 · 先不折腾 webhook | W2(代码先写好,后补 URL) |
| 公司只用飞书 / 钉钉 | W3(本轮跳过;B6 选 C2 飞书补偿) |
6. 【C 类凭据】P1 + P2 · 处理"暂无"的降级策略
6.1 P1 · 邮件服务凭据
需要的凭据(因 B3 选项不同而异):
| B3 选项 | 需要的凭据 | 降级行为(留空时) |
|---|---|---|
| M1 Resend | RESEND_API_KEY |
console.log('[email-skipped] no RESEND_API_KEY', leadData) |
| M2 阿里云 | ALI_DM_ACCESS_KEY + ALI_DM_ACCESS_SECRET + ALI_DM_REGION + ALI_DM_FROM_ADDR |
同上降级打印 |
| M3 SMTP | SMTP_HOST + SMTP_PORT + SMTP_USER + SMTP_PASS |
同上降级打印 |
| M4 无 | 无 | N/A |
代码策略:
// src/lib/email.ts
export async function sendEmail(data: LeadData) {
const apiKey = import.meta.env.RESEND_API_KEY;
if (!apiKey) {
console.log('[M6.1-email-skipped] no RESEND_API_KEY', data);
return { skipped: true };
}
// 真实发送...
}
6.2 P2 · 运营邮箱地址
需要的凭据:1 个接收通知的邮箱地址(如 ops@joysnd.com / sales@joysnd.com / 您自己的邮箱)
处理方式:
- 提供真实地址 → 写入 .env.local 的 OPS_EMAIL
- 暂无 → 使用占位 ops@joysnd.com(代码不会报错 · 但真实发送会 404 · 降级打印也无伤大雅)
7. 项目结构与代码布局(M6.1 完成后的目标)
07_web/xisound-website/
├── astro.config.mjs ← 修改:output 'static' → 'hybrid' · 加 @astrojs/node adapter
├── package.json ← 新增依赖:@astrojs/node · better-sqlite3 · zod(可选)
├── .env.example ← 新建 · 所有可能的环境变量
├── .env.local ← 新建 · 本地真实凭据(.gitignore)
├── data/ ← 新建 · .gitignore
│ └── leads.db ← 运行时自动创建
├── src/
│ ├── lib/ ← 新建目录
│ │ ├── db.ts ← 新建 · SQLite 连接 + schema 初始化
│ │ ├── email.ts ← 新建 · 按 B3 选项实现 · 未配置则降级打印
│ │ ├── wework.ts ← 新建 · 企微 webhook · 未配置则降级打印
│ │ └── lead-pipeline.ts ← 新建 · 编排器(save → email → wework)
│ ├── pages/
│ │ ├── api/
│ │ │ └── lead.ts ← 修改 · 移除 prerender=true · 实现 POST handler
│ │ └── admin/
│ │ └── leads.astro ← 可选新建(若 B6=C1)· 只读表查看
│ └── types/
│ └── lead.ts ← 新建 · LeadData + zod schema(可选)
└── tests/
└── lead-api.spec.ts ← 可选新建 · curl 模拟测试
8. M6.1 实现步骤(8 步 · 按顺序执行)
| # | 步骤 | 产出 | 预计耗时 |
|---|---|---|---|
| 1 | 准备:用户拍板 B3 / B6 / B7 选项 · 提供 P1/P2 凭据(可留空降级) | 决策清单 + .env.local |
10 min |
| 2 | 装依赖:npm i @astrojs/node better-sqlite3 + 按 B3 选项装邮件库 |
package.json + package-lock.json |
5 min |
| 3 | 改配置:astro.config.mjs 加 adapter: node({ mode: 'standalone' }) + output: 'hybrid' |
astro.config.mjs | 5 min |
| 4 | 建目录 + env 模板:data/ + .env.example(列所有变量) + .env.local(真实凭据)+ .gitignore 加 data/ .env.local |
3 个新文件 | 10 min |
| 5 | 写 lib:db.ts(SQLite 连接 + leads 表 CREATE IF NOT EXISTS)+ email.ts(按 B3)+ wework.ts(按 B7 · W2 时 URL 为空则 no-op) + lead-pipeline.ts(串联三者) |
4 个 lib 文件 | 60-90 min |
| 6 | 改 API:src/pages/api/lead.ts 去掉 prerender = true · 实现 POST · 调用 lead-pipeline · 返回 JSON |
1 个文件 | 30 min |
| 7 | 可选 /admin:若 B6=C1,建 src/pages/admin/leads.astro 只读页 · 简单 GET 查询 + 表格渲染 |
1 个文件 | 30 min |
| 8 | 验证:npm run dev 启动 · curl -X POST http://localhost:4321/api/lead -d '...' 验证三条链路 · 检查 SQLite 文件 + 邮件收件箱 + 企微群消息 |
验证通过 | 30 min |
总计:3-4 小时(取决于 B3/B6/B7 选的复杂度)
9. 验证脚本(Step 8 详解)
9.1 启动
9.2 模拟表单提交
curl -X POST http://localhost:4321/api/lead \
-H "Content-Type: application/json" \
-d '{
"name": "测试客户",
"company": "测试车企",
"email": "test@example.com",
"phone": "13800000000",
"message": "想了解 XiDSP 方案",
"source": "http://localhost:4321/products/xidsp"
}'
9.3 期望结果
| 副作用 | 验证方式 | 期望 |
|---|---|---|
| SQLite | sqlite3 data/leads.db "SELECT * FROM leads ORDER BY id DESC LIMIT 1" |
1 行返回 |
| 邮件(B3=M1) | 查看 OPS_EMAIL 收件箱 |
收到新邮件 |
| 邮件(B3=M4 或 P1 留空) | 查看控制台日志 | 有 [email-skipped] 打印 |
| 企微(B7=W1) | 查看企微群 | 收到机器人消息 |
| 企微(B7=W2 或 URL 空) | 查看控制台日志 | 有 [wework-skipped] 打印 |
| HTTP 响应 | curl 返回值 | {"ok":true,"id":<number>} |
10. M6.2-M6.4 延后项展望
| 阶段 | 目标 | 预计耗时 |
|---|---|---|
| M6.2 | 账号系统(登录 / 注册 / session · 基于 Lucia 或 Auth.js) | 3-5 天 |
| M6.3 | 下载签名 URL(白皮书 / SDK 受保护资源 · JWT + 时限) | 2-3 天 |
| M6.4 | 生产部署(阿里云 ECS + Nginx + ICP + Vercel 海外 · PostgreSQL 升级) | 5-7 天 |
M6.1 代码的前瞻设计:所有 lib(db / email / wework)都用依赖注入 + 接口抽象,M6.4 升 PostgreSQL 只需改 db.ts,其他不动。
11. 决策矩阵(请您回复)
| # | 决策点 | 选项 | 您的选择 |
|---|---|---|---|
| B3 | 邮件服务 | M1 Resend / M2 阿里云 / M3 SMTP / M4 不发邮件 | ? |
| B6 | 轻量 CRM | C1 SQLite only / C2 飞书多维表 / C3 企微推送即可 | ? |
| B7 | 企微 webhook | W1 已有URL(请附上)/ W2 写代码留空 / W3 跳过 | ? |
| P1 | 邮件凭据 | 有就给 / 无就写 mock | ? |
| P2 | 运营邮箱 | 真实地址 / 占位 ops@joysnd.com |
? |
建议回复格式:
推荐组合示例:
| 场景 | B3 | B6 | B7 | P1 | P2 |
|---|---|---|---|---|---|
| 🚀 最小版 · 快速跑通 | M4 | C1 | W2 | - | - |
| 🎯 海外快手 · 无备案 | M1 Resend | C3 | W2 | Resend KEY | ops@joysnd.com |
| 🏠 国内生产 · 已备案 | M2 阿里云 | C3 | W1 | 阿里云 AK/SK | ops@joysnd.com |
| 🧪 学习实验 · 全链路 | M3 SMTP (QQ) | C2 飞书 | W1 | SMTP 密码 + 飞书 token | 个人邮箱 |
12. 风险与缓解
| 风险 | 缓解 |
|---|---|
| better-sqlite3 是 native 模块 · Node 版本变化需重 build | npm rebuild better-sqlite3 · 文档提示 |
| M6.1 的 SQLite 文件在部署时可能被覆盖 | .gitignore 排除 data/ · M6.4 部署策略独立处理 |
| Astro 6 vs 4 的 API 差异 | 使用 Astro 6 标准 endpoint API · 不用已废弃的 get/post 导出方式 |
| 邮件被标垃圾(M3 SMTP) | 建议用带 SPF/DKIM 的 M1/M2 |
| 企微消息频次超限 | 企微机器人限频 20 次/分钟 · M6.1 留资量小远低于此 |
.env.local 泄漏 |
.gitignore 必须包含 · 首次 commit 前检查 |
13. 相关文档
- D3 Deployment Topology 总览
- D4 · Web-Astro 实现
- 技术开发文档体系方案 v1.0
- 既有规划:
AlgoDepartment/07_web/CLAUDE.md§ M6 部分 - 既有规划:
AlgoDepartment/07_web/doc/M6-deployment-guide.md(M6 完整部署指南)
版本历史
| 版本 | 日期 | 变更 |
|---|---|---|
| v1.0 | 2026-05-06 | 首次发布 · 列出 B3/B6/B7 三项决策的作用说明与选项优劣对比 · 不替用户拍板 · 等待决策矩阵回复 |
07_web · M6.1 最小可发布部署方案 · v1.0 · 2026-05-06 · © Xisound Inc.