mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-17 04:43:02 +00:00
Compare commits
262 Commits
0.1.6-rele
...
0.1.8.fixe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78eb51b3ca | ||
|
|
a2606be9d8 | ||
|
|
a4975c72ce | ||
|
|
58f96822a4 | ||
|
|
96b0d94986 | ||
|
|
70b38db8c5 | ||
|
|
b6a9c2d3a0 | ||
|
|
a01df6c7db | ||
|
|
4455bee570 | ||
|
|
cd0adef2ed | ||
|
|
4aa24a65fb | ||
|
|
760dca8772 | ||
|
|
8269673619 | ||
|
|
82ec586554 | ||
|
|
ca98cc8708 | ||
|
|
f07800985d | ||
|
|
b042df93b7 | ||
|
|
ecf4441946 | ||
|
|
39b2612840 | ||
|
|
218f486e6b | ||
|
|
cfcc25f175 | ||
|
|
155e88223c | ||
|
|
05039ece51 | ||
|
|
1c673f2b46 | ||
|
|
2232a70228 | ||
|
|
e661b1d817 | ||
|
|
5a6a65f580 | ||
|
|
5cdd3bcd30 | ||
|
|
1233a885b8 | ||
|
|
adf56cd768 | ||
|
|
cd4b208be9 | ||
|
|
502de1a5d0 | ||
|
|
4158f869a3 | ||
|
|
ff569d339c | ||
|
|
10eec323dd | ||
|
|
0a3db51c7d | ||
|
|
229aee0b30 | ||
|
|
44714aa981 | ||
|
|
2b6138a889 | ||
|
|
5e424f7bf4 | ||
|
|
294e47deed | ||
|
|
dc42547b73 | ||
|
|
7ef7f0706b | ||
|
|
a59b98a7c9 | ||
|
|
088fee9a4d | ||
|
|
d8666acfe8 | ||
|
|
209e9c2866 | ||
|
|
6c3195dea4 | ||
|
|
7d774a7433 | ||
|
|
f1ec4433cf | ||
|
|
1f825db261 | ||
|
|
1019f24f1d | ||
|
|
f5c5b99579 | ||
|
|
e002d19f1b | ||
|
|
0d5c9651f0 | ||
|
|
53fc13b95c | ||
|
|
694c3b0ddc | ||
|
|
9b3d4577cc | ||
|
|
77783915dd | ||
|
|
b67ac21a79 | ||
|
|
603afed2f2 | ||
|
|
c2a7c34496 | ||
|
|
edd40f48ba | ||
|
|
cca3d6b8b9 | ||
|
|
f004512903 | ||
|
|
6407bb6730 | ||
|
|
b914eeadec | ||
|
|
dcadc6783e | ||
|
|
bc9f43634f | ||
|
|
4778f0164c | ||
|
|
9904754a07 | ||
|
|
1b79077c9e | ||
|
|
c13afb05b3 | ||
|
|
03e320efb8 | ||
|
|
7846332476 | ||
|
|
2d5d3b86e0 | ||
|
|
7c9ba890af | ||
|
|
0d609daffa | ||
|
|
c12e56d402 | ||
|
|
c7b38c07d5 | ||
|
|
dc51066cea | ||
|
|
59d2fb3010 | ||
|
|
a0fe702c10 | ||
|
|
f886f7e366 | ||
|
|
1d475d88ed | ||
|
|
e64c901912 | ||
|
|
5fce02e623 | ||
|
|
13997bc543 | ||
|
|
3e05b0d6f9 | ||
|
|
966417f867 | ||
|
|
601a0d1b91 | ||
|
|
ae91ce773e | ||
|
|
d428380f79 | ||
|
|
b0b8b61688 | ||
|
|
7663320a55 | ||
|
|
aaae7ab9a6 | ||
|
|
db7aa2f442 | ||
|
|
d7349cbf05 | ||
|
|
3a5474c9d8 | ||
|
|
4e6ed0f936 | ||
|
|
11ae57856e | ||
|
|
66e67f3e1d | ||
|
|
8913221b97 | ||
|
|
4f4133b98c | ||
|
|
5768ba48e8 | ||
|
|
9780d09e28 | ||
|
|
8358d8d1f8 | ||
|
|
d2a7bf4417 | ||
|
|
24c5495e75 | ||
|
|
4158200ba5 | ||
|
|
2a75f9d34d | ||
|
|
441e69e5f8 | ||
|
|
0a3d91ee52 | ||
|
|
00b3af1c4c | ||
|
|
6d4e0fb56e | ||
|
|
0a296cc937 | ||
|
|
257063bae3 | ||
|
|
e21325a5d0 | ||
|
|
32284c6da0 | ||
|
|
56c68bcce6 | ||
|
|
2ef7efdc42 | ||
|
|
7f00413756 | ||
|
|
d26696d3fa | ||
|
|
595f301748 | ||
|
|
c33b0aa12c | ||
|
|
b21189b7d7 | ||
|
|
618f43da8e | ||
|
|
d28b54f694 | ||
|
|
093d651f2f | ||
|
|
ccc05c13b2 | ||
|
|
1dfcbcc132 | ||
|
|
e9770c8eb3 | ||
|
|
cb14be12e1 | ||
|
|
b0ca6aa4dd | ||
|
|
c958229960 | ||
|
|
8aa10f6f27 | ||
|
|
db073beeb5 | ||
|
|
3d46bcb5c8 | ||
|
|
66e62aead0 | ||
|
|
497a96a1df | ||
|
|
c6e0bed331 | ||
|
|
d678a5f4f0 | ||
|
|
8fcf3d225d | ||
|
|
4858461946 | ||
|
|
7afe52918b | ||
|
|
1a0064fc43 | ||
|
|
7216c84282 | ||
|
|
94a58e2174 | ||
|
|
aa2fae868c | ||
|
|
1b80f49079 | ||
|
|
135632309d | ||
|
|
4c366b7d84 | ||
|
|
e3efdc83e6 | ||
|
|
595575381b | ||
|
|
5cd1ef8bbf | ||
|
|
1aeb838fd9 | ||
|
|
ce414e80b0 | ||
|
|
c47c9bfc68 | ||
|
|
7b2c493cf7 | ||
|
|
b38291b6b2 | ||
|
|
66de667fc3 | ||
|
|
a1e9be0133 | ||
|
|
bf70a7c83d | ||
|
|
6706380558 | ||
|
|
b0d150319d | ||
|
|
03185d0592 | ||
|
|
46f092f2ed | ||
|
|
aea97caed1 | ||
|
|
6d3335d086 | ||
|
|
945e5a74e6 | ||
|
|
1f38201555 | ||
|
|
cfc5afc029 | ||
|
|
0526569d64 | ||
|
|
1c94f4ef8b | ||
|
|
26aabf19db | ||
|
|
2e14d8d345 | ||
|
|
09140d56a3 | ||
|
|
1eec14831c | ||
|
|
aafbf05d34 | ||
|
|
8ffd35f2f0 | ||
|
|
6808381292 | ||
|
|
72cc68e9ad | ||
|
|
7917870c6b | ||
|
|
facf9b645e | ||
|
|
46b4c524f0 | ||
|
|
804c6d96ec | ||
|
|
91c3b0c612 | ||
|
|
400952e33b | ||
|
|
c39f748a51 | ||
|
|
2464693288 | ||
|
|
cfce8c1aef | ||
|
|
258a7cb1cc | ||
|
|
049cfa3676 | ||
|
|
7591aa4336 | ||
|
|
2aef15c670 | ||
|
|
23f29be4b3 | ||
|
|
daea6bea68 | ||
|
|
a71028c665 | ||
|
|
afb997f516 | ||
|
|
940d73a132 | ||
|
|
214eaed56a | ||
|
|
3dab818dcb | ||
|
|
61db1af83d | ||
|
|
7d1cb5b5c1 | ||
|
|
878f61d349 | ||
|
|
9a1c9d8ba2 | ||
|
|
ffd4846924 | ||
|
|
3e1f29c12e | ||
|
|
ec9a6dfe6c | ||
|
|
a72ebbb83c | ||
|
|
a8a172c027 | ||
|
|
bcd71a844c | ||
|
|
3608eb0370 | ||
|
|
dbef574cbc | ||
|
|
e2273587a0 | ||
|
|
9f50299943 | ||
|
|
a51097e1ba | ||
|
|
5628d57299 | ||
|
|
774a069c0e | ||
|
|
5756fde338 | ||
|
|
5d6e22405a | ||
|
|
28497e900a | ||
|
|
00723cc2e2 | ||
|
|
91eb62d308 | ||
|
|
c73e614ce7 | ||
|
|
2586092760 | ||
|
|
0d5b771db8 | ||
|
|
67edd5b5d8 | ||
|
|
40eea9b5f4 | ||
|
|
29a993e1fc | ||
|
|
2c9f0d8e83 | ||
|
|
656a43e4b7 | ||
|
|
f0e28e1c37 | ||
|
|
7e5400d43c | ||
|
|
c7c932daf4 | ||
|
|
8ca0eca2ea | ||
|
|
bb925eba35 | ||
|
|
a105a7d864 | ||
|
|
60e4ac6094 | ||
|
|
8d80ff845a | ||
|
|
49c2bbe680 | ||
|
|
3017e8d17c | ||
|
|
f3ae2b79b8 | ||
|
|
c629525029 | ||
|
|
5d9b18ef41 | ||
|
|
bdee797f7e | ||
|
|
a052bcd841 | ||
|
|
616cc9ce6a | ||
|
|
f124c83286 | ||
|
|
d11fcfb282 | ||
|
|
5ec1ec7789 | ||
|
|
8b6075af37 | ||
|
|
5d23c57d0e | ||
|
|
409163ddf5 | ||
|
|
994202587d | ||
|
|
c01d41f9ca | ||
|
|
c42667fe3b | ||
|
|
c782d495ea | ||
|
|
3f4f9fa76b | ||
|
|
635c639bf5 | ||
|
|
a49b9254a4 | ||
|
|
0c91eea711 |
28
.devcontainer/devcontainer.json
Normal file
28
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/java
|
||||||
|
{
|
||||||
|
"name": "Java",
|
||||||
|
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/java:0-17",
|
||||||
|
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/java:1": {
|
||||||
|
"version": "none",
|
||||||
|
"installMaven": "true",
|
||||||
|
"installGradle": "false"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers-contrib/features/ant-sdkman:2": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// "forwardPorts": [],
|
||||||
|
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
// "postCreateCommand": "java -version",
|
||||||
|
|
||||||
|
// Configure tool-specific properties.
|
||||||
|
// "customizations": {},
|
||||||
|
|
||||||
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
// "remoteUser": "root"
|
||||||
|
}
|
||||||
8
.gitattributes
vendored
Normal file
8
.gitattributes
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 文本文件使用 LF 换行符,适用于 Linux 和 macOS
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.service text eol=lf
|
||||||
|
|
||||||
|
# Windows 执行的文件使用 CRLF 换行符
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.cmd text eol=crlf
|
||||||
|
bin/nfd-service-template.xml text eol=crlf
|
||||||
2
.github/workflows/maven.yml
vendored
2
.github/workflows/maven.yml
vendored
@@ -37,6 +37,8 @@ jobs:
|
|||||||
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
|
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
|
||||||
- name: Update dependency graph
|
- name: Update dependency graph
|
||||||
uses: advanced-security/maven-dependency-submission-action@v3
|
uses: advanced-security/maven-dependency-submission-action@v3
|
||||||
|
with:
|
||||||
|
ignore-maven-wrapper: true
|
||||||
|
|
||||||
# - uses: release-drafter/release-drafter@v5
|
# - uses: release-drafter/release-drafter@v5
|
||||||
# env:
|
# env:
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -38,3 +38,5 @@ gradlew
|
|||||||
gradlew.bat
|
gradlew.bat
|
||||||
unused.txt
|
unused.txt
|
||||||
/web-service/src/main/generated/
|
/web-service/src/main/generated/
|
||||||
|
/db
|
||||||
|
/webroot/nfd-front/
|
||||||
|
|||||||
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2007-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import java.net.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.channels.*;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class MavenWrapperDownloader {
|
||||||
|
|
||||||
|
private static final String WRAPPER_VERSION = "0.5.6";
|
||||||
|
/**
|
||||||
|
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||||
|
*/
|
||||||
|
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
|
||||||
|
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||||
|
* use instead of the default one.
|
||||||
|
*/
|
||||||
|
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||||
|
".mvn/wrapper/maven-wrapper.properties";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path where the maven-wrapper.jar will be saved to.
|
||||||
|
*/
|
||||||
|
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||||
|
".mvn/wrapper/maven-wrapper.jar";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the property which should be used to override the default download url for the wrapper.
|
||||||
|
*/
|
||||||
|
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
System.out.println("- Downloader started");
|
||||||
|
File baseDirectory = new File(args[0]);
|
||||||
|
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||||
|
|
||||||
|
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||||
|
// wrapperUrl parameter.
|
||||||
|
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||||
|
String url = DEFAULT_DOWNLOAD_URL;
|
||||||
|
if(mavenWrapperPropertyFile.exists()) {
|
||||||
|
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||||
|
try {
|
||||||
|
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||||
|
Properties mavenWrapperProperties = new Properties();
|
||||||
|
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||||
|
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if(mavenWrapperPropertyFileInputStream != null) {
|
||||||
|
mavenWrapperPropertyFileInputStream.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("- Downloading from: " + url);
|
||||||
|
|
||||||
|
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||||
|
if(!outputFile.getParentFile().exists()) {
|
||||||
|
if(!outputFile.getParentFile().mkdirs()) {
|
||||||
|
System.out.println(
|
||||||
|
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||||
|
try {
|
||||||
|
downloadFileFromURL(url, outputFile);
|
||||||
|
System.out.println("Done");
|
||||||
|
System.exit(0);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
System.out.println("- Error downloading");
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||||
|
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||||
|
String username = System.getenv("MVNW_USERNAME");
|
||||||
|
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||||
|
Authenticator.setDefault(new Authenticator() {
|
||||||
|
@Override
|
||||||
|
protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication(username, password);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
URL website = new URL(urlString);
|
||||||
|
ReadableByteChannel rbc;
|
||||||
|
rbc = Channels.newChannel(website.openStream());
|
||||||
|
FileOutputStream fos = new FileOutputStream(destination);
|
||||||
|
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||||
|
fos.close();
|
||||||
|
rbc.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
Binary file not shown.
2
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
2
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
|
||||||
|
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||||
15
.run/AppMain.run.xml
Normal file
15
.run/AppMain.run.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="AppMain" type="Application" factoryName="Application" nameIsGenerated="true">
|
||||||
|
<option name="MAIN_CLASS_NAME" value="cn.qaiu.lz.AppMain" />
|
||||||
|
<module name="web-service" />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="cn.qaiu.lz.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
70
.vscode/launch.json
vendored
Normal file
70
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
// 使用 IntelliSense 了解相关属性。
|
||||||
|
// 悬停以查看现有属性的描述。
|
||||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Current File",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "${file}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "StringCase",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.vx.core.util.StringCase",
|
||||||
|
"projectName": "core"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "FCURLParser",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.parser.FCURLParser",
|
||||||
|
"projectName": "parser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "QkTool",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.parser.impl.QkTool",
|
||||||
|
"projectName": "parser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "WebClientExample",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "qaiu.web.test.WebClientExample",
|
||||||
|
"projectName": "parser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "AppMain",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.lz.AppMain",
|
||||||
|
"projectName": "web-service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "TestJs",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.web.test.TestJs",
|
||||||
|
"projectName": "web-service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "TestOS",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.web.test.TestOS",
|
||||||
|
"projectName": "web-service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "WebProxyExamples",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "cn.qaiu.web.test.WebProxyExamples",
|
||||||
|
"projectName": "web-service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"java.compile.nullAnalysis.mode": "automatic"
|
"java.compile.nullAnalysis.mode": "automatic",
|
||||||
|
"java.configuration.updateBuildConfiguration": "interactive"
|
||||||
}
|
}
|
||||||
202
README.md
202
README.md
@@ -1,64 +1,87 @@
|
|||||||
# netdisk-fast-download
|
云盘解析服务 (nfd云解析)
|
||||||
|
预览地址 https://lz.qaiu.top
|
||||||
|
main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/netdisk-fast-download/tree/main-jdk11)
|
||||||
|
**注意: 请不要过度依赖lz.qaiu.top预览地址服务,建议本地搭建或者云服务器自行搭建。
|
||||||
|
解析次数过多IP会被部分网盘厂商限制,不推荐做公共解析。**
|
||||||
|
|
||||||
[](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml)
|
[](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml)
|
||||||
[](https://www.oracle.com/cn/java/technologies/downloads/)
|
[](https://www.oracle.com/cn/java/technologies/downloads/)
|
||||||
[](https://vertx-china.github.io/)
|
[](https://vertx-china.github.io/)
|
||||||
[](https://github.com/qaiu/netdisk-fast-download/releases/tag/0.1.6-releases)
|
[](https://github.com/qaiu/netdisk-fast-download/releases/latest)
|
||||||
|
|
||||||
|
## 项目介绍
|
||||||
|
网盘直链解析工具能把网盘分享下载链接转化为直链,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等,支持加密分享。
|
||||||
|
**0.1.8及以上版本json接口格式有调整,尤其依赖lz.qaiu.top做下载服务的朋友们记得修改, 参考json返回数据格式示例**
|
||||||
|
|
||||||
|
|
||||||
|
*重要声明:本项目仅供学习参考;请不要将此项目用于任何商业用途,否则可能带来严重的后果。转发/分享该项目请注明来源*
|
||||||
|
|
||||||
## 网盘支持情况:
|
## 网盘支持情况:
|
||||||
|
> 20230905 奶牛云直链做了防盗链,需加入请求头:Referer: https://cowtransfer.com/
|
||||||
|
> 20230824 123云盘解析大文件(>100MB)失效,需要登录
|
||||||
|
> 20230722 UC网盘解析失效,需要登录
|
||||||
|
|
||||||
`网盘名称(网盘标识):`
|
`网盘名称(网盘标识):`
|
||||||
|
|
||||||
- [蓝奏云 (lz)](https://pc.woozooo.com/)
|
- [蓝奏云 (lz)](https://pc.woozooo.com/)
|
||||||
- [ ] 登录, 上传, 下载, 分享
|
- [蓝奏云优享 (iz)](https://www.ilanzou.com/)
|
||||||
- [X] 直链解析
|
|
||||||
- [奶牛快传 (cow)](https://cowtransfer.com/)
|
- [奶牛快传 (cow)](https://cowtransfer.com/)
|
||||||
- [ ] 登录, 上传, 下载, 分享
|
- [移动云云空间 (ec)](https://www.ecpan.cn/web)
|
||||||
- [X] 直链解析
|
|
||||||
- [移动云空间 (ec)](https://www.ecpan.cn/web)
|
|
||||||
- [ ] 登录, 上传, 下载, 分享
|
|
||||||
- [X] 直链解析
|
|
||||||
- [UC网盘 (uc)](https://fast.uc.cn/)
|
|
||||||
- [ ] 登录, 上传, 下载, 分享
|
|
||||||
- [X] 直链解析
|
|
||||||
- [小飞机网盘 (fj)](https://www.feijipan.com/)
|
- [小飞机网盘 (fj)](https://www.feijipan.com/)
|
||||||
- [ ] 登录, 上传, 下载, 分享
|
|
||||||
- [X] 直链解析
|
|
||||||
- [亿方云 (fc)](https://www.fangcloud.com/)
|
- [亿方云 (fc)](https://www.fangcloud.com/)
|
||||||
- [ ] 登录, 上传, 下载, 分享
|
|
||||||
- [X] 直链解析
|
|
||||||
- [123云盘 (ye)](https://www.123pan.com/)
|
- [123云盘 (ye)](https://www.123pan.com/)
|
||||||
- [ ] 登录, 上传, 下载, 分享
|
- [文叔叔 (ws)](https://www.wenshushu.cn/)
|
||||||
- [X] 直链解析
|
- [联想乐云 (le)](https://lecloud.lenovo.com/)
|
||||||
- [文叔叔 (ws) 开发中](https://www.wenshushu.cn/)
|
- [QQ邮箱文件中转站 (qq)](https://mail.qq.com/)
|
||||||
- [夸克网盘 (qk) 开发中](https://pan.quark.cn/)
|
- [超星网盘-开发中 (cx)](https://passport2.chaoxing.com/login?newversion=true&refer=https%3A%2F%2Fpan-yz.chaoxing.com%2F)
|
||||||
|
- [城通网盘(ct)](https://www.ctfile.com)
|
||||||
|
- [网易云音乐(mne)](https://music.163.com)
|
||||||
|
- [Cloudreve自建网盘(ce)](https://github.com/cloudreve/Cloudreve)
|
||||||
|
|
||||||
**TODO:**
|
|
||||||
- 登录接口, 文件上传/下载/分享后端接口
|
|
||||||
- 短地址服务
|
|
||||||
- 前端界面(正则规划)
|
|
||||||
|
|
||||||
**技术栈:**
|
### API接口说明
|
||||||
Jdk17+Vert.x4.4.1
|
your_host指的是您的域名或者IP,实际使用时替换为实际域名或者IP,端口默认6400,可以使用nginx代理来做域名访问。
|
||||||
Core模块集成Vert.x实现类似spring的注解式路由API
|
解析方式分为两种类型直接跳转下载文件和获取下载链接,
|
||||||
|
每一种都提供了两种接口形式: `通用接口parser?url=`和`网盘标志/分享key拼接的短地址(标志短链)`,所有规则参考示例。
|
||||||
API接口
|
- 通用接口: `/parser?url=分享链接`,加密分享需要加上参数pwd=密码;
|
||||||
|
- 标志短链: `/网盘标识/分享key` 在分享Key后面加上@密码;
|
||||||
|
- 直链JSON: `通用接口`和`标志短链`前加上`/json` 加密分享的密码规则同上;
|
||||||
|
- 网盘标识参考上面网盘支持情况
|
||||||
|
- 当带有分享密码时需要加上密码参数(pwd)
|
||||||
|
- 移动云云空间,小飞机网盘的加密分享的密码可以忽略
|
||||||
|
- 移动云空间分享key取分享链接中的data参数,比如`&data=xxx`的参数就是xxx
|
||||||
|
|
||||||
|
API规则:
|
||||||
```
|
```
|
||||||
网盘标识参考上面网盘支持情况, 括号内是可选内容: 表示当带有分享密码时需要加上密码参数
|
|
||||||
parser接口可以直接解析分享链接: 加密分享需要加上参数pwd=密码;
|
|
||||||
其他接口在分享Key后面加上@密码;
|
|
||||||
|
|
||||||
1. 解析并自动302跳转 :
|
1. 解析并自动302跳转 :
|
||||||
http(s)://your_host/parser?url=分享链接(&pwd=xxx)
|
http://your_host/parser?url=分享链接&pwd=xxx
|
||||||
http(s)://your_host/网盘标识/分享key(@分享密码)
|
http://your_host/网盘标识/分享key@分享密码
|
||||||
2. 获取解析后的直链--JSON格式
|
2. 获取解析后的直链--JSON格式
|
||||||
http(s)://your_host/json/网盘标识/分享key(@分享密码)
|
http://your_host/json/parser?url=分享链接&pwd=xxx
|
||||||
3. 特别注意的地方:
|
http://your_host/json/网盘标识/分享key@分享密码
|
||||||
- 有些网盘的加密分享的密码可以忽略: 如移动云空间,小飞机网盘
|
|
||||||
- 移动云空间(ec)使用parser?url= 解析时因为分享链接比较特殊(链接带有参数且含有#符号)所以要么对#进行转义%23要么直接去掉# 或者URL直接是主机名+'/'跟一个data参数
|
```
|
||||||
比如 http://your_host/parser?url=https://www.ecpan.cn/web//yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
|
json返回数据格式示例:
|
||||||
http://your_host/parser?url=https://www.ecpan.cn/web/%23/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
|
`shareKey`: 全局分享key
|
||||||
http://your_host/parser?url=https://www.ecpan.cn/&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
|
`directLink`: 下载链接
|
||||||
|
`cacheHit`: 是否为缓存链接
|
||||||
|
`expires`: 缓存到期时间
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"success": true,
|
||||||
|
"count": 0,
|
||||||
|
"data": {
|
||||||
|
"shareKey": "lz:xxx",
|
||||||
|
"directLink": "下载直链",
|
||||||
|
"cacheHit": true,
|
||||||
|
"expires": "2024-09-18 01:48:02",
|
||||||
|
"expiration": 1726638482825
|
||||||
|
},
|
||||||
|
"timestamp": 1726637151902
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
IDEA HttpClient示例:
|
IDEA HttpClient示例:
|
||||||
@@ -100,20 +123,26 @@ GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
|
|||||||
# 网盘对比
|
# 网盘对比
|
||||||
|
|
||||||
|
|
||||||
| 网盘名称 | 可直接下载分享 | 加密分享 | 初始网盘空间 | 单文件大小限制 | 登录接口 |
|
| 网盘名称 | 免登陆下载分享 | 加密分享 | 初始网盘空间 | 单文件大小限制 |
|
||||||
|------------|------------------------|----------|-----------|---------|------|
|
|-------------|---------|----------|-----------|-----------------|
|
||||||
| 蓝奏云 | √ | √ | 不限空间 | 100M | TODO |
|
| 蓝奏云 | √ | √ | 不限空间 | 100M |
|
||||||
| 奶牛快传 | √ | X | 10G | 不限大小 | TODO |
|
| 奶牛快传 | √ | X | 10G | 不限大小 |
|
||||||
| 移动云空间 | √ | √(密码可忽略) | 5G(个人) | 不限大小 | TODO |
|
| 移动云云空间(个人版) | √ | √(密码可忽略) | 5G(个人) | 不限大小 |
|
||||||
| UC网盘 | √ | √ | 10G | 不限大小 | TODO |
|
| 小飞机网盘 | √ | √(密码可忽略) | 10G | 不限大小 |
|
||||||
| 小飞机网盘 | √ | √(密码可忽略) | 10G | 不限大小 | TODO |
|
| 360亿方云 | √ | √(密码可忽略) | 100G(须实名) | 不限大小 |
|
||||||
| 360亿方云 | √(试用账号有时间限制企业版需要599续费) | √(密码可忽略) | 100G(须实名) | 不限大小 | TODO |
|
| 123云盘 | √ | √ | 2T | 100G(>100M需要登录) |
|
||||||
| 123云盘 | √ | √ | 2T | 100G | TODO |
|
| 文叔叔 | √ | √ | 10G | 5GB |
|
||||||
| 文叔叔(TODO) | √(注意有时间限制) | √ | 10G | 5GB | TODO |
|
| 夸克网盘 | x | √ | 10G | 不限大小 |
|
||||||
| 夸克网盘(TODO) | 需要登录 | √ | 10G | 不限大小 | TODO |
|
| UC网盘 | x | √ | 10G | 不限大小 |
|
||||||
|
|
||||||
# 打包部署
|
# 打包部署
|
||||||
|
|
||||||
|
## JDK下载(lz.qaiu.top提供直链云解析服务)
|
||||||
|
- [阿里jdk17(Dragonwell17-windows-x86)](https://lz.qaiu.top/ec/e957acef36ce89e1053979672a90d219n)
|
||||||
|
- [阿里jdk17(Dragonwell17-linux-x86)](https://lz.qaiu.top/ec/6ebc9f2e0bbd53b4c4d5b11013f40a80NHvcYU)
|
||||||
|
- [阿里jdk17(Dragonwell17-linux-aarch64)](https://lz.qaiu.top/ec/d14c2d06296f61b52a876b525265e0f8tzxTc5)
|
||||||
|
- [解析有效性测试-移动云云空间-阿里jdk17-linux-x86](https://lz.qaiu.top/json/ec/6ebc9f2e0bbd53b4c4d5b11013f40a80NHvcYU)
|
||||||
|
|
||||||
## 开发和打包
|
## 开发和打包
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -122,41 +151,76 @@ mvn clean
|
|||||||
mvn package
|
mvn package
|
||||||
|
|
||||||
```
|
```
|
||||||
打包好的文件位于 web-service/target/netdisk-fast-download-x.x.x-bin.zip
|
打包好的文件位于 web-service/target/netdisk-fast-download-bin.zip
|
||||||
## Linux服务部署
|
## Linux服务部署
|
||||||
|
### [宝塔安装参考](https://blog.qaiu.top/archives/netdisk-fast-download-bao-ta-an-zhuang-jiao-cheng)
|
||||||
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
|
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
|
||||||
```shell
|
```shell
|
||||||
cd ~
|
cd ~
|
||||||
wget -O netdisk-fast-download-0.1.6-bin.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.6-releases/netdisk-fast-download-0.1.6-bin.zip
|
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.8-release-fixed2/netdisk-fast-download-bin-fixed2.zip
|
||||||
unzip netdisk-fast-download-*-bin.zip
|
unzip netdisk-fast-download-bin.zip
|
||||||
cd netdisk-fast-download-*-bin
|
cd netdisk-fast-download
|
||||||
bash service-install.sh
|
bash service-install.sh
|
||||||
```
|
```
|
||||||
服务相关命令:
|
服务相关命令:
|
||||||
1、查看服务状态
|
|
||||||
systemctl status netdisk-fast-download.service
|
|
||||||
|
|
||||||
2、控制服务
|
查看服务状态
|
||||||
|
`systemctl status netdisk-fast-download.service`
|
||||||
|
|
||||||
启动服务
|
启动服务
|
||||||
systemctl start netdisk-fast-download.service
|
`systemctl start netdisk-fast-download.service`
|
||||||
|
|
||||||
重启服务
|
重启服务
|
||||||
systemctl restart netdisk-fast-download.service
|
`systemctl restart netdisk-fast-download.service`
|
||||||
|
|
||||||
停止服务
|
停止服务
|
||||||
systemctl stop netdisk-fast-download.service
|
`systemctl stop netdisk-fast-download.service`
|
||||||
|
|
||||||
开机启动服务
|
开机启动服务
|
||||||
systemctl enable netdisk-fast-download.servic
|
`systemctl enable netdisk-fast-download.servic`
|
||||||
|
|
||||||
停止开机启动
|
停止开机启动
|
||||||
systemctl disable netdisk-fast-download.servic
|
`systemctl disable netdisk-fast-download.servic`
|
||||||
|
|
||||||
## Windows服务部署
|
## Windows服务部署
|
||||||
1. 下载并解压releases版本netdisk-fast-download-0.1.6-bin.zip
|
1. 下载并解压releases版本netdisk-fast-download-bin.zip
|
||||||
2. 进入netdisk-fast-download-0.1.6-bin目录
|
2. 进入netdisk-fast-download下的bin目录
|
||||||
3. 使用管理员权限运行nfd-service-install.bat
|
3. 使用管理员权限运行nfd-service-install.bat
|
||||||
如果不想使用服务运行可以直接运行run.bat
|
如果不想使用服务运行可以直接运行run.bat
|
||||||
|
> 注意: 如果jdk环境变量的java版本不是17请修改nfd-service-template.xml中的java命令的路径改为实际路径
|
||||||
|
|
||||||
|
## 相关配置说明
|
||||||
|
|
||||||
|
resources目录下包含服务端配置文件 配置文件自带说明,具体请查看配置文件内容,
|
||||||
|
app-dev.yml 可以配置解析服务相关信息, 包括端口,域名,缓存时长等
|
||||||
|
server-proxy.yml 可以配置代理服务运行的相关信息, 包括前端反向代理端口,路径等
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.9 开发计划
|
||||||
|
- 超星网盘解析 doing
|
||||||
|
- 带Referer头的js请求下载 doing
|
||||||
|
- 城通网盘解析 √
|
||||||
|
- 目录解析(专属版)
|
||||||
|
- 带cookie/token参数解析大文件(专属版)
|
||||||
|
- docker
|
||||||
|
|
||||||
|
**技术栈:**
|
||||||
|
Jdk17+Vert.x4.4.1
|
||||||
|
Core模块集成Vert.x实现类似spring的注解式路由API
|
||||||
|
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#qaiu/netdisk-fast-download&Date)
|
||||||
|
|
||||||
|
|
||||||
|
## 支持该项目
|
||||||
|
开源不易,用爱发电,本项目长期维护如果觉得有帮助, 可以请作者喝杯咖啡, 感谢支持
|
||||||
|
赞助88元以上, 可以优先体验专享版--大文件解析,目录解析
|
||||||
|

|
||||||
|
|
||||||
|
[手机端支付宝打赏跳转链接](https://qr.alipay.com/fkx01882dnoxxtjenhlxt53)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Docker部署
|
|
||||||
TODO
|
|
||||||
|
|||||||
22
bin/build.bat
Normal file
22
bin/build.bat
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
rem 获取当前 Java 版本信息并搜索是否包含 "17."
|
||||||
|
java -version 2>&1 | find "17." >nul
|
||||||
|
|
||||||
|
rem 如果找不到 JDK 17.x,则下载并安装
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo JDK 17.x not found. Downloading and installing...
|
||||||
|
|
||||||
|
REM 这里添加下载和安装 JDK 的代码
|
||||||
|
|
||||||
|
rem 验证安装
|
||||||
|
java -version
|
||||||
|
|
||||||
|
echo JDK 17.x installation complete.
|
||||||
|
) else (
|
||||||
|
echo JDK 17.x is already installed.
|
||||||
|
)
|
||||||
|
|
||||||
|
endlocal
|
||||||
|
pause
|
||||||
@@ -8,7 +8,7 @@ Wants=network-online.target
|
|||||||
Type=simple
|
Type=simple
|
||||||
# User=USER
|
# User=USER
|
||||||
# 需要JDK17及以上版本 注意修改为自己的路径
|
# 需要JDK17及以上版本 注意修改为自己的路径
|
||||||
ExecStart=/root/java/jdk-17.0.2/bin/java -server -Xmx128m -jar /root/java/netdisk-fast-download-0.1.6/netdisk-fast-download-0.1.6.jar
|
ExecStart=/root/java/jdk-17.0.2/bin/java -server -Xmx128m -jar /root/java/netdisk-fast-download/netdisk-fast-download.jar
|
||||||
ExecStop=/bin/kill -s QUIT $MAINPID
|
ExecStop=/bin/kill -s QUIT $MAINPID
|
||||||
Restart=always
|
Restart=always
|
||||||
StandOutput=syslog
|
StandOutput=syslog
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ pushd %~dp0
|
|||||||
set MY_DIR=%~dp0
|
set MY_DIR=%~dp0
|
||||||
set MY_DIR=%MY_DIR:~0,-1%
|
set MY_DIR=%MY_DIR:~0,-1%
|
||||||
|
|
||||||
for /f "delims=X" %%i in ('dir /b %MY_DIR%\netdisk-fast-download-*.jar') do (
|
for /f "delims=X" %%i in ('dir /b %MY_DIR%\netdisk-fast-download.jar') do (
|
||||||
set LAUNCH_JAR=%MY_DIR%\%%i
|
set LAUNCH_JAR=%MY_DIR%\%%i
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<name>netdisk-fast-download</name>
|
<name>netdisk-fast-download</name>
|
||||||
<description>netdisk fast download service</description>
|
<description>netdisk fast download service</description>
|
||||||
<executable>java</executable>
|
<executable>java</executable>
|
||||||
<arguments>-jar ${jar} -server -Xmx128m </arguments>
|
<arguments> -server -Xmx128m -jar ${jar} </arguments>
|
||||||
<logpath>${dd}\logs</logpath>
|
<logpath>${dd}\logs</logpath>
|
||||||
<log mode="roll-by-time">
|
<log mode="roll-by-time">
|
||||||
<pattern>yyyyMMdd</pattern>
|
<pattern>yyyyMMdd</pattern>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
@echo off && @chcp 65001 > nul
|
@echo off && @chcp 65001 > nul
|
||||||
pushd %~dp0
|
pushd %~dp0
|
||||||
set LIB_DIR=%~dp0
|
set LIB_DIR=%~dp0
|
||||||
for /f "delims=X" %%i in ('dir /b %LIB_DIR%\netdisk-fast-download-*.jar') do (
|
for /f "delims=X" %%i in ('dir /b %LIB_DIR%\netdisk-fast-download.jar') do (
|
||||||
set LAUNCH_JAR=%LIB_DIR%%%i
|
set LAUNCH_JAR=%LIB_DIR%%%i
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set "JAVA_HOME=D:\App\dragonwell-17.0.3.0.3+7-GA"
|
||||||
"%JAVA_HOME%\bin\java.exe" -Xmx512M -Dfile.encoding=utf8 -jar %LAUNCH_JAR% %*
|
"%JAVA_HOME%\bin\java.exe" -Xmx512M -Dfile.encoding=utf8 -jar %LAUNCH_JAR% %*
|
||||||
|
|
||||||
pause
|
pause
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# set -x
|
# set -x
|
||||||
LAUNCH_JAR="netdisk-fast-download-0.1.6.jar"
|
LAUNCH_JAR="netdisk-fast-download.jar"
|
||||||
nohup java -Xmx512M -jar "$LAUNCH_JAR" "$@" >startup.log 2>&1 &
|
nohup java -Xmx512M -jar "$LAUNCH_JAR" "$@" >startup.log 2>&1 &
|
||||||
tail -f startup.log
|
tail -f startup.log
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>netdisk-fast-download</artifactId>
|
<artifactId>netdisk-fast-download</artifactId>
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<version>0.1.6</version>
|
<version>${revision}</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
@@ -16,14 +16,13 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<slf4j.version>2.0.5</slf4j.version>
|
<slf4j.version>2.0.5</slf4j.version>
|
||||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
<commons-lang3.version>3.12.0</commons-lang3.version>
|
||||||
<vertx.version>4.4.1</vertx.version>
|
<vertx.version>4.5.6</vertx.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<artifactId>core</artifactId>
|
<artifactId>core</artifactId>
|
||||||
<version>1.0.8</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
|
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package cn.qaiu;
|
package cn.qaiu;
|
||||||
|
|
||||||
public class StartH2DatabaseServer {
|
public class StartH2DatabaseServer {
|
||||||
public static void main(String[] args) {
|
|
||||||
System.out.println("Hello world!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package cn.qaiu.db.ddl;
|
package cn.qaiu.db.ddl;
|
||||||
|
|
||||||
|
import cn.qaiu.db.pool.JDBCType;
|
||||||
import cn.qaiu.vx.core.util.ReflectionUtil;
|
import cn.qaiu.vx.core.util.ReflectionUtil;
|
||||||
import io.vertx.codegen.format.CamelCase;
|
import io.vertx.codegen.format.CamelCase;
|
||||||
import io.vertx.codegen.format.Case;
|
import io.vertx.codegen.format.Case;
|
||||||
@@ -38,6 +39,7 @@ public class CreateTable {
|
|||||||
javaProperty2SqlColumnMap.put(Boolean.class, "BOOLEAN");
|
javaProperty2SqlColumnMap.put(Boolean.class, "BOOLEAN");
|
||||||
javaProperty2SqlColumnMap.put(String.class, "VARCHAR");
|
javaProperty2SqlColumnMap.put(String.class, "VARCHAR");
|
||||||
javaProperty2SqlColumnMap.put(java.util.Date.class, "TIMESTAMP");
|
javaProperty2SqlColumnMap.put(java.util.Date.class, "TIMESTAMP");
|
||||||
|
javaProperty2SqlColumnMap.put(java.time.LocalDateTime.class, "TIMESTAMP");
|
||||||
javaProperty2SqlColumnMap.put(java.sql.Timestamp.class, "TIMESTAMP");
|
javaProperty2SqlColumnMap.put(java.sql.Timestamp.class, "TIMESTAMP");
|
||||||
javaProperty2SqlColumnMap.put(java.sql.Date.class, "DATE");
|
javaProperty2SqlColumnMap.put(java.sql.Date.class, "DATE");
|
||||||
javaProperty2SqlColumnMap.put(java.sql.Time.class, "TIME");
|
javaProperty2SqlColumnMap.put(java.sql.Time.class, "TIME");
|
||||||
@@ -64,7 +66,9 @@ public class CreateTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getCreateTableSQL(Class<?> clz) {
|
public static String getCreateTableSQL(Class<?> clz, JDBCType type) {
|
||||||
|
String quotationMarks = type == JDBCType.H2DB ? "\"" : "`";
|
||||||
|
String endStr = type == JDBCType.H2DB ? ");" : ")ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
||||||
// 判断类上是否有次注解
|
// 判断类上是否有次注解
|
||||||
String primaryKey = null; // 主键
|
String primaryKey = null; // 主键
|
||||||
String tableName = null; // 表名
|
String tableName = null; // 表名
|
||||||
@@ -92,7 +96,7 @@ public class CreateTable {
|
|||||||
int[] decimalSize = {22, 2};
|
int[] decimalSize = {22, 2};
|
||||||
int varcharSize = 255;
|
int varcharSize = 255;
|
||||||
StringBuilder sb = new StringBuilder(50);
|
StringBuilder sb = new StringBuilder(50);
|
||||||
sb.append("CREATE TABLE IF NOT EXISTS \"").append(tableName).append("\" ( \r\n ");
|
sb.append("CREATE TABLE IF NOT EXISTS ").append(quotationMarks).append(tableName).append(quotationMarks).append(" ( \r\n ");
|
||||||
boolean firstId = true;
|
boolean firstId = true;
|
||||||
for (Field f : fields) {
|
for (Field f : fields) {
|
||||||
Class<?> paramType = f.getType();
|
Class<?> paramType = f.getType();
|
||||||
@@ -113,7 +117,7 @@ public class CreateTable {
|
|||||||
decimalSize = fieldAnnotation.decimalSize();
|
decimalSize = fieldAnnotation.decimalSize();
|
||||||
varcharSize = fieldAnnotation.varcharSize();
|
varcharSize = fieldAnnotation.varcharSize();
|
||||||
}
|
}
|
||||||
sb.append("\"").append(column).append("\"");
|
sb.append(quotationMarks).append(column).append(quotationMarks);
|
||||||
sb.append(" ").append(sqlType);
|
sb.append(" ").append(sqlType);
|
||||||
// 添加类型长度
|
// 添加类型长度
|
||||||
if (sqlType.equals("DECIMAL")) {
|
if (sqlType.equals("DECIMAL")) {
|
||||||
@@ -154,17 +158,20 @@ public class CreateTable {
|
|||||||
//去掉最后一个逗号
|
//去掉最后一个逗号
|
||||||
int lastIndex = sql.lastIndexOf(",");
|
int lastIndex = sql.lastIndexOf(",");
|
||||||
sql = sql.substring(0, lastIndex) + sql.substring(lastIndex + 1);
|
sql = sql.substring(0, lastIndex) + sql.substring(lastIndex + 1);
|
||||||
return sql.substring(0, sql.length() - 1) + ");\r\n";
|
return sql.substring(0, sql.length() - 1) + endStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void createTable(JDBCPool pool, String tableClassPath) {
|
public static void createTable(JDBCPool pool, JDBCType type) {
|
||||||
Set<Class<?>> tableClassList = ReflectionUtil.getReflections(tableClassPath).getTypesAnnotatedWith(Table.class);
|
Set<Class<?>> tableClassList = ReflectionUtil.getReflections().getTypesAnnotatedWith(Table.class);
|
||||||
if (tableClassList.isEmpty()) LOGGER.info("Table model class not fount");
|
if (tableClassList.isEmpty()) LOGGER.info("Table model class not fount");
|
||||||
tableClassList.forEach(clazz -> {
|
tableClassList.forEach(clazz -> {
|
||||||
String createTableSQL = getCreateTableSQL(clazz);
|
String createTableSQL = getCreateTableSQL(clazz, type);
|
||||||
|
|
||||||
pool.query(createTableSQL).execute().onSuccess(
|
pool.query(createTableSQL).execute().onSuccess(
|
||||||
rs -> LOGGER.info("\n" + createTableSQL + "create table --> ok")
|
rs -> LOGGER.info("table auto generate:\n" + createTableSQL)
|
||||||
).onFailure(Throwable::printStackTrace);
|
).onFailure(e -> {
|
||||||
|
LOGGER.error(e.getMessage() + " SQL: \n" + createTableSQL);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
package cn.qaiu.db.pool;
|
package cn.qaiu.db.pool;
|
||||||
|
|
||||||
import cn.qaiu.db.ddl.CreateTable;
|
import cn.qaiu.db.ddl.CreateTable;
|
||||||
import cn.qaiu.db.server.H2ServerHolder;
|
|
||||||
import cn.qaiu.vx.core.util.VertxHolder;
|
import cn.qaiu.vx.core.util.VertxHolder;
|
||||||
import io.vertx.core.Promise;
|
|
||||||
import io.vertx.core.Vertx;
|
import io.vertx.core.Vertx;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.jdbcclient.JDBCPool;
|
import io.vertx.jdbcclient.JDBCPool;
|
||||||
import org.h2.tools.Server;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化JDBC
|
* 初始化JDBC
|
||||||
* <br>Create date 2021/8/10 12:04
|
* <br>Create date 2021/8/10 12:04
|
||||||
@@ -28,12 +21,14 @@ public class JDBCPoolInit {
|
|||||||
JsonObject dbConfig;
|
JsonObject dbConfig;
|
||||||
Vertx vertx = VertxHolder.getVertxInstance();
|
Vertx vertx = VertxHolder.getVertxInstance();
|
||||||
String url;
|
String url;
|
||||||
|
private final JDBCType type;
|
||||||
|
|
||||||
private static JDBCPoolInit instance;
|
private static JDBCPoolInit instance;
|
||||||
|
|
||||||
public JDBCPoolInit(Builder builder) {
|
public JDBCPoolInit(Builder builder) {
|
||||||
this.dbConfig = builder.dbConfig;
|
this.dbConfig = builder.dbConfig;
|
||||||
this.url = builder.url;
|
this.url = builder.url;
|
||||||
|
this.type = builder.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
@@ -47,10 +42,12 @@ public class JDBCPoolInit {
|
|||||||
public static class Builder {
|
public static class Builder {
|
||||||
private JsonObject dbConfig;
|
private JsonObject dbConfig;
|
||||||
private String url;
|
private String url;
|
||||||
|
private JDBCType type;
|
||||||
|
|
||||||
public Builder config(JsonObject dbConfig) {
|
public Builder config(JsonObject dbConfig) {
|
||||||
this.dbConfig = dbConfig;
|
this.dbConfig = dbConfig;
|
||||||
this.url = dbConfig.getString("jdbcUrl");
|
this.url = dbConfig.getString("jdbcUrl");
|
||||||
|
this.type = JDBCUtil.getJDBCType(dbConfig.getString("driverClassName"));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,73 +64,17 @@ public class JDBCPoolInit {
|
|||||||
* init h2db<br>
|
* init h2db<br>
|
||||||
* 这个方法只允许调用一次
|
* 这个方法只允许调用一次
|
||||||
*/
|
*/
|
||||||
public void initPool() {
|
synchronized public void initPool() {
|
||||||
if (pool != null) {
|
if (pool != null) {
|
||||||
LOGGER.error("pool 重复初始化");
|
LOGGER.error("pool 重复初始化");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步启动H2服务
|
|
||||||
vertx.createSharedWorkerExecutor("h2-server", 1, Long.MAX_VALUE)
|
|
||||||
.executeBlocking(this::h2serverExecute)
|
|
||||||
.onSuccess(res->{
|
|
||||||
LOGGER.info(res);
|
|
||||||
// 初始化数据库连接
|
// 初始化数据库连接
|
||||||
vertx.createSharedWorkerExecutor("sql-pool-init")
|
|
||||||
.executeBlocking(this::poolInitExecute)
|
|
||||||
.onSuccess(LOGGER::info)
|
|
||||||
.onFailure(Throwable::printStackTrace);
|
|
||||||
})
|
|
||||||
.onFailure(Throwable::printStackTrace);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void poolInitExecute(Promise<String> promise) {
|
|
||||||
// 初始化连接池
|
// 初始化连接池
|
||||||
pool = JDBCPool.pool(vertx, dbConfig);
|
pool = JDBCPool.pool(vertx, dbConfig);
|
||||||
CreateTable.createTable(pool, dbConfig.getString("tableClassPath"));
|
CreateTable.createTable(pool, type);
|
||||||
promise.complete("init jdbc pool success");
|
LOGGER.info("数据库连接初始化: URL=" + url);
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkOrCreateDBFile() {
|
|
||||||
LOGGER.info("init sql start");
|
|
||||||
String[] path = url.split("\\./");
|
|
||||||
path[1] = path[1].split(";")[0];
|
|
||||||
path[1] += ".mv.db";
|
|
||||||
File file = new File(path[1]);
|
|
||||||
if (!file.exists()) {
|
|
||||||
if (!file.getParentFile().exists()) {
|
|
||||||
if (file.getParentFile().mkdirs()) {
|
|
||||||
LOGGER.info("mkdirs -> {}", file.getParentFile().getAbsolutePath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (file.createNewFile()) {
|
|
||||||
LOGGER.info("create file -> {}", file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOGGER.error(e.getMessage());
|
|
||||||
throw new RuntimeException("file create failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void h2serverExecute(Promise<String> promise) {
|
|
||||||
// 初始化H2db, 创建本地db文件
|
|
||||||
checkOrCreateDBFile();
|
|
||||||
|
|
||||||
try {
|
|
||||||
String url = dbConfig.getString("jdbcUrl");
|
|
||||||
String[] portStr = url.split(":");
|
|
||||||
String port = portStr[portStr.length - 1].split("[/\\\\]")[0];
|
|
||||||
LOGGER.info("H2server listen port to {}", port);
|
|
||||||
H2ServerHolder.init(Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", port).start());
|
|
||||||
promise.complete("Start h2Server success");
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new RuntimeException("Start h2Server failed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -141,7 +82,7 @@ public class JDBCPoolInit {
|
|||||||
*
|
*
|
||||||
* @return pool
|
* @return pool
|
||||||
*/
|
*/
|
||||||
public JDBCPool getPool() {
|
synchronized public JDBCPool getPool() {
|
||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package cn.qaiu.db.pool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
* @date 2023/10/10 14:06
|
||||||
|
*/
|
||||||
|
public enum JDBCType {
|
||||||
|
MySQL, H2DB
|
||||||
|
}
|
||||||
18
core-database/src/main/java/cn/qaiu/db/pool/JDBCUtil.java
Normal file
18
core-database/src/main/java/cn/qaiu/db/pool/JDBCUtil.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package cn.qaiu.db.pool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
* @date 2023/10/10 14:05
|
||||||
|
*/
|
||||||
|
public class JDBCUtil {
|
||||||
|
public static JDBCType getJDBCType(String deviceName) {
|
||||||
|
switch (deviceName) {
|
||||||
|
case "com.mysql.cj.jdbc.Driver":
|
||||||
|
case "com.mysql.jdbc.Driver":
|
||||||
|
return JDBCType.MySQL;
|
||||||
|
case "org.h2.Driver":
|
||||||
|
return JDBCType.H2DB;
|
||||||
|
}
|
||||||
|
throw new RuntimeException("不支持的SQL驱动类型: " + deviceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
core/pom.xml
49
core/pom.xml
@@ -5,41 +5,21 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>netdisk-fast-download</artifactId>
|
<artifactId>netdisk-fast-download</artifactId>
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<version>0.1.6</version>
|
<version>${revision}</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<version>1.0.8</version>
|
|
||||||
<artifactId>core</artifactId>
|
<artifactId>core</artifactId>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>17</java.version>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<vertx.version>4.4.1</vertx.version>
|
|
||||||
<org.reflections.version>0.10.2</org.reflections.version>
|
|
||||||
<lombok.version>1.18.12</lombok.version>
|
|
||||||
<slf4j.version>2.0.5</slf4j.version>
|
|
||||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
|
||||||
<jackson.version>2.14.2</jackson.version>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.vertx</groupId>
|
|
||||||
<artifactId>vertx-dependencies</artifactId>
|
|
||||||
<version>${vertx.version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!--logback日志实现-->
|
<!--logback日志实现-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
<version>1.4.6</version>
|
<version>${logback.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
@@ -80,20 +60,28 @@
|
|||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>${commons-lang3.version}</version>
|
<version>${commons-lang3.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>commons-beanutils</groupId>
|
|
||||||
<artifactId>commons-beanutils</artifactId>
|
|
||||||
<version>1.9.4</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
<version>${jackson.version}</version>
|
<version>${jackson.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@@ -102,6 +90,13 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<source>${java.version}</source>
|
<source>${java.version}</source>
|
||||||
<target>${java.version}</target>
|
<target>${java.version}</target>
|
||||||
|
<!-- 代码生成器 -->
|
||||||
|
<annotationProcessors>
|
||||||
|
<annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor>
|
||||||
|
</annotationProcessors>
|
||||||
|
<generatedSourcesDirectory>
|
||||||
|
${project.basedir}/src/main/generated
|
||||||
|
</generatedSourcesDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package cn.qaiu.vx.core;
|
package cn.qaiu.vx.core;
|
||||||
|
|
||||||
import cn.qaiu.vx.core.util.ConfigConstant;
|
import cn.qaiu.vx.core.util.CommonUtil;
|
||||||
import cn.qaiu.vx.core.util.ConfigUtil;
|
import cn.qaiu.vx.core.util.ConfigUtil;
|
||||||
import cn.qaiu.vx.core.util.VertxHolder;
|
import cn.qaiu.vx.core.util.VertxHolder;
|
||||||
import cn.qaiu.vx.core.verticle.ReverseProxyVerticle;
|
import cn.qaiu.vx.core.verticle.ReverseProxyVerticle;
|
||||||
import cn.qaiu.vx.core.verticle.RouterVerticle;
|
import cn.qaiu.vx.core.verticle.RouterVerticle;
|
||||||
import cn.qaiu.vx.core.verticle.ServiceVerticle;
|
import cn.qaiu.vx.core.verticle.ServiceVerticle;
|
||||||
import io.vertx.core.*;
|
import io.vertx.core.*;
|
||||||
|
import io.vertx.core.dns.AddressResolverOptions;
|
||||||
|
import io.vertx.core.impl.launcher.commands.VersionCommand;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.core.shareddata.LocalMap;
|
import io.vertx.core.shareddata.LocalMap;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -17,6 +19,8 @@ import java.util.Calendar;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.concurrent.locks.LockSupport;
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
|
import static cn.qaiu.vx.core.util.ConfigConstant.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vertx启动类 需要在主启动类完成回调
|
* vertx启动类 需要在主启动类完成回调
|
||||||
* <br>Create date 2021-05-07 10:26:54
|
* <br>Create date 2021-05-07 10:26:54
|
||||||
@@ -42,6 +46,11 @@ public final class Deploy {
|
|||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param args 启动参数
|
||||||
|
* @param handle 启动完成后回调处理函数
|
||||||
|
*/
|
||||||
public void start(String[] args, Handler<JsonObject> handle) {
|
public void start(String[] args, Handler<JsonObject> handle) {
|
||||||
this.mainThread = Thread.currentThread();
|
this.mainThread = Thread.currentThread();
|
||||||
this.handle = handle;
|
this.handle = handle;
|
||||||
@@ -80,7 +89,7 @@ public final class Deploy {
|
|||||||
var calendar = Calendar.getInstance();
|
var calendar = Calendar.getInstance();
|
||||||
calendar.setTime(new Date());
|
calendar.setTime(new Date());
|
||||||
var year = calendar.get(Calendar.YEAR);
|
var year = calendar.get(Calendar.YEAR);
|
||||||
var logoTemplete = """
|
var logoTemplate = """
|
||||||
|
|
||||||
Web Server powered by:\s
|
Web Server powered by:\s
|
||||||
____ ____ _ _ _ \s
|
____ ____ _ _ _ \s
|
||||||
@@ -93,9 +102,9 @@ public final class Deploy {
|
|||||||
|
|
||||||
""";
|
""";
|
||||||
|
|
||||||
System.out.printf(logoTemplete,
|
System.out.printf(logoTemplate,
|
||||||
conf.getString("version_app"),
|
CommonUtil.getAppVersion(),
|
||||||
conf.getString("version_vertx"),
|
VersionCommand.getVersion(),
|
||||||
conf.getString("copyright"),
|
conf.getString("copyright"),
|
||||||
year
|
year
|
||||||
);
|
);
|
||||||
@@ -107,13 +116,19 @@ public final class Deploy {
|
|||||||
private void deployVerticle() {
|
private void deployVerticle() {
|
||||||
tempVertx.close();
|
tempVertx.close();
|
||||||
LOGGER.info("配置读取成功");
|
LOGGER.info("配置读取成功");
|
||||||
customConfig = globalConfig.getJsonObject(ConfigConstant.CUSTOM);
|
customConfig = globalConfig.getJsonObject(CUSTOM);
|
||||||
|
|
||||||
JsonObject vertxConfig = globalConfig.getJsonObject(ConfigConstant.VERTX);
|
JsonObject vertxConfig = globalConfig.getJsonObject(VERTX);
|
||||||
Integer vertxConfigELPS = vertxConfig.getInteger(ConfigConstant.EVENT_LOOP_POOL_SIZE);
|
Integer vertxConfigELPS = vertxConfig.getInteger(EVENT_LOOP_POOL_SIZE);
|
||||||
var vertxOptions = vertxConfigELPS == 0 ?
|
var vertxOptions = vertxConfigELPS == 0 ?
|
||||||
new VertxOptions() : new VertxOptions(vertxConfig);
|
new VertxOptions() : new VertxOptions(vertxConfig);
|
||||||
|
|
||||||
|
vertxOptions.setAddressResolverOptions(
|
||||||
|
new AddressResolverOptions().
|
||||||
|
addServer("114.114.114.114").
|
||||||
|
addServer("114.114.115.115").
|
||||||
|
addServer("8.8.8.8").
|
||||||
|
addServer("8.8.4.4"));
|
||||||
LOGGER.info("vertxConfigEventLoopPoolSize: {}, eventLoopPoolSize: {}, workerPoolSize: {}", vertxConfigELPS,
|
LOGGER.info("vertxConfigEventLoopPoolSize: {}, eventLoopPoolSize: {}, workerPoolSize: {}", vertxConfigELPS,
|
||||||
vertxOptions.getEventLoopPoolSize(),
|
vertxOptions.getEventLoopPoolSize(),
|
||||||
vertxOptions.getWorkerPoolSize());
|
vertxOptions.getWorkerPoolSize());
|
||||||
@@ -121,23 +136,27 @@ public final class Deploy {
|
|||||||
VertxHolder.init(vertx);
|
VertxHolder.init(vertx);
|
||||||
//配置保存在共享数据中
|
//配置保存在共享数据中
|
||||||
var sharedData = vertx.sharedData();
|
var sharedData = vertx.sharedData();
|
||||||
LocalMap<String, Object> localMap = sharedData.getLocalMap(ConfigConstant.LOCAL);
|
LocalMap<String, Object> localMap = sharedData.getLocalMap(LOCAL);
|
||||||
localMap.put(ConfigConstant.GLOBAL_CONFIG, globalConfig);
|
localMap.put(GLOBAL_CONFIG, globalConfig);
|
||||||
localMap.put(ConfigConstant.CUSTOM_CONFIG, customConfig);
|
localMap.put(CUSTOM_CONFIG, customConfig);
|
||||||
localMap.put(ConfigConstant.SERVER, globalConfig.getJsonObject(ConfigConstant.SERVER));
|
localMap.put(SERVER, globalConfig.getJsonObject(SERVER));
|
||||||
var future0 = vertx.createSharedWorkerExecutor("other-handle").executeBlocking(bch -> {
|
var future0 = vertx.createSharedWorkerExecutor("other-handle")
|
||||||
|
.executeBlocking(() -> {
|
||||||
handle.handle(globalConfig);
|
handle.handle(globalConfig);
|
||||||
bch.complete("other handle complete");
|
return "Other handle complete";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
future0.onSuccess(res -> {
|
||||||
|
LOGGER.info(res);
|
||||||
// 部署 路由、异步service、反向代理 服务
|
// 部署 路由、异步service、反向代理 服务
|
||||||
var future1 = vertx.deployVerticle(RouterVerticle.class, getWorkDeploymentOptions("Router"));
|
var future1 = vertx.deployVerticle(RouterVerticle.class, getWorkDeploymentOptions("Router"));
|
||||||
var future2 = vertx.deployVerticle(ServiceVerticle.class, getWorkDeploymentOptions("Service"));
|
var future2 = vertx.deployVerticle(ServiceVerticle.class, getWorkDeploymentOptions("Service"));
|
||||||
var future3 = vertx.deployVerticle(ReverseProxyVerticle.class, getWorkDeploymentOptions("proxy"));
|
var future3 = vertx.deployVerticle(ReverseProxyVerticle.class, getWorkDeploymentOptions("proxy"));
|
||||||
|
|
||||||
CompositeFuture.all(future1, future2, future3, future0)
|
Future.all(future1, future2, future3)
|
||||||
.onSuccess(this::deployWorkVerticalSuccess)
|
.onSuccess(this::deployWorkVerticalSuccess)
|
||||||
.onFailure(this::deployVerticalFailed);
|
.onFailure(this::deployVerticalFailed);
|
||||||
|
}).onFailure(e -> LOGGER.error("Other handle error", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,13 +187,13 @@ public final class Deploy {
|
|||||||
* @return Deployment Options
|
* @return Deployment Options
|
||||||
*/
|
*/
|
||||||
private DeploymentOptions getWorkDeploymentOptions(String name) {
|
private DeploymentOptions getWorkDeploymentOptions(String name) {
|
||||||
return getWorkDeploymentOptions(name, customConfig.getInteger(ConfigConstant.ASYNC_SERVICE_INSTANCES));
|
return getWorkDeploymentOptions(name, customConfig.getInteger(ASYNC_SERVICE_INSTANCES));
|
||||||
}
|
}
|
||||||
|
|
||||||
private DeploymentOptions getWorkDeploymentOptions(String name, int ins) {
|
private DeploymentOptions getWorkDeploymentOptions(String name, int ins) {
|
||||||
return new DeploymentOptions()
|
return new DeploymentOptions()
|
||||||
.setWorkerPoolName(name)
|
.setWorkerPoolName(name)
|
||||||
.setWorker(true)
|
.setThreadingModel(ThreadingModel.WORKER)
|
||||||
.setInstances(ins);
|
.setInstances(ins);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package cn.qaiu.vx.core.annotaions;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface HandleSortFilter {
|
||||||
|
/**
|
||||||
|
* 注册顺序,数字越大越先注册<br>
|
||||||
|
* 值<0时会过滤掉该处理器
|
||||||
|
*/
|
||||||
|
int value() default 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package cn.qaiu.vx.core.annotaions;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截器配置注解
|
||||||
|
* 正则匹配拦截途径
|
||||||
|
*
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface InterceptorConfig {
|
||||||
|
|
||||||
|
String pattern() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册顺序,数字越大越先注册
|
||||||
|
*/
|
||||||
|
int order() default 0;
|
||||||
|
}
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
package cn.qaiu.vx.core.base;
|
package cn.qaiu.vx.core.base;
|
||||||
|
|
||||||
|
import cn.qaiu.vx.core.interceptor.AfterInterceptor;
|
||||||
|
import cn.qaiu.vx.core.model.JsonResult;
|
||||||
|
import cn.qaiu.vx.core.util.CommonUtil;
|
||||||
|
import cn.qaiu.vx.core.util.ReflectionUtil;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.RoutingContext;
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.qaiu.vx.core.util.ResponseUtil.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一响应处理
|
* 统一响应处理
|
||||||
@@ -13,22 +20,43 @@ import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
|||||||
*/
|
*/
|
||||||
public interface BaseHttpApi {
|
public interface BaseHttpApi {
|
||||||
|
|
||||||
default void fireJsonResponse(RoutingContext ctx, JsonObject jsonResult) {
|
// 需要扫描注册的Router路径
|
||||||
ctx.response().putHeader(CONTENT_TYPE, "application/json; charset=utf-8")
|
Reflections reflections = ReflectionUtil.getReflections();
|
||||||
.setStatusCode(200)
|
|
||||||
.end(jsonResult.encode());
|
default void doFireJsonObjectResponse(RoutingContext ctx, JsonObject jsonObject) {
|
||||||
|
if (!ctx.response().ended()) {
|
||||||
|
fireJsonObjectResponse(ctx, jsonObject);
|
||||||
|
}
|
||||||
|
handleAfterInterceptor(ctx, jsonObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
default <T> void fireJsonResponse(RoutingContext ctx, T jsonResult) {
|
|
||||||
JsonObject jsonObject = JsonObject.mapFrom(jsonResult);
|
default <T> void doFireJsonResultResponse(RoutingContext ctx, JsonResult<T> jsonResult) {
|
||||||
fireJsonResponse(ctx, jsonObject);
|
if (!ctx.response().ended()) {
|
||||||
|
fireJsonResultResponse(ctx, jsonResult);
|
||||||
|
}
|
||||||
|
handleAfterInterceptor(ctx, jsonResult.toJsonObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
default void fireTextResponse(RoutingContext ctx, String text) {
|
|
||||||
ctx.response().putHeader("content-type", "text/html; charset=utf-8").end(text);
|
default Set<AfterInterceptor> getAfterInterceptor() {
|
||||||
|
|
||||||
|
Set<Class<? extends AfterInterceptor>> afterInterceptorClassSet =
|
||||||
|
reflections.getSubTypesOf(AfterInterceptor.class);
|
||||||
|
if (afterInterceptorClassSet == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return CommonUtil.sortClassSet(afterInterceptorClassSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
default void sendError(int statusCode, RoutingContext ctx) {
|
default void handleAfterInterceptor(RoutingContext ctx, JsonObject jsonObject) {
|
||||||
ctx.response().setStatusCode(statusCode).end();
|
Set<AfterInterceptor> afterInterceptor = getAfterInterceptor();
|
||||||
|
if (afterInterceptor != null) {
|
||||||
|
afterInterceptor.forEach(ai -> ai.handle(ctx, jsonObject));
|
||||||
}
|
}
|
||||||
|
if (!ctx.response().ended()) {
|
||||||
|
fireTextResponse(ctx, "handleAfterInterceptor: response not end");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import cn.qaiu.vx.core.annotaions.RouteHandler;
|
|||||||
import cn.qaiu.vx.core.annotaions.RouteMapping;
|
import cn.qaiu.vx.core.annotaions.RouteMapping;
|
||||||
import cn.qaiu.vx.core.annotaions.SockRouteMapper;
|
import cn.qaiu.vx.core.annotaions.SockRouteMapper;
|
||||||
import cn.qaiu.vx.core.base.BaseHttpApi;
|
import cn.qaiu.vx.core.base.BaseHttpApi;
|
||||||
import cn.qaiu.vx.core.enums.MIMEType;
|
import cn.qaiu.vx.core.interceptor.BeforeInterceptor;
|
||||||
import cn.qaiu.vx.core.model.JsonResult;
|
import cn.qaiu.vx.core.model.JsonResult;
|
||||||
import cn.qaiu.vx.core.util.*;
|
import cn.qaiu.vx.core.util.*;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.Handler;
|
import io.vertx.core.Handler;
|
||||||
import io.vertx.core.MultiMap;
|
import io.vertx.core.MultiMap;
|
||||||
@@ -19,26 +20,27 @@ import io.vertx.core.json.JsonObject;
|
|||||||
import io.vertx.ext.web.Route;
|
import io.vertx.ext.web.Route;
|
||||||
import io.vertx.ext.web.Router;
|
import io.vertx.ext.web.Router;
|
||||||
import io.vertx.ext.web.RoutingContext;
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import io.vertx.ext.web.handler.BodyHandler;
|
import io.vertx.ext.web.handler.*;
|
||||||
import io.vertx.ext.web.handler.CorsHandler;
|
|
||||||
import io.vertx.ext.web.handler.StaticHandler;
|
|
||||||
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
|
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
|
||||||
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
|
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
|
||||||
import javassist.CtClass;
|
import javassist.CtClass;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.reflections.Reflections;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static cn.qaiu.vx.core.util.ConfigConstant.ROUTE_TIME_OUT;
|
||||||
|
import static cn.qaiu.vx.core.verticle.ReverseProxyVerticle.REROUTE_PATH_PREFIX;
|
||||||
import static io.vertx.core.http.HttpHeaders.*;
|
import static io.vertx.core.http.HttpHeaders.*;
|
||||||
|
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由映射, 参数绑定
|
* 路由映射, 参数绑定
|
||||||
@@ -57,15 +59,11 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
add(HttpMethod.DELETE);
|
add(HttpMethod.DELETE);
|
||||||
add(HttpMethod.HEAD);
|
add(HttpMethod.HEAD);
|
||||||
}};
|
}};
|
||||||
// 需要扫描注册的Router路径
|
|
||||||
private static volatile Reflections reflections;
|
|
||||||
|
|
||||||
private final String gatewayPrefix;
|
private final String gatewayPrefix;
|
||||||
|
|
||||||
public RouterHandlerFactory(String routerScanAddress, String gatewayPrefix) {
|
public RouterHandlerFactory(String gatewayPrefix) {
|
||||||
Objects.requireNonNull(routerScanAddress, "The router package address scan is empty.");
|
|
||||||
Objects.requireNonNull(gatewayPrefix, "The gateway prefix is empty.");
|
Objects.requireNonNull(gatewayPrefix, "The gateway prefix is empty.");
|
||||||
reflections = ReflectionUtil.getReflections(routerScanAddress);
|
|
||||||
this.gatewayPrefix = gatewayPrefix;
|
this.gatewayPrefix = gatewayPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,24 +71,21 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
* 开始扫描并注册handler
|
* 开始扫描并注册handler
|
||||||
*/
|
*/
|
||||||
public Router createRouter() {
|
public Router createRouter() {
|
||||||
Router router = Router.router(VertxHolder.getVertxInstance());
|
// 主路由
|
||||||
|
Router mainRouter = Router.router(VertxHolder.getVertxInstance());
|
||||||
// 静态资源
|
mainRouter.route().handler(ctx -> {
|
||||||
String path = SharedDataUtil.getJsonConfig("server")
|
String realPath = ctx.request().uri();;
|
||||||
.getString("staticResourcePath");
|
if (realPath.startsWith(REROUTE_PATH_PREFIX)) {
|
||||||
if (!StringUtils.isEmpty(path)) {
|
// vertx web proxy暂不支持rewrite, 所以这里进行手动替换, 请求地址中的请求path前缀替换为originPath
|
||||||
// 静态资源
|
String rePath = realPath.substring(REROUTE_PATH_PREFIX.length());
|
||||||
router.route("/*").handler(StaticHandler
|
ctx.reroute(rePath);
|
||||||
.create(path)
|
return;
|
||||||
.setCachingEnabled(true)
|
|
||||||
.setDefaultContentEncoding("UTF-8"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
router.route().handler(ctx -> {
|
|
||||||
LOGGER.debug("The HTTP service request address information ===>path:{}, uri:{}, method:{}",
|
LOGGER.debug("The HTTP service request address information ===>path:{}, uri:{}, method:{}",
|
||||||
ctx.request().path(), ctx.request().absoluteURI(), ctx.request().method());
|
ctx.request().path(), ctx.request().absoluteURI(), ctx.request().method());
|
||||||
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||||
|
ctx.response().headers().add(DATE, LocalDateTime.now().format(ISO_LOCAL_DATE_TIME));
|
||||||
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, OPTIONS, PUT, DELETE, HEAD");
|
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, OPTIONS, PUT, DELETE, HEAD");
|
||||||
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGOTHER, Origin,Content-Type, Accept, " +
|
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGOTHER, Origin,Content-Type, Accept, " +
|
||||||
"X-Requested-With, Dev, Authorization, Version, Token");
|
"X-Requested-With, Dev, Authorization, Version, Token");
|
||||||
@@ -98,11 +93,15 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
ctx.next();
|
ctx.next();
|
||||||
});
|
});
|
||||||
// 添加跨域的方法
|
// 添加跨域的方法
|
||||||
router.route().handler(CorsHandler.create().addRelativeOrigin(".*").allowCredentials(true).allowedMethods(httpMethods));
|
mainRouter.route().handler(CorsHandler.create().addRelativeOrigin(".*").allowCredentials(true).allowedMethods(httpMethods));
|
||||||
|
|
||||||
// 配置文件上传路径
|
// 配置文件上传路径
|
||||||
router.route().handler(BodyHandler.create().setUploadsDirectory("uploads"));
|
mainRouter.route().handler(BodyHandler.create().setUploadsDirectory("uploads"));
|
||||||
|
|
||||||
|
// 拦截器
|
||||||
|
Set<Handler<RoutingContext>> interceptorSet = getInterceptorSet();
|
||||||
|
Route route0 = mainRouter.route("/*");
|
||||||
|
interceptorSet.forEach(route0::handler);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Set<Class<?>> handlers = reflections.getTypesAnnotatedWith(RouteHandler.class);
|
Set<Class<?>> handlers = reflections.getTypesAnnotatedWith(RouteHandler.class);
|
||||||
@@ -116,7 +115,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
for (Class<?> handler : sortedHandlers) {
|
for (Class<?> handler : sortedHandlers) {
|
||||||
try {
|
try {
|
||||||
// 注册请求处理方法
|
// 注册请求处理方法
|
||||||
registerNewHandler(router, handler);
|
registerNewHandler(mainRouter, handler);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
LOGGER.error("Error register {}, Error details:", handler, e.getCause());
|
LOGGER.error("Error register {}, Error details:", handler, e.getCause());
|
||||||
|
|
||||||
@@ -126,12 +125,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
LOGGER.error("Manually Register Handler Fail, Error details:" + e.getMessage());
|
LOGGER.error("Manually Register Handler Fail, Error details:" + e.getMessage());
|
||||||
}
|
}
|
||||||
// 错误请求处理
|
// 错误请求处理
|
||||||
router.errorHandler(405, ctx -> fireJsonResponse(ctx, JsonResult
|
mainRouter.errorHandler(405, ctx -> doFireJsonResultResponse(ctx, JsonResult
|
||||||
.error("Method Not Allowed", 405)));
|
.error("Method Not Allowed", 405)));
|
||||||
router.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true)
|
mainRouter.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true)
|
||||||
.end("Internal server error: 404 not found"));
|
.end("Internal server error: 404 not found"));
|
||||||
|
|
||||||
return router;
|
return mainRouter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,15 +154,13 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
method -> method.isAnnotationPresent(SockRouteMapper.class)
|
method -> method.isAnnotationPresent(SockRouteMapper.class)
|
||||||
).toList());
|
).toList());
|
||||||
|
|
||||||
// 拦截器
|
|
||||||
Handler<RoutingContext> interceptor = getInterceptor();
|
|
||||||
// 依次注册处理方法
|
// 依次注册处理方法
|
||||||
for (Method method : methodList) {
|
for (Method method : methodList) {
|
||||||
if (method.isAnnotationPresent(RouteMapping.class)) {
|
if (method.isAnnotationPresent(RouteMapping.class)) {
|
||||||
// 普通路由
|
// 普通路由
|
||||||
RouteMapping mapping = method.getAnnotation(RouteMapping.class);
|
RouteMapping mapping = method.getAnnotation(RouteMapping.class);
|
||||||
HttpMethod routeMethod = HttpMethod.valueOf(mapping.method().name());
|
HttpMethod routeMethod = HttpMethod.valueOf(mapping.method().name());
|
||||||
String routeUrl = getRouteUrl(method.getName(), mapping.value());
|
String routeUrl = getRouteUrl(mapping.value());
|
||||||
String url = root.concat(routeUrl);
|
String url = root.concat(routeUrl);
|
||||||
// 匹配方法
|
// 匹配方法
|
||||||
Route route = router.route(routeMethod, url);
|
Route route = router.route(routeMethod, url);
|
||||||
@@ -173,17 +170,18 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
route.consumes(mineType);
|
route.consumes(mineType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先执行拦截方法, 再进入业务请求
|
// 设置默认超时
|
||||||
route.handler(interceptor);
|
route.handler(TimeoutHandler.create(SharedDataUtil.getCustomConfig().getInteger(ROUTE_TIME_OUT)));
|
||||||
|
route.handler(ResponseTimeHandler.create());
|
||||||
route.handler(ctx -> handlerMethod(instance, method, ctx)).failureHandler(ctx -> {
|
route.handler(ctx -> handlerMethod(instance, method, ctx)).failureHandler(ctx -> {
|
||||||
if (ctx.response().ended()) return;
|
if (ctx.response().ended()) return;
|
||||||
ctx.failure().printStackTrace();
|
ctx.failure().printStackTrace();
|
||||||
fireJsonResponse(ctx, JsonResult.error(ctx.failure().getMessage(), 500));
|
doFireJsonResultResponse(ctx, JsonResult.error(ctx.failure().getMessage(), 500));
|
||||||
});
|
});
|
||||||
} else if (method.isAnnotationPresent(SockRouteMapper.class)) {
|
} else if (method.isAnnotationPresent(SockRouteMapper.class)) {
|
||||||
// websocket 基于sockJs
|
// websocket 基于sockJs
|
||||||
SockRouteMapper mapping = method.getAnnotation(SockRouteMapper.class);
|
SockRouteMapper mapping = method.getAnnotation(SockRouteMapper.class);
|
||||||
String routeUrl = getRouteUrl(method.getName(), mapping.value());
|
String routeUrl = getRouteUrl(mapping.value());
|
||||||
String url = root.concat(routeUrl);
|
String url = root.concat(routeUrl);
|
||||||
LOGGER.info("Register New Websocket Handler -> {}", url);
|
LOGGER.info("Register New Websocket Handler -> {}", url);
|
||||||
SockJSHandlerOptions options = new SockJSHandlerOptions()
|
SockJSHandlerOptions options = new SockJSHandlerOptions()
|
||||||
@@ -210,10 +208,9 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
/**
|
/**
|
||||||
* 获取并处理路由URL分隔符
|
* 获取并处理路由URL分隔符
|
||||||
*
|
*
|
||||||
* @param methodName 路由method
|
|
||||||
* @return String
|
* @return String
|
||||||
*/
|
*/
|
||||||
private String getRouteUrl(String methodName, String mapperValue) {
|
private String getRouteUrl(String mapperValue) {
|
||||||
String routeUrl;
|
String routeUrl;
|
||||||
if ("/".equals(mapperValue)) {
|
if ("/".equals(mapperValue)) {
|
||||||
routeUrl = mapperValue;
|
routeUrl = mapperValue;
|
||||||
@@ -229,15 +226,10 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
* 配置拦截
|
* 配置拦截
|
||||||
*
|
*
|
||||||
* @return Handler
|
* @return Handler
|
||||||
* @throws Throwable Throwable
|
|
||||||
*/
|
*/
|
||||||
private Handler<RoutingContext> getInterceptor() throws Throwable {
|
private Set<Handler<RoutingContext>> getInterceptorSet() {
|
||||||
// 配置拦截
|
// 配置拦截
|
||||||
Class<?> interceptorClass = Class.forName(SharedDataUtil.getValueForCustomConfig("interceptorClassPath"));
|
return getBeforeInterceptor().stream().map(BeforeInterceptor::doHandle).collect(Collectors.toSet());
|
||||||
Object handleInstance = ReflectionUtil.newWithNoParam(interceptorClass);
|
|
||||||
Method doHandle = interceptorClass.getMethod("doHandle");
|
|
||||||
// 反射调用
|
|
||||||
return CastUtil.cast(ReflectionUtil.invoke(doHandle, handleInstance));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -259,7 +251,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
if (handler.isAnnotationPresent(RouteHandler.class)) {
|
if (handler.isAnnotationPresent(RouteHandler.class)) {
|
||||||
RouteHandler routeHandler = handler.getAnnotation(RouteHandler.class);
|
RouteHandler routeHandler = handler.getAnnotation(RouteHandler.class);
|
||||||
String value = routeHandler.value();
|
String value = routeHandler.value();
|
||||||
root += ("/".equals(value) ? "" : value);
|
root += (value.startsWith("/") ? value.substring(1) : value);
|
||||||
}
|
}
|
||||||
if (!root.endsWith("/")) {
|
if (!root.endsWith("/")) {
|
||||||
root = root + "/";
|
root = root + "/";
|
||||||
@@ -302,37 +294,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final MultiMap queryParams = ctx.queryParams();
|
|
||||||
if ("POST".equals(ctx.request().method().name())) {
|
|
||||||
queryParams.addAll(ctx.request().params());
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonArray entityPackagesReg = SharedDataUtil.getJsonArrayForCustomConfig("entityPackagesReg");
|
JsonArray entityPackagesReg = SharedDataUtil.getJsonArrayForCustomConfig("entityPackagesReg");
|
||||||
// 绑定get或post请求头的请求参数
|
|
||||||
methodParametersTemp.forEach((k, v) -> {
|
final MultiMap queryParams = ctx.queryParams();
|
||||||
if (ReflectionUtil.isBasicType(v.getRight())) {
|
|
||||||
String fmt = getFmt(v.getLeft(), v.getRight());
|
|
||||||
String value = queryParams.get(k);
|
|
||||||
parameterValueList.put(k, ReflectionUtil.conversion(v.getRight(), value, fmt));
|
|
||||||
} else if (RoutingContext.class.getName().equals(v.getRight().getName())) {
|
|
||||||
parameterValueList.put(k, ctx);
|
|
||||||
} else if (HttpServerRequest.class.getName().equals(v.getRight().getName())) {
|
|
||||||
parameterValueList.put(k, ctx.request());
|
|
||||||
} else if (HttpServerResponse.class.getName().equals(v.getRight().getName())) {
|
|
||||||
parameterValueList.put(k, ctx.response());
|
|
||||||
} else if (CommonUtil.matchRegList(entityPackagesReg.getList(), v.getRight().getName())) {
|
|
||||||
// 绑定实体类
|
|
||||||
try {
|
|
||||||
Class<?> aClass = Class.forName(v.getRight().getName());
|
|
||||||
Object entity = ParamUtil.multiMapToEntity(queryParams, aClass);
|
|
||||||
parameterValueList.put(k, entity);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// 解析body-json参数
|
// 解析body-json参数
|
||||||
if ("application/json".equals(ctx.parsedHeaders().contentType().value()) && ctx.body().asJsonObject() != null) {
|
if (HttpHeaderValues.APPLICATION_JSON.toString().equals(ctx.parsedHeaders().contentType().value())
|
||||||
|
&& ctx.body().asJsonObject() != null) {
|
||||||
JsonObject body = ctx.body().asJsonObject();
|
JsonObject body = ctx.body().asJsonObject();
|
||||||
if (body != null) {
|
if (body != null) {
|
||||||
methodParametersTemp.forEach((k, v) -> {
|
methodParametersTemp.forEach((k, v) -> {
|
||||||
@@ -352,26 +319,66 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (ctx.body() != null) {
|
||||||
|
queryParams.addAll(ParamUtil.paramsToMap(ctx.body().asString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析其他参数
|
||||||
|
if ("POST".equals(ctx.request().method().name())) {
|
||||||
|
queryParams.addAll(ctx.request().params());
|
||||||
|
}
|
||||||
|
// 绑定get或post请求头的请求参数
|
||||||
|
methodParametersTemp.forEach((k, v) -> {
|
||||||
|
if (ReflectionUtil.isBasicType(v.getRight())) {
|
||||||
|
String fmt = getFmt(v.getLeft(), v.getRight());
|
||||||
|
String value = queryParams.get(k);
|
||||||
|
parameterValueList.put(k, ReflectionUtil.conversion(v.getRight(), value, fmt));
|
||||||
|
} else if (RoutingContext.class.getName().equals(v.getRight().getName())) {
|
||||||
|
parameterValueList.put(k, ctx);
|
||||||
|
} else if (HttpServerRequest.class.getName().equals(v.getRight().getName())) {
|
||||||
|
parameterValueList.put(k, ctx.request());
|
||||||
|
} else if (HttpServerResponse.class.getName().equals(v.getRight().getName())) {
|
||||||
|
parameterValueList.put(k, ctx.response());
|
||||||
|
} else if (parameterValueList.get(k) == null
|
||||||
|
&& CommonUtil.matchRegList(entityPackagesReg.getList(), v.getRight().getName())) {
|
||||||
|
// 绑定实体类
|
||||||
|
try {
|
||||||
|
Class<?> aClass = Class.forName(v.getRight().getName());
|
||||||
|
Object entity = ParamUtil.multiMapToEntity(queryParams, aClass);
|
||||||
|
parameterValueList.put(k, entity);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
// 调用handle 获取响应对象
|
// 调用handle 获取响应对象
|
||||||
Object[] parameterValueArray = parameterValueList.values().toArray(new Object[0]);
|
Object[] parameterValueArray = parameterValueList.values().toArray(new Object[0]);
|
||||||
try {
|
try {
|
||||||
// 反射调用
|
// 反射调用
|
||||||
Object data = ReflectionUtil.invokeWithArguments(method, instance, parameterValueArray);
|
Object data = ReflectionUtil.invokeWithArguments(method, instance, parameterValueArray);
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
|
|
||||||
if (data instanceof JsonResult) {
|
if (data instanceof JsonResult) {
|
||||||
fireJsonResponse(ctx, data);
|
doFireJsonResultResponse(ctx, (JsonResult<?>) data);
|
||||||
|
}
|
||||||
|
if (data instanceof JsonObject) {
|
||||||
|
doFireJsonObjectResponse(ctx, ((JsonObject) data));
|
||||||
} else if (data instanceof Future) { // 处理异步响应
|
} else if (data instanceof Future) { // 处理异步响应
|
||||||
((Future<?>) data).onSuccess(res -> {
|
((Future<?>) data).onSuccess(res -> {
|
||||||
if (res instanceof JsonObject) {
|
if (res instanceof JsonResult) {
|
||||||
fireJsonResponse(ctx, res);
|
doFireJsonResultResponse(ctx, (JsonResult<?>) res);
|
||||||
} else if (res != null) {
|
|
||||||
fireJsonResponse(ctx, JsonResult.data(res));
|
|
||||||
}
|
}
|
||||||
}).onFailure(e -> fireJsonResponse(ctx, JsonResult.error(e.getMessage())));
|
if (res instanceof JsonObject) {
|
||||||
|
doFireJsonObjectResponse(ctx, ((JsonObject) res));
|
||||||
|
} else if (res != null) {
|
||||||
|
doFireJsonResultResponse(ctx, JsonResult.data(res));
|
||||||
} else {
|
} else {
|
||||||
ctx.response().headers().set(CONTENT_TYPE, MIMEType.TEXT_HTML.getValue());
|
handleAfterInterceptor(ctx, null);
|
||||||
ctx.end(data.toString());
|
}
|
||||||
|
|
||||||
|
}).onFailure(e -> doFireJsonResultResponse(ctx, JsonResult.error(e.getMessage())));
|
||||||
|
} else {
|
||||||
|
doFireJsonResultResponse(ctx, JsonResult.data(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@@ -384,7 +391,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
err = e.getCause().getMessage();
|
err = e.getCause().getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fireJsonResponse(ctx, JsonResult.error(err));
|
doFireJsonResultResponse(ctx, JsonResult.error(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,4 +409,13 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
}
|
}
|
||||||
return fmt;
|
return fmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<BeforeInterceptor> getBeforeInterceptor() {
|
||||||
|
Set<Class<? extends BeforeInterceptor>> interceptorClassSet =
|
||||||
|
reflections.getSubTypesOf(BeforeInterceptor.class);
|
||||||
|
if (interceptorClassSet == null) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
return CommonUtil.sortClassSet(interceptorClassSet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package cn.qaiu.vx.core.interceptor;
|
||||||
|
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后置拦截器接口
|
||||||
|
*
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
*/
|
||||||
|
public interface AfterInterceptor {
|
||||||
|
|
||||||
|
void handle(RoutingContext ctx, JsonObject responseData);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package cn.qaiu.vx.core.interceptor;
|
||||||
|
|
||||||
|
import io.vertx.core.Handler;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
|
||||||
|
import static cn.qaiu.vx.core.util.ResponseUtil.sendError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前置拦截器接口
|
||||||
|
*
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
*/
|
||||||
|
public interface BeforeInterceptor extends Handler<RoutingContext> {
|
||||||
|
String IS_NEXT = "RoutingContextIsNext";
|
||||||
|
|
||||||
|
default Handler<RoutingContext> doHandle() {
|
||||||
|
|
||||||
|
return ctx -> {
|
||||||
|
ctx.put(IS_NEXT, false);
|
||||||
|
BeforeInterceptor.this.handle(ctx);
|
||||||
|
if (!(Boolean) ctx.get(IS_NEXT) && !ctx.response().ended()) {
|
||||||
|
sendError(ctx, 403);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default void doNext(RoutingContext context) {
|
||||||
|
context.put(IS_NEXT, true);
|
||||||
|
context.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle(RoutingContext context);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package cn.qaiu.vx.core.interceptor;
|
|
||||||
|
|
||||||
import io.vertx.core.Handler;
|
|
||||||
import io.vertx.ext.web.RoutingContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截器接口
|
|
||||||
* <br>Create date 2021-05-06 09:20:37
|
|
||||||
*
|
|
||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
|
||||||
*/
|
|
||||||
public interface Interceptor {
|
|
||||||
|
|
||||||
default Handler<RoutingContext> doHandle() {
|
|
||||||
return this::handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle(RoutingContext context);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package cn.qaiu.vx.core.model;
|
package cn.qaiu.vx.core.model;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.qaiu.vx.core.util.CastUtil;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -159,4 +162,16 @@ public class JsonResult<T> implements Serializable {
|
|||||||
public static <T> JsonResult<T> success() {
|
public static <T> JsonResult<T> success() {
|
||||||
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, null);
|
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 转为json对象
|
||||||
|
public JsonObject toJsonObject() {
|
||||||
|
return JsonObject.mapFrom(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
// 转为json对象
|
||||||
|
public static JsonResult<?> toJsonResult(JsonObject json) {
|
||||||
|
return CastUtil.cast(json.mapTo(JsonResult.class));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
core/src/main/java/cn/qaiu/vx/core/package-info.java
Normal file
7
core/src/main/java/cn/qaiu/vx/core/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* ModuleGen cn.qaiu.vx.core
|
||||||
|
*/
|
||||||
|
@ModuleGen(name = "vertx-http-proxy", groupPackage = "cn.qaiu.vx.core", useFutures = true)
|
||||||
|
package cn.qaiu.vx.core;
|
||||||
|
|
||||||
|
import io.vertx.codegen.annotations.ModuleGen;
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package cn.qaiu.vx.core.util;
|
package cn.qaiu.vx.core.util;
|
||||||
|
|
||||||
|
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
|
||||||
import io.vertx.core.buffer.Buffer;
|
import io.vertx.core.buffer.Buffer;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import org.apache.commons.beanutils.ConvertUtils;
|
|
||||||
import org.apache.commons.beanutils.Converter;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -15,6 +14,9 @@ import java.net.URL;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CommonUtil
|
* CommonUtil
|
||||||
@@ -70,10 +72,11 @@ public class CommonUtil {
|
|||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try(Socket ignored = new Socket(Address, port)) {
|
try (Socket ignored = new Socket(Address, port)) {
|
||||||
//建立一个Socket连接
|
//建立一个Socket连接
|
||||||
flag = true;
|
flag = true;
|
||||||
} catch (IOException ignoredException) {}
|
} catch (IOException ignoredException) {
|
||||||
|
}
|
||||||
return flag;
|
return flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,23 +98,6 @@ public class CommonUtil {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册枚举转换器
|
|
||||||
*
|
|
||||||
* @param enums 枚举类
|
|
||||||
*/
|
|
||||||
@SafeVarargs
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
public static void enumConvert(Class<? extends Enum>... enums) {
|
|
||||||
for (Class<? extends Enum> anEnum : enums) {
|
|
||||||
ConvertUtils.register(new Converter() {
|
|
||||||
public Object convert(Class type, Object value) {
|
|
||||||
return Enum.valueOf(anEnum, (String) value);
|
|
||||||
}
|
|
||||||
}, anEnum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理其他配置
|
* 处理其他配置
|
||||||
*
|
*
|
||||||
@@ -126,4 +112,49 @@ public class CommonUtil {
|
|||||||
LocalConstant.put(configName, map);
|
LocalConstant.put(configName, map);
|
||||||
LOGGER.info("读取配置{}成功", configName);
|
LOGGER.info("读取配置{}成功", configName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> Set<T> sortClassSet(Set<Class<? extends T>> set) {
|
||||||
|
return set.stream().filter(c1 -> {
|
||||||
|
HandleSortFilter s1 = c1.getAnnotation(HandleSortFilter.class);
|
||||||
|
if (s1 != null) {
|
||||||
|
return s1.value() > 0;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}).sorted((c1, c2) -> {
|
||||||
|
HandleSortFilter s1 = c1.getAnnotation(HandleSortFilter.class);
|
||||||
|
HandleSortFilter s2 = c2.getAnnotation(HandleSortFilter.class);
|
||||||
|
int n1 = 0, n2 = 0;
|
||||||
|
if (s1 != null) {
|
||||||
|
n1 = s1.value();
|
||||||
|
}
|
||||||
|
if (s2 != null) {
|
||||||
|
n2 = s2.value();
|
||||||
|
}
|
||||||
|
return n1 - n2;
|
||||||
|
}).map(c -> {
|
||||||
|
try {
|
||||||
|
return ReflectionUtil.newWithNoParam(c);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String appVersion;
|
||||||
|
|
||||||
|
public static String getAppVersion() {
|
||||||
|
if (null == appVersion) {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
try {
|
||||||
|
properties.load(CommonUtil.class.getClassLoader().getResourceAsStream("app.properties"));
|
||||||
|
if (!properties.isEmpty()) {
|
||||||
|
appVersion = properties.getProperty("app.version");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return appVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,17 @@ package cn.qaiu.vx.core.util;
|
|||||||
public interface ConfigConstant {
|
public interface ConfigConstant {
|
||||||
String CUSTOM = "custom";
|
String CUSTOM = "custom";
|
||||||
String VERTX = "vertx";
|
String VERTX = "vertx";
|
||||||
|
|
||||||
String EVENT_LOOP_POOL_SIZE = "eventLoopPoolSize";
|
String EVENT_LOOP_POOL_SIZE = "eventLoopPoolSize";
|
||||||
String LOCAL = "local";
|
String LOCAL = "local";
|
||||||
String SERVER = "server";
|
String SERVER = "server";
|
||||||
|
String CACHE = "cache";
|
||||||
|
|
||||||
|
String PROXY = "proxy";
|
||||||
String GLOBAL_CONFIG = "globalConfig";
|
String GLOBAL_CONFIG = "globalConfig";
|
||||||
String CUSTOM_CONFIG = "customConfig";
|
String CUSTOM_CONFIG = "customConfig";
|
||||||
String ASYNC_SERVICE_INSTANCES = "asyncServiceInstances";
|
String ASYNC_SERVICE_INSTANCES = "asyncServiceInstances";
|
||||||
|
String IGNORES_REG="ignoresReg";
|
||||||
|
String BASE_LOCATIONS="baseLocations";
|
||||||
|
|
||||||
|
String ROUTE_TIME_OUT="routeTimeOut";
|
||||||
}
|
}
|
||||||
|
|||||||
46
core/src/main/java/cn/qaiu/vx/core/util/JacksonConfig.java
Normal file
46
core/src/main/java/cn/qaiu/vx/core/util/JacksonConfig.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package cn.qaiu.vx.core.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||||
|
import io.vertx.core.json.jackson.DatabindCodec;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
* @date 2023/10/14 9:07
|
||||||
|
*/
|
||||||
|
public class JacksonConfig {
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 通过该方法对mapper对象进行设置,所有序列化的对象都将按改规则进行系列化
|
||||||
|
// Include.Include.ALWAYS 默认
|
||||||
|
// Include.NON_DEFAULT 属性为默认值不序列化
|
||||||
|
// Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化,则返回的json是没有这个字段的。这样对移动端会更省流量
|
||||||
|
// Include.NON_NULL 属性为NULL 不序列化,就是为null的字段不参加序列化
|
||||||
|
// objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
|
ObjectMapper objectMapper = DatabindCodec.mapper();
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
|
||||||
|
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||||
|
javaTimeModule.addDeserializer(LocalDateTime.class,
|
||||||
|
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||||
|
javaTimeModule.addDeserializer(LocalDate.class,
|
||||||
|
new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||||
|
javaTimeModule.addDeserializer(LocalTime.class,
|
||||||
|
new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||||
|
objectMapper.registerModule(javaTimeModule);
|
||||||
|
LoggerFactory.getLogger(JacksonConfig.class).info("Global JacksonConfig complete.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void nothing() {}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package cn.qaiu.vx.core.util;
|
package cn.qaiu.vx.core.util;
|
||||||
|
|
||||||
import io.vertx.core.MultiMap;
|
import io.vertx.core.MultiMap;
|
||||||
import org.apache.commons.beanutils.BeanUtils;
|
import io.vertx.core.json.JsonObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -18,28 +17,37 @@ import java.util.Map;
|
|||||||
public final class ParamUtil {
|
public final class ParamUtil {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ParamUtil.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(ParamUtil.class);
|
||||||
|
|
||||||
public static Map<String, String> multiMapToMap(MultiMap multiMap) {
|
public static Map<String, Object> multiMapToMap(MultiMap multiMap) {
|
||||||
if (multiMap == null) return null;
|
if (multiMap == null) return null;
|
||||||
Map<String, String> map = new HashMap<>();
|
Map<String, Object> map = new HashMap<>();
|
||||||
for (Map.Entry<String, String> entry : multiMap.entries()) {
|
for (Map.Entry<String, String> entry : multiMap.entries()) {
|
||||||
map.put(entry.getKey(), entry.getValue());
|
map.put(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T multiMapToEntity(MultiMap multiMap,Class<T> tClass) throws NoSuchMethodException {
|
public static <T> T multiMapToEntity(MultiMap multiMap, Class<T> tClass) {
|
||||||
Map<String,String> map = multiMapToMap(multiMap);
|
Map<String, Object> map = multiMapToMap(multiMap);
|
||||||
T obj = null;
|
if (map == null) {
|
||||||
try {
|
return null;
|
||||||
obj = tClass.getDeclaredConstructor().newInstance();
|
|
||||||
BeanUtils.populate(obj,map);
|
|
||||||
} catch (InstantiationException | IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
LOGGER.error("实例化异常");
|
|
||||||
} catch (InvocationTargetException e2) {
|
|
||||||
e2.printStackTrace();
|
|
||||||
LOGGER.error("map2bean转换异常");
|
|
||||||
}
|
}
|
||||||
return obj;
|
return new JsonObject(map).mapTo(tClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MultiMap paramsToMap(String paramString) {
|
||||||
|
MultiMap entries = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
if (paramString == null) return entries;
|
||||||
|
String[] params = paramString.split("&");
|
||||||
|
if (params.length == 0) return entries;
|
||||||
|
for (String param : params) {
|
||||||
|
String[] kv = param.split("=");
|
||||||
|
if (kv.length == 2) {
|
||||||
|
entries.set(kv[0], kv[1]);
|
||||||
|
} else {
|
||||||
|
entries.set(kv[0], "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import javassist.bytecode.AccessFlag;
|
|||||||
import javassist.bytecode.CodeAttribute;
|
import javassist.bytecode.CodeAttribute;
|
||||||
import javassist.bytecode.LocalVariableAttribute;
|
import javassist.bytecode.LocalVariableAttribute;
|
||||||
import javassist.bytecode.MethodInfo;
|
import javassist.bytecode.MethodInfo;
|
||||||
import org.apache.commons.beanutils.ConversionException;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
import org.reflections.scanners.*;
|
import org.reflections.scanners.MemberUsageScanner;
|
||||||
|
import org.reflections.scanners.MethodParameterNamesScanner;
|
||||||
|
import org.reflections.scanners.Scanners;
|
||||||
import org.reflections.util.ClasspathHelper;
|
import org.reflections.util.ClasspathHelper;
|
||||||
import org.reflections.util.ConfigurationBuilder;
|
import org.reflections.util.ConfigurationBuilder;
|
||||||
import org.reflections.util.FilterBuilder;
|
import org.reflections.util.FilterBuilder;
|
||||||
@@ -24,6 +25,8 @@ import java.net.URL;
|
|||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于org.reflection和javassist的反射工具包
|
* 基于org.reflection和javassist的反射工具包
|
||||||
* 通过包扫描实现路由地址的注解映射
|
* 通过包扫描实现路由地址的注解映射
|
||||||
@@ -33,6 +36,16 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public final class ReflectionUtil {
|
public final class ReflectionUtil {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以默认配置的基础包路径获取反射器
|
||||||
|
*
|
||||||
|
* @return Reflections object
|
||||||
|
*/
|
||||||
|
public static Reflections getReflections() {
|
||||||
|
return getReflections(SharedDataUtil.getStringForCustomConfig(BASE_LOCATIONS));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取反射器
|
* 获取反射器
|
||||||
*
|
*
|
||||||
@@ -48,6 +61,7 @@ public final class ReflectionUtil {
|
|||||||
} else {
|
} else {
|
||||||
packageAddressList = Collections.singletonList(packageAddress);
|
packageAddressList = Collections.singletonList(packageAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
return getReflections(packageAddressList);
|
return getReflections(packageAddressList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,11 +84,12 @@ public final class ReflectionUtil {
|
|||||||
// 发现注解api层 没有继承父类时 这里反射一直有问题(Scanner SubTypesScanner was not configured)
|
// 发现注解api层 没有继承父类时 这里反射一直有问题(Scanner SubTypesScanner was not configured)
|
||||||
// 因此这里需要手动配置各种Scanner扫描器 -- https://blog.csdn.net/qq_29499107/article/details/106889781
|
// 因此这里需要手动配置各种Scanner扫描器 -- https://blog.csdn.net/qq_29499107/article/details/106889781
|
||||||
configurationBuilder.setScanners(
|
configurationBuilder.setScanners(
|
||||||
new SubTypesScanner(false), //允许getAllTypes获取所有Object的子类, 不设置为false则 getAllTypes 会报错.默认为true.
|
Scanners.SubTypes.filterResultsBy(s -> true), //允许getAllTypes获取所有Object的子类, 不设置为false则 getAllTypes
|
||||||
|
// 会报错.默认为true.
|
||||||
new MethodParameterNamesScanner(), //设置方法参数名称 扫描器,否则调用getConstructorParamNames 会报错
|
new MethodParameterNamesScanner(), //设置方法参数名称 扫描器,否则调用getConstructorParamNames 会报错
|
||||||
new MethodAnnotationsScanner(), //设置方法注解 扫描器, 否则getConstructorsAnnotatedWith,getMethodsAnnotatedWith 会报错
|
Scanners.MethodsAnnotated, //设置方法注解 扫描器, 否则getConstructorsAnnotatedWith,getMethodsAnnotatedWith 会报错
|
||||||
new MemberUsageScanner(), //设置 member 扫描器,否则 getMethodUsage 会报错, 不推荐使用,有可能会报错 Caused by: java.lang.ClassCastException: javassist.bytecode.InterfaceMethodrefInfo cannot be cast to javassist.bytecode.MethodrefInfo
|
new MemberUsageScanner(), //设置 member 扫描器,否则 getMethodUsage 会报错
|
||||||
new TypeAnnotationsScanner() //设置类注解 扫描器 ,否则 getTypesAnnotatedWith 会报错
|
Scanners.TypesAnnotated //设置类注解 扫描器 ,否则 getTypesAnnotatedWith 会报错
|
||||||
);
|
);
|
||||||
|
|
||||||
configurationBuilder.filterInputsBy(filterBuilder);
|
configurationBuilder.filterInputsBy(filterBuilder);
|
||||||
@@ -98,7 +113,8 @@ public final class ReflectionUtil {
|
|||||||
MethodInfo methodInfo = cm.getMethodInfo();
|
MethodInfo methodInfo = cm.getMethodInfo();
|
||||||
CtClass[] parameterTypes = cm.getParameterTypes();
|
CtClass[] parameterTypes = cm.getParameterTypes();
|
||||||
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
|
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
|
||||||
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
|
LocalVariableAttribute attr =
|
||||||
|
(LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
|
||||||
|
|
||||||
boolean flag = true;
|
boolean flag = true;
|
||||||
boolean flag2 = cm.getModifiers() - 1 != AccessFlag.STATIC;
|
boolean flag2 = cm.getModifiers() - 1 != AccessFlag.STATIC;
|
||||||
@@ -110,7 +126,8 @@ public final class ReflectionUtil {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
flag = false;
|
flag = false;
|
||||||
paramMap.put(attr.variableName(j + (flag2 ? 1 : 0)), Pair.of(parameterAnnotations[j - k], parameterTypes[j - k]));
|
paramMap.put(attr.variableName(j + (flag2 ? 1 : 0)), Pair.of(parameterAnnotations[j - k],
|
||||||
|
parameterTypes[j - k]));
|
||||||
}
|
}
|
||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -169,10 +186,10 @@ public final class ReflectionUtil {
|
|||||||
return DateUtils.parseDate(value, fmt);
|
return DateUtils.parseDate(value, fmt);
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new ConversionException("无法将格式化日期");
|
throw new RuntimeException("无法将格式化日期");
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new ConversionException("无法将String类型" + value + "转为[" + name + "]");
|
throw new RuntimeException("无法将String类型" + value + "转为[" + name + "]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +201,7 @@ public final class ReflectionUtil {
|
|||||||
* @return Array
|
* @return Array
|
||||||
*/
|
*/
|
||||||
public static Object conversionArray(CtClass ctClass, String value) {
|
public static Object conversionArray(CtClass ctClass, String value) {
|
||||||
if (!isBasicTypeArray(ctClass)) throw new ConversionException("无法解析数组");
|
if (!isBasicTypeArray(ctClass)) throw new RuntimeException("无法解析数组");
|
||||||
String[] strArr = value.split(",");
|
String[] strArr = value.split(",");
|
||||||
List<Object> obj = new ArrayList<>();
|
List<Object> obj = new ArrayList<>();
|
||||||
Arrays.stream(strArr).forEach(v -> obj.add(conversion(ctClass, v, null)));
|
Arrays.stream(strArr).forEach(v -> obj.add(conversion(ctClass, v, null)));
|
||||||
@@ -214,7 +231,8 @@ public final class ReflectionUtil {
|
|||||||
if (ctClass.isPrimitive() || "java.util.Date".equals(ctClass.getName())) {
|
if (ctClass.isPrimitive() || "java.util.Date".equals(ctClass.getName())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return ctClass.getName().matches("^java\\.lang\\.((Boolean)|(Character)|(Byte)|(Short)|(Integer)|(Long)|(Float)|(Double)|(String))$");
|
return ctClass.getName().matches("^java\\.lang\\.((Boolean)|(Character)|(Byte)|(Short)|(Integer)|(Long)|" +
|
||||||
|
"(Float)|(Double)|(String))$");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,7 +256,8 @@ public final class ReflectionUtil {
|
|||||||
* @throws InstantiationException InstantiationException
|
* @throws InstantiationException InstantiationException
|
||||||
* @throws IllegalAccessException IllegalAccessException
|
* @throws IllegalAccessException IllegalAccessException
|
||||||
*/
|
*/
|
||||||
public static <T> T newWithNoParam(Class<T> handler) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
|
public static <T> T newWithNoParam(Class<T> handler) throws NoSuchMethodException, InvocationTargetException,
|
||||||
|
InstantiationException, IllegalAccessException {
|
||||||
return handler.getConstructor().newInstance();
|
return handler.getConstructor().newInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
40
core/src/main/java/cn/qaiu/vx/core/util/ResponseUtil.java
Normal file
40
core/src/main/java/cn/qaiu/vx/core/util/ResponseUtil.java
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package cn.qaiu.vx.core.util;
|
||||||
|
|
||||||
|
import cn.qaiu.vx.core.model.JsonResult;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
|
import io.vertx.core.http.HttpHeaders;
|
||||||
|
import io.vertx.core.http.HttpServerResponse;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
|
||||||
|
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
||||||
|
|
||||||
|
public class ResponseUtil {
|
||||||
|
|
||||||
|
public static void redirect(HttpServerResponse response, String url) {
|
||||||
|
response.putHeader(HttpHeaders.LOCATION, url).setStatusCode(302).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void redirect(HttpServerResponse response, String url, Promise<?> promise) {
|
||||||
|
redirect(response, url);
|
||||||
|
promise.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireJsonObjectResponse(RoutingContext ctx, JsonObject jsonObject) {
|
||||||
|
ctx.response().putHeader(CONTENT_TYPE, "application/json; charset=utf-8")
|
||||||
|
.setStatusCode(200)
|
||||||
|
.end(jsonObject.encode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void fireJsonResultResponse(RoutingContext ctx, JsonResult<T> jsonResult) {
|
||||||
|
fireJsonObjectResponse(ctx, jsonResult.toJsonObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fireTextResponse(RoutingContext ctx, String text) {
|
||||||
|
ctx.response().putHeader(CONTENT_TYPE, "text/html; charset=utf-8").end(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendError(RoutingContext ctx, int statusCode) {
|
||||||
|
ctx.response().setStatusCode(statusCode).end();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,11 +32,11 @@ public class SharedDataUtil {
|
|||||||
return (JsonObject) localMap.get(key);
|
return (JsonObject) localMap.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JsonObject getJsonObjectForCustomConfig(String key) {
|
public static JsonObject getCustomConfig() {
|
||||||
return getJsonConfig("customConfig").getJsonObject(key);
|
return getJsonConfig("customConfig");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getJsonStringForCustomConfig(String key) {
|
public static String getStringForCustomConfig(String key) {
|
||||||
return getJsonConfig("customConfig").getString(key);
|
return getJsonConfig("customConfig").getString(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package cn.qaiu.vx.core.verticle;
|
||||||
|
|
||||||
|
import io.vertx.core.AbstractVerticle;
|
||||||
|
import io.vertx.core.Vertx;
|
||||||
|
import io.vertx.core.VertxOptions;
|
||||||
|
import io.vertx.core.dns.AddressResolverOptions;
|
||||||
|
import io.vertx.core.http.*;
|
||||||
|
import io.vertx.core.net.NetClient;
|
||||||
|
import io.vertx.core.net.NetClientOptions;
|
||||||
|
import io.vertx.core.net.NetSocket;
|
||||||
|
import io.vertx.core.net.ProxyOptions;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class HttpProxyVerticle extends AbstractVerticle {
|
||||||
|
|
||||||
|
private HttpClient httpClient;
|
||||||
|
private NetClient netClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
ProxyOptions proxyOptions = new ProxyOptions().setHost("127.0.0.1").setPort(7890);
|
||||||
|
// 初始化 HTTP 客户端,用于向目标服务器发送 HTTP 请求
|
||||||
|
HttpClientOptions httpClientOptions = new HttpClientOptions();
|
||||||
|
httpClient = vertx.createHttpClient(httpClientOptions.setProxyOptions(proxyOptions));
|
||||||
|
|
||||||
|
// 创建并启动 HTTP 代理服务器,监听指定端口
|
||||||
|
HttpServer server = vertx.createHttpServer(new HttpServerOptions().setClientAuth(ClientAuth.REQUIRED));
|
||||||
|
server.requestHandler(this::handleClientRequest);
|
||||||
|
|
||||||
|
// 初始化 NetClient,用于在 CONNECT 请求中建立 TCP 连接隧道
|
||||||
|
netClient = vertx.createNetClient(new NetClientOptions()
|
||||||
|
.setProxyOptions(proxyOptions)
|
||||||
|
.setConnectTimeout(15000)
|
||||||
|
.setTrustAll(true));
|
||||||
|
|
||||||
|
// 启动 HTTP 代理服务器
|
||||||
|
server.listen(7891, ar -> {
|
||||||
|
if (ar.succeeded()) {
|
||||||
|
System.out.println("HTTP Proxy server started on port 7891");
|
||||||
|
} else {
|
||||||
|
System.err.println("Failed to start HTTP Proxy server: " + ar.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 HTTP CONNECT 请求,用于代理 HTTPS 流量
|
||||||
|
private void handleConnectRequest(HttpServerRequest clientRequest) {
|
||||||
|
String[] uriParts = clientRequest.uri().split(":");
|
||||||
|
if (uriParts.length != 2) {
|
||||||
|
clientRequest.response().setStatusCode(400).end("Bad Request: Invalid URI format");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析目标主机和端口
|
||||||
|
String targetHost = uriParts[0];
|
||||||
|
int targetPort;
|
||||||
|
try {
|
||||||
|
targetPort = Integer.parseInt(uriParts[1]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
clientRequest.response().setStatusCode(400).end("Bad Request: Invalid port");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clientRequest.pause();
|
||||||
|
// 通过 NetClient 连接目标服务器并创建隧道
|
||||||
|
netClient.connect(targetPort, targetHost, connectionAttempt -> {
|
||||||
|
if (connectionAttempt.succeeded()) {
|
||||||
|
NetSocket targetSocket = connectionAttempt.result();
|
||||||
|
|
||||||
|
// 升级客户端连接到 NetSocket 并实现双向数据流
|
||||||
|
clientRequest.toNetSocket().onComplete(clientSocketAttempt -> {
|
||||||
|
if (clientSocketAttempt.succeeded()) {
|
||||||
|
NetSocket clientSocket = clientSocketAttempt.result();
|
||||||
|
|
||||||
|
// 设置双向数据流转发
|
||||||
|
clientSocket.handler(targetSocket::write);
|
||||||
|
targetSocket.handler(clientSocket::write);
|
||||||
|
|
||||||
|
// 关闭其中一方时关闭另一方
|
||||||
|
clientSocket.closeHandler(v -> targetSocket.close());
|
||||||
|
targetSocket.closeHandler(v -> clientSocket.close());
|
||||||
|
} else {
|
||||||
|
System.err.println("Failed to upgrade client connection to socket: " + clientSocketAttempt.cause().getMessage());
|
||||||
|
targetSocket.close();
|
||||||
|
clientRequest.response().setStatusCode(500).end("Internal Server Error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
System.err.println("Failed to connect to target: " + connectionAttempt.cause().getMessage());
|
||||||
|
clientRequest.response().setStatusCode(502).end("Bad Gateway: Unable to connect to target");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理客户端的 HTTP 请求
|
||||||
|
private void handleClientRequest(HttpServerRequest clientRequest) {
|
||||||
|
String s = clientRequest.headers().get("Proxy-Authorization");
|
||||||
|
if (s == null) {
|
||||||
|
clientRequest.response().setStatusCode(403).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] split = new String(Base64.getDecoder().decode(s.replace("Basic ", ""))).split(":");
|
||||||
|
if (split.length > 1) {
|
||||||
|
System.out.println(split[0]);
|
||||||
|
System.out.println(split[1]);
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (clientRequest.method() == HttpMethod.CONNECT) {
|
||||||
|
// 处理 CONNECT 请求
|
||||||
|
handleConnectRequest(clientRequest);
|
||||||
|
} else {
|
||||||
|
// 处理普通的 HTTP 请求
|
||||||
|
handleHttpRequest(clientRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 HTTP 请求,转发至目标服务器并返回响应
|
||||||
|
private void handleHttpRequest(HttpServerRequest clientRequest) {
|
||||||
|
// 获取目标主机
|
||||||
|
String hostHeader = clientRequest.getHeader("Host");
|
||||||
|
if (hostHeader == null) {
|
||||||
|
clientRequest.response().setStatusCode(400).end("Host header is missing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetHost = hostHeader.split(":")[0];
|
||||||
|
int targetPort = 80; // 默认为 HTTP 的端口
|
||||||
|
clientRequest.pause(); // 暂停客户端请求的读取,避免数据丢失
|
||||||
|
|
||||||
|
httpClient.request(clientRequest.method(), targetPort, targetHost, clientRequest.uri())
|
||||||
|
.onSuccess(request -> {
|
||||||
|
clientRequest.resume(); // 恢复客户端请求的读取
|
||||||
|
|
||||||
|
// 逐个设置请求头
|
||||||
|
clientRequest.headers().forEach(header -> request.putHeader(header.getKey(), header.getValue()));
|
||||||
|
|
||||||
|
// 将客户端请求的 body 转发给目标服务器
|
||||||
|
clientRequest.bodyHandler(body -> request.send(body, ar -> {
|
||||||
|
if (ar.succeeded()) {
|
||||||
|
var response = ar.result();
|
||||||
|
clientRequest.response().setStatusCode(response.statusCode());
|
||||||
|
clientRequest.response().headers().setAll(response.headers());
|
||||||
|
response.body().onSuccess(b-> clientRequest.response().end(b));
|
||||||
|
} else {
|
||||||
|
clientRequest.response().setStatusCode(502).end("Bad Gateway: Unable to reach target");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.onFailure(err -> {
|
||||||
|
err.printStackTrace();
|
||||||
|
clientRequest.response().setStatusCode(502).end("Bad Gateway: Request failed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
// 停止 HTTP 客户端以释放资源
|
||||||
|
if (httpClient != null) {
|
||||||
|
httpClient.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO add Deploy
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 配置 DNS 解析器,使用多个 DNS 服务器来提升解析速度
|
||||||
|
Vertx vertx = Vertx.vertx(new VertxOptions()
|
||||||
|
.setAddressResolverOptions(new AddressResolverOptions()
|
||||||
|
.addServer("114.114.114.114")
|
||||||
|
.addServer("114.114.115.115")
|
||||||
|
.addServer("8.8.8.8")
|
||||||
|
.addServer("8.8.4.4")));
|
||||||
|
|
||||||
|
// 部署 Verticle 并启动动态 HTTP 代理服务器
|
||||||
|
vertx.deployVerticle(new HttpProxyVerticle());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,13 +6,13 @@ import io.vertx.core.Future;
|
|||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.http.HttpClient;
|
import io.vertx.core.http.HttpClient;
|
||||||
import io.vertx.core.http.HttpServer;
|
import io.vertx.core.http.HttpServer;
|
||||||
import io.vertx.core.http.WebSocket;
|
import io.vertx.core.http.HttpServerOptions;
|
||||||
import io.vertx.core.json.JsonArray;
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.core.net.PemKeyCertOptions;
|
||||||
|
import io.vertx.ext.web.Route;
|
||||||
import io.vertx.ext.web.Router;
|
import io.vertx.ext.web.Router;
|
||||||
import io.vertx.ext.web.handler.StaticHandler;
|
import io.vertx.ext.web.handler.StaticHandler;
|
||||||
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
|
|
||||||
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
|
|
||||||
import io.vertx.ext.web.proxy.handler.ProxyHandler;
|
import io.vertx.ext.web.proxy.handler.ProxyHandler;
|
||||||
import io.vertx.httpproxy.HttpProxy;
|
import io.vertx.httpproxy.HttpProxy;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -40,14 +40,17 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
.getJsonConfig(ConfigConstant.GLOBAL_CONFIG)
|
.getJsonConfig(ConfigConstant.GLOBAL_CONFIG)
|
||||||
.getString("proxyConf");
|
.getString("proxyConf");
|
||||||
private static final Future<JsonObject> CONFIG = ConfigUtil.readYamlConfig(PATH_PROXY_CONFIG);
|
private static final Future<JsonObject> CONFIG = ConfigUtil.readYamlConfig(PATH_PROXY_CONFIG);
|
||||||
private static final String DEFAULT_PATH_404 = "webroot/err/404.html";
|
private static final String DEFAULT_PATH_404 = "webroot/err/page404.html";
|
||||||
|
|
||||||
private static String serverName = "Vert.x-proxy-server"; //Server name in Http response header
|
private static String serverName = "Vert.x-proxy-server"; //Server name in Http response header
|
||||||
|
|
||||||
|
public static String REROUTE_PATH_PREFIX = "/__rrvpspp"; //re_route_vert_proxy_server_path_prefix 硬编码
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Promise<Void> startPromise) throws Exception {
|
public void start(Promise<Void> startPromise) {
|
||||||
CONFIG.onSuccess(this::handleProxyConfList);
|
CONFIG.onSuccess(this::handleProxyConfList);
|
||||||
|
// createFileListener
|
||||||
startPromise.complete();
|
startPromise.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,22 +77,24 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
* @param proxyConf 代理配置
|
* @param proxyConf 代理配置
|
||||||
*/
|
*/
|
||||||
private void handleProxyConf(JsonObject proxyConf) {
|
private void handleProxyConf(JsonObject proxyConf) {
|
||||||
// 404 path
|
// page404 path
|
||||||
if (proxyConf.containsKey("404")) {
|
if (proxyConf.containsKey(
|
||||||
|
|
||||||
|
"page404")) {
|
||||||
System.getProperty("user.dir");
|
System.getProperty("user.dir");
|
||||||
String path = proxyConf.getString("404");
|
String path = proxyConf.getString("page404");
|
||||||
if (StringUtils.isEmpty(path)) {
|
if (StringUtils.isEmpty(path)) {
|
||||||
proxyConf.put("404", DEFAULT_PATH_404);
|
proxyConf.put("page404", DEFAULT_PATH_404);
|
||||||
} else {
|
} else {
|
||||||
if (!path.startsWith("/")) {
|
if (!path.startsWith("/")) {
|
||||||
path = "/" + path;
|
path = "/" + path;
|
||||||
}
|
}
|
||||||
if (!new File(System.getProperty("user.dir") + path).exists()) {
|
if (!new File(System.getProperty("user.dir") + path).exists()) {
|
||||||
proxyConf.put("404", DEFAULT_PATH_404);
|
proxyConf.put("page404", DEFAULT_PATH_404);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
proxyConf.put("404", DEFAULT_PATH_404);
|
proxyConf.put("page404", DEFAULT_PATH_404);
|
||||||
}
|
}
|
||||||
|
|
||||||
final HttpClient httpClient = VertxHolder.getVertxInstance().createHttpClient();
|
final HttpClient httpClient = VertxHolder.getVertxInstance().createHttpClient();
|
||||||
@@ -111,17 +116,10 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
handleStatic(proxyConf.getJsonObject("static"), proxyRouter);
|
handleStatic(proxyConf.getJsonObject("static"), proxyRouter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static server
|
// Send page404 page
|
||||||
if (proxyConf.containsKey("sock")) {
|
proxyRouter.errorHandler(404, ctx -> ctx.response().sendFile(proxyConf.getString("page404")));
|
||||||
handleSock(proxyConf.getJsonArray("sock"), httpClient, proxyRouter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send 404 page
|
HttpServer server = getHttpsServer(proxyConf);
|
||||||
proxyRouter.errorHandler(404, ctx -> {
|
|
||||||
ctx.response().sendFile(proxyConf.getString("404"));
|
|
||||||
});
|
|
||||||
|
|
||||||
HttpServer server = vertx.createHttpServer();
|
|
||||||
server.requestHandler(proxyRouter);
|
server.requestHandler(proxyRouter);
|
||||||
|
|
||||||
Integer port = proxyConf.getInteger("listen");
|
Integer port = proxyConf.getInteger("listen");
|
||||||
@@ -129,6 +127,38 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
server.listen(port);
|
server.listen(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HttpServer getHttpsServer(JsonObject proxyConf) {
|
||||||
|
HttpServerOptions httpServerOptions = new HttpServerOptions();
|
||||||
|
if (proxyConf.containsKey("ssl")) {
|
||||||
|
JsonObject sslConfig = proxyConf.getJsonObject("ssl");
|
||||||
|
|
||||||
|
URL sslUrl = this.getClass().getClassLoader().getResource("");
|
||||||
|
if (sslUrl == null) {
|
||||||
|
throw new RuntimeException("SSL url not exist...");
|
||||||
|
}
|
||||||
|
if (sslConfig.containsKey("enable") && sslConfig.getBoolean("enable")) {
|
||||||
|
String sslCertificatePath = sslUrl.getPath() + sslConfig.getString("ssl_certificate");
|
||||||
|
String sslCertificateKeyPath = sslUrl.getPath() + sslConfig.getString("ssl_certificate_key");
|
||||||
|
LOGGER.info("enable ssl config. ");
|
||||||
|
httpServerOptions
|
||||||
|
.setSsl(true)
|
||||||
|
.setKeyCertOptions(
|
||||||
|
new PemKeyCertOptions()
|
||||||
|
.setKeyPath(sslCertificateKeyPath)
|
||||||
|
.setCertPath(sslCertificatePath)
|
||||||
|
).addEnabledSecureTransportProtocol(sslConfig.getString("ssl_protocols"));
|
||||||
|
String sslCiphers = sslConfig.getString("ssl_ciphers");
|
||||||
|
if (sslCiphers != null && !sslCiphers.isEmpty()) {
|
||||||
|
for (String s : sslCiphers.split(":")) {
|
||||||
|
httpServerOptions.addEnabledCipherSuite(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return vertx.createHttpServer(httpServerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理静态资源配置
|
* 处理静态资源配置
|
||||||
*
|
*
|
||||||
@@ -145,9 +175,12 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
ctx.next();
|
ctx.next();
|
||||||
});
|
});
|
||||||
|
|
||||||
final StaticHandler staticHandler = StaticHandler.create();
|
|
||||||
|
StaticHandler staticHandler;
|
||||||
if (staticConf.containsKey("root")) {
|
if (staticConf.containsKey("root")) {
|
||||||
staticHandler.setWebRoot(staticConf.getString("root"));
|
staticHandler = StaticHandler.create(staticConf.getString("root"));
|
||||||
|
} else {
|
||||||
|
staticHandler = StaticHandler.create();
|
||||||
}
|
}
|
||||||
if (staticConf.containsKey("directory-listing")) {
|
if (staticConf.containsKey("directory-listing")) {
|
||||||
staticHandler.setDirectoryListing(staticConf.getBoolean("directory-listing"));
|
staticHandler.setDirectoryListing(staticConf.getBoolean("directory-listing"));
|
||||||
@@ -178,7 +211,7 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
port = 80;
|
port = 80;
|
||||||
}
|
}
|
||||||
String originPath = url.getPath();
|
String originPath = url.getPath();
|
||||||
LOGGER.debug("Conf(path, originPath, host, port) ----> {},{},{},{}", path, originPath, host, port);
|
LOGGER.info("path {}, originPath {}, to {}:{}", path, originPath, host, port);
|
||||||
|
|
||||||
// 注意这里不能origin多个代理地址, 一个实例只能代理一个origin
|
// 注意这里不能origin多个代理地址, 一个实例只能代理一个origin
|
||||||
final HttpProxy httpProxy = HttpProxy.reverseProxy(httpClient);
|
final HttpProxy httpProxy = HttpProxy.reverseProxy(httpClient);
|
||||||
@@ -189,14 +222,21 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
|
|
||||||
// 代理目标路径为空 就像nginx一样路径穿透 (相对路径)
|
// 代理目标路径为空 就像nginx一样路径穿透 (相对路径)
|
||||||
if (StringUtils.isEmpty(originPath) || path.equals(originPath)) {
|
if (StringUtils.isEmpty(originPath) || path.equals(originPath)) {
|
||||||
proxyRouter.route(path + "*").handler(ProxyHandler.create(httpProxy));
|
Route route = path.startsWith("~") ? proxyRouter.routeWithRegex(path.substring(1))
|
||||||
|
: proxyRouter.route(path);
|
||||||
|
route.handler(ProxyHandler.create(httpProxy));
|
||||||
} else {
|
} else {
|
||||||
proxyRouter.route(originPath + "*").handler(ProxyHandler.create(httpProxy));
|
// 配置 /api/, / => 请求 /api/test 代理后 /test
|
||||||
proxyRouter.route(path + "*").handler(ctx -> {
|
// 配置 /api/, /xxx => 请求 /api/test 代理后 /xxx/test
|
||||||
String realPath = ctx.request().path();
|
final String path0 = path;
|
||||||
if (realPath.startsWith(path)) {
|
final String originPath0 = REROUTE_PATH_PREFIX + originPath;
|
||||||
|
|
||||||
|
proxyRouter.route(originPath0 + "*").handler(ProxyHandler.create(httpProxy));
|
||||||
|
proxyRouter.route(path0 + "*").handler(ctx -> {
|
||||||
|
String realPath = ctx.request().uri();
|
||||||
|
if (realPath.startsWith(path0)) {
|
||||||
// vertx web proxy暂不支持rewrite, 所以这里进行手动替换, 请求地址中的请求path前缀替换为originPath
|
// vertx web proxy暂不支持rewrite, 所以这里进行手动替换, 请求地址中的请求path前缀替换为originPath
|
||||||
String rePath = realPath.replaceAll("^" + path, originPath);
|
String rePath = realPath.replaceAll("^" + path0, originPath0);
|
||||||
ctx.reroute(rePath);
|
ctx.reroute(rePath);
|
||||||
} else {
|
} else {
|
||||||
ctx.next();
|
ctx.next();
|
||||||
@@ -210,54 +250,4 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理websocket
|
|
||||||
*
|
|
||||||
* @param confList sock配置
|
|
||||||
* @param httpClient 客户端
|
|
||||||
* @param proxyRouter 代理路由
|
|
||||||
*/
|
|
||||||
private void handleSock(JsonArray confList, HttpClient httpClient, Router proxyRouter) {
|
|
||||||
// 代理规则
|
|
||||||
confList.stream().map(e -> (JsonObject) e).forEach(conf -> {
|
|
||||||
|
|
||||||
String origin = conf.getString("origin");
|
|
||||||
String path = conf.getString("path");
|
|
||||||
LOGGER.info("websocket proxy: {}, {}",origin,path);
|
|
||||||
|
|
||||||
SockJSHandlerOptions options = new SockJSHandlerOptions()
|
|
||||||
.setHeartbeatInterval(2000)
|
|
||||||
.setRegisterWriteHandler(true);
|
|
||||||
|
|
||||||
SockJSHandler sockJSHandler = SockJSHandler.create(VertxHolder.getVertxInstance(), options);
|
|
||||||
if (!path.startsWith("/")) {
|
|
||||||
path = "/" + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Router route = sockJSHandler.socketHandler(sock -> {
|
|
||||||
sock.handler(buffer -> {
|
|
||||||
Future<WebSocket> webSocketFuture = httpClient.webSocket(8086,"127.0.0.1",sock.uri());
|
|
||||||
webSocketFuture.onSuccess(s -> {
|
|
||||||
System.out.println(buffer.toString());
|
|
||||||
s.write(buffer).onSuccess(v -> {
|
|
||||||
s.handler(w->{
|
|
||||||
System.out.println("--------"+w.toString());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
sock.endHandler(v -> {
|
|
||||||
|
|
||||||
});
|
|
||||||
sock.closeHandler(v -> {
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
proxyRouter.mountSubRouter("/real/serverApi/test", route);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cn.qaiu.vx.core.verticle;
|
|||||||
|
|
||||||
import cn.qaiu.vx.core.handlerfactory.RouterHandlerFactory;
|
import cn.qaiu.vx.core.handlerfactory.RouterHandlerFactory;
|
||||||
import cn.qaiu.vx.core.util.CommonUtil;
|
import cn.qaiu.vx.core.util.CommonUtil;
|
||||||
|
import cn.qaiu.vx.core.util.JacksonConfig;
|
||||||
import cn.qaiu.vx.core.util.SharedDataUtil;
|
import cn.qaiu.vx.core.util.SharedDataUtil;
|
||||||
import io.vertx.core.AbstractVerticle;
|
import io.vertx.core.AbstractVerticle;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
@@ -23,7 +24,6 @@ public class RouterVerticle extends AbstractVerticle {
|
|||||||
|
|
||||||
private static final int port = SharedDataUtil.getValueForServerConfig("port");
|
private static final int port = SharedDataUtil.getValueForServerConfig("port");
|
||||||
private static final Router router = new RouterHandlerFactory(
|
private static final Router router = new RouterHandlerFactory(
|
||||||
SharedDataUtil.getJsonStringForCustomConfig("routerLocations"),
|
|
||||||
SharedDataUtil.getJsonStringForServerConfig("contextPath")).createRouter();
|
SharedDataUtil.getJsonStringForServerConfig("contextPath")).createRouter();
|
||||||
|
|
||||||
private static final JsonObject globalConfig = SharedDataUtil.getJsonConfig("globalConfig");
|
private static final JsonObject globalConfig = SharedDataUtil.getJsonConfig("globalConfig");
|
||||||
@@ -31,6 +31,8 @@ public class RouterVerticle extends AbstractVerticle {
|
|||||||
private HttpServer server;
|
private HttpServer server;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
LOGGER.info(JacksonConfig.class.getSimpleName() + " >> ");
|
||||||
|
JacksonConfig.nothing();
|
||||||
LOGGER.info("To start listening to port {} ......", port);
|
LOGGER.info("To start listening to port {} ......", port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package cn.qaiu.vx.core.verticle;
|
|||||||
import cn.qaiu.vx.core.annotaions.Service;
|
import cn.qaiu.vx.core.annotaions.Service;
|
||||||
import cn.qaiu.vx.core.base.BaseAsyncService;
|
import cn.qaiu.vx.core.base.BaseAsyncService;
|
||||||
import cn.qaiu.vx.core.util.ReflectionUtil;
|
import cn.qaiu.vx.core.util.ReflectionUtil;
|
||||||
import cn.qaiu.vx.core.util.SharedDataUtil;
|
|
||||||
import io.vertx.core.AbstractVerticle;
|
import io.vertx.core.AbstractVerticle;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.serviceproxy.ServiceBinder;
|
import io.vertx.serviceproxy.ServiceBinder;
|
||||||
@@ -27,8 +26,7 @@ public class ServiceVerticle extends AbstractVerticle {
|
|||||||
private static final Set<Class<?>> handlers;
|
private static final Set<Class<?>> handlers;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
String handlerLocations = SharedDataUtil.getJsonStringForCustomConfig("handlerLocations");
|
Reflections reflections = ReflectionUtil.getReflections();
|
||||||
Reflections reflections = ReflectionUtil.getReflections(handlerLocations);
|
|
||||||
handlers = reflections.getTypesAnnotatedWith(Service.class);
|
handlers = reflections.getTypesAnnotatedWith(Service.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package cn.qaiu.vx.core.verticle.conf;
|
||||||
|
|
||||||
|
import io.vertx.codegen.annotations.DataObject;
|
||||||
|
import io.vertx.codegen.json.annotations.JsonGen;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.core.net.ProxyOptions;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@DataObject
|
||||||
|
@JsonGen(publicConverter = false)
|
||||||
|
public class HttpProxyConf {
|
||||||
|
|
||||||
|
public static final String DEFAULT_USERNAME = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
public static final String DEFAULT_PASSWORD = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
public static final Integer DEFAULT_PORT = 6402;
|
||||||
|
|
||||||
|
public static final Integer DEFAULT_TIMEOUT = 15000;
|
||||||
|
|
||||||
|
Integer timeout;
|
||||||
|
|
||||||
|
String username;
|
||||||
|
|
||||||
|
String password;
|
||||||
|
|
||||||
|
Integer port;
|
||||||
|
|
||||||
|
ProxyOptions preProxyOptions;
|
||||||
|
|
||||||
|
public HttpProxyConf() {
|
||||||
|
this.username = DEFAULT_USERNAME;
|
||||||
|
this.password = DEFAULT_PASSWORD;
|
||||||
|
this.timeout = DEFAULT_PORT;
|
||||||
|
this.timeout = DEFAULT_TIMEOUT;
|
||||||
|
this.preProxyOptions = new ProxyOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpProxyConf(JsonObject json) {
|
||||||
|
this();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Integer getTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpProxyConf setTimeout(Integer timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpProxyConf setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpProxyConf setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpProxyConf setPort(Integer port) {
|
||||||
|
this.port = port;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProxyOptions getPreProxyOptions() {
|
||||||
|
return preProxyOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpProxyConf setPreProxyOptions(ProxyOptions preProxyOptions) {
|
||||||
|
this.preProxyOptions = preProxyOptions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
core/src/main/resources/app.properties
Normal file
1
core/src/main/resources/app.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
app.version=${project.version}
|
||||||
310
mvnw
vendored
Normal file
310
mvnw
vendored
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Maven Start Up Batch script
|
||||||
|
#
|
||||||
|
# Required ENV vars:
|
||||||
|
# ------------------
|
||||||
|
# JAVA_HOME - location of a JDK home dir
|
||||||
|
#
|
||||||
|
# Optional ENV vars
|
||||||
|
# -----------------
|
||||||
|
# M2_HOME - location of maven2's installed home dir
|
||||||
|
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||||
|
# e.g. to debug Maven itself, use
|
||||||
|
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||||
|
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||||
|
|
||||||
|
if [ -f /etc/mavenrc ] ; then
|
||||||
|
. /etc/mavenrc
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$HOME/.mavenrc" ] ; then
|
||||||
|
. "$HOME/.mavenrc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OS specific support. $var _must_ be set to either true or false.
|
||||||
|
cygwin=false;
|
||||||
|
darwin=false;
|
||||||
|
mingw=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN*) cygwin=true ;;
|
||||||
|
MINGW*) mingw=true;;
|
||||||
|
Darwin*) darwin=true
|
||||||
|
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||||
|
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||||
|
if [ -z "$JAVA_HOME" ]; then
|
||||||
|
if [ -x "/usr/libexec/java_home" ]; then
|
||||||
|
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||||
|
else
|
||||||
|
export JAVA_HOME="/Library/Java/Home"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ] ; then
|
||||||
|
if [ -r /etc/gentoo-release ] ; then
|
||||||
|
JAVA_HOME=`java-config --jre-home`
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$M2_HOME" ] ; then
|
||||||
|
## resolve links - $0 may be a link to maven's home
|
||||||
|
PRG="$0"
|
||||||
|
|
||||||
|
# need this for relative symlinks
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG="`dirname "$PRG"`/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
saveddir=`pwd`
|
||||||
|
|
||||||
|
M2_HOME=`dirname "$PRG"`/..
|
||||||
|
|
||||||
|
# make it fully qualified
|
||||||
|
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||||
|
|
||||||
|
cd "$saveddir"
|
||||||
|
# echo Using m2 at $M2_HOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||||
|
if $cygwin ; then
|
||||||
|
[ -n "$M2_HOME" ] &&
|
||||||
|
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||||
|
[ -n "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||||
|
[ -n "$CLASSPATH" ] &&
|
||||||
|
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||||
|
if $mingw ; then
|
||||||
|
[ -n "$M2_HOME" ] &&
|
||||||
|
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||||
|
[ -n "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ]; then
|
||||||
|
javaExecutable="`which javac`"
|
||||||
|
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||||
|
# readlink(1) is not available as standard on Solaris 10.
|
||||||
|
readLink=`which readlink`
|
||||||
|
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||||
|
if $darwin ; then
|
||||||
|
javaHome="`dirname \"$javaExecutable\"`"
|
||||||
|
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||||
|
else
|
||||||
|
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||||
|
fi
|
||||||
|
javaHome="`dirname \"$javaExecutable\"`"
|
||||||
|
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||||
|
JAVA_HOME="$javaHome"
|
||||||
|
export JAVA_HOME
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVACMD" ] ; then
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="`which java`"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||||
|
echo " We cannot execute $JAVACMD" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ] ; then
|
||||||
|
echo "Warning: JAVA_HOME environment variable is not set."
|
||||||
|
fi
|
||||||
|
|
||||||
|
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||||
|
|
||||||
|
# traverses directory structure from process work directory to filesystem root
|
||||||
|
# first directory with .mvn subdirectory is considered project base directory
|
||||||
|
find_maven_basedir() {
|
||||||
|
|
||||||
|
if [ -z "$1" ]
|
||||||
|
then
|
||||||
|
echo "Path not specified to find_maven_basedir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
basedir="$1"
|
||||||
|
wdir="$1"
|
||||||
|
while [ "$wdir" != '/' ] ; do
|
||||||
|
if [ -d "$wdir"/.mvn ] ; then
|
||||||
|
basedir=$wdir
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||||
|
if [ -d "${wdir}" ]; then
|
||||||
|
wdir=`cd "$wdir/.."; pwd`
|
||||||
|
fi
|
||||||
|
# end of workaround
|
||||||
|
done
|
||||||
|
echo "${basedir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# concatenates all lines of a file
|
||||||
|
concat_lines() {
|
||||||
|
if [ -f "$1" ]; then
|
||||||
|
echo "$(tr -s '\n' ' ' < "$1")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||||
|
if [ -z "$BASE_DIR" ]; then
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||||
|
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||||
|
##########################################################################################
|
||||||
|
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||||
|
fi
|
||||||
|
if [ -n "$MVNW_REPOURL" ]; then
|
||||||
|
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||||
|
else
|
||||||
|
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||||
|
fi
|
||||||
|
while IFS="=" read key value; do
|
||||||
|
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||||
|
esac
|
||||||
|
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Downloading from: $jarUrl"
|
||||||
|
fi
|
||||||
|
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||||
|
if $cygwin; then
|
||||||
|
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v wget > /dev/null; then
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Found wget ... using wget"
|
||||||
|
fi
|
||||||
|
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||||
|
wget "$jarUrl" -O "$wrapperJarPath"
|
||||||
|
else
|
||||||
|
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
|
||||||
|
fi
|
||||||
|
elif command -v curl > /dev/null; then
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Found curl ... using curl"
|
||||||
|
fi
|
||||||
|
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||||
|
curl -o "$wrapperJarPath" "$jarUrl" -f
|
||||||
|
else
|
||||||
|
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Falling back to using Java to download"
|
||||||
|
fi
|
||||||
|
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||||
|
# For Cygwin, switch paths to Windows format before running javac
|
||||||
|
if $cygwin; then
|
||||||
|
javaClass=`cygpath --path --windows "$javaClass"`
|
||||||
|
fi
|
||||||
|
if [ -e "$javaClass" ]; then
|
||||||
|
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||||
|
fi
|
||||||
|
# Compiling the Java class
|
||||||
|
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||||
|
fi
|
||||||
|
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||||
|
# Running the downloader
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo " - Running MavenWrapperDownloader.java ..."
|
||||||
|
fi
|
||||||
|
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
##########################################################################################
|
||||||
|
# End of extension
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo $MAVEN_PROJECTBASEDIR
|
||||||
|
fi
|
||||||
|
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin; then
|
||||||
|
[ -n "$M2_HOME" ] &&
|
||||||
|
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||||
|
[ -n "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||||
|
[ -n "$CLASSPATH" ] &&
|
||||||
|
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||||
|
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||||
|
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Provide a "standardized" way to retrieve the CLI args that will
|
||||||
|
# work with both Windows and non-Windows executions.
|
||||||
|
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
|
||||||
|
export MAVEN_CMD_LINE_ARGS
|
||||||
|
|
||||||
|
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||||
|
|
||||||
|
exec "$JAVACMD" \
|
||||||
|
$MAVEN_OPTS \
|
||||||
|
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||||
|
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||||
|
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
||||||
182
mvnw.cmd
vendored
Normal file
182
mvnw.cmd
vendored
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
@REM or more contributor license agreements. See the NOTICE file
|
||||||
|
@REM distributed with this work for additional information
|
||||||
|
@REM regarding copyright ownership. The ASF licenses this file
|
||||||
|
@REM to you under the Apache License, Version 2.0 (the
|
||||||
|
@REM "License"); you may not use this file except in compliance
|
||||||
|
@REM with the License. You may obtain a copy of the License at
|
||||||
|
@REM
|
||||||
|
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@REM
|
||||||
|
@REM Unless required by applicable law or agreed to in writing,
|
||||||
|
@REM software distributed under the License is distributed on an
|
||||||
|
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
@REM KIND, either express or implied. See the License for the
|
||||||
|
@REM specific language governing permissions and limitations
|
||||||
|
@REM under the License.
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Maven Start Up Batch script
|
||||||
|
@REM
|
||||||
|
@REM Required ENV vars:
|
||||||
|
@REM JAVA_HOME - location of a JDK home dir
|
||||||
|
@REM
|
||||||
|
@REM Optional ENV vars
|
||||||
|
@REM M2_HOME - location of maven2's installed home dir
|
||||||
|
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||||
|
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||||
|
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||||
|
@REM e.g. to debug Maven itself, use
|
||||||
|
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||||
|
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||||
|
@echo off
|
||||||
|
@REM set title of command window
|
||||||
|
title %0
|
||||||
|
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||||
|
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||||
|
|
||||||
|
@REM set %HOME% to equivalent of $HOME
|
||||||
|
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||||
|
|
||||||
|
@REM Execute a user defined script before this one
|
||||||
|
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||||
|
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||||
|
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||||
|
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||||
|
:skipRcPre
|
||||||
|
|
||||||
|
@setlocal
|
||||||
|
|
||||||
|
set ERROR_CODE=0
|
||||||
|
|
||||||
|
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||||
|
@setlocal
|
||||||
|
|
||||||
|
@REM ==== START VALIDATION ====
|
||||||
|
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Error: JAVA_HOME not found in your environment. >&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||||
|
echo location of your Java installation. >&2
|
||||||
|
echo.
|
||||||
|
goto error
|
||||||
|
|
||||||
|
:OkJHome
|
||||||
|
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||||
|
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||||
|
echo location of your Java installation. >&2
|
||||||
|
echo.
|
||||||
|
goto error
|
||||||
|
|
||||||
|
@REM ==== END VALIDATION ====
|
||||||
|
|
||||||
|
:init
|
||||||
|
|
||||||
|
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||||
|
@REM Fallback to current working directory if not found.
|
||||||
|
|
||||||
|
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||||
|
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||||
|
|
||||||
|
set EXEC_DIR=%CD%
|
||||||
|
set WDIR=%EXEC_DIR%
|
||||||
|
:findBaseDir
|
||||||
|
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||||
|
cd ..
|
||||||
|
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||||
|
set WDIR=%CD%
|
||||||
|
goto findBaseDir
|
||||||
|
|
||||||
|
:baseDirFound
|
||||||
|
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||||
|
cd "%EXEC_DIR%"
|
||||||
|
goto endDetectBaseDir
|
||||||
|
|
||||||
|
:baseDirNotFound
|
||||||
|
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||||
|
cd "%EXEC_DIR%"
|
||||||
|
|
||||||
|
:endDetectBaseDir
|
||||||
|
|
||||||
|
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||||
|
|
||||||
|
@setlocal EnableExtensions EnableDelayedExpansion
|
||||||
|
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||||
|
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||||
|
|
||||||
|
:endReadAdditionalConfig
|
||||||
|
|
||||||
|
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||||
|
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||||
|
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||||
|
|
||||||
|
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||||
|
|
||||||
|
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||||
|
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||||
|
)
|
||||||
|
|
||||||
|
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||||
|
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||||
|
if exist %WRAPPER_JAR% (
|
||||||
|
if "%MVNW_VERBOSE%" == "true" (
|
||||||
|
echo Found %WRAPPER_JAR%
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
if not "%MVNW_REPOURL%" == "" (
|
||||||
|
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||||
|
)
|
||||||
|
if "%MVNW_VERBOSE%" == "true" (
|
||||||
|
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||||
|
echo Downloading from: %DOWNLOAD_URL%
|
||||||
|
)
|
||||||
|
|
||||||
|
powershell -Command "&{"^
|
||||||
|
"$webclient = new-object System.Net.WebClient;"^
|
||||||
|
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||||
|
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||||
|
"}"^
|
||||||
|
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
|
||||||
|
"}"
|
||||||
|
if "%MVNW_VERBOSE%" == "true" (
|
||||||
|
echo Finished downloading %WRAPPER_JAR%
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@REM End of extension
|
||||||
|
|
||||||
|
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||||
|
@REM work with both Windows and non-Windows executions.
|
||||||
|
set MAVEN_CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||||
|
if ERRORLEVEL 1 goto error
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:error
|
||||||
|
set ERROR_CODE=1
|
||||||
|
|
||||||
|
:end
|
||||||
|
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||||
|
|
||||||
|
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||||
|
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||||
|
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||||
|
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||||
|
:skipRcPost
|
||||||
|
|
||||||
|
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||||
|
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||||
|
|
||||||
|
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||||
|
|
||||||
|
exit /B %ERROR_CODE%
|
||||||
66
note.txt
Normal file
66
note.txt
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
直链缓存设计
|
||||||
|
每个网盘对应的标准分享URL如下
|
||||||
|
|
||||||
|
蓝奏云 (lz) https://lanzoux.com/{shareKey}
|
||||||
|
蓝奏云优享 (iz) https://www.ilanzou.com/s/{shareKey}
|
||||||
|
奶牛快传 (cow) https://cowtransfer.com/s/{shareKey}
|
||||||
|
移动云云空间 (ec) https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={shareKey}&isShare=1
|
||||||
|
小飞机网盘 (fj) https://share.feijipan.com/s/{shareKey}
|
||||||
|
亿方云 (fc) https://v2.fangcloud.com/sharing/{shareKey}
|
||||||
|
123云盘 (ye) https://www.123pan.com/s/{shareKey}.html
|
||||||
|
文叔叔 (ws) https://f.ws59.cn/f/{shareKey}
|
||||||
|
联想乐云 (le) https://lecloud.lenovo.com/share/{shareKey}
|
||||||
|
|
||||||
|
私有化网盘需要自己的域名也就是origin地址.
|
||||||
|
Cloudreve自建网盘 (ce) {origin}/s/{shareKey}
|
||||||
|
|
||||||
|
分享URL -> 类型+key
|
||||||
|
类型+key -> 标准分享URL
|
||||||
|
|
||||||
|
缓存key -> 下载URL
|
||||||
|
分享链接 -> add 网盘类型 pwd origin(私有化) -> 直链
|
||||||
|
|
||||||
|
|
||||||
|
https://f.ws59.cn/f/e3peohu6192
|
||||||
|
|
||||||
|
开源版 TODO
|
||||||
|
1. 缓存优化, 配置自动重载
|
||||||
|
|
||||||
|
|
||||||
|
专属版 功能设计
|
||||||
|
1. 支持绑定域名, 后台管理-账号管理, token管理, 账号解析次数限制
|
||||||
|
2. 流量统计, 文件分享信息, 目录解析, 文件云下载
|
||||||
|
3. IP代理池
|
||||||
|
|
||||||
|
网页跳转 防盗链
|
||||||
|
可禁用parser接口
|
||||||
|
|
||||||
|
标志短链 鉴权后 生成混淆链接
|
||||||
|
|
||||||
|
|
||||||
|
短链算法:
|
||||||
|
1. 基于Hash映射 hash(type:key:pwd) = h/xxxxx
|
||||||
|
|
||||||
|
鉴权实现:
|
||||||
|
auth-jdbc
|
||||||
|
|
||||||
|
// 基于标准SQL语法
|
||||||
|
|
||||||
|
支持H2, MySQL
|
||||||
|
|
||||||
|
用户:
|
||||||
|
jwt鉴权用户
|
||||||
|
|
||||||
|
角色:
|
||||||
|
超级管理员
|
||||||
|
注册用户
|
||||||
|
|
||||||
|
定义操作(权限):
|
||||||
|
用户的创建/删除/查询/修改, 生成短链/删除短链/修改解析次数和有效期/查询短链信息(
|
||||||
|
文件信息: 文件/文件夹, 文件数量, 文件大小, 文件类型; 链接信息: 解析次数, 缓存次数等)
|
||||||
|
|
||||||
|
微服务设计:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
144
parser/pom.xml
Normal file
144
parser/pom.xml
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>netdisk-fast-download</artifactId>
|
||||||
|
<groupId>cn.qaiu</groupId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>parser</artifactId>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>${project.groupId}:${project.artifactId}</name>
|
||||||
|
<description>NFD parser</description>
|
||||||
|
<url>https://qaiu.top</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!--logback日志实现-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>${logback.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vertx</groupId>
|
||||||
|
<artifactId>vertx-web-client</artifactId>
|
||||||
|
<version>${vertx.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>${commons-lang3.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.openjdk.nashorn/nashorn-core -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjdk.nashorn</groupId>
|
||||||
|
<artifactId>nashorn-core</artifactId>
|
||||||
|
<version>15.4</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>MIT License</name>
|
||||||
|
<url>https://opensource.org/license/mit</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<name>qaiu</name>
|
||||||
|
<email>qaiu00@gmail.com</email>
|
||||||
|
<organization>https://qaiu.top</organization>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
<scm>
|
||||||
|
<connection>scm:git@github.com:qaiu/netdisk-fast-download.git</connection>
|
||||||
|
<developerConnection>scm:git@github.com:qaiu/netdisk-fast-download.git</developerConnection>
|
||||||
|
<url>git@github.com:qaiu/netdisk-fast-download.git</url>
|
||||||
|
</scm>
|
||||||
|
<distributionManagement>
|
||||||
|
<snapshotRepository>
|
||||||
|
<id>sonatype</id>
|
||||||
|
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
|
||||||
|
</snapshotRepository>
|
||||||
|
<repository>
|
||||||
|
<id>sonatype</id>
|
||||||
|
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
||||||
|
</repository>
|
||||||
|
</distributionManagement>
|
||||||
|
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>3.3.1</version>
|
||||||
|
<configuration>
|
||||||
|
<attach>true</attach>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-site-plugin</artifactId>
|
||||||
|
<version>3.7.1</version>
|
||||||
|
</plugin>
|
||||||
|
<!-- Gpg Signature -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
|
<version>1.6</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>sign-artifacts</id>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>sign</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.sonatype.central</groupId>
|
||||||
|
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||||
|
<version>0.6.0</version>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
<configuration>
|
||||||
|
<publishingServerId>central</publishingServerId>
|
||||||
|
<autoPublish>true</autoPublish>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
24
parser/src/main/java/cn/qaiu/WebClientVertxInit.java
Normal file
24
parser/src/main/java/cn/qaiu/WebClientVertxInit.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package cn.qaiu;
|
||||||
|
|
||||||
|
import io.vertx.core.Vertx;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class WebClientVertxInit {
|
||||||
|
private Vertx vertx = null;
|
||||||
|
private static final WebClientVertxInit INSTANCE = new WebClientVertxInit();
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WebClientVertxInit.class);
|
||||||
|
|
||||||
|
public static void init(Vertx vx) {
|
||||||
|
INSTANCE.vertx = vx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vertx get() {
|
||||||
|
if (INSTANCE.vertx == null) {
|
||||||
|
log.info("getVertx: Vertx实例不存在, 创建Vertx实例.");
|
||||||
|
INSTANCE.vertx = Vertx.vertx();
|
||||||
|
}
|
||||||
|
return INSTANCE.vertx;
|
||||||
|
}
|
||||||
|
}
|
||||||
146
parser/src/main/java/cn/qaiu/entity/FileInfo.java
Normal file
146
parser/src/main/java/cn/qaiu/entity/FileInfo.java
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package cn.qaiu.entity;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class FileInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件名
|
||||||
|
*/
|
||||||
|
String fileName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件ID
|
||||||
|
*/
|
||||||
|
String fileId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件大小(byte)
|
||||||
|
*/
|
||||||
|
Long size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MIME类型
|
||||||
|
*/
|
||||||
|
String fileMIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件路径
|
||||||
|
*/
|
||||||
|
String filePath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建(上传)时间 yyyy-MM-dd HH:mm:ss格式
|
||||||
|
*/
|
||||||
|
String createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
String createBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件描述
|
||||||
|
*/
|
||||||
|
String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载次数
|
||||||
|
*/
|
||||||
|
Integer downloadCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展参数
|
||||||
|
*/
|
||||||
|
Map<String, Object> extParameters;
|
||||||
|
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setFileName(String fileName) {
|
||||||
|
this.fileName = fileName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileId() {
|
||||||
|
return fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setFileId(String fileId) {
|
||||||
|
this.fileId = fileId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setSize(Long size) {
|
||||||
|
this.size = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileMIME() {
|
||||||
|
return fileMIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setFileMIME(String fileMIME) {
|
||||||
|
this.fileMIME = fileMIME;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilePath() {
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setFilePath(String filePath) {
|
||||||
|
this.filePath = filePath;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreateTime() {
|
||||||
|
return createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setCreateTime(String createTime) {
|
||||||
|
this.createTime = createTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreateBy() {
|
||||||
|
return createBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setCreateBy(String createBy) {
|
||||||
|
this.createBy = createBy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getDownloadCount() {
|
||||||
|
return downloadCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setDownloadCount(Integer downloadCount) {
|
||||||
|
this.downloadCount = downloadCount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getExtParameters() {
|
||||||
|
return extParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setExtParameters(Map<String, Object> extParameters) {
|
||||||
|
this.extParameters = extParameters;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
159
parser/src/main/java/cn/qaiu/entity/ShareLinkInfo.java
Normal file
159
parser/src/main/java/cn/qaiu/entity/ShareLinkInfo.java
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package cn.qaiu.entity;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ShareLinkInfo {
|
||||||
|
|
||||||
|
private String shareKey; // 分享键
|
||||||
|
|
||||||
|
private String panName; // 网盘名称
|
||||||
|
private String type; // 分享类型
|
||||||
|
private String sharePassword; // 分享密码(如果存在)
|
||||||
|
private String shareUrl; // 原始分享链接
|
||||||
|
private String standardUrl; // 规范化的标准链接
|
||||||
|
|
||||||
|
private Map<String, Object> otherParam; // 其他参数
|
||||||
|
|
||||||
|
private ShareLinkInfo(Builder builder) {
|
||||||
|
this.shareKey = builder.shareKey;
|
||||||
|
this.panName = builder.panName;
|
||||||
|
this.type = builder.type;
|
||||||
|
this.sharePassword = builder.sharePassword;
|
||||||
|
this.shareUrl = builder.shareUrl;
|
||||||
|
this.standardUrl = builder.standardUrl;
|
||||||
|
this.otherParam = builder.otherParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter和Setter方法
|
||||||
|
|
||||||
|
public String getShareKey() {
|
||||||
|
return shareKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPanName() {
|
||||||
|
return panName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShareKey(String shareKey) {
|
||||||
|
this.shareKey = shareKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPanName(String panName) {
|
||||||
|
this.panName = panName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSharePassword() {
|
||||||
|
return sharePassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSharePassword(String sharePassword) {
|
||||||
|
this.sharePassword = sharePassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShareUrl() {
|
||||||
|
return shareUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShareUrl(String shareUrl) {
|
||||||
|
this.shareUrl = shareUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStandardUrl() {
|
||||||
|
return standardUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStandardUrl(String standardUrl) {
|
||||||
|
this.standardUrl = standardUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCacheKey() {
|
||||||
|
// 将type和shareKey组合成一个字符串作为缓存key
|
||||||
|
return type + ":" + shareKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShareLinkInfo setOtherParam(Map<String, Object> otherParam) {
|
||||||
|
this.otherParam = otherParam;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getOtherParam() {
|
||||||
|
return otherParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态方法创建建造者对象
|
||||||
|
public static ShareLinkInfo.Builder newBuilder() {
|
||||||
|
return new ShareLinkInfo.Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建造者类
|
||||||
|
public static class Builder {
|
||||||
|
public String panName; // 分享网盘名称
|
||||||
|
private String shareKey; // 分享键
|
||||||
|
private String type; // 分享类型 (网盘模板枚举的小写)
|
||||||
|
private String sharePassword = ""; // 分享密码(如果存在)
|
||||||
|
private String shareUrl; // 原始分享链接
|
||||||
|
private String standardUrl; // 规范化的标准链接
|
||||||
|
private Map<String, Object> otherParam = new HashMap<>(); // 其他参数
|
||||||
|
|
||||||
|
public Builder shareKey(String shareKey) {
|
||||||
|
this.shareKey = shareKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder panName(String panName) {
|
||||||
|
this.panName = panName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder type(String type) {
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder sharePassword(String sharePassword) {
|
||||||
|
this.sharePassword = sharePassword;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder shareUrl(String shareUrl) {
|
||||||
|
this.shareUrl = shareUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder standardUrl(String standardUrl) {
|
||||||
|
this.standardUrl = standardUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder otherParam(Map<String, Object> otherParam) {
|
||||||
|
this.otherParam = otherParam;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShareLinkInfo build() {
|
||||||
|
return new ShareLinkInfo(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ShareLinkInfo{" +
|
||||||
|
"shareKey='" + shareKey + '\'' +
|
||||||
|
", panName='" + panName + '\'' +
|
||||||
|
", type='" + type + '\'' +
|
||||||
|
", sharePassword='" + sharePassword + '\'' +
|
||||||
|
", shareUrl='" + shareUrl + '\'' +
|
||||||
|
", standardUrl='" + standardUrl + '\'' +
|
||||||
|
", otherParam='" + otherParam + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
11
parser/src/main/java/cn/qaiu/parser/IPanTool.java
Normal file
11
parser/src/main/java/cn/qaiu/parser/IPanTool.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package cn.qaiu.parser;//package cn.qaiu.lz.common.parser;
|
||||||
|
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
|
||||||
|
public interface IPanTool {
|
||||||
|
Future<String> parse();
|
||||||
|
|
||||||
|
default String parseSync() {
|
||||||
|
return parse().toCompletionStage().toCompletableFuture().join();
|
||||||
|
}
|
||||||
|
}
|
||||||
197
parser/src/main/java/cn/qaiu/parser/PanBase.java
Normal file
197
parser/src/main/java/cn/qaiu/parser/PanBase.java
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
package cn.qaiu.parser;
|
||||||
|
|
||||||
|
import cn.qaiu.WebClientVertxInit;
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.Handler;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
|
import io.vertx.core.json.DecodeException;
|
||||||
|
import io.vertx.core.json.Json;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.core.net.ProxyOptions;
|
||||||
|
import io.vertx.core.net.ProxyType;
|
||||||
|
import io.vertx.core.net.impl.VertxHandler;
|
||||||
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
import io.vertx.ext.web.client.WebClientOptions;
|
||||||
|
import io.vertx.ext.web.client.WebClientSession;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析器抽象类包含promise, HTTP Client, 默认失败方法等;
|
||||||
|
* 新增网盘解析器需要继承该类. <br>
|
||||||
|
* <h2>实现类命名规则: </h2>
|
||||||
|
* <p>{网盘标识}Tool, 网盘标识不超过5个字符, 可以取网盘名称首字母缩写或拼音首字母, <br>
|
||||||
|
* 音乐类型的解析以M开头, 例如网易云音乐Mne</p>
|
||||||
|
*/
|
||||||
|
public abstract class PanBase implements IPanTool {
|
||||||
|
protected Logger log = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
protected Promise<String> promise = Promise.promise();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http client
|
||||||
|
*/
|
||||||
|
protected WebClient client = WebClient.create(WebClientVertxInit.get(),
|
||||||
|
new WebClientOptions().setUserAgentEnabled(false));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http client session (会话管理, 带cookie请求)
|
||||||
|
*/
|
||||||
|
protected WebClientSession clientSession = WebClientSession.create(client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http client 不自动跳转
|
||||||
|
*/
|
||||||
|
protected WebClient clientNoRedirects = WebClient.create(WebClientVertxInit.get(),
|
||||||
|
new WebClientOptions().setFollowRedirects(false));
|
||||||
|
|
||||||
|
protected ShareLinkInfo shareLinkInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子类重写此构造方法不需要添加额外逻辑
|
||||||
|
* 如:
|
||||||
|
* <blockquote><pre>
|
||||||
|
* public XxTool(String key, String pwd) {
|
||||||
|
* super(key, pwd);
|
||||||
|
* }
|
||||||
|
* </pre></blockquote>
|
||||||
|
*
|
||||||
|
* @param shareLinkInfo 分享链接信息
|
||||||
|
*/
|
||||||
|
public PanBase(ShareLinkInfo shareLinkInfo) {
|
||||||
|
this.shareLinkInfo = shareLinkInfo;
|
||||||
|
if (shareLinkInfo.getOtherParam().containsKey("proxy")) {
|
||||||
|
JsonObject proxy = (JsonObject) shareLinkInfo.getOtherParam().get("proxy");
|
||||||
|
ProxyOptions proxyOptions = new ProxyOptions()
|
||||||
|
.setType(ProxyType.valueOf(proxy.getString("type").toUpperCase()))
|
||||||
|
.setHost(proxy.getString("host"))
|
||||||
|
.setPort(proxy.getInteger("port"));
|
||||||
|
if (StringUtils.isNotEmpty(proxy.getString("username"))) {
|
||||||
|
proxyOptions.setUsername(proxy.getString("username"));
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(proxy.getString("password"))) {
|
||||||
|
proxyOptions.setUsername(proxy.getString("password"));
|
||||||
|
}
|
||||||
|
this.client = WebClient.create(WebClientVertxInit.get(),
|
||||||
|
new WebClientOptions()
|
||||||
|
.setUserAgentEnabled(false)
|
||||||
|
.setProxyOptions(proxyOptions));
|
||||||
|
|
||||||
|
this.clientSession = WebClientSession.create(client);
|
||||||
|
this.clientNoRedirects = WebClient.create(WebClientVertxInit.get(),
|
||||||
|
new WebClientOptions().setFollowRedirects(false)
|
||||||
|
.setUserAgentEnabled(false)
|
||||||
|
.setProxyOptions(proxyOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PanBase() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败时生成异常消息
|
||||||
|
*
|
||||||
|
* @param t 异常实例
|
||||||
|
* @param errorMsg 提示消息
|
||||||
|
* @param args log参数变量
|
||||||
|
*/
|
||||||
|
protected void fail(Throwable t, String errorMsg, Object... args) {
|
||||||
|
try {
|
||||||
|
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
|
||||||
|
log.error("解析异常: " + s, t.fillInStackTrace());
|
||||||
|
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": 解析异常: " + s + " -> " + t);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("ErrorMsg format fail. The parameter has been discarded", e);
|
||||||
|
log.error("解析异常: " + errorMsg, t.fillInStackTrace());
|
||||||
|
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": 解析异常: " + errorMsg + " -> " + t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败时生成异常消息
|
||||||
|
*
|
||||||
|
* @param errorMsg 提示消息
|
||||||
|
* @param args log参数变量
|
||||||
|
*/
|
||||||
|
protected void fail(String errorMsg, Object... args) {
|
||||||
|
try {
|
||||||
|
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
|
||||||
|
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 解析异常: " + s);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("ErrorMsg format fail. The parameter has been discarded", e);
|
||||||
|
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 解析异常: " + errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void fail() {
|
||||||
|
fail("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成失败Future的处理器
|
||||||
|
*
|
||||||
|
* @param errorMsg 提示消息
|
||||||
|
* @return Handler
|
||||||
|
*/
|
||||||
|
protected Handler<Throwable> handleFail(String errorMsg) {
|
||||||
|
return t -> fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Handler<Throwable> handleFail() {
|
||||||
|
return handleFail("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bodyAsJsonObject的封装, 会自动处理异常
|
||||||
|
* @param res HttpResponse
|
||||||
|
* @return JsonObject
|
||||||
|
*/
|
||||||
|
protected JsonObject asJson(HttpResponse<?> res) {
|
||||||
|
try {
|
||||||
|
return res.bodyAsJsonObject();
|
||||||
|
} catch (DecodeException e) {
|
||||||
|
fail("解析失败: json格式异常: {}", res.bodyAsString());
|
||||||
|
throw new RuntimeException("解析失败: json格式异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void complete(String url) {
|
||||||
|
promise.complete(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Future<String> future() {
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用下一个解析器, 通用域名解析
|
||||||
|
*/
|
||||||
|
protected void nextParser() {
|
||||||
|
Iterator<PanDomainTemplate> iterator = Arrays.asList(PanDomainTemplate.values()).iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
if (iterator.next().name().equalsIgnoreCase(shareLinkInfo.getType())) {
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
PanDomainTemplate next = iterator.next();
|
||||||
|
log.debug("规则不匹配, 处理解析器转发: {} -> {}", shareLinkInfo.getPanName(), next.getDisplayName());
|
||||||
|
ParserCreate.fromType(next.name())
|
||||||
|
.fromAnyShareUrl(shareLinkInfo.getShareUrl())
|
||||||
|
.createTool()
|
||||||
|
.parse()
|
||||||
|
.onComplete(promise);
|
||||||
|
} else {
|
||||||
|
fail("error: 没有下一个解析处理器");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
262
parser/src/main/java/cn/qaiu/parser/PanDomainTemplate.java
Normal file
262
parser/src/main/java/cn/qaiu/parser/PanDomainTemplate.java
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
package cn.qaiu.parser;
|
||||||
|
|
||||||
|
import cn.qaiu.parser.impl.*;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static java.util.regex.Pattern.compile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 枚举类 PanDomainTemplate 定义了不同网盘服务的模板信息,包括:
|
||||||
|
* <ul>
|
||||||
|
* <li>displayName: 网盘服务的显示名称,用于用户界面展示。</li>
|
||||||
|
* <li>regexPattern: 用于匹配和解析分享链接的正则表达式。</li>
|
||||||
|
* <li>standardUrlTemplate: 网盘服务的标准URL模板,用于规范化分享链接。</li>
|
||||||
|
* <li>toolClass: 网盘解析工具实现类。</li>
|
||||||
|
* </ul>
|
||||||
|
* 请注意:增添网盘时,保证正则表达式最后一个捕捉组能匹配到分享key
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
* at 2023/6/13 4:26
|
||||||
|
*/
|
||||||
|
public enum PanDomainTemplate {
|
||||||
|
|
||||||
|
|
||||||
|
// 网盘定义
|
||||||
|
LZ("蓝奏云",
|
||||||
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?lanzou[a-z]\\.com/(.+/)?(?<KEY>.+)"),
|
||||||
|
"https://lanzoux.com/{shareKey}",
|
||||||
|
LzTool.class),
|
||||||
|
|
||||||
|
// https://www.feijix.com/s/
|
||||||
|
// https://share.feijipan.com/s/
|
||||||
|
FJ("小飞机网盘",
|
||||||
|
compile("https://(share\\.feijipan\\.com|www\\.feijix\\.com)/s/(?<KEY>.+)"),
|
||||||
|
"https://www.feijix.com/s/{shareKey}",
|
||||||
|
FjTool.class),
|
||||||
|
|
||||||
|
// https://lecloud.lenovo.com/share/
|
||||||
|
LE("联想乐云",
|
||||||
|
compile("https://lecloud?\\.lenovo\\.com/share/(?<KEY>.+)"),
|
||||||
|
"https://lecloud.lenovo.com/share/{shareKey}",
|
||||||
|
LeTool.class),
|
||||||
|
|
||||||
|
// https://v2.fangcloud.com/s/
|
||||||
|
FC("亿方云",
|
||||||
|
compile("https://v2\\.fangcloud\\.(com|cn)/(s|sharing)/(?<KEY>.+)"),
|
||||||
|
"https://v2.fangcloud.com/s/{shareKey}",
|
||||||
|
"https://www.fangcloud.com/",
|
||||||
|
FcTool.class),
|
||||||
|
// https://www.ilanzou.com/s/
|
||||||
|
IZ("蓝奏云优享",
|
||||||
|
compile("https://www\\.ilanzou\\.com/s/(?<KEY>.+)"),
|
||||||
|
"https://www.ilanzou.com/s/{shareKey}",
|
||||||
|
IzTool.class),
|
||||||
|
// https://wx.mail.qq.com/ftn/download?
|
||||||
|
QQ("QQ邮箱中转站",
|
||||||
|
compile("https://i?wx\\.mail\\.qq\\.com/ftn/download\\?(?<KEY>.+)"),
|
||||||
|
"https://iwx.mail.qq.com/ftn/download/{shareKey}",
|
||||||
|
"https://mail.qq.com",
|
||||||
|
QQTool.class),
|
||||||
|
// https://f.ws59.cn/f/或者https://www.wenshushu.cn/f/
|
||||||
|
WS("文叔叔",
|
||||||
|
compile("https://(f\\.ws(\\d{2})\\.cn|www\\.wenshushu\\.cn)/f/(?<KEY>.+)"),
|
||||||
|
"https://www.wenshushu.cn/f/{shareKey}",
|
||||||
|
WsTool.class),
|
||||||
|
// https://www.123pan.com/s/
|
||||||
|
YE("123网盘",
|
||||||
|
compile("https://www\\.(123pan|123865|123684)\\.com/s/(?<KEY>.+)(.html)?"),
|
||||||
|
"https://www.123pan.com/s/{shareKey}",
|
||||||
|
YeTool.class),
|
||||||
|
// https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={code}&isShare=1
|
||||||
|
EC("移动云空间",
|
||||||
|
compile("https://www\\.ecpan\\.cn/web(/%23|/#)?/yunpanProxy\\?path=.*&data=" +
|
||||||
|
"(?<KEY>[^&]+)&isShare=1"),
|
||||||
|
"https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={shareKey}&isShare=1",
|
||||||
|
EcTool.class),
|
||||||
|
// https://cowtransfer.com/s/
|
||||||
|
COW("奶牛快传",
|
||||||
|
compile("https://(.*)cowtransfer\\.com/s/(?<KEY>.+)"),
|
||||||
|
"https://cowtransfer.com/s/{shareKey}",
|
||||||
|
CowTool.class),
|
||||||
|
CT("城通网盘",
|
||||||
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?(ctfile|545c|u062|ghpym|474b)\\.com/file/(?<KEY>.+)"),
|
||||||
|
"https://474b.com/file/{shareKey}",
|
||||||
|
CtTool.class),
|
||||||
|
// https://xxx.118pan.com/bxxx
|
||||||
|
P118("118网盘",
|
||||||
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?118pan\\.com/b(?<KEY>.+)"),
|
||||||
|
"https://qaiu.118pan.com/b{shareKey}",
|
||||||
|
P118Tool.class),
|
||||||
|
// https://www.vyuyun.com/s/QMa6ie?password=I4KG7H
|
||||||
|
PVYY("微雨云存储",
|
||||||
|
compile("https://www\\.vyuyun\\.com/s/(?<KEY>[a-zA-Z\\d-]+)(\\?password=.*)?"),
|
||||||
|
"https://www.vyuyun.com/s/{shareKey}",
|
||||||
|
PvyyTool.class),
|
||||||
|
// https://1drv.ms/w/s!Alg0feQmCv2rnRFd60DQOmMa-Oh_?e=buaRtp
|
||||||
|
POD("OneDrive",
|
||||||
|
compile("https://1drv\\.ms/[uw]/s!(?<KEY>.+)"),
|
||||||
|
"https://onedrive.live.com/",
|
||||||
|
PodTool.class),
|
||||||
|
// 404网盘 https://drive.google.com/file/d/xxx/view?usp=sharing
|
||||||
|
PGD("GoogleDrive",
|
||||||
|
compile("https://drive\\.google\\.com/file/d/(?<KEY>.+)/view(\\?usp=(sharing|drive_link))?"),
|
||||||
|
"https://drive.google.com/file/d/{shareKey}/view?usp=sharing",
|
||||||
|
PgdTool.class),
|
||||||
|
// iCloud https://www.icloud.com.cn/iclouddrive/xxx#fonts
|
||||||
|
PIC("iCloud",
|
||||||
|
compile("https://www\\.icloud\\.com\\.cn/iclouddrive/(?<KEY>[a-z_A-Z\\d-=]+)(#(.+))?"),
|
||||||
|
"https://www.icloud.com.cn/iclouddrive/{shareKey}",
|
||||||
|
PicTool.class),
|
||||||
|
// https://www.dropbox.com/scl/fi/cwnbms1yn8u6rcatzyta7/emqx-5.0.26-el7-amd64.tar.gz?rlkey=3uoi4bxz5mv93jmlaws0nlol1&e=8&st=fe0lclc2&dl=0
|
||||||
|
PDB("dropbox",
|
||||||
|
compile("https://www.dropbox.com/scl/fi/(?<KEY>\\w+)/.+?rlkey=(?<PWD>\\w+).*"),
|
||||||
|
"https://www.dropbox.com/scl/fi/{shareKey}/?rlkey={pwd}&dl=0",
|
||||||
|
PdbTool.class),
|
||||||
|
|
||||||
|
// =====================音乐类解析 分享链接标志->MxxS (单歌曲/普通音质)==========================
|
||||||
|
// http://163cn.tv/xxx
|
||||||
|
MNES("网易云音乐分享",
|
||||||
|
compile("http(s)?://163cn\\.tv/(?<KEY>.+)"),
|
||||||
|
"http://163cn.tv/{shareKey}",
|
||||||
|
MnesTool.class),
|
||||||
|
// https://music.163.com/#/song?id=xxx
|
||||||
|
MNE("网易云音乐歌曲详情",
|
||||||
|
compile("https://music\\.163\\.com/(#/)?song\\?id=(?<KEY>.+)"),
|
||||||
|
"https://music.163.com/#/song?id={shareKey}",
|
||||||
|
MnesTool.MneTool.class),
|
||||||
|
// https://c6.y.qq.com/base/fcgi-bin/u?__=xxx
|
||||||
|
MQQS("QQ音乐分享",
|
||||||
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?y\\.qq\\.com/base/fcgi-bin/u\\?__=(?<KEY>.+)"),
|
||||||
|
"https://c6.y.qq.com/base/fcgi-bin/u?__={shareKey}",
|
||||||
|
MqqsTool.class),
|
||||||
|
// https://y.qq.com/n/ryqq/songDetail/000XjcLg0fbRjv?songtype=0
|
||||||
|
MQQ("QQ音乐歌曲详情",
|
||||||
|
compile("https://y\\.qq\\.com/n/ryqq/songDetail/(?<KEY>.+)(\\?.*)?"),
|
||||||
|
"https://y.qq.com/n/ryqq/songDetail/{shareKey}",
|
||||||
|
MqqsTool.MqqTool.class),
|
||||||
|
|
||||||
|
// https://t1.kugou.com/song.html?id=xxx
|
||||||
|
MKGS("酷狗音乐分享",
|
||||||
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?kugou\\.com/song\\.html\\?id=(?<KEY>.+)"),
|
||||||
|
"https://t1.kugou.com/song.html?id={shareKey}",
|
||||||
|
MkgsTool.class),
|
||||||
|
// https://www.kugou.com/share/2bi8Fe9CSV3.html?id=2bi8Fe9CSV3#6ed9gna4"
|
||||||
|
MKGS2("酷狗音乐分享2",
|
||||||
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?kugou\\.com/share/(?<KEY>.+).html.*"),
|
||||||
|
"https://www.kugou.com/share/{shareKey}.html",
|
||||||
|
MkgsTool.Mkgs2Tool.class),
|
||||||
|
// https://www.kugou.com/mixsong/2bi8Fe9CSV3
|
||||||
|
MKG("酷狗音乐歌曲详情",
|
||||||
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?kugou\\.com/mixsong/(?<KEY>.+)\\.html.*"),
|
||||||
|
"https://www.kugou.com/mixsong/{shareKey}.html",
|
||||||
|
MkgsTool.MkgTool.class),
|
||||||
|
// https://kuwo.cn/play_detail/395500809
|
||||||
|
// https://m.kuwo.cn/newh5app/play_detail/318448522
|
||||||
|
MKWS("酷我音乐分享*",
|
||||||
|
compile("https://(m\\.)?kuwo\\.cn/(newh5app/)?play_detail/(?<KEY>.+)"),
|
||||||
|
"https://kuwo.cn/play_detail/{shareKey}",
|
||||||
|
MkwTool.class),
|
||||||
|
// https://music.migu.cn/v3/music/song/6326951FKBJ?channelId=001002H
|
||||||
|
MMGS("咪咕音乐分享",
|
||||||
|
compile("https://music\\.migu\\.cn/v3/music/song/(?<KEY>.+)(\\?.*)?"),
|
||||||
|
"https://music.migu.cn/v3/music/song/{shareKey}",
|
||||||
|
MkwTool.class),
|
||||||
|
// =====================私有盘解析==========================
|
||||||
|
|
||||||
|
// Cloudreve自定义域名解析, 解析器CeTool兜底策略, 即任意域名如果匹配不到对应的规则, 则由CeTool统一处理,
|
||||||
|
// 如果不属于Cloudreve盘 则调用下一个自定义域名解析器, 若都处理不了则抛出异常, 这种匹配模式类似责任链
|
||||||
|
// https://pan.huang1111.cn/s/xxx
|
||||||
|
// 通用域名([a-z\\d]+(-[a-z\\d]+)*\.)+[a-z]{2,}
|
||||||
|
CE("Cloudreve",
|
||||||
|
compile("http(s)?://([a-zA-Z\\d]+(-[a-zA-Z\\d]+)*\\.)+[a-zA-Z]{2,}(/s)?/(?<KEY>.+)"),
|
||||||
|
"https://{any}/s/{shareKey}",
|
||||||
|
"https://cloudreve.org/",
|
||||||
|
CeTool.class),
|
||||||
|
// 可道云自定义域名解析
|
||||||
|
KD("可道云",
|
||||||
|
compile("http(s)?://([a-zA-Z\\d]+(-[a-zA-Z\\d]+)*\\.)+[a-zA-Z]{2,}(/#s)?/(?<KEY>.+)"),
|
||||||
|
"https://{any}/#s/{shareKey}",
|
||||||
|
"https://kodcloud.com/",
|
||||||
|
KdTool.class),
|
||||||
|
// 其他自定义域名解析
|
||||||
|
OTHER("其他网盘",
|
||||||
|
compile("http(s)?://([a-zA-Z\\d]+(-[a-zA-Z\\d]+)*\\.)+[a-zA-Z]{2,}/(?<KEY>.+)"),
|
||||||
|
"https://{any}/{shareKey}",
|
||||||
|
OtherTool.class);
|
||||||
|
|
||||||
|
public static final String KEY = "KEY";
|
||||||
|
|
||||||
|
public static final String PWD = "PWD";
|
||||||
|
|
||||||
|
// 网盘的显示名称,用于用户界面显示
|
||||||
|
private final String displayName;
|
||||||
|
|
||||||
|
// 用于匹配和解析分享链接的正则表达式,保证最后一个捕捉组能匹配到分享key
|
||||||
|
private final Pattern pattern;
|
||||||
|
|
||||||
|
private final String regex;
|
||||||
|
|
||||||
|
// 网盘的标准链接模板,不含占位符,用于规范化分享链接
|
||||||
|
private final String standardUrlTemplate;
|
||||||
|
|
||||||
|
// 网盘的域名, 如果在分享链接里能提取到, 则可不写
|
||||||
|
private String panDomain;
|
||||||
|
|
||||||
|
// 指向解析工具IPanTool实现类
|
||||||
|
private final Class<? extends IPanTool> toolClass;
|
||||||
|
|
||||||
|
PanDomainTemplate(String displayName, Pattern pattern, String standardUrlTemplate,
|
||||||
|
Class<? extends IPanTool> toolClass) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.pattern = pattern;
|
||||||
|
this.regex = pattern.pattern();
|
||||||
|
this.standardUrlTemplate = standardUrlTemplate;
|
||||||
|
this.toolClass = toolClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
PanDomainTemplate(String displayName, Pattern pattern, String standardUrlTemplate, String panDomain,
|
||||||
|
Class<? extends IPanTool> toolClass) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.pattern = pattern;
|
||||||
|
this.regex = pattern.pattern();
|
||||||
|
this.standardUrlTemplate = standardUrlTemplate;
|
||||||
|
this.panDomain = panDomain;
|
||||||
|
this.toolClass = toolClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pattern getPattern() {
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRegex() {
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStandardUrlTemplate() {
|
||||||
|
return standardUrlTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends IPanTool> getToolClass() {
|
||||||
|
return toolClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPanDomain() {
|
||||||
|
if (panDomain == null) {
|
||||||
|
String url = standardUrlTemplate
|
||||||
|
.replace("{shareKey}", "");
|
||||||
|
URL panDomainUrl = null;
|
||||||
|
try {
|
||||||
|
panDomainUrl = new URL(url);
|
||||||
|
} catch (MalformedURLException ignored) {}
|
||||||
|
|
||||||
|
return panDomainUrl != null ? (panDomainUrl.getProtocol() + "://" + panDomainUrl.getHost()) : "无";
|
||||||
|
}
|
||||||
|
return panDomain;
|
||||||
|
}
|
||||||
|
}
|
||||||
162
parser/src/main/java/cn/qaiu/parser/ParserCreate.java
Normal file
162
parser/src/main/java/cn/qaiu/parser/ParserCreate.java
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package cn.qaiu.parser;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
|
import static cn.qaiu.parser.PanDomainTemplate.KEY;
|
||||||
|
import static cn.qaiu.parser.PanDomainTemplate.PWD;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该类提供方法来解析和规范化不同来源的分享链接,确保它们可以转换为统一的标准链接格式。
|
||||||
|
* 通过这种方式,应用程序可以更容易地处理和识别不同网盘服务的分享链接。
|
||||||
|
*
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
* @date 2024/9/15 14:10
|
||||||
|
*/
|
||||||
|
public class ParserCreate {
|
||||||
|
private final PanDomainTemplate panDomainTemplate;
|
||||||
|
private final ShareLinkInfo shareLinkInfo;
|
||||||
|
|
||||||
|
public ParserCreate(PanDomainTemplate panDomainTemplate, ShareLinkInfo shareLinkInfo) {
|
||||||
|
this.panDomainTemplate = panDomainTemplate;
|
||||||
|
this.shareLinkInfo = shareLinkInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 解析并规范化分享链接
|
||||||
|
public ParserCreate normalizeShareLink() {
|
||||||
|
if (shareLinkInfo == null) {
|
||||||
|
throw new IllegalArgumentException("ShareLinkInfo not init");
|
||||||
|
}
|
||||||
|
// 匹配并提取shareKey
|
||||||
|
String shareUrl = shareLinkInfo.getShareUrl();
|
||||||
|
if (StringUtils.isEmpty(shareUrl)) {
|
||||||
|
throw new IllegalArgumentException("ShareLinkInfo shareUrl is empty");
|
||||||
|
}
|
||||||
|
Matcher matcher = this.panDomainTemplate.getPattern().matcher(shareUrl);
|
||||||
|
if (matcher.find()) {
|
||||||
|
String shareKey = matcher.group(KEY);
|
||||||
|
|
||||||
|
// 返回规范化的标准链接
|
||||||
|
String standardUrl = getStandardUrlTemplate()
|
||||||
|
.replace("{shareKey}", shareKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String pwd = matcher.group(PWD);
|
||||||
|
standardUrl = standardUrl .replace("{pwd}", pwd);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
|
shareLinkInfo.setShareUrl(shareUrl);
|
||||||
|
shareLinkInfo.setShareKey(shareKey);
|
||||||
|
if (!(panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal())) {
|
||||||
|
shareLinkInfo.setStandardUrl(standardUrl);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Invalid share URL for " + this.panDomainTemplate.getDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IPanTool createTool() {
|
||||||
|
if (shareLinkInfo == null || StringUtils.isEmpty(shareLinkInfo.getType())) {
|
||||||
|
throw new IllegalArgumentException("ShareLinkInfo not init or type is empty");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(shareLinkInfo.getShareKey())) {
|
||||||
|
this.normalizeShareLink();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return this.panDomainTemplate.getToolClass()
|
||||||
|
.getDeclaredConstructor(ShareLinkInfo.class)
|
||||||
|
.newInstance(shareLinkInfo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("无法创建工具实例: " + panDomainTemplate.getToolClass().getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set share key
|
||||||
|
public ParserCreate shareKey(String shareKey) {
|
||||||
|
if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
|
||||||
|
// 处理Cloudreve(ce)类: pan.huang1111.cn_s_wDz5TK _ -> /
|
||||||
|
String[] s = shareKey.split("_");
|
||||||
|
String standardUrl = "https://" + String.join("/", s);
|
||||||
|
shareLinkInfo.setShareKey(s[s.length - 1]);
|
||||||
|
shareLinkInfo.setStandardUrl(standardUrl);
|
||||||
|
shareLinkInfo.setShareUrl(standardUrl);
|
||||||
|
} else {
|
||||||
|
shareLinkInfo.setShareKey(shareKey);
|
||||||
|
shareLinkInfo.setStandardUrl(panDomainTemplate.getStandardUrlTemplate().replace("{shareKey}", shareKey));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set any share url
|
||||||
|
public ParserCreate fromAnyShareUrl(String url) {
|
||||||
|
shareLinkInfo.setStandardUrl(url);
|
||||||
|
shareLinkInfo.setShareUrl(url);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStandardUrlTemplate() {
|
||||||
|
return this.panDomainTemplate.getStandardUrlTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShareLinkInfo getShareLinkInfo() {
|
||||||
|
return shareLinkInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParserCreate setShareLinkInfoPwd(String pwd) {
|
||||||
|
shareLinkInfo.setSharePassword(pwd);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据分享链接获取PanDomainTemplate实例
|
||||||
|
public synchronized static ParserCreate fromShareUrl(String shareUrl) {
|
||||||
|
for (PanDomainTemplate panDomainTemplate : PanDomainTemplate.values()) {
|
||||||
|
if (panDomainTemplate.getPattern().matcher(shareUrl).matches()) {
|
||||||
|
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
|
||||||
|
.type(panDomainTemplate.name().toLowerCase())
|
||||||
|
.panName(panDomainTemplate.getDisplayName())
|
||||||
|
.shareUrl(shareUrl).build();
|
||||||
|
if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
|
||||||
|
shareLinkInfo.setStandardUrl(shareUrl);
|
||||||
|
}
|
||||||
|
ParserCreate parserCreate = new ParserCreate(panDomainTemplate, shareLinkInfo);
|
||||||
|
return parserCreate.normalizeShareLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unsupported share URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据type获取枚举实例
|
||||||
|
public synchronized static ParserCreate fromType(String type) {
|
||||||
|
try {
|
||||||
|
PanDomainTemplate panDomainTemplate = Enum.valueOf(PanDomainTemplate.class, type.toUpperCase());
|
||||||
|
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
|
||||||
|
.type(type.toLowerCase()).build();
|
||||||
|
shareLinkInfo.setPanName(panDomainTemplate.getDisplayName());
|
||||||
|
return new ParserCreate(panDomainTemplate, shareLinkInfo);
|
||||||
|
} catch (IllegalArgumentException ignore) {
|
||||||
|
// 如果没有找到对应的枚举实例,抛出异常
|
||||||
|
throw new IllegalArgumentException("No enum constant for type name: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成parser短链path(不包含domainName)
|
||||||
|
public String genPathSuffix() {
|
||||||
|
|
||||||
|
String path;
|
||||||
|
if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
|
||||||
|
// 处理Cloudreve(ce)类: pan.huang1111.cn_s_wDz5TK _ -> /
|
||||||
|
path = this.shareLinkInfo.getType() + "/"
|
||||||
|
+ this.shareLinkInfo.getShareUrl()
|
||||||
|
.substring("https://".length()).replace("/", "_");
|
||||||
|
} else {
|
||||||
|
path = this.shareLinkInfo.getType() + "/" + this.shareLinkInfo.getShareKey();
|
||||||
|
}
|
||||||
|
String sharePassword = this.shareLinkInfo.getSharePassword();
|
||||||
|
return path + (StringUtils.isBlank(sharePassword) ? "" : ("@" + sharePassword));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
84
parser/src/main/java/cn/qaiu/parser/impl/CeTool.java
Normal file
84
parser/src/main/java/cn/qaiu/parser/impl/CeTool.java
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.parser.PanDomainTemplate;
|
||||||
|
import cn.qaiu.parser.ParserCreate;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.client.HttpRequest;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://github.com/cloudreve/Cloudreve">Cloudreve自建网盘解析</a> <br>
|
||||||
|
* <a href="https://pan.xiaomuxi.cn">暮希云盘</a> <br>
|
||||||
|
* <a href="https://pan.huang1111.cn">huang1111</a> <br>
|
||||||
|
* <a href="https://pan.seeoss.com">看见存储</a> <br>
|
||||||
|
* <a href="https://dav.yiandrive.com">亿安云盘</a> <br>
|
||||||
|
*/
|
||||||
|
public class CeTool extends PanBase {
|
||||||
|
|
||||||
|
private static final String DOWNLOAD_API_PATH = "/api/v3/share/download/";
|
||||||
|
|
||||||
|
// api/v3/share/info/g31PcQ?password=qaiu
|
||||||
|
private static final String SHARE_API_PATH = "/api/v3/share/info/";
|
||||||
|
|
||||||
|
public CeTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
String key = shareLinkInfo.getShareKey();
|
||||||
|
String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
// https://pan.huang1111.cn/s/wDz5TK
|
||||||
|
// https://pan.huang1111.cn/s/y12bI6 -> https://pan.huang1111
|
||||||
|
// .cn/api/v3/share/download/y12bI6?path=undefined%2Fundefined;
|
||||||
|
// 类型解析 -> /ce/pan.huang1111.cn_s_wDz5TK
|
||||||
|
// parser接口 -> /parser?url=https://pan.huang1111.cn/s/wDz5TK
|
||||||
|
try {
|
||||||
|
// // 处理URL
|
||||||
|
URL url = new URL(shareLinkInfo.getShareUrl());
|
||||||
|
String downloadApiUrl = url.getProtocol() + "://" + url.getHost() + DOWNLOAD_API_PATH + key + "?path" +
|
||||||
|
"=undefined/undefined;";
|
||||||
|
String shareApiUrl = url.getProtocol() + "://" + url.getHost() + SHARE_API_PATH + key;
|
||||||
|
// 设置cookie
|
||||||
|
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
|
||||||
|
if (pwd != null) {
|
||||||
|
httpRequest.addQueryParam("password", pwd);
|
||||||
|
}
|
||||||
|
// 获取下载链接
|
||||||
|
httpRequest.send().onSuccess(res -> {
|
||||||
|
try {
|
||||||
|
if (res.statusCode() == 200 && res.bodyAsJsonObject().containsKey("code")) {
|
||||||
|
getDownURL(downloadApiUrl);
|
||||||
|
} else {
|
||||||
|
nextParser();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
nextParser();
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(shareApiUrl));
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail(e, "URL解析错误");
|
||||||
|
}
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void getDownURL(String shareApiUrl) {
|
||||||
|
clientSession.putAbs(shareApiUrl).send().onSuccess(res -> {
|
||||||
|
JsonObject jsonObject = asJson(res);
|
||||||
|
System.out.println(jsonObject.encodePrettily());
|
||||||
|
if (jsonObject.containsKey("code") && jsonObject.getInteger("code") == 0) {
|
||||||
|
promise.complete(jsonObject.getString("data"));
|
||||||
|
} else {
|
||||||
|
fail("JSON解析失败: {}", jsonObject.encodePrettily());
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(shareApiUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
64
parser/src/main/java/cn/qaiu/parser/impl/CowTool.java
Normal file
64
parser/src/main/java/cn/qaiu/parser/impl/CowTool.java
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 奶牛快传解析工具
|
||||||
|
*
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
* @date 2023/4/21 21:19
|
||||||
|
*/
|
||||||
|
public class CowTool extends PanBase {
|
||||||
|
|
||||||
|
private static final String API_REQUEST_URL = "https://cowtransfer.com/core/api/transfer/share";
|
||||||
|
|
||||||
|
public CowTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
final String key = shareLinkInfo.getShareKey();
|
||||||
|
String url = API_REQUEST_URL + "?uniqueUrl=" + key;
|
||||||
|
client.getAbs(url).send().onSuccess(res -> {
|
||||||
|
JsonObject resJson = asJson(res);
|
||||||
|
if ("success".equals(resJson.getString("message")) && resJson.containsKey("data")) {
|
||||||
|
JsonObject dataJson = resJson.getJsonObject("data");
|
||||||
|
String guid = dataJson.getString("guid");
|
||||||
|
StringBuilder url2Build = new StringBuilder(API_REQUEST_URL + "/download?transferGuid=" + guid);
|
||||||
|
if (dataJson.getBoolean("zipDownload")) {
|
||||||
|
// &title=xxx
|
||||||
|
JsonObject firstFolder = dataJson.getJsonObject("firstFolder");
|
||||||
|
url2Build.append("&title=").append(firstFolder.getString("title"));
|
||||||
|
} else {
|
||||||
|
String fileId = dataJson.getJsonObject("firstFile").getString("id");
|
||||||
|
url2Build.append("&fileId=").append(fileId);
|
||||||
|
}
|
||||||
|
String url2 = url2Build.toString();
|
||||||
|
client.getAbs(url2).send().onSuccess(res2 -> {
|
||||||
|
JsonObject res2Json = asJson(res2);
|
||||||
|
if ("success".equals(res2Json.getString("message")) && res2Json.containsKey("data")) {
|
||||||
|
JsonObject data2 = res2Json.getJsonObject("data");
|
||||||
|
String downloadUrl = data2.getString("downloadUrl");
|
||||||
|
if (StringUtils.isNotEmpty(downloadUrl)) {
|
||||||
|
log.info("cow parse success: {}", downloadUrl);
|
||||||
|
promise.complete(downloadUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail("cow parse fail: {}; downloadUrl is empty", url2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail("cow parse fail: {}; json: {}", url2, res2Json);
|
||||||
|
}).onFailure(handleFail(url2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail("cow parse fail: {}; json: {}", key, resJson);
|
||||||
|
}).onFailure(handleFail(url));
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
90
parser/src/main/java/cn/qaiu/parser/impl/CtTool.java
Normal file
90
parser/src/main/java/cn/qaiu/parser/impl/CtTool.java
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.RandomStringGenerator;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.client.HttpRequest;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://www.ctfile.com">诚通网盘</a>
|
||||||
|
*/
|
||||||
|
public class CtTool extends PanBase {
|
||||||
|
private static final String API_URL_PREFIX = "https://webapi.ctfile.com";
|
||||||
|
|
||||||
|
private static final String API1 = API_URL_PREFIX + "/getfile.php?path=file" +
|
||||||
|
"&f={shareKey}&passcode={pwd}&token={token}&r={rand}&ref=";
|
||||||
|
|
||||||
|
private static final String API2 = API_URL_PREFIX + "/get_file_url.php?" +
|
||||||
|
"uid={uid}&fid={fid}&folder_id=0&file_chk={file_chk}&mb=0&token={token}&app=0&acheck=0&verifycode=" +
|
||||||
|
"&rd={rand}";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子类重写此构造方法不需要添加额外逻辑
|
||||||
|
* 如:
|
||||||
|
* <blockquote><pre>
|
||||||
|
* public XxTool(String key, String pwd) {
|
||||||
|
* super(key, pwd);
|
||||||
|
* }
|
||||||
|
* </pre></blockquote>
|
||||||
|
*
|
||||||
|
* @param shareLinkInfo 分享链接信息
|
||||||
|
*/
|
||||||
|
public CtTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parse() {
|
||||||
|
final String shareKey = shareLinkInfo.getShareKey();
|
||||||
|
if (shareKey.indexOf('-') == -1) {
|
||||||
|
fail("shareKey格式不正确找不到'-': {}", shareKey);
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
String[] split = shareKey.split("-");
|
||||||
|
String uid = split[0], fid = split[1];
|
||||||
|
String token = RandomStringGenerator.generateRandomString();
|
||||||
|
|
||||||
|
HttpRequest<Buffer> bufferHttpRequest1 = clientSession.getAbs(UriTemplate.of(API1))
|
||||||
|
.setTemplateParam("shareKey", shareKey)
|
||||||
|
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
||||||
|
.setTemplateParam("token", token)
|
||||||
|
.setTemplateParam("r", Math.random() + "");
|
||||||
|
|
||||||
|
bufferHttpRequest1
|
||||||
|
.send().onSuccess(res -> {
|
||||||
|
var resJson = asJson(res);
|
||||||
|
if (resJson.containsKey("file")) {
|
||||||
|
var fileJson = resJson.getJsonObject("file");
|
||||||
|
if (fileJson.containsKey("file_chk")) {
|
||||||
|
var file_chk = fileJson.getString("file_chk");
|
||||||
|
HttpRequest<Buffer> bufferHttpRequest2 = clientSession.getAbs(UriTemplate.of(API2))
|
||||||
|
.setTemplateParam("uid", uid)
|
||||||
|
.setTemplateParam("fid", fid)
|
||||||
|
.setTemplateParam("file_chk", file_chk)
|
||||||
|
.setTemplateParam("token", token)
|
||||||
|
.setTemplateParam("rd", Math.random() + "");
|
||||||
|
bufferHttpRequest2
|
||||||
|
.send().onSuccess(res2 -> {
|
||||||
|
JsonObject resJson2 = asJson(res2);
|
||||||
|
if (resJson2.containsKey("downurl")) {
|
||||||
|
promise.complete(resJson2.getString("downurl"));
|
||||||
|
} else {
|
||||||
|
fail("解析失败, 可能分享已失效: json: {} 字段 {} 不存在", resJson2, "downurl");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
|
||||||
|
} else {
|
||||||
|
fail("解析失败, 可能分享已失效: json: {} 字段 {} 不存在", resJson, "file_chk");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail("解析失败, 可能分享已失效: json: {} 字段 {} 不存在", resJson, "file");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,46 +1,52 @@
|
|||||||
package cn.qaiu.lz.common.parser.impl;
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
import cn.qaiu.lz.common.parser.IPanTool;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.lz.common.util.CommonUtils;
|
import cn.qaiu.parser.PanBase;
|
||||||
import cn.qaiu.lz.common.util.PanExceptionUtils;
|
|
||||||
import cn.qaiu.vx.core.util.VertxHolder;
|
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.Promise;
|
|
||||||
import io.vertx.core.json.JsonArray;
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
|
||||||
import io.vertx.uritemplate.UriTemplate;
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移动云空间解析
|
* 移动云空间解析
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
public class EcTool extends PanBase {
|
||||||
public class EcTool implements IPanTool {
|
// https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx&isShare=1
|
||||||
private static final String FIRST_REQUEST_URL = "https://www.ecpan.cn/drive/fileextoverrid" +
|
private static final String FIRST_REQUEST_URL = "https://www.ecpan.cn/drive/fileextoverrid" +
|
||||||
".do?chainUrlTemplate=https:%2F%2Fwww.ecpan" +
|
".do?extractionCode={extractionCode}&chainUrlTemplate=https:%2F%2Fwww.ecpan" +
|
||||||
".cn%2Fweb%2F%23%2FyunpanProxy%3Fpath%3D%252F%2523%252Fdrive%252Foutside&parentId=-1&data={dataKey}";
|
".cn%2Fweb%2F%23%2FyunpanProxy%3Fpath%3D%252F%2523%252Fdrive%252Foutside&parentId=-1&data={dataKey}";
|
||||||
|
|
||||||
private static final String DOWNLOAD_REQUEST_URL = "https://www.ecpan.cn/drive/sharedownload.do";
|
private static final String DOWNLOAD_REQUEST_URL = "https://www.ecpan.cn/drive/sharedownload.do";
|
||||||
|
|
||||||
public static final String SHARE_URL_PREFIX = "www.ecpan.cn/";
|
public EcTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
final String dataKey = shareLinkInfo.getShareKey();
|
||||||
|
final String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
|
||||||
public Future<String> parse(String data, String code) {
|
|
||||||
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, data);
|
|
||||||
Promise<String> promise = Promise.promise();
|
|
||||||
WebClient client = WebClient.create(VertxHolder.getVertxInstance());
|
|
||||||
// 第一次请求 获取文件信息
|
// 第一次请求 获取文件信息
|
||||||
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("dataKey", dataKey).send().onSuccess(res -> {
|
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL))
|
||||||
JsonObject jsonObject = res.bodyAsJsonObject();
|
.setTemplateParam("dataKey", dataKey)
|
||||||
|
.setTemplateParam("extractionCode", pwd == null ? "" : pwd)
|
||||||
|
.send()
|
||||||
|
.onSuccess(res -> {
|
||||||
|
JsonObject jsonObject = asJson(res);
|
||||||
log.debug("ecPan get file info -> {}", jsonObject);
|
log.debug("ecPan get file info -> {}", jsonObject);
|
||||||
JsonObject fileInfo = jsonObject
|
JsonObject fileInfo = jsonObject
|
||||||
.getJsonObject("var")
|
.getJsonObject("var")
|
||||||
.getJsonObject("chainFileInfo");
|
.getJsonObject("chainFileInfo");
|
||||||
if (fileInfo.containsKey("errMesg")) {
|
if (fileInfo.containsKey("errMesg")) {
|
||||||
promise.fail(new RuntimeException(DOWNLOAD_REQUEST_URL + " 解析失败: "
|
fail("{} 解析失败:{} key = {}", FIRST_REQUEST_URL, fileInfo.getString("errMesg"), dataKey);
|
||||||
+ fileInfo.getString("errMesg")) + " key = " + dataKey);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!fileInfo.containsKey("cloudpFile")) {
|
||||||
|
fail("{} 解析失败:cloudpFile不存在 key = {}", FIRST_REQUEST_URL, dataKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
JsonObject cloudpFile = fileInfo.getJsonObject("cloudpFile");
|
JsonObject cloudpFile = fileInfo.getJsonObject("cloudpFile");
|
||||||
JsonArray fileIdList = JsonArray.of(cloudpFile);
|
JsonArray fileIdList = JsonArray.of(cloudpFile);
|
||||||
// 构造请求JSON {"extCodeFlag":0,"isIp":0}
|
// 构造请求JSON {"extCodeFlag":0,"isIp":0}
|
||||||
@@ -52,12 +58,12 @@ public class EcTool implements IPanTool {
|
|||||||
|
|
||||||
// 第二次请求 获取下载链接
|
// 第二次请求 获取下载链接
|
||||||
client.postAbs(DOWNLOAD_REQUEST_URL).sendJsonObject(requestBodyJson).onSuccess(res2 -> {
|
client.postAbs(DOWNLOAD_REQUEST_URL).sendJsonObject(requestBodyJson).onSuccess(res2 -> {
|
||||||
JsonObject jsonRes = res2.bodyAsJsonObject();
|
JsonObject jsonRes = asJson(res2);
|
||||||
log.debug("ecPan get download url -> {}", res2.body().toString());
|
log.debug("ecPan get download url -> {}", res2.body().toString());
|
||||||
promise.complete(jsonRes.getJsonObject("var").getString("downloadUrl"));
|
promise.complete(jsonRes.getJsonObject("var").getString("downloadUrl"));
|
||||||
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Ec", dataKey, t)));
|
}).onFailure(handleFail(""));
|
||||||
}
|
}
|
||||||
).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Ec", dataKey, t)));;
|
).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,13 @@
|
|||||||
package cn.qaiu.lz.common.parser.impl;
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
import cn.qaiu.lz.common.parser.IPanTool;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.lz.common.util.CommonUtils;
|
import cn.qaiu.parser.PanBase;
|
||||||
import cn.qaiu.lz.common.util.PanExceptionUtils;
|
|
||||||
import cn.qaiu.vx.core.util.VertxHolder;
|
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.MultiMap;
|
import io.vertx.core.MultiMap;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.Vertx;
|
|
||||||
import io.vertx.core.buffer.Buffer;
|
import io.vertx.core.buffer.Buffer;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.client.HttpResponse;
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
|
||||||
import io.vertx.ext.web.client.WebClientOptions;
|
|
||||||
import io.vertx.ext.web.client.WebClientSession;
|
import io.vertx.ext.web.client.WebClientSession;
|
||||||
import io.vertx.uritemplate.UriTemplate;
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -23,69 +18,68 @@ import java.util.regex.Pattern;
|
|||||||
/**
|
/**
|
||||||
* 360亿方云
|
* 360亿方云
|
||||||
*/
|
*/
|
||||||
public class FcTool implements IPanTool {
|
public class FcTool extends PanBase {
|
||||||
|
|
||||||
public static final String SHARE_URL_PREFIX = "https://v2.fangcloud.com/sharing/";
|
public static final String SHARE_URL_PREFIX = "https://v2.fangcloud.com/sharing/";
|
||||||
public static final String SHARE_URL_PREFIX2 = "https://v2.fangcloud.cn/sharing/";
|
public static final String SHARE_URL_PREFIX2 = "https://v2.fangcloud.cn/sharing/";
|
||||||
private static final String DOWN_REQUEST_URL = "https://v2.fangcloud.cn/apps/files/download?file_id={fid}" +
|
private static final String DOWN_REQUEST_URL = "https://v2.fangcloud.cn/apps/files/download?file_id={fid}" +
|
||||||
"&scenario=share&unique_name={uname}";
|
"&scenario=share&unique_name={uname}";
|
||||||
|
|
||||||
public Future<String> parse(String data, String code) {
|
public FcTool(ShareLinkInfo shareLinkInfo) {
|
||||||
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, data);
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
Promise<String> promise = Promise.promise();
|
|
||||||
|
|
||||||
Vertx vertx = VertxHolder.getVertxInstance();
|
public Future<String> parse() {
|
||||||
WebClient client = WebClient.create(vertx);
|
final String dataKey = shareLinkInfo.getShareKey();
|
||||||
|
final String pwd = shareLinkInfo.getSharePassword();
|
||||||
WebClientSession sClient = WebClientSession.create(client);
|
WebClientSession sClient = WebClientSession.create(client);
|
||||||
// 第一次请求 自动重定向
|
// 第一次请求 自动重定向
|
||||||
sClient.getAbs(SHARE_URL_PREFIX + dataKey).send().onSuccess(res -> {
|
sClient.getAbs(SHARE_URL_PREFIX + dataKey).send().onSuccess(res -> {
|
||||||
|
|
||||||
// 判断是否是加密分享
|
// 判断是否是加密分享
|
||||||
if (StringUtils.isNotEmpty(code)) {
|
if (StringUtils.isNotEmpty(pwd)) {
|
||||||
// 获取requesttoken
|
// 获取requesttoken
|
||||||
String html = res.bodyAsString();
|
String html = res.bodyAsString();
|
||||||
Pattern compile = Pattern.compile("name=\"requesttoken\"\\s+value=\"([a-zA-Z0-9_+=]+)\"");
|
Pattern compile = Pattern.compile("name=\"requesttoken\"\\s+value=\"([a-zA-Z0-9_+=]+)\"");
|
||||||
Matcher matcher = compile.matcher(html);
|
Matcher matcher = compile.matcher(html);
|
||||||
if (!matcher.find()) {
|
if (!matcher.find()) {
|
||||||
promise.fail(SHARE_URL_PREFIX + " 未匹配到加密分享的密码输入页面的requesttoken: \n" + html);
|
fail(SHARE_URL_PREFIX + " 未匹配到加密分享的密码输入页面的requesttoken");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String token = matcher.group(1);
|
String token = matcher.group(1);
|
||||||
|
|
||||||
sClient.postAbs(SHARE_URL_PREFIX2 + dataKey).sendForm(MultiMap.caseInsensitiveMultiMap()
|
sClient.postAbs(SHARE_URL_PREFIX2 + dataKey).sendForm(MultiMap.caseInsensitiveMultiMap()
|
||||||
.set("requesttoken", token)
|
.set("requesttoken", token)
|
||||||
.set("password", code)).onSuccess(res2 -> {
|
.set("password", pwd)).onSuccess(res2 -> {
|
||||||
if (res2.statusCode() == 302) {
|
if (res2.statusCode() == 302) {
|
||||||
sClient.getAbs(res2.getHeader("Location")).send().onSuccess(res3 -> {
|
sClient.getAbs(res2.getHeader("Location")).send()
|
||||||
getDownURL(dataKey, promise, res3, sClient);
|
.onSuccess(res3 -> getDownURL(dataKey, promise, res3, sClient))
|
||||||
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Fc", dataKey, t)));
|
.onFailure(handleFail(res2.getHeader("Location")));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
promise.fail(SHARE_URL_PREFIX + " 密码跳转后获取重定向失败 \n" + html);
|
fail(SHARE_URL_PREFIX + " 密码跳转后获取重定向失败");
|
||||||
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Fc", dataKey, t)));
|
}).onFailure(handleFail(SHARE_URL_PREFIX2));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getDownURL(dataKey, promise, res, sClient);
|
getDownURL(dataKey, promise, res, sClient);
|
||||||
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Fc", dataKey, t)));
|
}).onFailure(handleFail(SHARE_URL_PREFIX + dataKey));
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void getDownURL(String dataKey, Promise<String> promise, HttpResponse<Buffer> res,
|
private void getDownURL(String dataKey, Promise<String> promise, HttpResponse<Buffer> res,
|
||||||
WebClientSession sClient) {
|
WebClientSession sClient) {
|
||||||
// 从HTML中找到文件id
|
// 从HTML中找到文件id
|
||||||
String html = res.bodyAsString();
|
String html = res.bodyAsString();
|
||||||
Pattern compile = Pattern.compile("id=\"typed_id\"\\s+value=\"file_(\\d+)\"");
|
Pattern compile = Pattern.compile("id=\"typed_id\"\\s+value=\"file_(\\d+)\"");
|
||||||
Matcher matcher = compile.matcher(html);
|
Matcher matcher = compile.matcher(html);
|
||||||
if (!matcher.find()) {
|
if (!matcher.find()) {
|
||||||
promise.fail(SHARE_URL_PREFIX + " 未匹配到文件id(typed_id): \n" + html);
|
fail(SHARE_URL_PREFIX + " 未匹配到文件id(typed_id)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String fid = matcher.group(1);
|
String fid = matcher.group(1);
|
||||||
|
|
||||||
// 创建一个不自动重定向的WebClientSession
|
// 创建一个不自动重定向的WebClientSession
|
||||||
WebClient clientNoRedirects = WebClient.create(VertxHolder.getVertxInstance(),
|
|
||||||
new WebClientOptions().setFollowRedirects(false));
|
|
||||||
WebClientSession sClientNoRedirects = WebClientSession.create(clientNoRedirects, sClient.cookieStore());
|
WebClientSession sClientNoRedirects = WebClientSession.create(clientNoRedirects, sClient.cookieStore());
|
||||||
// 第二次请求
|
// 第二次请求
|
||||||
sClientNoRedirects.getAbs(UriTemplate.of(DOWN_REQUEST_URL))
|
sClientNoRedirects.getAbs(UriTemplate.of(DOWN_REQUEST_URL))
|
||||||
@@ -93,16 +87,16 @@ public class FcTool implements IPanTool {
|
|||||||
.setTemplateParam("unique_name", dataKey).send().onSuccess(res2 -> {
|
.setTemplateParam("unique_name", dataKey).send().onSuccess(res2 -> {
|
||||||
JsonObject resJson;
|
JsonObject resJson;
|
||||||
try {
|
try {
|
||||||
resJson = res2.bodyAsJsonObject();
|
resJson = asJson(res2);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
promise.fail(DOWN_REQUEST_URL + " 第二次请求没有返回JSON, 可能下载受限: " + res2.bodyAsString());
|
fail(e, DOWN_REQUEST_URL + " 第二次请求没有返回JSON, 可能下载受限");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!resJson.getBoolean("success")) {
|
if (!resJson.getBoolean("success")) {
|
||||||
promise.fail(DOWN_REQUEST_URL + " 第二次请求未得到正确相应: " + resJson);
|
fail(DOWN_REQUEST_URL + " 第二次请求未得到正确相应: " + resJson);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
promise.complete(resJson.getString("download_url"));
|
promise.complete(resJson.getString("download_url"));
|
||||||
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Fc", dataKey, t)));
|
}).onFailure(handleFail(DOWN_REQUEST_URL));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
110
parser/src/main/java/cn/qaiu/parser/impl/FjTool.java
Normal file
110
parser/src/main/java/cn/qaiu/parser/impl/FjTool.java
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.AESUtils;
|
||||||
|
import cn.qaiu.util.UUIDUtil;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.client.HttpRequest;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小飞机网盘
|
||||||
|
*
|
||||||
|
* @version V016_230609
|
||||||
|
*/
|
||||||
|
public class FjTool extends PanBase {
|
||||||
|
public static final String REFERER_URL = "https://share.feijipan.com/";
|
||||||
|
private static final String API_URL_PREFIX = "https://api.feijipan.com/ws/";
|
||||||
|
|
||||||
|
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome" +
|
||||||
|
"&uuid={uuid}&extra=2×tamp={ts}&shareId={shareId}&type=0&offset=1&limit=60";
|
||||||
|
/// recommend/list?devType=6&devModel=Chrome&uuid={uuid}&extra=2×tamp={ts}&shareId={shareId}&type=0&offset=1
|
||||||
|
// &limit=60
|
||||||
|
|
||||||
|
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
|
||||||
|
"&devType=6&uuid={uuid}×tamp={ts}&auth={auth}&shareId={dataKey}";
|
||||||
|
// https://api.feijipan.com/ws/file/redirect?downloadId={fidEncode}&enable=1&devType=6&uuid={uuid}×tamp={ts}&auth={auth}&shareId={dataKey}
|
||||||
|
|
||||||
|
|
||||||
|
private static final String VIP_REQUEST_URL = API_URL_PREFIX + "/buy/vip/list?devType=6&devModel=Chrome&uuid" +
|
||||||
|
"={uuid}&extra=2×tamp={ts}";
|
||||||
|
// https://api.feijipan.com/ws/buy/vip/list?devType=6&devModel=Chrome&uuid=WQAl5yBy1naGudJEILBvE&extra=2×tamp=E2C53155F6D09417A27981561134CB73
|
||||||
|
|
||||||
|
public FjTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
final String dataKey = shareLinkInfo.getShareKey();
|
||||||
|
|
||||||
|
// 240530 此处shareId又改为了原始的shareId
|
||||||
|
String shareId = dataKey; // String.valueOf(AESUtils.idEncrypt(dataKey));
|
||||||
|
long nowTs = System.currentTimeMillis();
|
||||||
|
String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs));
|
||||||
|
String uuid = UUIDUtil.fjUuid(); // 也可以使用 UUID.randomUUID().toString()
|
||||||
|
|
||||||
|
// 24.5.12 飞机盘 规则修改 需要固定UUID先请求会员接口, 再请求后续接口
|
||||||
|
client.postAbs(UriTemplate.of(VIP_REQUEST_URL))
|
||||||
|
.setTemplateParam("uuid", uuid)
|
||||||
|
.setTemplateParam("ts", tsEncode)
|
||||||
|
.send().onSuccess(r0 -> { // 忽略res
|
||||||
|
|
||||||
|
// long nowTs0 = System.currentTimeMillis();
|
||||||
|
String tsEncode0 = AESUtils.encrypt2Hex(Long.toString(nowTs));
|
||||||
|
// 第一次请求 获取文件信息
|
||||||
|
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
||||||
|
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL))
|
||||||
|
.setTemplateParam("shareId", shareId)
|
||||||
|
.setTemplateParam("uuid", uuid)
|
||||||
|
.setTemplateParam("ts", tsEncode0)
|
||||||
|
.send().onSuccess(res -> {
|
||||||
|
JsonObject resJson = asJson(res);
|
||||||
|
if (resJson.getInteger("code") != 200) {
|
||||||
|
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resJson.getJsonArray("list").size() == 0) {
|
||||||
|
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 文件Id
|
||||||
|
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
||||||
|
String fileId = fileInfo.getString("fileIds");
|
||||||
|
String userId = fileInfo.getString("userId");
|
||||||
|
// 其他参数
|
||||||
|
long nowTs2 = System.currentTimeMillis();
|
||||||
|
String tsEncode2 = AESUtils.encrypt2Hex(Long.toString(nowTs2));
|
||||||
|
String fidEncode = AESUtils.encrypt2Hex(fileId + "|" + userId);
|
||||||
|
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs2);
|
||||||
|
|
||||||
|
MultiMap headers0 = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
headers0.set("referer", REFERER_URL);
|
||||||
|
// 第二次请求
|
||||||
|
HttpRequest<Buffer> httpRequest =
|
||||||
|
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||||
|
.putHeaders(headers0)
|
||||||
|
.setTemplateParam("fidEncode", fidEncode)
|
||||||
|
.setTemplateParam("uuid", uuid)
|
||||||
|
.setTemplateParam("ts", tsEncode2)
|
||||||
|
.setTemplateParam("auth", auth)
|
||||||
|
.setTemplateParam("dataKey", dataKey);
|
||||||
|
System.out.println(httpRequest.toString());
|
||||||
|
httpRequest.send().onSuccess(res2 -> {
|
||||||
|
MultiMap headers = res2.headers();
|
||||||
|
if (!headers.contains("Location")) {
|
||||||
|
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.complete(headers.get("Location"));
|
||||||
|
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
||||||
|
}).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||||
|
|
||||||
|
}).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
}
|
||||||
132
parser/src/main/java/cn/qaiu/parser/impl/GenShortUrl.java
Normal file
132
parser/src/main/java/cn/qaiu/parser/impl/GenShortUrl.java
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.netty.handler.codec.http.cookie.Cookie;
|
||||||
|
import io.netty.handler.codec.http.cookie.DefaultCookie;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.http.impl.headers.HeadersMultiMap;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* t.cn 短链生成 //
|
||||||
|
*/
|
||||||
|
public class GenShortUrl extends PanBase {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GenShortUrl.class);
|
||||||
|
|
||||||
|
private static final String COMMENT_URL = "https://www.weibo.com/aj/v6/comment/add";
|
||||||
|
private static final String DELETE_COMMENT_URL = "https://www.weibo.com/aj/comment/del";
|
||||||
|
|
||||||
|
private static final String WRAPPER_URL = "https://www.so.com/link?m=ewgUSYiFWXIoTybC3fJH8YoJy8y10iRquo6cazgINwWjTn3HvVJ92TrCJu0PmMUR0RMDfOAucP3wa4G8j64SrhNH9Z0Cr0PEyn9ASuvpkUGmAjjUEGJkO5%2BIDGWVrEkPHsL7UsoKO6%2BlT%2BD6r&ccc=";
|
||||||
|
private static final String MID = "5095144728824883"; // 微博的mid
|
||||||
|
|
||||||
|
private static final MultiMap HEADER = HeadersMultiMap.headers()
|
||||||
|
.add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.add("Referer", "https://www.weibo.com")
|
||||||
|
.add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36");
|
||||||
|
|
||||||
|
Cookie cookie = new DefaultCookie("SUB", "_2A25KJE5vDeRhGeRJ6lsR9SjJzDuIHXVpWM-nrDV8PUJbkNAbLVPlkW1NUmJm3GjYtRHBsHdMUKafkdTL_YheMEmu");
|
||||||
|
public GenShortUrl(ShareLinkInfo build) {
|
||||||
|
super(build);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parse() {
|
||||||
|
String longUrl = shareLinkInfo.getShareUrl();
|
||||||
|
|
||||||
|
String url = WRAPPER_URL + Base64.getEncoder().encodeToString(longUrl.getBytes());
|
||||||
|
|
||||||
|
String payload = "mid=" + MID + "&content=" + URLEncoder.encode(url, StandardCharsets.UTF_8);
|
||||||
|
clientSession.cookieStore().put(cookie);
|
||||||
|
|
||||||
|
clientSession.postAbs(COMMENT_URL)
|
||||||
|
.putHeaders(HEADER)
|
||||||
|
.sendBuffer(Buffer.buffer(payload))
|
||||||
|
.onSuccess(res -> {
|
||||||
|
JsonObject data = asJson(res).getJsonObject("data");
|
||||||
|
if (data.isEmpty()) {
|
||||||
|
fail(asJson(res).getString("msg"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String comment = data.getString("comment");
|
||||||
|
String shortUrl = extractShortUrl(comment);
|
||||||
|
if (shortUrl != null) {
|
||||||
|
log.info("生成的短链:{}", shortUrl);
|
||||||
|
String commentId = extractCommentId(comment);
|
||||||
|
if (commentId != null) {
|
||||||
|
deleteComment(commentId);
|
||||||
|
} else {
|
||||||
|
promise.fail("未能提取评论ID");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
promise.fail("未能生成短链");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onFailure(err -> {
|
||||||
|
log.error("添加评论失败", err);
|
||||||
|
promise.fail(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void deleteComment(String commentId) {
|
||||||
|
String payload = "mid=" + MID + "&cid=" + commentId;
|
||||||
|
|
||||||
|
clientSession.postAbs(DELETE_COMMENT_URL)
|
||||||
|
.putHeaders(HEADER)
|
||||||
|
.sendBuffer(Buffer.buffer(payload))
|
||||||
|
.onSuccess(res -> {
|
||||||
|
JsonObject responseJson = res.bodyAsJsonObject();
|
||||||
|
if (responseJson.getString("code").equals("100000")) {
|
||||||
|
log.info("评论已删除: {}", commentId);
|
||||||
|
} else {
|
||||||
|
log.error("删除评论失败: {}", responseJson.encode());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onFailure(err -> {
|
||||||
|
log.error("删除评论失败", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractShortUrl(String comment) {
|
||||||
|
Pattern pattern = Pattern.compile("(https?)://t.cn/\\w+");
|
||||||
|
Matcher matcher = pattern.matcher(comment);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(0);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractCommentId(String comment) {
|
||||||
|
Pattern pattern = Pattern.compile("comment_id=\"(\\d+)\"");
|
||||||
|
Matcher matcher = pattern.matcher(comment);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// http://t.cn/A6nfZn86
|
||||||
|
// http://t.cn/A6nfZn86
|
||||||
|
|
||||||
|
new GenShortUrl(ShareLinkInfo.newBuilder().shareUrl("https://qaiu.top/sdfsdf").build()).parse().onSuccess(
|
||||||
|
System.out::println
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
80
parser/src/main/java/cn/qaiu/parser/impl/IzTool.java
Normal file
80
parser/src/main/java/cn/qaiu/parser/impl/IzTool.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.AESUtils;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 蓝奏云优享
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class IzTool extends PanBase {
|
||||||
|
|
||||||
|
public static final String SHARE_URL_PREFIX = "https://www.ilanzou.com/s/";
|
||||||
|
private static final String API_URL_PREFIX = "https://api.ilanzou.com/unproved/";
|
||||||
|
|
||||||
|
private static final String FIRST_REQUEST_URL = API_URL_PREFIX +
|
||||||
|
"recommend/list?devModel=Chrome&extra=2&shareId={shareId}&type=0&offset=1&limit=60";
|
||||||
|
|
||||||
|
private static final String SECOND_REQUEST_URL = API_URL_PREFIX +
|
||||||
|
"file/redirect?downloadId={fidEncode}&enable=1&devType=6&uuid={uuid}×tamp={ts}&auth={auth}&shareId={shareId}";
|
||||||
|
// downloadId=x&enable=1&devType=6&uuid=x×tamp=x&auth=x&shareId=lGFndCM
|
||||||
|
|
||||||
|
public IzTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
String dataKey = shareLinkInfo.getShareKey();
|
||||||
|
|
||||||
|
// 24.5.12 ilanzou改规则无需计算shareId
|
||||||
|
// String shareId = String.valueOf(AESUtils.idEncryptIz(dataKey));
|
||||||
|
|
||||||
|
// 第一次请求 获取文件信息
|
||||||
|
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
||||||
|
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", dataKey).send().onSuccess(res -> {
|
||||||
|
JsonObject resJson = asJson(res);
|
||||||
|
if (resJson.getInteger("code") != 200) {
|
||||||
|
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resJson.getJsonArray("list").size() == 0) {
|
||||||
|
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 文件Id
|
||||||
|
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
||||||
|
String fileId = fileInfo.getString("fileIds");
|
||||||
|
String userId = fileInfo.getString("userId");
|
||||||
|
// 其他参数
|
||||||
|
long nowTs = System.currentTimeMillis();
|
||||||
|
String tsEncode = AESUtils.encrypt2HexIz(Long.toString(nowTs));
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
// String fidEncode = AESUtils.encrypt2HexIz(fileId + "|");
|
||||||
|
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
|
||||||
|
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
|
||||||
|
// 第二次请求
|
||||||
|
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||||
|
.setTemplateParam("fidEncode", fidEncode)
|
||||||
|
.setTemplateParam("uuid", uuid)
|
||||||
|
.setTemplateParam("ts", tsEncode)
|
||||||
|
.setTemplateParam("auth", auth)
|
||||||
|
.setTemplateParam("shareId", dataKey).send().onSuccess(res2 -> {
|
||||||
|
MultiMap headers = res2.headers();
|
||||||
|
if (!headers.contains("Location")) {
|
||||||
|
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.complete(headers.get("Location"));
|
||||||
|
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
||||||
|
}).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
}
|
||||||
26
parser/src/main/java/cn/qaiu/parser/impl/KdTool.java
Normal file
26
parser/src/main/java/cn/qaiu/parser/impl/KdTool.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonArray;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://kodcloud.com/">可道云</a>
|
||||||
|
*/
|
||||||
|
public class KdTool extends PanBase {
|
||||||
|
private static final String API_URL_PREFIX = "";
|
||||||
|
|
||||||
|
public KdTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
nextParser();
|
||||||
|
// TODO
|
||||||
|
return future();
|
||||||
|
}
|
||||||
|
}
|
||||||
92
parser/src/main/java/cn/qaiu/parser/impl/LeTool.java
Normal file
92
parser/src/main/java/cn/qaiu/parser/impl/LeTool.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonArray;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://lecloud.lenovo.com/">联想乐云</a>
|
||||||
|
*/
|
||||||
|
public class LeTool extends PanBase {
|
||||||
|
private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/";
|
||||||
|
|
||||||
|
public LeTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
final String dataKey = shareLinkInfo.getShareKey();
|
||||||
|
final String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
// {"shareId":"xxx","password":"xxx","directoryId":"-1"}
|
||||||
|
String apiUrl1 = API_URL_PREFIX + "shareInfo";
|
||||||
|
client.postAbs(apiUrl1)
|
||||||
|
.sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", -1))
|
||||||
|
.onSuccess(res -> {
|
||||||
|
JsonObject resJson = asJson(res);
|
||||||
|
if (resJson.containsKey("result")) {
|
||||||
|
if (resJson.getBoolean("result")) {
|
||||||
|
JsonObject dataJson = resJson.getJsonObject("data");
|
||||||
|
// 密码验证失败
|
||||||
|
if (!dataJson.getBoolean("passwordVerified")) {
|
||||||
|
fail("密码验证失败, 分享key: {}, 密码: {}", dataKey, pwd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件信息
|
||||||
|
JsonArray files = dataJson.getJsonArray("files");
|
||||||
|
if (files == null || files.size() == 0) {
|
||||||
|
fail("Result JSON数据异常: files字段不存在或jsonArray长度为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonObject fileInfoJson = files.getJsonObject(0);
|
||||||
|
if (fileInfoJson != null) {
|
||||||
|
// TODO 文件大小fileSize和文件名fileName
|
||||||
|
String fileId = fileInfoJson.getString("fileId");
|
||||||
|
// 根据文件ID获取跳转链接
|
||||||
|
getDownURL(dataKey, fileId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail("Result JSON数据异常: result字段不存在");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(apiUrl1));
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getDownURL(String key, String fileId) {
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
JsonArray fileIds = JsonArray.of(fileId);
|
||||||
|
String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds";
|
||||||
|
// {"fileIds":[123],"shareId":"xxx","browserId":"uuid"}
|
||||||
|
client.postAbs(apiUrl2)
|
||||||
|
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid))
|
||||||
|
.onSuccess(res -> {
|
||||||
|
JsonObject resJson = asJson(res);
|
||||||
|
if (resJson.containsKey("result")) {
|
||||||
|
if (resJson.getBoolean("result")) {
|
||||||
|
JsonObject dataJson = resJson.getJsonObject("data");
|
||||||
|
// 获取重定向链接跳转链接
|
||||||
|
String downloadUrl = dataJson.getString("downloadUrl");
|
||||||
|
if (downloadUrl == null) {
|
||||||
|
fail("Result JSON数据异常: downloadUrl不存在");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取重定向链接跳转链接
|
||||||
|
clientNoRedirects.getAbs(downloadUrl).send()
|
||||||
|
.onSuccess(res2 -> promise.complete(res2.headers().get("Location")))
|
||||||
|
.onFailure(handleFail(downloadUrl));
|
||||||
|
} else {
|
||||||
|
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail("Result JSON数据异常: result字段不存在");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(apiUrl2));
|
||||||
|
}
|
||||||
|
}
|
||||||
128
parser/src/main/java/cn/qaiu/parser/impl/LzTool.java
Normal file
128
parser/src/main/java/cn/qaiu/parser/impl/LzTool.java
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.JsExecUtils;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||||
|
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 蓝奏云解析工具
|
||||||
|
*
|
||||||
|
* @author QAIU
|
||||||
|
*/
|
||||||
|
public class LzTool extends PanBase {
|
||||||
|
|
||||||
|
public static final String SHARE_URL_PREFIX = "https://wwwa.lanzoux.com";
|
||||||
|
|
||||||
|
|
||||||
|
public LzTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Future<String> parse() {
|
||||||
|
String sUrl = shareLinkInfo.getStandardUrl();
|
||||||
|
String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
|
||||||
|
WebClient client = clientNoRedirects;
|
||||||
|
client.getAbs(sUrl).send().onSuccess(res -> {
|
||||||
|
String html = res.bodyAsString();
|
||||||
|
// 匹配iframe
|
||||||
|
Pattern compile = Pattern.compile("src=\"(/fn\\?[a-zA-Z\\d_+/=]{16,})\"");
|
||||||
|
Matcher matcher = compile.matcher(html);
|
||||||
|
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
|
||||||
|
if (!matcher.find()) {
|
||||||
|
// 处理一下JS
|
||||||
|
String jsText = getJsText(html);
|
||||||
|
|
||||||
|
if (jsText == null) {
|
||||||
|
fail(SHARE_URL_PREFIX + " -> " + sUrl + ": js脚本匹配失败, 可能分享已失效");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\"");
|
||||||
|
int i = jsText.indexOf("document.getElementById('rpt')");
|
||||||
|
if (i > 0) {
|
||||||
|
jsText = jsText.substring(0, i);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
|
||||||
|
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
|
||||||
|
} catch (ScriptException | NoSuchMethodException e) {
|
||||||
|
fail(e, "js引擎执行失败");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 没有密码
|
||||||
|
String iframePath = matcher.group(1);
|
||||||
|
client.getAbs(SHARE_URL_PREFIX + iframePath).send().onSuccess(res2 -> {
|
||||||
|
String html2 = res2.bodyAsString();
|
||||||
|
|
||||||
|
// 去TMD正则
|
||||||
|
// Matcher matcher2 = Pattern.compile("'sign'\s*:\s*'(\\w+)'").matcher(html2);
|
||||||
|
String jsText = getJsText(html2);
|
||||||
|
if (jsText == null) {
|
||||||
|
fail(SHARE_URL_PREFIX + iframePath + " -> " + sUrl + ": js脚本匹配失败, 可能分享已失效");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
|
||||||
|
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
|
||||||
|
} catch (ScriptException | NoSuchMethodException e) {
|
||||||
|
fail(e, "js引擎执行失败");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(SHARE_URL_PREFIX));
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(sUrl));
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getJsText(String html) {
|
||||||
|
String jsTagStart = "<script type=\"text/javascript\">";
|
||||||
|
String jsTagEnd = "</script>";
|
||||||
|
int index = html.lastIndexOf(jsTagStart);
|
||||||
|
if (index == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int startPos = index + jsTagStart.length();
|
||||||
|
int endPos = html.indexOf(jsTagEnd, startPos);
|
||||||
|
return html.substring(startPos, endPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getDownURL(String key, WebClient client, Map<String, ?> signMap) {
|
||||||
|
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
signMap.forEach((k, v) -> {
|
||||||
|
map.set(k, v.toString());
|
||||||
|
});
|
||||||
|
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
|
||||||
|
"like " +
|
||||||
|
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
|
||||||
|
headers.set("User-Agent", userAgent2);
|
||||||
|
headers.set("referer", key);
|
||||||
|
headers.set("sec-ch-ua-platform", "Android");
|
||||||
|
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
|
||||||
|
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
|
||||||
|
|
||||||
|
String url = SHARE_URL_PREFIX + "/ajaxm.php";
|
||||||
|
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
||||||
|
JsonObject urlJson = asJson(res2);
|
||||||
|
if (urlJson.getInteger("zt") != 1) {
|
||||||
|
fail(urlJson.getString("inf"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
|
||||||
|
client.getAbs(downUrl).putHeaders(headers).send()
|
||||||
|
.onSuccess(res3 -> promise.complete(res3.headers().get("Location")))
|
||||||
|
.onFailure(handleFail(downUrl));
|
||||||
|
}).onFailure(handleFail(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
125
parser/src/main/java/cn/qaiu/parser/impl/MkgsTool.java
Normal file
125
parser/src/main/java/cn/qaiu/parser/impl/MkgsTool.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酷狗音乐分享
|
||||||
|
* <a href="https://t1.kugou.com/song.html?id=2bi8Fe9CSV3">分享链接1</a>
|
||||||
|
* <a href="https://www.kugou.com/share/2bi8Fe9CSV3.html?id=2bi8Fe9CSV3#6ed9gna4">分享链接2</a>
|
||||||
|
* <a href="https://www.kugou.com/share/2bi8Fe9CSV3.html">分享链接3</a>
|
||||||
|
* <a href="https://www.kugou.com/mixsong/8odv4ef8.html">歌曲链接1</a>
|
||||||
|
*/
|
||||||
|
public class MkgsTool extends PanBase {
|
||||||
|
|
||||||
|
public static final String API_URL = "https://m.kugou.com/app/i/getSongInfo.php?cmd=playInfo&hash={hash}";
|
||||||
|
|
||||||
|
private static final MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
static {
|
||||||
|
// 设置 User-Agent
|
||||||
|
headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0");
|
||||||
|
|
||||||
|
// 设置 Accept
|
||||||
|
headers.set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
|
||||||
|
|
||||||
|
// 设置 If-Modified-Since
|
||||||
|
headers.set("If-Modified-Since", "Mon, 21 Oct 2024 08:45:50 GMT");
|
||||||
|
|
||||||
|
// 设置 Priority
|
||||||
|
headers.set("Priority", "u=0, i");
|
||||||
|
|
||||||
|
// 设置 Sec-CH-UA
|
||||||
|
headers.set("Sec-CH-UA", "\"Microsoft Edge\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"");
|
||||||
|
|
||||||
|
// 设置 Sec-CH-UA-Mobile
|
||||||
|
headers.set("Sec-CH-UA-Mobile", "?0");
|
||||||
|
|
||||||
|
// 设置 Sec-CH-UA-Platform
|
||||||
|
headers.set("Sec-CH-UA-Platform", "\"Windows\"");
|
||||||
|
|
||||||
|
// 设置 Sec-Fetch-Dest
|
||||||
|
headers.set("Sec-Fetch-Dest", "document");
|
||||||
|
|
||||||
|
// 设置 Sec-Fetch-Mode
|
||||||
|
headers.set("Sec-Fetch-Mode", "navigate");
|
||||||
|
|
||||||
|
// 设置 Sec-Fetch-Site
|
||||||
|
headers.set("Sec-Fetch-Site", "none");
|
||||||
|
|
||||||
|
// 设置 Sec-Fetch-User
|
||||||
|
headers.set("Sec-Fetch-User", "?1");
|
||||||
|
|
||||||
|
// 设置 Upgrade-Insecure-Requests
|
||||||
|
headers.set("Upgrade-Insecure-Requests", "1");
|
||||||
|
};
|
||||||
|
|
||||||
|
public MkgsTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
String shareUrl = shareLinkInfo.getStandardUrl();
|
||||||
|
// String shareUrl = "https://t1.kugou.com/song.html?id=2bi8Fe9CSV3";
|
||||||
|
clientNoRedirects.getAbs(shareUrl).send().onSuccess(res -> {
|
||||||
|
String locationURL = res.headers().get("Location");
|
||||||
|
downUrl(locationURL);
|
||||||
|
}).onFailure(handleFail(shareUrl));
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void downUrl(String locationURL) {
|
||||||
|
client.getAbs(locationURL).putHeaders(headers).send().onSuccess(res2->{
|
||||||
|
String body = res2.bodyAsString();
|
||||||
|
// 正则表达式匹配 hash 字段
|
||||||
|
String regex = "\"hash\"\s*:\s*\"([A-F0-9]+)\"";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(body);
|
||||||
|
|
||||||
|
// 查找并输出 hash 字段的值
|
||||||
|
if (matcher.find()) {
|
||||||
|
String hashValue = matcher.group(1); // 获取第一个捕获组
|
||||||
|
System.out.println(hashValue);
|
||||||
|
client.getAbs(UriTemplate.of(API_URL)).setTemplateParam("hash", hashValue).send().onSuccess(res3 -> {
|
||||||
|
JsonObject jsonObject = asJson(res3);
|
||||||
|
System.out.println(jsonObject.encodePrettily());
|
||||||
|
if (jsonObject.containsKey("url")) {
|
||||||
|
promise.complete(jsonObject.getString("url"));
|
||||||
|
} else {
|
||||||
|
fail("下载链接不存在");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(API_URL.replace("{hash}", hashValue)));
|
||||||
|
} else {
|
||||||
|
fail("歌曲hash匹配失败, 可能分享已失效");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(locationURL));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MkgTool extends MkgsTool {
|
||||||
|
|
||||||
|
public MkgTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parse() {
|
||||||
|
downUrl(shareLinkInfo.getStandardUrl());
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Mkgs2Tool extends MkgTool {
|
||||||
|
public Mkgs2Tool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
parser/src/main/java/cn/qaiu/parser/impl/MkwTool.java
Normal file
68
parser/src/main/java/cn/qaiu/parser/impl/MkwTool.java
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.JsExecUtils;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 酷我音乐分享
|
||||||
|
* <a href="https://kuwo.cn/play_detail/395500809">分享示例</a>
|
||||||
|
* <a href="https://m.kuwo.cn/newh5app/play_detail/318448522">分享示例</a>
|
||||||
|
*/
|
||||||
|
public class MkwTool extends PanBase {
|
||||||
|
|
||||||
|
public static final String API_URL = "https://www.kuwo.cn/api/v1/www/music/playUrl?mid={mid}&type=music&httpsStatus=1&reqId=&plat=web_www&from=";
|
||||||
|
|
||||||
|
|
||||||
|
public MkwTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
String shareUrl = shareLinkInfo.getStandardUrl();
|
||||||
|
clientSession.getAbs(shareUrl).send().onSuccess(result -> {
|
||||||
|
String cookie = result.headers().get("set-cookie");
|
||||||
|
|
||||||
|
if (!cookie.isEmpty()) {
|
||||||
|
|
||||||
|
String regex = "([A-Za-z0-9_]+)=([A-Za-z0-9]+)";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(cookie);
|
||||||
|
if (matcher.find()) {
|
||||||
|
System.out.println(matcher.group(1));
|
||||||
|
System.out.println(matcher.group(2));
|
||||||
|
|
||||||
|
var key = matcher.group(1);
|
||||||
|
var token = matcher.group(2);
|
||||||
|
String sign = JsExecUtils.getKwSign(token, key);
|
||||||
|
System.out.println(sign);
|
||||||
|
clientSession.getAbs(UriTemplate.of(API_URL)).setTemplateParam("mid", shareLinkInfo.getShareKey())
|
||||||
|
.putHeader("Secret", sign).send().onSuccess(res -> {
|
||||||
|
JsonObject json = asJson(res);
|
||||||
|
log.debug(json.encodePrettily());
|
||||||
|
try {
|
||||||
|
if (json.getInteger("code") == 200) {
|
||||||
|
complete(json.getJsonObject("data").getString("url"));
|
||||||
|
} else {
|
||||||
|
fail("链接已失效/需要VIP");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail("解析失败");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
}
|
||||||
26
parser/src/main/java/cn/qaiu/parser/impl/MmgTool.java
Normal file
26
parser/src/main/java/cn/qaiu/parser/impl/MmgTool.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 咪咕音乐分享
|
||||||
|
*/
|
||||||
|
public class MmgTool extends PanBase {
|
||||||
|
|
||||||
|
public static final String API_URL = "";
|
||||||
|
|
||||||
|
|
||||||
|
public MmgTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
String shareUrl = shareLinkInfo.getStandardUrl();
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
promise.complete("暂未实现, 敬请期待");
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
}
|
||||||
59
parser/src/main/java/cn/qaiu/parser/impl/MnesTool.java
Normal file
59
parser/src/main/java/cn/qaiu/parser/impl/MnesTool.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.URLUtil;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网易云音乐, 单歌曲直链解析
|
||||||
|
* <a href="http://163cn.tv/ykLZJJT">示例分享1</a>
|
||||||
|
* <a href="https://music.163.com/#/song?id=472194327">示例分享2</a>
|
||||||
|
*/
|
||||||
|
public class MnesTool extends PanBase {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String API_URL = "https://music.163.com/song/media/outer/url?id={id}";
|
||||||
|
|
||||||
|
|
||||||
|
public MnesTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
String shareUrl = shareLinkInfo.getStandardUrl();
|
||||||
|
clientNoRedirects.getAbs(shareUrl).send().onSuccess(res -> {
|
||||||
|
String locationURL = res.headers().get("Location");
|
||||||
|
downUrl(locationURL);
|
||||||
|
}).onFailure(handleFail(shareUrl));
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void downUrl(String locationURL) {
|
||||||
|
String id = URLUtil.from(locationURL).getParam("id");
|
||||||
|
clientNoRedirects.getAbs(UriTemplate.of(API_URL)).setTemplateParam("id", id).send()
|
||||||
|
.onSuccess(res2 -> {
|
||||||
|
String location = res2.headers().get("Location");
|
||||||
|
if (location.endsWith("/404")) {
|
||||||
|
fail("链接已失效: id={}", id);
|
||||||
|
} else {
|
||||||
|
promise.complete(location);
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(API_URL.replace("{id}", id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class MneTool extends MnesTool{
|
||||||
|
|
||||||
|
public MneTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parse() {
|
||||||
|
downUrl(shareLinkInfo.getStandardUrl());
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
parser/src/main/java/cn/qaiu/parser/impl/MqqsTool.java
Normal file
78
parser/src/main/java/cn/qaiu/parser/impl/MqqsTool.java
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.URLUtil;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* QQ音乐分享解析
|
||||||
|
* <a href="https://c6.y.qq.com/base/fcgi-bin/u?__=w3lqEpOHACLO">分享示例</a>
|
||||||
|
* <a href="https://y.qq.com/n/ryqq/songDetail/000XjcLg0fbRjv?songtype=0">详情页</a>
|
||||||
|
*/
|
||||||
|
public class MqqsTool extends PanBase {
|
||||||
|
|
||||||
|
public static final String API_URL = "https://u.y.qq.com/cgi-bin/musicu" +
|
||||||
|
".fcg?-=getplaysongvkey2682247447678878&g_tk=5381&loginUin=956581739&hostUin=0&format=json&inCharset=utf8" +
|
||||||
|
"&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data=%7B%22req_0%22%3A%7B%22module%22%3A" +
|
||||||
|
"%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%222796982635" +
|
||||||
|
"%22%2C%22songmid%22%3A%5B%22{songmid}%22%5D%2C%22songtype%22%3A%5B1%5D%2C%22uin%22%3A%22956581739%22" +
|
||||||
|
"%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A956581739%2C" +
|
||||||
|
"%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D";
|
||||||
|
|
||||||
|
public MqqsTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
String shareUrl = shareLinkInfo.getStandardUrl();
|
||||||
|
// https://c6.y.qq.com/base/fcgi-bin/u?__=uXXtsB
|
||||||
|
// String shareUrl = "https://c6.y.qq.com/base/fcgi-bin/u?__=k8gafY6HAQ5Y";
|
||||||
|
|
||||||
|
clientNoRedirects.getAbs(shareUrl).send().onSuccess(res -> {
|
||||||
|
String locationURL = res.headers().get("Location");
|
||||||
|
String id = URLUtil.from(locationURL).getParam("songmid");
|
||||||
|
downUrl(id);
|
||||||
|
}).onFailure(handleFail(shareUrl));
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void downUrl(String id) {
|
||||||
|
clientNoRedirects.getAbs(UriTemplate.of(API_URL)).setTemplateParam("songmid", id).send().onSuccess(res2 -> {
|
||||||
|
JsonObject jsonObject = asJson(res2);
|
||||||
|
log.debug(jsonObject.encodePrettily());
|
||||||
|
try {
|
||||||
|
JsonObject data = jsonObject.getJsonObject("req_0").getJsonObject("data");
|
||||||
|
String path = data.getJsonArray("midurlinfo").getJsonObject(0).getString("purl");
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
fail("暂不支持VIP音乐");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String downURL = data.getJsonArray("sip").getString(0)
|
||||||
|
.replace("http://", "https://") + path;
|
||||||
|
promise.complete(downURL);
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail("获取失败");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(API_URL.replace("{id}", id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class MqqTool extends MqqsTool{
|
||||||
|
|
||||||
|
public MqqTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parse() {
|
||||||
|
downUrl(shareLinkInfo.getShareKey());
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
parser/src/main/java/cn/qaiu/parser/impl/OtherTool.java
Normal file
22
parser/src/main/java/cn/qaiu/parser/impl/OtherTool.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 其他网盘解析
|
||||||
|
*/
|
||||||
|
public class OtherTool extends PanBase {
|
||||||
|
private static final String API_URL_PREFIX = "";
|
||||||
|
|
||||||
|
public OtherTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
// TODO
|
||||||
|
fail("暂未实现, 敬请期待");
|
||||||
|
return future();
|
||||||
|
}
|
||||||
|
}
|
||||||
48
parser/src/main/java/cn/qaiu/parser/impl/P118Tool.java
Normal file
48
parser/src/main/java/cn/qaiu/parser/impl/P118Tool.java
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 118网盘解析
|
||||||
|
*/
|
||||||
|
public class P118Tool extends PanBase {
|
||||||
|
|
||||||
|
private static final String API_URL_PREFIX = "https://qaiu.118pan.com/ajax.php";
|
||||||
|
|
||||||
|
// private static final String
|
||||||
|
|
||||||
|
public P118Tool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
|
||||||
|
client.postAbs(API_URL_PREFIX)
|
||||||
|
.putHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
|
||||||
|
.sendBuffer(Buffer.buffer("action=load_down_addr1&file_id=" + shareLinkInfo.getShareKey()))
|
||||||
|
.onSuccess(res -> {
|
||||||
|
System.out.println(res.headers());
|
||||||
|
Pattern compile = Pattern.compile("href=\"([^\"]+)\"");
|
||||||
|
Matcher matcher = compile.matcher(res.bodyAsString());
|
||||||
|
if (matcher.find()) {
|
||||||
|
System.out.println(matcher.group(1));
|
||||||
|
complete(matcher.group(1));
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(""));
|
||||||
|
return future();
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static void main(String[] args) {
|
||||||
|
// String s = new P118Tool(ShareLinkInfo.newBuilder().shareUrl("https://xiguage.118pan.com/b11848261").shareKey(
|
||||||
|
// "11848261").build()).parseSync();
|
||||||
|
// System.out.println(s);
|
||||||
|
// }
|
||||||
|
}
|
||||||
95
parser/src/main/java/cn/qaiu/parser/impl/PdbTool.java
Normal file
95
parser/src/main/java/cn/qaiu/parser/impl/PdbTool.java
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.IPanTool;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.URLUtil;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.net.ProxyOptions;
|
||||||
|
import io.vertx.ext.web.client.HttpRequest;
|
||||||
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://www.dropbox.com/">dropbox</a>
|
||||||
|
* Dropbox网盘--不支持大陆地区
|
||||||
|
*/
|
||||||
|
public class PdbTool extends PanBase implements IPanTool {
|
||||||
|
|
||||||
|
private static final String API_URL =
|
||||||
|
"https://www.dropbox.com/sharing/fetch_user_content_link";
|
||||||
|
static final String COOKIE_KEY = "__Host-js_csrf=";
|
||||||
|
|
||||||
|
public PdbTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parse() {
|
||||||
|
// https://www.dropbox.com/scl/fi/cwnbms1yn8u6rcatzyta7/emqx-5.0.26-el7-amd64.tar.gz?rlkey=3uoi4bxz5mv93jmlaws0nlol1&e=8&st=fe0lclc2&dl=0
|
||||||
|
// https://www.dropbox.com/scl/fi/()/Get-Started-with-Dropbox.pdf?rlkey=yrddd6s9gxsq967pmbgtzvfl3&st=2trcc1f3&dl=0
|
||||||
|
//
|
||||||
|
clientSession.getAbs(shareLinkInfo.getShareUrl())
|
||||||
|
.send()
|
||||||
|
.onSuccess(res->{
|
||||||
|
List<String> collect =
|
||||||
|
res.cookies().stream().filter(key -> key.contains(COOKIE_KEY)).toList();
|
||||||
|
if (collect.isEmpty()) {
|
||||||
|
fail("cookie未找到");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Matcher matcher = Pattern.compile(COOKIE_KEY + "([\\w-]+);").matcher(collect.get(0));
|
||||||
|
String _t;
|
||||||
|
if (matcher.find()) {
|
||||||
|
_t = matcher.group(1);
|
||||||
|
} else {
|
||||||
|
fail("cookie未找到");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
headers.set("accept", "*/*");
|
||||||
|
headers.set("accept-language", "zh-CN,zh;q=0.9");
|
||||||
|
headers.set("cache-control", "no-cache");
|
||||||
|
headers.set("dnt", "1");
|
||||||
|
headers.set("origin", "https://www.dropbox.com");
|
||||||
|
headers.set("pragma", "no-cache");
|
||||||
|
headers.set("priority", "u=1, i");
|
||||||
|
headers.set("referer", shareLinkInfo.getShareUrl());
|
||||||
|
headers.set("sec-ch-ua", "\"Chromium\";v=\"130\", \"Microsoft Edge\";v=\"130\", \"Not?A_Brand\";v=\"99\"");
|
||||||
|
headers.set("sec-ch-ua-mobile", "?0");
|
||||||
|
headers.set("sec-ch-ua-platform", "\"Windows\"");
|
||||||
|
headers.set("sec-fetch-dest", "empty");
|
||||||
|
headers.set("sec-fetch-mode", "cors");
|
||||||
|
headers.set("sec-fetch-site", "same-origin");
|
||||||
|
headers.set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0");
|
||||||
|
headers.set("x-dropbox-client-yaps-attribution", "edison_atlasservlet.file_viewer-edison:prod");
|
||||||
|
headers.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||||
|
try {
|
||||||
|
URL url = new URL(shareLinkInfo.getShareUrl());
|
||||||
|
// https://www.dropbox.com/scl/fi/cwnbms1yn8u6rcatzyta7/xxx?rlkey=xx&dl=0
|
||||||
|
String u0 = URLEncoder.encode((url.getProtocol() + "://" + url.getHost() + url.getPath() + "?rlkey=%s&dl=0")
|
||||||
|
.formatted(URLUtil.from(shareLinkInfo.getShareUrl()).getParam("rlkey")), StandardCharsets.UTF_8);
|
||||||
|
clientSession.postAbs(API_URL)
|
||||||
|
.sendBuffer(Buffer.buffer("is_xhr=true&t=%s&url=%s&origin=PREVIEW_PAGE".formatted(_t, u0)))
|
||||||
|
.onSuccess(res2 -> {
|
||||||
|
complete(res2.bodyAsString());
|
||||||
|
})
|
||||||
|
.onFailure(handleFail());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.onFailure(handleFail("请求下载链接失败"));
|
||||||
|
return future();
|
||||||
|
}
|
||||||
|
}
|
||||||
98
parser/src/main/java/cn/qaiu/parser/impl/PgdTool.java
Normal file
98
parser/src/main/java/cn/qaiu/parser/impl/PgdTool.java
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.IPanTool;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.net.ProxyOptions;
|
||||||
|
import io.vertx.ext.web.client.HttpRequest;
|
||||||
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://drive.google.com/">GoogleDrive</a>
|
||||||
|
* Google Drive文件解析工具.
|
||||||
|
*/
|
||||||
|
public class PgdTool extends PanBase implements IPanTool {
|
||||||
|
|
||||||
|
private static final String DOWN_URL_TEMPLATE =
|
||||||
|
"https://drive.usercontent.google.com/download";
|
||||||
|
|
||||||
|
private String downloadUrl;
|
||||||
|
|
||||||
|
public PgdTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parse() {
|
||||||
|
downloadUrl = DOWN_URL_TEMPLATE + "?id=" + shareLinkInfo.getShareKey() + "&export=download";
|
||||||
|
|
||||||
|
if (shareLinkInfo.getOtherParam().containsKey("proxy")) {
|
||||||
|
// if (shareLinkInfo.getOtherParam().containsKey("bypassCheck")
|
||||||
|
// && "true".equalsIgnoreCase(shareLinkInfo.getOtherParam().get("bypassCheck").toString())) {
|
||||||
|
// 发起请求但不真正下载文件, 只检查响应头
|
||||||
|
client.headAbs(downloadUrl).send()
|
||||||
|
.onSuccess(this::handleResponse)
|
||||||
|
.onFailure(handleFail("请求下载链接失败"));
|
||||||
|
return future();
|
||||||
|
}
|
||||||
|
complete(downloadUrl);
|
||||||
|
return future();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理下载链接的响应.
|
||||||
|
*/
|
||||||
|
private void handleResponse(HttpResponse<Buffer> response) {
|
||||||
|
String contentType = response.getHeader("Content-Type");
|
||||||
|
if (contentType != null && !contentType.contains("text/html")) {
|
||||||
|
complete(downloadUrl);
|
||||||
|
} else {
|
||||||
|
// 如果不是文件流类型,从 HTML 中解析出真实下载链接
|
||||||
|
client.getAbs(downloadUrl)
|
||||||
|
.send()
|
||||||
|
.onSuccess(res0 -> {
|
||||||
|
parseHtmlForRealLink(res0.bodyAsString());
|
||||||
|
})
|
||||||
|
.onFailure(handleFail("请求下载链接失败"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从HTML内容中解析真实下载链接.
|
||||||
|
*/
|
||||||
|
private void parseHtmlForRealLink(String html) {
|
||||||
|
// 使用正则表达式匹配 id、export、confirm、uuid、at 等参数
|
||||||
|
String id = extractHiddenInputValue(html, "id");
|
||||||
|
String confirm = extractHiddenInputValue(html, "confirm");
|
||||||
|
String uuid = extractHiddenInputValue(html, "uuid");
|
||||||
|
String at = extractHiddenInputValue(html, "at");
|
||||||
|
|
||||||
|
if (id != null && confirm != null && uuid != null) {
|
||||||
|
String realDownloadLink = DOWN_URL_TEMPLATE +
|
||||||
|
"?id=" + id +
|
||||||
|
"&export=download" +
|
||||||
|
"&confirm=" + confirm +
|
||||||
|
"&uuid=" + uuid;
|
||||||
|
if (at != null) {
|
||||||
|
realDownloadLink += ( "&at=" + at);
|
||||||
|
}
|
||||||
|
complete(realDownloadLink);
|
||||||
|
} else {
|
||||||
|
fail("无法找到完整的下载链接参数");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 辅助方法: 从HTML中提取指定name的input隐藏字段的value
|
||||||
|
*/
|
||||||
|
private String extractHiddenInputValue(String html, String name) {
|
||||||
|
Pattern pattern = Pattern.compile("<input[^>]*name=\"" + name + "\"[^>]*value=\"([^\"]*)\"");
|
||||||
|
Matcher matcher = pattern.matcher(html);
|
||||||
|
return matcher.find() ? matcher.group(1) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
parser/src/main/java/cn/qaiu/parser/impl/PicTool.java
Normal file
58
parser/src/main/java/cn/qaiu/parser/impl/PicTool.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://www.icloud.com.cn/">iCloud云盘(pic)</a>
|
||||||
|
*/
|
||||||
|
public class PicTool extends PanBase {
|
||||||
|
|
||||||
|
|
||||||
|
private static final String api = "https://ckdatabasews.icloud.com.cn/database/1/com.apple.cloudkit/production/public/records/resolve";
|
||||||
|
|
||||||
|
public PicTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
// {"shortGUIDs":[{"value":"xxx"}]}
|
||||||
|
JsonObject jsonObject =
|
||||||
|
new JsonObject("{\"shortGUIDs\":[{\"value\":\"%s\"}]}".formatted(shareLinkInfo.getShareKey()));
|
||||||
|
|
||||||
|
client.postAbs(api).sendJsonObject(jsonObject).onSuccess(res -> {
|
||||||
|
// results->rootRecord->fields->fileContent->value->downloadURL // ${f}->fileName
|
||||||
|
// fileName: results->share->fields->cloudkit.title->value + "." + results->rootRecord->fields->extension->value
|
||||||
|
JsonObject json = asJson(res);
|
||||||
|
try {
|
||||||
|
JsonObject result = json.getJsonArray("results").getJsonObject(0);
|
||||||
|
JsonObject fileInfo = result
|
||||||
|
.getJsonObject("rootRecord")
|
||||||
|
.getJsonObject("fields");
|
||||||
|
|
||||||
|
String downURL = fileInfo
|
||||||
|
.getJsonObject("fileContent")
|
||||||
|
.getJsonObject("value")
|
||||||
|
.getString("downloadURL");
|
||||||
|
|
||||||
|
String extension = fileInfo
|
||||||
|
.getJsonObject("extension")
|
||||||
|
.getString("value");
|
||||||
|
|
||||||
|
String fileTitle = result
|
||||||
|
.getJsonObject("share")
|
||||||
|
.getJsonObject("fields")
|
||||||
|
.getJsonObject("cloudkit.title")
|
||||||
|
.getString("value");
|
||||||
|
complete(downURL.replace("${f}", fileTitle + "." + extension));
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail(e, "json解析失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
}).onFailure(handleFail());
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
}
|
||||||
67
parser/src/main/java/cn/qaiu/parser/impl/PodTool.java
Normal file
67
parser/src/main/java/cn/qaiu/parser/impl/PodTool.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://onedrive.live.com/">onedrive分享(od)</a>
|
||||||
|
*/
|
||||||
|
public class PodTool extends PanBase {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://1drv.ms/w/s!Alg0feQmCv2rnRFd60DQOmMa-Oh_?e=buaRtp --302->
|
||||||
|
* https://api.onedrive.com/v1.0/drives/abfd0a26e47d3458/items/ABFD0A26E47D3458!3729?authkey=!AF3rQNA6Yxr46H8
|
||||||
|
* https://onedrive.live.com/redir?resid=(?<cid>)!(?<cid2>)&authkey=(?<authkey>)&e=hV98W1
|
||||||
|
* cid: abfd0a26e47d3458, cid2: ABFD0A26E47D3458!3729 authkey: !AF3rQNA6Yxr46H8
|
||||||
|
* -> @content.downloadUrl
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final String api = "https://api.onedrive.com/v1.0/drives/{cid}/items/{cid20}?authkey={authkey}";
|
||||||
|
private static final Pattern redirectUrlRegex =
|
||||||
|
Pattern.compile("=(?<cid>.+)!(?<cid2>.+)&authkey=(?<authkey>.+)&e=(?<e>.+)");
|
||||||
|
|
||||||
|
public PodTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
clientNoRedirects.getAbs(shareLinkInfo.getShareUrl()).send().onSuccess(r0 -> {
|
||||||
|
String location = r0.getHeader("Location");
|
||||||
|
Matcher matcher = redirectUrlRegex.matcher(location);
|
||||||
|
if (matcher.find()) {
|
||||||
|
var cid= matcher.group("cid");
|
||||||
|
var cid2= matcher.group("cid2");
|
||||||
|
var authkey= matcher.group("authkey");
|
||||||
|
client.getAbs(UriTemplate.of(api))
|
||||||
|
.setTemplateParam("cid", cid)
|
||||||
|
.setTemplateParam("cid20", cid + "!" + cid2)
|
||||||
|
.setTemplateParam("authkey", authkey).send()
|
||||||
|
.onSuccess(res -> {
|
||||||
|
JsonObject jsonObject = asJson(res);
|
||||||
|
// System.out.println(jsonObject);
|
||||||
|
complete(jsonObject.getString("@content.downloadUrl"));
|
||||||
|
})
|
||||||
|
.onFailure(handleFail());
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail());
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
//https://onedrive.live.com/redir?resid=ABFD0A26E47D3458!4699&e=OggA4s&migratedtospo=true&redeem=aHR0cHM6Ly8xZHJ2Lm1zL3UvcyFBbGcwZmVRbUN2MnJwRnZ1NDQ0aGc1eVZxRGNLP2U9T2dnQTRz
|
||||||
|
// public static void main(String[] args) {
|
||||||
|
// Matcher matcher = redirectUrlRegex.matcher("https://onedrive.live.com/redir?resid=ABFD0A26E47D3458!4698" +
|
||||||
|
// "&authkey=!ACpvXghP5xhG_cg&e=hV98W1");
|
||||||
|
// if (matcher.find()) {
|
||||||
|
// System.out.println(matcher.group("cid"));
|
||||||
|
// System.out.println(matcher.group("cid2"));
|
||||||
|
// System.out.println(matcher.group("authkey"));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
}
|
||||||
51
parser/src/main/java/cn/qaiu/parser/impl/PvyyTool.java
Normal file
51
parser/src/main/java/cn/qaiu/parser/impl/PvyyTool.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微雨云
|
||||||
|
*/
|
||||||
|
public class PvyyTool extends PanBase {
|
||||||
|
private static final String API_URL_PREFIX1 = "https://www.vyuyun.com/apiv1/share/file/{key}?password={pwd}";
|
||||||
|
private static final String API_URL_PREFIX2 = "https://www.vyuyun.com/apiv1/share/getShareDownUrl/{key}/{id}?password={pwd}";
|
||||||
|
|
||||||
|
public PvyyTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
//
|
||||||
|
client.getAbs(UriTemplate.of(API_URL_PREFIX1))
|
||||||
|
.setTemplateParam("key", shareLinkInfo.getShareKey())
|
||||||
|
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
||||||
|
.putHeader("referer", "https://www.vyuyun.com")
|
||||||
|
.send().onSuccess(res -> {
|
||||||
|
try {
|
||||||
|
String id = asJson(res).getJsonObject("data").getJsonObject("data").getString("id");
|
||||||
|
|
||||||
|
client.getAbs(UriTemplate.of(API_URL_PREFIX2))
|
||||||
|
.setTemplateParam("key", shareLinkInfo.getShareKey())
|
||||||
|
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
||||||
|
.setTemplateParam("id", id)
|
||||||
|
.putHeader("referer", "https://www.vyuyun.com")
|
||||||
|
.send().onSuccess(res2 -> {
|
||||||
|
try {
|
||||||
|
// data->downInfo->url
|
||||||
|
String url =
|
||||||
|
asJson(res2).getJsonObject("data").getJsonObject("downInfo").getString("url");
|
||||||
|
complete(url);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
fail(asJson(res2).encodePrettily());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
fail(asJson(res).encodePrettily());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return future();
|
||||||
|
}
|
||||||
|
}
|
||||||
92
parser/src/main/java/cn/qaiu/parser/impl/QQTool.java
Normal file
92
parser/src/main/java/cn/qaiu/parser/impl/QQTool.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.StringUtils;
|
||||||
|
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://wx.mail.qq.com/">QQ邮箱</a>
|
||||||
|
*/
|
||||||
|
public class QQTool extends PanBase {
|
||||||
|
|
||||||
|
public static final String REDIRECT_URL_TEMP = "https://iwx.mail.qq.com/ftn/download?func=4&key={key}&code={code}";
|
||||||
|
|
||||||
|
public QQTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
// QQ mail 直接替换为302链接 无需请求
|
||||||
|
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(shareLinkInfo.getShareUrl(), StandardCharsets.UTF_8);
|
||||||
|
Map<String, List<String>> prms = queryStringDecoder.parameters();
|
||||||
|
if (prms.containsKey("key") && prms.containsKey("code") && prms.containsKey("func")) {
|
||||||
|
log.info(prms.get("func").get(0));
|
||||||
|
promise.complete(REDIRECT_URL_TEMP.replace("{key}",
|
||||||
|
prms.get("key").get(0)).replace("{code}", prms.get("code").get(0)));
|
||||||
|
} else {
|
||||||
|
fail("key 不合法");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 通过请求URL获取文件信息和直链 暂时不需要
|
||||||
|
// getFileInfo(key);
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getFileInfo(String key) {
|
||||||
|
// 设置基础HTTP头部
|
||||||
|
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
|
||||||
|
"like " +
|
||||||
|
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
|
||||||
|
|
||||||
|
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
headers.set("User-Agent", userAgent2);
|
||||||
|
headers.set("sec-ch-ua-platform", "Android");
|
||||||
|
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
|
||||||
|
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
|
||||||
|
|
||||||
|
// 获取下载中转站页面
|
||||||
|
client.getAbs(key).putHeaders(headers).send().onSuccess(res -> {
|
||||||
|
if (res.statusCode() == 200) {
|
||||||
|
String html = res.bodyAsString();
|
||||||
|
|
||||||
|
// 匹配文件信息
|
||||||
|
String filename = StringUtils.StringCutNot(html, "var filename = \"", "\"");
|
||||||
|
String filesize = StringUtils.StringCutNot(html, "var filesize = ", "\n");
|
||||||
|
String fileurl = StringUtils.StringCutNot(html, "var url = \"", "\"");
|
||||||
|
|
||||||
|
if (filename != null && filesize != null && fileurl != null) {
|
||||||
|
// 设置所需HTTP头部
|
||||||
|
headers.set("Referer", "https://" + StringUtils.StringCutNot(key, "https://", "/") + "/");
|
||||||
|
headers.set("Host", StringUtils.StringCutNot(fileurl, "https://", "/"));
|
||||||
|
res.headers().forEach((k, v) -> {
|
||||||
|
if (k.equalsIgnoreCase("set-cookie")) {
|
||||||
|
headers.set("Cookie", "mail5k=" + StringUtils.StringCutNot(v, "mail5k=", ";") + ";");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 调试匹配的情况
|
||||||
|
System.out.println("文件名称: " + filename);
|
||||||
|
System.out.println("文件大小: " + filesize);
|
||||||
|
System.out.println("文件直链: " + fileurl);
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
promise.complete(fileurl.replace("\\x26", "&"));
|
||||||
|
} else {
|
||||||
|
this.fail("匹配失败,可能是分享链接的方式已更新");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||||
|
}
|
||||||
|
}).onFailure(this.handleFail(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
41
parser/src/main/java/cn/qaiu/parser/impl/QkTool.java
Normal file
41
parser/src/main/java/cn/qaiu/parser/impl/QkTool.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
public class QkTool extends PanBase {
|
||||||
|
|
||||||
|
public QkTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
final String key = shareLinkInfo.getShareKey();
|
||||||
|
final String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
|
||||||
|
promise.complete("https://lz.qaiu.top");
|
||||||
|
IntStream.range(0, 1000).forEach(num -> {
|
||||||
|
clientNoRedirects.getAbs(key).send()
|
||||||
|
.onSuccess(res -> {
|
||||||
|
String location = res.headers().get("Location");
|
||||||
|
System.out.println(num + ":" + location);
|
||||||
|
})
|
||||||
|
.onFailure(handleFail("连接失败"));
|
||||||
|
try {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
164
parser/src/main/java/cn/qaiu/parser/impl/WsTool.java
Normal file
164
parser/src/main/java/cn/qaiu/parser/impl/WsTool.java
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.json.DecodeException;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://www.wenshushu.cn/">文叔叔</a>
|
||||||
|
*/
|
||||||
|
public class WsTool extends PanBase {
|
||||||
|
|
||||||
|
public static final String SHARE_URL_PREFIX = "www.wenshushu.cn/f/";
|
||||||
|
public static final String SHARE_URL_API = "https://www.wenshushu.cn/ap/";
|
||||||
|
|
||||||
|
public WsTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
|
||||||
|
WebClient httpClient = this.client;
|
||||||
|
final String key = shareLinkInfo.getShareKey();
|
||||||
|
final String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
|
||||||
|
|
||||||
|
// 设置基础HTTP头部
|
||||||
|
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
|
||||||
|
"like " +
|
||||||
|
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
|
||||||
|
|
||||||
|
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
headers.set("User-Agent", userAgent2);
|
||||||
|
headers.set("sec-ch-ua-platform", "Android");
|
||||||
|
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
|
||||||
|
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
|
||||||
|
|
||||||
|
// 获取匿名登录token
|
||||||
|
httpClient.postAbs(SHARE_URL_API + "login/anonymous").putHeaders(headers)
|
||||||
|
.sendJsonObject(JsonObject.of("dev_info", "{}"))
|
||||||
|
.onSuccess(res -> {
|
||||||
|
|
||||||
|
if (res.statusCode() == 200) {
|
||||||
|
try {
|
||||||
|
// 设置匿名登录token
|
||||||
|
String token = asJson(res).getJsonObject("data").getString("token");
|
||||||
|
headers.set("X-Token", token);
|
||||||
|
|
||||||
|
// 获取文件夹信息
|
||||||
|
httpClient.postAbs(SHARE_URL_API + "task/mgrtask").putHeaders(headers)
|
||||||
|
.sendJsonObject(JsonObject.of(
|
||||||
|
"tid", key,
|
||||||
|
"password", pwd
|
||||||
|
)).onSuccess(res2 -> {
|
||||||
|
|
||||||
|
if (res2.statusCode() == 200) {
|
||||||
|
try {
|
||||||
|
// 获取文件夹信息
|
||||||
|
String filetime = asJson(res2).getJsonObject("data").getString("expire"); // 文件夹剩余时间
|
||||||
|
String filesize = asJson(res2).getJsonObject("data").getString("file_size"); // 文件夹大小
|
||||||
|
String filepid = asJson(res2).getJsonObject("data").getString("ufileid"); // 文件夹pid
|
||||||
|
String filebid = asJson(res2).getJsonObject("data").getString("boxid"); // 文件夹bid
|
||||||
|
|
||||||
|
// 调试输出文件夹信息
|
||||||
|
System.out.println("文件夹期限: " + filetime);
|
||||||
|
System.out.println("文件夹大小: " + filesize);
|
||||||
|
System.out.println("文件夹pid: " + filepid);
|
||||||
|
System.out.println("文件夹bid: " + filebid);
|
||||||
|
|
||||||
|
// 获取文件信息
|
||||||
|
httpClient.postAbs(SHARE_URL_API + "ufile/list").putHeaders(headers)
|
||||||
|
.sendJsonObject(JsonObject.of(
|
||||||
|
"start", 0,
|
||||||
|
"sort", JsonObject.of(
|
||||||
|
"name", "asc"
|
||||||
|
),
|
||||||
|
"bid", filebid,
|
||||||
|
"pid", filepid,
|
||||||
|
"type", 1,
|
||||||
|
"options", JsonObject.of(
|
||||||
|
"uploader", "true"
|
||||||
|
),
|
||||||
|
"size", 50
|
||||||
|
)).onSuccess(res3 -> {
|
||||||
|
|
||||||
|
if (res3.statusCode() == 200) {
|
||||||
|
try {
|
||||||
|
// 获取文件信息
|
||||||
|
String filename = asJson(res3).getJsonObject("data")
|
||||||
|
.getJsonArray("fileList").getJsonObject(0).getString("fname"); // 文件名称
|
||||||
|
String filefid = asJson(res3).getJsonObject("data")
|
||||||
|
.getJsonArray("fileList").getJsonObject(0).getString("fid"); // 文件fid
|
||||||
|
|
||||||
|
// 调试输出文件信息
|
||||||
|
System.out.println("文件名称: " + filename);
|
||||||
|
System.out.println("文件fid: " + filefid);
|
||||||
|
|
||||||
|
// 检查文件是否失效
|
||||||
|
httpClient.postAbs(SHARE_URL_API + "dl/sign").putHeaders(headers)
|
||||||
|
.sendJsonObject(JsonObject.of(
|
||||||
|
"consumeCode", 0,
|
||||||
|
"type", 1,
|
||||||
|
"ufileid", filefid
|
||||||
|
)).onSuccess(res4 -> {
|
||||||
|
|
||||||
|
if (res4.statusCode() == 200) {
|
||||||
|
try {
|
||||||
|
// 获取直链
|
||||||
|
String fileurl = asJson(res4).getJsonObject("data").getString("url");
|
||||||
|
|
||||||
|
// 调试输出文件直链
|
||||||
|
System.out.println("文件直链: " + fileurl);
|
||||||
|
|
||||||
|
if (!fileurl.equals("")) {
|
||||||
|
promise.complete(URLDecoder.decode(fileurl, StandardCharsets.UTF_8));
|
||||||
|
} else {
|
||||||
|
this.fail("文件已失效");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (DecodeException | NullPointerException e) {
|
||||||
|
this.fail("获取文件信息失败,可能是分享链接的方式已更新,或者对方的文件已失效");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (DecodeException | NullPointerException e) {
|
||||||
|
this.fail("获取文件信息失败,可能是分享链接的方式已更新");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (DecodeException | NullPointerException e) {
|
||||||
|
this.fail("获取文件夹信息失败,可能是分享链接的方式已更新");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||||
|
}
|
||||||
|
|
||||||
|
}).onFailure(this.handleFail(key));
|
||||||
|
|
||||||
|
} catch (DecodeException | NullPointerException e) {
|
||||||
|
this.fail("token获取失败,可能是分享链接的方式已更新");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||||
|
}
|
||||||
|
|
||||||
|
}).onFailure(this.handleFail(key));
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
}
|
||||||
170
parser/src/main/java/cn/qaiu/parser/impl/YeTool.java
Normal file
170
parser/src/main/java/cn/qaiu/parser/impl/YeTool.java
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.CommonUtils;
|
||||||
|
import cn.qaiu.util.JsExecUtils;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 123网盘
|
||||||
|
*/
|
||||||
|
public class YeTool extends PanBase {
|
||||||
|
|
||||||
|
public static final String SHARE_URL_PREFIX = "https://www.123pan.com/s/";
|
||||||
|
public static final String FIRST_REQUEST_URL = SHARE_URL_PREFIX + "{key}.html";
|
||||||
|
|
||||||
|
private static final String GET_FILE_INFO_URL = "https://www.123pan.com/a/api/share/get?limit=100&next=1&orderBy" +
|
||||||
|
"=file_name&orderDirection=asc" +
|
||||||
|
"&shareKey={shareKey}&SharePwd={pwd}&ParentFileId=0&Page=1&event=homeListFile&operateType=1";
|
||||||
|
private static final String DOWNLOAD_API_URL = "https://www.123pan.com/a/api/share/download/info?{authK}={authV}";
|
||||||
|
|
||||||
|
public YeTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
|
||||||
|
final String dataKey = shareLinkInfo.getShareKey();
|
||||||
|
final String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
|
||||||
|
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("key", dataKey).send().onSuccess(res -> {
|
||||||
|
|
||||||
|
String html = res.bodyAsString();
|
||||||
|
// 判断分享是否已经失效
|
||||||
|
if (html.contains("分享链接已失效")) {
|
||||||
|
fail("该分享已失效({})已失效", shareLinkInfo.getShareUrl());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern compile = Pattern.compile("window.g_initialProps\\s*=\\s*(.*);");
|
||||||
|
Matcher matcher = compile.matcher(html);
|
||||||
|
|
||||||
|
if (!matcher.find()) {
|
||||||
|
fail("该分享({})文件信息找不到, 可能分享已失效", shareLinkInfo.getShareUrl());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String fileInfoString = matcher.group(1);
|
||||||
|
JsonObject fileInfoJson = new JsonObject(fileInfoString);
|
||||||
|
JsonObject resJson = fileInfoJson.getJsonObject("res");
|
||||||
|
JsonObject resListJson = fileInfoJson.getJsonObject("reslist");
|
||||||
|
|
||||||
|
if (resJson == null || resJson.getInteger("code") != 0) {
|
||||||
|
fail(dataKey + " 解析到异常JSON: " + resJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String shareKey = resJson.getJsonObject("data").getString("ShareKey");
|
||||||
|
|
||||||
|
if (resListJson == null || resListJson.getInteger("code") != 0) {
|
||||||
|
// 加密分享
|
||||||
|
if (StringUtils.isNotEmpty(pwd)) {
|
||||||
|
client.getAbs(UriTemplate.of(GET_FILE_INFO_URL))
|
||||||
|
.setTemplateParam("shareKey", shareKey)
|
||||||
|
.setTemplateParam("pwd", pwd)
|
||||||
|
// .setTemplateParam("authKey", AESUtils.getAuthKey("/a/api/share/get"))
|
||||||
|
.putHeader("Platform", "web")
|
||||||
|
.putHeader("App-Version", "3")
|
||||||
|
.send().onSuccess(res2 -> {
|
||||||
|
JsonObject infoJson = asJson(res2);
|
||||||
|
if (infoJson.getInteger("code") != 0) {
|
||||||
|
fail("{} 状态码异常 {}", dataKey, infoJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonObject getFileInfoJson =
|
||||||
|
infoJson.getJsonObject("data").getJsonArray("InfoList").getJsonObject(0);
|
||||||
|
getFileInfoJson.put("ShareKey", shareKey);
|
||||||
|
getDownUrl(client, getFileInfoJson);
|
||||||
|
}).onFailure(this.handleFail(GET_FILE_INFO_URL));
|
||||||
|
} else {
|
||||||
|
fail("该分享[{}]需要密码",dataKey);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject reqBodyJson = resListJson.getJsonObject("data").getJsonArray("InfoList").getJsonObject(0);
|
||||||
|
reqBodyJson.put("ShareKey", shareKey);
|
||||||
|
getDownUrl(client, reqBodyJson);
|
||||||
|
}).onFailure(this.handleFail(FIRST_REQUEST_URL));
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getDownUrl(WebClient client, JsonObject reqBodyJson) {
|
||||||
|
log.info(reqBodyJson.encodePrettily());
|
||||||
|
JsonObject jsonObject = new JsonObject();
|
||||||
|
// {"ShareKey":"iaKtVv-6OECd","FileID":2193732,"S3keyFlag":"1811834632-0","Size":4203111,
|
||||||
|
// "Etag":"69c94adbc0b9190cf23c4e958d8c7c53"}
|
||||||
|
jsonObject.put("ShareKey", reqBodyJson.getString("ShareKey"));
|
||||||
|
jsonObject.put("FileID", reqBodyJson.getInteger("FileId"));
|
||||||
|
jsonObject.put("S3keyFlag", reqBodyJson.getString("S3KeyFlag"));
|
||||||
|
jsonObject.put("Size", reqBodyJson.getLong("Size"));
|
||||||
|
jsonObject.put("Etag", reqBodyJson.getString("Etag"));
|
||||||
|
|
||||||
|
// 调用JS文件获取签名
|
||||||
|
ScriptObjectMirror getSign;
|
||||||
|
try {
|
||||||
|
getSign = JsExecUtils.executeJs("getSign", "/a/api/share/download/info");
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail(e, "JS函数执行异常");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("ye getSign: {}={}", getSign.get("0").toString(), getSign.get("1").toString());
|
||||||
|
|
||||||
|
client.postAbs(UriTemplate.of(DOWNLOAD_API_URL))
|
||||||
|
.setTemplateParam("authK", getSign.get("0").toString())
|
||||||
|
.setTemplateParam("authV", getSign.get("1").toString())
|
||||||
|
.putHeader("Platform", "web")
|
||||||
|
.putHeader("App-Version", "3")
|
||||||
|
.sendJsonObject(jsonObject).onSuccess(res2 -> {
|
||||||
|
JsonObject downURLJson = asJson(res2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (downURLJson.getInteger("code") != 0) {
|
||||||
|
fail("Ye: downURLJson返回值异常->" + downURLJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
fail("Ye: downURLJson格式异常->" + downURLJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String downURL = downURLJson.getJsonObject("data").getString("DownloadURL");
|
||||||
|
try {
|
||||||
|
Map<String, String> urlParams = CommonUtils.getURLParams(downURL);
|
||||||
|
String params = urlParams.get("params");
|
||||||
|
byte[] decodeByte = Base64.getDecoder().decode(params);
|
||||||
|
String downUrl2 = new String(decodeByte);
|
||||||
|
|
||||||
|
// 获取直链
|
||||||
|
client.getAbs(downUrl2).send().onSuccess(res3 -> {
|
||||||
|
JsonObject res3Json = asJson(res3);
|
||||||
|
try {
|
||||||
|
if (res3Json.getInteger("code") != 0) {
|
||||||
|
fail("Ye: downUrl2返回值异常->" + res3Json);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
fail("Ye: downUrl2格式异常->" + downURLJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.complete(res3Json.getJsonObject("data").getString("redirect_url"));
|
||||||
|
|
||||||
|
}).onFailure(this.handleFail("获取直链失败"));
|
||||||
|
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
fail("urlParams解析异常" + e.getMessage());
|
||||||
|
}
|
||||||
|
}).onFailure(this.handleFail(DOWNLOAD_API_URL));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
package cn.qaiu.lz.common.util;
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
import io.vertx.core.net.impl.URIDecoder;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import javax.crypto.*;
|
import javax.crypto.*;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.*;
|
||||||
import java.security.Key;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HexFormat;
|
import java.util.HexFormat;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AES加解密工具类
|
* AES加解密工具类
|
||||||
@@ -31,7 +37,16 @@ public class AESUtils {
|
|||||||
|
|
||||||
public static final String CIPHER_AES2 = "YbQHZqK/PdQql2+7ATcPQHREAxt0Hn0Ob9v317QirZM=";
|
public static final String CIPHER_AES2 = "YbQHZqK/PdQql2+7ATcPQHREAxt0Hn0Ob9v317QirZM=";
|
||||||
|
|
||||||
|
public static final String CIPHER_AES2_IZ = "1uQFS3sNeHd/bCrmrQpflXREAxt0Hn0Ob9v317QirZM=";
|
||||||
|
|
||||||
public static final String CIPHER_AES0;
|
public static final String CIPHER_AES0;
|
||||||
|
public static final String CIPHER_AES0_IZ;
|
||||||
|
|
||||||
|
|
||||||
|
public static final String MG_PKEY2 = "D8jg+H2iNX94zvHhRLnSM3oy59dH2QQjxQ0GgKJSL+mJclbCcItjV3AmkPY6WcbV4hNQk5+hN2J1eTrxPQqF4p28e3FTsGRCXVN80CLS+XqpFNY/9xuyf2bvbeq5JJU1IBCXgSZmEo8zu0/VGS3YNeDsHtjg92QSrRY8i4A+shihZBSz0/0KOL1VPd/K4tAYvsI9YjVFOI7z9mJJ8Ek8rVUplurJyGkjevRfvReN7xQ67PR+yZovk72yTZKlHDz5jVpLGLOy2iwTTSTbTvtnOi9TSE6sSPtRHv16cxZYZQY=";
|
||||||
|
public static final String MG_PKEY;
|
||||||
|
|
||||||
|
public static final String MG_KEY = "4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 秘钥长度
|
* 秘钥长度
|
||||||
@@ -56,6 +71,8 @@ public class AESUtils {
|
|||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
CIPHER_AES0 = decryptByBase64AES(CIPHER_AES2, CIPHER_AES);
|
CIPHER_AES0 = decryptByBase64AES(CIPHER_AES2, CIPHER_AES);
|
||||||
|
CIPHER_AES0_IZ = decryptByBase64AES(CIPHER_AES2_IZ, CIPHER_AES);
|
||||||
|
MG_PKEY = decryptByBase64AES(MG_PKEY2, CIPHER_AES2);
|
||||||
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException |
|
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException |
|
||||||
InvalidKeyException e) {
|
InvalidKeyException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -151,6 +168,15 @@ public class AESUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String encrypt2HexIz(String source) {
|
||||||
|
try {
|
||||||
|
return encryptHexByAES(source, CIPHER_AES0_IZ);
|
||||||
|
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException |
|
||||||
|
BadPaddingException e) {
|
||||||
|
throw new RuntimeException("加密失败: "+ e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AES解密
|
* AES解密
|
||||||
*
|
*
|
||||||
@@ -211,10 +237,14 @@ public class AESUtils {
|
|||||||
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
|
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
|
||||||
'p', 'q', 'r', 's', 't', 'u', 'L', 'R', 'S', 'I',
|
'p', 'q', 'r', 's', 't', 'u', 'L', 'R', 'S', 'I',
|
||||||
'J', 'K'};
|
'J', 'K'};
|
||||||
|
private static final char[] arrayIz =
|
||||||
private static int decodeChar(char c) {
|
{'Y', 'y', '0', 'Z', 'z', 'N', 'n', 'M', 'I', '6', 'm', 'W', 'w', '1', 'X', 'x', 'L', 'l', 'K', '7', 'k',
|
||||||
for (int i = 0; i < array.length; i++) {
|
'i', 'U', 'u', '2', 'V', 'v', 'J', 'j', '8', 'G', 'g', 'F', 'S', 's', '3', 'T', 't', 'H', 'h',
|
||||||
if (c == array[i]) {
|
'f', 'E', 'e', 'D', 'Q', 'q', '4', 'R', 'r', '9', 'd', 'a', 'C', 'c', 'B', 'O', 'o', '5', 'P',
|
||||||
|
'p', 'b', 'A'};
|
||||||
|
private static int decodeChar(char c, char[] keys) {
|
||||||
|
for (int i = 0; i < keys.length; i++) {
|
||||||
|
if (c == keys[i]) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,21 +253,47 @@ public class AESUtils {
|
|||||||
|
|
||||||
// id解密
|
// id解密
|
||||||
public static int idEncrypt(String str) {
|
public static int idEncrypt(String str) {
|
||||||
|
return idEncrypt0(str, array, 2, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ================================蓝奏优享版Id解密========================================== //
|
||||||
|
public static int idEncryptIz(String str) {
|
||||||
|
// idEncrypt(e) {
|
||||||
|
// let t = 1
|
||||||
|
// , n = 0;
|
||||||
|
// if ("" != e && e.length > 4) {
|
||||||
|
// let r;
|
||||||
|
// e = e.substring(3, e.length - 1);
|
||||||
|
// for (let v = 0; v < e.length; v++)
|
||||||
|
// r = e.charAt(e.length - v - 1),
|
||||||
|
// n += this.decodeChar(r) * t,
|
||||||
|
// t *= 62
|
||||||
|
// }
|
||||||
|
// return n
|
||||||
|
// },
|
||||||
|
|
||||||
|
return idEncrypt0(str, arrayIz, 3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int idEncrypt0(String str, char[] keys, int x1, int x2) {
|
||||||
// 倍数
|
// 倍数
|
||||||
int multiple = 1;
|
int multiple = 1;
|
||||||
int result = 0;
|
int result = 0;
|
||||||
if (StringUtils.isNotEmpty(str) && str.length() > 4) {
|
if (StringUtils.isNotEmpty(str) && str.length() > 4) {
|
||||||
str = str.substring(2, str.length() - 2);
|
str = str.substring(x1, str.length() - x2);
|
||||||
char c;
|
char c;
|
||||||
for (int i = 0; i < str.length(); i++) {
|
for (int i = 0; i < str.length(); i++) {
|
||||||
c = str.charAt(str.length() - i - 1);
|
c = str.charAt(str.length() - i - 1);
|
||||||
result += decodeChar(c) * multiple;
|
result += decodeChar(c, keys) * multiple;
|
||||||
multiple = multiple * 62;
|
multiple = multiple * 62;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ========================== musetransfer加密相关 ===========================
|
// ========================== musetransfer加密相关 ===========================
|
||||||
|
|
||||||
//length用户要求产生字符串的长度
|
//length用户要求产生字符串的长度
|
||||||
@@ -257,5 +313,99 @@ public class AESUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=============================== 123pan加密相关 ===============================
|
||||||
|
|
||||||
|
public static String getMD5Str(String str) {
|
||||||
|
byte[] digest;
|
||||||
|
try {
|
||||||
|
MessageDigest md5 = MessageDigest.getInstance("md5");
|
||||||
|
digest = md5.digest(str.getBytes(StandardCharsets.UTF_8));
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
//16是表示转换为16进制数
|
||||||
|
return new BigInteger(1, digest).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getAuthKey(String _0x2207af) {
|
||||||
|
String _0x467baa = "web";
|
||||||
|
int _0x4965f1 = 3;
|
||||||
|
|
||||||
|
String _0x430930 = String.valueOf(Math.round(0x989680 * Math.random()));
|
||||||
|
String _0x53928f = String.valueOf(new Date().getTime() / 0x3e8);
|
||||||
|
String _0x49ec94 = getMD5Str(_0x53928f + "|" + _0x430930 + "|" + _0x2207af + "|" + _0x467baa + "|" + _0x4965f1
|
||||||
|
+ "|8-8D$sL8gPjom7bk#cY");
|
||||||
|
|
||||||
|
return _0x53928f + "-" + _0x430930 + "-" + _0x49ec94;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String encrypt(String str, String pwd) {
|
||||||
|
if (pwd == null || pwd.length() <= 0) {
|
||||||
|
throw new IllegalArgumentException("Please enter a password with which to encrypt the message.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 prand 值
|
||||||
|
StringBuilder prand = new StringBuilder();
|
||||||
|
for (int i = 0; i < pwd.length(); i++) {
|
||||||
|
prand.append((int) pwd.charAt(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算 sPos, mult, incr, modu
|
||||||
|
int sPos = prand.length() / 5;
|
||||||
|
long mult = Long.parseLong(prand.substring(sPos, sPos + 1) +
|
||||||
|
prand.substring(sPos * 2, sPos * 2 + 1) +
|
||||||
|
prand.substring(sPos * 3, sPos * 3 + 1) +
|
||||||
|
prand.substring(sPos * 4, sPos * 4 + 1) +
|
||||||
|
prand.substring(sPos * 5, sPos * 5 + 1));
|
||||||
|
int incr = (int) Math.ceil(pwd.length() / 2.0);
|
||||||
|
long modu = (long) Math.pow(2, 31) - 1;
|
||||||
|
|
||||||
|
if (mult < 2) {
|
||||||
|
throw new IllegalArgumentException("Algorithm cannot find a suitable hash. Please choose a different password.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 salt 并加到 prand 上
|
||||||
|
long salt = Math.round(Math.random() * 1000000000) % 100000000;
|
||||||
|
prand.append(salt);
|
||||||
|
|
||||||
|
// 使用 BigInteger 处理超过 Long 范围的 prand 值
|
||||||
|
BigInteger prandValue = new BigInteger(prand.toString());
|
||||||
|
while (prandValue.toString().length() > 10) {
|
||||||
|
prandValue = prandValue.divide(BigInteger.TEN).add(prandValue.remainder(BigInteger.TEN));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 prand 转换为 long 进行后续处理
|
||||||
|
prandValue = prandValue.multiply(BigInteger.valueOf(mult)).add(BigInteger.valueOf(incr)).mod(BigInteger.valueOf(modu));
|
||||||
|
|
||||||
|
// 加密过程
|
||||||
|
StringBuilder encStr = new StringBuilder();
|
||||||
|
for (int i = 0; i < str.length(); i++) {
|
||||||
|
// 将字符和异或的结果强制转换为 int
|
||||||
|
int encChr = (int) (str.charAt(i) ^ (int) ((prandValue.doubleValue() / modu) * 255));
|
||||||
|
String hexStr = Integer.toHexString(encChr);
|
||||||
|
if (hexStr.length() < 2) {
|
||||||
|
encStr.append("0");
|
||||||
|
}
|
||||||
|
encStr.append(hexStr);
|
||||||
|
|
||||||
|
prandValue = prandValue.multiply(BigInteger.valueOf(mult)).add(BigInteger.valueOf(incr)).mod(BigInteger.valueOf(modu));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 salt 转换为 16 进制并追加到加密结果
|
||||||
|
String saltHex = Long.toHexString(salt);
|
||||||
|
while (saltHex.length() < 8) {
|
||||||
|
saltHex = "0" + saltHex;
|
||||||
|
}
|
||||||
|
encStr.append(saltHex);
|
||||||
|
|
||||||
|
return encStr.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 示例
|
||||||
|
String encrypted = encrypt("HelloWorld", "password123");
|
||||||
|
System.out.println("Encrypted String: " + encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package cn.qaiu.lz.common.util;
|
package cn.qaiu.util;
|
||||||
|
|
||||||
public class ArrayUtil {
|
public class ArrayUtil {
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package cn.qaiu.lz.common.util;
|
package cn.qaiu.util;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -7,13 +7,23 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class CommonUtils {
|
public class CommonUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分享key 比如: https://www.ilanzou.com/s/xxx -> xxx
|
||||||
|
* @param urlPrefix 不包含key的URL前缀
|
||||||
|
* @param url 完整URL
|
||||||
|
* @return 分享key
|
||||||
|
*/
|
||||||
public static String adaptShortPaths(String urlPrefix, String url) {
|
public static String adaptShortPaths(String urlPrefix, String url) {
|
||||||
if (!url.startsWith(urlPrefix)) {
|
|
||||||
url = urlPrefix + url;
|
|
||||||
}
|
|
||||||
if (url.endsWith(".html")) {
|
if (url.endsWith(".html")) {
|
||||||
url = url.substring(0, url.length() - 5);
|
url = url.substring(0, url.length() - 5);
|
||||||
}
|
}
|
||||||
|
String prefix = "https://";
|
||||||
|
if (!url.startsWith(urlPrefix) && url.startsWith(prefix)) {
|
||||||
|
urlPrefix = urlPrefix.substring(prefix.length());
|
||||||
|
return url.substring(url.indexOf(urlPrefix) + urlPrefix.length());
|
||||||
|
} else if (!url.startsWith(urlPrefix)) {
|
||||||
|
url = urlPrefix + url;
|
||||||
|
}
|
||||||
return url.substring(urlPrefix.length());
|
return url.substring(urlPrefix.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
80
parser/src/main/java/cn/qaiu/util/IpExtractor.java
Normal file
80
parser/src/main/java/cn/qaiu/util/IpExtractor.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.Vertx;
|
||||||
|
import io.vertx.core.http.impl.headers.HeadersMultiMap;
|
||||||
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
import io.vertx.ext.web.client.WebClientSession;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class IpExtractor {
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
|
||||||
|
|
||||||
|
// 创建请求头Map
|
||||||
|
MultiMap headers = new HeadersMultiMap();
|
||||||
|
headers.add("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
|
||||||
|
headers.add("accept-language", "zh-CN,zh;q=0.9,en;q=0.8");
|
||||||
|
headers.add("cache-control", "no-cache");
|
||||||
|
headers.add("dnt", "1");
|
||||||
|
headers.add("origin", "https://ip.ihuan.me");
|
||||||
|
headers.add("pragma", "no-cache");
|
||||||
|
headers.add("priority", "u=0, i");
|
||||||
|
headers.add("referer", "https://ip.ihuan.me/ti.html");
|
||||||
|
headers.add("sec-ch-ua", "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"");
|
||||||
|
headers.add("sec-ch-ua-mobile", "?0");
|
||||||
|
headers.add("sec-ch-ua-platform", "\"Windows\"");
|
||||||
|
headers.add("sec-fetch-dest", "document");
|
||||||
|
headers.add("sec-fetch-mode", "navigate");
|
||||||
|
headers.add("sec-fetch-site", "same-origin");
|
||||||
|
headers.add("sec-fetch-user", "?1");
|
||||||
|
headers.add("upgrade-insecure-requests", "1");
|
||||||
|
headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36");
|
||||||
|
headers.add("Content-Type", "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
|
WebClient client = WebClient.create(Vertx.vertx());
|
||||||
|
WebClientSession webClientSession = WebClientSession.create(client);
|
||||||
|
webClientSession.getAbs("https://ip.ihuan.me").putHeaders(headers).send().onSuccess(res->{
|
||||||
|
System.out.println(res.toString());
|
||||||
|
webClientSession.getAbs("https://ip.ihuan.me").putHeaders(headers).send().onSuccess(res2->{
|
||||||
|
System.out.println(res2.toString());
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//
|
||||||
|
// String htmlContent = "<div class=\"panel-heading\">提取结果</div>\n" +
|
||||||
|
// "<div class=\"panel-body\">111.1.27.85:80<br>42.63.65.46:80<br>118.195.242.20:8080<br>42.63.65.119:80<br>117.50.108.90:7890<br>116.62.50.250:7890<br>114.231.8.177:8089<br>190.43.92.90:999<br>221.178.85.68:8080<br><br>61.160.202.86:80<br>42.63.65.86:80<br>42.63.65.7:80<br>42.63.65.41:80<br>159.226.227.119:80<br>61.160.202.52:80<br>42.63.65.15:80<br>112.17.16.204:80<br>61.160.202.53:80<br>42.63.65.9:80<br>42.63.65.60:80<br>42.63.65.18:80<br>203.190.115.177:8071<br>42.63.65.38:80<br>42.63.65.31:80<br>91.185.3.126:8080<br>139.9.119.20:80<br>1.15.47.213:443<br>183.164.243.108:8089<br>165.225.208.177:80<br>194.163.132.232:3128<br>91.235.220.122:80<br>39.100.120.200:7890<br>141.147.33.121:80<br>183.164.243.138:8089<br>104.129.205.94:54321<br>117.160.250.138:81<br>180.120.213.208:8089<br>61.130.9.37:443<br>182.34.18.206:9999<br>117.86.12.150:8089<br>27.192.173.108:9000<br>183.164.243.11:8089<br>114.231.41.205:8089<br>103.104.233.78:8080<br>183.164.243.174:8089<br>36.6.144.230:8089<br>111.224.213.239:8089<br>182.92.73.106:80<br>36.6.145.81:8089<br>117.69.232.125:8089<br>36.6.144.64:8089<br>117.57.92.20:8089<br>47.100.69.29:8888<br>117.57.93.246:8089<br>120.234.203.171:9002<br>114.231.46.231:8089<br>183.164.242.199:8089<br>117.69.237.179:8089<br>182.44.32.239:7890<br>47.100.91.57:8080<br>117.69.236.127:8089<br>114.231.8.18:8089<br>117.69.232.183:8089<br>117.69.237.29:8089<br>183.164.242.67:8089<br>183.164.242.35:8089<br>183.164.243.71:8089<br>113.223.214.155:8089<br>36.6.145.132:8089<br>182.106.220.252:9091<br>113.223.212.176:8089<br>62.152.53.186:8909<br>117.57.92.16:8089<br>183.164.243.186:8089<br>36.6.144.210:8089<br>183.164.242.189:8089<br>213.178.39.170:8080<br>121.52.145.163:8080<br>36.6.144.240:8089<br>60.188.5.234:80<br>113.223.213.48:8089<br>183.164.243.149:8089<br>200.58.87.195:8080<br>36.6.144.153:8089<br>36.6.144.67:8089<br>36.6.145.182:8089<br>117.57.93.226:8089<br>42.112.24.127:8888<br>43.138.20.156:80<br>117.57.92.79:8089<br>65.109.111.238:3128<br>183.166.137.201:41122<br>113.223.213.150:8089<br>36.6.145.154:8089<br>185.5.209.101:80<br>36.6.144.17:8089<br>114.231.8.244:8089<br>117.69.237.24:8089<br>117.69.236.232:8089<br>117.69.236.127:8089<br>114.231.8.18:8089<br>117.69.232.183:8089<br>117.69.237.29:8089<br>183.164.242.67:8089<br>183.164.242.35:8089<br>183.164.243.71:8089<br>113.223.214.155:8089<br>36.6.145.132:8089<br>182.106.220.252:9091<br>113.223.212.176:8089<br>62.152.53.186:8909<br>117.57.92.16:8089<br>183.164.243.186:8089<br>36.6.144.210:8089<br>183.164.242.189:8089<br>213.178.39.170:8080<br>121.52.145.163:8080<br>36.6.144.240:8089<br>60.188.5.234:80<br>113.223.213.48:8089<br>183.164.243.149:8089<br>200.58.87.195:8080<br>36.6.144.153:8089<br>36.6.144.67:8089<br>36.6.145.182:8089<br>117.57.93.226:8089<br>42.112.24.127:8888<br>43.138.20.156:80<br>117.57.92.79:8089<br>65.109.111.238:3128<br>183.166.137.201:41122<br>113.223.213.150:8089<br>36.6.145.154:8089<br>185.5.209.101:80<br>36.6.144.17:8089<br>114.231.8.244:8089<br>117.69.237.24:8089<br>117.69.236.232:8089</div>";
|
||||||
|
//
|
||||||
|
// // 正则表达式匹配提取结果关键字下面的IP地址
|
||||||
|
// Pattern pattern = Pattern.compile("<div class=\"panel-heading\">提取结果</div>\\s*<div class=\"panel-body\">([\\s\\S]*?)(?=</div>)", Pattern.DOTALL);
|
||||||
|
// Matcher matcher = pattern.matcher(htmlContent);
|
||||||
|
//
|
||||||
|
// if (matcher.find()) {
|
||||||
|
// String ipText = matcher.group(1); // 获取匹配的IP地址部分
|
||||||
|
// Pattern ipPattern = Pattern.compile("(?:[0-9]{1,3}\\.){3}[0-9]{1,3}(?::\\d+)?");
|
||||||
|
// Matcher ipMatcher = ipPattern.matcher(ipText);
|
||||||
|
//
|
||||||
|
// List<String> ips = new ArrayList<>();
|
||||||
|
// while (ipMatcher.find()) {
|
||||||
|
// ips.add(ipMatcher.group());
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// System.out.println("提取到的IP地址:");
|
||||||
|
// for (String ip : ips) {
|
||||||
|
//// System.out.println(ip);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// System.out.println("没有找到匹配的IP地址");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// TimeUnit.SECONDS.sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1057
parser/src/main/java/cn/qaiu/util/JsContent.java
Normal file
1057
parser/src/main/java/cn/qaiu/util/JsContent.java
Normal file
File diff suppressed because it is too large
Load Diff
110
parser/src/main/java/cn/qaiu/util/JsExecUtils.java
Normal file
110
parser/src/main/java/cn/qaiu/util/JsExecUtils.java
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||||
|
|
||||||
|
import javax.script.Invocable;
|
||||||
|
import javax.script.ScriptEngine;
|
||||||
|
import javax.script.ScriptEngineManager;
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static cn.qaiu.util.AESUtils.encrypt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行Js脚本
|
||||||
|
*
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
* @date 2023/7/29 17:35
|
||||||
|
*/
|
||||||
|
public class JsExecUtils {
|
||||||
|
private static final Invocable inv;
|
||||||
|
|
||||||
|
// 初始化脚本引擎
|
||||||
|
static {
|
||||||
|
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||||
|
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
|
||||||
|
|
||||||
|
try {
|
||||||
|
engine.eval(JsContent.ye123);
|
||||||
|
inv = (Invocable) engine;
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用js文件
|
||||||
|
*/
|
||||||
|
public static ScriptObjectMirror executeJs(String functionName, Object... args) throws ScriptException,
|
||||||
|
NoSuchMethodException {
|
||||||
|
//调用js中的函数
|
||||||
|
return (ScriptObjectMirror) inv.invokeFunction(functionName, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用执行蓝奏云js文件
|
||||||
|
*/
|
||||||
|
public static ScriptObjectMirror executeDynamicJs(String jsText, String funName) throws ScriptException,
|
||||||
|
NoSuchMethodException {
|
||||||
|
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||||
|
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
|
||||||
|
engine.eval(JsContent.lz + "\n" + jsText);
|
||||||
|
Invocable inv = (Invocable) engine;
|
||||||
|
//调用js中的函数
|
||||||
|
if (StringUtils.isNotEmpty(funName)) {
|
||||||
|
inv.invokeFunction(funName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ScriptObjectMirror) engine.get("signObj");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用执行蓝奏云js文件
|
||||||
|
*/
|
||||||
|
public static Object executeOtherJs(String jsText, String funName, Object ... args) throws ScriptException,
|
||||||
|
NoSuchMethodException {
|
||||||
|
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||||
|
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
|
||||||
|
engine.eval(jsText);
|
||||||
|
Invocable inv = (Invocable) engine;
|
||||||
|
//调用js中的函数
|
||||||
|
if (StringUtils.isNotEmpty(funName)) {
|
||||||
|
return inv.invokeFunction(funName, args);
|
||||||
|
}
|
||||||
|
throw new ScriptException("funName is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getKwSign(String s, String pwd) {
|
||||||
|
try {
|
||||||
|
return executeOtherJs(JsContent.kwSignString, "encrypt", s, pwd).toString();
|
||||||
|
} catch (ScriptException | NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String mgEncData(String data, String key) {
|
||||||
|
try {
|
||||||
|
return executeOtherJs(JsContent.mgJS, "enc", data, key).toString();
|
||||||
|
} catch (ScriptException | NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// return OM.AES.encrypt('{"copyrightId":"6326951FKBL","type":1,"auditionsFlag":0}',
|
||||||
|
// '4ea5c508a6566e76240543f8feb06fd457777be39549c4016436afda65d2330e').toString()
|
||||||
|
//
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(URLEncoder
|
||||||
|
.encode(mgEncData("{\"copyrightId\":\"6326951FKBL\",\"type\":1,\"auditionsFlag\":0}", AESUtils.MG_KEY), StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
// U2FsdGVkX1/UiZC91ImQvQY7qDBSEbTykAcVoARiquibPCZhWSs3kWQw3j2PNme5wNLqt2oau498ni1hgjGFuxwORnkk6x9rzk/X0arElUo=
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package cn.qaiu.lz.common.util;
|
package cn.qaiu.util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
22
parser/src/main/java/cn/qaiu/util/RandomStringGenerator.java
Normal file
22
parser/src/main/java/cn/qaiu/util/RandomStringGenerator.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public class RandomStringGenerator {
|
||||||
|
private static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
private static final int LENGTH = 13; // 每段长度为13
|
||||||
|
|
||||||
|
public static String generateRandomString() {
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) { // 生成两段
|
||||||
|
for (int j = 0; j < LENGTH; j++) {
|
||||||
|
int index = random.nextInt(CHARACTERS.length());
|
||||||
|
sb.append(CHARACTERS.charAt(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
74
parser/src/main/java/cn/qaiu/util/ReqIpUtil.java
Normal file
74
parser/src/main/java/cn/qaiu/util/ReqIpUtil.java
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
import io.vertx.core.AsyncResult;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.Vertx;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.http.impl.headers.HeadersMultiMap;
|
||||||
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
import io.vertx.ext.web.client.WebClientSession;
|
||||||
|
|
||||||
|
public class ReqIpUtil {
|
||||||
|
public static String BASE_URL = "https://ip.ihuan.me";
|
||||||
|
public static String BASE_URL_TEMPLATE = BASE_URL + "/{path}";
|
||||||
|
|
||||||
|
// GET https://ip.ihuan.me/mouse.do -> $("input[name='key']").val("30b4975b5547fed806bd2b9caa18485a");
|
||||||
|
public static String PATH1 = "mouse.do";
|
||||||
|
|
||||||
|
public static String PATH2 = "tqdl.html";
|
||||||
|
|
||||||
|
// 创建请求头Map
|
||||||
|
static MultiMap headers = new HeadersMultiMap();
|
||||||
|
|
||||||
|
static {
|
||||||
|
|
||||||
|
headers.set("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
|
||||||
|
headers.set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8");
|
||||||
|
headers.set("cache-control", "no-cache");
|
||||||
|
headers.set("dnt", "1");
|
||||||
|
headers.set("origin", "https://ip.ihuan.me");
|
||||||
|
headers.set("pragma", "no-cache");
|
||||||
|
headers.set("priority", "u=0, i");
|
||||||
|
headers.set("referer", "https://ip.ihuan.me");
|
||||||
|
headers.set("sec-ch-ua", "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"");
|
||||||
|
headers.set("sec-ch-ua-mobile", "?0");
|
||||||
|
headers.set("sec-ch-ua-platform", "\"Windows\"");
|
||||||
|
headers.set("sec-fetch-dest", "document");
|
||||||
|
headers.set("sec-fetch-mode", "navigate");
|
||||||
|
headers.set("sec-fetch-site", "same-origin");
|
||||||
|
headers.set("sec-fetch-user", "?1");
|
||||||
|
headers.set("upgrade-insecure-requests", "1");
|
||||||
|
// headers.set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClient webClient = WebClient.create(vertx);
|
||||||
|
// 发送GET请求
|
||||||
|
WebClientSession webClientSession = WebClientSession.create(webClient);
|
||||||
|
|
||||||
|
|
||||||
|
public void exec() {
|
||||||
|
webClientSession.getAbs(BASE_URL)
|
||||||
|
.putHeaders(headers) // 将请求头Map添加到请求中
|
||||||
|
.send(this::next);
|
||||||
|
}
|
||||||
|
|
||||||
|
void next(AsyncResult<HttpResponse<Buffer>> response) {
|
||||||
|
if (response.failed()) {
|
||||||
|
response.cause().printStackTrace();
|
||||||
|
} else {
|
||||||
|
HttpResponse<Buffer> res = response.result();
|
||||||
|
System.out.println("Received response with status code " + res.statusCode());
|
||||||
|
System.out.println("Body: " + res.body());
|
||||||
|
webClientSession.getAbs(BASE_URL_TEMPLATE).setTemplateParam("path", PATH1)
|
||||||
|
.putHeaders(headers) // 将请求头Map添加到请求中
|
||||||
|
.send(response2 -> {
|
||||||
|
System.out.println(response2.result().bodyAsString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
36
parser/src/main/java/cn/qaiu/util/StringUtils.java
Normal file
36
parser/src/main/java/cn/qaiu/util/StringUtils.java
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
public class StringUtils {
|
||||||
|
|
||||||
|
// 非贪婪截断匹配
|
||||||
|
public static String StringCutNot(final String strtarget, final String strstart)
|
||||||
|
{
|
||||||
|
int startIdx = strtarget.indexOf(strstart);
|
||||||
|
|
||||||
|
if (startIdx != -1) {
|
||||||
|
startIdx += strstart.length();
|
||||||
|
return strtarget.substring(startIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非贪婪截断匹配
|
||||||
|
public static String StringCutNot(final String strtarget, final String strstart, final String strend)
|
||||||
|
{
|
||||||
|
int startIdx = strtarget.indexOf(strstart);
|
||||||
|
int endIdx = -1;
|
||||||
|
|
||||||
|
if (startIdx != -1) {
|
||||||
|
startIdx += strstart.length();
|
||||||
|
endIdx = strtarget.indexOf(strend, startIdx);
|
||||||
|
|
||||||
|
if (endIdx != -1) {
|
||||||
|
return strtarget.substring(startIdx, endIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
47
parser/src/main/java/cn/qaiu/util/URLUtil.java
Normal file
47
parser/src/main/java/cn/qaiu/util/URLUtil.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class URLUtil {
|
||||||
|
|
||||||
|
private final Map<String, String> queryParams = new HashMap<>();
|
||||||
|
|
||||||
|
// 构造函数,传入URL并解析参数
|
||||||
|
private URLUtil(String url) {
|
||||||
|
try {
|
||||||
|
URL parsedUrl = new URL(url);
|
||||||
|
String ref = parsedUrl.getRef();
|
||||||
|
if (StringUtils.isNotEmpty(ref)) {
|
||||||
|
parsedUrl = new URL(parsedUrl.getProtocol() + "://" + parsedUrl.getHost() + ref);
|
||||||
|
}
|
||||||
|
String query = parsedUrl.getQuery();
|
||||||
|
if (query != null) {
|
||||||
|
String[] pairs = query.split("&");
|
||||||
|
for (String pair : pairs) {
|
||||||
|
String[] keyValue = pair.split("=");
|
||||||
|
String key = URLDecoder.decode(keyValue[0], StandardCharsets.UTF_8);
|
||||||
|
String value = keyValue.length > 1 ? URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8) : "";
|
||||||
|
queryParams.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态方法,用于创建UrlUtil实例
|
||||||
|
public static URLUtil from(String url) {
|
||||||
|
return new URLUtil(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取参数的方法
|
||||||
|
public String getParam(String param) {
|
||||||
|
return queryParams.get(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
parser/src/main/java/cn/qaiu/util/UUIDUtil.java
Normal file
35
parser/src/main/java/cn/qaiu/util/UUIDUtil.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
|
* @date 2024/5/13 4:10
|
||||||
|
*/
|
||||||
|
public class UUIDUtil {
|
||||||
|
|
||||||
|
public static String fjUuid() {
|
||||||
|
return generateRandomString(21);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generateRandomString(int length) {
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
byte[] randomBytes = new byte[length];
|
||||||
|
random.nextBytes(randomBytes);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : randomBytes) {
|
||||||
|
int value = b & 0x3F; // 63 in hexadecimal
|
||||||
|
if (value < 36) {
|
||||||
|
sb.append(Integer.toString(value, 36));
|
||||||
|
} else if (value < 62) {
|
||||||
|
sb.append(Character.toUpperCase(Integer.toString(value - 26, 36).charAt(0)));
|
||||||
|
} else if (value > 62) {
|
||||||
|
sb.append("-");
|
||||||
|
} else { // value == 62 or 63
|
||||||
|
sb.append("_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
46
parser/src/main/resources/js/lz.js
Normal file
46
parser/src/main/resources/js/lz.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* 蓝奏云解析器js签名获取工具
|
||||||
|
*/
|
||||||
|
|
||||||
|
var signObj;
|
||||||
|
|
||||||
|
|
||||||
|
var $, jQuery;
|
||||||
|
|
||||||
|
$ = jQuery = function () {
|
||||||
|
return new jQuery.fn.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery.fn = jQuery.prototype = {
|
||||||
|
init: function () {
|
||||||
|
return {
|
||||||
|
focus: function (a) {
|
||||||
|
|
||||||
|
},
|
||||||
|
keyup: function(a) {
|
||||||
|
|
||||||
|
},
|
||||||
|
ajax: function (obj) {
|
||||||
|
signObj = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery.fn.init.prototype = jQuery.fn;
|
||||||
|
|
||||||
|
|
||||||
|
// 伪装jquery.ajax函数获取关键数据
|
||||||
|
$.ajax = function (obj) {
|
||||||
|
signObj = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
var document = {
|
||||||
|
getElementById: function (v) {
|
||||||
|
return {
|
||||||
|
value: 'v'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
95
parser/src/main/resources/js/ye123.js
Normal file
95
parser/src/main/resources/js/ye123.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
https://statics.123pan.com/share-static/dist/umi.fb72555e.js
|
||||||
|
eaefamemdead
|
||||||
|
eaefameidldy
|
||||||
|
_0x4f141a(1690439821|5790548|/b/api/share/download/info|web|3|1946841013) = 秘钥
|
||||||
|
|
||||||
|
_0x1e2592 1690439821 时间戳
|
||||||
|
_0x48562f 5790548 随机码
|
||||||
|
_0x1e37d5 /b/api/share/download/info
|
||||||
|
_0x4e2d74 web
|
||||||
|
_0x56f040 3
|
||||||
|
_0x43bdc6 1946841013 加密时间HASH戳
|
||||||
|
|
||||||
|
>>>>
|
||||||
|
_0x43bdc6=''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)
|
||||||
|
加密时间HASH戳 = 时间戳-随机码-秘钥
|
||||||
|
*/
|
||||||
|
|
||||||
|
function _0x1b5d95(_0x278d1a) {
|
||||||
|
var _0x839b57,
|
||||||
|
_0x4ed4dc = arguments['length'] > 0x2 && void 0x0 !== arguments[0x2] ? arguments[0x2] : 0x8;
|
||||||
|
if (0x0 === arguments['length'])
|
||||||
|
return null;
|
||||||
|
'object' === typeof _0x278d1a ? _0x839b57 = _0x278d1a : (0xa === ('' + _0x278d1a)['length'] && (_0x278d1a = 0x3e8 * parseInt(_0x278d1a)),
|
||||||
|
_0x839b57 = new Date(_0x278d1a));
|
||||||
|
var _0xc5c54a = _0x278d1a + 0xea60 * new Date(_0x278d1a)['getTimezoneOffset']()
|
||||||
|
, _0x3732dc = _0xc5c54a + 0x36ee80 * _0x4ed4dc;
|
||||||
|
return _0x839b57 = new Date(_0x3732dc),
|
||||||
|
{
|
||||||
|
'y': _0x839b57['getFullYear'](),
|
||||||
|
'm': _0x839b57['getMonth']() + 0x1 < 0xa ? '0' + (_0x839b57['getMonth']() + 0x1) : _0x839b57['getMonth']() + 0x1,
|
||||||
|
'd': _0x839b57['getDate']() < 0xa ? '0' + _0x839b57['getDate']() : _0x839b57['getDate'](),
|
||||||
|
'h': _0x839b57['getHours']() < 0xa ? '0' + _0x839b57['getHours']() : _0x839b57['getHours'](),
|
||||||
|
'f': _0x839b57['getMinutes']() < 0xa ? '0' + _0x839b57['getMinutes']() : _0x839b57['getMinutes']()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _0x4f141a(_0x4075b1) {
|
||||||
|
|
||||||
|
for (var _0x4eddcb = arguments['length'] > 0x1 && void 0x0 !== arguments[0x1] ? arguments[0x1] : 0xa,
|
||||||
|
_0x2fc680 = function() {
|
||||||
|
for (var _0x515c63, _0x361314 = [], _0x4cbdba = 0x0; _0x4cbdba < 0x100; _0x4cbdba++) {
|
||||||
|
_0x515c63 = _0x4cbdba;
|
||||||
|
for (var _0x460960 = 0x0; _0x460960 < 0x8; _0x460960++)
|
||||||
|
_0x515c63 = 0x1 & _0x515c63 ? 0xedb88320 ^ _0x515c63 >>> 0x1 : _0x515c63 >>> 0x1;
|
||||||
|
_0x361314[_0x4cbdba] = _0x515c63;
|
||||||
|
}
|
||||||
|
return _0x361314;
|
||||||
|
},
|
||||||
|
_0x4aed86 = _0x2fc680(),
|
||||||
|
_0x5880f0 = _0x4075b1,
|
||||||
|
_0x492393 = -0x1, _0x25d82c = 0x0;
|
||||||
|
_0x25d82c < _0x5880f0['length'];
|
||||||
|
_0x25d82c++)
|
||||||
|
|
||||||
|
_0x492393 = _0x492393 >>> 0x8 ^ _0x4aed86[0xff & (_0x492393 ^ _0x5880f0.charCodeAt(_0x25d82c))];
|
||||||
|
return _0x492393 = (-0x1 ^ _0x492393) >>> 0x0,
|
||||||
|
_0x492393.toString(_0x4eddcb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getSign(_0x1e37d5) {
|
||||||
|
var _0x4e2d74 = 'web';
|
||||||
|
var _0x56f040 = 3;
|
||||||
|
var _0x1e2592 = Math.round((new Date().getTime() + 0x3c * new Date().getTimezoneOffset() * 0x3e8 + 28800000) / 0x3e8).toString();
|
||||||
|
var key = 'a,d,e,f,g,h,l,m,y,i,j,n,o,p,k,q,r,s,t,u,b,c,v,w,s,z';
|
||||||
|
var _0x48562f = Math['round'](0x989680 * Math['random']());
|
||||||
|
|
||||||
|
var _0x2f7dfc;
|
||||||
|
var _0x35a889;
|
||||||
|
var _0x36f983;
|
||||||
|
var _0x3b043d;
|
||||||
|
var _0x5bc73b;
|
||||||
|
var _0x4b30b2;
|
||||||
|
var _0x32399e;
|
||||||
|
var _0x25d94e;
|
||||||
|
var _0x373490;
|
||||||
|
for (var _0x1c540f in (_0x2f7dfc = key.split(','),
|
||||||
|
_0x35a889 = _0x1b5d95(_0x1e2592),
|
||||||
|
_0x36f983 = _0x35a889['y'],
|
||||||
|
_0x3b043d = _0x35a889['m'],
|
||||||
|
_0x5bc73b = _0x35a889['d'],
|
||||||
|
_0x4b30b2 = _0x35a889['h'],
|
||||||
|
_0x32399e = _0x35a889['f'],
|
||||||
|
_0x25d94e = [_0x36f983, _0x3b043d, _0x5bc73b, _0x4b30b2, _0x32399e].join(''),
|
||||||
|
_0x373490 = [],
|
||||||
|
_0x25d94e))
|
||||||
|
_0x373490['push'](_0x2f7dfc[Number(_0x25d94e[_0x1c540f])]);
|
||||||
|
var _0x43bdc6;
|
||||||
|
var _0x406c4e;
|
||||||
|
return _0x43bdc6 = _0x4f141a(_0x373490['join']('')),
|
||||||
|
_0x406c4e = _0x4f141a(''['concat'](_0x1e2592, '|')['concat'](_0x48562f, '|')['concat'](_0x1e37d5, '|')['concat'](_0x4e2d74, '|')['concat'](_0x56f040, '|')['concat'](_0x43bdc6)),
|
||||||
|
[_0x43bdc6, ''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)];
|
||||||
|
}
|
||||||
61
parser/src/main/resources/logback.xml
Normal file
61
parser/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||||
|
<!-- 日志自定义颜色 -->
|
||||||
|
<!-- https://logback.qos.ch/manual/layouts.html#coloring -->
|
||||||
|
|
||||||
|
<!--日志文件主目录:这里${user.home}为当前服务器用户主目录-->
|
||||||
|
<property name="LOG_HOME" value="logs"/>
|
||||||
|
<!--日志文件名称:这里spring.application.name表示工程名称-->
|
||||||
|
|
||||||
|
<property name="LOGBACK_DEFAULT" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
|
||||||
|
<property name="CUSTOMER_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%t]) %highlight(%-5p) %yellow(${PID:-}) %cyan(%-40.40logger{39}) : %green(%m%n)"/>
|
||||||
|
<property name="CUSTOMER_PATTERN2" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) -> %magenta([%15.15thread]) %cyan(%-40.40logger{39}) : %msg%n"/>
|
||||||
|
|
||||||
|
<!--配置日志文件(File)-->
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<!--设置策略-->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!--日志文件路径:这里%d{yyyyMMdd}表示按天分类日志-->
|
||||||
|
<FileNamePattern>${LOG_HOME}/%d{yyyyMMdd}/run.log</FileNamePattern>
|
||||||
|
<!--日志保留天数-->
|
||||||
|
<MaxHistory>15</MaxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<!--设置格式-->
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
||||||
|
<!-- 或者使用默认配置 -->
|
||||||
|
<!--<pattern>${FILE_LOG_PATTERN}</pattern>-->
|
||||||
|
<charset>utf8</charset>
|
||||||
|
</encoder>
|
||||||
|
<!--日志文件最大的大小-->
|
||||||
|
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
|
||||||
|
<MaxFileSize>100MB</MaxFileSize>
|
||||||
|
</triggeringPolicy>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 将文件输出设置成异步输出 -->
|
||||||
|
<appender name="ASYNC-FILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
|
||||||
|
<discardingThreshold>0</discardingThreshold>
|
||||||
|
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
|
||||||
|
<queueSize>256</queueSize>
|
||||||
|
<!-- 添加附加的appender,最多只能添加一个 -->
|
||||||
|
<appender-ref ref="FILE"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||||
|
<pattern>${CUSTOMER_PATTERN2}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<logger name="io.netty" level="warn"/>
|
||||||
|
<logger name="io.vertx" level="info"/>
|
||||||
|
<logger name="com.zaxxer.hikari" level="info"/>
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
<!-- <appender-ref ref="FILE"/>-->
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user