From 8c41c8392b1bf5dbf3dc0658382bcb9137b3c06f Mon Sep 17 00:00:00 2001
From: octopusYan <935540320@qq.com>
Date: Wed, 6 Apr 2022 17:49:00 +0800
Subject: [PATCH] init
---
.idea/.gitignore | 0
.idea/compiler.xml | 17 +
.idea/cssdialects.xml | 6 +
.idea/encodings.xml | 8 +
.idea/inspectionProfiles/Project_Default.xml | 36 ++
.idea/jarRepositories.xml | 25 +
.idea/misc.xml | 13 +
.idea/modules.xml | 8 +
.idea/vcs.xml | 6 +
YanFrp.iml | 50 ++
pom.xml | 182 ++++++
.../top/octopusyan/YanFrpApplication.java | 97 +++
.../top/octopusyan/base/BaseController.java | 169 +++++
.../java/top/octopusyan/base/BaseWindow.java | 18 +
.../java/top/octopusyan/common/http/Demo.java | 57 ++
.../top/octopusyan/common/http/EasyHttp.java | 575 ++++++++++++++++++
.../top/octopusyan/common/http/EasyUtils.java | 326 ++++++++++
.../octopusyan/common/http/HttpConfig.java | 218 +++++++
.../top/octopusyan/common/http/HttpLog.java | 61 ++
.../common/http/annotation/HttpHeader.java | 14 +
.../common/http/annotation/HttpIgnore.java | 14 +
.../common/http/annotation/HttpRename.java | 20 +
.../common/http/api/NotParamApi.java | 19 +
.../octopusyan/common/http/api/ParamApi.java | 19 +
.../common/http/api/PathParamApi.java | 19 +
.../common/http/api/RequestApi.java | 34 ++
.../common/http/callback/BaseCallback.java | 72 +++
.../common/http/callback/NormalCallback.java | 61 ++
.../common/http/config/BodyType.java | 20 +
.../common/http/config/ContentType.java | 15 +
.../common/http/config/HttpConstant.java | 21 +
.../common/http/config/ILogStrategy.java | 109 ++++
.../common/http/config/LogStrategy.java | 85 +++
.../http/exception/CancelException.java | 15 +
.../common/http/exception/DataException.java | 15 +
.../common/http/exception/HttpException.java | 27 +
.../http/exception/NetworkException.java | 15 +
.../http/exception/ResponseException.java | 25 +
.../http/exception/ResultException.java | 24 +
.../http/exception/ServerException.java | 15 +
.../http/exception/TimeoutException.java | 15 +
.../common/http/exception/TokenException.java | 15 +
.../common/http/listener/OnHttpListener.java | 32 +
.../common/http/model/CallProxy.java | 92 +++
.../common/http/model/HttpHeaders.java | 50 ++
.../common/http/model/HttpParams.java | 61 ++
.../common/http/model/JsonBody.java | 74 +++
.../common/http/model/ResponseClass.java | 10 +
.../common/http/model/UpdateBody.java | 67 ++
.../common/http/request/IRequestHandler.java | 39 ++
.../common/http/request/RequestHandler.java | 176 ++++++
.../common/http/response/BaseRest.java | 46 ++
.../common/http/ssl/HttpSslConfig.java | 29 +
.../common/http/ssl/HttpSslFactory.java | 169 +++++
.../http/ssl/UnSafeHostnameVerifier.java | 21 +
.../common/http/ssl/UnSafeTrustManager.java | 24 +
src/main/java/top/octopusyan/common/test.java | 10 +
.../top/octopusyan/config/ProxyConfig.java | 57 ++
.../controller/LoginController.java | 254 ++++++++
.../octopusyan/controller/MainController.java | 372 +++++++++++
.../controller/RegisterController.java | 196 ++++++
src/main/java/top/octopusyan/http/Api.java | 80 +++
.../octopusyan/http/OkHttpClientConfig.java | 140 +++++
.../octopusyan/http/request/LoginParam.java | 19 +
.../octopusyan/http/request/ProxySetup.java | 31 +
.../http/request/RegisterParam.java | 19 +
.../http/request/SendEmailCheckParam.java | 16 +
.../octopusyan/manager/ThreadPoolManager.java | 38 ++
.../java/top/octopusyan/utils/AlertUtil.java | 205 +++++++
.../top/octopusyan/utils/ApplicatonStore.java | 102 ++++
.../top/octopusyan/utils/EncryptionUtil.java | 458 ++++++++++++++
.../top/octopusyan/utils/KaptchaUtil.java | 44 ++
.../java/top/octopusyan/utils/Loading.java | 84 +++
.../java/top/octopusyan/utils/ProxyUtil.java | 162 +++++
.../top/octopusyan/utils/TextValidate.java | 64 ++
src/main/resources/css/login.css | 47 ++
src/main/resources/css/main.css | 108 ++++
src/main/resources/css/register.css | 34 ++
src/main/resources/css/root.css | 35 ++
src/main/resources/fxml/login.fxml | 120 ++++
src/main/resources/fxml/main.fxml | 218 +++++++
src/main/resources/fxml/proxyItem.fxml | 18 +
src/main/resources/fxml/register.fxml | 61 ++
src/main/resources/image/common_pane_bkg.gif | Bin 0 -> 2819720 bytes
src/main/resources/image/icon.png | Bin 0 -> 3962 bytes
src/main/resources/logback.xml | 70 +++
86 files changed, 6512 insertions(+)
create mode 100644 .idea/.gitignore
create mode 100644 .idea/compiler.xml
create mode 100644 .idea/cssdialects.xml
create mode 100644 .idea/encodings.xml
create mode 100644 .idea/inspectionProfiles/Project_Default.xml
create mode 100644 .idea/jarRepositories.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/vcs.xml
create mode 100644 YanFrp.iml
create mode 100644 pom.xml
create mode 100644 src/main/java/top/octopusyan/YanFrpApplication.java
create mode 100644 src/main/java/top/octopusyan/base/BaseController.java
create mode 100644 src/main/java/top/octopusyan/base/BaseWindow.java
create mode 100644 src/main/java/top/octopusyan/common/http/Demo.java
create mode 100644 src/main/java/top/octopusyan/common/http/EasyHttp.java
create mode 100644 src/main/java/top/octopusyan/common/http/EasyUtils.java
create mode 100644 src/main/java/top/octopusyan/common/http/HttpConfig.java
create mode 100644 src/main/java/top/octopusyan/common/http/HttpLog.java
create mode 100644 src/main/java/top/octopusyan/common/http/annotation/HttpHeader.java
create mode 100644 src/main/java/top/octopusyan/common/http/annotation/HttpIgnore.java
create mode 100644 src/main/java/top/octopusyan/common/http/annotation/HttpRename.java
create mode 100644 src/main/java/top/octopusyan/common/http/api/NotParamApi.java
create mode 100644 src/main/java/top/octopusyan/common/http/api/ParamApi.java
create mode 100644 src/main/java/top/octopusyan/common/http/api/PathParamApi.java
create mode 100644 src/main/java/top/octopusyan/common/http/api/RequestApi.java
create mode 100644 src/main/java/top/octopusyan/common/http/callback/BaseCallback.java
create mode 100644 src/main/java/top/octopusyan/common/http/callback/NormalCallback.java
create mode 100644 src/main/java/top/octopusyan/common/http/config/BodyType.java
create mode 100644 src/main/java/top/octopusyan/common/http/config/ContentType.java
create mode 100644 src/main/java/top/octopusyan/common/http/config/HttpConstant.java
create mode 100644 src/main/java/top/octopusyan/common/http/config/ILogStrategy.java
create mode 100644 src/main/java/top/octopusyan/common/http/config/LogStrategy.java
create mode 100644 src/main/java/top/octopusyan/common/http/exception/CancelException.java
create mode 100644 src/main/java/top/octopusyan/common/http/exception/DataException.java
create mode 100644 src/main/java/top/octopusyan/common/http/exception/HttpException.java
create mode 100644 src/main/java/top/octopusyan/common/http/exception/NetworkException.java
create mode 100644 src/main/java/top/octopusyan/common/http/exception/ResponseException.java
create mode 100644 src/main/java/top/octopusyan/common/http/exception/ResultException.java
create mode 100644 src/main/java/top/octopusyan/common/http/exception/ServerException.java
create mode 100644 src/main/java/top/octopusyan/common/http/exception/TimeoutException.java
create mode 100644 src/main/java/top/octopusyan/common/http/exception/TokenException.java
create mode 100644 src/main/java/top/octopusyan/common/http/listener/OnHttpListener.java
create mode 100644 src/main/java/top/octopusyan/common/http/model/CallProxy.java
create mode 100644 src/main/java/top/octopusyan/common/http/model/HttpHeaders.java
create mode 100644 src/main/java/top/octopusyan/common/http/model/HttpParams.java
create mode 100644 src/main/java/top/octopusyan/common/http/model/JsonBody.java
create mode 100644 src/main/java/top/octopusyan/common/http/model/ResponseClass.java
create mode 100644 src/main/java/top/octopusyan/common/http/model/UpdateBody.java
create mode 100644 src/main/java/top/octopusyan/common/http/request/IRequestHandler.java
create mode 100644 src/main/java/top/octopusyan/common/http/request/RequestHandler.java
create mode 100644 src/main/java/top/octopusyan/common/http/response/BaseRest.java
create mode 100644 src/main/java/top/octopusyan/common/http/ssl/HttpSslConfig.java
create mode 100644 src/main/java/top/octopusyan/common/http/ssl/HttpSslFactory.java
create mode 100644 src/main/java/top/octopusyan/common/http/ssl/UnSafeHostnameVerifier.java
create mode 100644 src/main/java/top/octopusyan/common/http/ssl/UnSafeTrustManager.java
create mode 100644 src/main/java/top/octopusyan/common/test.java
create mode 100644 src/main/java/top/octopusyan/config/ProxyConfig.java
create mode 100644 src/main/java/top/octopusyan/controller/LoginController.java
create mode 100644 src/main/java/top/octopusyan/controller/MainController.java
create mode 100644 src/main/java/top/octopusyan/controller/RegisterController.java
create mode 100644 src/main/java/top/octopusyan/http/Api.java
create mode 100644 src/main/java/top/octopusyan/http/OkHttpClientConfig.java
create mode 100644 src/main/java/top/octopusyan/http/request/LoginParam.java
create mode 100644 src/main/java/top/octopusyan/http/request/ProxySetup.java
create mode 100644 src/main/java/top/octopusyan/http/request/RegisterParam.java
create mode 100644 src/main/java/top/octopusyan/http/request/SendEmailCheckParam.java
create mode 100644 src/main/java/top/octopusyan/manager/ThreadPoolManager.java
create mode 100644 src/main/java/top/octopusyan/utils/AlertUtil.java
create mode 100644 src/main/java/top/octopusyan/utils/ApplicatonStore.java
create mode 100644 src/main/java/top/octopusyan/utils/EncryptionUtil.java
create mode 100644 src/main/java/top/octopusyan/utils/KaptchaUtil.java
create mode 100644 src/main/java/top/octopusyan/utils/Loading.java
create mode 100644 src/main/java/top/octopusyan/utils/ProxyUtil.java
create mode 100644 src/main/java/top/octopusyan/utils/TextValidate.java
create mode 100644 src/main/resources/css/login.css
create mode 100644 src/main/resources/css/main.css
create mode 100644 src/main/resources/css/register.css
create mode 100644 src/main/resources/css/root.css
create mode 100644 src/main/resources/fxml/login.fxml
create mode 100644 src/main/resources/fxml/main.fxml
create mode 100644 src/main/resources/fxml/proxyItem.fxml
create mode 100644 src/main/resources/fxml/register.fxml
create mode 100644 src/main/resources/image/common_pane_bkg.gif
create mode 100644 src/main/resources/image/icon.png
create mode 100644 src/main/resources/logback.xml
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..9998fcf
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/cssdialects.xml b/.idea/cssdialects.xml
new file mode 100644
index 0000000..2e28c79
--- /dev/null
+++ b/.idea/cssdialects.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..63574ec
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..339315d
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..c09175e
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..5755a99
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..5002093
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/YanFrp.iml b/YanFrp.iml
new file mode 100644
index 0000000..811ef58
--- /dev/null
+++ b/YanFrp.iml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..08809d3
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,182 @@
+
+
+ 4.0.0
+
+ top.octopusyan
+ YanFrp
+ 1.0-SNAPSHOT
+ YanFrp
+
+
+ UTF-8
+ 5.8.1
+
+ 8.0.1
+ 4.8.1
+ 11.8
+ 2.6.0
+
+
+
+
+
+
+ com.jfoenix
+ jfoenix
+ 8.0.10
+
+
+
+ org.controlsfx
+ controlsfx
+ 11.0.3
+
+
+
+
+
+
+
+ org.kordamp.ikonli
+ ikonli-javafx
+ ${ikonli.version}
+
+
+
+ org.kordamp.ikonli
+ ikonli-fontawesome-pack
+ ${ikonli.version}
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.14
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.36
+
+
+ org.slf4j
+ slf4j-simple
+ 1.7.36
+ compile
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.12.0
+
+
+
+ cn.hutool
+ hutool-crypto
+ 5.8.0.M2
+
+
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ 1.70
+
+
+
+
+ com.alibaba
+ fastjson
+ 1.2.80
+
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ ${okhttp3.version}
+
+
+ com.squareup.okhttp3
+ logging-interceptor
+ ${okhttp3.version}
+
+
+ com.squareup.okio
+ okio
+ 2.8.0
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.version}
+ test
+
+
+
+
+ com.github.axet
+ kaptcha
+ 0.0.9
+
+
+
+
+
+ org.jsoup
+ jsoup
+ 1.14.3
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+ 8
+
+
+
+ org.openjfx
+ javafx-maven-plugin
+ 0.0.8
+
+
+
+ default-cli
+
+ top.octopusyan/top.octopusyan.YanFrpLauncher
+ app
+ app
+ app
+ true
+ true
+ true
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/YanFrpApplication.java b/src/main/java/top/octopusyan/YanFrpApplication.java
new file mode 100644
index 0000000..9f0e6ca
--- /dev/null
+++ b/src/main/java/top/octopusyan/YanFrpApplication.java
@@ -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);
+ }
+}
diff --git a/src/main/java/top/octopusyan/base/BaseController.java b/src/main/java/top/octopusyan/base/BaseController.java
new file mode 100644
index 0000000..cef2045
--- /dev/null
+++ b/src/main/java/top/octopusyan/base/BaseController.java
@@ -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
implements Initializable {
+
+ private double xOffSet = 0, yOffSet = 0;
+
+ private volatile Loading loading;
+
+ public void jumpTo(BaseController
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);
+ }
+}
diff --git a/src/main/java/top/octopusyan/base/BaseWindow.java b/src/main/java/top/octopusyan/base/BaseWindow.java
new file mode 100644
index 0000000..f01cb75
--- /dev/null
+++ b/src/main/java/top/octopusyan/base/BaseWindow.java
@@ -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 {
+
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/Demo.java b/src/main/java/top/octopusyan/common/http/Demo.java
new file mode 100644
index 0000000..591338b
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/Demo.java
@@ -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("/business/api", HttpConstant.Method.POST))
+ // 动态参数
+ .param("param")
+ // 异步请求
+ .request(new OnHttpListener() {
+ @Override
+ public void onSucceed(String result) {
+
+ }
+ });
+
+ try {
+ Integer num = EasyHttp.builder()
+ .api(new NotParamApi("/business/getnum", HttpConstant.Method.GET))
+ .execute(new ResponseClass() {
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/EasyHttp.java b/src/main/java/top/octopusyan/common/http/EasyHttp.java
new file mode 100644
index 0000000..6c65892
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/EasyHttp.java
@@ -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 {
+
+ /** 请求处理策略 */
+ 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 mRequestApi;
+
+ /** 请求参数 */
+ private Param param;
+
+ public static class Builder {
+ private String method;
+ private String apiPath;
+ private BodyType bodyType;
+
+ public ParamRequest api(ParamApi api) {
+ return new ParamRequest().api(api);
+ }
+
+ public UrlRequest api(NotParamApi api) {
+ return new UrlRequest().api(api);
+ }
+
+ public RestfulRequest api(PathParamApi api) {
+ return new RestfulRequest().api(api);
+ }
+
+ public EasyHttp api(RequestApi api) {
+ return new EasyHttp().api(api);
+ }
+
+ /**
+ * 无参数请求
+ */
+ public UrlRequest build(ResponseClass result) {
+ if(apiPath == null) throw new IllegalArgumentException("请求接口地址为空!");
+
+ NotParamApi requestApi = new NotParamApi<>(apiPath, method, bodyType);
+
+ return new UrlRequest().api(requestApi);
+ }
+
+ /**
+ * 带参请求
+ * @param param 请求参数
+ */
+ public ParamRequest build(Param param, ResponseClass result) {
+ ParamApi requestApi = new ParamApi<>(apiPath, method, bodyType);
+ return new ParamRequest().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 api(RequestApi api) {
+ this.mRequestApi = api;
+ return this;
+ }
+
+ protected EasyHttp param(Param param) {
+ this.param = param;
+ return this;
+ }
+
+ private RequestApi 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 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 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 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 extends EasyHttp {
+
+ @Override
+ public UrlRequest api(RequestApi api) {
+ super.api(api);
+ return this;
+ }
+ }
+
+ /**
+ * 路径参数请求
+ */
+ public static class RestfulRequest extends EasyHttp {
+
+ @Override
+ public RestfulRequest api(RequestApi api) {
+ super.api(api);
+ return this;
+ }
+
+ /**
+ * @param params 路径参数
+ */
+ public RestfulRequest pathParam(Object... params) {
+ super.path(MessageFormat.format(getPath(), params));
+ return this;
+ }
+ }
+
+ /**
+ *
+ * 带参数请求
+ *
+ * @param 参数类型
+ * @param 返回结果类型
+ */
+ public static class ParamRequest extends EasyHttp {
+
+ @Override
+ public ParamRequest api(RequestApi api) {
+ super.api(api);
+ return this;
+ }
+
+ @Override
+ public ParamRequest param(Param param) {
+ super.param(param);
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/EasyUtils.java b/src/main/java/top/octopusyan/common/http/EasyUtils.java
new file mode 100644
index 0000000..c5990b4
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/EasyUtils.java
@@ -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;
+ }
+ // Number:Long、Integer、Short、Double、Float、Byte
+ // CharSequence:String、StringBuilder、StringBuilder
+ 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 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
+ 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 beanToHashMap(Object object) {
+ if (object == null) {
+ return null;
+ }
+
+ Field[] fields = object.getClass().getDeclaredFields();
+ HashMap 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);
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/HttpConfig.java b/src/main/java/top/octopusyan/common/http/HttpConfig.java
new file mode 100644
index 0000000..9edbc18
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/HttpConfig.java
@@ -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 mParams;
+ /** 通用请求头 */
+ private HashMap 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();
+ mHeaders = new HashMap<>();
+ }
+ private HttpConfig() {
+ mParams = new HashMap();
+ 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 params) {
+ if (params == null) {
+ params = new HashMap();
+ }
+ mParams = params;
+ return this;
+ }
+
+ public HttpConfig setHeaders(HashMap headers) {
+ if (headers == null) {
+ headers = new HashMap<>();
+ }
+ mHeaders = headers;
+ return this;
+ }
+
+ public HashMap getParams() {
+ return mParams;
+ }
+
+ public HashMap 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() {
+ ;
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/HttpLog.java b/src/main/java/top/octopusyan/common/http/HttpLog.java
new file mode 100644
index 0000000..08b35a7
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/HttpLog.java
@@ -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);
+ }
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/annotation/HttpHeader.java b/src/main/java/top/octopusyan/common/http/annotation/HttpHeader.java
new file mode 100644
index 0000000..c164508
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/annotation/HttpHeader.java
@@ -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 {}
diff --git a/src/main/java/top/octopusyan/common/http/annotation/HttpIgnore.java b/src/main/java/top/octopusyan/common/http/annotation/HttpIgnore.java
new file mode 100644
index 0000000..83b08e1
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/annotation/HttpIgnore.java
@@ -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 {}
diff --git a/src/main/java/top/octopusyan/common/http/annotation/HttpRename.java b/src/main/java/top/octopusyan/common/http/annotation/HttpRename.java
new file mode 100644
index 0000000..eab82f2
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/annotation/HttpRename.java
@@ -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();
+}
diff --git a/src/main/java/top/octopusyan/common/http/api/NotParamApi.java b/src/main/java/top/octopusyan/common/http/api/NotParamApi.java
new file mode 100644
index 0000000..f2143a3
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/api/NotParamApi.java
@@ -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 extends RequestApi {
+ public NotParamApi(String api, String method) {
+ super(api, method);
+ }
+
+ public NotParamApi(String api, String method, BodyType bodyType) {
+ super(api, method, bodyType);
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/api/ParamApi.java b/src/main/java/top/octopusyan/common/http/api/ParamApi.java
new file mode 100644
index 0000000..522f1da
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/api/ParamApi.java
@@ -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 extends RequestApi {
+ public ParamApi(String method, String path) {
+ super(method, path);
+ }
+
+ public ParamApi(String api, String method, BodyType bodyType) {
+ super(api, method, bodyType);
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/api/PathParamApi.java b/src/main/java/top/octopusyan/common/http/api/PathParamApi.java
new file mode 100644
index 0000000..e34e8d5
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/api/PathParamApi.java
@@ -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 extends RequestApi {
+ public PathParamApi(String method, String path) {
+ super(method, path);
+ }
+
+ public PathParamApi(String api, String method, BodyType bodyType) {
+ super(api, method, bodyType);
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/api/RequestApi.java b/src/main/java/top/octopusyan/common/http/api/RequestApi.java
new file mode 100644
index 0000000..91cc1b0
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/api/RequestApi.java
@@ -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 {
+
+ /** 接口地址 必须以/
开头*/
+ String api;
+
+ /** 请求方式 */
+ String method;
+
+ /** 参数提交方式 默认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;
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/callback/BaseCallback.java b/src/main/java/top/octopusyan/common/http/callback/BaseCallback.java
new file mode 100644
index 0000000..706bef3
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/callback/BaseCallback.java
@@ -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);
+}
diff --git a/src/main/java/top/octopusyan/common/http/callback/NormalCallback.java b/src/main/java/top/octopusyan/common/http/callback/NormalCallback.java
new file mode 100644
index 0000000..f346ec0
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/callback/NormalCallback.java
@@ -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());
+ });
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/config/BodyType.java b/src/main/java/top/octopusyan/common/http/config/BodyType.java
new file mode 100644
index 0000000..bc5f8cf
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/config/BodyType.java
@@ -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
+}
diff --git a/src/main/java/top/octopusyan/common/http/config/ContentType.java b/src/main/java/top/octopusyan/common/http/config/ContentType.java
new file mode 100644
index 0000000..7c1cd9e
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/config/ContentType.java
@@ -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");
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/config/HttpConstant.java b/src/main/java/top/octopusyan/common/http/config/HttpConstant.java
new file mode 100644
index 0000000..054258a
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/config/HttpConstant.java
@@ -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";
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/config/ILogStrategy.java b/src/main/java/top/octopusyan/common/http/config/ILogStrategy.java
new file mode 100644
index 0000000..413180c
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/config/ILogStrategy.java
@@ -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();
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/config/LogStrategy.java b/src/main/java/top/octopusyan/common/http/config/LogStrategy.java
new file mode 100644
index 0000000..2f0e828
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/config/LogStrategy.java
@@ -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;
+ }
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/exception/CancelException.java b/src/main/java/top/octopusyan/common/http/exception/CancelException.java
new file mode 100644
index 0000000..fd3a8cb
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/exception/CancelException.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/exception/DataException.java b/src/main/java/top/octopusyan/common/http/exception/DataException.java
new file mode 100644
index 0000000..fc952cc
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/exception/DataException.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/exception/HttpException.java b/src/main/java/top/octopusyan/common/http/exception/HttpException.java
new file mode 100644
index 0000000..5fa9ae3
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/exception/HttpException.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/exception/NetworkException.java b/src/main/java/top/octopusyan/common/http/exception/NetworkException.java
new file mode 100644
index 0000000..35a0d17
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/exception/NetworkException.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/exception/ResponseException.java b/src/main/java/top/octopusyan/common/http/exception/ResponseException.java
new file mode 100644
index 0000000..276e4d1
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/exception/ResponseException.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/exception/ResultException.java b/src/main/java/top/octopusyan/common/http/exception/ResultException.java
new file mode 100644
index 0000000..ab960ba
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/exception/ResultException.java
@@ -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 getData() {
+ return (T) mData;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/exception/ServerException.java b/src/main/java/top/octopusyan/common/http/exception/ServerException.java
new file mode 100644
index 0000000..845608c
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/exception/ServerException.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/exception/TimeoutException.java b/src/main/java/top/octopusyan/common/http/exception/TimeoutException.java
new file mode 100644
index 0000000..ca05ca1
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/exception/TimeoutException.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/exception/TokenException.java b/src/main/java/top/octopusyan/common/http/exception/TokenException.java
new file mode 100644
index 0000000..136e7f0
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/exception/TokenException.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/listener/OnHttpListener.java b/src/main/java/top/octopusyan/common/http/listener/OnHttpListener.java
new file mode 100644
index 0000000..305e82b
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/listener/OnHttpListener.java
@@ -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 {
+
+ /**
+ * 请求开始
+ */
+ default void onStart(Call call) {}
+
+ /**
+ * 请求成功
+ */
+ void onSucceed(T result);
+
+ /**
+ * 请求出错
+ */
+ default void onFail(Exception e){};
+
+ /**
+ * 请求结束
+ */
+ default void onEnd(Call call) {}
+}
diff --git a/src/main/java/top/octopusyan/common/http/model/CallProxy.java b/src/main/java/top/octopusyan/common/http/model/CallProxy.java
new file mode 100644
index 0000000..3fe720b
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/model/CallProxy.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/model/HttpHeaders.java b/src/main/java/top/octopusyan/common/http/model/HttpHeaders.java
new file mode 100644
index 0000000..dbe8502
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/model/HttpHeaders.java
@@ -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 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 getNames() {
+ return mHeaders.keySet();
+ }
+
+ public HashMap getHeaders() {
+ return mHeaders;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/model/HttpParams.java b/src/main/java/top/octopusyan/common/http/model/HttpParams.java
new file mode 100644
index 0000000..f181cf7
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/model/HttpParams.java
@@ -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 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 getNames() {
+ return mParams.keySet();
+ }
+
+ public HashMap getParams() {
+ return mParams;
+ }
+
+ public boolean isMultipart() {
+ return mMultipart;
+ }
+
+ public void setMultipart(boolean multipart) {
+ mMultipart = multipart;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/model/JsonBody.java b/src/main/java/top/octopusyan/common/http/model/JsonBody.java
new file mode 100644
index 0000000..5ed52c9
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/model/JsonBody.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/model/ResponseClass.java b/src/main/java/top/octopusyan/common/http/model/ResponseClass.java
new file mode 100644
index 0000000..baf4397
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/model/ResponseClass.java
@@ -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 {
+}
diff --git a/src/main/java/top/octopusyan/common/http/model/UpdateBody.java b/src/main/java/top/octopusyan/common/http/model/UpdateBody.java
new file mode 100644
index 0000000..ff3f0ab
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/model/UpdateBody.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/request/IRequestHandler.java b/src/main/java/top/octopusyan/common/http/request/IRequestHandler.java
new file mode 100644
index 0000000..25e3cfe
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/request/IRequestHandler.java
@@ -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 Request requestStart(RequestApi api, Request request) {
+ return request;
+ }
+
+ /**
+ * 请求成功时回调
+ *
+ * @param response 响应对象
+ * @param type 解析类型
+ * @return 返回结果
+ *
+ * @throws Exception 如果抛出则回调失败
+ */
+ Object requestSucceed(RequestApi api, Response response, Type type) throws Exception;
+
+ /**
+ * 请求失败
+ *
+ * @param e 错误对象
+ * @return 错误对象
+ */
+ Exception requestFail(RequestApi api, Exception e);
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/request/RequestHandler.java b/src/main/java/top/octopusyan/common/http/request/RequestHandler.java
new file mode 100644
index 0000000..a7b61c4
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/request/RequestHandler.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/response/BaseRest.java b/src/main/java/top/octopusyan/common/http/response/BaseRest.java
new file mode 100644
index 0000000..72b2966
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/response/BaseRest.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/top/octopusyan/common/http/ssl/HttpSslConfig.java b/src/main/java/top/octopusyan/common/http/ssl/HttpSslConfig.java
new file mode 100644
index 0000000..0469c12
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/ssl/HttpSslConfig.java
@@ -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;
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/ssl/HttpSslFactory.java b/src/main/java/top/octopusyan/common/http/ssl/HttpSslFactory.java
new file mode 100644
index 0000000..9049ef4
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/ssl/HttpSslFactory.java
@@ -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;
+ };
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/ssl/UnSafeHostnameVerifier.java b/src/main/java/top/octopusyan/common/http/ssl/UnSafeHostnameVerifier.java
new file mode 100644
index 0000000..61daa48
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/ssl/UnSafeHostnameVerifier.java
@@ -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;
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/http/ssl/UnSafeTrustManager.java b/src/main/java/top/octopusyan/common/http/ssl/UnSafeTrustManager.java
new file mode 100644
index 0000000..b61d6a8
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/http/ssl/UnSafeTrustManager.java
@@ -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[]{};
+ }
+}
diff --git a/src/main/java/top/octopusyan/common/test.java b/src/main/java/top/octopusyan/common/test.java
new file mode 100644
index 0000000..98369f9
--- /dev/null
+++ b/src/main/java/top/octopusyan/common/test.java
@@ -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 {
+}
diff --git a/src/main/java/top/octopusyan/config/ProxyConfig.java b/src/main/java/top/octopusyan/config/ProxyConfig.java
new file mode 100644
index 0000000..a078d12
--- /dev/null
+++ b/src/main/java/top/octopusyan/config/ProxyConfig.java
@@ -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 serverPath = new HashMap<>();
+ private static Map 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);
+ }
+}
diff --git a/src/main/java/top/octopusyan/controller/LoginController.java b/src/main/java/top/octopusyan/controller/LoginController.java
new file mode 100644
index 0000000..ce2546a
--- /dev/null
+++ b/src/main/java/top/octopusyan/controller/LoginController.java
@@ -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 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() {
+ @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(
+ "/?page=panel&module=addproxy",
+ HttpConstant.Method.GET
+ ))
+ .request(new OnHttpListener() {
+ @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();
+ }
+}
diff --git a/src/main/java/top/octopusyan/controller/MainController.java b/src/main/java/top/octopusyan/controller/MainController.java
new file mode 100644
index 0000000..41b57c2
--- /dev/null
+++ b/src/main/java/top/octopusyan/controller/MainController.java
@@ -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 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