邀请注册流程
实现案例:会员插件
案例演示:https://blog.muyin.site/signup
核心流程
1. 邀请链接访问
用户点击邀请链接 → 前端提取邀请码 → 存储到 Cookie (register_invite_code)
→ 生成设备指纹 → 存储到 Cookie (device_fingerprint)
→ 跳转到注册页面
2. 注册表单提交
POST /signup → UserRegistrationInterceptor 拦截
↓
[频率限制检查]
↓
IP 频率检查 → 超限?→ 返回 429
↓ 否
设备频率检查 → 超限?→ 返回 429
↓ 否
[信息采集与缓存]
↓
提取表单用户名
↓
读取 Cookie 邀请码和设备指纹
↓
构建 RegisterClientInfo 对象
↓
缓存到 Redis (key: invite:register:{username}, TTL: 24h)
↓
放行到 Halo 注册流程
3. 注册成功处理
Halo 注册成功事件 → UserRegisteredEventListener
↓
从 Redis 读取 RegisterClientInfo
↓
验证邀请码有效性
↓
[重复检测]
↓
IP 重复检查 → 重复?→ 标记为重复注册
↓ 否
设备重复检查 → 重复?→ 标记为重复注册
↓ 否
创建推荐记录 (MemberReferralRecord)
↓
记录 IP 和设备指纹
↓
清理缓存
数据结构
RegisterClientInfo
{
"inviteCode": "ABC123", // 邀请码
"ip": "192.168.1.100", // 注册 IP
"userAgent": "Mozilla/5.0...", // User-Agent
"deviceFingerprint": "fp_xxx" // 设备指纹
}
存储: Redis
Key: invite:register:{username}
TTL: 24 小时
MemberReferralRecord
{
"inviterCode": "ABC123", // 邀请人的邀请码
"inviteeUsername": "newuser", // 被邀请人用户名
"registerIp": "192.168.1.100", // 注册 IP
"deviceFingerprint": "fp_xxx", // 设备指纹
"isDuplicate": false, // 是否重复注册
"createdAt": "2026-01-28T10:00:00Z"
}
防护机制
第一阶段:注册前拦截
| 检查项 | 说明 | 触发动作 |
| IP 频率限制 | 同一 IP 在时间窗口内注册次数超限 | 返回 429,拒绝注册 |
| 设备频率限制 | 同一设备在时间窗口内注册次数超限 | 返回 429,拒绝注册 |
实现方式: RegistrationRateLimitService.isIpRateLimitedRX() / isDeviceRateLimitedRX()
第二阶段:注册后检测
| 检查项 | 说明 | 触发动作 |
| IP 重复检测 | 同一 IP 已注册过其他账号 | 允许注册,标记为重复 |
| 设备重复检测 | 同一设备已注册过其他账号 | 允许注册,标记为重复 |
实现方式: 查询历史推荐记录中的 IP 和设备指纹
关键技术点
1. 响应式编程
// 非阻塞的频率检查
return rateLimitService.isIpRateLimitedRX(ip)
.flatMap(ipLimited -> {
if (ipLimited) {
return rejectWithMessage(exchange, "注册过于频繁,请稍后再试");
}
return rateLimitService.isDeviceRateLimitedRX(deviceFingerprint)
.flatMap(deviceLimited -> {
// 继续处理...
});
});
2. Cookie 读取
private String getCookieValue(ServerWebExchange exchange, String cookieName) {
return exchange.getRequest().getCookies().getFirst(cookieName) != null ?
exchange.getRequest().getCookies().getFirst(cookieName).getValue() : null;
}
3. 表单数据提取
private Mono<String> extractUsernameFromForm(ServerWebExchange exchange) {
return exchange.getFormData()
.map(formData -> formData.getFirst("username"));
}
4. 缓存操作
// 写入缓存
pluginCacheManager.putDataCache(
generateInviteRegisterKey(username), // key: invite:register:{username}
JSONUtil.toJsonStr(clientInfo), // value: JSON 字符串
TimeUnit.SECONDS.toMillis(60 * 60 * 24L) // TTL: 24 小时
);
// 读取缓存
String json = pluginCacheManager.getDataCache(generateInviteRegisterKey(username));
RegisterClientInfo clientInfo = JSONUtil.toBean(json, RegisterClientInfo.class);
配置建议
# 频率限制配置
registration:
rate_limit:
ip:
window_seconds: 3600 # IP 检查时间窗口(1小时)
max_count: 3 # 最大注册次数
device:
window_seconds: 3600 # 设备检查时间窗口(1小时)
max_count: 3 # 最大注册次数
duplicate_detection:
ip_check_enabled: true # 启用 IP 重复检测
device_check_enabled: true # 启用设备重复检测
check_window_days: 30 # 检查最近 30 天的记录
异常场景
场景 1:用户清除 Cookie
现象: 注册时无法获取邀请码
处理: 允许正常注册,但不建立推荐关系
日志: register_invite_code Cookie 不存在
场景 2:设备指纹缺失
现象: 用户禁用 JavaScript 或浏览器不支持
处理: deviceFingerprint 为 null,仅依赖 IP 检查
影响: 防护能力下降
场景 3:缓存过期
现象: 用户在 24 小时后才完成注册
处理: 无法从缓存读取邀请信息,不建立推荐关系
预防: 监控注册流程耗时
场景 4:代理/VPN 用户
现象: 多个真实用户共享同一 IP
处理: 可能触发 IP 频率限制
优化: 依赖设备指纹区分不同用户
前端集成
1. 邀请链接格式
https://example.com/signup?invite=ABC123
2. Cookie 设置
// 提取邀请码并存储到 Cookie
const urlParams = new URLSearchParams(window.location.search);
const inviteCode = urlParams.get('invite');
if (inviteCode) {
document.cookie = `register_invite_code=${inviteCode}; path=/; max-age=86400`;
}
3. 设备指纹生成
// 使用 FingerprintJS 生成设备指纹
import FingerprintJS from '@fingerprintjs/fingerprintjs';
const fp = await FingerprintJS.load();
const result = await fp.get();
document.cookie = `device_fingerprint=${result.visitorId}; path=/; max-age=86400`;
监控指标
metrics:
- registration.total # 总注册数
- registration.with_invite # 通过邀请注册数
- registration.ip_rate_limited # IP 频率限制触发次数
- registration.device_rate_limited # 设备频率限制触发次数
- registration.duplicate_ip # IP 重复注册次数
- registration.duplicate_device # 设备重复注册次数
- registration.cache_miss # 缓存未命中次数
测试检查清单
- [ ] 正常邀请注册流程
- [ ] 无邀请码注册流程
- [ ] IP 频率限制触发
- [ ] 设备频率限制触发
- [ ] IP 重复检测
- [ ] 设备重复检测
- [ ] Cookie 缺失场景
- [ ] 缓存过期场景
- [ ] 并发注册压力测试