☰
基于Cloudflare免费额度快速搭建SaaS登录支付后台脚手架
2026/10/14 0:36:36 网站建设 项目流程

经常有开发者私下问我:想做一个能收费的小产品,从零开始到底要多久?答案往往不是功能开发本身,而是被“服务器选型、登录注册、支付对接、后台管理”这些所有 SaaS 都绕不开的公共模块拖住了。过去走完这套流程,买服务器、配置环境、注册域名、搞定资质、再把前端后端数据库串起来,顺利也要一两周。

现在有一种更轻的路径:把整套 SaaS 脚手架开源,登录、支付、后台全部预先接好,直接跑在 Cloudflare 免费额度上。这就是本文要讲清楚的核心主题。它能让你在资质和支付渠道已经具备的前提下,用一个晚上把技术链路完整跑通,而不是花大量时间去处理重复的“基础设施”。

需要先声明一个判断:一个晚上跑通 demo 是可能的,但“正式收款”仍然绕不开资质审核、支付渠道申请、域名校验、税务等合规环节。本文解决的是“技术链路如何最短跑通”的问题,也就是登录、支付、后台这些模块在 Cloudflare 免费额度上到底怎么设计与落地的。

1. 为什么“跑在 Cloudflare 免费额度上”是个人 SaaS 的最优起点

先说结论:对于个人开发者、独立开发者、小团队做 MVP 验证,Cloudflare 免费额度几乎是当前性价比最高的起点,没有之一。原因不是“免费”这个字,而是 Cloudflare 把边缘计算、免费数据库、对象存储、访问控制、WAF 这些原本需要多供应商组合的能力,统一放到同一个账号体系下,并且对个人项目足够宽容。

它真正降低的是三类成本:

第一是固定成本。传统方案里,即使你的产品一个用户都没有,服务器月租、数据库费用、对象存储费用也是刚性的。Cloudflare 免费额度是按用量计费的,个人项目前期的请求量不大,账单长期保持为 0 是很常见的事情。

第二是运维成本。Workers 天然跑在边缘节点,不需要你配置 Nginx、不需要打补丁、不需要担心单点故障。D1 数据库虽然是 SQLite 兼容的,但创建表、执行迁移、备份导出都有对应的命令行工具,不需要单独部署数据库服务。

第三是组合成本。这个容易被忽略。单独看 Cloudflare 的每个产品,市面上都有替代品。但把 Workers、Pages、D1、KV、R2、Access、WAF 组合在一起,并且共享同一套域名、证书和访问策略时,工程复杂度会低很多。对于没有专职运维的个人项目来说,这种“全家桶”的省心程度比功能参数更重要。

但也要清醒看到边界:免费额度不适合重计算、GPU 推理、长时间 WebSocket 连接、海量文件存储,也不适合对数据主权有严格要求的业务。如果你要做的核心功能依赖 GPU,或者业务场景对数据库读写量极大,那 Cloudflare Stack 就不是最优选。它适合的是“标准 CRUD + 认证 + 支付 + 基础管理后台”这类典型 SaaS 形态。

2. SaaS 脚手架拆解:登录、支付、后台各解决了什么问题

一个 SaaS 的最小子集是什么?不是花哨的首页,而是三件事:用户是谁、用户付没付钱、你怎么管理用户。对应下来就是登录认证、支付订阅、后台管理。这三个模块几乎是所有 SaaS 的“最大公共子集”,也是开源脚手架最值得帮你提前做好的部分。

登录模块的本质不是“用户名密码校验”这么简单。它要管理注册、登录、会话保持、登出、密码恢复,还要考虑多端登录、token 过期、会话吊销。很多新手会乐观估计这个模块的工作量,结果自己写 JWT,写到最后发现 token 无法吊销、刷新过期时间没做、密码找回没接邮件服务。开源脚手架的价值在于它先把一条安全完整但通用的链路搭好,你只需要把业务字段加上去。

支付模块的本质也不是“调一个 API 收钱”。真正的复杂度在支付成功后的异步回调。用户在 Stripe 页面上完成支付,Stripe 通过 Webhook 通知你的服务器,你的服务器要验证签名、保证幂等、更新订单状态、给用户开通功能。这一整个流程如果从零开始搭,要踩不少坑:Webhook 地址没配置、签名验证失败、事件重复推送导致重复开通。脚手架把这些都处理好了,相当于把最容易出错的部分替你屏蔽了。

后台管理模块的本质是隔离与授权。很多人以为后台就是一个“能看数据的页面”,但更关键的是谁能访问、能做什么操作。自己写一套管理员登录体系意味着你要维护身份源、权限表、审计日志。在 Cloudflare Stack 里有一个更轻的方案:后台路径直接交给 Cloudflare Access 保护,身份校验由 Cloudflare 完成,你的 Worker 只需要读取请求头里的用户邮箱做二次判断。

这三个模块组合起来,才让“一个晚上上线能收钱的 SaaS”成为可能。你真正要写的是业务逻辑,而不是重复造轮子。

3. Cloudflare 免费额度技术矩阵与整体架构

在动手之前,需要先清楚 Cloudflare 免费额度覆盖了哪些产品,以及它们在 SaaS 架构中分别承担什么角色。下面用一张表格来快速定位:

需求推荐产品在 SaaS 中的角色
静态前端Pages前端页面托管,自带 CDN 与自动构建
API 与业务逻辑Workers处理注册、登录、支付回调、后台查询等请求
数据库D1用户表、订单表、订阅表等结构化数据
会话存储KV登录 token 的短时效存储,读写极快
对象存储R2头像、附件等文件资源
后台访问保护Access保护管理员页面和管理 API
安全防护WAF / Bots拦截恶意请求,保护 API 端点

这套架构的请求链路是这样的:用户浏览器先访问 Cloudflare CDN,静态资源从 Pages 加载;需要读取数据时,浏览器调用同一个域名下的/api/*路径,请求经过 Cloudflare WAF 后进入 Worker;Worker 读取 D1 数据库和 KV 会话存储,返回 JSON 数据。支付环节则由 Stripe 服务器主动把 Webhook 事件推送到 Worker 的公开端点,Worker 验签后更新数据库。

之所以选择这套组合而不是“前端放 Vercel、后端放一台云服务器、数据库单独买”,核心原因是同域部署带来的便利。前端和 API 在同一个域名下,没有跨域配置问题,登录 cookie 天然生效。如果前端在 Vercel、API 在另一台服务器,你要处理 CORS,要处理 cookie 跨域,还要维护两套部署流程。Cloudflare 把这一切统一了,对个人项目是极大的减负。

从架构模式上看,这是经典的“静态前端 + 边缘函数 + 托管数据库”组合,适合读多写少、按需计费的 SaaS 业务。项目前期请求量不大,免费额度足够覆盖;等用户量上来之后,再按量付费或迁移到独立计算资源都不算伤筋动骨,因为业务代码保持简单,依赖的是标准 SQL 和标准 HTTP 接口。

4. 环境准备与项目初始化

这套方案的基础环境并不复杂,所有工具都是跨平台的。开始之前,你需要准备以下内容:

  • Node.js 18 或更高版本,建议使用 LTS。
  • npm 或 pnpm 包管理器。
  • 一个 Cloudflare 账号。
  • 一个 Stripe 测试账号(用于支付流程验证)。
  • 一个自定义域名(可选但强烈建议,正式产品不要用默认的 workers.dev 域名)。

安装 Cloudflare 的命令行工具 wrangler:

npm install -g wrangler

安装完成后登录 Cloudflare 账号:

wrangler login

这个命令会打开浏览器完成授权。登录成功后,wrangler 会把凭证保存在本地,后续部署、操作 D1、管理环境变量都基于这个凭证。

项目结构建议采用一个清晰的分层结构,把静态前端、Worker、数据库迁移分开管理:

my-saas-starter/ ├── frontend/ # 前端页面(React/Vue/纯 HTML 均可) ├── worker/ # Worker 层,包含所有 API 逻辑 │ └── src/ │ └── index.ts ├── migrations/ # D1 数据库迁移 SQL ├── wrangler.toml # Cloudflare 项目配置 └── package.json

wrangler.toml 是这套方案的核心配置文件。下面是一个典型配置:

name = "my-saas" main = "worker/src/index.ts" compatibility_date = "2025-01-01" [[d1_databases]] binding = "DB" database_name = "my-saas-db" database_id = "your-database-id" migrations_dir = "migrations" [[kv_namespaces]] binding = "SESSIONS" id = "your-kv-namespace-id"

database_id和id目前是占位符,需要通过命令创建后获取。首先创建 D1 数据库:

wrangler d1 create my-saas-db

执行成功后,命令行会输出数据库的唯一 id,把它填入 wrangler.toml。接下来创建 KV 命名空间:

wrangler kv namespace create SESSIONS

同样会把输出的 id 填到 wrangler.toml 中。

初始化时,需要先建立用户表和订单表。在migrations/0001_init.sql中写入:

CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, salt TEXT NOT NULL, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS orders ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, stripe_session_id TEXT UNIQUE, status TEXT NOT NULL DEFAULT 'pending', amount INTEGER NOT NULL, created_at INTEGER NOT NULL );

表结构先保持最小化。实际业务中通常还要有 plans、subscriptions 等表,后续可以通过新的迁移文件继续增加。

执行迁移:

wrangler d1 execute my-saas-db --remote --file=./migrations/0001_init.sql

用--remote表示操作远程数据库,如果不加这个参数,默认操作本地开发环境。

在这之后,你需要创建一个.dev.vars文件用于本地开发时的环境变量,并且通过wrangler secret put设置生产环境变量。涉及的环境变量包括 Stripe 密钥、支付价格 ID、应用域名等。

5. 核心实现一:登录认证模块

登录认证模块的目标是覆盖“注册、登录、会话保持、受保护路由”这条完整链路。很多教程喜欢直接引入 JWT,但从小项目角度出发,使用 KV 存储 Session Token 反而更直观。Session Token 没有 JWT 那样的吊销难题,清理逻辑简单,对免费额度也足够友好。

以下示例基于 Hono 框架编写,这是一个专门面向 Workers 的轻量框架,API 风格接近 Express,非常适合这类场景。先定义 Worker 的类型绑定和基础入口:

// worker/src/index.ts import { Hono } from 'hono'; type Env = { DB: D1Database; SESSIONS: KVNamespace; STRIPE_SECRET_KEY: string; STRIPE_WEBHOOK_SECRET: string; STRIPE_PRICE_ID: string; APP_URL: string; }; type Variables = { userId: string; }; const app = new Hono<{ Bindings: Env; Variables: Variables }>();

密码哈希可以使用 Web Crypto API 的 PBKDF2,它不需要引入额外依赖。实现一个工具函数:

async function hashPassword(password: string, salt: string): Promise<string> { const enc = new TextEncoder(); const key = await crypto.subtle.importKey( 'raw', enc.encode(password), 'PBKDF2', false, ['deriveBits'] ); const bits = await crypto.subtle.deriveBits( { name: 'PBKDF2', salt: enc.encode(salt), iterations: 100000, hash: 'SHA-256', }, key, 256 ); return Array.from(new Uint8Array(bits)) .map((b) => b.toString(16).padStart(2, '0')) .join(''); }

注册接口负责创建用户,并返回用户基本信息:

app.post('/api/register', async (c) => { const { email, password } = await c.req.json(); const id = crypto.randomUUID(); const salt = crypto.randomUUID(); const passwordHash = await hashPassword(password, salt); try { await c.env.DB.prepare( 'INSERT INTO users (id, email, password_hash, salt, created_at) VALUES (?, ?, ?, ?, ?)' ) .bind(id, email, passwordHash, salt, Date.now()) .run(); } catch (e) { return c.json({ error: '邮箱已注册或参数不合法' }, 400); } return c.json({ id, email }, 201); });

登录接口校验密码,校验通过后生成 session token,写入 KV:

app.post('/api/login', async (c) => { const { email, password } = await c.req.json(); const user = await c.env.DB.prepare('SELECT * FROM users WHERE email = ?') .bind(email) .first(); if (!user) { return c.json({ error: '用户不存在' }, 401); } const hash = await hashPassword(password, user.salt); if (hash !== user.password_hash) { return c.json({ error: '密码错误' }, 401); } const token = crypto.randomUUID(); await c.env.SESSIONS.put(token, user.id, { expirationTtl: 60 * 60 * 24 * 7 }); return c.json({ token }); });

这里把 token 直接返回给前端,前端在后续请求中通过 Authorization 头携带。更安全的做法是设置为 HttpOnly Cookie,starter 为了演示简单优先返回 JSON。

受保护路由通过中间件完成会话校验:

app.use('/api/*', async (c, next) => { const auth = c.req.header('authorization'); if (!auth) { return c.json({ error: '未登录' }, 401); } const userId = await c.env.SESSIONS.get(auth); if (!userId) { return c.json({ error: '会话无效或过期' }, 401); } c.set('userId', userId); await next(); });

前端登录页调用示例:

const res = await fetch('/api/login', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ email, password }), }); const data = await res.json(); localStorage.setItem('token', data.token);

到这里,一个最小但完整的登录认证模块就成型了。代码量不大,但因为有了 KV 会话和 D1 用户表,它已经具备上线所需的核心逻辑。生产环境还可以把 PBKDF2 替换为更专业的密码哈希方案,或者接入第三方 Auth 服务。

6. 核心实现二:支付与订阅模块

支付模块以 Stri

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询