爆肝了,哪位大佬谁来接手?@bbs1org热
64011
注册控制系统(reg_control)设计文档
版本:1.0.0 | 适配核心:bbs1org | 单文件插件:
app/plugins/reg_control/plugin.php一、设计理念
- 零核心改动:所有能力通过核心提供的 Hook、路由、后台 Tab、Cron、Schema API 接入,核心代码一行不改,升级无冲突。
- 安全默认,纵深防御:令牌高熵哈希存储、单次有效、三类隔离;发送频率双维度(用户/IP)限流;密码找回防枚举;密码重置后全局会话失效。任何单点被绕过,下一层仍然拦截。
- 请求路径零阻塞:邮件发送是慢 IO(SMTP 握手 + 加密协商 + 传输),全部写入队列由计划任务异步派发,注册/找回请求本身只做内存级校验与一次 INSERT,响应速度不受邮件服务影响。
- 可观测:所有关键动作落审计日志(谁、何时、何 IP、改了什么),后台提供队列状态、系统自检,出问题可回溯。
- 存量兼容:安装时已有邮箱的存量用户一次性标记为已验证,启用插件不冲击老用户;校验只在用户名/邮箱"实际变化"时触发,保存其他资料不受影响。
- 失败友好:SMTP 配置缺失时前台入口自动隐藏并给出明确提示;队列失败自动退避重试并保留错误摘要;模板清空即恢复默认,不存在"配坏锁死"路径。
二、总体架构
2.1 分层
┌─────────────────────────────────────────────────────────┐ │ 接入层 7 Hook | 3 前台路由 | 后台 6 Tab | 1 Cron │ ├─────────────────────────────────────────────────────────┤ │ 领域层 令牌子系统 | 频率限制 | 校验器(用户名/域名) │ │ 邮箱变更状态机 | 审计记录 │ ├─────────────────────────────────────────────────────────┤ │ 传输层 邮件模板渲染 | 发送队列 | SMTP 客户端(socket) │ ├─────────────────────────────────────────────────────────┤ │ 基础层 配置归一化 | 5 张自持表 | 表就绪缓存 │ └─────────────────────────────────────────────────────────┘依赖方向自上而下单向:接入层只调用领域层,领域层不感知 HTTP;配置归一化(
reg_control_config())是唯一配置出口,任何读取都经过裁剪与兜底。2.2 一次注册验证的数据流
用户提交注册 → user.before_save(校验用户名规则/邮箱格式/域名名单/唯一性) → 核心 INSERT app_users → fire(user.after_save) → 初始化验证状态(verified=0) → reg_control_rate_allow('register') 频率检查 → 令牌签发(作废旧 verify 令牌 → INSERT 新哈希) → 邮件渲染入队(plugin_reg_control_queue) → 用户点击邮件链接 / 粘贴验证码 → reg_verify 路由 → 令牌校验(类型/已用/过期/邮箱匹配) → 单次消费(UPDATE ... WHERE used_at=0 抢占)→ 状态置已验证 → 审计 → Cron 任务从队列取邮件 → SMTP 发送 → 更新队列状态三、模块划分
模块 职责 代表函数 安装/Schema 5 张表幂等创建、存量用户初始化、卸载清理 reg_control_installreg_control_uninstall配置 默认值、裁剪、归一化、模板回落 reg_control_configreg_control_tpl_defaults基础设施 表就绪缓存、审计写入、验证状态读写 reg_control_table_readyreg_control_auditreg_control_state_setreg_control_user_verified频率限制 双维度窗口计数(user/ip 合并动作) reg_control_rate_allow令牌子系统 高熵签发、哈希检索、单次消费、类型隔离 reg_control_token_issuereg_control_token_rowreg_control_token_consume邮件模板 占位符替换、四套模板、必含链接校验 reg_control_mail_varsreg_control_tpl_render发送队列 入队、批量派发、退避重试、限流 reg_control_mail_queuereg_control_cron_dispatchSMTP 客户端 socket 连接、SSL/STARTTLS、AUTH LOGIN、base64 编码 reg_control_smtp_sendreg_control_smtp_cmd校验器 用户名规则、通配符编译、域名黑白名单 reg_control_validate_usernamereg_control_pattern_regexreg_control_validate_email_domainHook 接入 注册校验、保留名、发言权、资料页卡片等 reg_control_user_before_savereg_control_can_speak前台路由 验证中心、密码找回、邮箱管理 reg_control_verify_pagereg_control_reset_pagereg_control_email_page后台管理 6 子页签、表单渲染、POST 处理、自检 reg_control_admin_pagereg_control_admin_handle_postreg_control_selftest计划任务 队列派发 + 四类数据清理 reg_control_cron_dispatch样式/Manifest CSS 变量体系样式、插件声明 reg_control_css尾部return [...]共 69 个函数,全部
reg_control_前缀,无全局变量、无require依赖。四、数据库设计
5 张自持表(前缀
plugin_reg_control_),卸载时仅删除这 5 张,不触碰app_users。表 关键字段 说明 stateuser_id(UNIQUE), email, verified, verified_at 每用户一行验证状态;email 与 app_users.email 冗余存储用于"令牌与当前邮箱匹配"校验 tokenstoken_hash(UNIQUE), type, step, user_id, email, new_email, code, expire_at, used_at 令牌只存 sha256;code 为随令牌签发的 6 位数字验证码(全部有效令牌间唯一);type=verify/reset/change 三类隔离;change 用 step=1/2 区分两步 queueto_email, subject, body, status, attempts, next_try_at, last_error status 0待发/1已发/2失败终止;next_try_at 实现退避与派发排序 ratescope(user/ip), scope_key, action, created_at 频率计数明细行;窗口外数据由 Cron 与发送路径双清理 logsuser_id, action, detail, ip, created_at 审计日志;detail 含原邮箱→新邮箱完整链路 设计要点:
- 令牌不存明文:
hash('sha256', strtolower($token))入库,数据库泄露不暴露任何可用令牌。 - 单次消费用原子抢占:
UPDATE ... SET used_at=? WHERE id=? AND used_at=0,影响行数=1 才算消费成功,天然防并发重放。 - 索引贴合查询路径:tokens(user_id,type)、queue(status,next_try_at)、rate(scope,scope_key,created_at)、logs(created_at)。
五、核心功能实现方案
5.1 邮箱验证
- 令牌:
bin2hex(random_bytes(32))64 位十六进制,熵 256bit 不可猜测;有效期默认 30 分钟(1–1440 可配);发放前先作废同类型同邮箱的未用令牌(任意时刻同类仅一个有效,防令牌堆积与"重复生成")。 - 双通道验证:链接直达(
absolute_url(reg_verify?token=...)绝对地址,优先取后台配置的网站固定地址)与 6 位数字验证码(独立于链接令牌,手工输入友好);验证码在全部有效令牌间唯一,同一 IP 一小时内失败 20 次锁定(防 6 位码暴力枚举)。 - 统一兑换入口:验证中心输入 6 位验证码按令牌类型自动分派——verify 直接完成、reset 进入重置表单(验证码即凭证)、change 按步骤推进;密码找回页亦提供"验证码 + 新密码"直接重置表单。
- 消费四重校验:类型正确 → 未使用 → 未过期 → 令牌绑定邮箱与当前邮箱一致(用户中途改过邮箱则旧令牌作废)。
- 域名黑白名单按后缀匹配(条目
163.com命中sub.163.com),不区分大小写;邮箱变更激活环节对名单二次校验,防止申请后名单调整被旧申请绕过。 - 未验证用户发言限制挂在
user.can_speak(发帖回帖共用入口),管理员豁免;在发帖/回帖路由上渲染引导页(含"前往邮箱验证"与"返回主页"按钮),其余场景返回拒绝文案使发帖/回帖按钮隐藏。
5.2 密码找回(防枚举)
- 无论邮箱是否注册,统一提示"如果该邮箱已注册,重置邮件已发送",响应内容与耗时路径(对不存在邮箱不做可观测的差异)。
- 重置成功后:作废该用户全部未用令牌(防重置前遗留的改邮箱链接继续生效);因核心认证 Cookie 以密码哈希签名,改密自动使所有旧会话失效。
5.3 邮箱变更(两步状态机)
已验证用户发起(需登录密码 = 身份认证) → step=1 令牌(发原邮箱)── 用户点击确认 → step=2 令牌(发新邮箱)── 用户点击激活 → UPDATE app_users.email + 验证状态随迁(verified=1)- 每步都是独立高熵单次令牌;激活前原邮箱始终有效(中途放弃无损)。
- 激活时再次校验新邮箱未被占用(防确认期间被他人注册)。
- 未验证用户走简化路径:直接改邮箱 + 重发验证邮件(没有需要保护的"已验证"资产)。
- 管理员后台改邮箱:重置验证状态 + 审计记录 + 自动重发验证邮件。
5.4 SMTP 队列与限流
- 派发配额 =
min(单次批量, 每分钟上限 - 近60秒已发送数),双重限制防服务商限流。 - 失败退避:
next_try_at = now + 300,达到重试上限(默认 3 次)标记终态;终态与已发记录保留 7 天供排查。 - SMTP 客户端为原生 socket 实现(无第三方依赖):EHLO → 可选 STARTTLS 协商 → AUTH LOGIN → MAIL/RCPT/DATA;正文 base64 编码(规避点号填充问题);标题/发件人名 RFC 2047 B 编码;错误信息截断脱敏不含凭据。
- 发件人默认
noreply@站点域名(从app_url()解析),符合规范 1.2 要求。
5.5 用户名规则引擎
- 字符类型白名单编译为字符类正则:
/^[a-zA-Z0-9\x{4e00}-\x{9fff}]+$/u按开关拼装;长度按 UTF-8 码点计数(preg_match_all('/./us')),与核心"20 个汉字或英文"口径一致。 - 通配符编译:
a*→/^a.*$/iu、*a→/^.*a$/iu、b?→/^b.$/iu(逐字符转义,其余字符preg_quote,不区分大小写)。 - 三层过滤:
user.before_save(规则+大小写不敏感唯一性)→user.username_reserved(保留名+通配符,核心注册/改名路径都会触发)→ 核心自身唯一性检查。 - 保留名 = 内置 10 个(admin/system/root 等)+ 管理员自定义,始终生效。
5.6 频率限制
- 双维度(scope=user、scope=ip)各自计数,任一超限即拒绝;注册验证、找回密码、修改邮箱三类动作合并计数(防换入口绕过)。
- 窗口与上限可配(默认 60 分钟 / 3 次);写入路径顺带清理窗口外记录,Cron 兜底再清一遍。
六、关键技术选型
决策 选择 理由 邮件发送 原生 socket SMTP(非 mail()/PHPMailer)mail()依赖服务器 MTA 不可控;第三方库违反单文件零依赖约束;socket 实现约 120 行即可覆盖 SSL/STARTTLS/AUTH发送时机 队列 + Cron 异步(非同步发送) 注册接口不被 SMTP 慢 IO 拖垮;天然获得重试/限流/可观测能力 令牌存储 仅存 sha256 哈希 库泄露不暴露可用令牌;查询走 UNIQUE 索引 令牌消费 条件 UPDATE 抢占 原子性防并发重放,无需额外锁 表存在性 请求级静态缓存包装 规避每次请求多次 information_schema 查询(沿袭 points_manager 的 P1 修复经验) 配置读取 统一归一化出口 所有值经 min/max 裁剪与类型兜底,脏配置不会击穿下游 兼容基线 PHP 8.1+( never、str_contains)与核心 index.php 语言特性保持一致(核心大量使用 never/match)七、接口设计规范
7.1 Hook 接入(7 个)
Hook 回调 约定 user.before_savereg_control_user_before_savehook() 链式:返回修改后的 $value数组;校验失败err()直接中断user.username_reservedreg_control_username_reserved返回 true表示保留(核心据此拒绝),否则原样返回$valueuser.after_savereg_control_user_after_savefire() 通知型:注册初始化/管理员改邮箱联动,返回值忽略 user.can_speakreg_control_can_speak返回 true放行;返回字符串作为拒绝提示文案register.form_extrareg_control_register_form_extra追加注册表单提示 HTML login.after_formreg_control_login_after_form登录页追加"忘记密码"入口(SMTP 未配置时隐藏) profile.after_formreg_control_profile_after_form个人资料页邮箱管理卡片 均避开红区 Hook(markdown.render 等),配置读取走核心多级缓存。
7.2 前台路由(3 条)
路由 页面 鉴权 reg_verify验证中心(GET 链接 / POST 验证码) 公开;结果页按登录态区分 reg_reset密码找回(申请 / 重置表单) 公开;频率限制前置 reg_email邮箱管理(重发验证 / 发起变更 / 令牌回调) POST 需登录;GET 令牌回调公开 所有 POST 表单带
form_token()(核心全局 POST CSRF 校验兜底)。7.3 Cron 与后台
- Cron:
dispatch任务,间隔由reg_control_cron_interval动态返回(60–3600 秒,随队列配置同步);回调返回sent:n failed:n pending:n摘要写入计划任务日志。 - 后台 Tab:
reg_control→ 6 子页签(general/smtp/tpl/username/queue/logs),POST 统一入口reg_control_admin_handle_post按reg_control_action分发,need_admin()前置。
7.4 配置项清单(节选)
键 默认 裁剪范围 verify_enabled / block_unverified_post 0 / 1 布尔 expire_minutes 30 1–1440 rate_max / rate_window_minutes 3 / 60 1–100 / 1–1440 domain_mode off off/black/white 互斥 smtp_host/port/secure 空/25/none port 1–65535;secure 枚举 queue_batch / queue_rate_per_minute / queue_max_attempts 5 / 10 / 3 1–50 / 1–1000 / 1–10 username_min / username_max 2 / 20 1–20 且 min≤max allow_letters/digits/chinese 1/1/1 至少一项为真(双重兜底) log_retention_days 180 0–36500,0=永久 7.5 命名与编码规范
- 函数一律
reg_control_前缀;表一律plugin_reg_control_前缀;CSS 类一律reg-control-前缀。 - 输出转义统一
h();SQL 全参数化(拼接处仅整型强转后的 LIMIT/OFFSET);配置键读写统一走plugin_config/plugin_save_config。
八、安全设计要点
- 令牌安全:256bit 熵、仅存哈希、单次原子消费、30 分钟过期、三类隔离、发放前作废旧令牌、重置密码后全量作废。
- 防枚举:找回密码统一响应;注册路径的邮箱占用信息仅在已登录/已通过频率限制的上下文暴露。
- 频率限制:user+IP 双维度、三类动作合并计数,防暴力尝试与邮件轰炸。
- CSRF:全部 POST 表单携带 token,核心全局校验兜底;上传启用链接使用 HMAC token(核心机制)。
- 会话失效链:密码哈希参与认证 Cookie 签名 → 重置密码即全端下线。
- 信息脱敏:SMTP 错误截断 120 字符且不含凭据;队列 last_error 截断 200 字符。
- 权限分离:管理员豁免发言限制但改邮箱仍走完整流程;前台入口随 SMTP 配置状态动态显隐。
九、未来扩展规划
方向 说明 预估切入点 HTML 邮件模板 富文本验证邮件、模板预览 队列 body 加 mime 字段;模板页加预览 Tab 注册 IP 风控 注册 IP 黑名单、同 IP 注册频率限制 rate 表已有多维度计数模型,扩展 scope 类型即可 邀请码注册 邀请码生成/核销/绑定关系 新表 + user.before_save校验 + 后台管理页签图形验证码 注册/找回表单前置人机校验 复用 checkin_captcha 的验证码思路,接入 register.form_extra审计日志导出 CSV 导出(含公式注入防护) 参照 points_manager 的导出实现 管理员手动操作 后台强制标记已验证 / 手动重发 logs 页加用户搜索 + 操作按钮 邮件发送渠道扩展 API 邮件服务(如 SES/SendGrid)备用通道 SMTP 客户端抽象为驱动接口,配置加渠道选择 模板多语言 按用户语言选择模板 模板键加语言后缀 + 配置归一化回落 扩展原则:不破坏现有 5 张表结构(新能力新增表或字段走
app_db_ensure_columns),Hook 只增不改,保持单文件与零核心改动约束。主楼6666,支持大佬,我肝不行
- bbs1org2026-08-23
加油
你来接啊,快。
备用 SMTP 技术方案
实现方案:
- 配置:「邮件服务」页新增“备用 SMTP”分组(整套独立配置,留空禁用)+ 触发策略(连续失败 N 次切换 / 日发送量达阈值分流)
- 通道状态机:settings 维护当前活跃通道、连续失败计数、冷却截止时间;切换事件写审计日志
- 双层切换:
- 故障切换:主通道连续失败达阈值(连接失败/5xx)→ 标记冷却(如 10 分钟)→ 派发改走备用;冷却期满首封试发成功自动切回主
- 限额分流:主通道当日发送量达阈值 → 新邮件直接走备用,次日零点回主(queue 表按 sent_at 聚合,无表结构变更)
- 改造点集中:SMTP 客户端本身不变,仅把“读单一配置”参数化为“按通道配置数组注入”;队列重试机制天然配合(失败退避后由备用通道重发)
- 可观测:队列页显示当前活跃通道与两通道今日发送量;自检面板加备用通道测试发送按钮
预估工时:配置界面与存储 1/3 + 通道状态机与切换逻辑 1/3 + 自检/监控/文档 1/3
潜在风险:
| 风险 | 缓解措施 |
| --- | --- |
| 双套凭据扩大泄露面 | 沿用“密码留空保持不变”策略,不明文回显 |
| 偶发超时被误判为故障引发频繁切换 | 连续失败计数(非单次)+ 冷却窗口 + 试发探测回归 |
| 备用通道长期闲置授权码失效 | 自检面板提供备用通道定期测试发送 |
| 两通道登录账号不同致发件人漂移 | 建议两通道配置同一显示名;From 头随通道自动取该通道登录账号(沿用本次修复逻辑) |- bbs1org2026-08-23
功能考虑的很周全
- bbs1org2026-08-23
- bbs1org2026-08-23




