This commit is contained in:
octopusYan 2022-04-09 03:59:20 +08:00
parent 8c41c8392b
commit 13fa56b76f
67 changed files with 1867 additions and 635 deletions

71
pom.xml
View File

@ -55,6 +55,7 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.14</version>
<scope>compile</scope> <!-- 解决 slf4j 冲突问题 -->
</dependency>
<!-- slf4j -->
@ -70,12 +71,19 @@
<scope>compile</scope>
</dependency>
<!-- commons-lang3 -->
<!-- commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
@ -157,25 +165,60 @@
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<groupId>com.zenjava</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<version>8.8.3</version>
<configuration>
<vendor>analysis</vendor>
<mainClass>top.octopusyan.YanFrpLuncher</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<!-- Default configuration for running with: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<mainClass>top.octopusyan/top.octopusyan.YanFrpLauncher</mainClass>
<launcher>app</launcher>
<jlinkZipName>app</jlinkZipName>
<jlinkImageName>app</jlinkImageName>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
</configuration>
<id>make-assembly</id>
<!-- 绑定到package生命周期 -->
<phase>package</phase>
<goals>
<!-- 只运行一次 -->
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifest>
<mainClass>top.octopusyan.YanFrpLuncher</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- <plugin>-->
<!-- <groupId>org.openjfx</groupId>-->
<!-- <artifactId>javafx-maven-plugin</artifactId>-->
<!-- <version>0.0.8</version>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- &lt;!&ndash; Default configuration for running with: mvn clean javafx:run &ndash;&gt;-->
<!-- <id>default-cli</id>-->
<!-- <configuration>-->
<!-- <mainClass>top.octopusyan/top.octopusyan.YanFrpLuncher</mainClass>-->
<!-- <launcher>app</launcher>-->
<!-- <jlinkZipName>app</jlinkZipName>-->
<!-- <jlinkImageName>app</jlinkImageName>-->
<!-- <noManPages>true</noManPages>-->
<!-- <stripDebug>true</stripDebug>-->
<!-- <noHeaderFiles>true</noHeaderFiles>-->
<!-- </configuration>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- </plugin>-->
</plugins>
</build>

View File

@ -1,21 +1,25 @@
package top.octopusyan;
import com.sun.org.slf4j.internal.Logger;
import com.sun.org.slf4j.internal.LoggerFactory;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.fxml.JavaFXBuilderFactory;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import top.octopusyan.common.http.HttpConfig;
import top.octopusyan.common.http.config.LogStrategy;
import top.octopusyan.common.http.request.RequestHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.octopusyan.base.BaseController;
import top.octopusyan.manager.http.HttpConfig;
import top.octopusyan.manager.http.config.LogStrategy;
import top.octopusyan.manager.http.request.RequestHandler;
import top.octopusyan.http.OkHttpClientConfig;
import top.octopusyan.utils.AlertUtil;
import top.octopusyan.utils.ApplicatonStore;
import top.octopusyan.utils.FrpUtil;
import top.octopusyan.utils.FxmlUtil;
/**
* @author : octopus yan
@ -24,12 +28,15 @@ import top.octopusyan.utils.ApplicatonStore;
* @create : 2022-3-29 15:00
*/
public class YanFrpApplication extends Application {
private static final Logger LOGGER = LoggerFactory.getLogger(YanFrpApplication.class);
private static final Logger logger = LoggerFactory.getLogger(YanFrpApplication.class);
@Override
public void init() throws Exception {
super.init();
LOGGER.debug("init...");
logger.info("init...");
// 初始化frp客户端临时文件
FrpUtil.initFrpc();
// 网络请求设置
HttpConfig.with(OkHttpClientConfig.httpClient())
@ -49,7 +56,7 @@ public class YanFrpApplication extends Application {
@Override
public void start(Stage stage) {
LOGGER.debug("start...");
logger.info("start...");
// TODO 全局异常处理... (emm有点草率先这样了)
Thread.setDefaultUncaughtExceptionHandler((t, e) -> Platform.runLater(() -> showErrorDialog(t, e)));
@ -61,8 +68,12 @@ public class YanFrpApplication extends Application {
ApplicatonStore.setRegisterSuccess(false);
try {
// 静态写法无法获取controler
// StackPane root = new FXMLLoader(this.getClass().getResource("/fxml/login.fxml")).load();//底层面板
FXMLLoader fxmlLoader = FxmlUtil.init("/fxml/login.fxml");
StackPane root = fxmlLoader.load();//底层面板
stage.initStyle(StageStyle.TRANSPARENT);
StackPane root = new FXMLLoader(this.getClass().getResource("/fxml/login.fxml")).load();//底层面板
Scene scene = new Scene(
root,
root.getPrefWidth() + 20,
@ -73,22 +84,26 @@ public class YanFrpApplication extends Application {
stage.setScene(scene);
stage.show();
BaseController controller = fxmlLoader.getController();
controller.setApplication(this);
} catch (Throwable t) {
showErrorDialog(Thread.currentThread(), t);
}
LOGGER.debug("start end...");
logger.debug("start end...");
}
private void showErrorDialog(Thread t, Throwable e) {
e.printStackTrace();
AlertUtil.exceptionAlert(new Exception(e)).show();
Platform.runLater(() -> AlertUtil.exceptionAlert(new Exception(e)).show());
}
@Override
public void stop() throws Exception {
super.stop();
LOGGER.debug("stop...");
FrpUtil.clearTmp();
logger.info("stop...");
}
public static void main(String[] args) {

View File

@ -0,0 +1,33 @@
package top.octopusyan;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.octopusyan.base.BaseController;
import top.octopusyan.http.OkHttpClientConfig;
import top.octopusyan.manager.http.HttpConfig;
import top.octopusyan.manager.http.config.LogStrategy;
import top.octopusyan.manager.http.request.RequestHandler;
import top.octopusyan.utils.AlertUtil;
import top.octopusyan.utils.ApplicatonStore;
import top.octopusyan.utils.FrpUtil;
import top.octopusyan.utils.FxmlUtil;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : YanFrp Application
* @create : 2022-3-29 15:00
*/
public class YanFrpLuncher {
public static void main(String[] args) {
Application.launch(YanFrpApplication.class, args);
}
}

View File

@ -1,6 +1,7 @@
package top.octopusyan.base;
import com.jfoenix.controls.JFXButton;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
@ -11,6 +12,7 @@ import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import top.octopusyan.utils.FxmlUtil;
import top.octopusyan.utils.Loading;
import java.io.IOException;
@ -25,21 +27,35 @@ import java.util.ResourceBundle;
*/
public abstract class BaseController<P extends Pane> implements Initializable {
private Application application;
private double xOffSet = 0, yOffSet = 0;
private volatile Loading loading;
public void jumpTo(BaseController<P> controller){
public void jumpTo(BaseController<P> controller) throws IOException {
FXMLLoader fxmlLoader = FxmlUtil.init(controller.getRootFxmlPath());
Scene scene = getRootPanel().getScene();
try {
Pane root = FXMLLoader.load(getClass().getResource(controller.getRootFxmlPath()));
scene.setRoot(root);
Stage stage = (Stage) scene.getWindow();
stage.setWidth(root.getPrefWidth() + 20);
stage.setHeight(root.getPrefHeight() + 20);
} catch (IOException e) {
e.printStackTrace();
}
double oldHeight = getRootPanel().getPrefHeight();
double oldWidth = getRootPanel().getPrefWidth();
Pane root = fxmlLoader.load();
Stage stage = (Stage) scene.getWindow();
// 窗口大小
double newWidth = root.getPrefWidth() + 20;
double newHeight = root.getPrefHeight() + 20;
// 窗口位置
double newX = stage.getX() - (newWidth - oldWidth) / 2;
double newY = stage.getY() - (newHeight - oldHeight) / 2;
scene.setRoot(root);
stage.setX(newX < 0 ? 0 : newX);
stage.setY(newY < 0 ? 0 : newY);
stage.setWidth(newWidth);
stage.setHeight(newHeight);
controller = fxmlLoader.getController();
controller.setApplication(getApplication());
}
@Override
@ -86,21 +102,29 @@ public abstract class BaseController<P extends Pane> implements Initializable {
initViewAction();
}
public void showLoading(){
public void showLoading() {
showLoading(null);
}
public void showLoading(String message){
if(loading == null) loading = new Loading((Stage) getRootPanel().getScene().getWindow());
if(!StringUtils.isEmpty(message))loading.showMessage(message);
public void showLoading(String message) {
if (loading == null) loading = new Loading((Stage) getRootPanel().getScene().getWindow());
if (!StringUtils.isEmpty(message)) loading.showMessage(message);
loading.show();
}
public boolean isLoadShowing(){
public void setApplication(Application application) {
this.application = application;
}
public Application getApplication() {
return application;
}
public boolean isLoadShowing() {
return loading != null && loading.showing();
}
public void stopLoading(){
public void stopLoading() {
loading.closeStage();
}
@ -159,11 +183,16 @@ public abstract class BaseController<P extends Pane> implements Initializable {
/**
* 关闭窗口
*/
public void onDestroy(){
public void onDestroy() {
Stage stage = (Stage) getRootPanel().getScene().getWindow();
stage.hide();
stage.close();
Platform.exit();
System.exit(0);
try {
Thread.sleep(2000);
Platform.exit();
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

View File

@ -1,10 +0,0 @@
package top.octopusyan.common;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : test
* @create : 2022-4-1 23:36
*/
public class test {
}

View File

@ -1,5 +1,9 @@
package top.octopusyan.config;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@ -10,14 +14,19 @@ import java.util.Map;
* @create : 2022-4-5 18:14
*/
public class ProxyConfig {
private static Map<String, String> serverPath = new HashMap<>();
private static Map<String, Integer> typePort = new HashMap<>();
private static final Map<String, String> serverPath = new HashMap<>();
private static final Map<String, String> serverIp = new HashMap<>();
private static final Map<String, Integer> typePort = new HashMap<>();
static {
serverPath.put("香港", "xg.frp.octopusyan.top");
serverPath.put("北京", "bj.frp.octopusyan.top");
serverPath.put("上海", "frp.octopusyan.top");
serverIp.put("香港", "101.32.202.135");
serverIp.put("北京", "112.125.120.135");
serverIp.put("上海", "81.68.214.67");
typePort.put("http", 80);
typePort.put("https", 80);
typePort.put("ssh", 22);
@ -25,8 +34,25 @@ public class ProxyConfig {
}
@Getter
public enum ProxyServer {
香港, 北京, 上海
xg("香港", 1),
bj("北京", 2),
sh("上海", 3);
private final String serverName;
private final int value;
ProxyServer(String serverName, int value) {
this.serverName = serverName;
this.value = value;
}
public static ProxyServer valueOf(int node) {
for (ProxyServer value : values()) {
if (value.value == node) return value;
}
return null;
}
}
public enum ProxyType {
@ -36,7 +62,7 @@ public class ProxyConfig {
TCP("tcp"),
;
private String type;
private final String type;
public String getType() {
return type;
@ -51,7 +77,49 @@ public class ProxyConfig {
return serverPath.get(serverName);
}
public static int getServerNode(String serverName) {
for (ProxyServer server : Arrays.asList(ProxyServer.values())) {
if(server.serverName.equals(serverName)) return server.value;
}
return 3;
}
/**
* 获取服务名称
* @param node 服务器标签
*/
public static String getServerName(int node) {
ProxyServer proxyServer = ProxyServer.valueOf(node);
assert proxyServer != null;
return proxyServer.getServerName();
}
/**
* 获取服务器IP地址
* @param serverName 服务器名称
*/
public static String getServerIP(String serverName) {
return serverIp.get(serverName);
}
/**
* 获取服务器IP地址
* @param node 服务器标签
*/
public static String getServerIP(int node) {
return serverIp.get(getServerName(node));
}
/**
* 获取代理类型默认端口
* @param type 类型名称
*/
public static Integer getTypePort(String type) {
return typePort.get(type);
}
public static Integer getTypeIndex(String type) {
return Arrays.asList(ProxyType.values()).indexOf(ProxyType.valueOf(StringUtils.upperCase(type)));
}
}

View File

@ -0,0 +1,155 @@
package top.octopusyan.config;
import com.jfoenix.validation.RegexValidator;
import com.jfoenix.validation.RequiredFieldValidator;
import com.jfoenix.validation.StringLengthValidator;
import com.jfoenix.validation.base.ValidatorBase;
import top.octopusyan.config.ProxyConfig;
import top.octopusyan.model.ProxySetupModel;
import top.octopusyan.utils.DomainUtil;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : JFX文本校验
* @create : 2022-4-2 17:03
*/
public class TextValidate {
// /**
// * 账号格式错误
// */
// public static ValidatorBase AccountFail = new ValidatorBase() {
// @Override
// protected void eval() {
// setMessage("账号格式错误");
// hasErrors.set(true);
// }
// };
/**
* 账号不能为空
*/
public static RequiredFieldValidator AccoountRequired = new RequiredFieldValidator("账号不能为空!");
/**
* 账号格式
*/
public static RegexValidator AccoountValidator = new RegexValidator("账号格式有误!");
/**
* 密码不能为空
*/
public static RequiredFieldValidator PasswordRequired = new RequiredFieldValidator("密码不能为空!");
/**
* 邮箱地址格式验证
*/
public static RegexValidator EmailFormat = new RegexValidator("邮箱地址格式错误");
/**
* 域名为空检查
*/
public static RequiredFieldValidator DomainRequired = new RequiredFieldValidator("域名不能为空!");
/**
* 端口为空检查
*/
public static RequiredFieldValidator PortRequired = new RequiredFieldValidator("端口不能为空");
/**
* 端口格式检查
*/
public static final RegexValidator PortFormat = new RegexValidator("端口格式错误");
/**
* IP为空检查
*/
public static RequiredFieldValidator IpRequired = new RequiredFieldValidator("本地IP不能为空");
/**
* IP格式检查
*/
public static final RegexValidator IpFormat = new RegexValidator("本地IP格式错误");
static {
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])$");
}
/**
* 文本长度校验
* <p/> message > name + "长度有误"
*/
public static StringLengthValidator getLengthValidator(String name, int length) {
return new StringLengthValidator(name + "长度有误", length);
}
/**
* 文本长度校验
* <p/> message > name + "长度有误,应在" + min + "" + max + "之间"
*/
public static RegexValidator getLengthValidator(int min, int max, String name) {
String message = name + "长度有误,应在" + min + "" + max + "之间";
RegexValidator validator = new RegexValidator(message);
validator.setRegexPattern("[a-zA-Z0-9_-]{" + min + "," + max + "}$");
return validator;
}
/**
* 域名格式检查
*/
public static ValidatorBase domainFormatValidator(ProxySetupModel model) {
return new ValidatorBase("域名格式错误") {
@Override
protected void eval() {
if (!DomainUtil.isCustomize(model.get()))
hasErrors.set(false);
else {
boolean matches = Pattern.compile("^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$")
.matcher(model.getDomain() + model.getDomainSuffix()).matches();
hasErrors.set(!matches);
}
}
};
}
/**
* 域名格式检查
*/
public static ValidatorBase domainLengthValidator(ProxySetupModel model) {
return new RegexValidator("请输入子域名长度不小于3个字符") {
@Override
protected void eval() {
setRegexPattern("^[a-zA-Z0-9_-]{3,18}$");
if (DomainUtil.isHttp(model) && !DomainUtil.isCustomize(model.get())) {
super.eval();
} else {
hasErrors.set(false);
}
}
};
}
/**
* 自定义域名解析检查
*/
public static ValidatorBase domainAddressValidator(ProxySetupModel model) {
return new ValidatorBase("请输入您的域名,并解析至: " + ProxyConfig.getServerIP(model.getServer())) {
@Override
protected void eval() {
if (!DomainUtil.isCustomize(model.get()))
hasErrors.set(false);
else
hasErrors.set(
!Objects.equals(
ProxyConfig.getServerIP(model.getServer()),
DomainUtil.getDomainAddress(model.getDomain())
)
);
}
};
}
}

View File

@ -5,39 +5,28 @@ import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXTextField;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.kordamp.ikonli.javafx.FontIcon;
import top.octopusyan.base.BaseController;
import top.octopusyan.common.http.EasyHttp;
import top.octopusyan.common.http.api.NotParamApi;
import top.octopusyan.common.http.config.HttpConstant;
import top.octopusyan.common.http.listener.OnHttpListener;
import top.octopusyan.manager.http.EasyHttp;
import top.octopusyan.manager.http.api.NotParamApi;
import top.octopusyan.manager.http.config.HttpConstant;
import top.octopusyan.manager.http.listener.OnHttpListener;
import top.octopusyan.config.TextValidate;
import top.octopusyan.http.Api;
import top.octopusyan.http.request.LoginParam;
import top.octopusyan.http.request.ProxySetup;
import top.octopusyan.utils.AlertUtil;
import top.octopusyan.utils.ApplicatonStore;
import top.octopusyan.utils.ProxyUtil;
import top.octopusyan.utils.TextValidate;
import top.octopusyan.utils.*;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import java.io.IOException;
/**
* @author : octopus yan
@ -119,8 +108,8 @@ public class LoginController extends BaseController<StackPane> implements Initia
String account = ApplicatonStore.getAccount();
String password = ApplicatonStore.getPassword();
if(!StringUtils.isEmpty(account)) accountTextField.setText(account);
if(!StringUtils.isEmpty(password)) {
if (!StringUtils.isEmpty(account)) accountTextField.setText(account);
if (!StringUtils.isEmpty(password)) {
passwordTextField.setText(password);
seePwdTextField.setText(password);
}
@ -172,7 +161,13 @@ public class LoginController extends BaseController<StackPane> implements Initia
public void initViewAction() {
// 注册
registerBtn.setOnMouseClicked(event -> jumpTo(new RegisterController()));
registerBtn.setOnMouseClicked(event -> {
try {
jumpTo(new RegisterController());
} catch (IOException e) {
e.printStackTrace();
}
});
// 查看密码
seePwdIconBtn.setOnMouseClicked(event -> {
@ -187,22 +182,23 @@ public class LoginController extends BaseController<StackPane> implements Initia
// 登录
loginBtn.setOnMouseClicked(event -> login());
accountTextField.setOnKeyPressed(event -> {
if(event.getCode() == KeyCode.ENTER) login();
if (event.getCode() == KeyCode.ENTER) login();
});
passwordTextField.setOnKeyPressed(event -> {
if(event.getCode() == KeyCode.ENTER) login();
if (event.getCode() == KeyCode.ENTER) login();
});
// 自动登录
if((ApplicatonStore.isAutoLogin() || ApplicatonStore.isRegisterSuccess()) &&
if ((ApplicatonStore.isAutoLogin() || ApplicatonStore.isRegisterSuccess()) &&
!StringUtils.isEmpty(ApplicatonStore.getAccount()) &&
!StringUtils.isEmpty(ApplicatonStore.getPassword())
){
) {
login();
}
}
private void login(){
private void login() {
// 获取文本校验结果
boolean pwdValidate = pwdParent.getChildren().contains(passwordTextField) ?
passwordTextField.validate() : seePwdTextField.validate();
@ -215,15 +211,20 @@ public class LoginController extends BaseController<StackPane> implements Initia
.request(new OnHttpListener<String>() {
@Override
public void onSucceed(String result) {
Document html = Jsoup.parse(result);
// 登录出错
if (result.contains("alert-danger")) {
Platform.runLater(() -> AlertUtil.error(getHtmlErrorMessage(html)).show());
if (!JsoupUtil.isAlertSuccess(result)) {
Platform.runLater(() -> AlertUtil.error(JsoupUtil.getErrorMessage(result)).show());
return;
}
// TODO 登录成功
setCsrf();
jumpTo(new MainController());
Platform.runLater(() -> {
try {
jumpTo(new MainController());
} catch (IOException e) {
e.printStackTrace();
}
});
}
});
}
@ -242,10 +243,6 @@ public class LoginController extends BaseController<StackPane> implements Initia
});
}
private String getHtmlErrorMessage(Document html) {
return html.body().getElementsByClass("alert alert-danger alert-dismissable").text().substring(1);
}
@Override
public void onDestroy() {
EasyHttp.cancel();

View File

@ -1,14 +1,22 @@
package top.octopusyan.controller;
import com.jfoenix.controls.*;
import com.jfoenix.validation.IntegerValidator;
import com.jfoenix.validation.RequiredFieldValidator;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
@ -19,13 +27,18 @@ import top.octopusyan.config.ProxyConfig;
import top.octopusyan.config.ProxyConfig.ProxyServer;
import top.octopusyan.config.ProxyConfig.ProxyType;
import top.octopusyan.http.request.ProxySetup;
import top.octopusyan.model.ProxySetupModel;
import top.octopusyan.utils.AlertUtil;
import top.octopusyan.utils.ApplicatonStore;
import top.octopusyan.utils.DomainUtil;
import top.octopusyan.utils.FrpUtil;
import top.octopusyan.utils.ProxyUtil;
import top.octopusyan.config.TextValidate;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.*;
import java.util.List;
/**
@ -40,6 +53,9 @@ public class MainController extends BaseController<StackPane> implements Initial
public static final String PROXY_LIST_ITEM_STOP_CLASS = "proxyListItem-stop";
public static final String PROXY_LIST_ITEM_RUN_CLASS = "proxyListItem-run";
public static final String PROXY_LIST_ITEM_CLOSE_CLASS = "proxyListItem-close";
public static final String PROXY_LIST_ITEM_SELECT_CLASS = "proxyListItem-select";
public static final String INPUT_LEFT_CLASS = "inputText-left";
public static final String INPUT_CLASS = "inputText";
@FXML
public StackPane root;
@ -48,11 +64,9 @@ public class MainController extends BaseController<StackPane> implements Initial
public JFXButton closeBtn, minimizeBtn;
@FXML
public JFXButton startProxyBtn;
public JFXButton startProxyBtn, addProxyBtn;
@FXML
public JFXButton addProxyBtn;
@FXML
public JFXComboBox<Label> proxyProtocolCombox;
public JFXComboBox<String> proxyProtocolComboBox;
@FXML
public JFXButton customizeDomainBtn;
@FXML
@ -62,17 +76,32 @@ public class MainController extends BaseController<StackPane> implements Initial
@FXML
public JFXTextField proxyNameTextField, localHostTextField, localPortTextField;
@FXML
public JFXComboBox<Label> proxyServerCB;
public JFXComboBox<String> proxyServerComboBox;
@FXML
public JFXTabPane tabPane;
@FXML
public JFXListView<Label> proxyListView;
@FXML
public Label domainHtinTextField;
@FXML
public HBox domainPane;
@FXML
public JFXButton resetProxyBtn, clearLogBtn, copyDomainBtn;
@FXML
public Tab proxyLogPane;
@FXML
public Hyperlink domainLink;
@FXML
public HBox proxyStatusPane;
private ToggleGroup openProxyGroup = new ToggleGroup();
private boolean customizeDomain = false;
private List<ProxySetup> proxyList;
private int selectProxy = 0;
private SimpleBooleanProperty customizeDomain = new SimpleBooleanProperty(false);
private List<ProxySetup> proxyList = new ArrayList<>();
private Map<String, FrpUtil> frpUtilMap = new HashMap<>();
private SimpleIntegerProperty selectProxy = new SimpleIntegerProperty(0);
private ProxySetupModel proxySetupModel;
private ProxySetup proxySetup;
private SimpleBooleanProperty setting = new SimpleBooleanProperty(false);
@Override
public boolean dragWindow() {
@ -107,194 +136,294 @@ public class MainController extends BaseController<StackPane> implements Initial
@Override
public void initData() {
// 初始化视图模型
proxySetup = ProxyUtil.initProxy(null);
proxySetupModel = new ProxySetupModel(proxySetup);
// 隧道类型
Arrays.asList(ProxyType.values()).forEach((type) -> proxyProtocolCombox.getItems().add(new Label(type.name())));
Arrays.asList(ProxyType.values()).forEach((type) -> proxyProtocolComboBox.getItems().add(type.name()));
// 服务器
Arrays.asList(ProxyServer.values()).forEach((server) -> proxyServerCB.getItems().add(new Label(server.name())));
Arrays.asList(ProxyServer.values()).forEach((server) -> proxyServerComboBox.getItems().add(server.getServerName()));
// 获取用户隧道列表
ProxyUtil.getList(setupList -> {
proxyList = setupList;
// 主线程执行
Platform.runLater(() -> {
// 设置隧道列表
ApplicatonStore.setProxySetupList(proxyList);
if (setupList != null)
proxyList = setupList;
// 初始化隧道设置
if (proxyList != null && proxyList.size() > 0) {
// 显示隧道列表
initProxyList(proxyList);
// 显示隧道设置
proxySetup = proxyList.get(0);
// 默认选中第一个
proxyListView.getSelectionModel().select(0);
} else {
proxySetup = ProxyUtil.initProxy();
proxyList = new ArrayList<>();
proxyList.add(proxySetup);
initProxySetupView(proxySetup);
}
});
// 如果用户隧道列表不为空
if (proxyList != null && proxyList.size() > 0) {
// 显示隧道列表
initProxyList(proxyList);
// 默认选中第一个(配置隧道设置模型)
proxyListView.getSelectionModel().select(0);
} else {
// 配置隧道设置模型
proxyList = new ArrayList<>();
proxyList.add(proxySetup);
}
});
/* 数据绑定 */
// 服务器
proxyServerComboBox.valueProperty().bindBidirectional(proxySetupModel.serverProperty());
// 隧道名称
proxyNameTextField.textProperty().bindBidirectional(proxySetupModel.proxyNameProperty());
// 隧道类型
proxyProtocolComboBox.valueProperty().bindBidirectional(proxySetupModel.proxyTypeProperty());
// 内网地址
localHostTextField.textProperty().bindBidirectional(proxySetupModel.localIpProperty());
// 内网端口
localPortTextField.textProperty().bindBidirectional(proxySetupModel.localPortProperty());
// 外网访问子域名
domainTextField.textProperty().bindBidirectional(proxySetupModel.domainProperty());
// 外网访问主域名
domainSuffixTextField.textProperty().bindBidirectional(proxySetupModel.domainSuffixProperty());
// 是否自定义访问域名
customizeDomain.bindBidirectional(proxySetupModel.isCustomizeProperty());
// 启动成功提示
proxyStatusPane.visibleProperty().bindBidirectional(proxySetupModel.runningProperty());
}
@Override
public void initViewStyle() {
// 启用链接
openProxyRBtn.setToggleGroup(openProxyGroup);
openProxyRBtn.setSelected(true);
closeProxyRBtn.setToggleGroup(openProxyGroup);
openProxyRBtn.setUserData(true);
closeProxyRBtn.setUserData(false);
openProxyRBtn.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(proxySetup.getProxy_type())) {
host = "127.0.0.1";
port = ProxyConfig.getTypePort(newType).toString();
} else {
host = proxySetupModel.getLocalIp();
port = proxySetupModel.getLocalPort();
}
// 域名提示
domainPane.setVisible(DomainUtil.isHttp(proxySetupModel));
// 设置
localHostTextField.setText(host);
localPortTextField.setText(port);
});
// 隧道名称
proxySetupModel.proxyNameProperty().addListener((observable, oldValue, newValue) -> {
proxyListView.getItems().get(selectProxy.get()).setText(newValue);
});
// 运行状态监听
proxySetupModel.runningProperty().addListener((observable, oldValue, newValue) -> {
String startClass = "startProxyBtn";
String stopClass = "stopProxyBtn";
// 启动按钮
startProxyBtn.getStyleClass().remove(startClass);
startProxyBtn.getStyleClass().remove(stopClass);
startProxyBtn.getStyleClass().add(newValue ? stopClass : startClass);
startProxyBtn.setText(newValue ? "停止" : "启动");
// 列表显示
ObservableList<String> styleClass = proxyListView.getItems().get(selectProxy.get()).getStyleClass();
styleClass.remove(PROXY_LIST_ITEM_RUN_CLASS);
styleClass.remove(PROXY_LIST_ITEM_STOP_CLASS);
styleClass.add(newValue ? PROXY_LIST_ITEM_RUN_CLASS : PROXY_LIST_ITEM_STOP_CLASS);
// 外网访问连接
if (DomainUtil.isHttp(proxySetupModel)) {
// http / TODO https
// String prefix = DomainUtil.isHttps(proxySetupModel) ? "https://" : "http://";
// domainLink.textProperty().set(prefix + proxySetupModel.getDomain() + proxySetupModel.getDomainSuffix());
domainLink.textProperty().set(proxySetupModel.getDomain() + proxySetupModel.getDomainSuffix());
} else {
// ssh / tcp
domainLink.textProperty().set(ProxyConfig.getServerIP(ProxyConfig.getServerNode(proxySetupModel.getServer())) + ":" + proxySetupModel.getRemotePort());
}
});
// 自定义外网访问地址按钮
customizeDomain.addListener((observable, oldValue, newValue) -> {
HBox parent = (HBox) domainTextField.getParent();
ObservableList<String> styleClass = domainTextField.getStyleClass();
styleClass.remove(INPUT_LEFT_CLASS);
styleClass.remove(INPUT_CLASS);
// 是否切换为自定义域名
if (newValue) {
// 隐藏系统域名
if (parent.getChildren().contains(domainSuffixTextField))
parent.getChildren().remove(domainSuffixTextField);
// 用户域名是否自定义
if (!DomainUtil.isCustomize(proxySetup)) {
proxySetupModel.setDomain("");
} else {
proxySetupModel.setDomain(proxySetup.getDomain());
}
proxySetupModel.setDomainSuffix("");
styleClass.add(INPUT_CLASS);
domainTextField.promptTextProperty().set("自定义域名");
customizeDomainBtn.setText("系统分配");
domainHtinTextField.setText("请输入您的域名,并解析至: " + ProxyConfig.getServerIP(proxySetupModel.getServer()));
} else {
// 显示系统域名
if (!parent.getChildren().contains(domainSuffixTextField))
parent.getChildren().add(domainSuffixTextField);
// 用户域名是否自定义
if (DomainUtil.isCustomize(proxySetup)) {
proxySetupModel.setDomain("");
proxySetupModel.setDomainSuffix("." + ProxyConfig.getServerPath(proxySetupModel.getServer()));
} else {
proxySetupModel.setDomain(DomainUtil.getCustomize(proxySetup));
proxySetupModel.setDomainSuffix(DomainUtil.getSuffix(proxySetup));
}
styleClass.add(INPUT_LEFT_CLASS);
domainTextField.promptTextProperty().set("自定义子域名 大于3位");
customizeDomainBtn.setText("自定义");
domainHtinTextField.setText("请输入子域名长度不小于3个字符");
}
});
// 默认展示 "常见问题" 面板
tabPane.getSelectionModel().select(1);
}
/**
* 设置外网访问域名
*/
private void setDomain(ProxySetup setup) {
if (setup != null) {
String serverName = ProxyServer.values()[setup.getNode()].name();
String setupDomain = setup.getDomain();
String domain = setupDomain;
String serverPath = ProxyConfig.getServerPath(serverName);
// 是否自定义
customizeDomain = setupDomain.contains(serverPath);
if (customizeDomain) domain = setupDomain.substring(0, setupDomain.indexOf("." + serverPath));
domainSuffixTextField.setText("." + serverPath);
domainTextField.setText(domain);
}
if (!customizeDomain) {
domainTextField.styleProperty().set("-fx-border-radius: 5");
domainSuffixTextField.setVisible(false);
domainTextField.setText("");
customizeDomainBtn.setText("系统分配");
} else {
domainTextField.styleProperty().set("-fx-border-radius: 5 0 0 5");
domainSuffixTextField.setVisible(true);
customizeDomainBtn.setText("自定义");
}
}
@Override
public void initViewAction() {
// 重置
resetProxyBtn.setOnMouseClicked(event -> proxySetupModel.set(proxySetup));
// 日志清理
clearLogBtn.setOnMouseClicked(event -> {
if (frpUtilMap.get(proxySetupModel.getProxyName()) != null)
frpUtilMap.get(proxySetupModel.getProxyName()).clearLog();
});
// 更换服务器地址
proxyServerComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
// 重新设置是否自定义
proxySetupModel.set(proxySetupModel.get());
if (!customizeDomain.get()) {
proxySetupModel.setDomainSuffix("." + ProxyConfig.getServerPath(newValue));
} else {
proxySetupModel.setDomainSuffix("");
// 域名检查
domainTextField.validate();
}
});
// 是否启用
openProxyGroup.selectedToggleProperty().addListener(
(ObservableValue<? extends Toggle> ov, Toggle old_toggle, Toggle new_toggle) -> {
Toggle toggle = openProxyGroup.getSelectedToggle();
if (toggle != null) {
closeProxy(!(Boolean) toggle.getUserData());
// 设置状态
proxySetupModel.setStatus((Boolean) toggle.getUserData());
}
}
);
// 隧道类型
proxyProtocolCombox.valueProperty().addListener((observable, oldValue, newValue) -> {
String newType = StringUtils.lowerCase(newValue.getText());
String port;
if (!newType.equals(proxySetup.getProxy_type()))
port = ProxyConfig.getTypePort(newType).toString();
else
port = proxySetup.getLocal_port().toString();
// 自定义访问域名
customizeDomainBtn.setOnMouseClicked((event) -> customizeDomain.set(!customizeDomain.get()));
// 设置默认端口
localPortTextField.setText(port);
// 设置隧道类型
proxySetup.setProxy_type(newType);
// 本地IP检查
localHostTextField.getValidators().add(TextValidate.IpRequired);
localHostTextField.getValidators().add(TextValidate.IpFormat);
localHostTextField.textProperty().addListener((observable, oldValue, newValue) -> {
localHostTextField.validate();
});
// 自定义访问域名
customizeDomainBtn.setOnMouseClicked((event) -> {
setDomain(proxySetup != null ? proxySetup : null);
// 端口检查
localPortTextField.getValidators().add(TextValidate.PortRequired);
localPortTextField.getValidators().add(TextValidate.PortFormat);
localPortTextField.textProperty().addListener((observable, oldValue, newValue) -> {
localPortTextField.validate();
});
// 域名检查
setting.addListener((observable, oldValue, newValue) -> {
if (newValue) domainTextField.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));// 自定义域名解析检查
} else
domainTextField.validate();
});
// 点击隧道列表
proxyListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
ObservableList<Label> items = proxyListView.getItems();
for (Label item : items) {
item.getStyleClass().remove(PROXY_LIST_ITEM_SELECT_CLASS);
}
if (newValue != null) {
for (ProxySetup setup : proxyList) {
if (setup.getProxy_name().equals(newValue.getText()))
initProxySetupView(setup);
}
newValue.getStyleClass().add(PROXY_LIST_ITEM_SELECT_CLASS);
int oldIndex = items.indexOf(oldValue);
int newIndex = items.indexOf(newValue);
if (oldIndex != -1) proxyList.set(oldIndex, proxySetupModel.get());
proxySetup = proxyList.get(newIndex);
selectProxy.set(newIndex);
Platform.runLater(() -> {
setting.set(false);
proxySetupModel.set(proxySetup);
FrpUtil frpUtil = frpUtilMap.get(proxySetup.getProxy_name());
proxyLogPane.contentProperty().set(frpUtil == null ? null : frpUtil.getConsole());
setting.set(true);
});
}
});
// 添加隧道
addProxyBtn.setOnMouseClicked(event -> {
// 保存运行状态
proxyList.get(selectProxy.get()).setRuning(proxySetupModel.isRunning());
// 获取默认隧道设置
proxySetup = ProxyUtil.initProxy();
proxySetup = ProxyUtil.initProxy(proxyList.get(proxyList.size() - 1).getSort());
// 加入列表
proxyList.add(proxySetup);
selectProxy = proxyList.indexOf(proxySetup);
initProxyList(proxyList);
// 展示
initProxySetupView(proxySetup);
proxyListView.getSelectionModel().select(proxyList.size() - 1);
});
}
/**
* 显示隧道设置
*/
private void initProxySetupView(ProxySetup setup) {
// 启动
startProxyBtn.setOnMouseClicked(event -> {
if (proxySetupModel.isRunning())
stopProxy();
else
startProxy();
});
int select = proxyList.indexOf(setup);
//
if (setup.getLocal_ip() == null && ProxyUtil.info(setup) == null) {
AlertUtil.error("隧道设置获取失败!").show();
if (select != selectProxy) {
proxyListView.getSelectionModel().select(select);
}
return;
}
// 链接跳转
domainLink.setOnMouseClicked(event -> {
getApplication().getHostServices().showDocument(domainLink.getText());
});
// 选中的隧道下标
selectProxy = select;
// 隧道类型
for (Label label : proxyProtocolCombox.getItems()) {
// 获取用户设置
if (StringUtils.lowerCase(label.getText()).equals(setup.getProxy_type()))
proxyProtocolCombox.getSelectionModel().select(proxyProtocolCombox.getItems().indexOf(label));
}
// 服务器
String serverName = ProxyServer.values()[setup.getNode()].name();
proxyServerCB.getItems().forEach((label -> {
if (label.getText().equals(serverName))
proxyServerCB.getSelectionModel().select(proxyServerCB.getItems().indexOf(label));
}));
// 隧道名称
proxyNameTextField.setText(setup.getProxy_name());
// 本地服务地址
localPortTextField.setText(setup.getLocal_port().toString());
localHostTextField.setText(setup.getLocal_ip());
// 公网访问地址
setDomain(setup);
// 启动按钮
if (setup.isRuning() && !setup.isClose()) {
startProxyBtn.setText("停止");
startProxyBtn.getStyleClass().remove("startProxyBtn");
startProxyBtn.getStyleClass().add("stopProxyBtn");
} else {
startProxyBtn.setText("启动");
startProxyBtn.getStyleClass().remove("stoptProxyBtn");
startProxyBtn.getStyleClass().add("startProxyBtn");
}
// 是否停用
closeProxy(setup.isClose());
// 复制外网访问地址
copyDomainBtn.setOnMouseClicked(event -> {
//获取系统剪切板
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
//构建String数据类型
StringSelection selection = new StringSelection(domainLink.getText());
//添加文本到系统剪切板
clipboard.setContents(selection, null);
// 复制成功提示
AlertUtil.info("复制成功,快去分享给小伙伴吧!").show();
});
}
/**
@ -307,8 +436,9 @@ public class MainController extends BaseController<StackPane> implements Initial
for (ProxySetup proxy : proxyList) {
try {
Label label = FXMLLoader.load(getClass().getResource("/fxml/proxyItem.fxml"));
label.setText(proxy.getProxy_name());
label.getStyleClass().addAll(PROXY_LIST_ITEM_CLASS, PROXY_LIST_ITEM_STOP_CLASS);
label.textProperty().set(proxy.getProxy_name());
ObservableList<String> styleClass = label.getStyleClass();
styleClass.addAll(PROXY_LIST_ITEM_CLASS, proxy.isRuning() ? PROXY_LIST_ITEM_RUN_CLASS : PROXY_LIST_ITEM_STOP_CLASS);
proxyListView.getItems().add(label);
} catch (IOException e) {
e.printStackTrace();
@ -320,23 +450,23 @@ public class MainController extends BaseController<StackPane> implements Initial
* 是否启用当前隧道
*/
private void closeProxy(boolean close) {
if (close && proxySetup.isRuning()) stopProxy();
if (close && proxySetupModel.isRunning()) stopProxy();
proxyNameTextField.setDisable(close);// 隧道名称
proxyProtocolCombox.setDisable(close);// 隧道类型
proxyProtocolComboBox.setDisable(close);// 隧道类型
localHostTextField.setDisable(close);// 本地地址
localPortTextField.setDisable(close);// 本地端口
domainTextField.setDisable(close); // 外网子域名
startProxyBtn.setDisable(close); // 启动按钮
ObservableList<String> styleClass = proxyListView.getItems().get(selectProxy).getStyleClass();
ObservableList<String> styleClass = proxyListView.getItems().get(selectProxy.get()).getStyleClass();
styleClass.remove(PROXY_LIST_ITEM_STOP_CLASS);
styleClass.remove(PROXY_LIST_ITEM_RUN_CLASS);
styleClass.remove(PROXY_LIST_ITEM_CLOSE_CLASS);
if (close) {
styleClass.remove(PROXY_LIST_ITEM_STOP_CLASS);
styleClass.remove(PROXY_LIST_ITEM_RUN_CLASS);
styleClass.add(PROXY_LIST_ITEM_CLOSE_CLASS);
} else {
styleClass.remove(PROXY_LIST_ITEM_CLOSE_CLASS);
styleClass.add(proxySetup.isClose() ? PROXY_LIST_ITEM_CLOSE_CLASS : PROXY_LIST_ITEM_STOP_CLASS);
styleClass.add(proxySetupModel.isRunning() ? PROXY_LIST_ITEM_RUN_CLASS : PROXY_LIST_ITEM_STOP_CLASS);
}
}
@ -344,21 +474,32 @@ public class MainController extends BaseController<StackPane> implements Initial
* TODO 启动代理
*/
private void startProxy() {
// http 隧道系统随机分配端口
if (!DomainUtil.isHttp(proxySetupModel)) {
proxySetupModel.setRemotePort(String.valueOf(ProxyUtil.randomPort()));
}
// 删除旧隧道
if (proxySetup.getId() != null) ProxyUtil.delete(proxySetup.getId());
if (proxySetupModel.getId() != null && !proxySetupModel.get().equals(proxySetup)) {
// 删除旧隧道
ProxyUtil.delete(proxySetupModel.getId());
// 添加隧道
if (!ProxyUtil.add(result -> {
}, proxySetup)) return;
// 添加隧道
ProxyUtil.add(result -> {
proxySetup = result;
proxySetupModel.set(proxySetup);
}, proxySetupModel.get());
}
// TODO 修改设置
// TODO 调用cmd
// 初始化frputil
FrpUtil frpUtil;
if ((frpUtil = frpUtilMap.get(proxySetupModel.getProxyName())) == null)
frpUtilMap.put(proxySetupModel.getProxyName(), frpUtil = FrpUtil.init(proxySetupModel));
ObservableList<String> styleClass = proxyListView.getItems().get(selectProxy).getStyleClass();
styleClass.remove(PROXY_LIST_ITEM_STOP_CLASS);
styleClass.add(PROXY_LIST_ITEM_RUN_CLASS);
proxySetup.setRuning(true);
// 设置文本域对象
if (proxyLogPane.getContent() == null) proxyLogPane.contentProperty().set(frpUtil.getConsole());
// 开始
frpUtil.start();
}
/**
@ -366,7 +507,7 @@ public class MainController extends BaseController<StackPane> implements Initial
*/
private void stopProxy() {
// 关闭CMD
proxySetup.setRuning(false);
FrpUtil frpUtil = frpUtilMap.get(proxySetupModel.getProxyName());
if (frpUtil != null) frpUtil.stop();
}
}

View File

@ -9,18 +9,18 @@ import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import top.octopusyan.base.BaseController;
import top.octopusyan.common.http.EasyHttp;
import top.octopusyan.common.http.listener.OnHttpListener;
import top.octopusyan.manager.http.EasyHttp;
import top.octopusyan.manager.http.listener.OnHttpListener;
import top.octopusyan.http.Api;
import top.octopusyan.http.request.RegisterParam;
import top.octopusyan.http.request.SendEmailCheckParam;
import top.octopusyan.utils.AlertUtil;
import top.octopusyan.utils.ApplicatonStore;
import top.octopusyan.utils.TextValidate;
import top.octopusyan.utils.JsoupUtil;
import top.octopusyan.config.TextValidate;
import java.io.IOException;
import java.io.Serializable;
/**
@ -81,8 +81,8 @@ public class RegisterController extends BaseController<StackPane> 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.isEmpty(account)) accooundTextField.setText(account);
if (!StringUtils.isEmpty(password)) passwordTextField.setText(password);
}
@Override
@ -127,7 +127,13 @@ public class RegisterController extends BaseController<StackPane> implements Ser
@Override
public void initViewAction() {
// 登录
loginBtn.setOnMouseClicked(event -> jumpTo(new LoginController()));
loginBtn.setOnMouseClicked(event -> {
try {
jumpTo(new LoginController());
} catch (IOException e) {
e.printStackTrace();
}
});
// 点击了发送验证码
sendCheckCodeBtn.setOnMouseClicked(event -> {
@ -150,9 +156,9 @@ public class RegisterController extends BaseController<StackPane> implements Ser
registerBtn.setOnMouseClicked(event -> register());
}
private void register(){
private void register() {
// 文本校验
if(accooundTextField.validate() && passwordTextField.validate() && emailTf.validate() && emailCheckCodeTf.validate()){
if (accooundTextField.validate() && passwordTextField.validate() && emailTf.validate() && emailCheckCodeTf.validate()) {
EasyHttp.builder()
.api(Api.Register)
.param(new RegisterParam(
@ -161,34 +167,29 @@ public class RegisterController extends BaseController<StackPane> implements Ser
ApplicatonStore.getEmail(),
emailCheckCodeTf.getText()
))
.request(new OnHttpListener<String>() {
@Override
public void onSucceed(String result) {
Document html = Jsoup.parse(result);
// 注册出错
if(result.contains("alert-danger")) {
Platform.runLater(() -> AlertUtil.error(getHtmlErrorMessage(html)).header(null).show());
return;
}
.request(result -> {
// 注册出错
if (JsoupUtil.isAlertSuccess(result)) {
// 注册成功
if(result.contains("alert-success")){
Platform.runLater(() -> AlertUtil.info(getHtmlMessage(html)).header(null).show());
ApplicatonStore.setRegisterSuccess(true);
jumpTo(new LoginController());
}
}
Platform.runLater(() ->
AlertUtil.info(JsoupUtil.getHtmlMessage(result)).header(null).show()
);
ApplicatonStore.setRegisterSuccess(true);
Platform.runLater(() -> {
try {
jumpTo(new LoginController());
} catch (IOException e) {
e.printStackTrace();
}
});
} else
Platform.runLater(() ->
AlertUtil.error(JsoupUtil.getErrorMessage(result)).header(null).show()
);
});
}
}
private String getHtmlErrorMessage(Document html) {
return html.body().getElementsByClass("alert alert-danger alert-dismissable").text().substring(1);
}
private String getHtmlMessage(Document html) {
return html.body().getElementsByClass("alert alert-success alert-dismissable").text().substring(1);
}
@Override
public void onDestroy() {
super.onDestroy();

View File

@ -1,10 +1,10 @@
package top.octopusyan.http;
import top.octopusyan.common.http.api.NotParamApi;
import top.octopusyan.common.http.api.ParamApi;
import top.octopusyan.common.http.api.PathParamApi;
import top.octopusyan.common.http.config.BodyType;
import top.octopusyan.common.http.config.HttpConstant;
import top.octopusyan.manager.http.api.NotParamApi;
import top.octopusyan.manager.http.api.ParamApi;
import top.octopusyan.manager.http.api.PathParamApi;
import top.octopusyan.manager.http.config.BodyType;
import top.octopusyan.manager.http.config.HttpConstant;
import top.octopusyan.http.request.ProxySetup;
import top.octopusyan.http.request.RegisterParam;
import top.octopusyan.http.request.SendEmailCheckParam;
@ -32,13 +32,19 @@ public class Api {
BodyType.FORM
);
/** 发送邮箱验证码 */
/** 用户注册 */
public static final ParamApi<RegisterParam, String> Register = new ParamApi<>(
"/?action=register&page=register",
HttpConstant.Method.POST,
BodyType.FORM
);
/** 获取服务器配置 */
public static final PathParamApi<String> ServerConfiguration = new PathParamApi<>(
"/?page=panel&module=configuration&server={0}",
HttpConstant.Method.GET
);
/** 隧道列表 */
public static final NotParamApi<String> ProxyList = new NotParamApi<>(
"/?page=panel&module=proxies",
@ -71,7 +77,7 @@ public class Api {
}
/** 随机端口 */
public static NotParamApi<Integer> RandomPort() {
public static NotParamApi<String> RandomPort() {
return new NotParamApi<>(
"/?page=panel&module=addproxy&randomport&csrf=" + ProxyUtil.getCsrf(),
HttpConstant.Method.GET

View File

@ -7,8 +7,8 @@ import okio.BufferedSource;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.octopusyan.common.http.ssl.HttpSslConfig;
import top.octopusyan.common.http.ssl.HttpSslFactory;
import top.octopusyan.manager.http.ssl.HttpSslConfig;
import top.octopusyan.manager.http.ssl.HttpSslFactory;
import java.io.IOException;
import java.nio.charset.Charset;
@ -70,7 +70,7 @@ public class OkHttpClientConfig {
.cookieJar(cookieJar())
.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES))
.connectTimeout(10L, TimeUnit.SECONDS)
.readTimeout(500L, TimeUnit.SECONDS)
.readTimeout(30L, TimeUnit.SECONDS)
.writeTimeout(0L, TimeUnit.SECONDS)
.pingInterval(60L, TimeUnit.SECONDS)
.sslSocketFactory(httpSslConfig.getSslSocketFactory(), httpSslConfig.getTrustManager())

View File

@ -1,6 +1,8 @@
package top.octopusyan.http.request;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : octopus yan
@ -8,6 +10,8 @@ import lombok.Data;
* @description :
* @create : 2022-4-5 10:08
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ProxySetup {
private Integer id;
@ -16,7 +20,7 @@ public class ProxySetup {
private String proxy_type;
private String local_ip;
private Integer local_port;
private Integer remote_port;
private String remote_port;
private String domain;
private Boolean use_encryption;
private Boolean use_compression;
@ -24,8 +28,8 @@ public class ProxySetup {
private String host_header_rewrite;
private String header_X_From_Where;
private String sk;
/** 是否启用 */
private Boolean status;
private int sort;
private Integer sort;
private boolean runing;
private boolean isClose;
}

View File

@ -1,13 +1,13 @@
package top.octopusyan.common.http;
package top.octopusyan.manager.http;
import okhttp3.OkHttpClient;
import top.octopusyan.common.http.api.NotParamApi;
import top.octopusyan.common.http.api.ParamApi;
import top.octopusyan.common.http.config.HttpConstant;
import top.octopusyan.common.http.config.LogStrategy;
import top.octopusyan.common.http.listener.OnHttpListener;
import top.octopusyan.common.http.model.ResponseClass;
import top.octopusyan.common.http.request.RequestHandler;
import top.octopusyan.manager.http.api.NotParamApi;
import top.octopusyan.manager.http.api.ParamApi;
import top.octopusyan.manager.http.config.HttpConstant;
import top.octopusyan.manager.http.config.LogStrategy;
import top.octopusyan.manager.http.listener.OnHttpListener;
import top.octopusyan.manager.http.model.ResponseClass;
import top.octopusyan.manager.http.request.RequestHandler;
/**
* @author : octopus yan

View File

@ -1,23 +1,23 @@
package top.octopusyan.common.http;
package top.octopusyan.manager.http;
import okhttp3.*;
import top.octopusyan.common.http.annotation.HttpHeader;
import top.octopusyan.common.http.annotation.HttpIgnore;
import top.octopusyan.common.http.annotation.HttpRename;
import top.octopusyan.common.http.api.NotParamApi;
import top.octopusyan.common.http.api.ParamApi;
import top.octopusyan.common.http.api.PathParamApi;
import top.octopusyan.common.http.api.RequestApi;
import top.octopusyan.common.http.callback.NormalCallback;
import top.octopusyan.common.http.listener.OnHttpListener;
import top.octopusyan.common.http.model.CallProxy;
import top.octopusyan.common.http.model.HttpHeaders;
import top.octopusyan.common.http.model.HttpParams;
import top.octopusyan.common.http.model.JsonBody;
import top.octopusyan.common.http.config.BodyType;
import top.octopusyan.common.http.config.HttpConstant;
import top.octopusyan.common.http.request.IRequestHandler;
import top.octopusyan.common.http.model.ResponseClass;
import top.octopusyan.manager.http.annotation.HttpHeader;
import top.octopusyan.manager.http.annotation.HttpIgnore;
import top.octopusyan.manager.http.annotation.HttpRename;
import top.octopusyan.manager.http.api.NotParamApi;
import top.octopusyan.manager.http.api.ParamApi;
import top.octopusyan.manager.http.api.PathParamApi;
import top.octopusyan.manager.http.api.RequestApi;
import top.octopusyan.manager.http.callback.NormalCallback;
import top.octopusyan.manager.http.listener.OnHttpListener;
import top.octopusyan.manager.http.model.CallProxy;
import top.octopusyan.manager.http.model.HttpHeaders;
import top.octopusyan.manager.http.model.HttpParams;
import top.octopusyan.manager.http.model.JsonBody;
import top.octopusyan.manager.http.config.BodyType;
import top.octopusyan.manager.http.config.HttpConstant;
import top.octopusyan.manager.http.request.IRequestHandler;
import top.octopusyan.manager.http.model.ResponseClass;
import java.io.File;
import java.io.InputStream;
@ -545,7 +545,7 @@ public class EasyHttp<Param, Result> {
/**
* @param params 路径参数
*/
public RestfulRequest<Result> pathParam(Object... params) {
public RestfulRequest<Result> pathParam(String... params) {
super.path(MessageFormat.format(getPath(), params));
return this;
}

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http;
package top.octopusyan.manager.http;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
@ -7,10 +7,10 @@ import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import org.apache.commons.lang3.StringUtils;
import top.octopusyan.common.http.annotation.HttpIgnore;
import top.octopusyan.common.http.annotation.HttpRename;
import top.octopusyan.common.http.config.ContentType;
import top.octopusyan.common.http.model.UpdateBody;
import top.octopusyan.manager.http.annotation.HttpIgnore;
import top.octopusyan.manager.http.annotation.HttpRename;
import top.octopusyan.manager.http.config.ContentType;
import top.octopusyan.manager.http.model.UpdateBody;
import top.octopusyan.manager.ThreadPoolManager;
import java.io.*;

View File

@ -1,18 +1,12 @@
package top.octopusyan.common.http;
package top.octopusyan.manager.http;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import top.octopusyan.common.http.config.ILogStrategy;
import top.octopusyan.common.http.request.IRequestHandler;
import top.octopusyan.common.http.ssl.HttpSslConfig;
import top.octopusyan.common.http.ssl.HttpSslFactory;
import top.octopusyan.manager.http.config.ILogStrategy;
import top.octopusyan.manager.http.request.IRequestHandler;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author : octopus yan

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http;
package top.octopusyan.manager.http;
/**
* @author : octopus yan

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.annotation;
package top.octopusyan.manager.http.annotation;
import java.lang.annotation.*;

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.annotation;
package top.octopusyan.manager.http.annotation;
import java.lang.annotation.*;

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.annotation;
package top.octopusyan.manager.http.annotation;
import java.lang.annotation.*;

View File

@ -1,6 +1,6 @@
package top.octopusyan.common.http.api;
package top.octopusyan.manager.http.api;
import top.octopusyan.common.http.config.BodyType;
import top.octopusyan.manager.http.config.BodyType;
/**
* @author : octopus yan

View File

@ -1,6 +1,6 @@
package top.octopusyan.common.http.api;
package top.octopusyan.manager.http.api;
import top.octopusyan.common.http.config.BodyType;
import top.octopusyan.manager.http.config.BodyType;
/**
* @author : octopus yan

View File

@ -1,6 +1,6 @@
package top.octopusyan.common.http.api;
package top.octopusyan.manager.http.api;
import top.octopusyan.common.http.config.BodyType;
import top.octopusyan.manager.http.config.BodyType;
/**
* @author : octopus yan

View File

@ -1,7 +1,7 @@
package top.octopusyan.common.http.api;
package top.octopusyan.manager.http.api;
import lombok.Data;
import top.octopusyan.common.http.config.BodyType;
import top.octopusyan.manager.http.config.BodyType;
/**
* @author : octopus yan

View File

@ -1,12 +1,12 @@
package top.octopusyan.common.http.callback;
package top.octopusyan.manager.http.callback;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import top.octopusyan.common.http.HttpConfig;
import top.octopusyan.common.http.HttpLog;
import top.octopusyan.common.http.model.CallProxy;
import top.octopusyan.common.http.EasyUtils;
import top.octopusyan.manager.http.HttpConfig;
import top.octopusyan.manager.http.HttpLog;
import top.octopusyan.manager.http.model.CallProxy;
import top.octopusyan.manager.http.EasyUtils;
import java.io.IOException;
import java.net.SocketTimeoutException;

View File

@ -1,12 +1,13 @@
package top.octopusyan.common.http.callback;
package top.octopusyan.manager.http.callback;
import javafx.application.Platform;
import okhttp3.Response;
import top.octopusyan.common.http.listener.OnHttpListener;
import top.octopusyan.common.http.HttpLog;
import top.octopusyan.common.http.model.CallProxy;
import top.octopusyan.common.http.api.RequestApi;
import top.octopusyan.common.http.request.IRequestHandler;
import top.octopusyan.common.http.EasyUtils;
import top.octopusyan.manager.http.listener.OnHttpListener;
import top.octopusyan.manager.http.HttpLog;
import top.octopusyan.manager.http.model.CallProxy;
import top.octopusyan.manager.http.api.RequestApi;
import top.octopusyan.manager.http.request.IRequestHandler;
import top.octopusyan.manager.http.EasyUtils;
/**
* @author : octopus yan
@ -52,7 +53,7 @@ public final class NormalCallback extends BaseCallback {
@Override
protected void onFailure(Exception e) {
final Exception exception = mRequestHandler.requestFail(mRequestApi, e);
HttpLog.print(exception);
Platform.runLater(() -> HttpLog.print(exception));
EasyUtils.post(() -> {
mListener.onFail(exception);
mListener.onEnd(getCall());

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.config;
package top.octopusyan.manager.http.config;
/**
* @author : octopus yan

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.config;
package top.octopusyan.manager.http.config;
import okhttp3.MediaType;

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.config;
package top.octopusyan.manager.http.config;
/**
* @author : octopus yan

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.config;
package top.octopusyan.manager.http.config;
/**
* @author : octopus yan

View File

@ -1,10 +1,10 @@
package top.octopusyan.common.http.config;
package top.octopusyan.manager.http.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang3.StringUtils;
import top.octopusyan.common.http.EasyHttp;
import top.octopusyan.common.http.HttpConfig;
import top.octopusyan.manager.http.EasyHttp;
import top.octopusyan.manager.http.HttpConfig;
/**
* @author : octopus yan

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.exception;
package top.octopusyan.manager.http.exception;
/**
* 请求取消异常

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.exception;
package top.octopusyan.manager.http.exception;
/**
* 数据解析异常

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.exception;
package top.octopusyan.manager.http.exception;
/**
* 网络请求异常

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.exception;
package top.octopusyan.manager.http.exception;
/**
* 网络连接异常

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.exception;
package top.octopusyan.manager.http.exception;
import okhttp3.Response;

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.exception;
package top.octopusyan.manager.http.exception;
/**
* 返回结果异常

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.exception;
package top.octopusyan.manager.http.exception;
/**
* 服务器连接异常

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.exception;
package top.octopusyan.manager.http.exception;
/**
* 服务器超时异常

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.exception;
package top.octopusyan.manager.http.exception;
/**
* Token 失效异常

View File

@ -1,6 +1,9 @@
package top.octopusyan.common.http.listener;
package top.octopusyan.manager.http.listener;
import javafx.application.Platform;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Call;
import top.octopusyan.utils.AlertUtil;
/**
* @author : octopus yan
@ -23,7 +26,8 @@ public interface OnHttpListener<T> {
/**
* 请求出错
*/
default void onFail(Exception e){};
default void onFail(Exception e) {
}
/**
* 请求结束

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.model;
package top.octopusyan.manager.http.model;
import okhttp3.Call;
import okhttp3.Callback;

View File

@ -1,6 +1,6 @@
package top.octopusyan.common.http.model;
package top.octopusyan.manager.http.model;
import top.octopusyan.common.http.HttpConfig;
import top.octopusyan.manager.http.HttpConfig;
import java.util.HashMap;
import java.util.Set;

View File

@ -1,6 +1,6 @@
package top.octopusyan.common.http.model;
package top.octopusyan.manager.http.model;
import top.octopusyan.common.http.HttpConfig;
import top.octopusyan.manager.http.HttpConfig;
import java.util.HashMap;
import java.util.Set;

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.model;
package top.octopusyan.manager.http.model;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
@ -7,7 +7,7 @@ import lombok.NonNull;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;
import top.octopusyan.common.http.config.ContentType;
import top.octopusyan.manager.http.config.ContentType;
import java.io.IOException;
import java.util.List;

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.model;
package top.octopusyan.manager.http.model;
/**
* @author : octopus yan

View File

@ -1,12 +1,12 @@
package top.octopusyan.common.http.model;
package top.octopusyan.manager.http.model;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
import top.octopusyan.common.http.config.ContentType;
import top.octopusyan.common.http.EasyUtils;
import top.octopusyan.manager.http.config.ContentType;
import top.octopusyan.manager.http.EasyUtils;
import java.io.File;
import java.io.FileNotFoundException;

View File

@ -1,10 +1,10 @@
package top.octopusyan.common.http.request;
package top.octopusyan.manager.http.request;
import java.lang.reflect.Type;
import okhttp3.Request;
import okhttp3.Response;
import top.octopusyan.common.http.api.RequestApi;
import top.octopusyan.manager.http.api.RequestApi;
public interface IRequestHandler {

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.request;
package top.octopusyan.manager.http.request;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
@ -8,10 +8,10 @@ import okhttp3.Headers;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import top.octopusyan.common.http.api.RequestApi;
import top.octopusyan.common.http.exception.*;
import top.octopusyan.common.http.HttpLog;
import top.octopusyan.common.http.response.BaseRest;
import top.octopusyan.manager.http.api.RequestApi;
import top.octopusyan.manager.http.HttpLog;
import top.octopusyan.manager.http.exception.*;
import top.octopusyan.manager.http.response.BaseRest;
import java.io.IOException;
import java.io.InputStream;

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.response;
package top.octopusyan.manager.http.response;
import java.io.Serializable;

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.ssl;
package top.octopusyan.manager.http.ssl;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;

View File

@ -1,6 +1,6 @@
package top.octopusyan.common.http.ssl;
package top.octopusyan.manager.http.ssl;
import top.octopusyan.common.http.HttpLog;
import top.octopusyan.manager.http.HttpLog;
import javax.net.ssl.*;
import java.io.IOException;

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.ssl;
package top.octopusyan.manager.http.ssl;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

View File

@ -1,4 +1,4 @@
package top.octopusyan.common.http.ssl;
package top.octopusyan.manager.http.ssl;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

View File

@ -0,0 +1,335 @@
package top.octopusyan.model;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import org.apache.commons.lang3.StringUtils;
import top.octopusyan.config.ProxyConfig;
import top.octopusyan.http.request.ProxySetup;
import top.octopusyan.utils.DomainUtil;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 用户隧道设置模型
* @create : 2022-4-6 16:45
*/
public class ProxySetupModel {
private final SimpleIntegerProperty id = new SimpleIntegerProperty();
private final SimpleStringProperty server = new SimpleStringProperty();
private final SimpleStringProperty proxyName = new SimpleStringProperty();
private final SimpleStringProperty proxyType = new SimpleStringProperty();
private final SimpleStringProperty localIp = new SimpleStringProperty();
private final SimpleStringProperty localPort = new SimpleStringProperty();
private final SimpleStringProperty remotePort = new SimpleStringProperty();
private final SimpleStringProperty domain = new SimpleStringProperty();
private final SimpleStringProperty domainSuffix = new SimpleStringProperty();
private final SimpleBooleanProperty useEncryption = new SimpleBooleanProperty();
private final SimpleBooleanProperty useCompression = new SimpleBooleanProperty();
private final SimpleStringProperty locations = new SimpleStringProperty();
private final SimpleStringProperty hostHeaderRewrite = new SimpleStringProperty();
private final SimpleStringProperty header_X_From_Where = new SimpleStringProperty();
private final SimpleStringProperty sk = new SimpleStringProperty();
private final SimpleBooleanProperty status = new SimpleBooleanProperty();
private final SimpleIntegerProperty sort = new SimpleIntegerProperty();
private final SimpleBooleanProperty running = new SimpleBooleanProperty();
private final SimpleBooleanProperty isCustomize = new SimpleBooleanProperty();
public ProxySetupModel(ProxySetup setup) {
String domainStr = setup.getDomain();
String suffix = "";
isCustomize.set(DomainUtil.isCustomize(setup));
if (!isCustomize.get()) {
domainStr = DomainUtil.getCustomize(setup);
suffix = DomainUtil.getSuffix(setup);
}
setId(setup.getId());
setServer(ProxyConfig.getServerName(setup.getNode()));
setProxyName(setup.getProxy_name());
setProxyType(setup.getProxy_type());
setLocalIp(setup.getLocal_ip());
setLocalPort(setup.getLocal_port());
setRemotePort(setup.getRemote_port());
setDomain(domainStr);
setDomainSuffix(suffix);
setLocations(setup.getLocations());
setUseEncryption(setup.getUse_encryption());
setUseCompression(setup.getUse_compression());
setHeader_X_From_Where(setup.getHost_header_rewrite());
setHostHeaderRewrite(setup.getHost_header_rewrite());
setStatus(setup.getStatus());
setSk(setup.getSk());
setSort(setup.getSort());
setRunning(setup.isRuning());
}
public Integer getId() {
return id.get();
}
public String getServer() {
return server.get();
}
public String getProxyName() {
return proxyName.get();
}
public String getProxyType() {
return StringUtils.lowerCase(proxyType.get());
}
public String getLocalPort() {
return localPort.get();
}
public String getRemotePort() {
return remotePort.get();
}
public Integer getSort() {
return sort.get();
}
public String getLocalIp() {
return localIp.get();
}
public String getDomain() {
return domain.get();
}
public String getDomainSuffix() {
return domainSuffix.get();
}
public String getHeader_X_From_Where() {
return header_X_From_Where.get();
}
public String getHostHeaderRewrite() {
return hostHeaderRewrite.get();
}
public String getLocations() {
return locations.get();
}
public String getSk() {
return sk.get();
}
public boolean isRunning() {
return running.get();
}
public boolean getStatus() {
return status.get();
}
public boolean isUseCompression() {
return useCompression.get();
}
public boolean isUseEncryption() {
return useEncryption.get();
}
public void setId(Integer id) {
if(id != null) this.id.set(id);
}
public void setProxyName(String proxyName) {
this.proxyName.set(proxyName);
}
public void setProxyType(String proxyType) {
this.proxyType.set(StringUtils.upperCase(proxyType));
}
public void setServer(String server) {
this.server.set(server);
}
public void setLocalIp(String localIp) {
this.localIp.set(localIp);
}
public void setDomain(String domain) {
this.domain.set(domain);
}
public void setDomainSuffix(String domainSuffix) {
this.domainSuffix.set(domainSuffix);
}
public void setHeader_X_From_Where(String header_X_From_Where) {
this.header_X_From_Where.set(header_X_From_Where);
}
public void setHostHeaderRewrite(String hostHeaderRewrite) {
this.hostHeaderRewrite.set(hostHeaderRewrite);
}
public void setLocalPort(Integer localPort) {
this.localPort.set(Integer.toString(localPort));
}
public void setRemotePort(String remotePort) {
this.remotePort.set(remotePort);
}
public void setLocations(String locations) {
this.locations.set(locations);
}
public void setRunning(Boolean running) {
this.running.set(running);
}
public void setUseCompression(Boolean useCompression) {
this.useCompression.set(useCompression);
}
public void setSk(String sk) {
this.sk.set(sk);
}
public void setSort(Integer sort) {
if(sort != null) this.sort.set(sort);
}
public void setStatus(boolean status) {
this.status.set(status);
}
public void setUseEncryption(boolean useEncryption) {
this.useEncryption.set(useEncryption);
}
public SimpleStringProperty serverProperty() {
return server;
}
public SimpleStringProperty proxyNameProperty() {
return proxyName;
}
public SimpleStringProperty proxyTypeProperty() {
return proxyType;
}
public SimpleBooleanProperty runningProperty() {
return running;
}
public SimpleBooleanProperty statusProperty() {
return status;
}
public SimpleBooleanProperty useCompressionProperty() {
return useCompression;
}
public SimpleBooleanProperty useEncryptionProperty() {
return useEncryption;
}
public SimpleIntegerProperty idProperty() {
return id;
}
public SimpleStringProperty localPortProperty() {
return localPort;
}
public SimpleStringProperty remotePortProperty() {
return remotePort;
}
public SimpleIntegerProperty sortProperty() {
return sort;
}
public SimpleStringProperty domainProperty() {
return domain;
}
public SimpleStringProperty domainSuffixProperty() {
return domainSuffix;
}
public SimpleStringProperty header_X_From_WhereProperty() {
return header_X_From_Where;
}
public SimpleStringProperty localIpProperty() {
return localIp;
}
public SimpleStringProperty locationsProperty() {
return locations;
}
public SimpleStringProperty skProperty() {
return sk;
}
public SimpleStringProperty hostHeaderRewriteProperty() {
return hostHeaderRewrite;
}
public SimpleBooleanProperty isCustomizeProperty() {
return isCustomize;
}
public void set(ProxySetup setup) {
String domainStr = setup.getDomain();
String suffix = "";
isCustomize.set(DomainUtil.isCustomize(setup));
if (!isCustomize.get()) {
domainStr = DomainUtil.getCustomize(setup);
suffix = DomainUtil.getSuffix(setup);
}
setId(setup.getId());
setServer(ProxyConfig.getServerName(setup.getNode()));
setProxyName(setup.getProxy_name());
setProxyType(setup.getProxy_type());
setLocalIp(setup.getLocal_ip());
setLocalPort(setup.getLocal_port());
setRemotePort(setup.getRemote_port());
setDomain(domainStr);
setDomainSuffix(suffix);
setLocations(setup.getLocations());
setUseEncryption(setup.getUse_encryption());
setUseCompression(setup.getUse_compression());
setHeader_X_From_Where(setup.getHost_header_rewrite());
setHostHeaderRewrite(setup.getHost_header_rewrite());
setStatus(setup.getStatus());
setSk(setup.getSk());
setSort(setup.getSort());
setRunning(setup.isRuning());
}
public ProxySetup get() {
return new ProxySetup(
getId(),
ProxyConfig.getServerNode(server.get()),
getProxyName(),
StringUtils.lowerCase(proxyType.getValue()),
getLocalIp(),
getLocalPort() == null ? null : Integer.parseInt(getLocalPort()),
getRemotePort() == null ? "" : getRemotePort(),
getDomain() + getDomainSuffix(),
isUseEncryption(),
isUseCompression(),
getLocations(),
getHostHeaderRewrite(),
getHeader_X_From_Where(),
getSk(),
getStatus(),
getSort(),
isRunning()
);
}
}

View File

@ -8,7 +8,6 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import top.octopusyan.http.request.ProxySetup;
import java.util.ArrayList;
import java.util.List;
/**
@ -25,7 +24,7 @@ public class ApplicatonStore {
private static SimpleBooleanProperty autoLogin = new SimpleBooleanProperty();
private static SimpleBooleanProperty rememberMe = new SimpleBooleanProperty();
private static SimpleBooleanProperty registerSuccess = new SimpleBooleanProperty();
private static SimpleListProperty<ProxySetup> proxySetupList = new SimpleListProperty<>();
private static SimpleListProperty<ProxySetup> proxyList = new SimpleListProperty<>();
private static SimpleIntegerProperty selectProxy = new SimpleIntegerProperty(0);
public static void setAccount(String account) {
@ -84,12 +83,12 @@ public class ApplicatonStore {
return registerSuccess.get();
}
public static ObservableList<ProxySetup> getProxySetupList() {
return proxySetupList.get();
public static ObservableList<ProxySetup> getProxyList() {
return proxyList.get();
}
public static void setProxySetupList(List<ProxySetup> proxySetupList) {
ApplicatonStore.proxySetupList = new SimpleListProperty<>(FXCollections.observableList(proxySetupList));
public static void initProxyList(List<ProxySetup> proxySetupList) {
ApplicatonStore.proxyList = new SimpleListProperty<>(FXCollections.observableList(proxySetupList));
}
public static void setSelectProxy(int selectProxy) {

View File

@ -0,0 +1,61 @@
package top.octopusyan.utils;
import cn.hutool.core.util.ObjectUtil;
import top.octopusyan.config.ProxyConfig;
import top.octopusyan.http.request.ProxySetup;
import top.octopusyan.model.ProxySetupModel;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Objects;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description :
* @create : 2022-4-6 20:57
*/
public class DomainUtil {
public static String getCustomize(ProxySetup setup) {
String domain = setup.getDomain();
String serverPath = getServerPath(setup);
int end = domain.indexOf("." + serverPath);
if (domain.length() <= 0 || end == -1) return "";
return domain.substring(0, end);
}
public static String getSuffix(ProxySetup setup) {
String serverPath = getServerPath(setup);
return "." + serverPath;
}
public static boolean isHttp(ProxySetupModel model){
return ProxyConfig.getTypeIndex(model.getProxyType()) < 2;
}
public static boolean isHttps(ProxySetupModel model){
return ProxyConfig.getTypeIndex(model.getProxyType()) == 1;
}
/**
* 是否自定义隧道
*/
public static boolean isCustomize(ProxySetup setup) {
return !setup.getDomain().contains(getServerPath(setup));
}
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;
} catch (UnknownHostException e) {
e.printStackTrace();
return null;
}
}
}

View File

@ -0,0 +1,213 @@
package top.octopusyan.utils;
import javafx.application.Platform;
import javafx.scene.control.TextArea;
import org.apache.commons.io.FileUtils;
import top.octopusyan.manager.ThreadPoolManager;
import top.octopusyan.model.ProxySetupModel;
import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : TODO frp 工具
* @create : 2022-4-7 23:19
*/
public class FrpUtil {
private static final String YAN_FRP_TEMP_DIR_NAME = "frpclienttmp";
private 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 YAN_FRP_TEMP_DIR_PATH = System.getProperty("java.io.tmpdir") + System.currentTimeMillis() + YAN_FRP_TEMP_DIR_NAME;
private static final String FRPC_CLIENT_FILE_PATH = YAN_FRP_TEMP_DIR_PATH + File.separator + FRPC_CLIENT_FILE_NAME;
private static final File frpcDir = new File(YAN_FRP_TEMP_DIR_PATH);
public static final File frpc = new File(FRPC_CLIENT_FILE_PATH);
public File frpcConfigFile;
private final ProxySetupModel model;
private final TextArea console;
private Thread thread;
private Process exec;
private FrpUtil(TextArea console, ProxySetupModel model) {
this.console = console;
this.model = model;
}
public static FrpUtil init(ProxySetupModel model) {
TextArea console = new TextArea();
console.setPrefHeight(180);
console.setMinWidth(200);
return new FrpUtil(console, model);
}
/**
* 初始化frpc客户端文件
*/
public static void initFrpc() throws IOException {
// 存在就删除
if (frpcDir.exists()) FrpUtil.clearTmp();
// 重新创建
frpcDir.mkdir();
frpc.createNewFile();
// 发送删除请求
frpcDir.deleteOnExit();
frpc.deleteOnExit();
// 复制 frpc
String fprcFilePath = FrpUtil.class.getResource("/static/frpc.exe").getFile();
System.out.println(fprcFilePath);
copyFileUsingFileStreams(FrpUtil.class.getResourceAsStream("/static/frpc.exe"), frpc);
// FileUtils.copyFile(new File(fprcFilePath), frpc);
}
/**
* 清理缓存
*/
public static void clearTmp() {
try {
FileUtils.deleteDirectory(frpcDir);
} catch (IOException e) {
e.printStackTrace();
try {
Runtime.getRuntime().exec("taskkill /im " + FRPC_CLIENT_FILE_NAME + " /f");
Thread.sleep(2000);
FileUtils.deleteDirectory(frpcDir);
} catch (InterruptedException | IOException ex) {
ex.printStackTrace();
}
}
}
public TextArea getConsole() {
return console;
}
/**
* 开始
*/
public void start() {
thread = new Thread(() -> {
Platform.runLater(() -> console.appendText("yan-frp-info:正在启动\n"));
try {
// 检查客户端文件
if (!frpc.exists()) initFrpc();
// 初始化配置文件
initProxyConfigFile();
// 执行命令
String command = FRPC_CLIENT_FILE_PATH + " -c " + getConfigFilePath();
System.out.println(command);
exec = Runtime.getRuntime().exec(command, null, frpcDir);
// 设置运行状态
Platform.runLater(() -> model.setRunning(true));
// 获取标准输出
BufferedReader readStdout = new BufferedReader(new InputStreamReader(exec.getInputStream()));
// 逐行读取
String line;
while ((line = readStdout.readLine()) != null) {
String finalLine = line;
Platform.runLater(() -> console.appendText("yan-frp-info:" + finalLine + "\n"));
}
} catch (IOException e) {
e.printStackTrace();
//
Platform.runLater(() -> console.appendText("yan-frp-error:" + e.getMessage() + "\n"));
Platform.runLater(() -> console.appendText("yan-frp-error:启动失败\n"));
}
});
thread.start();
}
/**
* 停止隧道
*/
public void stop() {
ThreadPoolManager.getInstance().execute(() -> {
if (thread != null) {
do {
if (exec != null && exec.isAlive())
exec.destroy();
thread.interrupt();
} while (!thread.isInterrupted());
Platform.runLater(() -> {
model.setRunning(false);
console.appendText("yan-frp-info:已停止\n");
});
// 尝试删除缓存配置文件
if (frpcConfigFile.exists()) frpcConfigFile.delete();
}
});
}
/**
* 清理日志
*/
public void clearLog() {
if (console != null)
Platform.runLater(() -> console.clear());
}
/**
* 隧道配置文件地址
*/
private String getConfigFilePath() {
return YAN_FRP_TEMP_DIR_PATH + File.separator + FRPC_CONF_PREFIX_NAME + model.getProxyName() + FRPC_CONF_SUFFIX_NAME;
}
/**
* 初始化隧道配置文件
*/
private void initProxyConfigFile() throws IOException {
// 创建配置文件
frpcConfigFile = new File(getConfigFilePath());
if (frpcConfigFile.exists()) {
frpcConfigFile.delete();
}
frpcConfigFile.createNewFile();
frpcConfigFile.deleteOnExit();
// 写入服务配置
FileUtils.write(frpcConfigFile, ProxyUtil.getUserServerConfig(model.get().getNode()), StandardCharsets.UTF_8);
// 写入隧道配置
FileUtils.write(frpcConfigFile, ProxyUtil.getProxyConfig(model), StandardCharsets.UTF_8, true);
}
private static void copyFileUsingFileStreams(InputStream input, File dest)
throws IOException {
OutputStream output = null;
try {
output = new FileOutputStream(dest);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = input.read(buf)) > 0) {
output.write(buf, 0, bytesRead);
}
} finally {
input.close();
output.close();
}
}
}

View File

@ -0,0 +1,20 @@
package top.octopusyan.utils;
import javafx.fxml.FXMLLoader;
import javafx.fxml.JavaFXBuilderFactory;
/**
* <p> author : octopus yan
* <p> email : octopus_yan@foxmail.com
* <p> description :
* <p> create : 2022-4-9 01:45 *
*/
public class FxmlUtil {
public static FXMLLoader init(String path) {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setBuilderFactory(new JavaFXBuilderFactory());
fxmlLoader.setLocation(FxmlUtil.class.getResource(path));
return fxmlLoader;
}
}

View File

@ -0,0 +1,43 @@
package top.octopusyan.utils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : html解析工具类
* @create : 2022-4-8 11:04
*/
public class JsoupUtil {
public static Document getDocument(String htmlStr) {
return Jsoup.parse(htmlStr);
}
public static boolean isAlertSuccess(String htmlStr) {
return !htmlStr.contains("alert-danger");
}
public static String getErrorMessage(String htmlStr) {
return getDocument(htmlStr).body().getElementsByClass("alert alert-danger alert-dismissable").text().substring(1);
}
public static String getHtmlMessage(String htmlStr) {
return getDocument(htmlStr).getElementsByClass("alert alert-success alert-dismissable").text().substring(1);
}
public static String getServerConfiguration(String htmlStr) {
Document document = getDocument(htmlStr);
String configuration = document.getElementsByTag("pre").get(0).text();
int end = configuration.indexOf("\n\n");
// 服务器配置获取失败
if (end == -1 && !configuration.contains("[common]")) return null;
// 没有
if (end == -1) {
configuration += "\n\n";
return configuration;
}
return configuration.substring(0, end + 2);
}
}

View File

@ -1,17 +1,24 @@
package top.octopusyan.utils;
import javafx.application.Platform;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import top.octopusyan.common.http.EasyHttp;
import top.octopusyan.common.http.listener.OnHttpListener;
import top.octopusyan.common.http.model.ResponseClass;
import top.octopusyan.config.ProxyConfig.*;
import top.octopusyan.manager.http.EasyHttp;
import top.octopusyan.manager.http.listener.OnHttpListener;
import top.octopusyan.manager.http.model.ResponseClass;
import top.octopusyan.config.ProxyConfig;
import top.octopusyan.config.ProxyConfig.ProxyServer;
import top.octopusyan.config.ProxyConfig.ProxyType;
import top.octopusyan.http.Api;
import top.octopusyan.http.request.ProxySetup;
import top.octopusyan.model.ProxySetupModel;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
@ -22,32 +29,38 @@ import java.util.stream.Collectors;
*/
public class ProxyUtil {
private static String csrf;
private static Map<Integer, String> serverConfigraution = new HashMap<>(3);
/**
* 初始化隧道设置
*/
public static ProxySetup initProxy() {
public static ProxySetup initProxy(Integer maxsort) {
ProxySetup setup = new ProxySetup();
setup.setNode(3);// 上海服务器
setup.setLocal_ip("127.0.0.1");
setup.setLocal_port(80);
setup.setProxy_name("默认连接");
setup.setProxy_type(ProxyType.HTTP.name());
setup.setProxy_type(StringUtils.lowerCase(ProxyType.HTTP.name()));
setup.setDomain(".frp.octopusyan.top");
setup.setSort(1);
if (maxsort != null) setup.setSort(maxsort + 1);
setup.setUse_compression(true);
setup.setUse_encryption(true);
setup.setStatus(true);
setup.setRuning(false);
return setup;
}
public static void delete(Integer id) {
EasyHttp.builder()
.api(Api.DeleteProxy())
.pathParam(id, csrf)
.request(result -> { });
.pathParam(String.valueOf(id), csrf)
.request(result -> {
});
}
/**
* 获取隧道详情信息
* @return
*/
public static ProxySetup info(ProxySetup setup) {
@ -55,7 +68,7 @@ public class ProxyUtil {
try {
html = EasyHttp.builder()
.api(Api.ProxyInfo())
.pathParam(setup.getId())
.pathParam(String.valueOf(setup.getId()))
.execute(new ResponseClass<String>() {
});
} catch (Exception e) {
@ -75,14 +88,14 @@ public class ProxyUtil {
String domain = select.get(8).text();
String status = select.get(13).text();
Arrays.stream(ProxyServer.values()).forEach(server -> {
if (serverStr.contains(server.name()))
setup.setNode(Arrays.asList(ProxyServer.values()).indexOf(server));
});
for (ProxyServer value : ProxyServer.values()) {
String name = value.getServerName();
if (serverStr.contains(name))
setup.setNode(ProxyConfig.getServerNode(name));
}
setup.setLocal_ip(localIp);
setup.setLocal_port(Integer.parseInt(localPort));
setup.setRemote_port("".equals(webPort) ? null : Integer.parseInt(webPort));
setup.setRemote_port("".equals(webPort) ? "" : webPort);
setup.setDomain(domain);
setup.setUse_compression(useCom.equals("启用"));
setup.setUse_encryption(useEnc.equals("启用"));
@ -91,14 +104,15 @@ public class ProxyUtil {
return setup;
}
public static int randomPort(){
public static int randomPort() {
try {
return EasyHttp.builder()
return Integer.parseInt(EasyHttp.builder()
.api(Api.RandomPort())
.execute(new ResponseClass<Integer>() { });
.execute(new ResponseClass<String>() {
}));
} catch (Exception e) {
e.printStackTrace();
return (int) (Math.random() * 31000);
return (int) (Math.random() * 1000 + 30000);
}
}
@ -121,33 +135,35 @@ public class ProxyUtil {
proxySetup.setProxy_name(tds.get(1).text());
proxySetup.setProxy_type(tds.get(2).text());
proxySetup.setSort(Integer.parseInt(tds.get(5).text()));
ProxySetup info = info(proxySetup);
if (info != null) return info;
return proxySetup;
}).collect(Collectors.toList());
listener.onSucceed(proxySetupList);
Platform.runLater(() -> listener.onSucceed(proxySetupList));
}
@Override
public void onFail(Exception e) {
AlertUtil.exception(e).show();
}
});
}
public static boolean add(OnHttpListener<ProxySetup> listener, ProxySetup setup) {
String result = null;
try {
result = EasyHttp.builder()
.api(Api.AddProxy())
.param(setup)
.execute(new ResponseClass<String>() {
});
} catch (Exception e) {
e.printStackTrace();
return false;
}
if(result.equals("隧道创建成功")){
getList(result1 -> listener.onSucceed(result1.get(result1.size()-1)));
} else {
AlertUtil.error(result).show();
}
return result.equals("隧道创建成功");
public static void add(OnHttpListener<ProxySetup> listener, ProxySetup setup) {
EasyHttp.builder()
.api(Api.AddProxy())
.param(setup)
.request(result -> getList(result1 -> {
if (result1 != null && result1.size() > 0) {
for (ProxySetup proxySetup : result1) {
if (Objects.equals(proxySetup.getSort(), proxySetup.getSort())) {
listener.onSucceed(proxySetup);
}
}
}
}));
}
public static String getCsrf() {
@ -159,4 +175,53 @@ public class ProxyUtil {
int i1 = htmlStr.indexOf("\"", i + 18);
ProxyUtil.csrf = htmlStr.substring(i + 18, i1);
}
public static String getUserServerConfig(int node) {
String config = serverConfigraution.get(node);
if (StringUtils.isNotEmpty(config)) return config;
try {
String result = EasyHttp.builder()
.api(Api.ServerConfiguration)
.pathParam(String.valueOf(node))
.execute(new ResponseClass<String>() {
});
config = JsoupUtil.getServerConfiguration(result);
if (StringUtils.isNotEmpty(config)) serverConfigraution.put(node, config);
return config;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String getProxyConfig(ProxySetupModel setup) {
String n = "\n";
// 基础配置
StringBuffer stringBuffer = new StringBuffer("[" + setup.getProxyName() + "]\n");
stringBuffer.append("privilege_mode = true\n")
.append("type = ").append(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
stringBuffer.append("custom_domains = ").append(setup.getDomain()).append(setup.getDomainSuffix()).append(n);
if (!StringUtils.isEmpty(setup.getLocations()))
stringBuffer.append("locations = ").append(setup.getLocations()).append(n);
if (!StringUtils.isEmpty(setup.getLocations()))
stringBuffer.append("host_header_rewrite = ").append(setup.getHostHeaderRewrite()).append(n);
if (!StringUtils.isEmpty(setup.getLocations()))
stringBuffer.append("header_X-From-Where = ").append(setup.getHeader_X_From_Where()).append(n);
} else {
// TCP / UDP / XTCP / STCP
stringBuffer.append("remote_port = ").append(setup.getRemotePort()).append(n);
if (!StringUtils.isEmpty(setup.getSk()))
stringBuffer.append("sk = ").append(setup.getSk()).append(n);
}
// 压缩和加密
stringBuffer.append("use_encryption = ").append(setup.isUseEncryption()).append(n)
.append("use_compression = ").append(setup.isUseCompression()).append(n).append(n);
return stringBuffer.toString();
}
}

View File

@ -1,64 +0,0 @@
package top.octopusyan.utils;
import com.jfoenix.validation.RegexValidator;
import com.jfoenix.validation.RequiredFieldValidator;
import com.jfoenix.validation.StringLengthValidator;
import com.jfoenix.validation.base.ValidatorBase;
import org.apache.commons.lang3.StringUtils;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : JFX文本校验
* @create : 2022-4-2 17:03
*/
public class TextValidate {
// /**
// * 账号格式错误
// */
// public static ValidatorBase AccountFail = new ValidatorBase() {
// @Override
// protected void eval() {
// setMessage("账号格式错误");
// hasErrors.set(true);
// }
// };
/** 账号不能为空 */
public static RequiredFieldValidator AccoountRequired = new RequiredFieldValidator("账号不能为空!");
/** 账号格式 */
public static RegexValidator AccoountValidator = new RegexValidator("账号格式有误!");
/** 密码不能为空 */
public static RequiredFieldValidator PasswordRequired = new RequiredFieldValidator("密码不能为空!");
/** 邮箱地址格式验证 */
public static RegexValidator EmailFormat = new RegexValidator("邮箱地址格式错误");
static {
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_-]*$");
}
/**
* 文本长度校验
* <p/> message > name + "长度有误"
*/
public static StringLengthValidator getLengthValidator(String name, int length){
return new StringLengthValidator(name+"长度有误", length);
}
/**
* 文本长度校验
* <p/> message > name + "长度有误,应在" + min + "" + max + "之间"
*/
public static RegexValidator getLengthValidator(int min, int max, String name){
String message = name + "长度有误,应在" + min + "" + max + "之间";
RegexValidator validator = new RegexValidator(message);
validator.setRegexPattern("[a-zA-Z0-9_-]{" + min + "," + max + "}$");
return validator;
}
}

View File

@ -16,7 +16,7 @@
-fx-opacity: 0.4;
}
/* 布局边框 */
.mainPane, #addProxyBtn , .jfx-list-view{
.mainPane, #addProxyBtn, .jfx-list-view{
-fx-border-color: linear-gradient(#57b4f2, #9198e5);
}
@ -38,16 +38,23 @@
-fx-border-radius: 0 5 5 0;
}
/* 服务线路 */
#proxyServerCB {
#proxyServerComboBox {
-fx-font-size: 16;
-fx-text-fill: #757575;
}
/* TODO 隧道列表 */
#proxyListView .list-cell:selected {
-fx-text-fill: white;
-fx-background-color: linear-gradient(#9198e5, #57b4f2);
}
#proxyListView .list-cell:hover {
-fx-opacity: 0.7;
-fx-background-color: linear-gradient(#9198e5, #57b4f2);
}
#proxyListView .list-cell:hover lable, #proxyListView .list-cell:selected label, .proxyListItem-select{
-fx-text-fill: white;
}
.proxyListItem-run FontIcon {
-fx-icon-color: linear-gradient(#95f257, #91e5ac);
}
@ -73,18 +80,20 @@
-fx-font-family: "Microsoft YaHei";
}
/* 隧道类型 */
#proxyProtocolCombox {
#proxyProtocolComboBox {
-fx-font-size: 14px;
-fx-font-family: "Microsoft YaHei";
}
/* 自定义域名 */
#customizeDomainBtn {
#customizeDomainBtn, #resetProxyBtn, #copyDomainBtn {
-fx-text-fill: linear-gradient(#57b4f2, #9198e5);
}
/* 添加隧道 */
#addProxyBtn {
-fx-border-width: 0 1 1;
-fx-font-size: 14px;
-fx-text-fill: white;
-fx-background-color: linear-gradient(rgba(87, 180, 242, 0.7), rgba(145, 152, 229, 0.71));
-fx-font-family: "Microsoft YaHei";
}
/* 启动 */
@ -102,7 +111,12 @@
}
/* 修改Tab的背景颜色 */
.jfx-tab-pane .tab-header-background {
.jfx-tab-pane .tab-header-background {
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
}
/* 日志 */
.text-area {
-fx-font-size: 15;
-fx-font-family: monospace;
-fx-background-color: white;
}

View File

@ -1,8 +1,8 @@
.root, #root{
.root, #root {
-fx-background-radius: 14;
}
#titleLable, #closeBtn, #minimizeBtn{
#titleLable, #closeBtn, #minimizeBtn {
-fx-font-family: "Microsoft YaHei";
-fx-text-fill: white;
}
@ -28,8 +28,13 @@
#closeBtn:hover {
-fx-background-color: rgba(255, 0, 0, 0.70);
-fx-text-fill: white;
}
#minimizeBtn:hover {
-fx-background-color: rgba(243, 242, 242, 0.20);
}
.button:hover, .jfx-button:hover {
-fx-opacity: 0.8;
}

View File

@ -5,8 +5,10 @@
<?import com.jfoenix.controls.JFXListView?>
<?import com.jfoenix.controls.JFXRadioButton?>
<?import com.jfoenix.controls.JFXTabPane?>
<?import com.jfoenix.controls.JFXTextArea?>
<?import com.jfoenix.controls.JFXTextField?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.Cursor?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tab?>
@ -20,177 +22,228 @@
<?import javafx.scene.text.Font?>
<?import org.kordamp.ikonli.javafx.FontIcon?>
<StackPane fx:id="root" prefHeight="700.0" prefWidth="750.0" stylesheets="@../css/main.css" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.octopusyan.controller.MainController">
<StackPane fx:id="root" prefHeight="700.0" prefWidth="750.0" stylesheets="@../css/main.css"
xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="top.octopusyan.controller.MainController">
<VBox fx:id="MainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="750.0">
<VBox fx:id="MainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="700.0" prefWidth="750.0">
<AnchorPane fx:id="MainTopPane" prefHeight="35.0" prefWidth="750.0">
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="90.0" text="Yan Frp" />
<JFXButton fx:id="minimizeBtn" layoutX="678.0" prefHeight="35.0" prefWidth="36.0" text="—" />
<JFXButton fx:id="closeBtn" layoutX="714.0" prefHeight="35.0" prefWidth="36.0" text="X" />
<JFXComboBox fx:id="proxyServerCB" layoutX="90.0" prefHeight="36.0" prefWidth="100.0" />
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="90.0" text="Yan Frp"/>
<JFXButton fx:id="minimizeBtn" layoutX="678.0" prefHeight="35.0" prefWidth="36.0" text="—"/>
<JFXButton fx:id="closeBtn" layoutX="714.0" prefHeight="35.0" prefWidth="36.0" text="X"/>
<JFXComboBox fx:id="proxyServerComboBox" layoutX="90.0" prefHeight="36.0" prefWidth="90.0">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXComboBox>
</AnchorPane>
<GridPane fx:id="MainBottomPane" hgap="10.0" prefHeight="684.0" prefWidth="750.0" vgap="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="539.0" minWidth="10.0" prefWidth="520.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="370.0" minWidth="10.0" prefWidth="200.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="539.0" minWidth="10.0" prefWidth="520.0"/>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="370.0" minWidth="10.0" prefWidth="200.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="493.0" minHeight="10.0" prefHeight="404.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="337.0" minHeight="10.0" prefHeight="231.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="493.0" minHeight="10.0" prefHeight="404.0" vgrow="SOMETIMES"/>
<RowConstraints maxHeight="337.0" minHeight="10.0" prefHeight="231.0" vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<VBox fx:id="proxySetupPane" prefHeight="200.0" prefWidth="100.0" styleClass="mainPane">
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="隧道名称">
<HBox.margin>
<Insets right="20.0" />
</HBox.margin>
</Label>
<JFXTextField fx:id="proxyNameTextField" prefHeight="Infinity" prefWidth="320.0" styleClass="inputText">
<HBox.margin>
<Insets right="20.0" />
</HBox.margin>
<padding>
<Insets left="15.0" />
</padding>
<font>
<Font size="14.0" />
</font>
</JFXTextField>
<padding>
<Insets left="10.0"/>
</padding>
<HBox alignment="CENTER_RIGHT" prefHeight="50.0" prefWidth="520.0">
<padding>
<Insets bottom="10.0" top="10.0" />
<Insets bottom="10.0" top="10.0"/>
</padding>
<JFXButton fx:id="resetProxyBtn" prefHeight="Infinity" text="重置">
<HBox.margin>
<Insets right="10.0"/>
</HBox.margin>
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXButton>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="是否启用">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="隧道名称">
<HBox.margin>
<Insets right="20.0" />
<Insets right="20.0"/>
</HBox.margin>
</Label>
<JFXTextField fx:id="proxyNameTextField" prefHeight="Infinity" prefWidth="320.0"
styleClass="inputText">
<HBox.margin>
<Insets right="20.0"/>
</HBox.margin>
<padding>
<Insets left="15.0"/>
</padding>
<font>
<Font size="14.0"/>
</font>
</JFXTextField>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="是否启用">
<HBox.margin>
<Insets right="20.0"/>
</HBox.margin>
</Label>
<HBox alignment="CENTER_LEFT" prefHeight="Infinity" prefWidth="320.0">
<JFXRadioButton fx:id="openProxyRBtn" text="启用">
<HBox.margin>
<Insets right="20.0" />
<Insets right="20.0"/>
</HBox.margin>
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXRadioButton>
<JFXRadioButton fx:id="closeProxyRBtn" text="停用">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXRadioButton>
<JFXRadioButton fx:id="closeProxyRBtn" text="停用" />
</HBox>
<VBox.margin>
<Insets />
</VBox.margin>
<padding>
<Insets bottom="10.0" top="10.0" />
</padding>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="协议类型">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="协议类型">
<HBox.margin>
<Insets right="20.0" />
<Insets right="20.0"/>
</HBox.margin>
</Label>
<JFXComboBox fx:id="proxyProtocolCombox" prefHeight="Infinity" prefWidth="320.0" styleClass="inputText" />
<padding>
<Insets bottom="10.0" top="10.0" />
</padding>
<JFXComboBox fx:id="proxyProtocolComboBox" prefHeight="Infinity" prefWidth="320.0"
styleClass="inputText">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXComboBox>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="内网服务地址">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="内网服务地址">
<HBox.margin>
<Insets right="20.0" />
<Insets right="20.0"/>
</HBox.margin>
</Label>
<HBox prefHeight="Infinity" prefWidth="320.0">
<children>
<JFXTextField fx:id="localHostTextField" alignment="CENTER" prefWidth="250.0" promptText="内网服务IP (IPV4)" styleClass="inputText-left" text="127.0.0.1">
<font>
<Font size="14.0" />
</font>
</JFXTextField>
<JFXTextField fx:id="localPortTextField" alignment="CENTER" prefWidth="70.0" promptText="端口" styleClass="inputText-right" text="80">
<font>
<Font size="14.0" />
</font>
</JFXTextField>
</children>
<JFXTextField fx:id="localHostTextField" alignment="CENTER" prefWidth="220.0"
promptText="内网服务IP (IPV4)" styleClass="inputText-left" text="127.0.0.1">
<font>
<Font size="14.0"/>
</font>
</JFXTextField>
<JFXTextField fx:id="localPortTextField" alignment="CENTER" minWidth="70.0"
prefWidth="100.0" promptText="端口" styleClass="inputText-right" text="80">
<font>
<Font size="14.0"/>
</font>
</JFXTextField>
</HBox>
<padding>
<Insets bottom="10.0" top="10.0" />
</padding>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="外网访问地址">
<HBox fx:id="domainPane" alignment="CENTER_LEFT" prefHeight="40.0" prefWidth="520.0">
<padding>
<Insets top="10.0"/>
</padding>
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="外网访问地址">
<HBox.margin>
<Insets right="20.0" />
<Insets right="20.0"/>
</HBox.margin>
</Label>
<HBox prefWidth="320.0">
<JFXTextField fx:id="domainTextField" alignment="CENTER" prefHeight="30.0" prefWidth="280.0" promptText="自定义子域名大于3位" styleClass="inputText-left">
<JFXTextField fx:id="domainTextField" alignment="CENTER" prefHeight="30.0" prefWidth="320.0"
promptText="自定义子域名大于3位" styleClass="inputText-left">
<font>
<Font size="14.0" />
<Font size="14.0"/>
</font>
</JFXTextField>
<JFXTextField fx:id="domainSuffixTextField" alignment="CENTER" disable="true" minWidth="155.0" prefHeight="30.0" styleClass="inputText-right" text=".frp.octopusyan.top">
<JFXTextField fx:id="domainSuffixTextField" alignment="CENTER" disable="true"
minWidth="155.0" prefHeight="30.0" styleClass="inputText-right"
text=".frp.octopusyan.top">
<font>
<Font size="14.0" />
<Font size="14.0"/>
</font>
</JFXTextField>
</HBox>
<JFXButton fx:id="customizeDomainBtn" prefHeight="Infinity" text="自定义">
<HBox.margin>
<Insets left="10.0" />
<Insets left="5.0"/>
</HBox.margin>
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXButton>
<padding>
<Insets bottom="10.0" top="10.0" />
</padding>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="用户名 (可选)">
<Label fx:id="domainHtinTextField" alignment="CENTER" prefHeight="20.0" prefWidth="320.0"
text="Label" visible="false">
<VBox.margin>
<Insets left="120.0"/>
</VBox.margin>
<font>
<Font size="14.0"/>
</font>
</Label>
<HBox fx:id="proxyStatusPane" alignment="CENTER" prefHeight="40.0" prefWidth="520.0">
<padding>
<Insets bottom="10.0"/>
</padding>
<Label alignment="CENTER_RIGHT" prefHeight="30.0" styleClass="proxySetupLabel"
text="启动成功!立即访问 ">
<HBox.margin>
<Insets right="20.0" />
<Insets/>
</HBox.margin>
</Label>
<JFXTextField prefHeight="Infinity" prefWidth="320.0" styleClass="inputText">
<padding>
<Insets left="10.0" />
</padding>
</JFXTextField>
<padding>
<Insets bottom="10.0" top="10.0" />
</padding>
<Hyperlink fx:id="domainLink" prefHeight="30.0"/>
<JFXButton fx:id="copyDomainBtn" prefHeight="30.0" text="复制"/>
</HBox>
<padding>
<Insets left="10.0" top="40.0" />
</padding>
</VBox>
<VBox fx:id="proxyListPane" prefHeight="Infinity" prefWidth="Infinity" GridPane.columnIndex="1">
<JFXListView fx:id="proxyListView" prefHeight="360.0" prefWidth="Infinity" />
<JFXListView fx:id="proxyListView" prefHeight="360.0" prefWidth="Infinity"/>
<JFXButton fx:id="addProxyBtn" prefHeight="45.0" prefWidth="Infinity" text="新增连接">
<graphic>
<FontIcon iconLiteral="fa-plus" />
<FontIcon iconColor="white" iconLiteral="fa-plus"/>
</graphic>
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXButton>
</VBox>
<AnchorPane prefHeight="Infinity" prefWidth="Infinity" GridPane.rowIndex="1">
<JFXTabPane fx:id="tabPane" prefHeight="230" prefWidth="520.0" styleClass="mainPane">
<tabs>
<Tab text=" 日志 ">
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" />
<Tab fx:id="proxyLogPane" text=" 日志 ">
<JFXTextArea prefWidth="Infinity"/>
</Tab>
<Tab text="常见问题">
<VBox minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<HBox prefHeight="30.0" prefWidth="Infinity">
<Label prefHeight="Infinity" text="* 若启动后无法通过外网访问,请点击" />
<Hyperlink prefHeight="Infinity" text="此处" />
<Label prefHeight="Infinity" text="排查问题" />
</HBox>
<Label prefHeight="30" text="* 修改配置信息后,必须重启才能生效" />
<Label prefHeight="30" text="* 只有启动或重启内网穿透服务时,配置信息才会被保存" />
<Label prefHeight="30" text="* 同一个账号在同一时间只能在一台电脑上登录使用" />
<Label prefHeight="30" text="* 请勿将非法、暴力、色情等信息映射到外网上去,一经发现立即封号" />
<padding>
<Insets left="20.0" top="20.0" />
<Insets left="20.0" top="20.0"/>
</padding>
<HBox prefHeight="30.0" prefWidth="Infinity">
<Label prefHeight="Infinity" text="* 若启动后无法通过外网访问,请点击"/>
<Hyperlink prefHeight="Infinity" text="此处"/>
<Label prefHeight="Infinity" text="排查问题"/>
</HBox>
<Label prefHeight="30" text="* 修改配置信息后,必须重启才能生效"/>
<Label prefHeight="30" text="* 只有启动或重启内网穿透服务时,配置信息才会被保存"/>
<Label prefHeight="30" text="* 同一个账号在同一时间只能在一台电脑上登录使用"/>
<Label prefHeight="30" text="* 请勿将非法、暴力、色情等信息映射到外网上去,一经发现立即封号"/>
</VBox>
</Tab>
<Tab text="使用场景">
@ -200,16 +253,23 @@
</Tab>
</tabs>
</JFXTabPane>
<JFXButton layoutX="480.0" layoutY="-1.0" prefHeight="35.0" prefWidth="26.0" text="">
<JFXButton fx:id="clearLogBtn" layoutX="480.0" layoutY="-1.0" prefHeight="35.0" prefWidth="26.0"
text="">
<graphic>
<FontIcon iconColor="white" iconLiteral="fa-trash-o" iconSize="18" />
<FontIcon iconColor="white" iconLiteral="fa-trash-o" iconSize="18"/>
</graphic>
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXButton>
</AnchorPane>
<VBox prefHeight="200.0" prefWidth="100.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
<children>
<JFXButton fx:id="startProxyBtn" buttonType="RAISED" prefHeight="44.0" prefWidth="202.0" text="启动" />
</children>
<JFXButton fx:id="startProxyBtn" buttonType="RAISED" prefHeight="44.0" prefWidth="202.0"
styleClass="startProxyBtn" text="启动">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXButton>
</VBox>
</children>
</GridPane>

Binary file not shown.