修改白名单和网络配置问题

This commit is contained in:
xydkj 2026-04-09 17:23:01 +08:00
parent 57639e78a1
commit e86ada5c68
10 changed files with 627 additions and 278 deletions

View File

@ -21,31 +21,32 @@ public class IpWhitelistController {
@PostMapping("/add")
@Operation(summary = "添加IP白名单", description = "新增一条IP白名单记录")
public ApiResponse<String> addWhitelist(@RequestBody IpWhitelistRequest req) {
public ApiResponse<Void> addWhitelist(@RequestBody IpWhitelistRequest req) {
ipWhitelistService.addWhitelist(req);
return ApiResponse.success("添加成功");
return ApiResponse.success();
}
@PostMapping("/update")
@Operation(summary = "更新IP白名单", description = "根据ID更新IP白名单信息")
public ApiResponse<String> updateWhitelist(@RequestBody IpWhitelistRequest req) {
public ApiResponse<Void> updateWhitelist(@RequestBody IpWhitelistRequest req) {
ipWhitelistService.updateWhitelist(req);
return ApiResponse.success("更新成功");
return ApiResponse.success();
}
@PostMapping("/delete/{id}")
@Operation(summary = "删除IP白名单", description = "根据ID删除指定IP白名单")
public ApiResponse<String> deleteWhitelist(@PathVariable Long id) {
public ApiResponse<Void> deleteWhitelist(@PathVariable Long id) {
ipWhitelistService.deleteWhitelist(id);
return ApiResponse.success("删除成功");
return ApiResponse.success();
}
//todo 要么分页写到@RequestParam,要么改用post
@GetMapping("/page")
@Operation(summary = "分页查询IP白名单", description = "支持分页查询IP白名单列表")
public ApiResponse<IPage<IpWhitelistResponse>> getWhitelistPage(@RequestBody IpWhitelistRequest req) {
IPage<IpWhitelistResponse> page = ipWhitelistService.getWhitelistPage(req);
public ApiResponse<IPage<IpWhitelistResponse>> getWhitelistPage(@RequestParam int pageNum,
@RequestParam int pageSize) {
IPage<IpWhitelistResponse> page = ipWhitelistService.getWhitelistPage(pageNum, pageSize);
return ApiResponse.success(page);
}
}

View File

@ -36,9 +36,9 @@ public class NetworkConfigController {
@Operation(summary = "设置IPv4配置", description = "为指定网络接口配置IPv4地址、掩码、网关等信息")
@PostMapping("/ipv4-config/set")
public ApiResponse<String> setIpv4Config(@RequestBody Ipv4ConfigRequest req) {
String resultMessage = networkConfigService.setIpv4Config(req);
return ApiResponse.success(resultMessage);
public ApiResponse<Void> setIpv4Config(@RequestBody Ipv4ConfigRequest req) {
networkConfigService.setIpv4Config(req);
return ApiResponse.success();
}
@Operation(summary = "获取IPv6配置信息", description = "根据设备名称获取指定网络接口的IPv6配置详情")
@ -50,9 +50,9 @@ public class NetworkConfigController {
@Operation(summary = "设置IPv6配置", description = "为指定网络接口配置IPv6地址、前缀长度、网关等信息")
@PostMapping("/ipv6-config/set")
public ApiResponse<String> setIpv6Config(@RequestBody Ipv6ConfigRequest req) {
String resultMessage = networkConfigService.setIpv6Config(req);
return ApiResponse.success(resultMessage);
public ApiResponse<Void> setIpv6Config(@RequestBody Ipv6ConfigRequest req) {
networkConfigService.setIpv6Config(req);
return ApiResponse.success();
}
@Operation(summary = "获取所有Bond名称", description = "返回当前系统中所有已创建的Bond接口名称")
@ -63,24 +63,24 @@ public class NetworkConfigController {
@Operation(summary = "创建Bond", description = "创建一个新的Bond聚合接口需指定名称、模式和从属接口")
@PostMapping("/bond/create")
public ApiResponse<String> createBond(@RequestBody BondCreateRequest req) {
String resultMessage = networkConfigService.createBond(req);
return ApiResponse.success(resultMessage);
public ApiResponse<Void> createBond(@RequestBody BondCreateRequest req) {
networkConfigService.createBond(req);
return ApiResponse.success();
}
@Operation(summary = "删除Bond", description = "根据Bond名称删除指定的聚合接口")
@PostMapping("/bond/delete/{bondName}")
public ApiResponse<String> deleteBond(@PathVariable("bondName") String bondName){
String resultMessage = networkConfigService.deleteBond(bondName);
return ApiResponse.success(resultMessage);
public ApiResponse<Void> deleteBond(@PathVariable("bondName") String bondName){
networkConfigService.deleteBond(bondName);
return ApiResponse.success();
}
@Operation(summary = "添加从属接口到Bond", description = "向指定Bond中添加一个或多个从属网络接口")
@PostMapping("/bond-slave/add")
public ApiResponse<String> addSlavesTOBond(@RequestBody BondAddSlavesRequest req) {
String resultMessage = networkConfigService.addSlavesTOBond(req);
return ApiResponse.success(resultMessage);
public ApiResponse<Void> addSlavesTOBond(@RequestBody BondAddSlavesRequest req) {
networkConfigService.addSlavesTOBond(req);
return ApiResponse.success();
}
@Operation(summary = "从Bond中移除从属接口", description = "从指定Bond中移除一个或多个从属网络接口")
@ -112,10 +112,10 @@ public class NetworkConfigController {
@Operation(summary = "设置Bond模式", description = "修改指定Bond的绑定模式")
@PostMapping("/bond/mode/set")
public ApiResponse<String> setBondMode(@RequestBody BondModifyModeRequest req) {
public ApiResponse<Void> setBondMode(@RequestBody BondModifyModeRequest req) {
String resultMessage = networkConfigService.setBondMode(req);
return ApiResponse.success(resultMessage);
networkConfigService.setBondMode(req);
return ApiResponse.success();
}
@Operation(summary = "获取路由表", description = "返回当前系统的IPv4/IPv6路由表信息")
@ -127,29 +127,29 @@ public class NetworkConfigController {
@Operation(summary = "设置默认路由", description = "配置或修改系统的默认网关路由")
@PostMapping("/routes/default/set")
public ApiResponse<String> setDefaultRoute(@RequestBody SetDefaultRouteRequest req) {
String resultMessage = networkConfigService.setDefaultRoute(req);
return ApiResponse.success(resultMessage);
public ApiResponse<Void> setDefaultRoute(@RequestBody SetDefaultRouteRequest req) {
networkConfigService.setDefaultRoute(req);
return ApiResponse.success();
}
@Operation(summary = "添加静态路由", description = "新增一条静态路由规则")
@PostMapping("/routes/static/add")
public ApiResponse<String> addStaticRoute(@RequestBody AddStaticRouteRequest req) {
String resultMessage = networkConfigService.addStaticRoute(req);
return ApiResponse.success(resultMessage);
public ApiResponse<Void> addStaticRoute(@RequestBody AddStaticRouteRequest req) {
networkConfigService.addStaticRoute(req);
return ApiResponse.success();
}
@Operation(summary = "删除默认路由", description = "删除指定的默认路由")
@PostMapping("/routes/default/delete")
public ApiResponse<String> deleteDefaultRoute(@RequestBody DeleteDefaultRouteRequest req) {
String resultMessage = networkConfigService.deleteDefaultRoute(req);
return ApiResponse.success(resultMessage);
public ApiResponse<Void> deleteDefaultRoute(@RequestBody DeleteDefaultRouteRequest req) {
networkConfigService.deleteDefaultRoute(req);
return ApiResponse.success();
}
@Operation(summary = "删除静态路由", description = "删除指定的静态路由")
@PostMapping("/routes/static/delete")
public ApiResponse<String> deleteStaticRoute(@RequestBody DeleteStaticRouteRequest req) {
String resultMessage = networkConfigService.deleteStaticRoute(req);
return ApiResponse.success(resultMessage);
public ApiResponse<Void> deleteStaticRoute(@RequestBody DeleteStaticRouteRequest req) {
networkConfigService.deleteStaticRoute(req);
return ApiResponse.success();
}
}

View File

@ -28,8 +28,8 @@ public class TimeConfigRequest {
example = "[\"ntp.aliyun.com\", \"ntp.tencent.com\"]")
private List<String> ntpServers;
@Schema(description = "NTP同步间隔时间 (单位: 秒。默认 600)",
example = "600")
@Schema(description = "NTP同步间隔时间 (单位: 秒。默认 512)",
example = "512")
private Integer syncInterval;
}

View File

@ -86,6 +86,19 @@ public class IpWhitelistFilter implements Filter {
}
private String getClientIp(HttpServletRequest request) {
String remoteAddr = request.getRemoteAddr();
//TODO 后续添加网关ip
//添加信任的网关ip
String[] trustedProxies = {};
boolean isTrustedProxy = false;
for (String proxy : trustedProxies) {
if (remoteAddr.equals(proxy)) {
isTrustedProxy = true;
break;
}
}
//todo 这几个header获取的ip可能是客户端伪造的
String[] headers = {
"X-Forwarded-For",
@ -94,13 +107,15 @@ public class IpWhitelistFilter implements Filter {
"HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR"
};
if (isTrustedProxy) {
for (String header : headers) {
String ip = request.getHeader(header);
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
return ip.split(",")[0].trim();
}
}
return request.getRemoteAddr();
}
return remoteAddr;
}

View File

@ -22,7 +22,7 @@ public interface IpWhitelistRepository {
/**
* 分页查询白名单
*/
Page<IpWhitelistEntity> selectPage(Page<IpWhitelistEntity> page, IpWhitelistRequest req);
Page<IpWhitelistEntity> selectPage(Page<IpWhitelistEntity> page);
/**
* 查询所有白名单

View File

@ -53,7 +53,7 @@ public class IpWhiltelistRepositoryImpl implements IpWhitelistRepository {
}
@Override
public Page<IpWhitelistEntity> selectPage(Page<IpWhitelistEntity> page, IpWhitelistRequest req) {
public Page<IpWhitelistEntity> selectPage(Page<IpWhitelistEntity> page) {
// LambdaQueryWrapper<IpWhitelistEntity> wrapper = new LambdaQueryWrapper<>();
//
// if (StringUtils.hasText(req.getIp())) {

View File

@ -2,6 +2,7 @@ package com.cisd.tms.modules.device.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.modules.device.dto.network.IpWhitelistRequest;
import com.cisd.tms.modules.device.dto.network.IpWhitelistResponse;
@ -11,6 +12,7 @@ import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -19,41 +21,48 @@ public class IpWhitelistService {
private final IpWhitelistRepository ipWhitelistRepository;
private static final Pattern IPV4_PATTERN = Pattern.compile(
"^((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)$"
);
private static final Pattern CIDR_PATTERN = Pattern.compile(
"^((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)/(0|[1-9]|[1-2]\\d|3[0-2])$"
);
public IpWhitelistService(IpWhitelistRepository ipWhitelistRepository) {
this.ipWhitelistRepository = ipWhitelistRepository;
}
public void addWhitelist(IpWhitelistRequest req) {
// 新增前校验 IP+掩码 是否已存在
checkIpRule(req.getIp(), req.getMask(), null);
IpWhitelistEntity entity = new IpWhitelistEntity();
String ip = req.getIp();
String mask = req.getMask();
checkIpRule(ip, mask, null);
IpWhitelistEntity entity = new IpWhitelistEntity();
entity.setIp(ip);
entity.setMask(mask);
entity.setIp(ip.trim());
entity.setMask(mask.trim());
ipWhitelistRepository.addWhitelist(entity);
}
//
public void updateWhitelist(IpWhitelistRequest req) {
if (req.getId() == null || req.getId().trim().isEmpty()) {
throw new IllegalArgumentException("ID should not be null");
throw new IllegalArgumentException("ID 为空");
}
Long idLong = Long.valueOf(req.getId());
Long idLong = Long.valueOf(req.getId().trim());
checkIpRule(req.getIp(), req.getMask(), idLong);
Optional<IpWhitelistEntity> entityOpt = ipWhitelistRepository.findById(idLong);
if (entityOpt.isEmpty()) {
throw new BizException(404, "ID is not exist");
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "ID不存在");
}
IpWhitelistEntity entity = entityOpt.get();
entity.setIp(req.getIp());
entity.setMask(req.getMask());
entity.setIp(req.getIp().trim());
entity.setMask(req.getMask().trim());
ipWhitelistRepository.updateWhitelist(entity);
}
@ -61,15 +70,15 @@ public class IpWhitelistService {
public void deleteWhitelist(Long id) {
Optional<IpWhitelistEntity> entityOpt = ipWhitelistRepository.findById(id);
if (entityOpt.isEmpty()) {
throw new BizException(404, "ID is not exist");
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "ID不存在");
}
ipWhitelistRepository.deleteById(id);
}
public IPage<IpWhitelistResponse> getWhitelistPage(IpWhitelistRequest req) {
Page<IpWhitelistEntity> page = new Page<>(req.getPageNum(), req.getPageSize());
ipWhitelistRepository.selectPage(page, req);
public IPage<IpWhitelistResponse> getWhitelistPage(int pageNum, int pageSize) {
Page<IpWhitelistEntity> page = new Page<>(pageNum, pageSize);
ipWhitelistRepository.selectPage(page);
return page.convert(entity -> {
@ -93,13 +102,15 @@ public class IpWhitelistService {
*/
private void checkIpRule(String ip, String mask, Long excludeId) {
if (ip == null || ip.isEmpty()){
throw new IllegalArgumentException("ip is empty");
throw new IllegalArgumentException("ip地址为空");
}
if (mask == null || mask.isEmpty()){
throw new IllegalArgumentException("mask is empty");
throw new IllegalArgumentException("掩码为空");
}
if (!IPV4_PATTERN.matcher(ip.trim()).matches()){
throw new IllegalArgumentException("非法的 IPv4 地址: " + ip);
}
String[] octets = ip.split("\\.");
@ -125,7 +136,12 @@ public class IpWhitelistService {
if (ipWhitelistRepository.existsByIpAndMask(ip, mask, excludeId)) {
throw new BizException(404, "已存在该 IP 和掩码配置");
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "已存在该 IP 和掩码配置");
}
}
private static String trim(String value) {
return value == null ? "" : value.trim();
}
}

View File

@ -11,14 +11,20 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.*;
import java.util.regex.Pattern;
@Service
@Slf4j
public class NetworkConfigService {
private static final Pattern IPV4_PATTERN = Pattern.compile(
"^((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)$"
);
private static final Pattern CIDR_PATTERN = Pattern.compile(
"^((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)/(0|[1-9]|[1-2]\\d|3[0-2])$"
);
public List<NetworkInfoResponse> getNetworkInfo(){
@ -70,7 +76,7 @@ public class NetworkConfigService {
public Ipv4InfoResponse getIpv4Info(String deviceName){
String connectionName = getConnectionNameByDeviceName(deviceName);
String connectionName = getConnectionNameByDeviceName(deviceName.trim());
if (connectionName == null){
connectionName = deviceName;
}
@ -121,33 +127,41 @@ public class NetworkConfigService {
}
public String setIpv4Config(Ipv4ConfigRequest req){
if (req.getDeviceName() == null || req.getDeviceName().trim().isEmpty()) {
public void setIpv4Config(Ipv4ConfigRequest req){
String ipv4 = trim(req.getIpv4());
String deviceName = trim(req.getDeviceName());
String mask = trim(req.getMaskLength());
if (deviceName.isEmpty()) {
throw new IllegalArgumentException("网卡名称不能为空");
}
if (!isValidIpv4(req.getIpv4())) {
if (ipv4.isEmpty()) {
throw new IllegalArgumentException("ip地址不能为空");
}
if (mask.isEmpty()) {
throw new IllegalArgumentException("子网掩码长度不能为空");
}
if (!IPV4_PATTERN.matcher(ipv4).matches()) {
throw new IllegalArgumentException("无效的 IP 地址格式: " + req.getIpv4());
}
boolean hasGateway = req.getGateway() != null && !req.getGateway().trim().isEmpty();
if (hasGateway && !isValidIpv4(req.getGateway())) {
if (hasGateway && !IPV4_PATTERN.matcher(req.getGateway().trim()).matches()) {
throw new IllegalArgumentException("无效的网关地址格式: " + req.getGateway());
}
String connectionName = getConnectionNameByDeviceName(req.getDeviceName());
//判断连接是否存在如果不存在则新建新连接
boolean isNewConnection = false;
String connectionName = getConnectionNameByDeviceName(deviceName);
if (connectionName == null){
connectionName = req.getDeviceName();
connectionName = deviceName;
isNewConnection = true; // 标记为需要执行 add 创建配置
}
// 转化掩码格式并判断掩码格式是否正确
int maskLength = 0;
try {
String netmask = req.getMaskLength();
if (netmask == null || netmask.isEmpty()) {
String netmask = trim(req.getMaskLength());
if (netmask.isEmpty()) {
throw new IllegalArgumentException("掩码不能为空");
}
// 传的是数字/24
@ -170,9 +184,9 @@ public class NetworkConfigService {
}
//判断ip和网关是否在同一个子网里面
int ip1Int = ipToInt(req.getIpv4());
int ip1Int = ipToInt(req.getIpv4().trim());
if (hasGateway){
int ip2Int = ipToInt(req.getGateway());
int ip2Int = ipToInt(req.getGateway().trim());
int maskInt = (maskLength == 0) ? 0 : (0xFFFFFFFF << (32 - maskLength));
if (!((ip1Int & maskInt) == (ip2Int & maskInt))){
throw new IllegalArgumentException(
@ -184,17 +198,36 @@ public class NetworkConfigService {
String cidr = req.getIpv4() + "/" + maskLength;
//todo 这里需要判断hasGateway是否为true再确定命令是否有 ipv4.gateway
try {
if (isNewConnection) {
List<String> cmdArgs = new ArrayList<>();
cmdArgs.add("nmcli"); cmdArgs.add("con"); cmdArgs.add("add");
cmdArgs.add("type"); cmdArgs.add("ethernet"); // 默认创建以太网类型
cmdArgs.add("con-name"); cmdArgs.add(connectionName);
cmdArgs.add("ifname"); cmdArgs.add(deviceName);
cmdArgs.add("ipv4.method"); cmdArgs.add("manual");
cmdArgs.add("ipv4.addresses"); cmdArgs.add(cidr);
if (hasGateway) {
cmdArgs.add("ipv4.gateway");cmdArgs.add(req.getGateway());
cmdArgs.add("ipv4.never-default");cmdArgs.add("yes");
}
executeCommand(cmdArgs.toArray(new String[0]));
} else {
if (hasGateway) {
executeCommand("nmcli", "con", "mod", connectionName,
"ipv4.method", "manual",
"ipv4.addresses", cidr,
"ipv4.gateway", req.getGateway());
"ipv4.gateway", req.getGateway(),
"ipv4.never-default", "yes");
} else {
executeCommand("nmcli", "con", "mod", connectionName,
"ipv4.method", "manual",
"ipv4.addresses", cidr,
"ipv4.gateway", "");
}
}
executeCommand("nmcli", "con", "up", connectionName);
return "网络连接 [" + req.getDeviceName() + "] IPv4 配置成功";
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改网络配置失败: " + e.getMessage());
@ -203,7 +236,7 @@ public class NetworkConfigService {
public Ipv6InfoResponse getIpv6Info(String deviceName){
deviceName = trim(deviceName);
String connectionName = getConnectionNameByDeviceName(deviceName);
if (connectionName == null){
connectionName = deviceName;
@ -238,10 +271,10 @@ public class NetworkConfigService {
Ipv6AddressItem item = new Ipv6AddressItem();
if (ipAndMaskLength.contains("/")) {
String[] parts = ipAndMaskLength.split("/");
item.setIpv6(parts[0]);
item.setIpv6(parts[0].replace("\\:", ":"));
item.setMaskLength(parts[1]);
} else {
item.setIpv6(ipAndMaskLength);
item.setIpv6(ipAndMaskLength.replace("\\:", ":"));
}
// 将解析好的单个 IP 对象放入集合
addressList.add(item);
@ -251,7 +284,7 @@ public class NetworkConfigService {
if (lines.size()> 1 && !lines.get(1).trim().isEmpty()) {
resp.setGateway(lines.get(1).trim());
resp.setGateway(lines.get(1).trim().replace("\\:", ":"));
}
if (lines.size() > 2 && !lines.get(2).trim().isEmpty()) {
@ -268,7 +301,8 @@ public class NetworkConfigService {
return resp;
}
public String setIpv6Config(Ipv6ConfigRequest req){
public void setIpv6Config(Ipv6ConfigRequest req){
String deviceName = trim(req.getDeviceName());
if (req.getDeviceName() == null || req.getDeviceName().trim().isEmpty()) {
throw new IllegalArgumentException("网卡名称不能为空");
}
@ -293,29 +327,47 @@ public class NetworkConfigService {
throw new IllegalArgumentException("无效的 IPv6 前缀长度,必须在 0 到 128 之间");
}
String connectionName = getConnectionNameByDeviceName(req.getDeviceName());
// 判断连接是否存在如果不存在则需要新建
boolean isNewConnection = false;
String connectionName = getConnectionNameByDeviceName(deviceName);
if (connectionName == null){
connectionName = req.getDeviceName();
connectionName = deviceName;
isNewConnection = true;
}
String ipWithPrefix = req.getIpv6() + "/" + req.getMaskLength();
String ipWithPrefix = req.getIpv6().trim() + "/" + req.getMaskLength().trim();
try {
if (isNewConnection) {
List<String> cmdArgs = new ArrayList<>();
cmdArgs.add("nmcli"); cmdArgs.add("con"); cmdArgs.add("add");
cmdArgs.add("type"); cmdArgs.add("ethernet");
cmdArgs.add("con-name"); cmdArgs.add(connectionName);
cmdArgs.add("ifname"); cmdArgs.add(deviceName);
cmdArgs.add("ipv6.method"); cmdArgs.add("manual");
cmdArgs.add("ipv6.addresses"); cmdArgs.add(ipWithPrefix);
if (hasGateway) {
cmdArgs.add("ipv6.gateway"); cmdArgs.add(req.getGateway().trim());
cmdArgs.add("ipv6.never-default");cmdArgs.add("yes");
}
executeCommand(cmdArgs.toArray(new String[0]));
} else {
if (hasGateway) {
executeCommand("nmcli", "con", "mod", connectionName,
"ipv6.method", "manual",
"ipv6.addresses", ipWithPrefix,
"ipv6.gateway", req.getGateway());
"ipv6.gateway", req.getGateway().trim(),
"ipv6.never-default","yes");
} else {
executeCommand("nmcli", "con", "mod", connectionName,
"ipv6.method", "manual",
"ipv6.addresses", ipWithPrefix);
"ipv6.addresses", ipWithPrefix,
"ipv6.gateway", "");
}
}
executeCommand("nmcli", "con", "up", connectionName);
return "网络连接 [" + req.getDeviceName() + "] IPv6 配置成功";
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改 IPv6 网络配置失败: " + e.getMessage());
}
@ -327,7 +379,7 @@ public class NetworkConfigService {
List<String> lines = executeCommand("nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device");
for (String line : lines) {
String[] parts = line.split(":", -1);
String[] parts = line.split("(?<!\\\\):", -1);
if (parts.length >= 2 && "bond".equals(parts[1])) {
bondNames.add(parts[0]);
}
@ -335,8 +387,9 @@ public class NetworkConfigService {
return bondNames;
}
public String createBond(BondCreateRequest req){
if (req.getBondName() == null || req.getBondName().trim().isEmpty()) {
public void createBond(BondCreateRequest req){
String bondName = trim(req.getBondName());
if (bondName.isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空");
}
if (req.getMode() == null) {
@ -347,8 +400,8 @@ public class NetworkConfigService {
try {
List<String> lines = executeCommand("nmcli", "-g", "NAME", "con", "show");
for (String line : lines) {
if (req.getBondName().equals(line.trim())) {
throw new RuntimeException("网络连接名称 [" + req.getBondName() + "] 已存在,请勿重复创建");
if (bondName.equals(line.trim())) {
throw new RuntimeException("网络连接名称 [" + bondName + "] 已存在,请勿重复创建");
}
}
} catch (RuntimeException e) {
@ -365,58 +418,93 @@ public class NetworkConfigService {
try {
executeCommand("nmcli", "con", "add",
"type", "bond",
"con-name", req.getBondName(),
"ifname", req.getBondName(),
"con-name", bondName,
"ifname", bondName,
"bond.options", bondOptions);
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "创建 Bond 失败: " + e.getMessage());
}
if (req.getIpv4Config() != null) {
req.getIpv4Config().setDeviceName(req.getBondName());
req.getIpv4Config().setDeviceName(bondName);
try {
setIpv4Config(req.getIpv4Config());
} catch (Exception e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + req.getBondName() + "] 创建成功,但 IP 配置失败: " + e.getMessage());
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 创建成功,但 IP 配置失败: " + e.getMessage());
}
}
}
return "Bond [" + req.getBondName() + "] 创建并初始化成功";
}
public String deleteBond(String bondName) {
if (bondName == null || bondName.isEmpty()) {
public void deleteBond(String bondName) {
if (bondName == null || bondName.trim().isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空");
}
boolean bondExist = false;
bondName = bondName.trim();
List<String> uuidsToDelete = new ArrayList<>();
boolean bondFound = false;
try {
List<String> lines = executeCommand("nmcli", "-g", "NAME", "con", "show");
List<String> lines = executeCommand("nmcli", "-t", "-f", "UUID,NAME,TYPE", "con", "show");
for (String line : lines) {
if (bondName.equals(line.trim())) {
bondExist = true;
if (line.trim().isEmpty()) continue;
String[] parts = line.split("(?<!\\\\):", -1);
if (parts.length >= 3) {
String uuid = parts[0];
String name = parts[1].replace("\\:", ":");
String type = parts[2];
// 匹配到 Bond 自身
if (bondName.equals(name) && "bond".equals(type)) {
bondFound = true;
uuidsToDelete.add(uuid);
}
else if (type != null && type.contains("ethernet")) {
try {
List<String> masterOutput = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid);
if (!masterOutput.isEmpty() && bondName.equals(masterOutput.get(0).trim())) {
uuidsToDelete.add(0, uuid);
}
} catch (Exception e) {
log.warn("查询连接 [{}] 的 master出现异常: {}", uuid, e.getMessage());
}
}
}
}
if (bondExist) {
executeCommand("nmcli", "con", "delete", bondName);
} else {
if (!bondFound) {
throw new RuntimeException("Bond [" + bondName + "] 不存在");
}
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 删除失败 " + e.getMessage());
}
return "Bond [" + bondName + "] 删除成功";
for (String targetUuid : uuidsToDelete) {
//先停后删
try {
executeCommand("nmcli", "con", "down", "uuid", targetUuid);
} catch (Exception e) {
log.warn("停用连接 [{}] 时出现异常: {}", targetUuid, e.getMessage());
}
//todo
//这里是先删除物理网卡再添加bond如果添加失败那么之前删除的不可逆
//建议对添加的bondname和slaveList做存在性校验再执行添加 或者记录已删除连接 UUID/名称失败后回滚
public String addSlavesTOBond(BondAddSlavesRequest req) {
executeCommand("nmcli", "con", "delete", "uuid", targetUuid);
}
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 及从属网卡删除失败: " + e.getMessage());
}
}
public void addSlavesTOBond(BondAddSlavesRequest req) {
String bondName = req.getBondName();
List<String> slaves = req.getSlaveList();
if (bondName == null || bondName.trim().isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空");
}
@ -424,26 +512,53 @@ public class NetworkConfigService {
throw new IllegalArgumentException("物理网卡列表不能为空");
}
List<String> existingConnections;
List<String> existingDevices;
try {
existingConnections = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME", "con", "show");
existingConnections = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME,TYPE", "con", "show");
existingDevices = executeCommand("nmcli", "-t", "-f", "DEVICE,TYPE", "dev");
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络连接列表失败");
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络状态失败");
}
//查询bond是否存在
boolean bondExists = false;
for (String line : existingConnections) {
String[] parts = line.split("(?<!\\\\):", -1);
if (parts.length >= 4) {
String name = parts[2].replace("\\:", ":");
String type = parts[3];
if (bondName.equals(name) && "bond".equals(type)) {
bondExists = true;
break;
}
}
}
if (!bondExists) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "目标 Bond [" + bondName + "] 不存在");
}
for (String phyIf : slaves) {
if (phyIf == null || phyIf.trim().isEmpty()){
continue;
if (phyIf == null || phyIf.trim().isEmpty()) continue;
boolean devExists = existingDevices.stream()
.map(line -> line.split("(?<!\\\\):", -1)[0].replace("\\:", ":"))
.anyMatch(devName -> devName.equals(phyIf));
if (!devExists) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "当前物理网卡设备 [" + phyIf + "] 在系统中不存在");
}
}
for (String line : existingConnections) {
if (line.trim().isEmpty()){
continue;
}
List<String> uuidsToDown = new ArrayList<>();
for (String phyIf : slaves) {
if (phyIf == null || phyIf.trim().isEmpty()) continue;
String slaveConnectionName = bondName + "-slave-" + phyIf;
for (String line : existingConnections) {
if (line.trim().isEmpty()) continue;
String[] parts = line.split("(?<!\\\\):", -1);
if (parts.length >= 3) {
String uuid = parts[0];
@ -451,60 +566,91 @@ public class NetworkConfigService {
String name = parts[2].replace("\\:", ":");
if (slaveConnectionName.equals(name) || phyIf.equals(device)) {
try {
executeCommand("nmcli", "con", "delete", "uuid", uuid);
} catch (RuntimeException e) {
log.warn("清理物理网卡 {} 的旧连接/冲突连接 [{}] 失败: {}", phyIf, name, e.getMessage());
uuidsToDown.add(uuid);
}
}
}
}
//先停止后删除
for (String uuid : uuidsToDown) {
try {
executeCommand("nmcli", "con", "modify", "uuid", uuid, "connection.autoconnect", "no");
} catch (RuntimeException e) {
log.warn("修改旧连接 [{}] 的自启属性失败,该连接可能不存在: {}", uuid, e.getMessage());
}
try {
executeCommand("nmcli", "con", "down", "uuid", uuid);
} catch (RuntimeException e) {
log.warn("暂停旧连接 [{}] 失败,该连接可能已经处于断开状态: {}", uuid, e.getMessage());
}
}
List<String> newlyAddedSlaveNames = new ArrayList<>(); // 记录已经成功添加的用于添加失败回滚
try {
for (String phyIf : slaves) {
if (phyIf == null || phyIf.trim().isEmpty()) continue;
String slaveConnectionName = bondName + "-slave-" + phyIf;
executeCommand("nmcli", "con", "add",
"type", "bond-slave",
"con-name", slaveConnectionName,
"ifname", phyIf,
"master", bondName);
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(),
String.format("将物理网卡 [%s] 加入 Bond [%s] 失败: %s", phyIf, bondName, e.getMessage()));
}
newlyAddedSlaveNames.add(slaveConnectionName);
}
try {
executeCommand("nmcli", "con", "up", bondName);
//激活后删除
for (String uuid : uuidsToDown) {
try {
executeCommand("nmcli", "con", "delete", "uuid", uuid);
} catch (Exception e) {
log.warn("Bond配置已生效但清理废弃的旧连接 [{}] 失败: {}", uuid, e.getMessage());
}
}
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "激活 Bond [" + bondName + "] 失败: " + e.getMessage());
}
log.error("将网卡加入 Bond 失败,触发回滚,清理刚创建的 slave 连接", e);
return String.format("Bond [%s] 成功添加 %d 个从属网卡并已激活", bondName, slaves.size());
for (String addedSlaveName : newlyAddedSlaveNames) {
try {
executeCommand("nmcli", "con", "delete", addedSlaveName);
} catch (Exception rollbackEx) {
log.error("回滚失败: 无法删除的从属连接 [{}]", addedSlaveName, rollbackEx);
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "将网卡加入 Bond 失败,并且无法删除刚创建的从属连接");
}
}
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "添加物理网卡到 Bond 失败,已清理刚创建的配置");
}
}
public String removeSlaveFromBond(BondRemoveSlaveRequest req) {
String bondName = req.getBondName();
String bondName = trim(req.getBondName());
List<String> slaves = req.getSlaveList();
if (bondName == null || bondName.trim().isEmpty()) {
if (bondName.isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空");
}
if (slaves == null || slaves.isEmpty()) {
throw new IllegalArgumentException("物理网卡名称不能为空");
}
int deletedCount = 0;
List<String> notFoundSlaves = new ArrayList<>();
// 请求输出格式为: UUID:DEVICE:NAME
List<String> lines;
try {
lines = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME", "con", "show");
lines = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME,TYPE", "con", "show");
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络连接列表失败");
}
int deletedCount = 0;
for (String phyIf : slaves) {
if (phyIf == null || phyIf.trim().isEmpty()) continue;
String targetUuid = null;
String slaveName = bondName + "-slave-" + phyIf;
@ -515,7 +661,7 @@ public class NetworkConfigService {
String[] parts = line.split("(?<!\\\\):", -1);
if (parts.length >= 3) {
if (parts.length >= 4) {
String uuid = parts[0];
String device = parts[1].replace("\\:", ":");
String name = parts[2].replace("\\:", ":");
@ -529,83 +675,83 @@ public class NetworkConfigService {
break;
}
} catch (RuntimeException e) {
log.warn("查询网卡 [{}] master 属性失败,可能已被移除或状态异常", name);
log.warn("查询网卡 [{}] master 属性失败", name);
}
}
}
}
if (targetUuid == null) {
log.warn("未找到物理网卡 [{}] 对应的从属连接", phyIf);
notFoundSlaves.add(phyIf);
continue;
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "未找到网卡 [" + phyIf + "] 对应的从属配置,移除操作已中断");
}
try {
try {
executeCommand("nmcli", "con", "down", "uuid", targetUuid);
} catch (Exception e) {
log.warn("停用从属网卡连接 [{}] 失败,忽略并继续删除: {}", targetUuid, e.getMessage());
}
executeCommand("nmcli", "con", "delete", "uuid", targetUuid);
deletedCount++;
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(),
String.format("从 Bond [%s] 移除网卡 [%s] 失败。原因: %s",
bondName, phyIf, e.getMessage()));
log.error("移除网卡 [{}] 失败: {}", phyIf, e.getMessage());
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("从 Bond [%s] 移除网卡 [%s] 失败: %s", bondName, phyIf, e.getMessage()));
}
}
}
// 更新Bond状态
// 只要有成功删除的记录就重新激活 Bond 使其生效
if (deletedCount > 0) {
try {
executeCommand("nmcli", "con", "up", bondName);
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(),"更新 Bond [" + bondName + "] 失败: " + e.getMessage());
}
if (notFoundSlaves.isEmpty()) {
return String.format("Bond [%s] 删除 %d 个从属网卡并已激活", bondName, deletedCount);
} else if (deletedCount == 0) {
return String.format("Bond [%s] 激活成功,但请求的网卡 %s 均不存在于该 Bond 中", bondName, notFoundSlaves);
} else {
return String.format("Bond [%s] 删除 %d 个从属网卡并已激活。提示:网卡 %s 不存在,已被忽略",
bondName, deletedCount, notFoundSlaves);
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "网卡移除成功,但重新激活 Bond [" + bondName + "] 失败: " + e.getMessage());
}
}
return String.format("成功从 Bond [%s] 中移除了 %d 个网卡", bondName, deletedCount);
}
public List<String> getBondSlaves(String bondName) {
if (bondName == null || bondName.trim().isEmpty()) {
bondName = trim(bondName);
if (bondName.isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空");
}
List<String> slaveList = new ArrayList<>();
List<String> lines;
try {
lines = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME", "con", "show");
lines = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME,TYPE", "con", "show");
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "查询 Bond 从属网卡列表失败");
}
boolean bondExists = false;
for (String line : lines) {
if (line.trim().isEmpty()) {
continue;
}
String[] parts = line.split("(?<!\\\\):", -1);
if (parts.length >= 3) {
if (parts.length >= 4) {
String uuid = parts[0].replace("\\:", ":");
String device = parts[1].replace("\\:", ":");
String name = parts[2].replace("\\:", ":");
String type = parts[3];
if (bondName.equals(name) && "bond".equals(type)) {
bondExists = true;
}
if (type != null && type.contains("ethernet")) {
List<String> masterOutputs = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid);
String master = masterOutputs.isEmpty() ? "" : masterOutputs.get(0).trim();
if (bondName.equals(master)) {
if (!device.isEmpty() && !device.equals("--")) {
// 网卡处于激活连接状态
slaveList.add(device);
} else {
// 网卡处于断开状态
String prefix = bondName + "-slave-";
if (name.startsWith(prefix)) {
slaveList.add(name.substring(prefix.length()));
@ -614,6 +760,11 @@ public class NetworkConfigService {
}
}
}
}
if (!bondExists) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 不存在");
}
return slaveList;
}
@ -649,13 +800,14 @@ public class NetworkConfigService {
}
public String setBondMode(BondModifyModeRequest req) {
public void setBondMode(BondModifyModeRequest req) {
String bondName = req.getBondName();
Integer mode = req.getMode();
if (bondName == null || bondName.trim().isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空");
}
bondName = bondName.trim();
if (mode == null) {
throw new IllegalArgumentException("新的 Bond 模式不能为空");
}
@ -687,7 +839,6 @@ public class NetworkConfigService {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "重新激活 Bond [" + bondName + "] 失败" + e.getMessage());
}
return String.format("Bond [%s] 已成功切换至 [%s] 模式并重启生效", bondName, newMode);
}
@ -737,11 +888,11 @@ public class NetworkConfigService {
}
public String setDefaultRoute(SetDefaultRouteRequest req) {
public void setDefaultRoute(SetDefaultRouteRequest req) {
String deviceName = req.getDeviceName();
String gatewayIp = req.getGatewayIp();
// 1. 基础校验
if (deviceName == null || deviceName.trim().isEmpty()) {
throw new IllegalArgumentException("网卡名称不能为空");
}
@ -749,7 +900,11 @@ public class NetworkConfigService {
throw new IllegalArgumentException("网关 IP 不能为空");
}
String connectionName = getConnectionNameByDeviceName(req.getDeviceName());
if (!IPV4_PATTERN.matcher(req.getGatewayIp().trim()).matches()) {
throw new IllegalArgumentException("无效的网关地址格式: " + req.getGatewayIp());
}
String connectionName = getConnectionNameByDeviceName(req.getDeviceName().trim());
if (connectionName == null){
connectionName = req.getDeviceName();
}
@ -758,8 +913,9 @@ public class NetworkConfigService {
try {
executeCommand("nmcli", "con", "mod", connectionName,
"ipv4.gateway", gatewayIp,
//多张网卡配置了网关需调整 Metric 优先级
"ipv4.route-metric", "50");
//TODO待修改多张网卡配置了网关需调整 Metric 优先级)
"ipv4.route-metric", "50",
"ipv4.never-default", "no");
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("为网卡 [%s] 设置网关失败: %s", deviceName, e.getMessage()));
}
@ -772,27 +928,32 @@ public class NetworkConfigService {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage()));
}
return String.format("成功将网卡 [%s] 的网关设置为 [%s]", deviceName, gatewayIp);
}
public String addStaticRoute(AddStaticRouteRequest req) {
public void addStaticRoute(AddStaticRouteRequest req) {
String deviceName = req.getDeviceName();
String targetCidr = req.getTargetCidr();
String nextHop = req.getNextHop();
//todo 对gatewayIptargetCidrnextHop增加格式校验可用正则表达式
if (deviceName == null || deviceName.trim().isEmpty()) {
throw new IllegalArgumentException("网卡名称不能为空");
}
if (targetCidr == null || targetCidr.trim().isEmpty()) {
throw new IllegalArgumentException("目标网段(CIDR)不能为空");
}
if (!CIDR_PATTERN.matcher(targetCidr.trim()).matches()) {
throw new IllegalArgumentException("非法的CIDR目标网段格式");
}
if (nextHop != null && !IPV4_PATTERN.matcher(nextHop.trim()).matches()) {
throw new IllegalArgumentException("非法的下一跳IP格式");
}
// if (nextHop == null || nextHop.trim().isEmpty()) {
// throw new IllegalArgumentException("下一跳 IP 不能为空");
// }
String connectionName = getConnectionNameByDeviceName(req.getDeviceName());
String connectionName = getConnectionNameByDeviceName(req.getDeviceName().trim());
if (connectionName == null){
connectionName = req.getDeviceName();
}
@ -819,18 +980,17 @@ public class NetworkConfigService {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage()));
}
return String.format("成功向网卡 [%s] 添加静态路由", deviceName);
}
public String deleteDefaultRoute(DeleteDefaultRouteRequest req) {
public void deleteDefaultRoute(DeleteDefaultRouteRequest req) {
String deviceName = req.getDeviceName();
if (deviceName == null || deviceName.trim().isEmpty()) {
throw new IllegalArgumentException("网卡名称不能为空");
}
String connectionName = getConnectionNameByDeviceName(req.getDeviceName());
String connectionName = getConnectionNameByDeviceName(req.getDeviceName().trim());
if (connectionName == null){
connectionName = req.getDeviceName();
}
@ -848,12 +1008,11 @@ public class NetworkConfigService {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage()));
}
return String.format("已成功删除网卡 [%s] 的默认路由", deviceName);
}
public String deleteStaticRoute(DeleteStaticRouteRequest req) {
public void deleteStaticRoute(DeleteStaticRouteRequest req) {
String deviceName = req.getDeviceName();
String targetCidr = req.getTargetCidr();
String nextHop = req.getNextHop();
@ -868,6 +1027,14 @@ public class NetworkConfigService {
// throw new IllegalArgumentException("下一跳 IP 不能为空");
// }
if (!CIDR_PATTERN.matcher(targetCidr.trim()).matches()) {
throw new IllegalArgumentException("非法的CIDR目标网段格式");
}
if (nextHop != null && !IPV4_PATTERN.matcher(nextHop.trim()).matches()) {
throw new IllegalArgumentException("非法的下一跳IP格式");
}
String connectionName = getConnectionNameByDeviceName(req.getDeviceName());
if (connectionName == null){
connectionName = req.getDeviceName();
@ -896,24 +1063,23 @@ public class NetworkConfigService {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage()));
}
return String.format("成功从网卡 [%s] 删除静态路由", deviceName);
}
private boolean isValidIpv4(String ip) {
if (ip == null || ip.isEmpty()) return false;
try {
ipToInt(ip);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
// private boolean isValidIpv4(String ip) {
// if (ip == null || ip.isEmpty()) return false;
// try {
// ipToInt(ip);
// return true;
// } catch (IllegalArgumentException e) {
// return false;
// }
// }
private boolean isValidIpv6(String ip) {
if (ip == null || ip.isEmpty()) return false;
try {
InetAddress inetAddress = InetAddress.getByName(ip);
InetAddress inetAddress = InetAddress.getByName(ip.trim());
return inetAddress instanceof Inet6Address;
} catch (Exception e) {
return false;
@ -925,10 +1091,13 @@ public class NetworkConfigService {
if (parts.length != 4) {
throw new IllegalArgumentException("非法的 IPv4 地址: " + ipv4);
}
//TODO 前导零处理
int result = 0;
for (int i = 0; i < 4; i++) {
int octet = Integer.parseInt(parts[i]);
String part = parts[i];
int octet = Integer.parseInt(part);
if (part.length() > 1 && part.startsWith("0")) {
throw new IllegalArgumentException("非法的 IPv4 地址,存在前导零: " + part);
}
if (octet < 0 || octet > 255) {
throw new IllegalArgumentException("非法的 IPv4 地址段: " + octet);
}
@ -999,7 +1168,67 @@ public class NetworkConfigService {
throw new RuntimeException("服务器内部处理中断");}
}
private static String trim(String value) {
return value == null ? "" : value.trim();
}
}
//private void applyNmcliConnectionConfig(String deviceName, boolean isIpv6, String ipWithPrefix, String gateway) {
// String protocol = isIpv6 ? "ipv6" : "ipv4";
// boolean hasGateway = gateway != null && !gateway.trim().isEmpty();
//
// boolean isNewConnection = false;
// String connectionName = getConnectionNameByDeviceName(deviceName);
// if (connectionName == null) {
// connectionName = deviceName;
// isNewConnection = true;
// }
//
// if (isNewConnection) {
// // 新建连接
// List<String> cmdArgs = new ArrayList<>(Arrays.asList(
// "nmcli", "con", "add",
// "type", "ethernet",
// "con-name", connectionName,
// "ifname", deviceName,
// protocol + ".method", "manual",
// protocol + ".addresses", ipWithPrefix
// ));
//
// if (hasGateway) {
// cmdArgs.add(protocol + ".gateway");
// cmdArgs.add(gateway.trim());
// // 设置了网关但明确告诉系统不要把它当默认路由
// cmdArgs.add(protocol + ".never-default");
// cmdArgs.add("yes");
// }
// executeCommand(cmdArgs.toArray(new String[0]));
//
// } else {
// // 修改连接
// List<String> cmdArgs = new ArrayList<>(Arrays.asList(
// "nmcli", "con", "mod", connectionName,
// protocol + ".method", "manual",
// protocol + ".addresses", ipWithPrefix
// ));
//
// if (hasGateway) {
// cmdArgs.add(protocol + ".gateway");
// cmdArgs.add(gateway.trim());
// // 修改时同样加上这个限制
// cmdArgs.add(protocol + ".never-default");
// cmdArgs.add("yes");
// } else {
// // 如果没有网关清空旧网关
// cmdArgs.add(protocol + ".gateway");
// cmdArgs.add("");
// }
//
// executeCommand(cmdArgs.toArray(new String[0]));
// }
//
// // 激活连接
// executeCommand("nmcli", "con", "up", connectionName);
//}

View File

@ -1,18 +1,34 @@
package com.cisd.tms.modules.device.service.impl;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.modules.device.dto.TimeConfigRequest;
import com.cisd.tms.modules.device.service.TimeConfigService;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
@Slf4j
public class TimeConfigServiceImpl implements TimeConfigService {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm").withResolverStyle(ResolverStyle.STRICT);
@Override
public void processTimeConfig(TimeConfigRequest request) {
String mode = request.getMode();
@ -25,15 +41,35 @@ public class TimeConfigServiceImpl implements TimeConfigService {
}
String datetime = request.getDatetime();
//todo 建议用LocalDateTime进行校验格式
if (datetime == null || !datetime.matches("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$")) {
if (datetime == null) {
throw new IllegalArgumentException("Invalid or empty datetime format. Expected: YYYY-MM-DD HH:MM");
}
try {
LocalDateTime.parse(datetime, FORMATTER);
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("Invalid or empty datetime format. Expected: YYYY-MM-DD HH:MM", e);
}
executeCommand("timedatectl set-timezone " + request.getTimezone().trim());
executeCommand("systemctl stop chronyd");
//todo 失败后重启chronyd
executeCommand("date -s '" + request.getDatetime() + "'");
try{
executeCommand("timedatectl", "set-timezone", request.getTimezone().trim());
} catch (Exception e){
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "设置时区失败");
}
try {
executeCommand("systemctl", "stop", "chronyd");
} catch (Exception e){
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "停止chronyd服务失败");
}
try {
executeCommand("date", "-s", request.getDatetime());
} catch (Exception e) {
// 失败后重启 chronyd
executeCommand("systemctl", "start", "chronyd");
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "设置时间失败,已回退并重启 chronyd 服务");
}
} else if ("NTP".equalsIgnoreCase(mode)) {
@ -41,72 +77,112 @@ public class TimeConfigServiceImpl implements TimeConfigService {
if (servers == null || servers.isEmpty()) {
throw new IllegalArgumentException("At least one NTP server IP must be provided in NTP mode");
}
//todo 参数校验SyncInterval:限制正数范围或 2 的幂
int interval = request.getSyncInterval() != null ? request.getSyncInterval() : 600;
int pollExp = (int) Math.round(Math.log(interval) / Math.log(2));
int interval = request.getSyncInterval() != null ? request.getSyncInterval() : 512;
if (interval <= 0) {
throw new IllegalArgumentException("SyncInterval must be a positive integer.");
}
if ((interval & (interval - 1)) != 0) {
throw new IllegalArgumentException("SyncInterval must be a power of 2.");
}
int MIN_INTERVAL = 8;
int MAX_INTERVAL = 131072;
if (interval < MIN_INTERVAL || interval > MAX_INTERVAL) {
throw new IllegalArgumentException(String.format("SyncInterval must be between %d and %d.", MIN_INTERVAL, MAX_INTERVAL));
}
int pollExp = Integer.numberOfTrailingZeros(interval);
StringBuilder serverConfLines = new StringBuilder();
for (String server : servers) {
if (!server.matches("^[A-Za-z0-9.-]+$")) {
throw new IllegalArgumentException("Invalid NTP server address format: " + server);
}
String pingResult = executeCommand("ping -c 1 -W 1 " + server);
String pingResult = executeCommand("ping", "-c", "1", "-W", "1", server);
if (!pingResult.contains("1 received")) {
throw new RuntimeException("NTP server is unreachable via ping: " + server);
}
serverConfLines.append("server ").append(server)
.append(" iburst minpoll ").append(pollExp)
.append(" maxpoll ").append(pollExp).append("\\n");
.append(" maxpoll ").append(pollExp).append("\n");
}
String confContent = serverConfLines +
"driftfile /var/lib/chrony/drift\\n" +
"makestep 1.0 3\\n" +
"rtcsync\\n";
//todo 这里直接重写配置文件如果失败则原配置丢失建议备份失败后回滚
executeCommand("echo -e '" + confContent + "' > /etc/chrony.conf");
executeCommand("systemctl restart chronyd");
executeCommand("chronyc -a makestep");
String status = executeCommand("chronyc tracking");
"driftfile /var/lib/chrony/drift\n" +
"makestep 1.0 3\n" +
"rtcsync\n";
Path confPath = Paths.get("/etc/chrony.conf");
Path backupPath = Paths.get("/etc/chrony.conf.bak");
try {
if (Files.exists(confPath)) {
Files.copy(confPath, backupPath, StandardCopyOption.REPLACE_EXISTING);
}
Files.writeString(confPath, confContent);
executeCommand("systemctl", "restart", "chronyd");
executeCommand("chronyc", "-a", "makestep");
String status = executeCommand("chronyc", "tracking");
if (!status.contains("Reference ID")) {
throw new RuntimeException("NTP sync status check failed");
}
} catch (Exception e) {
try {
if (Files.exists(backupPath)) {
Files.copy(backupPath, confPath, StandardCopyOption.REPLACE_EXISTING);
executeCommand("systemctl", "restart", "chronyd");
}
} catch (Exception rollbackEx) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Failed to apply new Chrony configuration. CRITICAL: Rollback also failed!");
}
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Failed to apply new Chrony configuration. Rolled back to original state.");
}
} else {
throw new IllegalArgumentException("Unknown mode: " + mode);
}
}
private String executeCommand(String command) {
ProcessBuilder processBuilder = new ProcessBuilder("bash", "-c", command);
private String executeCommand(String... commandArgs) {
ProcessBuilder processBuilder = new ProcessBuilder(commandArgs);
processBuilder.redirectErrorStream(true);
Process process = null;
try {
process = processBuilder.start();
StringBuilder output = new StringBuilder();
StringBuilder outputBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
outputBuilder.append(line).append("\n");
}
}
int exitCode = process.waitFor();
String outputStr = outputBuilder.toString().trim();
if (exitCode != 0) {
throw new RuntimeException("command execution failed: [" + command + "] ");
log.error("Command execution failed. Exit code: {}, Command: {}, Output: {}",
exitCode, Arrays.toString(commandArgs), outputStr);
throw new RuntimeException("系统底层操作执行失败");
}
return output.toString();
return outputStr;
} catch (IOException e) {
throw new RuntimeException("I/O error executing command: [" + command + "]", e);
log.error("I/O error executing command: {}", Arrays.toString(commandArgs), e);
throw new RuntimeException("服务器内部操作异常", e);
} catch (InterruptedException e) {
if (process != null) {
process.destroy();
}
Thread.currentThread().interrupt();
throw new RuntimeException("process was interrupted while executing command: [" + command + "]", e);
log.error("Process was interrupted while executing command: {}", Arrays.toString(commandArgs), e);
throw new RuntimeException("服务器内部处理中断", e);
}
}
}

View File

@ -315,3 +315,15 @@ VALUES
CURRENT_TIMESTAMP(3),
CURRENT_TIMESTAMP(3)
);
-- -----------------------------------------------------------------------------
-- IP白名单表
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS tms_access_whitelist (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
ip VARCHAR(32) NOT NULL,
mask VARCHAR(10) NOT NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
);