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

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

View File

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

View File

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

View File

@ -86,6 +86,19 @@ public class IpWhitelistFilter implements Filter {
} }
private String getClientIp(HttpServletRequest request) { 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可能是客户端伪造的 //todo 这几个header获取的ip可能是客户端伪造的
String[] headers = { String[] headers = {
"X-Forwarded-For", "X-Forwarded-For",
@ -94,13 +107,15 @@ public class IpWhitelistFilter implements Filter {
"HTTP_CLIENT_IP", "HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR" "HTTP_X_FORWARDED_FOR"
}; };
for (String header : headers) { if (isTrustedProxy) {
String ip = request.getHeader(header); for (String header : headers) {
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) { String ip = request.getHeader(header);
return ip.split(",")[0].trim(); 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 @Override
public Page<IpWhitelistEntity> selectPage(Page<IpWhitelistEntity> page, IpWhitelistRequest req) { public Page<IpWhitelistEntity> selectPage(Page<IpWhitelistEntity> page) {
// LambdaQueryWrapper<IpWhitelistEntity> wrapper = new LambdaQueryWrapper<>(); // LambdaQueryWrapper<IpWhitelistEntity> wrapper = new LambdaQueryWrapper<>();
// //
// if (StringUtils.hasText(req.getIp())) { // 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.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 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.common.exception.BizException;
import com.cisd.tms.modules.device.dto.network.IpWhitelistRequest; import com.cisd.tms.modules.device.dto.network.IpWhitelistRequest;
import com.cisd.tms.modules.device.dto.network.IpWhitelistResponse; 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.List;
import java.util.Optional; import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -19,41 +21,48 @@ public class IpWhitelistService {
private final IpWhitelistRepository ipWhitelistRepository; 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) { public IpWhitelistService(IpWhitelistRepository ipWhitelistRepository) {
this.ipWhitelistRepository = ipWhitelistRepository; this.ipWhitelistRepository = ipWhitelistRepository;
} }
public void addWhitelist(IpWhitelistRequest req) { public void addWhitelist(IpWhitelistRequest req) {
// 新增前校验 IP+掩码 是否已存在 // 新增前校验 IP+掩码 是否已存在
checkIpRule(req.getIp(), req.getMask(), null);
IpWhitelistEntity entity = new IpWhitelistEntity();
String ip = req.getIp(); String ip = req.getIp();
String mask = req.getMask(); String mask = req.getMask();
checkIpRule(ip, mask, null);
IpWhitelistEntity entity = new IpWhitelistEntity();
entity.setIp(ip); entity.setIp(ip.trim());
entity.setMask(mask); entity.setMask(mask.trim());
ipWhitelistRepository.addWhitelist(entity); ipWhitelistRepository.addWhitelist(entity);
} }
// //
public void updateWhitelist(IpWhitelistRequest req) { public void updateWhitelist(IpWhitelistRequest req) {
if (req.getId() == null || req.getId().trim().isEmpty()) { 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); checkIpRule(req.getIp(), req.getMask(), idLong);
Optional<IpWhitelistEntity> entityOpt = ipWhitelistRepository.findById(idLong); Optional<IpWhitelistEntity> entityOpt = ipWhitelistRepository.findById(idLong);
if (entityOpt.isEmpty()) { if (entityOpt.isEmpty()) {
throw new BizException(404, "ID is not exist"); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "ID不存在");
} }
IpWhitelistEntity entity = entityOpt.get(); IpWhitelistEntity entity = entityOpt.get();
entity.setIp(req.getIp()); entity.setIp(req.getIp().trim());
entity.setMask(req.getMask()); entity.setMask(req.getMask().trim());
ipWhitelistRepository.updateWhitelist(entity); ipWhitelistRepository.updateWhitelist(entity);
} }
@ -61,15 +70,15 @@ public class IpWhitelistService {
public void deleteWhitelist(Long id) { public void deleteWhitelist(Long id) {
Optional<IpWhitelistEntity> entityOpt = ipWhitelistRepository.findById(id); Optional<IpWhitelistEntity> entityOpt = ipWhitelistRepository.findById(id);
if (entityOpt.isEmpty()) { if (entityOpt.isEmpty()) {
throw new BizException(404, "ID is not exist"); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "ID不存在");
} }
ipWhitelistRepository.deleteById(id); ipWhitelistRepository.deleteById(id);
} }
public IPage<IpWhitelistResponse> getWhitelistPage(IpWhitelistRequest req) { public IPage<IpWhitelistResponse> getWhitelistPage(int pageNum, int pageSize) {
Page<IpWhitelistEntity> page = new Page<>(req.getPageNum(), req.getPageSize()); Page<IpWhitelistEntity> page = new Page<>(pageNum, pageSize);
ipWhitelistRepository.selectPage(page, req); ipWhitelistRepository.selectPage(page);
return page.convert(entity -> { return page.convert(entity -> {
@ -93,13 +102,15 @@ public class IpWhitelistService {
*/ */
private void checkIpRule(String ip, String mask, Long excludeId) { private void checkIpRule(String ip, String mask, Long excludeId) {
if (ip == null || ip.isEmpty()){ if (ip == null || ip.isEmpty()){
throw new IllegalArgumentException("ip is empty"); throw new IllegalArgumentException("ip地址为空");
} }
if (mask == null || mask.isEmpty()){ 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("\\."); String[] octets = ip.split("\\.");
@ -118,14 +129,19 @@ public class IpWhitelistService {
} }
} }
int maskInt= Integer.parseInt(mask); int maskInt = Integer.parseInt(mask);
if (maskInt < 0 || maskInt > 32) { if (maskInt < 0 || maskInt > 32) {
throw new IllegalArgumentException("掩码范围需要在 0 到 32 之间"); throw new IllegalArgumentException("掩码范围需要在 0 到 32 之间");
} }
if (ipWhitelistRepository.existsByIpAndMask(ip, mask, excludeId)) { 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.io.InputStreamReader;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap; import java.util.regex.Pattern;
import java.util.List;
@Service @Service
@Slf4j @Slf4j
public class NetworkConfigService { 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(){ public List<NetworkInfoResponse> getNetworkInfo(){
@ -70,7 +76,7 @@ public class NetworkConfigService {
public Ipv4InfoResponse getIpv4Info(String deviceName){ public Ipv4InfoResponse getIpv4Info(String deviceName){
String connectionName = getConnectionNameByDeviceName(deviceName); String connectionName = getConnectionNameByDeviceName(deviceName.trim());
if (connectionName == null){ if (connectionName == null){
connectionName = deviceName; connectionName = deviceName;
} }
@ -121,33 +127,41 @@ public class NetworkConfigService {
} }
public String setIpv4Config(Ipv4ConfigRequest req){ public void setIpv4Config(Ipv4ConfigRequest req){
String ipv4 = trim(req.getIpv4());
if (req.getDeviceName() == null || req.getDeviceName().trim().isEmpty()) { String deviceName = trim(req.getDeviceName());
String mask = trim(req.getMaskLength());
if (deviceName.isEmpty()) {
throw new IllegalArgumentException("网卡名称不能为空"); throw new IllegalArgumentException("网卡名称不能为空");
} }
if (ipv4.isEmpty()) {
throw new IllegalArgumentException("ip地址不能为空");
if (!isValidIpv4(req.getIpv4())) { }
if (mask.isEmpty()) {
throw new IllegalArgumentException("子网掩码长度不能为空");
}
if (!IPV4_PATTERN.matcher(ipv4).matches()) {
throw new IllegalArgumentException("无效的 IP 地址格式: " + req.getIpv4()); throw new IllegalArgumentException("无效的 IP 地址格式: " + req.getIpv4());
} }
boolean hasGateway = req.getGateway() != null && !req.getGateway().trim().isEmpty(); boolean hasGateway = req.getGateway() != null && !req.getGateway().trim().isEmpty();
if (hasGateway && !IPV4_PATTERN.matcher(req.getGateway().trim()).matches()) {
if (hasGateway && !isValidIpv4(req.getGateway())) {
throw new IllegalArgumentException("无效的网关地址格式: " + req.getGateway()); throw new IllegalArgumentException("无效的网关地址格式: " + req.getGateway());
} }
String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); //判断连接是否存在如果不存在则新建新连接
boolean isNewConnection = false;
String connectionName = getConnectionNameByDeviceName(deviceName);
if (connectionName == null){ if (connectionName == null){
connectionName = req.getDeviceName(); connectionName = deviceName;
isNewConnection = true; // 标记为需要执行 add 创建配置
} }
// 转化掩码格式并判断掩码格式是否正确 // 转化掩码格式并判断掩码格式是否正确
int maskLength = 0; int maskLength = 0;
try { try {
String netmask = req.getMaskLength(); String netmask = trim(req.getMaskLength());
if (netmask == null || netmask.isEmpty()) { if (netmask.isEmpty()) {
throw new IllegalArgumentException("掩码不能为空"); throw new IllegalArgumentException("掩码不能为空");
} }
// 传的是数字/24 // 传的是数字/24
@ -170,9 +184,9 @@ public class NetworkConfigService {
} }
//判断ip和网关是否在同一个子网里面 //判断ip和网关是否在同一个子网里面
int ip1Int = ipToInt(req.getIpv4()); int ip1Int = ipToInt(req.getIpv4().trim());
if (hasGateway){ if (hasGateway){
int ip2Int = ipToInt(req.getGateway()); int ip2Int = ipToInt(req.getGateway().trim());
int maskInt = (maskLength == 0) ? 0 : (0xFFFFFFFF << (32 - maskLength)); int maskInt = (maskLength == 0) ? 0 : (0xFFFFFFFF << (32 - maskLength));
if (!((ip1Int & maskInt) == (ip2Int & maskInt))){ if (!((ip1Int & maskInt) == (ip2Int & maskInt))){
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -184,17 +198,36 @@ public class NetworkConfigService {
String cidr = req.getIpv4() + "/" + maskLength; String cidr = req.getIpv4() + "/" + maskLength;
//todo 这里需要判断hasGateway是否为true再确定命令是否有 ipv4.gateway
try { try {
executeCommand("nmcli", "con", "mod", connectionName, if (isNewConnection) {
"ipv4.method", "manual", List<String> cmdArgs = new ArrayList<>();
"ipv4.addresses", cidr, cmdArgs.add("nmcli"); cmdArgs.add("con"); cmdArgs.add("add");
"ipv4.gateway", req.getGateway()); 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.never-default", "yes");
} else {
executeCommand("nmcli", "con", "mod", connectionName,
"ipv4.method", "manual",
"ipv4.addresses", cidr,
"ipv4.gateway", "");
}
}
executeCommand("nmcli", "con", "up", connectionName); executeCommand("nmcli", "con", "up", connectionName);
return "网络连接 [" + req.getDeviceName() + "] IPv4 配置成功";
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改网络配置失败: " + e.getMessage()); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改网络配置失败: " + e.getMessage());
@ -203,7 +236,7 @@ public class NetworkConfigService {
public Ipv6InfoResponse getIpv6Info(String deviceName){ public Ipv6InfoResponse getIpv6Info(String deviceName){
deviceName = trim(deviceName);
String connectionName = getConnectionNameByDeviceName(deviceName); String connectionName = getConnectionNameByDeviceName(deviceName);
if (connectionName == null){ if (connectionName == null){
connectionName = deviceName; connectionName = deviceName;
@ -238,10 +271,10 @@ public class NetworkConfigService {
Ipv6AddressItem item = new Ipv6AddressItem(); Ipv6AddressItem item = new Ipv6AddressItem();
if (ipAndMaskLength.contains("/")) { if (ipAndMaskLength.contains("/")) {
String[] parts = ipAndMaskLength.split("/"); String[] parts = ipAndMaskLength.split("/");
item.setIpv6(parts[0]); item.setIpv6(parts[0].replace("\\:", ":"));
item.setMaskLength(parts[1]); item.setMaskLength(parts[1]);
} else { } else {
item.setIpv6(ipAndMaskLength); item.setIpv6(ipAndMaskLength.replace("\\:", ":"));
} }
// 将解析好的单个 IP 对象放入集合 // 将解析好的单个 IP 对象放入集合
addressList.add(item); addressList.add(item);
@ -251,7 +284,7 @@ public class NetworkConfigService {
if (lines.size()> 1 && !lines.get(1).trim().isEmpty()) { 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()) { if (lines.size() > 2 && !lines.get(2).trim().isEmpty()) {
@ -268,7 +301,8 @@ public class NetworkConfigService {
return resp; 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()) { if (req.getDeviceName() == null || req.getDeviceName().trim().isEmpty()) {
throw new IllegalArgumentException("网卡名称不能为空"); throw new IllegalArgumentException("网卡名称不能为空");
} }
@ -293,29 +327,47 @@ public class NetworkConfigService {
throw new IllegalArgumentException("无效的 IPv6 前缀长度,必须在 0 到 128 之间"); throw new IllegalArgumentException("无效的 IPv6 前缀长度,必须在 0 到 128 之间");
} }
String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); // 判断连接是否存在如果不存在则需要新建
boolean isNewConnection = false;
String connectionName = getConnectionNameByDeviceName(deviceName);
if (connectionName == null){ 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 { try {
if (hasGateway) { if (isNewConnection) {
executeCommand("nmcli", "con", "mod", connectionName, List<String> cmdArgs = new ArrayList<>();
"ipv6.method", "manual", cmdArgs.add("nmcli"); cmdArgs.add("con"); cmdArgs.add("add");
"ipv6.addresses", ipWithPrefix, cmdArgs.add("type"); cmdArgs.add("ethernet");
"ipv6.gateway", req.getGateway()); 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 { } else {
executeCommand("nmcli", "con", "mod", connectionName, if (hasGateway) {
"ipv6.method", "manual", executeCommand("nmcli", "con", "mod", connectionName,
"ipv6.addresses", ipWithPrefix); "ipv6.method", "manual",
"ipv6.addresses", ipWithPrefix,
"ipv6.gateway", req.getGateway().trim(),
"ipv6.never-default","yes");
} else {
executeCommand("nmcli", "con", "mod", connectionName,
"ipv6.method", "manual",
"ipv6.addresses", ipWithPrefix,
"ipv6.gateway", "");
}
} }
executeCommand("nmcli", "con", "up", connectionName); executeCommand("nmcli", "con", "up", connectionName);
return "网络连接 [" + req.getDeviceName() + "] IPv6 配置成功";
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改 IPv6 网络配置失败: " + e.getMessage()); 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"); List<String> lines = executeCommand("nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device");
for (String line : lines) { for (String line : lines) {
String[] parts = line.split(":", -1); String[] parts = line.split("(?<!\\\\):", -1);
if (parts.length >= 2 && "bond".equals(parts[1])) { if (parts.length >= 2 && "bond".equals(parts[1])) {
bondNames.add(parts[0]); bondNames.add(parts[0]);
} }
@ -335,8 +387,9 @@ public class NetworkConfigService {
return bondNames; return bondNames;
} }
public String createBond(BondCreateRequest req){ public void createBond(BondCreateRequest req){
if (req.getBondName() == null || req.getBondName().trim().isEmpty()) { String bondName = trim(req.getBondName());
if (bondName.isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空"); throw new IllegalArgumentException("Bond 名称不能为空");
} }
if (req.getMode() == null) { if (req.getMode() == null) {
@ -347,8 +400,8 @@ public class NetworkConfigService {
try { try {
List<String> lines = executeCommand("nmcli", "-g", "NAME", "con", "show"); List<String> lines = executeCommand("nmcli", "-g", "NAME", "con", "show");
for (String line : lines) { for (String line : lines) {
if (req.getBondName().equals(line.trim())) { if (bondName.equals(line.trim())) {
throw new RuntimeException("网络连接名称 [" + req.getBondName() + "] 已存在,请勿重复创建"); throw new RuntimeException("网络连接名称 [" + bondName + "] 已存在,请勿重复创建");
} }
} }
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -365,58 +418,93 @@ public class NetworkConfigService {
try { try {
executeCommand("nmcli", "con", "add", executeCommand("nmcli", "con", "add",
"type", "bond", "type", "bond",
"con-name", req.getBondName(), "con-name", bondName,
"ifname", req.getBondName(), "ifname", bondName,
"bond.options", bondOptions); "bond.options", bondOptions);
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "创建 Bond 失败: " + e.getMessage()); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "创建 Bond 失败: " + e.getMessage());
} }
if (req.getIpv4Config() != null) { if (req.getIpv4Config() != null) {
req.getIpv4Config().setDeviceName(req.getBondName()); req.getIpv4Config().setDeviceName(bondName);
try { try {
setIpv4Config(req.getIpv4Config()); setIpv4Config(req.getIpv4Config());
} catch (Exception e) { } 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 void deleteBond(String bondName) {
public String deleteBond(String bondName) { if (bondName == null || bondName.trim().isEmpty()) {
if (bondName == null || bondName.isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空"); throw new IllegalArgumentException("Bond 名称不能为空");
} }
boolean bondExist = false; bondName = bondName.trim();
List<String> uuidsToDelete = new ArrayList<>();
boolean bondFound = false;
try { 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) { for (String line : lines) {
if (bondName.equals(line.trim())) { if (line.trim().isEmpty()) continue;
bondExist = true; 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) { if (!bondFound) {
executeCommand("nmcli", "con", "delete", bondName);
} else {
throw new RuntimeException("Bond [" + bondName + "] 不存在"); throw new RuntimeException("Bond [" + bondName + "] 不存在");
} }
for (String targetUuid : uuidsToDelete) {
//先停后删
try {
executeCommand("nmcli", "con", "down", "uuid", targetUuid);
} catch (Exception e) {
log.warn("停用连接 [{}] 时出现异常: {}", targetUuid, e.getMessage());
}
executeCommand("nmcli", "con", "delete", "uuid", targetUuid);
}
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 删除失败 " + e.getMessage()); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 及从属网卡删除失败: " + e.getMessage());
} }
return "Bond [" + bondName + "] 删除成功";
} }
//todo
//这里是先删除物理网卡再添加bond如果添加失败那么之前删除的不可逆
//建议对添加的bondname和slaveList做存在性校验再执行添加 或者记录已删除连接 UUID/名称失败后回滚 public void addSlavesTOBond(BondAddSlavesRequest req) {
public String addSlavesTOBond(BondAddSlavesRequest req) {
String bondName = req.getBondName(); String bondName = req.getBondName();
List<String> slaves = req.getSlaveList(); List<String> slaves = req.getSlaveList();
if (bondName == null || bondName.trim().isEmpty()) { if (bondName == null || bondName.trim().isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空"); throw new IllegalArgumentException("Bond 名称不能为空");
} }
@ -424,87 +512,145 @@ public class NetworkConfigService {
throw new IllegalArgumentException("物理网卡列表不能为空"); throw new IllegalArgumentException("物理网卡列表不能为空");
} }
List<String> existingConnections; List<String> existingConnections;
List<String> existingDevices;
try { 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) { } catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络连接列表失败"); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络状态失败");
} }
for (String phyIf : slaves) {
if (phyIf == null || phyIf.trim().isEmpty()){ //查询bond是否存在
continue; 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;
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 + "] 在系统中不存在");
}
}
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) { for (String line : existingConnections) {
if (line.trim().isEmpty()){ if (line.trim().isEmpty()) continue;
continue;
}
String slaveConnectionName = bondName + "-slave-" + phyIf;
String[] parts = line.split("(?<!\\\\):", -1); String[] parts = line.split("(?<!\\\\):", -1);
if (parts.length >= 3) { if (parts.length >= 3) {
String uuid = parts[0]; String uuid = parts[0];
String device = parts[1].replace("\\:", ":"); String device = parts[1].replace("\\:", ":");
String name = parts[2].replace("\\:", ":"); String name = parts[2].replace("\\:", ":");
if (slaveConnectionName.equals(name) || phyIf.equals(device)) { if (slaveConnectionName.equals(name) || phyIf.equals(device)) {
try { uuidsToDown.add(uuid);
executeCommand("nmcli", "con", "delete", "uuid", uuid);
} catch (RuntimeException e) {
log.warn("清理物理网卡 {} 的旧连接/冲突连接 [{}] 失败: {}", phyIf, name, e.getMessage());
}
} }
} }
} }
}
//先停止后删除
for (String uuid : uuidsToDown) {
try {
executeCommand("nmcli", "con", "modify", "uuid", uuid, "connection.autoconnect", "no");
} catch (RuntimeException e) {
log.warn("修改旧连接 [{}] 的自启属性失败,该连接可能不存在: {}", uuid, e.getMessage());
}
try { 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; String slaveConnectionName = bondName + "-slave-" + phyIf;
executeCommand("nmcli", "con", "add", executeCommand("nmcli", "con", "add",
"type", "bond-slave", "type", "bond-slave",
"con-name", slaveConnectionName, "con-name", slaveConnectionName,
"ifname", phyIf, "ifname", phyIf,
"master", bondName); "master", bondName);
} catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), newlyAddedSlaveNames.add(slaveConnectionName);
String.format("将物理网卡 [%s] 加入 Bond [%s] 失败: %s", phyIf, bondName, e.getMessage()));
} }
}
try {
executeCommand("nmcli", "con", "up", bondName); executeCommand("nmcli", "con", "up", bondName);
} catch (RuntimeException e) { //激活后删除
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "激活 Bond [" + bondName + "] 失败: " + e.getMessage()); for (String uuid : uuidsToDown) {
} try {
executeCommand("nmcli", "con", "delete", "uuid", uuid);
} catch (Exception e) {
log.warn("Bond配置已生效但清理废弃的旧连接 [{}] 失败: {}", uuid, e.getMessage());
}
}
return String.format("Bond [%s] 成功添加 %d 个从属网卡并已激活", bondName, slaves.size()); } catch (RuntimeException e) {
log.error("将网卡加入 Bond 失败,触发回滚,清理刚创建的 slave 连接", e);
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) { public String removeSlaveFromBond(BondRemoveSlaveRequest req) {
String bondName = req.getBondName(); String bondName = trim(req.getBondName());
List<String> slaves = req.getSlaveList(); List<String> slaves = req.getSlaveList();
if (bondName == null || bondName.trim().isEmpty()) { if (bondName.isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空"); throw new IllegalArgumentException("Bond 名称不能为空");
} }
if (slaves == null || slaves.isEmpty()) { if (slaves == null || slaves.isEmpty()) {
throw new IllegalArgumentException("物理网卡名称不能为空"); throw new IllegalArgumentException("物理网卡名称不能为空");
} }
int deletedCount = 0;
List<String> notFoundSlaves = new ArrayList<>();
// 请求输出格式为: UUID:DEVICE:NAME
List<String> lines; List<String> lines;
try { 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) { } catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络连接列表失败"); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络连接列表失败");
} }
int deletedCount = 0;
for (String phyIf : slaves) { for (String phyIf : slaves) {
if (phyIf == null || phyIf.trim().isEmpty()) continue;
String targetUuid = null; String targetUuid = null;
String slaveName = bondName + "-slave-" + phyIf; String slaveName = bondName + "-slave-" + phyIf;
@ -515,106 +661,111 @@ public class NetworkConfigService {
String[] parts = line.split("(?<!\\\\):", -1); String[] parts = line.split("(?<!\\\\):", -1);
if (parts.length >= 3) { if (parts.length >= 4) {
String uuid = parts[0]; String uuid = parts[0];
String device = parts[1].replace("\\:", ":"); String device = parts[1].replace("\\:", ":");
String name = parts[2].replace("\\:", ":"); String name = parts[2].replace("\\:", ":");
if(slaveName.equals(name) || phyIf.equals(device)){ if (slaveName.equals(name) || phyIf.equals(device)) {
try{ try {
List<String> masterOutputs = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid); List<String> masterOutputs = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid);
String master = masterOutputs.isEmpty() ? "" : masterOutputs.get(0).trim(); String master = masterOutputs.isEmpty() ? "" : masterOutputs.get(0).trim();
if (bondName.equals(master)) { if (bondName.equals(master)) {
targetUuid = uuid; targetUuid = uuid;
break; break;
} }
}catch(RuntimeException e){ } catch (RuntimeException e) {
log.warn("查询网卡 [{}] master 属性失败,可能已被移除或状态异常", name); log.warn("查询网卡 [{}] master 属性失败", name);
} }
} }
} }
} }
if (targetUuid == null) { if (targetUuid == null) {
log.warn("未找到物理网卡 [{}] 对应的从属连接", phyIf); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "未找到网卡 [" + phyIf + "] 对应的从属配置,移除操作已中断");
notFoundSlaves.add(phyIf);
continue;
} }
try { try {
try {
executeCommand("nmcli", "con", "down", "uuid", targetUuid);
} catch (Exception e) {
log.warn("停用从属网卡连接 [{}] 失败,忽略并继续删除: {}", targetUuid, e.getMessage());
}
executeCommand("nmcli", "con", "delete", "uuid", targetUuid); executeCommand("nmcli", "con", "delete", "uuid", targetUuid);
deletedCount ++; deletedCount++;
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), log.error("移除网卡 [{}] 失败: {}", phyIf, e.getMessage());
String.format("从 Bond [%s] 移除网卡 [%s] 失败。原因: %s", throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("从 Bond [%s] 移除网卡 [%s] 失败: %s", bondName, phyIf, e.getMessage()));
bondName, phyIf, e.getMessage()));
} }
} }
// 更新Bond状态 // 只要有成功删除的记录就重新激活 Bond 使其生效
try { if (deletedCount > 0) {
executeCommand("nmcli", "con", "up", bondName); try {
} catch (RuntimeException e) { executeCommand("nmcli", "con", "up", bondName);
throw new BizException(ErrorCode.BIZ_ERROR.getCode(),"更新 Bond [" + bondName + "] 失败: " + e.getMessage()); } 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);
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);
}
} }
public List<String> getBondSlaves(String bondName) { public List<String> getBondSlaves(String bondName) {
if (bondName == null || bondName.trim().isEmpty()) { bondName = trim(bondName);
if (bondName.isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空"); throw new IllegalArgumentException("Bond 名称不能为空");
} }
List<String> slaveList = new ArrayList<>(); List<String> slaveList = new ArrayList<>();
List<String> lines; List<String> lines;
try { 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) { } catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "查询 Bond 从属网卡列表失败"); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "查询 Bond 从属网卡列表失败");
} }
boolean bondExists = false;
for (String line : lines) { for (String line : lines) {
if (line.trim().isEmpty()) { if (line.trim().isEmpty()) {
continue; continue;
} }
String[] parts = line.split("(?<!\\\\):", -1); String[] parts = line.split("(?<!\\\\):", -1);
if (parts.length >= 3) { if (parts.length >= 4) {
String uuid = parts[0].replace("\\:", ":"); String uuid = parts[0].replace("\\:", ":");
String device = parts[1].replace("\\:", ":"); String device = parts[1].replace("\\:", ":");
String name = parts[2].replace("\\:", ":"); String name = parts[2].replace("\\:", ":");
String type = parts[3];
List<String> masterOutputs = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid); if (bondName.equals(name) && "bond".equals(type)) {
String master = masterOutputs.isEmpty() ? "" : masterOutputs.get(0).trim(); bondExists = true;
}
if (bondName.equals(master)) { if (type != null && type.contains("ethernet")) {
if (!device.isEmpty() && !device.equals("--")) { List<String> masterOutputs = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid);
// 网卡处于激活连接状态 String master = masterOutputs.isEmpty() ? "" : masterOutputs.get(0).trim();
slaveList.add(device);
} else { if (bondName.equals(master)) {
// 网卡处于断开状态 if (!device.isEmpty() && !device.equals("--")) {
String prefix = bondName + "-slave-"; slaveList.add(device);
if (name.startsWith(prefix)) { } else {
slaveList.add(name.substring(prefix.length())); String prefix = bondName + "-slave-";
if (name.startsWith(prefix)) {
slaveList.add(name.substring(prefix.length()));
}
} }
} }
} }
} }
} }
if (!bondExists) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 不存在");
}
return slaveList; return slaveList;
} }
@ -649,13 +800,14 @@ public class NetworkConfigService {
} }
public String setBondMode(BondModifyModeRequest req) { public void setBondMode(BondModifyModeRequest req) {
String bondName = req.getBondName(); String bondName = req.getBondName();
Integer mode = req.getMode(); Integer mode = req.getMode();
if (bondName == null || bondName.trim().isEmpty()) { if (bondName == null || bondName.trim().isEmpty()) {
throw new IllegalArgumentException("Bond 名称不能为空"); throw new IllegalArgumentException("Bond 名称不能为空");
} }
bondName = bondName.trim();
if (mode == null) { if (mode == null) {
throw new IllegalArgumentException("新的 Bond 模式不能为空"); throw new IllegalArgumentException("新的 Bond 模式不能为空");
} }
@ -687,7 +839,6 @@ public class NetworkConfigService {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "重新激活 Bond [" + bondName + "] 失败" + e.getMessage()); 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 deviceName = req.getDeviceName();
String gatewayIp = req.getGatewayIp(); String gatewayIp = req.getGatewayIp();
// 1. 基础校验
if (deviceName == null || deviceName.trim().isEmpty()) { if (deviceName == null || deviceName.trim().isEmpty()) {
throw new IllegalArgumentException("网卡名称不能为空"); throw new IllegalArgumentException("网卡名称不能为空");
} }
@ -749,7 +900,11 @@ public class NetworkConfigService {
throw new IllegalArgumentException("网关 IP 不能为空"); 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){ if (connectionName == null){
connectionName = req.getDeviceName(); connectionName = req.getDeviceName();
} }
@ -758,8 +913,9 @@ public class NetworkConfigService {
try { try {
executeCommand("nmcli", "con", "mod", connectionName, executeCommand("nmcli", "con", "mod", connectionName,
"ipv4.gateway", gatewayIp, "ipv4.gateway", gatewayIp,
//多张网卡配置了网关需调整 Metric 优先级 //TODO待修改多张网卡配置了网关需调整 Metric 优先级)
"ipv4.route-metric", "50"); "ipv4.route-metric", "50",
"ipv4.never-default", "no");
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("为网卡 [%s] 设置网关失败: %s", deviceName, e.getMessage())); 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())); 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 deviceName = req.getDeviceName();
String targetCidr = req.getTargetCidr(); String targetCidr = req.getTargetCidr();
String nextHop = req.getNextHop(); String nextHop = req.getNextHop();
//todo 对gatewayIptargetCidrnextHop增加格式校验可用正则表达式
if (deviceName == null || deviceName.trim().isEmpty()) { if (deviceName == null || deviceName.trim().isEmpty()) {
throw new IllegalArgumentException("网卡名称不能为空"); throw new IllegalArgumentException("网卡名称不能为空");
} }
if (targetCidr == null || targetCidr.trim().isEmpty()) { if (targetCidr == null || targetCidr.trim().isEmpty()) {
throw new IllegalArgumentException("目标网段(CIDR)不能为空"); 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()) { // if (nextHop == null || nextHop.trim().isEmpty()) {
// throw new IllegalArgumentException("下一跳 IP 不能为空"); // throw new IllegalArgumentException("下一跳 IP 不能为空");
// } // }
String connectionName = getConnectionNameByDeviceName(req.getDeviceName().trim());
String connectionName = getConnectionNameByDeviceName(req.getDeviceName());
if (connectionName == null){ if (connectionName == null){
connectionName = req.getDeviceName(); connectionName = req.getDeviceName();
} }
@ -819,18 +980,17 @@ public class NetworkConfigService {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage())); 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(); String deviceName = req.getDeviceName();
if (deviceName == null || deviceName.trim().isEmpty()) { if (deviceName == null || deviceName.trim().isEmpty()) {
throw new IllegalArgumentException("网卡名称不能为空"); throw new IllegalArgumentException("网卡名称不能为空");
} }
String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); String connectionName = getConnectionNameByDeviceName(req.getDeviceName().trim());
if (connectionName == null){ if (connectionName == null){
connectionName = req.getDeviceName(); connectionName = req.getDeviceName();
} }
@ -848,12 +1008,11 @@ public class NetworkConfigService {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage())); 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 deviceName = req.getDeviceName();
String targetCidr = req.getTargetCidr(); String targetCidr = req.getTargetCidr();
String nextHop = req.getNextHop(); String nextHop = req.getNextHop();
@ -868,6 +1027,14 @@ public class NetworkConfigService {
// throw new IllegalArgumentException("下一跳 IP 不能为空"); // 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()); String connectionName = getConnectionNameByDeviceName(req.getDeviceName());
if (connectionName == null){ if (connectionName == null){
connectionName = req.getDeviceName(); connectionName = req.getDeviceName();
@ -896,24 +1063,23 @@ public class NetworkConfigService {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage())); throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage()));
} }
return String.format("成功从网卡 [%s] 删除静态路由", deviceName);
} }
private boolean isValidIpv4(String ip) { // private boolean isValidIpv4(String ip) {
if (ip == null || ip.isEmpty()) return false; // if (ip == null || ip.isEmpty()) return false;
try { // try {
ipToInt(ip); // ipToInt(ip);
return true; // return true;
} catch (IllegalArgumentException e) { // } catch (IllegalArgumentException e) {
return false; // return false;
} // }
} // }
private boolean isValidIpv6(String ip) { private boolean isValidIpv6(String ip) {
if (ip == null || ip.isEmpty()) return false; if (ip == null || ip.isEmpty()) return false;
try { try {
InetAddress inetAddress = InetAddress.getByName(ip); InetAddress inetAddress = InetAddress.getByName(ip.trim());
return inetAddress instanceof Inet6Address; return inetAddress instanceof Inet6Address;
} catch (Exception e) { } catch (Exception e) {
return false; return false;
@ -925,10 +1091,13 @@ public class NetworkConfigService {
if (parts.length != 4) { if (parts.length != 4) {
throw new IllegalArgumentException("非法的 IPv4 地址: " + ipv4); throw new IllegalArgumentException("非法的 IPv4 地址: " + ipv4);
} }
//TODO 前导零处理
int result = 0; int result = 0;
for (int i = 0; i < 4; i++) { 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) { if (octet < 0 || octet > 255) {
throw new IllegalArgumentException("非法的 IPv4 地址段: " + octet); throw new IllegalArgumentException("非法的 IPv4 地址段: " + octet);
} }
@ -999,7 +1168,67 @@ public class NetworkConfigService {
throw new RuntimeException("服务器内部处理中断");} 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; 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.dto.TimeConfigRequest;
import com.cisd.tms.modules.device.service.TimeConfigService; import com.cisd.tms.modules.device.service.TimeConfigService;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; 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 java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class TimeConfigServiceImpl implements TimeConfigService { public class TimeConfigServiceImpl implements TimeConfigService {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm").withResolverStyle(ResolverStyle.STRICT);
@Override @Override
public void processTimeConfig(TimeConfigRequest request) { public void processTimeConfig(TimeConfigRequest request) {
String mode = request.getMode(); String mode = request.getMode();
@ -25,15 +41,35 @@ public class TimeConfigServiceImpl implements TimeConfigService {
} }
String datetime = request.getDatetime(); String datetime = request.getDatetime();
//todo 建议用LocalDateTime进行校验格式 if (datetime == null) {
if (datetime == null || !datetime.matches("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$")) {
throw new IllegalArgumentException("Invalid or empty datetime format. Expected: YYYY-MM-DD HH:MM"); 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()); try{
executeCommand("systemctl stop chronyd"); executeCommand("timedatectl", "set-timezone", request.getTimezone().trim());
//todo 失败后重启chronyd } catch (Exception e){
executeCommand("date -s '" + request.getDatetime() + "'"); 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)) { } else if ("NTP".equalsIgnoreCase(mode)) {
@ -41,72 +77,112 @@ public class TimeConfigServiceImpl implements TimeConfigService {
if (servers == null || servers.isEmpty()) { if (servers == null || servers.isEmpty()) {
throw new IllegalArgumentException("At least one NTP server IP must be provided in NTP mode"); 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 interval = request.getSyncInterval() != null ? request.getSyncInterval() : 512;
int pollExp = (int) Math.round(Math.log(interval) / Math.log(2)); 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(); StringBuilder serverConfLines = new StringBuilder();
for (String server : servers) { for (String server : servers) {
if (!server.matches("^[A-Za-z0-9.-]+$")) { if (!server.matches("^[A-Za-z0-9.-]+$")) {
throw new IllegalArgumentException("Invalid NTP server address format: " + server); 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")) { if (!pingResult.contains("1 received")) {
throw new RuntimeException("NTP server is unreachable via ping: " + server); throw new RuntimeException("NTP server is unreachable via ping: " + server);
} }
serverConfLines.append("server ").append(server) serverConfLines.append("server ").append(server)
.append(" iburst minpoll ").append(pollExp) .append(" iburst minpoll ").append(pollExp)
.append(" maxpoll ").append(pollExp).append("\\n"); .append(" maxpoll ").append(pollExp).append("\n");
} }
String confContent = serverConfLines + String confContent = serverConfLines +
"driftfile /var/lib/chrony/drift\\n" + "driftfile /var/lib/chrony/drift\n" +
"makestep 1.0 3\\n" + "makestep 1.0 3\n" +
"rtcsync\\n"; "rtcsync\n";
//todo 这里直接重写配置文件如果失败则原配置丢失建议备份失败后回滚
executeCommand("echo -e '" + confContent + "' > /etc/chrony.conf");
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");
}
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 { } else {
throw new IllegalArgumentException("Unknown mode: " + mode); throw new IllegalArgumentException("Unknown mode: " + mode);
} }
} }
private String executeCommand(String command) { private String executeCommand(String... commandArgs) {
ProcessBuilder processBuilder = new ProcessBuilder("bash", "-c", command); ProcessBuilder processBuilder = new ProcessBuilder(commandArgs);
processBuilder.redirectErrorStream(true); processBuilder.redirectErrorStream(true);
Process process = null; Process process = null;
try { try {
process = processBuilder.start(); process = processBuilder.start();
StringBuilder output = new StringBuilder(); StringBuilder outputBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
output.append(line).append("\n"); outputBuilder.append(line).append("\n");
} }
} }
int exitCode = process.waitFor(); int exitCode = process.waitFor();
String outputStr = outputBuilder.toString().trim();
if (exitCode != 0) { 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) { } 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) { } catch (InterruptedException e) {
if (process != null) { if (process != null) {
process.destroy(); process.destroy();
} }
Thread.currentThread().interrupt(); 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),
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)
);