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