This commit is contained in:
octopusYan 2022-04-06 17:49:00 +08:00
parent 8c4d21a00b
commit 8c41c8392b
86 changed files with 6512 additions and 0 deletions

0
.idea/.gitignore vendored Normal file
View File

17
.idea/compiler.xml Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="YanFrp" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="YanFrp" target="8" />
</bytecodeTargetLevel>
</component>
</project>

6
.idea/cssdialects.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CssDialectMappings">
<file url="file://$PROJECT_DIR$/src/main/resources/css/login.css" dialect="CLASSIC" />
</component>
</project>

8
.idea/encodings.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View File

@ -0,0 +1,36 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="INNER_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="METHOD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
</value>
</option>
<option name="FIELD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="IGNORE_DEPRECATED" value="false" />
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="email,description,create" />
</inspection_tool>
</profile>
</component>

25
.idea/jarRepositories.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="aliyun" />
<option name="name" value="aliyun" />
<option name="url" value="https://maven.aliyun.com/repository/public" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://maven.aliyun.com/repository/public" />
</remote-repository>
</component>
</project>

13
.idea/misc.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/YanFrp.iml" filepath="$PROJECT_DIR$/YanFrp.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

50
YanFrp.iml Normal file
View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: com.jfoenix:jfoenix:8.0.10" level="project" />
<orderEntry type="library" name="Maven: org.controlsfx:controlsfx:11.0.3" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-graphics:11.0.2" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-graphics:win:11.0.2" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-base:11.0.2" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-base:win:11.0.2" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-media:11.0.2" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-media:win:11.0.2" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-controls:11.0.2" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-controls:win:11.0.2" level="project" />
<orderEntry type="library" name="Maven: org.kordamp.ikonli:ikonli-javafx:2.6.0" level="project" />
<orderEntry type="library" name="Maven: org.kordamp.ikonli:ikonli-core:2.6.0" level="project" />
<orderEntry type="library" name="Maven: org.kordamp.ikonli:ikonli-fontawesome-pack:2.6.0" level="project" />
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.14" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.36" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-simple:1.7.36" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.12.0" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-crypto:5.8.0.M2" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-core:5.8.0.M2" level="project" />
<orderEntry type="library" name="Maven: org.bouncycastle:bcprov-jdk15on:1.70" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:fastjson:1.2.80" level="project" />
<orderEntry type="library" name="Maven: com.squareup.okhttp3:okhttp:4.8.1" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib:1.3.72" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains:annotations:13.0" level="project" />
<orderEntry type="library" name="Maven: com.squareup.okhttp3:logging-interceptor:4.8.1" level="project" />
<orderEntry type="library" name="Maven: com.squareup.okio:okio:2.8.0" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-api:5.8.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.8.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.apiguardian:apiguardian-api:1.1.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-engine:5.8.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-engine:1.8.1" level="project" />
<orderEntry type="library" name="Maven: com.github.axet:kaptcha:0.0.9" level="project" />
<orderEntry type="library" name="Maven: com.jhlabs:filters:2.0.235" level="project" />
<orderEntry type="library" name="Maven: org.jsoup:jsoup:1.14.3" level="project" />
</component>
</module>

182
pom.xml Normal file
View File

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>top.octopusyan</groupId>
<artifactId>YanFrp</artifactId>
<version>1.0-SNAPSHOT</version>
<name>YanFrp</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.8.1</junit.version>
<datafx.version>8.0.1</datafx.version>
<okhttp3.version>4.8.1</okhttp3.version>
<feign.version>11.8</feign.version>
<ikonli.version>2.6.0</ikonli.version>
</properties>
<dependencies>
<!-- UI 库 -->
<dependency>
<groupId>com.jfoenix</groupId>
<artifactId>jfoenix</artifactId>
<version>8.0.10</version>
</dependency>
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>11.0.3</version>
</dependency>
<!-- 图标库 -->
<!-- https://kordamp.org/ikonli/ -->
<!-- https://mvnrepository.com/artifact/org.kordamp.ikonli/ikonli-javafx -->
<!-- javafx 支持 -->
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-javafx</artifactId>
<version>${ikonli.version}</version>
</dependency>
<!-- fontawesome图标 java8 不支持 ikonli-fontawesome5-pack -->
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-fontawesome-pack</artifactId>
<version>${ikonli.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.14</version>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
<scope>compile</scope>
</dependency>
<!-- commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>5.8.0.M2</version>
</dependency>
<!-- 算法库 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
<!-- okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp3.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>${okhttp3.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>2.8.0</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--验证码-->
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>0.0.9</version>
</dependency>
<!-- html 解析 -->
<dependency>
<!-- jsoup HTML parser library @ https://jsoup.org/ -->
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</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>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,97 @@
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.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 top.octopusyan.http.OkHttpClientConfig;
import top.octopusyan.utils.AlertUtil;
import top.octopusyan.utils.ApplicatonStore;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : YanFrp Application
* @create : 2022-3-29 15:00
*/
public class YanFrpApplication extends Application {
private static final Logger LOGGER = LoggerFactory.getLogger(YanFrpApplication.class);
@Override
public void init() throws Exception {
super.init();
LOGGER.debug("init...");
// 网络请求设置
HttpConfig.with(OkHttpClientConfig.httpClient())
// 请求服务地址
.serverPath("https://frp.octopusyan.top")
// 是否打印日志
.setLogEnabled(false)
// 设置日志打印策略
.setLogStrategy(new LogStrategy())
// 设置请求处理策略
.setHandler(new RequestHandler())
// 设置请求重试次数
.setRetryCount(3)
// 启用配置
.into();
}
@Override
public void start(Stage stage) {
LOGGER.debug("start...");
// TODO 全局异常处理... (emm有点草率先这样了)
Thread.setDefaultUncaughtExceptionHandler((t, e) -> Platform.runLater(() -> showErrorDialog(t, e)));
Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog);
// 初始化弹窗
AlertUtil.initOwner(stage);
// 初始化应用数据
ApplicatonStore.setRegisterSuccess(false);
try {
stage.initStyle(StageStyle.TRANSPARENT);
StackPane root = new FXMLLoader(this.getClass().getResource("/fxml/login.fxml")).load();//底层面板
Scene scene = new Scene(
root,
root.getPrefWidth() + 20,
root.getPrefHeight() + 20,
Color.TRANSPARENT
);
scene.getStylesheets().addAll(getClass().getResource("/css/root.css").toExternalForm());
stage.setScene(scene);
stage.show();
} catch (Throwable t) {
showErrorDialog(Thread.currentThread(), t);
}
LOGGER.debug("start end...");
}
private void showErrorDialog(Thread t, Throwable e) {
e.printStackTrace();
AlertUtil.exceptionAlert(new Exception(e)).show();
}
@Override
public void stop() throws Exception {
super.stop();
LOGGER.debug("stop...");
}
public static void main(String[] args) {
Application.launch(YanFrpApplication.class, args);
}
}

View File

@ -0,0 +1,169 @@
package top.octopusyan.base;
import com.jfoenix.controls.JFXButton;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import top.octopusyan.utils.Loading;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 通用视图控制器
* @create : 2022-3-31 18:09
*/
public abstract class BaseController<P extends Pane> implements Initializable {
private double xOffSet = 0, yOffSet = 0;
private volatile Loading loading;
public void jumpTo(BaseController<P> controller){
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();
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
// 全局窗口拖拽
if (dragWindow()) {
// 窗口拖拽
getRootPanel().setOnMousePressed(event -> {
xOffSet = event.getSceneX();
yOffSet = event.getSceneY();
});
getRootPanel().setOnMouseDragged(event -> {
Stage stage = (Stage) getRootPanel().getScene().getWindow();
stage.setX(event.getScreenX() - xOffSet);
stage.setY(event.getScreenY() - yOffSet);
});
}
// 这个位置的左边第一个 JFXBtn 会莫名其妙会的焦点效果启动时禁用焦点取消按钮效果
getFirstBtn().setDisableVisualFocus(true);
// 最小化窗口
getMinimizeBtn().setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
Stage stage = (Stage) getRootPanel().getScene().getWindow();
stage.setIconified(true);
}
});
// 关闭窗口
getClooseBtn().setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
onDestroy();
}
});
// 初始化数据
initData();
// 初始化视图样式
initViewStyle();
// 初始化视图事件
initViewAction();
}
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);
loading.show();
}
public boolean isLoadShowing(){
return loading != null && loading.showing();
}
public void stopLoading(){
loading.closeStage();
}
/**
* 窗口拖拽设置
*
* @return 是否启用
*/
public abstract boolean dragWindow();
/**
* 获取根布局
*
* @return 根布局对象
*/
public abstract P getRootPanel();
/**
* 获取根布局
*
* @return 根布局对象
*/
@NotNull
public abstract String getRootFxmlPath();
/**
* 关闭按钮
*/
public abstract Button getClooseBtn();
/**
* 最小化按钮
*/
public abstract Button getMinimizeBtn();
/**
* 获取第一个按钮
*/
public abstract JFXButton getFirstBtn();
/**
* 初始化数据
*/
public abstract void initData();
/**
* 视图样式
*/
public abstract void initViewStyle();
/**
* 视图事件
*/
public abstract void initViewAction();
/**
* 关闭窗口
*/
public void onDestroy(){
Stage stage = (Stage) getRootPanel().getScene().getWindow();
stage.hide();
stage.close();
Platform.exit();
System.exit(0);
}
}

View File

@ -0,0 +1,18 @@
package top.octopusyan.base;
import javafx.application.Application;
import javafx.stage.Stage;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : Base Window
* @create : 2022-4-1 11:26
*/
public class BaseWindow extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
}
}

View File

@ -0,0 +1,57 @@
package top.octopusyan.common.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;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description :
* @create : 2022-4-3 22:24
*/
public class Demo {
public static void main(String[] args) {
// 网络请求设置
HttpConfig.with(new OkHttpClient())
// 请求服务地址
.serverPath("https://server.example.com")
// 是否打印日志
.setLogEnabled(false)
// 设置日志打印策略
.setLogStrategy(new LogStrategy())
// 设置请求处理策略
.setHandler(new RequestHandler())
// 设置请求重试次数
.setRetryCount(3)
// 启用配置
.into();
EasyHttp.builder()
// 自定义接口
.api(new ParamApi<String,String>("/business/api", HttpConstant.Method.POST))
// 动态参数
.param("param")
// 异步请求
.request(new OnHttpListener<String>() {
@Override
public void onSucceed(String result) {
}
});
try {
Integer num = EasyHttp.builder()
.api(new NotParamApi<Integer>("/business/getnum", HttpConstant.Method.GET))
.execute(new ResponseClass<Integer>() {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,575 @@
package top.octopusyan.common.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 java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : http 请求工具
* @create : 2022-4-1 15:14
*/
public class EasyHttp<Param, Result> {
/** 请求处理策略 */
private final IRequestHandler mHandler = HttpConfig.getInstance().getHandler();
/** 请求执行代理类 */
private CallProxy mCallProxy;
/** 请求标记 */
private String mTag;
/** 请求延迟 */
private long mDelayMillis;
/** 请求服务地址 */
private final String server = HttpConfig.getInstance().getServerPath();
/** 请求接口 */
private RequestApi<Param, Result> mRequestApi;
/** 请求参数 */
private Param param;
public static class Builder {
private String method;
private String apiPath;
private BodyType bodyType;
public <Param, Result> ParamRequest<Param, Result> api(ParamApi<Param, Result> api) {
return new ParamRequest<Param, Result>().api(api);
}
public <Result> UrlRequest<Result> api(NotParamApi<Result> api) {
return new UrlRequest<Result>().api(api);
}
public <Result> RestfulRequest<Result> api(PathParamApi<Result> api) {
return new RestfulRequest<Result>().api(api);
}
public <Param, Result> EasyHttp<Param, Result> api(RequestApi<Param, Result> api) {
return new EasyHttp<Param, Result>().api(api);
}
/**
* 无参数请求
*/
public <Result> UrlRequest<Result> build(ResponseClass<Result> result) {
if(apiPath == null) throw new IllegalArgumentException("请求接口地址为空!");
NotParamApi<Result> requestApi = new NotParamApi<>(apiPath, method, bodyType);
return new UrlRequest<Result>().api(requestApi);
}
/**
* 带参请求
* @param param 请求参数
*/
public <Param, Result> ParamRequest<Param, Result> build(Param param, ResponseClass<Result> result) {
ParamApi<Param, Result> requestApi = new ParamApi<>(apiPath, method, bodyType);
return new ParamRequest<Param, Result>().api(requestApi).param(param);
}
public Builder setMethod(String method) {
this.method = method;
return this;
}
public Builder setApiPath(String apiPath) {
this.apiPath = apiPath;
return this;
}
public Builder setBodyType(BodyType bodyType) {
this.bodyType = bodyType;
return this;
}
}
public static Builder builder(){
return new Builder();
}
protected EasyHttp<Param, Result> api(RequestApi<Param, Result> api) {
this.mRequestApi = api;
return this;
}
protected EasyHttp<Param, Result> param(Param param) {
this.param = param;
return this;
}
private RequestApi<Param, Result> getRequestApi() {
return mRequestApi;
}
private IRequestHandler getRequestHandler() {
return mHandler;
}
protected void path(String path) {
this.mRequestApi.setApi(path);
}
protected String getPath(){
return this.mRequestApi.getApi();
}
public String getTag() {
return mTag;
}
public void setTag(String mTag) {
this.mTag = mTag;
}
public long getDelayMillis() {
return mDelayMillis;
}
public void setDelayMillis(long mDelayMillis) {
this.mDelayMillis = mDelayMillis;
}
/**
* 执行异步请求
*/
public void request(OnHttpListener<Result> listener) {
if (mDelayMillis > 0) {
// 打印请求延迟时间
HttpLog.print("RequestDelay", String.valueOf(mDelayMillis));
}
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
EasyUtils.postDelayed(() -> {
HttpLog.print(stackTrace);
mCallProxy = new CallProxy(createCall());
mCallProxy.enqueue(new NormalCallback(mCallProxy, mRequestApi, mHandler, listener));
}, mDelayMillis);
}
/**
* 执行同步请求
* @param responseClass 需要解析泛型的对象
* @return 返回解析完成的对象
* @throws Exception 如果请求失败或者解析失败则抛出异常
*/
public Result execute(ResponseClass<Result> responseClass) throws Exception {
if (mDelayMillis > 0) {
// 打印请求延迟时间
HttpLog.print("RequestDelay", String.valueOf(mDelayMillis));
Thread.sleep(mDelayMillis);
}
HttpLog.print(new Throwable().getStackTrace());
try {
mCallProxy = new CallProxy(createCall());
Response response = mCallProxy.execute();
return (Result) mHandler.requestSucceed(getRequestApi(), response, EasyUtils.getReflectType(responseClass));
} catch (Exception e) {
throw mHandler.requestFail(getRequestApi(), e);
}
}
/**
* 创建请求回调
*/
private Call createCall() {
String url = server + mRequestApi.getApi();
HttpParams params = new HttpParams();
HttpHeaders headers = new HttpHeaders();
BodyType type = mRequestApi.getBodyType();
if(param != null) setParam(params, headers, type);
return HttpConfig.getInstance().getClient().newCall(createRequest(url, mTag, params, headers, type));
}
private void setParam(HttpParams params, HttpHeaders headers, BodyType type){
List<Field> fields = new ArrayList<>();
Class<?> clazz = param.getClass();
do {
Field[] declaredFields = clazz.getDeclaredFields();
fields.addAll(0, Arrays.asList(declaredFields));
// 遍历获取父类的字段
clazz = clazz.getSuperclass();
} while (clazz != null && !Object.class.equals(clazz));
// 当前请求是否存在流参数
params.setMultipart(EasyUtils.isMultipart(fields));
// 如果参数中包含流参数并且当前请求方式不是表单的话
if (params.isMultipart() && type != BodyType.FORM) {
// 就强制设置成以表单形式提交参数
type = BodyType.FORM;
}
for (Field field : fields) {
// 允许访问私有字段
field.setAccessible(true);
try {
// 获取字段的对象
Object value = field.get(param);
// 获取字段的名称
String key;
HttpRename annotation = field.getAnnotation(HttpRename.class);
if (annotation != null) {
key = annotation.value();
} else {
key = field.getName();
// 如果是内部类则会出现一个字段名为 this$0 的外部类对象会导致无限递归这里要忽略掉如果使用静态内部类则不会出现这个问题
// 和规避 Kotlin 自动生成的伴生对象https://github.com/getActivity/EasyHttp/issues/15
if (key.matches("this\\$\\d+") || "Companion".equals(key)) {
continue;
}
}
// 如果这个字段需要忽略则进行忽略
if (field.isAnnotationPresent(HttpIgnore.class)) {
if (field.isAnnotationPresent(HttpHeader.class)) {
headers.remove(key);
} else {
params.remove(key);
}
continue;
}
// 前提是这个字段值不能为空基本数据类型有默认的值而对象默认的值为 null
if (EasyUtils.isEmpty(value)) {
// 遍历下一个字段
continue;
}
// 如果这是一个请求头参数
if (field.isAnnotationPresent(HttpHeader.class)) {
if (value instanceof Map) {
Map<?, ?> map = ((Map<?, ?>) value);
for (Object o : map.keySet()) {
if (o != null && map.get(o) != null) {
headers.put(String.valueOf(o), String.valueOf(map.get(o)));
}
}
} else {
headers.put(key, String.valueOf(value));
}
continue;
}
// 否则这就是一个普通的参数
switch (type) {
case FORM:
if (value instanceof Map) {
Map<?, ?> map = ((Map<?, ?>) value);
for (Object o : map.keySet()) {
if (o != null && map.get(o) != null) {
params.put(String.valueOf(o), map.get(o));
}
}
} else {
params.put(key, value);
}
break;
case JSON:
if (value instanceof List) {
// 如果这是一个 List 参数
params.put(key, EasyUtils.listToJsonArray(((List<?>) value)));
} else if (value instanceof Map) {
// 如果这是一个 Map 参数
params.put(key, EasyUtils.mapToJsonObject(((Map<?, ?>) value)));
} else if (EasyUtils.isBeanType(value)) {
// 如果这是一个 Bean 参数
params.put(key, EasyUtils.mapToJsonObject(EasyUtils.beanToHashMap(value)));
} else {
// 如果这是一个普通的参数
params.put(key, value);
}
break;
default:
break;
}
} catch (IllegalAccessException e) {
HttpLog.print(e);
}
}
}
/**
* 创建 okhttp 请求
*
* @param url 请求地址
* @param tag 标记
* @param params 参数
* @param headers 请求头
* @param type 参数提交方式
* @return okhttp 请求对象
*/
private Request createRequest(String url, String tag, HttpParams params, HttpHeaders headers, BodyType type) {
Request.Builder request = new Request.Builder();
if (tag != null) {
request.tag(tag);
}
// 添加请求头
if (!headers.isEmpty()) {
for (String key : headers.getNames()) {
request.addHeader(key, headers.get(key));
}
}
RequestBody body = null;
String requestUrl = "";
String httpMethod = getRequestApi().getMethod();
switch (httpMethod){
case HttpConstant.Method.GET:
HttpUrl.Builder builder = HttpUrl.get(url).newBuilder();
// 添加参数
if (!params.isEmpty()) {
for (String key : params.getNames()) {
builder.addEncodedQueryParameter(key, String.valueOf(params.get(key)));
}
}
HttpUrl link = builder.build();
requestUrl = String.valueOf(link);
request.url(link);
break;
case HttpConstant.Method.POST:
request.url(url);
requestUrl = url;
body = createBody(params, type);
break;
}
request.method(httpMethod, body);
HttpLog.print("RequestUrl", requestUrl);
HttpLog.print("RequestMethod", httpMethod);
// 打印请求头和参数的日志
if (HttpConfig.getInstance().isLogEnabled()) {
if (!headers.isEmpty() || !params.isEmpty()) {
HttpLog.print();
}
for (String key : headers.getNames()) {
HttpLog.print(key, headers.get(key));
}
if (!headers.isEmpty() && !params.isEmpty()) {
HttpLog.print();
}
for (String key : params.getNames()) {
HttpLog.print(key, String.valueOf(params.get(key)));
}
if (!headers.isEmpty() || !params.isEmpty()) {
HttpLog.print();
}
}
return getRequestHandler().requestStart(getRequestApi(), request.build());
}
/**
* 组装 RequestBody 对象
*/
private RequestBody createBody(HttpParams params, BodyType type) {
RequestBody body;
if (params.isMultipart() && !params.isEmpty()) {
MultipartBody.Builder builder = new MultipartBody.Builder();
builder.setType(MultipartBody.FORM);
for (String key : params.getNames()) {
Object object = params.get(key);
// 如果这是一个 File 对象
if (object instanceof File) {
MultipartBody.Part part = EasyUtils.createPart(key, (File) object);
if (part != null) {
builder.addPart(part);
}
continue;
}
// 如果这是一个 InputStream 对象
if (object instanceof InputStream) {
MultipartBody.Part part = EasyUtils.createPart(key, (InputStream) object);
if (part != null) {
builder.addPart(part);
}
continue;
}
// 如果这是一个自定义的 MultipartBody.Part 对象
if (object instanceof MultipartBody.Part) {
builder.addPart((MultipartBody.Part) object);
continue;
}
// 如果这是一个自定义的 RequestBody 对象
if (object instanceof RequestBody) {
builder.addFormDataPart(key, null, (RequestBody) object);
continue;
}
// 如果这是一个普通参数
builder.addFormDataPart(key, String.valueOf(object));
}
try {
body = builder.build();
} catch (IllegalStateException ignored) {
// 如果参数为空则会抛出异常Multipart body must have at least one part.
body = new FormBody.Builder().build();
}
} else if (type == BodyType.JSON) {
body = new JsonBody(params.getParams());
} else {
FormBody.Builder builder = new FormBody.Builder();
if (!params.isEmpty()) {
for (String key : params.getNames()) {
builder.add(key, String.valueOf(params.get(key)));
}
}
body = builder.build();
}
return body;
}
/**
* 根据 TAG 取消请求任务
*/
public static void cancel(Object tag) {
if (tag == null) {
return;
}
OkHttpClient client = HttpConfig.getInstance().getClient();
// 清除排队等候的任务
for (Call call : client.dispatcher().queuedCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
// 清除正在执行的任务
for (Call call : client.dispatcher().runningCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
}
/**
* 清除所有请求任务
*/
public static void cancel() {
OkHttpClient client = HttpConfig.getInstance().getClient();
// 清除排队等候的任务
for (Call call : client.dispatcher().queuedCalls()) {
call.cancel();
}
// 清除正在执行的任务
for (Call call : client.dispatcher().runningCalls()) {
call.cancel();
}
}
/**
* 无参数请求
*/
public static class UrlRequest<Result> extends EasyHttp<Void, Result> {
@Override
public UrlRequest<Result> api(RequestApi<Void, Result> api) {
super.api(api);
return this;
}
}
/**
* 路径参数请求
*/
public static class RestfulRequest<Result> extends EasyHttp<Void, Result> {
@Override
public RestfulRequest<Result> api(RequestApi<Void, Result> api) {
super.api(api);
return this;
}
/**
* @param params 路径参数
*/
public RestfulRequest<Result> pathParam(Object... params) {
super.path(MessageFormat.format(getPath(), params));
return this;
}
}
/**
*
* 带参数请求
*
* @param <Param> 参数类型
* @param <Result> 返回结果类型
*/
public static class ParamRequest<Param, Result> extends EasyHttp<Param, Result> {
@Override
public ParamRequest<Param, Result> api(RequestApi<Param, Result> api) {
super.api(api);
return this;
}
@Override
public ParamRequest<Param, Result> param(Param param) {
super.param(param);
return this;
}
}
}

View File

@ -0,0 +1,326 @@
package top.octopusyan.common.http;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
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.ThreadPoolManager;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description :
* @create : 2022-4-1 16:20
*/
public class EasyUtils {
/**
* 在主线程中执行
*/
public static void post(Runnable r) {
ThreadPoolManager.getScheduledExecutorService().execute(r);
}
/**
* 延迟一段时间执行
*/
public static void postDelayed(Runnable r, long delayMillis) {
try {
ThreadPoolManager.getScheduledExecutorService().schedule(r, delayMillis, TimeUnit.MILLISECONDS).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
/**
* 判断对象或者集合是否为空
*/
public static boolean isEmpty(Object object) {
if (object == null) {
return true;
}
if (object instanceof List && ((List<?>) object).isEmpty()) {
return true;
}
return object instanceof Map && ((Map<?, ?>) object).isEmpty();
}
/**
* 判断对象是否为 Bean
*/
public static boolean isBeanType(Object object) {
if (object == null) {
return false;
}
// NumberLongIntegerShortDoubleFloatByte
// CharSequenceStringStringBuilderStringBuilder
return !(object instanceof Number || object instanceof CharSequence || object instanceof Boolean ||
object instanceof File || object instanceof InputStream || object instanceof RequestBody ||
object instanceof Character || object instanceof JSONObject || object instanceof JSONArray);
}
/**
* 判断是否包含存在流参数
*/
public static boolean isMultipart(List<Field> fields) {
for (Field field : fields) {
// 允许访问私有字段
field.setAccessible(true);
// 获取对象的类型
Class<?> clazz = field.getType();
// 获取对象上面实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
for (int i = 0; i <= interfaces.length; i++) {
Class<?> temp;
if (i == interfaces.length) {
temp = clazz;
} else {
temp = interfaces[i];
}
// 判断类型是否是 List<File>
if (List.class.equals(temp)) {
Type[] actualTypeArguments = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
if (actualTypeArguments.length == 1 && File.class.equals(actualTypeArguments[0])) {
return true;
}
}
}
do {
if (File.class.equals(clazz) || InputStream.class.equals(clazz)
|| RequestBody.class.equals(clazz) || MultipartBody.Part.class.equals(clazz)) {
return true;
}
// 获取对象的父类类型
clazz = clazz.getSuperclass();
} while (clazz != null && !Object.class.equals(clazz));
}
return false;
}
/**
* List 集合转 JsonArray 对象
*/
public static JSONArray listToJsonArray(List<?> list) {
JSONArray jsonArray = new JSONArray();
if (list == null || list.isEmpty()) {
return jsonArray;
}
for (Object value : list) {
if (isEmpty(value)) {
continue;
}
if (value instanceof List) {
jsonArray.addAll(listToJsonArray(((List<?>) value)));
} else if (value instanceof Map) {
jsonArray.add(mapToJsonObject(((Map<?, ?>) value)));
} else if (isBeanType(value)) {
jsonArray.add(mapToJsonObject(beanToHashMap(value)));
} else {
jsonArray.add(value);
}
}
return jsonArray;
}
/**
* Map 集合转成 JsonObject 对象
*/
public static JSONObject mapToJsonObject(Map<?, ?> map) {
JSONObject jsonObject = new JSONObject();
if (map == null || map.isEmpty()) {
return jsonObject;
}
Set<?> keySet = map.keySet();
for (Object key : keySet) {
Object value = map.get(key);
if (isEmpty(value)) {
continue;
}
try {
if (value instanceof List) {
jsonObject.put(String.valueOf(key), listToJsonArray(((List<?>) value)));
} else if (value instanceof Map) {
jsonObject.put(String.valueOf(key), mapToJsonObject(((Map<?, ?>) value)));
} else if (isBeanType(value)) {
jsonObject.put(String.valueOf(key), mapToJsonObject(beanToHashMap(value)));
} else {
jsonObject.put(String.valueOf(key), value);
}
} catch (JSONException e) {
HttpLog.print(e);
}
}
return jsonObject;
}
/**
* Bean 类转成 HashMap 对象
*/
public static HashMap<String, Object> beanToHashMap(Object object) {
if (object == null) {
return null;
}
Field[] fields = object.getClass().getDeclaredFields();
HashMap<String, Object> data = new HashMap<>(fields.length);
for (Field field : fields) {
// 允许访问私有字段
field.setAccessible(true);
try {
// 获取字段的对象
Object value = field.get(object);
// 前提是这个字段值不能为空基本数据类型有默认的值而对象默认的值为 null
// 又或者这个字段需要忽略则进行忽略
if (isEmpty(value) || field.isAnnotationPresent(HttpIgnore.class)) {
continue;
}
// 获取字段的名称
String key;
if (field.isAnnotationPresent(HttpRename.class)) {
key = field.getAnnotation(HttpRename.class).value();
} else {
key = field.getName();
// 如果是内部类则会出现一个字段名为 this$0 的外部类对象会导致无限递归这里要忽略掉如果使用静态内部类则不会出现这个问题
// 和规避 Kotlin 自动生成的伴生对象https://github.com/getActivity/EasyHttp/issues/15
if (key.matches("this\\$\\d+") || "Companion".equals(key)) {
continue;
}
}
if (value instanceof List) {
data.put(key, listToJsonArray(((List<?>) value)));
} else if (value instanceof Map) {
data.put(key, mapToJsonObject(((Map<?, ?>) value)));
} else if (isBeanType(value)) {
data.put(key, beanToHashMap(value));
} else {
data.put(key, value);
}
} catch (IllegalAccessException e) {
HttpLog.print(e);
}
}
return data;
}
/**
* 获取对象反射类型
*/
public static Type getReflectType(Object object) {
if (object == null) {
return Void.class;
}
Type[] types = object.getClass().getGenericInterfaces();
if (types.length > 0) {
// 如果这个监听对象是直接实现了接口
return ((ParameterizedType) types[0]).getActualTypeArguments()[0];
}
// 如果这个监听对象是通过类继承
return ((ParameterizedType) object.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
/**
* 根据文件名获取 MIME 类型
*/
public static MediaType guessMimeType(String fileName) {
FileNameMap fileNameMap = URLConnection.getFileNameMap();
// 解决文件名中含有#号异常的问题
fileName = fileName.replace("#", "");
String contentType = fileNameMap.getContentTypeFor(fileName);
if (contentType == null) {
return ContentType.STREAM;
}
MediaType type = MediaType.parse(contentType);
if (type == null) {
type = ContentType.STREAM;
}
return type;
}
/**
* 根据 InputStream 对象创建一个流媒体
*/
public static MultipartBody.Part createPart(String key, InputStream inputStream) {
try {
return MultipartBody.Part.createFormData(key, null, new UpdateBody(inputStream, key));
} catch (IOException e) {
HttpLog.print(e);
return null;
}
}
/**
* 根据 File 对象创建一个流媒体
*/
public static MultipartBody.Part createPart(String key, File file) {
try {
// 文件名必须不能带中文所以这里要编码
return MultipartBody.Part.createFormData(key, encodeString(file.getName()), new UpdateBody(file));
} catch (FileNotFoundException e) {
HttpLog.print("文件不存在,将被忽略上传:" + key + " = " + file.getPath());
return null;
}
}
/**
* 关闭流
*/
public static void closeStream(Closeable closeable) {
if (closeable == null) {
return;
}
try {
closeable.close();
} catch (Exception e) {
HttpLog.print(e);
}
}
/**
* 字符串编码
*/
public static String encodeString(String text) {
if (StringUtils.isEmpty(text)) {
return "";
}
return URLEncoder.encode(text);
}
}

View File

@ -0,0 +1,218 @@
package top.octopusyan.common.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 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
* @email : octopus_yan@foxmail.com
* @description : http 设置
* @create : 2022-4-1 15:44
*/
public class HttpConfig {
private static volatile HttpConfig sConfig;
/** 服务器地址 */
private String serverPath;
/** 请求处理器 */
private IRequestHandler mHandler;
/** 日志打印策略 */
private ILogStrategy mLogStrategy;
/** OkHttp 客户端 */
private OkHttpClient mClient;
/** 通用参数 */
private HashMap<String, Object> mParams;
/** 通用请求头 */
private HashMap<String, String> mHeaders;
/** 日志开关 */
private boolean mLogEnabled = true;
/** 日志 TAG */
private String mLogTag = "EasyHttp";
/** 重试次数 */
private int mRetryCount;
/** 重试时间 */
private long mRetryTime = 2000;
private HttpConfig(OkHttpClient client) {
mClient = client;
mParams = new HashMap<String, Object>();
mHeaders = new HashMap<>();
}
private HttpConfig() {
mParams = new HashMap<String, Object>();
mHeaders = new HashMap<>();
initHttpClient();
}
public static HttpConfig getInstance() {
if (sConfig == null) {
// 当前没有初始化配置
throw new IllegalStateException("You haven't initialized the configuration yet");
}
return sConfig;
}
private static void setInstance(HttpConfig config) {
sConfig = config;
}
public static HttpConfig with(OkHttpClient client) {
return new HttpConfig(client);
}
/**
* 无参初始化 默认配置生成 okhttpclient
*/
public static HttpConfig init() {
return new HttpConfig();
}
public HttpConfig setParams(HashMap<String, Object> params) {
if (params == null) {
params = new HashMap<String, Object>();
}
mParams = params;
return this;
}
public HttpConfig setHeaders(HashMap<String, String> headers) {
if (headers == null) {
headers = new HashMap<>();
}
mHeaders = headers;
return this;
}
public HashMap<String, Object> getParams() {
return mParams;
}
public HashMap<String, String> getHeaders() {
return mHeaders;
}
public HttpConfig addHeader(String key, String value) {
if (key != null && value != null) {
mHeaders.put(key, value);
}
return this;
}
public HttpConfig addParam(String key, String value) {
if (key != null && value != null) {
mParams.put(key, value);
}
return this;
}
public OkHttpClient getClient() {
return mClient;
}
public HttpConfig setLogStrategy(ILogStrategy strategy) {
mLogStrategy = strategy;
return this;
}
public HttpConfig setLogEnabled(boolean enabled) {
mLogEnabled = enabled;
return this;
}
public HttpConfig setLogTag(String tag) {
mLogTag = tag;
return this;
}
public HttpConfig setHandler(IRequestHandler mHandler) {
this.mHandler = mHandler;
return this;
}
public HttpConfig setRetryCount(int count) {
if (count < 0) {
// 重试次数必须大于等于 0
throw new IllegalArgumentException("The number of retries must be greater than 0");
}
mRetryCount = count;
return this;
}
public HttpConfig serverPath(String serverPath) {
this.serverPath = serverPath;
return this;
}
public HttpConfig setRetryTime(long time) {
if (time < 0) {
// 重试时间必须大于等于 0 毫秒
throw new IllegalArgumentException("The retry time must be greater than 0");
}
mRetryTime = time;
return this;
}
public ILogStrategy getLogStrategy() {
return mLogStrategy;
}
public boolean isLogEnabled() {
return mLogEnabled && mLogStrategy != null;
}
public String getLogTag() {
return mLogTag;
}
public int getRetryCount() {
return mRetryCount;
}
public long getRetryTime() {
return mRetryTime;
}
public String getServerPath() {
return serverPath;
}
public IRequestHandler getHandler() {
return mHandler;
}
public void into() {
if (mClient == null)
throw new IllegalArgumentException("The OkHttp client object cannot be empty");
try {
// 校验主机和路径的 url 是否合法
new URL(serverPath);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("The configured host path url address is not correct");
}
HttpConfig.setInstance(this);
}
private void initHttpClient() {
;
}
}

View File

@ -0,0 +1,61 @@
package top.octopusyan.common.http;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description :
* @create : 2022-4-1 15:51
*/
public class HttpLog {
/**
* 打印分割线
*/
public static void print() {
print("----------------------------------------");
}
/**
* 打印日志
*/
public static void print(String log) {
if (HttpConfig.getInstance().isLogEnabled()) {
HttpConfig.getInstance().getLogStrategy().print(log);
}
}
/**
* 打印 Json
*/
public static void json(String json) {
if (HttpConfig.getInstance().isLogEnabled()) {
HttpConfig.getInstance().getLogStrategy().json(json);
}
}
/**
* 打印键值对
*/
public static void print(String key, String value) {
if (HttpConfig.getInstance().isLogEnabled()) {
HttpConfig.getInstance().getLogStrategy().print(key, value);
}
}
/**
* 打印异常
*/
public static void print(Throwable throwable) {
if (HttpConfig.getInstance().isLogEnabled()) {
HttpConfig.getInstance().getLogStrategy().print(throwable);
}
}
/**
* 打印堆栈
*/
public static void print(StackTraceElement[] stackTrace) {
if (HttpConfig.getInstance().isLogEnabled()) {
HttpConfig.getInstance().getLogStrategy().print(stackTrace);
}
}
}

View File

@ -0,0 +1,14 @@
package top.octopusyan.common.http.annotation;
import java.lang.annotation.*;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 请求头注解标记这个字段是一个请求头的参数
* @create : 2022-4-1 22:28
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface HttpHeader {}

View File

@ -0,0 +1,14 @@
package top.octopusyan.common.http.annotation;
import java.lang.annotation.*;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 忽略注解这个参数或者请求头将不会被发送到后台
* @create : 2022-4-1 22:27
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface HttpIgnore {}

View File

@ -0,0 +1,20 @@
package top.octopusyan.common.http.annotation;
import java.lang.annotation.*;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 重命名注解
* @create : 2022-4-1 22:27
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface HttpRename {
/**
* 默认以字段的名称作为参数名使用此注解可修改
*/
String value();
}

View File

@ -0,0 +1,19 @@
package top.octopusyan.common.http.api;
import top.octopusyan.common.http.config.BodyType;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 无参数请求接口
* @create : 2022-4-1 21:41
*/
public class NotParamApi<Result> extends RequestApi<Void, Result> {
public NotParamApi(String api, String method) {
super(api, method);
}
public NotParamApi(String api, String method, BodyType bodyType) {
super(api, method, bodyType);
}
}

View File

@ -0,0 +1,19 @@
package top.octopusyan.common.http.api;
import top.octopusyan.common.http.config.BodyType;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 带参数请求接口
* @create : 2022-4-1 21:38
*/
public class ParamApi<Param, Result> extends RequestApi<Param, Result> {
public ParamApi(String method, String path) {
super(method, path);
}
public ParamApi(String api, String method, BodyType bodyType) {
super(api, method, bodyType);
}
}

View File

@ -0,0 +1,19 @@
package top.octopusyan.common.http.api;
import top.octopusyan.common.http.config.BodyType;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : restful 带路由参数请求接口
* @create : 2022-4-1 21:39
*/
public class PathParamApi<Result> extends RequestApi<Void, Result> {
public PathParamApi(String method, String path) {
super(method, path);
}
public PathParamApi(String api, String method, BodyType bodyType) {
super(api, method, bodyType);
}
}

View File

@ -0,0 +1,34 @@
package top.octopusyan.common.http.api;
import lombok.Data;
import top.octopusyan.common.http.config.BodyType;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 请求基类
* @create : 2022-4-1 16:07
*/
@Data
public class RequestApi<Param, Result> {
/** 接口地址 <p> 必须以<code>/<code/> 开头*/
String api;
/** 请求方式 */
String method;
/** 参数提交方式 <p> 默认JSON*/
BodyType bodyType = BodyType.JSON;
public RequestApi(String api, String method) {
this.api = api;
this.method = method;
}
public RequestApi(String api, String method, BodyType bodyType) {
this.api = api;
this.method = method;
this.bodyType = bodyType;
}
}

View File

@ -0,0 +1,72 @@
package top.octopusyan.common.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 java.io.IOException;
import java.net.SocketTimeoutException;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 接口回调基类
* @create : 2022-4-1 15:42
*/
public abstract class BaseCallback implements Callback {
/** 请求任务对象 */
private final CallProxy mCall;
/** 当前重试次数 */
private int mRetryCount;
public BaseCallback(CallProxy call) {
mCall = call;
}
protected CallProxy getCall() {
return mCall;
}
@Override
public void onResponse(Call call, Response response) {
try {
// 收到响应
onResponse(response);
} catch (Exception e) {
// 回调失败
onFailure(e);
} finally {
// 关闭响应
response.close();
}
}
@Override
public void onFailure(Call call, IOException e) {
// 服务器请求超时重试
if (e instanceof SocketTimeoutException && mRetryCount < HttpConfig.getInstance().getRetryCount()) {
// 设置延迟 N 秒后重试该请求
EasyUtils.postDelayed(() -> {
mRetryCount++;
Call newCall = call.clone();
mCall.setCall(newCall);
newCall.enqueue(BaseCallback.this);
HttpLog.print("请求超时,正在延迟重试,重试次数:" + mRetryCount + "/" + HttpConfig.getInstance().getRetryCount());
}, HttpConfig.getInstance().getRetryTime());
return;
}
onFailure(e);
}
protected abstract void onResponse(Response response) throws Exception;
protected abstract void onFailure(Exception e);
}

View File

@ -0,0 +1,61 @@
package top.octopusyan.common.http.callback;
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;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 正常接口回调
* @create : 2022-4-1 16:08
*/
public final class NormalCallback extends BaseCallback {
private final OnHttpListener mListener;
private final RequestApi mRequestApi;
private final IRequestHandler mRequestHandler;
public NormalCallback(CallProxy call, RequestApi api, IRequestHandler handler, OnHttpListener listener) {
super(call);
mRequestApi = api;
mListener = listener;
mRequestHandler = handler;
EasyUtils.post(() -> {
if (mListener == null) {
return;
}
mListener.onStart(call);
});
}
@SuppressWarnings("unchecked")
@Override
protected void onResponse(Response response) throws Exception {
// 打印请求耗时时间
HttpLog.print("RequestConsuming" + (response.receivedResponseAtMillis() - response.sentRequestAtMillis()) + " ms");
final Object result = mRequestHandler.requestSucceed(mRequestApi, response, EasyUtils.getReflectType(mListener));
EasyUtils.post(() -> {
if (mListener == null) {
return;
}
mListener.onSucceed(result);
mListener.onEnd(getCall());
});
}
@Override
protected void onFailure(Exception e) {
final Exception exception = mRequestHandler.requestFail(mRequestApi, e);
HttpLog.print(exception);
EasyUtils.post(() -> {
mListener.onFail(exception);
mListener.onEnd(getCall());
});
}
}

View File

@ -0,0 +1,20 @@
package top.octopusyan.common.http.config;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 参数提交方式
* @create : 2022-4-1 16:48
*/
public enum BodyType {
/**
* 表单提交
*/
FORM,
/**
* JSON 提交
*/
JSON
}

View File

@ -0,0 +1,15 @@
package top.octopusyan.common.http.config;
import okhttp3.MediaType;
public final class ContentType {
/** 字节流 */
public static final MediaType STREAM = MediaType.parse("application/octet-stream");
/** Json */
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
/** 纯文本 */
public static final MediaType TEXT = MediaType.parse("text/plain; charset=utf-8");
}

View File

@ -0,0 +1,21 @@
package top.octopusyan.common.http.config;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : http 常量定义
* @create : 2022-4-1 22:06
*/
public class HttpConstant {
public static class Method{
public static final String GET = "GET";
public static final String POST = "POST";
}
public static class ResultKey{
public static final String DATA = "data";
public static final String CODE = "code";
public static final String MSG = "msg";
}
}

View File

@ -0,0 +1,109 @@
package top.octopusyan.common.http.config;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 日志打印策略
* @create : 2022-4-1 15:52
*/
public interface ILogStrategy {
/**
* 打印分割线
*/
default void print() {
print("--------------------");
}
/**
* 打印日志
*/
void print(String log);
/**
* 打印 Json
*/
void json(String json);
/**
* 打印键值对
*/
void print(String key, String value);
/**
* 打印异常
*/
void print(Throwable throwable);
/**
* 打印堆栈
*/
void print(StackTraceElement[] stackTrace);
/**
* 将字符串格式化成 JSON 格式
*/
static String formatJson(String json) {
if (json == null) {
return "";
}
// 计数tab的个数
int tabNum = 0;
StringBuilder builder = new StringBuilder();
int length = json.length();
char last = 0;
for (int i = 0; i < length; i++) {
char c = json.charAt(i);
if (c == '{') {
tabNum++;
builder.append(c).append("\n")
.append(getSpaceOrTab(tabNum));
} else if (c == '}') {
tabNum--;
builder.append("\n")
.append(getSpaceOrTab(tabNum))
.append(c);
} else if (c == ',') {
builder.append(c).append("\n")
.append(getSpaceOrTab(tabNum));
} else if (c == ':') {
if (i > 0 && json.charAt(i - 1) == '"') {
builder.append(c).append(" ");
} else {
builder.append(c);
}
} else if (c == '[') {
tabNum++;
char next = json.charAt(i + 1);
if (next == ']') {
builder.append(c);
} else {
builder.append(c).append("\n")
.append(getSpaceOrTab(tabNum));
}
} else if (c == ']') {
tabNum--;
if (last == '[') {
builder.append(c);
} else {
builder.append("\n").append(getSpaceOrTab(tabNum)).append(c);
}
} else {
builder.append(c);
}
last = c;
}
return builder.toString();
}
/**
* 创建对应数量的制表符
*/
static String getSpaceOrTab(int tabNum) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < tabNum; i++) {
sb.append('\t');
}
return sb.toString();
}
}

View File

@ -0,0 +1,85 @@
package top.octopusyan.common.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;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 网络请求日志打印默认实现
* @create : 2022-4-2 00:13
*/
public final class LogStrategy implements ILogStrategy {
public static String TAG = null;
public static Logger logger = null;
@Override
public void print(String log) {
String logTag = HttpConfig.getInstance().getLogTag();
if(logger == null || !logTag.equals(TAG)){
logger = LoggerFactory.getLogger(logTag);
TAG = logTag;
}
logger.info(log != null ? log : "null");
}
@Override
public void json(String json) {
String text = ILogStrategy.formatJson(json);
if (StringUtils.isEmpty(text)) {
return;
}
// 打印 Json 数据最好换一行再打印会好看一点
text = " \n" + text;
int segmentSize = 3 * 1024;
long length = text.length();
if (length <= segmentSize) {
// 长度小于等于限制直接打印
print(text);
return;
}
// 循环分段打印日志
while (text.length() > segmentSize) {
String logContent = text.substring(0, segmentSize);
text = text.replace(logContent, "");
print(logContent);
}
// 打印剩余日志
print(text);
}
@Override
public void print(String key, String value) {
print(key + " = " + value);
}
@Override
public void print(Throwable throwable) {
logger.info(HttpConfig.getInstance().getLogTag(), throwable.getMessage(), throwable);
}
@Override
public void print(StackTraceElement[] stackTrace) {
for (StackTraceElement element : stackTrace) {
// 获取代码行数
int lineNumber = element.getLineNumber();
// 获取类的全路径
String className = element.getClassName();
if (lineNumber <= 0 || className.startsWith(EasyHttp.class.getPackage().getName())) {
continue;
}
print("RequestCode = (" + element.getFileName() + ":" + lineNumber + ") ");
break;
}
}
}

View File

@ -0,0 +1,15 @@
package top.octopusyan.common.http.exception;
/**
* 请求取消异常
*/
public final class CancelException extends HttpException {
public CancelException(String message) {
super(message);
}
public CancelException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,15 @@
package top.octopusyan.common.http.exception;
/**
* 数据解析异常
*/
public final class DataException extends HttpException {
public DataException(String message) {
super(message);
}
public DataException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,27 @@
package top.octopusyan.common.http.exception;
/**
* 网络请求异常
*/
public class HttpException extends Exception {
private String mMessage;
public HttpException(String message) {
super(message);
mMessage = message;
}
public HttpException(String message, Throwable cause) {
super(message, cause);
mMessage = message;
}
/**
* 获取错误信息
*/
@Override
public String getMessage() {
return mMessage;
}
}

View File

@ -0,0 +1,15 @@
package top.octopusyan.common.http.exception;
/**
* 网络连接异常
*/
public final class NetworkException extends HttpException {
public NetworkException(String message) {
super(message);
}
public NetworkException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,25 @@
package top.octopusyan.common.http.exception;
import okhttp3.Response;
/**
* 服务器响应异常
*/
public final class ResponseException extends HttpException {
private final Response mResponse;
public ResponseException(String message, Response response) {
super(message);
mResponse = response;
}
public ResponseException(String message, Throwable cause, Response response) {
super(message, cause);
mResponse = response;
}
public Response getResponse() {
return mResponse;
}
}

View File

@ -0,0 +1,24 @@
package top.octopusyan.common.http.exception;
/**
* 返回结果异常
*/
public final class ResultException extends HttpException {
private final Object mData;
public ResultException(String message, Object data) {
super(message);
mData = data;
}
public ResultException(String message, Throwable cause, Object data) {
super(message, cause);
mData = data;
}
@SuppressWarnings("unchecked")
public <T extends Object> T getData() {
return (T) mData;
}
}

View File

@ -0,0 +1,15 @@
package top.octopusyan.common.http.exception;
/**
* 服务器连接异常
*/
public final class ServerException extends HttpException {
public ServerException(String message) {
super(message);
}
public ServerException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,15 @@
package top.octopusyan.common.http.exception;
/**
* 服务器超时异常
*/
public final class TimeoutException extends HttpException {
public TimeoutException(String message) {
super(message);
}
public TimeoutException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,15 @@
package top.octopusyan.common.http.exception;
/**
* Token 失效异常
*/
public final class TokenException extends HttpException {
public TokenException(String message) {
super(message);
}
public TokenException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,32 @@
package top.octopusyan.common.http.listener;
import okhttp3.Call;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 网络请求监听
* @create : 2022-4-1 15:17
*/
public interface OnHttpListener<T> {
/**
* 请求开始
*/
default void onStart(Call call) {}
/**
* 请求成功
*/
void onSucceed(T result);
/**
* 请求出错
*/
default void onFail(Exception e){};
/**
* 请求结束
*/
default void onEnd(Call call) {}
}

View File

@ -0,0 +1,92 @@
package top.octopusyan.common.http.model;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Request;
import okhttp3.Response;
import okio.Timeout;
import java.io.IOException;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 请求对象代理
* @create : 2022-4-1 15:48
*/
public final class CallProxy implements Call {
private Call mCall;
public CallProxy(Call call) {
mCall = call;
}
public void setCall(Call call) {
mCall = call;
}
@Override
public Request request() {
if (mCall == null) {
return null;
}
return mCall.request();
}
@Override
public Response execute() throws IOException {
if (mCall == null) {
return null;
}
return mCall.execute();
}
@Override
public void enqueue(Callback responseCallback) {
if (mCall == null) {
return;
}
mCall.enqueue(responseCallback);
}
@Override
public void cancel() {
if (mCall == null) {
return;
}
mCall.cancel();
}
@Override
public boolean isExecuted() {
if (mCall == null) {
return false;
}
return mCall.isExecuted();
}
@Override
public boolean isCanceled() {
if (mCall == null) {
return false;
}
return mCall.isCanceled();
}
@Override
public Timeout timeout() {
if (mCall == null) {
return null;
}
return mCall.timeout();
}
@Override
public Call clone() {
if (mCall == null) {
return null;
}
return mCall.clone();
}
}

View File

@ -0,0 +1,50 @@
package top.octopusyan.common.http.model;
import top.octopusyan.common.http.HttpConfig;
import java.util.HashMap;
import java.util.Set;
public final class HttpHeaders {
/** 请求头存放集合 */
private HashMap<String, String> mHeaders = HttpConfig.getInstance().getHeaders();
public void put(String key, String value) {
if (key == null || value == null) {
return;
}
if (mHeaders == HttpConfig.getInstance().getHeaders()) {
mHeaders = new HashMap<>(mHeaders);
}
mHeaders.put(key, value);
}
public void remove(String key) {
if (key == null) {
return;
}
if (mHeaders == HttpConfig.getInstance().getHeaders()) {
mHeaders = new HashMap<>(mHeaders);
}
mHeaders.remove(key);
}
public String get(String key) {
return mHeaders.get(key);
}
public boolean isEmpty() {
return mHeaders == null || mHeaders.isEmpty();
}
public Set<String> getNames() {
return mHeaders.keySet();
}
public HashMap<String, String> getHeaders() {
return mHeaders;
}
}

View File

@ -0,0 +1,61 @@
package top.octopusyan.common.http.model;
import top.octopusyan.common.http.HttpConfig;
import java.util.HashMap;
import java.util.Set;
public final class HttpParams {
/** 请求参数存放集合 */
private HashMap<String, Object> mParams = HttpConfig.getInstance().getParams();
/** 是否有流参数 */
private boolean mMultipart;
public void put(String key, Object value) {
if (key == null || value == null) {
return;
}
if (mParams == HttpConfig.getInstance().getParams()) {
mParams = new HashMap<>(mParams);
}
mParams.put(key, value);
}
public void remove(String key) {
if (key == null) {
return;
}
if (mParams == HttpConfig.getInstance().getParams()) {
mParams = new HashMap<>(mParams);
}
mParams.remove(key);
}
public Object get(String key) {
return mParams.get(key);
}
public boolean isEmpty() {
return mParams == null || mParams.isEmpty();
}
public Set<String> getNames() {
return mParams.keySet();
}
public HashMap<String, Object> getParams() {
return mParams;
}
public boolean isMultipart() {
return mMultipart;
}
public void setMultipart(boolean multipart) {
mMultipart = multipart;
}
}

View File

@ -0,0 +1,74 @@
package top.octopusyan.common.http.model;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.NonNull;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;
import top.octopusyan.common.http.config.ContentType;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class JsonBody extends RequestBody {
/** Json 数据 */
private final String mJson;
/** 字节数组 */
private final byte[] mBytes;
public JsonBody(Map<?, ?> map) {
this(JSON.parseObject(JSON.toJSONString(map)));
}
public JsonBody(JSONObject jsonObject) {
mJson = jsonObject.toString();
mBytes = mJson.getBytes();
}
public JsonBody(List<?> list) {
this(JSON.parseArray(JSON.toJSONString(list)));
}
public JsonBody(JSONArray jsonArray) {
mJson = jsonArray.toString();
mBytes = mJson.getBytes();
}
public JsonBody(String json) {
mJson = json;
mBytes = mJson.getBytes();
}
@Override
public MediaType contentType() {
return ContentType.JSON;
}
@Override
public long contentLength() {
// 需要注意这里需要用字节数组的长度来计算
return mBytes.length;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.write(mBytes, 0, mBytes.length);
}
/**
* 获取 Json 字符串
*/
public String getJson() {
return mJson;
}
@NonNull
@Override
public String toString() {
return mJson;
}
}

View File

@ -0,0 +1,10 @@
package top.octopusyan.common.http.model;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 数据解析模型类
* @create : 2022-4-1 17:06
*/
public abstract class ResponseClass<T> {
}

View File

@ -0,0 +1,67 @@
package top.octopusyan.common.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 java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class UpdateBody extends RequestBody {
/** 上传源 */
private final Source mSource;
/** 内容类型 */
private final MediaType mMediaType;
/** 内容名称 */
private final String mName;
/** 内容大小 */
private final long mLength;
public UpdateBody(File file) throws FileNotFoundException {
this(Okio.source(file), EasyUtils.guessMimeType(file.getName()), file.getName(), file.length());
}
public UpdateBody(InputStream inputStream, String name) throws IOException {
this(Okio.source(inputStream), ContentType.STREAM, name, inputStream.available());
}
public UpdateBody(Source source, MediaType type, String name, long length) {
mSource = source;
mMediaType = type;
mName = name;
mLength = length;
}
@Override
public MediaType contentType() {
return mMediaType;
}
@Override
public long contentLength() {
return mLength;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
try {
sink.writeAll(mSource);
} finally {
EasyUtils.closeStream(mSource);
}
}
public String getName() {
return mName;
}
}

View File

@ -0,0 +1,39 @@
package top.octopusyan.common.http.request;
import java.lang.reflect.Type;
import okhttp3.Request;
import okhttp3.Response;
import top.octopusyan.common.http.api.RequestApi;
public interface IRequestHandler {
/**
* 请求开始
*
* @param request 请求对象
* @return 返回新的请求对象
*/
default <Param, Result> Request requestStart(RequestApi<Param, Result> api, Request request) {
return request;
}
/**
* 请求成功时回调
*
* @param response 响应对象
* @param type 解析类型
* @return 返回结果
*
* @throws Exception 如果抛出则回调失败
*/
<Param, Result> Object requestSucceed(RequestApi<Param, Result> api, Response response, Type type) throws Exception;
/**
* 请求失败
*
* @param e 错误对象
* @return 错误对象
*/
<Param, Result> Exception requestFail(RequestApi<Param, Result> api, Exception e);
}

View File

@ -0,0 +1,176 @@
package top.octopusyan.common.http.request;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
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 java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
/**
* @author octopus yan
* @email octopus_yan@foxmail.com
* @Description 请求处理类
* @createTime 2021-07-23 11:55:26
*/
public final class RequestHandler implements IRequestHandler {
@Override
public Request requestStart(RequestApi api, Request request) {
return IRequestHandler.super.requestStart(api, request);
}
@Override
public Object requestSucceed(RequestApi api, Response response, Type type) throws Exception {
if (Response.class.equals(type)) {
return response;
}
if (!response.isSuccessful()) {
// 返回响应异常
throw new ResponseException("服务器响应异常responseCode" + response.code() + "message" + response.message(), response);
}
if (Headers.class.equals(type)) {
return response.headers();
}
ResponseBody body = response.body();
if (body == null) {
return null;
}
if (InputStream.class.equals(type)) {
return body.byteStream();
}
String text;
try {
text = body.string();
} catch (IOException e) {
// 返回结果读取异常
throw new DataException("数据解析异常", e);
}
// 打印这个 Json 或者文本
HttpLog.json(text);
if (String.class.equals(type)) {
return text;
}
if (JSONObject.class.equals(type)) {
try {
// 如果这是一个 JSONObject 对象
// return new JSONObject(text);
return JSON.parseObject(text);
} catch (JSONException e) {
throw new DataException("数据解析异常", e);
}
}
if (JSONArray.class.equals(type)) {
try {
// 如果这是一个 JSONArray 对象
// return new JSONArray(text);
return JSON.parseArray(text);
} catch (JSONException e) {
throw new DataException("数据解析异常", e);
}
}
final Object result;
try {
// result = GsonFactory.getSingletonGson().fromJson(text, type);
JSONObject jsonObject = JSONObject.parseObject(text);
result = jsonObject.toJavaObject(type);
// result = new Gson().fromJson(text, type);
} catch (JSONException e) {
// 返回结果读取异常
throw new DataException("数据解析异常", e);
}
if (result instanceof BaseRest) {
BaseRest model = (BaseRest) result;
if (model.isRequestSucceed()) {
// 代表执行成功
return result;
}
if (model.isTokenFailure()) {
// 代表登录失效需要重新登录
throw new TokenException("登录失效,请重新登录");
}
// 代表执行失败
throw new ResultException(response.request().body() + "\r\n" + model.getMsg(), model);
}
return result;
}
@Override
public Exception requestFail(RequestApi api, Exception e) {
// 判断这个异常是不是自己抛的
if (e instanceof HttpException) {
if (e instanceof TokenException) {
// TODO 登录信息失效
}
return e;
}
if (e instanceof SocketTimeoutException) {
return new TimeoutException("服务器请求超时,请稍后再试", e);
}
if (e instanceof UnknownHostException) {
// 利用百度服务器地址判断网络是否连接
if (!isReachable("14.215.177.38")) {
// 没有连接就是网络异常
return new NetworkException("请求失败,请检查网络设置", e);
}
// 有连接就是服务器的问题
return new ServerException("服务器连接异常,请稍后再试", e);
}
if (e instanceof IOException) {
//e = new CancelException(context.getString(R.string.http_request_cancel), e);
return new CancelException("", e);
}
return new HttpException(e.getMessage(), e);
}
/**
* 传入需要连接的IP返回是否连接成功
*
* @param remoteInetAddr
* @return
*/
public static boolean isReachable(String remoteInetAddr) {
boolean reachable = false;
try {
InetAddress address = InetAddress.getByName(remoteInetAddr);
reachable = address.isReachable(5000);
} catch (Exception e) {
e.printStackTrace();
}
return reachable;
}
}

View File

@ -0,0 +1,46 @@
package top.octopusyan.common.http.response;
import java.io.Serializable;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description :
* @create : 2022-4-1 15:24
*/
public class BaseRest implements Serializable {
private String msg;
private int code;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
@Override
public String toString() {
return "BaseRest{" +
"msg='" + msg + '\'' +
", code=" + code +
'}';
}
public boolean isRequestSucceed() {
return code == 0 || code == 200;
}
public boolean isTokenFailure() {
return code == 401;
}
}

View File

@ -0,0 +1,29 @@
package top.octopusyan.common.http.ssl;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : Https 配置类
* @create : 2022-4-1 23:18
*/
public final class HttpSslConfig {
private final SSLSocketFactory mSSLSocketFactory;
private final X509TrustManager mTrustManager;
HttpSslConfig(SSLSocketFactory factory, X509TrustManager manager) {
mSSLSocketFactory = factory;
mTrustManager = manager;
}
public SSLSocketFactory getSslSocketFactory() {
return mSSLSocketFactory;
}
public X509TrustManager getTrustManager() {
return mTrustManager;
}
}

View File

@ -0,0 +1,169 @@
package top.octopusyan.common.http.ssl;
import top.octopusyan.common.http.HttpLog;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : Https 证书校验工厂
* @create : 2022-4-1 23:19
*/
public final class HttpSslFactory {
/**
* 生成信任任何证书的配置
*/
public static HttpSslConfig generateSslConfig() {
return generateSslConfigBase(null, null, null);
}
/**
* https 单向认证
*/
public static HttpSslConfig generateSslConfig(X509TrustManager trustManager) {
return generateSslConfigBase(trustManager, null, null);
}
/**
* https 单向认证
*/
public static HttpSslConfig generateSslConfig(InputStream... certificates) {
return generateSslConfigBase(null, null, null, certificates);
}
/**
* https 双向认证
*/
public static HttpSslConfig generateSslConfig(InputStream bksFile, String password, InputStream... certificates) {
return generateSslConfigBase(null, bksFile, password, certificates);
}
/**
* https 双向认证
*/
public static HttpSslConfig generateSslConfig(InputStream bksFile, String password, X509TrustManager trustManager) {
return generateSslConfigBase(trustManager, bksFile, password);
}
/**
* 生成认证配置
*
* @param trustManager 可以额外配置信任服务端的证书策略否则默认是按CA证书去验证的若不是CA可信任的证书则无法通过验证
* @param bksFile 客户端使用 bks 证书校验服务端证书
* @param password
* @param certificates 用含有服务端公钥的证书校验服务端证书
*/
private static HttpSslConfig generateSslConfigBase(X509TrustManager trustManager, InputStream bksFile, String password, InputStream... certificates) {
try {
KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
TrustManager[] trustManagers = prepareTrustManager(certificates);
X509TrustManager manager;
if (trustManager != null) {
// 优先使用用户自定义的 TrustManager
manager = trustManager;
} else if (trustManagers != null) {
// 然后使用默认的 TrustManager
manager = chooseTrustManager(trustManagers);
} else {
// 否则使用不安全的 TrustManager
manager = new UnSafeTrustManager();
}
// 创建 TLS 类型的 SsLContext 对象使用我们的 TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
// 用上面得到的 TrustManagers 初始化 SsLContext这样 SslContext 就会信任keyStore中的证书
// 第一个参数是授权的密钥管理器用来授权验证比如授权自签名的证书验证第二个是被授权的证书管理器用来验证服务器端的证书
sslContext.init(keyManagers, new TrustManager[]{manager}, null);
// 通过 SslContext 获取 SSLSocketFactory 对象
return new HttpSslConfig(sslContext.getSocketFactory(), manager);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new AssertionError(e);
}
}
private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) {
try {
if (bksFile == null || password == null) {
return null;
}
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(bksFile, password.toCharArray());
KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
factory.init(keyStore, password.toCharArray());
return factory.getKeyManagers();
} catch (IOException | CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
HttpLog.print(e);
return null;
}
}
private static TrustManager[] prepareTrustManager(InputStream... certificates) {
if (certificates == null || certificates.length <= 0) {
return null;
}
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
// 创建一个默认类型的 KeyStore存储我们信任的证书
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certStream : certificates) {
String certificateAlias = Integer.toString(index++);
// 证书工厂根据证书文件的流生成证书 Cert
Certificate cert = certificateFactory.generateCertificate(certStream);
// Cert 作为可信证书放入到 KeyStore
keyStore.setCertificateEntry(certificateAlias, cert);
try {
if (certStream != null) {
certStream.close();
}
} catch (IOException e) {
HttpLog.print(e);
}
}
// 我们创建一个默认类型的 TrustManagerFactory
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// 用我们之前的 KeyStore 实例初始化 TrustManagerFactory这样 tmf 就会信任 KeyStore 中的证书
factory.init(keyStore);
// 通过 tmf 获取 TrustManager 数组TrustManager 也会信任 KeyStore 中的证书
return factory.getTrustManagers();
} catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException e) {
HttpLog.print(e);
return null;
}
}
private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
for (TrustManager trustManager : trustManagers) {
if (trustManager instanceof X509TrustManager) {
return (X509TrustManager) trustManager;
}
}
return null;
}
public static HostnameVerifier generateUnSafeHostnameVerifier() {
return new UnSafeHostnameVerifier();
}
public static HostnameVerifier generateHostnameVerifier(final String[] hostUrls) {
return (hostname, session) -> {
boolean ret = false;
for (String host : hostUrls) {
if (host.equalsIgnoreCase(hostname)) {
ret = true;
break;
}
}
return ret;
};
}
}

View File

@ -0,0 +1,21 @@
package top.octopusyan.common.http.ssl;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 不安全的证书域名校验
* @create : 2022-4-1 23:20
*/
public final class UnSafeHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
// 此类是用于主机名验证的基接口 在握手期间如果 URL 的主机名和服务器的标识主机名不匹配
// 则验证机制可以回调此接口的实现程序来确定是否应该允许此连接策略可以是基于证书的或依赖于其他验证方案
// 当验证 URL 主机名使用的默认规则失败时使用这些回调如果主机名是可接受的则返回 true
return true;
}
}

View File

@ -0,0 +1,24 @@
package top.octopusyan.common.http.ssl;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 为了解决客户端不信任服务器数字证书的问题网络上大部分的解决方案都是让客户端不对证书做任何检查这是一种有很大安全漏洞的办法
* @create : 2022-4-1 23:20
*/
public final class UnSafeTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}

View File

@ -0,0 +1,10 @@
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

@ -0,0 +1,57 @@
package top.octopusyan.config;
import java.util.HashMap;
import java.util.Map;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 代理设置
* @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<>();
static {
serverPath.put("香港", "xg.frp.octopusyan.top");
serverPath.put("北京", "bj.frp.octopusyan.top");
serverPath.put("上海", "frp.octopusyan.top");
typePort.put("http", 80);
typePort.put("https", 80);
typePort.put("ssh", 22);
typePort.put("tcp", 0);
}
public enum ProxyServer {
香港, 北京, 上海
}
public enum ProxyType {
HTTP("http"),
HTTPS("https"),
SSH("tcp"),
TCP("tcp"),
;
private String type;
public String getType() {
return type;
}
ProxyType(String type) {
this.type = type;
}
}
public static String getServerPath(String serverName) {
return serverPath.get(serverName);
}
public static Integer getTypePort(String type) {
return typePort.get(type);
}
}

View File

@ -0,0 +1,254 @@
package top.octopusyan.controller;
import com.jfoenix.controls.JFXButton;
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.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 java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : Login 控制器
* @create : 2022-3-30 16:06
*/
public class LoginController extends BaseController<StackPane> implements Initializable {
@FXML
public StackPane root;
@FXML
public JFXButton closeBtn, minimizeBtn, loginBtn;
@FXML
public AnchorPane loginBottomPane;
@FXML
public ImageView loginBkgPane;
@FXML
public JFXButton registerBtn;
@FXML
public JFXTextField accountTextField;
@FXML
public JFXPasswordField passwordTextField;
@FXML
public JFXTextField seePwdTextField;
@FXML
public JFXButton accountIconBtn;
@FXML
public FontIcon accountIcon;
@FXML
public JFXButton pwdIconBtn;
@FXML
public FontIcon pwdIcon;
@FXML
public JFXButton seePwdIconBtn;
@FXML
public FontIcon seePwdIcon;
@FXML
public JFXCheckBox autoLoginCBox;
@FXML
public JFXCheckBox rememberCBox;
private boolean autoLogin;
@Override
public boolean dragWindow() {
return true;
}
@Override
public StackPane getRootPanel() {
return root;
}
@NotNull
@Override
public String getRootFxmlPath() {
return "/fxml/login.fxml";
}
@Override
public Button getClooseBtn() {
return closeBtn;
}
@Override
public Button getMinimizeBtn() {
return minimizeBtn;
}
@Override
public JFXButton getFirstBtn() {
return minimizeBtn;
}
@Override
public void initData() {
String account = ApplicatonStore.getAccount();
String password = ApplicatonStore.getPassword();
if(!StringUtils.isEmpty(account)) accountTextField.setText(account);
if(!StringUtils.isEmpty(password)) {
passwordTextField.setText(password);
seePwdTextField.setText(password);
}
}
// 隐藏展示密码框
private AnchorPane pwdParent;
@Override
public void initViewStyle() {
// 文本框焦点状态监听
accountTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) accountTextField.resetValidation();
});
passwordTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) passwordTextField.resetValidation();
});
seePwdTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) seePwdTextField.resetValidation();
});
// 隐藏展示密码框
pwdParent = (AnchorPane) passwordTextField.getParent();
pwdParent.getChildren().remove(seePwdTextField);
// 同步用户数据
accountTextField.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setAccount(newValue));
passwordTextField.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setPassword(newValue));
seePwdTextField.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setPassword(newValue));
// 添加文本校验
accountTextField.getValidators().add(TextValidate.AccoountRequired);
accountTextField.getValidators().add(TextValidate.AccoountValidator);
accountTextField.getValidators().add(TextValidate.getLengthValidator(6, 18, "账号"));
passwordTextField.getValidators().add(TextValidate.PasswordRequired);
seePwdTextField.getValidators().add(TextValidate.PasswordRequired);
// 记住密码
rememberCBox.selectedProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setRememberMe(newValue));
// 自动登录
autoLoginCBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
ApplicatonStore.setAutoLogin(newValue);
if (newValue && !ApplicatonStore.isRememberMe()) rememberCBox.selectedProperty().set(true);
});
}
@Override
public void initViewAction() {
// 注册
registerBtn.setOnMouseClicked(event -> jumpTo(new RegisterController()));
// 查看密码
seePwdIconBtn.setOnMouseClicked(event -> {
boolean isHide = pwdParent.getChildren().contains(passwordTextField);
pwdParent.getChildren().remove(isHide ? passwordTextField : seePwdTextField);
pwdParent.getChildren().add(1, isHide ? seePwdTextField : passwordTextField);
(isHide ? seePwdTextField : passwordTextField).setText(ApplicatonStore.getPassword());
seePwdIcon.setIconColor(isHide ? Color.BLUE : Color.BLACK);
});
// 登录
loginBtn.setOnMouseClicked(event -> login());
accountTextField.setOnKeyPressed(event -> {
if(event.getCode() == KeyCode.ENTER) login();
});
passwordTextField.setOnKeyPressed(event -> {
if(event.getCode() == KeyCode.ENTER) login();
});
// 自动登录
if((ApplicatonStore.isAutoLogin() || ApplicatonStore.isRegisterSuccess()) &&
!StringUtils.isEmpty(ApplicatonStore.getAccount()) &&
!StringUtils.isEmpty(ApplicatonStore.getPassword())
){
login();
}
}
private void login(){
// 获取文本校验结果
boolean pwdValidate = pwdParent.getChildren().contains(passwordTextField) ?
passwordTextField.validate() : seePwdTextField.validate();
boolean accountValidate = accountTextField.validate();
if (pwdValidate && accountValidate)
EasyHttp.builder()
.api(Api.Login)
.param(new LoginParam(accountTextField.getText(), ApplicatonStore.getPassword()))
.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());
return;
}
// TODO 登录成功
setCsrf();
jumpTo(new MainController());
}
});
}
private void setCsrf() {
EasyHttp.builder()
.api(new NotParamApi<String>(
"/?page=panel&module=addproxy",
HttpConstant.Method.GET
))
.request(new OnHttpListener<String>() {
@Override
public void onSucceed(String result) {
ProxyUtil.setCsrf(result);
}
});
}
private String getHtmlErrorMessage(Document html) {
return html.body().getElementsByClass("alert alert-danger alert-dismissable").text().substring(1);
}
@Override
public void onDestroy() {
EasyHttp.cancel();
super.onDestroy();
}
}

View File

@ -0,0 +1,372 @@
package top.octopusyan.controller;
import com.jfoenix.controls.*;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import top.octopusyan.base.BaseController;
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.utils.AlertUtil;
import top.octopusyan.utils.ApplicatonStore;
import top.octopusyan.utils.ProxyUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description :
* @create : 2022-4-4 22:32
*/
public class MainController extends BaseController<StackPane> implements Initializable {
public static final String PROXY_LIST_ITEM_CLASS = "proxyListItem";
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";
@FXML
public StackPane root;
@FXML
public JFXButton closeBtn, minimizeBtn;
@FXML
public JFXButton startProxyBtn;
@FXML
public JFXButton addProxyBtn;
@FXML
public JFXComboBox<Label> proxyProtocolCombox;
@FXML
public JFXButton customizeDomainBtn;
@FXML
public JFXRadioButton openProxyRBtn, closeProxyRBtn;
@FXML
public JFXTextField domainTextField, domainSuffixTextField;
@FXML
public JFXTextField proxyNameTextField, localHostTextField, localPortTextField;
@FXML
public JFXComboBox<Label> proxyServerCB;
@FXML
public JFXTabPane tabPane;
@FXML
public JFXListView<Label> proxyListView;
private ToggleGroup openProxyGroup = new ToggleGroup();
private boolean customizeDomain = false;
private List<ProxySetup> proxyList;
private int selectProxy = 0;
private ProxySetup proxySetup;
@Override
public boolean dragWindow() {
return true;
}
@Override
public StackPane getRootPanel() {
return root;
}
@NotNull
@Override
public String getRootFxmlPath() {
return "/fxml/main.fxml";
}
@Override
public Button getClooseBtn() {
return closeBtn;
}
@Override
public Button getMinimizeBtn() {
return minimizeBtn;
}
@Override
public JFXButton getFirstBtn() {
return minimizeBtn;
}
@Override
public void initData() {
// 隧道类型
Arrays.asList(ProxyType.values()).forEach((type) -> proxyProtocolCombox.getItems().add(new Label(type.name())));
// 服务器
Arrays.asList(ProxyServer.values()).forEach((server) -> proxyServerCB.getItems().add(new Label(server.name())));
// 获取用户隧道列表
ProxyUtil.getList(setupList -> {
proxyList = setupList;
// 主线程执行
Platform.runLater(() -> {
// 设置隧道列表
ApplicatonStore.setProxySetupList(proxyList);
// 初始化隧道设置
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);
}
});
});
}
@Override
public void initViewStyle() {
// 启用链接
openProxyRBtn.setToggleGroup(openProxyGroup);
openProxyRBtn.setSelected(true);
closeProxyRBtn.setToggleGroup(openProxyGroup);
openProxyRBtn.setUserData(true);
closeProxyRBtn.setUserData(false);
// 默认展示 常见问题
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() {
// 是否启用
openProxyGroup.selectedToggleProperty().addListener(
(ObservableValue<? extends Toggle> ov, Toggle old_toggle, Toggle new_toggle) -> {
Toggle toggle = openProxyGroup.getSelectedToggle();
if (toggle != null) {
closeProxy(!(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();
// 设置默认端口
localPortTextField.setText(port);
// 设置隧道类型
proxySetup.setProxy_type(newType);
});
// 自定义访问域名
customizeDomainBtn.setOnMouseClicked((event) -> {
setDomain(proxySetup != null ? proxySetup : null);
});
// 点击隧道列表
proxyListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
for (ProxySetup setup : proxyList) {
if (setup.getProxy_name().equals(newValue.getText()))
initProxySetupView(setup);
}
}
});
// 添加隧道
addProxyBtn.setOnMouseClicked(event -> {
// 获取默认隧道设置
proxySetup = ProxyUtil.initProxy();
// 加入列表
proxyList.add(proxySetup);
selectProxy = proxyList.indexOf(proxySetup);
// 展示
initProxySetupView(proxySetup);
});
}
/**
* 显示隧道设置
*/
private void initProxySetupView(ProxySetup setup) {
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;
}
// 选中的隧道下标
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());
}
/**
* 显示隧道列表
*/
private void initProxyList(List<ProxySetup> proxyList) {
// 清空列表
proxyListView.getItems().clear();
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);
proxyListView.getItems().add(label);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 是否启用当前隧道
*/
private void closeProxy(boolean close) {
if (close && proxySetup.isRuning()) stopProxy();
proxyNameTextField.setDisable(close);// 隧道名称
proxyProtocolCombox.setDisable(close);// 隧道类型
localHostTextField.setDisable(close);// 本地地址
localPortTextField.setDisable(close);// 本地端口
domainTextField.setDisable(close); // 外网子域名
startProxyBtn.setDisable(close); // 启动按钮
ObservableList<String> styleClass = proxyListView.getItems().get(selectProxy).getStyleClass();
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);
}
}
/**
* TODO 启动代理
*/
private void startProxy() {
// 删除旧隧道
if (proxySetup.getId() != null) ProxyUtil.delete(proxySetup.getId());
// 添加隧道
if (!ProxyUtil.add(result -> {
}, proxySetup)) return;
// TODO 修改设置
// TODO 调用cmd
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);
}
/**
* TODO 暂停代理
*/
private void stopProxy() {
// 关闭CMD
proxySetup.setRuning(false);
}
}

View File

@ -0,0 +1,196 @@
package top.octopusyan.controller;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXTextField;
import javafx.application.Platform;
import javafx.fxml.FXML;
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.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 java.io.Serializable;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 注册表单
* @create : 2022-4-3 22:39
*/
public class RegisterController extends BaseController<StackPane> implements Serializable {
@FXML
public StackPane root;
@FXML
public JFXButton closeBtn, minimizeBtn;
@FXML
public JFXTextField accooundTextField, emailTf, emailCheckCodeTf;
@FXML
public JFXButton registerBtn, sendCheckCodeBtn;
@FXML
public JFXPasswordField passwordTextField;
@FXML
public JFXButton loginBtn;
@Override
public boolean dragWindow() {
return true;
}
@Override
public StackPane getRootPanel() {
return root;
}
@NotNull
@Override
public String getRootFxmlPath() {
return "/fxml/register.fxml";
}
@Override
public Button getClooseBtn() {
return closeBtn;
}
@Override
public Button getMinimizeBtn() {
return minimizeBtn;
}
@Override
public JFXButton getFirstBtn() {
return minimizeBtn;
}
@Override
public void initData() {
String account = ApplicatonStore.getAccount();
String password = ApplicatonStore.getPassword();
if(!StringUtils.isEmpty(account)) accooundTextField.setText(account);
if(!StringUtils.isEmpty(password)) passwordTextField.setText(password);
}
@Override
public void initViewStyle() {
// 记录账号
accooundTextField.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setAccount(newValue));
// 记录密码
passwordTextField.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setPassword(newValue));
// 记录密码
emailTf.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setEmail(newValue));
// 添加文本验证
accooundTextField.getValidators().add(TextValidate.AccoountRequired);
accooundTextField.getValidators().add(TextValidate.AccoountValidator);
accooundTextField.getValidators().add(TextValidate.getLengthValidator(6, 18, "账号"));
passwordTextField.getValidators().add(TextValidate.PasswordRequired);
emailTf.getValidators().add(TextValidate.EmailFormat);
emailCheckCodeTf.getValidators().add(TextValidate.getLengthValidator("验证码", 6));
// 文本验证
accooundTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) accooundTextField.resetValidation();
else accooundTextField.validate();
});
emailTf.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) emailTf.resetValidation();
else emailTf.validate();
});
passwordTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) passwordTextField.resetValidation();
else passwordTextField.validate();
});
emailCheckCodeTf.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue)
emailCheckCodeTf.resetValidation();
else
emailCheckCodeTf.validate();
});
}
@Override
public void initViewAction() {
// 登录
loginBtn.setOnMouseClicked(event -> jumpTo(new LoginController()));
// 点击了发送验证码
sendCheckCodeBtn.setOnMouseClicked(event -> {
// 如果邮箱地址正确
if (emailTf.validate()) {
// 发送邮箱验证
EasyHttp.builder()
.api(Api.SendEmailCheck)
.param(new SendEmailCheckParam(emailTf.getText()))
.request(new OnHttpListener<String>() {
@Override
public void onSucceed(String result) {
Platform.runLater(() -> AlertUtil.info("系统已发送一封邮件至您的邮箱,请查收。").show());
}
});
}
});
// 注册
registerBtn.setOnMouseClicked(event -> register());
}
private void register(){
// 文本校验
if(accooundTextField.validate() && passwordTextField.validate() && emailTf.validate() && emailCheckCodeTf.validate()){
EasyHttp.builder()
.api(Api.Register)
.param(new RegisterParam(
ApplicatonStore.getAccount(),
ApplicatonStore.getPassword(),
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;
}
// 注册成功
if(result.contains("alert-success")){
Platform.runLater(() -> AlertUtil.info(getHtmlMessage(html)).header(null).show());
ApplicatonStore.setRegisterSuccess(true);
jumpTo(new LoginController());
}
}
});
}
}
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

@ -0,0 +1,80 @@
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.http.request.ProxySetup;
import top.octopusyan.http.request.RegisterParam;
import top.octopusyan.http.request.SendEmailCheckParam;
import top.octopusyan.http.request.LoginParam;
import top.octopusyan.utils.ProxyUtil;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 业务请求接口
* @create : 2022-4-1 23:08
*/
public class Api {
/** 登录接口 获取Cookie */
public static final ParamApi<LoginParam, String> Login = new ParamApi<>(
"/?action=login&page=login",
HttpConstant.Method.POST,
BodyType.FORM
);
/** 发送邮箱验证码 */
public static final ParamApi<SendEmailCheckParam, String> SendEmailCheck = new ParamApi<>(
"/?action=sendmail",
HttpConstant.Method.POST,
BodyType.FORM
);
/** 发送邮箱验证码 */
public static final ParamApi<RegisterParam, String> Register = new ParamApi<>(
"/?action=register&page=register",
HttpConstant.Method.POST,
BodyType.FORM
);
/** 隧道列表 */
public static final NotParamApi<String> ProxyList = new NotParamApi<>(
"/?page=panel&module=proxies",
HttpConstant.Method.GET
);
/** 隧道详情 */
public static PathParamApi<String> ProxyInfo() {
return new PathParamApi<>(
"/?page=panel&module=proxies&getproxyinfo={0}&csrf=" + ProxyUtil.getCsrf(),
HttpConstant.Method.GET
);
}
/** 添加隧道 */
public static ParamApi<ProxySetup, String> AddProxy() {
return new ParamApi<>(
"/?page=panel&module=addproxy&action=addproxy&csrf=" + ProxyUtil.getCsrf(),
HttpConstant.Method.POST,
BodyType.FORM
);
}
/** 删除隧道 */
public static PathParamApi<Void> DeleteProxy() {
return new PathParamApi<>(
"/?page=panel&module=proxies&delete={0}&csrf=" + ProxyUtil.getCsrf(),
HttpConstant.Method.GET
);
}
/** 随机端口 */
public static NotParamApi<Integer> RandomPort() {
return new NotParamApi<>(
"/?page=panel&module=addproxy&randomport&csrf=" + ProxyUtil.getCsrf(),
HttpConstant.Method.GET
);
}
}

View File

@ -0,0 +1,140 @@
package top.octopusyan.http;
import kotlin.text.Charsets;
import okhttp3.*;
import okio.Buffer;
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 java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : okhttp 设置
* @create : 2022-4-2 15:27
*/
public class OkHttpClientConfig {
public static final String TAG = "OkHttpClientConfig";
private static final Logger log = LoggerFactory.getLogger(TAG);
public static final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
public static OkHttpClient httpClient() {
Interceptor tokenInterceptor = new Interceptor() {//全局拦截器往请求头部添加 token 字段实现全局添加 token
@Override
public Response intercept(Chain chain) throws IOException {// 打印请求报文
// 获取请求
Request request = chain.request();
// 取出请求参数
String reqBody = getRequestBody(request);
// 打印请求报文
log.info("" + TAG + "" + String.format("发送新请求\n-\tmethod%s\n-\turl%s\n-\theaders: %s\n-\tbody%s",
request.method(), request.url(), request.headers(), reqBody));
// 执行请求获取响应报文
Response response = chain.proceed(request);
// 获取响应内容 TODO 读取结果乱码
String respBody = getResponseBody(response);
// 打印响应报文
// log.info("" + TAG + "" + String.format("收到响应 %s %s\n-\t请求url%s\n-\t请求body%s\n-\t响应body%s",
// response.code(), response.message(), response.request().url(), reqBody, respBody));
log.info("" + TAG + "" + String.format("收到响应 %s %s\n-\t请求url%s\n-\t请求body%s",
response.code(), response.message(), response.request().url(), reqBody));
return response;
}
};
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(16);
dispatcher.setMaxRequestsPerHost(4);
HttpSslConfig httpSslConfig = HttpSslFactory.generateSslConfig();
return new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.addNetworkInterceptor(tokenInterceptor)
.dispatcher(dispatcher)
.cookieJar(cookieJar())
.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES))
.connectTimeout(10L, TimeUnit.SECONDS)
.readTimeout(500L, TimeUnit.SECONDS)
.writeTimeout(0L, TimeUnit.SECONDS)
.pingInterval(60L, TimeUnit.SECONDS)
.sslSocketFactory(httpSslConfig.getSslSocketFactory(), httpSslConfig.getTrustManager())
.hostnameVerifier(HttpSslFactory.generateUnSafeHostnameVerifier())
.build();
}
/**
* 获取请求体
*/
private static String getRequestBody(Request request) throws IOException {
RequestBody requestBody = request.body();
String reqBody = null;
if (requestBody != null) {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = StandardCharsets.UTF_8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(StandardCharsets.UTF_8);
}
reqBody = buffer.readString(charset);
}
return reqBody;
}
/**
* 获取返回体
*/
private static String getResponseBody(Response response) throws IOException {
ResponseBody responseBody = response.body();
String respBody = null;
if (responseBody != null) {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.getBuffer();
Charset charset = Charsets.UTF_8;
MediaType contentType = responseBody.contentType();
if (contentType != null)
charset = contentType.charset(charset);
//获取Response的body的字符串 并打印
respBody = buffer.clone().readString(charset);
}
return respBody;
}
private static CookieJar cookieJar() {
return new CookieJar() {
@Override
public void saveFromResponse(@NotNull HttpUrl httpUrl, @NotNull List<Cookie> list) {
cookieStore.put(httpUrl.host(), list);
}
@NotNull
@Override
public List<Cookie> loadForRequest(@NotNull HttpUrl httpUrl) {
List<Cookie> cookies = cookieStore.get(httpUrl.host());
return cookies != null ? cookies : new ArrayList<Cookie>();
}
};
}
}

View File

@ -0,0 +1,19 @@
package top.octopusyan.http.request;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 登录参数
* @create : 2022-4-1 23:36
*/
@AllArgsConstructor
@Data
public class LoginParam {
/** 用户名 */
private String username;
/** 密码 */
private String password;
}

View File

@ -0,0 +1,31 @@
package top.octopusyan.http.request;
import lombok.Data;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description :
* @create : 2022-4-5 10:08
*/
@Data
public class ProxySetup {
private Integer id;
private Integer node;
private String proxy_name;
private String proxy_type;
private String local_ip;
private Integer local_port;
private Integer remote_port;
private String domain;
private Boolean use_encryption;
private Boolean use_compression;
private String locations;
private String host_header_rewrite;
private String header_X_From_Where;
private String sk;
private Boolean status;
private int sort;
private boolean runing;
private boolean isClose;
}

View File

@ -0,0 +1,19 @@
package top.octopusyan.http.request;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 注册接口参数
* @create : 2022-4-4 20:57
*/
@Data
@AllArgsConstructor
public class RegisterParam {
private String username;
private String password;
private String email;
private String verifycode;
}

View File

@ -0,0 +1,16 @@
package top.octopusyan.http.request;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 发送邮箱验证接口参数
* @create : 2022-4-4 17:07
*/
@Data
@AllArgsConstructor
public class SendEmailCheckParam {
String mail;
}

View File

@ -0,0 +1,38 @@
package top.octopusyan.manager;
import java.util.concurrent.*;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2020/01/11
* desc : 线程池管理类
*/
public final class ThreadPoolManager extends ThreadPoolExecutor {
private static volatile ThreadPoolManager sInstance;
private static ScheduledExecutorService scheduledExecutorService;
private ThreadPoolManager() {
super(0, Integer.MAX_VALUE,
30L, TimeUnit.SECONDS,
new SynchronousQueue<>());
}
public static ThreadPoolManager getInstance() {
if(sInstance == null) sInstance = new ThreadPoolManager();
return sInstance;
}
public static ScheduledExecutorService getScheduledExecutorService(int corePoolSize) {
if (scheduledExecutorService == null) {
scheduledExecutorService = Executors.newScheduledThreadPool(corePoolSize);
}
return scheduledExecutorService;
}
public static ScheduledExecutorService getScheduledExecutorService() {
return getScheduledExecutorService(5);
}
}

View File

@ -0,0 +1,205 @@
package top.octopusyan.utils;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : Dialog对话框
* @create : 2022-4-4 18:21
*/
public class AlertUtil {
private static Window mOwner;
public static void initOwner(Stage stage) {
AlertUtil.mOwner = stage;
}
public static class Builder<T extends Dialog> {
T alert;
public Builder(T alert) {
this.alert = alert;
if(mOwner != null) this.alert.initOwner(mOwner);
}
public Builder title(String title) {
alert.setTitle(title);
return this;
}
public Builder header(String header) {
alert.setHeaderText(header);
return this;
}
public Builder content(String content) {
alert.setContentText(content);
return this;
}
public Builder icon(String path) {
icon(new Image(this.getClass().getResource(path).toString()));
return this;
}
public Builder icon(Image image) {
getStage().getIcons().add(image);
return this;
}
public void show() {
alert.showAndWait();
}
private void show(OnClickListener listener) {
Optional<ButtonType> result = alert.showAndWait();
listener.onClicked(result.get().getText());
}
private void show(OnChoseListener listener) {
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
listener.confirm();
} else {
listener.cancelOrClose();
}
}
/**
* 如果用户点击了取消按钮,将会返回null
*/
public String getInput() {
Optional<String> result = alert.showAndWait();
if (result.isPresent()) {
return result.get();
}
return null;
}
public <R> R getChoice(R... choices) {
Optional<R> result = alert.showAndWait();
return result.get();
}
private Stage getStage() {
return (Stage) alert.getDialogPane().getScene().getWindow();
}
}
public static Builder info(String content) {
return new Builder(new Alert(AlertType.INFORMATION)).content(content).header(null);
}
public static Builder info() {
return new Builder(new Alert(AlertType.INFORMATION));
}
public static Builder error(String message) {
return new Builder(new Alert(AlertType.ERROR)).header(null).content(message);
}
public static Builder warning() {
return new Builder(new Alert(AlertType.WARNING));
}
public static Builder exception(Exception ex) {
return new Builder(exceptionAlert(ex));
}
public static Alert exceptionAlert(Exception ex) {
Alert alert = new Alert(AlertType.ERROR);
alert.setTitle("Exception Dialog");
alert.setHeaderText(ex.getClass().getSimpleName());
alert.setContentText(ex.getMessage());
// 创建可扩展的异常
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
String exceptionText = sw.toString();
Label label = new Label("The exception stacktrace was :");
TextArea textArea = new TextArea(exceptionText);
textArea.setEditable(false);
textArea.setWrapText(true);
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(label, 0, 0);
expContent.add(textArea, 0, 1);
// 将可扩展异常设置到对话框窗格中
alert.getDialogPane().setExpandableContent(expContent);
return alert;
}
public static Builder confirm() {
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("确认对话框");
return new Builder(alert);
}
/**
* 自定义确认对话框 <p>
* <code>"Cancel"</code> OR <code>"取消"</code> 为取消按钮
*/
public static Builder confirm(String... buttons) {
Alert alert = new Alert(AlertType.CONFIRMATION);
List<ButtonType> buttonList = Arrays.stream(buttons).map((type) -> {
ButtonBar.ButtonData buttonData = ButtonBar.ButtonData.OTHER;
if ("Cancel".equals(type) || "取消".equals(type))
buttonData = ButtonBar.ButtonData.CANCEL_CLOSE;
return new ButtonType(type, buttonData);
}).collect(Collectors.toList());
alert.getButtonTypes().setAll(buttonList);
return new Builder(alert);
}
public static Builder input(String content) {
TextInputDialog dialog = new TextInputDialog();
dialog.setContentText(content);
return new Builder(dialog);
}
public static <T> Builder choices(String hintText, T... choices) {
ChoiceDialog<T> dialog = new ChoiceDialog<T>(choices[0], choices);
dialog.setContentText(hintText);
return new Builder(dialog);
}
interface OnChoseListener {
void confirm();
void cancelOrClose();
}
interface OnClickListener {
void onClicked(String result);
}
}

View File

@ -0,0 +1,102 @@
package top.octopusyan.utils;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import top.octopusyan.http.request.ProxySetup;
import java.util.ArrayList;
import java.util.List;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 应用信息
* @create : 2022-4-4 17:22
*/
public class ApplicatonStore {
private static SimpleStringProperty account = new SimpleStringProperty();
private static SimpleStringProperty password = new SimpleStringProperty();
private static SimpleStringProperty email = new SimpleStringProperty();
private static SimpleStringProperty sendMailCode = new SimpleStringProperty();
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 SimpleIntegerProperty selectProxy = new SimpleIntegerProperty(0);
public static void setAccount(String account) {
ApplicatonStore.account.set(account);
}
public static void setEmail(String email) {
ApplicatonStore.email.set(email);
}
public static void setPassword(String password) {
ApplicatonStore.password.set(password);
}
public static void setSendMailCode(String sendMailCode) {
ApplicatonStore.sendMailCode.set(sendMailCode);
}
public static String getAccount() {
return account.get();
}
public static String getEmail() {
return email.get();
}
public static String getPassword() {
return password.get();
}
public static String getSendMailCode() {
return sendMailCode.get();
}
public static void setAutoLogin(boolean autoLogin) {
ApplicatonStore.autoLogin.set(autoLogin);
}
public static void setRememberMe(boolean rememberMe) {
ApplicatonStore.rememberMe.set(rememberMe);
}
public static boolean isAutoLogin() {
return autoLogin.get();
}
public static boolean isRememberMe() {
return rememberMe.get();
}
public static void setRegisterSuccess(boolean registerSuccess) {
ApplicatonStore.registerSuccess.set(registerSuccess);
}
public static boolean isRegisterSuccess() {
return registerSuccess.get();
}
public static ObservableList<ProxySetup> getProxySetupList() {
return proxySetupList.get();
}
public static void setProxySetupList(List<ProxySetup> proxySetupList) {
ApplicatonStore.proxySetupList = new SimpleListProperty<>(FXCollections.observableList(proxySetupList));
}
public static void setSelectProxy(int selectProxy) {
ApplicatonStore.selectProxy.set(selectProxy);
}
public static int getSelectProxy() {
return selectProxy.get();
}
}

View File

@ -0,0 +1,458 @@
package top.octopusyan.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.MD5;
import sun.misc.BASE64Encoder;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 常用加密工具类
* @create : 2021-03-18 19:03
*/
public class EncryptionUtil {
private static EncryptionUtil util;
private static Logger logger = LoggerFactory.getLogger(EncryptionUtil.class);
private static BASE64Encoder BASE64Encoder;
private static MessageDigest MessageDigest_SHA1;
private static MessageDigest MessageDigest_MD5;
private static Cipher Cipher_ECB_MOB;
private static Cipher Cipher_ALGORITHM_MODE_PADDING;
private static Signature Signature_SHA256_RSA;
/**
* 密钥算法
*/
private static final String DES = "DES";
private static final String AES = "AES";
private static final String RES = "RES";
private static final String RSA = "RSA";
/**
* 加解密算法/工作模式/填充方式
*/
private static final String ECB_MOB = "DES/ECB/PKCS5Padding";
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
private static final Charset UTF_8 = StandardCharsets.UTF_8;
private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
'E', 'F'};
private static final char hexDigitsLower[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
'd', 'e', 'f'};
public static synchronized EncryptionUtil getInstance() {
if (util == null) {
BASE64Encoder = new BASE64Encoder();
try {
MessageDigest_SHA1 = MessageDigest.getInstance("SHA-1");
MessageDigest_MD5 = MessageDigest.getInstance("MD5");
Cipher_ECB_MOB = Cipher.getInstance(ECB_MOB);
Cipher_ALGORITHM_MODE_PADDING = Cipher.getInstance(ALGORITHM_MODE_PADDING);
Signature_SHA256_RSA = Signature.getInstance("SHA256WithRSA");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
}
}
return util;
}
public static String getSha1(String str) {
if (str == null || str.length() == 0) {
return null;
}
return getMessageDigestString(str, hexDigitsLower, MessageDigest_SHA1);
}
public static String getSHA(String spara) {
String sRtn = null;
try {
byte[] plainText = spara.getBytes(UTF_8);
// 开始使用算法
MessageDigest_SHA1.update(plainText);
// 输出算法运算结果
sRtn = BASE64Encoder.encode(MessageDigest_SHA1.digest());
} catch (Exception e) {
logger.error("SHA-1 160加密出错" + e.getMessage());
}
return sRtn;
}
/**
* DES加密
*
* @param data 加密内容
* @param password 秘钥
*/
public static String DESencode(String data, String password) {
byte[] pasByte;
try {
Key key = getDESKey(password);
Cipher_ECB_MOB.init(Cipher.ENCRYPT_MODE, key);
pasByte = Cipher_ECB_MOB.doFinal(data.getBytes(UTF_8));
} catch (Exception e) {
logger.error("DES加密出错" + e.getMessage());
throw new RuntimeException("DES加密出错" + e.getMessage());
}
return Base64.encode(pasByte);
}
/**
* DES解密
*
* @param data 加密内容
* @param password 秘钥
* @return
*/
public static String DESdecode(String data, String password) throws Exception {
byte[] pasByte;
Key key = getDESKey(password);
Cipher_ECB_MOB.init(Cipher.DECRYPT_MODE, key);
byte[] x = Base64.decode(data);
pasByte = Cipher_ECB_MOB.doFinal(x);
return new String(pasByte, UTF_8);
}
private static Key getDESKey(String password) throws Exception {
byte[] DESkey = password.getBytes(UTF_8);
DESKeySpec keySpec = new DESKeySpec(DESkey);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
return keyFactory.generateSecret(keySpec);
}
/**
* AES加密
*
* @param data 加密内容
* @param password 加密密码
*/
public static String AESencode(String data, String password) throws Exception {
byte[] result;
Security.addProvider(new BouncyCastleProvider());
// 创建密码器
// 初始化为加密模式的密码
Cipher_ALGORITHM_MODE_PADDING.init(Cipher.ENCRYPT_MODE, getSecretKey(password));
// 加密
result = Cipher_ALGORITHM_MODE_PADDING.doFinal(data.getBytes());
return Base64.encode(result);
}
/**
* AES解密
*
* @param base64Data 解密内容
* @param password 解密密码
*/
public static String AESdecode(String base64Data, String password) throws Exception {
byte[] result;
Security.addProvider(new BouncyCastleProvider());
// 使用密钥初始化设置为解密模式
Cipher_ALGORITHM_MODE_PADDING.init(Cipher.DECRYPT_MODE, getSecretKey(password));
// 执行操作
result = Cipher_ALGORITHM_MODE_PADDING.doFinal(Base64.decode(base64Data));
return new String(result, UTF_8);
}
/**
* 生成AES加密秘钥
*
* @return
*/
private static SecretKeySpec getSecretKey(String password) {
return new SecretKeySpec(SecureUtil.md5(password).toLowerCase().getBytes(), AES);
}
/**
* 对字符串 MD5 无盐值加密
*
* @param plainText 传入要加密的字符串
* @return MD5加密后生成32位(小写字母 + 数字)字符串
*/
public static String MD5Lower(String plainText) {
// 使用指定的字节更新摘要
MessageDigest_MD5.update(plainText.getBytes());
// digest()最后确定返回md5 hash值返回值为8位字符串因为md5 hash值是16位的hex值实际上就是8位的字符
// BigInteger函数则将8位的字符串转换成16位hex值用字符串来表示得到字符串形式的hash值1 固定值
return new BigInteger(1, MessageDigest_MD5.digest()).toString(16);
}
/**
* 对字符串 MD5 加密
*
* @param plainText 传入要加密的字符串
* @return MD5加密后生成32位(大写字母 + 数字)字符串
*/
public static String MD5Upper(String plainText) {
// 使用指定的字节更新摘要
MessageDigest_MD5.update(plainText.getBytes());
// 获得密文
byte[] mdResult = MessageDigest_MD5.digest();
// 把密文转换成十六进制的字符串形式
return getHexString(hexDigitsLower, mdResult);
}
/**
* 对字符串 MD5 加盐值加密
*
* @param plainText 传入要加密的字符串
* @param saltValue 传入要加的盐值
* @return MD5加密后生成32位(小写字母 + 数字)字符串
*/
public static String MD5Lower(String plainText, String saltValue) {
// 使用指定的字节更新摘要
MessageDigest_MD5.update(plainText.getBytes());
MessageDigest_MD5.update(saltValue.getBytes());
// digest()最后确定返回md5 hash值返回值为8位字符串因为md5 hash值是16位的hex值实际上就是8位的字符
// BigInteger函数则将8位的字符串转换成16位hex值用字符串来表示得到字符串形式的hash值1 固定值
return new BigInteger(1, MessageDigest_MD5.digest()).toString(16);
}
/**
* 对字符串 MD5 加盐值加密
*
* @param plainText 传入要加密的字符串
* @param saltValue 传入要加的盐值
* @return MD5加密后生成32位(大写字母 + 数字)字符串
*/
public static String MD5Upper(String plainText, String saltValue) {
try {
// 使用指定的字节更新摘要
MessageDigest_MD5.update(plainText.getBytes());
MessageDigest_MD5.update(saltValue.getBytes());
// 获得密文
byte[] mdResult = MessageDigest_MD5.digest();
// 把密文转换成十六进制的字符串形式
return getHexString(hexDigitsLower, mdResult);
} catch (Exception e) {
logger.error("MDS 加密出错:" + e.getMessage());
throw new RuntimeException("MDS 加密出错:" + e.getMessage());
}
}
/**
* MD5加密后生成32位(小写字母+数字)字符串
* MD5Lower() 一样
*/
public static String MD5(String plainText) {
return getMessageDigestString(plainText, hexDigitsLower, MessageDigest_MD5);
}
/**
* 校验MD5码
*
* @param text 要校验的字符串
* @param md5 md5值
* @return 校验结果
*/
public static boolean MD5valid(String text, String md5) throws Exception {
return md5.equals(MD5(text)) || md5.equals(MD5(text).toUpperCase());
}
/**
* 校验MD5码
*
* @param text 要校验的字符串
* @param md5 md5值
* @param salt 加密盐
* @return 校验结果
*/
public static boolean MD5valid(String text, String md5, String salt) {
return md5.equals(MD5Lower(text, salt)) || md5.equals(MD5Upper(text, salt).toUpperCase());
}
/**
* RSA2签名
*
* @param content 待签名的字符串
* @param privateKey rsa私钥字符串
* @param charset 字符集编码
* @return 签名结果
*/
public static String rsaSign(String content, String privateKey, String charset) throws Exception {
PrivateKey priKey = getPrivateKeyFromPKCS8(RSA, new ByteArrayInputStream(privateKey.getBytes()));
Signature_SHA256_RSA.initSign(priKey);
if (StringUtils.isBlank(charset)) {
Signature_SHA256_RSA.update(content.getBytes());
} else {
Signature_SHA256_RSA.update(content.getBytes(charset));
}
byte[] signed = Signature_SHA256_RSA.sign();
return new String(Base64.encode(signed));
}
/**
* RSA2验签
*
* @param content 被签名的内容
* @param sign 签名后的结果
* @param publicKey rsa公钥
* @param charset 字符集编码
* @return 验签结果
*/
public static boolean doCheck(String content, String sign, String publicKey, String charset) throws Exception {
PublicKey pubKey = getPublicKeyFromX509(RSA, new ByteArrayInputStream(publicKey.getBytes()));
Signature_SHA256_RSA.initVerify(pubKey);
Signature_SHA256_RSA.update(getContentBytes(content, charset));
return Signature_SHA256_RSA.verify(Base64.decode(sign));
}
/**
* 获取私钥对象
*
* @param algorithm 签名方式
* @param ins 私钥流
* @return
* @throws Exception
*/
private static PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception {
if (ins == null || StringUtils.isEmpty(algorithm)) {
return null;
}
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
byte[] encodedKey = readText(ins, UTF_8, true).getBytes();
encodedKey = Base64.decode(Arrays.toString(encodedKey));
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
}
/**
* 获取公钥对象
*
* @param algorithm 签名方式
* @param ins 公钥流
* @return
* @throws Exception
*/
private static PublicKey getPublicKeyFromX509(String algorithm, InputStream ins) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
StringWriter writer = new StringWriter();
io(new InputStreamReader(ins), writer, true, true);
byte[] encodedKey = writer.toString().getBytes();
// 先base64解码
encodedKey = Base64.decode(Arrays.toString(encodedKey));
return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
}
/**
* 获取字符串对应编码的字节
*
* @param content 字符串内容
* @param charset 字符集编码
* @return
* @throws UnsupportedEncodingException
*/
private static byte[] getContentBytes(String content, String charset) throws UnsupportedEncodingException {
if (StringUtils.isEmpty(charset)) {
return content.getBytes();
}
return content.getBytes(charset);
}
/**
* 将指定输入流的所有文本全部读出到一个字符串中
*
* @param in 输入流
* @param charset 字符集编码
* @param closeIn 是否关闭流
* @return
* @throws IOException
*/
private static String readText(InputStream in, Charset charset, boolean closeIn) throws IOException {
Reader reader = charset == null ? new InputStreamReader(in) : new InputStreamReader(in, charset);
return readText(reader, closeIn);
}
/**
* 将指定<code>Reader</code>的所有文本全部读出到一个字符串中
*
* @param in 输入流
* @param closeIn 是否关闭流
* @return
* @throws IOException
*/
private static String readText(Reader in, boolean closeIn) throws IOException {
StringWriter out = new StringWriter();
io(in, out, closeIn, true);
return out.toString();
}
/**
* 从输入流读取内容写入到输出流中
*
* @param in 输入流
* @param out 输出流
* @param closeIn 是否关闭流
* @param closeOut 是否关闭流
* @throws IOException
*/
private static void io(Reader in, Writer out, boolean closeIn, boolean closeOut) {
int bufferSize = 8192 >> 1;
char[] buffer = new char[bufferSize];
int amount;
try {
while ((amount = in.read(buffer)) >= 0) {
out.write(buffer, 0, amount);
}
out.flush();
} catch (Exception e) {
logger.error("从输入流读取内容,写入到输出流中异常:{}", e.getMessage(), e);
} finally {
if (closeIn) {
try {
in.close();
} catch (IOException e) {
logger.error("从输入流读取内容,写入到输出流中异常:{}", e.getMessage(), e);
}
}
if (closeOut) {
try {
out.close();
} catch (IOException e) {
logger.error("从输入流读取内容,写入到输出流中异常:{}", e.getMessage(), e);
}
}
}
}
private static String getMessageDigestString(String str, char[] hexDigits, MessageDigest messageDigest) {
messageDigest.update(str.getBytes(UTF_8));
byte[] md = messageDigest.digest();
return getHexString(hexDigits, md);
}
private static String getHexString(char[] hexDigits, byte[] md) {
int j = md.length;
char[] buf = new char[j * 2];
int k = 0;
for (byte byteO : md) {
buf[k++] = hexDigits[byteO >>> 4 & 0xf];
buf[k++] = hexDigits[byteO & 0xf];
}
return new String(buf);
}
}

View File

@ -0,0 +1,44 @@
package top.octopusyan.utils;
import com.google.code.kaptcha.Producer;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import java.awt.image.BufferedImage;
import java.util.Properties;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 验证码图片生成
* @create : 2022-4-3 23:34
*/
public class KaptchaUtil {
public static volatile KaptchaUtil util;
private DefaultKaptcha defaultKaptcha;
private KaptchaUtil(DefaultKaptcha kaptcha){
this.defaultKaptcha = kaptcha;
}
public static KaptchaUtil getInstance() {
Properties properties = new Properties();
properties.put("kaptcha.border", "no");
properties.put("kaptcha.textproducer.font.color", "black");
properties.put("kaptcha.textproducer.char.space", "5");
properties.put("kaptcha.textproducer.font.names", "Arial,Courier,cmr10,宋体,楷体,微软雅黑");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
if(util == null) util = new KaptchaUtil(defaultKaptcha);
return util;
}
public BufferedImage getCaptcha(String code) {
return util.defaultKaptcha.createImage(code);
}
public String createCode(){
return util.defaultKaptcha.createText();
}
}

View File

@ -0,0 +1,84 @@
package top.octopusyan.utils;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 加载等待弹窗
* @create : 2022-4-6 10:48
*/
public class Loading {
protected Stage stage;
protected StackPane root;
protected Label messageLb;
protected ImageView loadingView = new ImageView(new Image("https://blog-static.cnblogs.com/files/miaoqx/loading.gif"));
public Loading(Stage owner) {
messageLb = new Label("请耐心等待...");
messageLb.setFont(Font.font(20));
root = new StackPane();
root.setMouseTransparent(true);
root.setPrefSize(owner.getWidth(), owner.getHeight());
root.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.3), null, null)));
root.getChildren().addAll(loadingView, messageLb);
Scene scene = new Scene(root);
scene.setFill(Color.TRANSPARENT);
stage = new Stage();
stage.setX(owner.getX());
stage.setY(owner.getY());
stage.setScene(scene);
stage.setResizable(false);
stage.initOwner(owner);
stage.initStyle(StageStyle.TRANSPARENT);
stage.initModality(Modality.APPLICATION_MODAL);
stage.getIcons().addAll(owner.getIcons());
stage.setX(owner.getX());
stage.setY(owner.getY());
stage.setHeight(owner.getHeight());
stage.setWidth(owner.getWidth());
}
// 更改信息
public Loading showMessage(String message) {
Platform.runLater(() -> messageLb.setText(message));
return this;
}
// 更改信息
public Loading image(Image image) {
Platform.runLater(() -> loadingView.imageProperty().set(image));
return this;
}
// 显示
public void show() {
Platform.runLater(() -> stage.show());
}
// 关闭
public void closeStage() {
Platform.runLater(() -> stage.close());
}
// 是否正在展示
public boolean showing(){
return stage.isShowing();
}
}

View File

@ -0,0 +1,162 @@
package top.octopusyan.utils;
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.http.Api;
import top.octopusyan.http.request.ProxySetup;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author : octopus yan
* @email : octopus_yan@foxmail.com
* @description : 代理设置工具
* @create : 2022-4-5 09:56
*/
public class ProxyUtil {
private static String csrf;
/**
* 初始化隧道设置
*/
public static ProxySetup initProxy() {
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.setUse_compression(true);
setup.setUse_encryption(true);
return setup;
}
public static void delete(Integer id) {
EasyHttp.builder()
.api(Api.DeleteProxy())
.pathParam(id, csrf)
.request(result -> { });
}
/**
* 获取隧道详情信息
* @return
*/
public static ProxySetup info(ProxySetup setup) {
String html;
try {
html = EasyHttp.builder()
.api(Api.ProxyInfo())
.pathParam(setup.getId())
.execute(new ResponseClass<String>() {
});
} catch (Exception e) {
e.printStackTrace();
return null;
}
Document doc = Jsoup.parse(html);
Elements select = doc.select("tbody > tr > td");
String serverStr = select.get(0).text();
String localIp = select.get(3).text();
String localPort = select.get(4).text();
String webPort = select.get(5).text();
String useEnc = select.get(6).text();
String useCom = select.get(7).text();
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));
});
setup.setLocal_ip(localIp);
setup.setLocal_port(Integer.parseInt(localPort));
setup.setRemote_port("".equals(webPort) ? null : Integer.parseInt(webPort));
setup.setDomain(domain);
setup.setUse_compression(useCom.equals("启用"));
setup.setUse_encryption(useEnc.equals("启用"));
setup.setStatus(status.equals("启用"));
return setup;
}
public static int randomPort(){
try {
return EasyHttp.builder()
.api(Api.RandomPort())
.execute(new ResponseClass<Integer>() { });
} catch (Exception e) {
e.printStackTrace();
return (int) (Math.random() * 31000);
}
}
/**
* 获取用户隧道列表
*/
public static void getList(OnHttpListener<List<ProxySetup>> listener) {
EasyHttp.builder()
.api(Api.ProxyList)
.request(new OnHttpListener<String>() {
@Override
public void onSucceed(String result) {
Document doc = Jsoup.parse(result);
Elements elementsByClass = doc.select("table > tbody > tr");
List<ProxySetup> proxySetupList = elementsByClass.stream().map(element -> {
ProxySetup proxySetup = new ProxySetup();
Elements tds = element.getElementsByTag("td");
proxySetup.setId(Integer.parseInt(tds.get(0).text()));
proxySetup.setProxy_name(tds.get(1).text());
proxySetup.setProxy_type(tds.get(2).text());
proxySetup.setSort(Integer.parseInt(tds.get(5).text()));
return proxySetup;
}).collect(Collectors.toList());
listener.onSucceed(proxySetupList);
}
});
}
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 String getCsrf() {
return csrf;
}
public static void setCsrf(String htmlStr) {
int i = htmlStr.indexOf("var csrf_token = \"");
int i1 = htmlStr.indexOf("\"", i + 18);
ProxyUtil.csrf = htmlStr.substring(i + 18, i1);
}
}

View File

@ -0,0 +1,64 @@
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

@ -0,0 +1,47 @@
/* 圆角 */
#loginBkgPane, #loginMainPane {
-fx-background-radius: 14;
}
#loginTopPane {
-fx-background-radius: 14 14 0 0;
}
#loginBottomPane {
-fx-background-radius: 0 0 14 14;
}
/* 字体 */
#loginBtn {
-fx-font-family: "Microsoft YaHei";
-fx-text-fill: white;
}
/* 背景布局 */
#loginBkgPane {
-fx-image: url('../image/common_pane_bkg.gif');
}
/* 功能布局 */
#loginBottomPane {
-fx-background-color: rgba(243, 242, 242, 0.70);
}
/* 文本输入框点选状态下划线 */
#accountTextField, #pwdTextField, #seePwdTextField {
-jfx-focus-color: rgb(138, 103, 255);
}
#seePwdIconBtn{
-fx-opacity: 0.4;
}
/* 登录按钮 */
#loginBtn {
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
}
/* 注册按钮 */
#registerBtn:hover {
-fx-text-fill: #3e7398;
}

View File

@ -0,0 +1,108 @@
#MainPane {
-fx-background-color: rgb(240, 240, 240);
-fx-background-radius: 14 14 14 14;
}
#titleLable, #closeBtn, #minimizeBtn {
-fx-text-fill: black;
}
#minimizeBtn:hover {
-fx-background-color: rgba(148, 148, 148, 0.40);
}
#titleLable:disabled {
-fx-font-size: 18;
-fx-opacity: 0.4;
}
/* 布局边框 */
.mainPane, #addProxyBtn , .jfx-list-view{
-fx-border-color: linear-gradient(#57b4f2, #9198e5);
}
/* 文本框边框 */
.jfx-text-field, .jfx-combo-box {
-jfx-focus-color: #00000000;
-jfx-unfocus-color: #00000000;
}
/* 文本框 */
.inputText, .inputText-left, .inputText-right{
-fx-border-style: solid;
-fx-border-color: linear-gradient(#57b4f2, #9198e5);
-fx-border-radius: 5;
}
.inputText-left {
-fx-border-radius: 5 0 0 5;
}
.inputText-right {
-fx-border-radius: 0 5 5 0;
}
/* 服务线路 */
#proxyServerCB {
-fx-font-size: 16;
-fx-text-fill: #757575;
}
/* TODO 隧道列表 */
#proxyListView .list-cell:selected {
-fx-text-fill: white;
-fx-background-color: linear-gradient(#9198e5, #57b4f2);
}
.proxyListItem-run FontIcon {
-fx-icon-color: linear-gradient(#95f257, #91e5ac);
}
.proxyListItem-stop FontIcon {
-fx-icon-color: linear-gradient(#f25757, #e591b1);
}
.proxyListItem-close FontIcon {
-fx-icon-color: linear-gradient(grey, #524e50);
}
/* 面板背景 */
#proxySetupPane, #proxyListPane {
-fx-background-color: white;
}
#MainBottomPane {
-fx-padding: 10;
}
/* 代理设置-标签 */
.proxySetupLabel {
-fx-font-size: 15px;
-fx-font-family: "Microsoft YaHei";
}
/* 隧道类型 */
#proxyProtocolCombox {
-fx-font-size: 14px;
-fx-font-family: "Microsoft YaHei";
}
/* 自定义域名 */
#customizeDomainBtn {
-fx-text-fill: linear-gradient(#57b4f2, #9198e5);
}
/* 添加隧道 */
#addProxyBtn {
-fx-border-width: 0 1 1;
-fx-font-size: 14px;
-fx-font-family: "Microsoft YaHei";
}
/* 启动 */
#startProxyBtn {
-fx-font-size: 16px;
-fx-font-family: "Microsoft YaHei";
}
.startProxyBtn {
-fx-text-fill: white;
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
}
.stopProxyBtn {
-fx-text-fill: white;
-fx-background-color: linear-gradient(#f25757, #e591b1);
}
/* 修改Tab的背景颜色 */
.jfx-tab-pane .tab-header-background {
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
}

View File

@ -0,0 +1,34 @@
/* 圆角 */
#registMainPane {
-fx-background-radius: 14;
}
#registBottomPane {
-fx-background-radius: 0 0 14 14;
}
/* 字体 */
#registerBtn {
-fx-font-family: "Microsoft YaHei";
-fx-text-fill: white;
}
/* 背景布局 */
#registBkgPane {
-fx-image: url('../image/common_pane_bkg.gif');
}
/* 主布局背景 */
#registBottomPane {
-fx-background-color: rgba(243, 242, 242, 0.70);
}
/* 注册按钮 */
#registerBtn {
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
}
/* 登录按钮 */
#loginBtn:hover {
-fx-text-fill: #3e7398;
}

View File

@ -0,0 +1,35 @@
.root, #root{
-fx-background-radius: 14;
}
#titleLable, #closeBtn, #minimizeBtn{
-fx-font-family: "Microsoft YaHei";
-fx-text-fill: white;
}
#titleLable {
-fx-background-radius: 14 0 0 0;
}
#minimizeBtn {
-fx-background-radius: 0 14 0 14;
}
#closeBtn {
-fx-background-radius: 0 14 0 14;
}
/* 根布局阴影 */
#root {
-fx-background-color: rgba(255, 255, 255, 1);
-fx-effect: dropshadow(gaussian, black, 15, 0, 0, 0);
-fx-background-insets: 50;
}
#closeBtn:hover {
-fx-background-color: rgba(255, 0, 0, 0.70);
}
#minimizeBtn:hover {
-fx-background-color: rgba(243, 242, 242, 0.20);
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXCheckBox?>
<?import com.jfoenix.controls.JFXPasswordField?>
<?import com.jfoenix.controls.JFXTextField?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.text.Font?>
<?import org.kordamp.ikonli.javafx.FontIcon?>
<StackPane fx:id="root" prefHeight="330.0" prefWidth="430.0" stylesheets="@../css/login.css"
xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="top.octopusyan.controller.LoginController">
<ImageView fx:id="loginBkgPane" fitHeight="330" fitWidth="430">
<clip>
<Rectangle height="330" width="430">
<arcHeight>28</arcHeight>
<arcWidth>28</arcWidth>
</Rectangle>
</clip>
</ImageView>
<VBox fx:id="loginMainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="330.0" prefWidth="430.0">
<AnchorPane fx:id="loginTopPane" prefHeight="130.0" prefWidth="430.0">
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="64.0" text="Yan Frp" />
<JFXButton fx:id="minimizeBtn" layoutX="360.0" prefHeight="35.0" prefWidth="36.0" text="—"/>
<JFXButton fx:id="closeBtn" layoutX="395.0" prefHeight="35.0" prefWidth="36.0" text="X"/>
</AnchorPane>
<AnchorPane fx:id="loginBottomPane" prefHeight="200.0" prefWidth="430.0">
<VBox layoutX="96.0" layoutY="24.0" prefHeight="112.0" prefWidth="237.0">
<AnchorPane prefHeight="100.0" prefWidth="237.0">
<JFXButton fx:id="accountIconBtn" disable="true" prefHeight="30.0" text="">
<graphic>
<FontIcon fx:id="accountIcon" iconLiteral="fa-laptop" iconSize="17"/>
</graphic>
</JFXButton>
<JFXTextField fx:id="accountTextField" layoutX="30.0" prefHeight="20.0" prefWidth="208.0"
promptText="请输入账号">
<HBox.margin>
<Insets top="1.0"/>
</HBox.margin>
<font>
<Font size="15.0"/>
</font>
</JFXTextField>
</AnchorPane>
<AnchorPane prefHeight="100.0" prefWidth="237.0">
<JFXButton fx:id="pwdIconBtn" disable="true" prefHeight="30.0" text="">
<graphic>
<FontIcon fx:id="pwdIcon" iconLiteral="fa-expeditedssl" iconSize="17"/>
</graphic>
</JFXButton>
<JFXPasswordField fx:id="passwordTextField" layoutX="30.0" prefHeight="20.0" prefWidth="208.0"
promptText="请输入密码">
<font>
<Font size="15.0"/>
</font>
</JFXPasswordField>
<JFXTextField fx:id="seePwdTextField" layoutX="30.0" prefHeight="20.0" prefWidth="208.0"
promptText="请输入密码">
<font>
<Font size="15.0"/>
</font>
</JFXTextField>
<JFXButton fx:id="seePwdIconBtn" layoutX="210.0" layoutY="5.0" text="">
<graphic>
<FontIcon fx:id="seePwdIcon" iconLiteral="fa-eye" iconSize="17"/>
</graphic>
</JFXButton>
</AnchorPane>
<HBox alignment="CENTER" prefHeight="30.0" prefWidth="237.0">
<JFXCheckBox fx:id="autoLoginCBox" checkedColor="#57b4f2" prefHeight="18.0" prefWidth="49.0"
text="自动登录">
<HBox.margin>
<Insets right="20.0"/>
</HBox.margin>
<font>
<Font size="11.5"/>
</font>
</JFXCheckBox>
<JFXCheckBox fx:id="rememberCBox" checkedColor="#57b4f2" prefHeight="18.0" prefWidth="0.0"
text="记住密码">
<HBox.margin>
<Insets right="5.0"/>
</HBox.margin>
<font>
<Font size="11.5"/>
</font>
</JFXCheckBox>
<JFXButton alignment="TOP_CENTER" contentDisplay="RIGHT" prefHeight="23.0" prefWidth="64.0"
text="找回密码" textAlignment="RIGHT" textFill="#00000078">
<font>
<Font size="11.0"/>
</font>
</JFXButton>
</HBox>
</VBox>
<JFXButton fx:id="loginBtn" buttonType="RAISED" defaultButton="true" layoutX="96.0" layoutY="146.0"
prefHeight="35.0" prefWidth="237.0" text="登录" textFill="WHITE">
<font>
<Font size="15.0"/>
</font>
</JFXButton>
<JFXButton fx:id="registerBtn" alignment="TOP_CENTER" contentDisplay="RIGHT" layoutX="6.0" layoutY="170.0"
prefHeight="23.0" prefWidth="64.0" text="注册账号" textAlignment="RIGHT" textFill="#00000078">
<font>
<Font size="11.0"/>
</font>
</JFXButton>
</AnchorPane>
</VBox>
</StackPane>

View File

@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXComboBox?>
<?import com.jfoenix.controls.JFXListView?>
<?import com.jfoenix.controls.JFXRadioButton?>
<?import com.jfoenix.controls.JFXTabPane?>
<?import com.jfoenix.controls.JFXTextField?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?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">
<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" />
</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>
<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>
<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 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.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" />
</HBox.margin>
</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="协议类型">
<HBox.margin>
<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>
</HBox>
<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>
<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>
</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.margin>
<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">
<font>
<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">
<font>
<Font size="14.0" />
</font>
</JFXTextField>
</HBox>
<JFXButton fx:id="customizeDomainBtn" prefHeight="Infinity" text="自定义">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</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="用户名 (可选)">
<HBox.margin>
<Insets right="20.0" />
</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>
</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" />
<JFXButton fx:id="addProxyBtn" prefHeight="45.0" prefWidth="Infinity" text="新增连接">
<graphic>
<FontIcon iconLiteral="fa-plus" />
</graphic>
</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>
<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" />
</padding>
</VBox>
</Tab>
<Tab text="使用场景">
<VBox minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
</VBox>
</Tab>
</tabs>
</JFXTabPane>
<JFXButton layoutX="480.0" layoutY="-1.0" prefHeight="35.0" prefWidth="26.0" text="">
<graphic>
<FontIcon iconColor="white" iconLiteral="fa-trash-o" iconSize="18" />
</graphic>
</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>
</VBox>
</children>
</GridPane>
</VBox>
</StackPane>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.text.Font?>
<?import org.kordamp.ikonli.javafx.FontIcon?>
<Label text=" 默认连接" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
<graphic>
<FontIcon iconLiteral="fa-circle" iconSize="14"/>
</graphic>
<font>
<Font size="16.0"/>
</font>
<padding>
<Insets left="10.0"/>
</padding>
</Label>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXPasswordField?>
<?import com.jfoenix.controls.JFXTextField?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.text.Font?>
<StackPane fx:id="root" prefHeight="330.0" prefWidth="430.0" stylesheets="@../css/register.css" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.octopusyan.controller.RegisterController">
<ImageView fx:id="registBkgPane" fitHeight="330.0" fitWidth="430.0">
<clip>
<Rectangle height="330" width="430">
<arcHeight>28</arcHeight>
<arcWidth>28</arcWidth>
</Rectangle>
</clip>
</ImageView>
<VBox fx:id="registMainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="330.0" prefWidth="430.0">
<AnchorPane fx:id="registTopPane" prefHeight="130.0" prefWidth="430.0">
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="64.0" text="Yan Frp" />
<JFXButton fx:id="minimizeBtn" layoutX="360.0" prefHeight="35.0" prefWidth="36.0" text="—" />
<JFXButton fx:id="closeBtn" layoutX="395.0" prefHeight="35.0" prefWidth="36.0" text="X" />
</AnchorPane>
<AnchorPane fx:id="registBottomPane" prefHeight="200.0" prefWidth="430.0">
<JFXTextField fx:id="accooundTextField" labelFloat="true" layoutX="25.0" layoutY="25.0" prefHeight="29.0" prefWidth="279.0" promptText="请输入6-18位账号支持大小写数字下划线">
<font>
<Font size="14.0" />
</font>
</JFXTextField>
<JFXTextField fx:id="emailTf" labelFloat="true" layoutX="25.0" layoutY="90.0" promptText="请输入邮箱">
<font>
<Font size="14.0" />
</font>
</JFXTextField>
<HBox layoutX="25.0" layoutY="145.0" prefHeight="30.0" prefWidth="171.0">
<JFXTextField fx:id="emailCheckCodeTf" prefHeight="30.0" prefWidth="92.0" promptText="6位验证号码">
<font>
<Font size="14.0" />
</font>
</JFXTextField>
<JFXButton fx:id="sendCheckCodeBtn" buttonType="RAISED" layoutX="75.0" prefHeight="30.0" text="发送验证码" />
</HBox>
<JFXPasswordField fx:id="passwordTextField" labelFloat="true" layoutX="233.0" layoutY="70.0" promptText="请输入密码不少于6位">
<font>
<Font size="14.0" />
</font>
</JFXPasswordField>
<JFXButton fx:id="registerBtn" buttonType="RAISED" layoutX="227.0" layoutY="127.0" prefHeight="51.0" prefWidth="177.0" text="注册" textFill="WHITE">
<font>
<Font size="15.0" />
</font>
</JFXButton>
<JFXButton fx:id="loginBtn" layoutX="306.0" layoutY="24.0" text="已有账号,去登录" textFill="#00000080" />
</AnchorPane>
</VBox>
</StackPane>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>febs</contextName>
<property name="log.path" value="log" />
<property name="log.maxHistory" value="30" />
<property name="log.colorPattern" value="%date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<property name="log.pattern" value="%date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
<!--输出到控制台-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<Encoding>UTF-8</Encoding>
<encoder>
<pattern>${log.colorPattern}</pattern>
</encoder>
<!--
日志输出格式:%d表示日期时间%thread表示线程名%-5level级别从左显示5个字符宽度
%logger{50} 表示logger名字最长50个字符否则按照句点分割。 %msg日志消息%n是换行符
-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${log.colorPattern}</pattern>
</layout>
</appender>
<!--输出到文件-->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/info/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<MaxHistory>${log.maxHistory}</MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- 日志文件的最大大小 -->
<maxFileSize>5MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- 日志文件的最大大小 -->
<maxFileSize>5MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="debug">
<appender-ref ref="console" />
</root>
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
</configuration>