• one

    注册控制系统(reg_control)设计文档

    版本:1.0.0 | 适配核心:bbs1org | 单文件插件:app/plugins/reg_control/plugin.php


    一、设计理念

    1. 零核心改动:所有能力通过核心提供的 Hook、路由、后台 Tab、Cron、Schema API 接入,核心代码一行不改,升级无冲突。
    2. 安全默认,纵深防御:令牌高熵哈希存储、单次有效、三类隔离;发送频率双维度(用户/IP)限流;密码找回防枚举;密码重置后全局会话失效。任何单点被绕过,下一层仍然拦截。
    3. 请求路径零阻塞:邮件发送是慢 IO(SMTP 握手 + 加密协商 + 传输),全部写入队列由计划任务异步派发,注册/找回请求本身只做内存级校验与一次 INSERT,响应速度不受邮件服务影响。
    4. 可观测:所有关键动作落审计日志(谁、何时、何 IP、改了什么),后台提供队列状态、系统自检,出问题可回溯。
    5. 存量兼容:安装时已有邮箱的存量用户一次性标记为已验证,启用插件不冲击老用户;校验只在用户名/邮箱"实际变化"时触发,保存其他资料不受影响。
    6. 失败友好: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 发送 → 更新队列状态

    三、模块划分

    模块职责代表函数
    安装/Schema5 张表幂等创建、存量用户初始化、卸载清理reg_control_install reg_control_uninstall
    配置默认值、裁剪、归一化、模板回落reg_control_config reg_control_tpl_defaults
    基础设施表就绪缓存、审计写入、验证状态读写reg_control_table_ready reg_control_audit reg_control_state_set reg_control_user_verified
    频率限制双维度窗口计数(user/ip 合并动作)reg_control_rate_allow
    令牌子系统高熵签发、哈希检索、单次消费、类型隔离reg_control_token_issue reg_control_token_row reg_control_token_consume
    邮件模板占位符替换、四套模板、必含链接校验reg_control_mail_vars reg_control_tpl_render
    发送队列入队、批量派发、退避重试、限流reg_control_mail_queue reg_control_cron_dispatch
    SMTP 客户端socket 连接、SSL/STARTTLS、AUTH LOGIN、base64 编码reg_control_smtp_send reg_control_smtp_cmd
    校验器用户名规则、通配符编译、域名黑白名单reg_control_validate_username reg_control_pattern_regex reg_control_validate_email_domain
    Hook 接入注册校验、保留名、发言权、资料页卡片等reg_control_user_before_save reg_control_can_speak
    前台路由验证中心、密码找回、邮箱管理reg_control_verify_page reg_control_reset_page reg_control_email_page
    后台管理6 子页签、表单渲染、POST 处理、自检reg_control_admin_page reg_control_admin_handle_post reg_control_selftest
    计划任务队列派发 + 四类数据清理reg_control_cron_dispatch
    样式/ManifestCSS 变量体系样式、插件声明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_errorstatus 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 表示保留(核心据此拒绝),否则原样返回 $value
    user.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_post0 / 1布尔
    expire_minutes301–1440
    rate_max / rate_window_minutes3 / 601–100 / 1–1440
    domain_modeoffoff/black/white 互斥
    smtp_host/port/secure空/25/noneport 1–65535;secure 枚举
    queue_batch / queue_rate_per_minute / queue_max_attempts5 / 10 / 31–50 / 1–1000 / 1–10
    username_min / username_max2 / 201–20 且 min≤max
    allow_letters/digits/chinese1/1/1至少一项为真(双重兜底)
    log_retention_days1800–36500,0=永久

    7.5 命名与编码规范

    • 函数一律 reg_control_ 前缀;表一律 plugin_reg_control_ 前缀;CSS 类一律 reg-control- 前缀。
    • 输出转义统一 h();SQL 全参数化(拼接处仅整型强转后的 LIMIT/OFFSET);配置键读写统一走 plugin_config/plugin_save_config。

    八、安全设计要点

    1. 令牌安全:256bit 熵、仅存哈希、单次原子消费、30 分钟过期、三类隔离、发放前作废旧令牌、重置密码后全量作废。
    2. 防枚举:找回密码统一响应;注册路径的邮箱占用信息仅在已登录/已通过频率限制的上下文暴露。
    3. 频率限制:user+IP 双维度、三类动作合并计数,防暴力尝试与邮件轰炸。
    4. CSRF:全部 POST 表单携带 token,核心全局校验兜底;上传启用链接使用 HMAC token(核心机制)。
    5. 会话失效链:密码哈希参与认证 Cookie 签名 → 重置密码即全端下线。
    6. 信息脱敏:SMTP 错误截断 120 字符且不含凭据;队列 last_error 截断 200 字符。
    7. 权限分离:管理员豁免发言限制但改邮箱仍走完整流程;前台入口随 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 只增不改,保持单文件与零核心改动约束。

    主楼
  • one

    没错,叫的就是你 @bbs1org 。
    我昨晚测试了上百封邮件,163搞封号了

    #1
  • 桃子大人

    6666,支持大佬,我肝不行

    #2
  • one

    我已经没肝了,完成度约70%。
    烂尾吧(仅能自用,达不到发布要求)

    01.png


    02.png
    03.png


    04.png


    05.png

    #3
  • bbs1org

    加油

    #4
  • one

    你来接啊,快。

    备用 SMTP 技术方案

    实现方案:

    1. 配置:「邮件服务」页新增“备用 SMTP”分组(整套独立配置,留空禁用)+ 触发策略(连续失败 N 次切换 / 日发送量达阈值分流)
    2. 通道状态机:settings 维护当前活跃通道、连续失败计数、冷却截止时间;切换事件写审计日志
    3. 双层切换:
    • 故障切换:主通道连续失败达阈值(连接失败/5xx)→ 标记冷却(如 10 分钟)→ 派发改走备用;冷却期满首封试发成功自动切回主
    • 限额分流:主通道当日发送量达阈值 → 新邮件直接走备用,次日零点回主(queue 表按 sent_at 聚合,无表结构变更)
    1. 改造点集中:SMTP 客户端本身不变,仅把“读单一配置”参数化为“按通道配置数组注入”;队列重试机制天然配合(失败退避后由备用通道重发)
    2. 可观测:队列页显示当前活跃通道与两通道今日发送量;自检面板加备用通道测试发送按钮

    预估工时:配置界面与存储 1/3 + 通道状态机与切换逻辑 1/3 + 自检/监控/文档 1/3

    潜在风险:
    | 风险 | 缓解措施 |
    | --- | --- |
    | 双套凭据扩大泄露面 | 沿用“密码留空保持不变”策略,不明文回显 |
    | 偶发超时被误判为故障引发频繁切换 | 连续失败计数(非单次)+ 冷却窗口 + 试发探测回归 |
    | 备用通道长期闲置授权码失效 | 自检面板提供备用通道定期测试发送 |
    | 两通道登录账号不同致发件人漂移 | 建议两通道配置同一显示名;From 头随通道自动取该通道登录账号(沿用本次修复逻辑) |

    #5
  • bbs1org

    功能考虑的很周全

    #6
  • one

    虽然我是个落魄的站长,但怎么也曾经是个站长啊。哈哈

    #7
  • bbs1org

    所以搞下去,给自己一个精品,多好

    #8
  • one

    自用没问题达不到发布级。太多逻辑我自己都还没定下来。
    所以我希望官方来做啊。
    这段时间做了好几个2000行+的产品,都仅能自用。
    包括我做的积分管理,勋章自动授等都不敢发。半成品

    #9
  • bbs1org

    加油✊产品思路很好

    #10
  • one

    核心文档在主楼了,百花齐放。期待哪位来完善🤠

    #11

发表回复

登录后回复