cn.hutool
diff --git a/src/main/java/top/octopusyan/YanFrpApplication.java b/src/main/java/top/octopusyan/YanFrpApplication.java
index 51b7ea7..a7ab128 100644
--- a/src/main/java/top/octopusyan/YanFrpApplication.java
+++ b/src/main/java/top/octopusyan/YanFrpApplication.java
@@ -86,12 +86,7 @@ public class YanFrpApplication extends Application {
FXMLLoader fxmlLoader = FxmlUtil.init("/fxml/login.fxml");
StackPane root = fxmlLoader.load();//底层面板
stage.initStyle(StageStyle.TRANSPARENT);
- Scene scene = new Scene(
- root,
- root.getPrefWidth() + 20,
- root.getPrefHeight() + 20,
- Color.TRANSPARENT
- );
+ Scene scene = new Scene(root, root.getPrefWidth() + 20, root.getPrefHeight() + 20, Color.TRANSPARENT);
scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm());
stage.setScene(scene);
stage.show();
diff --git a/src/main/java/top/octopusyan/base/BaseController.java b/src/main/java/top/octopusyan/base/BaseController.java
index a35ef55..bc88f64 100644
--- a/src/main/java/top/octopusyan/base/BaseController.java
+++ b/src/main/java/top/octopusyan/base/BaseController.java
@@ -7,6 +7,7 @@ import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.Button;
+import javafx.scene.control.Label;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
@@ -17,6 +18,7 @@ import top.octopusyan.manager.FrpManager;
import top.octopusyan.model.ApplicatonStore;
import top.octopusyan.utils.FxmlUtil;
import top.octopusyan.utils.Loading;
+import top.octopusyan.utils.TooltipUtil;
import java.io.IOException;
import java.net.URL;
@@ -36,6 +38,8 @@ public abstract class BaseController implements Initializable {
private volatile Loading loading;
+ protected TooltipUtil tooltipUtil;
+
public void jumpTo(BaseController
controller) throws IOException {
FXMLLoader fxmlLoader = FxmlUtil.init(controller.getRootFxmlPath());
@@ -77,11 +81,14 @@ public abstract class BaseController
implements Initializable {
});
}
+ // app 版本信息
+ if (getAppVersionLabel() != null) getAppVersionLabel().setText("version : v" + ApplicatonStore.APP_VERSION);
+
// 这个位置的左边第一个 JFXBtn 会莫名其妙会的焦点效果,启动时禁用焦点,取消按钮效果
- getFirstBtn().setDisableVisualFocus(true);
+ if (getFirstBtn() != null) getFirstBtn().setDisableVisualFocus(true);
// 最小化窗口
- getMinimizeBtn().setOnMouseClicked(event -> {
+ if (getMinimizeBtn() != null) getMinimizeBtn().setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
Stage stage = (Stage) getRootPanel().getScene().getWindow();
stage.setIconified(true);
@@ -89,7 +96,7 @@ public abstract class BaseController
implements Initializable {
});
// 关闭窗口
- getClooseBtn().setOnMouseClicked(event -> {
+ if (getClooseBtn() != null) getClooseBtn().setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
onDestroy();
}
@@ -111,7 +118,7 @@ public abstract class BaseController
implements Initializable {
public void showLoading(String message) {
if (loading == null) loading = new Loading((Stage) getRootPanel().getScene().getWindow());
- if (!StringUtils.isEmpty(message)) loading.showMessage(message);
+ if (StringUtils.isNotEmpty(message)) loading.showMessage(message);
loading.show();
}
@@ -131,6 +138,11 @@ public abstract class BaseController
implements Initializable {
loading.closeStage();
}
+ protected TooltipUtil getTooltipUtil() {
+ if (tooltipUtil == null) tooltipUtil = TooltipUtil.getInstance(getRootPanel());
+ return tooltipUtil;
+ }
+
/**
* 窗口拖拽设置
*
@@ -153,6 +165,11 @@ public abstract class BaseController
implements Initializable {
@NotNull
public abstract String getRootFxmlPath();
+ /**
+ * App版本信息标签
+ */
+ public abstract Label getAppVersionLabel();
+
/**
* 关闭按钮
*/
diff --git a/src/main/java/top/octopusyan/config/ProxyConfig.java b/src/main/java/top/octopusyan/config/ProxyConfig.java
index 9fff32d..68b0a15 100644
--- a/src/main/java/top/octopusyan/config/ProxyConfig.java
+++ b/src/main/java/top/octopusyan/config/ProxyConfig.java
@@ -2,6 +2,7 @@ package top.octopusyan.config;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
+import top.octopusyan.model.ProxySetupModel;
import java.util.Arrays;
import java.util.HashMap;
@@ -29,7 +30,6 @@ public class ProxyConfig {
typePort.put("http", 80);
typePort.put("https", 80);
- typePort.put("ssh", 22);
typePort.put("tcp", 0);
typePort.put("udp", 0);
}
@@ -57,22 +57,13 @@ public class ProxyConfig {
}
public enum ProxyType {
- HTTP("http"),
- HTTPS("https"),
- SSH("tcp"),
- TCP("tcp"),
- UDP("udp"),
+ HTTP,
+ HTTPS,
+ TCP,
+ UDP,
+ STCP,
+ XTCP,
;
-
- private final String type;
-
- public String getType() {
- return type;
- }
-
- ProxyType(String type) {
- this.type = type;
- }
}
public static String getServerPath(String serverName) {
@@ -80,8 +71,8 @@ public class ProxyConfig {
}
public static int getServerNode(String serverName) {
- for (ProxyServer server : Arrays.asList(ProxyServer.values())) {
- if(server.serverName.equals(serverName)) return server.value;
+ for (ProxyServer server : ProxyServer.values()) {
+ if (server.serverName.equals(serverName)) return server.value;
}
return 3;
@@ -89,6 +80,7 @@ public class ProxyConfig {
/**
* 获取服务名称
+ *
* @param node 服务器标签
*/
public static String getServerName(int node) {
@@ -97,8 +89,17 @@ public class ProxyConfig {
return proxyServer.getServerName();
}
+ public static boolean isHttp(ProxySetupModel model) {
+ return ProxyConfig.getTypeIndex(model.getProxyType()) < 2;
+ }
+
+ public static boolean isP2p(ProxySetupModel model) {
+ return ProxyConfig.getTypeIndex(model.getProxyType()) > 3;
+ }
+
/**
* 获取服务器IP地址
+ *
* @param serverName 服务器名称
*/
public static String getServerIP(String serverName) {
@@ -107,6 +108,7 @@ public class ProxyConfig {
/**
* 获取服务器IP地址
+ *
* @param node 服务器标签
*/
public static String getServerIP(int node) {
@@ -115,10 +117,12 @@ public class ProxyConfig {
/**
* 获取代理类型默认端口
+ *
* @param type 类型名称
*/
public static Integer getTypePort(String type) {
- return typePort.get(type);
+ Integer port = typePort.get(type);
+ return port == null ? 0 : port;
}
public static Integer getTypeIndex(String type) {
diff --git a/src/main/java/top/octopusyan/config/TextValidate.java b/src/main/java/top/octopusyan/config/TextValidate.java
index 4d24786..c0ea21c 100644
--- a/src/main/java/top/octopusyan/config/TextValidate.java
+++ b/src/main/java/top/octopusyan/config/TextValidate.java
@@ -48,6 +48,14 @@ public class TextValidate {
* 域名为空检查
*/
public static RequiredFieldValidator DomainRequired = new RequiredFieldValidator("域名不能为空!");
+ /**
+ * 访问密码为空检查
+ */
+ public static RequiredFieldValidator p2pPwdRequired = new RequiredFieldValidator("访问密码不能为空!");
+ /**
+ * 访问服务名为空检查
+ */
+ public static RequiredFieldValidator p2pServerNameRequired = new RequiredFieldValidator("访问服务名称不能为空!");
/**
* 端口为空检查
*/
@@ -60,6 +68,18 @@ public class TextValidate {
* IP为空检查
*/
public static RequiredFieldValidator IpRequired = new RequiredFieldValidator("本地IP不能为空");
+ /**
+ * http访问用户名格式检查
+ */
+ public static final RegexValidator HttpUserFormat = new RegexValidator("用户名格式错误");
+ /**
+ * http访问密码格式检查
+ */
+ public static final RegexValidator HttpPwdFormat = new RegexValidator("密码格式错误");
+ /**
+ * http访问密码格式检查
+ */
+ public static final RegexValidator P2pPwdFormat = new RegexValidator("密码格式错误");
/**
* IP格式检查
*/
@@ -69,7 +89,11 @@ public class TextValidate {
EmailFormat.setRegexPattern("^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$");
AccoountValidator.setRegexPattern("^[a-zA-Z0-9_-]*$");
PortFormat.setRegexPattern("^([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-4]\\d{4}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$");
- IpFormat.setRegexPattern("^(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])$");
+ IpFormat.setRegexPattern("^(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\" +
+ ".(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])$");
+ HttpUserFormat.setRegexPattern("^[a-zA-Z0-9_-]*$");
+ HttpPwdFormat.setRegexPattern("^[a-zA-Z0-9_-]*$");
+ P2pPwdFormat.setRegexPattern("^[a-zA-Z0-9_-]{3,18}$");
}
/**
@@ -103,7 +127,7 @@ public class TextValidate {
@Override
protected void eval() {
if (!DomainUtil.isCustomize(model.get())) {
- if (DomainUtil.isHttp(model)) {
+ if (ProxyConfig.isHttp(model)) {
// http / https
boolean matches = Pattern.compile("^[a-zA-Z0-9_-]{3,18}$").matcher(model.getDomain()).matches();
hasErrors.set(!matches);
@@ -129,7 +153,7 @@ public class TextValidate {
@Override
protected void eval() {
setRegexPattern("^[a-zA-Z0-9_-]{3,18}$");
- if (DomainUtil.isHttp(model) && !DomainUtil.isCustomize(model.get())) {
+ if (ProxyConfig.isHttp(model) && !DomainUtil.isCustomize(model.get())) {
super.eval();
} else {
hasErrors.set(false);
diff --git a/src/main/java/top/octopusyan/controller/LoginController.java b/src/main/java/top/octopusyan/controller/LoginController.java
index f415596..7294be8 100644
--- a/src/main/java/top/octopusyan/controller/LoginController.java
+++ b/src/main/java/top/octopusyan/controller/LoginController.java
@@ -9,6 +9,8 @@ import javafx.beans.property.SimpleStringProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextInputDialog;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.AnchorPane;
@@ -76,8 +78,10 @@ public class LoginController extends BaseController implements Initia
public JFXCheckBox rememberCBox;
@FXML
public JFXButton findpassBtn;
+ @FXML
+ public Label appVersionLabel;
- private SimpleStringProperty tmppwd = new SimpleStringProperty();
+ private final SimpleStringProperty tmpPwd = new SimpleStringProperty();
@Override
public boolean dragWindow() {
@@ -95,6 +99,11 @@ public class LoginController extends BaseController implements Initia
return "/fxml/login.fxml";
}
+ @Override
+ public Label getAppVersionLabel() {
+ return appVersionLabel;
+ }
+
@Override
public Button getClooseBtn() {
return closeBtn;
@@ -116,22 +125,22 @@ public class LoginController extends BaseController implements Initia
// 账号
accountTextField.textProperty().bindBidirectional(ApplicatonStore.accountProperty());
// 密码
- if (ApplicatonStore.isRememberMe()) tmppwd.set(ApplicatonStore.getPassword());
+ if (ApplicatonStore.isRememberMe()) tmpPwd.set(ApplicatonStore.getPassword());
// 自动登录
autoLoginCBox.selectedProperty().bindBidirectional(ApplicatonStore.autoLoginProperty());
// 记住我
rememberCBox.selectedProperty().bindBidirectional(ApplicatonStore.rememberMeProperty());
- passwordTextField.textProperty().bindBidirectional(tmppwd);
- seePwdTextField.textProperty().bindBidirectional(tmppwd);
+ passwordTextField.textProperty().bindBidirectional(tmpPwd);
+ seePwdTextField.textProperty().bindBidirectional(tmpPwd);
}
// 隐藏展示密码框
private AnchorPane pwdParent;
- private AlertUtil.Builder findpassAlert;
+ private AlertUtil.Builder findpassAlert;
@Override
public void initViewStyle() {
@@ -169,7 +178,7 @@ public class LoginController extends BaseController implements Initia
// 注册
registerBtn.setOnMouseClicked(event -> {
- ApplicatonStore.setPassword(tmppwd.get());
+ ApplicatonStore.setPassword(tmpPwd.get());
try {
jumpTo(new RegisterController());
} catch (IOException e) {
@@ -183,7 +192,7 @@ public class LoginController extends BaseController implements Initia
pwdParent.getChildren().remove(isHide ? passwordTextField : seePwdTextField);
pwdParent.getChildren().add(1, isHide ? seePwdTextField : passwordTextField);
- (isHide ? seePwdTextField : passwordTextField).setText(tmppwd.get());
+ (isHide ? seePwdTextField : passwordTextField).setText(tmpPwd.get());
seePwdIcon.setIconColor(isHide ? Color.BLUE : Color.BLACK);
});
@@ -206,8 +215,8 @@ public class LoginController extends BaseController implements Initia
// 自动登录
if ((ApplicatonStore.isAutoLogin() || ApplicatonStore.isRegisterSuccess()) &&
- !StringUtils.isEmpty(ApplicatonStore.getAccount()) &&
- !StringUtils.isEmpty(ApplicatonStore.getPassword())
+ StringUtils.isNotEmpty(ApplicatonStore.getAccount()) &&
+ StringUtils.isNotEmpty(ApplicatonStore.getPassword())
) {
login();
}
@@ -250,7 +259,7 @@ public class LoginController extends BaseController implements Initia
if (pwdValidate && accountValidate)
EasyHttp.builder()
.api(Api.Login)
- .param(new LoginParam(accountTextField.getText(), tmppwd.get()))
+ .param(new LoginParam(accountTextField.getText(), tmpPwd.get()))
.request(new OnHttpListener() {
@Override
public void onSucceed(String result) {
@@ -262,7 +271,7 @@ public class LoginController extends BaseController implements Initia
// 登录成功
setCsrf();
// 记住我
- ApplicatonStore.rememberMe(tmppwd.get());
+ ApplicatonStore.rememberMe(tmpPwd.get());
// 跳转
Platform.runLater(() -> {
try {
diff --git a/src/main/java/top/octopusyan/controller/MainController.java b/src/main/java/top/octopusyan/controller/MainController.java
index 728b84f..92d43ee 100644
--- a/src/main/java/top/octopusyan/controller/MainController.java
+++ b/src/main/java/top/octopusyan/controller/MainController.java
@@ -1,18 +1,25 @@
package top.octopusyan.controller;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
import com.jfoenix.controls.*;
+import com.jfoenix.validation.base.ValidatorBase;
import javafx.application.Platform;
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
-import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
+import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
+import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@@ -27,12 +34,8 @@ import top.octopusyan.manager.ProxyManager;
import top.octopusyan.manager.http.request.ProxySetup;
import top.octopusyan.model.ApplicatonStore;
import top.octopusyan.model.ProxySetupModel;
-import top.octopusyan.utils.AlertUtil;
-import top.octopusyan.utils.DomainUtil;
+import top.octopusyan.utils.*;
-import java.awt.*;
-import java.awt.datatransfer.Clipboard;
-import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.util.List;
import java.util.*;
@@ -53,50 +56,57 @@ public class MainController extends BaseController implements Initial
public static final String PROXY_LIST_ITEM_CLOSE_CLASS = "proxyListItem-close";
public static final String PROXY_LIST_ITEM_SELECT_CLASS = "proxyListItem-select";
- @FXML
+
+ /* 窗口通用控件 */
public StackPane root;
+ public JFXButton minimizeBtn, closeBtn;
+ public Label appVersionLabel;
- @FXML
- public JFXButton closeBtn, minimizeBtn;
-
- @FXML
- public JFXButton startProxyBtn, addProxyBtn;
- @FXML
- public JFXComboBox proxyProtocolComboBox;
- @FXML
- public JFXButton customizeDomainBtn;
- @FXML
- public JFXRadioButton openProxyRBtn, closeProxyRBtn;
- @FXML
- public JFXTextField domainTextField, domainSuffixTextField;
- @FXML
- public JFXTextField proxyNameTextField, localHostTextField, localPortTextField;
- @FXML
+ /* 隧道设置控件 */
public JFXComboBox proxyServerComboBox;
- @FXML
- public JFXTabPane tabPane;
- @FXML
- public JFXListView proxyListView;
- @FXML
- public Label domainHtinTextField;
- @FXML
+ public JFXButton resetProxyBtn;
+ public JFXTextField proxyNameTextField;
+ public JFXRadioButton openProxyRBtn, closeProxyRBtn;
+ public JFXComboBox proxyProtocolComboBox;
+ public JFXRadioButton serverProviderRBtn, serverVisitorRBtn;
+ public Label localHostLabel;
+ public JFXTextField localHostTextField, localPortTextField;
public HBox domainPane;
- @FXML
- public JFXButton resetProxyBtn, clearLogBtn, copyDomainBtn;
- @FXML
- public Tab proxyLogPane;
- @FXML
- public Hyperlink domainLink;
- @FXML
+ public JFXTextField domainTextField, domainSuffixTextField;
+ public JFXButton customizeDomainBtn;
+ public HBox httpUserPane;
+ public JFXTextField httpUserTextField;
+ public HBox httpPwdPane;
+ public JFXTextField httpPwdTextField;
+ public HBox p2pRolePane, p2pPwdPane, p2pRoleView, serverNamePane;
+ public JFXButton copyP2pConfig, importP2pConfig;
+ public JFXTextField p2pPwdTextField, serverNameTextField;
+ public JFXButton proxyStatusTip, httpUserTip, proxyTypeTip, p2pPwdTip, serverNameTip;
public HBox proxyStatusPane;
- @FXML
- public JFXButton logoutBtn;
+ public Label runingLabel;
+ public Hyperlink domainLink;
+ public JFXButton copyDomainBtn;
- private final ToggleGroup openProxyGroup = new ToggleGroup();
- private final SimpleBooleanProperty customizeDomain = new SimpleBooleanProperty(false);
- private final Map frpUtilMap = new HashMap<>();
+ /* 隧道列表控件 */
+ public JFXListView proxyListView;
+ public JFXButton addProxyBtn;
+
+ /* 日志帮助面板控件 */
+ public JFXTabPane tabPane;
+ public Tab proxyLogPane;
+ public JFXButton clearLogBtn;
+
+ /* 功能控件 */
+ public JFXButton startProxyBtn, logoutBtn;
+
+ /* 自定义属性 */
+ private ObservableList children;
+ private final ProxySetupModel proxySetupModel = new ProxySetupModel(ProxyManager.initProxy(null));
+ private final BooleanProperty customizeDomain = new SimpleBooleanProperty(false);
private final Map userProxy = new HashMap<>();
- private ProxySetupModel proxySetupModel;
+ private final Map frpUtilMap = new HashMap<>();
+ private final ToggleGroup openProxyGroup = new ToggleGroup();
+ private final ToggleGroup serverRoleGroup = new ToggleGroup();
private final SimpleBooleanProperty setup = new SimpleBooleanProperty(false);
@Override
@@ -115,6 +125,11 @@ public class MainController extends BaseController implements Initial
return "/fxml/main.fxml";
}
+ @Override
+ public Label getAppVersionLabel() {
+ return appVersionLabel;
+ }
+
@Override
public Button getClooseBtn() {
return closeBtn;
@@ -132,18 +147,15 @@ public class MainController extends BaseController implements Initial
@Override
public void initData() {
- // 初始化视图模型
- ProxySetup proxySetup = ProxyManager.initProxy(null);
- proxySetupModel = new ProxySetupModel(proxySetup);
// 初始化用户隧道列表
- setProxyList(Collections.singletonList(proxySetup));
+ setProxyList(Collections.singletonList(proxySetupModel.get()));
// 重置隧道列表视图
initProxyListView();
- // 隧道类型
+ // 初始化隧道类型
Arrays.asList(ProxyType.values()).forEach((type) -> proxyProtocolComboBox.getItems().add(type.name()));
- // 服务器
+ // 初始化服务器
Arrays.asList(ProxyServer.values()).forEach((server) -> proxyServerComboBox.getItems().add(server.getServerName()));
// 获取用户隧道列表
@@ -185,7 +197,6 @@ public class MainController extends BaseController implements Initial
}
});
-
/* 数据绑定 */
// 服务器
@@ -204,12 +215,27 @@ public class MainController extends BaseController implements Initial
domainSuffixTextField.textProperty().bindBidirectional(proxySetupModel.domainSuffixProperty());
// 是否自定义访问域名
customizeDomain.bindBidirectional(proxySetupModel.isCustomizeProperty());
+ // http访问用户名
+ httpUserTextField.textProperty().bindBidirectional(proxySetupModel.httpUserProperty());
+ // http访问密码
+ httpPwdTextField.textProperty().bindBidirectional(proxySetupModel.httpPwdProperty());
+ // p2p访问密码
+ p2pPwdTextField.textProperty().bindBidirectional(proxySetupModel.skProperty());
+ // p2p访问服务提示
+ serverNameTextField.textProperty().bindBidirectional(proxySetupModel.serverNameProperty());
// 启动成功提示
proxyStatusPane.visibleProperty().bindBidirectional(proxySetupModel.runningProperty());
}
@Override
public void initViewStyle() {
+ children = ((Pane) domainPane.getParent()).getChildren();
+
+ // 默认隧道协议为http
+ children.remove(p2pRolePane);
+ children.remove(p2pPwdPane);
+ children.remove(serverNamePane);
+
// 设置列表
proxyListView.getSelectionModel().select(0);
// 启用链接
@@ -219,30 +245,144 @@ public class MainController extends BaseController implements Initial
closeProxyRBtn.setUserData(false);
openProxyRBtn.setSelected(true);
+ // 点对点服务角色
+ serverProviderRBtn.setToggleGroup(serverRoleGroup);
+ serverVisitorRBtn.setToggleGroup(serverRoleGroup);
+ serverProviderRBtn.setUserData(true);
+ serverVisitorRBtn.setUserData(false);
+ serverProviderRBtn.setSelected(true);
+
// 是否启用
proxySetupModel.statusProperty().addListener((observable, oldValue, newValue) -> {
openProxyRBtn.setSelected(newValue);
closeProxyRBtn.setSelected(!newValue);
closeProxy(!newValue);
});
+
+ // 隧道类型监听
proxySetupModel.proxyTypeProperty().addListener((observable, oldValue, newValue) -> {
String newType = StringUtils.lowerCase(newValue);
String port, host;
- if (!newType.equals(userProxy.get(proxySetupModel.getId()))) {
+ if (!newType.equals(StringUtils.upperCase(userProxy.get(proxySetupModel.getId()).getProxy_type()))) {
host = "127.0.0.1";
port = ProxyConfig.getTypePort(newType).toString();
} else {
host = proxySetupModel.getLocalIp();
port = proxySetupModel.getLocalPort();
}
- // 域名提示
- domainPane.setVisible(DomainUtil.isHttp(proxySetupModel));
+ // 隧道类型相关控件显示
+ boolean isHttp = ProxyConfig.isHttp(proxySetupModel);
+ boolean isP2p = ProxyConfig.isP2p(proxySetupModel);
+ if (isHttp) {
+ // http / https
+ children.remove(p2pRolePane);
+ children.remove(p2pPwdPane);
+ children.remove(serverNamePane);
+ if (!children.contains(domainPane)) {
+ int index = children.size() - 1;
+ children.add(index, httpPwdPane);
+ children.add(index, httpUserPane);
+ children.add(index, domainPane);
+ }
+ // 添加验证
+ ObservableList httpValid = domainTextField.getValidators();
+ if (!httpValid.contains(TextValidate.DomainRequired)) {
+ httpValid.add(TextValidate.DomainRequired);// 域名为空检查
+ httpValid.add(TextValidate.domainFormatValidator(proxySetupModel));// 域名格式检查
+ httpValid.add(TextValidate.domainLengthValidator(proxySetupModel));// 域名长度检查
+ httpValid.add(TextValidate.domainAddressValidator(proxySetupModel));// 自定义域名解析检查
+ }
+ p2pPwdTextField.getValidators().clear();
+ // 默认系统分配域名
+ ProxySetup proxySetup = proxySetupModel.get();
+ if (proxySetup.getDomain().equals("")) {
+ proxySetup.setDomain("." + ProxyConfig.getServerPath(proxySetupModel.getServer()));
+ proxySetupModel.set(proxySetup);
+ }
+ } else if (isP2p) {
+ // stcp / xtcp
+ children.remove(httpPwdPane);
+ children.remove(httpUserPane);
+ children.remove(domainPane);
+ int index = children.size() - 1;
+ if (!children.contains(p2pRolePane)) {
+ children.add(index, p2pPwdPane);
+ children.add(index - 1, p2pRolePane);
+ if(proxySetupModel.isProvider != null && !proxySetupModel.isProvider){
+ children.add(index, serverNamePane);
+ }
+ }
+ Boolean provider = proxySetupModel.isProvider();
+ importP2pConfig.setVisible(provider != null && !provider);
+ copyP2pConfig.setVisible(provider == null || provider);
+ // 添加验证
+ ObservableList p2pValid = p2pPwdTextField.getValidators();
+ if (!p2pValid.contains(TextValidate.p2pPwdRequired)) {
+ p2pValid.add(TextValidate.p2pPwdRequired);
+ p2pValid.add(TextValidate.p2pServerNameRequired);
+ }
- // 设置
+ domainTextField.getValidators().clear();
+ } else {
+ // tcp / udp
+ children.remove(p2pRolePane);
+ children.remove(p2pPwdPane);
+ children.remove(serverNamePane);
+ children.remove(httpPwdPane);
+ children.remove(httpUserPane);
+ children.remove(domainPane);
+
+ // 验证
+ p2pPwdTextField.getValidators().clear();
+ domainTextField.getValidators().clear();
+ }
+
+ // 设置本地端口
localHostTextField.setText(host);
localPortTextField.setText(port);
});
+ // 服务角色监听
+ proxySetupModel.serverNameProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue == null) {
+ serverProviderRBtn.setSelected(false);
+ serverVisitorRBtn.setSelected(false);
+ proxySetupModel.isProvider = null;
+ } else if (oldValue == null) {
+ if (proxySetupModel.isProvider == null) {
+ proxySetupModel.isProvider = proxySetupModel.isProvider();
+ }
+ serverProviderRBtn.setSelected(proxySetupModel.isProvider);
+ serverVisitorRBtn.setSelected(!proxySetupModel.isProvider);
+ }
+ });
+ serverRoleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue == null) return;
+ proxySetupModel.isProvider = (boolean) newValue.getUserData();
+ if (proxySetupModel.isProvider) {
+ localHostLabel.setText("内网服务地址");
+ localHostTextField.setPromptText("内网服务IP (IPv4)");
+ localPortTextField.setPromptText("端口");
+ copyP2pConfig.setVisible(true);
+ importP2pConfig.setVisible(false);
+ children.remove(serverNamePane);
+ proxySetupModel.setServerName("提供者");
+ } else {
+ localHostLabel.setText("服务绑定地址");
+ localHostTextField.setPromptText("服务绑定IP (IPv4)");
+ localPortTextField.setPromptText("端口");
+ copyP2pConfig.setVisible(false);
+ importP2pConfig.setVisible(true);
+ if (!children.contains(serverNamePane)) children.add(children.size() - 3, serverNamePane);
+ proxySetupModel.setServerName("");
+ }
+ ObservableList children = p2pRolePane.getChildren();
+ JFXButton remove = proxySetupModel.isProvider ? importP2pConfig : copyP2pConfig;
+ JFXButton add = proxySetupModel.isProvider ? copyP2pConfig : importP2pConfig;
+ if (children.contains(add)) children.add(add);
+ children.remove(remove);
+ });
+
// 隧道名称
proxySetupModel.proxyNameProperty().addListener((observable, oldValue, newValue) -> {
if (proxyListView.getItems().size() > 0)
@@ -267,38 +407,20 @@ public class MainController extends BaseController implements Initial
setDomainLink();
});
- // 自定义外网访问地址按钮
+ // 外网访问地址
customizeDomain.addListener((observable, oldValue, newValue) -> {
AnchorPane parent = (AnchorPane) domainTextField.getParent();
- // 是否切换为自定义域名
if (newValue) {
// 隐藏系统域名
parent.getChildren().remove(domainSuffixTextField);
- // 用户域名是否自定义
- if (!DomainUtil.isCustomize(proxySetupModel.get())) {
- proxySetupModel.setDomain("");
- } else {
- proxySetupModel.setDomain(proxySetupModel.getDomain());
- }
- proxySetupModel.setDomainSuffix("");
domainTextField.promptTextProperty().set("自定义域名");
customizeDomainBtn.setText("系统分配");
- domainHtinTextField.setText("请输入您的域名,并解析至: " + ProxyConfig.getServerIP(proxySetupModel.getServer()));
} else {
// 显示系统域名
if (!parent.getChildren().contains(domainSuffixTextField))
parent.getChildren().add(domainSuffixTextField);
- // 用户域名是否自定义
- if (DomainUtil.isCustomize(proxySetupModel.get())) {
- proxySetupModel.setDomain("");
- proxySetupModel.setDomainSuffix("." + ProxyConfig.getServerPath(proxySetupModel.getServer()));
- } else {
- proxySetupModel.setDomain(DomainUtil.getCustomize(proxySetupModel.get()));
- proxySetupModel.setDomainSuffix(DomainUtil.getSuffix(userProxy.get(proxySetupModel.getId())));
- }
domainTextField.promptTextProperty().set("自定义子域名 大于3位");
customizeDomainBtn.setText("自定义");
- domainHtinTextField.setText("请输入子域名,长度不小于3个字符");
}
});
@@ -311,10 +433,19 @@ public class MainController extends BaseController implements Initial
*/
private void setDomainLink() {
// 外网访问连接
- if (DomainUtil.isHttp(proxySetupModel)) {
+ if (ProxyConfig.isHttp(proxySetupModel)) {
+ runingLabel.setText("启动成功!立即访问 ");
+ domainLink.setVisible(true);
+ copyDomainBtn.setVisible(true);
// http / https
String prefix = proxySetupModel.getProxyType() + "://";
domainLink.textProperty().set(prefix + proxySetupModel.getDomain() + proxySetupModel.getDomainSuffix());
+ } else if (ProxyConfig.isP2p(proxySetupModel)) {
+ // stcp / xtcp
+ runingLabel.setText("启动成功!");
+ domainLink.setText("");
+ domainLink.setVisible(false);
+ copyDomainBtn.setVisible(false);
} else {
// ssh / tcp
domainLink.textProperty().set(ProxyConfig.getServerIP(ProxyConfig.getServerNode(proxySetupModel.getServer())) + ":" + proxySetupModel.getRemotePort());
@@ -326,7 +457,7 @@ public class MainController extends BaseController implements Initial
// 重置
resetProxyBtn.setOnMouseClicked(event -> {
ProxySetup proxySetup = userProxy.get(proxySetupModel.getId());
- if(proxySetup != null){
+ if (proxySetup != null) {
proxySetup.setRuning(proxySetupModel.isRunning());
proxySetupModel.set(proxySetup);
domainTextField.resetValidation();
@@ -336,20 +467,22 @@ public class MainController extends BaseController implements Initial
// 日志清理
clearLogBtn.setOnMouseClicked(event -> {
ProxySetup proxySetup = userProxy.get(proxySetupModel.getId());
- if (frpUtilMap.get(proxySetup.getId()) != null)
- frpUtilMap.get(proxySetup.getId()).clearLog();
+ if (frpUtilMap.get(proxySetup.getId().toString()) != null)
+ frpUtilMap.get(proxySetup.getId().toString()).clearLog();
});
// 更换服务器地址
proxyServerComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
// 重新设置是否自定义
- proxySetupModel.set(proxySetupModel.get());
- if (!customizeDomain.get()) {
+ boolean customize = DomainUtil.isCustomize(proxySetupModel.get());
+ if (!DomainUtil.isCustomize(proxySetupModel.get(), oldValue) || !customize) {
proxySetupModel.setDomainSuffix("." + ProxyConfig.getServerPath(newValue));
+ customizeDomain.set(false);
} else {
proxySetupModel.setDomainSuffix("");
// 域名检查
domainTextField.validate();
+ customizeDomain.set(true);
}
});
@@ -365,7 +498,28 @@ public class MainController extends BaseController implements Initial
);
// 自定义访问域名
- customizeDomainBtn.setOnMouseClicked((event) -> customizeDomain.set(!customizeDomain.get()));
+ customizeDomainBtn.setOnMouseClicked((event) -> {
+ // 是否切换为自定义域名
+ if (!customizeDomain.get()) {
+ // 用户域名是否自定义
+ if (!DomainUtil.isCustomize(proxySetupModel.get())) {
+ proxySetupModel.setDomain("");
+ } else {
+ proxySetupModel.setDomain(proxySetupModel.getDomain());
+ }
+ proxySetupModel.setDomainSuffix("");
+ } else {
+ // 用户域名是否自定义
+ if (DomainUtil.isCustomize(proxySetupModel.get())) {
+ proxySetupModel.setDomain("");
+ proxySetupModel.setDomainSuffix("." + ProxyConfig.getServerPath(proxySetupModel.getServer()));
+ } else {
+ proxySetupModel.setDomain(DomainUtil.getCustomize(proxySetupModel.get()));
+ proxySetupModel.setDomainSuffix(DomainUtil.getSuffix(userProxy.get(proxySetupModel.getId())));
+ }
+ }
+ customizeDomain.set(!customizeDomain.get());
+ });
// 本地IP检查
localHostTextField.getValidators().add(TextValidate.IpRequired);
@@ -380,15 +534,60 @@ public class MainController extends BaseController implements Initial
// 域名检查
setup.addListener((observable, oldValue, newValue) -> {
if (newValue) domainTextField.validate();
+ if (newValue) p2pPwdTextField.validate();
});
- domainTextField.textProperty().addListener((observable, oldValue, newValue) -> {
- if (!domainTextField.getValidators().contains(TextValidate.DomainRequired)) {
- domainTextField.getValidators().add(TextValidate.DomainRequired);// 域名为空检查
- domainTextField.getValidators().add(TextValidate.domainFormatValidator(proxySetupModel));// 域名格式检查
- domainTextField.getValidators().add(TextValidate.domainLengthValidator(proxySetupModel));// 域名长度检查
- domainTextField.getValidators().add(TextValidate.domainAddressValidator(proxySetupModel));// 自定义域名解析检查
+
+ // 复制服务设置
+ copyP2pConfig.setOnMouseClicked(event -> {
+ if (!localPortTextField.validate() || !localPortTextField.validate() || !p2pPwdTextField.validate())
+ return;
+
+ ProxySetup setup = proxySetupModel.get();
+ setup.setServer_name(EncryptionUtil.MD5_16(ApplicatonStore.getAccount() + "_" + setup.getSort()));
+ JSONObject json = JSON.parseObject(JSON.toJSONString(setup));
+
+ try {
+ String serverConfig = EncryptionUtil.DESencode(json.toJSONString(), EncryptionUtil.getDefaultPassword());
+ // 设置剪切板
+ ClipUtil.setClip(FrpManager.serverConfigHeader + serverConfig);
+ AlertUtil.info("复制成功").show();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ // 导入设置
+ importP2pConfig.setOnMouseClicked(event -> {
+ try {
+ String clipData = ClipUtil.getString();
+ // 服务设置检验
+ if (clipData == null || !clipData.contains(FrpManager.serverConfigHeader)) throw new Exception();
+ // 解密服务设置
+ clipData = clipData.replace(FrpManager.serverConfigHeader, "");
+ String jsonStr = EncryptionUtil.DESdecode(clipData, EncryptionUtil.getDefaultPassword());
+ JSONObject jsonObject = JSON.parseObject(jsonStr);
+ proxySetupModel.set(jsonObject.toJavaObject(ProxySetup.class));
+ AlertUtil.info("导入成功").show();
+ } catch (Exception e) {
+ AlertUtil.error("导入失败,请重新复制服务设置").show();
+ }
+ });
+
+ // 添加验证
+ ObservableList httpValid = domainTextField.getValidators();
+ httpValid.add(TextValidate.DomainRequired);// 域名为空检查
+ httpValid.add(TextValidate.domainFormatValidator(proxySetupModel));// 域名格式检查
+ httpValid.add(TextValidate.domainLengthValidator(proxySetupModel));// 域名长度检查
+ httpValid.add(TextValidate.domainAddressValidator(proxySetupModel));// 自定义域名解析检查
+ domainTextField.textProperty().addListener((observable, oldValue, newValue) -> domainTextField.validate());
+ p2pPwdTextField.textProperty().addListener((observable, oldValue, newValue) -> p2pPwdTextField.validate());
+
+ // 访问密码检查
+ p2pPwdTextField.textProperty().addListener((observable, oldValue, newValue) -> {
+ if (!p2pPwdTextField.getValidators().contains(TextValidate.p2pPwdRequired)) {
+ p2pPwdTextField.getValidators().add(TextValidate.p2pPwdRequired); // 访问密码为空检查
+ p2pPwdTextField.getValidators().add(TextValidate.HttpPwdFormat); // 访问密码格式检查
} else
- domainTextField.validate();
+ p2pPwdTextField.validate();
});
// 点击隧道列表
@@ -446,12 +645,8 @@ public class MainController extends BaseController implements Initial
// 复制外网访问地址
copyDomainBtn.setOnMouseClicked(event -> {
- //获取系统剪切板
- Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
- //构建String数据类型
- StringSelection selection = new StringSelection(domainLink.getText());
- //添加文本到系统剪切板
- clipboard.setContents(selection, null);
+ // 设置剪切板
+ ClipUtil.setClip(domainLink.getText());
// 复制成功提示
AlertUtil.info("复制成功,快去分享给小伙伴吧!").show();
});
@@ -468,15 +663,37 @@ public class MainController extends BaseController implements Initial
e.printStackTrace();
}
});
+ // 隧道类型提示
+ proxyTypeTip.setOnMouseClicked(event -> {
+ TooltipUtil util = getTooltipUtil();
+ if (util.isShowing()) util.hide();
+ else util.showProxyTypeTip(event);
+ });
+ // http访问用户名提示
+ httpUserTip.setOnMouseClicked(event -> {
+ TooltipUtil util = getTooltipUtil();
+ if (util.isShowing()) util.hide();
+ else util.showHttpUserTip(event);
+ });
+ // 隧道启用提示
+ proxyStatusTip.setOnMouseClicked(event -> {
+ TooltipUtil util = getTooltipUtil();
+ if (util.isShowing()) util.hide();
+ else util.showProxyStatusTip(event);
+ });
+ // p2p 访问密码提示
+ p2pPwdTip.setOnMouseClicked(event -> {
+ TooltipUtil util = getTooltipUtil();
+ if (util.isShowing()) util.hide();
+ else util.showP2pPwdTip(event);
+ });
+ // p2p 访问服务提示
+ serverNameTip.setOnMouseClicked(event -> {
+ TooltipUtil util = getTooltipUtil();
+ if (util.isShowing()) util.hide();
+ else util.showP2pServerName(event);
+ });
}
-//
-// private void initEmptyProxyListView() {
-// // 初始化视图模型
-// proxySetup = ProxyManager.initProxy(null);
-// proxySetupModel = new ProxySetupModel(proxySetup);
-// setProxyList(Collections.singletonList(proxySetup));
-// initProxyListView();
-// }
/**
* 是否启用当前隧道
@@ -484,12 +701,16 @@ public class MainController extends BaseController implements Initial
private void closeProxy(boolean close) {
if (close && proxySetupModel.isRunning()) stopProxy();
- proxyNameTextField.setDisable(close);// 隧道名称
+ proxyNameTextField.setDisable(close); // 隧道名称
proxyProtocolComboBox.setDisable(close);// 隧道类型
- localHostTextField.setDisable(close);// 本地地址
- localPortTextField.setDisable(close);// 本地端口
- domainTextField.setDisable(close); // 外网子域名
- startProxyBtn.setDisable(close); // 启动按钮
+ localHostTextField.setDisable(close); // 本地地址
+ localPortTextField.setDisable(close); // 本地端口
+ domainTextField.setDisable(close); // 外网子域名
+ httpUserTextField.setDisable(close); // 外网子域名
+ httpPwdTextField.setDisable(close); // 外网子域名
+ startProxyBtn.setDisable(close); // 启动按钮
+ p2pRoleView.setDisable(close); // p2p角色
+ p2pPwdTextField.setDisable(close); // p2p访问面板
ObservableList styleClass = proxyListView.getItems().get(selectProxy()).getStyleClass();
styleClass.remove(PROXY_LIST_ITEM_STOP_CLASS);
@@ -518,6 +739,7 @@ public class MainController extends BaseController implements Initial
label.textProperty().set(setup.getProxy_name());
ObservableList styleClass = label.getStyleClass();
styleClass.addAll(PROXY_LIST_ITEM_CLASS, setup.isRuning() ? PROXY_LIST_ITEM_RUN_CLASS : PROXY_LIST_ITEM_STOP_CLASS);
+ if (selectProxy() == index) styleClass.add(PROXY_LIST_ITEM_SELECT_CLASS);
if (index < items.size()) {
items.set(index, label);
} else {
@@ -532,16 +754,32 @@ public class MainController extends BaseController implements Initial
* 启动代理
*/
private void startProxy() {
+ // http访问用户名密码输入验证
+ if (httpUserPane.isVisible() && StringUtils.isNotEmpty(httpUserTextField.getText())) {
+ httpUserTextField.getValidators().add(TextValidate.HttpUserFormat);
+ httpPwdTextField.getValidators().add(TextValidate.HttpPwdFormat);
+ } else {
+ httpUserTextField.getValidators().remove(TextValidate.HttpUserFormat);
+ httpPwdTextField.getValidators().remove(TextValidate.HttpPwdFormat);
+ }
// 验证
- if (!domainTextField.validate() || !localHostTextField.validate() || !localPortTextField.validate())
- return;
+ if (!domainTextField.validate() || !localHostTextField.validate() || !localPortTextField.validate() ||
+ !httpUserTextField.validate() || !httpPwdTextField.validate()
+ ) return;
- // 非 http 隧道系统随机分配端口
- if (!DomainUtil.isHttp(proxySetupModel)) {
- proxySetupModel.setRemotePort(String.valueOf(ProxyManager.randomPort()));
+ // 非 http 隧道系统随机分配远程端口
+ if (!ProxyConfig.isHttp(proxySetupModel) && !ProxyConfig.isP2p(proxySetupModel)) {
+ // 远程端口为空
+ if (StringUtils.isEmpty(proxySetupModel.getRemotePort()))
+ proxySetupModel.setRemotePort(String.valueOf(ProxyManager.randomPort()));
+ } else {
+ proxySetupModel.setRemotePort(null);
}
+ // 清空非隧道类型属性
+ clearModel();
+
// 是否有修改
if (!proxySetupModel.get().equals(userProxy.get(proxySetupModel.getId()))) {
// 添加隧道
@@ -564,6 +802,33 @@ public class MainController extends BaseController implements Initial
}
}
+ /**
+ * 清空无效属性
+ */
+ private void clearModel() {
+ boolean http = ProxyConfig.isHttp(proxySetupModel);
+ boolean p2p = ProxyConfig.isP2p(proxySetupModel);
+ if (http) {
+ proxySetupModel.setSk(null);
+ proxySetupModel.setServerName(null);
+ proxySetupModel.setRemotePort("");
+ } else if (p2p) {
+ proxySetupModel.setHttpPwd(null);
+ proxySetupModel.setHttpUser(null);
+ proxySetupModel.setDomain("");
+ proxySetupModel.setDomainSuffix("");
+ proxySetupModel.setRemotePort("");
+ } else {
+ // tcp / udp
+ proxySetupModel.setSk(null);
+ proxySetupModel.setServerName(null);
+ proxySetupModel.setHttpPwd(null);
+ proxySetupModel.setHttpUser(null);
+ proxySetupModel.setDomain("");
+ proxySetupModel.setDomainSuffix("");
+ }
+ }
+
private void start(ProxySetup setup) {
// 添加成功,设置运行状态
setup.setRuning(frpUtilMap.containsKey(setup.getId().toString()));
diff --git a/src/main/java/top/octopusyan/controller/RegisterController.java b/src/main/java/top/octopusyan/controller/RegisterController.java
index 8f2b6e3..0e17bcf 100644
--- a/src/main/java/top/octopusyan/controller/RegisterController.java
+++ b/src/main/java/top/octopusyan/controller/RegisterController.java
@@ -6,6 +6,7 @@ import com.jfoenix.controls.JFXTextField;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
+import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@@ -44,6 +45,8 @@ public class RegisterController extends BaseController implements Ser
public JFXPasswordField passwordTextField;
@FXML
public JFXButton loginBtn;
+ @FXML
+ public Label appVersionLabel;
@Override
public boolean dragWindow() {
@@ -61,6 +64,11 @@ public class RegisterController extends BaseController implements Ser
return "/fxml/register.fxml";
}
+ @Override
+ public Label getAppVersionLabel() {
+ return appVersionLabel;
+ }
+
@Override
public Button getClooseBtn() {
return closeBtn;
@@ -81,8 +89,8 @@ public class RegisterController extends BaseController implements Ser
String account = ApplicatonStore.getAccount();
String password = ApplicatonStore.getPassword();
- if (!StringUtils.isEmpty(account)) accooundTextField.setText(account);
- if (!StringUtils.isEmpty(password)) passwordTextField.setText(password);
+ if (StringUtils.isNotEmpty(account)) accooundTextField.setText(account);
+ if (StringUtils.isNotEmpty(password)) passwordTextField.setText(password);
}
@Override
diff --git a/src/main/java/top/octopusyan/manager/FrpManager.java b/src/main/java/top/octopusyan/manager/FrpManager.java
index 7d1ebbf..df69a4b 100644
--- a/src/main/java/top/octopusyan/manager/FrpManager.java
+++ b/src/main/java/top/octopusyan/manager/FrpManager.java
@@ -11,6 +11,7 @@ import top.octopusyan.http.model.ResponseClass;
import top.octopusyan.manager.http.Api;
import top.octopusyan.model.ApplicatonStore;
import top.octopusyan.model.ProxySetupModel;
+import top.octopusyan.utils.EncryptionUtil;
import top.octopusyan.utils.JsoupUtil;
import java.io.*;
@@ -24,11 +25,12 @@ import java.util.*;
* @create : 2022-4-7 23:19
*/
public class FrpManager {
+ public static final String serverConfigHeader = "yanfrp://";
public static List frpcList = new ArrayList<>();
private static final String YAN_FRP_TEMP_DIR_NAME = "frpclienttmp";
public static final String FRPC_CLIENT_FILE_NAME = "frpclienttmpfile.exe";
- private final String FRPC_CONF_PREFIX_NAME = "proxy_";
- private final String FRPC_CONF_SUFFIX_NAME = ".ini";
+ private static final String FRPC_CONF_PREFIX_NAME = "proxy_";
+ private static final String FRPC_CONF_SUFFIX_NAME = ".ini";
private static final Map serverConfigraution = new HashMap<>(3);
/**
* 应用临时目录 地址
@@ -154,27 +156,66 @@ public class FrpManager {
public static String getProxyFrpConfig(ProxySetupModel setup) {
String n = "\n";
+ boolean ishttp = setup.getProxyType().contains("http");
+ boolean isp2p = "xtcp".equals(setup.getProxyType()) || "stcp".equals(setup.getProxyType());
// 基础配置
- StringBuilder stringBuilder = new StringBuilder("[" + ApplicatonStore.getAccount() + "_" + setup.getSort() + "]\n");
- stringBuilder.append("privilege_mode = true\n")
- .append("type = ").append(setup.getProxyType().contains("http") ? "http" : setup.getProxyType()).append(n)
- .append("local_ip = ").append(setup.getLocalIp()).append(n)
- .append("local_port = ").append(setup.getLocalPort()).append(n).append(n);
-
- if ("http".equals(setup.getProxyType()) || "https".equals(setup.getProxyType())) {
- // HTTP / HTTPS
- stringBuilder.append("custom_domains = ").append(setup.getDomain()).append(setup.getDomainSuffix()).append(n);
- if (!StringUtils.isEmpty(setup.getLocations()))
- stringBuilder.append("locations = ").append(setup.getLocations()).append(n);
- if (!StringUtils.isEmpty(setup.getLocations()))
- stringBuilder.append("host_header_rewrite = ").append(setup.getHostHeaderRewrite()).append(n);
- if (!StringUtils.isEmpty(setup.getLocations()))
- stringBuilder.append("header_X-From-Where = ").append(setup.getHeader_X_From_Where()).append(n);
+ StringBuilder stringBuilder = new StringBuilder("[");
+ // 服务名称
+ StringBuilder serverName = new StringBuilder();
+ // p2p 服务名
+ if (isp2p) {
+ if (setup.isProvider()) {
+ // 提供者
+ serverName.append(EncryptionUtil.MD5_16(serverName.toString()));
+ } else {
+ // 访问者
+ serverName.append(setup.getServerName()).append("_visitor");
+ }
} else {
- // TCP / UDP / XTCP / STCP
- stringBuilder.append("remote_port = ").append(setup.getRemotePort()).append(n);
- if (!StringUtils.isEmpty(setup.getSk()))
- stringBuilder.append("sk = ").append(setup.getSk()).append(n);
+ serverName.append(ApplicatonStore.getAccount()).append("_").append(setup.getSort());
+ }
+ stringBuilder.append(serverName).append("]\n");
+ stringBuilder.append("privilege_mode = true\n")
+ .append("type = ").append(setup.getProxyType().contains("http") ? "http" : setup.getProxyType()).append(n);
+
+ if (ishttp) {
+ // HTTP / HTTPS
+ stringBuilder.append("custom_domains = ").append(setup.getDomain()).append(setup.getDomainSuffix()).append(n)
+ .append("local_ip = ").append(setup.getLocalIp()).append(n)
+ .append("local_port = ").append(setup.getLocalPort()).append(n).append(n);
+
+ if (StringUtils.isNotEmpty(setup.getLocations()))
+ stringBuilder.append("locations = ").append(setup.getLocations()).append(n);
+
+ if (StringUtils.isNotEmpty(setup.getHttpUser()))
+ stringBuilder.append("http_user = ").append(setup.getHttpUser()).append(n);
+
+ if (StringUtils.isNotEmpty(setup.getHttpPwd()))
+ stringBuilder.append("http_pwd = ").append(setup.getHttpPwd()).append(n);
+
+ if (StringUtils.isNotEmpty(setup.getHostHeaderRewrite()))
+ stringBuilder.append("host_header_rewrite = ").append(setup.getHostHeaderRewrite()).append(n);
+
+ if (StringUtils.isNotEmpty(setup.getHeader_X_From_Where()))
+ stringBuilder.append("header_X-From-Where = ").append(setup.getHeader_X_From_Where()).append(n);
+
+ } else if (isp2p) {
+ // xtcp / stcp
+ if (!setup.isProvider()) {
+ stringBuilder.append("role = visitor").append(n);
+ stringBuilder.append("server_name = ").append(setup.getServerName()).append(n);
+ stringBuilder.append("bind_addr = ").append(setup.getLocalIp()).append(n);
+ stringBuilder.append("bind_port = ").append(setup.getLocalPort()).append(n);
+ } else {
+ stringBuilder.append("local_ip = ").append(setup.getLocalIp()).append(n);
+ stringBuilder.append("local_port = ").append(setup.getLocalPort()).append(n);
+ }
+ stringBuilder.append("sk = ").append(setup.getSk()).append(n);
+ } else {
+ // TCP / UDP
+ stringBuilder.append("local_ip = ").append(setup.getLocalIp()).append(n)
+ .append("local_port = ").append(setup.getLocalPort()).append(n).append(n)
+ .append("remote_port = ").append(setup.getRemotePort()).append(n);
}
// 压缩和加密
stringBuilder.append("use_encryption = ").append(setup.isUseEncryption()).append(n)
diff --git a/src/main/java/top/octopusyan/manager/ProxyManager.java b/src/main/java/top/octopusyan/manager/ProxyManager.java
index f23cb1b..74d45c8 100644
--- a/src/main/java/top/octopusyan/manager/ProxyManager.java
+++ b/src/main/java/top/octopusyan/manager/ProxyManager.java
@@ -28,6 +28,10 @@ import java.util.stream.Collectors;
public class ProxyManager {
private static String csrf;
+
+ private static final String Null = "无";
+ private static final String isOpen = "启用";
+
/**
* 初始化隧道设置
*/
@@ -48,6 +52,11 @@ public class ProxyManager {
return setup;
}
+ /**
+ * 删除隧道
+ *
+ * @param id 隧道ID
+ */
public static void delete(Integer id) {
EasyHttp.builder()
.api(Api.DeleteProxy())
@@ -92,7 +101,11 @@ public class ProxyManager {
String useEnc = select.get(6).text();
String useCom = select.get(7).text();
String domain = select.get(8).text();
+ String sk = select.get(11).text();
String status = select.get(13).text();
+ String httpUser = select.get(15).text();
+ String httpPwd = select.get(16).text();
+ String serverName = select.get(17).text();
for (ProxyServer value : ProxyServer.values()) {
String name = value.getServerName();
@@ -101,11 +114,15 @@ public class ProxyManager {
}
setup.setLocal_ip(localIp);
setup.setLocal_port(Integer.parseInt(localPort));
- setup.setRemote_port("无".equals(webPort) ? "" : webPort);
- setup.setDomain(domain);
- setup.setUse_compression(useCom.equals("启用"));
- setup.setUse_encryption(useEnc.equals("启用"));
- setup.setStatus(status.equals("启用"));
+ setup.setRemote_port(Null.equals(webPort) ? "" : webPort);
+ setup.setDomain(Null.equals(domain) ? "" : domain);
+ setup.setUse_compression(useCom.equals(isOpen));
+ setup.setUse_encryption(useEnc.equals(isOpen));
+ setup.setStatus(status.equals(isOpen));
+ setup.setSk(Null.equals(sk) ? null : sk);
+ setup.setHttp_user(Null.equals(httpUser) ? null : httpUser);
+ setup.setHttp_pwd(Null.equals(httpPwd) ? null : httpPwd);
+ setup.setServer_name(Null.equals(serverName) ? null : serverName);
return setup;
}
@@ -166,7 +183,7 @@ public class ProxyManager {
public void onSucceed(String result) {
// 不成功
- if(!result.contains("成功")){
+ if (!result.contains("成功")) {
Platform.runLater(() -> AlertUtil.error(result).header(null).show());
listener.onSucceed(null);
return;
diff --git a/src/main/java/top/octopusyan/manager/http/request/ProxySetup.java b/src/main/java/top/octopusyan/manager/http/request/ProxySetup.java
index b8ccc37..cf8428d 100644
--- a/src/main/java/top/octopusyan/manager/http/request/ProxySetup.java
+++ b/src/main/java/top/octopusyan/manager/http/request/ProxySetup.java
@@ -2,6 +2,7 @@ package top.octopusyan.manager.http.request;
import lombok.AllArgsConstructor;
import lombok.Data;
+import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
@@ -22,6 +23,8 @@ public class ProxySetup {
private Integer local_port;
private String remote_port;
private String domain;
+ private String http_user;
+ private String http_pwd;
private Boolean use_encryption;
private Boolean use_compression;
private String locations;
@@ -29,9 +32,13 @@ public class ProxySetup {
private String header_X_From_Where;
private String sk;
private Integer sort;
+ // null:不是p2p, 空字符:提供者
+ private String server_name;
// 非保留数据
/** 是否启用 */
+ @EqualsAndHashCode.Exclude
private Boolean status;
+ @EqualsAndHashCode.Exclude
private boolean runing;
}
diff --git a/src/main/java/top/octopusyan/model/ApplicatonStore.java b/src/main/java/top/octopusyan/model/ApplicatonStore.java
index 0a59818..438ba47 100644
--- a/src/main/java/top/octopusyan/model/ApplicatonStore.java
+++ b/src/main/java/top/octopusyan/model/ApplicatonStore.java
@@ -24,6 +24,8 @@ import java.util.List;
* @create : 2022-4-4 17:22
*/
public class ApplicatonStore {
+ public static final String APP_NAME = "YanFrp";
+ public static final String APP_VERSION = "1.0.4";
public static final String appDataDirPath = System.getProperty("user.home") + File.separator + "AppData" + File.separator + "Local" + File.separator + "yanfrp";
public static final String appDataFilePath = appDataDirPath + File.separator + "yanfrp";
private static final File appDataDir = new File(appDataDirPath);
@@ -155,7 +157,8 @@ public class ApplicatonStore {
// 记住
if (appDataJson.containsKey(REMEMBER_ME_KEY)) rememberMe.set(appDataJson.getBoolean(REMEMBER_ME_KEY));
// 已选择隧道
- if (appDataJson.containsKey(SELECT_PROXY_NAME_KEY)) selectProxyName = appDataJson.getString(SELECT_PROXY_NAME_KEY);
+ if (appDataJson.containsKey(SELECT_PROXY_NAME_KEY))
+ selectProxyName = appDataJson.getString(SELECT_PROXY_NAME_KEY);
}
/**
@@ -193,7 +196,7 @@ public class ApplicatonStore {
}
}
- public static void logout(){
+ public static void logout() {
setRegisterSuccess(false);
setAutoLogin(false);
setRememberMe(false);
diff --git a/src/main/java/top/octopusyan/model/ProxySetupModel.java b/src/main/java/top/octopusyan/model/ProxySetupModel.java
index ed27e91..34464ea 100644
--- a/src/main/java/top/octopusyan/model/ProxySetupModel.java
+++ b/src/main/java/top/octopusyan/model/ProxySetupModel.java
@@ -31,12 +31,16 @@ public class ProxySetupModel {
private final SimpleStringProperty header_X_From_Where = new SimpleStringProperty();
private final SimpleStringProperty sk = new SimpleStringProperty();
private final SimpleBooleanProperty status = new SimpleBooleanProperty();
+ private final SimpleStringProperty serverName = new SimpleStringProperty();
private final SimpleIntegerProperty sort = new SimpleIntegerProperty();
private final SimpleBooleanProperty running = new SimpleBooleanProperty();
private final SimpleBooleanProperty isCustomize = new SimpleBooleanProperty();
+ private final SimpleStringProperty httpUser = new SimpleStringProperty();
+ private final SimpleStringProperty httpPwd = new SimpleStringProperty();
+ public volatile Boolean isProvider = null;
public ProxySetupModel(ProxySetup setup) {
- if(setup == null) return;
+ if (setup == null) return;
String domainStr = setup.getDomain();
String suffix = "";
@@ -48,11 +52,13 @@ public class ProxySetupModel {
setId(setup.getId());
setServer(ProxyConfig.getServerName(setup.getNode()));
setProxyName(setup.getProxy_name());
- setProxyType(setup.getProxy_type());
+ setProxyType(StringUtils.upperCase(setup.getProxy_type()));
setLocalIp(setup.getLocal_ip());
setLocalPort(setup.getLocal_port());
setRemotePort(setup.getRemote_port());
setDomain(domainStr);
+ setHttpUser(setup.getHttp_user());
+ setHttpPwd(setup.getHttp_pwd());
setDomainSuffix(suffix);
setLocations(setup.getLocations());
setUseEncryption(setup.getUse_encryption());
@@ -60,6 +66,7 @@ public class ProxySetupModel {
setHeader_X_From_Where(setup.getHost_header_rewrite());
setHostHeaderRewrite(setup.getHost_header_rewrite());
setStatus(setup.getStatus() == null || setup.getStatus());
+ setServerName(setup.getServer_name());
setSk(setup.getSk());
setSort(setup.getSort());
setRunning(setup.isRuning());
@@ -129,6 +136,14 @@ public class ProxySetupModel {
return status.get();
}
+ public Boolean isProvider() {
+ return serverName.get() == null ? null : serverName.get().equals("提供者");
+ }
+
+ public String getServerName() {
+ return serverName.get();
+ }
+
public boolean isUseCompression() {
return useCompression.get();
}
@@ -137,6 +152,14 @@ public class ProxySetupModel {
return useEncryption.get();
}
+ public String getHttpUser() {
+ return httpUser.get();
+ }
+
+ public String getHttpPwd() {
+ return httpPwd.get();
+ }
+
public void setId(Integer id) {
this.id.set(id == null ? null : id.toString());
}
@@ -205,10 +228,30 @@ public class ProxySetupModel {
this.status.set(status);
}
+ public void setServerName(String serverName) {
+ this.serverName.set(serverName);
+ }
+
public void setUseEncryption(boolean useEncryption) {
this.useEncryption.set(useEncryption);
}
+ public void setHttpUser(String httpUser) {
+ this.httpUser.set(httpUser);
+ }
+
+ public void setHttpPwd(String httpPwd) {
+ this.httpPwd.set(httpPwd);
+ }
+
+ public SimpleStringProperty httpPwdProperty() {
+ return httpPwd;
+ }
+
+ public SimpleStringProperty httpUserProperty() {
+ return httpUser;
+ }
+
public SimpleStringProperty serverProperty() {
return server;
}
@@ -281,6 +324,10 @@ public class ProxySetupModel {
return hostHeaderRewrite;
}
+ public SimpleStringProperty serverNameProperty() {
+ return serverName;
+ }
+
public SimpleBooleanProperty isCustomizeProperty() {
return isCustomize;
}
@@ -307,6 +354,7 @@ public class ProxySetupModel {
setHeader_X_From_Where(setup.getHost_header_rewrite());
setHostHeaderRewrite(setup.getHost_header_rewrite());
setStatus(setup.getStatus() == null || setup.getStatus());
+ setServerName(setup.getServer_name());
setSk(setup.getSk());
setSort(setup.getSort());
setRunning(setup.isRuning());
@@ -322,8 +370,10 @@ public class ProxySetupModel {
StringUtils.lowerCase(proxyType.getValue()),
getLocalIp(),
getLocalPort() == null ? null : Integer.parseInt(getLocalPort()),
- getRemotePort() == null ? "" : getRemotePort(),
- getDomain() + getDomainSuffix(),
+ getRemotePort(),
+ getDomain() == null ? null : getDomain() + getDomainSuffix(),
+ ProxyConfig.isHttp(this) ? getHttpUser() : null,
+ ProxyConfig.isHttp(this) ? getHttpPwd() : null,
isUseEncryption(),
isUseCompression(),
getLocations(),
@@ -331,6 +381,7 @@ public class ProxySetupModel {
getHeader_X_From_Where(),
getSk(),
getSort(),
+ serverName.get(),
getStatus(),
isRunning()
);
diff --git a/src/main/java/top/octopusyan/utils/AlertUtil.java b/src/main/java/top/octopusyan/utils/AlertUtil.java
index 2571653..b1d2d22 100644
--- a/src/main/java/top/octopusyan/utils/AlertUtil.java
+++ b/src/main/java/top/octopusyan/utils/AlertUtil.java
@@ -12,6 +12,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -33,30 +34,30 @@ public class AlertUtil {
public Builder(T alert) {
this.alert = alert;
- if(mOwner != null) this.alert.initOwner(mOwner);
+ if (mOwner != null) this.alert.initOwner(mOwner);
}
- public Builder title(String title) {
+ public Builder title(String title) {
alert.setTitle(title);
return this;
}
- public Builder header(String header) {
+ public Builder header(String header) {
alert.setHeaderText(header);
return this;
}
- public Builder content(String content) {
+ public Builder content(String content) {
alert.setContentText(content);
return this;
}
- public Builder icon(String path) {
- icon(new Image(this.getClass().getResource(path).toString()));
+ public Builder icon(String path) {
+ icon(new Image(Objects.requireNonNull(this.getClass().getResource(path)).toString()));
return this;
}
- public Builder icon(Image image) {
+ public Builder icon(Image image) {
getStage().getIcons().add(image);
return this;
}
@@ -67,7 +68,6 @@ public class AlertUtil {
/**
* AlertUtil.confirm
- * @param listener
*/
public void show(OnClickListener listener) {
@@ -78,7 +78,6 @@ public class AlertUtil {
/**
* AlertUtil.confirm
- * @param listener
*/
public void show(OnChoseListener listener) {
Optional result = alert.showAndWait();
@@ -103,13 +102,10 @@ public class AlertUtil {
/**
* AlertUtil.choices
- * @param choices
- * @param
- * @return
*/
public R getChoice(R... choices) {
- Optional result = alert.showAndWait();
- return result.get();
+ Optional result = alert.showAndWait();
+ return (R) result.get();
}
private Stage getStage() {
@@ -117,24 +113,24 @@ public class AlertUtil {
}
}
- public static Builder info(String content) {
- return new Builder(new Alert(AlertType.INFORMATION)).content(content).header(null);
+ public static Builder info(String content) {
+ return new Builder(new Alert(AlertType.INFORMATION)).content(content).header(null);
}
- public static Builder info() {
- return new Builder(new Alert(AlertType.INFORMATION));
+ public static Builder info() {
+ return new Builder(new Alert(AlertType.INFORMATION));
}
- public static Builder error(String message) {
- return new Builder(new Alert(AlertType.ERROR)).header(null).content(message);
+ public static Builder error(String message) {
+ return new Builder(new Alert(AlertType.ERROR)).header(null).content(message);
}
- public static Builder warning() {
- return new Builder(new Alert(AlertType.WARNING));
+ public static Builder warning() {
+ return new Builder(new Alert(AlertType.WARNING));
}
- public static Builder exception(Exception ex) {
- return new Builder(exceptionAlert(ex));
+ public static Builder exception(Exception ex) {
+ return new Builder(exceptionAlert(ex));
}
public static Alert exceptionAlert(Exception ex) {
@@ -173,17 +169,17 @@ public class AlertUtil {
/**
* 确认对话框
*/
- public static Builder confirm() {
+ public static Builder confirm() {
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("确认对话框");
- return new Builder(alert);
+ return new Builder(alert);
}
/**
* 自定义确认对话框
* "Cancel"
OR "取消"
为取消按钮
*/
- public static Builder confirm(String... buttons) {
+ public static Builder confirm(String... buttons) {
Alert alert = new Alert(AlertType.CONFIRMATION);
List buttonList = Arrays.stream(buttons).map((type) -> {
@@ -195,19 +191,19 @@ public class AlertUtil {
alert.getButtonTypes().setAll(buttonList);
- return new Builder(alert);
+ return new Builder(alert);
}
- public static Builder input(String content) {
+ public static Builder input(String content) {
TextInputDialog dialog = new TextInputDialog();
dialog.setContentText(content);
- return new Builder(dialog);
+ return new Builder(dialog);
}
- public static Builder choices(String hintText, T... choices) {
+ public static Builder> choices(String hintText, T... choices) {
ChoiceDialog dialog = new ChoiceDialog(choices[0], choices);
dialog.setContentText(hintText);
- return new Builder(dialog);
+ return new Builder>(dialog);
}
diff --git a/src/main/java/top/octopusyan/utils/ClipUtil.java b/src/main/java/top/octopusyan/utils/ClipUtil.java
new file mode 100644
index 0000000..5965f24
--- /dev/null
+++ b/src/main/java/top/octopusyan/utils/ClipUtil.java
@@ -0,0 +1,37 @@
+package top.octopusyan.utils;
+
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.io.IOException;
+
+/**
+ * author : octopus yan
+ *
email : octopus_yan@foxmail.com
+ *
description : 剪切板工具
+ *
create : 2022-4-14 23:21
+ */
+public class ClipUtil {
+ //获取系统剪切板
+ private static final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+
+ public static void setClip(String data) {
+ //构建String数据类型
+ StringSelection stringSelection = new StringSelection(data);
+ //添加文本到系统剪切板
+ clipboard.setContents(stringSelection, null);
+ }
+
+ public static String getString() {
+ Transferable content = clipboard.getContents(null);//从系统剪切板中获取数据
+ if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {//判断是否为文本类型
+ try {
+ //从数据中获取文本值
+ return (String) content.getTransferData(DataFlavor.stringFlavor);
+ } catch (UnsupportedFlavorException | IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/top/octopusyan/utils/DomainUtil.java b/src/main/java/top/octopusyan/utils/DomainUtil.java
index 8b988a6..ddd911e 100644
--- a/src/main/java/top/octopusyan/utils/DomainUtil.java
+++ b/src/main/java/top/octopusyan/utils/DomainUtil.java
@@ -2,7 +2,6 @@ package top.octopusyan.utils;
import top.octopusyan.config.ProxyConfig;
import top.octopusyan.manager.http.request.ProxySetup;
-import top.octopusyan.model.ProxySetupModel;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -28,10 +27,6 @@ public class DomainUtil {
return "." + serverPath;
}
- public static boolean isHttp(ProxySetupModel model){
- return ProxyConfig.getTypeIndex(model.getProxyType()) < 2;
- }
-
/**
* 是否自定义隧道
*/
@@ -39,14 +34,17 @@ public class DomainUtil {
return !setup.getDomain().contains(getServerPath(setup));
}
+ public static boolean isCustomize(ProxySetup setup, String serverName) {
+ return !setup.getDomain().contains(ProxyConfig.getServerPath(serverName));
+ }
+
private static String getServerPath(ProxySetup setup) {
return ProxyConfig.getServerPath(ProxyConfig.getServerName(setup.getNode()));
}
public static String getDomainAddress(String domain) {
try {
- String hostAddress = InetAddress.getByName(domain).getHostAddress();
- return hostAddress;
+ return InetAddress.getByName(domain).getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
return null;
diff --git a/src/main/java/top/octopusyan/utils/EncryptionUtil.java b/src/main/java/top/octopusyan/utils/EncryptionUtil.java
index 90a3e00..047ee2c 100644
--- a/src/main/java/top/octopusyan/utils/EncryptionUtil.java
+++ b/src/main/java/top/octopusyan/utils/EncryptionUtil.java
@@ -50,7 +50,7 @@ public class EncryptionUtil {
* 加解密算法/工作模式/填充方式
*/
private static final String ECB_MOB = "DES/ECB/PKCS5Padding";
-// private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
+ // private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
private static final Charset UTF_8 = StandardCharsets.UTF_8;
@@ -79,7 +79,7 @@ public class EncryptionUtil {
return getSHA(defaultPassword + getMessageDigestString("frp." + defaultPassword, hexDigits, MessageDigest_SHA1));
}
- public static String decodeAppData(String str){
+ public static String decodeAppData(String str) {
try {
return DESdecode(str, getDefaultPassword());
} catch (Exception e) {
@@ -88,7 +88,7 @@ public class EncryptionUtil {
}
}
- public static String encodeAppData(String str){
+ public static String encodeAppData(String str) {
try {
return DESencode(str, getDefaultPassword());
} catch (Exception e) {
@@ -105,16 +105,14 @@ public class EncryptionUtil {
}
public static String getSHA(String spara) {
- String sRtn = null;
- try {
- byte[] plainText = spara.getBytes(UTF_8);
- // 开始使用算法
- MessageDigest_SHA1.update(plainText);
- // 输出算法运算结果
- sRtn = BASE64Encoder.encode(MessageDigest_SHA1.digest());
- } catch (Exception e) {
- logger.error("SHA-1 160加密出错:" + e.getMessage());
- }
+ String sRtn;
+
+ byte[] plainText = spara.getBytes(UTF_8);
+ // 开始使用算法
+ MessageDigest_SHA1.update(plainText);
+ // 输出算法运算结果
+ sRtn = BASE64Encoder.encode(MessageDigest_SHA1.digest());
+
return sRtn;
}
@@ -124,16 +122,13 @@ public class EncryptionUtil {
* @param data 加密内容
* @param password 秘钥
*/
- public static String DESencode(String data, String password) {
+ public static String DESencode(String data, String password) throws Exception {
byte[] pasByte;
- try {
- Key key = getDESKey(password);
- Cipher_ECB_MOB.init(Cipher.ENCRYPT_MODE, key);
- pasByte = Cipher_ECB_MOB.doFinal(data.getBytes(UTF_8));
- } catch (Exception e) {
- logger.error("DES加密出错:" + e.getMessage());
- throw new RuntimeException("DES加密出错:" + e.getMessage());
- }
+
+ Key key = getDESKey(password);
+ Cipher_ECB_MOB.init(Cipher.ENCRYPT_MODE, key);
+ pasByte = Cipher_ECB_MOB.doFinal(data.getBytes(UTF_8));
+
return Base64.encode(pasByte);
}
@@ -259,19 +254,16 @@ public class EncryptionUtil {
* @return MD5加密后生成32位(大写字母 + 数字)字符串
*/
public static String MD5Upper(String plainText, String saltValue) {
- try {
- // 使用指定的字节更新摘要
- MessageDigest_MD5.update(plainText.getBytes());
- MessageDigest_MD5.update(saltValue.getBytes());
- // 获得密文
- byte[] mdResult = MessageDigest_MD5.digest();
- // 把密文转换成十六进制的字符串形式
- return getHexString(hexDigitsLower, mdResult);
- } catch (Exception e) {
- logger.error("MDS 加密出错:" + e.getMessage());
- throw new RuntimeException("MDS 加密出错:" + e.getMessage());
- }
+ // 使用指定的字节更新摘要
+ MessageDigest_MD5.update(plainText.getBytes());
+ MessageDigest_MD5.update(saltValue.getBytes());
+
+ // 获得密文
+ byte[] mdResult = MessageDigest_MD5.digest();
+ // 把密文转换成十六进制的字符串形式
+ return getHexString(hexDigitsLower, mdResult);
+
}
/**
@@ -282,6 +274,14 @@ public class EncryptionUtil {
return getMessageDigestString(plainText, hexDigitsLower, MessageDigest_MD5);
}
+ /**
+ * MD5加密后生成16位(小写字母+数字)字符串
+ * 同 MD5Lower() 一样
+ */
+ public static String MD5_16(String plainText) {
+ return getMessageDigestString(plainText, hexDigitsLower, MessageDigest_MD5).substring(8, 24);
+ }
+
/**
* 校验MD5码
*
diff --git a/src/main/java/top/octopusyan/utils/TooltipUtil.java b/src/main/java/top/octopusyan/utils/TooltipUtil.java
new file mode 100644
index 0000000..2d4de39
--- /dev/null
+++ b/src/main/java/top/octopusyan/utils/TooltipUtil.java
@@ -0,0 +1,139 @@
+package top.octopusyan.utils;
+
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.event.EventHandler;
+import javafx.scene.control.Tooltip;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.Pane;
+import javafx.stage.Window;
+
+/**
+ *
author : octopus yan
+ *
email : octopus_yan@foxmail.com
+ *
description : 提示工具
+ *
create : 2022-4-12 17:16 *
+ */
+public class TooltipUtil {
+ private static TooltipUtil util;
+ private final Tooltip tooltip = new Tooltip();
+ private Window owner;
+ private ChangeListener xListener;
+ private ChangeListener yListener;
+ private boolean paneMove = false;
+
+ private TooltipUtil(Window window) {
+ this.owner = window;
+ this.tooltip.styleProperty().set(
+ "-fx-background-color: white;" +
+ "-fx-text-fill: grey;" +
+ "-fx-font-size: 12px;"
+ );
+ }
+
+ public static TooltipUtil getInstance(Pane pane) {
+ if (pane == null) return null;
+ Window window = pane.getScene().getWindow();
+ if (window == null) return null;
+
+ if (util == null) {
+ util = new TooltipUtil(window);
+ // 窗口位置监听
+ util.xListener = (observable, oldValue, newValue) -> {
+ util.tooltip.setAnchorX(util.tooltip.getAnchorX() + (newValue.doubleValue() - oldValue.doubleValue()));
+ util.paneMove = true;
+ };
+ util.yListener = (observable, oldValue, newValue) -> {
+ util.tooltip.setAnchorY(util.tooltip.getAnchorY() + (newValue.doubleValue() - oldValue.doubleValue()));
+ util.paneMove = true;
+ };
+ util.tooltip.focusedProperty().addListener((observable, oldValue, newValue) -> {
+ if (!newValue) util.paneMove = false;
+ });
+ // 随窗口移动
+ util.owner.xProperty().addListener(util.xListener);
+ util.owner.yProperty().addListener(util.yListener);
+ }
+
+ if (!window.equals(util.owner)) {
+ // 删除旧监听
+ util.owner.xProperty().removeListener(util.xListener);
+ util.owner.yProperty().removeListener(util.yListener);
+ // 新窗口
+ util.owner = window;
+ // 随窗口移动
+ util.owner.xProperty().addListener(util.xListener);
+ util.owner.yProperty().addListener(util.yListener);
+ }
+
+ // 点击关闭
+ pane.setOnMouseClicked(event -> {
+ if (!util.paneMove) util.tooltip.hide();
+ util.paneMove = false;
+ });
+
+ util.tooltip.hide();
+
+ return util;
+ }
+
+ public void showProxyTypeTip(MouseEvent event) {
+ tooltip.setText(
+ "提示:XTCP 映射成功率并不高,具体取决于 NAT 设备的复杂度。\n" +
+ "TCP :基础的 TCP 映射,适用于大多数服务,例如远程桌面、SSH、Minecraft、泰拉瑞亚等\n" +
+ "UDP :基础的 UDP 映射,适用于域名解析、部分基于 UDP 协议的游戏等\n" +
+ "HTTP :搭建网站专用映射,并通过 80 端口访问\n" +
+ "HTTPS :带有 SSL 加密的网站映射,通过 443 端口访问,服务器需要支持 SSL\n" +
+ "XTCP :客户端之间点对点 (P2P) 连接协议,流量不经过服务器,适合大流量传输的场景,需要两台设备之间都运行一个客户端\n" +
+ "STCP :安全交换 TCP 连接协议,基于 TCP,访问此服务的用户也需要运行一个客户端,才能建立连接,流量由服务器转发"
+ );
+ show(event);
+ }
+
+ public void showHttpUserTip(MouseEvent event) {
+ tooltip.setText(
+ "您的内网服务映射到外网之后,所有知道外网访问地址的人均可以访问;\n" +
+ "但如果您在此处设置用户名、密码,则只有知道用户名、密码的访客才能访问"
+ );
+ show(event);
+ }
+
+ public void showProxyStatusTip(MouseEvent event) {
+ tooltip.setText("当有多个连接时,您可以选择启用或停用某些连接");
+ show(event);
+ }
+
+ private void show(MouseEvent event) {
+
+ if (tooltip.isShowing()) {
+ tooltip.hide();
+ } else {
+ tooltip.show(owner);
+ double mx = event.getScreenX();
+ double my = event.getScreenY();
+ double tw = tooltip.widthProperty().doubleValue();
+ double th = tooltip.heightProperty().doubleValue();
+
+ tooltip.setX(mx - tw / 2);
+ tooltip.setY(my - th - 10);
+ }
+ }
+
+ public void hide(){
+ tooltip.hide();
+ }
+
+ public boolean isShowing(){
+ return tooltip.isShowing();
+ }
+
+ public void showP2pPwdTip(MouseEvent event) {
+ tooltip.setText("以访客模式连接时的密码,仅限 XTCP/STCP\n只有访问密码一致的用户才能访问到此服务");
+ show(event);
+ }
+
+ public void showP2pServerName(MouseEvent event) {
+ tooltip.setText("要访问的服务的名字(系统加密)");
+ show(event);
+ }
+}
diff --git a/src/main/resources/css/main.css b/src/main/resources/css/main.css
index 80c9d1f..cd7ca99 100644
--- a/src/main/resources/css/main.css
+++ b/src/main/resources/css/main.css
@@ -55,6 +55,13 @@
-fx-text-fill: white;
}
+.proxyListItemLabel {
+ -fx-font-size: 16px;
+}
+.proxyListItemIcon {
+ -fx-font-size: 14px;
+}
+
.proxyListItem-run FontIcon {
-fx-icon-color: linear-gradient(#95f257, #91e5ac);
}
@@ -66,7 +73,7 @@
}
/* 面板背景 */
-#proxySetupPane, #proxyListPane {
+#proxySetupPane, #proxyListPane, .whitePane {
-fx-background-color: white;
}
@@ -101,7 +108,7 @@
-fx-font-size: 16px;
-fx-font-family: "Microsoft YaHei";
}
-.startProxyBtn {
+.startProxyBtn, #copyP2pConfig, #importP2pConfig {
-fx-text-fill: white;
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
}
@@ -120,3 +127,9 @@
-fx-font-family: monospace;
-fx-background-color: white;
}
+
+.proxySetupItemBox {
+ -fx-pref-width: 520;
+ -fx-pref-height: 50;
+ -fx-padding: 10 0 10 0;
+}
\ No newline at end of file
diff --git a/src/main/resources/fxml/login.fxml b/src/main/resources/fxml/login.fxml
index 66b923b..3cfb8d5 100644
--- a/src/main/resources/fxml/login.fxml
+++ b/src/main/resources/fxml/login.fxml
@@ -2,6 +2,7 @@
+
@@ -23,7 +24,7 @@
-
+
@@ -88,8 +89,8 @@
-
+
@@ -108,6 +109,11 @@
+
+
+
+
+
diff --git a/src/main/resources/fxml/main.fxml b/src/main/resources/fxml/main.fxml
index ed5df71..65351ee 100644
--- a/src/main/resources/fxml/main.fxml
+++ b/src/main/resources/fxml/main.fxml
@@ -6,7 +6,7 @@
-
+
@@ -14,32 +14,33 @@
-
-
-
-
+
+
+
+
-
+
+
+
+
-
-
+
+
-
-
-
-
+
-
+
@@ -50,21 +51,15 @@
-
-
-
-
+
-
+
-
-
-
-
@@ -73,17 +68,14 @@
-
-
-
-
+
-
+
-
+
@@ -97,38 +89,94 @@
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
-
+
-
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ promptText="内网服务IP (IPv4)" styleClass="inputText-left" text="127.0.0.1">
@@ -141,28 +189,89 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
+
@@ -178,21 +287,54 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
@@ -202,7 +344,7 @@
-
+
@@ -213,19 +355,15 @@
-
+
-
+
-
-
-
-
-
@@ -265,7 +403,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/register.fxml b/src/main/resources/fxml/register.fxml
index abfe091..206b6e4 100644
--- a/src/main/resources/fxml/register.fxml
+++ b/src/main/resources/fxml/register.fxml
@@ -1,11 +1,14 @@
+
-
+
@@ -14,42 +17,51 @@
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+