VTY 品牌域名防护体系 · 产品需求文档(PRD)
| 字段 |
内容 |
| 项目代号 |
vty-domain-shield |
| 归属 |
VTY 体系 · 独立子项目(与前台、中控、操作文档解耦) |
| 文档版本 |
v3.0 |
| 修订日期 |
2026-09-02 |
| 线上评审 |
index.html · PRD.html |
文档目录
| 章节 |
读者 |
内容 |
| 一、需求背景 |
全员 |
为什么要做、vty.vip 基线、现状痛点、目标 |
| 二、PRD 总体方案 |
产品/架构 |
架构、四入口流程、安全审视、切流强更 |
| 三、运维实施手册 |
运维 |
DNS、301、URL 桶、监控、应急 |
| 四、技术实施方案 |
研发 |
接口、加密、池合并、IP/指纹实现、前端文案 |
| 五、测试方案 |
测试 |
P0/P1 用例、验收、自动化 |
| 附录 |
PM / 全员 |
RACI、名词数据字典、修订记录 |
一、需求背景
1.1 业务背景
VTY 对外有 品牌域名(如 winbet.com),用于广告、投放、口碑、线下物料等 品牌入口。
真正承载产品的是 站点域名池(play-*.com 等可轮换业务域),会因监管、投诉、DNS 污染、CDN 风控等原因失效。
当前架构矛盾:
- 品牌域若直接挂站点 → 爬虫/举报可抓到业务特征 → 品牌资产被污染或封禁。
- 站点域被封若入口写死 → 用户进不来、分享全失效。
- 后台 H5 域名池 与 App 域名池分开维护 → 切换不同步、探活重复、分享与 App 入口不一致。
- 技术反馈「产品方案无法落地」→ 缺少 接口何时返回域名、何时切换、谁负责什么 的可编码规格。
1.2 现状基线:vty.vip 品牌域(中国部分区域)
数据来源: 现网监测与运营反馈(2026-08~09);口径为「用户点击品牌链 → 最终进入可交互首页」。
1.2.1 当前表现
| 指标 |
现网 vty.vip 表现 |
说明 |
| 全链路成功进入率(中国) |
约 75% |
部分省份/运营商明显偏低 |
| 品牌域首跳可打开 |
约 80~85% |
个别地区 DNS 污染、链路中断 |
| 进入业务首页可交互 |
约 75% |
含二次跳转失败、站点域 regional 不可用 |
| 单省最低 |
< 60%(部分省/运营商) |
移动/联通/电信、跨省差异大 |
| 爬虫/扫描命中业务特征 |
有 |
品牌或跳转链路透出站点信息,易被举报 |
1.2.2 75% 失败原因拆解(归因假设,上线后用埋点验证)
| 原因类型 |
占比(估) |
典型表现 |
新方案对应能力 |
| 区域 DNS 污染 / 解析异常 |
~25% |
品牌域本身打不开或解析到错误 IP |
品牌零内容 + 多 Gate 热备;Site 与 Brand DNS 分离 |
| 跳转链路过长 / 单点 Site 不可用 |
~30% |
301/302 后目标域 regional 封或超时 |
加密 3 域 + 客户端轮询;探活自动摘除 |
| 品牌域被举报后连带 |
~15% |
品牌域出现业务特征或被反代 |
Brand 仅 301、零 Body;业务只在 Site |
| 机器扫描抓取域名池 |
~10% |
爬虫批量跟跳,加速封禁 |
IP+指纹双检;异常 空页;Resolve 加密 |
| 运营商劫持 / 中间盒干扰 |
~10% |
非预期跳转、证书告警 |
全链路 HTTPS;Gate/Site 证书独立;HSTS 评审 |
| 用户环境 / 弱网 |
~10% |
超时、WebView 异常 |
3 域轮询 + Resolve 重试 + 5min 后台探活 |
1.2.3 现状结论(为什么要换新方案)
vty.vip 作为品牌域长期承载跳转与内容暴露,在中国部分区域已出现 约 1/4 用户无法进入。
- 失败 不是单点,而是「区域解析 + 单域跳转 + 举报/扫描」叠加。
- 新方案目标不是「再换一个品牌域名字」,而是 Brand 干净、Gate 风控、Site 可轮换、客户端多域容错 的组合拳。
1.3 现状问题(方案层)
| 问题 |
影响 |
| 品牌域挂内容或反代站点 |
易被扫描/举报;品牌域高风险 |
| 无爬虫/机器识别,Gate 暴露域名 |
域名池被批量抓取 |
| 仅一次跳转、目标写死 |
站点封禁后入口全挂 |
| H5 / App 双池分离 |
主站切换不一致;运维/研发重复劳动 |
| 分享链接写死站点域 |
换域后历史分享失效 |
| 无法归因品牌进入 |
投放 ROI 不可量化 |
| 职责不清 |
出事后不知谁改 301、谁切池 |
1.4 v2 新增诉求(本次定案)
| 诉求 |
定案 |
| 防爬虫/机器检索 |
IP 正常 AND 浏览器指纹正常 才下发域名;任一异常 → 空页 |
| 域名下发 |
返回 开启状态最多 3 条 站点域,AES 加密;前端/App 解密 + 轮询探活 |
| 池合并 |
H5 / App 一张域名池表,channels 字段区分端 |
| App 独立链路 |
不经品牌域;app/resolve 非客户端请求 403 |
| 探活切换 |
服务端连续 3 次失败自动摘除;客户端当前域失败轮询下一域 |
1.5 目标与非目标
目标(Must)
- 品牌域零内容:探测仅见 301 或规范空响应,无业务页/登录/钱包文案。
- Gate 风控可落地:双检通过才 Resolve;爬虫见空页。
- 站点域可轮换:探活摘除 + 客户端轮询 + 加密下发 3 域。
- H5/App 池统一:一套配置、一套探活、一套主站决策。
- 分享动态解析:与 Resolve 同一主站数据源。
- 品牌来源可统计:
hop_id + brand_domain 归因。
- 职责清晰:运维/技术/测试各一章,可直接拆工单。
非目标
- 不实现充值/投注等业务功能。
- 不对抗国家级深度流量分析;目标是 降暴露 + 提可替换性。
- 不把短链平台做成完整 SaaS。
1.6 成功标准与 成功率目标(KPI)
1.6.1 安全与功能指标
| 指标 |
目标 |
| 品牌域扫描 |
0 命中业务特征页 |
| 爬虫/异常 IP |
100% 空页,响应中无域名明文 |
| 故障切换 |
摘除后新用户 ≤ 1 分钟落到活站 |
| H5/App 主站一致 |
后台改 primary 后两端一致 |
| 分享解析 |
≥ 99.9% 与 Resolve 主站一致 |
| 品牌归因 |
hop_id 核销覆盖率 ≥ 99% |
1.6.2 用户成功进入率(核心 KPI · 相对 vty.vip 基线)
口径定义(统一,上线前后同口径对比):
成功进入 = 用户发起访问后,在 30s 内 到达 Site 首页且 __health / 首屏关键接口至少 1 次成功(可交互)。
统计维度: 分省、分运营商(移动/联通/电信/广电)、分入口(品牌 H5 / App / 分享)。
| 指标 |
现网 vty.vip 基线 |
新品牌域上线目标(稳定 30 天后) |
验收 |
| 中国区全链路成功进入率 |
~75% |
≥ 88% |
较基线 +13pp |
| 品牌域首跳成功率(301 到 Gate) |
~80~85% |
≥ 95% |
品牌零内容 + 多 Gate |
| Gate 通过后进入 Site 成功率 |
未单拆(估 ~88% of 到达 Gate 用户) |
≥ 92% |
3 域轮询 + 探活 |
| 单省最低成功进入率 |
部分省 < 60% |
≥ 78%(不含西藏等极端样本省可单列) |
分省看板 |
| 三大运营商业网均值 |
未单独达标 |
≥ 85% |
分运营商看板 |
| App 冷启动 resolve 成功率 |
未与品牌同口径 |
≥ 90% |
不经品牌域独立链路 |
| 因举报/扫描导致的 7 日内品牌域失效次数 |
有发生 |
0 次(Brand 无业务内容) |
运维巡检 + 扫描 |
1.6.3 分期达标(上线后)
| 阶段 |
时间 |
成功进入率目标 |
说明 |
| 灰度 |
上线 0~7 天 |
≥ 82% |
小流量省份;调 ip/fp 阈值 |
| 全量 |
上线 8~30 天 |
≥ 85% |
全国放量;Gate/Site 池调优 |
| 稳定 |
上线 31 天+ |
≥ 88% |
正式 KPI;未达标走专项优化 |
1.6.4 未达标处置
| 情况 |
动作 |
| 全链路 < 85%(稳定期) |
产品+运维+技术联合排查:DNS/Gate/Site 分省;加备用 Gate 或 Site |
| 单省 < 78% |
该省切换备用 Gate 入口;增加 region_tags 匹配 Site |
| Gate 40301/40302 误杀 >15% |
风控下调阈值或分省策略 |
| 品牌域被投诉 |
不 回挂业务;运维保 301,必要时 换新品牌域(Brand 与 Site 仍隔离) |
1.6.5 监测实现(技术必须埋点)
| 事件 |
字段 |
用途 |
brand_request |
brand, province, isp, ok/fail, latency |
首跳 |
gate_check |
ip_score, fp_score, pass/fail, reason |
双检 |
resolve_result |
domain_count, latency, code |
下发 |
domain_probe |
try_order, domain, ok/fail, ms |
轮询 |
entry_success |
site, total_ms, hop_id |
成功进入(KPI 分子) |
entry_fail |
fail_stage, reason |
失败归因(KPI 分母拆解) |
二、PRD 总体方案
2.1 一页结论
品牌域(运维 301 零内容)
→ 【落地域 · 中间层】entry.xx.net(Gate 服务:双检 + resolve + probe 编排)
├─ 判定失败 → 地址栏仍是落地域,在落地域展示报错(空页/Loading/全屏)
└─ 判定成功 → location 进入 【站点主域】play-a.com(域名池,可轮换)
→ 运行期故障 → 在主站域内 Toast / FAILOVER(不切回落地域)
App → bootstrap 版本关 → resolve → probe → 同上进入主站域
四段链路:
| 段 |
域名角色 |
负责 |
做什么 |
| 第一段 |
Brand 品牌域 |
运维 |
纯 301 → 落地域;禁止绑 Site 源站 |
| 第二段 |
Landing 落地域(中间层) |
技术 |
双检 + Resolve + 探活编排;失败报错留在此域 |
| 第三段 |
Site 站点主域(池内) |
技术+运维 |
完整业务;成功后才进入 |
| App 段 |
不经 Brand/落地页 |
技术 |
客户端内等效落地逻辑 → 进主站域 |
2.2 域名分层(五层 · 含落地中间层)
| 角色 |
示例 |
用户地址栏 |
业务内容 |
主责 |
| Brand 品牌域 |
winbet.com |
一闪而过 |
禁止 |
运维:301 |
| Landing 落地域 |
entry.vty.net |
判定阶段停留 |
极简 bootstrap + 报错 UI;无投注/钱包 |
技术(Gate 服务) |
| Site 站点主域 |
play-a.com |
进站后 |
完整站(池内可轮换) |
技术+运维 CDN |
| Share 分享域 |
s-xx.com |
短链 |
302 → 主站域(可不经落地域) |
技术 |
| Admin 管理 |
内网 |
— |
域名池/落地域配置 |
技术 |
命名说明: 文档中的 Gate = 部署在 落地域 上的风控+引导 服务;entry.vty.net 为落地域 FQDN 示例(可与 go-gate.xx.net 并存热备,均属 Landing 角色)。
Brand ──301──→ Landing(落地中间层)──判定 OK──→ Site 主站域(池)
│ │ │
│ ├─ 判定 FAIL:报错在落地域 ├─ 运行期 FAIL:报错在主站域
│ │ (不暴露 play-*.com) │ (Toast / FAILOVER)
运维301 Gate双检+加密+probe编排 探活+轮询+业务
2.2.1 落地域 vs 主站域:判定归属与报错规则(产品定案)
核心规则:
- 落地域 = 中间层判定域:双检、resolve、解密、PROBE_LIST 全程在落地域 hostname 下完成(浏览器地址栏为
entry.vty.net)。
- 判定失败 → 当前域(落地域)报错:不跳主站、不在地址栏出现
play-*.com。
- 判定成功 → 才
location.replace 进入站点主域:此后报错归属 主站域。
- 站点主域 来自域名池
site_domain_pool,与落地域 证书/DNS/桶完全分离。
| 阶段 |
地址栏域名 |
判定内容 |
失败:报错在哪 |
成功:下一步 |
| 品牌跳转 |
winbet.com → entry.vty.net |
301 |
品牌打不开(运维) |
进入落地域 |
| 落地·风控 |
entry.vty.net |
IP + fp |
落地域 空页 E-GATE-01 |
继续 bootstrap |
| 落地·解析 |
entry.vty.net |
resolve + 解密 |
落地域 E-LOAD / E-DECRYPT / E-RESOLVE |
得 domain_list |
| 落地·探活 |
entry.vty.net |
HEAD 主站候选域 |
落地域 E-PROBE Toast / E-RESOLVE 全屏 |
跳转主站域 |
| 主站·运行 |
play-a.com |
API / health |
主站域 E-NET Toast / 业务错误 |
正常业务 |
┌──────────────────────────────────────┐
│ 落地域 entry.vty.net(中间层) │
│ ┌────────────────────────────────┐ │
Brand 301 ───────→│ │ IP/fp 双检 │ │
│ │ resolve → 解密 → probe 编排 │ │
│ │ FAIL → 本域报错(见 §4.10.5) │ │
│ └───────────────┬────────────────┘ │
└──────────────────│ PASS ─────────────┘
▼
┌──────────────────────────────────────┐
│ 站点主域 play-a.com(池内 · 可轮换) │
│ 首页 / 登录 / 投注 · FAILOVER 在此域 │
└──────────────────────────────────────┘
与分享/直访差异:
| 入口 |
是否经落地域 |
说明 |
| 品牌 H5 |
必须 |
Brand 301 → 落地域 → 判定 → 主站 |
| 分享 |
可选(默认 跳过) |
Share 302 直达主站域;失败在主站域 FAILOVER |
| 主站直访 |
否 |
已在主站域;失败在主站或跳 Brand |
| App |
否(客户端内等效) |
SDK 内完成「落地逻辑」,UI 在 App 内,不进 landing URL |
2.2.2 落地域配置(运维 + 技术)
| 项 |
规格 |
| 数量 |
≥2 热备 FQDN(如 entry-a.vty.net、entry-b.vty.net) |
| Brand 301 目标 |
仅指向 落地域,禁止 301 到 play-*.com |
| 桶 |
bucket-landing-* 独立桶;仅 bootstrap + Gate API |
| 证书 |
与 Brand / Site 分离 SAN |
| 页面限制 |
无 /login /wallet 等业务路由;仅 /r/{brand} + /api/v1/gate/* |
| 池关系 |
落地域 不在 site_domain_pool;主站域 仅在 resolve 加密包内下发 |
核心原则
- 品牌域 永不 托管业务内容。
- Site 可牺牲轮换;Brand 尽量不动。
- 分享永远解析 当前 Site。
hop_id 第一跳签发、落地核销,防刷量。
2.3 全入口端到端流程(品牌 / 分享 / 主站直访 / App)
本章定位: 四条入口共用 域名池(主站域)+ 落地域判定 + FAILOVER;品牌 H5 必须经 落地中间层。实现见 §4.3~§4.6。
2.3.0 四条入口总览
| 入口 |
典型 URL |
entry_type |
落地中间层 |
判定失败报错域 |
成功进入 |
| 品牌 H5 |
winbet.com |
brand |
✓ entry.xx.net |
落地域 |
主站域 play-*.com |
| 分享短链 |
s-xx.com/{code} |
share |
✗(默认) |
主站域 / Share 503 |
主站域 |
| 主站直访 |
play-a.com |
direct |
✗ |
主站域 |
主站域 |
| App |
冷启动 |
app |
App 内等效 |
App 内 / 40310 |
主站域 |
Brand ─301─→ Landing 落地域(判定层 · 失败留本域报错)
│
│ PASS:location.replace
▼
Site 主站域 play-a/b/c(池 · 运行期故障在本域处理)
sequenceDiagram
participant U as 用户浏览器
participant B as Brand<br/>winbet.com
participant L as 落地域<br/>entry.vty.net
participant R as Resolve API
participant P as 主站域名池
participant S as 站点主域<br/>play-a.com
U->>B: ① 点击品牌 URL
B->>L: ② 301(零 Body)
Note over U,L: 地址栏 = entry.vty.net(中间层)
L->>L: ③ IP 检测
L->>U: ④ bootstrap
U->>U: ⑤ 采集 fp
U->>L: ⑥ POST gate/resolve
alt 判定失败(IP/fp/resolve/probe)
L-->>U: 留在落地域报错(空页/Loading/全屏)
Note over U: 地址栏仍为 entry.vty.net
else 判定成功
L->>P: ⑧ 查 healthy TOP3
L-->>U: 加密 payload
U->>U: 解密 + PROBE_LIST(仍在落地域 orchestrate)
U->>S: ⑪ location.replace → play-a.com
Note over U,S: 地址栏变为主站域 · 此后报错在主站
alt 主站运行期故障
U->>U: FAILOVER(Toast · 不切回落地域)
end
end
| 阶段 |
地址栏 |
判断内容 |
失败 → |
成功 → |
| ①② |
Brand→Landing |
301 |
品牌打不开 |
进入 落地域 |
| ③~⑦ |
Landing |
IP + fp + 限流 |
落地域 报错 E-GATE-01 |
继续 resolve |
| ⑧~⑩ |
Landing |
resolve + 解密 |
落地域 E-LOAD / E-DECRYPT / E-RESOLVE |
domain_list |
| ⑪ probe |
Landing(编排) |
HEAD 主站候选 |
落地域 E-PROBE / E-RESOLVE |
跳转主站域 |
| 运行期 |
Site 主域 |
API / health |
主站域 Toast / 全屏 |
正常业务 |
2.3.2 品牌 H5:阶段一(Brand → 落地域)
触发: 用户点击 https://winbet.com/。
| 步骤 |
执行方 |
动作 |
分支 |
| S1~S2 |
Brand CDN |
DNS / TLS |
失败 → 品牌域不可用 |
| S3 |
Brand CDN |
301 → https://entry.vty.net/r/winbet?... |
禁止 301 到 play-*.com |
| S4 |
浏览器 |
地址栏变为 落地域 |
超时 → fail_stage=landing_unreachable |
原则: Brand 零 Body;301 Location 只能 是落地域 FQDN。
2.3.3 品牌 H5:阶段二(落地域 · 风控双检)
发生域: 地址栏 = entry.vty.net(落地中间层)。
Gate 服务收到 GET /r/{brandId} 后返回 bootstrap;判定失败不跳主站,在落地域处理。
判定失败 vs 成功的域归属
| 结果 |
地址栏 |
用户看到 |
是否出现 play-*.com |
| IP/fp 失败 |
落地域 |
空页 E-GATE-01 |
否 |
| resolve/probe 失败 |
落地域 |
Loading / 全屏 E-RESOLVE-01 |
否 |
| 成功 |
主站域 |
首页/登录(完整业务) |
是 |
判断顺序(短路,任一 FAIL 即停)
1. brand_id 有效? → 否:404
2. IP 硬拒绝 IP-H1~H5? → 是:空页 40301
3. IP 白名单 IP-W1? → 是:跳过 IP 打分(仅限流)
4. IP 评分 ip_score → <60:空页 40301
5. 频率限流 → 超限:429 或空页
6. (resolve 时)fp 硬拒绝 → 是:空页 40302
7. fp 评分 fp_score → <60:空页 40302
8. 全部通过 → 执行 Resolve 查池
Gate 全分支表
| # |
触发条件 |
判断时机 |
HTTP/响应 |
用户 UI |
是否下发域名 |
埋点 reason |
| G1 |
正常用户 |
resolve 时 |
200 code=0 |
E-LOAD-01 → 进站 |
是 |
pass |
| G2 |
IP 黑名单/扫描器/Tor |
首包 GET Gate |
200 空 HTML |
空页 E-GATE-01 |
否 |
ip_blacklist 等 |
| G3 |
ip_score < 60 |
GET 或 resolve |
200 空 / 40301 |
空页 |
否 |
ip_score_low |
| G4 |
fp webdriver/headless |
resolve |
40302 |
空页 |
否 |
FP-H* |
| G5 |
fp_score < 60 |
resolve |
40302 |
空页 |
否 |
fp_score_low |
| G6 |
fp 采集超时 >3s |
resolve 未带 fp |
40302 |
空页 |
否 |
fp_timeout |
| G7 |
同 IP >30 次/min |
resolve |
429 |
E-RATE-01 或空页 |
否 |
rate_limit |
| G8 |
搜索引擎 Bot(可选) |
GET Gate |
空页 |
空页 |
否 |
bot_ip |
| G9 |
brand 已禁用 |
resolve |
404 |
空页 |
否 |
brand_disabled |
关键: G2~G8 绝不调用 Resolve;响应中 无 domains 明文、无跳转 Site。
2.3.4 品牌 H5:阶段三(域名池 · Resolve · 解密)
A. 服务端:域名从哪来
Resolve 不接受客户端指定域名;只从统一池 site_domain_pool 读取:
SELECT domain FROM site_domain_pool
WHERE status = 'active'
AND health = 'ok'
AND fail_streak < 3
AND (channels @> ARRAY['all'] OR channels @> ARRAY['h5'])
ORDER BY is_primary DESC, weight DESC
LIMIT 3;
| 池状态 |
healthy 数量 |
Resolve 行为 |
返回 |
| 正常 |
≥3 |
取 weight 最高 3 条 |
加密 3 域 |
| 降级 |
1~2 |
取现有 |
加密 1~2 域 |
| 枯竭 |
0 |
不加密 |
503 code=50301 → E-RESOLVE-01 |
池维护(与 Resolve 联动):
- 探活 Worker 每 30s HEAD 各 Site
/__health;连续 3 败 → health=fail、status=disabled → 下次 Resolve 自动剔除。
- Admin 改 primary → 下次 Resolve 的
domains[0] 变化;H5/App/Share 同源。
- 人工 blocked → 立即剔除,不等待探活。
B. 客户端:如何请求 Resolve
Gate bootstrap 加载后,SDK 执行:
1. gate-fp-collect.js 采集 → fp_hash(≤3s,超时则 resolve 仍发但 fp 空 → 40302)
2. 生成 nonce(uuid)、ts、sig = HMAC(brand_id|ts|nonce|fp_hash)
3. POST /api/v1/gate/resolve Body: { brand_id, channel:"h5", fp_hash, ts, nonce, sig }
4. 超时 5s;网络错误 → RETRY_RESOLVE(最多 3 轮,间隔 2s)
C. 客户端:如何解密
Response 示例:
{
"code": 0,
"data": {
"key_id": "k2026-03",
"expires_at": 1735689900,
"ttl_sec": 300,
"payload": "<BASE64(iv+ciphertext+tag)>"
}
}
解密步骤(H5 / App 一致):
1. 用 key_id 从本地密钥表取 AES-256 密钥 K(H5 bootstrap 内嵌混淆;App 内置可热更新)
2. BASE64 解码 payload → 拆 iv(12B) + ciphertext + tag(16B)
3. AES-256-GCM 解密(K, iv, ciphertext, tag) → UTF-8 JSON 明文
4. 校验 JSON:v=1,domains 为非空数组,hop_id 存在,expires_at 未过期
5. domain_list = domains(顺序即 probe 顺序);写入 resolve_meta 到 sessionStorage
明文结构(解密后):
{
"v": 1,
"domains": ["play-a.com", "play-b.com", "play-c.com"],
"primary": "play-a.com",
"hop_id": "hop_8f3k2j",
"brand_id": "winbet"
}
| 解密分支 |
条件 |
处理 |
用户 UI |
| D1 |
解密成功 |
进入 PROBE_LIST |
E-LOAD-01 |
| D2 |
key_id 未知 |
重拉 resolve 1 次 |
E-LOAD-01 |
| D3 |
GCM tag 校验失败 / JSON 非法 |
停止,不上线 |
E-DECRYPT-01 |
| D4 |
expires_at 已过期 |
丢弃缓存,重新 resolve |
E-LOAD-01 |
| D5 |
domains 为空数组 |
RETRY_RESOLVE |
E-LOAD-01 → E-RESOLVE-01 |
2.3.5 品牌 H5:阶段四(探活 · 跳转主站域)
关键: PROBE_LIST 由落地域 bootstrap 发起,但 仅在 probe 成功 后执行 location.replace;此前地址栏 始终是落地域。
# 地址栏仍为 entry.vty.net
for domain in domain_list:
probe(domain) # HEAD https://play-x.com/__health
if ok:
location.replace("https://{domain}/?hop=...") # 地址栏 → 主站域
return
# 全失败:留在落地域,展示 E-RESOLVE-01(不暴露主站 URL 给用户点击)
| Probe 分支 |
条件 |
地址栏 |
下一步 |
用户 UI(报错域) |
| P1 |
d1 成功 |
落地→主站 |
进 d1 |
E-LOAD-01 消失 |
| P2 |
d1 失败 d2 成功 |
落地→主站 |
进 d2 |
落地域 Toast E-PROBE-01 |
| P4 |
3 域全失败 |
落地域 |
RETRY_RESOLVE |
落地域 E-LOAD-01 |
| P5 |
3 轮仍失败 |
落地域 |
停止 |
落地域 E-RESOLVE-01 |
进站后: 业务 API 基址 = https://{current_domain};hop_id 随首屏请求上报核销。
2.3.6 共用:运行期故障(主站域内 · 不回退落地域)
适用: 用户地址栏 已是 play-*.com(主站域)。FAILOVER 在主站域之间切换,不跳回落地域(避免二次双检损耗)。
故障检测(任一触发 FAILOVER)
| 信号 |
阈值 |
说明 |
| 业务 API 失败 |
连续 3 次(5xx/网络错误/超时>5s) |
主路径 |
/__health 失败 |
连续 2 次(间隔 30s) |
辅助 |
WebView onReceivedError |
1 次 |
App |
| 用户切回前台 |
立即 probe 1 次 |
App/H5 |
FAILOVER 流程
RUNNING 状态,current_domain = play-a.com
1. 标记 play-a.com 为 local_bad(本地 60s 内不再优先)
2. 从 domain_list 取下一未标记域 play-b.com
3. HEAD play-b.com/__health(3s)
├─ 成功 → 切换 API baseUrl 到 play-b
│ Toast E-NET-01「网络异常,已为您切换至备用线路」
│ 上报 domain_switch(from=play-a, to=play-b)
│ 用户 **无刷新** 继续操作(SPA 重绑 baseUrl)
└─ 失败 → 试 play-c.com
4. 列表内无可用域 → RETRY_RESOLVE(2 轮)
├─ 拿到新 domain_list → 再 PROBE_LIST
└─ 仍失败 → 全屏 E-RESOLVE-01
运行期全分支
| # |
场景 |
服务端池 |
客户端动作 |
用户感知 |
| R1 |
仅 play-a regional 慢 |
health 仍 ok |
PROBE 时可能跳过 a 进 b |
进站时 Toast |
| R2 |
play-a 宕机,b/c 正常 |
90s 后 a disabled |
FAILOVER → b |
Toast,不中断会话 |
| R3 |
用户正在 a,a 被探活摘除 |
新 resolve 无 a |
当前会话 FAILOVER;b/c 可用则切 |
Toast |
| R4 |
3 域全挂 |
503 |
RETRY → 全屏 |
「暂时无法连接」 |
| R5 |
弱网瞬断 |
— |
API 重试 1 次,未达 3 次不切换 |
无感 |
| R6 |
后台 5min 定时 probe |
— |
当前域失败 → 静默 FAILOVER |
多数无感 |
| R7 |
resolve_meta 将过期(<60s) |
— |
后台静默 refresh resolve |
无感 |
Brand 域不受影响: Site 全挂时 不改 Brand 301;用户刷新品牌链重新走 Gate 流程。
2.3.7 端到端分支 master 表(四入口速查)
| 入口 |
阶段 |
失败点 |
用户结果 |
恢复手段 |
| 品牌 |
Brand |
DNS/301 异常 |
打不开品牌链 |
运维换 Brand |
| 品牌 |
Gate IP/fp |
爬虫/机房 |
空页 |
无(故意) |
| 品牌 |
Resolve/Probe |
池空/全域挂 |
全屏 E-RESOLVE-01 |
启用 standby |
| 分享 |
Share 解析 |
code 无效 |
404 短链页 |
重新生成分享 |
| 分享 |
Share 解析 |
primary 全挂 |
Share 维护页 |
技术切 primary |
| 分享 |
302 落地 Site |
该域 regional 慢 |
进站后 FAILOVER |
Toast 切域 |
| 直访 |
当前域探活 |
play-a 挂、无缓存 |
引导官方入口 |
跳 Brand 或 Gate |
| 直访 |
有缓存 |
a 挂 b/c 可用 |
自动切 b/c |
FAILOVER |
| App |
bootstrap |
版本 < min |
40310 全屏强更 |
商店更新 |
| App |
切流日 |
旧包打开 |
E-APP-FORCE-01 |
不可跳过 |
| 共用 |
运行期 |
current 域挂 |
Toast 切域 |
§2.3.6 |
| 共用 |
运行期 |
列表全失效 |
全屏 |
「重新连接」 |
2.3.8 分享链路(点击分享短链)
触发: 用户在 IM/社媒点击 https://s-xx.com/{code}(或带路径 /p/match/123)。
sequenceDiagram
participant U as 用户浏览器
participant Sh as Share 域<br/>s-xx.com
participant P as 域名池 DB
participant S as Site<br/>play-*.com
U->>Sh: GET /{code}
Sh->>Sh: ① IP 限流 + 可选 Bot 识别
Sh->>Sh: ② 校验 code 有效 / 未过期
alt code 无效
Sh-->>U: 404 短链不存在
else code 有效
Sh->>P: ③ 查 is_primary + healthy 备份序
alt 无 healthy 域
Sh-->>U: 503 Share 维护页
else 有可用域
Sh->>Sh: ④ 选 target:primary 不通则次优 healthy
Sh-->>U: 302 Location=Site 落地页
U->>S: ⑤ 加载 Site SPA + SDK
S->>S: entry_type=share;写 share_code/hop_id
Note over U,S: 无 Gate 双检;运行期 FAILOVER 同 §2.3.6
end
end
逐步流程
| 步骤 |
执行方 |
动作 |
分支 |
| SH1 |
Share CDN |
DNS/TLS,GET /{code} |
域名挂 → 运维切 Share 备域 |
| SH2 |
Share 服务 |
IP 限流(如 60 次/min/IP);已知 Bot → 429 或空页 |
超限 → 429 |
| SH3 |
Share 服务 |
查短码表:code → { path, inviter_id, expires_at } |
无效/过期 → 404 |
| SH4 |
Share 服务 |
查池:is_primary 且 health=ok;若 primary probe 失败则取 weight 次高 healthy 域 |
池空 → 503 Share 维护页 |
| SH5 |
Share 服务 |
302 https://{target}{path}?entry=share&share_code={code}&hop_id={hop} |
— |
| SH6 |
Site SDK |
解析 URL 参数;entry_type=share;上报 entry_success |
当前域不可用 → §2.3.6 FAILOVER |
分享 vs 品牌:关键差异
| 项 |
品牌 H5 |
分享 |
| 是否经 Gate |
✓ |
✗ |
| 是否 IP+fp 双检 |
✓ 严格 |
Share 仅 轻量限流 |
| 如何知 Site 域 |
Resolve 加密 3 域 |
服务端 302 单域(primary) |
| 客户端 domain_list |
解密得 3 域 |
首次仅知当前 302 域;若本地有历史 resolve_meta 则复用 |
| 换 primary 后 |
下次 resolve 更新 |
下次点击分享 即 302 到新 primary |
| 埋点 |
entry_type=brand + hop |
entry_type=share + share_code |
分享分支表
| # |
条件 |
HTTP |
用户看到 |
处理 |
| S1 |
正常 |
302 → Site |
直达落地页(赛事/活动等) |
— |
| S2 |
code 不存在 |
404 |
「链接无效」 |
重新分享 |
| S3 |
code 过期 |
410 |
「链接已过期」 |
重新分享 |
| S4 |
池枯竭 |
503 |
Share 维护页 |
运维启 standby |
| S5 |
primary Regional 不通 |
302 到 backup 域 |
正常进站(用户无感) |
Share 服务端选次优域 |
| S6 |
302 落地后 Site 挂 |
— |
Toast FAILOVER |
有缓存则切 b/c;无缓存 → E-RESOLVE-01 +「官方入口」 |
分享接口(与 Resolve 同源)
GET /api/v1/resolve/current-site
Authorization: Bearer {admin_or_share_service}
→ { "primary": "play-a.com", "healthy": ["play-a.com","play-b.com"], "version": 12 }
用户短链边缘逻辑等价于:读 current-site → 302 到 primary + 原始 path。
2.3.9 主站直访(点击/输入 Site 域名)
触发: 用户 收藏夹、短信老链接、手动输入 打开 https://play-a.com/(或 /login),不经过 Brand / Gate / Share。
sequenceDiagram
participant U as 用户浏览器
participant S as Site<br/>play-a.com
participant G as Gate<br/>(仅兜底)
participant B as Brand<br/>(官方入口)
U->>S: GET https://play-a.com/
S->>U: 加载 SPA + domain-shield SDK
U->>S: HEAD /__health(3s)
alt 当前域可用
S->>S: entry_type=direct → RUNNING
else 当前域不可用
alt localStorage 有 resolve_meta
U->>U: PROBE_LIST 缓存域 → FAILOVER
else 无缓存
U->>G: 可选:跳 Gate bootstrap
U->>B: 或:跳 Brand 官方入口
Note over U: 全屏「请从官方入口进入」
end
end
逐步流程
| 步骤 |
执行方 |
动作 |
分支 |
| D1 |
用户 |
打开 https://play-a.com/... |
DNS 失败 → 浏览器报错 |
| D2 |
Site |
返回业务 SPA;内嵌 domain-shield SDK(与 Gate bootstrap 同库) |
— |
| D3 |
SDK |
current_domain = location.hostname;entry_type = direct |
— |
| D4 |
SDK |
HEAD https://play-a.com/__health(3s) |
见下表 |
| D5 |
SDK |
上报 entry_success(direct)或进入 FAILOVER |
— |
直访分支表
| # |
条件 |
SDK 动作 |
用户 UI |
说明 |
| D-OK |
health 200 |
RUNNING,正常业务 |
无额外提示 |
最常见:收藏仍可用 |
| D-F1 |
health 失败 + 有 resolve_meta |
PROBE 缓存 domain_list → 切 b/c |
Toast E-NET-01 |
曾从品牌进过,有 3 域缓存 |
| D-F2 |
health 失败 + 无缓存 |
location → 配置的 official_brand_url(如 winbet.com) |
全屏或中间页:「请从官方入口访问」 |
禁止在 Site 页暴露其他 Site 域明文列表 |
| D-F3 |
health 失败 + 无缓存 + 无 Brand 配置 |
location → Gate bootstrap ?from=direct |
E-RESOLVE-01 |
Gate 走完整双检+resolve |
| D-F4 |
运行期中 play-a 挂 |
同 §2.3.6 FAILOVER |
Toast |
与品牌进站后相同 |
产品定案(直访)
- 允许直访:不强制每次跳 Brand(否则收藏失效,体验差)。
- 不直访下发域名池:无 Gate 双检时 不得 在 Site 页调用
gate/resolve 展示备选域列表(防爬虫撞库)。
- 兜底顺序:本地缓存 FAILOVER → 跳 Brand 官方入口 → 跳 Gate → 全屏 E-RESOLVE-01。
- 配置项:
official_brand_url、gate_fallback_url 由配置中心下发,写入 Site SDK。
// Site SDK 直访 INIT 伪代码
async function initDirect() {
const host = location.hostname;
if (await probe(host)) {
setEntryType('direct');
return enterRunning(host);
}
const cached = loadResolveMeta();
if (cached?.domains?.length) {
return probeListAndFailover(cached.domains); // D-F1
}
const brand = config.official_brand_url;
if (brand) return location.replace(brand); // D-F2
return location.replace(config.gate_fallback_url + '?from=direct'); // D-F3
}
2.3.10 App 链路(不经品牌域 · 含版本校验)
触发: App 冷启动、网络恢复、后台切前台、定时 5min、FAILOVER 需新列表。
与 H5 差异: App 必须先过版本关,再 resolve;旧版在切流日 强制更新,否则无法拿域名。
sequenceDiagram
participant A as App
participant C as Config/bootstrap API
participant K as Keychain
participant API as app/resolve
participant P as 域名池
participant S as Site
A->>C: ① GET /app/bootstrap(X-App-Version)
alt version < min_version 或 force_update
C-->>A: force_update + store_url
A->>A: 全屏强制更新(不可关闭)
else 版本 OK
A->>K: ② 读 current_domain + resolve_meta
A->>S: ③ probe current(3s)
alt probe OK 且 meta 未过期
A->>A: 跳过 resolve,RUNNING
else
A->>API: ④ POST app/resolve(HMAC)
alt 40310 force_update
API-->>A: 强制更新
else 403 无签名
API-->>A: 拒绝
else OK
API->>P: 查池 TOP3(channel=app)
API-->>A: 加密 payload
A->>A: 解密 → PROBE_LIST → 写 Keychain
end
end
A->>S: ⑤ 业务 API(X-App-Sign)
end
| 步骤 |
动作 |
分支 |
| A0 |
GET /api/v1/app/bootstrap(最先,早于 resolve) |
见 App 版本分支表 |
| A1 |
读 Keychain current_domain |
空 → 直接 resolve |
| A2 |
probe 当前域 + 检查 expires_at |
OK 且未过期 → 跳过 resolve |
| A3 |
POST /api/v1/app/resolve + 签名头 |
40310 → 强制更新;403 → 拒绝 |
| A4 |
解密 → PROBE_LIST → 写 Keychain |
同 §2.3.5 |
| A5 |
业务请求 X-App-Sign;加载 brand_pack 皮肤资源 |
Site 网关校验 |
App 版本分支表
| # |
条件 |
服务端 |
客户端 UI |
能否 resolve |
| V-OK |
semver(App) >= min_version |
bootstrap code=0 |
正常启动 |
✓ |
| V-F1 |
semver(App) < min_version |
bootstrap 或 resolve 40310 |
全屏强制更新 E-APP-FORCE-01 |
✗ |
| V-F2 |
force_update=true(切流开关)且 App 非最新 bundle |
bootstrap 40310 |
全屏更新 |
✗ |
| V-F3 |
brand_pack_version 本地过旧 |
bootstrap 带 required_brand_pack |
强制更新或热更包(v1 仅强制整包) |
✗ |
| V-S1 |
可选升级:latest > current >= min |
bootstrap 带 optional_update |
可关闭弹窗 |
✓ |
App 与 H5 能力对照(版本相关)
| 能力 |
H5 |
App |
| 品牌域切换 |
Brand 301 即时生效,无版本概念 |
旧包可能 写死旧 Brand/Gate → 必须强更 |
| 皮肤改版 |
Gate bootstrap / Site 静态资源即时换 |
主题资源在 App 包内 或需新 native → 强更 |
| resolve 密钥轮换 |
bootstrap 热更新 |
需 App 内置 key 表 → 低版本 40310 |
| 切流策略 |
运维改 DNS 即可 |
服务端 min_version + force_update 双保险 |
2.3.11 四入口对照总表
| 维度 |
品牌 H5 |
分享 |
主站直访 |
App |
| 典型场景 |
广告/物料 |
好友分享比赛/活动 |
收藏/老短信链接 |
打开 App |
| entry_type |
brand |
share |
direct |
app |
| hop_id |
Gate resolve 签发 |
Share 302 附带 |
无(或直访不带) |
resolve 签发 |
| 风控 |
IP+fp 双检 |
Share 限流 |
无(靠 Site 自身) |
App HMAC |
| 拿 domain_list |
加密 resolve |
302 单域;缓存可 FAILOVER |
仅本地缓存 |
app/resolve |
| 探活 |
PROBE 3 域 |
落地后可选;FAILOVER 用缓存 |
先 probe 自身 |
同 H5 |
| 运行期切换 |
§2.3.6 |
§2.3.6 |
§2.3.6 |
§2.3.6 |
| 池数据源 |
site_domain_pool |
同左 is_primary |
同左(仅缓存) |
同左 channel=app |
| 失败兜底 |
E-RESOLVE-01 |
Share 503 / 官方入口 |
跳 Brand |
维护页 / 强制更新 |
| 版本关 |
无 |
无 |
无 |
bootstrap + 40310 强更 |
2.7 大版本上线切流:品牌域调整 + 品牌皮肤改版 + App 强制更新(一次性)
产品定案: 新品牌域、新 Gate、新 品牌皮肤(UI 主题)、新 App 域名防护能力 同一窗口上线;旧 App 一律强制更新,不允许静默降级访问。
2.7.1 为什么要绑在一起
| 变更项 |
旧 App 不更新的后果 |
| 品牌域 vty.vip → winbet.com |
旧包内嵌/配置旧 Brand、旧分享文案、旧 DeepLink |
| Gate / resolve 协议 |
旧签名算法或旧 key_id 解密失败 |
| 品牌皮肤改版 |
旧 UI 资产与新区块不匹配;运营物料与 App 视觉不一致 |
| 域名池合并 |
旧 App 池与 H5 池不一致,切主站后两端分裂 |
结论: 切流日 = 运维切 Brand + H5 Gate 上新 + 服务端抬高 min_version + App Store 仅保留新包。
2.7.2 上线包内容(Cutover Bundle 2026-brand-v2)
| 组件 |
版本/标识 |
负责 |
切流日动作 |
| Brand DNS |
winbet.com → Gate |
运维 |
301 生效;旧 Brand 保留 301 或退役 |
| Gate bootstrap |
gate_bs_v2 |
前端 |
部署新 fp 采集 + 新 key_id |
| 品牌皮肤 |
brand_pack_v2(主题色/Logo/启动图) |
客户端+设计 |
仅 App ≥4.0.0 包内携带 |
| App iOS/Android |
≥ 4.0.0(示例) |
客户端 |
商店上架;旧版下架或强更 |
| 服务端 |
min_version=4.0.0,force_update=true |
后端 |
配置中心一键开关 |
| H5 Site |
随 Site 部署 |
前端 |
与新皮肤 Web 版一致 |
| 域名池 |
合并池 primary 指向新主站 |
技术 |
Admin 确认 healthy ≥3 |
2.7.3 切流时间线(T 日 = 正式上线)
| 时间 |
动作 |
验证 |
| T-14 |
App 4.0.0 提审;内测 min_version=3.x 不变 |
TestFlight / 内测包过 P0 |
| T-7 |
商店 软发布(可选下载);物料仍用旧 Brand |
新包 bootstrap/resolve 连通预发 Gate |
| T-1 |
运维预配 winbet.com 301(灰度 5% 流量或 hosts 验证) |
curl 301 + Gate 双检 |
| T 00:00 |
① Brand 全量 301 → 新 Gate ② min_version=4.0.0 ③ force_update=true ④ brand_pack_version=2026-brand-v2 |
监控 entry_success |
| T+0~2h |
旧 App 打开 → 全屏强更;新 App 正常 resolve |
强更率、40310 占比 |
| T+24h |
评估 KPI;未达标不回滚 Brand 业务,只调 Gate/Site 池 |
分省看板 |
| T+7 |
force_update 可改 false(min_version 保持 4.0.0) |
仅 block <4.0.0 |
2.7.4 切流日各端行为
【旧 App <4.0.0 用户】
打开 App → bootstrap → 40310 / force_update
→ 全屏「版本过低,请更新」(不可关闭、不可进首页)
→ 跳转 App Store / APK 官网
【新 App ≥4.0.0 用户】
bootstrap OK → app/resolve → 新池 3 域 → probe → 新皮肤首页
【H5 用户】
点击 winbet.com(新 Brand 物料)→ 301 Gate → 双检 → resolve
(无强更;浏览器始终最新 bootstrap)
【分享 / 直访】
Share 302 新 primary;直访逻辑不变(§2.3.8~2.3.9)
2.7.5 回滚原则
| 情况 |
是否回滚 Brand 301 |
是否降低 min_version |
| Gate 故障 |
可切 备用 Gate Location |
否 |
| Site 池空 |
启 standby 域 |
否 |
| 新 App 崩溃率 >5% |
暂停商店推广;hotfix 4.0.1 |
否(除非灾难且新包不可用) |
| 误发强更 |
— |
仅 force_update=false 临时放开;min_version 不建议降 |
禁止: 切流后为救急 降低 min_version 让旧 App 带旧皮肤 + 旧 Brand 访问新 Gate(会造成双轨运营与举报风险)。
2.7.6 职责 RACI(切流专项)
| 事项 |
产品 |
运维 |
客户端 |
后端 |
测试 |
| min_version / force_update 配置 |
A |
I |
C |
R |
C |
| Brand 301 切换 |
C |
A/R |
I |
I |
C |
| App 商店上架/强更文案 |
A |
I |
R |
I |
C |
| brand_pack 皮肤验收 |
A |
I |
R |
I |
R |
| 切流当晚值班 |
C |
R |
R |
R |
R |
2.4 关键场景
| 场景 |
行为 |
切流日 旧 App < min_version |
bootstrap/resolve 40310 → 全屏强制更新,不可进业务 |
| 切流日 新 App ≥ min_version |
bootstrap OK → resolve → brand_pack_v2 皮肤 + 新域名池 |
| 正常用户经 品牌域 进入 |
301 → Gate 双检 → 3 域加密 → 轮询进站 |
| 用户点 分享短链 |
Share 302 到当前 primary → Site 落地;primary 挂则 Share 选 backup |
| 用户 直访 play-a.com |
probe 自身;OK 则 direct 进站;失败则用缓存 FAILOVER 或跳 Brand |
| 爬虫/机房 IP 走品牌 |
Gate 空页,Resolve 不调用 |
| Site-A 探活失败 3 次 |
自动 disabled;Resolve/Share 不再选 A |
| 用户正在 A,A 突然挂 |
客户端 FAILOVER B/C;必要时重拉 resolve |
| 后台切换主站 |
H5/App/Share 下次请求一致到新 primary |
| App 用 curl 调 resolve |
403 |
2.5 运维 × 技术协作
| 接口点 |
运维 |
技术 |
约定 |
| Brand→Gate |
301 Location |
Gate path + sig 算法 |
Location 含 brandId |
| Gate 域名 |
DNS/CDN/证书 |
部署 bootstrap + API |
异常空页 |
| Site 桶 |
每域独立 CDN 桶 + /__health |
探活 Worker 读 health |
90s 内 3 败摘除 |
| 切主站 |
无需改 Brand |
Admin 改 primary |
技术主责 |
| 告警 |
品牌非 301、证书 |
池可用数、探活失败 |
统一值班群 |
2.6 风险场景与应对(举报 · 机器扫描 · 区域不可用)
原则: 品牌域 永不承载可举报内容;机器/爬虫 看不到域名池;Site 被封 不影响 Brand;用户侧 多域容错 拉升成功进入率。
2.6.1 举报类场景
| 场景 |
现网 vty.vip 风险 |
新方案应对 |
负责 |
| 用户/竞品向渠道举报「赌博/违规页面」 |
若 Brand 反代 Site,举报材料含 品牌 URL + 业务截图 → 品牌域连带封禁 |
Brand 零 Body、零业务;举报页只能拍到 301 或空 Gate |
运维+产品 |
| 监管扫描 URL 库 |
品牌域在库中且可访问业务 |
Brand 响应 无登录/钱包/赛事关键词;业务特征仅在 Site |
运维巡检 |
| 证书透明度 / 关联域名连带 |
品牌与多个 Site 混 SAN |
Brand / Gate / Site 证书分离,SAN 不混配 |
运维 |
| 支付/应用商店审核抓链 |
审核爬虫跟跳看到业务 |
Gate 双检:审核机房里 IP 空页;真实用户正常 Resolve |
技术+风控 |
| 投诉后 ISP 局部阻断 |
单省 Brand 解析失败 |
多 Brand 热备(物料分省);Gate 多入口 301 目标可切 |
运维 |
| Site 被举报封禁 |
旧方案品牌入口同步失效 |
Brand 不改;技术 disabled Site + 轮询 其余 2 域;Resolve 不再下发死域 |
技术 |
| Gate 被举报 |
— |
Brand 301 切备用 Gate;Gate 页 无业务文案 |
运维+技术 |
举报应急 SOP(产品定案):
- 收到「品牌域打不开/被墙」→ 运维先验:
curl -sI Brand 是否仍 301、Body 是否为空。
- 若 Brand 本身污染 → 换新 Brand 指向同一 Gate(Site 不动)。
- 若仅 Site 被封 → 技术池内摘除,不动 Brand。
- 禁止「把业务面临时挂回 Brand 救急」—— 会重复 vty.vip 75% 问题并加速举报。
2.6.2 机器扫描 / 爬虫类场景
| 场景 |
攻击/探测方式 |
新方案应对 |
负责 |
| 搜索引擎爬虫 |
Baidusp/Googlebot 抓取 Brand/Gate |
Brand 301 无内容;Gate 对 已知 Bot IP 可配置 空页或仅 301(不调用 Resolve) |
运维 WAF + 技术 |
| 安全扫描器 |
Nessus/AWVS 扫路径 /login /api |
Brand 全路径 301;Gate 无业务路径;异常 IP 空页 |
运维 |
| 域名池撞库 |
脚本跟 302 批量收集 Site 域 |
Resolve 加密;异常 IP/Headless 不下发;响应 无域名列表 JSON |
技术 |
| Headless 批量访问 |
Puppeteer/Selenium 模拟 |
fp 检测 webdriver/HeadlessChrome → 空页 |
技术 |
| 机房 IP 批量 Resolve |
云主机刷 /gate/resolve |
IP 信誉库 + 30 次/min 限流 + nonce 去重 |
技术 |
| 中间人抓包 |
试图读明文域名 |
payload AES-256-GCM;HTTPS 全链路;App 证书 pinning(可选) |
技术+客户端 |
| Referer/链路分析 |
从 Brand 跟跳分析关联 |
Brand→Gate 301 不带 Site 域;Site 域仅加密包内出现 |
架构 |
| Share 短链被扫 |
批量请求 Share |
Share 限流;Resolve 鉴权;与 Entry 同源决策 |
技术 |
2.6.3 区域不可用 / 弱网(拉升 75%→88% 的关键)
| 场景 |
表现 |
应对 |
预期提升 |
| 单 Site 区域不可达 |
用户到 Gate 后卡死 |
3 域轮询 + 探活摘除死域 |
+8~12pp |
| 弱网超时 |
首域 3s 超时 |
自动试 d2/d3;Resolve 重试 3 次 |
+3~5pp |
| 省 DNS 污染 Brand |
品牌打不开 |
分省 备用 Brand + 多 Gate CNAME |
+5~8pp |
| Gate 边缘 5xx |
双检页加载失败 |
多 Gate 热备;CDN 健康检查 |
+2pp |
2.6.4 应对能力总览(评审一张表)
| 能力 |
解决什么问题 |
对应章节 |
| Brand 零内容 301 |
举报材料无业务、扫描无特征 |
§3.3 |
| Gate IP+指纹双检 |
机器不拿到域名池 |
§4.3 |
| Resolve 加密 3 域 |
撞库/嗅探难度高 |
§4.4~4.5 |
| 客户端轮询 |
单域 regional 失败 |
§4.6 |
| 探活自动摘除 |
死域不下发、快速切 |
§4.6 |
| H5/App 池合并 |
切换一致、减少误配 |
§4.2 |
| App 不经 Brand + 403 |
App 域不被浏览器刷 |
§2.3.10、§4.4.3 |
| 分省 Gate/Brand 热备 |
局部 DNS/阻断 |
§3.5 |
| entry_success 埋点 |
成功率 KPI 可验证 |
§1.6.5 |
2.8 安全审视:杜绝品牌域 / 主站域被标记危险或不可访问
本章定位: 从 安全 / 风控 / 合规 角度审视方案 漏洞与遗漏;最终目标:品牌域长期干净可访问,主站域 可轮换、可牺牲,不因连带导致 Brand 被标红或全网封禁。
评审入口 Tab: index.html →「安全」分册。
2.8.1 安全目标(North Star)
| 对象 |
目标 |
可接受代价 |
不可接受 |
| 品牌域 Brand |
7 日内 0 次因内容/举报导致失效;浏览器 无「危险网站」警告 |
换备用 Brand 物料 |
Brand 挂业务页、反代 Site、SAN 混配 |
| 落地域 Landing |
无业务特征;被扫仅见 bootstrap;可更换 FQDN |
301 改指向新 Landing |
Landing 出现 /login、钱包、赛事列表 |
| 主站域 Site |
单域可被标红/ regional 封;≤90s 摘除;Brand 不受影响 |
牺牲单域、换池内域 |
为救急把 Site 挂回 Brand |
| 用户感知 |
正常用户 ≥88% 进入(KPI);异常 IP 看不到 域名池 |
爬虫空页 |
明文下发 play-*.com 列表 |
【必须保护】 【可轮换牺牲】
Brand + Landing Site 主站域(池内 N 个)
零内容 · 无业务特征 业务承载 · 随时 disabled
2.8.2 威胁模型与攻击面
| # |
威胁 |
攻击路径 |
后果(若不防) |
方案覆盖 |
| T1 |
内容举报 |
用户/竞品提交 Brand URL + 业务截图 |
Brand 被渠道/浏览器标「危险」 |
Brand 301 零 Body |
| T2 |
监管/安全扫描 |
自动化扫 URL、路径字典 |
URL 入库、ISP 拦截 |
Brand/Gate 无 /login |
| T3 |
域名池撞库 |
脚本跟跳、暴力 resolve |
批量封 play-* |
加密 + 双检空页 |
| T4 |
关联分析 |
CT 日志、证书 SAN、Referer |
Brand 与 Site 关联连带 |
证书分离、Brand 不带 Site |
| T5 |
分享链暴露 |
Share 302 明文 primary |
主域被批量收集 |
Share 限流;primary 可换 |
| T6 |
直访 / 收藏 |
用户长期保存 play-a.com |
死域书签、主域被盯 |
可牺牲;Brand 兜底 |
| T7 |
App 硬编码 |
旧 App 写死 Brand/Site |
切域后仍访问死链 |
强更 + min_version |
| T8 |
密钥泄露 |
逆向 bootstrap 得 AES key |
离线解密域名池 |
混淆 + key 轮换 |
| T9 |
DNS 污染 |
区域 DNS 篡改 Brand |
Brand 打不开 |
多 Brand 分省 |
| T10 |
Landing/Gate 被举报 |
落地域被标红 |
301 链断 |
多 Landing 热备 |
| T11 |
Safe Browsing |
Google/360 威胁情报 |
浏览器大红屏 |
零内容 + 无恶意 JS |
| T12 |
中间人 / 劫持 |
运营商插入页面 |
假登录、Brand 污染 |
全链路 HTTPS、HSTS 评审 |
| T13 |
错误文案泄密 |
UI 暴露「域名池」「40301」 |
社工 + 特征识别 |
§4.10.4 禁止文案 |
| T14 |
运维误配 |
Brand CNAME 指 Site 桶 |
P0 事故 |
O-3 禁止 + 自动巡检 |
2.8.3 已覆盖能力评估
| 能力 |
强度 |
说明 |
| Brand 零内容 301 |
★★★★★ |
举报材料无法含业务截图(仅 301/空) |
| 落地域中间层 |
★★★★☆ |
判定失败不暴露主站;Landing 仍可能被扫 |
| IP+fp 双检 |
★★★★☆ |
挡机房/Headless;误杀需调阈值 |
| Resolve AES 加密 |
★★★★☆ |
提高撞库成本;非防逆向 |
| 主站池轮换 |
★★★★★ |
Site 死 Brand 活——核心设计 |
| 证书/SAN 分离 |
★★★★☆ |
需运维 强制执行 O-7 |
| 分享直达主站 |
★★☆☆☆ |
薄弱点:绕过落地双检 |
| 直访主站 |
★★☆☆☆ |
薄弱点:无 Gate 仍可达业务 |
| 多 Landing 热备 |
★★★☆☆ |
PRD 有述,需运维落地 |
| 浏览器 Safe Browsing 监测 |
★☆☆☆☆ |
遗漏:未写自动监测 |
2.8.4 漏洞与遗漏清单(须补齐)
| ID |
级别 |
问题 |
风险 |
建议加固 |
负责 |
| SEC-01 |
P0 |
Brand 误指向 Site 源站/桶 |
Brand 一次即可被标危险 |
每日自动 curl + Body 扫描;O-3 告警 |
运维 |
| SEC-02 |
P0 |
切流/应急把业务 挂回 Brand |
重复 vty.vip 悲剧 |
制度禁止 + 变更审批 |
产品+运维 |
| SEC-03 |
P0 |
落地页出现业务关键词/Logo 过大 |
扫描判定赌博页 |
bootstrap <2KB;无赛事/钱包文案 |
前端 |
| SEC-04 |
P1 |
Share 302 明文 primary 到主站 |
主域 URL 泄露、被批量举报 |
Share 也走 短期 token 或限 referer;高频换 primary |
技术 |
| SEC-05 |
P1 |
主站 直访 无 Gate,爬虫直达 Site |
Site 被扫加速封 |
robots 无效;靠 WAF 行为 + 可选 Landing 二次跳转(高价值市场) |
|
| SEC-06 |
P1 |
H5 AES 密钥在 bootstrap 可逆向 |
离线解密域名列表 |
白盒混淆 + 90d 轮换 + 服务端 nonce;App 证书 pinning(可选) |
安全+客户端 |
| SEC-07 |
P1 |
无 Google Safe Browsing / 360 威胁情报监控 |
标红后才发现 |
每日 API 查 Brand/Landing;P0 告警 |
运维+安全 |
| SEC-08 |
P1 |
Landing 单域 被举报全链断 |
用户经 Brand 进不来 |
≥2 Landing FQDN;Brand 301 可切 |
运维 |
| SEC-09 |
P1 |
错误页/Toast 中文赌博术语 |
特征匹配 |
仅用 §4.10 中性文案;i18n 同样约束 |
产品 |
| SEC-10 |
P2 |
Referer 从 Site 回 Landing 带主域 |
关联分析 |
Site API Referrer-Policy: strict-origin |
前端 |
| SEC-11 |
P2 |
历史 CT 证书 泄露 Brand-Site 同组织 |
被动关联 |
不同注册商/组织名申请证书 |
运维 |
| SEC-12 |
P2 |
App 分享带明文 Site 深链 |
域被传播 |
分享统一走 Share 短链 |
客户端 |
| SEC-13 |
P2 |
resolve 503 维护页托管在 Landing 且含品牌业务图 |
Landing 被标 |
维护页 纯文本、无 Logo |
设计 |
| SEC-14 |
P2 |
竞品 DDoS Brand 致「无法访问」 |
可用性非内容 |
边缘清洗;与标红区分 KPI |
运维 |
2.8.5 分层加固清单(执行项)
A. 品牌域(最高优先级 · 绝不能标危险)
| # |
措施 |
验收 |
| A1 |
全路径 仅 301 → 落地域;Body 0 字节 |
TC-B1~B5 + 日巡检 |
| A2 |
禁止 CNAME/A 记录指向 Site IP/桶 |
TC-P2 + SEC-01 脚本 |
| A3 |
证书 独立;SAN 不含 play-* |
证书审计 quarterly |
| A4 |
WAF:Brand 仅 GET/HEAD |
POST → 403 |
| A5 |
多 Brand 热备(分省物料) |
单 Brand 挂不影响全局 |
| A6 |
Safe Browsing / 360 日检 |
SEC-07 告警 |
| A7 |
应急 禁止 业务回挂 Brand(书面 SOP) |
变更工单强制字段 |
B. 落地域(中间层 · 可换不可脏)
| # |
措施 |
验收 |
| B1 |
仅 bootstrap + gate API;无业务路由 |
路径扫描 0 命中 |
| B2 |
风控失败 空页;resolve 失败 中性文案 |
§4.10.4 |
| B3 |
≥2 Landing 热备;Brand 301 可切换 |
演练 T+0 |
| B4 |
Bot IP 不调用 resolve |
TC-G2/G3 |
| B5 |
维护页 无业务视觉 |
设计评审 |
C. 主站域(可牺牲 · 与 Brand 隔离)
| # |
措施 |
验收 |
| C1 |
每域 独立 桶/CDN/证书 |
池表 channels |
| C2 |
探活 90s 摘除;Resolve 不再下发 |
TC-P1 |
| C3 |
FAILOVER 仅池内切换;不回 Brand |
TC-L4 |
| C4 |
单域被标红 → disabled + 换域;不动 Brand |
Runbook |
| C5 |
Share 302 目标 随 primary 变 |
TC-P3 |
| C6 |
物料/广告 不印刷 play-*(仅 Brand) |
市场规范 |
2.8.6 无法 100% 杜绝的情况(边界声明)
| 情况 |
说明 |
方案态度 |
| 国家级 深度封锁 Brand TLD |
超出产品能力 |
换 Brand TLD + 分省 |
| 人工向 Apple/Google 举报 App |
应用层非域名 |
独立 App 合规流程 |
| Site 内容本身违规被全球标红 |
业务合规问题 |
非域名架构能解 |
| 用户 截图 play-a.com 举报 |
无法阻止用户行为 |
牺牲该 Site,Brand 仍干净 |
| 0-day CDN/边缘 RCE 挂马 |
基础设施 |
WAF + 供应链安全 |
产品口径: 域名架构保证 Brand 举报材料无业务特征;不能保证 Site 永不被标红——保证 Site 可换、Brand 不连带。
2.8.7 安全验收用例(TC-SEC · P0)
| ID |
步骤 |
期望 |
| TC-SEC1 |
自动化扫 Brand / /login /api |
仅 301/空;0 业务关键词 |
| TC-SEC2 |
Headless resolve |
40302 + 空页;响应 无 domains |
| TC-SEC3 |
curl resolve 无 fp |
无 明文 play-* |
| TC-SEC4 |
检查 Brand 证书 SAN |
无 Site 域 |
| TC-SEC5 |
Safe Browsing 查 Brand |
无 威胁标记 |
| TC-SEC6 |
模拟「业务挂回 Brand」变更 |
工单 拒绝 / 告警 |
| TC-SEC7 |
Site disabled 后 curl Brand |
仍 301;Body 仍空 |
| TC-SEC8 |
错误页文案扫描 |
无「域名池」「赌博」等禁词 |
三、运维实施手册
运维职责一句话: 品牌域 只做 301,Gate/Site 分桶部署,禁止 品牌域指向业务源站;监控与应急按本章执行。
3.1 职责清单
| 编号 |
事项 |
说明 |
验收 |
| O-1 |
品牌域 DNS |
CNAME → Brand CDN(仅 301 规则) |
curl 仅 301 |
| O-2 |
301 规则 |
全路径 HTTPS → https://gate.xx/r/{brandId}?ts=&sig= |
Location 正确 |
| O-3 |
禁止绑 Site 桶 |
品牌域不得指向 Site IP/源站/对象存储 |
安全扫描 |
| O-4 |
Gate 桶/Worker |
托管 Gate 极简页(<2KB) |
双检页可加载 |
| O-5 |
Site 桶(每域) |
每 play-*.com 独立 CDN 桶 + 静态资源 |
站点可访问 |
| O-6 |
/__health |
每 Site 配置探活 URL,Cache-Control: no-store |
200 {"ok":true} |
| O-7 |
证书 |
Brand / Gate / Site 分别申请;30 天到期告警 |
日历 |
| O-8 |
WAF |
Brand:GET/HEAD;Gate:+POST resolve;Site:常规 |
POST Brand 拒绝 |
| O-9 |
监控告警 |
见 §3.4 |
钉钉/PagerDuty |
| O-10 |
应急 Runbook |
见 §3.5 |
季度演练 |
3.2 URL 桶 / CDN 配置
bucket-brand-winbet → 无文件;仅 301 → 落地域
bucket-landing-a → entry-a.vty.net:landing/index.html(Gate bootstrap <2KB)
bucket-landing-b → entry-b.vty.net:热备落地域
bucket-site-play-a → 【主站域】H5 构建产物 + __health.json
bucket-site-play-b → 同上
bucket-site-play-c → 同上
禁止: 落地域桶 不得 部署完整 Site 产物;主站域桶 不得 绑定 Brand 301。
Brand CDN 规则示例(逻辑):
server {
listen 443 ssl http2;
server_name winbet.com www.winbet.com;
location / {
return 301 https://entry-a.vty.net/r/winbet?ts=$msec&sig=$gate_sig;
}
}
sig 由边缘函数或配置中心下发,禁止在仓库写死可伪造密钥。
3.3 品牌域「零内容」规范
允许: DNS、TLS、301/308、必要安全响应头。
禁止: HTML/JS/CSS/图片、反代站点、下载页、客服页、业务 Set-Cookie、目录索引。
自检:
curl -sI https://winbet.com/
# 期望:301 + Location → Gate
curl -sL --max-redirs 0 https://winbet.com/ | wc -c
# 期望:Body 为 0 或极短
3.4 监控与告警
| 告警 |
条件 |
级别 |
接收 |
| 品牌域非 301 |
5min 内出现 200 含业务 body |
P0 |
运维 |
| 品牌 Body 非空 |
wc -c > 阈值 |
P0 |
运维 |
| 证书 30 天到期 |
— |
P1 |
运维 |
| DNS 漂移 |
解析非预期 CNAME |
P1 |
运维 |
| Gate 5xx 突增 |
— |
P1 |
运维+技术 |
| Site 探活失败 |
单域摘除事件 |
P1 |
运维+技术 |
| 可用 Site = 0 |
池内无 healthy 域 |
P0 |
全员 |
3.5 应急 Runbook
| 事件 |
运维动作 |
技术动作 |
| Site 单域被封 |
协助清理 DNS/缓存 |
自动/手动 disabled,切 primary |
| Site 全挂 |
不动 Brand |
维护页;补域/修活 |
| Gate 故障 |
Brand 301 切 备用 Gate Location |
修 Gate 服务 |
| 品牌域被投诉 |
保持 301+零内容;不挂业务 |
评估换 Entry/Gate |
| DDoS 品牌域 |
边缘清洗;保 301 可用 |
应用层限流 |
四、技术实施方案
技术职责一句话: Gate 双检 + 加密 Resolve + 统一域名池 + 探活 Worker + H5/App SDK 轮询 + App 签名校验。
4.1 研发工单清单
| 编号 |
模块 |
交付 |
优先级 |
| T-1 |
域名池合并 |
单表 + Admin + 数据迁移 |
P0 |
| T-2 |
Gate 风控 |
IP 分 + fp 分 + 空页 |
P0 |
| T-3 |
gate/resolve |
§4.4 接口 + 加密 |
P0 |
| T-4 |
app/resolve |
§4.4 + 403 非客户端 |
P0 |
| T-5 |
探活 Worker |
30s 周期,3 败 disabled |
P0 |
| T-6 |
H5 bootstrap |
解密 + 轮询 + 重试 |
P0 |
| T-7 |
App SDK |
签名 + Keychain + 轮询 |
P0 |
| T-8 |
Site API 网关 |
X-App-Sign 校验 |
P0 |
| T-9 |
告警 |
可用域阈值告警 |
P1 |
| T-10 |
埋点 |
hop 核销、切换事件 |
P1 |
| T-11 |
Share Resolve |
与 Resolve 同源 primary |
P1 |
| T-12 |
App bootstrap + 强更 |
§4.4.5 + §2.7 切流配置 |
P0 |
4.2 域名池合并(H5 + App)
现状
后台 H5 池、App 池 分开 → 切换不同步、双份探活、分享与 App 不一致。
目标表 site_domain_pool
| 字段 |
说明 |
domain |
FQDN,唯一 |
status |
active / standby / disabled / blocked |
health |
ok / fail / unknown |
channels |
h5 / app / pc / all(可多选) |
weight |
1–100 |
is_primary |
分享/默认主域 |
fail_streak |
连续探活失败次数 |
last_probe_at |
时间戳 |
operator |
审计 |
迁移: 双池导入 → 重复域合并 channels → 删旧 API /api/h5/domains、/api/app/domains → 统一 /api/v1/admin/domains。
Resolve 过滤:
WHERE status='active' AND health='ok'
AND (channels @> ARRAY['all'] OR channels @> ARRAY[:channel])
ORDER BY weight DESC LIMIT 3
4.3 风控:IP + 浏览器指纹(AND · 完整规则)
总规则(产品定案):
pass = (ip_verdict == NORMAL) AND (fp_verdict == NORMAL) AND (rate_limit_ok)
任一 FAIL → Gate 空页 或 Resolve 拒绝,不下发域名
判定顺序(研发按序短路):
- 黑名单 / 白名单(硬规则)
- 频率限流
- IP 评分
ip_score
- 浏览器指纹评分
fp_score(H5 必填;App 用 device 信号,见 §4.3.4)
- 通过 → 进入 Resolve
4.3.1 IP 异常规则(完整)
A. 硬拒绝(ip_verdict=BLOCK,不计算分数,直接空页)
| 编号 |
规则 ID |
条件 |
说明 |
| IP-H1 |
ip_blacklist |
IP 在运营/风控黑名单 |
配置中心维护 |
| IP-H2 |
ip_tor_exit |
命中 Tor 出口节点库 |
可选开启 |
| IP-H3 |
ip_known_scanner |
命中已知扫描器 IP 段(Censys、Shodan 等) |
威胁情报 |
| IP-H4 |
ip_empty_ua |
User-Agent 为空 且 非 App 签名请求 |
裸 curl |
| IP-H5 |
ip_geo_blocked |
国家/地区在 blocklist(配置项,默认可为空) |
与 allowlist 互斥优先 block |
B. 硬放行(仅内网/调试,生产默认关闭)
| 编号 |
规则 ID |
条件 |
| IP-W1 |
ip_whitelist |
IP 在白名单 → 跳过 IP 打分,仅做频率检查 |
C. IP 评分模型(ip_score:0~100,≥60 为 NORMAL)
初始分 100,逐项扣分,最低 0:
| 编号 |
规则 ID |
条件 |
扣分 |
数据来源 |
| IP-S1 |
ip_datacenter |
ASN 类型 = hosting/cloud/datacenter |
-35 |
IP 信誉库 |
| IP-S2 |
ip_proxy_vpn |
命中 VPN/代理/匿名代理 |
-25 |
信誉库 |
| IP-S3 |
ip_crawler_asn |
ASN 关联爬虫业务 |
-40 |
威胁情报 |
| IP-S4 |
ip_geo_not_allowed |
不在 allowlist 允许地区(若配置了 allowlist) |
-50 |
GeoIP |
| IP-S5 |
ip_recent_abuse |
24h 内同 IP Resolve 失败/403 累计 ≥10 |
-30 |
Redis |
| IP-S6 |
ip_rate_high |
同 IP >30 次/min 请求 Gate/Resolve |
-40 |
Redis |
| IP-S7 |
ip_rate_burst |
同 IP >5 次/10s |
-25 |
Redis |
| IP-S8 |
ip_multi_fp |
同 IP 10min 内关联 >20 个不同 fp_hash |
-35 |
Redis(撞库) |
| IP-S9 |
ip_header_anomaly |
缺少 Accept-Language 且 缺少 Accept(H5) |
-15 |
请求头 |
| IP-S10 |
ip_tls_fingerprint_bot |
JA3/JA4 命中已知 Bot 指纹(若边缘支持) |
-30 |
CDN |
判定:
| ip_score |
ip_verdict |
行为 |
| — |
BLOCK(硬规则) |
空页,code=40301,reason=规则 ID |
| 60~100 |
NORMAL |
可进入 fp 检测 |
| 40~59 |
SUSPICIOUS |
空页(v1 不做验证码降级;reason=ip_score_low) |
| 0~39 |
ABNORMAL |
空页,reason=ip_score_low |
配置项: IP_SCORE_PASS=60(可调);IP_RATE_MIN=30/min;IP_RATE_BURST=5/10s。
D. IP 检测伪代码(服务端)
def check_ip(req) -> IpResult:
ip = req.client_ip
if in_blacklist(ip): return BLOCK("ip_blacklist")
if in_whitelist(ip): return NORMAL(skip_score=True)
if rate_over(ip, "30/min") or rate_over(ip, "5/10s"):
return BLOCK("ip_rate") # 也可只扣分到 SUSPICIOUS
score = 100
for rule in IP_SCORE_RULES:
if rule.match(ip, req.headers):
score -= rule.penalty
score = max(0, score)
if score >= 60: return NORMAL(score=score)
return ABNORMAL(score=score, reason="ip_score_low")
4.3.2 浏览器指纹异常规则(H5 · 完整)
A. 采集时机与字段(Gate bootstrap 页)
页面加载 ≤800ms 内采集并提交;超时未提交视为 fp 异常。
| 字段 |
类型 |
必填 |
说明 |
canvas_hash |
string |
是 |
Canvas 2D 渲染哈希 |
webgl_vendor |
string |
是 |
WebGL UNMASKED_VENDOR |
webgl_renderer |
string |
否 |
渲染器 |
timezone |
string |
是 |
Intl 时区,如 Asia/Shanghai |
timezone_offset |
int |
是 |
getTimezoneOffset() |
languages |
string[] |
是 |
navigator.languages 前 3 项 |
screen |
string |
是 |
{w}x{h}x{depth} |
color_depth |
int |
是 |
|
device_memory |
number |
否 |
有则采 |
hardware_concurrency |
int |
否 |
CPU 核数 |
touch |
int |
是 |
maxTouchPoints |
platform |
string |
是 |
navigator.platform |
ua |
string |
是 |
仅辅助,不单独作为通过依据 |
webdriver |
bool |
是 |
navigator.webdriver |
plugins_length |
int |
否 |
|
cookie_enabled |
bool |
是 |
|
local_storage |
bool |
是 |
是否可用 |
session_id |
string |
是 |
本次 Gate 页 UUID |
提交格式:
{
"fp_hash": "sha256(canonical_json_signals + FP_SALT)",
"fp_signals": "base64(aes_compress(signals))",
"session_id": "uuid",
"collect_ms": 320
}
canonical_json:字段名 ASCII 排序后 JSON 序列化,确保同设备 hash 稳定。
B. 硬拒绝(fp_verdict=BLOCK)
| 编号 |
规则 ID |
条件 |
| FP-H1 |
fp_missing |
无 fp_hash 或 collect_ms 超时(>3000ms 未提交) |
| FP-H2 |
fp_webdriver |
webdriver === true |
| FP-H3 |
fp_headless_ua |
UA 匹配 `/HeadlessChrome |
| FP-H4 |
fp_zero_screen |
screen 为 0x0 或缺失 |
| FP-H5 |
fp_selenium |
document.documentElement.getAttribute("webdriver") 或 window 存在 __webdriver_* / _Selenium_* / callPhantom |
| FP-H6 |
fp_canvas_empty |
canvas_hash 为全零或已知 Headless 常量库命中 |
| FP-H7 |
fp_rate |
同 fp_hash >50 次/min 请求 Resolve |
| FP-H8 |
fp_blocklist |
fp_hash 在风控黑名单 |
C. 指纹评分模型(fp_score:0~100,≥60 为 NORMAL)
初始分 100:
| 编号 |
规则 ID |
条件 |
扣分 |
| FP-S1 |
fp_lang_tz_mismatch |
时区为 UTC 且 languages 含 zh-CN(常见 Headless 配置错误) |
-20 |
| FP-S2 |
fp_no_touch_desktop_ua |
UA 含 Mobile 但 maxTouchPoints=0 |
-15 |
| FP-S3 |
fp_plugins_zero |
plugins_length=0 且 非 iOS Safari |
-10 |
| FP-S4 |
fp_storage_disabled |
cookie 与 localStorage 均 false |
-25 |
| FP-S5 |
fp_webgl_software |
renderer 含 `SwiftShader |
llvmpipe |
| FP-S6 |
fp_screen_rare |
分辨率在已知 Bot 常见表(如 800×600、1×1) |
-20 |
| FP-S7 |
fp_ua_chrome_no_chrome_obj |
UA 含 Chrome 但无 window.chrome(部分 Headless) |
-25 |
| FP-S8 |
fp_inconsistent_platform |
iPhone UA 但 platform=Win32 等明显矛盾 |
-40 |
| FP-S9 |
fp_repeat_session |
同 session_id 重复 Resolve |
-50(重放) |
| FP-S10 |
fp_new_device_burst |
同 IP 1h 内首次出现的新 fp_hash >100 个 |
-30(刷量) |
判定:
| fp_score |
fp_verdict |
行为 |
| BLOCK(硬规则) |
— |
空页,code=40302,reason=规则 ID |
| 60~100 |
NORMAL |
可调 Resolve |
| 40~59 |
SUSPICIOUS |
空页(reason=fp_score_low) |
| 0~39 |
ABNORMAL |
空页 |
配置项: FP_SCORE_PASS=60;FP_COLLECT_TIMEOUT_MS=3000。
D. 双检合并决策表
| ip_verdict |
fp_verdict |
最终结果 |
用户看到 |
Resolve |
| NORMAL |
NORMAL |
PASS |
bootstrap 继续 |
加密 3 域 |
| BLOCK/ABNORMAL/SUSPICIOUS |
* |
FAIL |
空页 |
不调用 |
| NORMAL |
BLOCK/ABNORMAL/SUSPICIOUS |
FAIL |
空页 |
不调用 |
| NORMAL |
NORMAL 但 rate_limit 失败 |
FAIL |
空页/429 |
不调用 |
响应码映射:
| reason |
code |
日志字段 |
| ip 硬规则 |
40301 |
risk_ip, rule_id |
| ip 低分 |
40301 |
ip_score, value |
| fp 硬规则 |
40302 |
risk_fp, rule_id |
| fp 低分 |
40302 |
fp_score, value |
| 频率 |
429 |
rate_limit |
4.3.3 已知爬虫 / Bot 处理策略
| Bot 类型 |
识别方式 |
处理 |
| 搜索引擎(Baiduspider 等) |
UA + IP 段验证(可选 DNS 反查) |
空页 或 仅 301 不回 Resolve;不下发域名 |
| 安全扫描器 |
IP-S3 + 路径探测 |
空页 + WAF 封禁 |
| Headless 自动化 |
FP-H2~H6 |
空页 |
| 机房脚本 |
IP-S1 + FP 缺失 |
空页 |
| 正常用户移动浏览器 |
ip≥60 且 fp≥60 |
正常 Resolve |
4.3.4 App 端风控差异(不经 Gate 页)
App 无浏览器 Canvas,改用 设备信号 + 签名:
| 信号 |
异常规则 |
无 X-App-Sign 或签名错误 |
403(硬拒绝) |
device_id 为空或格式非法 |
403 |
同 device_id >20 次/min resolve |
429 |
同 IP 无签名浏览器访问 app/resolve |
403 |
| 越狱/Root 检测(可选) |
仅告警,不单独拦截(避免误杀) |
| IP 规则 |
与 H5 相同 IP 评分模型(§4.3.1) |
App 不采 fp_score 时:pass = ip_verdict == NORMAL && app_sign_valid && app_version_ok。
版本校验(与 H5 差异 · 切流必开):
| 规则 |
条件 |
响应 |
| APP-V1 |
semver(X-App-Version) < min_version |
40310 force_update |
| APP-V2 |
全局 force_update=true 且版本 < latest_version |
40310(切流窗口) |
| APP-V3 |
brand_pack_version 本地 < required_brand_pack |
40310(皮肤大改版) |
| APP-V4 |
可选:latest_version 高于当前 |
bootstrap 返回 optional_update,不拦 resolve |
4.3.5 空页规范(异常时必须满足)
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Cache-Control: no-store
禁止: 302 到 Site、JSON 带域名、错误文案暴露「域名池」。
4.3.6 IP 如何收集与如何判断(服务端 · 技术直接实现)
要点: IP 仅服务端从连接/边缘头获取,不信任客户端上报。
收集(Request → client_ip)
| 优先级 |
来源 |
说明 |
| 1 |
CF-Connecting-IP |
Cloudflare |
| 2 |
X-Real-IP |
Nginx/网关 |
| 3 |
X-Forwarded-For 第一个公网 IP |
需网关 strip 伪造 |
| 4 |
socket.remoteAddress |
直连兜底 |
实现清单:
function getClientIp(req) {
const cf = req.headers['cf-connecting-ip'];
if (cf) return cf.trim();
const real = req.headers['x-real-ip'];
if (real) return real.trim();
const xff = req.headers['x-forwarded-for'];
if (xff) return xff.split(',')[0].trim();
return req.socket.remoteAddress;
}
enrich(ipIntel.lookup)
| 字段 |
来源 |
用途 |
asn / asnType |
GeoIP/ASN DB |
IP-S1 datacenter |
isProxyOrVpn |
商业 IP 库 |
IP-S2 |
isTorExit |
Tor 列表 |
IP-H2 |
isKnownScanner |
威胁情报 |
IP-H3 |
country / region |
GeoIP |
IP-H5 / IP-S4 |
ja3Bot |
CDN Bot 管理 |
IP-S10 |
Redis 键(频率与撞库)
| Key |
TTL |
用途 |
ip:{ip}:m1 |
60s |
IP-S6 每分钟计数 |
ip:{ip}:10s |
10s |
IP-S7 _burst |
ip:{ip}:fp10m |
600s SET |
IP-S8 不同 fp_hash |
ip:{ip}:abuse24h |
86400s |
IP-S5 失败累计 |
判断输出(写入 Resolve 日志)
{
"ip": "1.2.3.4",
"ip_score": 72,
"ip_verdict": "NORMAL",
"ip_rules_hit": ["IP-S9"],
"asn": "AS4134",
"country": "CN"
}
参考代码: 仓库 js/gate-ip-check.reference.js(checkIp / gateAllowResolve)。
4.3.7 浏览器指纹如何收集与如何判断
A. 收集(Gate bootstrap 页 · 客户端)
| 步骤 |
动作 |
时限 |
| 1 |
用户 301 进入 Gate /r/{brand} |
— |
| 2 |
加载 bootstrap(<2KB)+ gate-fp-collect.js |
— |
| 3 |
VtyGateFp.collectFingerprint() |
≤800ms 目标 |
| 4 |
POST /api/v1/gate/resolve 带 fp_hash + 可选加密 fp_signals |
5s 总超时 |
| 5 |
服务端 checkFingerprint() 打分 |
— |
采集字段与代码: 见 §4.3.2 A 表;参考 js/gate-fp-collect.js。
// Gate 页主流程(前端必须实现)
VtyGateFp.collectFingerprint().then(function (fp) {
if (!VtyGateFp.clientPrecheck(fp).ok) {
document.documentElement.innerHTML = ''; // 本地预检失败也空页
return;
}
return fetch('/api/v1/gate/resolve', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
brand_id: 'winbet',
channel: 'h5',
fp_hash: fp.fp_hash,
session_id: fp.session_id,
collect_ms: fp.collect_ms,
ts: Math.floor(Date.now() / 1000),
nonce: crypto.randomUUID(),
sig: signResolve(...),
}),
});
});
B. 判断(服务端 · 硬规则优先于评分)
if FP-H1~H8 任一命中 → fp_verdict=BLOCK, code=40302
else 计算 fp_score → ≥60 NORMAL / 否则 SUSPICIOUS → 空页
if ip_verdict != NORMAL → 不执行 fp,直接空页
C. 服务端 fp 日志示例
{
"fp_hash": "a1b2...",
"fp_score": 85,
"fp_verdict": "NORMAL",
"fp_rules_hit": [],
"collect_ms": 320,
"session_id": "uuid"
}
D. 运维/客服说明
| 对用户 |
对客服 |
| 指纹/IP 失败 无任何提示 |
话术:「请换浏览器/关 VPN/换 4G 再试」,不解释指纹 |
4.4 接口契约
4.4.1 何时返回域名(决策表)
| # |
条件 |
结果 |
HTTP |
| 1 |
IP 异常 |
不返回 |
200 code=40301 或 Gate 空页 |
| 2 |
指纹异常(H5) |
不返回 |
code=40302 |
| 3 |
App 签名无效 |
不返回 |
403 |
| 3a |
App 版本 < min_version |
不返回 |
40310 force_update |
| 3b |
force_update=true 且版本不在 allowlist |
不返回 |
40310 |
| 4 |
频率超限 |
不返回 |
429 |
| 5 |
brand 禁用 |
不返回 |
404 |
| 6 |
healthy 域 = 0 |
不返回 |
503 code=50301 |
| 7 |
healthy 1~2 域 |
返回 1~2 条加密 |
200 code=0 |
| 8 |
healthy ≥ 3 域 |
返回 3 条加密 |
200 code=0 |
4.4.2 POST /api/v1/gate/resolve(H5)
Request
{
"brand_id": "winbet",
"channel": "h5",
"fp_hash": "a1b2...",
"ts": 1735689600,
"nonce": "uuid",
"sig": "hmac(brand_id|ts|nonce|fp_hash, gate_secret)"
}
Response 成功
{
"code": 0,
"data": {
"key_id": "k2026-03",
"expires_at": 1735689900,
"ttl_sec": 300,
"payload": "<BASE64 AES-256-GCM>"
}
}
payload 明文(加密前)
{
"v": 1,
"domains": ["play-a.com", "play-b.com", "play-c.com"],
"primary": "play-a.com",
"hop_id": "hop_8f3k...",
"brand_id": "winbet"
}
4.4.3 POST /api/v1/app/resolve(App)
Headers(缺一则 403)
X-Client-Type: app
X-App-Id: com.vty.app
X-App-Version: 3.2.0
X-Timestamp: 1735689600
X-Nonce: uuid
X-App-Sign: HMAC-SHA256(app_secret, method|path|timestamp|nonce|body_sha256)
Body
{
"channel": "app",
"platform": "ios",
"device_id": "哈希",
"fp_hash": "...",
"bundle_id": "com.vty.app"
}
| 请求特征 |
响应 |
| 无 X-App-Sign |
403 |
| 签名错误 |
403 |
| UA 为浏览器/curl |
403 |
X-App-Version < min_version |
40310 force_update |
切流日 force_update=true |
40310 |
40310 响应体:
{
"code": 40310,
"message": "force_update",
"data": {
"min_version": "4.0.0",
"latest_version": "4.0.2",
"update_url": {
"ios": "https://apps.apple.com/app/idXXXX",
"android": "https://download.example/app-v4.apk"
},
"brand_pack_version": "2026-brand-v2",
"title_key": "app.force_update_title",
"body_key": "app.force_update_body"
}
}
4.4.5 GET /api/v1/app/bootstrap(App · 冷启动第一请求)
调用时机: App 启动 Splash 阶段,早于 Keychain 读域与 app/resolve。切流日配置在此接口生效。
Headers
X-Client-Type: app
X-App-Id: com.vty.app
X-App-Version: 4.0.0
X-Platform: ios
X-App-Sign: HMAC-SHA256(...) # 与 resolve 同算法,path=/api/v1/app/bootstrap
Response 成功(可继续 resolve)
{
"code": 0,
"data": {
"min_version": "4.0.0",
"latest_version": "4.0.2",
"force_update": true,
"optional_update": false,
"brand_pack_version": "2026-brand-v2",
"required_brand_pack": "2026-brand-v2",
"official_brand_url": "https://winbet.com",
"update_url": {
"ios": "https://apps.apple.com/app/idXXXX",
"android": "https://download.example/app-v4.apk"
},
"resolve_enabled": true,
"cutover_bundle": "2026-brand-v2"
}
}
Response 强制更新(客户端不得继续 resolve)
{
"code": 40310,
"message": "force_update",
"data": { "...同上 update_url..." }
}
服务端判定顺序
1. 签名校验 → 403
2. semver(AppVersion) < min_version → 40310
3. force_update=true 且 AppVersion < latest_version → 40310
4. brand_pack 本地版本 < required_brand_pack → 40310
5. 通过 → code=0
配置中心键(切流 T 日一键改)
| 键 |
示例 |
说明 |
app.min_version.ios |
4.0.0 |
低于此 永久 40310 |
app.min_version.android |
4.0.0 |
同上 |
app.force_update |
true |
切流窗口;T+7 可改 false |
app.latest_version.* |
4.0.2 |
配合 force_update |
app.brand_pack.required |
2026-brand-v2 |
皮肤 bundle 版本 |
app.cutover_bundle |
2026-brand-v2 |
审计标识 |
客户端实现要点
1. Splash → bootstrap(超时 5s,失败重试 2 次)
2. code=40310 → 全屏 ForceUpdateActivity(禁返回键 / 禁侧滑)
3. code=0 → 比对 brand_pack_version,加载包内或 CDN 主题资源
4. 再进入 §2.3.10 A1~A5
5. resolve 若仍返回 40310 → 同全屏强更(双保险)
4.4.4 Site 健康检查
HEAD https://{site}/__health
→ 200 {"ok":true,"ts":1735689600}
4.5 加密方案
| 项 |
规格 |
| 算法 |
AES-256-GCM |
| 密钥 |
配置中心 key_id → key,90 天轮换 |
| 客户端 |
H5:bootstrap 内嵌(混淆);App:内置可热更新 |
| 防重放 |
expires_at + nonce Redis 去重 5min |
4.6 客户端轮询与域名切换(完整方案)
目标: 单域 regional 失败时 ≤3s 自动切下一域;全失败 ≤10s 内重拉 Resolve;运行期故障 无感切换。
4.6.1 名词与本地缓存
| 名词 |
说明 |
domain_list |
Resolve 解密后的有序数组,最多 3 个 FQDN |
primary |
列表首选域(与 domain_list[0] 一致,除非服务端另有指定) |
current_domain |
当前正在使用的 Site 基址 |
resolve_meta |
{ payload, expires_at, hop_id, key_id, resolved_at } |
probe_cache |
每域最近探活结果 { domain, ok, latency_ms, ts },TTL 60s |
持久化(H5): sessionStorage(优先)+ localStorage 备份;键名 vty_ds_v1。
持久化(App): Keychain / EncryptedSharedPreferences;键名 vty.baseUrl.v1。
4.6.2 状态机
┌─────────────┐
│ INIT │
└──────┬──────┘
│ 有未过期 resolve_meta?
┌────────────┼────────────┐
▼ yes ▼ no
┌────────────────┐ ┌────────────────┐
│ LOAD_CACHED │ │ FETCH_RESOLVE │
└───────┬────────┘ └───────┬────────┘
│ │
└──────────┬──────────────┘
▼
┌────────────────┐
│ PROBE_LIST │ 按序探活 domain_list
└───────┬────────┘
│ 有 ok 域?
┌────────────┼────────────┐
▼ yes ▼ no
┌─────────────────┐ ┌─────────────────┐
│ ENTER_SITE │ │ RETRY_RESOLVE │ 最多 3 轮
└────────┬────────┘ └────────┬────────┘
│ │ 仍失败
│ ▼
│ ┌─────────────────┐
│ │ MAINTENANCE_UI │
│ └─────────────────┘
▼
┌─────────────────┐
│ RUNNING │ 业务运行 + 后台探活
└────────┬────────┘
│ 当前域失败
▼
┌─────────────────┐
│ FAILOVER │ 切列表下一域 或 RETRY_RESOLVE
└─────────────────┘
4.6.3 阶段一:冷启动 / 品牌进入(H5)
| 步骤 |
动作 |
超时 |
失败处理 |
| 1 |
Gate 页采集 fp → 调 gate/resolve |
5s |
空页或维护 |
| 2 |
AES 解密 payload → 得 domain_list |
500ms |
重试 resolve 1 次 |
| 3 |
写入 resolve_meta(含 expires_at) |
— |
— |
| 4 |
PROBE_LIST(§4.6.4) |
9s 总预算 |
RETRY_RESOLVE |
| 5 |
location.replace(https://{current}/?hop=...) |
— |
— |
| 6 |
上报 entry_success / entry_fail |
— |
— |
Resolve 重试(RETRY_RESOLVE): 最大 3 轮 · 间隔 2s · 仍失败 → MAINTENANCE_UI。
4.6.4 PROBE_LIST:域名探活(H5 / App 共用)
对 domain_list 按服务端返回顺序依次探活:
for i, domain in enumerate(domain_list):
result = probe(domain) # 单次 ≤3s,不重试
if result.ok:
current_domain = domain
persist(...)
return SUCCESS
return FAIL
| 项 |
规格 |
| 方法 |
HEAD https://{domain}/__health(失败 fallback GET) |
| 单次超时 |
3s(连接+读合计) |
| 成功 |
HTTP 200 且 ok===true |
| 失败 |
超时、DNS 失败、4xx/5xx、证书错误 |
| 域间等待 |
0(立即下一域) |
| 缓存 |
RUNNING 阶段 60s 内成功可跳过 HEAD |
4.6.5 阶段二:运行期 FAILOVER
触发(任一): 当前域 API 连续 3 次失败 · /__health 2 次失败(30s 间隔)· 主接口超时 >5s 且重试仍失败 · WebView onReceivedError。
流程: 标记 local_bad → probe 列表下一域 → 成功则切换并上报 domain_switch → 无 ok 域则 RETRY_RESOLVE(2 轮)→ 仍失败 MAINTENANCE_UI。
后台探活: 每 5min HEAD 当前域;App/H5 回前台 立即探活 1 次。
4.6.6 App 冷启动差异
先读 Keychain current_domain → probe 1 次;成功且 meta 未过期则 跳过 resolve;否则 app/resolve + PROBE_LIST。expires 默认 300s,过期前 60s 后台静默 refresh。
4.6.7 服务端探活 Worker
| 项 |
规格 |
| 周期 |
30s |
| 成功 |
200 且 <3s;fail_streak=0 |
| 摘除 |
连续 3 次失败 → disabled + P1 告警 |
| 恢复 |
需 人工确认 回 active |
| primary 摘除 |
自动升 weight 最高 healthy 域 |
4.6.8 切换场景速查
| 场景 |
服务端 |
客户端 |
| 单 Site regional 慢 |
health 可能仍 ok |
PROBE_LIST 下一域 |
| 单 Site 宕机 |
90s disabled |
FAILOVER 或下次 Resolve 剔除 |
| 3 Site 全挂 |
503 |
MAINTENANCE_UI |
| 弱网 |
— |
3 域 + Resolve 3 轮 |
4.6.9 SDK 配置常量(实现必须对齐)
const CFG = {
probeTimeoutMs: 3000,
probeListBudgetMs: 9000,
resolveMaxRounds: 3,
resolveBackoffMs: 2000,
runningProbeIntervalMs: 300000,
apiFailThreshold: 3,
healthFailThreshold: 2,
resolveTtlSec: 300,
resolveRefreshBeforeExpirySec: 60,
};
4.6.10 轮询埋点(必填)
| 事件 |
字段 |
domain_probe |
try_order, domain, ok, latency_ms, err_code |
resolve_fetch |
round, code, domain_count, latency_ms |
domain_switch |
from_domain, to_domain, reason |
entry_success |
site_domain, total_ms, probe_tries, resolve_rounds |
entry_fail |
fail_stage: resolve/probe/enter, reason |
| 触发 |
动作 |
| 服务端 3 败 |
auto disabled,Resolve 剔除 |
| 客户端当前域挂 |
FAILOVER 下一域 |
| 列表全挂 |
RETRY_RESOLVE |
| 人工 blocked |
立即剔除 |
4.10 前端异常提示规范(完整文案 · 技术/UI 直接实现)
截图 Demo: demos/h5-error-states.html(430px 首页/登录 · 全部态)
4.10.1 UI 形态定案
| 形态 |
代码 |
用于 |
是否挡操作 |
| 空页 |
EMPTY |
Gate IP/指纹/爬虫 |
— |
| 顶栏 Loading |
TOP_LOADING |
Resolve + PROBE_LIST |
半透明,无取消按钮 |
| Toast |
TOAST |
单域 probe 失败、FAILOVER 成功 |
不挡,3s 消失 |
| 全屏蒙层 |
FULL_BLOCK |
Resolve 全失败、解密失败、429 |
挡,需点按钮 |
4.10.2 文案表(i18n key · zh-CN / vi-VN 定稿)
| ID |
触发条件 |
UI |
titleKey |
bodyKey |
primaryBtn |
secondaryBtn |
| E-GATE-01 |
ip/fp BLOCK,40301/40302 |
EMPTY |
— |
— |
— |
— |
| E-LOAD-01 |
开始 resolve 至 probe 结束 |
TOP_LOADING |
— |
gate.connecting |
— |
— |
| E-PROBE-01 |
probe[i] 失败且 i+1 存在 |
TOAST |
— |
gate.switching_line |
— |
— |
| E-NET-01 |
FAILOVER 切换成功 |
TOAST |
— |
gate.switched_line |
— |
— |
| E-RESOLVE-01 |
50301 或 3 轮 resolve 失败 |
FULL_BLOCK |
gate.unavailable_title |
gate.unavailable_body |
gate.retry |
gate.contact |
| E-DECRYPT-01 |
AES 解密/JSON 校验失败 |
FULL_BLOCK |
gate.load_fail_title |
gate.load_fail_body |
gate.refresh |
— |
| E-RATE-01 |
HTTP 429 |
FULL_BLOCK |
gate.rate_limit_title |
gate.rate_limit_body |
gate.retry_later |
— |
| E-APP-FORCE-01 |
bootstrap/resolve 40310 |
FULL_BLOCK(App 全屏) |
app.force_update_title |
app.force_update_body |
app.go_store |
— |
| E-SSL-01 |
探活证书错误(probe) |
TOAST |
— |
gate.switching_line |
— |
— |
语言包路径约定: locales/zh-CN/gate.json、locales/vi-VN/gate.json、locales/{lang}/app.json
切换规则: Accept-Language → 用户设置 → 默认 zh-CN;key 完全一致,仅 value 不同。
zh-CN(简体中文)
{
"gate.connecting": "正在连接最佳线路…",
"gate.switching_line": "当前线路响应较慢,正在为您切换备用线路…",
"gate.switched_line": "网络异常,已为您切换至备用线路",
"gate.unavailable_title": "暂时无法连接",
"gate.unavailable_body": "网络或服务繁忙,请稍后重试。若持续无法访问,请联系在线客服。",
"gate.retry": "重新连接",
"gate.contact": "联系客服",
"gate.load_fail_title": "加载失败",
"gate.load_fail_body": "页面数据异常,请刷新重试。如问题仍存在,请清除浏览器缓存后再试。",
"gate.refresh": "刷新重试",
"gate.rate_limit_title": "访问过于频繁",
"gate.rate_limit_body": "请 1 分钟后再试。",
"gate.retry_later": "稍后重试",
"app.force_update_title": "请更新至最新版本",
"app.force_update_body": "品牌服务已升级,当前版本无法继续使用。请更新 App 后重新打开。",
"app.go_store": "立即更新"
}
vi-VN(Tiếng Việt · 越南语)
{
"gate.connecting": "Đang kết nối tuyến tốt nhất…",
"gate.switching_line": "Tuyến hiện tại phản hồi chậm, đang chuyển sang tuyến dự phòng…",
"gate.switched_line": "Mạng bất thường, đã chuyển sang tuyến dự phòng",
"gate.unavailable_title": "Tạm thời không thể kết nối",
"gate.unavailable_body": "Mạng hoặc dịch vụ đang bận, vui lòng thử lại sau. Nếu vẫn không truy cập được, hãy liên hệ CSKH trực tuyến.",
"gate.retry": "Kết nối lại",
"gate.contact": "Liên hệ CSKH",
"gate.load_fail_title": "Tải thất bại",
"gate.load_fail_body": "Dữ liệu trang bất thường, vui lòng làm mới và thử lại. Nếu vẫn lỗi, hãy xóa bộ nhớ đệm trình duyệt rồi thử lại.",
"gate.refresh": "Làm mới và thử lại",
"gate.rate_limit_title": "Truy cập quá thường xuyên",
"gate.rate_limit_body": "Vui lòng thử lại sau 1 phút.",
"gate.retry_later": "Thử lại sau",
"app.force_update_title": "Vui lòng cập nhật phiên bản mới nhất",
"app.force_update_body": "Dịch vụ thương hiệu đã nâng cấp, phiên bản hiện tại không thể tiếp tục sử dụng. Vui lòng cập nhật App rồi mở lại.",
"app.go_store": "Cập nhật ngay"
}
中越对照速查(供 UI 走查)
| i18n key |
zh-CN |
vi-VN |
gate.connecting |
正在连接最佳线路… |
Đang kết nối tuyến tốt nhất… |
gate.switching_line |
当前线路响应较慢… |
Tuyến hiện tại phản hồi chậm… |
gate.switched_line |
网络异常,已为您切换… |
Mạng bất thường, đã chuyển sang tuyến dự phòng |
gate.unavailable_title |
暂时无法连接 |
Tạm thời không thể kết nối |
gate.unavailable_body |
网络或服务繁忙… |
Mạng hoặc dịch vụ đang bận… |
gate.retry |
重新连接 |
Kết nối lại |
gate.contact |
联系客服 |
Liên hệ CSKH |
gate.load_fail_title |
加载失败 |
Tải thất bại |
gate.refresh |
刷新重试 |
Làm mới và thử lại |
gate.rate_limit_title |
访问过于频繁 |
Truy cập quá thường xuyên |
app.force_update_title |
请更新至最新版本 |
Vui lòng cập nhật phiên bản mới nhất |
app.go_store |
立即更新 |
Cập nhật ngay |
E-GATE-01 中越一致:空页,无任何用户文案。
进站后: 地址栏 = 主站域;业务 API 基址 = https://{current_domain}。
4.10.5 报错归属域规则(落地域 vs 主站域)
| 文案 ID |
触发阶段 |
报错归属域 |
地址栏示例 |
备注 |
| E-GATE-01 |
落地·双检失败 |
落地域 |
entry.vty.net |
空页,无业务壳 |
| E-LOAD-01 |
落地·resolve/probe |
落地域 |
entry.vty.net |
可用登录/首页 骨架(非真业务) |
| E-PROBE-01 |
落地·probe 切换 |
落地域 |
entry.vty.net |
Toast |
| E-RESOLVE-01 |
落地·全失败 |
落地域 |
entry.vty.net |
全屏;不得链到 play-*.com |
| E-DECRYPT-01 |
落地·解密失败 |
落地域 |
entry.vty.net |
全屏 |
| E-NET-01 |
**主站·**FAILOVER |
主站域 |
play-a.com |
Toast |
| E-RESOLVE-01(重连) |
主站·列表耗尽 |
主站域 |
play-a.com |
用户已进站后的极端失败 |
| 业务 API 错误 |
主站·运行期 |
主站域 |
play-a.com |
常规业务错误码 |
实现约束:
// 落地域 bootstrap 内
const ERROR_DOMAIN = 'landing'; // location.hostname === LANDING_FQDN
function showError(code) {
if (getPhase() === 'landing') {
// 判定失败:不 location 到 play-*.com
renderOnLandingShell(code);
return;
}
renderOnSiteShell(code); // 主站域报错
}
function onProbeSuccess(siteFqdn) {
location.replace('https://' + siteFqdn + '/?hop=' + hopId);
// 此后 phase = 'site'
}
4.10.3 展示页面规则
| 当前路由 |
使用的壳 |
说明 |
| 品牌→落地域→尚未进主站 |
落地域 bootstrap + 骨架壳 |
Loading/全屏 必须在落地域 hostname |
| 已进主站 首页/登录 |
主站域 完整壳 |
FAILOVER 用 Toast |
| 落地·风控硬失败 |
落地域 空页 |
不渲染 VTY 业务壳 |
4.10.4 禁止文案(安全)
- 不得出现:「IP 被封」「指纹异常」「域名池」「40301」
- 不得出现具体 Site 域名
- Gate 失败不得用「访问受限」全屏(仅允许空页)
4.11 轮询过程 UI 与提示(逐步映射)
| 轮询阶段 |
SDK 状态 |
用户看到 |
文案 ID |
最长耗时 |
| 调 resolve |
FETCH_RESOLVE |
顶栏 Loading |
E-LOAD-01 |
5s |
| 解密 payload |
FETCH_RESOLVE |
同上 |
E-LOAD-01 |
+0.5s |
| probe d1 |
PROBE_LIST |
Loading;d1 失败则 Toast |
E-LOAD-01 → E-PROBE-01 |
3s |
| probe d2 |
PROBE_LIST |
Toast(若 d1 失败) |
E-PROBE-01 |
+3s |
| probe d3 |
PROBE_LIST |
Toast(若 d2 失败) |
E-PROBE-01 |
+3s |
| 全部 probe 失败 |
RETRY_RESOLVE |
Loading |
E-LOAD-01 |
2s×3 轮 |
| 3 轮仍失败 |
MAINTENANCE |
全屏 |
E-RESOLVE-01 |
— |
| 进入 Site |
ENTER_SITE |
Loading 消失 |
— |
— |
| 运行期 API 失败 |
FAILOVER |
Toast |
E-NET-01 |
≤6s |
| FAILOVER 仍失败 |
RETRY_RESOLVE → 全屏 |
E-RESOLVE-01 |
— |
|
「重新连接」: 清空 resolve_meta + local bad → INIT 重跑。
「联系客服」: 跳转 Site 客服路由或配置中心备用外链。
4.7 分享 Resolve
GET /api/v1/resolve/current-site(鉴权)→ 返回与 Resolve 同一 is_primary。
- 用户短链
GET /{code} → 302 当前 Site。
4.8 埋点字段
| 字段 |
说明 |
entry_type |
brand / share / direct / app |
brand_domain |
如 winbet.com |
hop_id |
去重 |
site_domain |
落地域 |
resolve_version |
池版本 |
domain_switch |
是否发生客户端切换 |
4.9 分期排期
| 阶段 |
范围 |
周期 |
| Phase A |
池合并 + Resolve + 探活 + Admin |
2 周 |
| Phase B |
Gate 双检 + 加密 + H5 bootstrap |
1.5 周 |
| Phase C |
App resolve + 签名网关 + SDK |
1.5 周 |
| Phase D |
告警 + 埋点 + Share + 压测 |
1 周 |
五、测试方案
测试职责一句话: 按 P0 用例验收 品牌零内容、双检空页、加密 3 域、轮询切换、App 403、池合并一致;P0 全过方可上线。
5.1 测试策略
| 层级 |
范围 |
工具 |
| 边缘/运维 |
Brand 301、Body 为空、证书 |
curl、监控脚本 |
| 接口 |
Resolve 决策表、加密解密 |
Postman、自动化 |
| 风控 |
IP/指纹/mock Headless |
代理 IP、Playwright |
| 端到端 |
品牌→Gate→Site 全链路 |
Playwright、真机 |
| App |
签名、403、轮询 |
XCTest/Espresso + mock |
| 回归 |
主站切换 H5/App/Share 一致 |
后台改 primary + 多端验证 |
5.2 P0 用例(必须全过)
品牌域与 Gate
| ID |
模块 |
步骤 |
期望 |
| TC-B1 |
品牌 |
HEAD/GET 品牌根 |
301 → 落地域 |
| TC-B2 |
品牌 |
GET /login /promo |
仍 301,不得 200 业务页 |
| TC-B3 |
品牌 |
Body 检测 |
无业务 HTML 特征 |
| TC-B4 |
品牌 |
POST 品牌域 |
拒绝 |
| TC-B5 |
品牌 |
301 Location |
仅 落地域 FQDN,非 play-*.com |
| TC-L1 |
落地域 |
IP/fp 失败 |
地址栏仍 landing;空页 |
| TC-L2 |
落地域 |
resolve 失败 |
E-RESOLVE 在 landing hostname |
| TC-L3 |
落地域 |
probe 成功 |
location → 主站域;地址栏变化 |
| TC-L4 |
主站域 |
FAILOVER |
Toast 在 play-*.com;不回 landing |
| TC-G1 |
Gate |
正常 IP+指纹 |
解密得 ≤3 域,能进站 |
| TC-G2 |
Gate |
机房 IP(IP-S1 datacenter) |
空页,40301 |
| TC-G3 |
Gate |
Headless 指纹(FP-H2 webdriver) |
空页,40302 |
| TC-G4 |
Gate |
伪造 resolve sig |
拒绝 |
| TC-G5 |
Gate |
ip_score=59 |
空页,reason=ip_score_low |
| TC-G6 |
Gate |
fp_score=59 |
空页,reason=fp_score_low |
| TC-G7 |
Gate |
同 IP 31 次/min |
429 或空页 |
| TC-G8 |
Gate |
fp 采集超时 >3s |
空页,FP-H1 |
Resolve 与加密
| ID |
模块 |
步骤 |
期望 |
| TC-R1 |
Resolve |
healthy≥3 |
payload 含 3 域 |
| TC-R2 |
Resolve |
healthy=0 |
503,无域名 |
| TC-R3 |
加密 |
解密 payload |
字段完整,expires 有效 |
| TC-R4 |
切换 |
disabled d1 |
Resolve 不再含 d1 |
| TC-R5 |
池 |
改 primary |
H5/App/Share 一致 |
客户端轮询
| ID |
模块 |
步骤 |
期望 |
| TC-C1 |
轮询 |
仅 d2 健康 |
自动进 d2,try_order=1 |
| TC-C2 |
轮询 |
3 域全挂 |
RETRY_RESOLVE 3 轮后维护页 |
| TC-C3 |
轮询 |
运行中 d1 挂 |
FAILOVER 切 d2 |
| TC-C4 |
轮询 |
单域 probe 超时 3s |
立即试下一域,总预算 ≤9s |
| TC-C5 |
轮询 |
API 连续 3 失败 |
触发 FAILOVER |
| TC-C6 |
轮询 |
resolve 缓存未过期 |
跳过 resolve,先 probe 缓存域 |
| TC-C7 |
App |
Keychain 域 probe 成功 |
跳过 resolve |
前端异常提示(§4.10 / §4.11)
| 编号 |
场景 |
步骤 |
期望 |
| TC-UI1 |
E-LOAD-01 |
正常进站,观察 resolve→probe |
顶栏「正在连接最佳线路…」,无按钮 |
| TC-UI2 |
E-PROBE-01 |
d1 探活失败 |
Toast「当前线路响应较慢…」,3s 消失 |
| TC-UI3 |
E-NET-01 |
运行期 API 失败触发 FAILOVER |
Toast「网络异常,已为您切换…」 |
| TC-UI4 |
E-RESOLVE-01 |
3 轮 resolve 全失败 |
全屏「暂时无法连接」+ 重新连接/联系客服 |
| TC-UI5 |
E-DECRYPT-01 |
篡改 payload |
全屏「加载失败」+ 刷新重试 |
| TC-UI6 |
E-GATE-01 |
IP/指纹 BLOCK |
空页,无任何 VTY 文案 |
| TC-UI7 |
登录页 |
同上各态在 /login |
文案与首页 完全一致 |
App
| ID |
模块 |
步骤 |
期望 |
| TC-A1 |
App |
正常签名 resolve |
得加密域 |
| TC-A2 |
App |
curl 无签名 |
403 |
| TC-A3 |
App |
浏览器调 app/resolve |
403 |
| TC-A4 |
App |
Site API 无签名 |
403 |
| TC-A5 |
App |
不经品牌域 |
冷启动直达 Site |
| TC-VER1 |
App 版本 |
bootstrap OK ≥4.0.0 |
正常 resolve |
| TC-VER2 |
App 版本 |
3.x 调 bootstrap |
40310 全屏强更 |
| TC-VER3 |
App 版本 |
3.x 调 app/resolve |
40310(绕过 bootstrap 仍拦) |
| TC-VER4 |
切流 |
T 日 force_update=true |
旧安全版本可选;<min 必拦 |
| TC-VER5 |
皮肤 |
brand_pack 过旧 |
40310 或强更 |
| TC-VER6 |
切流 |
Brand+Gate+min_version 同发 |
H5/App 新 Brand 一致 |
探活与运维
| ID |
模块 |
步骤 |
期望 |
| TC-P1 |
探活 |
模拟 d1 超时 3 次 |
90s 内 auto disabled |
| TC-P2 |
运维 |
品牌 DNS 自检 |
不指向 Site 源站 |
| TC-P3 |
分享 |
Resolve vs Share |
同一 primary |
| TC-SH1 |
分享 |
有效 code |
302 到 primary + entry=share |
| TC-SH2 |
分享 |
code 过期 |
410 |
| TC-SH3 |
分享 |
primary 挂 backup ok |
302 到 backup 域 |
| TC-SH4 |
分享 |
落地后 Site 挂 + 有缓存 |
FAILOVER Toast |
| TC-DIR1 |
直访 |
play-a health OK |
entry_type=direct,无 Gate |
| TC-DIR2 |
直访 |
play-a 挂 + 有 resolve_meta |
自动切 b/c |
| TC-DIR3 |
直访 |
play-a 挂 + 无缓存 |
跳 official_brand_url |
安全审视(§2.8)
| ID |
模块 |
步骤 |
期望 |
| TC-SEC1 |
安全 |
扫 Brand 全路径 |
仅 301/空;0 业务关键词 |
| TC-SEC2 |
安全 |
Headless resolve |
40302 空页;无 domains |
| TC-SEC3 |
安全 |
curl resolve |
无明文 play-* |
| TC-SEC4 |
安全 |
Brand 证书 SAN |
无 Site 域 |
| TC-SEC5 |
安全 |
Safe Browsing |
Brand 无威胁标记 |
| TC-SEC6 |
安全 |
业务挂回 Brand 变更 |
工单拒绝/告警 |
| TC-SEC7 |
安全 |
Site disabled 后 Brand |
仍 301 空 Body |
| TC-SEC8 |
安全 |
错误页文案 |
无禁词 |
埋点
| ID |
模块 |
步骤 |
期望 |
| TC-T1 |
埋点 |
品牌进入 |
entry_type=brand + hop_id |
| TC-T2 |
埋点 |
hop 重复 |
只计 1 次 |
| TC-T3 |
埋点 |
直连 Site |
不误计 brand |
5.3 P1 用例
| ID |
步骤 |
期望 |
| TC-F1 |
Entry 限流 |
429/保护响应 |
| TC-F2 |
管理 API 公网未授权 |
拒绝 |
| TC-F3 |
证书到期模拟 |
告警触发 |
| TC-F4 |
可用域≤1 |
P1 告警 |
| TC-F5 |
40301 占比>40% |
风控复核 |
成功率 KPI 验收(相对 vty.vip)
| ID |
模块 |
步骤 |
期望 |
| TC-K1 |
KPI |
中国区 7 天 entry_success 率 |
≥ 82%(灰度)/ ≥ 88%(稳定) |
| TC-K2 |
KPI |
分省最低(不含极端样本省) |
≥ 78% |
| TC-K3 |
KPI |
分运营商均值 |
≥ 85% |
| TC-K4 |
KPI |
较 vty.vip 基线 75% |
提升 ≥ 13pp(稳定期) |
| TC-K5 |
归因 |
entry_fail.fail_stage 报表 |
可拆解 DNS/Gate/Resolve/Probe |
| TC-K6 |
举报 |
自动化扫 Brand Body |
0 业务特征;7 日内 Brand 零内容失效 0 次 |
5.4 自动化建议
// Playwright 示例
test('brand zero content', async ({ request }) => {
const r = await request.get('https://winbet.com/', { maxRedirects: 0 });
expect(r.status()).toBe(301);
expect(r.headers()['location']).toMatch(/gate\./);
});
test('headless gets empty gate', async ({ browser }) => {
const ctx = await browser.newContext({ userAgent: 'HeadlessChrome' });
const page = await ctx.newPage();
await page.goto('https://gate.example/r/winbet');
const body = await page.content();
expect(body.replace(/\s/g, '')).toBe('<html><head></head><body></body></html>');
});
5.5 验收签字
| 角色 |
验收焦点 |
签字 |
| 运维 |
品牌 301 + 零内容 + 桶 + 监控 |
|
| 技术 |
池合并 + 双检 + 接口 + SDK + 探活 |
|
| 测试 |
P0 全过 |
|
| 产品 |
口径与流程 · 成功率 KPI |
|
| 风控 |
IP/fp 阈值 |
|
附录
A. RACI
| 事项 |
产品 |
技术 |
运维 |
测试 |
| 需求与口径 |
A |
C |
C |
C |
| 品牌 301 / 零内容 |
C |
C |
A/R |
R |
| Gate 双检 + Resolve |
C |
A/R |
C |
R |
| 域名池合并 |
C |
A/R |
I |
R |
| URL 桶 / CDN |
C |
C |
A/R |
R |
| 探活 Worker |
C |
A/R |
C |
R |
| App 签名网关 |
C |
A/R |
I |
R |
| 用例与验收 |
A |
C |
C |
R |
B. 开放问题
| # |
问题 |
决策人 |
| Q1 |
ip/fp 阈值 60 是否按地区分档 |
风控+产品 |
| Q2 |
H5 密钥混淆方案 |
安全+前端 |
| Q3 |
App 证书 pinning 是否强制 |
安全+客户端 |
| Q4 |
维护页托管在 Gate 还是独立域 |
产品+运维 |
D. 名词数据字典(Glossary)
用途: 产品 / 运维 / 前端 / 后端 / App / 测试 统一口径;工单、埋点、接口字段、会议沟通均以此为准。
约定: 「中文名」为口头沟通首选;「英文/代码名」为实现与 JSON 字段;勿混用 Gate 与落地域(Gate=服务,Landing=域名角色)。
D.1 域名角色(五层)
| 中文名 |
英文/代码 |
示例 FQDN |
含义 |
谁维护 |
详见 |
| 品牌域 |
Brand |
winbet.com |
对外广告/物料入口;仅 301,零业务 Body |
运维 |
§2.2、§3.3 |
| 落地域 |
Landing |
entry.vty.net |
中间层判定域;双检+resolve+probe 编排;判定失败 在本域报错 |
技术+运维 |
§2.2.1 |
| 站点主域 / 主站域 |
Site |
play-a.com |
域名池内业务域;完整 H5/App WebView 业务;判定 成功后 才进入 |
技术+运维 |
§4.2 |
| 分享域 |
Share |
s-xx.com |
短链解析;302 到主站域(默认不经落地域) |
技术 |
§2.3.8 |
| Gate 服务 |
Gate |
部署在 落地域 上 |
风控+resolve API+bootstrap;不是独立域名层级,是落地域上的逻辑服务 |
技术 |
§4.3 |
| 管理域 |
Admin |
内网 |
域名池/落地域/切流配置后台 |
技术 |
§4.2 |
易混辨析:
| 说法 |
正确理解 |
错误理解 |
| 「Gate 域」 |
通常指 落地域 FQDN(跑 Gate 服务) |
不要把 Gate 与主站域混为一谈 |
| 「落地」 |
用户 到达落地域 或 Share 302 到主站 |
不等于「进入业务首页」 |
| 「主站 / 主域」 |
池内 is_primary 或当前 current_domain |
不是 Brand,不是 Landing |
| 「站点池 / 域名池」 |
site_domain_pool 中 Site 主域 集合 |
不含 Landing、Brand |
D.2 入口与归因
| 中文名 |
字段/代码 |
类型 |
含义 |
取值 / 示例 |
| 入口类型 |
entry_type |
string |
用户从哪条链路进入主站 |
brand / share / direct / app |
| 品牌 ID |
brand_id |
string |
配置中心品牌标识,非 FQDN |
winbet |
| 品牌域名 |
brand_domain |
string |
用户点击的 Brand FQDN(埋点) |
winbet.com |
| 跳转凭证 |
hop_id |
string |
第一跳签发、主站核销;防刷量、归因 |
hop_8f3k2j |
| 分享码 |
share_code / {code} |
string |
Share 短链路径段 |
abc123 |
| 分享参数 |
entry=share |
query |
标记分享进入 |
URL 参数 |
| 官方入口 URL |
official_brand_url |
string |
直访失败兜底跳 Brand |
配置中心下发 |
| 落地兜底 URL |
gate_fallback_url |
string |
直访失败兜底跳落地域 bootstrap |
配置中心下发 |
entry_type |
中文 |
典型路径 |
是否经落地域 |
brand |
品牌 H5 |
Brand→Landing→Site |
是 |
share |
分享 |
Share→Site |
否(默认) |
direct |
主站直访 |
直接 play-*.com |
否 |
app |
App |
App SDK→Site |
否(App 内等效) |
D.3 域名池(site_domain_pool)
| 中文名 |
字段 |
类型 |
含义 |
取值 |
| 站点域名 |
domain |
string |
主站 FQDN,唯一 |
play-a.com |
| 状态 |
status |
enum |
运营状态 |
active / standby / disabled / blocked |
| 健康 |
health |
enum |
探活结果 |
ok / fail / unknown |
| 渠道 |
channels |
array |
哪些端可用 |
h5 / app / pc / all |
| 权重 |
weight |
int |
Resolve 排序,1–100 |
越大越优先 |
| 主站标记 |
is_primary |
bool |
分享默认、展示用主域 |
true/false |
| 连续失败次数 |
fail_streak |
int |
探活连续失败计数 |
≥3 摘除 |
| 最近探活时间 |
last_probe_at |
timestamp |
Worker 上次探测 |
— |
| 操作人 |
operator |
string |
人工变更审计 |
— |
| 池版本 |
resolve_version |
int/string |
池变更版本(埋点) |
配置递增 |
D.4 客户端/SDK 运行时对象
| 中文名 |
变量/键名 |
含义 |
持久化 |
| 域名列表 |
domain_list |
解密后最多 3 个主站 FQDN,probe 顺序 |
resolve_meta 内 |
| 当前主站 |
current_domain |
正在使用的 Site 基址 |
session + Keychain |
| 首选主站 |
primary |
服务端指定首选(通常=list[0]) |
resolve_meta |
| Resolve 缓存 |
resolve_meta |
加密包、过期时间、hop 等 |
H5: vty_ds_v1;App: Keychain |
| 探活缓存 |
probe_cache |
每域最近 probe 结果,TTL 60s |
内存 |
| 本地拉黑 |
local_bad |
当前会话内暂时不可用的主站域 |
内存,60s |
| 落地阶段 |
phase |
landing / site;决定报错归属域 |
SDK 内存 |
| SDK 配置 |
CFG |
超时、重试次数等常量 |
§4.6.9 |
resolve_meta 结构:
{
"payload": "<加密串>",
"expires_at": 1735689900,
"hop_id": "hop_8f3k2j",
"key_id": "k2026-03",
"resolved_at": 1735689600,
"domain_list": ["play-a.com", "play-b.com", "play-c.com"]
}
D.5 接口与 API
| 中文名 |
路径/服务 |
调用方 |
含义 |
| Gate Resolve |
POST /api/v1/gate/resolve |
H5 落地页 bootstrap |
双检通过后下发 加密 主站列表 |
| App Resolve |
POST /api/v1/app/resolve |
App |
签名通过下发加密主站列表 |
| App Bootstrap |
GET /api/v1/app/bootstrap |
App Splash |
版本关、强更、brand_pack |
| 当前主站查询 |
GET /api/v1/resolve/current-site |
Share 服务 |
与 Resolve 同源 primary |
| 主站健康检查 |
HEAD/GET /__health |
SDK / Worker |
200 + {"ok":true} |
| Share 短链 |
GET /{code} |
用户浏览器 |
302 到主站落地页 |
| 落地 bootstrap 页 |
GET /r/{brandId} |
用户浏览器 |
落地域极简 HTML+SDK |
| 响应码 |
中文 |
含义 |
code=0 |
成功 |
正常业务数据 |
40301 |
IP 风控拒绝 |
不下发域名;落地域空页 |
40302 |
指纹风控拒绝 |
同上 |
40310 |
强制更新 |
App 版本过低;不可 resolve |
50301 |
池枯竭 |
无 healthy 主站;落地域全屏维护 |
D.6 风控与评分
| 中文名 |
字段/规则 ID |
含义 |
阈值 |
| IP 判定 |
ip_verdict |
NORMAL / BLOCK / 低分 |
见 §4.3.1 |
| IP 分数 |
ip_score |
0–100,越高越可信 |
≥60 通过 |
| 指纹判定 |
fp_verdict |
同 IP |
见 §4.3.2 |
| 指纹分数 |
fp_score |
0–100 |
≥60 通过 |
| 指纹哈希 |
fp_hash |
浏览器指纹摘要 |
H5 必填 |
| 双检 |
— |
IP 且 指纹 均 NORMAL 才 Resolve |
AND |
| 硬拒绝 |
IP-H* / FP-H* |
命中即空页,不算分 |
— |
| App 签名 |
X-App-Sign |
HMAC 证明合法客户端 |
缺/错→403 |
| 限流 |
rate_limit |
同 IP/device 频率 |
429 |
D.7 加密与密钥
| 中文名 |
字段 |
含义 |
| 加密载荷 |
payload |
Base64(iv+ciphertext+tag) |
| 密钥 ID |
key_id |
指向 AES-256 密钥,如 k2026-03 |
| 过期时间 |
expires_at |
防重放;过期需重拉 resolve |
| TTL |
ttl_sec |
通常 300s |
| 随机数 |
nonce |
单次 resolve 唯一,Redis 去重 5min |
| 请求签名 |
sig |
HMAC(brand_id|ts|nonce|fp_hash) |
D.8 SDK 状态机
| 中文名 |
状态代码 |
含义 |
| 初始化 |
INIT |
冷启动 |
| 读缓存 |
LOAD_CACHED |
有未过期 resolve_meta |
| 拉 Resolve |
FETCH_RESOLVE |
请求 gate/app resolve |
| 探活列表 |
PROBE_LIST |
顺序 HEAD 主站 __health |
| 进入主站 |
ENTER_SITE |
location.replace 完成 |
| 运行中 |
RUNNING |
主站业务 |
| 重拉 Resolve |
RETRY_RESOLVE |
probe 全失败后再 resolve |
| 维护 UI |
MAINTENANCE_UI |
全屏不可进 |
| 故障切换 |
FAILOVER |
主站域间切换 |
D.9 前端异常文案(UI ID)
| ID |
中文摘要 |
归属域 |
UI 形态 |
| E-GATE-01 |
风控空页(无文案) |
落地域 |
EMPTY |
| E-LOAD-01 |
正在连接最佳线路… |
落地域 |
TOP_LOADING |
| E-PROBE-01 |
切换备用线路… |
落地域 |
TOAST |
| E-RESOLVE-01 |
暂时无法连接 |
落地域(未进站) |
FULL_BLOCK |
| E-DECRYPT-01 |
加载失败 |
落地域 |
FULL_BLOCK |
| E-RATE-01 |
访问过于频繁 |
落地域 |
FULL_BLOCK |
| E-NET-01 |
已切换备用线路 |
主站域 |
TOAST |
| E-APP-FORCE-01 |
请更新至最新版本 |
App 全屏 |
FULL_BLOCK |
| E-SSL-01 |
证书异常切线 |
落地域 probe 阶段 |
TOAST |
i18n 前缀:gate.*(H5 落地/主站壳)、app.*(App 强更)。已支持语言: zh-CN、vi-VN(§4.10.2)。
D.10 App 版本与皮肤
| 中文名 |
字段 |
含义 |
| 最低版本 |
min_version |
低于此 40310,永久拦截 |
| 最新版本 |
latest_version |
配合 force_update |
| 强制更新开关 |
force_update |
切流日 true |
| 品牌皮肤包 |
brand_pack / brand_pack_version |
主题 Logo 启动图 bundle |
| 必需皮肤版本 |
required_brand_pack |
本地低于此→强更 |
| 切流包标识 |
cutover_bundle |
如 2026-brand-v2,审计用 |
| 更新地址 |
update_url |
App Store / APK 链接 |
D.11 埋点事件(核心)
| 事件名 |
关键字段 |
含义 |
brand_request |
brand, province, isp |
品牌首跳 |
gate_check |
ip_score, fp_score, reason |
落地域双检 |
resolve_result |
domain_count, code |
Resolve 下发 |
resolve_fetch |
round, latency_ms |
重试轮次 |
domain_probe |
try_order, domain, ok |
探活 |
domain_switch |
from_domain, to_domain |
FAILOVER |
entry_success |
site_domain, hop_id, total_ms |
KPI 成功进入 |
entry_fail |
fail_stage, reason |
KPI 失败归因 |
fail_stage 常用值: brand_dns / landing_unreachable / gate_ip / gate_fp / resolve / decrypt / probe / enter
D.12 运维与 CDN
| 中文名 |
术语 |
含义 |
| URL 桶 |
bucket |
CDN 上隔离的静态/规则容器 |
| 品牌桶 |
bucket-brand-* |
仅 301 规则 |
| 落地桶 |
bucket-landing-* |
bootstrap,无业务 |
| 主站桶 |
bucket-site-* |
每主站域独立 |
| 零内容 |
— |
Brand Body 0 字节或极短 |
| 301 跳转 |
— |
Brand→Landing 唯一允许响应 |
| 探活 Worker |
— |
30s 周期 HEAD 主站 __health |
| 摘除 |
disabled |
连续 3 败自动下线 |
D.13 缩写与项目代号
| 缩写 |
全称 |
含义 |
| PRD |
Product Requirements Document |
本文档 |
| KPI |
Key Performance Indicator |
成功进入率等 |
| FQDN |
Fully Qualified Domain Name |
完整域名 |
| GCM |
Galois/Counter Mode |
AES 加密模式 |
| HMAC |
Hash-based MAC |
签名算法 |
| SDK |
Software Development Kit |
域名防护客户端库 |
| FAILOVER |
— |
主站域故障自动切换 |
| PROBE |
— |
对 __health 的探活请求 |
| P0/P1 |
Priority |
测试/告警优先级 |
D.14 端到端一句话(各端对齐用)
| 角色 |
一句话 |
| 产品 |
用户点 Brand → 落地域判定 → 成功才进 主站池;失败报错 留在落地域。 |
| 运维 |
Brand 只 301 到 落地域;主站池每域独立桶;Landing 与 Site 证书分离。 |
| 前端 H5 |
bootstrap 跑在 Landing;phase=landing 时所有 E-* 归属落地 hostname。 |
| 后端 |
Gate Resolve 只查 site_domain_pool;Landing 不在池内;40301/40302/50301 定案。 |
| App |
先 bootstrap 版本关,再 app/resolve;UI 在 App 内,逻辑同 H5 probe。 |
| 测试 |
TC-L* 验证报错域;TC-B5 验证 301 不指向 play-*.com。 |
C. 修订记录
| 版本 |
日期 |
说明 |
| v3.0 |
2026-09-02 |
§2.8 安全审视(威胁模型、SEC-01~14、TC-SEC);评审入口「安全」Tab |
| v2.9 |
2026-09-02 |
§4.10.2 前端异常文案 vi-VN 越南语 |
| v2.8 |
2026-09-02 |
附录 §D 名词数据字典 |
| v2.7 |
2026-09-02 |
§2.2 落地域中间层;判定失败留落地域报错、成功进主站域;§4.10.5 报错归属;TC-L1~4 |
| v2.6 |
2026-09-02 |
§2.3.10 App 版本校验;§2.7 切流+强更;§4.4.5 bootstrap |
| v2.5 |
2026-09-02 |
§2.3 扩充分享/主站直访/App 全流程;四入口对照表;TC-SH14、TC-DIR13 |
| v2.4 |
2026-09-02 |
§2.3 品牌 H5 五阶段完整流程:时序图、分支、解密、FAILOVER |
| v2.3 |
2026-09-02 |
§4.3.6/4.3.7 IP/指纹采集与判断;§4.10 前端文案+i18n;§4.11 轮询 UI 映射;H5 异常态 Demo;TC-UI1~7 |
| v2.2 |
2026-09-02 |
§4.3 IP/指纹完整规则(硬拒绝+评分);§4.6 轮询状态机/SDK 常量;测试用例扩充 |
| v2.1 |
2026-09-02 |
vty.vip 基线 ~75%、举报/扫描应对、KPI ≥88% |
| v2.0 |
2026-09-02 |
重构五章;双检+加密+池合并+App403 |
| v1.0 |
2026-08-26 |
初稿:双段跳转、域名池、分享、埋点 |
文档结束。