init
This commit is contained in:
parent
8c4d21a00b
commit
8c41c8392b
0
.idea/.gitignore
vendored
Normal file
0
.idea/.gitignore
vendored
Normal file
17
.idea/compiler.xml
Normal file
17
.idea/compiler.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="true" />
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="YanFrp" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel>
|
||||
<module name="YanFrp" target="8" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
6
.idea/cssdialects.xml
Normal file
6
.idea/cssdialects.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CssDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources/css/login.css" dialect="CLASSIC" />
|
||||
</component>
|
||||
</project>
|
8
.idea/encodings.xml
Normal file
8
.idea/encodings.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
36
.idea/inspectionProfiles/Project_Default.xml
Normal file
36
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="TOP_LEVEL_CLASS_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="INNER_CLASS_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="METHOD_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="FIELD_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="IGNORE_DEPRECATED" value="false" />
|
||||
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
|
||||
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
|
||||
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
|
||||
<option name="myAdditionalJavadocTags" value="email,description,create" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
25
.idea/jarRepositories.xml
Normal file
25
.idea/jarRepositories.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="aliyun" />
|
||||
<option name="name" value="aliyun" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
13
.idea/misc.xml
Normal file
13
.idea/misc.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/YanFrp.iml" filepath="$PROJECT_DIR$/YanFrp.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
50
YanFrp.iml
Normal file
50
YanFrp.iml
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Maven: com.jfoenix:jfoenix:8.0.10" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.controlsfx:controlsfx:11.0.3" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-graphics:11.0.2" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-graphics:win:11.0.2" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-base:11.0.2" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-base:win:11.0.2" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-media:11.0.2" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-media:win:11.0.2" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-controls:11.0.2" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.openjfx:javafx-controls:win:11.0.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.kordamp.ikonli:ikonli-javafx:2.6.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.kordamp.ikonli:ikonli-core:2.6.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.kordamp.ikonli:ikonli-fontawesome-pack:2.6.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.14" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.36" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-simple:1.7.36" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.12.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: cn.hutool:hutool-crypto:5.8.0.M2" level="project" />
|
||||
<orderEntry type="library" name="Maven: cn.hutool:hutool-core:5.8.0.M2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.bouncycastle:bcprov-jdk15on:1.70" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.alibaba:fastjson:1.2.80" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.squareup.okhttp3:okhttp:4.8.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib:1.3.72" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains:annotations:13.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.squareup.okhttp3:logging-interceptor:4.8.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.squareup.okio:okio:2.8.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-api:5.8.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.8.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.apiguardian:apiguardian-api:1.1.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-engine:5.8.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-engine:1.8.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.github.axet:kaptcha:0.0.9" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.jhlabs:filters:2.0.235" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jsoup:jsoup:1.14.3" level="project" />
|
||||
</component>
|
||||
</module>
|
182
pom.xml
Normal file
182
pom.xml
Normal file
@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>top.octopusyan</groupId>
|
||||
<artifactId>YanFrp</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<name>YanFrp</name>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<junit.version>5.8.1</junit.version>
|
||||
|
||||
<datafx.version>8.0.1</datafx.version>
|
||||
<okhttp3.version>4.8.1</okhttp3.version>
|
||||
<feign.version>11.8</feign.version>
|
||||
<ikonli.version>2.6.0</ikonli.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- UI 库 -->
|
||||
<dependency>
|
||||
<groupId>com.jfoenix</groupId>
|
||||
<artifactId>jfoenix</artifactId>
|
||||
<version>8.0.10</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.controlsfx</groupId>
|
||||
<artifactId>controlsfx</artifactId>
|
||||
<version>11.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 图标库 -->
|
||||
<!-- https://kordamp.org/ikonli/ -->
|
||||
<!-- https://mvnrepository.com/artifact/org.kordamp.ikonli/ikonli-javafx -->
|
||||
<!-- javafx 支持 -->
|
||||
<dependency>
|
||||
<groupId>org.kordamp.ikonli</groupId>
|
||||
<artifactId>ikonli-javafx</artifactId>
|
||||
<version>${ikonli.version}</version>
|
||||
</dependency>
|
||||
<!-- fontawesome图标 java8 不支持 ikonli-fontawesome5-pack -->
|
||||
<dependency>
|
||||
<groupId>org.kordamp.ikonli</groupId>
|
||||
<artifactId>ikonli-fontawesome-pack</artifactId>
|
||||
<version>${ikonli.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.14</version>
|
||||
</dependency>
|
||||
|
||||
<!-- slf4j -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.36</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.36</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- commons-lang3 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.12.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>5.8.0.M2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 算法库 -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.70</version>
|
||||
</dependency>
|
||||
|
||||
<!-- json -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.80</version>
|
||||
</dependency>
|
||||
|
||||
<!-- okhttp -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>${okhttp3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>logging-interceptor</artifactId>
|
||||
<version>${okhttp3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okio</groupId>
|
||||
<artifactId>okio</artifactId>
|
||||
<version>2.8.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 测试 -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!--验证码-->
|
||||
<dependency>
|
||||
<groupId>com.github.axet</groupId>
|
||||
<artifactId>kaptcha</artifactId>
|
||||
<version>0.0.9</version>
|
||||
</dependency>
|
||||
|
||||
<!-- html 解析 -->
|
||||
<dependency>
|
||||
<!-- jsoup HTML parser library @ https://jsoup.org/ -->
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.14.3</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- Default configuration for running with: mvn clean javafx:run -->
|
||||
<id>default-cli</id>
|
||||
<configuration>
|
||||
<mainClass>top.octopusyan/top.octopusyan.YanFrpLauncher</mainClass>
|
||||
<launcher>app</launcher>
|
||||
<jlinkZipName>app</jlinkZipName>
|
||||
<jlinkImageName>app</jlinkImageName>
|
||||
<noManPages>true</noManPages>
|
||||
<stripDebug>true</stripDebug>
|
||||
<noHeaderFiles>true</noHeaderFiles>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
97
src/main/java/top/octopusyan/YanFrpApplication.java
Normal file
97
src/main/java/top/octopusyan/YanFrpApplication.java
Normal file
@ -0,0 +1,97 @@
|
||||
package top.octopusyan;
|
||||
|
||||
import com.sun.org.slf4j.internal.Logger;
|
||||
import com.sun.org.slf4j.internal.LoggerFactory;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import top.octopusyan.common.http.HttpConfig;
|
||||
import top.octopusyan.common.http.config.LogStrategy;
|
||||
import top.octopusyan.common.http.request.RequestHandler;
|
||||
import top.octopusyan.http.OkHttpClientConfig;
|
||||
import top.octopusyan.utils.AlertUtil;
|
||||
import top.octopusyan.utils.ApplicatonStore;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : YanFrp Application
|
||||
* @create : 2022-3-29 15:00
|
||||
*/
|
||||
public class YanFrpApplication extends Application {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(YanFrpApplication.class);
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
super.init();
|
||||
LOGGER.debug("init...");
|
||||
|
||||
// 网络请求设置
|
||||
HttpConfig.with(OkHttpClientConfig.httpClient())
|
||||
// 请求服务地址
|
||||
.serverPath("https://frp.octopusyan.top")
|
||||
// 是否打印日志
|
||||
.setLogEnabled(false)
|
||||
// 设置日志打印策略
|
||||
.setLogStrategy(new LogStrategy())
|
||||
// 设置请求处理策略
|
||||
.setHandler(new RequestHandler())
|
||||
// 设置请求重试次数
|
||||
.setRetryCount(3)
|
||||
// 启用配置
|
||||
.into();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) {
|
||||
LOGGER.debug("start...");
|
||||
|
||||
// TODO 全局异常处理... (emm有点草率,先这样了。。)
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> Platform.runLater(() -> showErrorDialog(t, e)));
|
||||
Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog);
|
||||
|
||||
// 初始化弹窗
|
||||
AlertUtil.initOwner(stage);
|
||||
// 初始化应用数据
|
||||
ApplicatonStore.setRegisterSuccess(false);
|
||||
|
||||
try {
|
||||
stage.initStyle(StageStyle.TRANSPARENT);
|
||||
StackPane root = new FXMLLoader(this.getClass().getResource("/fxml/login.fxml")).load();//底层面板
|
||||
Scene scene = new Scene(
|
||||
root,
|
||||
root.getPrefWidth() + 20,
|
||||
root.getPrefHeight() + 20,
|
||||
Color.TRANSPARENT
|
||||
);
|
||||
scene.getStylesheets().addAll(getClass().getResource("/css/root.css").toExternalForm());
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
|
||||
} catch (Throwable t) {
|
||||
showErrorDialog(Thread.currentThread(), t);
|
||||
}
|
||||
|
||||
LOGGER.debug("start end...");
|
||||
}
|
||||
|
||||
private void showErrorDialog(Thread t, Throwable e) {
|
||||
e.printStackTrace();
|
||||
AlertUtil.exceptionAlert(new Exception(e)).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
super.stop();
|
||||
LOGGER.debug("stop...");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Application.launch(YanFrpApplication.class, args);
|
||||
}
|
||||
}
|
169
src/main/java/top/octopusyan/base/BaseController.java
Normal file
169
src/main/java/top/octopusyan/base/BaseController.java
Normal file
@ -0,0 +1,169 @@
|
||||
package top.octopusyan.base;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.octopusyan.utils.Loading;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 通用视图控制器
|
||||
* @create : 2022-3-31 18:09
|
||||
*/
|
||||
public abstract class BaseController<P extends Pane> implements Initializable {
|
||||
|
||||
private double xOffSet = 0, yOffSet = 0;
|
||||
|
||||
private volatile Loading loading;
|
||||
|
||||
public void jumpTo(BaseController<P> controller){
|
||||
Scene scene = getRootPanel().getScene();
|
||||
try {
|
||||
Pane root = FXMLLoader.load(getClass().getResource(controller.getRootFxmlPath()));
|
||||
scene.setRoot(root);
|
||||
Stage stage = (Stage) scene.getWindow();
|
||||
stage.setWidth(root.getPrefWidth() + 20);
|
||||
stage.setHeight(root.getPrefHeight() + 20);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
// 全局窗口拖拽
|
||||
if (dragWindow()) {
|
||||
// 窗口拖拽
|
||||
getRootPanel().setOnMousePressed(event -> {
|
||||
xOffSet = event.getSceneX();
|
||||
yOffSet = event.getSceneY();
|
||||
});
|
||||
getRootPanel().setOnMouseDragged(event -> {
|
||||
Stage stage = (Stage) getRootPanel().getScene().getWindow();
|
||||
stage.setX(event.getScreenX() - xOffSet);
|
||||
stage.setY(event.getScreenY() - yOffSet);
|
||||
});
|
||||
}
|
||||
|
||||
// 这个位置的左边第一个 JFXBtn 会莫名其妙会的焦点效果,启动时禁用焦点,取消按钮效果
|
||||
getFirstBtn().setDisableVisualFocus(true);
|
||||
|
||||
// 最小化窗口
|
||||
getMinimizeBtn().setOnMouseClicked(event -> {
|
||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
|
||||
Stage stage = (Stage) getRootPanel().getScene().getWindow();
|
||||
stage.setIconified(true);
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭窗口
|
||||
getClooseBtn().setOnMouseClicked(event -> {
|
||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
|
||||
onDestroy();
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化数据
|
||||
initData();
|
||||
|
||||
// 初始化视图样式
|
||||
initViewStyle();
|
||||
|
||||
// 初始化视图事件
|
||||
initViewAction();
|
||||
}
|
||||
|
||||
public void showLoading(){
|
||||
showLoading(null);
|
||||
}
|
||||
|
||||
public void showLoading(String message){
|
||||
if(loading == null) loading = new Loading((Stage) getRootPanel().getScene().getWindow());
|
||||
if(!StringUtils.isEmpty(message))loading.showMessage(message);
|
||||
loading.show();
|
||||
}
|
||||
|
||||
public boolean isLoadShowing(){
|
||||
return loading != null && loading.showing();
|
||||
}
|
||||
|
||||
public void stopLoading(){
|
||||
loading.closeStage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 窗口拖拽设置
|
||||
*
|
||||
* @return 是否启用
|
||||
*/
|
||||
public abstract boolean dragWindow();
|
||||
|
||||
/**
|
||||
* 获取根布局
|
||||
*
|
||||
* @return 根布局对象
|
||||
*/
|
||||
public abstract P getRootPanel();
|
||||
|
||||
/**
|
||||
* 获取根布局
|
||||
*
|
||||
* @return 根布局对象
|
||||
*/
|
||||
@NotNull
|
||||
public abstract String getRootFxmlPath();
|
||||
|
||||
/**
|
||||
* 关闭按钮
|
||||
*/
|
||||
public abstract Button getClooseBtn();
|
||||
|
||||
/**
|
||||
* 最小化按钮
|
||||
*/
|
||||
public abstract Button getMinimizeBtn();
|
||||
|
||||
/**
|
||||
* 获取第一个按钮
|
||||
*/
|
||||
public abstract JFXButton getFirstBtn();
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
public abstract void initData();
|
||||
|
||||
/**
|
||||
* 视图样式
|
||||
*/
|
||||
public abstract void initViewStyle();
|
||||
|
||||
/**
|
||||
* 视图事件
|
||||
*/
|
||||
public abstract void initViewAction();
|
||||
|
||||
/**
|
||||
* 关闭窗口
|
||||
*/
|
||||
public void onDestroy(){
|
||||
Stage stage = (Stage) getRootPanel().getScene().getWindow();
|
||||
stage.hide();
|
||||
stage.close();
|
||||
Platform.exit();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
18
src/main/java/top/octopusyan/base/BaseWindow.java
Normal file
18
src/main/java/top/octopusyan/base/BaseWindow.java
Normal file
@ -0,0 +1,18 @@
|
||||
package top.octopusyan.base;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : Base Window
|
||||
* @create : 2022-4-1 11:26
|
||||
*/
|
||||
public class BaseWindow extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
|
||||
}
|
||||
}
|
57
src/main/java/top/octopusyan/common/http/Demo.java
Normal file
57
src/main/java/top/octopusyan/common/http/Demo.java
Normal file
@ -0,0 +1,57 @@
|
||||
package top.octopusyan.common.http;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import top.octopusyan.common.http.api.NotParamApi;
|
||||
import top.octopusyan.common.http.api.ParamApi;
|
||||
import top.octopusyan.common.http.config.HttpConstant;
|
||||
import top.octopusyan.common.http.config.LogStrategy;
|
||||
import top.octopusyan.common.http.listener.OnHttpListener;
|
||||
import top.octopusyan.common.http.model.ResponseClass;
|
||||
import top.octopusyan.common.http.request.RequestHandler;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description :
|
||||
* @create : 2022-4-3 22:24
|
||||
*/
|
||||
public class Demo {
|
||||
public static void main(String[] args) {
|
||||
// 网络请求设置
|
||||
HttpConfig.with(new OkHttpClient())
|
||||
// 请求服务地址
|
||||
.serverPath("https://server.example.com")
|
||||
// 是否打印日志
|
||||
.setLogEnabled(false)
|
||||
// 设置日志打印策略
|
||||
.setLogStrategy(new LogStrategy())
|
||||
// 设置请求处理策略
|
||||
.setHandler(new RequestHandler())
|
||||
// 设置请求重试次数
|
||||
.setRetryCount(3)
|
||||
// 启用配置
|
||||
.into();
|
||||
|
||||
EasyHttp.builder()
|
||||
// 自定义接口
|
||||
.api(new ParamApi<String,String>("/business/api", HttpConstant.Method.POST))
|
||||
// 动态参数
|
||||
.param("param")
|
||||
// 异步请求
|
||||
.request(new OnHttpListener<String>() {
|
||||
@Override
|
||||
public void onSucceed(String result) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
Integer num = EasyHttp.builder()
|
||||
.api(new NotParamApi<Integer>("/business/getnum", HttpConstant.Method.GET))
|
||||
.execute(new ResponseClass<Integer>() {
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
575
src/main/java/top/octopusyan/common/http/EasyHttp.java
Normal file
575
src/main/java/top/octopusyan/common/http/EasyHttp.java
Normal file
@ -0,0 +1,575 @@
|
||||
package top.octopusyan.common.http;
|
||||
|
||||
import okhttp3.*;
|
||||
import top.octopusyan.common.http.annotation.HttpHeader;
|
||||
import top.octopusyan.common.http.annotation.HttpIgnore;
|
||||
import top.octopusyan.common.http.annotation.HttpRename;
|
||||
import top.octopusyan.common.http.api.NotParamApi;
|
||||
import top.octopusyan.common.http.api.ParamApi;
|
||||
import top.octopusyan.common.http.api.PathParamApi;
|
||||
import top.octopusyan.common.http.api.RequestApi;
|
||||
import top.octopusyan.common.http.callback.NormalCallback;
|
||||
import top.octopusyan.common.http.listener.OnHttpListener;
|
||||
import top.octopusyan.common.http.model.CallProxy;
|
||||
import top.octopusyan.common.http.model.HttpHeaders;
|
||||
import top.octopusyan.common.http.model.HttpParams;
|
||||
import top.octopusyan.common.http.model.JsonBody;
|
||||
import top.octopusyan.common.http.config.BodyType;
|
||||
import top.octopusyan.common.http.config.HttpConstant;
|
||||
import top.octopusyan.common.http.request.IRequestHandler;
|
||||
import top.octopusyan.common.http.model.ResponseClass;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : http 请求工具
|
||||
* @create : 2022-4-1 15:14
|
||||
*/
|
||||
public class EasyHttp<Param, Result> {
|
||||
|
||||
/** 请求处理策略 */
|
||||
private final IRequestHandler mHandler = HttpConfig.getInstance().getHandler();
|
||||
|
||||
/** 请求执行代理类 */
|
||||
private CallProxy mCallProxy;
|
||||
|
||||
/** 请求标记 */
|
||||
private String mTag;
|
||||
|
||||
/** 请求延迟 */
|
||||
private long mDelayMillis;
|
||||
|
||||
/** 请求服务地址 */
|
||||
private final String server = HttpConfig.getInstance().getServerPath();
|
||||
|
||||
/** 请求接口 */
|
||||
private RequestApi<Param, Result> mRequestApi;
|
||||
|
||||
/** 请求参数 */
|
||||
private Param param;
|
||||
|
||||
public static class Builder {
|
||||
private String method;
|
||||
private String apiPath;
|
||||
private BodyType bodyType;
|
||||
|
||||
public <Param, Result> ParamRequest<Param, Result> api(ParamApi<Param, Result> api) {
|
||||
return new ParamRequest<Param, Result>().api(api);
|
||||
}
|
||||
|
||||
public <Result> UrlRequest<Result> api(NotParamApi<Result> api) {
|
||||
return new UrlRequest<Result>().api(api);
|
||||
}
|
||||
|
||||
public <Result> RestfulRequest<Result> api(PathParamApi<Result> api) {
|
||||
return new RestfulRequest<Result>().api(api);
|
||||
}
|
||||
|
||||
public <Param, Result> EasyHttp<Param, Result> api(RequestApi<Param, Result> api) {
|
||||
return new EasyHttp<Param, Result>().api(api);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无参数请求
|
||||
*/
|
||||
public <Result> UrlRequest<Result> build(ResponseClass<Result> result) {
|
||||
if(apiPath == null) throw new IllegalArgumentException("请求接口地址为空!");
|
||||
|
||||
NotParamApi<Result> requestApi = new NotParamApi<>(apiPath, method, bodyType);
|
||||
|
||||
return new UrlRequest<Result>().api(requestApi);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带参请求
|
||||
* @param param 请求参数
|
||||
*/
|
||||
public <Param, Result> ParamRequest<Param, Result> build(Param param, ResponseClass<Result> result) {
|
||||
ParamApi<Param, Result> requestApi = new ParamApi<>(apiPath, method, bodyType);
|
||||
return new ParamRequest<Param, Result>().api(requestApi).param(param);
|
||||
}
|
||||
|
||||
public Builder setMethod(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setApiPath(String apiPath) {
|
||||
this.apiPath = apiPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setBodyType(BodyType bodyType) {
|
||||
this.bodyType = bodyType;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static Builder builder(){
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
protected EasyHttp<Param, Result> api(RequestApi<Param, Result> api) {
|
||||
this.mRequestApi = api;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected EasyHttp<Param, Result> param(Param param) {
|
||||
this.param = param;
|
||||
return this;
|
||||
}
|
||||
|
||||
private RequestApi<Param, Result> getRequestApi() {
|
||||
return mRequestApi;
|
||||
}
|
||||
|
||||
private IRequestHandler getRequestHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
protected void path(String path) {
|
||||
this.mRequestApi.setApi(path);
|
||||
}
|
||||
|
||||
protected String getPath(){
|
||||
return this.mRequestApi.getApi();
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
public void setTag(String mTag) {
|
||||
this.mTag = mTag;
|
||||
}
|
||||
|
||||
public long getDelayMillis() {
|
||||
return mDelayMillis;
|
||||
}
|
||||
|
||||
public void setDelayMillis(long mDelayMillis) {
|
||||
this.mDelayMillis = mDelayMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行异步请求
|
||||
*/
|
||||
public void request(OnHttpListener<Result> listener) {
|
||||
if (mDelayMillis > 0) {
|
||||
// 打印请求延迟时间
|
||||
HttpLog.print("RequestDelay", String.valueOf(mDelayMillis));
|
||||
}
|
||||
|
||||
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
|
||||
EasyUtils.postDelayed(() -> {
|
||||
HttpLog.print(stackTrace);
|
||||
mCallProxy = new CallProxy(createCall());
|
||||
mCallProxy.enqueue(new NormalCallback(mCallProxy, mRequestApi, mHandler, listener));
|
||||
|
||||
}, mDelayMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行同步请求
|
||||
* @param responseClass 需要解析泛型的对象
|
||||
* @return 返回解析完成的对象
|
||||
* @throws Exception 如果请求失败或者解析失败则抛出异常
|
||||
*/
|
||||
public Result execute(ResponseClass<Result> responseClass) throws Exception {
|
||||
if (mDelayMillis > 0) {
|
||||
// 打印请求延迟时间
|
||||
HttpLog.print("RequestDelay", String.valueOf(mDelayMillis));
|
||||
Thread.sleep(mDelayMillis);
|
||||
}
|
||||
|
||||
HttpLog.print(new Throwable().getStackTrace());
|
||||
try {
|
||||
mCallProxy = new CallProxy(createCall());
|
||||
Response response = mCallProxy.execute();
|
||||
return (Result) mHandler.requestSucceed(getRequestApi(), response, EasyUtils.getReflectType(responseClass));
|
||||
} catch (Exception e) {
|
||||
throw mHandler.requestFail(getRequestApi(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建请求回调
|
||||
*/
|
||||
private Call createCall() {
|
||||
String url = server + mRequestApi.getApi();
|
||||
HttpParams params = new HttpParams();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
BodyType type = mRequestApi.getBodyType();
|
||||
|
||||
if(param != null) setParam(params, headers, type);
|
||||
|
||||
return HttpConfig.getInstance().getClient().newCall(createRequest(url, mTag, params, headers, type));
|
||||
}
|
||||
|
||||
private void setParam(HttpParams params, HttpHeaders headers, BodyType type){
|
||||
|
||||
List<Field> fields = new ArrayList<>();
|
||||
|
||||
Class<?> clazz = param.getClass();
|
||||
do {
|
||||
Field[] declaredFields = clazz.getDeclaredFields();
|
||||
fields.addAll(0, Arrays.asList(declaredFields));
|
||||
// 遍历获取父类的字段
|
||||
clazz = clazz.getSuperclass();
|
||||
} while (clazz != null && !Object.class.equals(clazz));
|
||||
|
||||
// 当前请求是否存在流参数
|
||||
params.setMultipart(EasyUtils.isMultipart(fields));
|
||||
|
||||
// 如果参数中包含流参数并且当前请求方式不是表单的话
|
||||
if (params.isMultipart() && type != BodyType.FORM) {
|
||||
// 就强制设置成以表单形式提交参数
|
||||
type = BodyType.FORM;
|
||||
}
|
||||
|
||||
for (Field field : fields) {
|
||||
// 允许访问私有字段
|
||||
field.setAccessible(true);
|
||||
|
||||
try {
|
||||
// 获取字段的对象
|
||||
Object value = field.get(param);
|
||||
|
||||
// 获取字段的名称
|
||||
String key;
|
||||
HttpRename annotation = field.getAnnotation(HttpRename.class);
|
||||
if (annotation != null) {
|
||||
key = annotation.value();
|
||||
} else {
|
||||
key = field.getName();
|
||||
// 如果是内部类则会出现一个字段名为 this$0 的外部类对象,会导致无限递归,这里要忽略掉,如果使用静态内部类则不会出现这个问题
|
||||
// 和规避 Kotlin 自动生成的伴生对象:https://github.com/getActivity/EasyHttp/issues/15
|
||||
if (key.matches("this\\$\\d+") || "Companion".equals(key)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果这个字段需要忽略,则进行忽略
|
||||
if (field.isAnnotationPresent(HttpIgnore.class)) {
|
||||
if (field.isAnnotationPresent(HttpHeader.class)) {
|
||||
headers.remove(key);
|
||||
} else {
|
||||
params.remove(key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 前提是这个字段值不能为空(基本数据类型有默认的值,而对象默认的值为 null)
|
||||
if (EasyUtils.isEmpty(value)) {
|
||||
// 遍历下一个字段
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果这是一个请求头参数
|
||||
if (field.isAnnotationPresent(HttpHeader.class)) {
|
||||
if (value instanceof Map) {
|
||||
Map<?, ?> map = ((Map<?, ?>) value);
|
||||
for (Object o : map.keySet()) {
|
||||
if (o != null && map.get(o) != null) {
|
||||
headers.put(String.valueOf(o), String.valueOf(map.get(o)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
headers.put(key, String.valueOf(value));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 否则这就是一个普通的参数
|
||||
switch (type) {
|
||||
case FORM:
|
||||
if (value instanceof Map) {
|
||||
Map<?, ?> map = ((Map<?, ?>) value);
|
||||
for (Object o : map.keySet()) {
|
||||
if (o != null && map.get(o) != null) {
|
||||
params.put(String.valueOf(o), map.get(o));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params.put(key, value);
|
||||
}
|
||||
break;
|
||||
case JSON:
|
||||
if (value instanceof List) {
|
||||
// 如果这是一个 List 参数
|
||||
params.put(key, EasyUtils.listToJsonArray(((List<?>) value)));
|
||||
} else if (value instanceof Map) {
|
||||
// 如果这是一个 Map 参数
|
||||
params.put(key, EasyUtils.mapToJsonObject(((Map<?, ?>) value)));
|
||||
} else if (EasyUtils.isBeanType(value)) {
|
||||
// 如果这是一个 Bean 参数
|
||||
params.put(key, EasyUtils.mapToJsonObject(EasyUtils.beanToHashMap(value)));
|
||||
} else {
|
||||
// 如果这是一个普通的参数
|
||||
params.put(key, value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
HttpLog.print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 okhttp 请求
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param tag 标记
|
||||
* @param params 参数
|
||||
* @param headers 请求头
|
||||
* @param type 参数提交方式
|
||||
* @return okhttp 请求对象
|
||||
*/
|
||||
private Request createRequest(String url, String tag, HttpParams params, HttpHeaders headers, BodyType type) {
|
||||
Request.Builder request = new Request.Builder();
|
||||
|
||||
if (tag != null) {
|
||||
request.tag(tag);
|
||||
}
|
||||
|
||||
// 添加请求头
|
||||
if (!headers.isEmpty()) {
|
||||
for (String key : headers.getNames()) {
|
||||
request.addHeader(key, headers.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
RequestBody body = null;
|
||||
String requestUrl = "";
|
||||
String httpMethod = getRequestApi().getMethod();
|
||||
|
||||
switch (httpMethod){
|
||||
case HttpConstant.Method.GET:
|
||||
HttpUrl.Builder builder = HttpUrl.get(url).newBuilder();
|
||||
// 添加参数
|
||||
if (!params.isEmpty()) {
|
||||
for (String key : params.getNames()) {
|
||||
builder.addEncodedQueryParameter(key, String.valueOf(params.get(key)));
|
||||
}
|
||||
}
|
||||
HttpUrl link = builder.build();
|
||||
requestUrl = String.valueOf(link);
|
||||
request.url(link);
|
||||
break;
|
||||
case HttpConstant.Method.POST:
|
||||
request.url(url);
|
||||
requestUrl = url;
|
||||
body = createBody(params, type);
|
||||
break;
|
||||
}
|
||||
|
||||
request.method(httpMethod, body);
|
||||
HttpLog.print("RequestUrl", requestUrl);
|
||||
HttpLog.print("RequestMethod", httpMethod);
|
||||
|
||||
// 打印请求头和参数的日志
|
||||
if (HttpConfig.getInstance().isLogEnabled()) {
|
||||
|
||||
if (!headers.isEmpty() || !params.isEmpty()) {
|
||||
HttpLog.print();
|
||||
}
|
||||
|
||||
for (String key : headers.getNames()) {
|
||||
HttpLog.print(key, headers.get(key));
|
||||
}
|
||||
|
||||
if (!headers.isEmpty() && !params.isEmpty()) {
|
||||
HttpLog.print();
|
||||
}
|
||||
|
||||
for (String key : params.getNames()) {
|
||||
HttpLog.print(key, String.valueOf(params.get(key)));
|
||||
}
|
||||
|
||||
if (!headers.isEmpty() || !params.isEmpty()) {
|
||||
HttpLog.print();
|
||||
}
|
||||
}
|
||||
|
||||
return getRequestHandler().requestStart(getRequestApi(), request.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装 RequestBody 对象
|
||||
*/
|
||||
private RequestBody createBody(HttpParams params, BodyType type) {
|
||||
RequestBody body;
|
||||
|
||||
if (params.isMultipart() && !params.isEmpty()) {
|
||||
MultipartBody.Builder builder = new MultipartBody.Builder();
|
||||
builder.setType(MultipartBody.FORM);
|
||||
for (String key : params.getNames()) {
|
||||
Object object = params.get(key);
|
||||
|
||||
// 如果这是一个 File 对象
|
||||
if (object instanceof File) {
|
||||
MultipartBody.Part part = EasyUtils.createPart(key, (File) object);
|
||||
if (part != null) {
|
||||
builder.addPart(part);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果这是一个 InputStream 对象
|
||||
if (object instanceof InputStream) {
|
||||
MultipartBody.Part part = EasyUtils.createPart(key, (InputStream) object);
|
||||
if (part != null) {
|
||||
builder.addPart(part);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果这是一个自定义的 MultipartBody.Part 对象
|
||||
if (object instanceof MultipartBody.Part) {
|
||||
builder.addPart((MultipartBody.Part) object);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果这是一个自定义的 RequestBody 对象
|
||||
if (object instanceof RequestBody) {
|
||||
builder.addFormDataPart(key, null, (RequestBody) object);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果这是一个普通参数
|
||||
builder.addFormDataPart(key, String.valueOf(object));
|
||||
}
|
||||
|
||||
try {
|
||||
body = builder.build();
|
||||
} catch (IllegalStateException ignored) {
|
||||
// 如果参数为空则会抛出异常:Multipart body must have at least one part.
|
||||
body = new FormBody.Builder().build();
|
||||
}
|
||||
|
||||
} else if (type == BodyType.JSON) {
|
||||
body = new JsonBody(params.getParams());
|
||||
} else {
|
||||
FormBody.Builder builder = new FormBody.Builder();
|
||||
if (!params.isEmpty()) {
|
||||
for (String key : params.getNames()) {
|
||||
builder.add(key, String.valueOf(params.get(key)));
|
||||
}
|
||||
}
|
||||
body = builder.build();
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 TAG 取消请求任务
|
||||
*/
|
||||
public static void cancel(Object tag) {
|
||||
if (tag == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
OkHttpClient client = HttpConfig.getInstance().getClient();
|
||||
|
||||
// 清除排队等候的任务
|
||||
for (Call call : client.dispatcher().queuedCalls()) {
|
||||
if (tag.equals(call.request().tag())) {
|
||||
call.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// 清除正在执行的任务
|
||||
for (Call call : client.dispatcher().runningCalls()) {
|
||||
if (tag.equals(call.request().tag())) {
|
||||
call.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有请求任务
|
||||
*/
|
||||
public static void cancel() {
|
||||
OkHttpClient client = HttpConfig.getInstance().getClient();
|
||||
|
||||
// 清除排队等候的任务
|
||||
for (Call call : client.dispatcher().queuedCalls()) {
|
||||
call.cancel();
|
||||
}
|
||||
|
||||
// 清除正在执行的任务
|
||||
for (Call call : client.dispatcher().runningCalls()) {
|
||||
call.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 无参数请求
|
||||
*/
|
||||
public static class UrlRequest<Result> extends EasyHttp<Void, Result> {
|
||||
|
||||
@Override
|
||||
public UrlRequest<Result> api(RequestApi<Void, Result> api) {
|
||||
super.api(api);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径参数请求
|
||||
*/
|
||||
public static class RestfulRequest<Result> extends EasyHttp<Void, Result> {
|
||||
|
||||
@Override
|
||||
public RestfulRequest<Result> api(RequestApi<Void, Result> api) {
|
||||
super.api(api);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param params 路径参数
|
||||
*/
|
||||
public RestfulRequest<Result> pathParam(Object... params) {
|
||||
super.path(MessageFormat.format(getPath(), params));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 带参数请求
|
||||
*
|
||||
* @param <Param> 参数类型
|
||||
* @param <Result> 返回结果类型
|
||||
*/
|
||||
public static class ParamRequest<Param, Result> extends EasyHttp<Param, Result> {
|
||||
|
||||
@Override
|
||||
public ParamRequest<Param, Result> api(RequestApi<Param, Result> api) {
|
||||
super.api(api);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamRequest<Param, Result> param(Param param) {
|
||||
super.param(param);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
326
src/main/java/top/octopusyan/common/http/EasyUtils.java
Normal file
326
src/main/java/top/octopusyan/common/http/EasyUtils.java
Normal file
@ -0,0 +1,326 @@
|
||||
package top.octopusyan.common.http;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONException;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import top.octopusyan.common.http.annotation.HttpIgnore;
|
||||
import top.octopusyan.common.http.annotation.HttpRename;
|
||||
import top.octopusyan.common.http.config.ContentType;
|
||||
import top.octopusyan.common.http.model.UpdateBody;
|
||||
import top.octopusyan.manager.ThreadPoolManager;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.FileNameMap;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description :
|
||||
* @create : 2022-4-1 16:20
|
||||
*/
|
||||
public class EasyUtils {
|
||||
|
||||
/**
|
||||
* 在主线程中执行
|
||||
*/
|
||||
public static void post(Runnable r) {
|
||||
ThreadPoolManager.getScheduledExecutorService().execute(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟一段时间执行
|
||||
*/
|
||||
public static void postDelayed(Runnable r, long delayMillis) {
|
||||
try {
|
||||
ThreadPoolManager.getScheduledExecutorService().schedule(r, delayMillis, TimeUnit.MILLISECONDS).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对象或者集合是否为空
|
||||
*/
|
||||
public static boolean isEmpty(Object object) {
|
||||
if (object == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (object instanceof List && ((List<?>) object).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return object instanceof Map && ((Map<?, ?>) object).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对象是否为 Bean 类
|
||||
*/
|
||||
public static boolean isBeanType(Object object) {
|
||||
if (object == null) {
|
||||
return false;
|
||||
}
|
||||
// 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<Field> fields) {
|
||||
for (Field field : fields) {
|
||||
// 允许访问私有字段
|
||||
field.setAccessible(true);
|
||||
|
||||
// 获取对象的类型
|
||||
Class<?> clazz = field.getType();
|
||||
|
||||
// 获取对象上面实现的接口
|
||||
Class<?>[] interfaces = clazz.getInterfaces();
|
||||
for (int i = 0; i <= interfaces.length; i++) {
|
||||
Class<?> temp;
|
||||
if (i == interfaces.length) {
|
||||
temp = clazz;
|
||||
} else {
|
||||
temp = interfaces[i];
|
||||
}
|
||||
|
||||
// 判断类型是否是 List<File>
|
||||
if (List.class.equals(temp)) {
|
||||
Type[] actualTypeArguments = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
|
||||
if (actualTypeArguments.length == 1 && File.class.equals(actualTypeArguments[0])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
if (File.class.equals(clazz) || InputStream.class.equals(clazz)
|
||||
|| RequestBody.class.equals(clazz) || MultipartBody.Part.class.equals(clazz)) {
|
||||
return true;
|
||||
}
|
||||
// 获取对象的父类类型
|
||||
clazz = clazz.getSuperclass();
|
||||
} while (clazz != null && !Object.class.equals(clazz));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 List 集合转 JsonArray 对象
|
||||
*/
|
||||
public static JSONArray listToJsonArray(List<?> list) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
if (list == null || list.isEmpty()) {
|
||||
return jsonArray;
|
||||
}
|
||||
|
||||
for (Object value : list) {
|
||||
if (isEmpty(value)) {
|
||||
continue;
|
||||
}
|
||||
if (value instanceof List) {
|
||||
jsonArray.addAll(listToJsonArray(((List<?>) value)));
|
||||
} else if (value instanceof Map) {
|
||||
jsonArray.add(mapToJsonObject(((Map<?, ?>) value)));
|
||||
} else if (isBeanType(value)) {
|
||||
jsonArray.add(mapToJsonObject(beanToHashMap(value)));
|
||||
} else {
|
||||
jsonArray.add(value);
|
||||
}
|
||||
}
|
||||
return jsonArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Map 集合转成 JsonObject 对象
|
||||
*/
|
||||
public static JSONObject mapToJsonObject(Map<?, ?> map) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
if (map == null || map.isEmpty()) {
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
Set<?> keySet = map.keySet();
|
||||
for (Object key : keySet) {
|
||||
Object value = map.get(key);
|
||||
if (isEmpty(value)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (value instanceof List) {
|
||||
jsonObject.put(String.valueOf(key), listToJsonArray(((List<?>) value)));
|
||||
} else if (value instanceof Map) {
|
||||
jsonObject.put(String.valueOf(key), mapToJsonObject(((Map<?, ?>) value)));
|
||||
} else if (isBeanType(value)) {
|
||||
jsonObject.put(String.valueOf(key), mapToJsonObject(beanToHashMap(value)));
|
||||
} else {
|
||||
jsonObject.put(String.valueOf(key), value);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
HttpLog.print(e);
|
||||
}
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Bean 类转成 HashMap 对象
|
||||
*/
|
||||
public static HashMap<String, Object> beanToHashMap(Object object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Field[] fields = object.getClass().getDeclaredFields();
|
||||
HashMap<String, Object> data = new HashMap<>(fields.length);
|
||||
for (Field field : fields) {
|
||||
// 允许访问私有字段
|
||||
field.setAccessible(true);
|
||||
|
||||
try {
|
||||
|
||||
// 获取字段的对象
|
||||
Object value = field.get(object);
|
||||
|
||||
// 前提是这个字段值不能为空(基本数据类型有默认的值,而对象默认的值为 null)
|
||||
// 又或者这个字段需要忽略,则进行忽略
|
||||
if (isEmpty(value) || field.isAnnotationPresent(HttpIgnore.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取字段的名称
|
||||
String key;
|
||||
if (field.isAnnotationPresent(HttpRename.class)) {
|
||||
key = field.getAnnotation(HttpRename.class).value();
|
||||
} else {
|
||||
key = field.getName();
|
||||
// 如果是内部类则会出现一个字段名为 this$0 的外部类对象,会导致无限递归,这里要忽略掉,如果使用静态内部类则不会出现这个问题
|
||||
// 和规避 Kotlin 自动生成的伴生对象:https://github.com/getActivity/EasyHttp/issues/15
|
||||
if (key.matches("this\\$\\d+") || "Companion".equals(key)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof List) {
|
||||
data.put(key, listToJsonArray(((List<?>) value)));
|
||||
} else if (value instanceof Map) {
|
||||
data.put(key, mapToJsonObject(((Map<?, ?>) value)));
|
||||
} else if (isBeanType(value)) {
|
||||
data.put(key, beanToHashMap(value));
|
||||
} else {
|
||||
data.put(key, value);
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
HttpLog.print(e);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象反射类型
|
||||
*/
|
||||
public static Type getReflectType(Object object) {
|
||||
if (object == null) {
|
||||
return Void.class;
|
||||
}
|
||||
Type[] types = object.getClass().getGenericInterfaces();
|
||||
if (types.length > 0) {
|
||||
// 如果这个监听对象是直接实现了接口
|
||||
return ((ParameterizedType) types[0]).getActualTypeArguments()[0];
|
||||
}
|
||||
|
||||
// 如果这个监听对象是通过类继承
|
||||
return ((ParameterizedType) object.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据文件名获取 MIME 类型
|
||||
*/
|
||||
public static MediaType guessMimeType(String fileName) {
|
||||
FileNameMap fileNameMap = URLConnection.getFileNameMap();
|
||||
// 解决文件名中含有#号异常的问题
|
||||
fileName = fileName.replace("#", "");
|
||||
String contentType = fileNameMap.getContentTypeFor(fileName);
|
||||
if (contentType == null) {
|
||||
return ContentType.STREAM;
|
||||
}
|
||||
MediaType type = MediaType.parse(contentType);
|
||||
if (type == null) {
|
||||
type = ContentType.STREAM;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 InputStream 对象创建一个流媒体
|
||||
*/
|
||||
public static MultipartBody.Part createPart(String key, InputStream inputStream) {
|
||||
try {
|
||||
return MultipartBody.Part.createFormData(key, null, new UpdateBody(inputStream, key));
|
||||
} catch (IOException e) {
|
||||
HttpLog.print(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 根据 File 对象创建一个流媒体
|
||||
*/
|
||||
public static MultipartBody.Part createPart(String key, File file) {
|
||||
try {
|
||||
// 文件名必须不能带中文,所以这里要编码
|
||||
return MultipartBody.Part.createFormData(key, encodeString(file.getName()), new UpdateBody(file));
|
||||
} catch (FileNotFoundException e) {
|
||||
HttpLog.print("文件不存在,将被忽略上传:" + key + " = " + file.getPath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭流
|
||||
*/
|
||||
public static void closeStream(Closeable closeable) {
|
||||
if (closeable == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception e) {
|
||||
HttpLog.print(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串编码
|
||||
*/
|
||||
public static String encodeString(String text) {
|
||||
if (StringUtils.isEmpty(text)) {
|
||||
return "";
|
||||
}
|
||||
return URLEncoder.encode(text);
|
||||
}
|
||||
}
|
218
src/main/java/top/octopusyan/common/http/HttpConfig.java
Normal file
218
src/main/java/top/octopusyan/common/http/HttpConfig.java
Normal file
@ -0,0 +1,218 @@
|
||||
package top.octopusyan.common.http;
|
||||
|
||||
import okhttp3.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.octopusyan.common.http.config.ILogStrategy;
|
||||
import top.octopusyan.common.http.request.IRequestHandler;
|
||||
import top.octopusyan.common.http.ssl.HttpSslConfig;
|
||||
import top.octopusyan.common.http.ssl.HttpSslFactory;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : http 设置
|
||||
* @create : 2022-4-1 15:44
|
||||
*/
|
||||
public class HttpConfig {
|
||||
|
||||
private static volatile HttpConfig sConfig;
|
||||
|
||||
/** 服务器地址 */
|
||||
private String serverPath;
|
||||
|
||||
/** 请求处理器 */
|
||||
private IRequestHandler mHandler;
|
||||
|
||||
/** 日志打印策略 */
|
||||
private ILogStrategy mLogStrategy;
|
||||
|
||||
/** OkHttp 客户端 */
|
||||
private OkHttpClient mClient;
|
||||
|
||||
/** 通用参数 */
|
||||
private HashMap<String, Object> mParams;
|
||||
/** 通用请求头 */
|
||||
private HashMap<String, String> mHeaders;
|
||||
|
||||
/** 日志开关 */
|
||||
private boolean mLogEnabled = true;
|
||||
/** 日志 TAG */
|
||||
private String mLogTag = "EasyHttp";
|
||||
|
||||
/** 重试次数 */
|
||||
private int mRetryCount;
|
||||
/** 重试时间 */
|
||||
private long mRetryTime = 2000;
|
||||
|
||||
private HttpConfig(OkHttpClient client) {
|
||||
mClient = client;
|
||||
mParams = new HashMap<String, Object>();
|
||||
mHeaders = new HashMap<>();
|
||||
}
|
||||
private HttpConfig() {
|
||||
mParams = new HashMap<String, Object>();
|
||||
mHeaders = new HashMap<>();
|
||||
initHttpClient();
|
||||
}
|
||||
|
||||
public static HttpConfig getInstance() {
|
||||
if (sConfig == null) {
|
||||
// 当前没有初始化配置
|
||||
throw new IllegalStateException("You haven't initialized the configuration yet");
|
||||
}
|
||||
return sConfig;
|
||||
}
|
||||
|
||||
private static void setInstance(HttpConfig config) {
|
||||
sConfig = config;
|
||||
}
|
||||
|
||||
public static HttpConfig with(OkHttpClient client) {
|
||||
return new HttpConfig(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无参初始化 默认配置生成 okhttpclient
|
||||
*/
|
||||
public static HttpConfig init() {
|
||||
return new HttpConfig();
|
||||
}
|
||||
|
||||
public HttpConfig setParams(HashMap<String, Object> params) {
|
||||
if (params == null) {
|
||||
params = new HashMap<String, Object>();
|
||||
}
|
||||
mParams = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpConfig setHeaders(HashMap<String, String> headers) {
|
||||
if (headers == null) {
|
||||
headers = new HashMap<>();
|
||||
}
|
||||
mHeaders = headers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> getParams() {
|
||||
return mParams;
|
||||
}
|
||||
|
||||
public HashMap<String, String> getHeaders() {
|
||||
return mHeaders;
|
||||
}
|
||||
|
||||
public HttpConfig addHeader(String key, String value) {
|
||||
if (key != null && value != null) {
|
||||
mHeaders.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpConfig addParam(String key, String value) {
|
||||
if (key != null && value != null) {
|
||||
mParams.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public OkHttpClient getClient() {
|
||||
return mClient;
|
||||
}
|
||||
|
||||
public HttpConfig setLogStrategy(ILogStrategy strategy) {
|
||||
mLogStrategy = strategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpConfig setLogEnabled(boolean enabled) {
|
||||
mLogEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpConfig setLogTag(String tag) {
|
||||
mLogTag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpConfig setHandler(IRequestHandler mHandler) {
|
||||
this.mHandler = mHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpConfig setRetryCount(int count) {
|
||||
if (count < 0) {
|
||||
// 重试次数必须大于等于 0 次
|
||||
throw new IllegalArgumentException("The number of retries must be greater than 0");
|
||||
}
|
||||
mRetryCount = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpConfig serverPath(String serverPath) {
|
||||
this.serverPath = serverPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpConfig setRetryTime(long time) {
|
||||
if (time < 0) {
|
||||
// 重试时间必须大于等于 0 毫秒
|
||||
throw new IllegalArgumentException("The retry time must be greater than 0");
|
||||
}
|
||||
mRetryTime = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ILogStrategy getLogStrategy() {
|
||||
return mLogStrategy;
|
||||
}
|
||||
|
||||
public boolean isLogEnabled() {
|
||||
return mLogEnabled && mLogStrategy != null;
|
||||
}
|
||||
|
||||
public String getLogTag() {
|
||||
return mLogTag;
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return mRetryCount;
|
||||
}
|
||||
|
||||
public long getRetryTime() {
|
||||
return mRetryTime;
|
||||
}
|
||||
|
||||
public String getServerPath() {
|
||||
return serverPath;
|
||||
}
|
||||
|
||||
public IRequestHandler getHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
public void into() {
|
||||
if (mClient == null)
|
||||
throw new IllegalArgumentException("The OkHttp client object cannot be empty");
|
||||
|
||||
try {
|
||||
// 校验主机和路径的 url 是否合法
|
||||
new URL(serverPath);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("The configured host path url address is not correct");
|
||||
}
|
||||
|
||||
HttpConfig.setInstance(this);
|
||||
}
|
||||
|
||||
private void initHttpClient() {
|
||||
;
|
||||
}
|
||||
}
|
61
src/main/java/top/octopusyan/common/http/HttpLog.java
Normal file
61
src/main/java/top/octopusyan/common/http/HttpLog.java
Normal file
@ -0,0 +1,61 @@
|
||||
package top.octopusyan.common.http;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description :
|
||||
* @create : 2022-4-1 15:51
|
||||
*/
|
||||
public class HttpLog {
|
||||
/**
|
||||
* 打印分割线
|
||||
*/
|
||||
public static void print() {
|
||||
print("----------------------------------------");
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印日志
|
||||
*/
|
||||
public static void print(String log) {
|
||||
if (HttpConfig.getInstance().isLogEnabled()) {
|
||||
HttpConfig.getInstance().getLogStrategy().print(log);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印 Json
|
||||
*/
|
||||
public static void json(String json) {
|
||||
if (HttpConfig.getInstance().isLogEnabled()) {
|
||||
HttpConfig.getInstance().getLogStrategy().json(json);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印键值对
|
||||
*/
|
||||
public static void print(String key, String value) {
|
||||
if (HttpConfig.getInstance().isLogEnabled()) {
|
||||
HttpConfig.getInstance().getLogStrategy().print(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印异常
|
||||
*/
|
||||
public static void print(Throwable throwable) {
|
||||
if (HttpConfig.getInstance().isLogEnabled()) {
|
||||
HttpConfig.getInstance().getLogStrategy().print(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印堆栈
|
||||
*/
|
||||
public static void print(StackTraceElement[] stackTrace) {
|
||||
if (HttpConfig.getInstance().isLogEnabled()) {
|
||||
HttpConfig.getInstance().getLogStrategy().print(stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {}
|
@ -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 {}
|
@ -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();
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package top.octopusyan.common.http.api;
|
||||
|
||||
import top.octopusyan.common.http.config.BodyType;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 无参数请求接口
|
||||
* @create : 2022-4-1 21:41
|
||||
*/
|
||||
public class NotParamApi<Result> extends RequestApi<Void, Result> {
|
||||
public NotParamApi(String api, String method) {
|
||||
super(api, method);
|
||||
}
|
||||
|
||||
public NotParamApi(String api, String method, BodyType bodyType) {
|
||||
super(api, method, bodyType);
|
||||
}
|
||||
}
|
19
src/main/java/top/octopusyan/common/http/api/ParamApi.java
Normal file
19
src/main/java/top/octopusyan/common/http/api/ParamApi.java
Normal file
@ -0,0 +1,19 @@
|
||||
package top.octopusyan.common.http.api;
|
||||
|
||||
import top.octopusyan.common.http.config.BodyType;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 带参数请求接口
|
||||
* @create : 2022-4-1 21:38
|
||||
*/
|
||||
public class ParamApi<Param, Result> extends RequestApi<Param, Result> {
|
||||
public ParamApi(String method, String path) {
|
||||
super(method, path);
|
||||
}
|
||||
|
||||
public ParamApi(String api, String method, BodyType bodyType) {
|
||||
super(api, method, bodyType);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package top.octopusyan.common.http.api;
|
||||
|
||||
import top.octopusyan.common.http.config.BodyType;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : restful 带路由参数请求接口
|
||||
* @create : 2022-4-1 21:39
|
||||
*/
|
||||
public class PathParamApi<Result> extends RequestApi<Void, Result> {
|
||||
public PathParamApi(String method, String path) {
|
||||
super(method, path);
|
||||
}
|
||||
|
||||
public PathParamApi(String api, String method, BodyType bodyType) {
|
||||
super(api, method, bodyType);
|
||||
}
|
||||
}
|
34
src/main/java/top/octopusyan/common/http/api/RequestApi.java
Normal file
34
src/main/java/top/octopusyan/common/http/api/RequestApi.java
Normal file
@ -0,0 +1,34 @@
|
||||
package top.octopusyan.common.http.api;
|
||||
|
||||
import lombok.Data;
|
||||
import top.octopusyan.common.http.config.BodyType;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 请求基类
|
||||
* @create : 2022-4-1 16:07
|
||||
*/
|
||||
@Data
|
||||
public class RequestApi<Param, Result> {
|
||||
|
||||
/** 接口地址 <p> 必须以<code>/<code/> 开头*/
|
||||
String api;
|
||||
|
||||
/** 请求方式 */
|
||||
String method;
|
||||
|
||||
/** 参数提交方式 <p> 默认JSON*/
|
||||
BodyType bodyType = BodyType.JSON;
|
||||
|
||||
public RequestApi(String api, String method) {
|
||||
this.api = api;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public RequestApi(String api, String method, BodyType bodyType) {
|
||||
this.api = api;
|
||||
this.method = method;
|
||||
this.bodyType = bodyType;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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");
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package top.octopusyan.common.http.exception;
|
||||
|
||||
/**
|
||||
* 返回结果异常
|
||||
*/
|
||||
public final class ResultException extends HttpException {
|
||||
|
||||
private final Object mData;
|
||||
|
||||
public ResultException(String message, Object data) {
|
||||
super(message);
|
||||
mData = data;
|
||||
}
|
||||
|
||||
public ResultException(String message, Throwable cause, Object data) {
|
||||
super(message, cause);
|
||||
mData = data;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Object> T getData() {
|
||||
return (T) mData;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package top.octopusyan.common.http.listener;
|
||||
|
||||
import okhttp3.Call;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 网络请求监听
|
||||
* @create : 2022-4-1 15:17
|
||||
*/
|
||||
public interface OnHttpListener<T> {
|
||||
|
||||
/**
|
||||
* 请求开始
|
||||
*/
|
||||
default void onStart(Call call) {}
|
||||
|
||||
/**
|
||||
* 请求成功
|
||||
*/
|
||||
void onSucceed(T result);
|
||||
|
||||
/**
|
||||
* 请求出错
|
||||
*/
|
||||
default void onFail(Exception e){};
|
||||
|
||||
/**
|
||||
* 请求结束
|
||||
*/
|
||||
default void onEnd(Call call) {}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package top.octopusyan.common.http.model;
|
||||
|
||||
import top.octopusyan.common.http.HttpConfig;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
public final class HttpHeaders {
|
||||
|
||||
/** 请求头存放集合 */
|
||||
private HashMap<String, String> mHeaders = HttpConfig.getInstance().getHeaders();
|
||||
|
||||
public void put(String key, String value) {
|
||||
if (key == null || value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHeaders == HttpConfig.getInstance().getHeaders()) {
|
||||
mHeaders = new HashMap<>(mHeaders);
|
||||
}
|
||||
mHeaders.put(key, value);
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHeaders == HttpConfig.getInstance().getHeaders()) {
|
||||
mHeaders = new HashMap<>(mHeaders);
|
||||
}
|
||||
mHeaders.remove(key);
|
||||
}
|
||||
|
||||
public String get(String key) {
|
||||
return mHeaders.get(key);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mHeaders == null || mHeaders.isEmpty();
|
||||
}
|
||||
|
||||
public Set<String> getNames() {
|
||||
return mHeaders.keySet();
|
||||
}
|
||||
|
||||
public HashMap<String, String> getHeaders() {
|
||||
return mHeaders;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package top.octopusyan.common.http.model;
|
||||
|
||||
import top.octopusyan.common.http.HttpConfig;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
public final class HttpParams {
|
||||
|
||||
/** 请求参数存放集合 */
|
||||
private HashMap<String, Object> mParams = HttpConfig.getInstance().getParams();
|
||||
|
||||
/** 是否有流参数 */
|
||||
private boolean mMultipart;
|
||||
|
||||
public void put(String key, Object value) {
|
||||
if (key == null || value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mParams == HttpConfig.getInstance().getParams()) {
|
||||
mParams = new HashMap<>(mParams);
|
||||
}
|
||||
mParams.put(key, value);
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mParams == HttpConfig.getInstance().getParams()) {
|
||||
mParams = new HashMap<>(mParams);
|
||||
}
|
||||
mParams.remove(key);
|
||||
}
|
||||
|
||||
public Object get(String key) {
|
||||
return mParams.get(key);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mParams == null || mParams.isEmpty();
|
||||
}
|
||||
|
||||
public Set<String> getNames() {
|
||||
return mParams.keySet();
|
||||
}
|
||||
|
||||
public HashMap<String, Object> getParams() {
|
||||
return mParams;
|
||||
}
|
||||
|
||||
public boolean isMultipart() {
|
||||
return mMultipart;
|
||||
}
|
||||
|
||||
public void setMultipart(boolean multipart) {
|
||||
mMultipart = multipart;
|
||||
}
|
||||
}
|
74
src/main/java/top/octopusyan/common/http/model/JsonBody.java
Normal file
74
src/main/java/top/octopusyan/common/http/model/JsonBody.java
Normal file
@ -0,0 +1,74 @@
|
||||
package top.octopusyan.common.http.model;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.NonNull;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import okio.BufferedSink;
|
||||
import top.octopusyan.common.http.config.ContentType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JsonBody extends RequestBody {
|
||||
|
||||
/** Json 数据 */
|
||||
private final String mJson;
|
||||
/** 字节数组 */
|
||||
private final byte[] mBytes;
|
||||
|
||||
public JsonBody(Map<?, ?> map) {
|
||||
this(JSON.parseObject(JSON.toJSONString(map)));
|
||||
}
|
||||
|
||||
public JsonBody(JSONObject jsonObject) {
|
||||
mJson = jsonObject.toString();
|
||||
mBytes = mJson.getBytes();
|
||||
}
|
||||
|
||||
public JsonBody(List<?> list) {
|
||||
this(JSON.parseArray(JSON.toJSONString(list)));
|
||||
}
|
||||
|
||||
public JsonBody(JSONArray jsonArray) {
|
||||
mJson = jsonArray.toString();
|
||||
mBytes = mJson.getBytes();
|
||||
}
|
||||
|
||||
public JsonBody(String json) {
|
||||
mJson = json;
|
||||
mBytes = mJson.getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return ContentType.JSON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() {
|
||||
// 需要注意:这里需要用字节数组的长度来计算
|
||||
return mBytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
sink.write(mBytes, 0, mBytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Json 字符串
|
||||
*/
|
||||
public String getJson() {
|
||||
return mJson;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return mJson;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package top.octopusyan.common.http.model;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 数据解析模型类
|
||||
* @create : 2022-4-1 17:06
|
||||
*/
|
||||
public abstract class ResponseClass<T> {
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package top.octopusyan.common.http.request;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import top.octopusyan.common.http.api.RequestApi;
|
||||
|
||||
public interface IRequestHandler {
|
||||
|
||||
/**
|
||||
* 请求开始
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return 返回新的请求对象
|
||||
*/
|
||||
default <Param, Result> Request requestStart(RequestApi<Param, Result> api, Request request) {
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求成功时回调
|
||||
*
|
||||
* @param response 响应对象
|
||||
* @param type 解析类型
|
||||
* @return 返回结果
|
||||
*
|
||||
* @throws Exception 如果抛出则回调失败
|
||||
*/
|
||||
<Param, Result> Object requestSucceed(RequestApi<Param, Result> api, Response response, Type type) throws Exception;
|
||||
|
||||
/**
|
||||
* 请求失败
|
||||
*
|
||||
* @param e 错误对象
|
||||
* @return 错误对象
|
||||
*/
|
||||
<Param, Result> Exception requestFail(RequestApi<Param, Result> api, Exception e);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
169
src/main/java/top/octopusyan/common/http/ssl/HttpSslFactory.java
Normal file
169
src/main/java/top/octopusyan/common/http/ssl/HttpSslFactory.java
Normal file
@ -0,0 +1,169 @@
|
||||
package top.octopusyan.common.http.ssl;
|
||||
|
||||
import top.octopusyan.common.http.HttpLog;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : Https 证书校验工厂
|
||||
* @create : 2022-4-1 23:19
|
||||
*/
|
||||
public final class HttpSslFactory {
|
||||
|
||||
/**
|
||||
* 生成信任任何证书的配置
|
||||
*/
|
||||
public static HttpSslConfig generateSslConfig() {
|
||||
return generateSslConfigBase(null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* https 单向认证
|
||||
*/
|
||||
public static HttpSslConfig generateSslConfig(X509TrustManager trustManager) {
|
||||
return generateSslConfigBase(trustManager, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* https 单向认证
|
||||
*/
|
||||
public static HttpSslConfig generateSslConfig(InputStream... certificates) {
|
||||
return generateSslConfigBase(null, null, null, certificates);
|
||||
}
|
||||
|
||||
/**
|
||||
* https 双向认证
|
||||
*/
|
||||
public static HttpSslConfig generateSslConfig(InputStream bksFile, String password, InputStream... certificates) {
|
||||
return generateSslConfigBase(null, bksFile, password, certificates);
|
||||
}
|
||||
|
||||
/**
|
||||
* https 双向认证
|
||||
*/
|
||||
public static HttpSslConfig generateSslConfig(InputStream bksFile, String password, X509TrustManager trustManager) {
|
||||
return generateSslConfigBase(trustManager, bksFile, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成认证配置
|
||||
*
|
||||
* @param trustManager 可以额外配置信任服务端的证书策略,否则默认是按CA证书去验证的,若不是CA可信任的证书,则无法通过验证
|
||||
* @param bksFile 客户端使用 bks 证书校验服务端证书
|
||||
* @param password
|
||||
* @param certificates 用含有服务端公钥的证书校验服务端证书
|
||||
*/
|
||||
private static HttpSslConfig generateSslConfigBase(X509TrustManager trustManager, InputStream bksFile, String password, InputStream... certificates) {
|
||||
try {
|
||||
KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
|
||||
TrustManager[] trustManagers = prepareTrustManager(certificates);
|
||||
X509TrustManager manager;
|
||||
if (trustManager != null) {
|
||||
// 优先使用用户自定义的 TrustManager
|
||||
manager = trustManager;
|
||||
} else if (trustManagers != null) {
|
||||
// 然后使用默认的 TrustManager
|
||||
manager = chooseTrustManager(trustManagers);
|
||||
} else {
|
||||
// 否则使用不安全的 TrustManager
|
||||
manager = new UnSafeTrustManager();
|
||||
}
|
||||
// 创建 TLS 类型的 SsLContext 对象,使用我们的 TrustManager
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
// 用上面得到的 TrustManagers 初始化 SsLContext,这样 SslContext 就会信任keyStore中的证书
|
||||
// 第一个参数是授权的密钥管理器,用来授权验证,比如授权自签名的证书验证。第二个是被授权的证书管理器,用来验证服务器端的证书
|
||||
sslContext.init(keyManagers, new TrustManager[]{manager}, null);
|
||||
// 通过 SslContext 获取 SSLSocketFactory 对象
|
||||
return new HttpSslConfig(sslContext.getSocketFactory(), manager);
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) {
|
||||
try {
|
||||
if (bksFile == null || password == null) {
|
||||
return null;
|
||||
}
|
||||
KeyStore keyStore = KeyStore.getInstance("BKS");
|
||||
keyStore.load(bksFile, password.toCharArray());
|
||||
KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
factory.init(keyStore, password.toCharArray());
|
||||
return factory.getKeyManagers();
|
||||
} catch (IOException | CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
HttpLog.print(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static TrustManager[] prepareTrustManager(InputStream... certificates) {
|
||||
if (certificates == null || certificates.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
// 创建一个默认类型的 KeyStore,存储我们信任的证书
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(null);
|
||||
int index = 0;
|
||||
for (InputStream certStream : certificates) {
|
||||
String certificateAlias = Integer.toString(index++);
|
||||
// 证书工厂根据证书文件的流生成证书 Cert
|
||||
Certificate cert = certificateFactory.generateCertificate(certStream);
|
||||
// 将 Cert 作为可信证书放入到 KeyStore 中
|
||||
keyStore.setCertificateEntry(certificateAlias, cert);
|
||||
try {
|
||||
if (certStream != null) {
|
||||
certStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
HttpLog.print(e);
|
||||
}
|
||||
}
|
||||
// 我们创建一个默认类型的 TrustManagerFactory
|
||||
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
// 用我们之前的 KeyStore 实例初始化 TrustManagerFactory,这样 tmf 就会信任 KeyStore 中的证书
|
||||
factory.init(keyStore);
|
||||
// 通过 tmf 获取 TrustManager 数组,TrustManager 也会信任 KeyStore 中的证书
|
||||
return factory.getTrustManagers();
|
||||
} catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException e) {
|
||||
HttpLog.print(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
|
||||
for (TrustManager trustManager : trustManagers) {
|
||||
if (trustManager instanceof X509TrustManager) {
|
||||
return (X509TrustManager) trustManager;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static HostnameVerifier generateUnSafeHostnameVerifier() {
|
||||
return new UnSafeHostnameVerifier();
|
||||
}
|
||||
|
||||
public static HostnameVerifier generateHostnameVerifier(final String[] hostUrls) {
|
||||
|
||||
return (hostname, session) -> {
|
||||
boolean ret = false;
|
||||
for (String host : hostUrls) {
|
||||
if (host.equalsIgnoreCase(hostname)) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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[]{};
|
||||
}
|
||||
}
|
10
src/main/java/top/octopusyan/common/test.java
Normal file
10
src/main/java/top/octopusyan/common/test.java
Normal file
@ -0,0 +1,10 @@
|
||||
package top.octopusyan.common;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : test
|
||||
* @create : 2022-4-1 23:36
|
||||
*/
|
||||
public class test {
|
||||
}
|
57
src/main/java/top/octopusyan/config/ProxyConfig.java
Normal file
57
src/main/java/top/octopusyan/config/ProxyConfig.java
Normal file
@ -0,0 +1,57 @@
|
||||
package top.octopusyan.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 代理设置
|
||||
* @create : 2022-4-5 18:14
|
||||
*/
|
||||
public class ProxyConfig {
|
||||
private static Map<String, String> serverPath = new HashMap<>();
|
||||
private static Map<String, Integer> typePort = new HashMap<>();
|
||||
|
||||
static {
|
||||
serverPath.put("香港", "xg.frp.octopusyan.top");
|
||||
serverPath.put("北京", "bj.frp.octopusyan.top");
|
||||
serverPath.put("上海", "frp.octopusyan.top");
|
||||
|
||||
typePort.put("http", 80);
|
||||
typePort.put("https", 80);
|
||||
typePort.put("ssh", 22);
|
||||
typePort.put("tcp", 0);
|
||||
}
|
||||
|
||||
|
||||
public enum ProxyServer {
|
||||
香港, 北京, 上海
|
||||
}
|
||||
|
||||
public enum ProxyType {
|
||||
HTTP("http"),
|
||||
HTTPS("https"),
|
||||
SSH("tcp"),
|
||||
TCP("tcp"),
|
||||
;
|
||||
|
||||
private String type;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
ProxyType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getServerPath(String serverName) {
|
||||
return serverPath.get(serverName);
|
||||
}
|
||||
|
||||
public static Integer getTypePort(String type) {
|
||||
return typePort.get(type);
|
||||
}
|
||||
}
|
254
src/main/java/top/octopusyan/controller/LoginController.java
Normal file
254
src/main/java/top/octopusyan/controller/LoginController.java
Normal file
@ -0,0 +1,254 @@
|
||||
package top.octopusyan.controller;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import top.octopusyan.base.BaseController;
|
||||
import top.octopusyan.common.http.EasyHttp;
|
||||
import top.octopusyan.common.http.api.NotParamApi;
|
||||
import top.octopusyan.common.http.config.HttpConstant;
|
||||
import top.octopusyan.common.http.listener.OnHttpListener;
|
||||
import top.octopusyan.http.Api;
|
||||
import top.octopusyan.http.request.LoginParam;
|
||||
import top.octopusyan.http.request.ProxySetup;
|
||||
import top.octopusyan.utils.AlertUtil;
|
||||
import top.octopusyan.utils.ApplicatonStore;
|
||||
import top.octopusyan.utils.ProxyUtil;
|
||||
import top.octopusyan.utils.TextValidate;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : Login 控制器
|
||||
* @create : 2022-3-30 16:06
|
||||
*/
|
||||
public class LoginController extends BaseController<StackPane> implements Initializable {
|
||||
|
||||
@FXML
|
||||
public StackPane root;
|
||||
|
||||
@FXML
|
||||
public JFXButton closeBtn, minimizeBtn, loginBtn;
|
||||
@FXML
|
||||
public AnchorPane loginBottomPane;
|
||||
@FXML
|
||||
public ImageView loginBkgPane;
|
||||
@FXML
|
||||
public JFXButton registerBtn;
|
||||
@FXML
|
||||
public JFXTextField accountTextField;
|
||||
@FXML
|
||||
public JFXPasswordField passwordTextField;
|
||||
@FXML
|
||||
public JFXTextField seePwdTextField;
|
||||
@FXML
|
||||
public JFXButton accountIconBtn;
|
||||
@FXML
|
||||
public FontIcon accountIcon;
|
||||
@FXML
|
||||
public JFXButton pwdIconBtn;
|
||||
@FXML
|
||||
public FontIcon pwdIcon;
|
||||
@FXML
|
||||
public JFXButton seePwdIconBtn;
|
||||
@FXML
|
||||
public FontIcon seePwdIcon;
|
||||
@FXML
|
||||
public JFXCheckBox autoLoginCBox;
|
||||
@FXML
|
||||
public JFXCheckBox rememberCBox;
|
||||
|
||||
private boolean autoLogin;
|
||||
|
||||
@Override
|
||||
public boolean dragWindow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StackPane getRootPanel() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getRootFxmlPath() {
|
||||
return "/fxml/login.fxml";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button getClooseBtn() {
|
||||
return closeBtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button getMinimizeBtn() {
|
||||
return minimizeBtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JFXButton getFirstBtn() {
|
||||
return minimizeBtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initData() {
|
||||
String account = ApplicatonStore.getAccount();
|
||||
String password = ApplicatonStore.getPassword();
|
||||
|
||||
if(!StringUtils.isEmpty(account)) accountTextField.setText(account);
|
||||
if(!StringUtils.isEmpty(password)) {
|
||||
passwordTextField.setText(password);
|
||||
seePwdTextField.setText(password);
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏展示密码框
|
||||
private AnchorPane pwdParent;
|
||||
|
||||
@Override
|
||||
public void initViewStyle() {
|
||||
|
||||
// 文本框焦点状态监听
|
||||
accountTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) accountTextField.resetValidation();
|
||||
});
|
||||
passwordTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) passwordTextField.resetValidation();
|
||||
});
|
||||
seePwdTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) seePwdTextField.resetValidation();
|
||||
});
|
||||
|
||||
// 隐藏展示密码框
|
||||
pwdParent = (AnchorPane) passwordTextField.getParent();
|
||||
pwdParent.getChildren().remove(seePwdTextField);
|
||||
|
||||
// 同步用户数据
|
||||
accountTextField.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setAccount(newValue));
|
||||
passwordTextField.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setPassword(newValue));
|
||||
seePwdTextField.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setPassword(newValue));
|
||||
|
||||
// 添加文本校验
|
||||
accountTextField.getValidators().add(TextValidate.AccoountRequired);
|
||||
accountTextField.getValidators().add(TextValidate.AccoountValidator);
|
||||
accountTextField.getValidators().add(TextValidate.getLengthValidator(6, 18, "账号"));
|
||||
passwordTextField.getValidators().add(TextValidate.PasswordRequired);
|
||||
seePwdTextField.getValidators().add(TextValidate.PasswordRequired);
|
||||
|
||||
// 记住密码
|
||||
rememberCBox.selectedProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setRememberMe(newValue));
|
||||
// 自动登录
|
||||
autoLoginCBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
ApplicatonStore.setAutoLogin(newValue);
|
||||
if (newValue && !ApplicatonStore.isRememberMe()) rememberCBox.selectedProperty().set(true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initViewAction() {
|
||||
|
||||
// 注册
|
||||
registerBtn.setOnMouseClicked(event -> jumpTo(new RegisterController()));
|
||||
|
||||
// 查看密码
|
||||
seePwdIconBtn.setOnMouseClicked(event -> {
|
||||
boolean isHide = pwdParent.getChildren().contains(passwordTextField);
|
||||
|
||||
pwdParent.getChildren().remove(isHide ? passwordTextField : seePwdTextField);
|
||||
pwdParent.getChildren().add(1, isHide ? seePwdTextField : passwordTextField);
|
||||
(isHide ? seePwdTextField : passwordTextField).setText(ApplicatonStore.getPassword());
|
||||
seePwdIcon.setIconColor(isHide ? Color.BLUE : Color.BLACK);
|
||||
});
|
||||
|
||||
// 登录
|
||||
loginBtn.setOnMouseClicked(event -> login());
|
||||
accountTextField.setOnKeyPressed(event -> {
|
||||
if(event.getCode() == KeyCode.ENTER) login();
|
||||
});
|
||||
passwordTextField.setOnKeyPressed(event -> {
|
||||
if(event.getCode() == KeyCode.ENTER) login();
|
||||
});
|
||||
|
||||
// 自动登录
|
||||
if((ApplicatonStore.isAutoLogin() || ApplicatonStore.isRegisterSuccess()) &&
|
||||
!StringUtils.isEmpty(ApplicatonStore.getAccount()) &&
|
||||
!StringUtils.isEmpty(ApplicatonStore.getPassword())
|
||||
){
|
||||
login();
|
||||
}
|
||||
}
|
||||
|
||||
private void login(){
|
||||
// 获取文本校验结果
|
||||
boolean pwdValidate = pwdParent.getChildren().contains(passwordTextField) ?
|
||||
passwordTextField.validate() : seePwdTextField.validate();
|
||||
boolean accountValidate = accountTextField.validate();
|
||||
|
||||
if (pwdValidate && accountValidate)
|
||||
EasyHttp.builder()
|
||||
.api(Api.Login)
|
||||
.param(new LoginParam(accountTextField.getText(), ApplicatonStore.getPassword()))
|
||||
.request(new OnHttpListener<String>() {
|
||||
@Override
|
||||
public void onSucceed(String result) {
|
||||
Document html = Jsoup.parse(result);
|
||||
// 登录出错
|
||||
if (result.contains("alert-danger")) {
|
||||
Platform.runLater(() -> AlertUtil.error(getHtmlErrorMessage(html)).show());
|
||||
return;
|
||||
}
|
||||
// TODO 登录成功
|
||||
setCsrf();
|
||||
jumpTo(new MainController());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setCsrf() {
|
||||
EasyHttp.builder()
|
||||
.api(new NotParamApi<String>(
|
||||
"/?page=panel&module=addproxy",
|
||||
HttpConstant.Method.GET
|
||||
))
|
||||
.request(new OnHttpListener<String>() {
|
||||
@Override
|
||||
public void onSucceed(String result) {
|
||||
ProxyUtil.setCsrf(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getHtmlErrorMessage(Document html) {
|
||||
return html.body().getElementsByClass("alert alert-danger alert-dismissable").text().substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
EasyHttp.cancel();
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
372
src/main/java/top/octopusyan/controller/MainController.java
Normal file
372
src/main/java/top/octopusyan/controller/MainController.java
Normal file
@ -0,0 +1,372 @@
|
||||
package top.octopusyan.controller;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.octopusyan.base.BaseController;
|
||||
import top.octopusyan.config.ProxyConfig;
|
||||
import top.octopusyan.config.ProxyConfig.ProxyServer;
|
||||
import top.octopusyan.config.ProxyConfig.ProxyType;
|
||||
import top.octopusyan.http.request.ProxySetup;
|
||||
import top.octopusyan.utils.AlertUtil;
|
||||
import top.octopusyan.utils.ApplicatonStore;
|
||||
import top.octopusyan.utils.ProxyUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description :
|
||||
* @create : 2022-4-4 22:32
|
||||
*/
|
||||
public class MainController extends BaseController<StackPane> implements Initializable {
|
||||
|
||||
public static final String PROXY_LIST_ITEM_CLASS = "proxyListItem";
|
||||
public static final String PROXY_LIST_ITEM_STOP_CLASS = "proxyListItem-stop";
|
||||
public static final String PROXY_LIST_ITEM_RUN_CLASS = "proxyListItem-run";
|
||||
public static final String PROXY_LIST_ITEM_CLOSE_CLASS = "proxyListItem-close";
|
||||
|
||||
@FXML
|
||||
public StackPane root;
|
||||
|
||||
@FXML
|
||||
public JFXButton closeBtn, minimizeBtn;
|
||||
|
||||
@FXML
|
||||
public JFXButton startProxyBtn;
|
||||
@FXML
|
||||
public JFXButton addProxyBtn;
|
||||
@FXML
|
||||
public JFXComboBox<Label> proxyProtocolCombox;
|
||||
@FXML
|
||||
public JFXButton customizeDomainBtn;
|
||||
@FXML
|
||||
public JFXRadioButton openProxyRBtn, closeProxyRBtn;
|
||||
@FXML
|
||||
public JFXTextField domainTextField, domainSuffixTextField;
|
||||
@FXML
|
||||
public JFXTextField proxyNameTextField, localHostTextField, localPortTextField;
|
||||
@FXML
|
||||
public JFXComboBox<Label> proxyServerCB;
|
||||
@FXML
|
||||
public JFXTabPane tabPane;
|
||||
@FXML
|
||||
public JFXListView<Label> proxyListView;
|
||||
|
||||
private ToggleGroup openProxyGroup = new ToggleGroup();
|
||||
private boolean customizeDomain = false;
|
||||
private List<ProxySetup> proxyList;
|
||||
private int selectProxy = 0;
|
||||
private ProxySetup proxySetup;
|
||||
|
||||
@Override
|
||||
public boolean dragWindow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StackPane getRootPanel() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getRootFxmlPath() {
|
||||
return "/fxml/main.fxml";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button getClooseBtn() {
|
||||
return closeBtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button getMinimizeBtn() {
|
||||
return minimizeBtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JFXButton getFirstBtn() {
|
||||
return minimizeBtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initData() {
|
||||
|
||||
// 隧道类型
|
||||
Arrays.asList(ProxyType.values()).forEach((type) -> proxyProtocolCombox.getItems().add(new Label(type.name())));
|
||||
|
||||
// 服务器
|
||||
Arrays.asList(ProxyServer.values()).forEach((server) -> proxyServerCB.getItems().add(new Label(server.name())));
|
||||
|
||||
// 获取用户隧道列表
|
||||
ProxyUtil.getList(setupList -> {
|
||||
proxyList = setupList;
|
||||
|
||||
// 主线程执行
|
||||
Platform.runLater(() -> {
|
||||
// 设置隧道列表
|
||||
ApplicatonStore.setProxySetupList(proxyList);
|
||||
|
||||
// 初始化隧道设置
|
||||
if (proxyList != null && proxyList.size() > 0) {
|
||||
// 显示隧道列表
|
||||
initProxyList(proxyList);
|
||||
// 显示隧道设置
|
||||
proxySetup = proxyList.get(0);
|
||||
// 默认选中第一个
|
||||
proxyListView.getSelectionModel().select(0);
|
||||
} else {
|
||||
proxySetup = ProxyUtil.initProxy();
|
||||
proxyList = new ArrayList<>();
|
||||
proxyList.add(proxySetup);
|
||||
initProxySetupView(proxySetup);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initViewStyle() {
|
||||
// 启用链接
|
||||
openProxyRBtn.setToggleGroup(openProxyGroup);
|
||||
openProxyRBtn.setSelected(true);
|
||||
closeProxyRBtn.setToggleGroup(openProxyGroup);
|
||||
openProxyRBtn.setUserData(true);
|
||||
closeProxyRBtn.setUserData(false);
|
||||
|
||||
// 默认展示 常见问题
|
||||
tabPane.getSelectionModel().select(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置外网访问域名
|
||||
*/
|
||||
private void setDomain(ProxySetup setup) {
|
||||
|
||||
if (setup != null) {
|
||||
String serverName = ProxyServer.values()[setup.getNode()].name();
|
||||
String setupDomain = setup.getDomain();
|
||||
String domain = setupDomain;
|
||||
String serverPath = ProxyConfig.getServerPath(serverName);
|
||||
// 是否自定义
|
||||
customizeDomain = setupDomain.contains(serverPath);
|
||||
if (customizeDomain) domain = setupDomain.substring(0, setupDomain.indexOf("." + serverPath));
|
||||
domainSuffixTextField.setText("." + serverPath);
|
||||
domainTextField.setText(domain);
|
||||
}
|
||||
|
||||
if (!customizeDomain) {
|
||||
domainTextField.styleProperty().set("-fx-border-radius: 5");
|
||||
domainSuffixTextField.setVisible(false);
|
||||
domainTextField.setText("");
|
||||
customizeDomainBtn.setText("系统分配");
|
||||
} else {
|
||||
domainTextField.styleProperty().set("-fx-border-radius: 5 0 0 5");
|
||||
domainSuffixTextField.setVisible(true);
|
||||
customizeDomainBtn.setText("自定义");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initViewAction() {
|
||||
|
||||
// 是否启用
|
||||
openProxyGroup.selectedToggleProperty().addListener(
|
||||
(ObservableValue<? extends Toggle> ov, Toggle old_toggle, Toggle new_toggle) -> {
|
||||
Toggle toggle = openProxyGroup.getSelectedToggle();
|
||||
if (toggle != null) {
|
||||
closeProxy(!(Boolean) toggle.getUserData());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 隧道类型
|
||||
proxyProtocolCombox.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
String newType = StringUtils.lowerCase(newValue.getText());
|
||||
String port;
|
||||
if (!newType.equals(proxySetup.getProxy_type()))
|
||||
port = ProxyConfig.getTypePort(newType).toString();
|
||||
else
|
||||
port = proxySetup.getLocal_port().toString();
|
||||
|
||||
// 设置默认端口
|
||||
localPortTextField.setText(port);
|
||||
// 设置隧道类型
|
||||
proxySetup.setProxy_type(newType);
|
||||
});
|
||||
|
||||
// 自定义访问域名
|
||||
customizeDomainBtn.setOnMouseClicked((event) -> {
|
||||
setDomain(proxySetup != null ? proxySetup : null);
|
||||
});
|
||||
|
||||
// 点击隧道列表
|
||||
proxyListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
for (ProxySetup setup : proxyList) {
|
||||
if (setup.getProxy_name().equals(newValue.getText()))
|
||||
initProxySetupView(setup);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 添加隧道
|
||||
addProxyBtn.setOnMouseClicked(event -> {
|
||||
// 获取默认隧道设置
|
||||
proxySetup = ProxyUtil.initProxy();
|
||||
// 加入列表
|
||||
proxyList.add(proxySetup);
|
||||
selectProxy = proxyList.indexOf(proxySetup);
|
||||
// 展示
|
||||
initProxySetupView(proxySetup);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示隧道设置
|
||||
*/
|
||||
private void initProxySetupView(ProxySetup setup) {
|
||||
|
||||
int select = proxyList.indexOf(setup);
|
||||
//
|
||||
if (setup.getLocal_ip() == null && ProxyUtil.info(setup) == null) {
|
||||
AlertUtil.error("隧道设置获取失败!").show();
|
||||
if (select != selectProxy) {
|
||||
proxyListView.getSelectionModel().select(select);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 选中的隧道下标
|
||||
selectProxy = select;
|
||||
|
||||
// 隧道类型
|
||||
for (Label label : proxyProtocolCombox.getItems()) {
|
||||
// 获取用户设置
|
||||
if (StringUtils.lowerCase(label.getText()).equals(setup.getProxy_type()))
|
||||
proxyProtocolCombox.getSelectionModel().select(proxyProtocolCombox.getItems().indexOf(label));
|
||||
}
|
||||
|
||||
// 服务器
|
||||
String serverName = ProxyServer.values()[setup.getNode()].name();
|
||||
proxyServerCB.getItems().forEach((label -> {
|
||||
if (label.getText().equals(serverName))
|
||||
proxyServerCB.getSelectionModel().select(proxyServerCB.getItems().indexOf(label));
|
||||
}));
|
||||
|
||||
// 隧道名称
|
||||
proxyNameTextField.setText(setup.getProxy_name());
|
||||
|
||||
// 本地服务地址
|
||||
localPortTextField.setText(setup.getLocal_port().toString());
|
||||
localHostTextField.setText(setup.getLocal_ip());
|
||||
|
||||
// 公网访问地址
|
||||
setDomain(setup);
|
||||
|
||||
// 启动按钮
|
||||
if (setup.isRuning() && !setup.isClose()) {
|
||||
startProxyBtn.setText("停止");
|
||||
startProxyBtn.getStyleClass().remove("startProxyBtn");
|
||||
startProxyBtn.getStyleClass().add("stopProxyBtn");
|
||||
} else {
|
||||
startProxyBtn.setText("启动");
|
||||
startProxyBtn.getStyleClass().remove("stoptProxyBtn");
|
||||
startProxyBtn.getStyleClass().add("startProxyBtn");
|
||||
}
|
||||
|
||||
// 是否停用
|
||||
closeProxy(setup.isClose());
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示隧道列表
|
||||
*/
|
||||
private void initProxyList(List<ProxySetup> proxyList) {
|
||||
// 清空列表
|
||||
proxyListView.getItems().clear();
|
||||
|
||||
for (ProxySetup proxy : proxyList) {
|
||||
try {
|
||||
Label label = FXMLLoader.load(getClass().getResource("/fxml/proxyItem.fxml"));
|
||||
label.setText(proxy.getProxy_name());
|
||||
label.getStyleClass().addAll(PROXY_LIST_ITEM_CLASS, PROXY_LIST_ITEM_STOP_CLASS);
|
||||
proxyListView.getItems().add(label);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用当前隧道
|
||||
*/
|
||||
private void closeProxy(boolean close) {
|
||||
if (close && proxySetup.isRuning()) stopProxy();
|
||||
|
||||
proxyNameTextField.setDisable(close);// 隧道名称
|
||||
proxyProtocolCombox.setDisable(close);// 隧道类型
|
||||
localHostTextField.setDisable(close);// 本地地址
|
||||
localPortTextField.setDisable(close);// 本地端口
|
||||
domainTextField.setDisable(close); // 外网子域名
|
||||
startProxyBtn.setDisable(close); // 启动按钮
|
||||
|
||||
ObservableList<String> styleClass = proxyListView.getItems().get(selectProxy).getStyleClass();
|
||||
if (close) {
|
||||
styleClass.remove(PROXY_LIST_ITEM_STOP_CLASS);
|
||||
styleClass.remove(PROXY_LIST_ITEM_RUN_CLASS);
|
||||
styleClass.add(PROXY_LIST_ITEM_CLOSE_CLASS);
|
||||
} else {
|
||||
styleClass.remove(PROXY_LIST_ITEM_CLOSE_CLASS);
|
||||
styleClass.add(proxySetup.isClose() ? PROXY_LIST_ITEM_CLOSE_CLASS : PROXY_LIST_ITEM_STOP_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 启动代理
|
||||
*/
|
||||
private void startProxy() {
|
||||
|
||||
// 删除旧隧道
|
||||
if (proxySetup.getId() != null) ProxyUtil.delete(proxySetup.getId());
|
||||
|
||||
// 添加隧道
|
||||
if (!ProxyUtil.add(result -> {
|
||||
}, proxySetup)) return;
|
||||
|
||||
// TODO 修改设置
|
||||
// TODO 调用cmd
|
||||
|
||||
ObservableList<String> styleClass = proxyListView.getItems().get(selectProxy).getStyleClass();
|
||||
styleClass.remove(PROXY_LIST_ITEM_STOP_CLASS);
|
||||
styleClass.add(PROXY_LIST_ITEM_RUN_CLASS);
|
||||
proxySetup.setRuning(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 暂停代理
|
||||
*/
|
||||
private void stopProxy() {
|
||||
// 关闭CMD
|
||||
|
||||
proxySetup.setRuning(false);
|
||||
}
|
||||
}
|
196
src/main/java/top/octopusyan/controller/RegisterController.java
Normal file
196
src/main/java/top/octopusyan/controller/RegisterController.java
Normal file
@ -0,0 +1,196 @@
|
||||
package top.octopusyan.controller;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import top.octopusyan.base.BaseController;
|
||||
import top.octopusyan.common.http.EasyHttp;
|
||||
import top.octopusyan.common.http.listener.OnHttpListener;
|
||||
import top.octopusyan.http.Api;
|
||||
import top.octopusyan.http.request.RegisterParam;
|
||||
import top.octopusyan.http.request.SendEmailCheckParam;
|
||||
import top.octopusyan.utils.AlertUtil;
|
||||
import top.octopusyan.utils.ApplicatonStore;
|
||||
import top.octopusyan.utils.TextValidate;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 注册表单
|
||||
* @create : 2022-4-3 22:39
|
||||
*/
|
||||
public class RegisterController extends BaseController<StackPane> implements Serializable {
|
||||
|
||||
@FXML
|
||||
public StackPane root;
|
||||
|
||||
@FXML
|
||||
public JFXButton closeBtn, minimizeBtn;
|
||||
@FXML
|
||||
public JFXTextField accooundTextField, emailTf, emailCheckCodeTf;
|
||||
@FXML
|
||||
public JFXButton registerBtn, sendCheckCodeBtn;
|
||||
@FXML
|
||||
public JFXPasswordField passwordTextField;
|
||||
@FXML
|
||||
public JFXButton loginBtn;
|
||||
|
||||
@Override
|
||||
public boolean dragWindow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StackPane getRootPanel() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getRootFxmlPath() {
|
||||
return "/fxml/register.fxml";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button getClooseBtn() {
|
||||
return closeBtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button getMinimizeBtn() {
|
||||
return minimizeBtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JFXButton getFirstBtn() {
|
||||
return minimizeBtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initData() {
|
||||
String account = ApplicatonStore.getAccount();
|
||||
String password = ApplicatonStore.getPassword();
|
||||
|
||||
if(!StringUtils.isEmpty(account)) accooundTextField.setText(account);
|
||||
if(!StringUtils.isEmpty(password)) passwordTextField.setText(password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initViewStyle() {
|
||||
|
||||
// 记录账号
|
||||
accooundTextField.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setAccount(newValue));
|
||||
// 记录密码
|
||||
passwordTextField.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setPassword(newValue));
|
||||
// 记录密码
|
||||
emailTf.textProperty().addListener((observable, oldValue, newValue) -> ApplicatonStore.setEmail(newValue));
|
||||
|
||||
// 添加文本验证
|
||||
accooundTextField.getValidators().add(TextValidate.AccoountRequired);
|
||||
accooundTextField.getValidators().add(TextValidate.AccoountValidator);
|
||||
accooundTextField.getValidators().add(TextValidate.getLengthValidator(6, 18, "账号"));
|
||||
passwordTextField.getValidators().add(TextValidate.PasswordRequired);
|
||||
emailTf.getValidators().add(TextValidate.EmailFormat);
|
||||
emailCheckCodeTf.getValidators().add(TextValidate.getLengthValidator("验证码", 6));
|
||||
|
||||
// 文本验证
|
||||
accooundTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) accooundTextField.resetValidation();
|
||||
else accooundTextField.validate();
|
||||
});
|
||||
emailTf.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) emailTf.resetValidation();
|
||||
else emailTf.validate();
|
||||
});
|
||||
passwordTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) passwordTextField.resetValidation();
|
||||
else passwordTextField.validate();
|
||||
});
|
||||
emailCheckCodeTf.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue)
|
||||
emailCheckCodeTf.resetValidation();
|
||||
else
|
||||
emailCheckCodeTf.validate();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initViewAction() {
|
||||
// 登录
|
||||
loginBtn.setOnMouseClicked(event -> jumpTo(new LoginController()));
|
||||
|
||||
// 点击了发送验证码
|
||||
sendCheckCodeBtn.setOnMouseClicked(event -> {
|
||||
// 如果邮箱地址正确
|
||||
if (emailTf.validate()) {
|
||||
// 发送邮箱验证
|
||||
EasyHttp.builder()
|
||||
.api(Api.SendEmailCheck)
|
||||
.param(new SendEmailCheckParam(emailTf.getText()))
|
||||
.request(new OnHttpListener<String>() {
|
||||
@Override
|
||||
public void onSucceed(String result) {
|
||||
Platform.runLater(() -> AlertUtil.info("系统已发送一封邮件至您的邮箱,请查收。").show());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 注册
|
||||
registerBtn.setOnMouseClicked(event -> register());
|
||||
}
|
||||
|
||||
private void register(){
|
||||
// 文本校验
|
||||
if(accooundTextField.validate() && passwordTextField.validate() && emailTf.validate() && emailCheckCodeTf.validate()){
|
||||
EasyHttp.builder()
|
||||
.api(Api.Register)
|
||||
.param(new RegisterParam(
|
||||
ApplicatonStore.getAccount(),
|
||||
ApplicatonStore.getPassword(),
|
||||
ApplicatonStore.getEmail(),
|
||||
emailCheckCodeTf.getText()
|
||||
))
|
||||
.request(new OnHttpListener<String>() {
|
||||
@Override
|
||||
public void onSucceed(String result) {
|
||||
Document html = Jsoup.parse(result);
|
||||
// 注册出错
|
||||
if(result.contains("alert-danger")) {
|
||||
Platform.runLater(() -> AlertUtil.error(getHtmlErrorMessage(html)).header(null).show());
|
||||
return;
|
||||
}
|
||||
// 注册成功
|
||||
if(result.contains("alert-success")){
|
||||
Platform.runLater(() -> AlertUtil.info(getHtmlMessage(html)).header(null).show());
|
||||
ApplicatonStore.setRegisterSuccess(true);
|
||||
jumpTo(new LoginController());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private String getHtmlErrorMessage(Document html) {
|
||||
return html.body().getElementsByClass("alert alert-danger alert-dismissable").text().substring(1);
|
||||
}
|
||||
|
||||
private String getHtmlMessage(Document html) {
|
||||
return html.body().getElementsByClass("alert alert-success alert-dismissable").text().substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
80
src/main/java/top/octopusyan/http/Api.java
Normal file
80
src/main/java/top/octopusyan/http/Api.java
Normal file
@ -0,0 +1,80 @@
|
||||
package top.octopusyan.http;
|
||||
|
||||
import top.octopusyan.common.http.api.NotParamApi;
|
||||
import top.octopusyan.common.http.api.ParamApi;
|
||||
import top.octopusyan.common.http.api.PathParamApi;
|
||||
import top.octopusyan.common.http.config.BodyType;
|
||||
import top.octopusyan.common.http.config.HttpConstant;
|
||||
import top.octopusyan.http.request.ProxySetup;
|
||||
import top.octopusyan.http.request.RegisterParam;
|
||||
import top.octopusyan.http.request.SendEmailCheckParam;
|
||||
import top.octopusyan.http.request.LoginParam;
|
||||
import top.octopusyan.utils.ProxyUtil;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 业务请求接口
|
||||
* @create : 2022-4-1 23:08
|
||||
*/
|
||||
public class Api {
|
||||
/** 登录接口 获取Cookie */
|
||||
public static final ParamApi<LoginParam, String> Login = new ParamApi<>(
|
||||
"/?action=login&page=login",
|
||||
HttpConstant.Method.POST,
|
||||
BodyType.FORM
|
||||
);
|
||||
|
||||
/** 发送邮箱验证码 */
|
||||
public static final ParamApi<SendEmailCheckParam, String> SendEmailCheck = new ParamApi<>(
|
||||
"/?action=sendmail",
|
||||
HttpConstant.Method.POST,
|
||||
BodyType.FORM
|
||||
);
|
||||
|
||||
/** 发送邮箱验证码 */
|
||||
public static final ParamApi<RegisterParam, String> Register = new ParamApi<>(
|
||||
"/?action=register&page=register",
|
||||
HttpConstant.Method.POST,
|
||||
BodyType.FORM
|
||||
);
|
||||
|
||||
/** 隧道列表 */
|
||||
public static final NotParamApi<String> ProxyList = new NotParamApi<>(
|
||||
"/?page=panel&module=proxies",
|
||||
HttpConstant.Method.GET
|
||||
);
|
||||
|
||||
/** 隧道详情 */
|
||||
public static PathParamApi<String> ProxyInfo() {
|
||||
return new PathParamApi<>(
|
||||
"/?page=panel&module=proxies&getproxyinfo={0}&csrf=" + ProxyUtil.getCsrf(),
|
||||
HttpConstant.Method.GET
|
||||
);
|
||||
}
|
||||
|
||||
/** 添加隧道 */
|
||||
public static ParamApi<ProxySetup, String> AddProxy() {
|
||||
return new ParamApi<>(
|
||||
"/?page=panel&module=addproxy&action=addproxy&csrf=" + ProxyUtil.getCsrf(),
|
||||
HttpConstant.Method.POST,
|
||||
BodyType.FORM
|
||||
);
|
||||
}
|
||||
|
||||
/** 删除隧道 */
|
||||
public static PathParamApi<Void> DeleteProxy() {
|
||||
return new PathParamApi<>(
|
||||
"/?page=panel&module=proxies&delete={0}&csrf=" + ProxyUtil.getCsrf(),
|
||||
HttpConstant.Method.GET
|
||||
);
|
||||
}
|
||||
|
||||
/** 随机端口 */
|
||||
public static NotParamApi<Integer> RandomPort() {
|
||||
return new NotParamApi<>(
|
||||
"/?page=panel&module=addproxy&randomport&csrf=" + ProxyUtil.getCsrf(),
|
||||
HttpConstant.Method.GET
|
||||
);
|
||||
}
|
||||
}
|
140
src/main/java/top/octopusyan/http/OkHttpClientConfig.java
Normal file
140
src/main/java/top/octopusyan/http/OkHttpClientConfig.java
Normal file
@ -0,0 +1,140 @@
|
||||
package top.octopusyan.http;
|
||||
|
||||
import kotlin.text.Charsets;
|
||||
import okhttp3.*;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.octopusyan.common.http.ssl.HttpSslConfig;
|
||||
import top.octopusyan.common.http.ssl.HttpSslFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : okhttp 设置
|
||||
* @create : 2022-4-2 15:27
|
||||
*/
|
||||
public class OkHttpClientConfig {
|
||||
public static final String TAG = "OkHttpClientConfig";
|
||||
private static final Logger log = LoggerFactory.getLogger(TAG);
|
||||
|
||||
|
||||
public static final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
|
||||
|
||||
public static OkHttpClient httpClient() {
|
||||
Interceptor tokenInterceptor = new Interceptor() {//全局拦截器,往请求头部添加 token 字段,实现全局添加 token
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {// 打印请求报文
|
||||
// 获取请求
|
||||
Request request = chain.request();
|
||||
// 取出请求参数
|
||||
String reqBody = getRequestBody(request);
|
||||
|
||||
// 打印请求报文
|
||||
log.info("【" + TAG + "】 " + String.format("发送新请求\n-\tmethod:%s\n-\turl:%s\n-\theaders: %s\n-\tbody:%s",
|
||||
request.method(), request.url(), request.headers(), reqBody));
|
||||
// 执行请求,获取响应报文
|
||||
Response response = chain.proceed(request);
|
||||
// 获取响应内容 TODO 读取结果乱码
|
||||
String respBody = getResponseBody(response);
|
||||
// 打印响应报文
|
||||
// log.info("【" + TAG + "】 " + String.format("收到响应 %s %s\n-\t请求url:%s\n-\t请求body:%s\n-\t响应body:%s",
|
||||
// response.code(), response.message(), response.request().url(), reqBody, respBody));
|
||||
log.info("【" + TAG + "】 " + String.format("收到响应 %s %s\n-\t请求url:%s\n-\t请求body:%s",
|
||||
response.code(), response.message(), response.request().url(), reqBody));
|
||||
|
||||
return response;
|
||||
}
|
||||
};
|
||||
|
||||
Dispatcher dispatcher = new Dispatcher();
|
||||
dispatcher.setMaxRequests(16);
|
||||
dispatcher.setMaxRequestsPerHost(4);
|
||||
|
||||
HttpSslConfig httpSslConfig = HttpSslFactory.generateSslConfig();
|
||||
|
||||
return new OkHttpClient.Builder()
|
||||
.retryOnConnectionFailure(true)
|
||||
.addNetworkInterceptor(tokenInterceptor)
|
||||
.dispatcher(dispatcher)
|
||||
.cookieJar(cookieJar())
|
||||
.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES))
|
||||
.connectTimeout(10L, TimeUnit.SECONDS)
|
||||
.readTimeout(500L, TimeUnit.SECONDS)
|
||||
.writeTimeout(0L, TimeUnit.SECONDS)
|
||||
.pingInterval(60L, TimeUnit.SECONDS)
|
||||
.sslSocketFactory(httpSslConfig.getSslSocketFactory(), httpSslConfig.getTrustManager())
|
||||
.hostnameVerifier(HttpSslFactory.generateUnSafeHostnameVerifier())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求体
|
||||
*/
|
||||
private static String getRequestBody(Request request) throws IOException {
|
||||
RequestBody requestBody = request.body();
|
||||
String reqBody = null;
|
||||
if (requestBody != null) {
|
||||
Buffer buffer = new Buffer();
|
||||
requestBody.writeTo(buffer);
|
||||
|
||||
Charset charset = StandardCharsets.UTF_8;
|
||||
MediaType contentType = requestBody.contentType();
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(StandardCharsets.UTF_8);
|
||||
}
|
||||
reqBody = buffer.readString(charset);
|
||||
}
|
||||
|
||||
return reqBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取返回体
|
||||
*/
|
||||
private static String getResponseBody(Response response) throws IOException {
|
||||
ResponseBody responseBody = response.body();
|
||||
String respBody = null;
|
||||
if (responseBody != null) {
|
||||
BufferedSource source = responseBody.source();
|
||||
source.request(Long.MAX_VALUE); // Buffer the entire body.
|
||||
Buffer buffer = source.getBuffer();
|
||||
|
||||
Charset charset = Charsets.UTF_8;
|
||||
MediaType contentType = responseBody.contentType();
|
||||
if (contentType != null)
|
||||
charset = contentType.charset(charset);
|
||||
|
||||
//获取Response的body的字符串 并打印
|
||||
respBody = buffer.clone().readString(charset);
|
||||
}
|
||||
|
||||
return respBody;
|
||||
}
|
||||
|
||||
private static CookieJar cookieJar() {
|
||||
return new CookieJar() {
|
||||
@Override
|
||||
public void saveFromResponse(@NotNull HttpUrl httpUrl, @NotNull List<Cookie> list) {
|
||||
cookieStore.put(httpUrl.host(), list);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<Cookie> loadForRequest(@NotNull HttpUrl httpUrl) {
|
||||
List<Cookie> cookies = cookieStore.get(httpUrl.host());
|
||||
return cookies != null ? cookies : new ArrayList<Cookie>();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
19
src/main/java/top/octopusyan/http/request/LoginParam.java
Normal file
19
src/main/java/top/octopusyan/http/request/LoginParam.java
Normal file
@ -0,0 +1,19 @@
|
||||
package top.octopusyan.http.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 登录参数
|
||||
* @create : 2022-4-1 23:36
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class LoginParam {
|
||||
/** 用户名 */
|
||||
private String username;
|
||||
/** 密码 */
|
||||
private String password;
|
||||
}
|
31
src/main/java/top/octopusyan/http/request/ProxySetup.java
Normal file
31
src/main/java/top/octopusyan/http/request/ProxySetup.java
Normal file
@ -0,0 +1,31 @@
|
||||
package top.octopusyan.http.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description :
|
||||
* @create : 2022-4-5 10:08
|
||||
*/
|
||||
@Data
|
||||
public class ProxySetup {
|
||||
private Integer id;
|
||||
private Integer node;
|
||||
private String proxy_name;
|
||||
private String proxy_type;
|
||||
private String local_ip;
|
||||
private Integer local_port;
|
||||
private Integer remote_port;
|
||||
private String domain;
|
||||
private Boolean use_encryption;
|
||||
private Boolean use_compression;
|
||||
private String locations;
|
||||
private String host_header_rewrite;
|
||||
private String header_X_From_Where;
|
||||
private String sk;
|
||||
private Boolean status;
|
||||
private int sort;
|
||||
private boolean runing;
|
||||
private boolean isClose;
|
||||
}
|
19
src/main/java/top/octopusyan/http/request/RegisterParam.java
Normal file
19
src/main/java/top/octopusyan/http/request/RegisterParam.java
Normal file
@ -0,0 +1,19 @@
|
||||
package top.octopusyan.http.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 注册接口参数
|
||||
* @create : 2022-4-4 20:57
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class RegisterParam {
|
||||
private String username;
|
||||
private String password;
|
||||
private String email;
|
||||
private String verifycode;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package top.octopusyan.http.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 发送邮箱验证接口参数
|
||||
* @create : 2022-4-4 17:07
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class SendEmailCheckParam {
|
||||
String mail;
|
||||
}
|
38
src/main/java/top/octopusyan/manager/ThreadPoolManager.java
Normal file
38
src/main/java/top/octopusyan/manager/ThreadPoolManager.java
Normal file
@ -0,0 +1,38 @@
|
||||
package top.octopusyan.manager;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* author : Android 轮子哥
|
||||
* github : https://github.com/getActivity/AndroidProject
|
||||
* time : 2020/01/11
|
||||
* desc : 线程池管理类
|
||||
*/
|
||||
public final class ThreadPoolManager extends ThreadPoolExecutor {
|
||||
|
||||
private static volatile ThreadPoolManager sInstance;
|
||||
|
||||
private static ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
private ThreadPoolManager() {
|
||||
super(0, Integer.MAX_VALUE,
|
||||
30L, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<>());
|
||||
}
|
||||
|
||||
public static ThreadPoolManager getInstance() {
|
||||
if(sInstance == null) sInstance = new ThreadPoolManager();
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public static ScheduledExecutorService getScheduledExecutorService(int corePoolSize) {
|
||||
if (scheduledExecutorService == null) {
|
||||
scheduledExecutorService = Executors.newScheduledThreadPool(corePoolSize);
|
||||
}
|
||||
return scheduledExecutorService;
|
||||
}
|
||||
|
||||
public static ScheduledExecutorService getScheduledExecutorService() {
|
||||
return getScheduledExecutorService(5);
|
||||
}
|
||||
}
|
205
src/main/java/top/octopusyan/utils/AlertUtil.java
Normal file
205
src/main/java/top/octopusyan/utils/AlertUtil.java
Normal file
@ -0,0 +1,205 @@
|
||||
package top.octopusyan.utils;
|
||||
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : Dialog对话框
|
||||
* @create : 2022-4-4 18:21
|
||||
*/
|
||||
public class AlertUtil {
|
||||
private static Window mOwner;
|
||||
|
||||
public static void initOwner(Stage stage) {
|
||||
AlertUtil.mOwner = stage;
|
||||
}
|
||||
|
||||
public static class Builder<T extends Dialog> {
|
||||
T alert;
|
||||
|
||||
public Builder(T alert) {
|
||||
this.alert = alert;
|
||||
if(mOwner != null) this.alert.initOwner(mOwner);
|
||||
}
|
||||
|
||||
public Builder title(String title) {
|
||||
alert.setTitle(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder header(String header) {
|
||||
alert.setHeaderText(header);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder content(String content) {
|
||||
alert.setContentText(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder icon(String path) {
|
||||
icon(new Image(this.getClass().getResource(path).toString()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder icon(Image image) {
|
||||
getStage().getIcons().add(image);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void show(OnClickListener listener) {
|
||||
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
|
||||
listener.onClicked(result.get().getText());
|
||||
}
|
||||
|
||||
private void show(OnChoseListener listener) {
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
if (result.get() == ButtonType.OK) {
|
||||
listener.confirm();
|
||||
} else {
|
||||
listener.cancelOrClose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果用户点击了取消按钮,将会返回null
|
||||
*/
|
||||
public String getInput() {
|
||||
Optional<String> result = alert.showAndWait();
|
||||
if (result.isPresent()) {
|
||||
return result.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <R> R getChoice(R... choices) {
|
||||
Optional<R> result = alert.showAndWait();
|
||||
return result.get();
|
||||
}
|
||||
|
||||
private Stage getStage() {
|
||||
return (Stage) alert.getDialogPane().getScene().getWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public static Builder info(String content) {
|
||||
return new Builder(new Alert(AlertType.INFORMATION)).content(content).header(null);
|
||||
}
|
||||
|
||||
public static Builder info() {
|
||||
return new Builder(new Alert(AlertType.INFORMATION));
|
||||
}
|
||||
|
||||
public static Builder error(String message) {
|
||||
return new Builder(new Alert(AlertType.ERROR)).header(null).content(message);
|
||||
}
|
||||
|
||||
public static Builder warning() {
|
||||
return new Builder(new Alert(AlertType.WARNING));
|
||||
}
|
||||
|
||||
public static Builder exception(Exception ex) {
|
||||
return new Builder(exceptionAlert(ex));
|
||||
}
|
||||
|
||||
public static Alert exceptionAlert(Exception ex) {
|
||||
Alert alert = new Alert(AlertType.ERROR);
|
||||
alert.setTitle("Exception Dialog");
|
||||
alert.setHeaderText(ex.getClass().getSimpleName());
|
||||
alert.setContentText(ex.getMessage());
|
||||
|
||||
// 创建可扩展的异常。
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
ex.printStackTrace(pw);
|
||||
String exceptionText = sw.toString();
|
||||
|
||||
Label label = new Label("The exception stacktrace was :");
|
||||
|
||||
TextArea textArea = new TextArea(exceptionText);
|
||||
textArea.setEditable(false);
|
||||
textArea.setWrapText(true);
|
||||
|
||||
textArea.setMaxWidth(Double.MAX_VALUE);
|
||||
textArea.setMaxHeight(Double.MAX_VALUE);
|
||||
GridPane.setVgrow(textArea, Priority.ALWAYS);
|
||||
GridPane.setHgrow(textArea, Priority.ALWAYS);
|
||||
|
||||
GridPane expContent = new GridPane();
|
||||
expContent.setMaxWidth(Double.MAX_VALUE);
|
||||
expContent.add(label, 0, 0);
|
||||
expContent.add(textArea, 0, 1);
|
||||
|
||||
// 将可扩展异常设置到对话框窗格中。
|
||||
alert.getDialogPane().setExpandableContent(expContent);
|
||||
return alert;
|
||||
}
|
||||
|
||||
public static Builder confirm() {
|
||||
Alert alert = new Alert(AlertType.CONFIRMATION);
|
||||
alert.setTitle("确认对话框");
|
||||
return new Builder(alert);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义确认对话框 <p>
|
||||
* <code>"Cancel"</code> OR <code>"取消"</code> 为取消按钮
|
||||
*/
|
||||
public static Builder confirm(String... buttons) {
|
||||
Alert alert = new Alert(AlertType.CONFIRMATION);
|
||||
|
||||
List<ButtonType> buttonList = Arrays.stream(buttons).map((type) -> {
|
||||
ButtonBar.ButtonData buttonData = ButtonBar.ButtonData.OTHER;
|
||||
if ("Cancel".equals(type) || "取消".equals(type))
|
||||
buttonData = ButtonBar.ButtonData.CANCEL_CLOSE;
|
||||
return new ButtonType(type, buttonData);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
alert.getButtonTypes().setAll(buttonList);
|
||||
|
||||
return new Builder(alert);
|
||||
}
|
||||
|
||||
public static Builder input(String content) {
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setContentText(content);
|
||||
return new Builder(dialog);
|
||||
}
|
||||
|
||||
public static <T> Builder choices(String hintText, T... choices) {
|
||||
ChoiceDialog<T> dialog = new ChoiceDialog<T>(choices[0], choices);
|
||||
dialog.setContentText(hintText);
|
||||
return new Builder(dialog);
|
||||
}
|
||||
|
||||
|
||||
interface OnChoseListener {
|
||||
void confirm();
|
||||
|
||||
void cancelOrClose();
|
||||
}
|
||||
|
||||
interface OnClickListener {
|
||||
void onClicked(String result);
|
||||
}
|
||||
}
|
102
src/main/java/top/octopusyan/utils/ApplicatonStore.java
Normal file
102
src/main/java/top/octopusyan/utils/ApplicatonStore.java
Normal file
@ -0,0 +1,102 @@
|
||||
package top.octopusyan.utils;
|
||||
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import top.octopusyan.http.request.ProxySetup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 应用信息
|
||||
* @create : 2022-4-4 17:22
|
||||
*/
|
||||
public class ApplicatonStore {
|
||||
private static SimpleStringProperty account = new SimpleStringProperty();
|
||||
private static SimpleStringProperty password = new SimpleStringProperty();
|
||||
private static SimpleStringProperty email = new SimpleStringProperty();
|
||||
private static SimpleStringProperty sendMailCode = new SimpleStringProperty();
|
||||
private static SimpleBooleanProperty autoLogin = new SimpleBooleanProperty();
|
||||
private static SimpleBooleanProperty rememberMe = new SimpleBooleanProperty();
|
||||
private static SimpleBooleanProperty registerSuccess = new SimpleBooleanProperty();
|
||||
private static SimpleListProperty<ProxySetup> proxySetupList = new SimpleListProperty<>();
|
||||
private static SimpleIntegerProperty selectProxy = new SimpleIntegerProperty(0);
|
||||
|
||||
public static void setAccount(String account) {
|
||||
ApplicatonStore.account.set(account);
|
||||
}
|
||||
|
||||
public static void setEmail(String email) {
|
||||
ApplicatonStore.email.set(email);
|
||||
}
|
||||
|
||||
public static void setPassword(String password) {
|
||||
ApplicatonStore.password.set(password);
|
||||
}
|
||||
|
||||
public static void setSendMailCode(String sendMailCode) {
|
||||
ApplicatonStore.sendMailCode.set(sendMailCode);
|
||||
}
|
||||
|
||||
public static String getAccount() {
|
||||
return account.get();
|
||||
}
|
||||
|
||||
public static String getEmail() {
|
||||
return email.get();
|
||||
}
|
||||
|
||||
public static String getPassword() {
|
||||
return password.get();
|
||||
}
|
||||
|
||||
public static String getSendMailCode() {
|
||||
return sendMailCode.get();
|
||||
}
|
||||
|
||||
public static void setAutoLogin(boolean autoLogin) {
|
||||
ApplicatonStore.autoLogin.set(autoLogin);
|
||||
}
|
||||
|
||||
public static void setRememberMe(boolean rememberMe) {
|
||||
ApplicatonStore.rememberMe.set(rememberMe);
|
||||
}
|
||||
|
||||
public static boolean isAutoLogin() {
|
||||
return autoLogin.get();
|
||||
}
|
||||
|
||||
public static boolean isRememberMe() {
|
||||
return rememberMe.get();
|
||||
}
|
||||
|
||||
public static void setRegisterSuccess(boolean registerSuccess) {
|
||||
ApplicatonStore.registerSuccess.set(registerSuccess);
|
||||
}
|
||||
|
||||
public static boolean isRegisterSuccess() {
|
||||
return registerSuccess.get();
|
||||
}
|
||||
|
||||
public static ObservableList<ProxySetup> getProxySetupList() {
|
||||
return proxySetupList.get();
|
||||
}
|
||||
|
||||
public static void setProxySetupList(List<ProxySetup> proxySetupList) {
|
||||
ApplicatonStore.proxySetupList = new SimpleListProperty<>(FXCollections.observableList(proxySetupList));
|
||||
}
|
||||
|
||||
public static void setSelectProxy(int selectProxy) {
|
||||
ApplicatonStore.selectProxy.set(selectProxy);
|
||||
}
|
||||
|
||||
public static int getSelectProxy() {
|
||||
return selectProxy.get();
|
||||
}
|
||||
}
|
458
src/main/java/top/octopusyan/utils/EncryptionUtil.java
Normal file
458
src/main/java/top/octopusyan/utils/EncryptionUtil.java
Normal file
@ -0,0 +1,458 @@
|
||||
package top.octopusyan.utils;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.digest.MD5;
|
||||
import sun.misc.BASE64Encoder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.DESKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 常用加密工具类
|
||||
* @create : 2021-03-18 19:03
|
||||
*/
|
||||
public class EncryptionUtil {
|
||||
private static EncryptionUtil util;
|
||||
private static Logger logger = LoggerFactory.getLogger(EncryptionUtil.class);
|
||||
|
||||
private static BASE64Encoder BASE64Encoder;
|
||||
private static MessageDigest MessageDigest_SHA1;
|
||||
private static MessageDigest MessageDigest_MD5;
|
||||
private static Cipher Cipher_ECB_MOB;
|
||||
private static Cipher Cipher_ALGORITHM_MODE_PADDING;
|
||||
private static Signature Signature_SHA256_RSA;
|
||||
|
||||
/**
|
||||
* 密钥算法
|
||||
*/
|
||||
private static final String DES = "DES";
|
||||
private static final String AES = "AES";
|
||||
private static final String RES = "RES";
|
||||
private static final String RSA = "RSA";
|
||||
/**
|
||||
* 加解密算法/工作模式/填充方式
|
||||
*/
|
||||
private static final String ECB_MOB = "DES/ECB/PKCS5Padding";
|
||||
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
|
||||
|
||||
private static final Charset UTF_8 = StandardCharsets.UTF_8;
|
||||
|
||||
private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
|
||||
'E', 'F'};
|
||||
private static final char hexDigitsLower[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
|
||||
'd', 'e', 'f'};
|
||||
|
||||
public static synchronized EncryptionUtil getInstance() {
|
||||
if (util == null) {
|
||||
BASE64Encoder = new BASE64Encoder();
|
||||
try {
|
||||
MessageDigest_SHA1 = MessageDigest.getInstance("SHA-1");
|
||||
MessageDigest_MD5 = MessageDigest.getInstance("MD5");
|
||||
Cipher_ECB_MOB = Cipher.getInstance(ECB_MOB);
|
||||
Cipher_ALGORITHM_MODE_PADDING = Cipher.getInstance(ALGORITHM_MODE_PADDING);
|
||||
Signature_SHA256_RSA = Signature.getInstance("SHA256WithRSA");
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return util;
|
||||
}
|
||||
|
||||
public static String getSha1(String str) {
|
||||
if (str == null || str.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
return getMessageDigestString(str, hexDigitsLower, MessageDigest_SHA1);
|
||||
}
|
||||
|
||||
public static String getSHA(String spara) {
|
||||
String sRtn = null;
|
||||
try {
|
||||
byte[] plainText = spara.getBytes(UTF_8);
|
||||
// 开始使用算法
|
||||
MessageDigest_SHA1.update(plainText);
|
||||
// 输出算法运算结果
|
||||
sRtn = BASE64Encoder.encode(MessageDigest_SHA1.digest());
|
||||
} catch (Exception e) {
|
||||
logger.error("SHA-1 160加密出错:" + e.getMessage());
|
||||
}
|
||||
return sRtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* DES加密
|
||||
*
|
||||
* @param data 加密内容
|
||||
* @param password 秘钥
|
||||
*/
|
||||
public static String DESencode(String data, String password) {
|
||||
byte[] pasByte;
|
||||
try {
|
||||
Key key = getDESKey(password);
|
||||
Cipher_ECB_MOB.init(Cipher.ENCRYPT_MODE, key);
|
||||
pasByte = Cipher_ECB_MOB.doFinal(data.getBytes(UTF_8));
|
||||
} catch (Exception e) {
|
||||
logger.error("DES加密出错:" + e.getMessage());
|
||||
throw new RuntimeException("DES加密出错:" + e.getMessage());
|
||||
}
|
||||
return Base64.encode(pasByte);
|
||||
}
|
||||
|
||||
/**
|
||||
* DES解密
|
||||
*
|
||||
* @param data 加密内容
|
||||
* @param password 秘钥
|
||||
* @return
|
||||
*/
|
||||
public static String DESdecode(String data, String password) throws Exception {
|
||||
byte[] pasByte;
|
||||
Key key = getDESKey(password);
|
||||
Cipher_ECB_MOB.init(Cipher.DECRYPT_MODE, key);
|
||||
byte[] x = Base64.decode(data);
|
||||
pasByte = Cipher_ECB_MOB.doFinal(x);
|
||||
return new String(pasByte, UTF_8);
|
||||
}
|
||||
|
||||
private static Key getDESKey(String password) throws Exception {
|
||||
byte[] DESkey = password.getBytes(UTF_8);
|
||||
DESKeySpec keySpec = new DESKeySpec(DESkey);
|
||||
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
|
||||
return keyFactory.generateSecret(keySpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES加密
|
||||
*
|
||||
* @param data 加密内容
|
||||
* @param password 加密密码
|
||||
*/
|
||||
public static String AESencode(String data, String password) throws Exception {
|
||||
byte[] result;
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
// 创建密码器
|
||||
// 初始化为加密模式的密码
|
||||
Cipher_ALGORITHM_MODE_PADDING.init(Cipher.ENCRYPT_MODE, getSecretKey(password));
|
||||
// 加密
|
||||
result = Cipher_ALGORITHM_MODE_PADDING.doFinal(data.getBytes());
|
||||
|
||||
return Base64.encode(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
*
|
||||
* @param base64Data 解密内容
|
||||
* @param password 解密密码
|
||||
*/
|
||||
public static String AESdecode(String base64Data, String password) throws Exception {
|
||||
byte[] result;
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
// 使用密钥初始化,设置为解密模式
|
||||
Cipher_ALGORITHM_MODE_PADDING.init(Cipher.DECRYPT_MODE, getSecretKey(password));
|
||||
// 执行操作
|
||||
result = Cipher_ALGORITHM_MODE_PADDING.doFinal(Base64.decode(base64Data));
|
||||
|
||||
return new String(result, UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成AES加密秘钥
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static SecretKeySpec getSecretKey(String password) {
|
||||
return new SecretKeySpec(SecureUtil.md5(password).toLowerCase().getBytes(), AES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对字符串 MD5 无盐值加密
|
||||
*
|
||||
* @param plainText 传入要加密的字符串
|
||||
* @return MD5加密后生成32位(小写字母 + 数字)字符串
|
||||
*/
|
||||
public static String MD5Lower(String plainText) {
|
||||
// 使用指定的字节更新摘要
|
||||
MessageDigest_MD5.update(plainText.getBytes());
|
||||
// digest()最后确定返回md5 hash值,返回值为8位字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
|
||||
// BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值。1 固定值
|
||||
return new BigInteger(1, MessageDigest_MD5.digest()).toString(16);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 对字符串 MD5 加密
|
||||
*
|
||||
* @param plainText 传入要加密的字符串
|
||||
* @return MD5加密后生成32位(大写字母 + 数字)字符串
|
||||
*/
|
||||
public static String MD5Upper(String plainText) {
|
||||
// 使用指定的字节更新摘要
|
||||
MessageDigest_MD5.update(plainText.getBytes());
|
||||
// 获得密文
|
||||
byte[] mdResult = MessageDigest_MD5.digest();
|
||||
// 把密文转换成十六进制的字符串形式
|
||||
return getHexString(hexDigitsLower, mdResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对字符串 MD5 加盐值加密
|
||||
*
|
||||
* @param plainText 传入要加密的字符串
|
||||
* @param saltValue 传入要加的盐值
|
||||
* @return MD5加密后生成32位(小写字母 + 数字)字符串
|
||||
*/
|
||||
public static String MD5Lower(String plainText, String saltValue) {
|
||||
// 使用指定的字节更新摘要
|
||||
MessageDigest_MD5.update(plainText.getBytes());
|
||||
MessageDigest_MD5.update(saltValue.getBytes());
|
||||
|
||||
// digest()最后确定返回md5 hash值,返回值为8位字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
|
||||
// BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值。1 固定值
|
||||
return new BigInteger(1, MessageDigest_MD5.digest()).toString(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对字符串 MD5 加盐值加密
|
||||
*
|
||||
* @param plainText 传入要加密的字符串
|
||||
* @param saltValue 传入要加的盐值
|
||||
* @return MD5加密后生成32位(大写字母 + 数字)字符串
|
||||
*/
|
||||
public static String MD5Upper(String plainText, String saltValue) {
|
||||
try {
|
||||
// 使用指定的字节更新摘要
|
||||
MessageDigest_MD5.update(plainText.getBytes());
|
||||
MessageDigest_MD5.update(saltValue.getBytes());
|
||||
|
||||
// 获得密文
|
||||
byte[] mdResult = MessageDigest_MD5.digest();
|
||||
// 把密文转换成十六进制的字符串形式
|
||||
return getHexString(hexDigitsLower, mdResult);
|
||||
} catch (Exception e) {
|
||||
logger.error("MDS 加密出错:" + e.getMessage());
|
||||
throw new RuntimeException("MDS 加密出错:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MD5加密后生成32位(小写字母+数字)字符串
|
||||
* 同 MD5Lower() 一样
|
||||
*/
|
||||
public static String MD5(String plainText) {
|
||||
return getMessageDigestString(plainText, hexDigitsLower, MessageDigest_MD5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验MD5码
|
||||
*
|
||||
* @param text 要校验的字符串
|
||||
* @param md5 md5值
|
||||
* @return 校验结果
|
||||
*/
|
||||
public static boolean MD5valid(String text, String md5) throws Exception {
|
||||
return md5.equals(MD5(text)) || md5.equals(MD5(text).toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验MD5码
|
||||
*
|
||||
* @param text 要校验的字符串
|
||||
* @param md5 md5值
|
||||
* @param salt 加密盐
|
||||
* @return 校验结果
|
||||
*/
|
||||
public static boolean MD5valid(String text, String md5, String salt) {
|
||||
return md5.equals(MD5Lower(text, salt)) || md5.equals(MD5Upper(text, salt).toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA2签名
|
||||
*
|
||||
* @param content 待签名的字符串
|
||||
* @param privateKey rsa私钥字符串
|
||||
* @param charset 字符集编码
|
||||
* @return 签名结果
|
||||
*/
|
||||
public static String rsaSign(String content, String privateKey, String charset) throws Exception {
|
||||
PrivateKey priKey = getPrivateKeyFromPKCS8(RSA, new ByteArrayInputStream(privateKey.getBytes()));
|
||||
Signature_SHA256_RSA.initSign(priKey);
|
||||
if (StringUtils.isBlank(charset)) {
|
||||
Signature_SHA256_RSA.update(content.getBytes());
|
||||
} else {
|
||||
Signature_SHA256_RSA.update(content.getBytes(charset));
|
||||
}
|
||||
byte[] signed = Signature_SHA256_RSA.sign();
|
||||
return new String(Base64.encode(signed));
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA2验签
|
||||
*
|
||||
* @param content 被签名的内容
|
||||
* @param sign 签名后的结果
|
||||
* @param publicKey rsa公钥
|
||||
* @param charset 字符集编码
|
||||
* @return 验签结果
|
||||
*/
|
||||
public static boolean doCheck(String content, String sign, String publicKey, String charset) throws Exception {
|
||||
PublicKey pubKey = getPublicKeyFromX509(RSA, new ByteArrayInputStream(publicKey.getBytes()));
|
||||
Signature_SHA256_RSA.initVerify(pubKey);
|
||||
Signature_SHA256_RSA.update(getContentBytes(content, charset));
|
||||
return Signature_SHA256_RSA.verify(Base64.decode(sign));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取私钥对象
|
||||
*
|
||||
* @param algorithm 签名方式
|
||||
* @param ins 私钥流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
private static PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception {
|
||||
if (ins == null || StringUtils.isEmpty(algorithm)) {
|
||||
return null;
|
||||
}
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
|
||||
byte[] encodedKey = readText(ins, UTF_8, true).getBytes();
|
||||
encodedKey = Base64.decode(Arrays.toString(encodedKey));
|
||||
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公钥对象
|
||||
*
|
||||
* @param algorithm 签名方式
|
||||
* @param ins 公钥流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
private static PublicKey getPublicKeyFromX509(String algorithm, InputStream ins) throws Exception {
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
|
||||
StringWriter writer = new StringWriter();
|
||||
io(new InputStreamReader(ins), writer, true, true);
|
||||
byte[] encodedKey = writer.toString().getBytes();
|
||||
// 先base64解码
|
||||
encodedKey = Base64.decode(Arrays.toString(encodedKey));
|
||||
return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串对应编码的字节
|
||||
*
|
||||
* @param content 字符串内容
|
||||
* @param charset 字符集编码
|
||||
* @return
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
private static byte[] getContentBytes(String content, String charset) throws UnsupportedEncodingException {
|
||||
if (StringUtils.isEmpty(charset)) {
|
||||
return content.getBytes();
|
||||
}
|
||||
return content.getBytes(charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定输入流的所有文本全部读出到一个字符串中
|
||||
*
|
||||
* @param in 输入流
|
||||
* @param charset 字符集编码
|
||||
* @param closeIn 是否关闭流
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private static String readText(InputStream in, Charset charset, boolean closeIn) throws IOException {
|
||||
Reader reader = charset == null ? new InputStreamReader(in) : new InputStreamReader(in, charset);
|
||||
return readText(reader, closeIn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定<code>Reader</code>的所有文本全部读出到一个字符串中
|
||||
*
|
||||
* @param in 输入流
|
||||
* @param closeIn 是否关闭流
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private static String readText(Reader in, boolean closeIn) throws IOException {
|
||||
StringWriter out = new StringWriter();
|
||||
io(in, out, closeIn, true);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从输入流读取内容,写入到输出流中
|
||||
*
|
||||
* @param in 输入流
|
||||
* @param out 输出流
|
||||
* @param closeIn 是否关闭流
|
||||
* @param closeOut 是否关闭流
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void io(Reader in, Writer out, boolean closeIn, boolean closeOut) {
|
||||
int bufferSize = 8192 >> 1;
|
||||
char[] buffer = new char[bufferSize];
|
||||
int amount;
|
||||
try {
|
||||
while ((amount = in.read(buffer)) >= 0) {
|
||||
out.write(buffer, 0, amount);
|
||||
}
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
logger.error("从输入流读取内容,写入到输出流中异常:{}", e.getMessage(), e);
|
||||
} finally {
|
||||
if (closeIn) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("从输入流读取内容,写入到输出流中异常:{}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
if (closeOut) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("从输入流读取内容,写入到输出流中异常:{}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getMessageDigestString(String str, char[] hexDigits, MessageDigest messageDigest) {
|
||||
messageDigest.update(str.getBytes(UTF_8));
|
||||
|
||||
byte[] md = messageDigest.digest();
|
||||
return getHexString(hexDigits, md);
|
||||
}
|
||||
|
||||
private static String getHexString(char[] hexDigits, byte[] md) {
|
||||
int j = md.length;
|
||||
char[] buf = new char[j * 2];
|
||||
int k = 0;
|
||||
for (byte byteO : md) {
|
||||
buf[k++] = hexDigits[byteO >>> 4 & 0xf];
|
||||
buf[k++] = hexDigits[byteO & 0xf];
|
||||
}
|
||||
return new String(buf);
|
||||
}
|
||||
}
|
||||
|
44
src/main/java/top/octopusyan/utils/KaptchaUtil.java
Normal file
44
src/main/java/top/octopusyan/utils/KaptchaUtil.java
Normal file
@ -0,0 +1,44 @@
|
||||
package top.octopusyan.utils;
|
||||
|
||||
import com.google.code.kaptcha.Producer;
|
||||
import com.google.code.kaptcha.impl.DefaultKaptcha;
|
||||
import com.google.code.kaptcha.util.Config;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 验证码图片生成
|
||||
* @create : 2022-4-3 23:34
|
||||
*/
|
||||
public class KaptchaUtil {
|
||||
public static volatile KaptchaUtil util;
|
||||
private DefaultKaptcha defaultKaptcha;
|
||||
|
||||
private KaptchaUtil(DefaultKaptcha kaptcha){
|
||||
this.defaultKaptcha = kaptcha;
|
||||
}
|
||||
|
||||
public static KaptchaUtil getInstance() {
|
||||
Properties properties = new Properties();
|
||||
properties.put("kaptcha.border", "no");
|
||||
properties.put("kaptcha.textproducer.font.color", "black");
|
||||
properties.put("kaptcha.textproducer.char.space", "5");
|
||||
properties.put("kaptcha.textproducer.font.names", "Arial,Courier,cmr10,宋体,楷体,微软雅黑");
|
||||
Config config = new Config(properties);
|
||||
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
|
||||
defaultKaptcha.setConfig(config);
|
||||
if(util == null) util = new KaptchaUtil(defaultKaptcha);
|
||||
return util;
|
||||
}
|
||||
|
||||
public BufferedImage getCaptcha(String code) {
|
||||
return util.defaultKaptcha.createImage(code);
|
||||
}
|
||||
|
||||
public String createCode(){
|
||||
return util.defaultKaptcha.createText();
|
||||
}
|
||||
}
|
84
src/main/java/top/octopusyan/utils/Loading.java
Normal file
84
src/main/java/top/octopusyan/utils/Loading.java
Normal file
@ -0,0 +1,84 @@
|
||||
package top.octopusyan.utils;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 加载等待弹窗
|
||||
* @create : 2022-4-6 10:48
|
||||
*/
|
||||
public class Loading {
|
||||
protected Stage stage;
|
||||
protected StackPane root;
|
||||
protected Label messageLb;
|
||||
protected ImageView loadingView = new ImageView(new Image("https://blog-static.cnblogs.com/files/miaoqx/loading.gif"));
|
||||
|
||||
public Loading(Stage owner) {
|
||||
|
||||
messageLb = new Label("请耐心等待...");
|
||||
messageLb.setFont(Font.font(20));
|
||||
|
||||
root = new StackPane();
|
||||
root.setMouseTransparent(true);
|
||||
root.setPrefSize(owner.getWidth(), owner.getHeight());
|
||||
root.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.3), null, null)));
|
||||
root.getChildren().addAll(loadingView, messageLb);
|
||||
|
||||
Scene scene = new Scene(root);
|
||||
scene.setFill(Color.TRANSPARENT);
|
||||
|
||||
stage = new Stage();
|
||||
stage.setX(owner.getX());
|
||||
stage.setY(owner.getY());
|
||||
stage.setScene(scene);
|
||||
stage.setResizable(false);
|
||||
stage.initOwner(owner);
|
||||
stage.initStyle(StageStyle.TRANSPARENT);
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
stage.getIcons().addAll(owner.getIcons());
|
||||
stage.setX(owner.getX());
|
||||
stage.setY(owner.getY());
|
||||
stage.setHeight(owner.getHeight());
|
||||
stage.setWidth(owner.getWidth());
|
||||
}
|
||||
|
||||
// 更改信息
|
||||
public Loading showMessage(String message) {
|
||||
Platform.runLater(() -> messageLb.setText(message));
|
||||
return this;
|
||||
}
|
||||
|
||||
// 更改信息
|
||||
public Loading image(Image image) {
|
||||
Platform.runLater(() -> loadingView.imageProperty().set(image));
|
||||
return this;
|
||||
}
|
||||
|
||||
// 显示
|
||||
public void show() {
|
||||
Platform.runLater(() -> stage.show());
|
||||
}
|
||||
|
||||
// 关闭
|
||||
public void closeStage() {
|
||||
Platform.runLater(() -> stage.close());
|
||||
}
|
||||
|
||||
// 是否正在展示
|
||||
public boolean showing(){
|
||||
return stage.isShowing();
|
||||
}
|
||||
}
|
162
src/main/java/top/octopusyan/utils/ProxyUtil.java
Normal file
162
src/main/java/top/octopusyan/utils/ProxyUtil.java
Normal file
@ -0,0 +1,162 @@
|
||||
package top.octopusyan.utils;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
import top.octopusyan.common.http.EasyHttp;
|
||||
import top.octopusyan.common.http.listener.OnHttpListener;
|
||||
import top.octopusyan.common.http.model.ResponseClass;
|
||||
import top.octopusyan.config.ProxyConfig.*;
|
||||
import top.octopusyan.http.Api;
|
||||
import top.octopusyan.http.request.ProxySetup;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : 代理设置工具
|
||||
* @create : 2022-4-5 09:56
|
||||
*/
|
||||
public class ProxyUtil {
|
||||
private static String csrf;
|
||||
|
||||
/**
|
||||
* 初始化隧道设置
|
||||
*/
|
||||
public static ProxySetup initProxy() {
|
||||
ProxySetup setup = new ProxySetup();
|
||||
setup.setNode(3);// 上海服务器
|
||||
setup.setLocal_ip("127.0.0.1");
|
||||
setup.setLocal_port(80);
|
||||
setup.setProxy_name("默认连接");
|
||||
setup.setProxy_type(ProxyType.HTTP.name());
|
||||
setup.setUse_compression(true);
|
||||
setup.setUse_encryption(true);
|
||||
return setup;
|
||||
}
|
||||
|
||||
public static void delete(Integer id) {
|
||||
EasyHttp.builder()
|
||||
.api(Api.DeleteProxy())
|
||||
.pathParam(id, csrf)
|
||||
.request(result -> { });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取隧道详情信息
|
||||
* @return
|
||||
*/
|
||||
public static ProxySetup info(ProxySetup setup) {
|
||||
|
||||
String html;
|
||||
try {
|
||||
html = EasyHttp.builder()
|
||||
.api(Api.ProxyInfo())
|
||||
.pathParam(setup.getId())
|
||||
.execute(new ResponseClass<String>() {
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
Document doc = Jsoup.parse(html);
|
||||
Elements select = doc.select("tbody > tr > td");
|
||||
|
||||
String serverStr = select.get(0).text();
|
||||
String localIp = select.get(3).text();
|
||||
String localPort = select.get(4).text();
|
||||
String webPort = select.get(5).text();
|
||||
String useEnc = select.get(6).text();
|
||||
String useCom = select.get(7).text();
|
||||
String domain = select.get(8).text();
|
||||
String status = select.get(13).text();
|
||||
|
||||
Arrays.stream(ProxyServer.values()).forEach(server -> {
|
||||
if (serverStr.contains(server.name()))
|
||||
setup.setNode(Arrays.asList(ProxyServer.values()).indexOf(server));
|
||||
});
|
||||
|
||||
setup.setLocal_ip(localIp);
|
||||
setup.setLocal_port(Integer.parseInt(localPort));
|
||||
setup.setRemote_port("无".equals(webPort) ? null : Integer.parseInt(webPort));
|
||||
setup.setDomain(domain);
|
||||
setup.setUse_compression(useCom.equals("启用"));
|
||||
setup.setUse_encryption(useEnc.equals("启用"));
|
||||
setup.setStatus(status.equals("启用"));
|
||||
|
||||
return setup;
|
||||
}
|
||||
|
||||
public static int randomPort(){
|
||||
try {
|
||||
return EasyHttp.builder()
|
||||
.api(Api.RandomPort())
|
||||
.execute(new ResponseClass<Integer>() { });
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return (int) (Math.random() * 31000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户隧道列表
|
||||
*/
|
||||
public static void getList(OnHttpListener<List<ProxySetup>> listener) {
|
||||
EasyHttp.builder()
|
||||
.api(Api.ProxyList)
|
||||
.request(new OnHttpListener<String>() {
|
||||
@Override
|
||||
public void onSucceed(String result) {
|
||||
Document doc = Jsoup.parse(result);
|
||||
Elements elementsByClass = doc.select("table > tbody > tr");
|
||||
|
||||
List<ProxySetup> proxySetupList = elementsByClass.stream().map(element -> {
|
||||
ProxySetup proxySetup = new ProxySetup();
|
||||
Elements tds = element.getElementsByTag("td");
|
||||
proxySetup.setId(Integer.parseInt(tds.get(0).text()));
|
||||
proxySetup.setProxy_name(tds.get(1).text());
|
||||
proxySetup.setProxy_type(tds.get(2).text());
|
||||
proxySetup.setSort(Integer.parseInt(tds.get(5).text()));
|
||||
return proxySetup;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
listener.onSucceed(proxySetupList);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean add(OnHttpListener<ProxySetup> listener, ProxySetup setup) {
|
||||
String result = null;
|
||||
try {
|
||||
result = EasyHttp.builder()
|
||||
.api(Api.AddProxy())
|
||||
.param(setup)
|
||||
.execute(new ResponseClass<String>() {
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(result.equals("隧道创建成功")){
|
||||
getList(result1 -> listener.onSucceed(result1.get(result1.size()-1)));
|
||||
} else {
|
||||
AlertUtil.error(result).show();
|
||||
}
|
||||
return result.equals("隧道创建成功");
|
||||
}
|
||||
|
||||
public static String getCsrf() {
|
||||
return csrf;
|
||||
}
|
||||
|
||||
public static void setCsrf(String htmlStr) {
|
||||
int i = htmlStr.indexOf("var csrf_token = \"");
|
||||
int i1 = htmlStr.indexOf("\"", i + 18);
|
||||
ProxyUtil.csrf = htmlStr.substring(i + 18, i1);
|
||||
}
|
||||
}
|
64
src/main/java/top/octopusyan/utils/TextValidate.java
Normal file
64
src/main/java/top/octopusyan/utils/TextValidate.java
Normal file
@ -0,0 +1,64 @@
|
||||
package top.octopusyan.utils;
|
||||
|
||||
import com.jfoenix.validation.RegexValidator;
|
||||
import com.jfoenix.validation.RequiredFieldValidator;
|
||||
import com.jfoenix.validation.StringLengthValidator;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* @author : octopus yan
|
||||
* @email : octopus_yan@foxmail.com
|
||||
* @description : JFX文本校验
|
||||
* @create : 2022-4-2 17:03
|
||||
*/
|
||||
public class TextValidate {
|
||||
// /**
|
||||
// * 账号格式错误
|
||||
// */
|
||||
// public static ValidatorBase AccountFail = new ValidatorBase() {
|
||||
// @Override
|
||||
// protected void eval() {
|
||||
// setMessage("账号格式错误");
|
||||
// hasErrors.set(true);
|
||||
// }
|
||||
// };
|
||||
|
||||
/** 账号不能为空 */
|
||||
public static RequiredFieldValidator AccoountRequired = new RequiredFieldValidator("账号不能为空!");
|
||||
|
||||
/** 账号格式 */
|
||||
public static RegexValidator AccoountValidator = new RegexValidator("账号格式有误!");
|
||||
/** 密码不能为空 */
|
||||
public static RequiredFieldValidator PasswordRequired = new RequiredFieldValidator("密码不能为空!");
|
||||
|
||||
/** 邮箱地址格式验证 */
|
||||
public static RegexValidator EmailFormat = new RegexValidator("邮箱地址格式错误");
|
||||
|
||||
static {
|
||||
EmailFormat.setRegexPattern("^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$");
|
||||
AccoountValidator.setRegexPattern("^[a-zA-Z0-9_-]*$");
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本长度校验
|
||||
* <p/> message > name + "长度有误"
|
||||
*/
|
||||
public static StringLengthValidator getLengthValidator(String name, int length){
|
||||
return new StringLengthValidator(name+"长度有误", length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本长度校验
|
||||
* <p/> message > name + "长度有误,应在" + min + "到" + max + "之间"
|
||||
*/
|
||||
public static RegexValidator getLengthValidator(int min, int max, String name){
|
||||
String message = name + "长度有误,应在" + min + "到" + max + "之间";
|
||||
|
||||
RegexValidator validator = new RegexValidator(message);
|
||||
|
||||
validator.setRegexPattern("[a-zA-Z0-9_-]{" + min + "," + max + "}$");
|
||||
|
||||
return validator;
|
||||
}
|
||||
}
|
47
src/main/resources/css/login.css
Normal file
47
src/main/resources/css/login.css
Normal file
@ -0,0 +1,47 @@
|
||||
/* 圆角 */
|
||||
#loginBkgPane, #loginMainPane {
|
||||
-fx-background-radius: 14;
|
||||
}
|
||||
|
||||
#loginTopPane {
|
||||
-fx-background-radius: 14 14 0 0;
|
||||
}
|
||||
|
||||
#loginBottomPane {
|
||||
-fx-background-radius: 0 0 14 14;
|
||||
}
|
||||
|
||||
/* 字体 */
|
||||
#loginBtn {
|
||||
-fx-font-family: "Microsoft YaHei";
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
|
||||
/* 背景布局 */
|
||||
#loginBkgPane {
|
||||
-fx-image: url('../image/common_pane_bkg.gif');
|
||||
}
|
||||
|
||||
/* 功能布局 */
|
||||
#loginBottomPane {
|
||||
-fx-background-color: rgba(243, 242, 242, 0.70);
|
||||
}
|
||||
|
||||
/* 文本输入框点选状态下划线 */
|
||||
#accountTextField, #pwdTextField, #seePwdTextField {
|
||||
-jfx-focus-color: rgb(138, 103, 255);
|
||||
}
|
||||
|
||||
#seePwdIconBtn{
|
||||
-fx-opacity: 0.4;
|
||||
}
|
||||
|
||||
/* 登录按钮 */
|
||||
#loginBtn {
|
||||
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
|
||||
}
|
||||
|
||||
/* 注册按钮 */
|
||||
#registerBtn:hover {
|
||||
-fx-text-fill: #3e7398;
|
||||
}
|
108
src/main/resources/css/main.css
Normal file
108
src/main/resources/css/main.css
Normal file
@ -0,0 +1,108 @@
|
||||
#MainPane {
|
||||
-fx-background-color: rgb(240, 240, 240);
|
||||
-fx-background-radius: 14 14 14 14;
|
||||
}
|
||||
|
||||
#titleLable, #closeBtn, #minimizeBtn {
|
||||
-fx-text-fill: black;
|
||||
}
|
||||
|
||||
#minimizeBtn:hover {
|
||||
-fx-background-color: rgba(148, 148, 148, 0.40);
|
||||
}
|
||||
|
||||
#titleLable:disabled {
|
||||
-fx-font-size: 18;
|
||||
-fx-opacity: 0.4;
|
||||
}
|
||||
/* 布局边框 */
|
||||
.mainPane, #addProxyBtn , .jfx-list-view{
|
||||
-fx-border-color: linear-gradient(#57b4f2, #9198e5);
|
||||
}
|
||||
|
||||
/* 文本框边框 */
|
||||
.jfx-text-field, .jfx-combo-box {
|
||||
-jfx-focus-color: #00000000;
|
||||
-jfx-unfocus-color: #00000000;
|
||||
}
|
||||
/* 文本框 */
|
||||
.inputText, .inputText-left, .inputText-right{
|
||||
-fx-border-style: solid;
|
||||
-fx-border-color: linear-gradient(#57b4f2, #9198e5);
|
||||
-fx-border-radius: 5;
|
||||
}
|
||||
.inputText-left {
|
||||
-fx-border-radius: 5 0 0 5;
|
||||
}
|
||||
.inputText-right {
|
||||
-fx-border-radius: 0 5 5 0;
|
||||
}
|
||||
/* 服务线路 */
|
||||
#proxyServerCB {
|
||||
-fx-font-size: 16;
|
||||
-fx-text-fill: #757575;
|
||||
}
|
||||
|
||||
/* TODO 隧道列表 */
|
||||
#proxyListView .list-cell:selected {
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: linear-gradient(#9198e5, #57b4f2);
|
||||
}
|
||||
.proxyListItem-run FontIcon {
|
||||
-fx-icon-color: linear-gradient(#95f257, #91e5ac);
|
||||
}
|
||||
.proxyListItem-stop FontIcon {
|
||||
-fx-icon-color: linear-gradient(#f25757, #e591b1);
|
||||
}
|
||||
.proxyListItem-close FontIcon {
|
||||
-fx-icon-color: linear-gradient(grey, #524e50);
|
||||
}
|
||||
|
||||
/* 面板背景 */
|
||||
#proxySetupPane, #proxyListPane {
|
||||
-fx-background-color: white;
|
||||
}
|
||||
|
||||
#MainBottomPane {
|
||||
-fx-padding: 10;
|
||||
}
|
||||
|
||||
/* 代理设置-标签 */
|
||||
.proxySetupLabel {
|
||||
-fx-font-size: 15px;
|
||||
-fx-font-family: "Microsoft YaHei";
|
||||
}
|
||||
/* 隧道类型 */
|
||||
#proxyProtocolCombox {
|
||||
-fx-font-size: 14px;
|
||||
-fx-font-family: "Microsoft YaHei";
|
||||
}
|
||||
/* 自定义域名 */
|
||||
#customizeDomainBtn {
|
||||
-fx-text-fill: linear-gradient(#57b4f2, #9198e5);
|
||||
}
|
||||
/* 添加隧道 */
|
||||
#addProxyBtn {
|
||||
-fx-border-width: 0 1 1;
|
||||
-fx-font-size: 14px;
|
||||
-fx-font-family: "Microsoft YaHei";
|
||||
}
|
||||
/* 启动 */
|
||||
#startProxyBtn {
|
||||
-fx-font-size: 16px;
|
||||
-fx-font-family: "Microsoft YaHei";
|
||||
}
|
||||
.startProxyBtn {
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
|
||||
}
|
||||
.stopProxyBtn {
|
||||
-fx-text-fill: white;
|
||||
-fx-background-color: linear-gradient(#f25757, #e591b1);
|
||||
}
|
||||
|
||||
/* 修改Tab的背景颜色 */
|
||||
.jfx-tab-pane .tab-header-background {
|
||||
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
|
||||
}
|
||||
|
34
src/main/resources/css/register.css
Normal file
34
src/main/resources/css/register.css
Normal file
@ -0,0 +1,34 @@
|
||||
/* 圆角 */
|
||||
#registMainPane {
|
||||
-fx-background-radius: 14;
|
||||
}
|
||||
|
||||
#registBottomPane {
|
||||
-fx-background-radius: 0 0 14 14;
|
||||
}
|
||||
|
||||
/* 字体 */
|
||||
#registerBtn {
|
||||
-fx-font-family: "Microsoft YaHei";
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
|
||||
/* 背景布局 */
|
||||
#registBkgPane {
|
||||
-fx-image: url('../image/common_pane_bkg.gif');
|
||||
}
|
||||
|
||||
/* 主布局背景 */
|
||||
#registBottomPane {
|
||||
-fx-background-color: rgba(243, 242, 242, 0.70);
|
||||
}
|
||||
|
||||
/* 注册按钮 */
|
||||
#registerBtn {
|
||||
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
|
||||
}
|
||||
|
||||
/* 登录按钮 */
|
||||
#loginBtn:hover {
|
||||
-fx-text-fill: #3e7398;
|
||||
}
|
35
src/main/resources/css/root.css
Normal file
35
src/main/resources/css/root.css
Normal file
@ -0,0 +1,35 @@
|
||||
.root, #root{
|
||||
-fx-background-radius: 14;
|
||||
}
|
||||
|
||||
#titleLable, #closeBtn, #minimizeBtn{
|
||||
-fx-font-family: "Microsoft YaHei";
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
|
||||
#titleLable {
|
||||
-fx-background-radius: 14 0 0 0;
|
||||
}
|
||||
|
||||
#minimizeBtn {
|
||||
-fx-background-radius: 0 14 0 14;
|
||||
}
|
||||
|
||||
#closeBtn {
|
||||
-fx-background-radius: 0 14 0 14;
|
||||
}
|
||||
|
||||
/* 根布局阴影 */
|
||||
#root {
|
||||
-fx-background-color: rgba(255, 255, 255, 1);
|
||||
-fx-effect: dropshadow(gaussian, black, 15, 0, 0, 0);
|
||||
-fx-background-insets: 50;
|
||||
}
|
||||
|
||||
#closeBtn:hover {
|
||||
-fx-background-color: rgba(255, 0, 0, 0.70);
|
||||
}
|
||||
|
||||
#minimizeBtn:hover {
|
||||
-fx-background-color: rgba(243, 242, 242, 0.20);
|
||||
}
|
120
src/main/resources/fxml/login.fxml
Normal file
120
src/main/resources/fxml/login.fxml
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import com.jfoenix.controls.JFXCheckBox?>
|
||||
<?import com.jfoenix.controls.JFXPasswordField?>
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||
|
||||
<StackPane fx:id="root" prefHeight="330.0" prefWidth="430.0" stylesheets="@../css/login.css"
|
||||
xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="top.octopusyan.controller.LoginController">
|
||||
|
||||
<ImageView fx:id="loginBkgPane" fitHeight="330" fitWidth="430">
|
||||
<clip>
|
||||
<Rectangle height="330" width="430">
|
||||
<arcHeight>28</arcHeight>
|
||||
<arcWidth>28</arcWidth>
|
||||
</Rectangle>
|
||||
</clip>
|
||||
</ImageView>
|
||||
|
||||
<VBox fx:id="loginMainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||
prefHeight="330.0" prefWidth="430.0">
|
||||
<AnchorPane fx:id="loginTopPane" prefHeight="130.0" prefWidth="430.0">
|
||||
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="64.0" text="Yan Frp" />
|
||||
<JFXButton fx:id="minimizeBtn" layoutX="360.0" prefHeight="35.0" prefWidth="36.0" text="—"/>
|
||||
<JFXButton fx:id="closeBtn" layoutX="395.0" prefHeight="35.0" prefWidth="36.0" text="X"/>
|
||||
</AnchorPane>
|
||||
<AnchorPane fx:id="loginBottomPane" prefHeight="200.0" prefWidth="430.0">
|
||||
<VBox layoutX="96.0" layoutY="24.0" prefHeight="112.0" prefWidth="237.0">
|
||||
<AnchorPane prefHeight="100.0" prefWidth="237.0">
|
||||
<JFXButton fx:id="accountIconBtn" disable="true" prefHeight="30.0" text="">
|
||||
<graphic>
|
||||
<FontIcon fx:id="accountIcon" iconLiteral="fa-laptop" iconSize="17"/>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<JFXTextField fx:id="accountTextField" layoutX="30.0" prefHeight="20.0" prefWidth="208.0"
|
||||
promptText="请输入账号">
|
||||
<HBox.margin>
|
||||
<Insets top="1.0"/>
|
||||
</HBox.margin>
|
||||
<font>
|
||||
<Font size="15.0"/>
|
||||
</font>
|
||||
</JFXTextField>
|
||||
</AnchorPane>
|
||||
<AnchorPane prefHeight="100.0" prefWidth="237.0">
|
||||
<JFXButton fx:id="pwdIconBtn" disable="true" prefHeight="30.0" text="">
|
||||
<graphic>
|
||||
<FontIcon fx:id="pwdIcon" iconLiteral="fa-expeditedssl" iconSize="17"/>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<JFXPasswordField fx:id="passwordTextField" layoutX="30.0" prefHeight="20.0" prefWidth="208.0"
|
||||
promptText="请输入密码">
|
||||
<font>
|
||||
<Font size="15.0"/>
|
||||
</font>
|
||||
</JFXPasswordField>
|
||||
<JFXTextField fx:id="seePwdTextField" layoutX="30.0" prefHeight="20.0" prefWidth="208.0"
|
||||
promptText="请输入密码">
|
||||
<font>
|
||||
<Font size="15.0"/>
|
||||
</font>
|
||||
</JFXTextField>
|
||||
<JFXButton fx:id="seePwdIconBtn" layoutX="210.0" layoutY="5.0" text="">
|
||||
<graphic>
|
||||
<FontIcon fx:id="seePwdIcon" iconLiteral="fa-eye" iconSize="17"/>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</AnchorPane>
|
||||
<HBox alignment="CENTER" prefHeight="30.0" prefWidth="237.0">
|
||||
<JFXCheckBox fx:id="autoLoginCBox" checkedColor="#57b4f2" prefHeight="18.0" prefWidth="49.0"
|
||||
text="自动登录">
|
||||
<HBox.margin>
|
||||
<Insets right="20.0"/>
|
||||
</HBox.margin>
|
||||
<font>
|
||||
<Font size="11.5"/>
|
||||
</font>
|
||||
</JFXCheckBox>
|
||||
<JFXCheckBox fx:id="rememberCBox" checkedColor="#57b4f2" prefHeight="18.0" prefWidth="0.0"
|
||||
text="记住密码">
|
||||
<HBox.margin>
|
||||
<Insets right="5.0"/>
|
||||
</HBox.margin>
|
||||
<font>
|
||||
<Font size="11.5"/>
|
||||
</font>
|
||||
</JFXCheckBox>
|
||||
<JFXButton alignment="TOP_CENTER" contentDisplay="RIGHT" prefHeight="23.0" prefWidth="64.0"
|
||||
text="找回密码" textAlignment="RIGHT" textFill="#00000078">
|
||||
<font>
|
||||
<Font size="11.0"/>
|
||||
</font>
|
||||
</JFXButton>
|
||||
</HBox>
|
||||
</VBox>
|
||||
<JFXButton fx:id="loginBtn" buttonType="RAISED" defaultButton="true" layoutX="96.0" layoutY="146.0"
|
||||
prefHeight="35.0" prefWidth="237.0" text="登录" textFill="WHITE">
|
||||
<font>
|
||||
<Font size="15.0"/>
|
||||
</font>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="registerBtn" alignment="TOP_CENTER" contentDisplay="RIGHT" layoutX="6.0" layoutY="170.0"
|
||||
prefHeight="23.0" prefWidth="64.0" text="注册账号" textAlignment="RIGHT" textFill="#00000078">
|
||||
<font>
|
||||
<Font size="11.0"/>
|
||||
</font>
|
||||
</JFXButton>
|
||||
</AnchorPane>
|
||||
</VBox>
|
||||
</StackPane>
|
218
src/main/resources/fxml/main.fxml
Normal file
218
src/main/resources/fxml/main.fxml
Normal file
@ -0,0 +1,218 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import com.jfoenix.controls.JFXComboBox?>
|
||||
<?import com.jfoenix.controls.JFXListView?>
|
||||
<?import com.jfoenix.controls.JFXRadioButton?>
|
||||
<?import com.jfoenix.controls.JFXTabPane?>
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Hyperlink?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Tab?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.RowConstraints?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||
|
||||
<StackPane fx:id="root" prefHeight="700.0" prefWidth="750.0" stylesheets="@../css/main.css" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.octopusyan.controller.MainController">
|
||||
|
||||
<VBox fx:id="MainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="750.0">
|
||||
<AnchorPane fx:id="MainTopPane" prefHeight="35.0" prefWidth="750.0">
|
||||
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="90.0" text="Yan Frp" />
|
||||
<JFXButton fx:id="minimizeBtn" layoutX="678.0" prefHeight="35.0" prefWidth="36.0" text="—" />
|
||||
<JFXButton fx:id="closeBtn" layoutX="714.0" prefHeight="35.0" prefWidth="36.0" text="X" />
|
||||
<JFXComboBox fx:id="proxyServerCB" layoutX="90.0" prefHeight="36.0" prefWidth="100.0" />
|
||||
</AnchorPane>
|
||||
<GridPane fx:id="MainBottomPane" hgap="10.0" prefHeight="684.0" prefWidth="750.0" vgap="10.0">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="539.0" minWidth="10.0" prefWidth="520.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="370.0" minWidth="10.0" prefWidth="200.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints maxHeight="493.0" minHeight="10.0" prefHeight="404.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="337.0" minHeight="10.0" prefHeight="231.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<VBox fx:id="proxySetupPane" prefHeight="200.0" prefWidth="100.0" styleClass="mainPane">
|
||||
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
|
||||
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="隧道名称">
|
||||
<HBox.margin>
|
||||
<Insets right="20.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<JFXTextField fx:id="proxyNameTextField" prefHeight="Infinity" prefWidth="320.0" styleClass="inputText">
|
||||
<HBox.margin>
|
||||
<Insets right="20.0" />
|
||||
</HBox.margin>
|
||||
<padding>
|
||||
<Insets left="15.0" />
|
||||
</padding>
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</JFXTextField>
|
||||
<padding>
|
||||
<Insets bottom="10.0" top="10.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
|
||||
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="是否启用">
|
||||
<HBox.margin>
|
||||
<Insets right="20.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<HBox alignment="CENTER_LEFT" prefHeight="Infinity" prefWidth="320.0">
|
||||
<JFXRadioButton fx:id="openProxyRBtn" text="启用">
|
||||
<HBox.margin>
|
||||
<Insets right="20.0" />
|
||||
</HBox.margin>
|
||||
</JFXRadioButton>
|
||||
<JFXRadioButton fx:id="closeProxyRBtn" text="停用" />
|
||||
</HBox>
|
||||
<VBox.margin>
|
||||
<Insets />
|
||||
</VBox.margin>
|
||||
<padding>
|
||||
<Insets bottom="10.0" top="10.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
|
||||
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="协议类型">
|
||||
<HBox.margin>
|
||||
<Insets right="20.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<JFXComboBox fx:id="proxyProtocolCombox" prefHeight="Infinity" prefWidth="320.0" styleClass="inputText" />
|
||||
<padding>
|
||||
<Insets bottom="10.0" top="10.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
|
||||
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="内网服务地址">
|
||||
<HBox.margin>
|
||||
<Insets right="20.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<HBox prefHeight="Infinity" prefWidth="320.0">
|
||||
<children>
|
||||
<JFXTextField fx:id="localHostTextField" alignment="CENTER" prefWidth="250.0" promptText="内网服务IP (IPV4)" styleClass="inputText-left" text="127.0.0.1">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</JFXTextField>
|
||||
<JFXTextField fx:id="localPortTextField" alignment="CENTER" prefWidth="70.0" promptText="端口" styleClass="inputText-right" text="80">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</JFXTextField>
|
||||
</children>
|
||||
</HBox>
|
||||
<padding>
|
||||
<Insets bottom="10.0" top="10.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
|
||||
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="外网访问地址">
|
||||
<HBox.margin>
|
||||
<Insets right="20.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<HBox prefWidth="320.0">
|
||||
<JFXTextField fx:id="domainTextField" alignment="CENTER" prefHeight="30.0" prefWidth="280.0" promptText="自定义子域名大于3位" styleClass="inputText-left">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</JFXTextField>
|
||||
<JFXTextField fx:id="domainSuffixTextField" alignment="CENTER" disable="true" minWidth="155.0" prefHeight="30.0" styleClass="inputText-right" text=".frp.octopusyan.top">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</JFXTextField>
|
||||
</HBox>
|
||||
<JFXButton fx:id="customizeDomainBtn" prefHeight="Infinity" text="自定义">
|
||||
<HBox.margin>
|
||||
<Insets left="10.0" />
|
||||
</HBox.margin>
|
||||
</JFXButton>
|
||||
<padding>
|
||||
<Insets bottom="10.0" top="10.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
|
||||
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0" styleClass="proxySetupLabel" text="用户名 (可选)">
|
||||
<HBox.margin>
|
||||
<Insets right="20.0" />
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<JFXTextField prefHeight="Infinity" prefWidth="320.0" styleClass="inputText">
|
||||
<padding>
|
||||
<Insets left="10.0" />
|
||||
</padding>
|
||||
</JFXTextField>
|
||||
<padding>
|
||||
<Insets bottom="10.0" top="10.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<padding>
|
||||
<Insets left="10.0" top="40.0" />
|
||||
</padding>
|
||||
</VBox>
|
||||
<VBox fx:id="proxyListPane" prefHeight="Infinity" prefWidth="Infinity" GridPane.columnIndex="1">
|
||||
<JFXListView fx:id="proxyListView" prefHeight="360.0" prefWidth="Infinity" />
|
||||
<JFXButton fx:id="addProxyBtn" prefHeight="45.0" prefWidth="Infinity" text="新增连接">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="fa-plus" />
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</VBox>
|
||||
<AnchorPane prefHeight="Infinity" prefWidth="Infinity" GridPane.rowIndex="1">
|
||||
<JFXTabPane fx:id="tabPane" prefHeight="230" prefWidth="520.0" styleClass="mainPane">
|
||||
<tabs>
|
||||
<Tab text=" 日志 ">
|
||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" />
|
||||
</Tab>
|
||||
<Tab text="常见问题">
|
||||
<VBox minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
|
||||
<HBox prefHeight="30.0" prefWidth="Infinity">
|
||||
<Label prefHeight="Infinity" text="* 若启动后无法通过外网访问,请点击" />
|
||||
<Hyperlink prefHeight="Infinity" text="此处" />
|
||||
<Label prefHeight="Infinity" text="排查问题" />
|
||||
</HBox>
|
||||
<Label prefHeight="30" text="* 修改配置信息后,必须重启才能生效" />
|
||||
<Label prefHeight="30" text="* 只有启动或重启内网穿透服务时,配置信息才会被保存" />
|
||||
<Label prefHeight="30" text="* 同一个账号在同一时间只能在一台电脑上登录使用" />
|
||||
<Label prefHeight="30" text="* 请勿将非法、暴力、色情等信息映射到外网上去,一经发现立即封号" />
|
||||
<padding>
|
||||
<Insets left="20.0" top="20.0" />
|
||||
</padding>
|
||||
</VBox>
|
||||
</Tab>
|
||||
<Tab text="使用场景">
|
||||
<VBox minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
|
||||
|
||||
</VBox>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</JFXTabPane>
|
||||
<JFXButton layoutX="480.0" layoutY="-1.0" prefHeight="35.0" prefWidth="26.0" text="">
|
||||
<graphic>
|
||||
<FontIcon iconColor="white" iconLiteral="fa-trash-o" iconSize="18" />
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</AnchorPane>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||
<children>
|
||||
<JFXButton fx:id="startProxyBtn" buttonType="RAISED" prefHeight="44.0" prefWidth="202.0" text="启动" />
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</GridPane>
|
||||
</VBox>
|
||||
|
||||
</StackPane>
|
18
src/main/resources/fxml/proxyItem.fxml
Normal file
18
src/main/resources/fxml/proxyItem.fxml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||
|
||||
<Label text=" 默认连接" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="fa-circle" iconSize="14"/>
|
||||
</graphic>
|
||||
<font>
|
||||
<Font size="16.0"/>
|
||||
</font>
|
||||
<padding>
|
||||
<Insets left="10.0"/>
|
||||
</padding>
|
||||
</Label>
|
61
src/main/resources/fxml/register.fxml
Normal file
61
src/main/resources/fxml/register.fxml
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import com.jfoenix.controls.JFXPasswordField?>
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<StackPane fx:id="root" prefHeight="330.0" prefWidth="430.0" stylesheets="@../css/register.css" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.octopusyan.controller.RegisterController">
|
||||
<ImageView fx:id="registBkgPane" fitHeight="330.0" fitWidth="430.0">
|
||||
<clip>
|
||||
<Rectangle height="330" width="430">
|
||||
<arcHeight>28</arcHeight>
|
||||
<arcWidth>28</arcWidth>
|
||||
</Rectangle>
|
||||
</clip>
|
||||
</ImageView>
|
||||
<VBox fx:id="registMainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="330.0" prefWidth="430.0">
|
||||
<AnchorPane fx:id="registTopPane" prefHeight="130.0" prefWidth="430.0">
|
||||
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="64.0" text="Yan Frp" />
|
||||
<JFXButton fx:id="minimizeBtn" layoutX="360.0" prefHeight="35.0" prefWidth="36.0" text="—" />
|
||||
<JFXButton fx:id="closeBtn" layoutX="395.0" prefHeight="35.0" prefWidth="36.0" text="X" />
|
||||
</AnchorPane>
|
||||
<AnchorPane fx:id="registBottomPane" prefHeight="200.0" prefWidth="430.0">
|
||||
<JFXTextField fx:id="accooundTextField" labelFloat="true" layoutX="25.0" layoutY="25.0" prefHeight="29.0" prefWidth="279.0" promptText="请输入6-18位账号,支持大小写数字下划线">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</JFXTextField>
|
||||
<JFXTextField fx:id="emailTf" labelFloat="true" layoutX="25.0" layoutY="90.0" promptText="请输入邮箱">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</JFXTextField>
|
||||
<HBox layoutX="25.0" layoutY="145.0" prefHeight="30.0" prefWidth="171.0">
|
||||
<JFXTextField fx:id="emailCheckCodeTf" prefHeight="30.0" prefWidth="92.0" promptText="6位验证号码">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</JFXTextField>
|
||||
<JFXButton fx:id="sendCheckCodeBtn" buttonType="RAISED" layoutX="75.0" prefHeight="30.0" text="发送验证码" />
|
||||
</HBox>
|
||||
<JFXPasswordField fx:id="passwordTextField" labelFloat="true" layoutX="233.0" layoutY="70.0" promptText="请输入密码不少于6位">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</JFXPasswordField>
|
||||
<JFXButton fx:id="registerBtn" buttonType="RAISED" layoutX="227.0" layoutY="127.0" prefHeight="51.0" prefWidth="177.0" text="注册" textFill="WHITE">
|
||||
<font>
|
||||
<Font size="15.0" />
|
||||
</font>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="loginBtn" layoutX="306.0" layoutY="24.0" text="已有账号,去登录" textFill="#00000080" />
|
||||
</AnchorPane>
|
||||
</VBox>
|
||||
</StackPane>
|
BIN
src/main/resources/image/common_pane_bkg.gif
Normal file
BIN
src/main/resources/image/common_pane_bkg.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 MiB |
BIN
src/main/resources/image/icon.png
Normal file
BIN
src/main/resources/image/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
70
src/main/resources/logback.xml
Normal file
70
src/main/resources/logback.xml
Normal file
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
<contextName>febs</contextName>
|
||||
<property name="log.path" value="log" />
|
||||
<property name="log.maxHistory" value="30" />
|
||||
<property name="log.colorPattern" value="%date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
|
||||
<property name="log.pattern" value="%date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||
|
||||
<!--输出到控制台-->
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<Encoding>UTF-8</Encoding>
|
||||
<encoder>
|
||||
<pattern>${log.colorPattern}</pattern>
|
||||
</encoder>
|
||||
<!--
|
||||
日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度
|
||||
%logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符
|
||||
-->
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<pattern>${log.colorPattern}</pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<!--输出到文件-->
|
||||
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log.path}/info/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<MaxHistory>${log.maxHistory}</MaxHistory>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<!-- 日志文件的最大大小 -->
|
||||
<maxFileSize>5MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log.path}/error/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<!-- 日志文件的最大大小 -->
|
||||
<maxFileSize>5MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<root level="debug">
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="file_info" />
|
||||
<appender-ref ref="file_error" />
|
||||
</root>
|
||||
</configuration>
|
Loading…
Reference in New Issue
Block a user