feat(simnet): add SimNet protocol end-to-end support

- 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
This commit is contained in:
2026-07-27 00:17:02 -07:00
parent 5ef3f2717e
commit 634b5a7bd0
23 changed files with 1728 additions and 107 deletions
+813
View File
@@ -0,0 +1,813 @@
# 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. 阶段 3OmnXT 服务端配置下发
### 目标
让 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. 阶段 5OmnXT 用户授权同步
### 目标
让 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. 阶段 8TLS、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 契约清楚、测试环境可用的情况下:
- 阶段 00.5-1 天
- 阶段 1-21.5-2 天
- 阶段 31-1.5 天
- 阶段 41.5-2 天
- 阶段 51-1.5 天
- 阶段 61-1.5 天
- 阶段 71-2 天
- 阶段 81 天
- 阶段 92-3 天
- 阶段 101 天
完整生产可用版本预计 10-15 个有效开发日。如果 OmnXT 或 SlagClient 字段契约需要同步改动,额外预留 2-4 天联调时间。
## 21. 推荐执行顺序
第一周先完成最小闭环:
1. 阶段 0:确认契约。
2. 阶段 1:协议模型与校验。
3. 阶段 2:管理端保存和查询。
4. 阶段 3OmnXT 配置下发。
第二周完成用户链路:
1. 阶段 4:用户级凭据。
2. 阶段 5OmnXT 用户授权。
3. 阶段 6SlagClient 订阅。
4. 阶段 7:流量和在线用户映射。
最后做生产化:
1. 阶段 8TLS、AF、Fallback 运维验证。
2. 阶段 9:自动化测试补齐。
3. 阶段 10:灰度发布和回滚演练。
## 22. 当前结论
最合理的方案是在旧后端内部补齐 `simnet` 的纵向链路,不建议整体迁移 Pro 新后端。这样风险最小,旧业务稳定性最好,也最贴近当前问题:SlagClient 和 OmnXT 需要的是一个一致、完整、不会泄露敏感字段的 `simnet` 契约。
第一版真正必须做的是:协议模型、管理端保存、OmnXT 配置、用户级凭据、OmnXT 授权、SlagClient 订阅、流量归属。只要这七个点闭环,`simnet` 就不是“配置看起来存在”,而是能在真实客户端和真实节点上稳定使用。