- model: SimNet protocol fields + NormalizeSimnet; type+port protocol uniqueness - server config: OmnXT runtime config delivery via compatible() simnet case - credentials: derive per-user psk/key_id from subscription (pkg/simnet), no new table - subscription: adapter buildOmnxtSimnetConfigs + base64 buildOmnxtProtocolLinks + OmnXT SimNet application (migration 02161) - UA gating: hide experimental protocols from non first-party clients (download + JSON node-list) - admin: normalize simnet on create/update and on GET responses - tests: 21 simnet unit tests; full suite green
32 KiB
PPanel Server Simnet 协议接入实施计划
本文档用于指导在现有自维护后端 /Users/Apple/code_vpn/vpn/ppanel-server 中接入 simnet 协议。目标不是把 Pro 新版后端整体迁移进来,而是在保留旧系统架构、数据库主链路和现有节点管理模型的前提下,把 simnet 做到管理端可配置、OmnXT 节点可拉取、SlagClient 可订阅连接、用户授权和流量统计闭环。
参考实现来自新版 Pro 后端:/Users/Apple/Downloads/NPanelPro-pro/NPanel-backend。
1. 项目背景
当前旧后端已经有完整的 Server、Node、Subscribe、Traffic、Online User 等链路,协议配置主要保存在 Server 的 protocols JSON 字段里,Node 侧用 protocol + port + address 描述对外节点。新版 Pro 后端已经加入了 simnet 协议字段、管理端接口、节点兼容接口和订阅交付逻辑,但它的整体工程结构和旧仓库不同。
旧仓库是 Gin/goctl/Gorm 风格,核心入口包括:
- API 定义:
apis/admin/server.api、apis/node/node.api、apis/public/subscribe.api、apis/types.api - 生成类型:
internal/types/types.go - 管理端 Server 逻辑:
internal/logic/admin/server/* - 节点服务端配置拉取:
internal/logic/server/getServerConfigLogic.go - 节点用户列表拉取:
internal/logic/server/getServerUserListLogic.go - 公共订阅节点返回:
internal/logic/public/subscribe/queryUserSubscribeNodeListLogic.go - 节点在线与流量上报:
internal/logic/server/pushOnlineUsersLogic.go、internal/logic/server/serverPushUserTrafficLogic.go
新版 Pro 的关键参考入口包括:
- Simnet 管理端字段:
api/admin/server/v1/server.proto - OmnXT 节点兼容接口:
internal/server/http_compat_server.go - 公共订阅响应:
api/public/subscribe/v1/subscribe.proto - 公共订阅映射:
internal/service/public/subscribe/subscribe.go - UA/capability 过滤:
internal/biz/public/subscribe/subscribe.go - 节点交付数据:
internal/data/delivery_node.go - 协议模型和默认值:
internal/model/server/protocol.go
2. 目标与非目标
目标
- 在旧后端中完整支持
simnet协议的保存、查询、下发、订阅和统计。 - 继续使用旧系统 Server 的
protocolsJSON 保存协议配置,不强制拆表保存管理端协议配置。 - 第一版支持当前实际需要的能力:H2、TLS/SNI、AF、HTTPS Fallback。
- Reverse 字段先纳入模型和接口,默认关闭;不在第一版强制上线 Reverse 转发能力。
- 管理端配置、OmnXT 服务端运行配置、SlagClient 客户端订阅配置使用不同 DTO,避免敏感字段误下发。
- 使用
type + port唯一定位一个 Server 内的协议实例,支持同一 Server 未来存在多个协议。 - OmnXT 拉取配置必须校验
secret_key。 - Server 级 PSK 不得下发给普通用户。
- 优先设计每用户独立 Simnet Key ID/PSK,使用户隔离、封禁、重置和审计可控。
- SlagClient 订阅响应兼容
protocolsJSON 和顶层simnet_*字段。
非目标
- 不整体替换旧后端为 Pro 新后端。
- 不一次性迁移 Pro 的全部协议字段、路由系统、完整 delivery node 架构。
- 不第一版实现 OmniFlow 或其他新协议。
- 不改变现有套餐、订单、余额、邀请等业务主链路。
- 不把生产服务器凭据、JWT、节点 SSH 密码写入代码或文档。
3. 总体技术策略
最科学的迁移方式是“协议纵向切入”,而不是“代码横向搬运”。也就是沿着 simnet 从管理端保存到节点运行,再到用户订阅、授权、流量统计的完整链路逐层补齐。
建议分三段落地:
- Server 侧先闭环:管理端能保存
simnet,OmnXT 能用secret_key拉到运行配置。 - User 侧再闭环:每个用户生成独立凭据,OmnXT 用户列表和 SlagClient 订阅使用同一套凭据。
- 运维侧最后闭环:流量、在线、到期、限额、TLS/AF/Fallback、灰度和回滚全部验证。
核心原则:
- 旧架构优先:沿用 goctl API、
internal/types、现有 logic/model 风格。 - DTO 分层:管理端 DTO 可以看到完整配置;节点 DTO 只给 OmnXT 运行需要;订阅 DTO 只给用户连接需要。
- 敏感字段隔离:Server PSK、证书 DNS 环境变量、节点密钥不得进入普通用户订阅响应。
- 渐进兼容:老协议、老客户端、老节点不受影响。
- 可回滚:每个阶段都能通过关闭
simnet协议或恢复旧接口行为回滚。
4. Simnet 数据链路
完整链路如下:
Admin UI
-> POST /api/v1/admin/server/create or update
-> Server.protocols JSON contains type=simnet
OmnXT Node
-> GET /api/v1/server/config?server_id=...&protocol=simnet&secret_key=...
-> receives server runtime config, including server-side PSK and TLS/AF/Fallback settings
OmnXT Node
-> GET /api/v1/server/user/list?server_id=...&protocol=simnet&secret_key=...
-> receives active user authorization list and per-user simnet credentials
SlagClient
-> GET /api/v1/public/subscribe?token=... with capability headers
-> receives node address, port, TLS/SNI, path, AF/Fallback public fields and user credential
OmnXT Node
-> POST traffic / online user report
-> backend maps simnet user credential to user subscribe and records traffic
simnet 的运行配置不能只靠 server.protocols 原样下发,因为同一份 JSON 同时包含管理端字段、Server 密钥字段和用户连接字段。必须在每个出口做字段筛选和转换。
5. 阶段 0:建立基线与确认契约
目标
确认旧后端、OmnXT、SlagClient 对 simnet 的最小契约,先把边界钉牢,避免后续实现时字段名、鉴权方式或客户端解析格式反复改。
具体任务
- 从新版 Pro 提取
simnet管理字段、服务端字段、订阅字段的差异表。 - 用当前 OmnXT 安装脚本部署的版本抓取真实请求路径和请求参数。
- 用 SlagClient 抓取订阅请求 header,确认 capability header 名称和版本值。
- 确认
secret_key当前在旧仓库internal/middleware/serverMiddleware.go或节点接口 handler 中的校验方式。 - 确认
server_id + protocol是否已经足够定位节点运行配置;如果端口也会重复,需要补充port查询参数。
预计修改位置
本阶段原则上不改业务代码,只新增测试夹具或临时验证脚本。可新增:
tests/simnet/fixtures/docs/simnet-contract.md,如需要更细的契约文档
依赖关系
- 需要可运行的旧后端本地环境或测试库。
- 需要 OmnXT 当前版本真实请求样本。
- 需要 SlagClient 当前版本订阅响应解析规则。
验收条件
- 明确 OmnXT 配置接口路径、方法、请求参数和响应字段。
- 明确 SlagClient 识别
simnet的字段格式。 - 明确 capability header 优先级:先 capability header,再 User-Agent 兜底。
- 明确
type + port是协议实例唯一键。
回滚点
本阶段不涉及生产行为,无需业务回滚。
6. 阶段 1:协议模型与参数校验
目标
让旧后端的 Protocol 类型可以完整表达第一版 simnet 配置,并在创建/更新 Server 时有默认值和校验。
具体任务
- 在
apis/types.api的Protocol结构加入simnet字段。 - 重新生成
internal/types/types.go。 - 在
internal/model/node中的协议模型加入同名 JSON 字段,保证 Server 的protocolsJSON 能完整 marshal/unmarshal。 - 新增
simnet默认值函数,例如ApplySimnetDefaults。 - 新增
simnet参数校验函数,例如ValidateSimnetProtocol。 - 校验
type + port唯一,避免同一 Server 下出现两个simnet:443。 - 限制第一版允许值:
simnet_carrier=h2、security=tls|none,生产建议默认tls。 - 校验 path 必须以
/开头,fallback host 非空时端口必须在 1-65535。 - 校验
simnet_psk最小长度和字符集;自动生成时使用安全随机。
字段范围
核心字段:
simnet_psk
simnet_key_id
simnet_ticket_id
simnet_path
simnet_carrier
TLS 字段:
security
sni
allow_insecure
cert_mode
cert_dns_provider
cert_dns_env
AF 字段:
simnet_af_enabled
simnet_af_path_mode
simnet_af_path_prefix
simnet_af_path_suffix
simnet_af_magic_mode
simnet_af_response_jitter_ms
simnet_af_handshake_polymorphism
simnet_af_settings_jitter
simnet_af_fake_header_injection
Fallback 字段:
simnet_fallback_enabled
simnet_fallback_target_scheme
simnet_fallback_target_host
simnet_fallback_target_port
simnet_fallback_host_header
simnet_fallback_tls_sni
Reverse 字段:
simnet_reverse_enabled
simnet_reverse_listen_addr
simnet_reverse_listen_port
simnet_reverse_target_host
simnet_reverse_target_port
默认值
建议默认值如下:
port: 443
simnet_path: /simnet/session
simnet_carrier: h2
security: tls
allow_insecure: false
simnet_af_path_mode: api
simnet_af_magic_mode: derived
simnet_af_response_jitter_ms: 1
simnet_reverse_enabled: false
simnet_reverse_listen_addr: 127.0.0.1
simnet_fallback_enabled: true
simnet_fallback_target_scheme: https
simnet_fallback_target_port: 443
预计修改位置
apis/types.apiinternal/types/types.gointernal/model/node/*或实际定义node.Protocol的文件internal/logic/admin/server/createServerLogic.gointernal/logic/admin/server/updateServerLogic.go- 可新增
internal/logic/admin/server/protocol_simnet.go
依赖关系
- 阶段 0 的字段契约。
- goctl 代码生成命令可用。
验收条件
- 管理端提交
type=simnet时,Server 可以保存完整 JSON。 - 未传默认字段时自动补齐默认值。
- 非法 path、非法 port、重复
type + port会被拒绝。 - 旧协议保存和返回不变。
回滚点
关闭管理端提交 simnet 的入口校验;或恢复 apis/types.api 和生成类型,旧协议数据仍可继续工作。
7. 阶段 2:管理端 Server 接口
目标
让管理端 Server 创建、更新、查询能完整展示和编辑 simnet,并保持 Node 更新接口与 Server 协议配置一致。
具体任务
- 更新
CreateServerRequest、UpdateServerRequest、FilterServerListResponse、GetServerProtocolsResponse中的协议字段。 - 在 create/update Server 时对每个 protocol 先做 normalize,再落库。
- 在 filter/list/detail 接口中返回规范化后的
simnet字段。 - 检查
CreateNodeRequest、UpdateNodeRequest是否允许protocol=simnet。 - Node 端
node_type=front的创建/更新要允许simnet,并校验其port与 Server 里的simnet协议端口一致。 - 如果管理端前端需要协议选项,
GetServerProtocols要返回simnet,并带默认字段方便 UI 填充。
预计修改位置
apis/admin/server.apiinternal/types/types.gointernal/logic/admin/server/createServerLogic.gointernal/logic/admin/server/updateServerLogic.gointernal/logic/admin/server/filterServerListLogic.gointernal/logic/admin/server/getServerProtocolsLogic.gointernal/logic/admin/server/createNodeLogic.gointernal/logic/admin/server/updateNodeLogic.go
依赖关系
- 阶段 1 协议模型已经可表达
simnet。
验收条件
- 管理端能创建一个 Server,包含
simnet:443。 - 管理端能更新
simnet_path、sni、AF 和 fallback 字段。 - 管理端节点列表显示
HK simnet这类节点时,协议类型不丢失。 GetServerProtocols返回的protocolsJSON 与数据库一致且字段完整。
回滚点
从管理端把 simnet 协议 disabled,保留数据但不对节点下发;或回滚 Server 相关 API 和 logic。
8. 阶段 3:OmnXT 服务端配置下发
目标
让 OmnXT 节点通过旧后端节点 API 拉到可运行的 simnet 服务端配置。
具体任务
- 检查
apis/node/node.api中GetServerConfigRequest是否有secret_key、server_id、protocol。 - 在
GetServerConfigLogic中加入protocol=simnet分支。 - 根据
server_id + protocol + port找到启用的simnet协议配置。 - 验证
secret_key,失败时返回明确错误,并记录来源 IP 和 server_id。 - 构造 OmnXT 服务端运行 DTO,包含 Server 运行需要的 PSK、path、carrier、TLS、SNI、AF、fallback、reverse 默认关闭字段。
- 不把管理端专用字段、无关协议字段原样塞给 OmnXT。
- 缓存 key 要包含
server_id + protocol + port,避免同端口多协议污染缓存。 - OmnXT 配置变更后要能通过更新 Server 或清理缓存生效。
服务端 DTO 建议
{
"protocol": "simnet",
"port": 443,
"listen": ":443",
"simnet_psk": "server-side-secret",
"simnet_path": "/simnet/session",
"simnet_carrier": "h2",
"security": "tls",
"sni": "example.com",
"allow_insecure": false,
"simnet_af_enabled": true,
"simnet_fallback_enabled": true
}
预计修改位置
apis/node/node.apiinternal/types/types.gointernal/logic/server/getServerConfigLogic.gointernal/logic/server/constant.gointernal/middleware/serverMiddleware.go- 可新增
internal/logic/server/simnet_config.go
依赖关系
- 阶段 1 和阶段 2。
- OmnXT 实际接口字段确认完成。
验收条件
secret_key正确时,OmnXT 能拉到simnet服务端配置。secret_key错误时,请求被拒绝。- 修改管理端
simnet_path后,OmnXT 重启或刷新能拿到新 path。 - Server PSK 只出现在 OmnXT 服务端配置中,不出现在普通用户订阅中。
回滚点
关闭 simnet.enable 或回滚 GetServerConfigLogic 的 simnet 分支;旧协议节点不受影响。
9. 阶段 4:用户级 Simnet 凭据
目标
为每个有效用户订阅生成独立 simnet 凭据,避免所有用户共享 Server PSK,支持单用户封禁、重置和流量归属。
具体任务
- 新增用户级凭据模型,建议按
user_subscribe_id + server_id + protocol + port维度唯一。 - 字段建议包括:
id、user_id、user_subscribe_id、server_id、protocol、port、key_id、psk、ticket_id、enabled、created_at、updated_at、rotated_at。 - 添加数据库 migration,并在初始化兼容逻辑中保证表存在。
- 用户第一次订阅或节点第一次拉用户列表时懒生成凭据。
- 支持管理员重置某个用户订阅 token 时同步重置
simnet凭据,避免旧凭据继续可用。 - 凭据生成使用加密安全随机;
key_id可用递增 id 或稳定 hash,但必须避免全局冲突。 - 保留
ticket_id字段,第一版可为空或由 OmnXT 需要时生成。
预计修改位置
internal/model/user/*或新增internal/model/simnet/*initialize/migrate/*initialize/schema_compat.gointernal/logic/public/subscribe/queryUserSubscribeNodeListLogic.gointernal/logic/server/getServerUserListLogic.go- 用户订阅 token 重置逻辑:
internal/logic/admin/user/resetUserSubscribeTokenHandler.go对应 logic
依赖关系
- 阶段 0 确认 OmnXT 和 SlagClient 需要的用户凭据格式。
- 阶段 1 的协议模型完成。
验收条件
- 同一用户同一节点多次订阅拿到稳定凭据。
- 不同用户拿到不同凭据。
- 重置用户订阅 token 后旧凭据失效,新凭据生效。
- 凭据表有唯一约束,重复生成不会产生两条有效凭据。
回滚点
可以停止向 OmnXT 下发 simnet 用户授权,并禁用 simnet 节点。数据库表可保留,不影响旧协议。
10. 阶段 5:OmnXT 用户授权同步
目标
让 OmnXT 拉取用户列表时获得 simnet 可认证用户,并且用户到期、限额、禁用、套餐节点组变化后同步生效。
具体任务
- 在
GetServerUserListLogic中加入simnet用户映射。 - 沿用旧系统的有效用户筛选条件:订阅有效、未到期、流量未超限、用户未禁用、节点组有权限。
- 对
simnet用户返回user_id、subscribe_id、uuid、key_id、psk、ticket_id、限速字段。 - OmnXT 请求
protocol=simnet时,只返回有simnet权限的用户。 - 缓存 key 加入
protocol + port,用户订阅变更、流量变更、节点组变更时能失效。 - 对
hysteria2等旧兼容映射不做破坏;normalizeServerUserListProtocol仅新增simnet透传。
预计修改位置
apis/node/node.apiinternal/types/types.gointernal/logic/server/getServerUserListLogic.gointernal/logic/server/constant.go- 用户订阅、节点组、流量相关 model/service
- 可新增
internal/logic/server/simnet_user.go
依赖关系
- 阶段 4 用户级凭据。
- 现有用户有效性判断需要梳理清楚。
验收条件
- OmnXT 拉用户列表时能看到有效用户的
simnet凭据。 - 用户到期、禁用或流量超限后,从 OmnXT 用户列表消失。
- 套餐节点组取消该节点后,从 OmnXT 用户列表消失。
- 老协议用户列表响应不变。
回滚点
保留凭据表,但关闭 GetServerUserListLogic 的 simnet 分支或禁用节点。
11. 阶段 6:公共订阅与 SlagClient
目标
让 SlagClient 冷启动、重启、重新订阅时都能拿到完整 simnet 节点,并正确构造连接。
具体任务
- 在
apis/public/subscribe.api的UserSubscribeNodeInfo加入用户连接需要的顶层simnet_*字段。 - 保留
protocolsJSON,确保 SlagClient 旧解析路径仍可读取。 - 在
QueryUserSubscribeNodeListLogic中解析 Server 的protocolsJSON,并把匹配node.protocol + node.port的simnet配置映射到订阅响应。 - 订阅响应只下发用户级
simnet_key_id、用户级simnet_psk、可公开 path/carrier/TLS/SNI/AF/Fallback 字段。 - 不下发 Server 级
simnet_psk、DNS provider env、管理端密钥字段。 - 新增 capability header 判断,例如
X-Client-Capabilities: simnet或当前 SlagClient 实际 header。 - 如果没有 capability header,则使用 User-Agent 作为兼容兜底;不应单纯依赖 UA。
- 对不支持
simnet的客户端隐藏simnet节点,避免客户端崩溃或展示不可用节点。 - 如果 SlagClient 同时支持
protocolsJSON 和顶层字段,优先让顶层字段完整,protocols作为兼容冗余。
订阅 DTO 建议
{
"id": 1,
"name": "HK simnet",
"protocol": "simnet",
"port": 443,
"address": "node.example.com",
"sni": "net.example.com",
"simnet_key_id": 10001,
"simnet_psk": "user-side-secret",
"simnet_ticket_id": "",
"simnet_path": "/simnet/session",
"simnet_carrier": "h2",
"security": "tls",
"allow_insecure": false,
"simnet_af_enabled": true,
"simnet_af_path_mode": "api",
"simnet_af_magic_mode": "derived",
"simnet_fallback_enabled": true,
"simnet_fallback_target_scheme": "https",
"simnet_fallback_target_host": "www.example.com",
"simnet_fallback_target_port": 443
}
预计修改位置
apis/public/subscribe.apiinternal/types/types.gointernal/logic/public/subscribe/queryUserSubscribeNodeListLogic.gointernal/logic/common/subscriptionTrace.go,如有订阅 UA 或设备记录- 可新增
internal/logic/public/subscribe/simnet_mapper.go
依赖关系
- 阶段 4 用户级凭据。
- SlagClient capability header 契约确认。
验收条件
- SlagClient 冷启动订阅后能看到
simnet节点。 - SlagClient 重启后仍能从订阅恢复连接配置。
- 不支持
simnet的客户端订阅不返回simnet节点。 - 普通用户订阅响应不包含 Server 级 PSK。
回滚点
订阅侧隐藏 simnet 节点或关闭 capability 开关;旧协议订阅不受影响。
12. 阶段 7:流量和在线用户映射
目标
让 OmnXT 上报的 simnet 在线用户和流量能正确归属到用户订阅,并触发旧系统现有的限额、日志、后台统计。
具体任务
- 确认 OmnXT 上报用户标识是
uuid、key_id、user_id还是其他字段。 - 如果 OmnXT 上报
key_id,后端通过用户级凭据表反查user_subscribe_id和user_id。 - 如果 OmnXT 上报
uuid,需要确认uuid与simnet凭据绑定关系,不允许跨用户伪造。 - 在
serverPushUserTrafficLogic中加入simnet标识解析。 - 在
pushOnlineUsersLogic中加入simnet在线用户映射。 - 更新后台节点在线数统计,确保
simnet:443与其他协议隔离。 - 失败上报要记录协议、server_id、port、用户标识和错误原因,方便排查。
预计修改位置
apis/node/node.apiinternal/types/types.gointernal/logic/server/serverPushUserTrafficLogic.gointernal/logic/server/pushOnlineUsersLogic.gointernal/model/traffic/*internal/model/node/*- 凭据表 model
依赖关系
- 阶段 4 用户级凭据。
- OmnXT 上报格式确认。
验收条件
simnet连接产生流量后,用户已用流量增加。- 节点后台能看到
simnet在线人数。 - 用户超限后 OmnXT 用户列表不再包含该用户。
- 旧协议流量统计不受影响。
回滚点
禁用 simnet 流量上报分支或关闭 simnet 节点;旧协议统计不受影响。
13. 阶段 8:TLS、AF 与 Fallback
目标
把当前实际部署需要的 TLS/SNI、AF 和 HTTPS Fallback 做到可配置、可验证、可运维。
具体任务
- TLS:支持
security=tls、sni、allow_insecure=false。 - 证书模式:第一版支持
cert_mode=http;DNS provider 字段先保留,不在普通订阅下发。 - AF:支持
simnet_af_enabled、path_mode=api、magic_mode=derived、response_jitter_ms。 - Fallback:支持 fallback scheme、host、port、host header、TLS SNI。
- Reverse:字段保存和下发给 OmnXT,但默认关闭;如果开启必须要求 target host/port 完整。
- 添加配置快照日志,OmnXT 拉取时打印非敏感字段,便于确认线上配置是否生效。
- 对真实节点做
443端口监听、证书申请、fallback 站点访问验证。
预计修改位置
internal/logic/admin/server/protocol_simnet.gointernal/logic/server/simnet_config.gointernal/logic/public/subscribe/simnet_mapper.goetc/ppanel.yaml,如需要新增全局开关- 节点部署文档或运维脚本,视 OmnXT 实际需求决定
依赖关系
- 阶段 3 OmnXT 配置下发。
- 节点服务器域名、证书、端口和 fallback 目标准备完成。
验收条件
- OmnXT 能在
443启动simnetH2 TLS。 - SNI 与证书匹配。
- AF 开启后 SlagClient 仍可连接。
- Fallback 目标在非协议请求时可访问。
- OmnXT 重启后配置仍然生效。
回滚点
关闭 AF 或 fallback;必要时把 simnet.enable=false,保留旧协议节点承载用户。
14. 阶段 9:自动化测试
目标
用测试保护 simnet 的关键契约,减少后续修改协议字段时再次出现“面板有配置、节点拿不到、客户端不识别”的问题。
具体任务
- 协议模型测试:默认值、校验、marshal/unmarshal。
- 管理端测试:create/update Server 保存
simnet字段完整。 - 节点配置测试:
secret_key正确/错误、simnetDTO 字段筛选。 - 用户凭据测试:生成稳定性、用户隔离、重置失效。
- 订阅测试:capability header 支持时返回
simnet;不支持时隐藏。 - 敏感字段测试:普通订阅中不得出现 Server PSK、DNS env。
- 流量测试:OmnXT 上报
key_id后可归属用户。 - 回归测试:现有 vless、trojan、hysteria2、shadowsocks 订阅不变。
预计修改位置
tests/acceptance/*internal/logic/admin/server/*_test.gointernal/logic/server/*_test.gointernal/logic/public/subscribe/*_test.gointernal/model/simnet/*_test.go
依赖关系
- 阶段 1 到阶段 7 基本实现完成。
验收条件
go test ./...通过,或项目当前可执行测试集全部通过。- 新增测试能覆盖 Server、OmnXT、SlagClient、Traffic 四条主链路。
- 任意敏感字段泄露测试失败时,CI 阻断。
回滚点
测试本身不影响生产;如果某阶段实现回滚,相应测试应标记待实现或一并回滚。
15. 阶段 10:灰度发布与回滚
目标
把 simnet 以可控方式上线,先让一个节点和少量测试用户跑通,再扩大范围。
具体任务
- 增加全局或配置级开关:
simnet_enabled。 - 管理端先创建一个独立测试 Server 和一个
simnetfront node。 - 只给测试套餐或测试节点组分配该节点。
- 部署 OmnXT,确认能拉配置、拉用户、启动监听。
- 用测试用户订阅 SlagClient,验证冷启动、重启、切换网络、重拉订阅。
- 观察在线用户、流量上报、错误日志、证书续期和 fallback 访问。
- 稳定后把节点加入正式套餐节点组。
- 保留旧协议节点作为回退路径,不把全部用户一次性切到
simnet。
预计修改位置
etc/ppanel.yaml,如需要全局开关internal/config/config.gointernal/svc/serviceContext.go- 运维部署文档
依赖关系
- 阶段 1 到阶段 9 完成。
- 测试节点服务器、域名、证书、OmnXT 可用。
验收条件
- 测试用户能稳定连接
simnet。 - SlagClient 重启后无需人工操作即可恢复。
- OmnXT 重启后能自动拉配置和用户授权。
- 管理端能看到在线和流量。
- 关闭
simnet后用户可回退到旧协议节点。
回滚点
- 管理端将
simnet协议enable=false。 - 从套餐节点组移除
simnet节点。 - OmnXT 停止
simnetinbound。 - 回滚后端到上一版本。
- 保留凭据表和字段,后续排查后可再次启用。
16. 文件改动范围
预计完整生产可用版本会影响 27-45 个业务/配置文件、12-20 个测试文件,新增约 3,000-6,000 行代码和测试。实际数量取决于 goctl 生成文件体积、现有 model 组织方式和 OmnXT/SlagClient 契约是否稳定。
必改范围
apis/types.apiapis/admin/server.apiapis/node/node.apiapis/public/subscribe.apiinternal/types/types.gointernal/model/node/*internal/logic/admin/server/createServerLogic.gointernal/logic/admin/server/updateServerLogic.gointernal/logic/admin/server/getServerProtocolsLogic.gointernal/logic/admin/server/filterServerListLogic.gointernal/logic/server/getServerConfigLogic.gointernal/logic/server/getServerUserListLogic.gointernal/logic/server/serverPushUserTrafficLogic.gointernal/logic/server/pushOnlineUsersLogic.gointernal/logic/public/subscribe/queryUserSubscribeNodeListLogic.go
可能新增范围
internal/model/simnet/*internal/logic/admin/server/protocol_simnet.gointernal/logic/server/simnet_config.gointernal/logic/server/simnet_user.gointernal/logic/public/subscribe/simnet_mapper.goinitialize/migrate/*simnet*tests/simnet/*docs/simnet-contract.md
前端联动范围
如果管理端前端也要同步配置,需要在前端仓库补齐:
- Server 创建/编辑表单的
simnet协议字段 - 协议默认值填充
- 字段校验提示
- Node 创建/更新时允许
protocol=simnet - 隐藏 Server PSK 的展示或复制入口
17. 提交拆分
建议按以下提交拆分,方便 review 和回滚:
simnet: add protocol model fields and validationsimnet: support admin server create/update/listsimnet: expose server runtime config for OmnXTsimnet: add per-user credentialssimnet: sync OmnXT user authorizationsimnet: expose public subscribe fields for SlagClientsimnet: map traffic and online reportssimnet: add tls af fallback handlingsimnet: add tests and rollout switch
每个提交都应该能单独说明行为变化,并尽量避免把 goctl 生成文件和手写逻辑混在一个巨大提交里。如果生成文件不可避免较大,提交说明中要明确哪些是生成结果。
18. 验收标准
最终验收必须覆盖下面场景:
- 管理端能创建 Server,协议为
simnet,端口443,TLS/SNI、AF、Fallback 字段保存完整。 - 管理端能创建或更新 Node,
protocol=simnet,address指向实际节点服务器。 - OmnXT 使用正确
secret_key能拉取simnet服务端运行配置。 - OmnXT 使用错误
secret_key被拒绝。 - OmnXT 重启后自动恢复
simnetinbound。 - 有效用户能通过 OmnXT 用户列表获得授权。
- 不同用户的
simnet_key_id或simnet_psk不相同。 - 用户禁用、到期或流量超限后,OmnXT 用户列表移除该用户。
- SlagClient 冷启动能通过订阅拿到
simnet节点并连接。 - SlagClient 重启后不丢失协议配置。
- 不支持
simnet的客户端订阅不会收到simnet节点。 - 普通用户订阅响应不泄露 Server PSK、DNS provider env、节点
secret_key。 simnet连接产生流量后,用户流量、节点流量、后台日志同步更新。- 关闭
simnet后,旧协议订阅、节点运行和流量统计不受影响。 go test ./...或项目当前有效测试集通过。
19. 风险清单
| 风险 | 影响 | 控制方式 |
|---|---|---|
| Server PSK 被下发给普通用户 | 所有用户共享密钥,泄露后整节点风险扩大 | DTO 分层,订阅敏感字段测试阻断 |
| OmnXT 和后端字段名不一致 | 节点启动失败或配置不生效 | 阶段 0 固化契约,用真实 OmnXT 请求回放测试 |
| SlagClient 只读顶层字段或只读 protocols JSON | 客户端拿到节点但无法连接 | 双格式兼容,顶层字段和 protocols 都保持可读 |
| 单用户凭据缺失 | 无法隔离用户,封禁和流量归属困难 | 阶段 4 必须先做凭据表,不走全员共享 PSK |
| capability 判断不准确 | 老客户端看到不可用节点 | capability header 优先,UA 只兜底,默认隐藏不支持客户端 |
| 缓存 key 未包含 port | 多协议或同协议多端口串配置 | cache key 包含 server_id + protocol + port |
| 流量上报标识不明确 | 用户流量无法入账或串账 | 与 OmnXT 明确上报 key_id,后端反查凭据表 |
| TLS/证书/fallback 运维失败 | 节点 443 无法正常服务 | 灰度节点先跑,保留旧协议回退 |
| goctl 生成覆盖手写改动 | 代码冲突或字段丢失 | 所有类型先改 api 文件,再生成;手写扩展放独立文件 |
20. 工期估算
在 OmnXT 和 SlagClient 契约清楚、测试环境可用的情况下:
- 阶段 0:0.5-1 天
- 阶段 1-2:1.5-2 天
- 阶段 3:1-1.5 天
- 阶段 4:1.5-2 天
- 阶段 5:1-1.5 天
- 阶段 6:1-1.5 天
- 阶段 7:1-2 天
- 阶段 8:1 天
- 阶段 9:2-3 天
- 阶段 10:1 天
完整生产可用版本预计 10-15 个有效开发日。如果 OmnXT 或 SlagClient 字段契约需要同步改动,额外预留 2-4 天联调时间。
21. 推荐执行顺序
第一周先完成最小闭环:
- 阶段 0:确认契约。
- 阶段 1:协议模型与校验。
- 阶段 2:管理端保存和查询。
- 阶段 3:OmnXT 配置下发。
第二周完成用户链路:
- 阶段 4:用户级凭据。
- 阶段 5:OmnXT 用户授权。
- 阶段 6:SlagClient 订阅。
- 阶段 7:流量和在线用户映射。
最后做生产化:
- 阶段 8:TLS、AF、Fallback 运维验证。
- 阶段 9:自动化测试补齐。
- 阶段 10:灰度发布和回滚演练。
22. 当前结论
最合理的方案是在旧后端内部补齐 simnet 的纵向链路,不建议整体迁移 Pro 新后端。这样风险最小,旧业务稳定性最好,也最贴近当前问题:SlagClient 和 OmnXT 需要的是一个一致、完整、不会泄露敏感字段的 simnet 契约。
第一版真正必须做的是:协议模型、管理端保存、OmnXT 配置、用户级凭据、OmnXT 授权、SlagClient 订阅、流量归属。只要这七个点闭环,simnet 就不是“配置看起来存在”,而是能在真实客户端和真实节点上稳定使用。