TL;DR: 设计良好的推荐项目是 SaaS 产品中投资回报率最高的获客渠道,被推荐客户的留存率通常高出 25-30%,终身价值(LTV)高出 16%。本指南涵盖完整实现方案——从奖励结构心理学、邀请码生成,到信用引擎架构、防欺诈策略和数据分析。包含可直接用于生产环境的数据库 Schema 和基于 TanStack Start + Cloudflare D1 的服务端代码。
推荐项目为何有效(数据支撑)
推荐营销不仅仅是一种增长手段——它本质上是一种信任机制。人们对同龄人推荐的信任度远高于其他任何营销形式:
| 渠道 | 信任率 | 转化率 | 平均 LTV | 数据来源 |
|---|---|---|---|---|
| 朋友推荐 | 92% | 10-30% | +16% 更高 | Nielsen |
| 在线评价 | 70% | 3-10% | 基准值 | BrightLocal |
| 付费广告 | 25% | 0.5-2% | -20% 更低 | Meta Ads |
对于 SaaS 来说,被推荐客户的流失率低 25-30%,终身价值比自然获客客户高出 16%(Deloitte)。原因如下:
- 预筛选的潜在客户:推荐人只会邀请真正能从中受益的人
- 社交式上手:新用户有一位内置的"伙伴"帮助他们起步
- 网络效应:团队或组织中加入的人越多,黏性就越强
奖励结构:什么有效,什么无效
双侧奖励模型
最有效的 SaaS 推荐项目会同时奖励推荐人和被推荐用户:
| 模型 | 推荐人获得 | 被推荐人获得 | 代表公司 |
|---|---|---|---|
| 双侧折扣 | 1 个月免费 | 首 3 个月 8 折 | Dropbox、Airbnb |
| 双侧积分 | $50 账户积分 | $25 账户积分 | Uber、Robinhood |
| 单侧奖励 | 1 个月免费 | 无 | (效果较差) |
| 公益捐赠 | 捐 $5 给慈善 | 捐 $5 给慈善 | TOMS、Warby Parker(B2C) |
确定奖励金额
黄金法则:奖励金额应相当于 1 个月订阅费用的 25-50%。
以 $29/月的 SaaS 为例:
- 推荐人奖励:$7.25 - $14.50 积分(1-2 周免费)
- 被推荐人奖励:$7.25 - $14.50 积分
以 $99/月的 SaaS 为例:
- 推荐人奖励:$25 - $50 积分
- 被推荐人奖励:$25 - $50 积分
积分 vs. 折扣 vs. 现金
| 奖励类型 | 优点 | 缺点 | 最适合场景 |
|---|---|---|---|
| 服务积分 | 成本低(零边际成本),鼓励持续使用 | 用户可能不需要更多积分 | 按用量计费的 SaaS |
| 订阅折扣 | 直接降低流失壁垒 | 与账单周期对接复杂 | 月订阅 SaaS |
| 现金/PayPal | 激励效果最强 | 成本高,体感像交易 | 企业级 SaaS |
| 礼品卡 | 管理简单 | 感知价值低于现金 | B2C/B2B 混合 |
| 功能解锁 | 零成本,感知价值高 | 仅适用于免费增值模式 | 免费增值产品 |
推荐系统的数据库 Schema
结构良好的数据库是任何推荐系统的基础。以下 Schema 支持可配置奖励的邀请码、兑换追踪和完整的积分账本:
-- 表 1:邀请码(由推荐人生成)
CREATE TABLE invite_codes (
id TEXT PRIMARY KEY,
code TEXT NOT NULL UNIQUE, -- 例如 'FRIEND-ABC123'
creator_id TEXT NOT NULL, -- 创建此码的用户
max_uses INTEGER DEFAULT 10,
use_count INTEGER NOT NULL DEFAULT 0,
reward_type TEXT NOT NULL DEFAULT 'credit' CHECK (
reward_type IN ('credit', 'discount_percent', 'discount_fixed', 'month_free')
),
reward_value INTEGER NOT NULL DEFAULT 500, -- 以分为单位或百分比点数
is_active INTEGER NOT NULL DEFAULT 1,
expires_at INTEGER, -- 可选的过期时间
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
FOREIGN KEY (creator_id) REFERENCES users(id)
);
CREATE INDEX idx_invite_codes_code ON invite_codes(code);
CREATE INDEX idx_invite_codes_creator ON invite_codes(creator_id);
-- 表 2:兑换记录
CREATE TABLE invite_redemptions (
id TEXT PRIMARY KEY,
invite_code_id TEXT NOT NULL,
referrer_id TEXT NOT NULL, -- 分享码的人
referred_user_id TEXT NOT NULL, -- 使用该码的新用户
reward_referrer INTEGER NOT NULL DEFAULT 0, -- 授予推荐人的积分(分)
reward_referred INTEGER NOT NULL DEFAULT 0, -- 授予被推荐人的积分(分)
status TEXT NOT NULL DEFAULT 'pending' CHECK (
status IN ('pending', 'completed', 'expired', 'fraudulent')
),
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
completed_at INTEGER, -- 条件达成时的时间(例如被推荐人付款)
FOREIGN KEY (invite_code_id) REFERENCES invite_codes(id),
FOREIGN KEY (referrer_id) REFERENCES users(id),
FOREIGN KEY (referred_user_id) REFERENCES users(id)
);
CREATE INDEX idx_invite_redemptions_referrer ON invite_redemptions(referrer_id);
CREATE INDEX idx_invite_redemptions_referred ON invite_redemptions(referred_user_id);
-- 表 3:积分账本(基于积分的奖励)
CREATE TABLE credit_ledger (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
amount_cents INTEGER NOT NULL, -- 正数为积分,负数为消费
balance_after_cents INTEGER NOT NULL,
reason TEXT NOT NULL CHECK (
reason IN (
'referral_reward', 'referral_signup_bonus',
'credit_purchase', 'subscription_payment',
'admin_adjustment', 'expired'
)
),
reference_id TEXT, -- 关联 invite_redemptions 或 invoice
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE INDEX idx_credit_ledger_user ON credit_ledger(user_id);
-- 视图:每位用户的推荐统计
CREATE VIEW referral_stats AS
SELECT
ic.creator_id as user_id,
COUNT(DISTINCT ir.id) as total_referrals,
COUNT(DISTINCT CASE WHEN ir.status = 'completed' THEN ir.id END) as completed_referrals,
COALESCE(SUM(CASE WHEN ir.status = 'completed' THEN ir.reward_referrer END), 0) as total_rewards_earned,
ROUND(AVG(CASE WHEN ir.status = 'completed' THEN ir.reward_referrer END), 0) as avg_reward_per_referral
FROM invite_codes ic
LEFT JOIN invite_redemptions ir ON ir.invite_code_id = ic.id
GROUP BY ic.creator_id;
邀请码生成
邀请码需要唯一、方便人类阅读,同时防止被猜测。以下工具函数使用不含歧义字符的字符集来生成码,并通过服务端函数创建:
// src/lib/invite/codes.ts
import { createServerFn } from "@tanstack/react-start"
// 安全、方便人类阅读的邀请码生成
export function generateInviteCode(length: number = 8): string {
// 使用避免歧义字符的字符集
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" // 排除 I、O、0、1
const prefix = "FRIEND-"
const random = Array.from({ length }, () =>
chars[Math.floor(Math.random() * chars.length)]
).join("")
return `${prefix}${random}`
}
export const createInviteCode = createServerFn({ method: "POST" }).handler(
async (_, { request }) => {
const userId = await getUserId(request)
const code = generateInviteCode()
// 确保唯一性(碰撞概率可忽略但仍检查)
const existing = await env.DB.prepare(
"SELECT id FROM invite_codes WHERE code = ?"
).bind(code).first()
if (existing) {
return createInviteCode(_, { request }) // 重试
}
// 默认奖励:双方各获得 500 分($5.00)账户积分
await env.DB.prepare(
`INSERT INTO invite_codes (id, code, creator_id, reward_value)
VALUES (?, ?, ?, 500)`
).bind(crypto.randomUUID(), code, userId).run()
return { code, shareUrl: `https://tanstackship.com/invite/${code}` }
}
)
推荐流程实现
步骤 1:分享推荐链接
// src/components/invite/ShareInvite.tsx
import { useMutation } from "@tanstack/react-query"
import { createInviteCode } from "../../lib/invite/codes"
export function ShareInvite() {
const mutation = useMutation({
mutationFn: () => createInviteCode(),
})
const shareUrl = mutation.data?.shareUrl ?? ""
return (
<div className="p-6 border rounded-lg">
<h2 className="text-xl font-bold mb-4">邀请朋友,赚取积分</h2>
<p className="text-gray-600 mb-4">
分享你的邀请链接,朋友注册即可获得 $5
</p>
<button
onClick={() => mutation.mutate()}
className="bg-blue-600 text-white px-6 py-2 rounded-lg"
disabled={mutation.isPending}
>
{mutation.isPending ? "生成中..." : "获取你的推荐链接"}
</button>
{shareUrl && (
<div className="mt-4">
<label className="block text-sm font-medium mb-1">
你的推荐链接
</label>
<div className="flex gap-2">
<input
type="text"
value={shareUrl}
readOnly
className="flex-1 px-3 py-2 border rounded"
/>
<button
onClick={() => navigator.clipboard.writeText(shareUrl)}
className="px-4 py-2 bg-gray-100 rounded hover:bg-gray-200"
>
复制
</button>
</div>
</div>
)}
</div>
)
}
步骤 2:注册时兑换邀请码
// src/lib/invite/redeem.ts
import { createServerFn } from "@tanstack/react-start"
export const redeemInviteCode = createServerFn({ method: "POST" }).handler(
async ({ code, newUserId }: { code: string; newUserId: string }) => {
// 验证邀请码
const invite = await env.DB.prepare(
`SELECT ic.*, u.email as creator_email
FROM invite_codes ic
JOIN users u ON u.id = ic.creator_id
WHERE ic.code = ? AND ic.is_active = 1
AND (ic.expires_at IS NULL OR ic.expires_at > unixepoch())
AND ic.use_count < ic.max_uses`
).bind(code).first()
if (!invite) {
return { success: false, error: "邀请码无效或已过期" }
}
// 防止自我推荐
if (invite.creator_id === newUserId) {
return { success: false, error: "不能使用自己的邀请码" }
}
const now = Math.floor(Date.now() / 1000)
const redemptionId = crypto.randomUUID()
// 创建兑换记录(条件达成前状态为 pending)
await env.DB.prepare(
`INSERT INTO invite_redemptions
(id, invite_code_id, referrer_id, referred_user_id,
reward_referrer, reward_referred, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, 'pending', ?)`
).bind(
redemptionId,
invite.id,
invite.creator_id,
newUserId,
invite.reward_value, // 推荐人获得奖励
invite.reward_value, // 被推荐人获得奖励
now
).run()
// 增加邀请码使用次数
await env.DB.prepare(
`UPDATE invite_codes SET use_count = use_count + 1 WHERE id = ?`
).bind(invite.id).run()
// 立即授予被推荐人积分(注册奖励)
await awardCredit(newUserId, invite.reward_value, "referral_signup_bonus", redemptionId)
return { success: true, bonusAmount: invite.reward_value }
}
)
步骤 3:条件达成时发放推荐人奖励
// src/lib/invite/rewards.ts
// 当被推荐用户完成首次付款时触发
export const processReferralReward = createServerFn({ method: "POST" }).handler(
async ({ referredUserId }: { referredUserId: string }) => {
const pendingRedemption = await env.DB.prepare(
`SELECT ir.*, ic.reward_type
FROM invite_redemptions ir
JOIN invite_codes ic ON ic.id = ir.invite_code_id
WHERE ir.referred_user_id = ? AND ir.status = 'pending'`
).bind(referredUserId).first()
if (!pendingRedemption) return { processed: false }
// 授予推荐人积分
await awardCredit(
pendingRedemption.referrer_id,
pendingRedemption.reward_referrer,
"referral_reward",
pendingRedemption.id
)
// 标记为已完成
await env.DB.prepare(
`UPDATE invite_redemptions SET status = 'completed', completed_at = unixepoch()
WHERE id = ?`
).bind(pendingRedemption.id).run()
return { processed: true, amount: pendingRedemption.reward_referrer }
}
)
信用引擎架构
一个稳健的积分系统,处理推荐奖励、订阅支付和手动调整:
// src/lib/credit/engine.ts
export async function awardCredit(
userId: string,
amountCents: number,
reason: CreditLedger["reason"],
referenceId: string
) {
const currentBalance = await getCreditBalance(userId)
const newBalance = currentBalance + amountCents
await env.DB.prepare(
`INSERT INTO credit_ledger (id, user_id, amount_cents, balance_after_cents, reason, reference_id)
VALUES (?, ?, ?, ?, ?, ?)`
).bind(
crypto.randomUUID(),
userId,
amountCents,
newBalance,
reason,
referenceId
).run()
return newBalance
}
export async function spendCredit(
userId: string,
amountCents: number,
referenceId: string
): Promise<boolean> {
const balance = await getCreditBalance(userId)
if (balance < amountCents) return false // 积分不足
const newBalance = balance - amountCents
await env.DB.prepare(
`INSERT INTO credit_ledger (id, user_id, amount_cents, balance_after_cents, reason, reference_id)
VALUES (?, ?, ?, ?, 'subscription_payment', ?)`
).bind(
crypto.randomUUID(),
userId,
-amountCents,
newBalance,
referenceId
).run()
return true
}
export async function getCreditBalance(userId: string): Promise<number> {
const result = await env.DB.prepare(
`SELECT balance_after_cents
FROM credit_ledger
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 1`
).bind(userId).first()
return (result?.balance_after_cents as number) ?? 0
}
防欺诈策略
推荐欺诈是一个真实存在的问题。以下是多层防御体系:
// src/lib/invite/fraud-detection.ts
import { createServerFn } from "@tanstack/react-start"
export const validateReferral = createServerFn({ method: "POST" }).handler(
async ({ referrerId, refereeId, ipAddress }: {
referrerId: string
refereeId: string
ipAddress: string
}) => {
const checks = await Promise.all([
// 检查 1:同一 IP 检测
env.DB.prepare(
`SELECT COUNT(*) as count FROM users
WHERE ip_address = ? AND id != ?`
).bind(ipAddress, refereeId).first(),
// 检查 2:推荐速度(过去 24 小时内多少个)
env.DB.prepare(
`SELECT COUNT(*) as count FROM invite_redemptions
WHERE referrer_id = ? AND created_at > unixepoch() - 86400`
).bind(referrerId).first(),
// 检查 3:相同设备指纹(如果有的话)
// 实现取决于你的指纹方案
])
const [ipCheck, velocityCheck] = (checks as [{ count: number }, { count: number }])
if (ipCheck.count > 3) {
// 标记审查 — 同一 IP 下不同用户过多
await flagForReview(referrerId, "referral_fraud_same_ip")
return { valid: false, reason: "suspicious_activity" }
}
if (velocityCheck.count > 10) {
// 24 小时内超过 10 次推荐对大多数 SaaS 来说很可疑
await flagForReview(referrerId, "referral_fraud_high_velocity")
return { valid: false, reason: "rate_limited" }
}
return { valid: true }
}
)
推荐项目数据分析
追踪推荐漏斗指标有助于持续优化项目。以下 SQL 查询提供推荐健康的完整视图:
-- 监控推荐项目健康的仪表板查询
-- 1. 推荐漏斗
SELECT
COUNT(DISTINCT ic.creator_id) as users_with_codes,
COUNT(DISTINCT ir.referred_user_id) as users_who_clicked,
COUNT(DISTINCT CASE WHEN ir.status = 'completed' THEN ir.referred_user_id END) as converted_referrals,
COUNT(DISTINCT CASE WHEN s.status = 'active' THEN ir.referred_user_id END) as retained_referrals
FROM invite_codes ic
LEFT JOIN invite_redemptions ir ON ir.invite_code_id = ic.id
LEFT JOIN subscriptions s ON s.user_id = ir.referred_user_id AND s.status = 'active'
-- 2. 推荐排行榜
SELECT
u.email,
COUNT(ir.id) as referrals_sent,
COUNT(CASE WHEN ir.status = 'completed' THEN 1 END) as referrals_completed,
SUM(CASE WHEN ir.status = 'completed' THEN ir.reward_referrer END) / 100.0 as rewards_earned
FROM users u
JOIN invite_codes ic ON ic.creator_id = u.id
LEFT JOIN invite_redemptions ir ON ir.invite_code_id = ic.id
GROUP BY u.id
ORDER BY referrals_completed DESC
LIMIT 25
-- 3. 推荐 vs. 自然获客留存对比
SELECT
CASE WHEN ir.id IS NOT NULL THEN 'referred' ELSE 'organic' END as acquisition_channel,
COUNT(DISTINCT u.id) as total_users,
COUNT(DISTINCT CASE WHEN s.status = 'active' THEN u.id END) as active_users,
ROUND(AVG(s.mrr), 2) as avg_mrr,
ROUND(AVG(s.created_at - u.created_at) / 86400, 0) as avg_days_to_churn_or_now
FROM users u
LEFT JOIN subscriptions s ON s.user_id = u.id
LEFT JOIN invite_redemptions ir ON ir.referred_user_id = u.id
GROUP BY acquisition_channel
推荐项目优化清单
- 奖励结构为双侧(推荐人和被推荐人均受益)
- 奖励金额为 1 个月订阅费用的 25-50%
- 邀请码易于分享(URL、复制按钮、邮件、社交平台)
- 已建立欺诈检查(同 IP、速度限制、自我推荐)
- 积分系统正确处理并发消费
- 推荐人奖励在条件达成后才释放(非立即发放)
- 推荐数据分析仪表板展示漏斗和 ROI
- 朋友注册后向推荐人发送邮件通知
- 奖励到账后向推荐人发送邮件通知
- 已建立 A/B 测试框架用于奖励金额实验
- 服务条款涵盖推荐欺诈和奖励撤销
- 自我推荐检测防止用户刷单
总结
推荐项目不是"设置好就不用管"的增长渠道——它需要精心设计、实施和持续优化。核心原则如下:
- 双向奖励——推荐人和被推荐人都应感到自己是赢家
- 延迟发放推荐人奖励——直到被推荐人采取了有价值的行动(付款、激活)才发放,以防止欺诈
- 优先建设积分基础设施——能处理今日推荐奖励的积分引擎,明天也能处理客服积分、测试用户奖励和促销活动
- 持续监控欺诈——缺乏适当管控时,增长渠道会迅速变成成本中心
- 衡量推荐 LTV vs. 自然 LTV——如果被推荐客户没有更高的价值,需要调整奖励结构或目标人群
做得好,推荐就会成为你最高质量、最低成本的获客渠道——并把你的客户变成最有效的销售团队。
将推荐项目纳入更广泛增长策略,请参阅我们的 SaaS 增长框架:从零到百、UTM 归因完整指南 用于追踪推荐来源、SaaS 定价心理学 用于奖励优化,以及 SaaS 邮件营销自动化 用于推荐通知流程。