跳转至
D3 · Deploy · 07_web M6.1 · v2.0 Final

07_web · M6.1 最小可发布部署方案

官网纯静态 CF Pages · 独立 xisound-api · .NET 8 + SQLite + Docker · 留资闭环已交付
19
后端文件交付
1501
后端代码行数
6/6
端到端验证通过

07_web · M6.1 最小可发布部署方案

摘要

本方案定义 Xisound 官网 M6.1 阶段最小可发布架构与实施方案:官网保持纯静态(Cloudflare Pages 免备案上线),同级新建独立 xisound-api/ 仓库承载后端(ASP.NET Core 8 + EF Core + SQLite/PostgreSQL 双栈 + Docker),本地端到端跑通留资闭环(表单提交 → SQLite 存储 → 企微推送 → 邮件通知 · 空凭据降级日志)。 v2.0 为已交付定稿版,反映实际落地状态(63706c9 后端首提交 + 32b3b40 前端 LeadForm fetch + 6/6 端到端验证通过)。v1.0 对比版已归档至 07web-m6.1-deploy-plan-v1.0-archive.md 留存决策脉络。

本文档与既有规划的关系

  • AlgoDepartment/07_web/CLAUDE.md · 规划 M6 完整里程碑(M6.1 已标记 ✅ + M6.2/M6.3/M6.4 拆分待启)
  • AlgoDepartment/07_web/doc/M6-deployment-guide.md · 本地开发 agent 副本(顶部 banner 指向本文档作为权威)
  • AlgoDepartment/06_docs/官网部署.md · M6 战略级建议书 v1.0(4 阶段路线图)
  • 归档:07web-m6.1-deploy-plan-v1.0-archive.md · v1.0 对比版(待拍板阶段的决策对比表)

1. 现状与目标

1.1 M6.1 交付现状(2026-05-06)

维度 交付状态
前端官网 纯静态保持(不引入 Astro adapter)· 35 页 build · commit 32b3b40
后端仓库 独立 xisound-api/(ASP.NET Core 8 · 19 文件 · 1501 行)· root commit 63706c9
数据库 SQLite./data/app.db · EF Core · M6.2 切 PostgreSQL 一行配置)
邮件 Resend(M4→M1 · 空 RESEND_API_KEY 时降级日志)
企微 群机器人(W2 · 空 WEWORK_WEBHOOK_URL 时降级日志)
CORS 允许 localhost:4321/4322 + www.joysnd.com + xisound-website.pages.dev
管理端 GET /admin/leads · X-Admin-Key header 保护 · 分页 + UTM 筛选
限流 内存 ConcurrentDictionary · 5 次/分钟/IP(M6.2 升 Redis)
部署 Docker Compose(api + postgres/redis prod profile)· VPS 上线延后 M6.2
端到端验证 6/6 通过dotnet restore + dotnet build + /health 200 + POST 202 + GET admin 200 + no-op 降级日志全中
文档站(M6.1.1) 独立仓 mengliliusha/xisound-docs · GitHub Actions 自动 mkdocs buildgh-pages 分支 · CF Pages 发布 docs.joysnd.com · 详见 §5.4 与 docs-site-deployment.md

1.2 M6.1 目标(一句话)

已达成

本地 dotnet run(后端 :5000)+ npm run dev(前端 :4321)· 打开官网留资表单提交一条线索 · 三个副作用全部跑通: 1. 写入 ./data/app.db(SQLite · leads 表) 2. 推送企微群消息(WEWORK_WEBHOOK_URL 空 → 日志 [WeworkBot-NoOp]) 3. 发送运营通知邮件(RESEND_API_KEY 空 → 日志 [ResendEmail-NoOp]

1.3 不在 M6.1 范围(M6.2-M6.4 延后)

延后里程碑 范围 依赖前置
M6.2 腾讯云香港 VPS 部署 · api.joysnd.com 上线 · PostgreSQL 持久化 · nginx/certbot · GitHub Actions CI/CD · UptimeRobot + Sentry 腾讯云账号 + 支付方式 + CF DNS api 子域
M6.3 账号系统(JWT /api/auth/*)· 删 xisound-website/src/lib/auth.ts 前端假账号 · /api/downloads/list + R2 签名 URL · /admin/leads 升级 JWT + Excel 导出 M6.2 完成
M6.4 Resend 添加 mail.joysnd.com 域名验证(SPF/DKIM/DMARC) · From 换 noreply@joysnd.com · 飞书多维表格同步(可选)· 阿里云 DirectMail 国内双通道(需 ICP 备案) Resend 账号 + 域名 DNS 管理权

2. 架构总览

2.1 M6.1 交付后的运行拓扑

graph TB
    subgraph Static["静态官网层"]
      CF[Cloudflare Pages]
      W1[www.joysnd.com]
      W2[xisound-website.pages.dev]
    end
    subgraph Backend["后端服务层 · 本地"]
      API[dotnet run · :5000]
      DB[(SQLite · data/app.db)]
      Admin[/admin/leads · X-Admin-Key/]
    end
    subgraph Downstream["下游通知"]
      Resend[Resend 邮件]
      Wework[企微群机器人]
    end

    W1 --> CF
    W2 --> CF
    CF -->|表单提交 · CORS fetch| API
    API -->|EF Core| DB
    API -.空 Key 降级日志.-> Resend
    API -.空 URL 降级日志.-> Wework
    API --> Admin

    class CF,W1,W2 xyL4
    class API,Admin xyL3
    class DB xyL0
    class Resend,Wework xyL5
L4 静态官网 L3 API 层 L0 数据库 L5 下游服务

2.2 留资闭环数据流

sequenceDiagram
    autonumber
    participant User as 访客浏览器
    participant Web as xisound-website (L4)
    participant Api as xisound-api (L3)
    participant Db as SQLite (L0)
    participant Ops as 运营通知 (L5)

    User->>Web: 填写表单 · 点击提交
    Web->>Web: trackLead('submit') 埋点
    Web->>Api: POST /api/lead · CORS
    Api->>Api: DataAnnotations 校验 + 限流 5/min
    Api->>Db: EF Core SaveChanges
    Db-->>Api: leadId 返回
    Api-->>Web: 202 Accepted · {ok, leadId}
    Web->>Web: trackLead('success') 埋点
    Web->>User: 切换成功态 · 显示感谢页
    Api->>Ops: 异步 Wework 推送 · 空 URL 走日志
    Api->>Ops: 异步 Resend 邮件 · 空 Key 走日志

2.3 关键架构决策(与 v1.0 对比版的差异)

维度 v1.0 对比版(初版 A2) v2.0 定稿(实际交付 S0+S1-code) 变化原因
官网部署 @astrojs/node hybrid adapter Cloudflare Pages 纯静态(不引入 adapter) 对齐 06_docs/官网部署.md 阶段 0 · 免备案 · 保留 M5.8.1 性能优化成果(JS bundle 5.2KB)
后端技术栈 Astro endpoint(一栈通 TypeScript) 独立 ASP.NET Core 8 仓库 对齐团队 C# 主栈 · 与 04_development 后端产品线统一
数据库 driver better-sqlite3(Node 原生扩展) EF Core + Microsoft.Data.Sqlite + Npgsql(双栈兼容) SQLite/PostgreSQL 零代码切换 · M6.2 升 Postgres 仅改 Database:Provider
仓库结构 单仓库(07_web/xisound-website) 双独立仓库(xisound-website + xisound-api) 独立 CI/CD · 前后端各自演进 · 对齐 06_docs/官网部署.md §14 表格
部署目标 本地 + 生产二合一 本地跑通 + 部署延后 M6.2 VPS 采购 / 域名 NS 切 / CI/CD 配置是独立路径 · 单轮混做易踩坑

3. 前端改造(xisound-website)

3.1 改动清单

xisound-website/
├── .env.example                           ← 新增 · PUBLIC_API_BASE_URL 等环境变量
└── src/components/resource/
    └── LeadFormCapture.astro              ← 修改 · setTimeout mock → fetch ${PUBLIC_API_BASE_URL}/api/lead

未改动项(M5.8.1 保留):astro.config.mjs output: 'static' · 0 client:* 指令 · src/lib/auth.ts 保留(M6.3 再删)· src/pages/api/lead.ts static stub 保留(CF Pages 无后端时的 UI 降级)

3.2 LeadFormCapture.astro 核心逻辑

// xisound-website/src/components/resource/LeadFormCapture.astro · <script> 段核心
import { trackLead } from '../../lib/analytics';

const API_BASE = (import.meta.env.PUBLIC_API_BASE_URL ?? '').replace(/\/+$/, '');

// 提交时
try {
  if (API_BASE) {
    // M6.1 真实后端:POST 到 xisound-api
    const res = await fetch(`${API_BASE}/api/lead`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ...payload, stage: 'resource-download', consent: Boolean(payload.consent) }),
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
  } else {
    // 无后端降级:本地 mock(仅 UI 演示)
    console.info('[lead/mock] PUBLIC_API_BASE_URL 为空 · 走降级模拟');
    await new Promise((resolve) => setTimeout(resolve, 800));
  }
  trackLead('success', { audience, topic });
} catch (err) {
  trackLead('error', { audience, message: err.message });
}

3.3 环境变量(.env.example 摘要)

# M6.1 · 留资 API
PUBLIC_API_BASE_URL=
# 本地:http://localhost:5000 · 生产(M6.2 后):https://api.joysnd.com · 留空则降级 mock

# M7 · 埋点(M6.1 不启用)
PUBLIC_BAIDU_ID=
PUBLIC_GA4_ID=

4. 后端架构(xisound-api)

4.1 项目结构

xisound-api/
├── XiSound.Api.sln
├── Dockerfile                       # 多阶段构建 · 非 root 用户 · runtime ~220MB
├── docker-compose.yml               # api + postgres(prod profile) + redis(prod profile)
├── .env.example                     # 所有密钥占位
├── .gitignore                       # 排除 data/ · .env · bin/ · obj/
├── README.md                        # 5 分钟起步指引
└── src/XiSound.Api/
    ├── XiSound.Api.csproj           # EF Core + Sqlite + Npgsql + Swagger
    ├── Program.cs                   # Minimal API bootstrap + CORS + DI
    ├── appsettings.json             # 生产默认值
    ├── appsettings.Development.json # 本地覆写(AdminKey=dev-key)
    ├── Properties/launchSettings.json
    ├── Models/Lead.cs               # 留资实体(UTM 扁平化 + 请求元数据)
    ├── Data/AppDbContext.cs         # SQLite/PostgreSQL 双栈
    ├── Dtos/LeadDto.cs              # DataAnnotations 校验 + ToEntity 映射
    ├── Services/
    │   ├── EmailServices.cs         # IEmailService + ResendEmailService(空 Key no-op)
    │   └── WeworkServices.cs        # IWeworkNotifier + WeworkBotNotifier(空 URL no-op)
    ├── Middleware/AdminKeyMiddleware.cs  # /admin/* · X-Admin-Key header 保护
    └── Controllers/
        ├── LeadsController.cs       # POST /api/lead · GET /admin/leads · GET /admin/leads/{id}
        └── HealthController.cs      # GET /health · GET /health/ready

4.2 API 合约

POST /api/lead · 公开入口

{
  "stage": "resource-download",
  "resourceTitle": "声学设计方案白皮书",
  "name": "张三",
  "phone": "13800138000",
  "email": "zhangsan@example.com",
  "company": "某声学公司",
  "role": "声学工程师",
  "audience": "oem-new",
  "topic": "acoustic-design",
  "need": "希望了解 XiStudio 的定价",
  "consent": true,
  "utm": { "source": "baidu", "medium": "cpc", "campaign": "brand" }
}
状态码 含义
202 Accepted 入库成功 · 异步推送 · { ok, leadId, message }
400 Bad Request 字段校验失败 · RFC7807 ValidationProblemDetails
429 Too Many Requests 同 IP 超过 5 次/分钟
500 Internal Server Error 服务端异常

GET /admin/leads · 管理端(需 X-Admin-Key

Query 参数 类型 说明
limit int 1-500 · 默认 50
offset int 默认 0
stage string 按来源阶段筛选
status string 按处理状态筛选(pending / notified / contacted / closed)
utmSource string 按 UTM 来源筛选

返回字段按 createdAt DESC 排序 · 含 Id / CreatedAt / Stage / Status / Name / Phone / Email / Company / Role / Audience / Topic / ResourceTitle / Need / Utm × 3 / IpAddress。

GET /health · GET /health/ready

无需鉴权 · 用于 UptimeRobot / Docker healthcheck / K8s probe。

4.3 留资入库流程

// LeadsController.Submit · 核心逻辑
[HttpPost("/api/lead")]
public async Task<IActionResult> Submit([FromBody] LeadDto dto, CancellationToken ct)
{
    // 1. 限流
    if (!CheckRateLimit(GetClientIp()))
        return StatusCode(429, new { ok = false, error = "rate_limited" });

    // 2. 业务校验(Phone/Email 至少一项)
    if (dto.ValidateContacts() is { } err)
    {
        ModelState.AddModelError(nameof(dto.Phone), err.ErrorMessage);
        return ValidationProblem(ModelState);
    }

    // 3. 存库 + 填充请求元数据
    var lead = dto.ToEntity();
    lead.IpAddress = GetClientIp();
    lead.UserAgent = Request.Headers.UserAgent.ToString();
    _db.Leads.Add(lead);
    await _db.SaveChangesAsync(ct);

    // 4. 异步推送(不阻塞响应 · 失败不影响主流程)
    _ = Task.Run(async () => {
        try { await _wework.SendLeadAsync(lead); } catch (Exception ex) { _logger.LogError(ex, "..."); }
        try { await _email.SendLeadNotifyAsync(lead); } catch (Exception ex) { _logger.LogError(ex, "..."); }
    });

    return Accepted(new { ok = true, leadId = lead.Id, message = "已收到您的留资" });
}

5. 部署方案

5.1 阶段 0 · Cloudflare Pages 官网上线(Day 1-3 · 用户手工)

5.1.1 前置条件

  • 已有域名 joysnd.com
  • GitHub 仓库 mengliliusha/xisound-website 已 push(M6.1 完成 · 17 次 commit · 最新 32b3b40
  • Cloudflare 账号(免费 · 没有 → https://dash.cloudflare.com/sign-up 注册)
  • 域名注册商登录凭据(用于改 NS)

5.1.2 步骤 1 · 域名 NS 切换到 Cloudflare(A → B → C → D 四步走)

本步骤是阶段 0 最关键、也是最容易踩坑的一步 —— NS 切换本质上是把域名的"解析大权"从原注册商移交给 Cloudflare。执行前请务必看完 §5.1.2.E 的 QQ 企业邮箱保护警告(用户若在用 QQ 企业邮箱 / 阿里企业邮 / 网易企业邮等任何第三方邮箱托管,NS 切换后必须保留原 MX/SPF 记录)。

5.1.2.A · 在 Cloudflare 添加站点并获取 NS 记录
  1. 登录 Cloudflare Dashboard(https://dash.cloudflare.com
  2. 右上角 Add a Site → 输入 joysnd.com → 点击 Continue
  3. 选择 Free 计划 → Continue
  4. CF 会自动扫描现有 DNS 记录(含已有的 MX/SPF/TXT 记录)· 完成后展示一个带 两条 NS 记录 的大框,形如:
cora.ns.cloudflare.com
eric.ns.cloudflare.com

其中 cora / eric 等前缀是 CF 随机分配的人名占位 · 每个账号获取到的前缀不同,请以 CF 界面实际展示的为准。

找不到 NS 记录? CF 可能把它放在不同位置 · 两种方法都能找到:

  • 方法 1(最常见):进入 joysnd.com 的 Zone 概览页(Overview)· 页面顶部会有"Cloudflare Nameservers"的大框 + 两条 NS
  • 方法 2(备选):左侧菜单 DNSRecords → 滚到页面底部(或点 DNSSettings 标签页)· 也能看到这两条 NS

请把两条 NS 记下来(或保持浏览器 tab 打开)· §5.1.2.B 要用。

5.1.2.B · 在腾讯云域名注册商改 NS

⚠️ 关键认知:这一步不在 Cloudflare 做 · 必须去你买 joysnd.com 的那个平台(注册商)操作。 Cloudflare 只是"借用"你的域名做解析 · 域名的所有权依然在腾讯云。

腾讯云操作路径(2024-2026 · 用户实测)

  1. 登录腾讯云控制台(https://console.cloud.tencent.com
  2. 顶部导航 → 云产品域名注册我的域名
  3. 域名列表中找到 joysnd.com → 点击右侧 管理
  4. 左侧菜单 → DNS 服务器 → 点击 修改
  5. 界面会出现 NS 记录输入框 · 把原有的腾讯云 DNS(通常是 ns1.dnspod.net / ns2.dnspod.net · 或 dns1.qcloud.com完全删除
  6. 逐行填入 §5.1.2.A 获取的两条 CF NS
cora.ns.cloudflare.com
eric.ns.cloudflare.com
  1. 点击 确定 / 保存 · 腾讯云可能要求短信验证(发到你注册账号的手机号)· 输入验证码完成

腾讯云"新老版面"差异提示

腾讯云控制台在 2025 年做过 UI 改版 · 有些用户可能看到: - "DNS 修改" 菜单项(旧版名字) - "自定义 DNS" 标签(部分特殊域名) - "DNS 管理" 二级菜单(合并后的入口) 本质都是同一功能 · 只要能看到当前 NS 并能替换即可。

5.1.2.C · 回到 Cloudflare 点"Check nameservers"触发验证
  1. 在腾讯云改完 NS 后 · 回到 Cloudflare Dashboard 的 joysnd.com 概览页(Overview)
  2. 页面上会有一个按钮 · 文案为 Check nameservers检查名称服务器(取决于 CF 界面语言设置)
  3. 点击它 · CF 会主动查询全球 DNS 系统 · 确认 NS 是否已切换到 cora/eric.ns.cloudflare.com

状态流转

阶段 状态文案 含义 时长
刚改完 NS Pending Nameserver Update DNS 还没生效 · CF 查不到新 NS 几分钟 - 24 小时
生效后 Active NS 已全球传播 · CF 完全接管解析 -

生效后 CF 会给你的注册邮箱发一封邮件Your site is now active on Cloudflare,这时才可以进入 §5.1.3 创建 CF Pages 项目。

别等"Active"太久

CF 的 Check nameservers 按钮可以反复点 · 每次触发一次查询。 大陆地区 DNS 传播通常 10 分钟 - 2 小时 · 国际线路可能更快。 如果 24 小时后还是 Pending · 请回 §5.1.2.B 检查腾讯云那边 NS 是否真的保存了。

5.1.2.D · 本地 nslookup 验证(推荐但非必需)

在本地 PowerShell / CMD 跑:

nslookup -type=ns joysnd.com

生效后的预期输出

Server:  your-dns-server.local
Address: 192.168.x.x

Non-authoritative answer:
joysnd.com  nameserver = cora.ns.cloudflare.com
joysnd.com  nameserver = eric.ns.cloudflare.com

未生效时可能看到

joysnd.com  nameserver = ns1.dnspod.net   ← 腾讯云旧 NS
joysnd.com  nameserver = ns2.dnspod.net

说明 DNS 还在全球传播 · 耐心等待 · 或清本地 DNS 缓存:

ipconfig /flushdns
5.1.2.E · QQ 企业邮箱等第三方邮箱的保护(重要 · 可能踩坑)

切换 NS 不会自动断邮件 · 但错误操作会让邮箱挂掉

如果你已经在用 QQ 企业邮箱 / 阿里企业邮 / 网易企业邮 / Google Workspace 等第三方邮箱托管服务 · joysnd.com 已经有 MX/SPF/TXT 记录在生效。

好消息:CF 在 §5.1.2.A 扫描现有 DNS 时会自动保留这些记录 · NS 切换后邮箱继续能正常收发

坏消息:如果在 CF DNS 面板手贱点亮了 MX/SPF/TXT 记录的代理云朵(橙色) · 邮箱会立即挂掉(收不到邮件 · 发出去的邮件被退回)· 这是新手最容易踩的坑。

必须遵守的 3 条规则

  1. MX 记录(所有 mailto:xxx@joysnd.com 投递路径)· 代理状态必须是"仅 DNS"灰色云朵 · ❌ 绝不能点亮橙色云朵
  2. SPF TXT 记录v=spf1 include:spf.mail.qq.com ...)· 代理状态必须是"仅 DNS"灰色云朵 · 同上
  3. DKIM TXT 记录_domainkey.xxx 开头)· 代理状态必须是"仅 DNS"灰色云朵 · 同上

原因:CF 代理(橙色云朵)只作用于 HTTP/HTTPS 流量(即 www.joysnd.com 这类网站访问)· MX/SMTP/SPF 是邮件协议 · 走 CF 代理会被过滤导致邮件丢失。

QQ 企业邮箱典型配置(参考 · NS 切换前已存在请勿删除)

类型 名称 内容 TTL 代理状态(关键)
MX @(根域) mxbiz1.qq.com(优先级 5) Auto 🔘 仅 DNS
MX @(根域) mxbiz2.qq.com(优先级 10) Auto 🔘 仅 DNS
TXT @(根域) v=spf1 include:spf.mail.qq.com ~all Auto 🔘 仅 DNS

点亮代理云朵的"撤销"方法

如果不小心把 MX 记录点亮成橙色 · 邮箱会在几秒内挂掉。 补救:CF DNS 面板找到那条记录 · 点击橙色云朵让它变回灰色("仅 DNS")· 邮件服务通常 1-5 分钟内恢复。 如果已经有邮件在此期间丢失 · 一般无法找回(发件方会收到退信 · 可以请他们重发)。

5.1.3 步骤 2 · Cloudflare Pages 创建项目(⚠️ 最容易踩坑的一步)

切勿进错成 Workers 项目 · 这是最常见的陷阱

Cloudflare 在 2024 年 9 月把 Workers 和 Pages 整合到同一菜单"Workers & Pages" · 新版 UI 非常容易点进去就创建了 Workers 项目(而不是 Pages)。两者的区别:

维度 Cloudflare Pages Cloudflare Workers
定位 静态站托管 + Git 自动部署 边缘计算 + Serverless 函数
适合 ✅ Astro / Next / Hugo / 纯 HTML JS/TS 函数 / API / SSR
构建配置 Framework preset + Build + Output dir Wrangler + wrangler.toml
部署命令 (CF 自动部署) 需要 npx wrangler deploy
Zero-config ✅ 是 ❌ 否(需配置)

踩坑识别信号:如果你创建项目后 · 构建配置页出现 "部署命令 (Deploy command)" 或 "版本命令 (Version command)" 或 "兼容性日期 (Compatibility date)" 字段 · 你进错 Workers 了 · 本项目 不需要 wrangler

补救方案:删除错建的 Workers 项目(Settings → Delete project)· 重新回 Workers & Pages 主页 · 重新选 Pages 标签创建。

正确创建流程(逐步)

  1. CF Dashboard 左侧菜单 → Workers & Pages
  2. 点击右上角蓝色 Create application 按钮
  3. 顶部会出现两个 tab 标签
┌─────────────────────────────────┐
│  [ Workers ]  [ Pages ]         │
└─────────────────────────────────┘
          本项目务必点这里(默认可能停在 Workers)
  1. 确认切到 Pages 标签后 · 看到两个选项卡片:
    • Connect to Git ← ✅ 点这个(本项目的流程)
    • Upload assets ← 不要点(是手动上传压缩包的方式)
  2. 首次使用需授权 GitHub:
  3. 点击 Connect GitHub
  4. 跳转到 GitHub 授权页 → 登录 mengliliusha 账号
  5. 选择授权范围:
    • All repositories(授权所有仓库 · 最省事)
    • Only select repositories → 勾选 xisound-website(最小权限)
  6. 点击 Install & Authorize → 自动跳回 CF
  7. 回到 CF 项目创建页 · 会看到 GitHub 仓库列表 · 选择 mengliliusha/xisound-website → 点击 Begin setup

关于"Git 存储库 URL"字段

如果界面显示可填的 "Git 存储库 URL" 输入框,这个字段不用手动填。只要前面步骤已通过 GitHub OAuth 授权 + 从列表选择仓库,CF 会自动识别并填充此字段。 手动填的场景:只有连接非 GitHub 来源(比如 GitLab 自托管 / 自建 Gitea)时才需要。本项目仓库在 GitHub 官方,走 OAuth 流程即可,忽略这个字段 / 保持它自动填充的默认值

如何删除错建的 Workers 项目(踩坑后的补救)

如果已经错建成 Workers 项目(访问 .pages.dev URL 报 404 或显示 Worker 错误页):

  1. CF Dashboard → Workers & Pages → 找到错建的项目 → 点进去
  2. 右上角 / 左侧菜单 → Settings 标签
  3. 滚到最底部 → Danger Zone / 危险操作区 → 点击 Delete project
  4. 弹窗要求输入项目名(如 xisound-website)确认删除
  5. 回 Workers & Pages 主页 · 重新执行上面的正确创建流程

删除 + 重建整个过程约 5 分钟 · 不会影响 GitHub 仓库和代码。

5.1.4 步骤 3 · 构建配置(Set up builds and deployments)

点击 Begin setup 后进入构建配置页 · 逐字段填写:

字段 说明
Project name xisound-website 默认与仓库同名 · 保持即可 · 决定 .pages.dev 子域名
Production branch main 默认 main · 保持即可
Framework preset Astro(下拉选择) CF 会自动识别并推荐 · 手动确认选中 Astro
Build command npm run build Astro preset 自动填好 · 不用改
Build output directory /dist Astro preset 自动填好 · 不是 dist/,前面有斜杠
Root directory 留空(代表 / 仓库根就是 Astro 项目 · 不要填 xisound-website
Environment variables 见下一节 5.1.5 Add variable 添加 3 个

Node 版本关注

Cloudflare Pages 默认 Node 18 · 本项目 package.json engines 声明 >=22.12.0。如果首次构建报 Node 版本错,添加环境变量: NODE_VERSION = 22 (加到 5.1.5 的环境变量里)

5.1.5 步骤 4 · 环境变量配置

点击 Add variable 依次添加:

Variable name Value 说明
NODE_VERSION 22 修复 CF 默认 Node 18 与本项目 >=22.12.0 的不兼容
PUBLIC_API_BASE_URL 留空(空字符串) M6.1 无后端 · 前端 LeadForm 会降级 mock 演示 · M6.2 部署后改填 https://api.joysnd.com
PUBLIC_BAIDU_ID 留空 M7 埋点激活时填入
PUBLIC_GA4_ID 留空 M7 埋点激活时填入

留空 vs 不添加

CF Pages 环境变量里留空 = 有 key 无 value = 空字符串。本项目 LeadFormCapture 逻辑 (import.meta.env.PUBLIC_API_BASE_URL ?? '').replace(/\/+$/, '') 会把 undefined / '' 都视为无后端,降级到 mock。两种处理等价。 推荐:至少把 NODE_VERSIONPUBLIC_API_BASE_URL 添加进去(即使后者留空)· 后续 M6.2 改值时不用重跑 "Add variable" 流程。

5.1.6 步骤 5 · 首次部署

  1. 检查所有字段正确后 · 点击底部 Save and Deploy(蓝色大按钮)
  2. CF 会立即触发首次构建 · 跳转到部署页面
  3. 观察构建日志(时长约 1-3 分钟):
  4. Cloning repository · clone mengliliusha/xisound-website main 分支
  5. Installing dependencies · npm install(淘宝镜像不生效 · CF 走 npm 官方源 · 可能慢)
  6. Building application · npm run build · Astro 产出 35 页到 dist/
  7. Uploading build output · CF 上传到全球 edge
  8. 构建完成会显示:
  9. Success! Your project is ready
  10. Deployment URLhttps://xisound-website.pages.dev(或类似子域 · CF 首次分配)
  11. 点击 Deployment URL 打开 · 首屏应该看到 Xisound 首页(M5.8.1 Hero + 14 产品 + 四方案入口)

构建成功验收清单

  • 部署页面显示 ✅ Success
  • https://xisound-website.pages.dev 返回首页 200
  • 首页 Hero 渲染正常(Trinity 渐变 + 极光青点缀)
  • 打开 /products 总览页 · 六层 14 产品渲染
  • 打开任一资源页 · 填写留资表单 · 提交 · 看到"已收到"感谢页(走 mock 降级是预期行为)
  • 浏览器 Console 看到 [lead/mock] PUBLIC_API_BASE_URL 为空 · 走降级模拟 日志

5.1.7 步骤 6 · DNS 配置(自定义域名)

回到 Cloudflare DNS 面板(左侧菜单 → joysnd.com → DNS → Records),添加:

类型 名称 内容 代理状态 TTL
CNAME www xisound-website.pages.dev 🟠 Proxied Auto
A @ 192.0.2.1(占位 IP · 仅用于 Page Rules 激活) 🟠 Proxied Auto
CNAME docs (延后 M6.3 · 暂不添加) - -
CNAME download (延后 M6.2 · 暂不添加) - -
CNAME api (延后 M6.2 · VPS IP 或 CNAME) - -

根域 @ 为什么用占位 IP

根域 A 记录必须指向一个 IP(CNAME flattening 是 CF 付费特性)。用 192.0.2.1 是 RFC 5737 文档保留地址 · 永远不可达。真实流量会被 5.1.9 的 Page Rule 301 拦截到 www · 所以 A 记录的 IP 不会被命中。

5.1.8 步骤 7 · 绑定 www.joysnd.com 自定义域

  1. CF Dashboard → Workers & Pages → 选中刚创建的 xisound-website 项目
  2. 左侧 Custom domains 标签
  3. 点击 Set up a custom domain
  4. 输入 www.joysnd.com → 点击 Continue
  5. CF 检测到 5.1.7 已添加的 www CNAME → 点击 Activate domain
  6. CF 自动签发 HTTPS 证书(3-5 分钟 · 期间 Status 会从 VerifyingActive
  7. 验证:打开 https://www.joysnd.com · 应返回官网首页(与 .pages.dev 同内容)
  8. 浏览器地址栏左侧应显示🔒绿锁 · 证书颁发者 Cloudflare Inc ECC CA-3

5.1.8B 步骤 7.5 · 绑定 docs.joysnd.com(MkDocs 文档站 · M6.1.1)

M6.1.1 新增

本节为 M6.1.1 新增内容 · 在 §5.1.8 绑定官网自定义域之后执行。完整文档站部署手册见 docs-site-deployment.md。 此处为精简操作步骤,对齐 §5.1.3-§5.1.6 的 CF Pages 创建流程,但目标仓库为 mengliliusha/xisound-docs、监听分支为 gh-pages

5.1.8B.1 前置条件
  • mengliliusha/xisound-docs 仓库已首次 push(本地 06_docs/site-build/ 嵌套 git · 用户已 git remote add origin
  • GitHub Actions 首次构建通过(约 100-140 秒)· gh-pages 分支已自动生成
  • Cloudflare Dashboard 中 joysnd.com zone 状态 Active(5.1.2 已完成)

首次 push 命令:

cd D:\work\25_claude\workspace\AlgoDepartment\06_docs\site-build

# 如果还未 git init
git init -b main

# 确认远程已加
git remote -v
# origin  git@github.com:mengliliusha/xisound-docs.git (fetch/push)

# 首次全量提交
git add .
git commit -m "feat(docs): initial commit · M6.1.1 site build with GitHub Actions deploy"
git push -u origin main
5.1.8B.2 创建 CF Pages 项目(监听 gh-pages)
  1. CF Dashboard 左侧 → Workers & Pages → 右上角 Create application
  2. 切到 Pages 标签(非 Workers · 详见 §5.1.3 踩坑提醒)
  3. Connect to Git → 选择 mengliliusha/xisound-docs(首次需 GitHub OAuth 授权)
  4. 项目配置:
字段 说明
Project name xisound-docs 保持默认 · 决定 .pages.dev 子域名
Production branch gh-pages ⚠️ 关键 · 不是 main · gh-pages 是 Actions 自动推的产物分支
Framework preset None / 留空 产物已是静态 HTML · 无需再构建
Build command 留空 Actions 已构建好
Build output directory / gh-pages 根目录就是 _site/ 内容
Root directory (advanced) 留空
  1. 点击 Save and Deploy
  2. 如果此时 gh-pages 分支还不存在:CF Pages 会报错 "No branch named gh-pages found"
  3. 回 GitHub → mengliliusha/xisound-docs/actions → 手动 Run workflow(选 main 分支)· 等 ~2 分钟 workflow 完成自动创建 gh-pages 分支
  4. CF Pages → xisound-docs 项目 → Deployments → Retry deployment

  5. 等约 1 分钟部署成功 · 获得 CF 分配的子域 · 形如 xisound-docs-abc.pages.dev

5.1.8B.3 DNS 配置(docs 子域)

CF Dashboard → joysnd.com → DNS → Records → Add record

类型 名称 内容 代理状态 TTL
CNAME docs xisound-docs-abc.pages.dev(5.1.8B.2 拿到的子域) 🟠 Proxied Auto

不能是 .joysnd.com 的 CNAME

CNAME 目标必须填 CF Pages 分配的 .pages.dev 子域,不要docs.joysnd.com 自指(会导致解析环路)。

5.1.8B.4 绑定 docs.joysnd.com 自定义域
  1. CF Dashboard → Workers & Pages → 选中 xisound-docs 项目
  2. 左侧 Custom domains → 点击 Set up a custom domain
  3. 输入 docs.joysnd.com → Continue
  4. CF 检测到 5.1.8B.3 已添加的 CNAME → 点击 Activate domain
  5. HTTPS 证书自动签发(3-5 分钟 · Verifying → Active)
  6. 验证:打开 https://docs.joysnd.com · 应返回文档站首页(Xisound · 算法部文档中心)
  7. 浏览器地址栏左侧应显示🔒绿锁
5.1.8B.5 CF SSL 模式确认

CF Dashboard → SSL/TLS → Overview → Mode 应为 Full(CF Pages 自带证书 · 不需要 Full (strict))。

5.1.8B.6 xisound-website 环境变量补齐(生产)

CF Pages → xisound-website 项目 → Settings → Environment variables → Production → Edit variables

Variable name Value
PUBLIC_DOCS_BASE_URL https://docs.joysnd.com
PUBLIC_INTERNAL_DOCS_BASE_URL https://docs.joysnd.com

Save → Deployments → Retry latest deployment(或 push 新 commit 触发)。

5.1.8B.7 M6.1.1 部署验收清单
  • https://docs.joysnd.com 返回首页 200 · HTTPS 证书 Active
  • 首页 Hero 渲染正常 · Mermaid 图正常 · 搜索功能可用
  • 随机打开一个内页(如 /D3-architecture/deployment-topology/docs-site-deployment/)· 内容正常
  • https://www.joysnd.com/docs 文档门户两个卡片 note 字段已去敏感化
  • https://www.joysnd.com/docs-public/ 所有外链跳转到 docs.joysnd.com(不再是 127.0.0.1:8000)
  • https://www.joysnd.com/docs/internal-gate 页面不再显示明文凭证 "账号 xisound · 密码 DevPreview2026"
  • 使用前端假账号 xisound / DevPreview2026 登录仍能成功(auth.ts 守卫保留)
  • 登录后访问 /docs-internal · 外链跳转到 docs.joysnd.com
  • 作者侧修改 md → git push main → 90 秒到 3 分钟内 docs.joysnd.com 可见新内容
5.1.8B.8 故障排查
症状 可能原因 解决
CF Pages 创建项目报 "No branch named gh-pages" Actions 首次未跑 / 首次失败 GitHub Actions → Run workflow 手动触发
docs.joysnd.com 打开报 525 / SSL 错 CF SSL 模式设为 Full (strict) 但 CF Pages 无源站证书 改为 Full(非 strict)· 或重绑定自定义域
作者 push 后文档站内容没更新 Actions 构建失败 / CF Pages 缓存 先查 Actions log · 再 CF Pages Purge Cache
搜索功能不工作 mkdocs-material search plugin 未启用 检查 mkdocs.yml plugins 段包含 - search:

完整故障树见 docs-site-deployment.md §7

5.1.9 步骤 8 · 根域 joysnd.comwww.joysnd.com 301 重定向

  1. CF Dashboard → joysnd.com zone → 左侧 RulesPage Rules
  2. 点击 Create Page Rule
  3. 填写:
  4. If the URL matchesjoysnd.com/*(注意没有 www · 也没有 http/https 前缀)
  5. Then the settings are
    • 下拉选 Forwarding URL
    • Status code:301 - Permanent Redirect
    • Destination URL:https://www.joysnd.com/$1
  6. 点击 Save and Deploy
  7. 验证:
    curl -I https://joysnd.com
    # 预期返回 HTTP/2 301 · location: https://www.joysnd.com/
    

5.1.10 阶段 0 最终验收

  • https://xisound-website.pages.dev 返回首页 200
  • https://www.joysnd.com 返回首页 200 · 证书有效(🔒绿锁)
  • https://joysnd.com 301 → https://www.joysnd.com
  • http://www.joysnd.com 自动升级 HTTPS(CF 默认 Always Use HTTPS)
  • Lighthouse 性能 > 90(M5.8.1 bundle 5.2KB · 应该轻松过)
  • 留资表单提交显示感谢页 + Console 看到 [lead/mock] 日志(预期 · M6.2 接入后端后消失)

5.1.11 后续自动化(无需配置)

  • 每次 git push origin main → CF 自动触发新部署(~2 分钟完成)
  • Preview deployments:任何 PR / 非 main 分支 push → 自动生成预览 URL(形如 xxx-abc.xisound-website.pages.dev)· 可分享给 review 人
  • 构建缓存:CF 自动缓存 node_modules/,后续构建缩短到 ~30 秒

环境变量后续更新路径

M6.2 VPS 部署完成后 · 修改 PUBLIC_API_BASE_URLhttps://api.joysnd.com: CF Dashboard → xisound-website 项目 → Settings → Environment variables → Edit → 填入新值 → Save → 手动触发 Retry deployment 即生效。

CF Pages Free 计划限额

  • 每月 500 次构建(足够 M6 阶段 · 一天 ~15 次 push 不到限)
  • 每月 100 GB 带宽(官网 5.2KB bundle + 121 占位 SVG · 支撑 100 万次访问)
  • 静态文件不限大小
  • 升级 Pro 计划 $20/月 · M8 社区期再评估

5.2 阶段 1 · 后端代码交付(M6.1 ✅ 本轮已完成)

M6.1 交付物

仓库AlgoDepartment/xisound-api/(独立 .NET 8 仓库 · root commit 63706c9 · 待 push 到 mengliliusha/xisound-api规模:19 文件 · 1501 行代码 验证:6/6 本地端到端验证全部通过(详见 §6)

5.2.1 后端交付清单

模块 路径 状态
项目脚手架 XiSound.Api.sln + src/XiSound.Api/XiSound.Api.csproj (net8.0)
Program.cs Minimal API + CORS + DI + AdminKey middleware
配置文件 appsettings.json + appsettings.Development.json(SQLite 默认)
数据层 Models/Lead.cs + Data/AppDbContext.cs(EF Core · SQLite/PostgreSQL 双栈)
DTO + 校验 Dtos/LeadDto.cs(DataAnnotations + Phone/Email 业务校验)
邮件服务 Services/EmailServices.csIEmailService + ResendEmailService · M4 降级 no-op 日志)
企微服务 Services/WeworkServices.csIWeworkNotifier + WeworkBotNotifier · W2 降级 no-op 日志)
管理端保护 Middleware/AdminKeyMiddleware.csX-Admin-Key header · 空则 503 拒绝)
留资入口 Controllers/LeadsController.cs(POST /api/lead + GET /admin/leads + 限流 5/min)
健康检查 Controllers/HealthController.cs(GET /health · GET /health/ready)
容器化 Dockerfile(多阶段 · 非 root 用户 · runtime ~220 MB)
编排 docker-compose.yml(api + postgres/redis prod profile)
密钥占位 .env.example(所有 Resend/Wework/Db 字段)
文档 README.md(5 分钟起步)

5.2.2 关键设计决策

# 决策 选择 理由
B3 后端语言 .NET 8 Team C# 主栈 · 与 04_development 后端产品线统一
B6 数据库 SQLite(M6.1 本地)→ PostgreSQL(M6.2 部署) EF Core 双 provider 预置 · 切换仅需改配置
B7 仓库结构 独立 xisound-api/ 仓库 前后端各自 CI/CD · 对齐 06_docs/官网部署.md §14
M4 邮件 Resend 起步 · 空 Key 降级日志 免费 3000 封/月 · 国际节点 · 无需备案
W2 企微通知 群机器人 · 空 URL 降级日志 免费 · 集成简单 · 无需企业微信企业号认证
C1+C3 CRM 同步 数据库 + 企微机器人双通道 数据库兜底 · 企微实时通知

5.2.3 前瞻设计(为 M6.2-M6.4 预留)

  • EF Core 双 providerDatabase:Provider = "sqlite" | "postgres" 切换一行配置 · 无代码改动
  • IEmailService 接口抽象:M6.4 接阿里云 DirectMail 国内双通道 · 只需新加实现类
  • AdminKeyMiddleware 与 JWT 同 pipeline:M6.3 升级 JWT 时并存 · 不互相干扰
  • 限流内存实现 → Redis 升级ConcurrentDictionary 当前内存实现 · M6.2 Redis 容器上线后切 IDistributedCache 即可
  • EnsureCreated → migrations:M6.1 本地用 db.Database.EnsureCreated() · M6.2 Postgres 时切正式 migrations(dotnet ef migrations add Init

5.2.4 commit 速查

仓库 Commit 变更
xisound-website 32b3b40 feat(M6.1): wire LeadForm to xisound-api with env base URL
xisound-api(新建) 63706c9 feat(M6.1): ASP.NET Core 8 backend scaffold with lead API + admin + Resend/Wework no-op

本地跑通命令与 6/6 验证结果见 §6.

5.3 阶段 3 · VPS 生产部署(M6.2 ⏳ 下一里程碑)

权威简化版 · 完整 TODO 见 TODOLIST

本节为 M6.2 VPS 部署核心路径与架构决策。详细分阶段步骤(前置准备 / VPS 基础环境 / 服务编排 / DNS 集成 / CI/CD / 监控 / 验证)见: 👉 AlgoDepartment/07_web/xisound-website/TODOLIST.md §3 · P0 M6.2 章节 · 35+ TODO 项 预计工期:3-5 个工作日

非运维背景?先看账号采购零基础指南

如果你对 VPS / 腾讯云产品线 / Resend / 企微机器人不熟悉 · 先阅读: 👉 M6.2 · VPS 账号申请与采购零基础指南 覆盖:VPS 是什么 · 腾讯云4 种镜像类型详解(应用模板 / 操作系统 / 容器 / 自定义) · Resend 免费版限额 · 企微机器人限流规则 · 常见坑与解决方案。 本节(§5.3)假设你已完成账号采购(阶段 0 前置准备) · 从阶段 1 VPS 基础环境开始。

5.3.1 部署架构(M6.2 目标态)

graph TB
    subgraph Edge["Cloudflare 边缘"]
      User[访客浏览器]
      CF[CF CDN + DDoS 防护]
    end

    subgraph VPS["腾讯云香港 · Ubuntu 22.04 · 2C4G"]
      Nginx[nginx:1.27-alpine<br/>80/443 · 反代 + CORS + 限流]
      Certbot[certbot<br/>Let's Encrypt 证书]
      ApiCtn[xisound-api<br/>:5000 · ASP.NET Core 8]
      PgCtn[(postgres:16<br/>pg_data volume)]
      RdsCtn[(redis:7-alpine<br/>AOF persistence)]
    end

    subgraph Downstream["下游服务"]
      Resend[Resend 邮件<br/>mail.joysnd.com M6.4]
      Wework[企微群机器人]
      Sentry[Sentry .NET SDK]
      UptimeRobot[UptimeRobot<br/>5min 探活]
    end

    User -->|HTTPS| CF
    CF -->|api.joysnd.com 443| Nginx
    Nginx -->|/health /api/lead /admin/*| ApiCtn
    Certbot -.每月续签.-> Nginx
    ApiCtn -->|EF Core| PgCtn
    ApiCtn -.限流 · 会话.-> RdsCtn
    ApiCtn -.入库后异步.-> Resend
    ApiCtn -.入库后异步.-> Wework
    ApiCtn -.异常采集 10%.-> Sentry
    UptimeRobot -.探活.-> Nginx

    class User,CF xyL4
    class Nginx,Certbot xyL3
    class ApiCtn xyL2
    class PgCtn,RdsCtn xyL0
    class Resend,Wework,Sentry,UptimeRobot xyL5

5.3.2 核心部署步骤(6 阶段)

阶段 工作内容 工期 关键输出
阶段 0 前置准备:腾讯云账号 / VPS 下单(香港 2C4G 80G)· Resend 账号 + API Key · 企微 Webhook · xisound-api 首次 push 到 GitHub 0.5-1 天 VPS 公网 IP · 各类密钥
阶段 1 VPS 基础环境:SSH ed25519 key · 系统加固(禁 root · UFW · fail2ban · unattended-upgrades)· Docker + Compose 0.5 天 加固的 VPS · Docker 运行正常
阶段 2 服务编排docker-compose.prod.yml(api + postgres:16 + redis:7 + nginx + certbot)· nginx.conf(反代 + CORS + 限流)· SQLite→PostgreSQL 迁移 · certbot HTTP-01 签发 · .env.production 填密钥 1-1.5 天 VPS 本地 HTTPS 可访问 api
阶段 3 DNS + 前端集成:CF DNS 加 api A 记录(🟠 Proxied)· CF SSL Mode: Full (strict)· xisound-website 环境变量 PUBLIC_API_BASE_URL = https://api.joysnd.com · Retry deployment 0.5 天 https://api.joysnd.com/health 200
阶段 4 CI/CD:GitHub Actions workflow(build-push-action 推 GHCR → ssh-action 到 VPS → docker compose pull api + up -d)· GitHub Secrets(VPS_SSH_KEY / VPS_HOST / GHCR_TOKEN 1 天 push main 自动部署
阶段 5 监控 + 报警:UptimeRobot(api / www / docs 三端点 5min 探活)· Sentry .NET SDK(DSN + 10% trace 采样)· 腾讯云主机监控(CPU/内存/磁盘阈值) 0.5 天 异常自动告警
阶段 6 生产验证:端到端留资测试 · SSL Labs A+ · 限流 429 · CORS 白名单 · PostgreSQL 每日备份 cron + 恢复演练 0.5 天 M6.2 验收通过

5.3.3 数据库迁移策略

graph LR
    Local[M6.1 本地<br/>SQLite · data/app.db<br/>EnsureCreated]
    Migrate[dotnet ef migrations<br/>add InitialPostgres]
    Prod[M6.2 生产<br/>PostgreSQL 16<br/>正式 migrations]

    Local -.appsettings 改 Provider.-> Migrate
    Migrate -->|dotnet ef database update| Prod

    class Local xyL0
    class Migrate xyL2
    class Prod xySuccess

迁移决策: - M6.1 本地数据量小(验证数据约 1-10 条)· 生产启动时直接 truncate 重新开始(推荐) - 如需保留 · 用 sqlite3 data/app.db .dump 导出 SQL · 手动适配 Postgres 语法(AUTOINCREMENT → SERIAL)

5.3.4 M6.2 验收清单

  • https://api.joysnd.com/health 返回 200 · SSL Labs A+ 评级
  • 生产端到端留资提交成功 · 后端入库 + 企微推送 + 邮件通知三项全中
  • GitHub Actions 自动部署流水线跑通 · push main → 2-3 分钟后生产更新
  • UptimeRobot 三个端点(api/www/docs)全部 ✅ Up
  • Sentry 能捕获到测试异常
  • PostgreSQL 自动备份已跑一次 · 恢复验证成功
  • 限流测试:连续 POST 6 次 /api/lead · 第 6 次返回 429
  • CORS 测试:www.joysnd.com 允许 · 第三方域拒绝
  • 文档更新:07_web/CLAUDE.md 里程碑加 M6.2 ✅ 行 · TODOLIST.md 相关项勾选

5.3.5 成本估算

项目 月费 年费 说明
腾讯云轻量应用(香港 2C4G 80G 1TB) ¥60-80 ¥700-900 免备案 · 年付优惠
域名 joysnd.com(腾讯云) ¥55-65 续费
Cloudflare Pages (Free) ¥0 ¥0 500 构建/月 · 100 GB 带宽/月(远超需求)
Resend Free ¥0 ¥0 3,000 封/月
UptimeRobot Free ¥0 ¥0 50 monitors · 5min 间隔
Sentry Free ¥0 ¥0 5,000 errors/月
合计 ¥60-80 ¥755-965 第一年总成本 ≤ ¥1000

5.3.6 风险与缓解

风险 可能性 缓解
VPS 宕机 UptimeRobot 5min 告警 · 腾讯云 99.95% SLA
邮件被标记垃圾 M6.4 完成 Resend 域名验证(SPF/DKIM/DMARC)
企微机器人限流 20 次/分钟 留资量远低于此 · M6.3 可选引入消息队列缓冲
SQLite 并发写入锁 已规避 M6.2 切 PostgreSQL 即消失
CI/CD 部署失败 健康检查失败自动回滚 · 保留最近 5 版镜像
PostgreSQL 数据丢失 每日 pg_dump + 本地/COS 保留 30 天 · 季度恢复演练

下一步: 1. 用户侧完成阶段 0 前置准备(账号 + 采购) 2. 按 TODOLIST.md §3 分阶段执行 3. M6.2 完成后 · 扩展本节为"M6.2 ✅ 已交付"版本(类似 v2.0 对 M6.1 的改写)

5.4 阶段 2 · MkDocs 文档站部署(M6.1.1)

权威手册另行

本节仅为 M6.1.1 文档站部署摘要。完整的仓库架构、Actions workflow 细节、作者操作手册、本地预览、故障排查见同目录: 👉 MkDocs 文档站部署 · docs.joysnd.com

5.4.1 背景与决策

  • 06_docs 全库(含 BP / 融资 / 薪酬等敏感资料)归属私有 openclaw.git,不能整仓推到公共托管
  • site-build/ 子目录(MkDocs 源)需要独立成公共仓 mengliliusha/xisound-docs,供 GitHub Actions + Cloudflare Pages 消费
  • Q1 决策 = B:采用 GitHub Actions 自动化方案(作者只 push 源码,产物由 CI 自动构建并推 gh-pages 分支)

5.4.2 部署链路(三段式)

graph LR
    Author[作者本地<br/>site-build/] -->|git push main| DocsRepo[xisound-docs<br/>main 分支]
    DocsRepo -->|Actions<br/>mkdocs build| GhPages[gh-pages 分支<br/>_site/ 产物]
    GhPages -->|Webhook 监听| CFPages[CF Pages<br/>xisound-docs 项目]
    CFPages --> Domain[docs.joysnd.com]

    class Author,DocsRepo xyL4
    class GhPages xyL5
    class CFPages xyL1
    class Domain xySuccess

5.4.3 交付清单(本轮已完成)

# 交付物 路径 状态
1 MkDocs site_url 补齐 site-build/mkdocs.yml L14
2 依赖锁定 site-build/requirements.txt
3 GitHub Actions workflow site-build/.github/workflows/deploy.yml
4 权威部署手册 site-build/docs/D3-architecture/deployment-topology/docs-site-deployment.md
5 07_web 前端环境变量 + 明文凭证提示清理 xisound-website/.env.example + src/pages/docs/** 🔄 进行中
6 用户首次 push 到 mengliliusha/xisound-docs (用户手动执行) ⏳ 待办
7 CF Pages 项目创建 + 自定义域 docs.joysnd.com 绑定 CF Dashboard ⏳ 待办

5.4.4 与 07_web 前端的集成点

xisound-website 修改 目的
.env.example 新增 PUBLIC_DOCS_BASE_URL + PUBLIC_INTERNAL_DOCS_BASE_URL 生产域名 https://docs.joysnd.com · 本地 .env.local 可覆盖
src/pages/docs-public/index.astro 环境变量化 替换 M5.7 硬编码 http://127.0.0.1:8000
src/pages/docs-internal/index.astro 环境变量化 同上
src/pages/docs/internal-gate.astro 删除明文凭证 UI Q2 决策:保留登录功能但页面不再明文显示 xisound/DevPreview2026
src/pages/docs/index.astro note 脱敏 + 演进路径重写 反映 M6.1.1 已上线现状

5.4.5 M6.1.1 验收清单

  • mengliliusha/xisound-docs:main 首次 push 后 GitHub Actions 构建通过
  • gh-pages 分支自动生成 · 含 _site/index.html
  • CF Pages 项目 xisound-docs 创建并连接 gh-pages 分支
  • docs.joysnd.com 自定义域绑定成功 · HTTPS 证书 Active
  • 首页 + 随机内页 + Mermaid + 搜索功能验证通过
  • www.joysnd.com/docs-public/ 外链全部跳转到 docs.joysnd.com
  • www.joysnd.com/docs/internal-gate 页面无明文凭证 · 登录功能保留
  • CF Pages → xisound-website 项目环境变量 PUBLIC_DOCS_BASE_URL 补齐并重部署

6. 本地端到端验证(6/6 通过 · 已归档命令与结果)

6.1 启动两端

# 终端 A · 后端
cd AlgoDepartment/xisound-api
cp .env.example .env         # 所有密钥可留空 · 走 no-op 降级
dotnet restore
dotnet run --project src/XiSound.Api
# → 监听 http://localhost:5000 · Swagger: /swagger

# 终端 B · 前端
cd AlgoDepartment/07_web/xisound-website
echo "PUBLIC_API_BASE_URL=http://localhost:5000" > .env.local
npm run dev
# → 监听 http://localhost:4321

6.2 6 项验证全部通过

# 验证点 命令 实际结果
1 dotnet restore dotnet restore XiSound.Api.sln ✅ 22.3 秒 · 还原完成
2 dotnet build dotnet build XiSound.Api.sln --no-restore -c Debug ✅ 8.3 秒 · 0 warnings · 0 errors
3 GET /health curl http://localhost:5000/health ✅ HTTP 200 · {"ok":true,"status":"alive","milestone":"M6.1"}
4 POST /api/lead 见下方 JSON 示例 ✅ HTTP 202 · {"ok":true,"leadId":"7252f48a3cc9492d8d5844cd8b394fcb"}
5 GET /admin/leads curl -H "X-Admin-Key: dev-key" http://localhost:5000/admin/leads?limit=5 ✅ HTTP 200 · total:1 · 字段完整 · UTM 拆分正确
6 no-op 降级日志 Select-String -Pattern "\[Lead\]|\[WeworkBot-NoOp\]|\[ResendEmail-NoOp\]" run.log ✅ 4 条日志全部命中(含 XiSound.Api started · Lead saved · WeworkBot-NoOp · ResendEmail-NoOp)
{
  "stage": "resource-download",
  "resourceTitle": "声学设计方案白皮书",
  "name": "张三",
  "phone": "13800138000",
  "email": "zhangsan@example.com",
  "company": "某声学公司",
  "role": "声学工程师",
  "audience": "oem-new",
  "consent": true,
  "utm": { "source": "baidu", "medium": "cpc", "campaign": "brand" }
}

7. 配置与环境变量

7.1 xisound-api 配置项

Key 说明 默认/占位
Database:Provider sqlite / postgres sqlite
ConnectionStrings:Default DB 连接串 Data Source=data/app.db
Cors:AllowedOrigins 官网域白名单(逗号分隔) 含 localhost:4321 + joysnd.com
Admin:ApiKey /admin/* 保护 key · 空则 503 拒绝 dev 环境 dev-key · 生产必须覆盖
Resend:ApiKey Resend API Key · 空则 no-op
Resend:FromAddress 发信地址 AutoAudio <onboarding@resend.dev>
Resend:ToOps 运营通知邮箱 ops@joysnd.com
Wework:WebhookUrl 企微群机器人 · 空则 no-op
Lead:RateLimit:WindowSeconds 限流窗口 60
Lead:RateLimit:MaxRequests 窗口内最大请求数 5

7.2 优先级

环境变量(__ 分隔)> appsettings.{Env}.json > appsettings.json

7.3 降级策略(M4 + W2)

空凭据不报错

Resend:ApiKey 为空 → 打印 [ResendEmail-NoOp] would send to {To} 日志 · 不抛异常 Wework:WebhookUrl 为空 → 打印 [WeworkBot-NoOp] content=... 日志 · 不抛异常 Admin:ApiKey 为空 → /admin/* 所有请求返回 503 Service Unavailable(避免裸奔)


8. 风险与缓解

风险 缓解策略
SQLite 文件并发写入锁 M6.1 量小不触发 · M6.2 切 PostgreSQL 即消失
CF Pages 跨域 fetch 到 api.joysnd.com 失败 Cors:AllowedOrigins 默认已含 https://xisound-website.pages.dev(CF 自动域)+ https://www.joysnd.com
.env 密钥泄漏 .gitignore 已排除 · 首次 commit 前 git status 自查
限流误伤(内网 NAT 共享 IP) M6.2 Redis 限流升级时改用 X-Forwarded-For + 账号维度双键
Resend 海外节点国内丢信 M6.4 切 M2 阿里云 DirectMail 国内线路(需 ICP 备案)
企微机器人限频 20 次/分钟 M6.1 留资量远低于此 · M6.2 引入消息队列缓冲
EF Core EnsureCreated 与 migrations 冲突 M6.2 升 Postgres 时切正式 migrations(dotnet ef migrations add Init

9. M6.2 - M6.4 延后项展望

阶段 核心目标 依赖前置 预计耗时
M6.2 VPS 部署上线 · api.joysnd.com · PostgreSQL 持久化 · GitHub Actions CI/CD 腾讯云账号 + CF DNS 3-5 天
M6.3 账号系统 JWT · 下载签名 URL · 删前端 auth.ts M6.2 完成 3-5 天
M6.4 Resend 域名验证 + 飞书同步 + 阿里云 DirectMail 国内双通道 Resend 账号 + ICP 备案 2-3 天

M6.1 代码的前瞻设计: - EF Core 双 provider 已预置 · M6.2 升 Postgres 仅改配置 - IEmailService / IWeworkNotifier 接口抽象 · M6.4 切 DirectMail 只需新加实现 - AdminKeyMiddleware 与 JWT 在同一 pipeline · M6.3 升 JWT 时并存不互相干扰


10. Commit 对应关系

仓库 Commit 变更
xisound-website 32b3b40 feat(M6.1): wire LeadForm to xisound-api with env base URL
xisound-api(新建) 63706c9 feat(M6.1): ASP.NET Core 8 backend scaffold with lead API + admin + Resend/Wework no-op

11. 相关文档


版本历史

版本 日期 变更
v1.0 2026-05-06 首次发布 · 方案对比版 · B3/B6/B7 三项决策的作用说明与选项优劣对比 · 已归档为 07web-m6.1-deploy-plan-v1.0-archive.md
v2.0 2026-05-06 定稿版 · 反映实际交付(CF Pages 纯静态 + 独立 xisound-api .NET 8 仓库 + SQLite 双栈)· 6/6 端到端验证通过 · 与 v1.0 决策差异见 §2.3

07_web · M6.1 最小可发布部署方案 · v2.0 · 2026-05-06 · © Xisound Inc.