mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-17 04:43:02 +00:00
remove yarn.lock
This commit is contained in:
@@ -3,6 +3,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.lz.common.interceptorImpl.RateLimiter;
|
||||
import cn.qaiu.vx.core.Deploy;
|
||||
import cn.qaiu.vx.core.util.ConfigConstant;
|
||||
import cn.qaiu.vx.core.util.VertxHolder;
|
||||
@@ -11,8 +12,9 @@ import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.core.json.jackson.DatabindCodec;
|
||||
import io.vertx.core.shareddata.LocalMap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static cn.qaiu.vx.core.util.ConfigConstant.LOCAL;
|
||||
|
||||
@@ -25,8 +27,6 @@ import static cn.qaiu.vx.core.util.ConfigConstant.LOCAL;
|
||||
*/
|
||||
public class AppMain {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AppMain.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
Deploy.instance().start(args, AppMain::exec);
|
||||
}
|
||||
@@ -40,14 +40,22 @@ public class AppMain {
|
||||
private static void exec(JsonObject jsonObject) {
|
||||
WebClientVertxInit.init(VertxHolder.getVertxInstance());
|
||||
DatabindCodec.mapper().registerModule(new JavaTimeModule());
|
||||
// 限流
|
||||
if (jsonObject.containsKey("rateLimit")) {
|
||||
JsonObject rateLimit = jsonObject.getJsonObject("rateLimit");
|
||||
RateLimiter.init(rateLimit);
|
||||
}
|
||||
// 数据库
|
||||
if (jsonObject.getJsonObject(ConfigConstant.SERVER).getBoolean("enableDatabase")) {
|
||||
JDBCPoolInit.builder().config(jsonObject.getJsonObject("dataSource"))
|
||||
.build()
|
||||
.initPool().onSuccess(PreparedStatement -> {
|
||||
LOGGER.info("数据库连接成功");
|
||||
String addr = jsonObject.getJsonObject(ConfigConstant.SERVER).getString("domainName");
|
||||
LOGGER.info("启动成功: \n本地服务地址: {}", addr);
|
||||
VertxHolder.getVertxInstance().setTimer(1000, id -> {
|
||||
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS"));
|
||||
System.out.println("数据库连接成功");
|
||||
String addr = jsonObject.getJsonObject(ConfigConstant.SERVER).getString("domainName");
|
||||
System.out.println("启动成功: \n本地服务地址: " + addr);
|
||||
});
|
||||
});
|
||||
}
|
||||
// 缓存
|
||||
|
||||
@@ -2,12 +2,15 @@ package cn.qaiu.lz.common.interceptorImpl;
|
||||
|
||||
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
|
||||
import cn.qaiu.vx.core.interceptor.BeforeInterceptor;
|
||||
import cn.qaiu.vx.core.util.ConfigConstant;
|
||||
import cn.qaiu.vx.core.util.SharedDataUtil;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static cn.qaiu.vx.core.util.ConfigConstant.IGNORES_REG;
|
||||
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
||||
|
||||
/**
|
||||
* 前置拦截器实现
|
||||
@@ -20,8 +23,43 @@ public class DefaultInterceptor implements BeforeInterceptor {
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext ctx) {
|
||||
// System.out.println("进入前置拦截器1->" + ctx.request().path());
|
||||
doNext(ctx);
|
||||
// 读取配置 如果配置了限流 则进行限流
|
||||
if (!SharedDataUtil.getJsonConfig(ConfigConstant.GLOBAL_CONFIG).containsKey("rateLimit")) {
|
||||
doNext(ctx);
|
||||
return;
|
||||
}
|
||||
JsonObject rateLimit = SharedDataUtil.getJsonConfig(ConfigConstant.GLOBAL_CONFIG)
|
||||
.getJsonObject("rateLimit");
|
||||
// # 限流配置
|
||||
//rateLimit:
|
||||
// # 是否启用限流
|
||||
// enable: true
|
||||
// # 限流的请求数
|
||||
// limit: 1000
|
||||
// # 限流的时间窗口(单位秒)
|
||||
// timeWindow: 60
|
||||
if (rateLimit.getBoolean("enable")) {
|
||||
// 获取当前请求的路径
|
||||
String path = ctx.request().path();
|
||||
// 正则匹配路径
|
||||
if (ignores.stream().anyMatch(ignore -> path.matches(ignore.toString()))) {
|
||||
// 如果匹配到忽略的路径,则不进行限流
|
||||
doNext(ctx);
|
||||
return;
|
||||
}
|
||||
RateLimiter.checkRateLimit(ctx.request())
|
||||
.onSuccess(v -> {
|
||||
// 继续执行下一个拦截器
|
||||
doNext(ctx);
|
||||
})
|
||||
.onFailure(t -> {
|
||||
// 限流失败,返回错误响应
|
||||
log.warn("Rate limit exceeded for path: {}", path);
|
||||
ctx.response().putHeader(CONTENT_TYPE, "text/html; charset=utf-8")
|
||||
.setStatusCode(429)
|
||||
.end(t.getMessage());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.qaiu.lz.common.interceptorImpl;
|
||||
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.http.HttpServerRequest;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Slf4j
|
||||
public class RateLimiter {
|
||||
|
||||
private static final Map<String, RequestInfo> ipRequestMap = new ConcurrentHashMap<>();
|
||||
private static int MAX_REQUESTS = 10; // 最大请求次数
|
||||
private static long TIME_WINDOW = 60 * 1000; // 时间窗口(毫秒)
|
||||
|
||||
private static String PATH_REG; // 限流路径正则
|
||||
|
||||
public static void init(JsonObject rateLimitConfig) {
|
||||
MAX_REQUESTS = rateLimitConfig.getInteger("limit", 10);
|
||||
TIME_WINDOW = rateLimitConfig.getInteger("timeWindow", 60) * 1000L; // 转换为毫秒
|
||||
PATH_REG = rateLimitConfig.getString("pathReg", "/.*");
|
||||
log.info("RateLimiter initialized with max requests: {}, time window: {} ms, path regex: {}",
|
||||
MAX_REQUESTS, TIME_WINDOW, PATH_REG);
|
||||
}
|
||||
|
||||
synchronized public static Future<Void> checkRateLimit(HttpServerRequest request) {
|
||||
Promise<Void> promise = Promise.promise();
|
||||
if (!request.path().matches(PATH_REG)) {
|
||||
// 如果请求路径不匹配正则,则不进行限流
|
||||
promise.complete();
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
String ip = request.remoteAddress().host();
|
||||
|
||||
ipRequestMap.compute(ip, (key, requestInfo) -> {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (requestInfo == null || currentTime - requestInfo.timestamp > TIME_WINDOW) {
|
||||
// 初始化或重置计数器
|
||||
return new RequestInfo(1, currentTime);
|
||||
} else {
|
||||
// 增加计数器
|
||||
requestInfo.count++;
|
||||
return requestInfo;
|
||||
}
|
||||
});
|
||||
|
||||
RequestInfo info = ipRequestMap.get(ip);
|
||||
if (info.count > MAX_REQUESTS) {
|
||||
// 超过限制
|
||||
// 计算剩余时间
|
||||
long remainingTime = TIME_WINDOW - (System.currentTimeMillis() - info.timestamp);
|
||||
BigDecimal bigDecimal = BigDecimal.valueOf(remainingTime / 1000.0)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
promise.fail("请求次数太多了,请" + bigDecimal + "秒后再试。");
|
||||
} else {
|
||||
// 未超过限制,继续处理
|
||||
promise.complete();
|
||||
}
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
private static class RequestInfo {
|
||||
int count;
|
||||
long timestamp;
|
||||
|
||||
RequestInfo(int count, long time) {
|
||||
this.count = count;
|
||||
this.timestamp = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.qaiu.lz.web.controller;
|
||||
|
||||
import cn.qaiu.lz.web.service.ShoutService;
|
||||
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.model.JsonResult;
|
||||
import cn.qaiu.vx.core.util.AsyncServiceUtil;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
@RouteHandler("/v2/shout")
|
||||
public class ShoutController {
|
||||
|
||||
private final ShoutService shoutService = AsyncServiceUtil.getAsyncServiceInstance(ShoutService.class);
|
||||
|
||||
@RouteMapping(value = "/submit", method = RouteMethod.POST)
|
||||
public Future<JsonObject> submitMessage(RoutingContext ctx) {
|
||||
String content = ctx.body().asJsonObject().getString("content");
|
||||
if (content == null || content.trim().isEmpty()) {
|
||||
return Future.failedFuture("内容不能为空");
|
||||
}
|
||||
return shoutService.submitMessage(content, ctx.request().remoteAddress().host()).compose(code ->
|
||||
Future.succeededFuture(JsonResult.data(code).toJsonObject()));
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/retrieve", method = RouteMethod.GET)
|
||||
public Future<JsonObject> retrieveMessage(RoutingContext ctx) {
|
||||
String code = ctx.request().getParam("code");
|
||||
if (code == null || code.length() != 6) {
|
||||
return Future.failedFuture("提取码必须为6位数字");
|
||||
}
|
||||
return shoutService.retrieveMessage(code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.qaiu.lz.web.model;
|
||||
|
||||
import cn.qaiu.db.ddl.Constraint;
|
||||
import cn.qaiu.db.ddl.Length;
|
||||
import cn.qaiu.db.ddl.Table;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 隔空喊话消息
|
||||
*/
|
||||
@Data
|
||||
@Table("t_messages")
|
||||
public class ShoutMessage {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Constraint(autoIncrement= true, notNull = true)
|
||||
private Long id;
|
||||
|
||||
@Length(varcharSize = 16)
|
||||
@Constraint(notNull = true, uniqueKey = "uk_code")
|
||||
private String code; // 6位提取码
|
||||
|
||||
@Length(varcharSize = 4096)
|
||||
private String content; // 消息内容
|
||||
|
||||
@Length(varcharSize = 32)
|
||||
private String ip; // 发送者IP
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date createTime = new Date(); // 创建时间
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date expireTime; // 过期时间
|
||||
|
||||
private Boolean isUsed = false; // 是否已使用
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.qaiu.lz.web.service;
|
||||
|
||||
import cn.qaiu.vx.core.base.BaseAsyncService;
|
||||
import io.vertx.codegen.annotations.ProxyGen;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
||||
@ProxyGen
|
||||
public interface ShoutService extends BaseAsyncService {
|
||||
// 提交消息并返回提取码
|
||||
Future<String> submitMessage(String content, String host);
|
||||
|
||||
// 通过提取码获取消息
|
||||
Future<JsonObject> retrieveMessage(String code);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package cn.qaiu.lz.web.service.impl;
|
||||
|
||||
import cn.qaiu.db.pool.JDBCPoolInit;
|
||||
import cn.qaiu.lz.web.service.ShoutService;
|
||||
import cn.qaiu.vx.core.annotaions.Service;
|
||||
import cn.qaiu.vx.core.model.JsonResult;
|
||||
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.Tuple;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Random;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@SuppressWarnings("SqlResolve") // 这里是为了避免检查SQL语句的警告
|
||||
public class ShoutServiceImpl implements ShoutService {
|
||||
private static final int CODE_LENGTH = 6;
|
||||
private static final int EXPIRE_HOURS = 24;
|
||||
private final JDBCPool jdbcPool = JDBCPoolInit.instance().getPool();
|
||||
|
||||
@Override
|
||||
public Future<String> submitMessage(String content, String host) {
|
||||
Promise<String> promise = Promise.promise();
|
||||
String code = generateRandomCode();
|
||||
// 判断一下当前code是否存在消息
|
||||
LocalDateTime expireTime = LocalDateTime.now().plusHours(EXPIRE_HOURS);
|
||||
|
||||
String sql = "INSERT INTO t_messages (code, content, expire_time, ip) VALUES (?, ?, ?, ?)";
|
||||
|
||||
jdbcPool.preparedQuery(sql)
|
||||
.execute(Tuple.of(code, content,
|
||||
java.sql.Timestamp.from(expireTime.atZone(ZoneId.systemDefault()).toInstant()),
|
||||
host))
|
||||
.onSuccess(res -> {
|
||||
log.info("Message submitted with code: {}", code);
|
||||
promise.complete(code);
|
||||
})
|
||||
.onFailure(err -> {
|
||||
log.error("Failed to submit message", err);
|
||||
promise.fail(err);
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<JsonObject> retrieveMessage(String code) {
|
||||
Promise<JsonObject> promise = Promise.promise();
|
||||
|
||||
String sql = "SELECT content FROM t_messages WHERE code = ? AND expire_time > NOW()";
|
||||
|
||||
jdbcPool.preparedQuery(sql)
|
||||
.execute(Tuple.of(code))
|
||||
.onSuccess(rows -> {
|
||||
if (rows.size() > 0) {
|
||||
String content = rows.iterator().next().getString("content");
|
||||
// 标记为已使用
|
||||
markAsUsed(code);
|
||||
promise.complete(JsonResult.data(content).toJsonObject());
|
||||
} else {
|
||||
promise.fail("无效的提取码或消息已过期");
|
||||
}
|
||||
})
|
||||
.onFailure(err -> {
|
||||
log.error("Failed to retrieve message", err);
|
||||
promise.fail(err);
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
private void markAsUsed(String code) {
|
||||
String sql = "UPDATE t_messages SET is_used = TRUE WHERE code = ?";
|
||||
jdbcPool.preparedQuery(sql).execute(Tuple.of(code));
|
||||
}
|
||||
|
||||
private String generateRandomCode() {
|
||||
Random random = new Random();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < CODE_LENGTH; i++) {
|
||||
sb.append(random.nextInt(10));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user