|
|
@@ -1,9 +1,13 @@
|
|
|
package top.lvzhiqiang.service.impl;
|
|
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.zaxxer.hikari.HikariDataSource;
|
|
|
+
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.core.io.ClassPathResource;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.yaml.snakeyaml.DumperOptions;
|
|
|
@@ -17,11 +21,23 @@ import top.lvzhiqiang.util.StringUtils;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
+import java.sql.Connection;
|
|
|
+import java.sql.DriverManager;
|
|
|
+import java.sql.PreparedStatement;
|
|
|
+import java.sql.ResultSet;
|
|
|
+import java.sql.SQLException;
|
|
|
+import java.text.DecimalFormat;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalTime;
|
|
|
+import java.time.ZoneId;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.LinkedHashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+import javax.annotation.PreDestroy;
|
|
|
+
|
|
|
@Service
|
|
|
@Slf4j
|
|
|
public class SubscribeServiceImpl implements SubscribeService {
|
|
|
@@ -32,9 +48,89 @@ public class SubscribeServiceImpl implements SubscribeService {
|
|
|
// Jackson 实例建议定义为全局单例或静态常量
|
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
|
|
+ // 1. 定义成员变量,不直接初始化
|
|
|
+ private HikariDataSource privateDataSource;
|
|
|
+ // 2. 注入配置参数
|
|
|
+ @Value("${rn.db.url}")
|
|
|
+ private String url;
|
|
|
+ @Value("${rn.db.username}")
|
|
|
+ private String userName;
|
|
|
+ @Value("${rn.db.password}")
|
|
|
+ private String password;
|
|
|
+
|
|
|
+ // 定义单位数组
|
|
|
+ private static final String[] UNITS = new String[] { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 3. 初始化方法
|
|
|
+ * 该方法会在 @Value 注入完成后,由 Spring 自动调用一次
|
|
|
+ */
|
|
|
+ @PostConstruct
|
|
|
+ public void init() {
|
|
|
+ System.out.println("正在初始化 Service 私有的连接池...");
|
|
|
+
|
|
|
+ privateDataSource = new HikariDataSource();
|
|
|
+ privateDataSource.setJdbcUrl(url);
|
|
|
+ privateDataSource.setUsername(userName);
|
|
|
+ privateDataSource.setPassword(password);
|
|
|
+ privateDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
|
|
|
+
|
|
|
+ // 建议设置特定的 PoolName,方便排查问题
|
|
|
+ privateDataSource.setPoolName("Private-Pool-Service-1");
|
|
|
+ // 针对私有连接池,通常建议调小连接数
|
|
|
+ privateDataSource.setMaximumPoolSize(5);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 4. 销毁方法 (非常重要!)
|
|
|
+ * 当这个 Service 被销毁(或应用关闭)时,必须关闭连接池,
|
|
|
+ * 否则会导致数据库连接泄露(Connection Leak)。
|
|
|
+ */
|
|
|
+ @PreDestroy
|
|
|
+ public void cleanup() {
|
|
|
+ if (privateDataSource != null && !privateDataSource.isClosed()) {
|
|
|
+ System.out.println("正在关闭 Service 私有的连接池...");
|
|
|
+ privateDataSource.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public NetAccount getAccount(String token) {
|
|
|
- return netMapper.findAccountByToken(token);
|
|
|
+ NetAccount netAccount = netMapper.findAccountByToken(token);
|
|
|
+
|
|
|
+ if (netAccount == null) {
|
|
|
+ throw new BusinessException("Account not found");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (netAccount.getStatus() != null && netAccount.getStatus() == 0) {
|
|
|
+ throw new BusinessException("Account is disabled");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取流量使用信息
|
|
|
+ try (Connection connection = privateDataSource.getConnection();
|
|
|
+ PreparedStatement statement = connection.prepareStatement("select * from users where username = ?")) {
|
|
|
+ statement.setObject(1, netAccount.getUsername());
|
|
|
+ try (ResultSet rs = statement.executeQuery()) {
|
|
|
+ while (rs.next()) {
|
|
|
+ Long download = rs.getLong("download");
|
|
|
+ Long upload = rs.getLong("upload");
|
|
|
+ String expiryDate = rs.getString("expiryDate");
|
|
|
+ if (StringUtils.isEmpty(expiryDate)) {
|
|
|
+ expiryDate = "9999-12-31";
|
|
|
+ }
|
|
|
+
|
|
|
+ netAccount.setDownloadUsedTraffic(download);
|
|
|
+ netAccount.setUploadUsedTraffic(upload);
|
|
|
+ netAccount.setTotalTraffic(1125899906842624L);
|
|
|
+ long expire = LocalDate.parse(expiryDate).atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
|
|
|
+ netAccount.setExpireTimeSeconds(expire);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (SQLException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ return netAccount;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -159,8 +255,9 @@ public class SubscribeServiceImpl implements SubscribeService {
|
|
|
// 2. 处理 JSON 扩展字段 (Hysteria2/Vless 特有字段等)
|
|
|
try {
|
|
|
if (StringUtils.isNotEmpty(node.getMetaJson())) {
|
|
|
- Map<String, Object> meta = objectMapper.readValue(node.getMetaJson(), new TypeReference<Map<String, Object>>() {
|
|
|
- });
|
|
|
+ Map<String, Object> meta = objectMapper.readValue(node.getMetaJson(),
|
|
|
+ new TypeReference<Map<String, Object>>() {
|
|
|
+ });
|
|
|
map.putAll(meta);
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
@@ -173,4 +270,29 @@ public class SubscribeServiceImpl implements SubscribeService {
|
|
|
|
|
|
return map;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化文件大小
|
|
|
+ * @param size 字节数 (long)
|
|
|
+ * @return 格式化后的字符串 (例如: "1.74 MB")
|
|
|
+ */
|
|
|
+ public static String formatFileSize(long size) {
|
|
|
+ if (size <= 0) {
|
|
|
+ return "0";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算层级 (0=B, 1=KB, 2=MB, etc.)
|
|
|
+ // 使用 log10(size) / log10(1024) 可以快速计算出该数字属于哪个量级
|
|
|
+ int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
|
|
|
+
|
|
|
+ // 格式化数字,保留两位小数 (#.##)
|
|
|
+ // new DecimalFormat("#,##0.##") 也可以处理千分位,如 1,024.50 MB
|
|
|
+ DecimalFormat df = new DecimalFormat("#0.##");
|
|
|
+
|
|
|
+ // 计算显示值:原数值 / (1024的n次方)
|
|
|
+ double displayValue = size / Math.pow(1024, digitGroups);
|
|
|
+
|
|
|
+ // 拼接结果:数字 + 空格 + 单位
|
|
|
+ return df.format(displayValue) + " " + UNITS[digitGroups];
|
|
|
+ }
|
|
|
}
|