精简: 去掉外部流量上报(ExternalTrafficInform): 设置字段/校验/job 上报逻辑
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
# 3x-ui 路由器主控精简版 — 定版清单
|
||||||
|
|
||||||
|
> 2026-10-20 与用户逐项讨论中。本文档定版前不开工。
|
||||||
|
> 背景:AX6600 路由器 = **主控**(3x-ui 3.7.0,纯控制面,本机 xray 已 chmod -x 禁用,RSS ~130MB);
|
||||||
|
> Alpine 主控 = 备用,趋于退役。精简目标:二开构建压内存,验收线 RSS ≤ 90MB。
|
||||||
|
|
||||||
|
## 保留(已定)
|
||||||
|
|
||||||
|
- 入站/客户端管理、节点同步、内部流量统计(主控本职)
|
||||||
|
- 订阅服务,但**极简输出**:只给节点本身,不塞分流规则模板(geosite/geoip 规则集去掉);多格式转换保留
|
||||||
|
- VLESS、SS2022 入站(SS2022 为全网硬约束,不得移除)
|
||||||
|
- **API 令牌系统(暂保留)**:存量原版节点与主控的同步靠原版令牌机制(远程面板令牌 + node-sync scoped token);二开 agent 走自己的注册令牌是另一套。等存量节点全换二开 agent 后再议删除
|
||||||
|
- 数据库 schema 不动(回滚保险:换回原版二进制可读同一份库)
|
||||||
|
|
||||||
|
## 裁剪(已定)
|
||||||
|
|
||||||
|
- Telegram 机器人 + 面板设置里的「通知」(用户改手动备份;路由器上的 TG 备份 cron 绕行方案同步弃用)
|
||||||
|
- xray 配置模块**部分裁**:**出站 + 路由保留**(页面与生成逻辑都要;节点内网中转靠主控下发出站/路由,用户明确);其余模板部件(DNS、日志、策略等本机向设置)固定为内置默认值,去掉页面与生成/校验逻辑
|
||||||
|
- xray 可执行文件不再随包(x25519 内置生成后无硬依赖)
|
||||||
|
- **xray-core 库剥离**:配置生成改为纯 JSON 结构实现。最大一刀、风险最高,**最后动**
|
||||||
|
- API 文档(Swagger 页)
|
||||||
|
- 面板设置:外部流量(对外上报接口,未接任何外部系统;内部统计不受影响)
|
||||||
|
- 面板设置:LDAP
|
||||||
|
- 安全设置:双重验证(2FA,用户不用)
|
||||||
|
- 邮件模块(SMTP 发送:到期/流量提醒等,用户不用)
|
||||||
|
- 分组功能(客户端归类标签 + 按组批量操作/聚合订阅;用户从未用过,裁功能不影响已建客户端与订阅)
|
||||||
|
|
||||||
|
## 待用户确认
|
||||||
|
|
||||||
|
- 协议范围:是否只留 VLESS + SS2022,裁其余协议模板(VMess、Trojan、WireGuard、TUIC、Hysteria、MTProto 等)——建议裁
|
||||||
|
- IP 检查 / 测速类后台任务——建议裁
|
||||||
|
- 面板内置 ACME 签证书(走 CF Tunnel,不直签)——建议裁
|
||||||
|
- 多用户功能(单管理员使用)——建议裁
|
||||||
|
|
||||||
|
## 修复(非裁剪,已定)
|
||||||
|
|
||||||
|
- **x25519 内置生成**:原版生成 Reality 密钥靠调 `xray x25519`,主控禁用 xray 后建 VLESS+Reality 节点报"获取 x25519 证书错误"。改面板 Go 内置实现(crypto/ecdh),不再依赖 xray 二进制
|
||||||
|
- **mixed 协议节点部署 bug**(原版 bug):主控建入站选 mixed 协议时节点选择框消失,只能建本机;但节点本地建的 mixed 入站可同步回主控管理,证明后端/同步通道支持,纯前端对话框条件问题。修前端条件,允许 mixed 选节点部署
|
||||||
|
|
||||||
|
## 流程与验收(已定)
|
||||||
|
|
||||||
|
- 基线:3.7.0 二开版,不升级版本
|
||||||
|
- 顺序:边角裁剪 → 两个 bug 修复 → 最后剥 xray-core
|
||||||
|
- 交付:沙箱构建 → 路由器旁路端口冒烟 → 用户验收后替换;原版二进制保留,换回即回滚
|
||||||
|
- 验收线:RSS ≤ 90MB;订阅可拉;节点同步正常;Reality 建节点不报错(含 x25519 获取);mixed 可部署到节点;流量统计正常
|
||||||
|
- 内存预估(2026-10-20):基线 130.6MB → 预计 50-70MB。边角裁剪(TG/邮件/LDAP/2FA/文档/分组)对 RSS 贡献仅个位数 MB(多为休眠代码,主要瘦二进制);大头在剥 xray-core(库初始化/注册表/API 客户端栈)。若 xray-core 那刀不动,改完仍在 110-120MB。下限参考:框架(Gin+GORM+SQLite+Web UI+节点同步+订阅渲染)底盘约 40-50MB,到不了 NexusGate Go 版的 15-30MB(那是 stdlib 重写的量级)
|
||||||
@@ -90,8 +90,6 @@ type AllSetting struct {
|
|||||||
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||||
SubUpdates int `json:"subUpdates" form:"subUpdates" validate:"gte=0,lte=525600"`
|
SubUpdates int `json:"subUpdates" form:"subUpdates" validate:"gte=0,lte=525600"`
|
||||||
ExternalTrafficInformEnable bool `json:"externalTrafficInformEnable" form:"externalTrafficInformEnable"`
|
|
||||||
ExternalTrafficInformURI string `json:"externalTrafficInformURI" form:"externalTrafficInformURI"`
|
|
||||||
RestartXrayOnClientDisable bool `json:"restartXrayOnClientDisable" form:"restartXrayOnClientDisable"`
|
RestartXrayOnClientDisable bool `json:"restartXrayOnClientDisable" form:"restartXrayOnClientDisable"`
|
||||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||||
SubURI string `json:"subURI" form:"subURI"`
|
SubURI string `json:"subURI" form:"subURI"`
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/web/service/outbound"
|
"github.com/mhsanaei/3x-ui/v3/internal/web/service/outbound"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/web/websocket"
|
"github.com/mhsanaei/3x-ui/v3/internal/web/websocket"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// XrayTrafficJob collects and processes traffic statistics from Xray, updating the database and optionally informing external APIs.
|
// XrayTrafficJob collects and processes traffic statistics from Xray, updating the database.
|
||||||
type XrayTrafficJob struct {
|
type XrayTrafficJob struct {
|
||||||
settingService service.SettingService
|
settingService service.SettingService
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
@@ -54,15 +49,6 @@ func splitMovedClientTraffics(clientTraffics []*xray.ClientTraffic) ([]*xray.Cli
|
|||||||
return moved, emails, active
|
return moved, emails, active
|
||||||
}
|
}
|
||||||
|
|
||||||
const externalInformTimeout = 3 * time.Second
|
|
||||||
|
|
||||||
var externalInformClient = &fasthttp.Client{
|
|
||||||
ReadTimeout: externalInformTimeout,
|
|
||||||
WriteTimeout: externalInformTimeout,
|
|
||||||
MaxIdleConnDuration: time.Minute,
|
|
||||||
MaxConnsPerHost: 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewXrayTrafficJob creates a new traffic collection job instance.
|
// NewXrayTrafficJob creates a new traffic collection job instance.
|
||||||
func NewXrayTrafficJob() *XrayTrafficJob {
|
func NewXrayTrafficJob() *XrayTrafficJob {
|
||||||
return new(XrayTrafficJob)
|
return new(XrayTrafficJob)
|
||||||
@@ -100,11 +86,6 @@ func (j *XrayTrafficJob) Run() {
|
|||||||
}
|
}
|
||||||
websocket.BroadcastInvalidate(websocket.MessageTypeInbounds)
|
websocket.BroadcastInvalidate(websocket.MessageTypeInbounds)
|
||||||
}
|
}
|
||||||
if ExternalTrafficInformEnable, err := j.settingService.GetExternalTrafficInformEnable(); ExternalTrafficInformEnable {
|
|
||||||
j.informTrafficToExternalAPI(traffics, clientTraffics)
|
|
||||||
} else if err != nil {
|
|
||||||
logger.Warning("get ExternalTrafficInformEnable failed:", err)
|
|
||||||
}
|
|
||||||
if needRestart0 || needRestart1 {
|
if needRestart0 || needRestart1 {
|
||||||
j.xrayService.SetToNeedRestart()
|
j.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -223,36 +204,3 @@ func (j *XrayTrafficJob) Run() {
|
|||||||
logger.Warning("get all outbounds for websocket failed:", err)
|
logger.Warning("get all outbounds for websocket failed:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *XrayTrafficJob) informTrafficToExternalAPI(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) {
|
|
||||||
if len(inboundTraffics) == 0 && len(clientTraffics) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
informURL, err := j.settingService.GetExternalTrafficInformURI()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("get ExternalTrafficInformURI failed:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
informURL, err = service.SanitizePublicHTTPURL(informURL, false)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("ExternalTrafficInformURI blocked:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
requestBody, err := json.Marshal(map[string]any{"clientTraffics": clientTraffics, "inboundTraffics": inboundTraffics})
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("parse client/inbound traffic failed:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
request := fasthttp.AcquireRequest()
|
|
||||||
defer fasthttp.ReleaseRequest(request)
|
|
||||||
request.Header.SetMethod("POST")
|
|
||||||
request.Header.SetContentType("application/json; charset=UTF-8")
|
|
||||||
request.SetBody(requestBody)
|
|
||||||
request.SetRequestURI(informURL)
|
|
||||||
request.Header.SetConnectionClose()
|
|
||||||
response := fasthttp.AcquireResponse()
|
|
||||||
defer fasthttp.ReleaseResponse(response)
|
|
||||||
if err := externalInformClient.DoTimeout(request, response, externalInformTimeout); err != nil {
|
|
||||||
logger.Warning("POST ExternalTrafficInformURI failed:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -125,8 +125,6 @@ var defaultValueMap = map[string]string{
|
|||||||
"warpUpdateInterval": "0",
|
"warpUpdateInterval": "0",
|
||||||
"nord": "",
|
"nord": "",
|
||||||
"pia": "",
|
"pia": "",
|
||||||
"externalTrafficInformEnable": "false",
|
|
||||||
"externalTrafficInformURI": "",
|
|
||||||
"restartXrayOnClientDisable": "true",
|
"restartXrayOnClientDisable": "true",
|
||||||
"xrayOutboundTestUrl": "https://www.google.com/generate_204",
|
"xrayOutboundTestUrl": "https://www.google.com/generate_204",
|
||||||
"panelOutbound": "",
|
"panelOutbound": "",
|
||||||
@@ -930,22 +928,6 @@ func (s *SettingService) SetPia(data string) error {
|
|||||||
return s.setString("pia", data)
|
return s.setString("pia", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetExternalTrafficInformEnable() (bool, error) {
|
|
||||||
return s.getBool("externalTrafficInformEnable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) SetExternalTrafficInformEnable(value bool) error {
|
|
||||||
return s.setBool("externalTrafficInformEnable", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetExternalTrafficInformURI() (string, error) {
|
|
||||||
return s.getString("externalTrafficInformURI")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) SetExternalTrafficInformURI(InformURI string) error {
|
|
||||||
return s.setString("externalTrafficInformURI", InformURI)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetRestartXrayOnClientDisable() (bool, error) {
|
func (s *SettingService) GetRestartXrayOnClientDisable() (bool, error) {
|
||||||
return s.getBool("restartXrayOnClientDisable")
|
return s.getBool("restartXrayOnClientDisable")
|
||||||
}
|
}
|
||||||
@@ -1321,13 +1303,6 @@ func (s *SettingService) preserveRedactedSecrets(allSetting *entity.AllSetting,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateSettingsURLs(allSetting *entity.AllSetting) error {
|
func validateSettingsURLs(allSetting *entity.AllSetting) error {
|
||||||
if allSetting.ExternalTrafficInformURI != "" {
|
|
||||||
u, err := SanitizeHTTPURL(allSetting.ExternalTrafficInformURI)
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("external traffic inform URI is invalid:", err)
|
|
||||||
}
|
|
||||||
allSetting.ExternalTrafficInformURI = u
|
|
||||||
}
|
|
||||||
if allSetting.TgBotAPIServer != "" {
|
if allSetting.TgBotAPIServer != "" {
|
||||||
u, err := SanitizeHTTPURL(allSetting.TgBotAPIServer)
|
u, err := SanitizeHTTPURL(allSetting.TgBotAPIServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user