1. 添加缓存

2. 优化解析架构
3. 优化核心模块
This commit is contained in:
qaiu
2024-09-15 06:53:11 +08:00
parent 5fce02e623
commit f886f7e366
40 changed files with 1056 additions and 409 deletions

View File

@@ -2,6 +2,7 @@ package cn.qaiu.lz;
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.db.pool.JDBCPoolInit;
import cn.qaiu.lz.common.cache.CacheConfigLoader;
import cn.qaiu.vx.core.Deploy;
import cn.qaiu.vx.core.util.ConfigConstant;
import cn.qaiu.vx.core.util.VertxHolder;
@@ -23,17 +24,20 @@ public class AppMain {
}
/**
* 初始化数据库
* 初始化数据库/缓存等
*
* @param jsonObject 配置
*/
private static void exec(JsonObject jsonObject) {
WebClientVertxInit.init(VertxHolder.getVertxInstance());
DatabindCodec.mapper().registerModule(new JavaTimeModule());
// 数据库
if (jsonObject.getJsonObject(ConfigConstant.SERVER).getBoolean("enableDatabase")) {
JDBCPoolInit.builder().config(jsonObject.getJsonObject("dataSource")).build().initPool();
}
// 缓存
if (jsonObject.containsKey(ConfigConstant.CACHE)) {
CacheConfigLoader.init(jsonObject.getJsonObject(ConfigConstant.CACHE));
}
}
}

View File

@@ -0,0 +1,37 @@
package cn.qaiu.lz.common.cache;
import cn.qaiu.parser.PanDomainTemplate;
import io.vertx.core.json.JsonObject;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/12 7:38
*/
public class CacheConfigLoader {
private static final Map<String, Integer> CONFIGS = new HashMap<>();
public static String TYPE;
public static Integer DEFAULT_DURATION;
public static void init(JsonObject config) {
TYPE = config.getString("type");
Integer defaultDuration = config.getInteger("defaultDuration");
DEFAULT_DURATION = defaultDuration == null ? 60 : defaultDuration;
config.getJsonObject("duration").getMap().forEach((k,v) -> {
if (v == null) {
CONFIGS.put(k, DEFAULT_DURATION);
} else {
CONFIGS.put(k, (Integer) v);
}
});
}
public static Integer getDuration(PanDomainTemplate pdt) {
return CONFIGS.get(pdt.getShortName());
}
public static Integer getDuration(String shortName) {
return CONFIGS.get(shortName);
}
}

View File

@@ -0,0 +1,52 @@
package cn.qaiu.lz.common.cache;
import cn.qaiu.db.pool.JDBCPoolInit;
import cn.qaiu.lz.web.model.CacheLinkInfo;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.jdbcclient.JDBCPool;
import io.vertx.sqlclient.templates.SqlTemplate;
import java.util.HashMap;
import java.util.Map;
public class CacheManager {
private final JDBCPool jdbcPool = JDBCPoolInit.instance().getPool();
public Future<CacheLinkInfo> get(String cacheKey) {
String sql = "SELECT direct_link as directLink, expiration FROM cache_link_info WHERE share_key = #{share_key}";
Map<String, Object> params = new HashMap<>();
params.put("share_key", cacheKey);
Promise<CacheLinkInfo> promise = Promise.promise();
SqlTemplate.forQuery(jdbcPool, sql)
.mapTo(CacheLinkInfo.class)
.execute(params)
.onSuccess(rows->{
CacheLinkInfo cacheHit;
if (rows.size() > 0) {
cacheHit = rows.iterator().next();
cacheHit.setCacheHit(true);
} else {
cacheHit = new CacheLinkInfo(JsonObject.of("cacheHit", false));
}
promise.complete(cacheHit);
}).onFailure(Throwable::printStackTrace);
return promise.future();
}
// 插入或更新缓存数据
public Future<Void> cacheShareLink(CacheLinkInfo cacheLinkInfo) {
String sql = "MERGE INTO cache_link_info (share_key, direct_link, expiration) " +
"KEY (share_key) " +
"VALUES (#{shareKey}, #{directLink}, #{expiration})";
// 直接传递 CacheLinkInfo 实体类
return SqlTemplate.forUpdate(jdbcPool, sql)
.mapFrom(CacheLinkInfo.class) // 将实体类映射为 Tuple 参数
.execute(cacheLinkInfo)
.mapEmpty();
}
}

View File

@@ -0,0 +1,76 @@
package cn.qaiu.lz.common.util;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpServerRequest;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
/**
* 处理URL截断问题拼接被截断的参数特殊处理pwd参数。
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/13
*/
public class URLParamUtil {
/**
* 解析并处理截断的URL参数
*
* @param request HttpServerRequest对象
* @return 完整的URL字符串
*/
public static String parserParams(HttpServerRequest request) {
String url = request.absoluteURI();
MultiMap params = request.params();
// 处理URL截断的情况例如: url='https://...&key=...&code=...'
if (params.contains("url")) {
String encodedUrl = params.get("url");
url = handleTruncatedUrl(encodedUrl, params);
}
return url;
}
/**
* 处理被截断的URL拼接所有参数特殊处理pwd参数。
*
* @param encodedUrl 被截断的url参数
* @param params 请求的其他参数
* @return 重新拼接后的完整URL
*/
private static String handleTruncatedUrl(String encodedUrl, MultiMap params) {
// 对URL进行解码以便获取完整的URL
String decodedUrl = URLDecoder.decode(encodedUrl, StandardCharsets.UTF_8);
// 如果URL已经包含查询参数不需要额外拼接
if (params.contains("pwd")) {
if (params.size() == 2) {
return decodedUrl;
}
} else {
if (params.size() == 1) {
return decodedUrl;
}
}
// 拼接被截断的URL参数忽略pwd参数
StringBuilder urlBuilder = new StringBuilder(decodedUrl);
boolean firstParam = !decodedUrl.contains("?");
for (String paramName : params.names()) {
if (!paramName.equals("url") && !paramName.equals("pwd")) { // 忽略 "url" 和 "pwd" 参数
if (firstParam) {
urlBuilder.append("?");
firstParam = false;
} else {
urlBuilder.append("&");
}
urlBuilder.append(paramName).append("=").append(params.get(paramName));
}
}
return urlBuilder.toString();
}
}

View File

@@ -1,13 +1,13 @@
package cn.qaiu.lz.web.http;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.impl.EcTool;
import cn.qaiu.parser.impl.QQTool;
import cn.qaiu.lz.common.util.URLParamUtil;
import cn.qaiu.lz.web.service.CacheService;
import cn.qaiu.parser.PanDomainTemplate;
import cn.qaiu.vx.core.annotaions.RouteHandler;
import cn.qaiu.vx.core.annotaions.RouteMapping;
import cn.qaiu.vx.core.enums.RouteMethod;
import cn.qaiu.vx.core.util.AsyncServiceUtil;
import cn.qaiu.vx.core.util.ResponseUtil;
import cn.qaiu.vx.core.util.VertxHolder;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServerRequest;
@@ -24,33 +24,27 @@ import lombok.extern.slf4j.Slf4j;
@RouteHandler("/")
public class ServerApi {
@RouteMapping(value = "/parser", method = RouteMethod.GET, order = 4)
public Future<Void> parse(HttpServerResponse response, HttpServerRequest request, String url, String pwd) {
private final CacheService cacheService = AsyncServiceUtil.getAsyncServiceInstance(CacheService.class);
@RouteMapping(value = "/parser", method = RouteMethod.GET, order = 4)
public Future<Void> parse(HttpServerResponse response, HttpServerRequest request, String pwd) {
Promise<Void> promise = Promise.promise();
if (url.contains(EcTool.SHARE_URL_PREFIX)) {
// 默认读取Url参数会被截断手动获取一下其他参数
url = EcTool.SHARE_URL_PREFIX + request.getParam("data");
}
if (url.contains(QQTool.SHARE_URL_PREFIX)) {
// 默认读取Url参数会被截断手动获取一下其他参数
url = url + "&key=" + request.getParam("key") +
"&code=" + request.getParam("code") + "&k=" + request.getParam("k") +
"&fweb=" + request.getParam("fweb") + "&cl=" + request.getParam("cl");
}
IPanTool.shareURLPrefixMatching(url, pwd).parse()
.onSuccess(resUrl -> ResponseUtil.redirect(response, resUrl, promise))
String url = URLParamUtil.parserParams(request);
PanDomainTemplate panDomainTemplate = PanDomainTemplate.fromShareUrl(url).setShareLinkInfoPwd(pwd);
cacheService.getAndSaveCachedShareLink(panDomainTemplate)
.onSuccess(res -> ResponseUtil.redirect(
response.putHeader("nfd-cache-hit", res.getCacheHit().toString())
.putHeader("nfd-cache-expires", res.getExpires()),
res.getDirectLink(), promise))
.onFailure(t -> promise.fail(t.fillInStackTrace()));
return promise.future();
}
@RouteMapping(value = "/json/parser", method = RouteMethod.GET, order = 3)
public Future<String> parseJson(HttpServerRequest request, String url, String pwd) {
if (url.contains(EcTool.SHARE_URL_PREFIX)) {
// 默认读取Url参数会被截断手动获取一下其他参数
url = EcTool.SHARE_URL_PREFIX + request.getParam("data");
}
return IPanTool.shareURLPrefixMatching(url, pwd).parse();
public Future<String> parseJson(HttpServerRequest request, String pwd) {
String url = URLParamUtil.parserParams(request);
return PanDomainTemplate.fromShareUrl(url).setShareLinkInfoPwd(pwd).createTool().parse();
}
@RouteMapping(value = "/json/:type/:key", method = RouteMethod.GET, order = 2)
@@ -61,7 +55,8 @@ public class ServerApi {
key = keys[0];
code = keys[1];
}
return IPanTool.typeMatching(type, key, code).parse();
return PanDomainTemplate.fromShortName(type)
.generateShareLink(key).setShareLinkInfoPwd(code).createTool().parse();
}
@RouteMapping(value = "/:type/:key", method = RouteMethod.GET, order = 1)
@@ -74,8 +69,16 @@ public class ServerApi {
code = keys[1];
}
IPanTool.typeMatching(type, key, code).parse()
.onSuccess(resUrl -> ResponseUtil.redirect(response, resUrl, promise))
PanDomainTemplate panDomainTemplate = PanDomainTemplate
.fromShortName(type)
.generateShareLink(key)
.setShareLinkInfoPwd(code);
cacheService.getAndSaveCachedShareLink(panDomainTemplate)
.onSuccess(res -> ResponseUtil.redirect(
response.putHeader("nfd-cache-hit", res.getCacheHit().toString())
.putHeader("nfd-cache-expires", res.getExpires()),
res.getDirectLink(), promise))
.onFailure(t -> promise.fail(t.fillInStackTrace()));
return promise.future();
}

View File

@@ -0,0 +1,56 @@
package cn.qaiu.lz.web.model;
import cn.qaiu.db.ddl.Length;
import cn.qaiu.db.ddl.Table;
import cn.qaiu.db.ddl.TableGenIgnore;
import cn.qaiu.lz.common.ToJson;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.json.JsonObject;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/11 16:06
*/
@Table(value = "cache_link_info", keyFields = "share_key")
@Data
@DataObject
@NoArgsConstructor
public class CacheLinkInfo implements ToJson {
/**
* 缓存key: type@ShareKey; e.g. lz@xxxx
*/
@Length(varcharSize = 4096)
private String shareKey;
/**
* 解析后的直链
*/
@Length(varcharSize = 4096)
private String directLink;
/**
* 是否命中缓存
*/
@TableGenIgnore
private Boolean cacheHit;
/**
* 到期时间 yyyy-MM-dd hh:mm:ss
*/
@TableGenIgnore
private String expires;
/**
* 有效期
*/
private Long expiration;
// 使用 JsonObject 构造
public CacheLinkInfo(JsonObject json) {
CacheLinkInfoConverter.fromJson(json, this);
}
}

View File

@@ -0,0 +1,23 @@
package cn.qaiu.lz.web.model;
import io.vertx.core.json.JsonObject;
// CacheLinkInfoConverter.java
public class CacheLinkInfoConverter {
public static void fromJson(JsonObject json, CacheLinkInfo obj) {
if (json.containsKey("shareKey")) {
obj.setShareKey(json.getString("shareKey"));
}
if (json.containsKey("directLink")) {
obj.setDirectLink(json.getString("directLink"));
}
if (json.containsKey("expires")) {
obj.setExpires(json.getString("expires"));
}
if (json.containsKey("expiration")) {
obj.setExpiration(json.getLong("expiration"));
}
obj.setCacheHit(json.getBoolean("cacheHit", false));
}
}

View File

@@ -0,0 +1,17 @@
package cn.qaiu.lz.web.service;
import cn.qaiu.lz.web.model.CacheLinkInfo;
import cn.qaiu.parser.PanDomainTemplate;
import cn.qaiu.vx.core.base.BaseAsyncService;
import io.vertx.codegen.annotations.ProxyGen;
import io.vertx.core.Future;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/12 8:26
*/
@ProxyGen
public interface CacheService extends BaseAsyncService {
Future<CacheLinkInfo> getAndSaveCachedShareLink(PanDomainTemplate shareLinkInfo);
}

View File

@@ -19,4 +19,5 @@ public interface DbService extends BaseAsyncService {
Future<JsonObject> sayOk2(String data, UserInfo holder);
Future<StatisticsInfo> getStatisticsInfo();
}

View File

@@ -1,18 +0,0 @@
package cn.qaiu.lz.web.service;
import cn.qaiu.vx.core.util.CastUtil;
import java.lang.reflect.Proxy;
/**
* JDK代理类工厂
*/
public class JdkProxyFactory {
public static <T> T getProxy(T target) {
return CastUtil.cast(Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new ServiceJdkProxy<>(target))
);
}
}

View File

@@ -0,0 +1,65 @@
package cn.qaiu.lz.web.service.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.lz.common.cache.CacheConfigLoader;
import cn.qaiu.lz.common.cache.CacheManager;
import cn.qaiu.lz.web.model.CacheLinkInfo;
import cn.qaiu.lz.web.service.CacheService;
import cn.qaiu.parser.PanDomainTemplate;
import cn.qaiu.vx.core.annotaions.Service;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.util.Date;
@Service
public class CacheServiceImpl implements CacheService {
private final CacheManager cacheManager = new CacheManager();
@Override
public Future<CacheLinkInfo> getAndSaveCachedShareLink(PanDomainTemplate template) {
Promise<CacheLinkInfo> promise = Promise.promise();
// 构建组合的缓存key
ShareLinkInfo shareLinkInfo = template.getShareLinkInfo();
String cacheKey = generateCacheKey(shareLinkInfo.getType(), shareLinkInfo.getShareKey());
// 尝试从缓存中获取
cacheManager.get(cacheKey).onSuccess(result -> {
// 判断是否已过期
// 未命中或者过期
if (!result.getCacheHit() || result.getExpiration() < System.currentTimeMillis()) {
template.createTool().parse().onSuccess(redirectUrl -> {
long expires = System.currentTimeMillis() +
CacheConfigLoader.getDuration(shareLinkInfo.getType()) * 60 * 1000;
result.setDirectLink(redirectUrl);
// result.setExpires(generateDate(expires));
promise.complete(result);
// 更新缓存
// 将直链存储到缓存
CacheLinkInfo cacheLinkInfo = new CacheLinkInfo(JsonObject.of(
"directLink", redirectUrl,
"expiration", expires,
"shareKey", cacheKey
));
cacheManager.cacheShareLink(cacheLinkInfo).onFailure(Throwable::printStackTrace);
}).onFailure(promise::fail);
} else {
result.setExpires(generateDate(result.getExpiration()));
promise.complete(result);
}
}).onFailure(t -> promise.fail(t.fillInStackTrace()));
return promise.future();
}
private String generateCacheKey(String type, String shareKey) {
// 将type和shareKey组合成一个字符串作为缓存key
return type + ":" + shareKey;
}
private String generateDate(Long ts) {
return DateFormatUtils.format(new Date(ts), "yyyy-MM-dd hh:mm:ss");
}
}

View File

@@ -39,3 +39,23 @@ dataSource:
driverClassName: org.h2.Driver
username: root
password: '123456'
# 缓存配置
cache:
type: h2db
# 默认时长: 单位分钟, 实际有效期分钟-1
defaultDuration: 59
duration:
ce:
cow:
ec:
fc:
fj:
iz:
le: 2879
lz:
qq:
ws:
ye:

View File

@@ -1,6 +1,6 @@
# 要激活的配置: dev--连接本地数据库; prod连接线上数据库
active: dev
# 版本号
version_app: 0.1.7
version_app: 0.1.8
# 公司名称 -> LOGO版权文字
copyright: QAIU

View File

@@ -0,0 +1,10 @@
###超星
### 直链 Referer
https://d0.ananas.chaoxing.com/download/8e8c9baca640d24680d974331390a158?at_=1717958244333&ak_=783925f9ba6eb2d0c711977b777a13e0&ad_=58ffecd38be494bea68f0cda68b18c0a&fn=testgles.c
Referer: https://pan-yz.chaoxing.com/external/m/file/1006748113111711744
###

View File

@@ -59,3 +59,10 @@ Origin:https://www.feijipan.com
Referer:https://www.feijipan.com/
{"loginName":"","loginPwd":""}
### 240530 规则修改
### https://share.feijipan.com/s/nMtCOXL
POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&uuid=WQAl5yBy1naGudJEILBvE&extra=2&timestamp=A8ECC3C7C50191ACEB9CB8444FD37624&shareId=nMtCOXL&type=0&offset=1&limit=60

View File

@@ -1,6 +1,6 @@
### 蓝奏云
### PASS 蓝奏云
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://lanzoux.com/ia2cntg
GET http://127.0.0.1:6400/parser?url=https://www.lanzoux.com/ia2cntg
### 蓝奏云
# @no-redirect
@@ -20,7 +20,7 @@ GET http://127.0.0.1:6400/lz/icBp6qqj82b@QAIU
### 蓝奏云
GET http://127.0.0.1:6400/json/lz/ia2cntg
### 蓝奏云优享
### PASS 蓝奏云优享
GET http://127.0.0.1:6400/json/iz/lGFndCM
###
@@ -34,7 +34,7 @@ GET http://127.0.0.1:6400/json/parser?url=https://www.ilanzou.com/s/zHkna1S
GET http://127.0.0.1:6400/json/cow/9a644fe3e3a748
Referer: https://cowtransfer.com/
### 奶牛
### PASS 奶牛
# @no-redirect
GET http://127.0.0.1:6400/cow/e4f41b51b5da4f
@@ -47,9 +47,9 @@ GET http://127.0.0.1:6400/parser?url=https://cowtransfer.com/s/9a644fe3e3a748
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://goldrepo.cowtransfer.com/s/026a638795634b
### 移动云空间 https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
### 移动云空间 PASS https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://www.ecpan.cn/web//yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
GET http://127.0.0.1:6400/parser?url=https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
### 移动云空间 https://www.ecpan.cn/drive/fileextoverrid.do?chainUrlTemplate=https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=aa0cae0164d8885e6d35826b5b2901eckbWJBalM&parentId=-1
# @no-redirect
@@ -62,21 +62,7 @@ GET http://127.0.0.1:6400/json/ec/4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx@T6O
#@no-redirect
GET http://127.0.0.1:6400/parser?url=https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx&isShare=1&pwd=T6O1RC
### UC网盘
# @no-redirect
GET http://127.0.0.1:6400/uc/33197dd53ace4
### UC网盘
GET http://127.0.0.1:6400/json/uc/33197dd53ace4
### UC网盘
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://fast.uc.cn/s/33197dd53ace4
### 小飞机盘https://share.feijipan.com/s/nMtCOXL
### 小飞机盘 PASS https://share.feijipan.com/s/nMtCOXL
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://share.feijipan.com/s/nMtCOXL
@@ -98,7 +84,7 @@ GET http://127.0.0.1:6400/parser?url=https://v2.fangcloud.com/sharing/fb54bdf03c
### 360亿方云
GET http://127.0.0.1:6400/json/fc/fb54bdf03c66c04334fe3687a3
### 360亿方云
### 360亿方云 PASS
# @no-redirect
GET http://127.0.0.1:6400/fc/e5079007dc31226096628870c7@QAIU
@@ -113,7 +99,7 @@ GET http://127.0.0.1:6400/json/ye/iaKtVv-6OECd@DcGe
### 123
GET https://lz.qaiu.top/json/ye/iaKtVv-6OECd@DcGe
### 123
### 123 PASS
# @no-redirect
GET http://127.0.0.1:6400/ye/iaKtVv-qOECd
@@ -121,7 +107,7 @@ GET http://127.0.0.1:6400/ye/iaKtVv-qOECd
# @no-redirect
GET http://127.0.0.1:6400/ye/Ev1lVv-t3SY3
### 123 https://www.123pan.com/s/iaKtVv-6OECd.html
### 123 PASS https://www.123pan.com/s/iaKtVv-6OECd.html
# @no-redirect
GET http://127.0.0.1:6400/json/parser?url=https://www.123pan.com/s/iaKtVv-6OECd.html&pwd=DcGe
@@ -131,29 +117,37 @@ GET http://127.0.0.1:6400/json/parser?url=https://www.123pan.com/s/iaKtVv-6OECd.
GET http://127.0.0.1:6400/parser?url=https://www.123pan.com/s/zF07Vv-WkHWd.html&pwd=bios
### 联想乐云
### 联想乐云 PASS
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://lecloud.lenovo.com/share/4DANWdRQsHHyiFB4a
### 联想乐云
### 联想乐云 PASS
# @no-redirect
GET http://127.0.0.1:6400/le/4DANWdRQsHHyiFB4a
### 联想乐云
### 联想乐云 PASS
# @no-redirect
GET http://127.0.0.1:6400/le/2RkKbLP9BrppS9b43@ex2b
### 联想乐云
### 联想乐云 PASS
GET http://127.0.0.1:6400/json/le/2RkKbLP9BrppS9b43@ex2b
### 文叔叔
GET http://127.0.0.1:6400/json/parser?url=https://f.ws59.cn/f/e3peohu6192
### PASS 文叔叔
GET http://127.0.0.1:6400/json/parser?url=https://f.ws59.cn/f/f25625rv6p6
###
https://f.wss.cc/f/f25625rv6p6
### Cloudreve
### TODO Cloudreve
GET http://127.0.0.1:6400/json/ce/https_pan.huang1111.cn_s_wDz5TK
### Cloudreve https://pan.huang1111.cn/s/g31PcQ
GET http://127.0.0.1:6400/json/parser?url=https://pan.huang1111.cn/s/g31PcQ&pwd=qaiu
GET http://127.0.0.1:6400/parser?url=https://pan.huang1111.cn/s/g31PcQ&pwd=qaiu
### PASS QQ
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://iwx.mail.qq.com/ftn/download?func=3&key=c79c5732038ad41cf5ef1e3261663537f2cf4133636635371d4c484657050c0e5d561d070252021a0a0552024e5257530b4e0157540256525e5751565a053b374014540057560c070b511e53130d21f0361c829bf15b3e4a6355fbada6dd10dfdd11a33263663537386330&code=8c02cf57&k=c79c5732038ad41cf5ef1e3261663537f2cf4133636635371d4c484657050c0e5d561d070252021a0a0552024e5257530b4e0157540256525e5751565a053b374014540057560c070b511e53130d21f0361c829bf15b3e4a6355fbada6dd10dfdd11a33263663537386330
###
GET http://127.0.0.1:6400/v2/statisticsInfo