SaaS 套件
官方 saas 插件(付费)让 InsightSite 站点直接变成一个 SaaS 产品:用户注册登录、用户中心、后台用户管理。它是对标 ShipFast、FlareStarter 这类 SaaS 模板的能力,但运行在已有的 CMS、多语言、SEO、商城之上,无需另起一个项目。插件随框架一起发布(plugins/saas)。
node node_modules/nuxtcms/scripts/plugin.mjs add saas
pnpm build:cf && pnpm deploy
包含用户账户、套餐与收费、按套餐限制功能、积分、API 密钥和团队。
功能
| 功能 | 说明 |
|---|---|
| 注册与登录 | 邮箱 + 密码;邮件免密登录(一次性链接,15 分钟有效,没有账户时自动注册);忘记密码;可选「必须验证邮箱后才能登录」 |
| 第三方登录 | Google、GitHub、微信开放平台网站应用(扫码),按邮箱自动关联已有账户(仅当第三方确认该邮箱已验证) |
用户中心 /account | 个人资料、修改 / 设置密码、在所有设备退出、当前套餐与积分;插槽 account:main、account:aside 供站点或其他插件扩展 |
| 后台 用户账户 | 搜索、按状态筛选、登录方式、标记已验证、停用 / 启用、强制退出、以用户身份查看(记录日志,用户中心显示提示条) |
| 安全 | 独立的 Cookie 与签名密钥(用户令牌不可能被当作后台令牌);改密码、停用、强制退出会让所有旧会话失效;一次性链接只存哈希、只能用一次;登录 / 注册 / 发信均有按 IP 和按账户的频率限制,并接入站点验证码;跳转地址只允许站内路径 |
设置
在 后台 → 插件 → SaaS 套件 → 配置 中设置:是否开放注册、密码登录、邮件登录、是否必须验证邮箱、登录后跳转的页面,以及各第三方登录的密钥。邮件登录、验证邮件和重置密码依赖 后台 → 邮件配置 中的 SMTP(本地开发没有配置 SMTP 时,链接会打印在开发服务器控制台)。
密钥也可以用环境变量提供,后台留空即可:NUXT_SAAS_GOOGLE_CLIENT_ID、NUXT_SAAS_GOOGLE_CLIENT_SECRET、NUXT_SAAS_GITHUB_CLIENT_ID、NUXT_SAAS_GITHUB_CLIENT_SECRET、NUXT_SAAS_WECHAT_APP_ID、NUXT_SAAS_WECHAT_APP_SECRET。
第三方平台中填写的回调地址:
| 平台 | 回调地址 |
|---|---|
| Google(OAuth 客户端 ID,Web 应用) | https://你的域名/api/saas/oauth/google/callback |
| GitHub(OAuth App) | https://你的域名/api/saas/oauth/github/callback |
| 微信开放平台(网站应用) | 授权回调域填 你的域名 |
在站点中使用
页面需要登录时使用 saas-auth 中间件,组件中用 useSaasUser() 读取当前用户(用户数据只在浏览器端加载,页面可以被 CDN 缓存):
<script setup lang="ts">
definePageMeta({ middleware: 'saas-auth' })
const { user } = useSaasUser()
</script>
服务端接口中:
export default defineEventHandler(async (event) => {
const user = await requireSaasUser(event) // 未登录时 401
return { plan: user.plan, credits: user.credits }
})
getSaasUser(event) 在访客时返回 null。用户中心扩展:
// app.config.ts
export default defineAppConfig({
nuxtcms: {
slots: {
'account:main': { MyUsageCard: 10 },
'header:actions': { SaasAccountButton: false }, // 隐藏页头的登录按钮
},
},
})
同时启用商城时,页头会同时出现商城会员和 SaaS 用户两个入口,可按上面的方式隐藏其中一个。
数据表 {前缀}saas_users、saas_identities、saas_tokens 在首次使用时自动创建(SQLite / D1、PostgreSQL、MySQL)。
套餐与收费
在 后台 → 套餐 新建套餐:标识(如 pro,写入用户的 plan)、等级(数字越大越高级)、月付 / 年付价格、每月赠送积分、各语言的名称 / 简介 / 功能列表、是否推荐、是否上架。free 是内置的免费版,不需要创建。
| 支付方式 | 说明 |
|---|---|
| Stripe 订阅 | 在 Stripe 后台建好按月 / 按年的循环价格,把价格 ID(price_...)填到套餐里。用户通过 Stripe Checkout 订阅,Stripe 自动续费;用户在「账单」页打开 Stripe 客户门户换卡、下载发票、切换套餐或取消。Webhook:/api/saas/webhooks/stripe,事件 customer.subscription.*、checkout.session.completed、invoice.paid |
| 微信支付 / 支付宝 | 按月或按年购买(币种 CNY)。电脑端微信扫码(Native)、手机浏览器 H5(需在商户平台开通)、支付宝电脑 / 手机网站支付。续费同一套餐时从当前到期日往后顺延;换成其他套餐时从支付当天开始计算。回调:/api/saas/webhooks/wechat、/api/saas/webhooks/alipay |
支付密钥在 后台 → 插件 → SaaS 套件 → 配置 中填写,也可以用环境变量(NUXT_SAAS_STRIPE_SECRET_KEY、NUXT_SAAS_WECHAT_PAY_MCH_ID 等)。留空时自动使用商城插件的支付配置,同一个站点不用重复填写(Stripe Webhook 签名密钥除外,每个 Webhook 地址各有一个)。演示站(NUXT_DEMO_MODE=true)上微信支付 / 支付宝为模拟支付。
页面:
| 页面 | 说明 |
|---|---|
/pricing | 价格表(按月 / 按年切换、推荐标记、年付优惠比例)。站点可以在任意页面放 <SaasPricing />,或在自己的 pages/pricing.vue 覆盖默认页面 |
/account/billing | 当前套餐与到期 / 续订时间、积分、付款记录、「管理订阅与发票」(Stripe 门户)、续费、升级 |
/account/pay/<订单号> | 微信扫码 / 跳转支付宝,自动轮询支付结果 |
| 后台 套餐、订阅收入 | 套餐管理;近 30 天收入、付费用户数、有效订阅数、订单列表;用户账户 中可手动设置套餐、到期日与积分(赠送、对公转账、退款) |
每笔付款会触发 CMS 事件 saas.payment.paid,可配合机器人 / Webhook 插件通知。
按套餐限制功能
页面(浏览器端引导,未登录去登录页,套餐不够去 /pricing):
<script setup lang="ts">
definePageMeta({ saasPlan: 'pro' }) // 专业版及更高等级;['team', 'enterprise'] 表示只限这几个
</script>
接口(真正的保护在服务端):
export default defineEventHandler(async (event) => {
const user = await requireSaasPlan(event, 'pro') // 未登录 401,套餐不够 403
return { ok: true, credits: user.credits }
})
前端判断是否显示付费功能:GET /api/saas/billing/access?plan=pro 返回 { allowed, plan };或用 useSaasUser().user.value.plan 配合 saasPlanAllows()。
积分
用户有一个积分余额和一份明细(每次增减的原因、关联订单 / 请求、变动后余额)。套餐付款按「每月赠送积分」自动入账,后台可以手动增减。按次收费的功能在服务端扣积分:
export default defineEventHandler(async (event) => {
const user = await requireSaasApiUser(event)
const { balance } = await spendSaasCredits(event, user.id, 5, 'ai.summary', { ref: 'post-42' })
// 积分不足时自动返回 402 saas.credits.insufficient({ needed, balance }),不会扣款
return { ok: true, balance }
})
扣减是一条带条件的 UPDATE,并发请求下也不会扣成负数;addSaasCredits(event, userId, n, reason) 用于赠送或退还。用户在「账单与套餐」页查看积分明细。
API 密钥
用户在 /account/api-keys 自行创建密钥(nck_ 开头,只显示一次,数据库只保存哈希),可命名、设置有效期、查看最近使用时间、随时撤销;每人最多 10 个。调用时带 Authorization: Bearer nck_... 或 X-API-Key 请求头。
const user = await requireSaasApiUser(event) // 浏览器登录或 API 密钥都可以
const user = await requireSaasApiPlan(event, 'pro') // 并且套餐为专业版或更高
示例接口:GET /api/saas/v1/me。插件设置中可以关闭 API 密钥、指定最低套餐(如 pro),以及每个密钥每分钟的请求数(默认 120)。以用户身份查看时不能创建密钥;API 密钥永远不能访问后台。
团队
套餐有「团队席位」(含所有者,1 表示不能建团队)。席位大于 1 的用户可以在 /account/team 创建一个团队,按邮箱邀请成员(邀请 7 天有效,邮件发送失败时页面直接给出邀请链接)。被邀请人用该邮箱登录或注册后接受邀请即可加入。
- 成员共享所有者的套餐:
requireSaasPlan、页面的saasPlan、/api/saas/billing/access都按「自己的套餐与所在团队套餐中等级最高的」计算;积分和 API 密钥仍是个人的。 - 角色:所有者(解散团队、设管理员)、管理员(邀请、移除普通成员)、成员(可退出)。
- 所有者套餐到期或降级后,团队成员同时失去该套餐;席位变少时按加入顺序保留前面的成员。
- 可以在插件设置中关闭团队功能。
自动邮件
插件设置中开启「自动邮件」(默认关闭)后,定时任务每小时检查一次并发送:
- 欢迎邮件:注册后 3 天内发送一次;
- 到期提醒:按期购买的套餐到期前若干天(「提醒天数」,默认
7,1)提醒续费;会自动续订的 Stripe 订阅不提醒; - 到期通知:套餐到期后发送一次,引导重新开通。
每封邮件在 saas_mail_log 中登记后才发送,并发运行也不会重复;发送失败的邮件下次重试。需要配置邮件(SMTP)和定时任务 Worker(NUXT_CRON_SECRET),接口为 POST /api/cron/saas/lifecycle。
测试
pnpm smoke:saas --base http://localhost:3192 --db .data/saas-demo.sqlite 针对开发服务器(启动配置 nuxtcms-dev-saas)跑完整流程:注册、登录、改密码、邮件链接、重置密码、验证邮箱、开关设置、后台操作、伪造令牌,以及套餐、微信 / 支付宝回调(含伪造回调)、续费与升级、Stripe Webhook、后台调整套餐、积分明细、API 密钥(限流、套餐限制、撤销)和团队(邀请、席位、角色、共享套餐)。脚本会修改该数据库,切勿指向真实站点。