mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-06 01:56:37 +08:00
添加屏蔽协议到面板,免去手动操作
This commit is contained in:
@@ -5,7 +5,6 @@ go 1.23.0
|
|||||||
toolchain go1.23.4
|
toolchain go1.23.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apernet/OpenGFW v0.0.0-00010101000000-000000000000
|
|
||||||
github.com/go-gost/core v0.3.1
|
github.com/go-gost/core v0.3.1
|
||||||
github.com/go-gost/x v0.5.3
|
github.com/go-gost/x v0.5.3
|
||||||
github.com/judwhite/go-svc v1.2.1
|
github.com/judwhite/go-svc v1.2.1
|
||||||
@@ -149,4 +148,3 @@ require (
|
|||||||
|
|
||||||
replace github.com/go-gost/x => ./x
|
replace github.com/go-gost/x => ./x
|
||||||
|
|
||||||
replace github.com/apernet/OpenGFW => ./gfw
|
|
||||||
|
|||||||
+1
-2
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
//"github.com/apernet/OpenGFW/cmd"
|
|
||||||
"log"
|
"log"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
@@ -120,7 +119,7 @@ func main() {
|
|||||||
log := xlogger.NewLogger()
|
log := xlogger.NewLogger()
|
||||||
logger.SetDefault(log)
|
logger.SetDefault(log)
|
||||||
|
|
||||||
wsReporter := socket.StartWebSocketReporterWithConfig(config.Addr, config.Secret, "1.2.2")
|
wsReporter := socket.StartWebSocketReporterWithConfig(config.Addr, config.Secret, config.Http, config.Tls, config.Socks, "1.2.3")
|
||||||
defer wsReporter.Stop()
|
defer wsReporter.Stop()
|
||||||
service.SetHTTPReportURL(config.Addr, config.Secret)
|
service.SetHTTPReportURL(config.Addr, config.Secret)
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ var isSocks = 0
|
|||||||
|
|
||||||
var needWrap = false
|
var needWrap = false
|
||||||
|
|
||||||
|
// SetProtocolBlock sets protocol blocking switches and recomputes wrapper need
|
||||||
|
func SetProtocolBlock(httpOn int, tlsOn int, socksOn int) {
|
||||||
|
isHttp = httpOn
|
||||||
|
isTls = tlsOn
|
||||||
|
isSocks = socksOn
|
||||||
|
needWrap = isTls+isSocks+isHttp > 0
|
||||||
|
}
|
||||||
|
|
||||||
type Option func(opts *options)
|
type Option func(opts *options)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -8,17 +8,20 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync" // 新增:用于管理连接状态的互斥锁
|
"sync" // 新增:用于管理连接状态的互斥锁
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
|
"github.com/go-gost/x/service"
|
||||||
"github.com/go-gost/x/internal/util/crypto"
|
"github.com/go-gost/x/internal/util/crypto"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/shirou/gopsutil/v3/cpu"
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
"github.com/shirou/gopsutil/v3/host"
|
"github.com/shirou/gopsutil/v3/host"
|
||||||
"github.com/shirou/gopsutil/v3/mem"
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
psnet "github.com/shirou/gopsutil/v3/net"
|
psnet "github.com/shirou/gopsutil/v3/net"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SystemInfo 系统信息结构体
|
// SystemInfo 系统信息结构体
|
||||||
@@ -524,6 +527,11 @@ func (w *WebSocketReporter) routeCommand(cmd CommandMessage) {
|
|||||||
response.Type = "TcpPingResponse"
|
response.Type = "TcpPingResponse"
|
||||||
response.Data = tcpPingResult
|
response.Data = tcpPingResult
|
||||||
|
|
||||||
|
// Protocol blocking switches
|
||||||
|
case "SetProtocol":
|
||||||
|
err = w.handleSetProtocol(cmd.Data)
|
||||||
|
response.Type = "SetProtocolResponse"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("未知命令类型: %s", cmd.Type)
|
err = fmt.Errorf("未知命令类型: %s", cmd.Type)
|
||||||
response.Type = "UnknownCommandResponse"
|
response.Type = "UnknownCommandResponse"
|
||||||
@@ -765,6 +773,85 @@ func (w *WebSocketReporter) handleDeleteLimiter(data interface{}) error {
|
|||||||
return deleteLimiter(deleteReq)
|
return deleteLimiter(deleteReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleSetProtocol 处理设置屏蔽协议的命令
|
||||||
|
func (w *WebSocketReporter) handleSetProtocol(data interface{}) error {
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("序列化协议设置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支持 {"http":0/1, "tls":0/1, "socks":0/1}
|
||||||
|
var req struct {
|
||||||
|
HTTP *int `json:"http"`
|
||||||
|
TLS *int `json:"tls"`
|
||||||
|
SOCKS *int `json:"socks"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(jsonData, &req); err != nil {
|
||||||
|
return fmt.Errorf("解析协议设置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取当前值作为默认
|
||||||
|
httpVal, tlsVal, socksVal := 0, 0, 0
|
||||||
|
|
||||||
|
if req.HTTP != nil {
|
||||||
|
if *req.HTTP != 0 && *req.HTTP != 1 {
|
||||||
|
return fmt.Errorf("http 取值必须为0或1")
|
||||||
|
}
|
||||||
|
httpVal = *req.HTTP
|
||||||
|
}
|
||||||
|
if req.TLS != nil {
|
||||||
|
if *req.TLS != 0 && *req.TLS != 1 {
|
||||||
|
return fmt.Errorf("tls 取值必须为0或1")
|
||||||
|
}
|
||||||
|
tlsVal = *req.TLS
|
||||||
|
}
|
||||||
|
if req.SOCKS != nil {
|
||||||
|
if *req.SOCKS != 0 && *req.SOCKS != 1 {
|
||||||
|
return fmt.Errorf("socks 取值必须为0或1")
|
||||||
|
}
|
||||||
|
socksVal = *req.SOCKS
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置至 service,全量传递(未提供的值沿用0)
|
||||||
|
service.SetProtocolBlock(httpVal, tlsVal, socksVal)
|
||||||
|
|
||||||
|
// 同步写入本地 config.json
|
||||||
|
if err := updateLocalConfigJSON(httpVal, tlsVal, socksVal); err != nil {
|
||||||
|
return fmt.Errorf("写入config.json失败: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateLocalConfigJSON 将 http/tls/socks 写入工作目录下的 config.json
|
||||||
|
func updateLocalConfigJSON(httpVal int, tlsVal int, socksVal int) error {
|
||||||
|
path := "config.json"
|
||||||
|
|
||||||
|
// 读取现有配置
|
||||||
|
type LocalConfig struct {
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
Http int `json:"http"`
|
||||||
|
Tls int `json:"tls"`
|
||||||
|
Socks int `json:"socks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg LocalConfig
|
||||||
|
if b, err := os.ReadFile(path); err == nil {
|
||||||
|
_ = json.Unmarshal(b, &cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Http = httpVal
|
||||||
|
cfg.Tls = tlsVal
|
||||||
|
cfg.Socks = socksVal
|
||||||
|
|
||||||
|
// 写回
|
||||||
|
data, err := json.MarshalIndent(cfg, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
// handleCall 处理服务端的call回调消息
|
// handleCall 处理服务端的call回调消息
|
||||||
func (w *WebSocketReporter) handleCall(data interface{}) error {
|
func (w *WebSocketReporter) handleCall(data interface{}) error {
|
||||||
// 解析call数据
|
// 解析call数据
|
||||||
@@ -931,15 +1018,15 @@ func getMemoryInfo() MemoryInfo {
|
|||||||
return memInfo
|
return memInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartWebSocketReporterWithConfig 使用配置启动WebSocket报告器
|
// StartWebSocketReporterWithConfig 使用配置字段启动WebSocket报告器
|
||||||
func StartWebSocketReporterWithConfig(Addr string, Secret string, Version string) *WebSocketReporter {
|
func StartWebSocketReporterWithConfig(addr string, secret string, http int, tls int, socks int, version string) *WebSocketReporter {
|
||||||
|
|
||||||
// 构建包含本机IP的WebSocket URL
|
// 构建包含本机IP的WebSocket URL
|
||||||
var fullURL = "ws://" + Addr + "/system-info?type=1&secret=" + Secret + "&version=" + Version
|
fullURL := "ws://" + addr + "/system-info?type=1&secret=" + secret + "&version=" + version + "&http=" + strconv.Itoa(http) + "&tls=" + strconv.Itoa(tls) + "&socks=" + strconv.Itoa(socks)
|
||||||
|
|
||||||
fmt.Printf("🔗 WebSocket连接URL: %s\n", fullURL)
|
fmt.Printf("🔗 WebSocket连接URL: %s\n", fullURL)
|
||||||
|
|
||||||
reporter := NewWebSocketReporter(fullURL, Secret) // Pass Secret here
|
reporter := NewWebSocketReporter(fullURL, secret)
|
||||||
reporter.Start()
|
reporter.Start()
|
||||||
return reporter
|
return reporter
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ CREATE TABLE `node` (
|
|||||||
`port_sta` int(10) NOT NULL,
|
`port_sta` int(10) NOT NULL,
|
||||||
`port_end` int(10) NOT NULL,
|
`port_end` int(10) NOT NULL,
|
||||||
`version` varchar(100) DEFAULT NULL,
|
`version` varchar(100) DEFAULT NULL,
|
||||||
|
`http` int(10) NOT NULL DEFAULT '0',
|
||||||
|
`tls` int(10) NOT NULL DEFAULT '0',
|
||||||
|
`socks` int(10) NOT NULL DEFAULT '0',
|
||||||
`created_time` bigint(20) NOT NULL,
|
`created_time` bigint(20) NOT NULL,
|
||||||
`updated_time` bigint(20) DEFAULT NULL,
|
`updated_time` bigint(20) DEFAULT NULL,
|
||||||
`status` int(10) NOT NULL
|
`status` int(10) NOT NULL
|
||||||
|
|||||||
@@ -537,6 +537,64 @@ UPDATE \`node\`
|
|||||||
SET \`port_sta\` = 1000, \`port_end\` = 65535
|
SET \`port_sta\` = 1000, \`port_end\` = 65535
|
||||||
WHERE \`port_sta\` IS NULL OR \`port_end\` IS NULL;
|
WHERE \`port_sta\` IS NULL OR \`port_end\` IS NULL;
|
||||||
|
|
||||||
|
-- node 表:添加 http、tls、socks 字段(如果不存在)
|
||||||
|
SET @sql = (
|
||||||
|
SELECT IF(
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'node'
|
||||||
|
AND column_name = 'http'
|
||||||
|
),
|
||||||
|
'ALTER TABLE \`node\` ADD COLUMN \`http\` INT(10) DEFAULT 0 COMMENT "HTTP 服务端口";',
|
||||||
|
'SELECT "Column \`http\` already exists in \`node\`";'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @sql = (
|
||||||
|
SELECT IF(
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'node'
|
||||||
|
AND column_name = 'tls'
|
||||||
|
),
|
||||||
|
'ALTER TABLE \`node\` ADD COLUMN \`tls\` INT(10) DEFAULT 0 COMMENT "TLS 服务端口";',
|
||||||
|
'SELECT "Column \`tls\` already exists in \`node\`";'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @sql = (
|
||||||
|
SELECT IF(
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'node'
|
||||||
|
AND column_name = 'socks'
|
||||||
|
),
|
||||||
|
'ALTER TABLE \`node\` ADD COLUMN \`socks\` INT(10) DEFAULT 0 COMMENT "SOCKS 服务端口";',
|
||||||
|
'SELECT "Column \`socks\` already exists in \`node\`";'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- 为现有节点设置 http、tls、socks 默认值
|
||||||
|
UPDATE \`node\`
|
||||||
|
SET \`http\` = IFNULL(\`http\`, 0),
|
||||||
|
\`tls\` = IFNULL(\`tls\`, 0),
|
||||||
|
\`socks\` = IFNULL(\`socks\`, 0);
|
||||||
|
|
||||||
-- tunnel 表:删除废弃字段(如果存在)
|
-- tunnel 表:删除废弃字段(如果存在)
|
||||||
SET @sql = (
|
SET @sql = (
|
||||||
SELECT IF(
|
SELECT IF(
|
||||||
|
|||||||
@@ -31,4 +31,8 @@ public class NodeUpdateDto {
|
|||||||
@Min(value = 1, message = "结束端口必须大于0")
|
@Min(value = 1, message = "结束端口必须大于0")
|
||||||
@Max(value = 65535, message = "结束端口不能超过65535")
|
@Max(value = 65535, message = "结束端口不能超过65535")
|
||||||
private Integer portEnd;
|
private Integer portEnd;
|
||||||
|
|
||||||
|
private Integer http;
|
||||||
|
private Integer tls;
|
||||||
|
private Integer socks;
|
||||||
}
|
}
|
||||||
@@ -224,6 +224,9 @@ public class WebSocketServer extends TextWebSocketHandler {
|
|||||||
// 客户端节点连接
|
// 客户端节点连接
|
||||||
Long nodeId = Long.valueOf(id);
|
Long nodeId = Long.valueOf(id);
|
||||||
String version = (String) session.getAttributes().get("nodeVersion");
|
String version = (String) session.getAttributes().get("nodeVersion");
|
||||||
|
String http = (String) session.getAttributes().get("http");
|
||||||
|
String tls = (String) session.getAttributes().get("tls");
|
||||||
|
String socks = (String) session.getAttributes().get("socks");
|
||||||
|
|
||||||
log.info("节点 {} 尝试连接,开始处理连接逻辑", nodeId);
|
log.info("节点 {} 尝试连接,开始处理连接逻辑", nodeId);
|
||||||
|
|
||||||
@@ -256,6 +259,16 @@ public class WebSocketServer extends TextWebSocketHandler {
|
|||||||
if (version != null) {
|
if (version != null) {
|
||||||
node.setVersion(version);
|
node.setVersion(version);
|
||||||
}
|
}
|
||||||
|
if (http != null) {
|
||||||
|
node.setHttp(Integer.parseInt(http));
|
||||||
|
}
|
||||||
|
if (tls != null) {
|
||||||
|
node.setTls(Integer.parseInt(tls));
|
||||||
|
}
|
||||||
|
if (socks != null) {
|
||||||
|
node.setSocks(Integer.parseInt(socks));
|
||||||
|
}
|
||||||
|
|
||||||
boolean updateResult = nodeService.updateById(node);
|
boolean updateResult = nodeService.updateById(node);
|
||||||
|
|
||||||
if (updateResult) {
|
if (updateResult) {
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
|
|||||||
String secret = serverHttpRequest.getServletRequest().getParameter("secret");
|
String secret = serverHttpRequest.getServletRequest().getParameter("secret");
|
||||||
String type = serverHttpRequest.getServletRequest().getParameter("type");
|
String type = serverHttpRequest.getServletRequest().getParameter("type");
|
||||||
String version = serverHttpRequest.getServletRequest().getParameter("version");
|
String version = serverHttpRequest.getServletRequest().getParameter("version");
|
||||||
|
String http = serverHttpRequest.getServletRequest().getParameter("http");
|
||||||
|
String tls = serverHttpRequest.getServletRequest().getParameter("tls");
|
||||||
|
String socks = serverHttpRequest.getServletRequest().getParameter("socks");
|
||||||
if (Objects.equals(type, "1")) {
|
if (Objects.equals(type, "1")) {
|
||||||
System.out.println("type: " + type + " - version: " + version + " - secret: " + secret + " - IP: " + getClientIp(request));
|
System.out.println("type: " + type + " - version: " + version + " - secret: " + secret + " - IP: " + getClientIp(request));
|
||||||
Node node = nodeService.getOne(new QueryWrapper<Node>().eq("secret", secret));
|
Node node = nodeService.getOne(new QueryWrapper<Node>().eq("secret", secret));
|
||||||
@@ -48,6 +51,9 @@ public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
|
|||||||
attributes.put("id", node.getId());
|
attributes.put("id", node.getId());
|
||||||
attributes.put("nodeSecret", secret);
|
attributes.put("nodeSecret", secret);
|
||||||
attributes.put("nodeVersion", version);
|
attributes.put("nodeVersion", version);
|
||||||
|
attributes.put("http",http);
|
||||||
|
attributes.put("tls",tls);
|
||||||
|
attributes.put("socks",socks);
|
||||||
log.info("节点 {} 通过验证,版本: {}", node.getId(), version);
|
log.info("节点 {} 通过验证,版本: {}", node.getId(), version);
|
||||||
// 不在这里更新状态,等到连接建立后再统一更新
|
// 不在这里更新状态,等到连接建立后再统一更新
|
||||||
}else {
|
}else {
|
||||||
|
|||||||
@@ -32,4 +32,10 @@ public class Node extends BaseEntity {
|
|||||||
|
|
||||||
private Integer portEnd;
|
private Integer portEnd;
|
||||||
|
|
||||||
|
private Integer http;
|
||||||
|
|
||||||
|
private Integer tls;
|
||||||
|
|
||||||
|
private Integer socks;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package com.admin.service.impl;
|
|||||||
|
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.admin.common.dto.GostDto;
|
||||||
import com.admin.common.dto.NodeDto;
|
import com.admin.common.dto.NodeDto;
|
||||||
import com.admin.common.dto.NodeUpdateDto;
|
import com.admin.common.dto.NodeUpdateDto;
|
||||||
import com.admin.common.lang.R;
|
import com.admin.common.lang.R;
|
||||||
|
import com.admin.common.utils.WebSocketServer;
|
||||||
import com.admin.entity.Node;
|
import com.admin.entity.Node;
|
||||||
import com.admin.entity.Tunnel;
|
import com.admin.entity.Tunnel;
|
||||||
import com.admin.entity.ViteConfig;
|
import com.admin.entity.ViteConfig;
|
||||||
@@ -13,6 +15,7 @@ import com.admin.mapper.TunnelMapper;
|
|||||||
import com.admin.service.NodeService;
|
import com.admin.service.NodeService;
|
||||||
import com.admin.service.TunnelService;
|
import com.admin.service.TunnelService;
|
||||||
import com.admin.service.ViteConfigService;
|
import com.admin.service.ViteConfigService;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
@@ -22,6 +25,8 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,10 +119,34 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
|||||||
@Override
|
@Override
|
||||||
public R updateNode(NodeUpdateDto nodeUpdateDto) {
|
public R updateNode(NodeUpdateDto nodeUpdateDto) {
|
||||||
// 1. 验证节点是否存在
|
// 1. 验证节点是否存在
|
||||||
if (!isNodeExists(nodeUpdateDto.getId())) {
|
Node node = this.getById(nodeUpdateDto.getId());
|
||||||
|
if (node == null) {
|
||||||
return R.err(ERROR_NODE_NOT_FOUND);
|
return R.err(ERROR_NODE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//1.1 如果节点在线 且传入更新的 http/tls/socks 任意一项与数据库不一致,则通过 WS 通知节点更新设置
|
||||||
|
boolean online = node.getStatus() != null && node.getStatus() == 1;
|
||||||
|
Integer newHttp = nodeUpdateDto.getHttp();
|
||||||
|
Integer newTls = nodeUpdateDto.getTls();
|
||||||
|
Integer newSocks = nodeUpdateDto.getSocks();
|
||||||
|
|
||||||
|
boolean httpChanged = newHttp != null && !newHttp.equals(node.getHttp());
|
||||||
|
boolean tlsChanged = newTls != null && !newTls.equals(node.getTls());
|
||||||
|
boolean socksChanged = newSocks != null && !newSocks.equals(node.getSocks());
|
||||||
|
|
||||||
|
if (online && (httpChanged || tlsChanged || socksChanged)) {
|
||||||
|
JSONObject req = new JSONObject();
|
||||||
|
req.put("http", newHttp);
|
||||||
|
req.put("tls", newTls);
|
||||||
|
req.put("socks", newSocks);
|
||||||
|
|
||||||
|
GostDto gostResult = WebSocketServer.send_msg(node.getId(), req, "SetProtocol");
|
||||||
|
if (!Objects.equals(gostResult.getMsg(), "OK")){
|
||||||
|
return R.err(gostResult.getMsg());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 2. 构建更新对象并执行更新
|
// 2. 构建更新对象并执行更新
|
||||||
Node updateNode = buildUpdateNode(nodeUpdateDto);
|
Node updateNode = buildUpdateNode(nodeUpdateDto);
|
||||||
boolean result = this.updateById(updateNode);
|
boolean result = this.updateById(updateNode);
|
||||||
@@ -153,7 +182,8 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
|||||||
@Override
|
@Override
|
||||||
public R deleteNode(Long id) {
|
public R deleteNode(Long id) {
|
||||||
// 1. 验证节点是否存在
|
// 1. 验证节点是否存在
|
||||||
if (!isNodeExists(id)) {
|
Node node = this.getById(id);
|
||||||
|
if (node == null) {
|
||||||
return R.err(ERROR_NODE_NOT_FOUND);
|
return R.err(ERROR_NODE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +255,9 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
|||||||
node.setServerIp(nodeUpdateDto.getServerIp());
|
node.setServerIp(nodeUpdateDto.getServerIp());
|
||||||
node.setPortSta(nodeUpdateDto.getPortSta());
|
node.setPortSta(nodeUpdateDto.getPortSta());
|
||||||
node.setPortEnd(nodeUpdateDto.getPortEnd());
|
node.setPortEnd(nodeUpdateDto.getPortEnd());
|
||||||
|
node.setHttp(nodeUpdateDto.getHttp());
|
||||||
|
node.setTls(nodeUpdateDto.getTls());
|
||||||
|
node.setSocks(nodeUpdateDto.getSocks());
|
||||||
// 验证端口范围
|
// 验证端口范围
|
||||||
validatePortRange(node.getPortSta(), node.getPortEnd());
|
validatePortRange(node.getPortSta(), node.getPortEnd());
|
||||||
|
|
||||||
@@ -242,15 +274,6 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
|||||||
nodeList.forEach(node -> node.setSecret(null));
|
nodeList.forEach(node -> node.setSecret(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查节点是否存在
|
|
||||||
*
|
|
||||||
* @param nodeId 节点ID
|
|
||||||
* @return 节点是否存在
|
|
||||||
*/
|
|
||||||
private boolean isNodeExists(Long nodeId) {
|
|
||||||
return this.getById(nodeId) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查节点使用情况
|
* 检查节点使用情况
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
VITE_API_BASE=http://192.168.100.11:6365
|
VITE_API_BASE=http://127.0.0.1:6365
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export type SiteConfig = typeof siteConfig;
|
|||||||
|
|
||||||
// 缓存相关常量
|
// 缓存相关常量
|
||||||
const CACHE_PREFIX = 'vite_config_';
|
const CACHE_PREFIX = 'vite_config_';
|
||||||
const VERSION = "1.4.0";
|
const VERSION = "1.4.1";
|
||||||
const APP_VERSION = "1.0.3";
|
const APP_VERSION = "1.0.3";
|
||||||
|
|
||||||
const getInitialConfig = () => {
|
const getInitialConfig = () => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Input } from "@heroui/input";
|
|||||||
import { Textarea } from "@heroui/input";
|
import { Textarea } from "@heroui/input";
|
||||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/modal";
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/modal";
|
||||||
import { Chip } from "@heroui/chip";
|
import { Chip } from "@heroui/chip";
|
||||||
|
import { Switch } from "@heroui/switch";
|
||||||
import { Spinner } from "@heroui/spinner";
|
import { Spinner } from "@heroui/spinner";
|
||||||
import { Alert } from "@heroui/alert";
|
import { Alert } from "@heroui/alert";
|
||||||
import { Progress } from "@heroui/progress";
|
import { Progress } from "@heroui/progress";
|
||||||
@@ -28,6 +29,9 @@ interface Node {
|
|||||||
portSta: number;
|
portSta: number;
|
||||||
portEnd: number;
|
portEnd: number;
|
||||||
version?: string;
|
version?: string;
|
||||||
|
http?: number; // 0 关 1 开
|
||||||
|
tls?: number; // 0 关 1 开
|
||||||
|
socks?: number; // 0 关 1 开
|
||||||
status: number; // 1: 在线, 0: 离线
|
status: number; // 1: 在线, 0: 离线
|
||||||
connectionStatus: 'online' | 'offline';
|
connectionStatus: 'online' | 'offline';
|
||||||
systemInfo?: {
|
systemInfo?: {
|
||||||
@@ -49,6 +53,9 @@ interface NodeForm {
|
|||||||
serverIp: string;
|
serverIp: string;
|
||||||
portSta: number;
|
portSta: number;
|
||||||
portEnd: number;
|
portEnd: number;
|
||||||
|
http: number; // 0 关 1 开
|
||||||
|
tls: number; // 0 关 1 开
|
||||||
|
socks: number; // 0 关 1 开
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NodePage() {
|
export default function NodePage() {
|
||||||
@@ -61,13 +68,18 @@ export default function NodePage() {
|
|||||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
const [nodeToDelete, setNodeToDelete] = useState<Node | null>(null);
|
const [nodeToDelete, setNodeToDelete] = useState<Node | null>(null);
|
||||||
|
const [protocolDisabled, setProtocolDisabled] = useState(false);
|
||||||
|
const [protocolDisabledReason, setProtocolDisabledReason] = useState('');
|
||||||
const [form, setForm] = useState<NodeForm>({
|
const [form, setForm] = useState<NodeForm>({
|
||||||
id: null,
|
id: null,
|
||||||
name: '',
|
name: '',
|
||||||
ipString: '',
|
ipString: '',
|
||||||
serverIp: '',
|
serverIp: '',
|
||||||
portSta: 1000,
|
portSta: 1000,
|
||||||
portEnd: 65535
|
portEnd: 65535,
|
||||||
|
http: 0,
|
||||||
|
tls: 0,
|
||||||
|
socks: 0
|
||||||
});
|
});
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
@@ -404,6 +416,8 @@ export default function NodePage() {
|
|||||||
setIsEdit(false);
|
setIsEdit(false);
|
||||||
setDialogVisible(true);
|
setDialogVisible(true);
|
||||||
resetForm();
|
resetForm();
|
||||||
|
setProtocolDisabled(true);
|
||||||
|
setProtocolDisabledReason('节点未在线,等待节点上线后再设置');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 编辑节点
|
// 编辑节点
|
||||||
@@ -416,8 +430,14 @@ export default function NodePage() {
|
|||||||
ipString: node.ip ? node.ip.split(',').map(ip => ip.trim()).join('\n') : '',
|
ipString: node.ip ? node.ip.split(',').map(ip => ip.trim()).join('\n') : '',
|
||||||
serverIp: node.serverIp || '',
|
serverIp: node.serverIp || '',
|
||||||
portSta: node.portSta,
|
portSta: node.portSta,
|
||||||
portEnd: node.portEnd
|
portEnd: node.portEnd,
|
||||||
|
http: typeof node.http === 'number' ? node.http : 1,
|
||||||
|
tls: typeof node.tls === 'number' ? node.tls : 1,
|
||||||
|
socks: typeof node.socks === 'number' ? node.socks : 1
|
||||||
});
|
});
|
||||||
|
const offline = node.connectionStatus !== 'online';
|
||||||
|
setProtocolDisabled(offline);
|
||||||
|
setProtocolDisabledReason(offline ? '节点未在线,等待节点上线后再设置' : '');
|
||||||
setDialogVisible(true);
|
setDialogVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -514,7 +534,10 @@ export default function NodePage() {
|
|||||||
ip: ipString,
|
ip: ipString,
|
||||||
serverIp: form.serverIp,
|
serverIp: form.serverIp,
|
||||||
portSta: form.portSta,
|
portSta: form.portSta,
|
||||||
portEnd: form.portEnd
|
portEnd: form.portEnd,
|
||||||
|
http: form.http,
|
||||||
|
tls: form.tls,
|
||||||
|
socks: form.socks
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await apiCall(data);
|
const res = await apiCall(data);
|
||||||
@@ -530,7 +553,10 @@ export default function NodePage() {
|
|||||||
ip: ipString,
|
ip: ipString,
|
||||||
serverIp: form.serverIp,
|
serverIp: form.serverIp,
|
||||||
portSta: form.portSta,
|
portSta: form.portSta,
|
||||||
portEnd: form.portEnd
|
portEnd: form.portEnd,
|
||||||
|
http: form.http,
|
||||||
|
tls: form.tls,
|
||||||
|
socks: form.socks
|
||||||
} : n
|
} : n
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
@@ -554,7 +580,10 @@ export default function NodePage() {
|
|||||||
ipString: '',
|
ipString: '',
|
||||||
serverIp: '',
|
serverIp: '',
|
||||||
portSta: 1000,
|
portSta: 1000,
|
||||||
portEnd: 65535
|
portEnd: 65535,
|
||||||
|
http: 0,
|
||||||
|
tls: 0,
|
||||||
|
socks: 0
|
||||||
});
|
});
|
||||||
setErrors({});
|
setErrors({});
|
||||||
};
|
};
|
||||||
@@ -869,9 +898,84 @@ export default function NodePage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 屏蔽协议 */}
|
||||||
|
<div className="mt-1">
|
||||||
|
<div className="text-sm font-medium text-default-700">屏蔽协议</div>
|
||||||
|
<div className="text-xs text-default-500 mb-2">开启开关以屏蔽对应协议</div>
|
||||||
|
{protocolDisabled && (
|
||||||
|
<Alert
|
||||||
|
color="warning"
|
||||||
|
variant="flat"
|
||||||
|
description={protocolDisabledReason || '等待节点上线后再设置'}
|
||||||
|
className="mb-2"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={`grid grid-cols-1 sm:grid-cols-3 gap-3 bg-default-50 dark:bg-default-100 p-3 rounded-md border border-default-200 dark:border-default-100/30 ${protocolDisabled ? 'opacity-70' : ''}`}>
|
||||||
|
{/* HTTP tile */}
|
||||||
|
<div className="px-3 py-3 rounded-lg bg-white dark:bg-default-50 border border-default-200 dark:border-default-100/30 hover:border-primary-200 transition-colors">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<svg className="w-4 h-4 text-default-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M2 10h20"/></svg>
|
||||||
|
<div className="text-sm font-medium text-default-700">HTTP</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-xs text-default-500">禁用/启用</div>
|
||||||
|
<Switch
|
||||||
|
size="sm"
|
||||||
|
isSelected={form.http === 1}
|
||||||
|
isDisabled={protocolDisabled}
|
||||||
|
onValueChange={(v) => setForm(prev => ({ ...prev, http: v ? 1 : 0 }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-xs text-default-400">{form.http === 1 ? '已开启' : '已关闭'}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* TLS tile */}
|
||||||
|
<div className="px-3 py-3 rounded-lg bg-white dark:bg-default-50 border border-default-200 dark:border-default-100/30 hover:border-primary-200 transition-colors">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<svg className="w-4 h-4 text-default-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 10V7a6 6 0 1 1 12 0v3"/><rect x="4" y="10" width="16" height="10" rx="2"/></svg>
|
||||||
|
<div className="text-sm font-medium text-default-700">TLS</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-xs text-default-500">禁用/启用</div>
|
||||||
|
<Switch
|
||||||
|
size="sm"
|
||||||
|
isSelected={form.tls === 1}
|
||||||
|
isDisabled={protocolDisabled}
|
||||||
|
onValueChange={(v) => setForm(prev => ({ ...prev, tls: v ? 1 : 0 }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-xs text-default-400">{form.tls === 1 ? '已开启' : '已关闭'}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* SOCKS tile */}
|
||||||
|
<div className="px-3 py-3 rounded-lg bg-white dark:bg-default-50 border border-default-200 dark:border-default-100/30 hover:border-primary-200 transition-colors">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<svg className="w-4 h-4 text-default-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||||
|
<div className="text-sm font-medium text-default-700">SOCKS</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-xs text-default-500">禁用/启用</div>
|
||||||
|
<Switch
|
||||||
|
size="sm"
|
||||||
|
isSelected={form.socks === 1}
|
||||||
|
isDisabled={protocolDisabled}
|
||||||
|
onValueChange={(v) => setForm(prev => ({ ...prev, socks: v ? 1 : 0 }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-xs text-default-400">{form.socks === 1 ? '已开启' : '已关闭'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
color="danger"
|
||||||
|
variant="flat"
|
||||||
|
description="请不要在出口节点执行屏蔽协议,否则可能影响转发;屏蔽协议仅需在入口节点执行。"
|
||||||
|
className="mt-3"
|
||||||
|
/>
|
||||||
|
|
||||||
<Alert
|
<Alert
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
|
|||||||
Reference in New Issue
Block a user