From 73852fc5bd1f33a5e96db0925512644999955749 Mon Sep 17 00:00:00 2001 From: lys <5660053555@qq.com> Date: Tue, 24 Mar 2026 11:52:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E6=B5=81?= =?UTF-8?q?=E9=87=8F=E4=B8=8A=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/shardingsphere.yml | 12 +- .../sharding/DailyShardingAlgorithm.java | 87 +++++++++++++ .../dromara/net/domain/NetNodeFlowRecord.java | 55 +++++++++ .../dromara/net/domain/NetUserFlowRecord.java | 55 +++++++++ .../dromara/net/dto/AddUserToNodeRequest.java | 114 ++++++++++++++++++ .../net/dto/AddUserToNodeResponse.java | 32 +++++ .../dromara/net/dto/NodeUserStatsRequest.java | 26 ++++ .../net/dto/NodeUserStatsResponse.java | 50 ++++++++ .../net/mapper/NetNodeFlowRecordMapper.java | 18 +++ .../net/mapper/NetUserFlowRecordMapper.java | 18 +++ 10 files changed, 461 insertions(+), 6 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/sharding/DailyShardingAlgorithm.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/domain/NetNodeFlowRecord.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/domain/NetUserFlowRecord.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/AddUserToNodeRequest.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/AddUserToNodeResponse.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/NodeUserStatsRequest.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/NodeUserStatsResponse.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/mapper/NetNodeFlowRecordMapper.java create mode 100644 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/mapper/NetUserFlowRecordMapper.java diff --git a/ruoyi-admin/src/main/resources/shardingsphere.yml b/ruoyi-admin/src/main/resources/shardingsphere.yml index 349ab2c0d..144a41e97 100644 --- a/ruoyi-admin/src/main/resources/shardingsphere.yml +++ b/ruoyi-admin/src/main/resources/shardingsphere.yml @@ -16,28 +16,28 @@ dataSources: rules: - !SHARDING - tables: # 数据分片规则配置 - # 节点流量记录表(按天分表) + tables: net_node_flow_record: - actualDataNodes: net_flow_record.net_node_flow_record_${['20260323']} + actualDataNodes: net_flow_record.net_node_flow_record_${['20260324','20260325','20260326','20260327','20260328','20260329','20260330','20260331','20260401','20260402','20260403','20260404','20260405','20260406','20260407','20260408','20260409','20260410','20260411','20260412','20260413','20260414','20260415','20260416','20260417','20260418','20260419','20260420','20260421','20260422','20260423','20260424','20260425','20260426','20260427','20260428','20260429','20260430','20260501','20260502','20260503','20260504','20260505','20260506','20260507','20260508','20260509','20260510','20260511','20260512','20260513','20260514','20260515','20260516','20260517','20260518','20260519','20260520','20260521','20260522','20260523','20260524','20260525','20260526','20260527','20260528','20260529','20260530','20260531','20260601','20260602','20260603','20260604','20260605','20260606','20260607','20260608','20260609','20260610','20260611','20260612','20260613','20260614','20260615','20260616','20260617','20260618','20260619','20260620','20260621','20260622','20260623','20260624','20260625','20260626','20260627','20260628','20260629','20260630','20260701','20260702','20260703','20260704','20260705','20260706','20260707','20260708','20260709','20260710','20260711','20260712','20260713','20260714','20260715','20260716','20260717','20260718','20260719','20260720','20260721','20260722','20260723','20260724','20260725','20260726','20260727','20260728','20260729','20260730','20260731','20260801','20260802','20260803','20260804','20260805','20260806','20260807','20260808','20260809','20260810','20260811','20260812','20260813','20260814','20260815','20260816','20260817','20260818','20260819','20260820','20260821','20260822','20260823','20260824','20260825','20260826','20260827','20260828','20260829','20260830','20260831','20260901','20260902','20260903','20260904','20260905','20260906','20260907','20260908','20260909','20260910','20260911','20260912','20260913','20260914','20260915','20260916','20260917','20260918','20260919','20260920','20260921','20260922','20260923','20260924','20260925','20260926','20260927','20260928','20260929','20260930','20261001','20261002','20261003','20261004','20261005','20261006','20261007','20261008','20261009','20261010','20261011','20261012','20261013','20261014','20261015','20261016','20261017','20261018','20261019','20261020','20261021','20261022','20261023','20261024','20261025','20261026','20261027','20261028','20261029','20261030','20261031','20261101','20261102','20261103','20261104','20261105','20261106','20261107','20261108','20261109','20261110','20261111','20261112','20261113','20261114','20261115','20261116','20261117','20261118','20261119','20261120','20261121','20261122','20261123','20261124','20261125','20261126','20261127','20261128','20261129','20261130','20261201','20261202','20261203','20261204','20261205','20261206','20261207','20261208','20261209','20261210','20261211','20261212','20261213','20261214','20261215','20261216','20261217','20261218','20261219','20261220','20261221','20261222','20261223','20261224','20261225','20261226','20261227','20261228','20261229','20261230','20261231','20270101','20270102','20270103','20270104','20270105','20270106','20270107','20270108','20270109','20270110','20270111','20270112','20270113','20270114','20270115','20270116','20270117','20270118','20270119','20270120','20270121','20270122','20270123','20270124','20270125','20270126','20270127','20270128','20270129','20270130','20270131','20270201','20270202','20270203','20270204','20270205','20270206','20270207','20270208','20270209','20270210','20270211','20270212','20270213','20270214','20270215','20270216','20270217','20270218','20270219','20270220','20270221','20270222','20270223','20270224','20270225','20270226','20270227','20270228','20270301','20270302','20270303','20270304','20270305','20270306','20270307','20270308','20270309','20270310','20270311','20270312','20270313','20270314','20270315','20270316','20270317','20270318','20270319','20270320','20270321','20270322','20270323','20270324']} tableStrategy: standard: shardingColumn: create_time shardingAlgorithmName: daily_sharding - # 用户流量记录表(按天分表) net_user_flow_record: - actualDataNodes: net_flow_record.net_user_flow_record_${['20260323']} + actualDataNodes: net_flow_record.net_user_flow_record_${['20260324','20260325','20260326','20260327','20260328','20260329','20260330','20260331','20260401','20260402','20260403','20260404','20260405','20260406','20260407','20260408','20260409','20260410','20260411','20260412','20260413','20260414','20260415','20260416','20260417','20260418','20260419','20260420','20260421','20260422','20260423','20260424','20260425','20260426','20260427','20260428','20260429','20260430','20260501','20260502','20260503','20260504','20260505','20260506','20260507','20260508','20260509','20260510','20260511','20260512','20260513','20260514','20260515','20260516','20260517','20260518','20260519','20260520','20260521','20260522','20260523','20260524','20260525','20260526','20260527','20260528','20260529','20260530','20260531','20260601','20260602','20260603','20260604','20260605','20260606','20260607','20260608','20260609','20260610','20260611','20260612','20260613','20260614','20260615','20260616','20260617','20260618','20260619','20260620','20260621','20260622','20260623','20260624','20260625','20260626','20260627','20260628','20260629','20260630','20260701','20260702','20260703','20260704','20260705','20260706','20260707','20260708','20260709','20260710','20260711','20260712','20260713','20260714','20260715','20260716','20260717','20260718','20260719','20260720','20260721','20260722','20260723','20260724','20260725','20260726','20260727','20260728','20260729','20260730','20260731','20260801','20260802','20260803','20260804','20260805','20260806','20260807','20260808','20260809','20260810','20260811','20260812','20260813','20260814','20260815','20260816','20260817','20260818','20260819','20260820','20260821','20260822','20260823','20260824','20260825','20260826','20260827','20260828','20260829','20260830','20260831','20260901','20260902','20260903','20260904','20260905','20260906','20260907','20260908','20260909','20260910','20260911','20260912','20260913','20260914','20260915','20260916','20260917','20260918','20260919','20260920','20260921','20260922','20260923','20260924','20260925','20260926','20260927','20260928','20260929','20260930','20261001','20261002','20261003','20261004','20261005','20261006','20261007','20261008','20261009','20261010','20261011','20261012','20261013','20261014','20261015','20261016','20261017','20261018','20261019','20261020','20261021','20261022','20261023','20261024','20261025','20261026','20261027','20261028','20261029','20261030','20261031','20261101','20261102','20261103','20261104','20261105','20261106','20261107','20261108','20261109','20261110','20261111','20261112','20261113','20261114','20261115','20261116','20261117','20261118','20261119','20261120','20261121','20261122','20261123','20261124','20261125','20261126','20261127','20261128','20261129','20261130','20261201','20261202','20261203','20261204','20261205','20261206','20261207','20261208','20261209','20261210','20261211','20261212','20261213','20261214','20261215','20261216','20261217','20261218','20261219','20261220','20261221','20261222','20261223','20261224','20261225','20261226','20261227','20261228','20261229','20261230','20261231','20270101','20270102','20270103','20270104','20270105','20270106','20270107','20270108','20270109','20270110','20270111','20270112','20270113','20270114','20270115','20270116','20270117','20270118','20270119','20270120','20270121','20270122','20270123','20270124','20270125','20270126','20270127','20270128','20270129','20270130','20270131','20270201','20270202','20270203','20270204','20270205','20270206','20270207','20270208','20270209','20270210','20270211','20270212','20270213','20270214','20270215','20270216','20270217','20270218','20270219','20270220','20270221','20270222','20270223','20270224','20270225','20270226','20270227','20270228','20270301','20270302','20270303','20270304','20270305','20270306','20270307','20270308','20270309','20270310','20270311','20270312','20270313','20270314','20270315','20270316','20270317','20270318','20270319','20270320','20270321','20270322','20270323','20270324']} tableStrategy: standard: shardingColumn: create_time shardingAlgorithmName: daily_sharding shardingAlgorithms: - # 新增: 按天分表算法 daily_sharding: type: CLASS_BASED props: strategy: standard algorithmClassName: org.dromara.common.mybatis.sharding.DailyShardingAlgorithm + +props: + sql-show: true diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/sharding/DailyShardingAlgorithm.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/sharding/DailyShardingAlgorithm.java new file mode 100644 index 000000000..673bd0fb6 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/sharding/DailyShardingAlgorithm.java @@ -0,0 +1,87 @@ +package org.dromara.common.mybatis.sharding; + +import cn.hutool.core.date.DateUtil; +import com.google.common.collect.Range; +import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; +import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue; +import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; + +import java.util.*; + +/** + * 按天分表算法 + * 表名后缀格式: _yyyyMMdd + * + * @author lys + * @date 2025-03-23 + */ +public class DailyShardingAlgorithm implements StandardShardingAlgorithm> { + + + private static final String DATE_PATTERN = "yyyyMMdd"; + + @Override + public void init(Properties props) { + // 无需初始化 + } + + /** + * 从分片值中提取日期 + * + * @param shardingValue 分片值 + * @return 日期 + */ + private Date extractDate(Comparable shardingValue) { + if (shardingValue instanceof Date) { + return (Date) shardingValue; + } + throw new UnsupportedOperationException("分片值必须是 Date 类型,当前值: " + shardingValue); + } + + /** + * 从可用表名集合中获取逻辑表名 + * + * @param availableTargetNames 可用表名集合 + * @return 逻辑表名(不带后缀) + */ + private String getLogicTableName(Collection availableTargetNames) { + // 从第一个可用表名中提取逻辑表名 + String firstTableName = availableTargetNames.iterator().next(); + if (firstTableName != null) { + int underscoreIndex = firstTableName.lastIndexOf('_'); + if (underscoreIndex > 0) { + return firstTableName.substring(0, underscoreIndex); + } + } + throw new IllegalStateException("无法从可用表名中提取逻辑表名"); + } + @Override + public String doSharding(Collection availableTargetNames, PreciseShardingValue> shardingValue) { + // 获取分片值对应的日期 + Date date = extractDate(shardingValue.getValue()); + // 格式化为 yyyyMMdd + String suffix = DateUtil.format(date, DATE_PATTERN); + // 构建目标表名 + String logicTableName = getLogicTableName(availableTargetNames); + return logicTableName + "_" + suffix; + } + + @Override + public Collection doSharding(Collection availableTargetNames, RangeShardingValue> shardingValue) { + // 获取范围查询的日期范围 + Range> range = shardingValue.getValueRange(); + Date lower = extractDate(range.lowerEndpoint()); + Date upper = extractDate(range.upperEndpoint()); + String logicTableName = getLogicTableName(availableTargetNames); + // 生成日期范围内的所有表名 + Set result = new LinkedHashSet<>(); + Date current = lower; + while (!current.after(upper)) { + result.add(logicTableName + "_" + DateUtil.format(current, DATE_PATTERN)); + current = DateUtil.offsetDay(current, 1); + } + // 包含上边界 + result.add(logicTableName + "_" + DateUtil.format(upper, DATE_PATTERN)); + return result; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/domain/NetNodeFlowRecord.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/domain/NetNodeFlowRecord.java new file mode 100644 index 000000000..e13708103 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/domain/NetNodeFlowRecord.java @@ -0,0 +1,55 @@ +package org.dromara.net.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 节点流量记录表 net_node_flow_record + * 记录每个节点的流量汇总数据 + * + * @author lys + * @date 2025-03-23 + */ +@Data +@TableName("net_node_flow_record") +public class NetNodeFlowRecord implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + private Long id; + + /** + * 节点ID + */ + private Long nodeId; + + /** + * 上传流量(字节) + */ + private Long upNum; + + /** + * 下载流量(字节) + */ + private Long downNum; + + /** + * 总流量(字节) + */ + private Long totalNum; + + /** + * 记录时间 + */ + private Date createTime; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/domain/NetUserFlowRecord.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/domain/NetUserFlowRecord.java new file mode 100644 index 000000000..f466a8dd7 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/domain/NetUserFlowRecord.java @@ -0,0 +1,55 @@ +package org.dromara.net.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 用户流量记录表 net_user_flow_record + * 记录每个用户的流量汇总数据 + * + * @author lys + * @date 2025-03-23 + */ +@Data +@TableName("net_user_flow_record") +public class NetUserFlowRecord implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 上传流量(字节) + */ + private Long upNum; + + /** + * 下载流量(字节) + */ + private Long downNum; + + /** + * 总流量(字节) + */ + private Long totalNum; + + /** + * 记录时间 + */ + private Date createTime; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/AddUserToNodeRequest.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/AddUserToNodeRequest.java new file mode 100644 index 000000000..a1a4e2b83 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/AddUserToNodeRequest.java @@ -0,0 +1,114 @@ +package org.dromara.net.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 添加用户到节点请求DTO + * 对应 POST /node/handler/add-user 接口 + * + * @author lys + * @date 2025-03-20 + */ +@Data +public class AddUserToNodeRequest { + + /** + * 用户数据列表 + */ + private List data; + + /** + * 哈希数据 + */ + private HashData hashData; + + /** + * 入站数据 - 基类 + */ + @Data + public static class InboundData { + /** + * 协议类型: vless, trojan, shadowsocks + */ + private String type; + + /** + * 入站标签 + */ + private String tag; + + /** + * 用户名 + */ + private String username; + } + + /** + * VLESS入站数据 + */ + @Data + public static class VlessInboundData extends InboundData { + /** + * UUID + */ + private String uuid; + + /** + * 流控: xtls-rprx-vision 或空字符串 + */ + private String flow; + } + + /** + * Trojan入站数据 + */ + @Data + public static class TrojanInboundData extends InboundData { + /** + * 密码 + */ + private String password; + } + + /** + * Shadowsocks入站数据 + */ + @Data + public static class ShadowsocksInboundData extends InboundData { + /** + * 密码 + */ + private String password; + + /** + * 加密类型 + * 5 = AES_128_GCM + * 6 = AES_256_GCM + * 7 = CHACHA20_POLY1305 + */ + private Integer cipherType; + + /** + * IV检查 + */ + private Boolean ivCheck; + } + + /** + * 哈希数据 + */ + @Data + public static class HashData { + /** + * VLESS UUID + */ + private String vlessUuid; + + /** + * 上一个VLESS UUID(可选) + */ + private String prevVlessUuid; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/AddUserToNodeResponse.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/AddUserToNodeResponse.java new file mode 100644 index 000000000..f14d96790 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/AddUserToNodeResponse.java @@ -0,0 +1,32 @@ +package org.dromara.net.dto; + +import lombok.Data; + +/** + * 添加用户到节点响应DTO + * 对应 POST /node/handler/add-user 接口响应 + * + * @author lys + * @date 2025-03-20 + */ +@Data +public class AddUserToNodeResponse { + + /** + * 响应数据 + */ + private ResponseData response; + + @Data + public static class ResponseData { + /** + * 是否成功 + */ + private Boolean success; + + /** + * 错误信息 + */ + private String error; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/NodeUserStatsRequest.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/NodeUserStatsRequest.java new file mode 100644 index 000000000..91c6402a1 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/NodeUserStatsRequest.java @@ -0,0 +1,26 @@ +package org.dromara.net.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 节点用户流量统计请求DTO + * 对应 POST /node/stats/get-users-stats 接口的请求体 + * + * @author lys + * @date 2025-03-23 + */ +@Data +@NoArgsConstructor +public class NodeUserStatsRequest { + + /** + * 是否重置统计数据 + * true - 获取数据后重置节点上的流量统计 + */ + private Boolean reset; + + public NodeUserStatsRequest(Boolean reset) { + this.reset = reset; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/NodeUserStatsResponse.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/NodeUserStatsResponse.java new file mode 100644 index 000000000..0f4333a4c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/dto/NodeUserStatsResponse.java @@ -0,0 +1,50 @@ +package org.dromara.net.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 节点用户流量统计响应DTO + * 对应 POST /node/stats/get-users-stats 接口的响应 + * + * @author lys + * @date 2025-03-23 + */ +@Data +public class NodeUserStatsResponse { + + /** + * 响应数据 + */ + private ResponseData response; + + @Data + public static class ResponseData { + /** + * 用户流量列表 + */ + private List users; + } + + /** + * 用户流量数据 + */ + @Data + public static class UserFlowData { + /** + * 用户名(用户ID字符串形式) + */ + private String username; + + /** + * 下载流量(字节) + */ + private Long downlink; + + /** + * 上传流量(字节) + */ + private Long uplink; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/mapper/NetNodeFlowRecordMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/mapper/NetNodeFlowRecordMapper.java new file mode 100644 index 000000000..3db433313 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/mapper/NetNodeFlowRecordMapper.java @@ -0,0 +1,18 @@ +package org.dromara.net.mapper; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.dromara.net.domain.NetNodeFlowRecord; + +/** + * 节点流量记录Mapper接口 + * + * @author lys + * @date 2025-03-23 + */ +@Mapper +@DS(value = "net_flow_record") +public interface NetNodeFlowRecordMapper extends BaseMapper { + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/mapper/NetUserFlowRecordMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/mapper/NetUserFlowRecordMapper.java new file mode 100644 index 000000000..ef1b81dba --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/net/mapper/NetUserFlowRecordMapper.java @@ -0,0 +1,18 @@ +package org.dromara.net.mapper; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.dromara.net.domain.NetUserFlowRecord; + +/** + * 用户流量记录Mapper接口 + * + * @author lys + * @date 2025-03-23 + */ +@Mapper +@DS(value = "net_flow_record") +public interface NetUserFlowRecordMapper extends BaseMapper { + +}