diff --git a/pom.xml b/pom.xml
index d891a32..2127f8c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,6 +52,12 @@
runtime
+
+ com.baomidou
+ mybatis-plus-jsqlparser
+ ${mybatis-plus.version}
+
+
org.springdoc
springdoc-openapi-starter-webmvc-ui
diff --git a/src/main/java/com/cisd/tms/common/config/MybatisPlusConfig.java b/src/main/java/com/cisd/tms/common/config/MybatisPlusConfig.java
index 96d9499..54aaf7c 100644
--- a/src/main/java/com/cisd/tms/common/config/MybatisPlusConfig.java
+++ b/src/main/java/com/cisd/tms/common/config/MybatisPlusConfig.java
@@ -1,6 +1,8 @@
package com.cisd.tms.common.config;
+import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -9,6 +11,8 @@ public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
- return new MybatisPlusInterceptor();
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+ return interceptor;
}
}
diff --git a/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java b/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java
index 05d5f36..d3d4917 100644
--- a/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java
+++ b/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java
@@ -1,24 +1,26 @@
package com.cisd.tms.common.config;
+import com.cisd.tms.common.util.TraceIdUtil;
import com.cisd.tms.modules.auth.security.InternalAuthorizationInterceptor;
+import com.cisd.tms.security.internal.CachedBodyHttpServletRequest;
import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
import com.cisd.tms.security.internal.InternalApiReplayInterceptor;
-import com.cisd.tms.security.internal.CachedBodyHttpServletRequest;
import com.cisd.tms.security.openapi.OpenApiSignAuthInterceptor;
-import com.cisd.tms.common.util.TraceIdUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
-import java.io.IOException;
import org.slf4j.MDC;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import java.io.IOException;
+
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@@ -117,7 +119,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
.allowedOriginPatterns("*")
.allowedMethods("*")
.allowedHeaders("*")
- .exposedHeaders("X-Trace-Id")
+ .exposedHeaders("X-Trace-Id", HttpHeaders.CONTENT_DISPOSITION)
.allowCredentials(true)
.maxAge(3600);
}
diff --git a/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java b/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java
index 2610cce..ecd0d4b 100644
--- a/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java
+++ b/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java
@@ -2,23 +2,21 @@ package com.cisd.tms.modules.device.controller;
import com.cisd.tms.common.api.ApiResponse;
-import com.cisd.tms.common.enums.ErrorCode;
-import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.modules.device.dto.network.*;
+import com.cisd.tms.modules.device.entity.PageResult;
import com.cisd.tms.modules.device.service.NetworkConfigService;
import com.cisd.tms.modules.log.annotation.AuditedOperation;
import com.cisd.tms.modules.log.enums.ActionType;
import com.cisd.tms.modules.log.enums.ModuleCode;
-import com.cisd.tms.security.internal.ReplayProtected;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
-import java.util.List;
-
@Tag(name = "网络配置管理", description = "提供设备网络信息查询、IPv4/IPv6配置、Bond配置及路由管理等接口")
@RestController
-@RequestMapping("/api/v1/device/network-config")
+@RequestMapping("/api/v1/device/network")
public class NetworkConfigController {
private final NetworkConfigService networkConfigService;
@@ -27,158 +25,88 @@ public class NetworkConfigController {
this.networkConfigService = networkConfigService;
}
-
- @Operation(summary = "获取网络信息", description = "获取当前设备的所有网络连接信息")
- @GetMapping("/network-info")
- public ApiResponse> getNetworkInfo(){
- return ApiResponse.success(networkConfigService.getNetworkInfo());
+ @Operation(summary = "分页查询网卡配置", description = "分页查询设备网卡配置列表,支持按网卡名称、状态等条件筛选")
+ @PostMapping("/card/Page")
+ public ApiResponse> getNetworkTablePage(@RequestBody NetworkTableQueryRequest request) {
+ PageResult pageData = networkConfigService.getNetworkTablePage(request);
+ return ApiResponse.success(pageData);
}
-
- @Operation(summary = "获取IPv4配置信息", description = "根据设备名称获取指定网络接口的IPv4配置详情")
- @GetMapping("/ipv4-info/{deviceName}")
- public ApiResponse getIpv4Info(@PathVariable String deviceName){
- return ApiResponse.success(networkConfigService.getIpv4Info(deviceName));
- }
-
-
- @Operation(summary = "设置IPv4配置", description = "为指定网络接口配置IPv4地址、掩码、网关等信息")
- @PostMapping("/ipv4-config/set")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.CREATE, summary = "设置IPv4配置")
- public ApiResponse setIpv4Config(@RequestBody Ipv4ConfigRequest req) {
- networkConfigService.setIpv4Config(req);
+ @Operation(summary = "配置网卡", description = "配置指定网卡的 IPv4 或 IPv6 地址、前缀长度、网关和状态")
+ @PostMapping("/card/configure")
+ public ApiResponse configureNetworkCard(@RequestBody @Validated NetworkConfigureRequest request) {
+ networkConfigService.configureNetworkCard(request);
return ApiResponse.success();
}
- @Operation(summary = "获取IPv6配置信息", description = "根据设备名称获取指定网络接口的IPv6配置详情")
- @GetMapping("/ipv6-info/{deviceName}")
- public ApiResponse getIpv6Info(@PathVariable String deviceName){
- return ApiResponse.success(networkConfigService.getIpv6Info(deviceName));
- }
-
-
- @Operation(summary = "设置IPv6配置", description = "为指定网络接口配置IPv6地址、前缀长度、网关等信息")
- @PostMapping("/ipv6-config/set")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.CREATE, summary = "设置IPv6配置")
- public ApiResponse setIpv6Config(@RequestBody Ipv6ConfigRequest req) {
- networkConfigService.setIpv6Config(req);
+ @Operation(summary = "新增网卡配置", description = "新增或配置指定网卡的网络地址信息")
+ @PostMapping("/card/add")
+ public ApiResponse addNetworkCard(@RequestBody @Validated NetworkConfigureRequest req) {
+ networkConfigService.configureNetworkCard(req);
return ApiResponse.success();
}
- @Operation(summary = "获取所有Bond名称", description = "返回当前系统中所有已创建的Bond接口名称")
- @GetMapping("/all-bond-name")
- public ApiResponse> getAllBondName(){
- return ApiResponse.success(networkConfigService.getAllBondName());
- }
-
- @Operation(summary = "创建Bond", description = "创建一个新的Bond聚合接口,需指定名称、模式和从属接口")
- @PostMapping("/bond/create")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.CREATE, summary = "创建Bond")
- public ApiResponse createBond(@RequestBody BondCreateRequest req) {
- networkConfigService.createBond(req);
+ @Operation(summary = "删除网卡 IP 配置", description = "删除指定网卡下的某条 IPv4 或 IPv6 地址配置")
+ @PostMapping("/card/delete")
+ public ApiResponse deleteNetworkCardRecord(@RequestBody NetworkDeleteRequest req) {
+ networkConfigService.deleteNetworkCardRecord(req);
return ApiResponse.success();
}
+ @Operation(summary = "分页查询 Bond 配置", description = "分页查询 Bond 聚合接口配置列表,包含 Bond 名称、模式、IP、网关和状态")
+ @PostMapping("/bond/page")
+ public ApiResponse> getBondPage(@RequestBody BondTableRequest req) {
+ return ApiResponse.success(networkConfigService.getBondPage(req));
+ }
- @Operation(summary = "删除Bond", description = "根据Bond名称删除指定的聚合接口")
+
+ @Operation(summary = "删除 Bond", description = "删除指定 Bond 聚合接口及其从属网卡连接配置")
@PostMapping("/bond/delete/{bondName}")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.DELETE, summary = "删除Bond")
- public ApiResponse deleteBond(@PathVariable("bondName") String bondName){
+ public ApiResponse deleteBond(@PathVariable String bondName) {
networkConfigService.deleteBond(bondName);
return ApiResponse.success();
}
- @Operation(summary = "添加从属接口到Bond", description = "向指定Bond中添加一个或多个从属网络接口")
- @PostMapping("/bond-slave/add")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.UPDATE, summary = "添加从属接口到Bond")
- public ApiResponse addSlavesTOBond(@RequestBody BondAddSlavesRequest req) {
- networkConfigService.addSlavesTOBond(req);
+
+ @Operation(summary = "创建Bond", description = "创建一个新的Bond聚合接口,需指定名称、模式和从属接口")
+ @PostMapping("/bond/create")
+ @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.CREATE, summary = "创建Bond")
+ public ApiResponse createBond(@Valid @RequestBody BondCreateRequest req) {
+ networkConfigService.createBond(req);
return ApiResponse.success();
}
- @Operation(summary = "从Bond中移除从属接口", description = "从指定Bond中移除一个或多个从属网络接口")
- @PostMapping("/bond-slave/remove")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.DELETE, summary = "从Bond中移除从属接口")
- public ApiResponse removeSlaveFromBond(@RequestBody BondRemoveSlaveRequest req) {
- String resultMessage = networkConfigService.removeSlaveFromBond(req);
- return ApiResponse.success(resultMessage);
- }
-
- @Operation(summary = "获取Bond的从属接口列表", description = "根据Bond名称返回其所有从属接口名称")
- @GetMapping("/bond-slave/{bondName}")
- public ApiResponse> getBondSlaves(@PathVariable String bondName){
- return ApiResponse.success(networkConfigService.getBondSlaves(bondName));
- }
-
-
- @Operation(summary = "获取Bond模式", description = "根据Bond名称查询当前的绑定模式")
- @GetMapping("/bond/mode/{bondName}")
- public ApiResponse getBondMode(@PathVariable("bondName") String bondName) {
-
- String mode = networkConfigService.getBondMode(bondName);
-
- if (mode == null) {
- throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "未找到该连接的 Bond 模式");
- }
-
- return ApiResponse.success(mode);
- }
-
- @Operation(summary = "设置Bond模式", description = "修改指定Bond的绑定模式")
- @PostMapping("/bond/mode/set")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.UPDATE, summary = "设置Bond模式")
- public ApiResponse setBondMode(@RequestBody BondModifyModeRequest req) {
- networkConfigService.setBondMode(req);
+ @Operation(summary = "配置 Bond", description = "修改指定 Bond 的模式、IP 地址、网关、从属物理网卡和激活状态")
+ @PostMapping("/bond/config")
+ public ApiResponse configBond(@Valid @RequestBody BondConfigRequest req) {
+ networkConfigService.configBond(req);
return ApiResponse.success();
}
- @Operation(summary = "获取路由表", description = "返回当前系统的IPv4/IPv6路由表信息")
- @GetMapping("/routes/routingTable")
- public ApiResponse> getRoutingTable() {
- List routes = networkConfigService.getRoutingTable();
- return ApiResponse.success(routes);
+ @Operation(summary = "分页查询路由配置", description = "分页查询静态路由列表,支持 IPv4/IPv6、目标类型和状态筛选")
+ @PostMapping("/route/page")
+ public ApiResponse> routePage(@RequestBody RouteTableRequest req) {
+ return ApiResponse.success(networkConfigService.getRoutePage(req));
}
- @Operation(summary = "设置默认路由", description = "配置或修改系统的默认网关路由")
- @PostMapping("/routes/default/set")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.UPDATE, summary = "设置默认路由")
- public ApiResponse setDefaultRoute(@RequestBody SetDefaultRouteRequest req) {
- networkConfigService.setDefaultRoute(req);
+ @Operation(summary = "新增路由", description = "新增一条 IPv4 或 IPv6 静态路由配置,可指定目标地址、前缀长度、网口和下一跳")
+ @PostMapping("/route/add")
+ public ApiResponse routeAdd(@Valid @RequestBody RouteCreateRequest req) {
+ networkConfigService.addRoute(req);
return ApiResponse.success();
}
- @Operation(summary = "添加静态路由", description = "新增一条静态路由规则")
- @PostMapping("/routes/static/add")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.UPDATE, summary = "添加静态路由")
- public ApiResponse addStaticRoute(@RequestBody AddStaticRouteRequest req) {
- networkConfigService.addStaticRoute(req);
+ @Operation(summary = "修改路由", description = "修改一条已有静态路由,通过删除旧路由并新增新路由实现")
+ @PostMapping("/route/update")
+ public ApiResponse routeUpdate(@Valid @RequestBody RouteUpdateRequest req) {
+ networkConfigService.updateRoute(req);
return ApiResponse.success();
}
- @Operation(summary = "删除默认路由", description = "删除指定的默认路由")
- @PostMapping("/routes/default/delete")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.DELETE, summary = "删除默认路由")
- public ApiResponse deleteDefaultRoute(@RequestBody DeleteDefaultRouteRequest req) {
- networkConfigService.deleteDefaultRoute(req);
- return ApiResponse.success();
- }
-
- @Operation(summary = "删除静态路由", description = "删除指定的静态路由")
- @PostMapping("/routes/static/delete")
- @ReplayProtected
- @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.DELETE, summary = "删除静态路由")
- public ApiResponse deleteStaticRoute(@RequestBody DeleteStaticRouteRequest req) {
- networkConfigService.deleteStaticRoute(req);
+ @Operation(summary = "删除路由", description = "删除指定网口下的一条 IPv4 或 IPv6 静态路由配置")
+ @PostMapping("/route/delete")
+ public ApiResponse routeDelete(@Valid @RequestBody RouteDeleteRequest req) {
+ networkConfigService.deleteRoute(req);
return ApiResponse.success();
}
}
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/AddStaticRouteRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/AddStaticRouteRequest.java
deleted file mode 100644
index 7e298eb..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/AddStaticRouteRequest.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "添加静态路由请求参数")
-public class AddStaticRouteRequest {
-
- @Schema(description = "网络设备名称(如 eth0、bond0)", example = "eth0")
- private String deviceName;
- @Schema(description = "目标网络CIDR", example = "192.168.1.0/24")
- private String targetCidr;
- @Schema(description = "下一跳地址(网关IP)", example = "192.168.1.1")
- private String nextHop;
-}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/BondConfigRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/BondConfigRequest.java
new file mode 100644
index 0000000..399d552
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/BondConfigRequest.java
@@ -0,0 +1,77 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Schema(description = "Bond 配置请求")
+public class BondConfigRequest {
+
+ /**
+ * Bond 名称,例如 bond0
+ */
+ @Schema(description = "Bond 名称,例如 bond0", example = "bond0", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotBlank(message = "Bond名称不能为空")
+ private String bondName;
+
+ /**
+ * Bond 模式
+ * 0: balance-rr
+ * 1: active-backup
+ * 2: balance-xor
+ * 4: 802.3ad
+ */
+ @Schema(
+ description = "Bond 模式:0=balance-rr平衡轮询,1=active-backup主备,2=balance-xor平衡异或,4=802.3ad动态链路聚合",
+ example = "1",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotNull(message = "Bond类型不能为空")
+ private Integer mode;
+
+ /**
+ * 地址类型:IPv4 / IPv6
+ */
+ @Schema(description = "地址类型:IPv4 或 IPv6", example = "IPv4", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotBlank(message = "地址类型不能为空")
+ private String addressType;
+
+ /**
+ * 网络地址
+ */
+ @Schema(description = "网络地址,不包含前缀长度", example = "192.168.10.100", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotBlank(message = "网络地址不能为空")
+ private String ipAddress;
+
+ /**
+ * 前缀长度
+ */
+ @Schema(description = "前缀长度,IPv4 范围 0-32,IPv6 范围 0-128", example = "24", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "前缀长度不能为空")
+ private Integer prefixLength;
+
+ /**
+ * 网关地址,可为空
+ */
+ @Schema(description = "网关地址,可为空", example = "192.168.10.1")
+ private String gateway;
+
+ /**
+ * 选择的物理网卡
+ */
+ @Schema(description = "选择加入 Bond 的物理网卡列表", example = "[\"eth0\", \"eth1\"]", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "请选择至少一个物理网卡")
+ private List slaveList;
+
+ /**
+ * Active / Inactive
+ */
+ @Schema(description = "Bond 状态:Active=激活,Inactive=未激活", example = "Active", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotBlank(message = "状态不能为空")
+ private String status;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/BondCreateRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/BondCreateRequest.java
index 94ae083..68aff31 100644
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/BondCreateRequest.java
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/BondCreateRequest.java
@@ -1,32 +1,95 @@
package com.cisd.tms.modules.device.dto.network;
import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
import lombok.Data;
+import java.util.List;
+
@Data
@Schema(description = "创建Bond请求参数")
public class BondCreateRequest {
+
/**
- * Bond 名称,例如 "bond0"
+ * bond名称,例如 bond0
*/
- @Schema(description = "Bond 名称", example = "bond0")
+ @Schema(
+ description = "Bond名称,例如 bond0、bond1",
+ example = "bond0",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "Bond名称不能为空")
private String bondName;
/**
- * 模式 (例如: 0, 1, 2, 4)
- * 0: balance-rr (轮询)
- * 1: active-backup (主备)
+ * bond模式
+ * 0: balance-rr
+ * 1: active-backup
* 2: balance-xor
- * 4: 802.3ad (动态链路聚合)
+ * 4: 802.3ad
*/
- @Schema(description = "Bond 模式(0: balance-rr 轮询, 1: active-backup 主备, 2: balance-xor, 4: 802.3ad 动态链路聚合"
- ,example = "1")
+ @Schema(
+ description = "Bond模式:0=balance-rr平衡轮询,1=active-backup主备,2=balance-xor平衡异或,4=802.3ad动态链路聚合",
+ example = "1",
+ allowableValues = {"0", "1", "2", "4"},
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotNull(message = "Bond类型不能为空")
private Integer mode;
/**
- * 可选的 IPv4 配置
- * 如果前端传了该对象,创建 Bond 后会自动配置 IP
+ * 地址类型:IPv4 / IPv6
*/
- @Schema(description = "可选的 IPv4 配置,如果提供该对象,创建 Bond 后会自动配置 IP")
- private Ipv4ConfigRequest ipv4Config;
+ @Schema(
+ description = "地址类型:IPv4 或 IPv6",
+ example = "IPv4",
+ allowableValues = {"IPv4", "IPv6"},
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "地址类型不能为空")
+ private String addressType;
+
+ /**
+ * 网络地址
+ */
+ @Schema(
+ description = "网络地址,不包含前缀长度",
+ example = "192.168.10.100",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "网络地址不能为空")
+ private String ipAddress;
+
+ /**
+ * 前缀长度
+ */
+ @Schema(
+ description = "前缀长度,IPv4范围0-32,IPv6范围0-128",
+ example = "24",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotNull(message = "前缀长度不能为空")
+ private Integer prefixLength;
+
+ /**
+ * 网关地址,可为空
+ */
+ @Schema(
+ description = "网关地址,可为空",
+ example = "192.168.10.1"
+ )
+ private String gateway;
+
+ /**
+ * 选择的物理网卡,例如 eth0、eth1
+ */
+ @Schema(
+ description = "选择加入Bond的物理网卡列表",
+ example = "[\"eth0\", \"eth1\"]",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotEmpty(message = "请选择至少一个物理网卡")
+ private List slaveList;
}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/BondModifyModeRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/BondModifyModeRequest.java
deleted file mode 100644
index 1d888e5..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/BondModifyModeRequest.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "修改bond模式请求参数")
-public class BondModifyModeRequest {
-
- @Schema(description = "Bond 名称", example = "bond0")
- private String bondName;
- @Schema(description = "Bond 模式(0: balance-rr 轮询, 1: active-backup 主备, 2: balance-xor, 4: 802.3ad 动态链路聚合"
- ,example = "1")
- private Integer mode;
-}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/BondTableRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/BondTableRequest.java
new file mode 100644
index 0000000..fd5bc55
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/BondTableRequest.java
@@ -0,0 +1,37 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "Bond分页查询请求参数")
+public class BondTableRequest {
+
+ /**
+ * Bond 名称
+ */
+ @Schema(description = "Bond名称,支持按Bond名称筛选", example = "bond0")
+ private String bondName;
+
+ /**
+ * 状态:Active / Inactive
+ */
+ @Schema(
+ description = "Bond状态:Active=激活,Inactive=未激活",
+ example = "Active",
+ allowableValues = {"Active", "Inactive"}
+ )
+ private String status;
+
+ /**
+ * 页码
+ */
+ @Schema(description = "页码,从1开始", example = "1")
+ private Integer pageNum = 1;
+
+ /**
+ * 每页条数
+ */
+ @Schema(description = "每页条数", example = "10")
+ private Integer pageSize = 10;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/BondTableResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/BondTableResponse.java
new file mode 100644
index 0000000..51261ec
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/BondTableResponse.java
@@ -0,0 +1,62 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "Bond分页查询响应数据")
+public class BondTableResponse {
+
+ /**
+ * bond名称
+ */
+ @Schema(description = "Bond名称", example = "bond0")
+ private String bondName;
+
+ /**
+ * bond模型
+ */
+ @Schema(
+ description = "Bond模式",
+ example = "bond1-主备"
+ )
+ private String bondMode;
+
+ /**
+ * 地址类型:IPV4 / IPV6
+ */
+ @Schema(
+ description = "地址类型:IPV4 或 IPV6",
+ example = "IPV4",
+ allowableValues = {"IPV4", "IPV6", "-"}
+ )
+ private String addressType;
+
+ /**
+ * 地址
+ */
+ @Schema(description = "IP地址", example = "192.168.10.100")
+ private String address;
+
+ /**
+ * 前缀长度
+ */
+ @Schema(description = "前缀长度", example = "24")
+ private String prefixLength;
+
+ /**
+ * 网关
+ */
+ @Schema(description = "网关地址", example = "192.168.10.1")
+ private String gateway;
+
+ /**
+ * 状态:Active / Inactive
+ */
+ @Schema(
+ description = "Bond状态:Active=激活,Inactive=未激活",
+ example = "Active",
+ allowableValues = {"Active", "Inactive"}
+ )
+ private String status;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteDefaultRouteRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteDefaultRouteRequest.java
deleted file mode 100644
index 8b3ca59..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteDefaultRouteRequest.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "删除默认路由请求参数")
-public class DeleteDefaultRouteRequest {
- @Schema(description = "网络设备名称(如 eth0、bond0)", example = "eth0")
- private String deviceName;
-}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteStaticRouteRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteStaticRouteRequest.java
deleted file mode 100644
index ddcf1d7..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteStaticRouteRequest.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "删除静态路由请求参数")
-public class DeleteStaticRouteRequest {
-
- @Schema(description = "网络设备名称", example = "eth0")
- private String deviceName;
-
- @Schema(description = "目标网络CIDR", example = "192.168.1.0/24")
- private String targetCidr;
-
- @Schema(description = "下一跳地址(网关IP)", example = "192.168.1.1")
- private String nextHop;
-}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4ConfigRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4ConfigRequest.java
deleted file mode 100644
index 921b965..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4ConfigRequest.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "IPv4配置请求参数")
-public class Ipv4ConfigRequest {
- @Schema(description = "网络设备名称", example = "eth0")
- private String deviceName;
- @Schema(description = "IPv4地址", example = "192.168.1.100")
- private String ipv4;
- @Schema(description = "子网掩码长度", example = "24")
- private String maskLength;
- @Schema(description = "默认网关地址", example = "192.168.1.1")
- private String gateway;
-}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4InfoResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4InfoResponse.java
deleted file mode 100644
index adb07f3..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4InfoResponse.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "IPv4配置信息响应")
-public class Ipv4InfoResponse {
- @Schema(description = "IPv4地址", example = "192.168.1.100")
- private String ipv4;
- @Schema(description = "子网掩码长度", example = "24")
- private String maskLength;
- @Schema(description = "默认网关地址", example = "192.168.1.1")
- private String gateway;
- @Schema(description = "IP获取方式(如 static、dhcp)", example = "static")
- private String method;
-}
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6AddressItem.java b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6AddressItem.java
deleted file mode 100644
index e32fa7f..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6AddressItem.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "单个IPv6地址及掩码")
-public class Ipv6AddressItem {
- @Schema(description = "IPv6地址", example = "2001:db8::2")
- private String ipv6;
- @Schema(description = "子网掩码前缀长度", example = "64")
- private String maskLength;
-}
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6ConfigRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6ConfigRequest.java
deleted file mode 100644
index df9f0aa..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6ConfigRequest.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "IPv6配置请求参数")
-public class Ipv6ConfigRequest {
- @Schema(description = "网络设备名称(如 eth0、bond0)", example = "eth0")
- private String deviceName;
- @Schema(description = "IPv6地址", example = "2001:db8::1")
- private String ipv6;
- @Schema(description = "默认网关地址", example = "2001:db8::1")
- private String gateway;
- @Schema(description = "子网前缀长度(如 64)", example = "64")
- private String maskLength;
-}
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6InfoResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6InfoResponse.java
deleted file mode 100644
index c88cc32..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6InfoResponse.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.util.List;
-
-@Data
-@Schema(description = "IPv6配置信息响应")
-public class Ipv6InfoResponse {
- @Schema(description = "IPv6地址及掩码列表")
- private List ipv6List;
- @Schema(description = "默认网关地址", example = "2001:db8::1")
- private String gateway;
- @Schema(description = "IP获取方式(如 static、dhcp、auto)", example = "static")
- private String method;
-}
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkConfigureRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkConfigureRequest.java
new file mode 100644
index 0000000..914f649
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkConfigureRequest.java
@@ -0,0 +1,62 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+@Schema(description = "网卡配置请求参数")
+public class NetworkConfigureRequest {
+
+ @Schema(
+ description = "网卡名称,例如 eth0、ens33、bond0",
+ example = "eth0",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "网卡名称不能为空")
+ private String deviceName;
+
+ @Schema(
+ description = "地址类型:IPv4 或 IPv6",
+ example = "IPv4",
+ allowableValues = {"IPv4", "IPv6"},
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "地址类型不能为空")
+ private String addressType;
+
+ @Schema(
+ description = "网络地址,不包含前缀长度",
+ example = "192.168.10.100",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "网络地址不能为空")
+ private String ipAddress;
+
+ @Schema(
+ description = "前缀长度,IPv4范围0-32,IPv6范围0-128",
+ example = "24",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotNull(message = "前缀长度不能为空")
+ private Integer prefixLength;
+
+ /**
+ * 网关地址
+ */
+ @Schema(
+ description = "网关地址,可为空",
+ example = "192.168.10.1"
+ )
+ private String gateway;
+
+ @Schema(
+ description = "网卡状态:Active=激活,Inactive=未激活",
+ example = "Active",
+ allowableValues = {"Active", "Inactive"},
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "网卡状态不能为空")
+ private String status;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkDeleteRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkDeleteRequest.java
new file mode 100644
index 0000000..03d80be
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkDeleteRequest.java
@@ -0,0 +1,44 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+@Schema(description = "删除网卡IP配置请求参数")
+public class NetworkDeleteRequest {
+
+ @Schema(
+ description = "网卡名称,例如 eth0、ens33、bond0",
+ example = "eth0",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "网卡名称不能为空")
+ private String deviceName;
+
+ @Schema(
+ description = "地址类型:IPv4 或 IPv6",
+ example = "IPv4",
+ allowableValues = {"IPv4", "IPv6"},
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "地址类型不能为空")
+ private String addressType;
+
+ @Schema(
+ description = "需要删除的网络地址,不包含前缀长度",
+ example = "192.168.10.100",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "网络地址不能为空")
+ private String ipAddress;
+
+ @Schema(
+ description = "前缀长度,IPv4范围0-32,IPv6范围0-128",
+ example = "24",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotNull(message = "前缀长度不能为空")
+ private Integer prefixLength;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkInfoResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkInfoResponse.java
deleted file mode 100644
index 80c9c05..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkInfoResponse.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.util.List;
-
-@Data
-@Schema(description = "网络接口信息响应参数")
-public class NetworkInfoResponse {
-
- @Schema(description = "设备名称", example = "eth0")
- private String deviceName;
- @Schema(description = "接口类型", example = "ethernet")
- private String type;
- @Schema(description = "连接状态", example = "connected")
- private String state;
- @Schema(description = "NetworkManager中的连接名称(通常与deviceName相同)", example = "eth0")
- private String connectionName;
- @Schema(description = "IPv4 地址", example = "192.168.1.100")
- private String ipv4;
- @Schema(description = "IPv6 地址", example = "2001:db8::1")
- private List ipv6;
- @Schema(description = "MAC 地址", example = "00:11:22:33:44:55")
- private String mac;
- @Schema(description = "默认网关地址", example = "192.168.1.1")
- private String gateway;
-
-}
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkTableQueryRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkTableQueryRequest.java
new file mode 100644
index 0000000..7d2c97b
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkTableQueryRequest.java
@@ -0,0 +1,37 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "网卡配置分页查询请求参数")
+public class NetworkTableQueryRequest {
+
+ /**
+ * 网卡名条件,可选
+ */
+ @Schema(description = "网卡名称,支持按网卡名筛选", example = "eth0")
+ private String deviceName;
+
+ /**
+ * 状态条件,可选
+ */
+ @Schema(
+ description = "网卡状态:Active=激活,Inactive=未激活",
+ example = "Active",
+ allowableValues = {"Active", "Inactive"}
+ )
+ private String status;
+
+ /**
+ * 当前页码,默认 1
+ */
+ @Schema(description = "当前页码,从1开始", example = "1")
+ private Integer pageNum = 1;
+
+ /**
+ * 每页条数,默认 10
+ */
+ @Schema(description = "每页条数", example = "10")
+ private Integer pageSize = 10;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkTableResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkTableResponse.java
new file mode 100644
index 0000000..f0eea3b
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkTableResponse.java
@@ -0,0 +1,53 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "网卡配置分页查询响应数据")
+public class NetworkTableResponse {
+
+ /**
+ * 网卡名,如 eth0、eth1
+ */
+ @Schema(description = "网卡名称", example = "eth0")
+ private String deviceName;
+
+ /**
+ * 地址类型,如 IPV4、IPV6、-
+ */
+ @Schema(
+ description = "地址类型:IPV4、IPV6 或 -",
+ example = "IPV4",
+ allowableValues = {"IPV4", "IPV6", "-"}
+ )
+ private String addressType;
+
+ /**
+ * 地址,如 172.16.18.1
+ */
+ @Schema(description = "IP地址", example = "172.16.18.1")
+ private String address;
+
+ /**
+ * 前缀长度,如 24、64
+ */
+ @Schema(description = "前缀长度", example = "24")
+ private String prefixLength;
+
+ /**
+ * 网关,如 172.16.18.254
+ */
+ @Schema(description = "网关地址", example = "172.16.18.254")
+ private String gateway;
+
+ /**
+ * 状态,如 Active、Inactive
+ */
+ @Schema(
+ description = "网卡状态:Active=激活,Inactive=未激活",
+ example = "Active",
+ allowableValues = {"Active", "Inactive"}
+ )
+ private String status;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/RouteCreateRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteCreateRequest.java
new file mode 100644
index 0000000..dba92f7
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteCreateRequest.java
@@ -0,0 +1,81 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+@Schema(description = "新增路由请求参数")
+public class RouteCreateRequest {
+
+ /**
+ * HOST / NETWORK
+ */
+ @Schema(
+ description = "目标类型:HOST=主机地址,NETWORK=网段地址",
+ example = "NETWORK",
+ allowableValues = {"HOST", "NETWORK"},
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "目标类型不能为空")
+ private String targetType;
+
+ /**
+ * 目标地址,例如:
+ * HOST: 172.2.2.2
+ * NETWORK: 192.168.10.0
+ */
+ @Schema(
+ description = "目标地址。HOST时填写主机IP,NETWORK时填写网段地址",
+ example = "192.168.10.0",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "目标地址不能为空")
+ private String targetAddress;
+
+ /**
+ * 前缀长度:
+ * HOST 一般为 32
+ * NETWORK 例如 24、64
+ */
+ @Schema(
+ description = "前缀长度。IPv4主机路由一般为32,IPv6主机路由一般为128,网段路由例如IPv4为24、IPv6为64",
+ example = "24",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotNull(message = "前缀长度不能为空")
+ private Integer prefixLength;
+
+ /**
+ * 网口,例如 eth0
+ */
+ @Schema(
+ description = "出接口名称,例如 eth0、ens33、bond0",
+ example = "eth0",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "网口不能为空")
+ private String interfaceName;
+
+ /**
+ * 下一跳,可为空
+ */
+ @Schema(
+ description = "下一跳网关地址,可为空。为空时表示直连路由",
+ example = "192.168.10.1"
+ )
+ private String nextHop;
+
+ /**
+ * 地址类型:IPv4 / IPv6
+ */
+ @Schema(
+ description = "地址类型:IPv4 或 IPv6",
+ example = "IPv4",
+ allowableValues = {"IPv4", "IPv6"},
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "地址类型不能为空")
+ private String addressType;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/RouteDeleteRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteDeleteRequest.java
new file mode 100644
index 0000000..3ca9cb2
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteDeleteRequest.java
@@ -0,0 +1,62 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+@Schema(description = "删除路由请求参数")
+public class RouteDeleteRequest {
+
+ @Schema(
+ description = "目标类型:HOST=主机地址,NETWORK=网段地址",
+ example = "NETWORK",
+ allowableValues = {"HOST", "NETWORK"},
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "目标类型不能为空")
+ private String targetType;
+
+ @Schema(
+ description = "目标地址。HOST时填写主机IP,NETWORK时填写网段地址",
+ example = "192.168.10.0",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "目标地址不能为空")
+ private String targetAddress;
+
+ @Schema(
+ description = "前缀长度。IPv4主机路由一般为32,IPv6主机路由一般为128,网段路由例如IPv4为24、IPv6为64",
+ example = "24",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotNull(message = "前缀长度不能为空")
+ private Integer prefixLength;
+
+ @Schema(
+ description = "出接口名称,例如 eth0、ens33、bond0",
+ example = "eth0",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "网口不能为空")
+ private String interfaceName;
+
+ @Schema(
+ description = "地址类型:IPv4 或 IPv6",
+ example = "IPv4",
+ allowableValues = {"IPv4", "IPv6"},
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @NotBlank(message = "地址类型不能为空")
+ private String addressType;
+
+ /**
+ * 下一跳,可为空
+ */
+ @Schema(
+ description = "下一跳网关地址,可为空。为空时表示直连路由",
+ example = "192.168.10.1"
+ )
+ private String nextHop;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/RouteInfoResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteInfoResponse.java
deleted file mode 100644
index 28ca406..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/RouteInfoResponse.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "路由信息响应")
-public class RouteInfoResponse {
-
- @Schema(description = "目标网络或主机(CIDR格式或主机地址)", example = "0.0.0.0/0")
- private String destination;
-
- @Schema(description = "下一跳地址(网关)", example = "192.168.1.1")
- private String nextHop;
-
- @Schema(description = "出口网络接口名称", example = "eth0")
- private String interfaceName;
-}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/RouteTableRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteTableRequest.java
new file mode 100644
index 0000000..be69b77
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteTableRequest.java
@@ -0,0 +1,38 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "路由分页查询请求参数")
+public class RouteTableRequest {
+
+ /**
+ * 目标类型:
+ * HOST:主机地址
+ * NETWORK:网段地址
+ */
+ @Schema(
+ description = "目标类型:HOST=主机地址,NETWORK=网段地址",
+ example = "NETWORK",
+ allowableValues = {"HOST", "NETWORK"}
+ )
+ private String targetType;
+
+ /**
+ * 网卡状态:
+ * Active / Inactive
+ */
+ @Schema(
+ description = "网卡状态:Active=激活,Inactive=未激活",
+ example = "Active",
+ allowableValues = {"Active", "Inactive"}
+ )
+ private String status;
+
+ @Schema(description = "当前页码,从1开始", example = "1")
+ private Integer pageNum = 1;
+
+ @Schema(description = "每页条数", example = "10")
+ private Integer pageSize = 10;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/RouteTableResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteTableResponse.java
new file mode 100644
index 0000000..6e38915
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteTableResponse.java
@@ -0,0 +1,61 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(description = "路由分页查询响应数据")
+public class RouteTableResponse {
+
+ /**
+ * 目标类型:主机地址 / 网段地址
+ */
+ @Schema(
+ description = "目标类型:HOST=主机地址,NETWORK=网段地址",
+ example = "NETWORK",
+ allowableValues = {"HOST", "NETWORK"}
+ )
+ private String targetType;
+
+ /**
+ * 目标地址
+ */
+ @Schema(description = "目标地址", example = "192.168.10.0")
+ private String targetAddress;
+
+ /**
+ * 前缀长度
+ */
+ @Schema(description = "前缀长度", example = "24")
+ private String prefixLength;
+
+ /**
+ * 网口
+ */
+ @Schema(description = "出接口名称", example = "eth0")
+ private String interfaceName;
+
+ /**
+ * 下一跳,可为空
+ */
+ @Schema(description = "下一跳网关地址,可为空。为空时表示直连路由", example = "192.168.10.1")
+ private String nextHop;
+
+ /**
+ * 状态:Active / Inactive
+ */
+ @Schema(
+ description = "网口状态:Active=激活,Inactive=未激活",
+ example = "Active",
+ allowableValues = {"Active", "Inactive"}
+ )
+ private String status;
+
+ @Schema(
+ description = "网口状态:Active=激活,Inactive=未激活",
+ example = "Active",
+ allowableValues = {"Active", "Inactive"}
+ )
+ private String addressType;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/RouteUpdateRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteUpdateRequest.java
new file mode 100644
index 0000000..c5b4f1b
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteUpdateRequest.java
@@ -0,0 +1,27 @@
+package com.cisd.tms.modules.device.dto.network;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+@Schema(description = "修改路由请求参数")
+public class RouteUpdateRequest {
+
+ @Schema(
+ description = "旧路由信息,用于定位需要被修改的原始路由",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @Valid
+ @NotNull(message = "旧路由信息不能为空")
+ private RouteCreateRequest oldRoute;
+
+ @Schema(
+ description = "新路由信息,用于替换旧路由",
+ requiredMode = Schema.RequiredMode.REQUIRED
+ )
+ @Valid
+ @NotNull(message = "新路由信息不能为空")
+ private RouteCreateRequest newRoute;
+}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/SetDefaultRouteRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/SetDefaultRouteRequest.java
deleted file mode 100644
index 634027b..0000000
--- a/src/main/java/com/cisd/tms/modules/device/dto/network/SetDefaultRouteRequest.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.cisd.tms.modules.device.dto.network;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "设置默认路由请求参数")
-public class SetDefaultRouteRequest {
-
- @Schema(description = "网络设备名称(如 eth0、bond0)", example = "eth0")
- private String deviceName;
-
- @Schema(description = "默认网关IP地址", example = "192.168.1.1")
- private String gatewayIp;
-}
\ No newline at end of file
diff --git a/src/main/java/com/cisd/tms/modules/device/entity/PageResult.java b/src/main/java/com/cisd/tms/modules/device/entity/PageResult.java
new file mode 100644
index 0000000..596906d
--- /dev/null
+++ b/src/main/java/com/cisd/tms/modules/device/entity/PageResult.java
@@ -0,0 +1,18 @@
+package com.cisd.tms.modules.device.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class PageResult {
+
+ private long total;
+
+ private List records;
+
+ public PageResult(long total, List records) {
+ this.total = total;
+ this.records = records;
+ }
+}
diff --git a/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java b/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java
index 509da7f..86efd55 100644
--- a/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java
+++ b/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java
@@ -3,6 +3,7 @@ package com.cisd.tms.modules.device.service;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.modules.device.dto.network.*;
+import com.cisd.tms.modules.device.entity.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -26,690 +27,2637 @@ public class NetworkConfigService {
"^((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 getNetworkInfo(){
- List networkInfo = new ArrayList<>();
+ private static class OldConnectionSnapshot {
+ private final String uuid;
+ private final String autoconnect;
+ private final boolean active;
- List lines = executeCommand("nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device");
- for (String line : lines){
- String[] parts = line.split(":", -1);
- String device = parts[0];
+ public OldConnectionSnapshot(String uuid, String autoconnect, boolean active) {
+ this.uuid = uuid;
+ this.autoconnect = autoconnect;
+ this.active = active;
+ }
+ }
+
+ private static class BondConfigSnapshot {
+ private final String bondOptions;
+ private final String ipv4Method;
+ private final String ipv4Addresses;
+ private final String ipv4Gateway;
+ private final String ipv6Method;
+ private final String ipv6Addresses;
+ private final String ipv6Gateway;
+ private final List slaveList;
+ private final boolean active;
+
+ public BondConfigSnapshot(
+ String bondOptions,
+ String ipv4Method,
+ String ipv4Addresses,
+ String ipv4Gateway,
+ String ipv6Method,
+ String ipv6Addresses,
+ String ipv6Gateway,
+ List slaveList,
+ boolean active
+ ) {
+ this.bondOptions = bondOptions;
+ this.ipv4Method = ipv4Method;
+ this.ipv4Addresses = ipv4Addresses;
+ this.ipv4Gateway = ipv4Gateway;
+ this.ipv6Method = ipv6Method;
+ this.ipv6Addresses = ipv6Addresses;
+ this.ipv6Gateway = ipv6Gateway;
+ this.slaveList = slaveList;
+ this.active = active;
+ }
+ }
+
+
+ private static class RouteConfigSnapshot {
+ private final String connectionName;
+ private final String interfaceName;
+ private final boolean ipv6;
+ private final String oldRoutes;
+
+ public RouteConfigSnapshot(
+ String connectionName,
+ String interfaceName,
+ boolean ipv6,
+ String oldRoutes
+ ) {
+ this.connectionName = connectionName;
+ this.interfaceName = interfaceName;
+ this.ipv6 = ipv6;
+ this.oldRoutes = oldRoutes;
+ }
+ }
+
+
+ private static class BondSlaveSnapshot {
+ private final String uuid;
+ private final String name;
+ private final String interfaceName;
+
+ public BondSlaveSnapshot(String uuid, String name, String interfaceName) {
+ this.uuid = uuid;
+ this.name = name;
+ this.interfaceName = interfaceName;
+ }
+ }
+
+
+ public PageResult getNetworkTablePage(NetworkTableQueryRequest req) {
+ List resultList = new ArrayList<>();
+ String targetDeviceName = trim(req.getDeviceName());
+ String targetStatus = trim(req.getStatus());
+
+ int pageNum = req.getPageNum();
+ int pageSize = req.getPageSize();
+
+ List lines = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "DEVICE,TYPE,STATE,CONNECTION",
+ "device"
+ );
+
+ Set bondSlaveDeviceSet = getBondSlaveDeviceSet();
+
+ for (String line : lines) {
+ if (line == null || line.trim().isEmpty()) {
+ continue;
+ }
+
+ String[] parts = line.split("(? infoLines = executeCommand("nmcli", "-t", "-f", "IP4.ADDRESS,IP4.GATEWAY,IP6.ADDRESS,GENERAL.HWADDR", "device", "show", device);
- Listipv6List = new ArrayList<>();
- for(String info : infoLines){
- int idx = info.indexOf(':'); //IP4.ADDRESS[1]:192.168.7.65/20
- String key = info.substring(0, idx);
- String value = info.substring(idx + 1);
-
- if (key.startsWith("IP4.ADDRESS")){
- resp.setIpv4(value);
- } else if (key.startsWith("IP6.ADDRESS")) {
- ipv6List.add(value);
- } else if (key.startsWith("IP4.GATEWAY")) {
- resp.setGateway(value);
- } else if ("GENERAL.HWADDR".equals(key)) {
- resp.setMac(value);
- }
+ if (!targetDeviceName.isEmpty() && !device.equals(targetDeviceName)) {
+ continue;
}
- resp.setIpv6(ipv6List);
- networkInfo.add(resp);
- }
- return networkInfo;
- }
+ String mappedStatus = "connected".equalsIgnoreCase(state) ? "Active" : "Inactive";
- public Ipv4InfoResponse getIpv4Info(String deviceName){
- String connectionName = getConnectionNameByDeviceName(deviceName.trim());
- if (connectionName == null){
- connectionName = deviceName;
- }
+ if (bondSlaveDeviceSet.contains(device)) {
+ continue;
+ }
- List lines;
- try{
- lines = executeCommand("nmcli", "-g", "IPv4.ADDRESSES,IPv4.GATEWAY,IPv4.METHOD", "con", "show", connectionName);
- } catch (RuntimeException e){
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "读取网络配置信息失败,请检查网卡 [" + deviceName + "] 是否存在或已配置");
- }
+ if (!targetStatus.isEmpty() && !mappedStatus.equals(targetStatus)) {
+ continue;
+ }
- Ipv4InfoResponse resp = new Ipv4InfoResponse();
+ List ipv4List = new ArrayList<>();
+ List ipv6List = new ArrayList<>();
+ String ipv4Gateway = "";
+ String ipv6Gateway = "";
- // 命令输出格式
- // IP/前缀
- // 网关
- // 模式
-
- if (lines.size() > 0 && !lines.get(0).trim().isEmpty()) {
- String ipAndMaskLength = lines.get(0).trim();
- if (ipAndMaskLength.contains("/")) {
- String[] parts = ipAndMaskLength.split("/");
- resp.setIpv4(parts[0]);
- resp.setMaskLength(parts[1]);
+ String connectionName;
+ if (connection != null && !connection.trim().isEmpty() && !"--".equals(connection.trim())) {
+ connectionName = connection.trim();
} else {
- resp.setIpv4(ipAndMaskLength);
+ connectionName = getConNameByDeviceNameIncludeInactive(device);
}
- }
-
- if (lines.size() > 1 && !lines.get(1).trim().isEmpty()) {
- resp.setGateway(lines.get(1).trim());
- }
-
-
- if (lines.size() > 2 && !lines.get(2).trim().isEmpty()) {
- String method = lines.get(2).trim();
- if ("manual".equalsIgnoreCase(method)) {
- resp.setMethod("static");
- } else if ("auto".equalsIgnoreCase(method)) {
- resp.setMethod("dhcp");
- } else {
- resp.setMethod(method);
- }
- }
-
- return resp;
- }
-
-
- 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 (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 && !IPV4_PATTERN.matcher(req.getGateway().trim()).matches()) {
- throw new IllegalArgumentException("无效的网关地址格式: " + req.getGateway());
- }
-
- //判断连接是否存在,如果不存在则新建新连接
- boolean isNewConnection = false;
- String connectionName = getConnectionNameByDeviceName(deviceName);
- if (connectionName == null){
- connectionName = deviceName;
- isNewConnection = true; // 标记为需要执行 add 创建配置
- }
-
-
- // 转化掩码格式并判断掩码格式是否正确
- int maskLength = 0;
- try {
- String netmask = trim(req.getMaskLength());
- if (netmask.isEmpty()) {
- throw new IllegalArgumentException("掩码不能为空");
- }
- // 传的是数字/24
- if (!netmask.contains(".")) {
- int prefix = Integer.parseInt(netmask);
- if (prefix < 0 || prefix > 32) throw new IllegalArgumentException("非法的掩码前缀: " + prefix);
- maskLength = prefix;
- }
- //传的是255.255.255.0
-// int maskInt = ipToInt(netmask);
-// for (int i = 31; i >= 0; i --) {
-// if ((maskInt & (1 << i)) != 0) {
-// maskLength ++;
-// } else {
-// break;
-// }
-// }
- } catch (Exception e) {
- throw new IllegalArgumentException("无效的子网掩码格式: " + req.getMaskLength());
- }
-
- //判断ip和网关是否在同一个子网里面
- int ip1Int = ipToInt(req.getIpv4().trim());
- if (hasGateway){
- int ip2Int = ipToInt(req.getGateway().trim());
- int maskInt = (maskLength == 0) ? 0 : (0xFFFFFFFF << (32 - maskLength));
- if (!((ip1Int & maskInt) == (ip2Int & maskInt))){
- throw new IllegalArgumentException(
- String.format("网关 (%s) 不在 IP (%s/%d) 所在的子网内",
- req.getGateway(), req.getIpv4(), maskLength)
+ if ("connected".equalsIgnoreCase(state)) {
+ List infoLines = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "IP4.ADDRESS,IP6.ADDRESS",
+ "device",
+ "show",
+ device
);
- }
- }
+ for (String info : infoLines) {
+ if (info == null || info.trim().isEmpty()) {
+ continue;
+ }
- String cidr = req.getIpv4() + "/" + maskLength;
- try {
- if (isNewConnection) {
- List 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.never-default", "yes");
- } else {
- executeCommand("nmcli", "con", "mod", connectionName,
- "ipv4.method", "manual",
- "ipv4.addresses", cidr,
- "ipv4.gateway", "");
+ int idx = info.indexOf(':');
+ if (idx == -1) {
+ continue;
+ }
+
+ String key = info.substring(0, idx);
+ String value = info.substring(idx + 1).replace("\\:", ":");
+
+ if (key.startsWith("IP4.ADDRESS")) {
+ ipv4List.add(value);
+ } else if (key.startsWith("IP6.ADDRESS")) {
+ ipv6List.add(value);
+ }
}
}
- executeCommand("nmcli", "con", "up", connectionName);
- } catch (RuntimeException e) {
+ if (connectionName != null && !connectionName.trim().isEmpty()) {
+ try {
+ List conLines = executeCommand(
+ "nmcli",
+ "-g",
+ "ipv4.addresses,ipv4.gateway,ipv6.addresses,ipv6.gateway",
+ "con",
+ "show",
+ connectionName
+ );
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改网络配置失败: " + e.getMessage());
- }
- }
+ if (!"connected".equalsIgnoreCase(state)) {
+ if (conLines.size() > 0 && !conLines.get(0).trim().isEmpty()) {
+ ipv4List.addAll(splitAddressList(conLines.get(0)));
+ }
+ if (conLines.size() > 2 && !conLines.get(2).trim().isEmpty()) {
+ ipv6List.addAll(splitAddressList(conLines.get(2)));
+ }
+ }
- public Ipv6InfoResponse getIpv6Info(String deviceName){
- deviceName = trim(deviceName);
- String connectionName = getConnectionNameByDeviceName(deviceName);
- if (connectionName == null){
- connectionName = deviceName;
- }
+ if (conLines.size() > 1 && !conLines.get(1).trim().isEmpty()) {
+ ipv4Gateway = conLines.get(1).trim();
+ }
- List lines;
- try{
- lines = executeCommand("nmcli", "-g", "IPv6.ADDRESSES,IPv6.GATEWAY,IPv6.METHOD", "con", "show", connectionName);
- } catch (RuntimeException e){
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "读取网络配置信息失败,请检查网卡 [" + deviceName + "] 是否存在或已配置");
- }
+ if (conLines.size() > 3 && !conLines.get(3).trim().isEmpty()) {
+ ipv6Gateway = conLines.get(3).trim().replace("\\:", ":");
+ }
+ } catch (Exception e) {
+ log.warn("读取网卡 [{}] 的连接配置 [{}] 信息失败: {}", device, connectionName, e.getMessage());
+ }
+ }
- Ipv6InfoResponse resp = new Ipv6InfoResponse();
+ boolean hasIpConfig = false;
- // 命令输出格式
- // IP/前缀
- // 网关
- // 模式
- List addressList = new ArrayList<>();
- if (lines.size() > 0 && !lines.get(0).trim().isEmpty()) {
- // 按逗号分割出所有的 IPv6 地址
- String rawAddresses = lines.get(0).trim();
- String[] addressArray = rawAddresses.split(",");
-
- for (String ipAndMaskLength : addressArray) {
- ipAndMaskLength = ipAndMaskLength.trim();
- if (ipAndMaskLength.isEmpty()) {
+ for (String ipPrefix : ipv4List) {
+ if (ipPrefix == null || ipPrefix.trim().isEmpty()) {
continue;
}
- Ipv6AddressItem item = new Ipv6AddressItem();
- if (ipAndMaskLength.contains("/")) {
- String[] parts = ipAndMaskLength.split("/");
- item.setIpv6(parts[0].replace("\\:", ":"));
- item.setMaskLength(parts[1]);
- } else {
- item.setIpv6(ipAndMaskLength.replace("\\:", ":"));
+ resultList.add(createTableItem(device, "IPV4", ipPrefix, ipv4Gateway, mappedStatus));
+ hasIpConfig = true;
+ }
+
+ for (String ipPrefix : ipv6List) {
+ if (ipPrefix == null || ipPrefix.trim().isEmpty()) {
+ continue;
}
- // 将解析好的单个 IP 对象放入集合
- addressList.add(item);
+
+ resultList.add(createTableItem(device, "IPV6", ipPrefix, ipv6Gateway, mappedStatus));
+ hasIpConfig = true;
}
- }
- resp.setIpv6List(addressList);
-
- if (lines.size()> 1 && !lines.get(1).trim().isEmpty()) {
- resp.setGateway(lines.get(1).trim().replace("\\:", ":"));
- }
-
- if (lines.size() > 2 && !lines.get(2).trim().isEmpty()) {
- String method = lines.get(2).trim();
- if ("manual".equalsIgnoreCase(method)) {
- resp.setMethod("static");
- } else if ("auto".equalsIgnoreCase(method)) {
- resp.setMethod("dhcp");
- } else {
- resp.setMethod(method);
+ if (!hasIpConfig) {
+ NetworkTableResponse emptyItem = new NetworkTableResponse();
+ emptyItem.setDeviceName(device);
+// emptyItem.setAddressType("-");
+// emptyItem.setAddress("-");
+// emptyItem.setPrefixLength("-");
+// emptyItem.setGateway("-");
+ emptyItem.setStatus(mappedStatus);
+ resultList.add(emptyItem);
}
}
- return resp;
+ int total = resultList.size();
+ int fromIndex = (pageNum - 1) * pageSize;
+ int toIndex = Math.min(fromIndex + pageSize, total);
+
+ List pageList;
+ if (fromIndex >= total) {
+ pageList = new ArrayList<>();
+ } else {
+ pageList = resultList.subList(fromIndex, toIndex);
+ }
+
+ return new PageResult<>(total, pageList);
}
- public void setIpv6Config(Ipv6ConfigRequest req){
+
+ public void configureNetworkCard(NetworkConfigureRequest req) {
String deviceName = trim(req.getDeviceName());
- if (req.getDeviceName() == null || req.getDeviceName().trim().isEmpty()) {
- throw new IllegalArgumentException("网卡名称不能为空");
- }
+ String addressType = trim(req.getAddressType());
+ String ipAddress = trim(req.getIpAddress());
+ String gateway = trim(req.getGateway());
+ Integer prefixLength = req.getPrefixLength();
+ String status = trim(req.getStatus());
+ boolean isIpv6 = verifyAddressType(addressType);
+ boolean hasGateway = !gateway.isEmpty();
- if (!isValidIpv6(req.getIpv6())) {
- throw new IllegalArgumentException("无效的 IPv6 地址格式: " + req.getIpv6());
- }
+ validateIpAndPrefix(ipAddress, gateway, prefixLength, isIpv6);
- //判断网关格式
- boolean hasGateway = req.getGateway() != null && !req.getGateway().trim().isEmpty();
- if (hasGateway && !isValidIpv6(req.getGateway())) {
- throw new IllegalArgumentException("无效的 IPv6 网关格式: " + req.getGateway());
- }
-
- if (req.getMaskLength() == null || req.getMaskLength().isEmpty()) {
- throw new IllegalArgumentException("掩码不能为空");
- }
-
- int maskLength = Integer.parseInt(req.getMaskLength());
- if (maskLength < 0 || maskLength > 128) {
- throw new IllegalArgumentException("无效的 IPv6 前缀长度,必须在 0 到 128 之间");
- }
-
- // 判断连接是否存在,如果不存在则需要新建
boolean isNewConnection = false;
- String connectionName = getConnectionNameByDeviceName(deviceName);
- if (connectionName == null){
+ String connectionName = getConNameByDeviceNameIncludeInactive(deviceName);
+ if (connectionName == null) {
connectionName = deviceName;
isNewConnection = true;
}
+ String ipWithPrefix = ipAddress + "/" + prefixLength;
+ String protocol = isIpv6 ? "ipv6" : "ipv4";
- String ipWithPrefix = req.getIpv6().trim() + "/" + req.getMaskLength().trim();
try {
+ List cmdArgs;
+
if (isNewConnection) {
- List 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]));
+ cmdArgs = new ArrayList<>(Arrays.asList(
+ "nmcli",
+ "con",
+ "add",
+ "type",
+ "ethernet",
+ "con-name",
+ connectionName,
+ "ifname",
+ deviceName,
+ protocol + ".method",
+ "manual",
+ protocol + ".addresses",
+ ipWithPrefix
+ ));
} else {
- if (hasGateway) {
- executeCommand("nmcli", "con", "mod", connectionName,
- "ipv6.method", "manual",
- "ipv6.addresses", ipWithPrefix,
- "ipv6.gateway", req.getGateway().trim(),
- "ipv6.never-default","yes");
+ cmdArgs = new ArrayList<>(Arrays.asList(
+ "nmcli",
+ "con",
+ "mod",
+ connectionName,
+ protocol + ".method",
+ "manual",
+ protocol + ".addresses",
+ ipWithPrefix
+ ));
+ }
+
+ if (hasGateway) {
+ cmdArgs.add(protocol + ".gateway");
+ cmdArgs.add(gateway);
+
+ cmdArgs.add(protocol + ".never-default");
+ cmdArgs.add("no");
+
+ // todo 后续改
+ cmdArgs.add(protocol + ".route-metric");
+ cmdArgs.add("200");
+ } else {
+ cmdArgs.add(protocol + ".gateway");
+ cmdArgs.add("");
+
+ cmdArgs.add(protocol + ".never-default");
+ cmdArgs.add("yes");
+ }
+
+ executeCommand(cmdArgs.toArray(new String[0]));
+
+ if ("Active".equalsIgnoreCase(status)) {
+ try {
+ executeCommand("nmcli", "dev", "reapply", deviceName);
+ } catch (RuntimeException e) {
+ log.warn("reapply 网卡 [{}] 失败,尝试重新激活连接 [{}]: {}", deviceName, connectionName, e.getMessage());
+ executeCommand("nmcli", "con", "up", connectionName);
+ }
+ } else if ("Inactive".equalsIgnoreCase(status)) {
+ try {
+ executeCommand("nmcli", "con", "down", connectionName);
+ } catch (Exception e) {
+ log.warn("设置网卡为未激活时,down网卡操作抛出异常: {}", e.getMessage());
+ }
+ } else {
+ throw new IllegalArgumentException("状态只支持 Active 或 Inactive");
+ }
+
+ } catch (RuntimeException e) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "配置网卡保存失败: " + e.getMessage()
+ );
+ }
+ }
+
+ public void deleteNetworkCardRecord(NetworkDeleteRequest req) {
+ String deviceName = trim(req.getDeviceName());
+ String addressType = trim(req.getAddressType());
+ String ipAddress = trim(req.getIpAddress());
+ Integer prefixLength = req.getPrefixLength();
+
+ boolean isIpv6 = verifyAddressType(addressType);
+
+ String protocol = isIpv6 ? "ipv6" : "ipv4";
+ String addressesKey = isIpv6 ? "IPv6.ADDRESSES" : "IPv4.ADDRESSES";
+ String methodKey = protocol + ".method";
+ String nmcliAddressKey = protocol + ".addresses";
+
+ validateIpAndPrefix(ipAddress, null, prefixLength, isIpv6);
+
+ String connectionName = getConNameByDeviceNameIncludeInactive(deviceName);
+ if (connectionName == null) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "未找到网卡 [" + deviceName + "] 对应的网络连接配置"
+ );
+ }
+
+ boolean wasActive = isConnectionActive(connectionName);
+
+ String targetAddress = ipAddress + "/" + prefixLength;
+
+ try {
+ List lines = executeCommand(
+ "nmcli",
+ "-g",
+ addressesKey,
+ "con",
+ "show",
+ connectionName
+ );
+
+ if (lines.isEmpty() || lines.get(0).trim().isEmpty()) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "当前网卡没有可删除的地址配置"
+ );
+ }
+
+ String rawAddresses = lines.get(0).trim().replace("\\:", ":");
+ List addressList = new ArrayList<>(Arrays.asList(rawAddresses.split(",")));
+
+ boolean removed = false;
+ List remainAddressList = new ArrayList<>();
+
+ for (String item : addressList) {
+ if (item == null || item.trim().isEmpty()) {
+ continue;
+ }
+
+ String current = item.trim().replace("\\:", ":");
+
+ if (current.equalsIgnoreCase(targetAddress)) {
+ removed = true;
} else {
- executeCommand("nmcli", "con", "mod", connectionName,
- "ipv6.method", "manual",
- "ipv6.addresses", ipWithPrefix,
- "ipv6.gateway", "");
+ remainAddressList.add(current);
}
}
- executeCommand("nmcli", "con", "up", connectionName);
+ if (!removed) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "未找到要删除的网络地址: " + targetAddress
+ );
+ }
+ if (remainAddressList.isEmpty()) {
+ clearProtocolConfig(connectionName, protocol);
+ } else {
+ String newAddresses = String.join(",", remainAddressList);
+
+ executeCommand(
+ "nmcli",
+ "con",
+ "mod",
+ connectionName,
+ methodKey,
+ "manual",
+ nmcliAddressKey,
+ newAddresses
+ );
+ }
+
+ if (wasActive) {
+ applyConnection(connectionName, deviceName);
+ }
+
+ } catch (BizException e) {
+ throw e;
} catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改 IPv6 网络配置失败: " + e.getMessage());
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "删除网卡记录失败: " + e.getMessage()
+ );
}
}
- public List getAllBondName(){
- List bondNames = new ArrayList<>();
- List lines = executeCommand("nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device");
+
+ private NetworkTableResponse createTableItem(String device, String type, String ipWithPrefix, String gateway, String status) {
+ NetworkTableResponse item = new NetworkTableResponse();
+ item.setDeviceName(device);
+ item.setAddressType(type);
+ item.setGateway(gateway != null ? gateway : "");
+ item.setStatus(status);
+
+
+ if (ipWithPrefix.contains("/")) {
+ String[] parts = ipWithPrefix.split("/");
+ item.setAddress(parts[0]);
+ item.setPrefixLength(parts[1]);
+ } else {
+ item.setAddress(ipWithPrefix);
+ item.setPrefixLength("");
+ }
+ return item;
+ }
+
+
+ public PageResult getBondPage(BondTableRequest req) {
+ List resultList = new ArrayList<>();
+
+ String targetBondName = trim(req.getBondName());
+ String targetStatus = trim(req.getStatus());
+
+ int pageNum = req.getPageNum();
+ int pageSize = req.getPageSize();
+
+ List lines = executeCommand("nmcli", "-t", "-f", "NAME,TYPE,DEVICE", "con", "show");
for (String line : lines) {
+ if (line == null || line.trim().isEmpty()) {
+ continue;
+ }
+
String[] parts = line.split("(?= 2 && "bond".equals(parts[1])) {
- bondNames.add(parts[0]);
+ if (parts.length < 3) {
+ continue;
+ }
+
+ String bondName = parts[0].replace("\\:", ":");
+ String type = parts[1];
+ String device = parts[2].replace("\\:", ":");
+
+ if (!"bond".equals(type)) {
+ continue;
+ }
+
+ if (!targetBondName.isEmpty() && !bondName.equals(targetBondName)) {
+ continue;
+ }
+
+ Map bondStatusMap = getBondDeviceStatusMap();
+ String status = bondStatusMap.getOrDefault(bondName, "Inactive");
+
+ if (!targetStatus.isEmpty() && !status.equals(targetStatus)) {
+ continue;
+ }
+
+ String bondMode = getBondMode(bondName);
+
+ List conLines = executeCommand(
+ "nmcli",
+ "-g",
+ "IPv4.ADDRESSES,IPv4.GATEWAY,IPv6.ADDRESSES,IPv6.GATEWAY",
+ "con",
+ "show",
+ bondName
+ );
+
+ String ipv4Addresses = getLineValue(conLines, 0);
+ String ipv4Gateway = getLineValue(conLines, 1);
+ String ipv6Addresses = getLineValue(conLines, 2).replace("\\:", ":");
+ String ipv6Gateway = getLineValue(conLines, 3).replace("\\:", ":");
+
+ boolean hasIp = false;
+
+ for (String ipWithPrefix : splitAddressList(ipv4Addresses)) {
+ resultList.add(createBondTableItem(bondName, bondMode, "IPV4", ipWithPrefix, ipv4Gateway, status));
+ hasIp = true;
+ }
+
+ for (String ipWithPrefix : splitAddressList(ipv6Addresses)) {
+ resultList.add(createBondTableItem(bondName, bondMode, "IPV6", ipWithPrefix, ipv6Gateway, status));
+ hasIp = true;
+ }
+
+ if (!hasIp) {
+ BondTableResponse empty = new BondTableResponse();
+ empty.setBondName(bondName);
+ empty.setBondMode(bondMode);
+ empty.setStatus(status);
+ resultList.add(empty);
}
}
- return bondNames;
+
+ int total = resultList.size();
+ int fromIndex = (pageNum - 1) * pageSize;
+ int toIndex = Math.min(fromIndex + pageSize, total);
+
+ List pageList;
+ if (fromIndex >= total) {
+ pageList = new ArrayList<>();
+ } else {
+ pageList = resultList.subList(fromIndex, toIndex);
+ }
+
+ return new PageResult<>(total, pageList);
}
- public void createBond(BondCreateRequest req){
+
+ private Map getBondDeviceStatusMap() {
+ Map result = new HashMap<>();
+
+ List lines = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "DEVICE,TYPE,STATE",
+ "device",
+ "status"
+ );
+
+ for (String line : lines) {
+ if (line == null || line.trim().isEmpty()) {
+ continue;
+ }
+
+ String[] parts = line.split("(? slaveList = normalizeSlaveList(req.getSlaveList(), true);
+
+ boolean isIpv6 = verifyAddressType(addressType);
+
+ if (mode != 0 && mode != 1 && mode != 2 && mode != 4) {
+ throw new IllegalArgumentException("不支持当前Bond类型");
}
+ validateIpAndPrefix(ipAddress, gateway, prefixLength, isIpv6);
+
+ List lines = executeCommand("nmcli", "-g", "NAME", "con", "show");
+ for (String line : lines) {
+ if (bondName.equals(line.trim())) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "Bond连接名称 [" + bondName + "] 已存在,请勿重复创建"
+ );
+ }
+ }
+
+ checkSlaveDevicesExist(slaveList);
+
+ String bondOptions = String.format("mode=%s,miimon=100", mapBondMode(mode));
+ String protocol = isIpv6 ? "ipv6" : "ipv4";
+ String ipWithPrefix = ipAddress + "/" + prefixLength;
+
+ List createdSlaveConnectionNames = new ArrayList<>();
+ List oldConnectionSnapshots = new ArrayList<>();
try {
- List lines = executeCommand("nmcli", "-g", "NAME", "con", "show");
- for (String line : lines) {
- if (bondName.equals(line.trim())) {
- throw new RuntimeException("网络连接名称 [" + bondName + "] 已存在,请勿重复创建");
+ executeCommand(
+ "nmcli",
+ "con",
+ "add",
+ "type",
+ "bond",
+ "con-name",
+ bondName,
+ "ifname",
+ bondName,
+ "bond.options",
+ bondOptions
+ );
+
+ List ipCmdArgs = new ArrayList<>(Arrays.asList(
+ "nmcli",
+ "con",
+ "mod",
+ bondName,
+ protocol + ".method",
+ "manual",
+ protocol + ".addresses",
+ ipWithPrefix
+ ));
+
+ if (!gateway.isEmpty()) {
+ ipCmdArgs.add(protocol + ".gateway");
+ ipCmdArgs.add(gateway);
+ ipCmdArgs.add(protocol + ".never-default");
+ ipCmdArgs.add("no");
+ ipCmdArgs.add(protocol + ".route-metric");
+ ipCmdArgs.add("200");
+ } else {
+ ipCmdArgs.add(protocol + ".gateway");
+ ipCmdArgs.add("");
+ ipCmdArgs.add(protocol + ".never-default");
+ ipCmdArgs.add("yes");
+ }
+
+ executeCommand(ipCmdArgs.toArray(new String[0]));
+
+ for (String slave : slaveList) {
+ if (slave == null || slave.trim().isEmpty()) {
+ continue;
}
+
+ String slaveName = slave.trim();
+ String slaveConnectionName = bondName + "-slave-" + slaveName;
+
+ // 避免旧连接占用物理网卡
+ oldConnectionSnapshots.addAll(downOldConnectionByDevice(slaveName));
+
+ executeCommand(
+ "nmcli",
+ "con",
+ "add",
+ "type",
+ "bond-slave",
+ "con-name",
+ slaveConnectionName,
+ "ifname",
+ slaveName,
+ "master",
+ bondName
+ );
+
+ createdSlaveConnectionNames.add(slaveConnectionName);
}
+
+ executeCommand("nmcli", "con", "up", bondName);
+
} catch (RuntimeException e) {
- if (e.getMessage().contains("已存在")) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), e.getMessage());
- }else{
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "创建 Bond 失败: " + e.getMessage());
- }
+ rollbackCreateBond(bondName, createdSlaveConnectionNames, oldConnectionSnapshots);
+
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "新增 Bond 失败,已尝试回滚配置: " + e.getMessage()
+ );
}
+ }
+ public void configBond(BondConfigRequest req) {
+ String bondName = trim(req.getBondName());
+ String addressType = trim(req.getAddressType());
+ String ipAddress = trim(req.getIpAddress());
+ String gateway = trim(req.getGateway());
+ Integer prefixLength = req.getPrefixLength();
+ Integer mode = req.getMode();
+ String status = trim(req.getStatus());
- String bondOptions = String.format("mode=%d,miimon=100", req.getMode());
+ assertBondExists(bondName);
+
+ boolean isIpv6 = verifyAddressType(addressType);
+
+ List newSlaveList = normalizeSlaveList(req.getSlaveList(), true);
+
+ validateIpAndPrefix(ipAddress, gateway, prefixLength, isIpv6);
+ checkSlaveDevicesExist(newSlaveList);
+
+ BondConfigSnapshot snapshot = snapshotBondConfig(bondName);
+
+ String protocol = isIpv6 ? "ipv6" : "ipv4";
+ String ipWithPrefix = ipAddress + "/" + prefixLength;
try {
- executeCommand("nmcli", "con", "add",
- "type", "bond",
- "con-name", bondName,
- "ifname", bondName,
- "bond.options", bondOptions);
- } catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "创建 Bond 失败: " + e.getMessage());
- }
+ String bondOptions = String.format("mode=%s,miimon=100", mapBondMode(mode));
- if (req.getIpv4Config() != null) {
- req.getIpv4Config().setDeviceName(bondName);
- try {
- setIpv4Config(req.getIpv4Config());
- } catch (Exception e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 创建成功,但 IP 配置失败: " + e.getMessage());
+ executeCommand(
+ "nmcli",
+ "con",
+ "mod",
+ bondName,
+ "bond.options",
+ bondOptions
+ );
+
+ List ipCmdArgs = new ArrayList<>(Arrays.asList(
+ "nmcli",
+ "con",
+ "mod",
+ bondName,
+ protocol + ".method",
+ "manual",
+ protocol + ".addresses",
+ ipWithPrefix
+ ));
+
+ if (!gateway.isEmpty()) {
+ ipCmdArgs.add(protocol + ".gateway");
+ ipCmdArgs.add(gateway);
+ ipCmdArgs.add(protocol + ".never-default");
+ ipCmdArgs.add("no");
+ ipCmdArgs.add(protocol + ".route-metric");
+ ipCmdArgs.add("200");
+ } else {
+ ipCmdArgs.add(protocol + ".gateway");
+ ipCmdArgs.add("");
+ ipCmdArgs.add(protocol + ".never-default");
+ ipCmdArgs.add("yes");
}
+
+ executeCommand(ipCmdArgs.toArray(new String[0]));
+
+ syncBondSlaves(bondName, newSlaveList);
+
+ if ("Active".equalsIgnoreCase(status)) {
+ executeCommand("nmcli", "con", "up", bondName);
+ } else if ("Inactive".equalsIgnoreCase(status)) {
+ try {
+ executeCommand("nmcli", "con", "down", bondName);
+ } catch (RuntimeException e) {
+ log.warn("停用 Bond [{}] 失败: {}", bondName, e.getMessage());
+ }
+ } else {
+ throw new IllegalArgumentException("状态只支持 Active 或 Inactive");
+ }
+
+ } catch (BizException e) {
+ rollbackBondConfig(bondName, snapshot);
+ throw e;
+ } catch (RuntimeException e) {
+ rollbackBondConfig(bondName, snapshot);
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "配置 Bond [" + bondName + "] 失败,已尝试回滚配置: " + e.getMessage()
+ );
}
}
public void deleteBond(String bondName) {
- if (bondName == null || bondName.trim().isEmpty()) {
- throw new IllegalArgumentException("Bond 名称不能为空");
- }
-
- bondName = bondName.trim();
-
- List uuidsToDelete = new ArrayList<>();
- boolean bondFound = false;
-
- try {
- List lines = executeCommand("nmcli", "-t", "-f", "UUID,NAME,TYPE", "con", "show");
-
- for (String line : lines) {
- if (line.trim().isEmpty()) continue;
- String[] parts = line.split("(?= 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 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 (!bondFound) {
- 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) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 及从属网卡删除失败: " + e.getMessage());
- }
-
- }
-
-
-
- public void addSlavesTOBond(BondAddSlavesRequest req) {
- String bondName = req.getBondName();
- List slaves = req.getSlaveList();
-
-
- if (bondName == null || bondName.trim().isEmpty()) {
- throw new IllegalArgumentException("Bond 名称不能为空");
- }
- if (slaves == null || slaves.isEmpty()) {
- throw new IllegalArgumentException("物理网卡列表不能为空");
- }
-
- List existingConnections;
- List existingDevices;
- try {
- 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(), "获取系统网络状态失败");
- }
-
-
- //查询bond是否存在
- boolean bondExists = false;
- for (String line : existingConnections) {
- String[] parts = line.split("(?= 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("(? devName.equals(phyIf));
-
- if (!devExists) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "当前物理网卡设备 [" + phyIf + "] 在系统中不存在");
- }
- }
-
-
- List 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("(?= 3) {
- String uuid = parts[0];
- String device = parts[1].replace("\\:", ":");
- String name = parts[2].replace("\\:", ":");
-
- if (slaveConnectionName.equals(name) || phyIf.equals(device)) {
- 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 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);
-
- newlyAddedSlaveNames.add(slaveConnectionName);
- }
-
- 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) {
- 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) {
- String bondName = trim(req.getBondName());
- List slaves = req.getSlaveList();
+ bondName = trim(bondName);
if (bondName.isEmpty()) {
- throw new IllegalArgumentException("Bond 名称不能为空");
- }
- if (slaves == null || slaves.isEmpty()) {
- throw new IllegalArgumentException("物理网卡名称不能为空");
+ throw new IllegalArgumentException("Bond名称不能为空");
}
- List lines;
+ List uuidToDeleteList = new ArrayList<>();
+ String bondUuid = null;
+
try {
- 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;
+ List lines = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "UUID,NAME,TYPE",
+ "con",
+ "show"
+ );
for (String line : lines) {
- if (line.trim().isEmpty()) {
+ if (line == null || line.trim().isEmpty()) {
continue;
}
String[] parts = line.split("(?= 4) {
- String uuid = parts[0];
- String device = parts[1].replace("\\:", ":");
- String name = parts[2].replace("\\:", ":");
+ String uuid = parts[0];
+ String name = parts[1].replace("\\:", ":");
+ String type = parts[2];
- if (slaveName.equals(name) || phyIf.equals(device)) {
- try {
- List masterOutputs = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid);
- String master = masterOutputs.isEmpty() ? "" : masterOutputs.get(0).trim();
- if (bondName.equals(master)) {
- targetUuid = uuid;
- break;
- }
- } catch (RuntimeException e) {
- log.warn("查询网卡 [{}] master 属性失败", name);
+ if (bondName.equals(name) && "bond".equals(type)) {
+ bondUuid = uuid;
+ continue;
+ }
+
+ if ("ethernet".equals(type) || "802-3-ethernet".equals(type)) {
+ try {
+ List masterLines = executeCommand(
+ "nmcli",
+ "-g",
+ "connection.master",
+ "con",
+ "show",
+ "uuid",
+ uuid
+ );
+
+ String master = masterLines.isEmpty() ? "" : masterLines.get(0).trim();
+
+ if (bondName.equals(master)) {
+ uuidToDeleteList.add(uuid);
}
+ } catch (RuntimeException e) {
+ log.warn("查询连接 [{}] 的 master 失败: {}", uuid, e.getMessage());
}
}
}
- if (targetUuid == null) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "未找到网卡 [" + phyIf + "] 对应的从属配置,移除操作已中断");
+ if (bondUuid == null) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "Bond [" + bondName + "] 不存在"
+ );
}
- try {
+ for (String uuid : uuidToDeleteList) {
try {
- executeCommand("nmcli", "con", "down", "uuid", targetUuid);
- } catch (Exception e) {
- log.warn("停用从属网卡连接 [{}] 失败,忽略并继续删除: {}", targetUuid, e.getMessage());
+ executeCommand("nmcli", "con", "down", "uuid", uuid);
+ } catch (RuntimeException e) {
+ log.warn("停用 Bond 从属连接 [{}] 失败,继续删除: {}", uuid, e.getMessage());
}
- executeCommand("nmcli", "con", "delete", "uuid", targetUuid);
- deletedCount++;
- } catch (RuntimeException e) {
- log.error("移除网卡 [{}] 失败: {}", phyIf, e.getMessage());
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("从 Bond [%s] 移除网卡 [%s] 失败: %s", bondName, phyIf, e.getMessage()));
+ try {
+ executeCommand("nmcli", "con", "delete", "uuid", uuid);
+ } catch (RuntimeException e) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "删除 Bond 从属连接失败: " + e.getMessage()
+ );
+ }
}
- }
- // 只要有成功删除的记录,就重新激活 Bond 使其生效
- if (deletedCount > 0) {
try {
- executeCommand("nmcli", "con", "up", bondName);
+ executeCommand("nmcli", "con", "down", "uuid", bondUuid);
} catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "网卡移除成功,但重新激活 Bond [" + bondName + "] 失败: " + e.getMessage());
+ log.warn("停用 Bond 主连接 [{}] 失败,继续删除: {}", bondName, e.getMessage());
+ }
+
+ executeCommand("nmcli", "con", "delete", "uuid", bondUuid);
+
+ } catch (BizException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "删除 Bond [" + bondName + "] 失败: " + e.getMessage()
+ );
+ }
+ }
+
+
+ public PageResult getRoutePage(RouteTableRequest req) {
+ List resultList = new ArrayList<>();
+
+ String targetType = trim(req.getTargetType());
+ String targetStatus = trim(req.getStatus());
+
+ int pageNum = req.getPageNum() != null && req.getPageNum() > 0 ? req.getPageNum() : 1;
+ int pageSize = req.getPageSize() != null && req.getPageSize() > 0 ? req.getPageSize() : 10;
+
+ Map deviceStatusMap = getDeviceStatusMap();
+
+ readRouteLines(resultList, false, deviceStatusMap, targetType, targetStatus);
+ readRouteLines(resultList, true, deviceStatusMap, targetType, targetStatus);
+
+ int total = resultList.size();
+ int fromIndex = (pageNum - 1) * pageSize;
+ int toIndex = Math.min(fromIndex + pageSize, total);
+
+ List pageList;
+ if (fromIndex >= total) {
+ pageList = new ArrayList<>();
+ } else {
+ pageList = resultList.subList(fromIndex, toIndex);
+ }
+
+ return new PageResult<>(total, pageList);
+ }
+
+ public void addRoute(RouteCreateRequest req) {
+ String addressType = trim(req.getAddressType());
+ String targetType = trim(req.getTargetType());
+ String targetAddress = trim(req.getTargetAddress());
+ Integer prefixLength = req.getPrefixLength();
+ String interfaceName = trim(req.getInterfaceName());
+ String nextHop = trim(req.getNextHop());
+
+ boolean isIpv6 = verifyAddressType(addressType);
+
+ validateRouteRequest(targetType, targetAddress, prefixLength, interfaceName, nextHop, isIpv6);
+
+ String connectionName = getConNameByDeviceNameIncludeInactive(interfaceName);
+ if (connectionName == null) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "未找到网口 [" + interfaceName + "] 对应的网络连接配置"
+ );
+ }
+
+ String targetCidr = targetAddress + "/" + prefixLength;
+ String routeValue = nextHop.isEmpty() ? targetCidr : targetCidr + " " + nextHop;
+
+ String routeKey = isIpv6 ? "+ipv6.routes" : "+ipv4.routes";
+
+ // 修改前保存旧路由,失败时回滚
+ String oldRoutes = getConnectionRoutes(connectionName, isIpv6);
+
+ try {
+ executeCommand(
+ "nmcli",
+ "con",
+ "mod",
+ connectionName,
+ routeKey,
+ routeValue
+ );
+
+ applyConnection(connectionName, interfaceName);
+
+ } catch (RuntimeException e) {
+ rollbackRoutes(connectionName, oldRoutes, interfaceName, isIpv6);
+
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "新增路由失败,已回滚: " + e.getMessage()
+ );
+ }
+ }
+
+ public void updateRoute(RouteUpdateRequest req) {
+ RouteCreateRequest oldRoute = req.getOldRoute();
+ RouteCreateRequest newRoute = req.getNewRoute();
+
+ if (oldRoute == null) {
+ throw new IllegalArgumentException("旧路由信息不能为空");
+ }
+ if (newRoute == null) {
+ throw new IllegalArgumentException("新路由信息不能为空");
+ }
+
+ String oldAddressType = trim(oldRoute.getAddressType());
+ String oldTargetType = trim(oldRoute.getTargetType());
+ String oldTargetAddress = trim(oldRoute.getTargetAddress());
+ Integer oldPrefixLength = oldRoute.getPrefixLength();
+ String oldInterfaceName = trim(oldRoute.getInterfaceName());
+ String oldNextHop = trim(oldRoute.getNextHop());
+
+ String newAddressType = trim(newRoute.getAddressType());
+ String newTargetType = trim(newRoute.getTargetType());
+ String newTargetAddress = trim(newRoute.getTargetAddress());
+ Integer newPrefixLength = newRoute.getPrefixLength();
+ String newInterfaceName = trim(newRoute.getInterfaceName());
+ String newNextHop = trim(newRoute.getNextHop());
+
+ boolean oldIsIpv6 = verifyAddressType(oldAddressType);
+ boolean newIsIpv6 = verifyAddressType(newAddressType);
+
+ validateRouteRequest(
+ oldTargetType,
+ oldTargetAddress,
+ oldPrefixLength,
+ oldInterfaceName,
+ oldNextHop,
+ oldIsIpv6
+ );
+
+ validateRouteRequest(
+ newTargetType,
+ newTargetAddress,
+ newPrefixLength,
+ newInterfaceName,
+ newNextHop,
+ newIsIpv6
+ );
+
+ String oldConnectionName = getConNameByDeviceNameIncludeInactive(oldInterfaceName);
+ if (oldConnectionName == null) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "未找到旧路由网口 [" + oldInterfaceName + "] 对应的网络连接配置"
+ );
+ }
+
+ String newConnectionName = getConNameByDeviceNameIncludeInactive(newInterfaceName);
+ if (newConnectionName == null) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "未找到新路由网口 [" + newInterfaceName + "] 对应的网络连接配置"
+ );
+ }
+
+ String oldRouteValue = buildRouteValue(oldTargetAddress, oldPrefixLength, oldNextHop);
+ String newRouteValue = buildRouteValue(newTargetAddress, newPrefixLength, newNextHop);
+
+ String oldRouteKey = oldIsIpv6 ? "ipv6.routes" : "ipv4.routes";
+ String newRouteKey = newIsIpv6 ? "ipv6.routes" : "ipv4.routes";
+
+ List snapshots = new ArrayList<>();
+
+ addRouteSnapshot(
+ snapshots,
+ oldConnectionName,
+ oldInterfaceName,
+ oldIsIpv6
+ );
+
+ addRouteSnapshot(
+ snapshots,
+ newConnectionName,
+ newInterfaceName,
+ newIsIpv6
+ );
+
+ try {
+ executeCommand(
+ "nmcli",
+ "con",
+ "mod",
+ oldConnectionName,
+ "-" + oldRouteKey,
+ oldRouteValue
+ );
+
+ executeCommand(
+ "nmcli",
+ "con",
+ "mod",
+ newConnectionName,
+ "+" + newRouteKey,
+ newRouteValue
+ );
+
+ applyRouteSnapshots(snapshots);
+
+ } catch (RuntimeException e) {
+ rollbackRouteSnapshots(snapshots);
+
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "修改路由失败,已尝试回滚: " + e.getMessage()
+ );
+ }
+ }
+
+
+ public void deleteRoute(RouteDeleteRequest req) {
+ String addressType = trim(req.getAddressType());
+ String targetType = trim(req.getTargetType());
+ String targetAddress = trim(req.getTargetAddress());
+ Integer prefixLength = req.getPrefixLength();
+ String interfaceName = trim(req.getInterfaceName());
+ String nextHop = trim(req.getNextHop());
+
+ boolean isIpv6 = verifyAddressType(addressType);
+
+ validateRouteRequest(
+ targetType,
+ targetAddress,
+ prefixLength,
+ interfaceName,
+ nextHop,
+ isIpv6
+ );
+
+ String connectionName = getConNameByDeviceNameIncludeInactive(interfaceName);
+ if (connectionName == null) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "未找到网口 [" + interfaceName + "] 对应的网络连接配置"
+ );
+ }
+
+ String routeValue = buildRouteValue(targetAddress, prefixLength, nextHop);
+ String routeKey = isIpv6 ? "-ipv6.routes" : "-ipv4.routes";
+
+ // 删除前保存旧路由配置,失败时回滚
+ String oldRoutes = getConnectionRoutes(connectionName, isIpv6);
+
+ try {
+ executeCommand(
+ "nmcli",
+ "con",
+ "mod",
+ connectionName,
+ routeKey,
+ routeValue
+ );
+
+ applyConnection(connectionName, interfaceName);
+
+ } catch (RuntimeException e) {
+ rollbackRoutes(connectionName, oldRoutes, interfaceName, isIpv6);
+
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "删除路由失败,已尝试回滚,请检查该路由是否存在: " + e.getMessage()
+ );
+ }
+ }
+
+
+ private Set getBondSlaveDeviceSet() {
+ Set result = new HashSet<>();
+
+ List lines = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "NAME,TYPE",
+ "con",
+ "show"
+ );
+
+ for (String line : lines) {
+ if (line == null || line.trim().isEmpty()) {
+ continue;
+ }
+
+ String[] parts = line.split("(? resultList,
+ boolean isIpv6,
+ Map deviceStatusMap,
+ String targetType,
+ String targetStatus
+ ) {
+ List lines;
+
+ try {
+ if (isIpv6) {
+ lines = executeCommand("ip", "-6", "route", "show");
+ } else {
+ lines = executeCommand("ip", "route", "show");
+ }
+ } catch (RuntimeException e) {
+ String type = isIpv6 ? "IPv6" : "IPv4";
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "读取系统" + type + "路由表失败: " + e.getMessage()
+ );
+ }
+
+ for (String line : lines) {
+ if (line == null || line.trim().isEmpty()) {
+ continue;
+ }
+
+ RouteTableResponse item = parseRouteLine(line.trim(), deviceStatusMap, isIpv6);
+ if (item == null) {
+ continue;
+ }
+
+ if (!targetType.isEmpty() && !targetType.equals(item.getTargetType())) {
+ continue;
+ }
+
+ if (!targetStatus.isEmpty() && !targetStatus.equals(item.getStatus())) {
+ continue;
+ }
+
+ resultList.add(item);
+ }
+ }
+
+
+ private void applyRouteSnapshots(List snapshots) {
+ List appliedConnectionList = new ArrayList<>();
+
+ for (RouteConfigSnapshot snapshot : snapshots) {
+ String key = snapshot.connectionName + "|" + snapshot.interfaceName;
+
+ if (appliedConnectionList.contains(key)) {
+ continue;
+ }
+
+ applyConnection(snapshot.connectionName, snapshot.interfaceName);
+ appliedConnectionList.add(key);
+ }
+ }
+
+ private void rollbackRouteSnapshots(List snapshots) {
+ if (snapshots == null || snapshots.isEmpty()) {
+ return;
+ }
+
+ for (RouteConfigSnapshot snapshot : snapshots) {
+ String routeKey = snapshot.ipv6 ? "ipv6.routes" : "ipv4.routes";
+
+ try {
+ executeCommand(
+ "nmcli",
+ "con",
+ "mod",
+ snapshot.connectionName,
+ routeKey,
+ snapshot.oldRoutes == null ? "" : snapshot.oldRoutes
+ );
+ } catch (RuntimeException e) {
+ log.warn(
+ "回滚连接 [{}] 的 [{}] 失败: {}",
+ snapshot.connectionName,
+ routeKey,
+ e.getMessage()
+ );
+ }
+ }
+
+ for (RouteConfigSnapshot snapshot : snapshots) {
+ try {
+ applyConnection(snapshot.connectionName, snapshot.interfaceName);
+ } catch (RuntimeException e) {
+ log.warn(
+ "回滚后应用连接 [{}] 失败: {}",
+ snapshot.connectionName,
+ e.getMessage()
+ );
+ }
+ }
+ }
+
+ private String buildRouteValue(String targetAddress, Integer prefixLength, String nextHop) {
+ String targetCidr = trim(targetAddress) + "/" + prefixLength;
+ nextHop = trim(nextHop);
+
+ return nextHop.isEmpty() ? targetCidr : targetCidr + " " + nextHop;
+ }
+
+ private void addRouteSnapshot(
+ List snapshots,
+ String connectionName,
+ String interfaceName,
+ boolean isIpv6
+ ) {
+ for (RouteConfigSnapshot snapshot : snapshots) {
+ if (snapshot.connectionName.equals(connectionName) && snapshot.ipv6 == isIpv6) {
+ return;
+ }
+ }
+
+ String oldRoutes = getConnectionRoutes(connectionName, isIpv6);
+ snapshots.add(new RouteConfigSnapshot(connectionName, interfaceName, isIpv6, oldRoutes));
+ }
+
+
+
+
+ private void rollbackRoutes(String connectionName, String oldRoutes, String interfaceName, boolean isIpv6) {
+ String routeKey = isIpv6 ? "ipv6.routes" : "ipv4.routes";
+
+ try {
+ executeCommand(
+ "nmcli",
+ "con",
+ "mod",
+ connectionName,
+ routeKey,
+ oldRoutes == null ? "" : oldRoutes
+ );
+
+ try {
+ applyConnection(connectionName, interfaceName);
+ } catch (RuntimeException e) {
+ log.warn("回滚 [{}] 后应用连接 [{}] 失败: {}", routeKey, connectionName, e.getMessage());
+ }
+
+ } catch (RuntimeException e) {
+ log.warn("回滚连接 [{}] 的 [{}] 失败: {}", connectionName, routeKey, e.getMessage());
+ }
+ }
+
+
+ private String getConnectionRoutes(String connectionName, boolean isIpv6) {
+ String routeKey = isIpv6 ? "ipv6.routes" : "ipv4.routes";
+
+ try {
+ List lines = executeCommand(
+ "nmcli",
+ "-g",
+ routeKey,
+ "con",
+ "show",
+ connectionName
+ );
+
+ if (lines.isEmpty() || lines.get(0) == null) {
+ return "";
+ }
+
+ return lines.get(0).trim().replace("\\:", ":");
+
+ } catch (RuntimeException e) {
+ log.warn("读取连接 [{}] 的 [{}] 配置失败: {}", connectionName, routeKey, e.getMessage());
+ return "";
+ }
+ }
+
+ private void rollbackBondConfig(String bondName, BondConfigSnapshot snapshot) {
+ if (snapshot == null) {
+ return;
+ }
+
+ try {
+ if (snapshot.bondOptions != null && !snapshot.bondOptions.trim().isEmpty()) {
+ executeCommand(
+ "nmcli",
+ "con",
+ "mod",
+ bondName,
+ "bond.options",
+ snapshot.bondOptions
+ );
+ }
+ } catch (Exception e) {
+ log.warn("回滚 Bond [{}] 模式失败: {}", bondName, e.getMessage());
+ }
+
+ try {
+ List ipv4Args = new ArrayList<>(Arrays.asList(
+ "nmcli",
+ "con",
+ "mod",
+ bondName,
+ "ipv4.method",
+ snapshot.ipv4Method == null || snapshot.ipv4Method.trim().isEmpty()
+ ? "disabled"
+ : snapshot.ipv4Method,
+ "ipv4.addresses",
+ snapshot.ipv4Addresses == null ? "" : snapshot.ipv4Addresses,
+ "ipv4.gateway",
+ snapshot.ipv4Gateway == null ? "" : snapshot.ipv4Gateway
+ ));
+
+ executeCommand(ipv4Args.toArray(new String[0]));
+ } catch (Exception e) {
+ log.warn("回滚 Bond [{}] IPv4 配置失败: {}", bondName, e.getMessage());
+ }
+
+ try {
+ List ipv6Args = new ArrayList<>(Arrays.asList(
+ "nmcli",
+ "con",
+ "mod",
+ bondName,
+ "ipv6.method",
+ snapshot.ipv6Method == null || snapshot.ipv6Method.trim().isEmpty()
+ ? "disabled"
+ : snapshot.ipv6Method,
+ "ipv6.addresses",
+ snapshot.ipv6Addresses == null ? "" : snapshot.ipv6Addresses,
+ "ipv6.gateway",
+ snapshot.ipv6Gateway == null ? "" : snapshot.ipv6Gateway
+ ));
+
+ executeCommand(ipv6Args.toArray(new String[0]));
+ } catch (Exception e) {
+ log.warn("回滚 Bond [{}] IPv6 配置失败: {}", bondName, e.getMessage());
+ }
+
+ try {
+ syncBondSlaves(bondName, snapshot.slaveList);
+ } catch (Exception e) {
+ log.warn("回滚 Bond [{}] 从属网卡失败: {}", bondName, e.getMessage());
+ }
+
+ try {
+ if (snapshot.active) {
+ executeCommand("nmcli", "con", "up", bondName);
+ } else {
+ try {
+ executeCommand("nmcli", "con", "down", bondName);
+ } catch (Exception e) {
+ log.warn("回滚停用 Bond [{}] 失败: {}", bondName, e.getMessage());
+ }
+ }
+ } catch (Exception e) {
+ log.warn("回滚 Bond [{}] 激活状态失败: {}", bondName, e.getMessage());
+ }
+ }
+
+
+ private BondConfigSnapshot snapshotBondConfig(String bondName) {
+ List lines = executeCommand(
+ "nmcli",
+ "-g",
+ "bond.options,IPv4.METHOD,IPv4.ADDRESSES,IPv4.GATEWAY,IPv6.METHOD,IPv6.ADDRESSES,IPv6.GATEWAY",
+ "con",
+ "show",
+ bondName
+ );
+
+ String bondOptions = getLineValue(lines, 0);
+ String ipv4Method = getLineValue(lines, 1);
+ String ipv4Addresses = getLineValue(lines, 2).replace("\\:", ":");
+ String ipv4Gateway = getLineValue(lines, 3);
+ String ipv6Method = getLineValue(lines, 4);
+ String ipv6Addresses = getLineValue(lines, 5).replace("\\:", ":");
+ String ipv6Gateway = getLineValue(lines, 6).replace("\\:", ":");
+
+ List oldSlaveList = getBondSlaves(bondName);
+ boolean active = isConnectionActive(bondName);
+
+ return new BondConfigSnapshot(
+ bondOptions,
+ ipv4Method,
+ ipv4Addresses,
+ ipv4Gateway,
+ ipv6Method,
+ ipv6Addresses,
+ ipv6Gateway,
+ oldSlaveList,
+ active
+ );
+ }
+
+
+ private boolean verifyAddressType(String addressType){
+ boolean isIpv6;
+ if ("IPv6".equalsIgnoreCase(addressType)) {
+ isIpv6 = true;
+ } else if ("IPv4".equalsIgnoreCase(addressType)) {
+ isIpv6 = false;
+ } else {
+ throw new IllegalArgumentException("地址类型只支持 IPv4 或 IPv6");
+ }
+ return isIpv6;
+ }
+
+ private List downOldConnectionByDevice(String deviceName) {
+ List snapshots = new ArrayList<>();
+
+ deviceName = trim(deviceName);
+ if (deviceName.isEmpty()) {
+ return snapshots;
+ }
+
+ List activeUuids = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "UUID",
+ "con",
+ "show",
+ "--active"
+ );
+
+ List lines = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "UUID,NAME,TYPE,DEVICE",
+ "con",
+ "show"
+ );
+
+ for (String line : lines) {
+ if (line == null || line.trim().isEmpty()) {
+ continue;
+ }
+
+ String[] parts = line.split("(? ifLines = executeCommand(
+ "nmcli",
+ "-g",
+ "connection.interface-name",
+ "con",
+ "show",
+ "uuid",
+ uuid
+ );
+
+ if (!ifLines.isEmpty() && ifLines.get(0) != null) {
+ interfaceName = ifLines.get(0).trim().replace("\\:", ":");
+ }
+ } catch (RuntimeException e) {
+ log.warn("读取连接 [{}] interface-name 失败,uuid: {}, 原因: {}", name, uuid, e.getMessage());
+ }
+
+ boolean matchDevice = deviceName.equals(activeDevice) || deviceName.equals(interfaceName);
+ if (!matchDevice) {
+ continue;
+ }
+
+ String autoconnect = "yes";
+ try {
+ List autoLines = executeCommand(
+ "nmcli",
+ "-g",
+ "connection.autoconnect",
+ "con",
+ "show",
+ "uuid",
+ uuid
+ );
+
+ if (!autoLines.isEmpty() && !autoLines.get(0).trim().isEmpty()) {
+ autoconnect = autoLines.get(0).trim();
+ }
+ } catch (RuntimeException e) {
+ log.warn("读取旧连接 [{}] 自启配置失败,uuid: {}, 原因: {}", name, uuid, e.getMessage());
+ }
+
+ boolean active = activeUuids.stream()
+ .anyMatch(activeUuid -> uuid.equals(activeUuid.trim()));
+
+ snapshots.add(new OldConnectionSnapshot(uuid, autoconnect, active));
+
+ try {
+ executeCommand(
+ "nmcli",
+ "con",
+ "modify",
+ "uuid",
+ uuid,
+ "connection.autoconnect",
+ "no"
+ );
+ } catch (RuntimeException e) {
+ log.warn("禁用旧连接 [{}] 自启失败,uuid: {}, 原因: {}", name, uuid, e.getMessage());
+ }
+
+
+ if (active) {
+ try {
+ executeCommand("nmcli", "con", "down", "uuid", uuid);
+ } catch (RuntimeException e) {
+ log.warn("停用旧连接 [{}] 失败,uuid: {}, 原因: {}", name, uuid, e.getMessage());
+ }
+ }
+ }
+
+ return snapshots;
+ }
+
+
+
+ private String mapBondMode(Integer mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("Bond类型不能为空");
+ }
+ switch (mode) {
+ case 0:
+ return "balance-rr";
+ case 1:
+ return "active-backup";
+ case 2:
+ return "balance-xor";
+ case 4:
+ return "802.3ad";
+ default:
+ throw new IllegalArgumentException("不支持当前Bond类型");
+ }
+ }
+
+ private String getLineValue(List lines, int index) {
+ if (lines == null || lines.size() <= index || lines.get(index) == null) {
+ return "";
+ }
+ return lines.get(index).trim();
+ }
+
+ private BondTableResponse createBondTableItem(
+ String bondName,
+ String bondMode,
+ String addressType,
+ String ipWithPrefix,
+ String gateway,
+ String status
+ ) {
+ BondTableResponse item = new BondTableResponse();
+ item.setBondName(bondName);
+ item.setBondMode(bondMode);
+ item.setAddressType(addressType);
+ item.setGateway(gateway == null ? "" : gateway);
+ item.setStatus(status);
+
+ String normalizedIp = ipWithPrefix == null ? "" : ipWithPrefix.trim().replace("\\:", ":");
+
+ if (normalizedIp.contains("/")) {
+ String[] parts = normalizedIp.split("/", 2);
+ item.setAddress(parts[0]);
+ item.setPrefixLength(parts[1]);
+ } else {
+ item.setAddress(normalizedIp);
+ item.setPrefixLength("");
+ }
+
+ return item;
+ }
+
+ private List splitAddressList(String rawAddresses) {
+ List result = new ArrayList<>();
+
+ if (rawAddresses == null || rawAddresses.trim().isEmpty()) {
+ return result;
+ }
+
+ String[] arr = rawAddresses.trim().replace("\\:", ":").split(",");
+ for (String item : arr) {
+ if (item != null && !item.trim().isEmpty()) {
+ result.add(item.trim());
+ }
+ }
+
+ return result;
+ }
+
+ private String getConNameByDeviceNameIncludeInactive(String deviceName) {
+ deviceName = trim(deviceName);
+ if (deviceName.isEmpty()) {
+ return null;
+ }
+
+ String activeConnectionName = getConnectionNameByDeviceName(deviceName);
+ if (activeConnectionName != null && !activeConnectionName.trim().isEmpty()) {
+ return activeConnectionName;
+ }
+
+ try {
+ List lines = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "UUID,NAME,TYPE,DEVICE",
+ "con",
+ "show"
+ );
+
+ for (String line : lines) {
+ if (line == null || line.trim().isEmpty()) {
+ continue;
+ }
+
+ String[] parts = line.split("(? interfaceLines = executeCommand(
+ "nmcli",
+ "-g",
+ "connection.interface-name",
+ "con",
+ "show",
+ "uuid",
+ uuid
+ );
+
+ if (!interfaceLines.isEmpty() && interfaceLines.get(0) != null) {
+ interfaceName = interfaceLines.get(0).trim().replace("\\:", ":");
+ }
+ } catch (RuntimeException e) {
+ log.warn(
+ "读取连接 [{}] 的 interface-name 失败,uuid: {}, type: {}, 原因: {}",
+ connectionName,
+ uuid,
+ type,
+ e.getMessage()
+ );
+ }
+
+ if (deviceName.equals(interfaceName)) {
+ return connectionName;
+ }
+
+// if ((interfaceName == null || interfaceName.isEmpty() || "--".equals(interfaceName))
+// && deviceName.equals(connectionName)) {
+// return connectionName;
+// }
+ }
+
+ } catch (RuntimeException e) {
+ log.warn("根据设备名 [{}] 查找连接配置失败: {}", deviceName, e.getMessage());
+ }
+
+ return null;
+ }
+
+
+
+
+ private void rollbackCreateBond(
+ String bondName,
+ List slaveConnectionNames,
+ List oldConnectionSnapshots
+ ) {
+
+ if (slaveConnectionNames != null) {
+ for (String slaveConnectionName : slaveConnectionNames) {
+ if (slaveConnectionName == null || slaveConnectionName.trim().isEmpty()) {
+ continue;
+ }
+
+ try {
+ executeCommand("nmcli", "con", "down", slaveConnectionName);
+ } catch (Exception e) {
+ log.warn("回滚停用 Bond 从属连接失败: {}, 原因: {}", slaveConnectionName, e.getMessage());
+ }
+
+ try {
+ executeCommand("nmcli", "con", "delete", slaveConnectionName);
+ } catch (Exception e) {
+ log.warn("回滚删除 Bond 从属连接失败: {}, 原因: {}", slaveConnectionName, e.getMessage());
+ }
+ }
+ }
+
+
+ if (bondName != null && !bondName.trim().isEmpty()) {
+ try {
+ executeCommand("nmcli", "con", "down", bondName);
+ } catch (Exception e) {
+ log.warn("回滚停用 Bond 主连接失败: {}, 原因: {}", bondName, e.getMessage());
+ }
+
+ try {
+ executeCommand("nmcli", "con", "delete", bondName);
+ } catch (Exception e) {
+ log.warn("回滚删除 Bond 主连接失败: {}, 原因: {}", bondName, e.getMessage());
+ }
+ }
+
+
+ if (oldConnectionSnapshots != null) {
+ for (OldConnectionSnapshot snapshot : oldConnectionSnapshots) {
+ if (snapshot == null || snapshot.uuid == null || snapshot.uuid.trim().isEmpty()) {
+ continue;
+ }
+
+ String uuid = snapshot.uuid.trim();
+ String autoconnect = snapshot.autoconnect == null || snapshot.autoconnect.trim().isEmpty()
+ ? "yes"
+ : snapshot.autoconnect.trim();
+
+ try {
+ executeCommand(
+ "nmcli",
+ "con",
+ "modify",
+ "uuid",
+ uuid,
+ "connection.autoconnect",
+ autoconnect
+ );
+ } catch (Exception e) {
+ log.warn("回滚恢复旧连接自启失败,uuid: {}, 原因: {}", uuid, e.getMessage());
+ }
+
+ if (snapshot.active) {
+ try {
+ executeCommand("nmcli", "con", "up", "uuid", uuid);
+ } catch (Exception e) {
+ log.warn("回滚恢复旧连接激活失败,uuid: {}, 原因: {}", uuid, e.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+
+
+
+ private void assertBondExists(String bondName) {
+ if (bondName == null || bondName.trim().isEmpty()) {
+ throw new IllegalArgumentException("Bond名称不能为空");
+ }
+
+ try {
+ List lines = executeCommand(
+ "nmcli",
+ "-g",
+ "connection.type",
+ "con",
+ "show",
+ bondName
+ );
+
+ String type = lines.isEmpty() ? "" : lines.get(0).trim();
+
+ if (!"bond".equals(type)) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "连接 [" + bondName + "] 不是 Bond 类型"
+ );
+ }
+ } catch (BizException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "Bond [" + bondName + "] 不存在"
+ );
+ }
+ }
+
+
+ private void syncBondSlaves(String bondName, List newSlaveList) {
+ List oldSlaveList = normalizeSlaveList(getBondSlaves(bondName), false);
+ List normalizedNewSlaveList = normalizeSlaveList(newSlaveList, true);
+
+ List needAddList = new ArrayList<>();
+ for (String newSlave : normalizedNewSlaveList) {
+ if (!oldSlaveList.contains(newSlave)) {
+ needAddList.add(newSlave);
+ }
+ }
+
+ List needRemoveList = new ArrayList<>();
+ for (String oldSlave : oldSlaveList) {
+ if (!normalizedNewSlaveList.contains(oldSlave)) {
+ needRemoveList.add(oldSlave);
+ }
+ }
+
+ if (!needAddList.isEmpty()) {
+ BondAddSlavesRequest addReq = new BondAddSlavesRequest();
+ addReq.setBondName(bondName);
+ addReq.setSlaveList(needAddList);
+ addSlavesTOBond(addReq);
+ }
+
+ if (!needRemoveList.isEmpty()) {
+ BondRemoveSlaveRequest removeReq = new BondRemoveSlaveRequest();
+ removeReq.setBondName(bondName);
+ removeReq.setSlaveList(needRemoveList);
+ removeSlaveFromBond(removeReq);
+ }
+ }
+
+
+ private void checkSlaveDevicesExist(List slaveList) {
+ List deviceLines = executeCommand("nmcli", "-t", "-f", "DEVICE,TYPE", "dev");
+
+ for (String slave : slaveList) {
+ String slaveName = slave.trim();
+
+ boolean exists = false;
+ boolean isEthernet = false;
+
+ for (String line : deviceLines) {
+ String[] parts = line.split("(? lines = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "NAME",
+ "con",
+ "show",
+ "--active"
+ );
+
+ for (String line : lines) {
+ if (connectionName.equals(line.replace("\\:", ":").trim())) {
+ return true;
+ }
+ }
+ } catch (RuntimeException e) {
+ log.warn("判断连接 [{}] 是否激活失败: {}", connectionName, e.getMessage());
+ }
+
+ return false;
+ }
+
+
+ private void validateIpAndPrefix(String ipAddress, String gateway, Integer prefixLength, boolean isIpv6) {
+ if (isIpv6) {
+ if (!isValidIpv6(ipAddress)) {
+ throw new IllegalArgumentException("无效的 IPv6 地址格式: " + ipAddress);
+ }
+ if (gateway != null && !gateway.trim().isEmpty() && !isValidIpv6(gateway)) {
+ throw new IllegalArgumentException("无效的 IPv6 网关格式: " + gateway);
+ }
+ if (prefixLength == null || prefixLength < 0 || prefixLength > 128) {
+ throw new IllegalArgumentException("IPv6 前缀长度必须在 0-128 之间");
+ }
+ } else {
+ if (!IPV4_PATTERN.matcher(ipAddress).matches()) {
+ throw new IllegalArgumentException("无效的 IPv4 地址格式: " + ipAddress);
+ }
+ if (gateway != null && !gateway.trim().isEmpty() && !IPV4_PATTERN.matcher(gateway).matches()) {
+ throw new IllegalArgumentException("无效的 IPv4 网关格式: " + gateway);
+ }
+ if (prefixLength == null || prefixLength < 0 || prefixLength > 32) {
+ throw new IllegalArgumentException("IPv4 前缀长度必须在 0-32 之间");
+ }
+ }
+ }
+
+
+ private RouteTableResponse parseRouteLine(
+ String line,
+ Map deviceStatusMap,
+ boolean isIpv6
+ ) {
+ String[] parts = line.split("\\s+");
+ if (parts.length == 0) {
+ return null;
+ }
+
+ String destination = parts[0];
+
+ // 默认路由不展示在这个静态路由列表里
+ if ("default".equals(destination)) {
+ return null;
+ }
+
+ // 过滤掉一些非普通静态路由
+ if ("unreachable".equals(destination)
+ || "throw".equals(destination)
+ || "prohibit".equals(destination)
+ || "blackhole".equals(destination)) {
+ return null;
+ }
+
+ String nextHop = "";
+ String interfaceName = "";
+
+ for (int i = 1; i < parts.length; i++) {
+ if ("via".equals(parts[i]) && i + 1 < parts.length) {
+ nextHop = parts[i + 1];
+ } else if ("dev".equals(parts[i]) && i + 1 < parts.length) {
+ interfaceName = parts[i + 1];
+ }
+ }
+
+ if (interfaceName.isEmpty()) {
+ return null;
+ }
+
+ String targetAddress;
+ String prefixLength;
+
+ if (destination.contains("/")) {
+ String[] destParts = destination.split("/", 2);
+ targetAddress = destParts[0];
+ prefixLength = destParts[1];
+ } else {
+ targetAddress = destination;
+ prefixLength = isIpv6 ? "128" : "32";
+ }
+
+ String hostPrefix = isIpv6 ? "128" : "32";
+ String targetType = hostPrefix.equals(prefixLength) ? "HOST" : "NETWORK";
+
+ RouteTableResponse item = new RouteTableResponse();
+
+ item.setAddressType(isIpv6 ? "IPv6" : "IPv4");
+
+ item.setTargetType(targetType);
+ item.setTargetAddress(targetAddress);
+ item.setPrefixLength(prefixLength);
+ item.setInterfaceName(interfaceName);
+ item.setNextHop(nextHop);
+ item.setStatus(deviceStatusMap.getOrDefault(interfaceName, "Inactive"));
+
+ return item;
+ }
+
+
+ private Map getDeviceStatusMap() {
+ Map statusMap = new HashMap<>();
+
+ List lines = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "DEVICE,STATE",
+ "dev",
+ "status"
+ );
+
+ for (String line : lines) {
+ if (line == null || line.trim().isEmpty()) {
+ continue;
+ }
+
+ String[] parts = line.split("(? 128) {
+ throw new IllegalArgumentException("IPv6 前缀长度必须在 0-128 之间");
+ }
+
+ if (("HOST".equalsIgnoreCase(targetType) || "主机地址".equals(targetType))
+ && prefixLength != 128) {
+ throw new IllegalArgumentException("IPv6 主机地址的前缀长度必须为 128");
+ }
+
+ } else {
+ if (!IPV4_PATTERN.matcher(targetAddress).matches()) {
+ throw new IllegalArgumentException("无效的 IPv4 目标地址格式: " + targetAddress);
+ }
+
+ if (nextHop != null && !nextHop.trim().isEmpty()
+ && !IPV4_PATTERN.matcher(nextHop.trim()).matches()) {
+ throw new IllegalArgumentException("无效的 IPv4 下一跳地址格式: " + nextHop);
+ }
+
+ if (prefixLength == null || prefixLength < 0 || prefixLength > 32) {
+ throw new IllegalArgumentException("IPv4 前缀长度必须在 0-32 之间");
+ }
+
+ if (("HOST".equalsIgnoreCase(targetType) || "主机地址".equals(targetType))
+ && prefixLength != 32) {
+ throw new IllegalArgumentException("IPv4 主机地址的前缀长度必须为 32");
+ }
+ }
+ }
+
+ private void applyConnection(String connectionName, String interfaceName) {
+ try {
+ executeCommand("nmcli", "dev", "reapply", interfaceName);
+ } catch (RuntimeException e) {
+ log.warn("reapply 网口 [{}] 失败,尝试重新激活连接 [{}]: {}", interfaceName, connectionName, e.getMessage());
+ executeCommand("nmcli", "con", "up", connectionName);
+ }
+ }
+
+
+ public void addSlavesTOBond(BondAddSlavesRequest req) {
+ String bondName = trim(req.getBondName());
+ List slaves = normalizeSlaveList(req.getSlaveList(), true);
+
+ assertBondExists(bondName);
+ checkSlaveDevicesExist(slaves);
+
+ List existingSlaveList = normalizeSlaveList(getBondSlaves(bondName), false);
+
+ List needAddList = new ArrayList<>();
+ for (String slave : slaves) {
+ if (!existingSlaveList.contains(slave)) {
+ needAddList.add(slave);
+ }
+ }
+
+ if (needAddList.isEmpty()) {
+ return;
+ }
+
+ List createdSlaveConnectionNames = new ArrayList<>();
+ List oldConnectionSnapshots = new ArrayList<>();
+
+ try {
+ for (String slaveName : needAddList) {
+ String slaveConnectionName = bondName + "-slave-" + slaveName;
+
+ checkConnectionNameNotExists(slaveConnectionName);
+
+ oldConnectionSnapshots.addAll(downOldConnectionByDevice(slaveName));
+
+ executeCommand(
+ "nmcli",
+ "con",
+ "add",
+ "type",
+ "bond-slave",
+ "con-name",
+ slaveConnectionName,
+ "ifname",
+ slaveName,
+ "master",
+ bondName
+ );
+
+ createdSlaveConnectionNames.add(slaveConnectionName);
+ }
+
+ executeCommand("nmcli", "con", "up", bondName);
+
+ } catch (RuntimeException e) {
+ rollbackAddSlavesToBond(createdSlaveConnectionNames, oldConnectionSnapshots);
+
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "添加物理网卡到 Bond 失败,已尝试回滚: " + e.getMessage()
+ );
+ }
+ }
+
+
+ public String removeSlaveFromBond(BondRemoveSlaveRequest req) {
+ String bondName = trim(req.getBondName());
+ List slaves = normalizeSlaveList(req.getSlaveList(), true);
+
+ assertBondExists(bondName);
+
+ List allSlaveSnapshots = getBondSlaveSnapshots(bondName);
+ List targetSlaveSnapshots = new ArrayList<>();
+
+ for (String slave : slaves) {
+ BondSlaveSnapshot target = null;
+
+ for (BondSlaveSnapshot snapshot : allSlaveSnapshots) {
+ if (slave.equals(snapshot.interfaceName)) {
+ target = snapshot;
+ break;
+ }
+ }
+
+ if (target == null) {
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "未找到网卡 [" + slave + "] 对应的从属配置,移除操作已中断"
+ );
+ }
+
+ targetSlaveSnapshots.add(target);
+ }
+
+ List deletedSlaveSnapshots = new ArrayList<>();
+
+ try {
+ for (BondSlaveSnapshot snapshot : targetSlaveSnapshots) {
+ try {
+ executeCommand("nmcli", "con", "down", "uuid", snapshot.uuid);
+ } catch (Exception e) {
+ log.warn("停用从属网卡连接 [{}] 失败,忽略并继续删除: {}", snapshot.name, e.getMessage());
+ }
+
+ executeCommand("nmcli", "con", "delete", "uuid", snapshot.uuid);
+ deletedSlaveSnapshots.add(snapshot);
+ }
+
+ if (!deletedSlaveSnapshots.isEmpty()) {
+ try {
+ executeCommand("nmcli", "con", "up", bondName);
+ } catch (RuntimeException e) {
+ rollbackDeletedBondSlaves(bondName, deletedSlaveSnapshots);
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "网卡移除成功,但重新激活 Bond [" + bondName + "] 失败,已尝试恢复: " + e.getMessage()
+ );
+ }
+ }
+
+ return String.format("成功从 Bond [%s] 中移除了 %d 个网卡", bondName, deletedSlaveSnapshots.size());
+
+ } catch (BizException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ rollbackDeletedBondSlaves(bondName, deletedSlaveSnapshots);
+
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "从 Bond [" + bondName + "] 移除网卡失败,已尝试恢复: " + e.getMessage()
+ );
+ }
+ }
+
+ private List getBondSlaveSnapshots(String bondName) {
+ List result = new ArrayList<>();
+
+ bondName = trim(bondName);
+ if (bondName.isEmpty()) {
+ return result;
+ }
+
+ List lines = executeCommand(
+ "nmcli",
+ "-t",
+ "-f",
+ "UUID,NAME,TYPE,DEVICE",
+ "con",
+ "show"
+ );
+
+ String bondUuid = null;
+
+ for (String line : lines) {
+ if (line == null || line.trim().isEmpty()) {
+ continue;
+ }
+
+ String[] parts = line.split("(? detailLines = executeCommand(
+ "nmcli",
+ "-g",
+ "connection.master,connection.interface-name",
+ "con",
+ "show",
+ "uuid",
+ uuid
+ );
+
+ if (detailLines.size() > 0 && detailLines.get(0) != null) {
+ master = detailLines.get(0).trim().replace("\\:", ":");
+ }
+
+ if (detailLines.size() > 1 && detailLines.get(1) != null) {
+ interfaceName = detailLines.get(1).trim().replace("\\:", ":");
+ }
+ } catch (RuntimeException e) {
+ log.warn("读取连接 [{}] 的 master/interface-name 失败,uuid: {}, 原因: {}", name, uuid, e.getMessage());
+ continue;
+ }
+
+ boolean belongToBond = bondName.equals(master)
+ || (bondUuid != null && bondUuid.equals(master));
+
+ if (!belongToBond) {
+ continue;
+ }
+
+ String slaveName = interfaceName;
+
+ if (slaveName == null || slaveName.trim().isEmpty() || "--".equals(slaveName.trim())) {
+ slaveName = activeDevice;
+ }
+
+ if (slaveName == null || slaveName.trim().isEmpty() || "--".equals(slaveName.trim())) {
+ String prefix = bondName + "-slave-";
+ if (name.startsWith(prefix)) {
+ slaveName = name.substring(prefix.length());
+ }
+ }
+
+ if (slaveName == null || slaveName.trim().isEmpty() || "--".equals(slaveName.trim())) {
+ log.warn("Bond [{}] 的从属连接 [{}] 无法识别物理网卡名", bondName, name);
+ continue;
+ }
+
+ result.add(new BondSlaveSnapshot(uuid, name, slaveName));
+ }
+
+ return result;
+ }
+
+
+ private void rollbackDeletedBondSlaves(String bondName, List deletedSlaveSnapshots) {
+ if (deletedSlaveSnapshots == null || deletedSlaveSnapshots.isEmpty()) {
+ return;
+ }
+
+ for (BondSlaveSnapshot snapshot : deletedSlaveSnapshots) {
+ if (snapshot == null || snapshot.interfaceName == null || snapshot.interfaceName.trim().isEmpty()) {
+ continue;
+ }
+
+ try {
+ executeCommand(
+ "nmcli",
+ "con",
+ "add",
+ "type",
+ "bond-slave",
+ "con-name",
+ snapshot.name,
+ "ifname",
+ snapshot.interfaceName,
+ "master",
+ bondName
+ );
+ } catch (Exception e) {
+ log.warn(
+ "恢复 Bond [{}] 从属连接 [{}] 失败,物理网卡 [{}],原因: {}",
+ bondName,
+ snapshot.name,
+ snapshot.interfaceName,
+ e.getMessage()
+ );
+ }
+ }
+
+ try {
+ executeCommand("nmcli", "con", "up", bondName);
+ } catch (Exception e) {
+ log.warn("恢复 Bond [{}] 激活失败: {}", bondName, e.getMessage());
+ }
+ }
+
+ private void rollbackAddSlavesToBond(
+ List createdSlaveConnectionNames,
+ List oldConnectionSnapshots
+ ) {
+ if (createdSlaveConnectionNames != null) {
+ for (String slaveConnectionName : createdSlaveConnectionNames) {
+ if (slaveConnectionName == null || slaveConnectionName.trim().isEmpty()) {
+ continue;
+ }
+
+ try {
+ executeCommand("nmcli", "con", "down", slaveConnectionName);
+ } catch (Exception e) {
+ log.warn("回滚停用 Bond 从属连接 [{}] 失败: {}", slaveConnectionName, e.getMessage());
+ }
+
+ try {
+ executeCommand("nmcli", "con", "delete", slaveConnectionName);
+ } catch (Exception e) {
+ log.warn("回滚删除 Bond 从属连接 [{}] 失败: {}", slaveConnectionName, e.getMessage());
+ }
+ }
+ }
+
+ rollbackOldConnections(oldConnectionSnapshots);
+ }
+
+ private void checkConnectionNameNotExists(String connectionName) {
+ try {
+ executeCommand("nmcli", "con", "show", connectionName);
+
+ throw new BizException(
+ ErrorCode.BIZ_ERROR.getCode(),
+ "网络连接名称 [" + connectionName + "] 已存在,请先清理旧配置"
+ );
+ } catch (BizException e) {
+ throw e;
+ } catch (RuntimeException e) {
+
+ }
+ }
+
+
+ private void rollbackOldConnections(List oldConnectionSnapshots) {
+ if (oldConnectionSnapshots == null) {
+ return;
+ }
+
+ for (OldConnectionSnapshot snapshot : oldConnectionSnapshots) {
+ if (snapshot == null || snapshot.uuid == null || snapshot.uuid.trim().isEmpty()) {
+ continue;
+ }
+
+ String uuid = snapshot.uuid.trim();
+ String autoconnect = snapshot.autoconnect == null || snapshot.autoconnect.trim().isEmpty()
+ ? "yes"
+ : snapshot.autoconnect.trim();
+
+ try {
+ executeCommand(
+ "nmcli",
+ "con",
+ "modify",
+ "uuid",
+ uuid,
+ "connection.autoconnect",
+ autoconnect
+ );
+ } catch (Exception e) {
+ log.warn("恢复旧连接自启失败,uuid: {}, 原因: {}", uuid, e.getMessage());
+ }
+
+ if (snapshot.active) {
+ try {
+ executeCommand("nmcli", "con", "up", "uuid", uuid);
+ } catch (Exception e) {
+ log.warn("恢复旧连接激活失败,uuid: {}, 原因: {}", uuid, e.getMessage());
+ }
+ }
+ }
+ }
+
+
+ private List normalizeSlaveList(List slaveList, boolean required) {
+ List result = new ArrayList<>();
+
+ if (slaveList == null || slaveList.isEmpty()) {
+ if (required) {
+ throw new IllegalArgumentException("请选择至少一个物理网卡");
+ }
+ return result;
+ }
+
+ for (String slave : slaveList) {
+ String slaveName = trim(slave);
+
+ if (slaveName.isEmpty()) {
+ if (required) {
+ throw new IllegalArgumentException("物理网卡名称不能为空");
+ }
+ continue;
+ }
+
+ if (!result.contains(slaveName)) {
+ result.add(slaveName);
+ }
+ }
+
+ if (required && result.isEmpty()) {
+ throw new IllegalArgumentException("请选择至少一个物理网卡");
+ }
+
+ return result;
}
public List getBondSlaves(String bondName) {
@@ -800,282 +2748,6 @@ public class NetworkConfigService {
}
- 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 模式不能为空");
- }
-
- HashMap modeMap = new HashMap<>();
- modeMap.put(0, "balance-rr");
- modeMap.put(1, "active-backup");
- modeMap.put(2, "balance-xor");
- modeMap.put(4, "802.3ad");
-
- String newMode = modeMap.get(mode);
- if (newMode == null) {
- throw new IllegalArgumentException("不支持当前Bond模式");
- }
-
- String bondOptions = String.format("mode=%s,miimon=100", newMode);
- try {
- executeCommand("nmcli", "con", "mod", bondName, "bond.options", bondOptions);
- } catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改 Bond [" + bondName + "] 模式配置失败: " + e.getMessage());
- }
-
-
- //重启激活服务
- try {
- executeCommand("nmcli", "con", "down", bondName);
- executeCommand("nmcli", "con", "up", bondName);
- } catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "重新激活 Bond [" + bondName + "] 失败" + e.getMessage());
- }
-
- }
-
-
-
- public List getRoutingTable() {
- List routeList = new ArrayList<>();
- List lines;
-
- try {
- lines = executeCommand("ip", "route", "show");
- } catch (RuntimeException e) {
- throw new RuntimeException("读取系统路由表失败", e);
- }
-
- for (String line : lines) {
- if (line.trim().isEmpty()) {
- continue;
- }
-
- String[] parts = line.trim().split("\\s+");
- if (parts.length == 0) {
- continue;
- }
-
- RouteInfoResponse route = new RouteInfoResponse();
- route.setDestination(parts[0]);
- for (int i = 1; i < parts.length; i ++ ) {
- if ("via".equals(parts[i]) && i + 1 < parts.length) {
- route.setNextHop(parts[i + 1]);
- }
- else if ("dev".equals(parts[i]) && i + 1 < parts.length) {
- route.setInterfaceName(parts[i + 1]);
- }
- }
-
- if (route.getNextHop() == null) {
- route.setNextHop("");
- }
- if (route.getInterfaceName() == null) {
- route.setInterfaceName("");
- }
-
- routeList.add(route);
- }
-
- return routeList;
- }
-
-
- public void setDefaultRoute(SetDefaultRouteRequest req) {
- String deviceName = req.getDeviceName();
- String gatewayIp = req.getGatewayIp();
-
-
- if (deviceName == null || deviceName.trim().isEmpty()) {
- throw new IllegalArgumentException("网卡名称不能为空");
- }
- if (gatewayIp == null || gatewayIp.trim().isEmpty()) {
- throw new IllegalArgumentException("网关 IP 不能为空");
- }
-
- 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();
- }
-
-
- try {
- executeCommand("nmcli", "con", "mod", connectionName,
- "ipv4.gateway", gatewayIp,
- //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()));
- }
-
-
- try {
- executeCommand("nmcli", "con", "down", connectionName);
- executeCommand("nmcli", "con", "up", connectionName);
- } catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage()));
- }
-
- }
-
-
- public void addStaticRoute(AddStaticRouteRequest req) {
- String deviceName = req.getDeviceName();
- String targetCidr = req.getTargetCidr();
- String nextHop = req.getNextHop();
-
- 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().trim());
- if (connectionName == null){
- connectionName = req.getDeviceName();
- }
-
- String routeValue = "";
- if (nextHop == null || nextHop.trim().isEmpty()) {
- routeValue = String.format("%s", targetCidr.trim());
- }
- else{
- routeValue = String.format("%s %s", targetCidr.trim(), nextHop.trim());
- }
-
-
- try {
- executeCommand("nmcli", "con", "mod", connectionName, "+ipv4.routes", routeValue);
- } catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("为网卡 [%s] 添加静态路由失败: %s", deviceName, e.getMessage()));
- }
-
- try {
- executeCommand("nmcli", "con", "down", connectionName);
- executeCommand("nmcli", "con", "up", connectionName);
- } catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage()));
- }
-
- }
-
-
- public void deleteDefaultRoute(DeleteDefaultRouteRequest req) {
- String deviceName = req.getDeviceName();
-
- if (deviceName == null || deviceName.trim().isEmpty()) {
- throw new IllegalArgumentException("网卡名称不能为空");
- }
-
- String connectionName = getConnectionNameByDeviceName(req.getDeviceName().trim());
- if (connectionName == null){
- connectionName = req.getDeviceName();
- }
-
- try {
- executeCommand("nmcli", "con", "mod", connectionName, "ipv4.gateway", "");
- } catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("清空网卡 [%s] 的网关失败: %s", deviceName, e.getMessage()));
- }
-
- try {
- executeCommand("nmcli", "con", "down", connectionName);
- executeCommand("nmcli", "con", "up", connectionName);
- } catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage()));
- }
-
- }
-
-
-
- public void deleteStaticRoute(DeleteStaticRouteRequest req) {
- String deviceName = req.getDeviceName();
- String targetCidr = req.getTargetCidr();
- String nextHop = req.getNextHop();
-
- if (deviceName == null || deviceName.trim().isEmpty()) {
- throw new IllegalArgumentException("网卡名称不能为空");
- }
- if (targetCidr == null || targetCidr.trim().isEmpty()) {
- throw new IllegalArgumentException("目标网段(CIDR)不能为空");
- }
-// if (nextHop == null || nextHop.trim().isEmpty()) {
-// 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();
- }
-
- String routeValue = "";
- if (nextHop == null || nextHop.trim().isEmpty()) {
- routeValue = String.format("%s", targetCidr.trim());
- }
- else{
- routeValue = String.format("%s %s", targetCidr.trim(), nextHop.trim());
- }
-
-
- try {
- executeCommand("nmcli", "con", "mod", connectionName, "-ipv4.routes", routeValue);
- } catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("为网卡 [%s] 删除静态路由失败,请检查该路由是否存在: %s", deviceName, e.getMessage()));
- }
-
-
- try {
- executeCommand("nmcli", "con", "down", connectionName);
- executeCommand("nmcli", "con", "up", connectionName);
- } catch (RuntimeException e) {
- throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage()));
- }
-
- }
-
-
-// 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 {
@@ -1086,27 +2758,6 @@ public class NetworkConfigService {
}
}
- private int ipToInt(String ipv4) {
- String[] parts = ipv4.split("\\.");
- if (parts.length != 4) {
- throw new IllegalArgumentException("非法的 IPv4 地址: " + ipv4);
- }
- int result = 0;
- for (int i = 0; i < 4; 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);
- }
- result |= (octet << (24 - (8 * i)));
- }
- return result;
- }
-
-
private String getConnectionNameByDeviceName(String deviceName) {
if (deviceName == null || deviceName.trim().isEmpty()) return null;
@@ -1173,62 +2824,3 @@ public class NetworkConfigService {
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 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 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);
-//}
diff --git a/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java b/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java
index 5321c44..98a6181 100644
--- a/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java
+++ b/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java
@@ -4,6 +4,10 @@ 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 lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -12,15 +16,12 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
+import java.time.ZoneId;
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
@@ -40,6 +41,10 @@ public class TimeConfigServiceImpl implements TimeConfigService {
throw new IllegalArgumentException("Invalid or empty timezone format");
}
+ if (!ZoneId.getAvailableZoneIds().contains(timezone)) {
+ throw new IllegalArgumentException( "Invalid timezone");
+ }
+
String datetime = request.getDatetime();
if (datetime == null) {
throw new IllegalArgumentException("Invalid or empty datetime format. Expected: YYYY-MM-DD HH:MM");
diff --git a/src/main/java/com/cisd/tms/modules/log/repository/impl/BackupRecordRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/log/repository/impl/BackupRecordRepositoryImpl.java
index 4bb3d7f..e35c6f2 100644
--- a/src/main/java/com/cisd/tms/modules/log/repository/impl/BackupRecordRepositoryImpl.java
+++ b/src/main/java/com/cisd/tms/modules/log/repository/impl/BackupRecordRepositoryImpl.java
@@ -2,7 +2,6 @@ package com.cisd.tms.modules.log.repository.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cisd.tms.modules.log.dto.BackupRecordPageRequest;
import com.cisd.tms.modules.log.entity.BackupRecordEntity;
@@ -33,8 +32,11 @@ public class BackupRecordRepositoryImpl implements BackupRecordRepository {
public IPage findPage(BackupRecordPageRequest req){
IPage page = new Page<>(req.getPageNum(), req.getPageSize());
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
- wrapper.eq(StringUtils.isNotBlank(req.getBackupType()),
- BackupRecordEntity::getBackupType, req.getBackupType());
+
+ if (req.getBackupType() != null){
+ wrapper.eq(BackupRecordEntity::getBackupType, req.getBackupType());
+ }
+
if (req.getOperationDate() != null) {
LocalDateTime startOfDay = req.getOperationDate().atStartOfDay();
@@ -45,7 +47,6 @@ public class BackupRecordRepositoryImpl implements BackupRecordRepository {
}
IPage entityPage = backupRecordMapper.selectPage(page, wrapper);
-
return entityPage;
}
diff --git a/src/main/java/com/cisd/tms/modules/log/repository/impl/OperationAuditLogRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/log/repository/impl/OperationAuditLogRepositoryImpl.java
index 69f7f53..d7583bc 100644
--- a/src/main/java/com/cisd/tms/modules/log/repository/impl/OperationAuditLogRepositoryImpl.java
+++ b/src/main/java/com/cisd/tms/modules/log/repository/impl/OperationAuditLogRepositoryImpl.java
@@ -41,8 +41,7 @@ public class OperationAuditLogRepositoryImpl implements OperationAuditLogReposit
.eq(req.getOperationResult() != null, OperationAuditLogEntity::getOperationResult, req.getOperationResult())
.eq(req.getAuditStatus() != null, OperationAuditLogEntity::getAuditStatus, req.getAuditStatus())
.eq(req.getAuditResult() != null, OperationAuditLogEntity::getAuditResult, req.getAuditResult())
- .eq(StringUtils.isNotBlank(req.getRemoteIp()), OperationAuditLogEntity::getRemoteIp, req.getRemoteIp())
- .eq(StringUtils.isNotBlank(req.getKeyword()), OperationAuditLogEntity::getSummary, req.getKeyword());
+ .eq(StringUtils.isNotBlank(req.getRemoteIp()), OperationAuditLogEntity::getRemoteIp, req.getRemoteIp());
if (req.getDateFrom() != null) {
wrapper.ge(OperationAuditLogEntity::getOccurredAt, req.getDateFrom());
diff --git a/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java b/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java
index 23b7763..1c45b7d 100644
--- a/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java
+++ b/src/main/java/com/cisd/tms/modules/log/service/AuditBackupService.java
@@ -84,7 +84,7 @@ public class AuditBackupService {
private void executeBackup() {
log.info("开始执行审计日志定期备份任务");
String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
- String zipFileName = "audit_backup_" + timeStr + ".zip";
+ String zipFileName = "audit_logs_backup_" + timeStr + ".zip";
String zipFilePath = ZIPFILEPATH + zipFileName;
File zipFile = new File(zipFilePath);
@@ -220,7 +220,7 @@ public class AuditBackupService {
log.info("开始执行审计日志手动备份任务");
String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
- String zipFileName = "audit_backup_" + timeStr + ".zip";
+ String zipFileName = "audit_logs_backup_" + timeStr + ".zip";
String zipFilePath = ZIPFILEPATH + zipFileName;
File zipFile = new File(zipFilePath);
diff --git a/src/test/java/com/cisd/tms/modules/log/aspect/OperationAuditAspectTest.java b/src/test/java/com/cisd/tms/modules/log/aspect/OperationAuditAspectTest.java
index c117696..15d9fd5 100644
--- a/src/test/java/com/cisd/tms/modules/log/aspect/OperationAuditAspectTest.java
+++ b/src/test/java/com/cisd/tms/modules/log/aspect/OperationAuditAspectTest.java
@@ -3,15 +3,10 @@ package com.cisd.tms.modules.log.aspect;
import com.cisd.tms.common.config.WebMvcConfig;
import com.cisd.tms.modules.device.controller.NetworkConfigController;
import com.cisd.tms.modules.device.service.NetworkConfigService;
-import com.cisd.tms.modules.log.dto.OperationAuditCommand;
import com.cisd.tms.modules.log.service.OperationAuditService;
import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
import com.cisd.tms.security.internal.InternalApiReplayInterceptor;
import com.cisd.tms.security.openapi.OpenApiSignAuthInterceptor;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
@@ -22,12 +17,6 @@ import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
-import java.util.Collections;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
@WebMvcTest(controllers = NetworkConfigController.class,
excludeAutoConfiguration = {SecurityAutoConfiguration.class},
excludeFilters = @ComponentScan.Filter(
@@ -55,44 +44,5 @@ class OperationAuditAspectTest {
@MockitoBean
private OpenApiSignAuthInterceptor openApiSignAuthInterceptor;
- @Test
- void testAuditLog_OnSuccess() throws Exception {
- Mockito.when(networkConfigService.getNetworkInfo())
- .thenReturn(Collections.emptyList());
- mockMvc.perform(get("/api/v1/device/network-config/network-info")
- .requestAttr(OperationAuditAspect.ATTR_ROLE_CODE, "SYS_ADMIN")
- .requestAttr(OperationAuditAspect.ATTR_AUTH_LEVEL, "FULL"))
- .andExpect(status().isOk());
-
- ArgumentCaptor cmdCaptor = ArgumentCaptor.forClass(OperationAuditCommand.class);
-
- Mockito.verify(auditService, Mockito.times(1))
- .record(cmdCaptor.capture(), eq(true));
-
- OperationAuditCommand actualCmd = cmdCaptor.getValue();
- Assertions.assertEquals("NETWORK", actualCmd.getModuleCode(), "模块名应从注解中获取");
- Assertions.assertEquals("get", actualCmd.getActionType(), "动作类型应从注解中获取");
- Assertions.assertEquals("获取网络信息", actualCmd.getSummary(), "摘要应从注解中获取");
- Assertions.assertEquals("SUCCESS", actualCmd.getOperationResult(), "方法执行成功,结果应为 SUCCESS");
- }
-
- @Test
- void testAuditLog_OnException() throws Exception {
- Mockito.when(networkConfigService.getNetworkInfo())
- .thenThrow(new RuntimeException("获取网卡信息超时报错啦!"));
- try {
- mockMvc.perform(get("/api/v1/device/network-config/network-info"));
- } catch (Exception e) {
- }
-
- ArgumentCaptor cmdCaptor = ArgumentCaptor.forClass(OperationAuditCommand.class);
- Mockito.verify(auditService, Mockito.times(1))
- .record(cmdCaptor.capture(), eq(true));
-
- OperationAuditCommand actualCmd = cmdCaptor.getValue();
- Assertions.assertEquals("NETWORK", actualCmd.getModuleCode());
- Assertions.assertEquals("FAILED", actualCmd.getOperationResult(), "抛出异常,结果应为 FAILED");
- Assertions.assertEquals("获取网卡信息超时报错啦!", actualCmd.getErrorMessage(), "应该截获到异常信息");
- }
}
\ No newline at end of file
diff --git a/src/test/java/com/cisd/tms/security/internal/ReplayProtectedEndpointsTest.java b/src/test/java/com/cisd/tms/security/internal/ReplayProtectedEndpointsTest.java
index c1389bd..69f4156 100644
--- a/src/test/java/com/cisd/tms/security/internal/ReplayProtectedEndpointsTest.java
+++ b/src/test/java/com/cisd/tms/security/internal/ReplayProtectedEndpointsTest.java
@@ -2,19 +2,16 @@ package com.cisd.tms.security.internal;
import com.cisd.tms.modules.auth.controller.AuthAdminController;
import com.cisd.tms.modules.auth.controller.AuthController;
-import com.cisd.tms.modules.device.controller.CryptoCardController;
-import com.cisd.tms.modules.device.controller.DeviceController;
-import com.cisd.tms.modules.device.controller.IpWhitelistController;
-import com.cisd.tms.modules.device.controller.NetworkConfigController;
-import com.cisd.tms.modules.device.controller.TimeConfigController;
+import com.cisd.tms.modules.device.controller.*;
import com.cisd.tms.modules.init.controller.InitController;
import com.cisd.tms.modules.mk.controller.LmkController;
import com.cisd.tms.modules.upgrade.controller.UpgradeController;
-import java.lang.reflect.Method;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AnnotatedElementUtils;
+import java.lang.reflect.Method;
+
class ReplayProtectedEndpointsTest {
@Test
@@ -56,36 +53,6 @@ class ReplayProtectedEndpointsTest {
Assertions.assertNull(AnnotatedElementUtils.findMergedAnnotation(IpWhitelistController.class, ReplayProtected.class));
Assertions.assertNull(AnnotatedElementUtils.findMergedAnnotation(LmkController.class, ReplayProtected.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "setIpv4Config",
- com.cisd.tms.modules.device.dto.network.Ipv4ConfigRequest.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "setIpv6Config",
- com.cisd.tms.modules.device.dto.network.Ipv6ConfigRequest.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "createBond",
- com.cisd.tms.modules.device.dto.network.BondCreateRequest.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "deleteBond", String.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "addSlavesTOBond",
- com.cisd.tms.modules.device.dto.network.BondAddSlavesRequest.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "removeSlaveFromBond",
- com.cisd.tms.modules.device.dto.network.BondRemoveSlaveRequest.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "setBondMode",
- com.cisd.tms.modules.device.dto.network.BondModifyModeRequest.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "setDefaultRoute",
- com.cisd.tms.modules.device.dto.network.SetDefaultRouteRequest.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "addStaticRoute",
- com.cisd.tms.modules.device.dto.network.AddStaticRouteRequest.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "deleteDefaultRoute",
- com.cisd.tms.modules.device.dto.network.DeleteDefaultRouteRequest.class));
- Assertions.assertNotNull(effectiveAnnotation(NetworkConfigController.class, "deleteStaticRoute",
- com.cisd.tms.modules.device.dto.network.DeleteStaticRouteRequest.class));
-
- Assertions.assertNull(effectiveAnnotation(NetworkConfigController.class, "getNetworkInfo"));
- Assertions.assertNull(effectiveAnnotation(NetworkConfigController.class, "getIpv4Info", String.class));
- Assertions.assertNull(effectiveAnnotation(NetworkConfigController.class, "getIpv6Info", String.class));
- Assertions.assertNull(effectiveAnnotation(NetworkConfigController.class, "getAllBondName"));
- Assertions.assertNull(effectiveAnnotation(NetworkConfigController.class, "getBondSlaves", String.class));
- Assertions.assertNull(effectiveAnnotation(NetworkConfigController.class, "getBondMode", String.class));
- Assertions.assertNull(effectiveAnnotation(NetworkConfigController.class, "getRoutingTable"));
-
Assertions.assertNotNull(effectiveAnnotation(IpWhitelistController.class, "addWhitelist",
com.cisd.tms.modules.device.dto.network.IpWhitelistRequest.class));
Assertions.assertNotNull(effectiveAnnotation(IpWhitelistController.class, "updateWhitelist",