Files
olons_admin/docs/admin_reference_prd/URL地址池设计方案.md
T

33 KiB
Raw Blame History

URL 地址池与会员注册域名绑定设计方案

文档状态:方案设计 方案版本:v2.1 最后更新:2026-10-15 适用项目:Olons Admin / HotGo 关联模块:会员注册、会员登录、会员管理、URL 地址池管理、会员登录日志

1. 方案变更说明

原方案采用“会员注册时从 URL 地址池轮询取出一个 URL”的模式。现调整为“会员通过哪个域名打开注册页面,就与哪个域名建立绑定关系”。

新流程如下:

管理员或推广人员先向用户提供一个注册链接
  → 用户通过该链接打开注册页面
  → 注册页面获取浏览器地址栏中的域名
  → 用户提交注册
  → 后端校验本次请求的真实注册域名
  → 创建会员
  → 建立会员与注册域名的绑定关系

本方案不再使用 URL 轮询顺序、分配游标和自动轮询算法。URL 地址池的作用调整为:

  1. 维护允许用于会员注册的域名;
  2. 管理域名的启用、失效状态;
  3. 统计每个域名带来的注册会员;
  4. 查询会员与注册域名的绑定关系。

每个地址同时维护累计绑定次数。地址池列表默认把绑定次数较少的地址排在前面;点击地址详情可以查看当前绑定该地址的全部会员。

2. 设计目标

  1. 用户使用某个域名注册时,自动与该域名绑定。
  2. 注册域名由后端根据可信请求信息确认,不能只信任前端传入值。
  3. 每个会员只能存在一个当前绑定域名。
  4. 域名未配置或已失效时,会员仍允许注册成功,但保持“待绑定”。
  5. 后台不提供创建会员、手动绑定或重新绑定域名的入口。
  6. 域名失效后不再接受新的自动绑定,但保留原有绑定用于审计。
  7. 支持同域部署、前后端跨域部署以及 CDN/反向代理部署。
  8. 所有注册自动绑定和地址状态变更都有业务审计记录。
  9. 每个地址记录累计绑定次数,地址池列表默认按绑定次数升序排列。
  10. 后台可以手动将启用地址切换为失效,也可以将失效地址重新启用。
  11. C 端会员登录成功后更新会员最后登录时间、IP脱敏信息和地区。
  12. C 端会员每次登录成功或失败均记录独立日志,并可在后台查询、查看和导出。

3. 核心概念

3.1 注册 URL

管理员或推广人员提供给用户的完整注册链接,例如:

https://register-a.example.com/register?inviteCode=ABC123

完整 URL 中可能包含路径、邀请码或其他查询参数,但会员绑定只取域名部分:

register-a.example.com

默认端口不作为域名的一部分:

https://register-a.example.com:443 → register-a.example.com
http://register-a.example.com:80   → register-a.example.com

非默认端口的测试环境可以保留端口:

http://127.0.0.1:5173 → 127.0.0.1:5173

3.2 URL 地址池

URL 地址池是允许用于注册绑定的域名白名单。地址池中的域名分为:

值 状态 说明
1 启用 允许新注册会员自动绑定
2 失效 不允许新注册会员自动绑定,保留原绑定关系

3.3 会员域名绑定

会员注册成功后,如果识别出的域名在地址池中存在且为启用状态,则创建绑定记录。

一个会员最多有一个绑定域名。绑定只允许在 C 端会员注册事务中自动建立,后台只能查看,不能手动绑定、解除绑定或重新绑定。

3.4 待绑定会员

以下情况会员注册成功但不建立域名绑定:

  • 请求中无法识别有效注册域名;
  • 注册域名没有配置在地址池中;
  • 注册域名已经失效;

没有绑定记录的会员统一显示为“待绑定”。

4. 业务规则

4.1 正常注册绑定

用户打开 https://register-a.example.com/register
  → 前端读取 window.location.origin
  → 用户提交注册
  → 后端确认真实来源域名为 register-a.example.com
  → 查询地址池
  → 地址存在且启用
  → 创建会员及钱包
  → 创建会员与 register-a.example.com 的绑定
  → 注册成功

4.2 地址池没有该域名

用户通过 unknown.example.com 注册
  → 后端未在地址池找到该域名
  → 会员和钱包正常创建
  → 不创建域名绑定记录
  → 记录“未知注册域名”业务审计
  → 会员显示为待绑定

4.3 域名已经失效

用户通过已失效域名注册
  → 会员正常创建
  → 不建立自动绑定
  → 记录“通过失效域名注册”业务审计
  → 会员显示为待绑定

4.4 重复注册请求

同一会员创建请求因网络原因重复提交时,应通过现有手机号、用户名唯一约束和注册幂等机制防止重复创建。

对已经存在的会员重复执行域名绑定:

  • 已绑定同一域名:直接返回成功;
  • 已绑定其他域名:不能由注册接口覆盖,也不提供后台改绑入口;
  • 既有会员尚未绑定:保持待绑定,注册接口不做事后补绑。

5. 注册域名识别

5.1 前端采集

注册页面提交以下辅助字段:

const registerOrigin = window.location.origin;
const registerHost = window.location.host;

请求示例:

{
  "username": "demo_user",
  "mobile": "13800000000",
  "password": "******",
  "registerOrigin": "https://register-a.example.com",
  "registerHost": "register-a.example.com"
}

前端字段只用于辅助校验和问题排查,不能作为后端绑定的唯一依据,因为普通 HTTP 客户端可以伪造请求参数。

禁止保存完整 window.location.href,避免把邀请码、临时令牌或其他查询参数写入数据库和日志。

5.2 后端可信来源

后端按照部署方式解析真实注册域名。

同域部署

注册页面和 API 使用同一个域名时,优先读取:

HTTP Host

前后端跨域部署

例如:

注册页面:https://register-a.example.com
注册接口:https://api.example.com/api/member/register

此时 API 的 Host 是 api.example.com,不能用于会员绑定。浏览器跨域请求通常携带:

Origin: https://register-a.example.com

后端应从经过 CORS 白名单校验的 Origin 中提取注册域名。

CDN 或反向代理部署

后端可能收到:

X-Forwarded-Host
X-Forwarded-Proto
Forwarded

只有请求来自配置的可信代理 IP 时,才允许使用这些请求头。外部客户端直接传入的 X-Forwarded-Host 不可信。

5.3 建议解析优先级

1. 已通过 CORS 白名单校验的 Origin 域名
2. 来自可信代理的 X-Forwarded-Host / Forwarded
3. HTTP Host
4. 前端 registerHost,仅用于一致性校验,不单独作为可信值

当可信来源域名与前端 registerHost 不一致时:

  • 会员仍可注册;
  • 不自动绑定域名;
  • 写入安全警告和业务审计;
  • 后台显示“注册域名校验异常”。

5.4 域名标准化

后端统一执行:

  • 转换为小写;
  • 移除首尾空格;
  • 移除末尾的点;
  • 国际化域名转换为 Punycode;
  • 移除默认端口 80 和 443;
  • 拒绝包含路径、用户名、密码、Fragment 的值;
  • 开发环境允许配置带非默认端口的 Host;
  • 生产环境建议只允许 HTTPS 注册入口。

6. 数据模型

域名绑定方案使用两张业务表,不再需要轮询游标表。会员登录审计另增一张独立日志表。

hg_url_pool
    │ 1
    │
    │ N
hg_member_url_binding ─── N:1 ─── hg_app_member

hg_app_member ─── 1:N ─── hg_app_member_login_log

6.1 URL 地址池表 hg_url_pool

CREATE TABLE IF NOT EXISTS `hg_url_pool` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `tenant_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT '租户ID',
  `name` varchar(100) NOT NULL DEFAULT '' COMMENT '地址名称',
  `scheme` varchar(10) NOT NULL DEFAULT 'https' COMMENT '协议:http或https',
  `domain` varchar(255) NOT NULL COMMENT '标准化域名,可含非默认端口',
  `domain_hash` char(64) NOT NULL COMMENT '标准化域名SHA256',
  `base_url` varchar(512) NOT NULL COMMENT '注册入口基础地址',
  `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1启用,2失效',
  `bind_count` bigint unsigned NOT NULL DEFAULT 0 COMMENT '累计成功绑定次数',
  `last_bound_at` datetime(3) DEFAULT NULL COMMENT '最后绑定时间',
  `remark` varchar(500) NOT NULL DEFAULT '' COMMENT '备注',
  `created_by` bigint unsigned NOT NULL DEFAULT 0 COMMENT '创建者ID',
  `updated_by` bigint unsigned NOT NULL DEFAULT 0 COMMENT '更新者ID',
  `deleted_by` bigint unsigned NOT NULL DEFAULT 0 COMMENT '删除者ID',
  `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
  `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
    ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
  `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_url_pool_domain` (`tenant_id`,`domain_hash`),
  KEY `idx_url_pool_list` (`tenant_id`,`bind_count`,`id`,`deleted_at`),
  KEY `idx_url_pool_status` (`tenant_id`,`status`,`bind_count`,`deleted_at`),
  KEY `idx_url_pool_deleted` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
  COMMENT='URL地址池';

字段说明:

字段 说明
scheme 注册入口协议,生产环境一般为 HTTPS
domain 绑定和匹配使用的标准化域名
domain_hash 用于唯一索引,避免域名大小写或格式差异导致重复
base_url 后台展示、复制链接时使用,例如 https://a.example.com/register
bind_count C 端注册成功并实际建立域名绑定的累计次数
last_bound_at 最近一次成功建立绑定的时间

地址一旦产生会员绑定,禁止直接修改 domain。需要使用新域名时应新增地址;已有会员继续保留原绑定关系。

bind_count 是累计使用次数。当前绑定会员数通过 hg_member_url_binding 按 url_pool_id 实时统计。正常情况下两者一致;保留累计字段便于快速排序和审计校验。

6.2 会员域名绑定表 hg_member_url_binding

CREATE TABLE IF NOT EXISTS `hg_member_url_binding` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `tenant_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT '租户ID',
  `member_id` bigint unsigned NOT NULL COMMENT '会员ID',
  `url_pool_id` bigint unsigned NOT NULL COMMENT 'URL地址池ID',
  `domain_snapshot` varchar(255) NOT NULL COMMENT '绑定时域名快照',
  `origin_snapshot` varchar(512) NOT NULL DEFAULT '' COMMENT '绑定时Origin快照',
  `bound_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '绑定时间',
  `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
  `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
    ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_member_url_binding` (`tenant_id`,`member_id`),
  KEY `idx_member_url_pool` (`tenant_id`,`url_pool_id`,`member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
  COMMENT='会员注册域名绑定';

设计说明:

  • 未绑定会员没有该表记录;
  • member_id 唯一约束保证一个会员只有一个当前绑定;
  • domain_snapshot 用于保留绑定时的域名;
  • origin_snapshot 只保存 Scheme 和 Host,不保存路径及查询参数;
  • 绑定记录由 C 端注册事务创建,后台仅提供只读查询。

6.3 会员登录日志表 hg_app_member_login_log

CREATE TABLE IF NOT EXISTS `hg_app_member_login_log` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '日志ID',
  `tenant_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT '租户ID',
  `req_id` varchar(64) NOT NULL DEFAULT '' COMMENT '请求ID',
  `member_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT '会员ID,未识别会员时为0',
  `username` varchar(64) NOT NULL DEFAULT '' COMMENT '用户名快照',
  `mobile_mask` varchar(32) NOT NULL DEFAULT '' COMMENT '手机号脱敏值',
  `login_type` tinyint NOT NULL DEFAULT 1 COMMENT '登录方式:1账号密码,2手机验证码',
  `status` tinyint NOT NULL DEFAULT 1 COMMENT '登录状态:1成功,2失败',
  `err_msg` varchar(255) NOT NULL DEFAULT '' COMMENT '失败原因',
  `login_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '登录时间',
  `login_ip_cipher` text COMMENT '登录IP密文',
  `login_ip_mask` varchar(64) NOT NULL DEFAULT '' COMMENT '登录IP脱敏值',
  `login_region` varchar(128) NOT NULL DEFAULT '' COMMENT '登录地区',
  `login_domain` varchar(255) NOT NULL DEFAULT '' COMMENT '登录页面标准化域名',
  `user_agent` varchar(512) NOT NULL DEFAULT '' COMMENT '浏览器UA',
  `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_member_login_time` (`tenant_id`,`member_id`,`login_at`),
  KEY `idx_member_login_status` (`tenant_id`,`status`,`login_at`),
  KEY `idx_member_login_username` (`tenant_id`,`username`,`login_at`),
  KEY `idx_member_login_domain` (`tenant_id`,`login_domain`,`login_at`),
  KEY `idx_member_login_req` (`req_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
  COMMENT='C端会员登录日志';

设计说明:

  • 登录成功和失败都必须写入日志;
  • 登录失败且无法识别会员时,member_id 为 0,仅保存用户名快照或手机号脱敏值;
  • 不保存密码、短信验证码、Token、完整手机号及登录响应正文;
  • IP 仅保存密文和脱敏值,后台页面不得返回 IP 密文;
  • 日志为审计数据,后台只读,不提供人工新增、编辑和删除功能;
  • 操作系统和浏览器名称由 user_agent 在查询时解析,无需重复存储。

7. 字典设计

继续使用 HotGo 后端统一字典,不在前端硬编码。

const (
    UrlPoolStatusEnabled = 1 // 启用
    UrlPoolStatusInvalid = 2 // 失效

    AppMemberLoginTypePassword = 1 // 账号密码
    AppMemberLoginTypeSms      = 2 // 手机验证码
)

建议字典:

字典键 说明
urlPoolStatus URL 地址状态
appMemberLoginTypeOptions C 端会员登录方式,新建后端枚举字典
sys_login_status 登录成功/失败状态,直接复用 HotGo 已有字典

会员绑定状态不需要单独存储:

  • 存在绑定记录:已绑定;
  • 不存在绑定记录:待绑定;
  • 存在绑定但地址池状态为失效:绑定域名已失效。

8. 注册事务设计

8.1 注册流程

解析可信注册域名
  → 查询 URL 地址池
  → 开启事务
      → 创建会员主档
      → 初始化会员钱包
      → 如果域名存在且启用,创建绑定
      → 地址池 bind_count 加 1,并更新 last_bound_at
      → 写业务审计
  → 提交事务

8.2 错误边界

以下情况不阻断注册:

  • 没有识别到域名;
  • 域名未配置;
  • 域名已经失效;
  • 前端域名与可信来源不一致。

以上情况注册成功但会员保持待绑定,并写入审计。

以下情况必须回滚注册:

  • 查询地址池时数据库异常;
  • 创建会员失败;
  • 初始化钱包失败;
  • 已确认需要绑定,但插入绑定记录失败;
  • 更新注册统计失败;

不能将数据库错误误判成“域名不存在”。

8.3 并发与幂等

  • hg_member_url_binding(tenant_id, member_id) 唯一,防止重复绑定;
  • hg_url_pool(tenant_id, domain_hash) 唯一,防止重复域名;
  • 创建绑定前先查询会员现有绑定;
  • 同域名重复绑定直接返回成功;
  • 注册接口不允许覆盖不同域名的已有绑定。

只有 C 端注册事务实际新建绑定时才增加 bind_count。同一会员对同一地址的幂等重试不增加次数。

9. 后端服务设计

建议新增共享服务:

type RegisterDomainModel struct {
    Scheme   string
    Domain   string
    Origin   string
    Trusted  bool
    Source   string
}

type MemberDomainResult struct {
    Bound       bool   `json:"bound"`
    UrlPoolId   uint64 `json:"urlPoolId"`
    Domain      string `json:"domain"`
    BaseUrl     string `json:"baseUrl"`
    StatusLabel string `json:"statusLabel"`
}

核心方法:

ResolveRegisterDomain(ctx, registerOrigin, registerHost)
    解析并校验真实注册域名

BindOnRegister(ctx, memberId, domain)
    注册事务内自动绑定

GetMemberDomain(ctx, memberId)
    查询会员当前绑定及域名有效状态

会员登录日志服务增加:

PushMemberLoginLog(ctx, member, loginType, err)
    收集请求上下文并推送会员登录日志

WriteMemberLoginLog(ctx, log)
    队列消费或同步兜底写入日志

UpdateMemberLastLogin(ctx, memberId, ipData)
    登录成功后更新会员主表最后登录信息

项目现有 /site/register 是后台管理员账号注册,不是 hg_app_member 的 C 端会员注册。新域名绑定应接入真正的 C 端会员注册接口。

后台当前不提供创建 C 端会员的产品功能,本方案也不新增该入口。会员及其域名绑定只能由 C 端注册流程创建。

10. 后台地址池管理

10.1 菜单位置

会员管理
└── URL地址池

10.2 汇总数据

  • URL 总数;
  • 启用 URL 数;
  • 失效 URL 数;
  • 已绑定会员数;
  • 待绑定会员数;
  • 绑定域名已失效会员数。

10.3 查询条件

  • 地址名称;
  • 域名关键词;
  • URL 状态;
  • 创建时间。

10.4 列表字段

  • 地址名称;
  • 注册域名;
  • 注册入口 URL;
  • 状态;
  • 当前绑定会员数;
  • 累计绑定次数;
  • 最后绑定时间;
  • 备注;
  • 创建时间;
  • 操作。

列表默认排序固定为:

ORDER BY bind_count ASC, id ASC

即累计绑定次数最少的地址排在最前面;绑定次数相同时,先创建的地址排在前面。分页查询必须由后端应用该排序,不能只在前端对当前页排序。

10.5 操作按钮

按钮 条件 功能
新增地址 有新增权限 添加注册域名和入口地址
Excel批量导入 有导入权限 按“地址名称、注册入口URL、备注”批量导入,导入地址统一启用
编辑 始终可见 编辑名称、入口路径和备注
启用 当前失效 手动将地址切换为启用,恢复自动注册绑定
设为失效 当前启用 手动将地址切换为失效,停止新的自动绑定
复制注册链接 当前启用 复制完整注册入口 URL
详情 始终可见 进入地址详情页,查看地址信息和全部绑定会员

地址池不提供删除功能,历史地址统一通过“启用/失效”管理。启用和失效由管理员手动点击切换。状态变更成功后立即刷新列表,并写入业务审计。地址变为失效后:

  • 不再接受新的注册自动绑定;
  • 已有会员绑定关系保留;
  • 详情页仍然可以查看原有绑定会员。

10.6 地址详情页

点击列表中的“详情”进入独立详情页。页面分为地址信息和绑定会员两个区域。

地址信息展示:

  • 地址名称;
  • 注册域名;
  • 注册入口 URL;
  • 当前状态;
  • 累计绑定次数;
  • 当前绑定会员数;
  • 最后绑定时间;
  • 创建人、创建时间;
  • 备注。

绑定会员列表展示当前绑定该地址的全部会员,并支持分页。字段包括:

  • 会员编号;
  • 用户名;
  • 手机号脱敏值;
  • 会员状态;
  • 绑定时间。

详情页会员列表支持按用户名、手机号和绑定时间查询。这里的“全部用户”指绑定该地址的全部会员,列表仅供查看,不提供添加、解绑或改绑操作。

11. 会员列表扩展

会员列表增加字段:

  • 注册域名;
  • 域名绑定状态;
  • 绑定时间。

增加筛选:

筛选值 条件
全部 不限制
已绑定 存在绑定且域名启用
待绑定 不存在绑定记录
域名已失效 存在绑定但域名已失效

操作栏不增加绑定、解绑或重新绑定按钮。会员详情可只读查看域名信息。

会员详情增加“注册域名信息”区域,展示:

  • 当前域名;
  • 注册入口;
  • 域名状态;
  • 绑定时间。

12. 会员登录与日志设计

12.1 建设范围

当前项目的登录日志分为两类理解:

  • hg_sys_login_log 及“系统登录日志”页面记录的是后台管理员登录。现有后台账号、手机号登录均会调用 SysLoginLog().Push() 写入成功或失败结果;
  • hg_app_member 虽然预留了 last_login_at、last_login_ip_cipher、last_login_ip_mask、last_login_region 字段,会员列表也展示最后登录信息,但当前代码中没有找到 C 端会员登录接口对这些字段的更新逻辑;
  • 当前也没有独立的会员登录历史表或会员登录日志列表。

因此,现状结论是:后台管理员登录有日志,C 端会员登录目前没有完整可用的日志功能。

本次将以下两项能力纳入正式实施范围:

  1. 登录成功后更新会员主表中的最后登录时间、IP 脱敏值和地区;
  2. 使用独立的 hg_app_member_login_log 记录每次会员登录成功或失败,避免与后台管理员的 hg_sys_login_log 混用。

12.2 登录成功后的会员主表更新

会员身份校验通过后、返回登录成功响应前,更新 hg_app_member:

字段 更新内容
last_login_at 当前登录成功时间
last_login_ip_cipher 使用项目统一加密工具保存的客户端 IP 密文
last_login_ip_mask 用于后台展示的 IP 脱敏值
last_login_region 根据客户端 IP 解析的国家/省/市摘要

处理规则:

  • 只有登录成功才更新上述字段,登录失败不得覆盖会员最后一次成功登录信息;
  • 客户端 IP 使用项目现有 location.GetClientIp() 识别,并遵守可信代理配置;
  • 地区解析失败不阻断登录,地区保存为空,同时记录 Warning 日志;
  • 会员主表更新失败时不签发 Token,向客户端返回“登录失败,请稍后重试”;
  • 更新字段必须使用 DAO 列名,禁止硬编码字段字符串;
  • 不在业务日志中输出 IP 密文、密码、短信验证码或 Token。

12.3 会员登录日志写入流程

所有 C 端会员登录入口必须调用同一个会员登录日志服务,不允许每种登录方式自行拼装日志。

收到登录请求
  → 解析请求ID、登录方式、客户端IP、地区、域名和User-Agent
  → 校验会员及登录凭证
  → 失败:写入失败日志,返回统一登录错误
  → 成功:更新会员最后登录信息
  → 签发Token
  → 写入成功日志
  → 返回登录结果

为保证失败请求也能记录,登录控制器或登录服务应使用统一的收口逻辑,在方法返回前根据最终 err 和会员识别结果生成日志。日志优先通过项目消息队列异步写入;队列推送失败时同步写库兜底,并记录 Warning。日志写入最终失败不得把已经完成的身份认证改成登录失败,但必须留下服务端告警。

失败原因只记录可审计的标准化文案,例如:

  • 账号或密码错误;
  • 验证码错误或已过期;
  • 会员不存在;
  • 会员已禁用;
  • 风控限制登录;
  • 系统异常。

12.4 后台会员登录日志页面

菜单位置:

会员管理
└── 会员登录日志

查询条件:

  • 会员编号、用户名或手机号关键词;
  • 登录方式;
  • 登录状态;
  • 登录域名;
  • IP 脱敏值;
  • 登录时间范围。

列表默认按照 login_at DESC, id DESC 排序,展示:

  • 日志ID;
  • 会员编号;
  • 用户名;
  • 手机号脱敏值;
  • 登录方式;
  • 登录状态;
  • 登录时间;
  • 登录IP脱敏值;
  • 登录地区;
  • 登录域名;
  • 操作系统;
  • 浏览器;
  • 失败原因;
  • 操作。

操作栏只提供“详情”。列表顶部提供“导出”,导出内容与当前查询条件一致。详情弹窗补充展示请求ID和完整 User-Agent,但不展示 IP 密文及任何登录凭证。

列表查询必须按 tenant_id 隔离数据。使用完整手机号查询时,先在会员表中匹配会员ID,再按 member_id 查询日志;对无法识别会员的失败记录,只支持按已保存的手机号脱敏值查询。

13. 接口设计

13.1 后台接口

接口 方法 说明
/urlPool/list GET 地址池分页列表
/urlPool/view GET 地址详情及绑定统计
/urlPool/edit POST 新增或编辑地址
/urlPool/import POST Excel批量导入地址,成功记录统一设为启用
/urlPool/status POST 启用或设为失效
/urlPool/memberList GET 分页查看域名当前绑定的全部会员
/urlPool/summary GET 地址池汇总数据
/appMemberLoginLog/list GET 会员登录日志分页列表
/appMemberLoginLog/view GET 会员登录日志详情
/appMemberLoginLog/export GET 按当前条件导出会员登录日志

13.2 C 端接口

注册请求增加:

{
  "registerOrigin": "https://register-a.example.com",
  "registerHost": "register-a.example.com"
}

会员查询当前域名响应建议:

{
  "bound": true,
  "domain": "register-a.example.com",
  "baseUrl": "https://register-a.example.com/register",
  "domainStatus": 1,
  "domainStatusLabel": "启用"
}

未绑定时:

{
  "bound": false,
  "domain": "",
  "baseUrl": "",
  "domainStatus": 0,
  "domainStatusLabel": "待绑定"
}

所有 C 端会员登录接口的响应格式保持不变,日志采集由服务端统一完成,不要求前端额外提交 IP、地区或 User-Agent。跨域部署时,前端可提交 loginHost 作为辅助校验值,但最终登录域名仍按可信 Origin、可信代理转发头和 Host 解析。

14. HotGo 模块结构

建议作为主模块开发:

server/
├── api/admin/urlpool/urlpool.go
├── api/admin/appmemberloginlog/appmemberloginlog.go
├── internal/consts/url_pool.go
├── internal/consts/app_member_login_log.go
├── internal/controller/admin/sys/url_pool.go
├── internal/controller/admin/sys/app_member_login_log.go
├── internal/logic/sys/url_pool.go
├── internal/logic/sys/app_member_login_log.go
├── internal/model/input/sysin/url_pool.go
├── internal/model/input/sysin/app_member_login_log.go
├── internal/router/genrouter/url_pool.go
├── internal/router/genrouter/app_member_login_log.go
├── internal/queues/app_member_login_log.go
├── internal/service/sys.go                  # 追加 ISysUrlPool、ISysAppMemberLoginLog
├── internal/dao/url_pool.go                 # CLI 生成
├── internal/dao/member_url_binding.go       # CLI 生成
├── internal/dao/app_member_login_log.go      # CLI 生成
├── storage/data/migrations/<date>_url_pool.sql
└── storage/data/migrations/<date>_app_member_login_log.sql

web/src/
├── api/urlPool/index.ts
├── api/appMemberLoginLog/index.ts
├── views/app-member/url-pool/
    ├── index.vue
    ├── edit.vue
    ├── detail.vue
    └── model.ts
└── views/app-member/login-log/
    ├── index.vue
    ├── detail.vue
    └── model.ts

DAO、Entity、DO 和 DAO Internal 必须通过 GoFrame CLI 生成,不手工修改。

15. 权限设计

/urlPool/list
/urlPool/view
/urlPool/edit
/urlPool/import
/urlPool/status
/urlPool/memberList
/urlPool/summary
/appMemberLoginLog/list
/appMemberLoginLog/view
/appMemberLoginLog/export

域名启用/失效和注册自动绑定均写入业务审计。

16. 缓存设计

会员当前域名可以使用项目统一缓存:

urlpool:member:{tenantId}:{memberId}

地址池域名查询可以缓存:

urlpool:domain:{tenantId}:{domainHash}

建议缓存 10~30 分钟。以下操作后主动清除缓存:

  • 地址编辑;
  • 地址启用或失效;
  • 注册自动绑定;

缓存统一使用 cache.Instance(),数据库仍是唯一事实来源。

17. 安全要求

  1. 不得只信任前端 registerHost 参数。
  2. Origin 必须通过地址池或 CORS 白名单校验。
  3. 转发请求头只能在可信代理来源下使用。
  4. 后台保存域名前必须进行标准化和 URL 解析。
  5. 禁止保存包含用户名、密码、Token 的 URL。
  6. 日志只记录标准化域名和 Origin,不记录完整注册查询参数。
  7. 后台不得提供会员域名绑定、解绑或改绑接口。
  8. 生产环境建议只允许 HTTPS 注册入口。
  9. 域名变更不得静默覆盖已有会员绑定。
  10. 未知或校验异常域名不自动创建地址池记录。
  11. 会员登录日志不得保存密码、短信验证码、Token、完整手机号或登录响应正文。
  12. 后台日志接口不得返回 last_login_ip_cipher 或 login_ip_cipher。
  13. 失败日志使用统一错误文案,避免暴露账号是否存在等敏感认证细节。
  14. 会员登录日志列表、详情和导出必须执行租户隔离,禁止跨租户查询。

18. 异常处理

场景 处理方式
地址池为空 注册成功,会员待绑定
注册域名未配置 注册成功,会员待绑定并写审计
注册域名失效 注册成功,会员待绑定并写审计
前端域名与可信来源不一致 注册成功但不绑定,记录安全警告
域名重复添加 拒绝保存
已绑定同一域名 幂等返回成功
注册接口试图覆盖其他域名 拒绝覆盖,保留原绑定关系
地址池查询数据库异常 注册失败并回滚
创建绑定失败 注册事务回滚
已产生绑定的域名被直接修改 拒绝修改域名字段
列表绑定次数相同 按地址ID升序稳定排序
登录成功但会员最后登录信息更新失败 不签发Token,返回通用系统错误并记录失败日志
登录IP地区解析失败 登录继续,地区留空并记录Warning
登录日志队列推送失败 同步写入日志表兜底并记录Warning
队列与同步写入均失败 不撤销已完成登录,记录服务端告警供运维处理
登录失败且无法识别会员 以 member_id=0 写入脱敏失败日志

19. 验收标准

19.1 注册绑定

  • 用户通过 A 域名注册后,会员绑定 A。
  • 用户通过 B 域名注册后,会员绑定 B。
  • URL 路径和查询参数不同但域名相同,绑定结果相同。
  • 默认端口不会造成重复域名。
  • 地址池为空时会员仍注册成功并显示待绑定。
  • 未配置域名和失效域名不会建立自动绑定。

19.2 部署场景

  • 前后端同域部署能够从 Host 正确识别域名。
  • 前后端跨域部署能够从通过校验的 Origin 正确识别域名。
  • CDN/反向代理部署能够从可信转发头正确识别域名。
  • 外部客户端伪造 X-Forwarded-Host 不会产生错误绑定。
  • 前端伪造 registerHost 不会覆盖后端可信域名。

19.3 并发和幂等

  • 并发提交不会为同一会员创建多条绑定。
  • 相同注册请求重试不会重复创建会员或绑定。
  • 已绑定会员不能通过普通注册接口更换域名。

19.4 后台管理

  • 可以新增、编辑、启用和失效注册域名。
  • 可以使用 Excel 批量导入地址,表头包含“地址名称、注册入口URL、备注”,所有导入地址默认为启用。
  • 可以手动点击“启用”或“设为失效”,状态立即生效。
  • 地址池列表默认按累计绑定次数升序排列。
  • 可以复制完整注册链接。
  • 点击地址详情可以分页查看当前绑定的全部会员。
  • 每次实际建立新绑定时,地址累计绑定次数准确加一。
  • 幂等重试不会重复增加累计绑定次数。
  • 后台不存在创建会员入口。
  • 后台不存在会员域名绑定、解绑或重新绑定入口。
  • 失效域名不允许新增绑定。
  • 所有关键操作均可在业务审计中查询。

19.5 会员登录日志

  • 会员登录成功后,会员主表最后登录时间、IP密文、IP脱敏值和地区正确更新。
  • 登录失败不会覆盖会员最后一次成功登录信息。
  • 账号密码登录和手机验证码登录均写入独立会员登录日志。
  • 登录成功、密码错误、验证码错误、账号禁用和系统异常场景均能生成对应日志。
  • 无法识别会员的失败请求以 member_id=0 记录,且不保存完整手机号。
  • 后台“会员登录日志”可以按会员、方式、状态、域名、IP和时间查询。
  • 日志列表默认按登录时间倒序排列,可以查看详情并按筛选条件导出。
  • 后台页面和导出文件均不出现IP密文、密码、验证码或Token。
  • 登录日志队列异常时会同步写库兜底;两种写入都失败时产生服务端 Warning 告警。

20. 待确认事项

  1. 生产环境是否强制只允许 HTTPS。
  2. 前端页面和注册 API 是同域部署还是跨域部署。
  3. 是否存在 App、桌面客户端等没有浏览器 Origin 的注册入口。
  4. 注册入口是否统一为 /register,还是每个域名可以配置不同路径。
  5. 地址池是否需要按业务线、渠道或租户进一步分组。
  6. 会员登录日志的在线保留时长,以及是否需要定时归档历史日志。