2.0版本测试

This commit is contained in:
qaq
2025-11-19 14:28:45 +08:00
parent a27b4d781e
commit 65ca18d649
72 changed files with 3906 additions and 6289 deletions
@@ -0,0 +1,41 @@
package com.admin.service.impl;
import com.admin.common.dto.ForwardDto;
import com.admin.common.dto.ForwardUpdateDto;
import com.admin.common.dto.ForwardWithTunnelDto;
import com.admin.common.dto.GostDto;
import com.admin.common.lang.R;
import com.admin.common.utils.GostUtil;
import com.admin.common.utils.JwtUtil;
import com.admin.common.utils.WebSocketServer;
import com.admin.entity.*;
import com.admin.mapper.ChainTunnelMapper;
import com.admin.mapper.ForwardMapper;
import com.admin.service.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
/**
* <p>
* 端口转发服务实现类
* </p>
*
* @author QAQ
* @since 2025-06-03
*/
@Slf4j
@Service
public class ChainTunnelServiceImpl extends ServiceImpl<ChainTunnelMapper, ChainTunnel> implements ChainTunnelService {
}
@@ -0,0 +1,15 @@
package com.admin.service.impl;
import com.admin.entity.ChainTunnel;
import com.admin.entity.ForwardPort;
import com.admin.mapper.ChainTunnelMapper;
import com.admin.mapper.ForwardPortMapper;
import com.admin.service.ChainTunnelService;
import com.admin.service.ForwardPortService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class ForwardPortServiceImpl extends ServiceImpl<ForwardPortMapper, ForwardPort> implements ForwardPortService {
}
File diff suppressed because it is too large Load Diff
@@ -7,14 +7,10 @@ import com.admin.common.dto.NodeDto;
import com.admin.common.dto.NodeUpdateDto;
import com.admin.common.lang.R;
import com.admin.common.utils.WebSocketServer;
import com.admin.entity.Node;
import com.admin.entity.Tunnel;
import com.admin.entity.ViteConfig;
import com.admin.entity.*;
import com.admin.mapper.NodeMapper;
import com.admin.mapper.TunnelMapper;
import com.admin.service.NodeService;
import com.admin.service.TunnelService;
import com.admin.service.ViteConfigService;
import com.admin.service.*;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -26,51 +22,13 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Value;
/**
* <p>
* 节点服务实现类
* 提供节点的增删改查功能,包括节点创建、更新、删除和查询操作
* </p>
*
* @author QAQ
* @since 2025-06-03
*/
@Service
public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements NodeService {
// ========== 常量定义 ==========
/** 节点默认状态:启用 */
private static final int NODE_STATUS_ACTIVE = 0;
/** 成功响应消息 */
private static final String SUCCESS_CREATE_MSG = "节点创建成功";
private static final String SUCCESS_UPDATE_MSG = "节点更新成功";
private static final String SUCCESS_DELETE_MSG = "节点删除成功";
/** 错误响应消息 */
private static final String ERROR_CREATE_MSG = "节点创建失败";
private static final String ERROR_UPDATE_MSG = "节点更新失败";
private static final String ERROR_DELETE_MSG = "节点删除失败";
private static final String ERROR_NODE_NOT_FOUND = "节点不存在";
/** 隧道使用检查相关消息 */
private static final String ERROR_IN_NODE_IN_USE = "该节点还有 %d 个隧道作为入口节点在使用,请先删除相关隧道";
private static final String ERROR_OUT_NODE_IN_USE = "该节点还有 %d 个隧道作为出口节点在使用,请先删除相关隧道";
/** 端口范围验证相关消息 */
private static final String ERROR_PORT_STA_REQUIRED = "起始端口不能为空";
private static final String ERROR_PORT_END_REQUIRED = "结束端口不能为空";
private static final String ERROR_PORT_RANGE_INVALID = "端口必须在1-65535范围内";
private static final String ERROR_PORT_ORDER_INVALID = "结束端口不能小于起始端口";
// ========== 依赖注入 ==========
@Resource
private TunnelMapper tunnelMapper;
@Resource
@Lazy
@@ -79,52 +37,41 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
@Resource
ViteConfigService viteConfigService;
@Resource
ChainTunnelService chainTunnelService;
// ========== 公共接口实现 ==========
/**
* 创建新节点
*
* @param nodeDto 节点创建数据传输对象
* @return 创建结果响应
*/
@Override
public R createNode(NodeDto nodeDto) {
Node node = buildNewNode(nodeDto);
boolean result = this.save(node);
return result ? R.ok(SUCCESS_CREATE_MSG) : R.err(ERROR_CREATE_MSG);
validatePortRange(nodeDto.getPort());
Node node = new Node();
node.setSecret(IdUtil.simpleUUID());
node.setStatus(0);
node.setPort(nodeDto.getPort());
node.setName(nodeDto.getName());
node.setServerIp(nodeDto.getServerIp());
long currentTime = System.currentTimeMillis();
node.setCreatedTime(currentTime);
node.setUpdatedTime(currentTime);
node.setInterfaceName(nodeDto.getInterfaceName());
this.save(node);
return R.ok();
}
/**
* 获取所有节点列表
* 注意:返回结果中会隐藏节点密钥信息
*
* @return 包含所有节点的响应对象
*/
@Override
public R getAllNodes() {
List<Node> nodeList = this.list();
hideNodeSecrets(nodeList);
List<Node> nodeList = this.list(new QueryWrapper<Node>().orderByDesc("status"));
nodeList.forEach(node -> node.setSecret(null));
return R.ok(nodeList);
}
/**
* 更新节点信息
*
* @param nodeUpdateDto 节点更新数据传输对象
* @return 更新结果响应
*/
@Override
public R updateNode(NodeUpdateDto nodeUpdateDto) {
// 1. 验证节点是否存在
Node node = this.getById(nodeUpdateDto.getId());
if (node == null) {
return R.err(ERROR_NODE_NOT_FOUND);
return R.err("节点不存在");
}
//1.1 如果节点在线 且传入更新的 http/tls/socks 任意一项与数据库不一致,则通过 WS 通知节点更新设置
boolean online = node.getStatus() != null && node.getStatus() == 1;
Integer newHttp = nodeUpdateDto.getHttp();
Integer newTls = nodeUpdateDto.getTls();
@@ -147,242 +94,94 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
}
// 2. 构建更新对象并执行更新
Node updateNode = buildUpdateNode(nodeUpdateDto);
boolean result = this.updateById(updateNode);
// 更新隧道入口ip
List<Tunnel> inNodeId = tunnelService.list(new QueryWrapper<Tunnel>().eq("in_node_id", updateNode.getId()));
if (!inNodeId.isEmpty()) {
for (Tunnel tunnel : inNodeId) {
tunnel.setInIp(updateNode.getIp());
}
tunnelService.updateBatchById(inNodeId);
}
// 更新服务器出口ip
List<Tunnel> outNodeId = tunnelService.list(new QueryWrapper<Tunnel>().eq("out_node_id", updateNode.getId()));
if (!outNodeId.isEmpty()) {
for (Tunnel tunnel : outNodeId) {
tunnel.setOutIp(updateNode.getServerIp());
}
tunnelService.updateBatchById(outNodeId);
}
return result ? R.ok(SUCCESS_UPDATE_MSG) : R.err(ERROR_UPDATE_MSG);
this.updateById(updateNode);
return R.ok();
}
/**
* 删除节点
* 删除前会检查是否有隧道正在使用该节点
*
* @param id 节点ID
* @return 删除结果响应
*/
@Override
public R deleteNode(Long id) {
// 1. 验证节点是否存在
Node node = this.getById(id);
if (node == null) {
return R.err(ERROR_NODE_NOT_FOUND);
return R.err("节点不存在");
}
// 2. 检查节点使用情况
R usageCheckResult = checkNodeUsage(id);
if (usageCheckResult.getCode() != 0) {
return usageCheckResult;
List<ChainTunnel> list = chainTunnelService.list(new QueryWrapper<ChainTunnel>().eq("node_id", id).groupBy("tunnel_id"));
for (ChainTunnel tunnel : list) {
tunnelService.deleteTunnel(tunnel.getTunnelId());
}
// 3. 执行删除操作
boolean result = this.removeById(id);
return result ? R.ok(SUCCESS_DELETE_MSG) : R.err(ERROR_DELETE_MSG);
this.removeById(id);
return R.ok();
}
/**
* 根据ID获取节点信息
*
* @param id 节点ID
* @return 节点对象
* @throws RuntimeException 当节点不存在时抛出异常
*/
@Override
public Node getNodeById(Long id) {
public R getInstallCommand(Long id) {
Node node = this.getById(id);
if (node == null) {
throw new RuntimeException(ERROR_NODE_NOT_FOUND);
return R.err("节点不存在");
}
return node;
ViteConfig viteConfig = viteConfigService.getOne(new QueryWrapper<ViteConfig>().eq("name", "ip"));
if (viteConfig == null) return R.err("请先前往网站配置中设置ip");
StringBuilder command = new StringBuilder();
command.append("curl -L https://github.com/bqlpfy/flux-panel/releases/download/2.0.0-beta/install.sh")
.append(" -o ./install.sh && chmod +x ./install.sh && ");
String processedServerAddr = processServerAddress(viteConfig.getValue());
command.append("./install.sh")
.append(" -a ").append(processedServerAddr) // 服务器地址
.append(" -s ").append(node.getSecret()); // 节点密钥
return R.ok(command);
}
// ========== 私有辅助方法 ==========
/**
* 构建新节点对象
*
* @param nodeDto 节点创建DTO
* @return 构建完成的节点对象
*/
private Node buildNewNode(NodeDto nodeDto) {
Node node = new Node();
BeanUtils.copyProperties(nodeDto, node);
// 验证端口范围
validatePortRange(node.getPortSta(), node.getPortEnd());
// 设置默认属性
node.setSecret(IdUtil.simpleUUID());
node.setStatus(NODE_STATUS_ACTIVE);
// 设置时间戳
long currentTime = System.currentTimeMillis();
node.setCreatedTime(currentTime);
node.setUpdatedTime(currentTime);
return node;
}
/**
* 构建节点更新对象
*
* @param nodeUpdateDto 节点更新DTO
* @return 构建完成的更新对象
*/
private Node buildUpdateNode(NodeUpdateDto nodeUpdateDto) {
validatePortRange(nodeUpdateDto.getPort());
Node node = new Node();
node.setId(nodeUpdateDto.getId());
node.setName(nodeUpdateDto.getName());
node.setIp(nodeUpdateDto.getIp());
node.setServerIp(nodeUpdateDto.getServerIp());
node.setPortSta(nodeUpdateDto.getPortSta());
node.setPortEnd(nodeUpdateDto.getPortEnd());
node.setPort(nodeUpdateDto.getPort());
node.setHttp(nodeUpdateDto.getHttp());
node.setTls(nodeUpdateDto.getTls());
node.setSocks(nodeUpdateDto.getSocks());
// 验证端口范围
validatePortRange(node.getPortSta(), node.getPortEnd());
node.setUpdatedTime(System.currentTimeMillis());
node.setInterfaceName(nodeUpdateDto.getInterfaceName());
node.setTcpListenAddr(nodeUpdateDto.getTcpListenAddr());
node.setUdpListenAddr(nodeUpdateDto.getUdpListenAddr());
return node;
}
/**
* 隐藏节点列表中的密钥信息
*
* @param nodeList 节点列表
*/
private void hideNodeSecrets(List<Node> nodeList) {
nodeList.forEach(node -> node.setSecret(null));
}
/**
* 检查节点使用情况
* 验证是否有隧道正在使用该节点作为入口或出口节点
*
* @param nodeId 节点ID
* @return 检查结果响应
*/
private R checkNodeUsage(Long nodeId) {
// 检查入口节点使用情况
R inNodeCheckResult = checkInNodeUsage(nodeId);
if (inNodeCheckResult.getCode() != 0) {
return inNodeCheckResult;
private void validatePortRange(String port) {
Pattern PORT_PATTERN = Pattern.compile( "([0-9]{1,5})(-([0-9]{1,5}))?");
if (port == null || port.isEmpty()) {
throw new RuntimeException("可用端口不合法");
}
// 检查出口节点使用情况
return checkOutNodeUsage(nodeId);
}
/**
* 检查节点作为入口节点的使用情况
*
* @param nodeId 节点ID
* @return 检查结果响应
*/
private R checkInNodeUsage(Long nodeId) {
QueryWrapper<Tunnel> query = new QueryWrapper<>();
query.eq("in_node_id", nodeId);
long tunnelCount = tunnelMapper.selectCount(query);
if (tunnelCount > 0) {
String errorMsg = String.format(ERROR_IN_NODE_IN_USE, tunnelCount);
return R.err(errorMsg);
String[] parts = port.split(",");
for (String part : parts) {
part = part.trim();
if (!PORT_PATTERN.matcher(part).matches()) {
throw new RuntimeException("可用端口不合法");
}
if (part.contains("-")) {
String[] range = part.split("-");
int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]);
if (start < 0 || end < 0 || end > 65535 || start > end) {
throw new RuntimeException("可用端口不合法");
}
} else {
int ports = Integer.parseInt(part);
if (ports < 0 || ports > 65535) {
throw new RuntimeException("可用端口不合法");
}
}
}
return R.ok();
}
/**
* 检查节点作为出口节点的使用情况
*
* @param nodeId 节点ID
* @return 检查结果响应
*/
private R checkOutNodeUsage(Long nodeId) {
QueryWrapper<Tunnel> query = new QueryWrapper<>();
query.eq("out_node_id", nodeId);
long tunnelCount = tunnelMapper.selectCount(query);
if (tunnelCount > 0) {
String errorMsg = String.format(ERROR_OUT_NODE_IN_USE, tunnelCount);
return R.err(errorMsg);
}
return R.ok();
}
/**
* 获取节点安装命令
* 根据节点信息生成对应的安装命令
*
* @param id 节点ID
* @return 包含安装命令的响应对象
*/
@Override
public R getInstallCommand(Long id) {
// 1. 验证节点是否存在
Node node = this.getById(id);
if (node == null) {
return R.err(ERROR_NODE_NOT_FOUND);
}
// 2. 构建安装命令
return buildInstallCommand(node);
}
/**
* 构建节点安装命令
*
* @param node 节点对象
* @return 格式化的安装命令
*/
private R buildInstallCommand(Node node) {
ViteConfig viteConfig = viteConfigService.getOne(new QueryWrapper<ViteConfig>().eq("name", "ip"));
if (viteConfig == null) return R.err("请先前往网站配置中设置ip");
StringBuilder command = new StringBuilder();
// 第一部分:下载安装脚本
command.append("curl -L https://github.com/bqlpfy/flux-panel/releases/download/1.4.2/install.sh")
.append(" -o ./install.sh && chmod +x ./install.sh && ");
// 处理服务器地址,如果是IPv6需要添加方括号
String processedServerAddr = processServerAddress(viteConfig.getValue());
// 第二部分:执行安装脚本(去掉-u参数)
command.append("./install.sh")
.append(" -a ").append(processedServerAddr) // 服务器地址
.append(" -s ").append(node.getSecret()); // 节点密钥
return R.ok(command.toString());
}
/**
* 处理服务器地址,确保IPv6地址被方括号包裹
*
* @param serverAddr 原始服务器地址,格式可能为 host:port
* @return 处理后的服务器地址
*/
private String processServerAddress(String serverAddr) {
if (StrUtil.isBlank(serverAddr)) {
return serverAddr;
@@ -411,12 +210,7 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
return serverAddr;
}
/**
* 判断是否为IPv6地址
*
* @param address 地址字符串(不包含端口号)
* @return 是否为IPv6地址
*/
private boolean isIPv6Address(String address) {
// IPv6地址包含多个冒号,至少2个
if (!address.contains(":")) {
@@ -428,33 +222,4 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
return colonCount >= 2;
}
/**
* 验证端口范围的有效性
*
* @param portSta 起始端口
* @param portEnd 结束端口
* @throws RuntimeException 当端口范围无效时抛出异常
*/
private void validatePortRange(Integer portSta, Integer portEnd) {
// 检查起始端口是否为空
if (portSta == null) {
throw new RuntimeException(ERROR_PORT_STA_REQUIRED);
}
// 检查结束端口是否为空
if (portEnd == null) {
throw new RuntimeException(ERROR_PORT_END_REQUIRED);
}
// 检查端口范围是否在有效区间内
if (portSta < 1 || portSta > 65535 || portEnd < 1 || portEnd > 65535) {
throw new RuntimeException(ERROR_PORT_RANGE_INVALID);
}
// 检查端口顺序是否正确
if (portEnd < portSta) {
throw new RuntimeException(ERROR_PORT_ORDER_INVALID);
}
}
}
@@ -5,15 +5,9 @@ import com.admin.common.dto.SpeedLimitDto;
import com.admin.common.dto.SpeedLimitUpdateDto;
import com.admin.common.lang.R;
import com.admin.common.utils.GostUtil;
import com.admin.entity.Node;
import com.admin.entity.SpeedLimit;
import com.admin.entity.Tunnel;
import com.admin.entity.UserTunnel;
import com.admin.entity.*;
import com.admin.mapper.SpeedLimitMapper;
import com.admin.service.NodeService;
import com.admin.service.SpeedLimitService;
import com.admin.service.TunnelService;
import com.admin.service.UserTunnelService;
import com.admin.service.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.Data;
@@ -22,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
@@ -41,354 +36,96 @@ import java.util.UUID;
@Service
public class SpeedLimitServiceImpl extends ServiceImpl<SpeedLimitMapper, SpeedLimit> implements SpeedLimitService {
// ========== 常量定义 ==========
/** Gost操作成功响应消息 */
private static final String GOST_SUCCESS_MSG = "OK";
/** Gost未找到资源响应消息 */
private static final String GOST_NOT_FOUND_MSG = "not found";
/** 限速规则状态 */
private static final int SPEED_LIMIT_ACTIVE_STATUS = 1;
private static final int SPEED_LIMIT_INACTIVE_STATUS = 0;
/** 速度转换比率:比特到字节 */
private static final double BITS_TO_BYTES_RATIO = 8.0;
/** 成功响应消息 */
private static final String SUCCESS_UPDATE_MSG = "限速规则更新成功";
private static final String SUCCESS_DELETE_MSG = "限速规则删除成功";
/** 错误响应消息 */
private static final String ERROR_CREATE_MSG = "限速规则创建失败";
private static final String ERROR_UPDATE_MSG = "限速规则更新失败";
private static final String ERROR_DELETE_MSG = "限速规则删除失败";
private static final String ERROR_SPEED_LIMIT_NOT_FOUND = "限速规则不存在";
private static final String ERROR_TUNNEL_NOT_FOUND = "指定的隧道不存在";
private static final String ERROR_TUNNEL_NOT_EXISTS = "隧道不存在";
private static final String ERROR_TUNNEL_NAME_MISMATCH = "隧道名称与隧道ID不匹配";
private static final String ERROR_SPEED_LIMIT_IN_USE = "该限速规则还有用户在使用 请先取消分配";
// ========== 依赖注入 ==========
@Autowired
@Resource
@Lazy
private TunnelService tunnelService;
TunnelService tunnelService;
@Autowired
private NodeService nodeService;
@Resource
NodeService nodeService;
@Autowired
private UserTunnelService userTunnelService;
@Resource
UserTunnelService userTunnelService;
@Autowired
@Lazy
private SpeedLimitService speedLimitService;
@Resource
ChainTunnelService chainTunnelService;
// ========== 公共接口实现 ==========
/**
* 创建限速规则
*
* @param speedLimitDto 限速规则创建数据传输对象
* @return 创建结果响应
*/
@Override
public R createSpeedLimit(SpeedLimitDto speedLimitDto) {
// 1. 验证隧道
TunnelValidationResult tunnelValidation = validateTunnelWithResult(speedLimitDto.getTunnelId(), speedLimitDto.getTunnelName());
if (tunnelValidation.isHasError()) {
return R.err(tunnelValidation.getErrorMessage());
}
Tunnel tunnel = tunnelService.getById(speedLimitDto.getTunnelId());
if (tunnel == null) return R.err("隧道不存在");
// 2. 创建限速规则实体
SpeedLimit speedLimit = createSpeedLimitEntity(speedLimitDto);
if (!this.save(speedLimit)) {
return R.err(ERROR_CREATE_MSG);
}
SpeedLimit speedLimit = new SpeedLimit();
BeanUtils.copyProperties(speedLimitDto, speedLimit);
long currentTime = System.currentTimeMillis();
speedLimit.setCreatedTime(currentTime);
speedLimit.setUpdatedTime(currentTime);
speedLimit.setStatus(1);
this.save(speedLimit);
// 3. 调用Gost API添加限速器
R gostResult = addGostLimiter(speedLimit, tunnelValidation.getTunnel());
if (gostResult.getCode() != 0) {
handleGostOperationFailure(speedLimit);
this.removeById(speedLimit.getId());
return gostResult;
String speedInMBps = convertBitsToMBps(speedLimit.getSpeed());
List<ChainTunnel> tunnelList = chainTunnelService.list(new QueryWrapper<ChainTunnel>().eq("tunnel_id", speedLimit.getTunnelId()));
for (ChainTunnel chainTunnel : tunnelList) {
Node node = nodeService.getById(chainTunnel.getNodeId());
if (node != null) {
GostUtil.AddLimiters(node.getId(),speedLimit.getId(),speedInMBps);
}
}
return R.ok();
}
/**
* 获取所有限速规则
*
* @return 包含所有限速规则的响应对象
*/
@Override
public R getAllSpeedLimits() {
List<SpeedLimit> speedLimits = this.list();
return R.ok(speedLimits);
}
/**
* 更新限速规则
*
* @param speedLimitUpdateDto 限速规则更新数据传输对象
* @return 更新结果响应
*/
@Override
public R updateSpeedLimit(SpeedLimitUpdateDto speedLimitUpdateDto) {
// 1. 验证限速规则是否存在
SpeedLimit speedLimit = this.getById(speedLimitUpdateDto.getId());
if (speedLimit == null) {
return R.err(ERROR_SPEED_LIMIT_NOT_FOUND);
if (speedLimit == null) return R.err("限速不存在");
BeanUtils.copyProperties(speedLimitUpdateDto, speedLimit);
speedLimit.setUpdatedTime(System.currentTimeMillis());
String speedInMBps = convertBitsToMBps(speedLimit.getSpeed());
List<ChainTunnel> tunnelList = chainTunnelService.list(new QueryWrapper<ChainTunnel>().eq("tunnel_id", speedLimit.getTunnelId()));
for (ChainTunnel chainTunnel : tunnelList) {
Node node = nodeService.getById(chainTunnel.getNodeId());
if (node != null) {
GostUtil.UpdateLimiters(node.getId(),speedLimit.getId(),speedInMBps);
}
}
// 2. 验证隧道
TunnelValidationResult tunnelValidation = validateTunnelWithResult(speedLimitUpdateDto.getTunnelId(), speedLimitUpdateDto.getTunnelName());
if (tunnelValidation.isHasError()) {
return R.err(tunnelValidation.getErrorMessage());
}
// 3. 更新限速规则数据
updateSpeedLimitEntity(speedLimitUpdateDto, speedLimit);
// 4. 调用Gost API更新限速器
R gostResult = updateGostLimiter(speedLimit, tunnelValidation.getTunnel());
if (gostResult.getCode() != 0) {
return gostResult;
}
// 5. 保存更新
boolean result = this.updateById(speedLimit);
return result ? R.ok(SUCCESS_UPDATE_MSG) : R.err(ERROR_UPDATE_MSG);
this.updateById(speedLimit);
return R.ok();
}
/**
* 删除限速规则
* 删除前会检查是否有用户正在使用该限速规则
*
* @param id 限速规则ID
* @return 删除结果响应
*/
@Override
public R deleteSpeedLimit(Long id) {
// 1. 验证限速规则是否存在
SpeedLimit speedLimit = this.getById(id);
if (speedLimit == null) {
return R.err(ERROR_SPEED_LIMIT_NOT_FOUND);
}
// 2. 检查使用情况
R usageCheckResult = checkSpeedLimitUsage(id);
if (usageCheckResult.getCode() != 0) {
return usageCheckResult;
}
// 3. 获取隧道信息
Tunnel tunnel = tunnelService.getById(speedLimit.getTunnelId());
if (tunnel == null) {
this.removeById(id);
return R.ok();
}
// 4. 调用Gost API删除限速器
deleteGostLimiter(id, tunnel);
// 5. 删除限速规则
boolean result = this.removeById(id);
return result ? R.ok(SUCCESS_DELETE_MSG) : R.err(ERROR_DELETE_MSG);
}
// ========== 私有辅助方法 ==========
/**
* 验证隧道是否存在且名称匹配(返回详细结果)
*
* @param tunnelId 隧道ID
* @param tunnelName 隧道名称
* @return 隧道验证结果
*/
private TunnelValidationResult validateTunnelWithResult(Long tunnelId, String tunnelName) {
Tunnel tunnel = tunnelService.getById(tunnelId);
if (tunnel == null) {
return TunnelValidationResult.error(ERROR_TUNNEL_NOT_FOUND);
}
if (!tunnel.getName().equals(tunnelName)) {
return TunnelValidationResult.error(ERROR_TUNNEL_NAME_MISMATCH);
}
return TunnelValidationResult.success(tunnel);
}
/**
* 验证隧道是否存在且名称匹配(兼容原有方法)
*
* @param tunnelId 隧道ID
* @param tunnelName 隧道名称
* @return 验证结果响应
*/
private R validateTunnel(Long tunnelId, String tunnelName) {
TunnelValidationResult result = validateTunnelWithResult(tunnelId, tunnelName);
return result.isHasError() ? R.err(result.getErrorMessage()) : R.ok(result.getTunnel());
}
/**
* 创建限速规则实体对象
*
* @param speedLimitDto 限速规则创建DTO
* @return 构建完成的限速规则对象
*/
private SpeedLimit createSpeedLimitEntity(SpeedLimitDto speedLimitDto) {
SpeedLimit speedLimit = new SpeedLimit();
BeanUtils.copyProperties(speedLimitDto, speedLimit);
// 设置默认属性
long currentTime = System.currentTimeMillis();
speedLimit.setCreatedTime(currentTime);
speedLimit.setUpdatedTime(currentTime);
speedLimit.setStatus(SPEED_LIMIT_ACTIVE_STATUS);
return speedLimit;
}
/**
* 更新限速规则实体对象
*
* @param speedLimitUpdateDto 限速规则更新DTO
* @param speedLimit 待更新的限速规则对象
*/
private void updateSpeedLimitEntity(SpeedLimitUpdateDto speedLimitUpdateDto, SpeedLimit speedLimit) {
BeanUtils.copyProperties(speedLimitUpdateDto, speedLimit);
speedLimit.setUpdatedTime(System.currentTimeMillis());
}
/**
* 检查限速规则使用情况
*
* @param speedLimitId 限速规则ID
* @return 检查结果响应
*/
private R checkSpeedLimitUsage(Long speedLimitId) {
int userCount = userTunnelService.count(new QueryWrapper<UserTunnel>().eq("speed_id", speedLimitId));
if (userCount != 0) {
return R.err(ERROR_SPEED_LIMIT_IN_USE);
if (speedLimit == null) return R.err("限速规则不存在");
int userCount = userTunnelService.count(new QueryWrapper<UserTunnel>().eq("speed_id", speedLimit.getId()));
if (userCount != 0) return R.err("该限速规则还有用户在使用 请先取消分配");
List<ChainTunnel> tunnelList = chainTunnelService.list(new QueryWrapper<ChainTunnel>().eq("tunnel_id", speedLimit.getTunnelId()));
for (ChainTunnel chainTunnel : tunnelList) {
Node node = nodeService.getById(chainTunnel.getNodeId());
if (node != null) {
GostUtil.DeleteLimiters(node.getId(),speedLimit.getId());
}
}
this.removeById(id);
return R.ok();
}
/**
* 添加Gost限速器
*
* @param speedLimit 限速规则对象
* @param tunnel 隧道对象
* @return 操作结果响应
*/
private R addGostLimiter(SpeedLimit speedLimit, Tunnel tunnel) {
String speedInMBps = convertBitsToMBps(speedLimit.getSpeed());
Node node = nodeService.getNodeById(tunnel.getInNodeId());
GostDto gostResult = GostUtil.AddLimiters(
node.getId(),
speedLimit.getId(),
speedInMBps
);
return isGostOperationSuccess(gostResult) ? R.ok() : R.err(gostResult.getMsg());
}
/**
* 更新Gost限速器
*
* @param speedLimit 限速规则对象
* @param tunnel 隧道对象
* @return 操作结果响应
*/
private R updateGostLimiter(SpeedLimit speedLimit, Tunnel tunnel) {
String speedInMBps = convertBitsToMBps(speedLimit.getSpeed());
Node node = nodeService.getNodeById(tunnel.getInNodeId());
// 尝试更新限速器
GostDto gostResult = GostUtil.UpdateLimiters(node.getId(), speedLimit.getId(), speedInMBps);
// 如果限速器不存在,则创建新的
if (gostResult.getMsg().contains(GOST_NOT_FOUND_MSG)) {
gostResult = GostUtil.AddLimiters(node.getId(), speedLimit.getId(), speedInMBps);
}
return isGostOperationSuccess(gostResult) ? R.ok() : R.err(gostResult.getMsg());
}
/**
* 删除Gost限速器
*
* @param speedLimitId 限速规则ID
* @param tunnel 隧道对象
* @return 操作结果响应
*/
private R deleteGostLimiter(Long speedLimitId, Tunnel tunnel) {
Node node = nodeService.getNodeById(tunnel.getInNodeId());
GostDto gostResult = GostUtil.DeleteLimiters(node.getId(), speedLimitId);
return isGostOperationSuccess(gostResult) ? R.ok() : R.err(gostResult.getMsg());
}
/**
* 处理Gost操作失败的情况
*
* @param speedLimit 限速规则对象
*/
private void handleGostOperationFailure(SpeedLimit speedLimit) {
speedLimit.setStatus(SPEED_LIMIT_INACTIVE_STATUS);
speedLimitService.updateById(speedLimit);
}
/**
* 将比特率转换为兆字节每秒
*
* @param speedInBits 比特率速度
* @return 兆字节每秒字符串
*/
private String convertBitsToMBps(Integer speedInBits) {
double mbs = speedInBits / BITS_TO_BYTES_RATIO;
double mbs = speedInBits / 8.0;
BigDecimal bd = new BigDecimal(mbs).setScale(1, RoundingMode.HALF_UP);
return bd.doubleValue() + "";
}
/**
* 检查Gost操作是否成功
*
* @param gostResult Gost操作结果
* @return 是否成功
*/
private boolean isGostOperationSuccess(GostDto gostResult) {
return Objects.equals(gostResult.getMsg(), GOST_SUCCESS_MSG);
}
// ========== 内部数据类 ==========
/**
* 隧道验证结果封装类
*/
@Data
private static class TunnelValidationResult {
private final boolean hasError;
private final String errorMessage;
private final Tunnel tunnel;
private TunnelValidationResult(boolean hasError, String errorMessage, Tunnel tunnel) {
this.hasError = hasError;
this.errorMessage = errorMessage;
this.tunnel = tunnel;
}
public static TunnelValidationResult success(Tunnel tunnel) {
return new TunnelValidationResult(false, null, tunnel);
}
public static TunnelValidationResult error(String errorMessage) {
return new TunnelValidationResult(true, errorMessage, null);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -10,9 +10,7 @@ import com.admin.common.utils.GostUtil;
import com.admin.common.utils.JwtUtil;
import com.admin.common.utils.Md5Util;
import com.admin.entity.*;
import com.admin.mapper.ForwardMapper;
import com.admin.mapper.UserMapper;
import com.admin.mapper.UserTunnelMapper;
import com.admin.service.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -20,6 +18,7 @@ import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -28,83 +27,25 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* <p>
* 用户服务实现类
* 提供用户的增删改查功能,包括用户登录、创建、更新、删除和套餐信息查询
* 支持用户关联数据的级联删除,包括转发和Gost服务的清理
* </p>
*
* @author QAQ
* @since 2025-06-03
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// ========== 常量定义 ==========
/** 用户角色常量 */
private static final int ADMIN_ROLE_ID = 0; // 管理员角色ID
private static final int USER_ROLE_ID = 1; // 普通用户角色ID
/** 用户状态常量 */
private static final int USER_STATUS_ACTIVE = 1; // 用户启用状态
private static final int USER_STATUS_DISABLED = 0; // 用户停用状态
/** 隧道类型常量 */
private static final int TUNNEL_TYPE_TUNNEL_FORWARD = 2; // 隧道转发类型
/** 成功响应消息 */
private static final String SUCCESS_CREATE_MSG = "用户创建成功";
private static final String SUCCESS_UPDATE_MSG = "用户更新成功";
private static final String SUCCESS_DELETE_MSG = "用户及关联数据删除成功";
/** 错误响应消息 */
private static final String ERROR_LOGIN_CREDENTIALS = "账号或密码错误";
private static final String ERROR_ACCOUNT_DISABLED = "账户停用";
private static final String ERROR_CREATE_FAILED = "用户创建失败";
private static final String ERROR_UPDATE_FAILED = "用户更新失败";
private static final String ERROR_DELETE_FAILED = "用户删除失败";
private static final String ERROR_USER_NOT_FOUND = "用户不存在";
private static final String ERROR_USERNAME_EXISTS = "用户名已存在";
private static final String ERROR_USERNAME_TAKEN = "用户名已被其他用户使用";
private static final String ERROR_CANNOT_DELETE_ADMIN = "不能删除管理员用户";
private static final String ERROR_CANNOT_UPDATE_ADMIN = "不能修改管理员用户信息";
private static final String ERROR_USER_NOT_LOGGED_IN = "用户未登录或token无效";
private static final String ERROR_GET_PACKAGE_INFO_FAILED = "获取套餐信息失败";
private static final String ERROR_CURRENT_PASSWORD_WRONG = "当前密码错误";
private static final String ERROR_PASSWORD_NOT_MATCH = "新密码和确认密码不匹配";
@Resource
@Lazy
ForwardService forwardService;
/** 默认账号密码 */
private static final String DEFAULT_USERNAME = "admin_user";
private static final String DEFAULT_PASSWORD = "admin_user";
/** 登录响应字段名 */
private static final String LOGIN_TOKEN_FIELD = "token";
private static final String LOGIN_NAME_FIELD = "name";
private static final String LOGIN_ROLE_ID_FIELD = "role_id";
private static final String LOGIN_REQUIRE_PASSWORD_CHANGE_FIELD = "requirePasswordChange";
@Resource
UserMapper userMapper;
// ========== 依赖注入 ==========
@Resource
private UserMapper userMapper;
@Lazy
TunnelService tunnelService;
@Resource
@Lazy
private ForwardMapper forwardMapper;
@Resource
private UserTunnelMapper userTunnelMapper;
@Resource
@Lazy
private TunnelService tunnelService;
@Resource
@Lazy
private NodeService nodeService;
NodeService nodeService;
@Resource
UserTunnelService userTunnelService;
@@ -116,21 +57,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
StatisticsFlowService statisticsFlowService;
@Resource
private ImageCaptchaApplication application;
@Lazy
ForwardPortService forwardPortService;
@Resource
ImageCaptchaApplication application;
// ========== 公共接口实现 ==========
/**
* 用户登录
* 验证验证码、用户名密码,检查账户状态,生成JWT令牌
*
* @param loginDto 登录数据传输对象
* @return 登录结果响应,包含令牌和用户信息
*/
@Override
public R login(LoginDto loginDto) {
// 1. 验证验证码
ViteConfig viteConfig = viteConfigService.getOne(new QueryWrapper<ViteConfig>().eq("name", "captcha_enabled"));
if (viteConfig != null && Objects.equals(viteConfig.getValue(), "true")) {
if (StringUtils.isBlank(loginDto.getCaptchaId())) return R.err("验证码校验失败");
@@ -138,216 +73,129 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
if (!valid) return R.err("验证码校验失败");
}
// 2. 验证用户凭据
LoginValidationResult validationResult = validateUserCredentials(loginDto);
if (validationResult.isHasError()) {
return R.err(validationResult.getErrorMessage());
}
// 3. 生成令牌并返回用户信息
User user = validationResult.getUser();
User user = this.getOne(new QueryWrapper<User>().eq("user", loginDto.getUsername()));
if (user == null) return R.err("账号或密码错误");
if (!user.getPwd().equals(Md5Util.md5(loginDto.getPassword()))) return R.err("账号或密码错误");
if (user.getStatus() == 0) return R.err("账号被停用");
String token = JwtUtil.generateToken(user);
// 4. 检查是否使用默认账号密码
boolean requirePasswordChange = isDefaultCredentials(loginDto.getUsername(), loginDto.getPassword());
boolean requirePasswordChange = Objects.equals(loginDto.getUsername(), "admin_user") || Objects.equals(loginDto.getPassword(), "admin_user");
return R.ok(MapUtil.builder()
.put(LOGIN_TOKEN_FIELD, token)
.put(LOGIN_NAME_FIELD, user.getUser())
.put(LOGIN_ROLE_ID_FIELD, user.getRoleId())
.put(LOGIN_REQUIRE_PASSWORD_CHANGE_FIELD, requirePasswordChange)
.put("token", token)
.put("name", user.getUser())
.put("role_id", user.getRoleId())
.put("requirePasswordChange", requirePasswordChange)
.build());
}
/**
* 创建用户
* 检查用户名唯一性,设置默认属性,加密密码
*
* @param userDto 用户创建数据传输对象
* @return 创建结果响应
*/
@Override
public R createUser(UserDto userDto) {
// 1. 验证用户名唯一性
R usernameValidationResult = validateUsernameUniqueness(userDto.getUser(), null);
if (usernameValidationResult.getCode() != 0) {
return usernameValidationResult;
}
// 2. 构建用户实体并保存
User user = buildNewUserEntity(userDto);
boolean result = this.save(user);
if (result) {
// 3. 添加到期时间延时任务
return R.ok(SUCCESS_CREATE_MSG);
} else {
return R.err(ERROR_CREATE_FAILED);
}
int count = this.count(new QueryWrapper<User>().eq("user", userDto.getUser()));
if (count > 0) return R.err("用户名已存在");
User user = new User();
BeanUtils.copyProperties(userDto, user);
user.setPwd(Md5Util.md5(userDto.getPwd()));
user.setStatus(1);
user.setRoleId(1);
long currentTime = System.currentTimeMillis();
user.setCreatedTime(currentTime);
user.setUpdatedTime(currentTime);
this.save(user);
return R.ok();
}
/**
* 获取所有用户(分页)
* 支持关键字搜索,排除管理员用户,清除密码信息
*
* @return 分页用户列表响应
*/
@Override
public R getAllUsers() {
return R.ok(this.list(new QueryWrapper<User>().ne("role_id", ADMIN_ROLE_ID)));
List<User> list = this.list(new QueryWrapper<User>().ne("role_id", 0));
return R.ok(list);
}
/**
* 更新用户信息
* 验证用户存在性和用户名唯一性,处理密码加密
*
* @param userUpdateDto 用户更新数据传输对象
* @return 更新结果响应
*/
@Override
public R updateUser(UserUpdateDto userUpdateDto) {
// 1. 验证用户是否存在
if (!isUserExists(userUpdateDto.getId())) {
return R.err(ERROR_USER_NOT_FOUND);
}
User user = this.getById(userUpdateDto.getId());
if (user == null) return R.err("用户不存在");
if (user.getRoleId() == 0) return R.err("请不要作死");
// 2. 验证用户名唯一性
R usernameValidationResult = validateUsernameUniqueness(userUpdateDto.getUser(), userUpdateDto.getId());
if (usernameValidationResult.getCode() != 0) {
return usernameValidationResult;
}
int count = this.count(new QueryWrapper<User>().eq("user", userUpdateDto.getUser()).ne("id", userUpdateDto.getId()));
if (count > 0) return R.err("用户名已存在");
// 3. 验证更新条件
R updateValidationResult = validateUserUpdate(userUpdateDto.getId());
if (updateValidationResult.getCode() != 0) {
return updateValidationResult;
}
// 4. 构建更新实体并保存
User updateUser = buildUpdateUserEntity(userUpdateDto);
boolean result = this.updateById(updateUser);
if (result) {
// 5. 处理到期时间延时任务
return R.ok(SUCCESS_UPDATE_MSG);
User updateUser = new User();
BeanUtils.copyProperties(userUpdateDto, updateUser);
if (StrUtil.isNotBlank(userUpdateDto.getPwd())) {
updateUser.setPwd(Md5Util.md5(userUpdateDto.getPwd()));
} else {
return R.err(ERROR_UPDATE_FAILED);
updateUser.setPwd(null); // 不更新密码字段
}
updateUser.setUpdatedTime(System.currentTimeMillis());
this.updateById(updateUser);
return R.ok();
}
/**
* 删除用户
* 级联删除用户相关的所有数据,包括转发、Gost服务和隧道权限
*
* @param id 用户ID
* @return 删除结果响应
*/
@Override
public R deleteUser(Long id) {
// 1. 验证删除条件
R deleteValidationResult = validateUserDeletion(id);
if (deleteValidationResult.getCode() != 0) {
return deleteValidationResult;
}
try {
// 2. 级联删除用户相关数据
deleteUserRelatedData(id);
statisticsFlowService.remove(new QueryWrapper<StatisticsFlow>().eq("user_id", id));
// 3. 删除用户
boolean result = this.removeById(id);
return result ? R.ok(SUCCESS_DELETE_MSG) : R.err(ERROR_DELETE_FAILED);
} catch (Exception e) {
e.printStackTrace();
return R.err("删除用户时发生错误:" + e.getMessage());
User user = this.getById(id);
if (user == null) return R.err("用户不存在");
if (user.getRoleId() == 0) return R.err("请不要作死");
List<Forward> forwardList = forwardService.list(new QueryWrapper<Forward>().eq("user_id", id));
for (Forward forward : forwardList) {
forwardService.deleteForward(forward.getId());
}
forwardService.remove(new QueryWrapper<Forward>().eq("user_id", id));
userTunnelService.remove(new QueryWrapper<UserTunnel>().eq("user_id", id));
statisticsFlowService.remove(new QueryWrapper<StatisticsFlow>().eq("user_id", id));
this.removeById(id);
return R.ok();
}
/**
* 获取用户套餐信息
* 包括用户基本信息、隧道权限详情和转发详情
*
* @return 用户套餐信息响应
*/
@Override
public R getUserPackageInfo() {
try {
// 1. 获取当前用户信息
CurrentUserInfo currentUser = getCurrentUserInfo();
if (currentUser.isHasError()) {
return R.err(currentUser.getErrorMessage());
}
// 2. 构建套餐信息
UserPackageDto packageDto = buildUserPackageDto(currentUser);
return R.ok(packageDto);
} catch (Exception e) {
e.printStackTrace();
return R.err(ERROR_GET_PACKAGE_INFO_FAILED);
}
Integer userId = JwtUtil.getUserIdFromToken();
User user = this.getById(userId);
if (user == null) return R.err("用户不存在");
UserPackageDto.UserInfoDto userInfo = buildUserInfoDto(user);
List<UserPackageDto.UserTunnelDetailDto> tunnelPermissions = userMapper.getUserTunnelDetails(userId);
List<UserPackageDto.UserForwardDetailDto> forwards = userMapper.getUserForwardDetails(user.getId().intValue());
fillForwardInIpAndPort(forwards);
List<StatisticsFlow> statisticsFlows = getLast24HoursFlowStatistics(user.getId());
UserPackageDto packageDto = new UserPackageDto();
packageDto.setUserInfo(userInfo);
packageDto.setTunnelPermissions(tunnelPermissions);
packageDto.setForwards(forwards);
packageDto.setStatisticsFlows(statisticsFlows);
return R.ok(packageDto);
}
/**
* 修改账号密码
* 验证当前密码、新密码确认、用户名唯一性、更新用户账号密码
*
* @param changePasswordDto 修改账号密码数据传输对象
* @return 修改结果响应
*/
@Override
public R updatePassword(ChangePasswordDto changePasswordDto) {
try {
// 1. 获取当前用户信息
CurrentUserInfo currentUser = getCurrentUserInfo();
if (currentUser.isHasError()) {
return R.err(currentUser.getErrorMessage());
}
// 2. 验证新密码和确认密码是否匹配
if (!changePasswordDto.getNewPassword().equals(changePasswordDto.getConfirmPassword())) {
return R.err(ERROR_PASSWORD_NOT_MATCH);
}
// 3. 验证当前密码是否正确
User user = currentUser.getUser();
String currentPasswordMd5 = Md5Util.md5(changePasswordDto.getCurrentPassword());
if (!user.getPwd().equals(currentPasswordMd5)) {
return R.err(ERROR_CURRENT_PASSWORD_WRONG);
}
// 4. 验证新用户名唯一性(如果与当前用户名不同)
if (!user.getUser().equals(changePasswordDto.getNewUsername())) {
R usernameValidationResult = validateUsernameUniqueness(changePasswordDto.getNewUsername(), user.getId());
if (usernameValidationResult.getCode() != 0) {
return usernameValidationResult;
}
}
// 5. 更新用户名和密码
User updateUser = new User();
updateUser.setId(user.getId());
updateUser.setUser(changePasswordDto.getNewUsername());
updateUser.setPwd(Md5Util.md5(changePasswordDto.getNewPassword()));
updateUser.setUpdatedTime(System.currentTimeMillis());
boolean result = this.updateById(updateUser);
return result ? R.ok("账号密码修改成功") : R.err(ERROR_UPDATE_FAILED);
} catch (Exception e) {
e.printStackTrace();
return R.err("修改账号密码时发生错误:" + e.getMessage());
Integer userId = JwtUtil.getUserIdFromToken();
User user = this.getById(userId);
if (user == null) return R.err("用户不存在");
if (!changePasswordDto.getNewPassword().equals(changePasswordDto.getConfirmPassword())) {
return R.err("新密码和确认密码不匹配");
}
String currentPasswordMd5 = Md5Util.md5(changePasswordDto.getCurrentPassword());
if (!user.getPwd().equals(currentPasswordMd5)) {
return R.err("当前密码错误");
}
if (!user.getUser().equals(changePasswordDto.getNewUsername())) {
user.setPwd(Md5Util.md5(changePasswordDto.getNewPassword()));
int count = this.count(new QueryWrapper<User>().eq("user", changePasswordDto.getNewUsername()).ne("id", user.getId()));
if (count > 0) return R.err("用户名已存在");
}
User updateUser = new User();
updateUser.setId(user.getId());
updateUser.setUser(changePasswordDto.getNewUsername());
updateUser.setPwd(Md5Util.md5(changePasswordDto.getNewPassword()));
updateUser.setUpdatedTime(System.currentTimeMillis());
this.updateById(updateUser);
return R.ok();
}
@Override
public R reset(ResetFlowDto resetFlowDto) {
if (resetFlowDto.getType() == 1){ // 清零账号流量
User user = this.getById(resetFlowDto.getId());
if (user == null) return R.err(ERROR_USER_NOT_FOUND);
if (user == null) return R.err("用户不存在");
user.setInFlow(0L);
user.setOutFlow(0L);
this.updateById(user);
@@ -361,339 +209,6 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
return R.ok();
}
// ========== 私有辅助方法 ==========
/**
* 验证用户登录凭据
*
* @param loginDto 登录数据传输对象
* @return 登录验证结果
*/
private LoginValidationResult validateUserCredentials(LoginDto loginDto) {
User user = this.getOne(new QueryWrapper<User>().eq("user", loginDto.getUsername()));
if (user == null) {
return LoginValidationResult.error(ERROR_LOGIN_CREDENTIALS);
}
if (!user.getPwd().equals(Md5Util.md5(loginDto.getPassword()))) {
return LoginValidationResult.error(ERROR_LOGIN_CREDENTIALS);
}
if (user.getStatus() == USER_STATUS_DISABLED) {
return LoginValidationResult.error(ERROR_ACCOUNT_DISABLED);
}
return LoginValidationResult.success(user);
}
/**
* 检查是否使用默认账号密码
*
* @param username 用户名
* @param password 密码
* @return 是否是默认凭据
*/
private boolean isDefaultCredentials(String username, String password) {
return DEFAULT_USERNAME.equals(username) || DEFAULT_PASSWORD.equals(password);
}
/**
* 验证用户名唯一性
*
* @param username 用户名
* @param excludeUserId 排除的用户ID(用于更新时排除自己)
* @return 验证结果响应
*/
private R validateUsernameUniqueness(String username, Long excludeUserId) {
QueryWrapper<User> queryWrapper = new QueryWrapper<User>().eq("user", username);
if (excludeUserId != null) {
queryWrapper.ne("id", excludeUserId);
}
User existUser = this.getOne(queryWrapper);
if (existUser != null) {
String errorMsg = excludeUserId != null ? ERROR_USERNAME_TAKEN : ERROR_USERNAME_EXISTS;
return R.err(errorMsg);
}
return R.ok();
}
/**
* 构建新用户实体对象
*
* @param userDto 用户创建DTO
* @return 构建完成的用户对象
*/
private User buildNewUserEntity(UserDto userDto) {
User user = new User();
BeanUtils.copyProperties(userDto, user);
// 设置加密密码
user.setPwd(Md5Util.md5(userDto.getPwd()));
// 设置默认属性
user.setStatus(userDto.getStatus() != null ? userDto.getStatus() : USER_STATUS_ACTIVE);
user.setRoleId(USER_ROLE_ID);
// 设置时间戳
long currentTime = System.currentTimeMillis();
user.setCreatedTime(currentTime);
user.setUpdatedTime(currentTime);
return user;
}
/**
* 检查用户是否存在
*
* @param userId 用户ID
* @return 用户是否存在
*/
private boolean isUserExists(Long userId) {
return this.getById(userId) != null;
}
/**
* 构建用户更新实体对象
*
* @param userUpdateDto 用户更新DTO
* @return 构建完成的更新对象
*/
private User buildUpdateUserEntity(UserUpdateDto userUpdateDto) {
User user = new User();
BeanUtils.copyProperties(userUpdateDto, user);
// 处理密码更新
if (StrUtil.isNotBlank(userUpdateDto.getPwd())) {
user.setPwd(Md5Util.md5(userUpdateDto.getPwd()));
} else {
user.setPwd(null); // 不更新密码字段
}
// 设置更新时间
user.setUpdatedTime(System.currentTimeMillis());
return user;
}
/**
* 验证用户删除条件
*
* @param userId 用户ID
* @return 验证结果响应
*/
private R validateUserDeletion(Long userId) {
User user = this.getById(userId);
if (user == null) {
return R.err(ERROR_USER_NOT_FOUND);
}
if (user.getRoleId() == ADMIN_ROLE_ID) {
return R.err(ERROR_CANNOT_DELETE_ADMIN);
}
return R.ok();
}
/**
* 验证用户更新条件
*
* @param userId 用户ID
* @return 验证结果响应
*/
private R validateUserUpdate(Long userId) {
User user = this.getById(userId);
if (user == null) {
return R.err(ERROR_USER_NOT_FOUND);
}
if (user.getRoleId() == ADMIN_ROLE_ID) {
return R.err(ERROR_CANNOT_UPDATE_ADMIN);
}
return R.ok();
}
/**
* 删除用户相关的所有数据
*
* @param userId 用户ID
*/
private void deleteUserRelatedData(Long userId) {
// 1. 删除用户的所有转发和对应的Gost服务
deleteUserForwardsAndGostServices(userId);
// 2. 删除用户隧道权限
deleteUserTunnelPermissions(userId);
}
/**
* 删除用户转发和对应的Gost服务
*
* @param userId 用户ID
*/
private void deleteUserForwardsAndGostServices(Long userId) {
QueryWrapper<Forward> forwardQuery = new QueryWrapper<>();
forwardQuery.eq("user_id", userId);
List<Forward> userForwards = forwardMapper.selectList(forwardQuery);
for (Forward forward : userForwards) {
try {
// 删除Gost服务
deleteGostServicesForForward(forward, userId);
} catch (Exception e) {
// 记录错误但继续删除,避免因为Gost服务删除失败而阻断用户删除
System.err.println("删除用户转发对应的Gost服务失败,转发ID: " + forward.getId() + ", 错误: " + e.getMessage());
}
// 删除数据库中的转发记录
forwardMapper.deleteById(forward.getId());
}
}
/**
* 删除转发对应的Gost服务
*
* @param forward 转发对象
* @param userId 用户ID
*/
private void deleteGostServicesForForward(Forward forward, Long userId) {
Tunnel tunnel = tunnelService.getById(forward.getTunnelId());
if (tunnel == null) return;
Node inNode = nodeService.getNodeById(tunnel.getInNodeId());
if (inNode == null) return;
// 获取用户隧道关系
UserTunnel userTunnel = getUserTunnelRelation(userId, tunnel.getId());
if (userTunnel == null) return;
String serviceName = buildServiceName(forward.getId(), userId, userTunnel.getId());
// 删除主服务
GostUtil.DeleteService(inNode.getId(), serviceName);
// 如果是隧道转发,还需要删除链和远程服务
if (tunnel.getType() == TUNNEL_TYPE_TUNNEL_FORWARD) {
deleteGostTunnelForwardServices(tunnel, serviceName, inNode);
}
}
/**
* 删除隧道转发相关的Gost服务
*
* @param tunnel 隧道对象
* @param serviceName 服务名称
* @param inNode 入口节点
*/
private void deleteGostTunnelForwardServices(Tunnel tunnel, String serviceName, Node inNode) {
Node outNode = nodeService.getNodeById(tunnel.getOutNodeId());
if (outNode != null) {
GostUtil.DeleteChains(inNode.getId(), serviceName);
GostUtil.DeleteRemoteService(outNode.getId(), serviceName);
}
}
/**
* 获取用户隧道关系
*
* @param userId 用户ID
* @param tunnelId 隧道ID
* @return 用户隧道关系对象
*/
private UserTunnel getUserTunnelRelation(Long userId, Long tunnelId) {
return userTunnelService.getOne(new QueryWrapper<UserTunnel>()
.eq("user_id", userId)
.eq("tunnel_id", tunnelId));
}
/**
* 构建服务名称
*
* @param forwardId 转发ID
* @param userId 用户ID
* @param userTunnelId 用户隧道ID
* @return 服务名称
*/
private String buildServiceName(Long forwardId, Long userId, Integer userTunnelId) {
return forwardId + "_" + userId + "_" + userTunnelId;
}
/**
* 删除用户隧道权限
*
* @param userId 用户ID
*/
private void deleteUserTunnelPermissions(Long userId) {
QueryWrapper<UserTunnel> userTunnelQuery = new QueryWrapper<>();
userTunnelQuery.eq("user_id", userId);
userTunnelMapper.delete(userTunnelQuery);
}
/**
* 获取当前用户信息
*
* @return 当前用户信息结果
*/
private CurrentUserInfo getCurrentUserInfo() {
Integer userId = JwtUtil.getUserIdFromToken();
Integer roleId = JwtUtil.getRoleIdFromToken();
if (userId == null) {
return CurrentUserInfo.error(ERROR_USER_NOT_LOGGED_IN);
}
User user = this.getById(userId);
if (user == null) {
return CurrentUserInfo.error(ERROR_USER_NOT_FOUND);
}
return CurrentUserInfo.success(user, roleId);
}
/**
* 构建用户套餐信息DTO
*
* @param currentUser 当前用户信息
* @return 用户套餐信息DTO
*/
private UserPackageDto buildUserPackageDto(CurrentUserInfo currentUser) {
User user = currentUser.getUser();
Integer roleId = currentUser.getRoleId();
// 1. 构造用户基本信息
UserPackageDto.UserInfoDto userInfo = buildUserInfoDto(user);
// 2. 获取隧道权限详情
List<UserPackageDto.UserTunnelDetailDto> tunnelPermissions = getTunnelPermissions(user.getId());
// 3. 获取转发详情
List<UserPackageDto.UserForwardDetailDto> forwards = userMapper.getUserForwardDetails(user.getId().intValue());
// 4. 查询最近24小时流量信息,没有的补0
List<StatisticsFlow> statisticsFlows = getLast24HoursFlowStatistics(user.getId());
// 5. 构造返回结果
UserPackageDto packageDto = new UserPackageDto();
packageDto.setUserInfo(userInfo);
packageDto.setTunnelPermissions(tunnelPermissions);
packageDto.setForwards(forwards);
packageDto.setStatisticsFlows(statisticsFlows);
return packageDto;
}
/**
* 构建用户基本信息DTO
*
* @param user 用户对象
* @return 用户基本信息DTO
*/
private UserPackageDto.UserInfoDto buildUserInfoDto(User user) {
UserPackageDto.UserInfoDto userInfo = new UserPackageDto.UserInfoDto();
userInfo.setId(user.getId());
@@ -710,134 +225,85 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
return userInfo;
}
/**
* 获取隧道权限详情
*
* @param userId 用户ID
* @return 隧道权限详情列表
*/
private List<UserPackageDto.UserTunnelDetailDto> getTunnelPermissions(Long userId) {
return userMapper.getUserTunnelDetails(userId.intValue());
}
/**
* 获取用户最近24小时的流量统计数据,没有数据的时间点补0
*
* @param userId 用户ID
* @return 最近24小时流量统计列表
*/
private List<StatisticsFlow> getLast24HoursFlowStatistics(Long userId) {
// 按ID倒序查最近24条记录(ID越大越新,时间就是23:00, 22:00, 21:00...这样倒序)
List<StatisticsFlow> recentFlows = statisticsFlowService.list(
new QueryWrapper<StatisticsFlow>()
.eq("user_id", userId)
.orderByDesc("id")
.last("LIMIT 24")
);
List<StatisticsFlow> result = new ArrayList<>(recentFlows);
// 如果查出来的记录不足24条,需要补0和对应的时间
if (result.size() < 24) {
// 获取最早记录的时间,继续往前推
int startHour = getCurrentHour();
int startHour = java.time.LocalDateTime.now().getHour();
if (!result.isEmpty()) {
// 从最后一条记录的时间继续往前推
String lastTime = result.get(result.size() - 1).getTime();
String lastTime = result.getLast().getTime();
startHour = parseHour(lastTime) - 1;
}
// 补0到24条
while (result.size() < 24) {
if (startHour < 0) startHour = 23; // 跨天处理
if (startHour < 0) startHour = 23;
StatisticsFlow emptyFlow = new StatisticsFlow();
emptyFlow.setUserId(userId);
emptyFlow.setFlow(0L);
emptyFlow.setTotalFlow(0L);
emptyFlow.setTime(String.format("%02d:00", startHour));
result.add(emptyFlow);
startHour--;
}
}
log.info("用户 {} 获取到 {} 条实际记录,补齐为 {} 条24小时记录", userId, recentFlows.size(), result.size());
return result;
}
/**
* 获取当前小时(0-23)
*/
private int getCurrentHour() {
private int parseHour(String timeStr) {
if (timeStr != null && timeStr.contains(":")) {
return Integer.parseInt(timeStr.split(":")[0]);
}
return java.time.LocalDateTime.now().getHour();
}
/**
* 解析时间字符串获取小时数
*/
private int parseHour(String timeStr) {
try {
if (timeStr != null && timeStr.contains(":")) {
return Integer.parseInt(timeStr.split(":")[0]);
private void fillForwardInIpAndPort(List<UserPackageDto.UserForwardDetailDto> forwards) {
for (UserPackageDto.UserForwardDetailDto forward : forwards) {
Tunnel tunnel = tunnelService.getById(forward.getTunnelId());
if (tunnel == null) continue;
List<ForwardPort> forwardPorts = forwardPortService.list(
new QueryWrapper<ForwardPort>().eq("forward_id", forward.getId())
);
if (forwardPorts.isEmpty()) continue;
boolean useTunnelInIp = tunnel.getInIp() != null && !tunnel.getInIp().trim().isEmpty();
List<String> ipList = new ArrayList<>();
List<Integer> portList = new ArrayList<>();
if (useTunnelInIp) {
String[] tunnelInIps = tunnel.getInIp().split(",");
for (String ip : tunnelInIps) {
if (ip != null && !ip.trim().isEmpty()) {
ipList.add(ip.trim());
}
}
} else {
for (ForwardPort forwardPort : forwardPorts) {
Node node = nodeService.getById(forwardPort.getNodeId());
if (node != null && node.getServerIp() != null) {
ipList.add(node.getServerIp());
}
}
}
for (ForwardPort forwardPort : forwardPorts) {
if (forwardPort.getPort() != null) {
portList.add(forwardPort.getPort());
}
}
List<String> uniqueIps = ipList.stream().distinct().toList();
List<Integer> uniquePorts = portList.stream().distinct().toList();
java.util.Set<String> ipPortSet = new java.util.LinkedHashSet<>();
for (String ip : uniqueIps) {
for (Integer port : uniquePorts) {
ipPortSet.add(ip + ":" + port);
}
}
if (!ipPortSet.isEmpty()) {
forward.setInIp(String.join(",", ipPortSet));
forward.setInPort(uniquePorts.getFirst());
}
} catch (Exception e) {
// 解析失败,返回当前小时
}
return getCurrentHour();
}
// ========== 内部数据类 ==========
/**
* 登录验证结果封装类
*/
@Data
private static class LoginValidationResult {
private final boolean hasError;
private final String errorMessage;
private final User user;
private LoginValidationResult(boolean hasError, String errorMessage, User user) {
this.hasError = hasError;
this.errorMessage = errorMessage;
this.user = user;
}
public static LoginValidationResult success(User user) {
return new LoginValidationResult(false, null, user);
}
public static LoginValidationResult error(String errorMessage) {
return new LoginValidationResult(true, errorMessage, null);
}
}
/**
* 当前用户信息封装类
*/
@Data
private static class CurrentUserInfo {
private final boolean hasError;
private final String errorMessage;
private final User user;
private final Integer roleId;
private CurrentUserInfo(boolean hasError, String errorMessage, User user, Integer roleId) {
this.hasError = hasError;
this.errorMessage = errorMessage;
this.user = user;
this.roleId = roleId;
}
public static CurrentUserInfo success(User user, Integer roleId) {
return new CurrentUserInfo(false, null, user, roleId);
}
public static CurrentUserInfo error(String errorMessage) {
return new CurrentUserInfo(true, errorMessage, null, null);
}
}
}
@@ -1,9 +1,6 @@
package com.admin.service.impl;
import com.admin.common.dto.UserTunnelDto;
import com.admin.common.dto.UserTunnelQueryDto;
import com.admin.common.dto.UserTunnelUpdateDto;
import com.admin.common.dto.UserTunnelWithDetailDto;
import com.admin.common.dto.*;
import com.admin.common.lang.R;
import com.admin.entity.UserTunnel;
import com.admin.mapper.TunnelMapper;
@@ -42,347 +39,74 @@ import java.util.Map;
@Service
public class UserTunnelServiceImpl extends ServiceImpl<UserTunnelMapper, UserTunnel> implements UserTunnelService {
// ========== 常量定义 ==========
/** 成功响应消息 */
private static final String SUCCESS_ASSIGN_MSG = "用户隧道权限分配成功";
private static final String SUCCESS_REMOVE_MSG = "用户隧道权限删除成功";
private static final String SUCCESS_UPDATE_FLOW_MSG = "用户隧道流量限制更新成功";
private static final String SUCCESS_UPDATE_MSG = "用户隧道权限更新成功";
/** 错误响应消息 */
private static final String ERROR_ASSIGN_FAILED = "用户隧道权限分配失败";
private static final String ERROR_PERMISSION_EXISTS = "该用户已拥有此隧道权限";
private static final String ERROR_PERMISSION_NOT_FOUND = "未找到对应的用户隧道权限记录";
private static final String ERROR_USER_TUNNEL_NOT_EXISTS = "用户隧道权限不存在";
private static final String ERROR_NOT_EXISTS = "不存在";
private static final String ERROR_UPDATE_FAILED = "用户隧道权限更新失败";
// ========== 依赖注入 ==========
@Autowired
@Resource
@Lazy
private ForwardService forwardService;
@Autowired
@Lazy
private TunnelService tunnelService;
@Autowired
private NodeService nodeService;
// ========== 公共接口实现 ==========
/**
* 分配用户隧道权限
* 检查权限是否已存在,避免重复分配
*
* @param userTunnelDto 用户隧道权限分配数据传输对象
* @return 分配结果响应
*/
@Override
public R assignUserTunnel(UserTunnelDto userTunnelDto) {
// 1. 检查权限是否已存在
if (isUserTunnelPermissionExists(userTunnelDto.getUserId(), userTunnelDto.getTunnelId())) {
return R.err(ERROR_PERMISSION_EXISTS);
}
// 2. 创建用户隧道权限实体并保存
UserTunnel userTunnel = buildUserTunnelEntity(userTunnelDto);
// 设置默认状态为启用
userTunnel.setStatus(1);
boolean success = this.save(userTunnel);
if (success) {
return R.ok(SUCCESS_ASSIGN_MSG);
}
return R.err(ERROR_ASSIGN_FAILED);
}
/**
* 获取用户隧道权限列表
* 通过连表查询获取用户隧道权限及隧道详细信息
*
* @param queryDto 用户隧道权限查询数据传输对象
* @return 用户隧道权限详情列表响应
*/
@Override
public R getUserTunnelList(UserTunnelQueryDto queryDto) {
List<UserTunnelWithDetailDto> userTunnelDetails = getUserTunnelDetailsFromDatabase(queryDto.getUserId());
return R.ok(userTunnelDetails);
}
/**
* 删除用户隧道权限
*
* @param id 用户隧道权限ID
* @return 删除结果响应
*/
@Override
public R removeUserTunnel(Integer id) {
// 1. 获取用户隧道权限信息
UserTunnel userTunnel = this.getById(id);
if (userTunnel == null) {
return R.err(ERROR_PERMISSION_NOT_FOUND);
}
// 2. 删除该用户在该隧道下的所有转发
try {
removeUserTunnelForwards(userTunnel.getUserId(), userTunnel.getTunnelId());
} catch (Exception e) {
// 转发删除失败,记录日志但不阻止权限删除
}
// 4. 删除用户隧道权限记录
boolean success = this.removeById(id);
return success ? R.ok(SUCCESS_REMOVE_MSG) : R.err(ERROR_PERMISSION_NOT_FOUND);
}
/**
* 更新用户隧道权限
* 支持更新流量限制、数量限制、流量重置时间、过期时间和限速规则
*
* @param updateDto 用户隧道权限更新数据传输对象
* @return 更新结果响应
*/
@Override
public R updateUserTunnel(UserTunnelUpdateDto updateDto) {
// 1. 验证用户隧道权限是否存在
UserTunnel existingUserTunnel = this.getById(updateDto.getId());
if (existingUserTunnel == null) {
return R.err(ERROR_USER_TUNNEL_NOT_EXISTS);
}
// 2. 检查是否更新了限速规则
boolean speedChanged = hasSpeedChanged(existingUserTunnel.getSpeedId(), updateDto.getSpeedId());
// 3. 更新用户隧道权限属性
updateUserTunnelProperties(existingUserTunnel, updateDto);
// 4. 保存更新
boolean success = this.updateById(existingUserTunnel);
if (success) {
// 6. 如果限速规则发生变化,更新该用户隧道下的所有转发
if (speedChanged) {
updateUserTunnelForwardsSpeed(existingUserTunnel.getUserId(), existingUserTunnel.getTunnelId(), updateDto.getSpeedId());
}
return R.ok(SUCCESS_UPDATE_MSG);
}
return R.err(ERROR_UPDATE_FAILED);
}
// ========== 私有辅助方法 ==========
/**
* 检查用户隧道权限是否已存在
*
* @param userId 用户ID
* @param tunnelId 隧道ID
* @return 权限是否已存在
*/
private boolean isUserTunnelPermissionExists(Integer userId, Integer tunnelId) {
QueryWrapper<UserTunnel> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId).eq("tunnel_id", tunnelId);
UserTunnel existingUserTunnel = this.getOne(queryWrapper);
return existingUserTunnel != null;
}
/**
* 构建用户隧道权限实体对象
*
* @param userTunnelDto 用户隧道权限DTO
* @return 构建完成的用户隧道权限对象
*/
private UserTunnel buildUserTunnelEntity(UserTunnelDto userTunnelDto) {
int count = this.count(new QueryWrapper<UserTunnel>().eq("user_id", userTunnelDto.getUserId()).eq("tunnel_id", userTunnelDto.getTunnelId()));
if (count > 0) return R.err("该用户已拥有此隧道权限");
UserTunnel userTunnel = new UserTunnel();
BeanUtils.copyProperties(userTunnelDto, userTunnel);
return userTunnel;
userTunnel.setStatus(1);
this.save(userTunnel);
return R.ok();
}
/**
* 从数据库获取用户隧道权限详情
*
* @param userId 用户ID
* @return 用户隧道权限详情列表
*/
private List<UserTunnelWithDetailDto> getUserTunnelDetailsFromDatabase(Integer userId) {
return this.baseMapper.getUserTunnelWithDetails(userId);
@Override
public R getUserTunnelList(UserTunnelQueryDto queryDto) {
List<UserTunnelWithDetailDto> userTunnelWithDetails = this.baseMapper.getUserTunnelWithDetails(queryDto.getUserId());
return R.ok(userTunnelWithDetails);
}
/**
* 更新用户隧道权限属性
*
* @param existingUserTunnel 现有的用户隧道权限对象
* @param updateDto 更新数据传输对象
*/
private void updateUserTunnelProperties(UserTunnel existingUserTunnel, UserTunnelUpdateDto updateDto) {
// 更新基本属性
existingUserTunnel.setFlow(updateDto.getFlow());
existingUserTunnel.setNum(updateDto.getNum());
// 更新可选属性(仅在非空时更新)
updateOptionalProperty(existingUserTunnel::setFlowResetTime, updateDto.getFlowResetTime());
updateOptionalProperty(existingUserTunnel::setExpTime, updateDto.getExpTime());
updateOptionalProperty(existingUserTunnel::setStatus, updateDto.getStatus());
// 更新限速规则ID(允许设置为null,表示不限速)
existingUserTunnel.setSpeedId(updateDto.getSpeedId());
@Override
public R removeUserTunnel(Integer id) {
UserTunnel userTunnel = this.getById(id);
if (userTunnel == null) return R.err("未找到对应的用户隧道权限记录");
List<Forward> forwardList = forwardService.list(new QueryWrapper<Forward>().eq("user_id", userTunnel.getUserId()).eq("tunnel_id", userTunnel.getTunnelId()));
for (Forward forward : forwardList) {
forwardService.deleteForward(forward.getId());
}
this.removeById(id);
return R.ok();
}
@Override
public R updateUserTunnel(UserTunnelUpdateDto updateDto) {
UserTunnel userTunnel = this.getById(updateDto.getId());
if (userTunnel == null) return R.err("隧道不存在");
boolean speedChanged = hasSpeedChanged(userTunnel.getSpeedId(), updateDto.getSpeedId());
userTunnel.setFlow(updateDto.getFlow());
userTunnel.setNum(updateDto.getNum());
updateOptionalProperty(userTunnel::setFlowResetTime, updateDto.getFlowResetTime());
updateOptionalProperty(userTunnel::setExpTime, updateDto.getExpTime());
updateOptionalProperty(userTunnel::setStatus, updateDto.getStatus());
userTunnel.setSpeedId(updateDto.getSpeedId());
this.updateById(userTunnel);
if (speedChanged) {
List<Forward> forwardList = forwardService.list(new QueryWrapper<Forward>().eq("user_id", userTunnel.getUserId()).eq("tunnel_id", userTunnel.getTunnelId()));
for (Forward forward : forwardList) {
ForwardUpdateDto forwardUpdateDto = new ForwardUpdateDto();
forwardUpdateDto.setId(forward.getId());
forwardUpdateDto.setUserId(forward.getUserId());
forwardUpdateDto.setName(forward.getName());
forwardUpdateDto.setRemoteAddr(forward.getRemoteAddr());
forwardUpdateDto.setStrategy(forward.getStrategy());
forwardService.updateForward(forwardUpdateDto);
}
}
return R.err("用户隧道权限更新失败");
}
/**
* 更新可选属性(仅在值非空时更新)
*
* @param setter 属性设置方法
* @param value 属性值
* @param <T> 属性类型
*/
private <T> void updateOptionalProperty(java.util.function.Consumer<T> setter, T value) {
if (value != null) {
setter.accept(value);
}
}
/**
* 删除用户在指定隧道下的所有转发
*
* @param userId 用户ID
* @param tunnelId 隧道ID
*/
private void removeUserTunnelForwards(Integer userId, Integer tunnelId) {
// 查询该用户在该隧道下的所有转发
QueryWrapper<Forward> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId).eq("tunnel_id", tunnelId);
List<Forward> userTunnelForwards = forwardService.list(queryWrapper);
if (!userTunnelForwards.isEmpty()) {
// 获取用户隧道权限信息,用于构建服务名称
UserTunnel userTunnel = getUserTunnelByUserAndTunnel(userId, tunnelId);
for (Forward forward : userTunnelForwards) {
try {
// 先调用GostUtil删除/停止服务
stopForwardService(forward, userId, userTunnel != null ? userTunnel.getId() : 0);
// 然后删除数据库记录
forwardService.removeById(forward.getId());
} catch (Exception e) {
// 单个转发删除失败,记录错误但继续处理其他转发
}
}
}
}
/**
* 删除转发服务(按创建的反向顺序删除:主服务 -> 远端服务 -> 转发链)
*
* @param forward 转发对象
* @param userId 用户ID
* @param userTunnelId 用户隧道ID
*/
private void stopForwardService(Forward forward, Integer userId, Integer userTunnelId) {
try {
Tunnel tunnel = tunnelService.getById(forward.getTunnelId());
if (tunnel == null) {
return;
}
Node inNode = nodeService.getById(tunnel.getInNodeId());
Node outNode = nodeService.getById(tunnel.getOutNodeId());
String serviceName = buildServiceName(forward.getId(), Long.valueOf(userId), userTunnelId);
// 1. 先删除主服务
if (inNode != null) {
try {
GostUtil.DeleteService(inNode.getId(), serviceName);
} catch (Exception e) {
// 主服务删除失败,记录但继续
}
}
// 2. 如果是隧道转发,删除远端服务
if (tunnel.getType() == 1 && outNode != null && !outNode.getId().equals(inNode != null ? inNode.getId() : null)) {
try {
GostUtil.DeleteRemoteService(outNode.getId(), serviceName);
} catch (Exception e) {
// 远端服务删除失败,记录但继续
}
}
// 3. 如果是隧道转发,最后删除转发链
if (tunnel.getType() == 1 && inNode != null) {
try {
GostUtil.DeleteChains(inNode.getId(), serviceName);
} catch (Exception e) {
// 转发链删除失败,记录但继续
}
}
} catch (Exception e) {
// 服务删除失败,记录错误
throw new RuntimeException("删除转发服务失败,转发ID:" + forward.getId() + ",错误:" + e.getMessage(), e);
}
}
/**
* 根据用户ID和隧道ID获取用户隧道权限
*
* @param userId 用户ID
* @param tunnelId 隧道ID
* @return 用户隧道权限对象
*/
private UserTunnel getUserTunnelByUserAndTunnel(Integer userId, Integer tunnelId) {
try {
QueryWrapper<UserTunnel> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId).eq("tunnel_id", tunnelId);
return this.getOne(queryWrapper);
} catch (Exception e) {
return null;
}
}
/**
* 构建服务名称
*
* @param forwardId 转发ID
* @param userId 用户ID
* @param userTunnelId 用户隧道ID
* @return 服务名称
*/
private String buildServiceName(Long forwardId, Long userId, Integer userTunnelId) {
return forwardId + "_" + userId + "_" + userTunnelId;
}
/**
* 检查用户隧道是否启用且有到期时间
*
* @param userTunnel 用户隧道对象
* @return 是否启用且有到期时间
*/
private boolean isEnabledAndHasExpTime(UserTunnel userTunnel) {
return userTunnel.getStatus() != null && userTunnel.getStatus() == 1
&& userTunnel.getExpTime() != null;
}
/**
* 检查限速规则是否发生变化
*
* @param oldSpeedId 原始限速规则ID
* @param newSpeedId 新的限速规则ID
* @return 限速规则是否发生变化
*/
private boolean hasSpeedChanged(Integer oldSpeedId, Integer newSpeedId) {
if (oldSpeedId == null && newSpeedId == null) {
return false;
@@ -392,56 +116,5 @@ public class UserTunnelServiceImpl extends ServiceImpl<UserTunnelMapper, UserTun
}
return !oldSpeedId.equals(newSpeedId);
}
/**
* 更新用户隧道下所有转发的限速规则
* 管理员操作,不需要权限检查,直接查出该用户在该隧道下的所有转发并应用新的限速
*
* @param userId 用户ID
* @param tunnelId 隧道ID
* @param speedId 新的限速规则ID
*/
private void updateUserTunnelForwardsSpeed(Integer userId, Integer tunnelId, Integer speedId) {
// 1. 查询该用户在该隧道下的所有转发
QueryWrapper<Forward> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId).eq("tunnel_id", tunnelId);
List<Forward> userTunnelForwards = forwardService.list(queryWrapper);
if (userTunnelForwards.isEmpty()) {
return;
}
// 2. 获取隧道信息
Tunnel tunnel = tunnelService.getById(tunnelId);
if (tunnel == null) {
return;
}
// 3. 获取用户隧道权限信息
UserTunnel userTunnel = getUserTunnelByUserAndTunnel(userId, tunnelId);
if (userTunnel == null) {
return;
}
// 4. 获取入口节点信息
Node inNode = nodeService.getById(tunnel.getInNodeId());
if (inNode == null) {
return;
}
// 5. 批量更新该用户在该隧道下所有转发的限速配置(只更新入口节点)
for (Forward forward : userTunnelForwards) {
String serviceName = buildServiceName(forward.getId(), Long.valueOf(userId), userTunnel.getId());
String interfaceName = null;
// 创建主服务
if (tunnel.getType() != 2) { // 不是隧道转发服务才会存在网络接口
interfaceName = forward.getInterfaceName();
}
// 6. 更新入口节点的主服务限速配置(使用批量UpdateService接口)
GostUtil.UpdateService(inNode.getId(), serviceName, forward.getInPort(), speedId, forward.getRemoteAddr(), tunnel.getType(), tunnel, forward.getStrategy(), interfaceName);
}
}
}
@@ -24,24 +24,7 @@ import java.util.Map;
@Service
public class ViteConfigServiceImpl extends ServiceImpl<ViteConfigMapper, ViteConfig> implements ViteConfigService {
// ========== 常量定义 ==========
/** 成功响应消息 */
private static final String SUCCESS_UPDATE_MSG = "配置更新成功";
/** 错误响应消息 */
private static final String ERROR_UPDATE_MSG = "配置更新失败";
private static final String ERROR_CONFIG_NOT_FOUND = "配置不存在";
private static final String ERROR_CONFIG_NAME_REQUIRED = "配置名称不能为空";
private static final String ERROR_CONFIG_VALUE_REQUIRED = "配置值不能为空";
// ========== 公共接口实现 ==========
/**
* 获取所有网站配置
*
* @return 包含所有配置的Map
*/
@Override
public R getConfigs() {
List<ViteConfig> configList = this.list();
@@ -54,89 +37,48 @@ public class ViteConfigServiceImpl extends ServiceImpl<ViteConfigMapper, ViteCon
return R.ok(configMap);
}
/**
* 根据配置名称获取配置值
*
* @param name 配置名称
* @return 配置响应对象
*/
@Override
public R getConfigByName(String name) {
if (!StringUtils.hasText(name)) {
return R.err(ERROR_CONFIG_NAME_REQUIRED);
}
if (!StringUtils.hasText(name)) return R.err("配置名称不能为空");
QueryWrapper<ViteConfig> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", name);
ViteConfig config = this.getOne(queryWrapper);
if (config == null) {
return R.err(ERROR_CONFIG_NOT_FOUND);
}
if (config == null) return R.err("配置不存在");
return R.ok(config);
}
/**
* 批量更新网站配置
*
* @param configMap 配置Map
* @return 更新结果响应
*/
@Override
public R updateConfigs(Map<String, String> configMap) {
if (configMap == null || configMap.isEmpty()) {
return R.err("配置数据不能为空");
}
if (configMap == null || configMap.isEmpty()) return R.err("配置数据不能为空");
try {
for (Map.Entry<String, String> entry : configMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if (!StringUtils.hasText(name)) {
continue; // 跳过无效的配置名
}
updateOrCreateConfig(name, value);
for (Map.Entry<String, String> entry : configMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if (!StringUtils.hasText(name)) {
continue;
}
return R.ok(SUCCESS_UPDATE_MSG);
} catch (Exception e) {
return R.err(ERROR_UPDATE_MSG + ": " + e.getMessage());
updateOrCreateConfig(name, value);
}
return R.ok();
}
/**
* 更新单个配置项
*
* @param name 配置名
* @param value 配置值
* @return 更新结果响应
*/
@Override
public R updateConfig(String name, String value) {
// 1. 验证必填字段
if (!StringUtils.hasText(name)) {
return R.err(ERROR_CONFIG_NAME_REQUIRED);
}
if (!StringUtils.hasText(value)) {
return R.err(ERROR_CONFIG_VALUE_REQUIRED);
}
try {
updateOrCreateConfig(name, value);
return R.ok(SUCCESS_UPDATE_MSG);
} catch (Exception e) {
return R.err(ERROR_UPDATE_MSG + ": " + e.getMessage());
}
if (!StringUtils.hasText(name)) return R.err("配置名称不能为空");
if (!StringUtils.hasText(value)) return R.err("配置值不能为空");
updateOrCreateConfig(name, value);
return R.ok();
}
// ========== 私有辅助方法 ==========
/**
* 更新或创建配置项
* 如果配置存在则更新,不存在则创建
*/
private void updateOrCreateConfig(String name, String value) {
QueryWrapper<ViteConfig> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", name);