mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-17 04:43:02 +00:00
1. 添加缓存
2. 优化解析架构 3. 优化核心模块
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
37
web-service/src/main/java/cn/qaiu/lz/common/cache/CacheConfigLoader.java
vendored
Normal file
37
web-service/src/main/java/cn/qaiu/lz/common/cache/CacheConfigLoader.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
52
web-service/src/main/java/cn/qaiu/lz/common/cache/CacheManager.java
vendored
Normal file
52
web-service/src/main/java/cn/qaiu/lz/common/cache/CacheManager.java
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -19,4 +19,5 @@ public interface DbService extends BaseAsyncService {
|
||||
Future<JsonObject> sayOk2(String data, UserInfo holder);
|
||||
|
||||
Future<StatisticsInfo> getStatisticsInfo();
|
||||
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user