From 4b0c793e42a170f791aa0493a73b4cb094daf52b Mon Sep 17 00:00:00 2001 From: octopusYan <935540320@qq.com> Date: Sat, 16 Apr 2022 00:47:52 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0stcp,xtcp=E9=9A=A7=E9=81=93?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=20=E6=B7=BB=E5=8A=A0stcp,xtcp=E9=9A=A7?= =?UTF-8?q?=E9=81=93=E7=B1=BB=E5=9E=8B=E8=AE=BE=E7=BD=AE=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E3=80=81=E5=AF=BC=E5=87=BA=E5=8A=9F=E8=83=BD=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0HTTP=20Basic=20Auth=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 +- pom.xml | 3 +- .../top/octopusyan/YanFrpApplication.java | 7 +- .../top/octopusyan/base/BaseController.java | 25 +- .../top/octopusyan/config/ProxyConfig.java | 42 +- .../top/octopusyan/config/TextValidate.java | 30 +- .../controller/LoginController.java | 31 +- .../octopusyan/controller/MainController.java | 487 ++++++++++++++---- .../controller/RegisterController.java | 12 +- .../top/octopusyan/manager/FrpManager.java | 83 ++- .../top/octopusyan/manager/ProxyManager.java | 29 +- .../manager/http/request/ProxySetup.java | 7 + .../top/octopusyan/model/ApplicatonStore.java | 7 +- .../top/octopusyan/model/ProxySetupModel.java | 59 ++- .../java/top/octopusyan/utils/AlertUtil.java | 60 +-- .../java/top/octopusyan/utils/ClipUtil.java | 37 ++ .../java/top/octopusyan/utils/DomainUtil.java | 12 +- .../top/octopusyan/utils/EncryptionUtil.java | 68 +-- .../top/octopusyan/utils/TooltipUtil.java | 139 +++++ src/main/resources/css/main.css | 17 +- src/main/resources/fxml/login.fxml | 12 +- src/main/resources/fxml/main.fxml | 304 ++++++++--- src/main/resources/fxml/register.fxml | 48 +- 23 files changed, 1169 insertions(+), 365 deletions(-) create mode 100644 src/main/java/top/octopusyan/utils/ClipUtil.java create mode 100644 src/main/java/top/octopusyan/utils/TooltipUtil.java diff --git a/README.md b/README.md index 50885ba..5fc5501 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,23 @@ # Yan Frp -基于javafx开发的 frp 客户端, 后端使用的是开源的 frp管理面板 [SakuraPanel](https://github.com/ZeroDream-CN/SakuraPanel) -网址:https://frp.octopusyan.top +基于javafx开发的 frp 客户端 +后台管理面板使用的是开源的 frp 管理面板 [SakuraPanel](https://github.com/ZeroDream-CN/SakuraPanel) + + ps:为了方便 YanFrp 我自己修改了些面板代码,如果想自己搭建需要改造 + + 管理面板网址:https://frp.octopusyan.top/?page=login # 说明: 1. +# 后续开发计划 + +- ~~HTTP Basic Auth~~ (已完成) +- ~~添加p2p连接方式(xtcp,stcp)~~ (已完成) +- HTTP URL路由 +- 待添加... + # 界面: 登录 diff --git a/pom.xml b/pom.xml index 78b628f..f5086b8 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ top.octopusyan YanFrp - 1.0.3-SNAPSHOT + 1.1 YanFrp @@ -84,7 +84,6 @@ 2.11.0 - 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

* "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 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + @@ -213,19 +355,15 @@ - + - + - - 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 @@ - + - - - + + + - + - + - + - + - - + + - + - + - + - + - + - + - + +