Browse Source

重构 NetworkStackConfig 和 Stack_CoreIMS_Binding

1. 删除 StackCoreIMSBindings 模块
   - 删除整个 StackCoreIMSBindings 目录及其所有文件
   - 绑定关系操作已完全集成到 NetworkStackConfig 中

2. 移除 Stack_CoreIMS_Binding 的 AuditableEntity 继承
   - 改为继承 Entity,简化设计
   - 移除 CreatedAt 和 UpdatedAt 属性
   - 简化 Create 和 Update 方法

3. 修复 Swagger 冲突
   - 重命名响应类和命令类中的绑定项类名
   - 解决 schemaId 冲突问题

4. 更新相关文件
   - 更新配置、接口、实现和响应类
   - 保留必要的仓储接口和实现

5. 优化代码结构
   - 删除独立的 StackCoreIMSBindingsController
   - 简化 API 设计,提高一致性
feature/x1-web-request
root 6 days ago
parent
commit
8ef2af8476
  1. 229
      modify.md
  2. 40
      src/X1.Application/Features/NetworkStackConfigs/Commands/CreateNetworkStackConfig/CreateNetworkStackConfigCommand.cs
  3. 73
      src/X1.Application/Features/NetworkStackConfigs/Commands/CreateNetworkStackConfig/CreateNetworkStackConfigCommandHandler.cs
  4. 45
      src/X1.Application/Features/NetworkStackConfigs/Commands/CreateNetworkStackConfig/CreateNetworkStackConfigResponse.cs
  5. 2
      src/X1.Application/Features/NetworkStackConfigs/Commands/DeleteNetworkStackConfig/DeleteNetworkStackConfigCommand.cs
  6. 43
      src/X1.Application/Features/NetworkStackConfigs/Commands/DeleteNetworkStackConfig/DeleteNetworkStackConfigCommandHandler.cs
  7. 27
      src/X1.Application/Features/NetworkStackConfigs/Commands/DeleteNetworkStackConfig/DeleteNetworkStackConfigResponse.cs
  8. 38
      src/X1.Application/Features/NetworkStackConfigs/Commands/UpdateNetworkStackConfig/UpdateNetworkStackConfigCommand.cs
  9. 75
      src/X1.Application/Features/NetworkStackConfigs/Commands/UpdateNetworkStackConfig/UpdateNetworkStackConfigCommandHandler.cs
  10. 50
      src/X1.Application/Features/NetworkStackConfigs/Commands/UpdateNetworkStackConfig/UpdateNetworkStackConfigResponse.cs
  11. 29
      src/X1.Application/Features/NetworkStackConfigs/Queries/GetNetworkStackConfigById/GetNetworkStackConfigByIdQueryHandler.cs
  12. 50
      src/X1.Application/Features/NetworkStackConfigs/Queries/GetNetworkStackConfigById/GetNetworkStackConfigByIdResponse.cs
  13. 6
      src/X1.Application/Features/NetworkStackConfigs/Queries/GetNetworkStackConfigs/GetNetworkStackConfigsQuery.cs
  14. 47
      src/X1.Application/Features/NetworkStackConfigs/Queries/GetNetworkStackConfigs/GetNetworkStackConfigsQueryHandler.cs
  15. 50
      src/X1.Application/Features/NetworkStackConfigs/Queries/GetNetworkStackConfigs/GetNetworkStackConfigsResponse.cs
  16. 39
      src/X1.Application/Features/StackCoreIMSBindings/Commands/CreateStackCoreIMSBinding/CreateStackCoreIMSBindingCommand.cs
  17. 95
      src/X1.Application/Features/StackCoreIMSBindings/Commands/CreateStackCoreIMSBinding/CreateStackCoreIMSBindingCommandHandler.cs
  18. 37
      src/X1.Application/Features/StackCoreIMSBindings/Commands/CreateStackCoreIMSBinding/CreateStackCoreIMSBindingResponse.cs
  19. 17
      src/X1.Application/Features/StackCoreIMSBindings/Commands/DeleteStackCoreIMSBinding/DeleteStackCoreIMSBindingCommand.cs
  20. 64
      src/X1.Application/Features/StackCoreIMSBindings/Commands/DeleteStackCoreIMSBinding/DeleteStackCoreIMSBindingCommandHandler.cs
  21. 38
      src/X1.Application/Features/StackCoreIMSBindings/Commands/UpdateStackCoreIMSBinding/UpdateStackCoreIMSBindingCommand.cs
  22. 103
      src/X1.Application/Features/StackCoreIMSBindings/Commands/UpdateStackCoreIMSBinding/UpdateStackCoreIMSBindingCommandHandler.cs
  23. 37
      src/X1.Application/Features/StackCoreIMSBindings/Commands/UpdateStackCoreIMSBinding/UpdateStackCoreIMSBindingResponse.cs
  24. 17
      src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindingById/GetStackCoreIMSBindingByIdQuery.cs
  25. 68
      src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindingById/GetStackCoreIMSBindingByIdQueryHandler.cs
  26. 52
      src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindingById/GetStackCoreIMSBindingByIdResponse.cs
  27. 41
      src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindings/GetStackCoreIMSBindingsQuery.cs
  28. 80
      src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindings/GetStackCoreIMSBindingsQueryHandler.cs
  29. 83
      src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindings/GetStackCoreIMSBindingsResponse.cs
  30. 15
      src/X1.Domain/Entities/NetworkProfile/NetworkStackConfig.cs
  31. 25
      src/X1.Domain/Entities/NetworkProfile/Stack_CoreIMS_Binding.cs
  32. 62
      src/X1.Domain/Repositories/NetworkProfile/INetworkStackConfigRepository.cs
  33. 16
      src/X1.Domain/Repositories/NetworkProfile/IStack_CoreIMS_BindingRepository.cs
  34. 8
      src/X1.Infrastructure/Configurations/NetworkProfile/NetworkStackConfigConfiguration.cs
  35. 26
      src/X1.Infrastructure/Configurations/NetworkProfile/Stack_CoreIMS_BindingConfiguration.cs
  36. 122
      src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs
  37. 40
      src/X1.Infrastructure/Repositories/NetworkProfile/Stack_CoreIMS_BindingRepository.cs
  38. 8
      src/X1.Presentation/Controllers/NetworkStackConfigsController.cs
  39. 136
      src/X1.Presentation/Controllers/StackCoreIMSBindingsController.cs
  40. 4
      src/X1.WebAPI/Properties/launchSettings.json
  41. 6163
      src/X1.WebAPI/logs/app-20250728.log
  42. 1890
      src/X1.WebAPI/logs/app-20250729.log
  43. 23
      src/X1.WebAPI/logs/error-20250728.log
  44. 75
      src/X1.WebAPI/logs/error-20250729.log
  45. 253
      src/X1.WebUI/src/components/ui/ConfigContentViewer.tsx
  46. 2
      src/X1.WebUI/src/config/core/env.config.ts
  47. 125
      src/X1.WebUI/src/pages/core-network-configs/CoreNetworkConfigViewDialog.tsx
  48. 24
      src/X1.WebUI/src/pages/core-network-configs/CoreNetworkConfigsTable.tsx
  49. 68
      src/X1.WebUI/src/pages/core-network-configs/CoreNetworkConfigsView.tsx
  50. 125
      src/X1.WebUI/src/pages/ims-configurations/IMSConfigurationViewDialog.tsx
  51. 24
      src/X1.WebUI/src/pages/ims-configurations/IMSConfigurationsTable.tsx
  52. 68
      src/X1.WebUI/src/pages/ims-configurations/IMSConfigurationsView.tsx
  53. 125
      src/X1.WebUI/src/pages/ran-configurations/RANConfigurationViewDialog.tsx
  54. 24
      src/X1.WebUI/src/pages/ran-configurations/RANConfigurationsTable.tsx
  55. 17
      src/X1.WebUI/src/pages/ran-configurations/RANConfigurationsView.tsx

229
modify.md

@ -431,4 +431,231 @@ if (IsMessageTimeout(messageStartTime))
- **修改效果**
- ✅ 日志文件不会被 Git 跟踪
- ✅ 避免日志文件占用版本控制空间
- ✅ 保持代码仓库的整洁
- ✅ 保持代码仓库的整洁
## 2024-12-19 - NetworkStackConfig 重构
### 修改概述
重构 NetworkStackConfig 实体,移除冗余的 StackId 字段,简化实体设计。
### 修改内容
#### 1. 实体类修改
- **NetworkStackConfig.cs**
- 移除 `StackId` 属性
- 添加 `NetworkStackName` 属性
- 更新 `Create` 方法,移除 `stackId` 参数,添加 `networkStackName` 参数
- 更新 `Update` 方法,添加 `networkStackName` 参数
- 简化实体结构,直接使用 `Id` 作为主键
- **Stack_CoreIMS_Binding.cs**
- 将 `StackId` 属性改为 `NetworkStackConfigId`
- 更新 `Create` 方法参数
- 更新注释说明
#### 2. 配置类修改
- **NetworkStackConfigConfiguration.cs**
- 移除 `StackId` 相关索引配置
- 添加 `NetworkStackName` 唯一索引配置
- 更新与 `Stack_CoreIMS_Binding` 的关系配置
- 将外键从 `StackId` 改为 `NetworkStackConfigId`
- **Stack_CoreIMS_BindingConfiguration.cs**
- 更新索引名称和配置
- 将 `StackId` 相关索引改为 `NetworkStackConfigId`
- 更新复合唯一索引配置
#### 3. 仓储接口修改
- **INetworkStackConfigRepository.cs**
- 移除 `GetNetworkStackConfigByStackIdAsync` 方法
- 移除 `StackIdExistsAsync` 方法
- 添加 `GetNetworkStackConfigByNameAsync` 方法
- 添加 `NameExistsAsync` 方法
- 添加 `GetNetworkStackConfigByIdWithBindingsAsync` 方法
- 添加 `SearchNetworkStackConfigsWithBindingsAsync` 方法
- 简化接口定义
- **IStack_CoreIMS_BindingRepository.cs**
- 将 `GetBindingsByStackIdAsync` 改为 `GetBindingsByNetworkStackConfigIdAsync`
- 将 `GetBindingByStackIdAndIndexAsync` 改为 `GetBindingByNetworkStackConfigIdAndIndexAsync`
- 将 `StackIdAndIndexExistsAsync` 改为 `NetworkStackConfigIdAndIndexExistsAsync`
- 更新搜索方法参数
#### 4. 仓储实现修改
- **NetworkStackConfigRepository.cs**
- 移除所有 `StackId` 相关的方法实现
- 添加 `NetworkStackName` 相关的方法实现
- 添加包含导航属性的查询方法
- 更新排序逻辑,使用 `NetworkStackName` 替代 `StackId`
- 更新搜索逻辑,添加 `NetworkStackName` 搜索条件
- **Stack_CoreIMS_BindingRepository.cs**
- 更新所有方法中的 `StackId` 引用为 `NetworkStackConfigId`
- 更新排序逻辑
- 更新搜索和查询条件
#### 5. Application层修改
- **NetworkStackConfigs Commands**
- **CreateNetworkStackConfigCommand.cs**: 将 `StackId` 改为 `NetworkStackName`,添加 `StackCoreIMSBindingItem` 列表
- **CreateNetworkStackConfigResponse.cs**: 将 `StackId` 改为 `NetworkStackName`,添加 `StackCoreIMSBindingResponseItem` 列表
- **CreateNetworkStackConfigCommandHandler.cs**: 更新处理器逻辑,支持同时创建绑定关系
- **UpdateNetworkStackConfigCommand.cs**: 将 `StackId` 改为 `NetworkStackName`,添加 `StackCoreIMSBindingItem` 列表
- **UpdateNetworkStackConfigResponse.cs**: 将 `StackId` 改为 `NetworkStackName`,添加 `StackCoreIMSBindingResponseItem` 列表
- **UpdateNetworkStackConfigCommandHandler.cs**: 更新处理器逻辑,支持同时更新绑定关系
- **DeleteNetworkStackConfigCommand.cs**: 更新返回类型为响应对象
- **DeleteNetworkStackConfigResponse.cs**: 新增删除响应类,包含删除的绑定关系信息
- **DeleteNetworkStackConfigCommandHandler.cs**: 更新处理器逻辑,支持同时删除绑定关系
- **NetworkStackConfigs Queries**
- **GetNetworkStackConfigByIdResponse.cs**: 将 `StackId` 改为 `NetworkStackName`,添加 `StackCoreIMSBindingResponseItem` 列表
- **GetNetworkStackConfigByIdQueryHandler.cs**: 更新处理器逻辑,使用导航属性优化性能
- **GetNetworkStackConfigsResponse.cs**: 将 `StackId` 改为 `NetworkStackName`,添加 `StackCoreIMSBindingResponseItem` 列表
- **GetNetworkStackConfigsQueryHandler.cs**: 更新处理器逻辑,使用导航属性优化性能
- **StackCoreIMSBindings Commands**
- **CreateStackCoreIMSBindingCommand.cs**: 将 `StackId` 改为 `NetworkStackConfigId`
- **CreateStackCoreIMSBindingResponse.cs**: 将 `StackId` 改为 `NetworkStackConfigId`
- **CreateStackCoreIMSBindingCommandHandler.cs**: 更新处理器逻辑
- **UpdateStackCoreIMSBindingResponse.cs**: 将 `StackId` 改为 `NetworkStackConfigId`
- **UpdateStackCoreIMSBindingCommandHandler.cs**: 更新处理器逻辑
- **DeleteStackCoreIMSBindingCommandHandler.cs**: 更新日志信息
- **StackCoreIMSBindings Queries**
- **GetStackCoreIMSBindingByIdResponse.cs**: 将 `StackId` 改为 `NetworkStackConfigId`
- **GetStackCoreIMSBindingByIdQueryHandler.cs**: 更新处理器逻辑
- **GetStackCoreIMSBindingsResponse.cs**: 将 `StackId` 改为 `NetworkStackConfigId`
- **GetStackCoreIMSBindingsQueryHandler.cs**: 更新处理器逻辑
- **GetStackCoreIMSBindingsQuery.cs**: 将 `StackId` 改为 `NetworkStackConfigId`
### 新增功能
#### CreateNetworkStackConfig 命令增强
- **新增 `StackCoreIMSBindingItem` 类**: 用于表示绑定关系项,包含索引、核心网配置ID和IMS配置ID
- **新增 `StackCoreIMSBindingResponseItem` 类**: 用于响应中返回创建的绑定关系信息
- **增强处理器逻辑**:
- 先创建 NetworkStackConfig
- 再创建相关的 Stack_CoreIMS_Binding 记录
- 支持批量创建绑定关系
- 添加索引重复检查
- 事务性操作确保数据一致性
#### UpdateNetworkStackConfig 命令增强
- **新增 `StackCoreIMSBindingItem` 类**: 用于表示绑定关系项
- **新增 `StackCoreIMSBindingResponseItem` 类**: 用于响应中返回更新的绑定关系信息
- **增强处理器逻辑**:
- 支持更新网络栈配置基本信息
- 支持添加新的绑定关系
- 检查网络栈名称唯一性
- 检查绑定关系索引重复性
- 事务性操作确保数据一致性
#### DeleteNetworkStackConfig 命令增强
- **新增 `DeleteNetworkStackConfigResponse` 类**: 用于返回删除操作的详细信息
- **增强处理器逻辑**:
- 先删除相关的 Stack_CoreIMS_Binding 记录
- 再删除 NetworkStackConfig
- 返回删除的绑定关系数量
- 事务性操作确保数据一致性
#### GetNetworkStackConfig 查询增强
- **新增 `StackCoreIMSBindingResponseItem` 类**: 用于响应中返回绑定关系信息
- **增强处理器逻辑**:
- 使用导航属性优化查询性能
- 避免N+1查询问题
- 一次性获取网络栈配置及其绑定关系
- 支持单个查询和列表查询
### 性能优化
#### 查询性能优化
- **使用导航属性**: 通过 `Include()` 方法一次性加载相关数据
- **避免N+1查询**: 在列表查询中使用 `Include(nsc => nsc.StackCoreIMSBindings)`
- **新增专用方法**:
- `GetNetworkStackConfigByIdWithBindingsAsync` - 单个查询包含绑定关系
- `SearchNetworkStackConfigsWithBindingsAsync` - 列表查询包含绑定关系
- **减少数据库往返**: 从多次查询优化为单次查询
### 修改原因
1. **简化设计**:移除冗余的 `StackId` 字段,直接使用 `Id` 作为关联键
2. **减少维护成本**:不需要维护两个标识符
3. **避免数据不一致**:消除了 `Id``StackId` 可能不一致的风险
4. **提高性能**:减少索引维护开销,优化查询性能
5. **增加业务语义**:添加 `NetworkStackName` 提供更好的业务标识
6. **提升用户体验**:支持一次性操作网络栈配置及其绑定关系,减少API调用次数
7. **数据完整性**:确保删除操作级联删除相关数据
8. **查询性能**:使用导航属性避免N+1查询问题
### 影响范围
- 后端实体层 ✅
- 后端仓储层 ✅
- 后端应用层 ✅
- 数据库配置 ✅
- 前端代码需要相应更新(暂未修改)
### 注意事项
- 需要创建数据库迁移来重构表结构
- 前端代码需要更新以适配新的API接口
- 现有数据需要迁移
- Application层所有文件已修复完成
- Create、Update、Delete、Query 命令现在都支持绑定关系操作
- 查询性能已通过导航属性优化
## 2025-01-29
### 删除 StackCoreIMSBindings 模块
- **删除目录**: `CellularManagement/src/X1.Application/Features/StackCoreIMSBindings/`
- **删除文件**:
- `CreateStackCoreIMSBindingCommand.cs`
- `CreateStackCoreIMSBindingResponse.cs`
- `CreateStackCoreIMSBindingCommandHandler.cs`
- `UpdateStackCoreIMSBindingResponse.cs`
- `UpdateStackCoreIMSBindingCommandHandler.cs`
- `DeleteStackCoreIMSBindingCommandHandler.cs`
- `GetStackCoreIMSBindingByIdResponse.cs`
- `GetStackCoreIMSBindingByIdQueryHandler.cs`
- `GetStackCoreIMSBindingsResponse.cs`
- `GetStackCoreIMSBindingsQueryHandler.cs`
- `GetStackCoreIMSBindingsQuery.cs`
- **原因**: 绑定关系的操作已完全集成到 NetworkStackConfig 中,不再需要独立的模块
### 移除 Stack_CoreIMS_Binding 的 AuditableEntity 继承
- **修改文件**: `CellularManagement/src/X1.Domain/Entities/NetworkProfile/Stack_CoreIMS_Binding.cs`
- **变更**:
- 移除 `AuditableEntity` 继承,改为继承 `Entity`
- 移除 `CreatedAt``UpdatedAt` 属性
- 简化 `Create``Update` 方法,移除 `createdBy``updatedBy` 参数
- **原因**: 作为 NetworkStackConfig 的子表,审计信息可以从父表获取,简化设计
### 更新相关文件
- **更新配置**: `Stack_CoreIMS_BindingConfiguration.cs` - 移除审计字段配置
- **更新响应类**: 所有响应类中的 `CreatedAt` 字段改为使用 `DateTime.UtcNow`
- **更新接口**: `IStack_CoreIMS_BindingRepository` - 更新方法名以使用 `NetworkStackConfigId`
- **更新实现**: `Stack_CoreIMS_BindingRepository` - 实现所有接口方法
### 删除 StackCoreIMSBindingsController
- **删除文件**: `CellularManagement/src/X1.Presentation/Controllers/StackCoreIMSBindingsController.cs`
- **原因**: 绑定关系的操作已集成到 NetworkStackConfig 中,不再需要独立的控制器
### 修复 Swagger 冲突
- **问题**: 多个响应类中使用相同的 `StackCoreIMSBindingResponseItem` 类名导致 Swagger 生成时出现 schemaId 冲突
- **解决方案**: 将各个响应类中的绑定项类重命名为更具体的名称:
- `CreateNetworkStackConfigResponse.StackCoreIMSBindingResponseItem``CreateStackCoreIMSBindingResponseItem`
- `UpdateNetworkStackConfigResponse.StackCoreIMSBindingResponseItem``UpdateStackCoreIMSBindingResponseItem`
- `GetNetworkStackConfigByIdResponse.StackCoreIMSBindingResponseItem``GetNetworkStackConfigByIdBindingResponseItem`
- `GetNetworkStackConfigsResponse.StackCoreIMSBindingResponseItem``GetNetworkStackConfigsBindingResponseItem`
- **影响文件**:
- `CreateNetworkStackConfigResponse.cs`
- `CreateNetworkStackConfigCommandHandler.cs`
- `UpdateNetworkStackConfigResponse.cs`
- `UpdateNetworkStackConfigCommandHandler.cs`
- `GetNetworkStackConfigByIdResponse.cs`
- `GetNetworkStackConfigByIdQueryHandler.cs`
- `GetNetworkStackConfigsResponse.cs`
- `GetNetworkStackConfigsQueryHandler.cs`
### 修复命令类 Swagger 冲突
- **问题**: 命令类中使用相同的 `StackCoreIMSBindingItem` 类名导致 Swagger 冲突
- **解决方案**: 将命令类中的绑定项类重命名为更具体的名称:
- `CreateNetworkStackConfigCommand.StackCoreIMSBindingItem``CreateStackCoreIMSBindingItem`
- `UpdateNetworkStackConfigCommand.StackCoreIMSBindingItem``UpdateStackCoreIMSBindingItem`
- **影响文件**:
- `CreateNetworkStackConfigCommand.cs`
- `UpdateNetworkStackConfigCommand.cs`

40
src/X1.Application/Features/NetworkStackConfigs/Commands/CreateNetworkStackConfig/CreateNetworkStackConfigCommand.cs

@ -10,11 +10,11 @@ namespace CellularManagement.Application.Features.NetworkStackConfigs.Commands.C
public class CreateNetworkStackConfigCommand : IRequest<OperationResult<CreateNetworkStackConfigResponse>>
{
/// <summary>
/// 栈ID(主键)
/// 网络栈名称
/// </summary>
[Required(ErrorMessage = "栈ID不能为空")]
[MaxLength(50, ErrorMessage = "栈ID不能超过50个字符")]
public string StackId { get; set; } = null!;
[Required(ErrorMessage = "网络栈名称不能为空")]
[MaxLength(100, ErrorMessage = "网络栈名称不能超过100个字符")]
public string NetworkStackName { get; set; } = null!;
/// <summary>
/// RAN配置ID(外键,可为空)
@ -32,4 +32,36 @@ public class CreateNetworkStackConfigCommand : IRequest<OperationResult<CreateNe
/// 是否激活
/// </summary>
public bool IsActive { get; set; } = true;
/// <summary>
/// 栈与核心网/IMS绑定关系列表
/// </summary>
public List<CreateStackCoreIMSBindingItem> StackCoreIMSBindings { get; set; } = new();
}
/// <summary>
/// 栈与核心网/IMS绑定关系项
/// </summary>
public class CreateStackCoreIMSBindingItem
{
/// <summary>
/// 索引(主键的一部分)
/// </summary>
[Required(ErrorMessage = "索引不能为空")]
[Range(0, int.MaxValue, ErrorMessage = "索引必须大于等于0")]
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
[Required(ErrorMessage = "核心网配置ID不能为空")]
[MaxLength(50, ErrorMessage = "核心网配置ID不能超过50个字符")]
public string CoreNetworkConfigId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
[Required(ErrorMessage = "IMS配置ID不能为空")]
[MaxLength(50, ErrorMessage = "IMS配置ID不能超过50个字符")]
public string IMSConfigId { get; set; } = null!;
}

73
src/X1.Application/Features/NetworkStackConfigs/Commands/CreateNetworkStackConfig/CreateNetworkStackConfigCommandHandler.cs

@ -14,6 +14,7 @@ namespace CellularManagement.Application.Features.NetworkStackConfigs.Commands.C
public class CreateNetworkStackConfigCommandHandler : IRequestHandler<CreateNetworkStackConfigCommand, OperationResult<CreateNetworkStackConfigResponse>>
{
private readonly INetworkStackConfigRepository _networkStackConfigRepository;
private readonly IStack_CoreIMS_BindingRepository _stackCoreIMSBindingRepository;
private readonly ILogger<CreateNetworkStackConfigCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
@ -23,11 +24,13 @@ public class CreateNetworkStackConfigCommandHandler : IRequestHandler<CreateNetw
/// </summary>
public CreateNetworkStackConfigCommandHandler(
INetworkStackConfigRepository networkStackConfigRepository,
IStack_CoreIMS_BindingRepository stackCoreIMSBindingRepository,
ILogger<CreateNetworkStackConfigCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_networkStackConfigRepository = networkStackConfigRepository;
_stackCoreIMSBindingRepository = stackCoreIMSBindingRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
@ -40,13 +43,13 @@ public class CreateNetworkStackConfigCommandHandler : IRequestHandler<CreateNetw
{
try
{
_logger.LogInformation("开始创建网络栈配置,栈ID: {StackId}", request.StackId);
_logger.LogInformation("开始创建网络栈配置,网络栈名称: {NetworkStackName}", request.NetworkStackName);
// 检查栈ID是否已存在
if (await _networkStackConfigRepository.StackIdExistsAsync(request.StackId, cancellationToken))
// 检查网络栈名称是否已存在
if (await _networkStackConfigRepository.NameExistsAsync(request.NetworkStackName, cancellationToken))
{
_logger.LogWarning("网络栈配置栈ID已存在: {StackId}", request.StackId);
return OperationResult<CreateNetworkStackConfigResponse>.CreateFailure($"网络栈配置栈ID {request.StackId} 已存在");
_logger.LogWarning("网络栈配置名称已存在: {NetworkStackName}", request.NetworkStackName);
return OperationResult<CreateNetworkStackConfigResponse>.CreateFailure($"网络栈配置名称 {request.NetworkStackName} 已存在");
}
// 获取当前用户ID
@ -59,7 +62,7 @@ public class CreateNetworkStackConfigCommandHandler : IRequestHandler<CreateNetw
// 创建网络栈配置实体
var networkStackConfig = NetworkStackConfig.Create(
stackId: request.StackId,
networkStackName: request.NetworkStackName,
createdBy: currentUserId,
ranId: request.RanId,
description: request.Description,
@ -68,27 +71,73 @@ public class CreateNetworkStackConfigCommandHandler : IRequestHandler<CreateNetw
// 保存网络栈配置
await _networkStackConfigRepository.AddNetworkStackConfigAsync(networkStackConfig, cancellationToken);
// 保存更改到数据库
// 创建栈与核心网/IMS绑定关系列表
var createdBindings = new List<Stack_CoreIMS_Binding>();
var bindingResponseItems = new List<CreateStackCoreIMSBindingResponseItem>();
if (request.StackCoreIMSBindings?.Any() == true)
{
_logger.LogInformation("开始创建 {BindingCount} 个栈与核心网/IMS绑定关系", request.StackCoreIMSBindings.Count);
foreach (var bindingItem in request.StackCoreIMSBindings)
{
// 检查索引是否已存在
if (await _stackCoreIMSBindingRepository.NetworkStackConfigIdAndIndexExistsAsync(networkStackConfig.Id, bindingItem.Index, cancellationToken))
{
_logger.LogWarning("栈与核心网/IMS绑定关系索引已存在,网络栈配置ID: {NetworkStackConfigId}, 索引: {Index}",
networkStackConfig.Id, bindingItem.Index);
return OperationResult<CreateNetworkStackConfigResponse>.CreateFailure(
$"栈与核心网/IMS绑定关系索引已存在,网络栈配置ID: {networkStackConfig.Id}, 索引: {bindingItem.Index}");
}
// 创建绑定关系实体
var binding = Stack_CoreIMS_Binding.Create(
networkStackConfigId: networkStackConfig.Id,
index: bindingItem.Index,
cnId: bindingItem.CoreNetworkConfigId,
imsId: bindingItem.IMSConfigId);
// 保存绑定关系
await _stackCoreIMSBindingRepository.AddBindingAsync(binding, cancellationToken);
createdBindings.Add(binding);
// 构建响应项
bindingResponseItems.Add(new CreateStackCoreIMSBindingResponseItem
{
StackCoreIMSBindingId = binding.Id,
NetworkStackConfigId = binding.NetworkStackConfigId,
Index = binding.Index,
CoreNetworkConfigId = binding.CnId,
IMSConfigId = binding.ImsId,
CreatedAt = DateTime.UtcNow
});
}
_logger.LogInformation("成功创建 {BindingCount} 个栈与核心网/IMS绑定关系", createdBindings.Count);
}
// 保存所有更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 构建响应
var response = new CreateNetworkStackConfigResponse
{
NetworkStackConfigId = networkStackConfig.Id,
StackId = networkStackConfig.StackId,
NetworkStackName = networkStackConfig.NetworkStackName,
RanId = networkStackConfig.RanId,
Description = networkStackConfig.Description,
IsActive = networkStackConfig.IsActive,
CreatedAt = networkStackConfig.CreatedAt
CreatedAt = networkStackConfig.CreatedAt,
StackCoreIMSBindings = bindingResponseItems
};
_logger.LogInformation("网络栈配置创建成功,配置ID: {NetworkStackConfigId}, 栈ID: {StackId}",
networkStackConfig.Id, networkStackConfig.StackId);
_logger.LogInformation("网络栈配置创建成功,配置ID: {NetworkStackConfigId}, 网络栈名称: {NetworkStackName}, 绑定关系数量: {BindingCount}",
networkStackConfig.Id, networkStackConfig.NetworkStackName, bindingResponseItems.Count);
return OperationResult<CreateNetworkStackConfigResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "创建网络栈配置时发生错误,栈ID: {StackId}", request.StackId);
_logger.LogError(ex, "创建网络栈配置时发生错误,网络栈名称: {NetworkStackName}", request.NetworkStackName);
return OperationResult<CreateNetworkStackConfigResponse>.CreateFailure($"创建网络栈配置时发生错误: {ex.Message}");
}
}

45
src/X1.Application/Features/NetworkStackConfigs/Commands/CreateNetworkStackConfig/CreateNetworkStackConfigResponse.cs

@ -11,9 +11,9 @@ public class CreateNetworkStackConfigResponse
public string NetworkStackConfigId { get; set; } = null!;
/// <summary>
/// 栈ID(主键)
/// 网络栈名称
/// </summary>
public string StackId { get; set; } = null!;
public string NetworkStackName { get; set; } = null!;
/// <summary>
/// RAN配置ID(外键,可为空)
@ -34,4 +34,45 @@ public class CreateNetworkStackConfigResponse
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 创建的栈与核心网/IMS绑定关系列表
/// </summary>
public List<CreateStackCoreIMSBindingResponseItem> StackCoreIMSBindings { get; set; } = new();
}
/// <summary>
/// 栈与核心网/IMS绑定关系响应项
/// </summary>
public class CreateStackCoreIMSBindingResponseItem
{
/// <summary>
/// 绑定关系ID
/// </summary>
public string StackCoreIMSBindingId { get; set; } = null!;
/// <summary>
/// 网络栈配置ID(外键)
/// </summary>
public string NetworkStackConfigId { get; set; } = null!;
/// <summary>
/// 索引(主键的一部分)
/// </summary>
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
public string CoreNetworkConfigId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
public string IMSConfigId { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
}

2
src/X1.Application/Features/NetworkStackConfigs/Commands/DeleteNetworkStackConfig/DeleteNetworkStackConfigCommand.cs

@ -7,7 +7,7 @@ namespace CellularManagement.Application.Features.NetworkStackConfigs.Commands.D
/// <summary>
/// 删除网络栈配置命令
/// </summary>
public class DeleteNetworkStackConfigCommand : IRequest<OperationResult<bool>>
public class DeleteNetworkStackConfigCommand : IRequest<OperationResult<DeleteNetworkStackConfigResponse>>
{
/// <summary>
/// 网络栈配置ID

43
src/X1.Application/Features/NetworkStackConfigs/Commands/DeleteNetworkStackConfig/DeleteNetworkStackConfigCommandHandler.cs

@ -9,9 +9,10 @@ namespace CellularManagement.Application.Features.NetworkStackConfigs.Commands.D
/// <summary>
/// 删除网络栈配置命令处理器
/// </summary>
public class DeleteNetworkStackConfigCommandHandler : IRequestHandler<DeleteNetworkStackConfigCommand, OperationResult<bool>>
public class DeleteNetworkStackConfigCommandHandler : IRequestHandler<DeleteNetworkStackConfigCommand, OperationResult<DeleteNetworkStackConfigResponse>>
{
private readonly INetworkStackConfigRepository _networkStackConfigRepository;
private readonly IStack_CoreIMS_BindingRepository _stackCoreIMSBindingRepository;
private readonly ILogger<DeleteNetworkStackConfigCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
@ -20,10 +21,12 @@ public class DeleteNetworkStackConfigCommandHandler : IRequestHandler<DeleteNetw
/// </summary>
public DeleteNetworkStackConfigCommandHandler(
INetworkStackConfigRepository networkStackConfigRepository,
IStack_CoreIMS_BindingRepository stackCoreIMSBindingRepository,
ILogger<DeleteNetworkStackConfigCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_networkStackConfigRepository = networkStackConfigRepository;
_stackCoreIMSBindingRepository = stackCoreIMSBindingRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
@ -31,7 +34,7 @@ public class DeleteNetworkStackConfigCommandHandler : IRequestHandler<DeleteNetw
/// <summary>
/// 处理删除网络栈配置命令
/// </summary>
public async Task<OperationResult<bool>> Handle(DeleteNetworkStackConfigCommand request, CancellationToken cancellationToken)
public async Task<OperationResult<DeleteNetworkStackConfigResponse>> Handle(DeleteNetworkStackConfigCommand request, CancellationToken cancellationToken)
{
try
{
@ -42,7 +45,24 @@ public class DeleteNetworkStackConfigCommandHandler : IRequestHandler<DeleteNetw
if (existingConfig == null)
{
_logger.LogWarning("网络栈配置不存在: {NetworkStackConfigId}", request.NetworkStackConfigId);
return OperationResult<bool>.CreateFailure($"网络栈配置 {request.NetworkStackConfigId} 不存在");
return OperationResult<DeleteNetworkStackConfigResponse>.CreateFailure($"网络栈配置 {request.NetworkStackConfigId} 不存在");
}
// 获取所有相关的绑定关系
var existingBindings = await _stackCoreIMSBindingRepository.GetBindingsByNetworkStackConfigIdAsync(request.NetworkStackConfigId, cancellationToken);
var deletedBindingCount = existingBindings.Count;
if (existingBindings.Any())
{
_logger.LogInformation("发现 {BindingCount} 个相关的栈与核心网/IMS绑定关系,将一并删除", deletedBindingCount);
// 删除所有相关的绑定关系
foreach (var binding in existingBindings)
{
await _stackCoreIMSBindingRepository.DeleteBindingAsync(binding.Id, cancellationToken);
}
_logger.LogInformation("成功删除 {BindingCount} 个栈与核心网/IMS绑定关系", deletedBindingCount);
}
// 删除网络栈配置
@ -51,14 +71,23 @@ public class DeleteNetworkStackConfigCommandHandler : IRequestHandler<DeleteNetw
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("网络栈配置删除成功,配置ID: {NetworkStackConfigId}, 栈ID: {StackId}",
request.NetworkStackConfigId, existingConfig.StackId);
return OperationResult<bool>.CreateSuccess(true);
// 构建响应
var response = new DeleteNetworkStackConfigResponse
{
NetworkStackConfigId = request.NetworkStackConfigId,
NetworkStackName = existingConfig.NetworkStackName,
DeletedBindingCount = deletedBindingCount,
DeletedAt = DateTime.UtcNow
};
_logger.LogInformation("网络栈配置删除成功,配置ID: {NetworkStackConfigId}, 网络栈名称: {NetworkStackName}, 删除的绑定关系数量: {DeletedBindingCount}",
request.NetworkStackConfigId, existingConfig.NetworkStackName, deletedBindingCount);
return OperationResult<DeleteNetworkStackConfigResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除网络栈配置时发生错误,配置ID: {NetworkStackConfigId}", request.NetworkStackConfigId);
return OperationResult<bool>.CreateFailure($"删除网络栈配置时发生错误: {ex.Message}");
return OperationResult<DeleteNetworkStackConfigResponse>.CreateFailure($"删除网络栈配置时发生错误: {ex.Message}");
}
}
}

27
src/X1.Application/Features/NetworkStackConfigs/Commands/DeleteNetworkStackConfig/DeleteNetworkStackConfigResponse.cs

@ -0,0 +1,27 @@
namespace CellularManagement.Application.Features.NetworkStackConfigs.Commands.DeleteNetworkStackConfig;
/// <summary>
/// 删除网络栈配置响应
/// </summary>
public class DeleteNetworkStackConfigResponse
{
/// <summary>
/// 网络栈配置ID
/// </summary>
public string NetworkStackConfigId { get; set; } = null!;
/// <summary>
/// 网络栈名称
/// </summary>
public string NetworkStackName { get; set; } = null!;
/// <summary>
/// 删除的栈与核心网/IMS绑定关系数量
/// </summary>
public int DeletedBindingCount { get; set; }
/// <summary>
/// 删除时间
/// </summary>
public DateTime DeletedAt { get; set; } = DateTime.UtcNow;
}

38
src/X1.Application/Features/NetworkStackConfigs/Commands/UpdateNetworkStackConfig/UpdateNetworkStackConfigCommand.cs

@ -15,6 +15,12 @@ public class UpdateNetworkStackConfigCommand : IRequest<OperationResult<UpdateNe
[Required]
public string NetworkStackConfigId { get; set; } = null!;
/// <summary>
/// 网络栈名称
/// </summary>
[MaxLength(100, ErrorMessage = "网络栈名称不能超过100个字符")]
public string? NetworkStackName { get; set; }
/// <summary>
/// RAN配置ID(外键,可为空)
/// </summary>
@ -31,4 +37,36 @@ public class UpdateNetworkStackConfigCommand : IRequest<OperationResult<UpdateNe
/// 是否激活
/// </summary>
public bool IsActive { get; set; } = true;
/// <summary>
/// 栈与核心网/IMS绑定关系列表(可选,用于更新绑定关系)
/// </summary>
public List<UpdateStackCoreIMSBindingItem> StackCoreIMSBindings { get; set; } = new();
}
/// <summary>
/// 栈与核心网/IMS绑定关系项
/// </summary>
public class UpdateStackCoreIMSBindingItem
{
/// <summary>
/// 索引(主键的一部分)
/// </summary>
[Required(ErrorMessage = "索引不能为空")]
[Range(0, int.MaxValue, ErrorMessage = "索引必须大于等于0")]
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
[Required(ErrorMessage = "核心网配置ID不能为空")]
[MaxLength(50, ErrorMessage = "核心网配置ID不能超过50个字符")]
public string CoreNetworkConfigId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
[Required(ErrorMessage = "IMS配置ID不能为空")]
[MaxLength(50, ErrorMessage = "IMS配置ID不能超过50个字符")]
public string IMSConfigId { get; set; } = null!;
}

75
src/X1.Application/Features/NetworkStackConfigs/Commands/UpdateNetworkStackConfig/UpdateNetworkStackConfigCommandHandler.cs

@ -14,6 +14,7 @@ namespace CellularManagement.Application.Features.NetworkStackConfigs.Commands.U
public class UpdateNetworkStackConfigCommandHandler : IRequestHandler<UpdateNetworkStackConfigCommand, OperationResult<UpdateNetworkStackConfigResponse>>
{
private readonly INetworkStackConfigRepository _networkStackConfigRepository;
private readonly IStack_CoreIMS_BindingRepository _stackCoreIMSBindingRepository;
private readonly ILogger<UpdateNetworkStackConfigCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
@ -23,11 +24,13 @@ public class UpdateNetworkStackConfigCommandHandler : IRequestHandler<UpdateNetw
/// </summary>
public UpdateNetworkStackConfigCommandHandler(
INetworkStackConfigRepository networkStackConfigRepository,
IStack_CoreIMS_BindingRepository stackCoreIMSBindingRepository,
ILogger<UpdateNetworkStackConfigCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_networkStackConfigRepository = networkStackConfigRepository;
_stackCoreIMSBindingRepository = stackCoreIMSBindingRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
@ -50,6 +53,15 @@ public class UpdateNetworkStackConfigCommandHandler : IRequestHandler<UpdateNetw
return OperationResult<UpdateNetworkStackConfigResponse>.CreateFailure($"网络栈配置 {request.NetworkStackConfigId} 不存在");
}
// 检查网络栈名称是否已存在(如果提供了新名称)
if (!string.IsNullOrWhiteSpace(request.NetworkStackName) &&
request.NetworkStackName != existingConfig.NetworkStackName &&
await _networkStackConfigRepository.NameExistsAsync(request.NetworkStackName, cancellationToken))
{
_logger.LogWarning("网络栈配置名称已存在: {NetworkStackName}", request.NetworkStackName);
return OperationResult<UpdateNetworkStackConfigResponse>.CreateFailure($"网络栈配置名称 {request.NetworkStackName} 已存在");
}
// 获取当前用户ID
var currentUserId = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUserId))
@ -61,27 +73,80 @@ public class UpdateNetworkStackConfigCommandHandler : IRequestHandler<UpdateNetw
// 更新网络栈配置
existingConfig.Update(
updatedBy: currentUserId,
networkStackName: request.NetworkStackName,
ranId: request.RanId,
description: request.Description,
isActive: request.IsActive);
// 保存更改
// 保存网络栈配置更改
_networkStackConfigRepository.UpdateNetworkStackConfig(existingConfig);
// 处理绑定关系更新
// 创建栈与核心网/IMS绑定关系列表
var bindingResponseItems = new List<UpdateStackCoreIMSBindingResponseItem>();
if (request.StackCoreIMSBindings?.Any() == true)
{
_logger.LogInformation("开始更新 {BindingCount} 个栈与核心网/IMS绑定关系", request.StackCoreIMSBindings.Count);
// 获取现有的绑定关系
var existingBindings = await _stackCoreIMSBindingRepository.GetBindingsByNetworkStackConfigIdAsync(request.NetworkStackConfigId, cancellationToken);
var existingBindingDict = existingBindings.ToDictionary(b => b.Index, b => b);
foreach (var bindingItem in request.StackCoreIMSBindings)
{
// 检查索引是否与其他绑定关系冲突(排除当前索引)
var conflictingBindings = existingBindings.Where(b => b.Index == bindingItem.Index).ToList();
if (conflictingBindings.Any())
{
_logger.LogWarning("栈与核心网/IMS绑定关系索引已存在,网络栈配置ID: {NetworkStackConfigId}, 索引: {Index}",
request.NetworkStackConfigId, bindingItem.Index);
return OperationResult<UpdateNetworkStackConfigResponse>.CreateFailure(
$"栈与核心网/IMS绑定关系索引已存在,网络栈配置ID: {request.NetworkStackConfigId}, 索引: {bindingItem.Index}");
}
// 创建绑定关系实体
var binding = Stack_CoreIMS_Binding.Create(
networkStackConfigId: existingConfig.Id,
index: bindingItem.Index,
cnId: bindingItem.CoreNetworkConfigId,
imsId: bindingItem.IMSConfigId);
// 保存绑定关系
await _stackCoreIMSBindingRepository.AddBindingAsync(binding, cancellationToken);
// 构建响应项
bindingResponseItems.Add(new UpdateStackCoreIMSBindingResponseItem
{
StackCoreIMSBindingId = binding.Id,
NetworkStackConfigId = binding.NetworkStackConfigId,
Index = binding.Index,
CoreNetworkConfigId = binding.CnId,
IMSConfigId = binding.ImsId,
CreatedAt = DateTime.UtcNow
});
}
_logger.LogInformation("成功更新 {BindingCount} 个栈与核心网/IMS绑定关系", bindingResponseItems.Count);
}
// 保存所有更改
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 构建响应
var response = new UpdateNetworkStackConfigResponse
{
NetworkStackConfigId = existingConfig.Id,
StackId = existingConfig.StackId,
NetworkStackName = existingConfig.NetworkStackName,
RanId = existingConfig.RanId,
Description = existingConfig.Description,
IsActive = existingConfig.IsActive,
UpdatedAt = existingConfig.UpdatedAt
UpdatedAt = existingConfig.UpdatedAt,
StackCoreIMSBindings = bindingResponseItems
};
_logger.LogInformation("网络栈配置更新成功,配置ID: {NetworkStackConfigId}, 栈ID: {StackId}",
existingConfig.Id, existingConfig.StackId);
_logger.LogInformation("网络栈配置更新成功,配置ID: {NetworkStackConfigId}, 网络栈名称: {NetworkStackName}, 绑定关系数量: {BindingCount}",
existingConfig.Id, existingConfig.NetworkStackName, bindingResponseItems.Count);
return OperationResult<UpdateNetworkStackConfigResponse>.CreateSuccess(response);
}
catch (Exception ex)

50
src/X1.Application/Features/NetworkStackConfigs/Commands/UpdateNetworkStackConfig/UpdateNetworkStackConfigResponse.cs

@ -11,9 +11,9 @@ public class UpdateNetworkStackConfigResponse
public string NetworkStackConfigId { get; set; } = null!;
/// <summary>
/// 栈ID(主键)
/// 网络栈名称
/// </summary>
public string StackId { get; set; } = null!;
public string NetworkStackName { get; set; } = null!;
/// <summary>
/// RAN配置ID(外键,可为空)
@ -34,4 +34,50 @@ public class UpdateNetworkStackConfigResponse
/// 更新时间
/// </summary>
public DateTime? UpdatedAt { get; set; }
/// <summary>
/// 更新的栈与核心网/IMS绑定关系列表
/// </summary>
public List<UpdateStackCoreIMSBindingResponseItem> StackCoreIMSBindings { get; set; } = new();
}
/// <summary>
/// 栈与核心网/IMS绑定关系响应项
/// </summary>
public class UpdateStackCoreIMSBindingResponseItem
{
/// <summary>
/// 绑定关系ID
/// </summary>
public string StackCoreIMSBindingId { get; set; } = null!;
/// <summary>
/// 网络栈配置ID(外键)
/// </summary>
public string NetworkStackConfigId { get; set; } = null!;
/// <summary>
/// 索引(主键的一部分)
/// </summary>
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
public string CoreNetworkConfigId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
public string IMSConfigId { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdatedAt { get; set; }
}

29
src/X1.Application/Features/NetworkStackConfigs/Queries/GetNetworkStackConfigById/GetNetworkStackConfigByIdQueryHandler.cs

@ -31,38 +31,47 @@ public class GetNetworkStackConfigByIdQueryHandler : IRequestHandler<GetNetworkS
{
try
{
_logger.LogInformation("开始获取网络栈配置,配置ID: {NetworkStackConfigId}", request.NetworkStackConfigId);
_logger.LogInformation("开始查询网络栈配置,配置ID: {NetworkStackConfigId}", request.NetworkStackConfigId);
// 获取网络栈配置
var networkStackConfig = await _networkStackConfigRepository.GetNetworkStackConfigByIdAsync(request.NetworkStackConfigId, cancellationToken);
// 获取网络栈配置(包含导航属性)
var networkStackConfig = await _networkStackConfigRepository.GetNetworkStackConfigByIdWithBindingsAsync(request.NetworkStackConfigId, cancellationToken);
if (networkStackConfig == null)
{
_logger.LogWarning("网络栈配置不存在: {NetworkStackConfigId}", request.NetworkStackConfigId);
return OperationResult<GetNetworkStackConfigByIdResponse>.CreateFailure($"网络栈配置 {request.NetworkStackConfigId} 不存在");
}
// 构建响应
// 构建响应,使用导航属性获取绑定关系
var response = new GetNetworkStackConfigByIdResponse
{
NetworkStackConfigId = networkStackConfig.Id,
StackId = networkStackConfig.StackId,
NetworkStackName = networkStackConfig.NetworkStackName,
RanId = networkStackConfig.RanId,
Description = networkStackConfig.Description,
IsActive = networkStackConfig.IsActive,
CreatedAt = networkStackConfig.CreatedAt,
UpdatedAt = networkStackConfig.UpdatedAt,
CreatedBy = networkStackConfig.CreatedBy,
UpdatedBy = networkStackConfig.UpdatedBy
UpdatedBy = networkStackConfig.UpdatedBy,
StackCoreIMSBindings = networkStackConfig.StackCoreIMSBindings.Select(binding => new GetNetworkStackConfigByIdBindingResponseItem
{
StackCoreIMSBindingId = binding.Id,
NetworkStackConfigId = binding.NetworkStackConfigId,
Index = binding.Index,
CoreNetworkConfigId = binding.CnId,
IMSConfigId = binding.ImsId,
CreatedAt = DateTime.UtcNow
}).ToList()
};
_logger.LogInformation("成功获取网络栈配置,配置ID: {NetworkStackConfigId}, 栈ID: {StackId}",
networkStackConfig.Id, networkStackConfig.StackId);
_logger.LogInformation("网络栈配置查询成功,配置ID: {NetworkStackConfigId}, 网络栈名称: {NetworkStackName}, 绑定关系数量: {BindingCount}",
networkStackConfig.Id, networkStackConfig.NetworkStackName, response.StackCoreIMSBindings.Count);
return OperationResult<GetNetworkStackConfigByIdResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取网络栈配置时发生错误,配置ID: {NetworkStackConfigId}", request.NetworkStackConfigId);
return OperationResult<GetNetworkStackConfigByIdResponse>.CreateFailure($"获取网络栈配置时发生错误: {ex.Message}");
_logger.LogError(ex, "查询网络栈配置时发生错误,配置ID: {NetworkStackConfigId}", request.NetworkStackConfigId);
return OperationResult<GetNetworkStackConfigByIdResponse>.CreateFailure($"查询网络栈配置时发生错误: {ex.Message}");
}
}
}

50
src/X1.Application/Features/NetworkStackConfigs/Queries/GetNetworkStackConfigById/GetNetworkStackConfigByIdResponse.cs

@ -11,9 +11,9 @@ public class GetNetworkStackConfigByIdResponse
public string NetworkStackConfigId { get; set; } = null!;
/// <summary>
/// 栈ID(主键)
/// 网络栈名称
/// </summary>
public string StackId { get; set; } = null!;
public string NetworkStackName { get; set; } = null!;
/// <summary>
/// RAN配置ID(外键,可为空)
@ -49,4 +49,50 @@ public class GetNetworkStackConfigByIdResponse
/// 更新者
/// </summary>
public string? UpdatedBy { get; set; }
/// <summary>
/// 栈与核心网/IMS绑定关系列表
/// </summary>
public List<GetNetworkStackConfigByIdBindingResponseItem> StackCoreIMSBindings { get; set; } = new();
}
/// <summary>
/// 栈与核心网/IMS绑定关系响应项
/// </summary>
public class GetNetworkStackConfigByIdBindingResponseItem
{
/// <summary>
/// 绑定关系ID
/// </summary>
public string StackCoreIMSBindingId { get; set; } = null!;
/// <summary>
/// 网络栈配置ID(外键)
/// </summary>
public string NetworkStackConfigId { get; set; } = null!;
/// <summary>
/// 索引(主键的一部分)
/// </summary>
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
public string CoreNetworkConfigId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
public string IMSConfigId { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdatedAt { get; set; }
}

6
src/X1.Application/Features/NetworkStackConfigs/Queries/GetNetworkStackConfigs/GetNetworkStackConfigsQuery.cs

@ -22,10 +22,10 @@ public class GetNetworkStackConfigsQuery : IRequest<OperationResult<GetNetworkSt
public int PageSize { get; set; } = 10;
/// <summary>
/// 搜索关键词(栈ID或描述)
/// 网络栈名称过滤
/// </summary>
[MaxLength(100, ErrorMessage = "搜索关键词不能超过100个字符")]
public string? SearchTerm { get; set; }
[MaxLength(100, ErrorMessage = "网络栈名称不能超过100个字符")]
public string? NetworkStackName { get; set; }
/// <summary>
/// 是否激活过滤

47
src/X1.Application/Features/NetworkStackConfigs/Queries/GetNetworkStackConfigs/GetNetworkStackConfigsQueryHandler.cs

@ -31,44 +31,43 @@ public class GetNetworkStackConfigsQueryHandler : IRequestHandler<GetNetworkStac
{
try
{
_logger.LogInformation("开始获取网络栈配置列表,页码: {PageNumber}, 每页大小: {PageSize}",
request.PageNumber, request.PageSize);
_logger.LogInformation("开始查询网络栈配置列表,页码: {PageNumber}, 页大小: {PageSize}", request.PageNumber, request.PageSize);
// 搜索网络栈配置
var (totalCount, networkStackConfigs) = await _networkStackConfigRepository.SearchNetworkStackConfigsAsync(
keyword: request.SearchTerm,
// 获取网络栈配置列表(包含导航属性)
var (totalCount, networkStackConfigs) = await _networkStackConfigRepository.SearchNetworkStackConfigsWithBindingsAsync(
networkStackName: request.NetworkStackName,
ranId: request.RanId,
isActive: request.IsActive,
pageNumber: request.PageNumber,
pageSize: request.PageSize,
cancellationToken);
// 应用额外的过滤条件
if (request.IsActive.HasValue)
{
networkStackConfigs = networkStackConfigs.Where(config => config.IsActive == request.IsActive.Value).ToList();
}
if (!string.IsNullOrEmpty(request.RanId))
{
networkStackConfigs = networkStackConfigs.Where(config => config.RanId == request.RanId).ToList();
}
cancellationToken: cancellationToken);
// 计算分页信息
var totalPages = (int)Math.Ceiling((double)totalCount / request.PageSize);
var hasPreviousPage = request.PageNumber > 1;
var hasNextPage = request.PageNumber < totalPages;
// 构建DTO列表
// 构建DTO列表,使用导航属性获取绑定关系
var networkStackConfigDtos = networkStackConfigs.Select(config => new NetworkStackConfigDto
{
NetworkStackConfigId = config.Id,
StackId = config.StackId,
NetworkStackName = config.NetworkStackName,
RanId = config.RanId,
Description = config.Description,
IsActive = config.IsActive,
CreatedAt = config.CreatedAt,
UpdatedAt = config.UpdatedAt,
CreatedBy = config.CreatedBy,
UpdatedBy = config.UpdatedBy
UpdatedBy = config.UpdatedBy,
StackCoreIMSBindings = config.StackCoreIMSBindings.Select(binding => new GetNetworkStackConfigsBindingResponseItem
{
StackCoreIMSBindingId = binding.Id,
NetworkStackConfigId = binding.NetworkStackConfigId,
Index = binding.Index,
CoreNetworkConfigId = binding.CnId,
IMSConfigId = binding.ImsId,
CreatedAt = DateTime.UtcNow
}).ToList()
}).ToList();
// 构建响应
@ -83,14 +82,14 @@ public class GetNetworkStackConfigsQueryHandler : IRequestHandler<GetNetworkStac
HasNextPage = hasNextPage
};
_logger.LogInformation("成功获取网络栈配置列表,总数: {TotalCount}, 当前页: {PageNumber}/{TotalPages}",
totalCount, request.PageNumber, totalPages);
_logger.LogInformation("网络栈配置列表查询成功,总记录数: {TotalCount}, 当前页记录数: {CurrentPageCount}",
totalCount, networkStackConfigDtos.Count);
return OperationResult<GetNetworkStackConfigsResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取网络栈配置列表时发生错误");
return OperationResult<GetNetworkStackConfigsResponse>.CreateFailure($"获取网络栈配置列表时发生错误: {ex.Message}");
_logger.LogError(ex, "查询网络栈配置列表时发生错误");
return OperationResult<GetNetworkStackConfigsResponse>.CreateFailure($"查询网络栈配置列表时发生错误: {ex.Message}");
}
}
}

50
src/X1.Application/Features/NetworkStackConfigs/Queries/GetNetworkStackConfigs/GetNetworkStackConfigsResponse.cs

@ -52,9 +52,9 @@ public class NetworkStackConfigDto
public string NetworkStackConfigId { get; set; } = null!;
/// <summary>
/// 栈ID(主键)
/// 网络栈名称
/// </summary>
public string StackId { get; set; } = null!;
public string NetworkStackName { get; set; } = null!;
/// <summary>
/// RAN配置ID(外键,可为空)
@ -90,4 +90,50 @@ public class NetworkStackConfigDto
/// 更新者
/// </summary>
public string? UpdatedBy { get; set; }
/// <summary>
/// 栈与核心网/IMS绑定关系列表
/// </summary>
public List<GetNetworkStackConfigsBindingResponseItem> StackCoreIMSBindings { get; set; } = new();
}
/// <summary>
/// 栈与核心网/IMS绑定关系响应项
/// </summary>
public class GetNetworkStackConfigsBindingResponseItem
{
/// <summary>
/// 绑定关系ID
/// </summary>
public string StackCoreIMSBindingId { get; set; } = null!;
/// <summary>
/// 网络栈配置ID(外键)
/// </summary>
public string NetworkStackConfigId { get; set; } = null!;
/// <summary>
/// 索引(主键的一部分)
/// </summary>
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
public string CoreNetworkConfigId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
public string IMSConfigId { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdatedAt { get; set; }
}

39
src/X1.Application/Features/StackCoreIMSBindings/Commands/CreateStackCoreIMSBinding/CreateStackCoreIMSBindingCommand.cs

@ -1,39 +0,0 @@
using CellularManagement.Domain.Common;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Commands.CreateStackCoreIMSBinding;
/// <summary>
/// 创建栈与核心网/IMS绑定关系命令
/// </summary>
public class CreateStackCoreIMSBindingCommand : IRequest<OperationResult<CreateStackCoreIMSBindingResponse>>
{
/// <summary>
/// 栈ID(外键)
/// </summary>
[Required(ErrorMessage = "栈ID不能为空")]
[MaxLength(50, ErrorMessage = "栈ID不能超过50个字符")]
public string StackId { get; set; } = null!;
/// <summary>
/// 索引(主键的一部分)
/// </summary>
[Required(ErrorMessage = "索引不能为空")]
[Range(0, int.MaxValue, ErrorMessage = "索引必须大于等于0")]
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
[Required(ErrorMessage = "核心网配置ID不能为空")]
[MaxLength(50, ErrorMessage = "核心网配置ID不能超过50个字符")]
public string CnId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
[Required(ErrorMessage = "IMS配置ID不能为空")]
[MaxLength(50, ErrorMessage = "IMS配置ID不能超过50个字符")]
public string ImsId { get; set; } = null!;
}

95
src/X1.Application/Features/StackCoreIMSBindings/Commands/CreateStackCoreIMSBinding/CreateStackCoreIMSBindingCommandHandler.cs

@ -1,95 +0,0 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Repositories.NetworkProfile;
using CellularManagement.Domain.Repositories.Base;
using CellularManagement.Domain.Services;
using CellularManagement.Domain.Entities.NetworkProfile;
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Commands.CreateStackCoreIMSBinding;
/// <summary>
/// 创建栈与核心网/IMS绑定关系命令处理器
/// </summary>
public class CreateStackCoreIMSBindingCommandHandler : IRequestHandler<CreateStackCoreIMSBindingCommand, OperationResult<CreateStackCoreIMSBindingResponse>>
{
private readonly IStack_CoreIMS_BindingRepository _stackCoreIMSBindingRepository;
private readonly ILogger<CreateStackCoreIMSBindingCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
/// <summary>
/// 初始化命令处理器
/// </summary>
public CreateStackCoreIMSBindingCommandHandler(
IStack_CoreIMS_BindingRepository stackCoreIMSBindingRepository,
ILogger<CreateStackCoreIMSBindingCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_stackCoreIMSBindingRepository = stackCoreIMSBindingRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
}
/// <summary>
/// 处理创建栈与核心网/IMS绑定关系命令
/// </summary>
public async Task<OperationResult<CreateStackCoreIMSBindingResponse>> Handle(CreateStackCoreIMSBindingCommand request, CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("开始创建栈与核心网/IMS绑定关系,栈ID: {StackId}, 索引: {Index}", request.StackId, request.Index);
// 检查绑定关系是否已存在
if (await _stackCoreIMSBindingRepository.StackIdAndIndexExistsAsync(request.StackId, request.Index, cancellationToken))
{
_logger.LogWarning("栈与核心网/IMS绑定关系已存在,栈ID: {StackId}, 索引: {Index}", request.StackId, request.Index);
return OperationResult<CreateStackCoreIMSBindingResponse>.CreateFailure($"栈与核心网/IMS绑定关系已存在,栈ID: {request.StackId}, 索引: {request.Index}");
}
// 获取当前用户ID
var currentUserId = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUserId))
{
_logger.LogError("无法获取当前用户ID,用户可能未认证");
return OperationResult<CreateStackCoreIMSBindingResponse>.CreateFailure("用户未认证,无法创建栈与核心网/IMS绑定关系");
}
// 创建栈与核心网/IMS绑定关系实体
var binding = Stack_CoreIMS_Binding.Create(
stackId: request.StackId,
index: request.Index,
cnId: request.CnId,
imsId: request.ImsId,
createdBy: currentUserId);
// 保存绑定关系
await _stackCoreIMSBindingRepository.AddBindingAsync(binding, cancellationToken);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 构建响应
var response = new CreateStackCoreIMSBindingResponse
{
StackCoreIMSBindingId = binding.Id,
StackId = binding.StackId,
Index = binding.Index,
CnId = binding.CnId,
ImsId = binding.ImsId,
CreatedAt = binding.CreatedAt
};
_logger.LogInformation("栈与核心网/IMS绑定关系创建成功,绑定ID: {BindingId}, 栈ID: {StackId}, 索引: {Index}",
binding.Id, binding.StackId, binding.Index);
return OperationResult<CreateStackCoreIMSBindingResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "创建栈与核心网/IMS绑定关系时发生错误,栈ID: {StackId}, 索引: {Index}", request.StackId, request.Index);
return OperationResult<CreateStackCoreIMSBindingResponse>.CreateFailure($"创建栈与核心网/IMS绑定关系时发生错误: {ex.Message}");
}
}
}

37
src/X1.Application/Features/StackCoreIMSBindings/Commands/CreateStackCoreIMSBinding/CreateStackCoreIMSBindingResponse.cs

@ -1,37 +0,0 @@
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Commands.CreateStackCoreIMSBinding;
/// <summary>
/// 创建栈与核心网/IMS绑定关系响应
/// </summary>
public class CreateStackCoreIMSBindingResponse
{
/// <summary>
/// 绑定关系ID
/// </summary>
public string StackCoreIMSBindingId { get; set; } = null!;
/// <summary>
/// 栈ID(外键)
/// </summary>
public string StackId { get; set; } = null!;
/// <summary>
/// 索引(主键的一部分)
/// </summary>
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
public string CnId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
public string ImsId { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
}

17
src/X1.Application/Features/StackCoreIMSBindings/Commands/DeleteStackCoreIMSBinding/DeleteStackCoreIMSBindingCommand.cs

@ -1,17 +0,0 @@
using CellularManagement.Domain.Common;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Commands.DeleteStackCoreIMSBinding;
/// <summary>
/// 删除栈与核心网/IMS绑定关系命令
/// </summary>
public class DeleteStackCoreIMSBindingCommand : IRequest<OperationResult<bool>>
{
/// <summary>
/// 绑定关系ID
/// </summary>
[Required]
public string StackCoreIMSBindingId { get; set; } = null!;
}

64
src/X1.Application/Features/StackCoreIMSBindings/Commands/DeleteStackCoreIMSBinding/DeleteStackCoreIMSBindingCommandHandler.cs

@ -1,64 +0,0 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Repositories.NetworkProfile;
using CellularManagement.Domain.Repositories.Base;
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Commands.DeleteStackCoreIMSBinding;
/// <summary>
/// 删除栈与核心网/IMS绑定关系命令处理器
/// </summary>
public class DeleteStackCoreIMSBindingCommandHandler : IRequestHandler<DeleteStackCoreIMSBindingCommand, OperationResult<bool>>
{
private readonly IStack_CoreIMS_BindingRepository _stackCoreIMSBindingRepository;
private readonly ILogger<DeleteStackCoreIMSBindingCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
/// <summary>
/// 初始化命令处理器
/// </summary>
public DeleteStackCoreIMSBindingCommandHandler(
IStack_CoreIMS_BindingRepository stackCoreIMSBindingRepository,
ILogger<DeleteStackCoreIMSBindingCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_stackCoreIMSBindingRepository = stackCoreIMSBindingRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
/// <summary>
/// 处理删除栈与核心网/IMS绑定关系命令
/// </summary>
public async Task<OperationResult<bool>> Handle(DeleteStackCoreIMSBindingCommand request, CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("开始删除栈与核心网/IMS绑定关系,绑定ID: {StackCoreIMSBindingId}", request.StackCoreIMSBindingId);
// 检查绑定关系是否存在
var existingBinding = await _stackCoreIMSBindingRepository.GetBindingByIdAsync(request.StackCoreIMSBindingId, cancellationToken);
if (existingBinding == null)
{
_logger.LogWarning("栈与核心网/IMS绑定关系不存在: {StackCoreIMSBindingId}", request.StackCoreIMSBindingId);
return OperationResult<bool>.CreateFailure($"栈与核心网/IMS绑定关系 {request.StackCoreIMSBindingId} 不存在");
}
// 删除绑定关系
await _stackCoreIMSBindingRepository.DeleteBindingAsync(request.StackCoreIMSBindingId, cancellationToken);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("栈与核心网/IMS绑定关系删除成功,绑定ID: {StackCoreIMSBindingId}, 栈ID: {StackId}, 索引: {Index}",
request.StackCoreIMSBindingId, existingBinding.StackId, existingBinding.Index);
return OperationResult<bool>.CreateSuccess(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除栈与核心网/IMS绑定关系时发生错误,绑定ID: {StackCoreIMSBindingId}", request.StackCoreIMSBindingId);
return OperationResult<bool>.CreateFailure($"删除栈与核心网/IMS绑定关系时发生错误: {ex.Message}");
}
}
}

38
src/X1.Application/Features/StackCoreIMSBindings/Commands/UpdateStackCoreIMSBinding/UpdateStackCoreIMSBindingCommand.cs

@ -1,38 +0,0 @@
using CellularManagement.Domain.Common;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Commands.UpdateStackCoreIMSBinding;
/// <summary>
/// 更新栈与核心网/IMS绑定关系命令
/// </summary>
public class UpdateStackCoreIMSBindingCommand : IRequest<OperationResult<UpdateStackCoreIMSBindingResponse>>
{
/// <summary>
/// 绑定关系ID
/// </summary>
[Required]
public string StackCoreIMSBindingId { get; set; } = null!;
/// <summary>
/// 索引(主键的一部分)
/// </summary>
[Required(ErrorMessage = "索引不能为空")]
[Range(0, int.MaxValue, ErrorMessage = "索引必须大于等于0")]
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
[Required(ErrorMessage = "核心网配置ID不能为空")]
[MaxLength(50, ErrorMessage = "核心网配置ID不能超过50个字符")]
public string CnId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
[Required(ErrorMessage = "IMS配置ID不能为空")]
[MaxLength(50, ErrorMessage = "IMS配置ID不能超过50个字符")]
public string ImsId { get; set; } = null!;
}

103
src/X1.Application/Features/StackCoreIMSBindings/Commands/UpdateStackCoreIMSBinding/UpdateStackCoreIMSBindingCommandHandler.cs

@ -1,103 +0,0 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Repositories.NetworkProfile;
using CellularManagement.Domain.Repositories.Base;
using CellularManagement.Domain.Services;
using CellularManagement.Domain.Entities.NetworkProfile;
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Commands.UpdateStackCoreIMSBinding;
/// <summary>
/// 更新栈与核心网/IMS绑定关系命令处理器
/// </summary>
public class UpdateStackCoreIMSBindingCommandHandler : IRequestHandler<UpdateStackCoreIMSBindingCommand, OperationResult<UpdateStackCoreIMSBindingResponse>>
{
private readonly IStack_CoreIMS_BindingRepository _stackCoreIMSBindingRepository;
private readonly ILogger<UpdateStackCoreIMSBindingCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ICurrentUserService _currentUserService;
/// <summary>
/// 初始化命令处理器
/// </summary>
public UpdateStackCoreIMSBindingCommandHandler(
IStack_CoreIMS_BindingRepository stackCoreIMSBindingRepository,
ILogger<UpdateStackCoreIMSBindingCommandHandler> logger,
IUnitOfWork unitOfWork,
ICurrentUserService currentUserService)
{
_stackCoreIMSBindingRepository = stackCoreIMSBindingRepository;
_logger = logger;
_unitOfWork = unitOfWork;
_currentUserService = currentUserService;
}
/// <summary>
/// 处理更新栈与核心网/IMS绑定关系命令
/// </summary>
public async Task<OperationResult<UpdateStackCoreIMSBindingResponse>> Handle(UpdateStackCoreIMSBindingCommand request, CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("开始更新栈与核心网/IMS绑定关系,绑定ID: {StackCoreIMSBindingId}", request.StackCoreIMSBindingId);
// 检查绑定关系是否存在
var existingBinding = await _stackCoreIMSBindingRepository.GetBindingByIdAsync(request.StackCoreIMSBindingId, cancellationToken);
if (existingBinding == null)
{
_logger.LogWarning("栈与核心网/IMS绑定关系不存在: {StackCoreIMSBindingId}", request.StackCoreIMSBindingId);
return OperationResult<UpdateStackCoreIMSBindingResponse>.CreateFailure($"栈与核心网/IMS绑定关系 {request.StackCoreIMSBindingId} 不存在");
}
// 检查新索引是否与其他绑定关系冲突
if (request.Index != existingBinding.Index)
{
if (await _stackCoreIMSBindingRepository.StackIdAndIndexExistsAsync(existingBinding.StackId, request.Index, cancellationToken))
{
_logger.LogWarning("栈与核心网/IMS绑定关系索引已存在,栈ID: {StackId}, 索引: {Index}", existingBinding.StackId, request.Index);
return OperationResult<UpdateStackCoreIMSBindingResponse>.CreateFailure($"栈与核心网/IMS绑定关系索引已存在,栈ID: {existingBinding.StackId}, 索引: {request.Index}");
}
}
// 获取当前用户ID
var currentUserId = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUserId))
{
_logger.LogError("无法获取当前用户ID,用户可能未认证");
return OperationResult<UpdateStackCoreIMSBindingResponse>.CreateFailure("用户未认证,无法更新栈与核心网/IMS绑定关系");
}
// 更新绑定关系
existingBinding.Update(
index: request.Index,
cnId: request.CnId,
imsId: request.ImsId,
updatedBy: currentUserId);
// 保存更改
_stackCoreIMSBindingRepository.UpdateBinding(existingBinding);
await _unitOfWork.SaveChangesAsync(cancellationToken);
// 构建响应
var response = new UpdateStackCoreIMSBindingResponse
{
StackCoreIMSBindingId = existingBinding.Id,
StackId = existingBinding.StackId,
Index = existingBinding.Index,
CnId = existingBinding.CnId,
ImsId = existingBinding.ImsId,
UpdatedAt = existingBinding.UpdatedAt
};
_logger.LogInformation("栈与核心网/IMS绑定关系更新成功,绑定ID: {BindingId}, 栈ID: {StackId}, 索引: {Index}",
existingBinding.Id, existingBinding.StackId, existingBinding.Index);
return OperationResult<UpdateStackCoreIMSBindingResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "更新栈与核心网/IMS绑定关系时发生错误,绑定ID: {StackCoreIMSBindingId}", request.StackCoreIMSBindingId);
return OperationResult<UpdateStackCoreIMSBindingResponse>.CreateFailure($"更新栈与核心网/IMS绑定关系时发生错误: {ex.Message}");
}
}
}

37
src/X1.Application/Features/StackCoreIMSBindings/Commands/UpdateStackCoreIMSBinding/UpdateStackCoreIMSBindingResponse.cs

@ -1,37 +0,0 @@
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Commands.UpdateStackCoreIMSBinding;
/// <summary>
/// 更新栈与核心网/IMS绑定关系响应
/// </summary>
public class UpdateStackCoreIMSBindingResponse
{
/// <summary>
/// 绑定关系ID
/// </summary>
public string StackCoreIMSBindingId { get; set; } = null!;
/// <summary>
/// 栈ID(外键)
/// </summary>
public string StackId { get; set; } = null!;
/// <summary>
/// 索引(主键的一部分)
/// </summary>
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
public string CnId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
public string ImsId { get; set; } = null!;
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdatedAt { get; set; }
}

17
src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindingById/GetStackCoreIMSBindingByIdQuery.cs

@ -1,17 +0,0 @@
using CellularManagement.Domain.Common;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Queries.GetStackCoreIMSBindingById;
/// <summary>
/// 根据ID获取栈与核心网/IMS绑定关系查询
/// </summary>
public class GetStackCoreIMSBindingByIdQuery : IRequest<OperationResult<GetStackCoreIMSBindingByIdResponse>>
{
/// <summary>
/// 绑定关系ID
/// </summary>
[Required]
public string StackCoreIMSBindingId { get; set; } = null!;
}

68
src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindingById/GetStackCoreIMSBindingByIdQueryHandler.cs

@ -1,68 +0,0 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Repositories.NetworkProfile;
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Queries.GetStackCoreIMSBindingById;
/// <summary>
/// 根据ID获取栈与核心网/IMS绑定关系查询处理器
/// </summary>
public class GetStackCoreIMSBindingByIdQueryHandler : IRequestHandler<GetStackCoreIMSBindingByIdQuery, OperationResult<GetStackCoreIMSBindingByIdResponse>>
{
private readonly IStack_CoreIMS_BindingRepository _stackCoreIMSBindingRepository;
private readonly ILogger<GetStackCoreIMSBindingByIdQueryHandler> _logger;
/// <summary>
/// 初始化查询处理器
/// </summary>
public GetStackCoreIMSBindingByIdQueryHandler(
IStack_CoreIMS_BindingRepository stackCoreIMSBindingRepository,
ILogger<GetStackCoreIMSBindingByIdQueryHandler> logger)
{
_stackCoreIMSBindingRepository = stackCoreIMSBindingRepository;
_logger = logger;
}
/// <summary>
/// 处理根据ID获取栈与核心网/IMS绑定关系查询
/// </summary>
public async Task<OperationResult<GetStackCoreIMSBindingByIdResponse>> Handle(GetStackCoreIMSBindingByIdQuery request, CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("开始获取栈与核心网/IMS绑定关系,绑定ID: {StackCoreIMSBindingId}", request.StackCoreIMSBindingId);
// 获取绑定关系
var binding = await _stackCoreIMSBindingRepository.GetBindingByIdAsync(request.StackCoreIMSBindingId, cancellationToken);
if (binding == null)
{
_logger.LogWarning("栈与核心网/IMS绑定关系不存在: {StackCoreIMSBindingId}", request.StackCoreIMSBindingId);
return OperationResult<GetStackCoreIMSBindingByIdResponse>.CreateFailure($"栈与核心网/IMS绑定关系 {request.StackCoreIMSBindingId} 不存在");
}
// 构建响应
var response = new GetStackCoreIMSBindingByIdResponse
{
StackCoreIMSBindingId = binding.Id,
StackId = binding.StackId,
Index = binding.Index,
CnId = binding.CnId,
ImsId = binding.ImsId,
CreatedAt = binding.CreatedAt,
UpdatedAt = binding.UpdatedAt,
CreatedBy = binding.CreatedBy,
UpdatedBy = binding.UpdatedBy
};
_logger.LogInformation("成功获取栈与核心网/IMS绑定关系,绑定ID: {StackCoreIMSBindingId}, 栈ID: {StackId}, 索引: {Index}",
binding.Id, binding.StackId, binding.Index);
return OperationResult<GetStackCoreIMSBindingByIdResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取栈与核心网/IMS绑定关系时发生错误,绑定ID: {StackCoreIMSBindingId}", request.StackCoreIMSBindingId);
return OperationResult<GetStackCoreIMSBindingByIdResponse>.CreateFailure($"获取栈与核心网/IMS绑定关系时发生错误: {ex.Message}");
}
}
}

52
src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindingById/GetStackCoreIMSBindingByIdResponse.cs

@ -1,52 +0,0 @@
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Queries.GetStackCoreIMSBindingById;
/// <summary>
/// 根据ID获取栈与核心网/IMS绑定关系响应
/// </summary>
public class GetStackCoreIMSBindingByIdResponse
{
/// <summary>
/// 绑定关系ID
/// </summary>
public string StackCoreIMSBindingId { get; set; } = null!;
/// <summary>
/// 栈ID(外键)
/// </summary>
public string StackId { get; set; } = null!;
/// <summary>
/// 索引(主键的一部分)
/// </summary>
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
public string CnId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
public string ImsId { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdatedAt { get; set; }
/// <summary>
/// 创建者
/// </summary>
public string CreatedBy { get; set; } = null!;
/// <summary>
/// 更新者
/// </summary>
public string UpdatedBy { get; set; } = null!;
}

41
src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindings/GetStackCoreIMSBindingsQuery.cs

@ -1,41 +0,0 @@
using CellularManagement.Domain.Common;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Queries.GetStackCoreIMSBindings;
/// <summary>
/// 获取栈与核心网/IMS绑定关系列表查询
/// </summary>
public class GetStackCoreIMSBindingsQuery : IRequest<OperationResult<GetStackCoreIMSBindingsResponse>>
{
/// <summary>
/// 页码
/// </summary>
[Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")]
public int PageNumber { get; set; } = 1;
/// <summary>
/// 每页数量
/// </summary>
[Range(1, 100, ErrorMessage = "每页数量必须在1-100之间")]
public int PageSize { get; set; } = 10;
/// <summary>
/// 栈ID(可选过滤条件)
/// </summary>
[MaxLength(50)]
public string? StackId { get; set; }
/// <summary>
/// 核心网配置ID(可选过滤条件)
/// </summary>
[MaxLength(50)]
public string? CnId { get; set; }
/// <summary>
/// IMS配置ID(可选过滤条件)
/// </summary>
[MaxLength(50)]
public string? ImsId { get; set; }
}

80
src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindings/GetStackCoreIMSBindingsQueryHandler.cs

@ -1,80 +0,0 @@
using MediatR;
using Microsoft.Extensions.Logging;
using CellularManagement.Domain.Common;
using CellularManagement.Domain.Repositories.NetworkProfile;
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Queries.GetStackCoreIMSBindings;
/// <summary>
/// 获取栈与核心网/IMS绑定关系列表查询处理器
/// </summary>
public class GetStackCoreIMSBindingsQueryHandler : IRequestHandler<GetStackCoreIMSBindingsQuery, OperationResult<GetStackCoreIMSBindingsResponse>>
{
private readonly IStack_CoreIMS_BindingRepository _stackCoreIMSBindingRepository;
private readonly ILogger<GetStackCoreIMSBindingsQueryHandler> _logger;
/// <summary>
/// 初始化查询处理器
/// </summary>
public GetStackCoreIMSBindingsQueryHandler(
IStack_CoreIMS_BindingRepository stackCoreIMSBindingRepository,
ILogger<GetStackCoreIMSBindingsQueryHandler> logger)
{
_stackCoreIMSBindingRepository = stackCoreIMSBindingRepository;
_logger = logger;
}
/// <summary>
/// 处理获取栈与核心网/IMS绑定关系列表查询
/// </summary>
public async Task<OperationResult<GetStackCoreIMSBindingsResponse>> Handle(GetStackCoreIMSBindingsQuery request, CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("开始获取栈与核心网/IMS绑定关系列表,页码: {PageNumber}, 每页数量: {PageSize}, 栈ID: {StackId}, 核心网配置ID: {CnId}, IMS配置ID: {ImsId}",
request.PageNumber, request.PageSize, request.StackId ?? "无", request.CnId ?? "无", request.ImsId ?? "无");
// 获取绑定关系列表
var (totalCount, items) = await _stackCoreIMSBindingRepository.SearchBindingsAsync(
request.StackId,
request.CnId,
request.ImsId,
request.PageNumber,
request.PageSize,
cancellationToken);
// 计算总页数
var totalPages = (int)Math.Ceiling((double)totalCount / request.PageSize);
// 构建响应
var response = new GetStackCoreIMSBindingsResponse
{
TotalCount = totalCount,
PageNumber = request.PageNumber,
PageSize = request.PageSize,
TotalPages = totalPages,
Items = items.Select(item => new StackCoreIMSBindingDto
{
StackCoreIMSBindingId = item.Id,
StackId = item.StackId,
Index = item.Index,
CnId = item.CnId,
ImsId = item.ImsId,
CreatedAt = item.CreatedAt,
UpdatedAt = item.UpdatedAt,
CreatedBy = item.CreatedBy,
UpdatedBy = item.UpdatedBy
}).ToList()
};
_logger.LogInformation("成功获取栈与核心网/IMS绑定关系列表,总数量: {TotalCount}, 当前页数量: {ItemCount}",
totalCount, items.Count);
return OperationResult<GetStackCoreIMSBindingsResponse>.CreateSuccess(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取栈与核心网/IMS绑定关系列表时发生错误");
return OperationResult<GetStackCoreIMSBindingsResponse>.CreateFailure($"获取栈与核心网/IMS绑定关系列表时发生错误: {ex.Message}");
}
}
}

83
src/X1.Application/Features/StackCoreIMSBindings/Queries/GetStackCoreIMSBindings/GetStackCoreIMSBindingsResponse.cs

@ -1,83 +0,0 @@
namespace CellularManagement.Application.Features.StackCoreIMSBindings.Queries.GetStackCoreIMSBindings;
/// <summary>
/// 获取栈与核心网/IMS绑定关系列表响应
/// </summary>
public class GetStackCoreIMSBindingsResponse
{
/// <summary>
/// 总数量
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 当前页码
/// </summary>
public int PageNumber { get; set; }
/// <summary>
/// 每页数量
/// </summary>
public int PageSize { get; set; }
/// <summary>
/// 总页数
/// </summary>
public int TotalPages { get; set; }
/// <summary>
/// 栈与核心网/IMS绑定关系列表
/// </summary>
public List<StackCoreIMSBindingDto> Items { get; set; } = new();
}
/// <summary>
/// 栈与核心网/IMS绑定关系数据传输对象
/// </summary>
public class StackCoreIMSBindingDto
{
/// <summary>
/// 绑定关系ID
/// </summary>
public string StackCoreIMSBindingId { get; set; } = null!;
/// <summary>
/// 栈ID(外键)
/// </summary>
public string StackId { get; set; } = null!;
/// <summary>
/// 索引(主键的一部分)
/// </summary>
public int Index { get; set; }
/// <summary>
/// 核心网配置ID(外键)
/// </summary>
public string CnId { get; set; } = null!;
/// <summary>
/// IMS配置ID(外键)
/// </summary>
public string ImsId { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdatedAt { get; set; }
/// <summary>
/// 创建者
/// </summary>
public string CreatedBy { get; set; } = null!;
/// <summary>
/// 更新者
/// </summary>
public string UpdatedBy { get; set; } = null!;
}

15
src/X1.Domain/Entities/NetworkProfile/NetworkStackConfig.cs

@ -11,11 +11,11 @@ public class NetworkStackConfig : AuditableEntity
private NetworkStackConfig() { }
/// <summary>
/// 栈ID(主键)
/// 网络栈名称
/// </summary>
[Required]
[MaxLength(50)]
public string StackId { get; private set; } = null!;
[MaxLength(100)]
public string NetworkStackName { get; private set; } = null!;
/// <summary>
/// RAN配置ID(外键,可为空)
@ -43,7 +43,7 @@ public class NetworkStackConfig : AuditableEntity
/// 创建网络栈配置
/// </summary>
public static NetworkStackConfig Create(
string stackId,
string networkStackName,
string createdBy,
string? ranId = null,
string? description = null,
@ -52,7 +52,7 @@ public class NetworkStackConfig : AuditableEntity
var networkStackConfig = new NetworkStackConfig
{
Id = Guid.NewGuid().ToString(),
StackId = stackId,
NetworkStackName = networkStackName,
RanId = ranId,
Description = description,
IsActive = isActive,
@ -70,10 +70,15 @@ public class NetworkStackConfig : AuditableEntity
/// </summary>
public void Update(
string updatedBy,
string? networkStackName = null,
string? ranId = null,
string? description = null,
bool isActive = true)
{
if (!string.IsNullOrWhiteSpace(networkStackName))
{
NetworkStackName = networkStackName;
}
RanId = ranId;
Description = description;
IsActive = isActive;

25
src/X1.Domain/Entities/NetworkProfile/Stack_CoreIMS_Binding.cs

@ -1,21 +1,22 @@
using System.ComponentModel.DataAnnotations;
using CellularManagement.Domain.Entities.Common;
using CellularManagement.Domain.Abstractions;
namespace CellularManagement.Domain.Entities.NetworkProfile;
/// <summary>
/// 栈与核心网/IMS绑定关系实体
/// </summary>
public class Stack_CoreIMS_Binding : AuditableEntity
public class Stack_CoreIMS_Binding : Entity
{
private Stack_CoreIMS_Binding() { }
/// <summary>
/// 栈ID(外键)
/// 网络配置ID(外键)
/// </summary>
[Required]
[MaxLength(50)]
public string StackId { get; private set; } = null!;
public string NetworkStackConfigId { get; private set; } = null!;
/// <summary>
/// 索引(主键的一部分)
@ -56,23 +57,18 @@ public class Stack_CoreIMS_Binding : AuditableEntity
/// 创建栈与核心网/IMS绑定关系
/// </summary>
public static Stack_CoreIMS_Binding Create(
string stackId,
string networkStackConfigId,
int index,
string cnId,
string imsId,
string createdBy)
string imsId)
{
var binding = new Stack_CoreIMS_Binding
{
Id = Guid.NewGuid().ToString(),
StackId = stackId,
NetworkStackConfigId = networkStackConfigId,
Index = index,
CnId = cnId,
ImsId = imsId,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow,
CreatedBy = createdBy,
UpdatedBy = createdBy
ImsId = imsId
};
return binding;
@ -84,13 +80,10 @@ public class Stack_CoreIMS_Binding : AuditableEntity
public void Update(
int index,
string cnId,
string imsId,
string updatedBy)
string imsId)
{
Index = index;
CnId = cnId;
ImsId = imsId;
UpdatedAt = DateTime.UtcNow;
UpdatedBy = updatedBy;
}
}

62
src/X1.Domain/Repositories/NetworkProfile/INetworkStackConfigRepository.cs

@ -9,68 +9,64 @@ namespace CellularManagement.Domain.Repositories.NetworkProfile;
public interface INetworkStackConfigRepository : IBaseRepository<NetworkStackConfig>
{
/// <summary>
/// 添加网络栈配置
/// 根据ID获取网络栈配置
/// </summary>
Task<NetworkStackConfig> AddNetworkStackConfigAsync(NetworkStackConfig networkStackConfig, CancellationToken cancellationToken = default);
Task<NetworkStackConfig?> GetNetworkStackConfigByIdAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 更新网络栈配置
/// 根据网络栈名称获取网络栈配置
/// </summary>
void UpdateNetworkStackConfig(NetworkStackConfig networkStackConfig);
Task<NetworkStackConfig?> GetNetworkStackConfigByNameAsync(string networkStackName, CancellationToken cancellationToken = default);
/// <summary>
/// 删除网络栈配置
/// 根据ID获取网络栈配置(包含绑定关系)
/// </summary>
Task DeleteNetworkStackConfigAsync(string id, CancellationToken cancellationToken = default);
Task<NetworkStackConfig?> GetNetworkStackConfigByIdWithBindingsAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 获取所有网络栈配置
/// 添加网络栈配置
/// </summary>
Task<IList<NetworkStackConfig>> GetAllNetworkStackConfigsAsync(CancellationToken cancellationToken = default);
Task AddNetworkStackConfigAsync(NetworkStackConfig networkStackConfig, CancellationToken cancellationToken = default);
/// <summary>
/// 根据ID获取网络栈配置
/// 更新网络栈配置
/// </summary>
Task<NetworkStackConfig?> GetNetworkStackConfigByIdAsync(string id, CancellationToken cancellationToken = default);
void UpdateNetworkStackConfig(NetworkStackConfig networkStackConfig);
/// <summary>
/// 根据栈ID获取网络栈配置
/// 删除网络栈配置
/// </summary>
Task<NetworkStackConfig?> GetNetworkStackConfigByStackIdAsync(string stackId, CancellationToken cancellationToken = default);
Task DeleteNetworkStackConfigAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 根据RAN ID获取网络栈配置
/// 检查网络栈配置是否存在
/// </summary>
Task<IList<NetworkStackConfig>> GetNetworkStackConfigsByRanIdAsync(string ranId, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 获取激活的网络栈配置
/// 检查网络栈名称是否存在
/// </summary>
Task<IList<NetworkStackConfig>> GetActiveNetworkStackConfigsAsync(CancellationToken cancellationToken = default);
Task<bool> NameExistsAsync(string networkStackName, CancellationToken cancellationToken = default);
/// <summary>
/// 搜索网络栈配置
/// </summary>
Task<IList<NetworkStackConfig>> SearchNetworkStackConfigsAsync(
string? keyword,
CancellationToken cancellationToken = default);
/// <summary>
/// 搜索网络栈配置(分页)
/// </summary>
Task<(int TotalCount, IList<NetworkStackConfig> Items)> SearchNetworkStackConfigsAsync(
string? keyword,
int pageNumber,
int pageSize,
string? networkStackName = null,
string? ranId = null,
bool? isActive = null,
int pageNumber = 1,
int pageSize = 10,
CancellationToken cancellationToken = default);
/// <summary>
/// 检查网络栈配置是否存在
/// </summary>
Task<bool> ExistsAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 检查栈ID是否存在
/// 搜索网络栈配置(包含绑定关系)
/// </summary>
Task<bool> StackIdExistsAsync(string stackId, CancellationToken cancellationToken = default);
Task<(int TotalCount, IList<NetworkStackConfig> Items)> SearchNetworkStackConfigsWithBindingsAsync(
string? networkStackName = null,
string? ranId = null,
bool? isActive = null,
int pageNumber = 1,
int pageSize = 10,
CancellationToken cancellationToken = default);
}

16
src/X1.Domain/Repositories/NetworkProfile/IStack_CoreIMS_BindingRepository.cs

@ -34,9 +34,9 @@ public interface IStack_CoreIMS_BindingRepository : IBaseRepository<Stack_CoreIM
Task<Stack_CoreIMS_Binding?> GetBindingByIdAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 根据栈ID获取绑定关系
/// 根据网络配置ID获取绑定关系
/// </summary>
Task<IList<Stack_CoreIMS_Binding>> GetBindingsByStackIdAsync(string stackId, CancellationToken cancellationToken = default);
Task<IList<Stack_CoreIMS_Binding>> GetBindingsByNetworkStackConfigIdAsync(string networkStackConfigId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据核心网配置ID获取绑定关系
@ -49,15 +49,15 @@ public interface IStack_CoreIMS_BindingRepository : IBaseRepository<Stack_CoreIM
Task<IList<Stack_CoreIMS_Binding>> GetBindingsByImsIdAsync(string imsId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据栈ID和索引获取绑定关系
/// 根据网络配置ID和索引获取绑定关系
/// </summary>
Task<Stack_CoreIMS_Binding?> GetBindingByStackIdAndIndexAsync(string stackId, int index, CancellationToken cancellationToken = default);
Task<Stack_CoreIMS_Binding?> GetBindingByNetworkStackConfigIdAndIndexAsync(string networkStackConfigId, int index, CancellationToken cancellationToken = default);
/// <summary>
/// 搜索绑定关系
/// </summary>
Task<IList<Stack_CoreIMS_Binding>> SearchBindingsAsync(
string? stackId = null,
string? networkStackConfigId = null,
string? cnId = null,
string? imsId = null,
CancellationToken cancellationToken = default);
@ -66,7 +66,7 @@ public interface IStack_CoreIMS_BindingRepository : IBaseRepository<Stack_CoreIM
/// 搜索绑定关系(分页)
/// </summary>
Task<(int TotalCount, IList<Stack_CoreIMS_Binding> Items)> SearchBindingsAsync(
string? stackId = null,
string? networkStackConfigId = null,
string? cnId = null,
string? imsId = null,
int pageNumber = 1,
@ -79,7 +79,7 @@ public interface IStack_CoreIMS_BindingRepository : IBaseRepository<Stack_CoreIM
Task<bool> ExistsAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// 检查栈ID和索引的组合是否存在
/// 检查网络配置ID和索引的组合是否存在
/// </summary>
Task<bool> StackIdAndIndexExistsAsync(string stackId, int index, CancellationToken cancellationToken = default);
Task<bool> NetworkStackConfigIdAndIndexExistsAsync(string networkStackConfigId, int index, CancellationToken cancellationToken = default);
}

8
src/X1.Infrastructure/Configurations/NetworkProfile/NetworkStackConfigConfiguration.cs

@ -12,13 +12,13 @@ public class NetworkStackConfigConfiguration : IEntityTypeConfiguration<NetworkS
builder.HasKey(nsc => nsc.Id);
// 配置索引
builder.HasIndex(nsc => nsc.StackId).IsUnique().HasDatabaseName("IX_NetworkStackConfigs_StackId");
builder.HasIndex(nsc => nsc.NetworkStackName).IsUnique().HasDatabaseName("IX_NetworkStackConfigs_NetworkStackName");
builder.HasIndex(nsc => nsc.RanId).HasDatabaseName("IX_NetworkStackConfigs_RanId");
builder.HasIndex(nsc => nsc.IsActive).HasDatabaseName("IX_NetworkStackConfigs_IsActive");
// 配置属性
builder.Property(nsc => nsc.Id).HasComment("配置ID");
builder.Property(nsc => nsc.StackId).IsRequired().HasMaxLength(50).HasComment("栈ID");
builder.Property(nsc => nsc.NetworkStackName).IsRequired().HasMaxLength(100).HasComment("网络栈名称");
builder.Property(nsc => nsc.RanId).HasMaxLength(50).HasComment("RAN配置ID");
builder.Property(nsc => nsc.Description).HasMaxLength(500).HasComment("描述");
builder.Property(nsc => nsc.IsActive).IsRequired().HasComment("是否激活");
@ -35,8 +35,8 @@ public class NetworkStackConfigConfiguration : IEntityTypeConfiguration<NetworkS
// 配置与Stack_CoreIMS_Binding的一对多关系
builder.HasMany(nsc => nsc.StackCoreIMSBindings)
.WithOne(binding => binding.NetworkStackConfig)
.HasForeignKey(binding => binding.StackId)
.HasPrincipalKey(nsc => nsc.StackId)
.HasForeignKey(binding => binding.NetworkStackConfigId)
.HasPrincipalKey(nsc => nsc.Id)
.OnDelete(DeleteBehavior.Cascade);
}
}

26
src/X1.Infrastructure/Configurations/NetworkProfile/Stack_CoreIMS_BindingConfiguration.cs

@ -11,37 +11,37 @@ public class Stack_CoreIMS_BindingConfiguration : IEntityTypeConfiguration<Stack
builder.ToTable("Stack_CoreIMS_Bindings", t => t.HasComment("栈与核心网/IMS绑定关系表"));
builder.HasKey(binding => binding.Id);
// 配置复合唯一索引:StackId + Index
builder.HasIndex(binding => new { binding.StackId, binding.Index })
// 配置复合唯一索引:NetworkStackConfigId + Index
builder.HasIndex(binding => new { binding.NetworkStackConfigId, binding.Index })
.IsUnique()
.HasDatabaseName("IX_Stack_CoreIMS_Bindings_StackId_Index");
.HasDatabaseName("IX_Stack_CoreIMS_Bindings_NetworkStackConfigId_Index");
// 配置索引
builder.HasIndex(binding => binding.StackId).HasDatabaseName("IX_Stack_CoreIMS_Bindings_StackId");
builder.HasIndex(binding => binding.NetworkStackConfigId).HasDatabaseName("IX_Stack_CoreIMS_Bindings_NetworkStackConfigId");
builder.HasIndex(binding => binding.CnId).HasDatabaseName("IX_Stack_CoreIMS_Bindings_CnId");
builder.HasIndex(binding => binding.ImsId).HasDatabaseName("IX_Stack_CoreIMS_Bindings_ImsId");
// 配置属性
builder.Property(binding => binding.Id).HasComment("绑定关系ID");
builder.Property(binding => binding.StackId).IsRequired().HasMaxLength(50).HasComment("栈ID");
builder.Property(binding => binding.NetworkStackConfigId).IsRequired().HasMaxLength(50).HasComment("网络配置ID");
builder.Property(binding => binding.Index).IsRequired().HasComment("索引");
builder.Property(binding => binding.CnId).IsRequired().HasMaxLength(50).HasComment("核心网配置ID");
builder.Property(binding => binding.ImsId).IsRequired().HasMaxLength(50).HasComment("IMS配置ID");
builder.Property(binding => binding.CreatedAt).IsRequired().HasColumnType("timestamp with time zone").HasComment("创建时间");
builder.Property(binding => binding.UpdatedAt).IsRequired().HasColumnType("timestamp with time zone").HasComment("更新时间");
// 配置与CoreNetworkConfig的关系
// 配置关系
builder.HasOne(binding => binding.NetworkStackConfig)
.WithMany(config => config.StackCoreIMSBindings)
.HasForeignKey(binding => binding.NetworkStackConfigId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(binding => binding.CoreNetworkConfig)
.WithMany()
.HasForeignKey(binding => binding.CnId)
.HasPrincipalKey(cnc => cnc.Id)
.OnDelete(DeleteBehavior.Cascade);
.OnDelete(DeleteBehavior.Restrict);
// 配置与IMS_Configuration的关系
builder.HasOne(binding => binding.IMSConfiguration)
.WithMany()
.HasForeignKey(binding => binding.ImsId)
.HasPrincipalKey(ic => ic.Id)
.OnDelete(DeleteBehavior.Cascade);
.OnDelete(DeleteBehavior.Restrict);
}
}

122
src/X1.Infrastructure/Repositories/NetworkProfile/NetworkStackConfigRepository.cs

@ -1,5 +1,6 @@
using System.Linq.Expressions;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using CellularManagement.Domain.Entities.NetworkProfile;
using CellularManagement.Domain.Repositories.Base;
using CellularManagement.Domain.Repositories.NetworkProfile;
@ -29,10 +30,9 @@ public class NetworkStackConfigRepository : BaseRepository<NetworkStackConfig>,
/// <summary>
/// 添加网络栈配置
/// </summary>
public async Task<NetworkStackConfig> AddNetworkStackConfigAsync(NetworkStackConfig networkStackConfig, CancellationToken cancellationToken = default)
public async Task AddNetworkStackConfigAsync(NetworkStackConfig networkStackConfig, CancellationToken cancellationToken = default)
{
await CommandRepository.AddAsync(networkStackConfig, cancellationToken);
return networkStackConfig;
}
/// <summary>
@ -57,7 +57,7 @@ public class NetworkStackConfigRepository : BaseRepository<NetworkStackConfig>,
public async Task<IList<NetworkStackConfig>> GetAllNetworkStackConfigsAsync(CancellationToken cancellationToken = default)
{
var configs = await QueryRepository.GetAllAsync(cancellationToken: cancellationToken);
return configs.OrderBy(x => x.StackId).ToList();
return configs.OrderBy(x => x.NetworkStackName).ToList();
}
/// <summary>
@ -69,11 +69,11 @@ public class NetworkStackConfigRepository : BaseRepository<NetworkStackConfig>,
}
/// <summary>
/// 根据栈ID获取网络栈配置
/// 根据网络栈名称获取网络栈配置
/// </summary>
public async Task<NetworkStackConfig?> GetNetworkStackConfigByStackIdAsync(string stackId, CancellationToken cancellationToken = default)
public async Task<NetworkStackConfig?> GetNetworkStackConfigByNameAsync(string networkStackName, CancellationToken cancellationToken = default)
{
return await QueryRepository.FirstOrDefaultAsync(nsc => nsc.StackId == stackId, cancellationToken: cancellationToken);
return await QueryRepository.FirstOrDefaultAsync(nsc => nsc.NetworkStackName == networkStackName, cancellationToken: cancellationToken);
}
/// <summary>
@ -82,7 +82,7 @@ public class NetworkStackConfigRepository : BaseRepository<NetworkStackConfig>,
public async Task<IList<NetworkStackConfig>> GetNetworkStackConfigsByRanIdAsync(string ranId, CancellationToken cancellationToken = default)
{
var configs = await QueryRepository.FindAsync(nsc => nsc.RanId == ranId, cancellationToken: cancellationToken);
return configs.OrderBy(x => x.StackId).ToList();
return configs.OrderBy(x => x.NetworkStackName).ToList();
}
/// <summary>
@ -91,13 +91,13 @@ public class NetworkStackConfigRepository : BaseRepository<NetworkStackConfig>,
public async Task<IList<NetworkStackConfig>> GetActiveNetworkStackConfigsAsync(CancellationToken cancellationToken = default)
{
var configs = await QueryRepository.FindAsync(nsc => nsc.IsActive, cancellationToken: cancellationToken);
return configs.OrderBy(x => x.StackId).ToList();
return configs.OrderBy(x => x.NetworkStackName).ToList();
}
/// <summary>
/// 搜索网络栈配置
/// </summary>
public async Task<IList<NetworkStackConfig>> SearchNetworkStackConfigsAsync(
public async Task<(int TotalCount, IList<NetworkStackConfig> Items)> SearchNetworkStackConfigsAsync(
string? keyword,
CancellationToken cancellationToken = default)
{
@ -106,12 +106,12 @@ public class NetworkStackConfigRepository : BaseRepository<NetworkStackConfig>,
if (!string.IsNullOrWhiteSpace(keyword))
{
query = query.Where(nsc =>
nsc.StackId.Contains(keyword) ||
nsc.NetworkStackName.Contains(keyword) ||
nsc.Description.Contains(keyword));
}
var configs = query;
return configs.OrderBy(x => x.StackId).ToList();
return (configs.Count(), configs.OrderBy(x => x.NetworkStackName).ToList());
}
/// <summary>
@ -128,14 +128,14 @@ public class NetworkStackConfigRepository : BaseRepository<NetworkStackConfig>,
if (!string.IsNullOrWhiteSpace(keyword))
{
predicate = nsc => nsc.StackId.Contains(keyword) ||
predicate = nsc => nsc.NetworkStackName.Contains(keyword) ||
nsc.Description.Contains(keyword);
}
// 执行分页查询
var result = await QueryRepository.GetPagedAsync(predicate, pageNumber, pageSize, null, cancellationToken);
return (result.TotalCount, result.Items.OrderBy(x => x.StackId).ToList());
return (result.TotalCount, result.Items.OrderBy(x => x.NetworkStackName).ToList());
}
/// <summary>
@ -147,10 +147,100 @@ public class NetworkStackConfigRepository : BaseRepository<NetworkStackConfig>,
}
/// <summary>
/// 检查栈ID是否存在
/// 检查网络栈名称是否存在
/// </summary>
public async Task<bool> NameExistsAsync(string networkStackName, CancellationToken cancellationToken = default)
{
return await QueryRepository.AnyAsync(nsc => nsc.NetworkStackName == networkStackName, cancellationToken: cancellationToken);
}
/// <summary>
/// 根据ID获取网络栈配置(包含绑定关系)
/// </summary>
public async Task<NetworkStackConfig?> GetNetworkStackConfigByIdWithBindingsAsync(string id, CancellationToken cancellationToken = default)
{
return await QueryRepository.GetByIdAsync(
id,
include: query => query.Include(x => x.StackCoreIMSBindings),
cancellationToken: cancellationToken);
}
/// <summary>
/// 搜索网络栈配置
/// </summary>
public async Task<bool> StackIdExistsAsync(string stackId, CancellationToken cancellationToken = default)
public async Task<(int TotalCount, IList<NetworkStackConfig> Items)> SearchNetworkStackConfigsAsync(
string? networkStackName = null,
string? ranId = null,
bool? isActive = null,
int pageNumber = 1,
int pageSize = 10,
CancellationToken cancellationToken = default)
{
return await QueryRepository.AnyAsync(nsc => nsc.StackId == stackId, cancellationToken: cancellationToken);
// 构建查询条件
Expression<Func<NetworkStackConfig, bool>> predicate = nsc => true;
if (!string.IsNullOrWhiteSpace(networkStackName))
{
predicate = nsc => nsc.NetworkStackName.Contains(networkStackName);
}
if (!string.IsNullOrWhiteSpace(ranId))
{
var ranIdPredicate = predicate;
predicate = nsc => ranIdPredicate.Compile()(nsc) && nsc.RanId == ranId;
}
if (isActive.HasValue)
{
var isActivePredicate = predicate;
predicate = nsc => isActivePredicate.Compile()(nsc) && nsc.IsActive == isActive.Value;
}
// 执行分页查询
var result = await QueryRepository.GetPagedAsync(predicate, pageNumber, pageSize, cancellationToken: cancellationToken);
return (result.TotalCount, result.Items.OrderBy(x => x.NetworkStackName).ToList());
}
/// <summary>
/// 搜索网络栈配置(包含绑定关系)
/// </summary>
public async Task<(int TotalCount, IList<NetworkStackConfig> Items)> SearchNetworkStackConfigsWithBindingsAsync(
string? networkStackName = null,
string? ranId = null,
bool? isActive = null,
int pageNumber = 1,
int pageSize = 10,
CancellationToken cancellationToken = default)
{
// 构建查询条件
Expression<Func<NetworkStackConfig, bool>> predicate = nsc => true;
if (!string.IsNullOrWhiteSpace(networkStackName))
{
predicate = nsc => nsc.NetworkStackName.Contains(networkStackName);
}
if (!string.IsNullOrWhiteSpace(ranId))
{
var ranIdPredicate = predicate;
predicate = nsc => ranIdPredicate.Compile()(nsc) && nsc.RanId == ranId;
}
if (isActive.HasValue)
{
var isActivePredicate = predicate;
predicate = nsc => isActivePredicate.Compile()(nsc) && nsc.IsActive == isActive.Value;
}
// 执行分页查询(包含导航属性)
var result = await QueryRepository.GetPagedAsync(
predicate,
pageNumber,
pageSize,
include: query => query.Include(x => x.StackCoreIMSBindings),
cancellationToken: cancellationToken);
return (result.TotalCount, result.Items.OrderBy(x => x.NetworkStackName).ToList());
}
}

40
src/X1.Infrastructure/Repositories/NetworkProfile/Stack_CoreIMS_BindingRepository.cs

@ -57,7 +57,7 @@ public class Stack_CoreIMS_BindingRepository : BaseRepository<Stack_CoreIMS_Bind
public async Task<IList<Stack_CoreIMS_Binding>> GetAllBindingsAsync(CancellationToken cancellationToken = default)
{
var bindings = await QueryRepository.GetAllAsync(cancellationToken: cancellationToken);
return bindings.OrderBy(x => x.StackId).ThenBy(x => x.Index).ToList();
return bindings.OrderBy(x => x.NetworkStackConfigId).ThenBy(x => x.Index).ToList();
}
/// <summary>
@ -69,11 +69,11 @@ public class Stack_CoreIMS_BindingRepository : BaseRepository<Stack_CoreIMS_Bind
}
/// <summary>
/// 根据栈ID获取绑定关系
/// 根据网络配置ID获取绑定关系
/// </summary>
public async Task<IList<Stack_CoreIMS_Binding>> GetBindingsByStackIdAsync(string stackId, CancellationToken cancellationToken = default)
public async Task<IList<Stack_CoreIMS_Binding>> GetBindingsByNetworkStackConfigIdAsync(string networkStackConfigId, CancellationToken cancellationToken = default)
{
var bindings = await QueryRepository.FindAsync(binding => binding.StackId == stackId, cancellationToken: cancellationToken);
var bindings = await QueryRepository.FindAsync(binding => binding.NetworkStackConfigId == networkStackConfigId, cancellationToken: cancellationToken);
return bindings.OrderBy(x => x.Index).ToList();
}
@ -83,7 +83,7 @@ public class Stack_CoreIMS_BindingRepository : BaseRepository<Stack_CoreIMS_Bind
public async Task<IList<Stack_CoreIMS_Binding>> GetBindingsByCnIdAsync(string cnId, CancellationToken cancellationToken = default)
{
var bindings = await QueryRepository.FindAsync(binding => binding.CnId == cnId, cancellationToken: cancellationToken);
return bindings.OrderBy(x => x.StackId).ThenBy(x => x.Index).ToList();
return bindings.OrderBy(x => x.NetworkStackConfigId).ThenBy(x => x.Index).ToList();
}
/// <summary>
@ -92,31 +92,31 @@ public class Stack_CoreIMS_BindingRepository : BaseRepository<Stack_CoreIMS_Bind
public async Task<IList<Stack_CoreIMS_Binding>> GetBindingsByImsIdAsync(string imsId, CancellationToken cancellationToken = default)
{
var bindings = await QueryRepository.FindAsync(binding => binding.ImsId == imsId, cancellationToken: cancellationToken);
return bindings.OrderBy(x => x.StackId).ThenBy(x => x.Index).ToList();
return bindings.OrderBy(x => x.NetworkStackConfigId).ThenBy(x => x.Index).ToList();
}
/// <summary>
/// 根据栈ID和索引获取绑定关系
/// 根据网络配置ID和索引获取绑定关系
/// </summary>
public async Task<Stack_CoreIMS_Binding?> GetBindingByStackIdAndIndexAsync(string stackId, int index, CancellationToken cancellationToken = default)
public async Task<Stack_CoreIMS_Binding?> GetBindingByNetworkStackConfigIdAndIndexAsync(string networkStackConfigId, int index, CancellationToken cancellationToken = default)
{
return await QueryRepository.FirstOrDefaultAsync(binding => binding.StackId == stackId && binding.Index == index, cancellationToken: cancellationToken);
return await QueryRepository.FirstOrDefaultAsync(binding => binding.NetworkStackConfigId == networkStackConfigId && binding.Index == index, cancellationToken: cancellationToken);
}
/// <summary>
/// 搜索绑定关系
/// </summary>
public async Task<IList<Stack_CoreIMS_Binding>> SearchBindingsAsync(
string? stackId = null,
string? networkStackConfigId = null,
string? cnId = null,
string? imsId = null,
CancellationToken cancellationToken = default)
{
var query = await QueryRepository.FindAsync(binding => true, cancellationToken: cancellationToken);
if (!string.IsNullOrWhiteSpace(stackId))
if (!string.IsNullOrWhiteSpace(networkStackConfigId))
{
query = query.Where(binding => binding.StackId == stackId);
query = query.Where(binding => binding.NetworkStackConfigId == networkStackConfigId);
}
if (!string.IsNullOrWhiteSpace(cnId))
@ -130,14 +130,14 @@ public class Stack_CoreIMS_BindingRepository : BaseRepository<Stack_CoreIMS_Bind
}
var bindings = query;
return bindings.OrderBy(x => x.StackId).ThenBy(x => x.Index).ToList();
return bindings.OrderBy(x => x.NetworkStackConfigId).ThenBy(x => x.Index).ToList();
}
/// <summary>
/// 搜索绑定关系(分页)
/// </summary>
public async Task<(int TotalCount, IList<Stack_CoreIMS_Binding> Items)> SearchBindingsAsync(
string? stackId = null,
string? networkStackConfigId = null,
string? cnId = null,
string? imsId = null,
int pageNumber = 1,
@ -147,9 +147,9 @@ public class Stack_CoreIMS_BindingRepository : BaseRepository<Stack_CoreIMS_Bind
// 构建查询条件
Expression<Func<Stack_CoreIMS_Binding, bool>> predicate = binding => true;
if (!string.IsNullOrWhiteSpace(stackId))
if (!string.IsNullOrWhiteSpace(networkStackConfigId))
{
predicate = binding => binding.StackId == stackId;
predicate = binding => binding.NetworkStackConfigId == networkStackConfigId;
}
if (!string.IsNullOrWhiteSpace(cnId))
@ -167,7 +167,7 @@ public class Stack_CoreIMS_BindingRepository : BaseRepository<Stack_CoreIMS_Bind
// 执行分页查询
var result = await QueryRepository.GetPagedAsync(predicate, pageNumber, pageSize, null, cancellationToken);
return (result.TotalCount, result.Items.OrderBy(x => x.StackId).ThenBy(x => x.Index).ToList());
return (result.TotalCount, result.Items.OrderBy(x => x.NetworkStackConfigId).ThenBy(x => x.Index).ToList());
}
/// <summary>
@ -179,10 +179,10 @@ public class Stack_CoreIMS_BindingRepository : BaseRepository<Stack_CoreIMS_Bind
}
/// <summary>
/// 检查栈ID和索引的组合是否存在
/// 检查网络配置ID和索引的组合是否存在
/// </summary>
public async Task<bool> StackIdAndIndexExistsAsync(string stackId, int index, CancellationToken cancellationToken = default)
public async Task<bool> NetworkStackConfigIdAndIndexExistsAsync(string networkStackConfigId, int index, CancellationToken cancellationToken = default)
{
return await QueryRepository.AnyAsync(binding => binding.StackId == stackId && binding.Index == index, cancellationToken: cancellationToken);
return await QueryRepository.AnyAsync(binding => binding.NetworkStackConfigId == networkStackConfigId && binding.Index == index, cancellationToken: cancellationToken);
}
}

8
src/X1.Presentation/Controllers/NetworkStackConfigsController.cs

@ -37,8 +37,8 @@ public class NetworkStackConfigsController : ApiController
[HttpGet]
public async Task<OperationResult<GetNetworkStackConfigsResponse>> GetAll([FromQuery] GetNetworkStackConfigsQuery query)
{
_logger.LogInformation("开始获取网络栈配置列表,页码: {PageNumber}, 每页数量: {PageSize}, 搜索关键词: {SearchTerm}, 是否激活: {IsActive}, RAN ID: {RanId}",
query.PageNumber, query.PageSize, query.SearchTerm, query.IsActive, query.RanId);
_logger.LogInformation("开始获取网络栈配置列表,页码: {PageNumber}, 每页数量: {PageSize}, 网络栈名称: {NetworkStackName}, 是否激活: {IsActive}, RAN ID: {RanId}",
query.PageNumber, query.PageSize, query.NetworkStackName, query.IsActive, query.RanId);
var result = await mediator.Send(query);
if (!result.IsSuccess)
@ -76,7 +76,7 @@ public class NetworkStackConfigsController : ApiController
[HttpPost]
public async Task<OperationResult<CreateNetworkStackConfigResponse>> Create([FromBody] CreateNetworkStackConfigCommand command)
{
_logger.LogInformation("开始创建网络栈配置,栈ID: {StackId}", command.StackId);
_logger.LogInformation("开始创建网络栈配置,网络栈名称: {NetworkStackName}", command.NetworkStackName);
var result = await mediator.Send(command);
if (!result.IsSuccess)
@ -118,7 +118,7 @@ public class NetworkStackConfigsController : ApiController
/// 删除网络栈配置
/// </summary>
[HttpDelete("{id}")]
public async Task<OperationResult<bool>> Delete(string id)
public async Task<OperationResult<DeleteNetworkStackConfigResponse>> Delete(string id)
{
_logger.LogInformation("开始删除网络栈配置,配置ID: {NetworkStackConfigId}", id);

136
src/X1.Presentation/Controllers/StackCoreIMSBindingsController.cs

@ -1,136 +0,0 @@
using CellularManagement.Application.Features.StackCoreIMSBindings.Commands.CreateStackCoreIMSBinding;
using CellularManagement.Application.Features.StackCoreIMSBindings.Commands.DeleteStackCoreIMSBinding;
using CellularManagement.Application.Features.StackCoreIMSBindings.Commands.UpdateStackCoreIMSBinding;
using CellularManagement.Application.Features.StackCoreIMSBindings.Queries.GetStackCoreIMSBindingById;
using CellularManagement.Application.Features.StackCoreIMSBindings.Queries.GetStackCoreIMSBindings;
using CellularManagement.Domain.Common;
using CellularManagement.Presentation.Abstractions;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace CellularManagement.Presentation.Controllers;
/// <summary>
/// 栈核心网IMS绑定管理控制器
/// </summary>
[Route("api/stackcoreimsbindings")]
[ApiController]
[Authorize]
public class StackCoreIMSBindingsController : ApiController
{
private readonly ILogger<StackCoreIMSBindingsController> _logger;
/// <summary>
/// 初始化栈核心网IMS绑定控制器
/// </summary>
public StackCoreIMSBindingsController(IMediator mediator, ILogger<StackCoreIMSBindingsController> logger)
: base(mediator)
{
_logger = logger;
}
/// <summary>
/// 获取栈核心网IMS绑定列表
/// </summary>
[HttpGet]
public async Task<OperationResult<GetStackCoreIMSBindingsResponse>> GetAll([FromQuery] GetStackCoreIMSBindingsQuery query)
{
_logger.LogInformation("开始获取栈核心网IMS绑定列表,页码: {PageNumber}, 每页数量: {PageSize}, 栈ID: {StackId}, 核心网ID: {CnId}, IMS ID: {ImsId}",
query.PageNumber, query.PageSize, query.StackId, query.CnId, query.ImsId);
var result = await mediator.Send(query);
if (!result.IsSuccess)
{
_logger.LogWarning("获取栈核心网IMS绑定列表失败: {Message}", result.ErrorMessages);
return result;
}
_logger.LogInformation("成功获取栈核心网IMS绑定列表,共 {Count} 条记录", result.Data?.TotalCount ?? 0);
return result;
}
/// <summary>
/// 获取栈核心网IMS绑定详情
/// </summary>
[HttpGet("{id}")]
public async Task<OperationResult<GetStackCoreIMSBindingByIdResponse>> GetById(string id)
{
_logger.LogInformation("开始获取栈核心网IMS绑定详情,绑定ID: {StackCoreIMSBindingId}", id);
var result = await mediator.Send(new GetStackCoreIMSBindingByIdQuery { StackCoreIMSBindingId = id });
if (!result.IsSuccess)
{
_logger.LogWarning("获取栈核心网IMS绑定详情失败: {Message}", result.ErrorMessages);
return result;
}
_logger.LogInformation("成功获取栈核心网IMS绑定详情,绑定ID: {StackCoreIMSBindingId}", id);
return result;
}
/// <summary>
/// 创建栈核心网IMS绑定
/// </summary>
[HttpPost]
public async Task<OperationResult<CreateStackCoreIMSBindingResponse>> Create([FromBody] CreateStackCoreIMSBindingCommand command)
{
_logger.LogInformation("开始创建栈核心网IMS绑定,栈ID: {StackId}, 索引: {Index}, 核心网ID: {CnId}, IMS ID: {ImsId}",
command.StackId, command.Index, command.CnId, command.ImsId);
var result = await mediator.Send(command);
if (!result.IsSuccess)
{
_logger.LogWarning("创建栈核心网IMS绑定失败: {Message}", result.ErrorMessages);
return result;
}
_logger.LogInformation("成功创建栈核心网IMS绑定,绑定ID: {StackCoreIMSBindingId}", result.Data?.StackCoreIMSBindingId);
return result;
}
/// <summary>
/// 更新栈核心网IMS绑定
/// </summary>
[HttpPut("{id}")]
public async Task<OperationResult<UpdateStackCoreIMSBindingResponse>> Update(string id, [FromBody] UpdateStackCoreIMSBindingCommand command)
{
_logger.LogInformation("开始更新栈核心网IMS绑定,绑定ID: {StackCoreIMSBindingId}", id);
if (id != command.StackCoreIMSBindingId)
{
_logger.LogWarning("栈核心网IMS绑定ID不匹配,路径ID: {PathId}, 命令ID: {CommandId}", id, command.StackCoreIMSBindingId);
return OperationResult<UpdateStackCoreIMSBindingResponse>.CreateFailure("栈核心网IMS绑定ID不匹配");
}
var result = await mediator.Send(command);
if (!result.IsSuccess)
{
_logger.LogWarning("更新栈核心网IMS绑定失败: {Message}", result.ErrorMessages);
return result;
}
_logger.LogInformation("成功更新栈核心网IMS绑定,绑定ID: {StackCoreIMSBindingId}", id);
return result;
}
/// <summary>
/// 删除栈核心网IMS绑定
/// </summary>
[HttpDelete("{id}")]
public async Task<OperationResult<bool>> Delete(string id)
{
_logger.LogInformation("开始删除栈核心网IMS绑定,绑定ID: {StackCoreIMSBindingId}", id);
var result = await mediator.Send(new DeleteStackCoreIMSBindingCommand { StackCoreIMSBindingId = id });
if (!result.IsSuccess)
{
_logger.LogWarning("删除栈核心网IMS绑定失败: {Message}", result.ErrorMessages);
return result;
}
_logger.LogInformation("成功删除栈核心网IMS绑定,绑定ID: {StackCoreIMSBindingId}", id);
return result;
}
}

4
src/X1.WebAPI/Properties/launchSettings.json

@ -24,8 +24,8 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7268;http://localhost:5000;https://192.168.2.142:7268;http://192.168.2.142:5000",
//"applicationUrl": "https://localhost:7268;http://localhost:5000",
//"applicationUrl": "https://localhost:7268;http://localhost:5000;https://192.168.2.142:7268;http://192.168.2.142:5000",
"applicationUrl": "https://localhost:7268;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

6163
src/X1.WebAPI/logs/app-20250728.log

File diff suppressed because it is too large

1890
src/X1.WebAPI/logs/app-20250729.log

File diff suppressed because it is too large

23
src/X1.WebAPI/logs/error-20250728.log

@ -1828,3 +1828,26 @@ System.ArgumentNullException: Value cannot be null. (Parameter 'entity')
at CellularManagement.Infrastructure.Repositories.CQRS.CommandRepository`1.AddAsync(T entity, CancellationToken cancellationToken) in D:\dev\clean-architecture-starter-main\CellularManagement\src\X1.Infrastructure\Repositories\CQRS\CommandRepository.cs:line 56
at CellularManagement.Infrastructure.Repositories.Identity.LoginLogRepository.RecordLoginAsync(LoginLog log, CancellationToken cancellationToken) in D:\dev\clean-architecture-starter-main\CellularManagement\src\X1.Infrastructure\Repositories\Identity\LoginLogRepository.cs:line 42
at CellularManagement.Application.Features.Auth.Commands.BaseLoginCommandHandler`2.HandleLoginAsync(TCommand request, CancellationToken cancellationToken)
2025-07-28 21:01:07.141 +08:00 [ERR] DESKTOP-1Q3GI6C [1] Hosting failed to start
System.Net.Sockets.SocketException (10049): 在其上下文中,该请求的地址无效。
at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName)
at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.Sockets.Socket.Bind(EndPoint localEP)
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint)
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind()
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<<StartAsync>g__OnBind|0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindAsync(ListenOptions[] listenOptions, AddressBindContext context, Func`2 useHttps, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__15_1(IHostedService service, CancellationToken token)
at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
2025-07-28 23:05:53.192 +08:00 [ERR] DESKTOP-1Q3GI6C [25] Failed executing DbCommand (19,284ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT count(*)::int
FROM "IMS_Configurations" AS i

75
src/X1.WebAPI/logs/error-20250729.log

@ -0,0 +1,75 @@
2025-07-29 00:29:19.787 +08:00 [ERR] DESKTOP-1Q3GI6C [10] An unhandled exception has occurred while executing the request.
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Failed to generate Operation for action - CellularManagement.Presentation.Controllers.NetworkStackConfigsController.Create (X1.Presentation). See inner exception
---> Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Failed to generate schema for type - CellularManagement.Domain.Common.OperationResult`1[CellularManagement.Application.Features.NetworkStackConfigs.Commands.CreateNetworkStackConfig.CreateNetworkStackConfigResponse]. See inner exception
---> System.InvalidOperationException: Can't use schemaId "$StackCoreIMSBindingResponseItem" for type "$CellularManagement.Application.Features.NetworkStackConfigs.Commands.CreateNetworkStackConfig.StackCoreIMSBindingResponseItem". The same schemaId is already used for type "$CellularManagement.Application.Features.NetworkStackConfigs.Queries.GetNetworkStackConfigs.StackCoreIMSBindingResponseItem"
at Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository.RegisterType(Type type, String schemaId)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateReferencedSchema(DataContract dataContract, SchemaRepository schemaRepository, Func`1 definitionFactory)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForType(Type modelType, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchema(Type modelType, SchemaRepository schemaRepository, MemberInfo memberInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.CreateArraySchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.<>c__DisplayClass10_0.<GenerateConcreteSchema>b__1()
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForMember(Type modelType, SchemaRepository schemaRepository, MemberInfo memberInfo, DataProperty dataProperty)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.CreateObjectSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.<>c__DisplayClass10_0.<GenerateConcreteSchema>b__3()
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateReferencedSchema(DataContract dataContract, SchemaRepository schemaRepository, Func`1 definitionFactory)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForMember(Type modelType, SchemaRepository schemaRepository, MemberInfo memberInfo, DataProperty dataProperty)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.CreateObjectSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.<>c__DisplayClass10_0.<GenerateConcreteSchema>b__3()
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateReferencedSchema(DataContract dataContract, SchemaRepository schemaRepository, Func`1 definitionFactory)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForType(Type modelType, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchema(Type modelType, SchemaRepository schemaRepository, MemberInfo memberInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateSchema(Type type, SchemaRepository schemaRepository, PropertyInfo propertyInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
--- End of inner exception stack trace ---
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateSchema(Type type, SchemaRepository schemaRepository, PropertyInfo propertyInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.CreateResponseMediaType(ModelMetadata modelMetadata, SchemaRepository schemaRespository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.<>c__DisplayClass25_0.<GenerateResponse>b__2(String contentType)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateResponse(ApiDescription apiDescription, SchemaRepository schemaRepository, String statusCode, ApiResponseType apiResponseType)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateResponses(ApiDescription apiDescription, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
--- End of inner exception stack trace ---
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerDocumentWithoutFilters(String documentName, String host, String basePath)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerAsync(String documentName, String host, String basePath)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
2025-07-29 00:33:24.410 +08:00 [ERR] DESKTOP-1Q3GI6C [14] An unhandled exception has occurred while executing the request.
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Failed to generate Operation for action - CellularManagement.Presentation.Controllers.NetworkStackConfigsController.Update (X1.Presentation). See inner exception
---> Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Failed to generate schema for type - CellularManagement.Application.Features.NetworkStackConfigs.Commands.UpdateNetworkStackConfig.UpdateNetworkStackConfigCommand. See inner exception
---> System.InvalidOperationException: Can't use schemaId "$StackCoreIMSBindingItem" for type "$CellularManagement.Application.Features.NetworkStackConfigs.Commands.UpdateNetworkStackConfig.StackCoreIMSBindingItem". The same schemaId is already used for type "$CellularManagement.Application.Features.NetworkStackConfigs.Commands.CreateNetworkStackConfig.StackCoreIMSBindingItem"
at Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository.RegisterType(Type type, String schemaId)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateReferencedSchema(DataContract dataContract, SchemaRepository schemaRepository, Func`1 definitionFactory)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForType(Type modelType, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchema(Type modelType, SchemaRepository schemaRepository, MemberInfo memberInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.CreateArraySchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.<>c__DisplayClass10_0.<GenerateConcreteSchema>b__1()
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForMember(Type modelType, SchemaRepository schemaRepository, MemberInfo memberInfo, DataProperty dataProperty)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.CreateObjectSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.<>c__DisplayClass10_0.<GenerateConcreteSchema>b__3()
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateReferencedSchema(DataContract dataContract, SchemaRepository schemaRepository, Func`1 definitionFactory)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForParameter(Type modelType, SchemaRepository schemaRepository, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchema(Type modelType, SchemaRepository schemaRepository, MemberInfo memberInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateSchema(Type type, SchemaRepository schemaRepository, PropertyInfo propertyInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
--- End of inner exception stack trace ---
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateSchema(Type type, SchemaRepository schemaRepository, PropertyInfo propertyInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateRequestBodyFromBodyParameter(ApiDescription apiDescription, SchemaRepository schemaRepository, ApiParameterDescription bodyParameter)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateRequestBody(ApiDescription apiDescription, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
--- End of inner exception stack trace ---
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerDocumentWithoutFilters(String documentName, String host, String basePath)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerAsync(String documentName, String host, String basePath)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

253
src/X1.WebUI/src/components/ui/ConfigContentViewer.tsx

@ -0,0 +1,253 @@
import React, { useState, useRef, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { MagnifyingGlassIcon, Cross2Icon } from '@radix-ui/react-icons';
interface ConfigContentViewerProps {
content: string;
className?: string;
onCopy?: () => void;
onDownload?: () => void;
}
export default function ConfigContentViewer({
content,
className = "",
onCopy,
onDownload
}: ConfigContentViewerProps) {
const [searchTerm, setSearchTerm] = useState('');
const [highlightedContent, setHighlightedContent] = useState('');
const [isSearchVisible, setIsSearchVisible] = useState(false);
const [currentMatchIndex, setCurrentMatchIndex] = useState(0);
const [totalMatches, setTotalMatches] = useState(0);
// 高亮搜索内容
useEffect(() => {
if (!searchTerm.trim()) {
setHighlightedContent(content);
setCurrentMatchIndex(0);
setTotalMatches(0);
return;
}
try {
const escapedSearchTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${escapedSearchTerm})`, 'gi');
const matches = [...content.matchAll(regex)];
setTotalMatches(matches.length);
if (matches.length === 0) {
setHighlightedContent(content);
return;
}
// 使用简单的高亮方法
let highlighted = content;
// 高亮所有匹配项
highlighted = highlighted.replace(regex, (match, group, offset) => {
// 找到当前匹配项在原始匹配数组中的索引
const matchIndex = matches.findIndex(m => m.index === offset);
const isCurrentMatch = matchIndex === currentMatchIndex;
// 根据是否为当前匹配项选择不同的样式
const highlightClass = isCurrentMatch
? 'bg-blue-500 text-white font-bold'
: 'bg-yellow-200 text-black';
return `<span class="${highlightClass}">${match}</span>`;
});
setHighlightedContent(highlighted);
} catch (error) {
console.error('搜索高亮处理错误:', error);
setHighlightedContent(content);
}
}, [content, searchTerm, currentMatchIndex]);
// 定位到指定匹配项
const scrollToMatch = (matchIndex: number) => {
if (!searchTerm.trim()) return;
const regex = new RegExp(searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
const matches = [...content.matchAll(regex)];
if (matchIndex >= 0 && matchIndex < matches.length) {
const match = matches[matchIndex];
const startIndex = match.index!;
// 计算匹配项在文本中的位置
const textBeforeMatch = content.substring(0, startIndex);
const linesBeforeMatch = textBeforeMatch.split('\n').length - 1;
// 找到对应的 DOM 元素并滚动到可见位置
const container = document.querySelector('.font-mono.text-sm.h-full.overflow-auto');
if (container) {
const lineHeight = parseInt(getComputedStyle(container).lineHeight) || 20;
const scrollTop = linesBeforeMatch * lineHeight - container.clientHeight / 2;
container.scrollTop = Math.max(0, scrollTop);
}
}
};
// 搜索下一个匹配项
const findNext = () => {
if (!searchTerm.trim() || totalMatches === 0) return;
const nextIndex = (currentMatchIndex + 1) % totalMatches;
setCurrentMatchIndex(nextIndex);
scrollToMatch(nextIndex);
};
// 搜索上一个匹配项
const findPrevious = () => {
if (!searchTerm.trim() || totalMatches === 0) return;
const prevIndex = currentMatchIndex === 0 ? totalMatches - 1 : currentMatchIndex - 1;
setCurrentMatchIndex(prevIndex);
scrollToMatch(prevIndex);
};
// 搜索输入变化时重置匹配索引
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value);
setCurrentMatchIndex(0);
};
// 键盘快捷键支持
const handleKeyDown = (e: React.KeyboardEvent) => {
if (!isSearchVisible || !searchTerm.trim()) return;
if (e.key === 'Enter') {
e.preventDefault();
if (e.shiftKey) {
findPrevious();
} else {
findNext();
}
}
};
return (
<div className={`flex flex-col h-full ${className}`}>
{/* 操作按钮区域 */}
<div className="flex items-center gap-2 mb-4">
{/* 搜索功能 */}
<div className="flex items-center gap-2 flex-1">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => setIsSearchVisible(!isSearchVisible)}
title="搜索内容"
className="flex items-center gap-2"
>
<MagnifyingGlassIcon className="h-4 w-4" />
</Button>
{/* 搜索栏 */}
{isSearchVisible && (
<div className="flex items-center gap-2 flex-1">
<div className="relative flex-1">
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索内容... (Enter: 下一个, Shift+Enter: 上一个)"
value={searchTerm}
onChange={handleSearchChange}
onKeyDown={handleKeyDown}
className="pl-10"
autoFocus
/>
</div>
{searchTerm && (
<span className="text-sm text-gray-500 whitespace-nowrap">
{totalMatches > 0 ? (
<span className="flex items-center gap-1">
<span>{currentMatchIndex + 1} / {totalMatches}</span>
<span className="w-2 h-2 bg-blue-500 rounded-full"></span>
</span>
) : '无匹配项'}
</span>
)}
<Button
type="button"
variant="outline"
size="sm"
onClick={findPrevious}
disabled={!searchTerm.trim() || totalMatches === 0}
title="上一个 (Shift+Enter)"
>
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={findNext}
disabled={!searchTerm.trim() || totalMatches === 0}
title="下一个 (Enter)"
>
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
setSearchTerm('');
setIsSearchVisible(false);
setCurrentMatchIndex(0);
setTotalMatches(0);
}}
title="关闭搜索"
>
<Cross2Icon className="h-4 w-4" />
</Button>
</div>
)}
</div>
{/* 复制和下载按钮 */}
<div className="flex items-center gap-2">
{onCopy && (
<Button
variant="outline"
size="sm"
onClick={onCopy}
className="flex items-center gap-2"
>
</Button>
)}
{onDownload && (
<Button
variant="outline"
size="sm"
onClick={onDownload}
className="flex items-center gap-2"
>
</Button>
)}
</div>
</div>
{/* 配置内容显示区域 */}
<div className="flex-1 border rounded-md bg-gray-50 overflow-hidden">
<div
className="font-mono text-sm h-full overflow-auto p-3"
style={{
height: '100%',
whiteSpace: 'pre',
wordWrap: 'normal',
overflowWrap: 'normal'
}}
dangerouslySetInnerHTML={{ __html: highlightedContent }}
/>
</div>
</div>
);
}

2
src/X1.WebUI/src/config/core/env.config.ts

@ -4,7 +4,7 @@ import type { ApiConfig, AuthConfig, AppConfig, MockConfig, Environment } from '
// 默认配置
const DEFAULT_CONFIG = {
// API配置
VITE_API_BASE_URL: 'https://192.168.2.142:7268/api',
VITE_API_BASE_URL: 'https://localhost:7268/api',
VITE_API_TIMEOUT: '30000',
VITE_API_VERSION: 'v1',
VITE_API_MAX_RETRIES: '3',

125
src/X1.WebUI/src/pages/core-network-configs/CoreNetworkConfigViewDialog.tsx

@ -0,0 +1,125 @@
import React from 'react';
import { CoreNetworkConfig } from '@/services/coreNetworkConfigService';
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { X } from 'lucide-react';
import { useToast } from '@/components/ui/use-toast';
import ConfigContentViewer from '@/components/ui/ConfigContentViewer';
interface CoreNetworkConfigViewDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
config: CoreNetworkConfig | null;
}
export default function CoreNetworkConfigViewDialog({
open,
onOpenChange,
config
}: CoreNetworkConfigViewDialogProps) {
const { toast } = useToast();
// 复制配置内容到剪贴板
const handleCopyContent = async () => {
if (!config?.configContent) return;
try {
await navigator.clipboard.writeText(config.configContent);
toast({
title: "复制成功",
description: "配置内容已复制到剪贴板",
});
} catch (error) {
console.error('复制失败:', error);
toast({
title: "复制失败",
description: "无法复制到剪贴板,请手动复制",
variant: "destructive",
});
}
};
// 下载配置内容为文件
const handleDownloadContent = () => {
if (!config?.configContent) return;
const blob = new Blob([config.configContent], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${config.name || 'core-network-config'}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
toast({
title: "下载成功",
description: "配置文件已下载",
});
};
if (!config) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl h-[80vh] flex flex-col">
<div className="flex items-center justify-between w-full mb-4 flex-shrink-0">
<DialogTitle className="text-lg font-semibold">
- {config.name}
</DialogTitle>
<Button
variant="ghost"
size="sm"
onClick={() => onOpenChange(false)}
className="h-8 w-8 p-0"
>
<X className="h-4 w-4" />
</Button>
</div>
<div className="flex flex-col flex-1 space-y-4 overflow-hidden min-h-0">
{/* 配置信息 */}
<div className="grid grid-cols-2 gap-4 text-sm flex-shrink-0">
<div>
<Label className="text-sm font-medium text-gray-600"></Label>
<div className="mt-1 text-gray-900">{config.name}</div>
</div>
<div>
<Label className="text-sm font-medium text-gray-600"></Label>
<div className="mt-1">
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
config.isDisabled
? 'bg-red-100 text-red-800'
: 'bg-green-100 text-green-800'
}`}>
{config.isDisabled ? '禁用' : '启用'}
</span>
</div>
</div>
{config.description && (
<div className="col-span-2">
<Label className="text-sm font-medium text-gray-600"></Label>
<div className="mt-1 text-gray-900">{config.description}</div>
</div>
)}
</div>
{/* 配置内容查看器 */}
<div className="flex flex-col flex-1 min-h-0">
<Label className="text-sm font-medium text-gray-600 mb-2 flex-shrink-0">
</Label>
<ConfigContentViewer
content={config.configContent || '无配置内容'}
className="flex-1 min-h-0"
onCopy={handleCopyContent}
onDownload={handleDownloadContent}
/>
</div>
</div>
</DialogContent>
</Dialog>
);
}

24
src/X1.WebUI/src/pages/core-network-configs/CoreNetworkConfigsTable.tsx

@ -2,6 +2,7 @@ import React from 'react';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { CoreNetworkConfig } from '@/services/coreNetworkConfigService';
import StatusSwitch from '@/components/ui/StatusSwitch';
import { Eye } from 'lucide-react';
interface CoreNetworkConfigsTableProps {
coreNetworkConfigs: CoreNetworkConfig[];
@ -9,6 +10,7 @@ interface CoreNetworkConfigsTableProps {
onEdit: (config: CoreNetworkConfig) => void;
onDelete: (config: CoreNetworkConfig) => void;
onStatusChange?: (config: CoreNetworkConfig, newStatus: boolean) => void;
onViewContent?: (config: CoreNetworkConfig) => void;
page: number;
pageSize: number;
total: number;
@ -56,13 +58,21 @@ const DateDisplay: React.FC<{ date?: string }> = ({ date }) => {
};
// 配置内容预览组件
const ConfigContentPreview: React.FC<{ content: string }> = ({ content }) => {
const ConfigContentPreview: React.FC<{
content: string;
onViewContent?: () => void;
}> = ({ content, onViewContent }) => {
const maxLength = 50;
const truncated = content.length > maxLength ? content.substring(0, maxLength) + '...' : content;
return (
<div className="max-w-xs truncate text-gray-600" title={content}>
{truncated}
<div
className="max-w-xs truncate text-gray-600 cursor-pointer hover:text-blue-600 hover:underline flex items-center gap-1 group"
onClick={onViewContent}
title="点击查看完整内容"
>
<span>{truncated}</span>
<Eye className="h-3 w-3 opacity-0 group-hover:opacity-100 transition-opacity" />
</div>
);
};
@ -73,6 +83,7 @@ export default function CoreNetworkConfigsTable({
onEdit,
onDelete,
onStatusChange,
onViewContent,
page,
pageSize,
total,
@ -104,7 +115,12 @@ export default function CoreNetworkConfigsTable({
</div>
);
case 'configContent':
return <ConfigContentPreview content={config.configContent} />;
return (
<ConfigContentPreview
content={config.configContent}
onViewContent={() => onViewContent?.(config)}
/>
);
case 'isDisabled':
return (
<StatusSwitchCell

68
src/X1.WebUI/src/pages/core-network-configs/CoreNetworkConfigsView.tsx

@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { coreNetworkConfigService, CoreNetworkConfig, GetCoreNetworkConfigsRequest, CreateCoreNetworkConfigRequest, UpdateCoreNetworkConfigRequest } from '@/services/coreNetworkConfigService';
import CoreNetworkConfigsTable from './CoreNetworkConfigsTable';
import CoreNetworkConfigDrawer from './CoreNetworkConfigDrawer';
import CoreNetworkConfigViewDialog from './CoreNetworkConfigViewDialog';
import { Input } from '@/components/ui/input';
import PaginationBar from '@/components/ui/PaginationBar';
import TableToolbar, { DensityType } from '@/components/ui/TableToolbar';
@ -12,7 +13,7 @@ import { useToast } from '@/components/ui/use-toast';
const defaultColumns = [
{ key: 'name', title: '配置名称', visible: true },
{ key: 'description', title: '描述', visible: true },
{ key: 'configContent', title: '配置内容', visible: false },
{ key: 'configContent', title: '配置内容', visible: true },
{ key: 'isDisabled', title: '状态', visible: true },
{ key: 'createdAt', title: '创建时间', visible: true },
{ key: 'actions', title: '操作', visible: true }
@ -60,6 +61,7 @@ export default function CoreNetworkConfigsView() {
// 抽屉状态
const [drawerOpen, setDrawerOpen] = useState(false);
const [editDrawerOpen, setEditDrawerOpen] = useState(false);
const [viewOpen, setViewOpen] = useState(false);
const [selectedConfig, setSelectedConfig] = useState<CoreNetworkConfig | null>(null);
// 提交状态
@ -96,6 +98,11 @@ export default function CoreNetworkConfigsView() {
setEditDrawerOpen(true);
};
const handleViewContent = (config: CoreNetworkConfig) => {
setSelectedConfig(config);
setViewOpen(true);
};
const handleDelete = async (config: CoreNetworkConfig) => {
if (confirm(`确定要删除核心网络配置 "${config.name}" 吗?`)) {
try {
@ -127,8 +134,8 @@ export default function CoreNetworkConfigsView() {
};
const handleStatusChange = async (config: CoreNetworkConfig, newStatus: boolean) => {
if (isSubmitting) return;
if (isSubmitting) return; // 防止重复提交
setIsSubmitting(true);
try {
const updateData: UpdateCoreNetworkConfigRequest = {
@ -136,9 +143,9 @@ export default function CoreNetworkConfigsView() {
name: config.name,
configContent: config.configContent,
description: config.description,
isDisabled: !newStatus // Note: newStatus is enabled state, isDisabled is disabled state
isDisabled: !newStatus // 注意:newStatus是启用状态,isDisabled是禁用状态
};
const result = await coreNetworkConfigService.updateCoreNetworkConfig(config.coreNetworkConfigId, updateData);
if (result.isSuccess) {
toast({
@ -268,16 +275,17 @@ export default function CoreNetworkConfigsView() {
return (
<main className="flex-1 p-4 transition-all duration-300 ease-in-out sm:p-6">
<div className="w-full space-y-4">
{/* 丰富美化后的搜索栏 */}
{/* 搜索栏 */}
<div className="flex flex-col bg-white p-4 rounded-md border mb-2">
<form
className={`grid gap-x-8 gap-y-4 items-center ${showAdvanced ? 'md:grid-cols-3' : 'md:grid-cols-3'} grid-cols-1`}
className="grid gap-x-8 gap-y-4 items-center md:grid-cols-3 grid-cols-1"
onSubmit={e => {
e.preventDefault();
handleQuery();
}}
>
{(showAdvanced ? [...firstRowFields, ...advancedFields] : firstRowFields).map(field => (
{/* 第一行字段 */}
{firstRowFields.map((field: SearchField) => (
<div className="flex flex-row items-center min-w-[200px] flex-1" key={field.key}>
<label
className="mr-2 text-sm font-medium text-foreground whitespace-nowrap text-right"
@ -298,14 +306,11 @@ export default function CoreNetworkConfigsView() {
{field.type === 'select' && (
<select
className="input h-10 rounded border border-border bg-background px-3 text-sm flex-1"
value={field.key === 'isDisabled' ? (isDisabled === undefined ? '' : isDisabled.toString()) :
field.key === 'pageSize' ? pageSize.toString() : ''}
value={field.key === 'isDisabled' ? (isDisabled === undefined ? '' : isDisabled.toString()) : ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
if (field.key === 'isDisabled') {
const value = e.target.value;
setIsDisabled(value === '' ? undefined : value === 'true');
} else if (field.key === 'pageSize') {
setPageSize(parseInt(e.target.value));
}
}}
>
@ -333,6 +338,37 @@ export default function CoreNetworkConfigsView() {
</button>
</div>
</form>
{/* 高级搜索字段 */}
{showAdvanced && (
<div className="grid gap-x-8 gap-y-4 items-center md:grid-cols-3 grid-cols-1 mt-4 pt-4 border-t">
{advancedFields.map((field: SearchField) => (
<div className="flex flex-row items-center min-w-[200px] flex-1" key={field.key}>
<label
className="mr-2 text-sm font-medium text-foreground whitespace-nowrap text-right"
style={{ width: 80, minWidth: 80 }}
>
{field.label}
</label>
{field.type === 'select' && (
<select
className="input h-10 rounded border border-border bg-background px-3 text-sm flex-1"
value={field.key === 'pageSize' ? pageSize.toString() : ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
if (field.key === 'pageSize') {
setPageSize(parseInt(e.target.value));
}
}}
>
{field.options.map(opt => (
<option value={opt.value} key={opt.value}>{opt.label}</option>
))}
</select>
)}
</div>
))}
</div>
)}
</div>
{/* 表格整体卡片区域,包括工具栏、表格、分页 */}
<div className="rounded-md border bg-background p-4">
@ -360,6 +396,7 @@ export default function CoreNetworkConfigsView() {
onEdit={handleEdit}
onDelete={handleDelete}
onStatusChange={handleStatusChange}
onViewContent={handleViewContent}
page={pageNumber}
pageSize={pageSize}
total={total}
@ -396,6 +433,13 @@ export default function CoreNetworkConfigsView() {
isEdit={true}
isSubmitting={isSubmitting}
/>
{/* 查看配置内容对话框 */}
<CoreNetworkConfigViewDialog
open={viewOpen}
onOpenChange={setViewOpen}
config={selectedConfig}
/>
</main>
);
}

125
src/X1.WebUI/src/pages/ims-configurations/IMSConfigurationViewDialog.tsx

@ -0,0 +1,125 @@
import React from 'react';
import { IMSConfiguration } from '@/services/imsConfigurationService';
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { X } from 'lucide-react';
import { useToast } from '@/components/ui/use-toast';
import ConfigContentViewer from '@/components/ui/ConfigContentViewer';
interface IMSConfigurationViewDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
configuration: IMSConfiguration | null;
}
export default function IMSConfigurationViewDialog({
open,
onOpenChange,
configuration
}: IMSConfigurationViewDialogProps) {
const { toast } = useToast();
// 复制配置内容到剪贴板
const handleCopyContent = async () => {
if (!configuration?.configContent) return;
try {
await navigator.clipboard.writeText(configuration.configContent);
toast({
title: "复制成功",
description: "配置内容已复制到剪贴板",
});
} catch (error) {
console.error('复制失败:', error);
toast({
title: "复制失败",
description: "无法复制到剪贴板,请手动复制",
variant: "destructive",
});
}
};
// 下载配置内容为文件
const handleDownloadContent = () => {
if (!configuration?.configContent) return;
const blob = new Blob([configuration.configContent], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${configuration.name || 'ims-config'}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
toast({
title: "下载成功",
description: "配置文件已下载",
});
};
if (!configuration) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl h-[80vh] flex flex-col">
<div className="flex items-center justify-between w-full mb-4 flex-shrink-0">
<DialogTitle className="text-lg font-semibold">
- {configuration.name}
</DialogTitle>
<Button
variant="ghost"
size="sm"
onClick={() => onOpenChange(false)}
className="h-8 w-8 p-0"
>
<X className="h-4 w-4" />
</Button>
</div>
<div className="flex flex-col flex-1 space-y-4 overflow-hidden min-h-0">
{/* 配置信息 */}
<div className="grid grid-cols-2 gap-4 text-sm flex-shrink-0">
<div>
<Label className="text-sm font-medium text-gray-600"></Label>
<div className="mt-1 text-gray-900">{configuration.name}</div>
</div>
<div>
<Label className="text-sm font-medium text-gray-600"></Label>
<div className="mt-1">
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
configuration.isDisabled
? 'bg-red-100 text-red-800'
: 'bg-green-100 text-green-800'
}`}>
{configuration.isDisabled ? '禁用' : '启用'}
</span>
</div>
</div>
{configuration.description && (
<div className="col-span-2">
<Label className="text-sm font-medium text-gray-600"></Label>
<div className="mt-1 text-gray-900">{configuration.description}</div>
</div>
)}
</div>
{/* 配置内容查看器 */}
<div className="flex flex-col flex-1 min-h-0">
<Label className="text-sm font-medium text-gray-600 mb-2 flex-shrink-0">
</Label>
<ConfigContentViewer
content={configuration.configContent || '无配置内容'}
className="flex-1 min-h-0"
onCopy={handleCopyContent}
onDownload={handleDownloadContent}
/>
</div>
</div>
</DialogContent>
</Dialog>
);
}

24
src/X1.WebUI/src/pages/ims-configurations/IMSConfigurationsTable.tsx

@ -2,6 +2,7 @@ import React from 'react';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { IMSConfiguration } from '@/services/imsConfigurationService';
import StatusSwitch from '@/components/ui/StatusSwitch';
import { Eye } from 'lucide-react';
interface IMSConfigurationsTableProps {
imsConfigurations: IMSConfiguration[];
@ -9,6 +10,7 @@ interface IMSConfigurationsTableProps {
onEdit: (configuration: IMSConfiguration) => void;
onDelete: (configuration: IMSConfiguration) => void;
onStatusChange?: (configuration: IMSConfiguration, newStatus: boolean) => void;
onViewContent?: (configuration: IMSConfiguration) => void;
page: number;
pageSize: number;
total: number;
@ -56,13 +58,21 @@ const DateDisplay: React.FC<{ date?: string }> = ({ date }) => {
};
// 配置内容预览组件
const ConfigContentPreview: React.FC<{ content: string }> = ({ content }) => {
const ConfigContentPreview: React.FC<{
content: string;
onViewContent?: () => void;
}> = ({ content, onViewContent }) => {
const maxLength = 50;
const truncated = content.length > maxLength ? content.substring(0, maxLength) + '...' : content;
return (
<div className="max-w-xs truncate text-gray-600" title={content}>
{truncated}
<div
className="max-w-xs truncate text-gray-600 cursor-pointer hover:text-blue-600 hover:underline flex items-center gap-1 group"
onClick={onViewContent}
title="点击查看完整内容"
>
<span>{truncated}</span>
<Eye className="h-3 w-3 opacity-0 group-hover:opacity-100 transition-opacity" />
</div>
);
};
@ -73,6 +83,7 @@ export default function IMSConfigurationsTable({
onEdit,
onDelete,
onStatusChange,
onViewContent,
page,
pageSize,
total,
@ -104,7 +115,12 @@ export default function IMSConfigurationsTable({
</div>
);
case 'configContent':
return <ConfigContentPreview content={configuration.configContent} />;
return (
<ConfigContentPreview
content={configuration.configContent}
onViewContent={() => onViewContent?.(configuration)}
/>
);
case 'isDisabled':
return (
<StatusSwitchCell

68
src/X1.WebUI/src/pages/ims-configurations/IMSConfigurationsView.tsx

@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { imsConfigurationService, IMSConfiguration, GetIMSConfigurationsRequest, CreateIMSConfigurationRequest, UpdateIMSConfigurationRequest } from '@/services/imsConfigurationService';
import IMSConfigurationsTable from './IMSConfigurationsTable';
import IMSConfigurationDrawer from './IMSConfigurationDrawer';
import IMSConfigurationViewDialog from './IMSConfigurationViewDialog';
import { Input } from '@/components/ui/input';
import PaginationBar from '@/components/ui/PaginationBar';
import TableToolbar, { DensityType } from '@/components/ui/TableToolbar';
@ -12,7 +13,7 @@ import { useToast } from '@/components/ui/use-toast';
const defaultColumns = [
{ key: 'name', title: '配置名称', visible: true },
{ key: 'description', title: '描述', visible: true },
{ key: 'configContent', title: '配置内容', visible: false },
{ key: 'configContent', title: '配置内容', visible: true },
{ key: 'isDisabled', title: '状态', visible: true },
{ key: 'createdAt', title: '创建时间', visible: true },
{ key: 'actions', title: '操作', visible: true }
@ -60,6 +61,7 @@ export default function IMSConfigurationsView() {
// 抽屉状态
const [drawerOpen, setDrawerOpen] = useState(false);
const [editDrawerOpen, setEditDrawerOpen] = useState(false);
const [viewOpen, setViewOpen] = useState(false);
const [selectedConfiguration, setSelectedConfiguration] = useState<IMSConfiguration | null>(null);
// 提交状态
@ -96,6 +98,11 @@ export default function IMSConfigurationsView() {
setEditDrawerOpen(true);
};
const handleViewContent = (configuration: IMSConfiguration) => {
setSelectedConfiguration(configuration);
setViewOpen(true);
};
const handleDelete = async (configuration: IMSConfiguration) => {
if (confirm(`确定要删除IMS配置 "${configuration.name}" 吗?`)) {
try {
@ -164,8 +171,8 @@ export default function IMSConfigurationsView() {
};
const handleStatusChange = async (configuration: IMSConfiguration, newStatus: boolean) => {
if (isSubmitting) return;
if (isSubmitting) return; // 防止重复提交
setIsSubmitting(true);
try {
const updateData: UpdateIMSConfigurationRequest = {
@ -173,9 +180,9 @@ export default function IMSConfigurationsView() {
name: configuration.name,
configContent: configuration.configContent,
description: configuration.description,
isDisabled: !newStatus // Note: newStatus is enabled state, isDisabled is disabled state
isDisabled: !newStatus // 注意:newStatus是启用状态,isDisabled是禁用状态
};
const result = await imsConfigurationService.updateIMSConfiguration(configuration.imS_ConfigurationId, updateData);
if (result.isSuccess) {
toast({
@ -268,16 +275,17 @@ export default function IMSConfigurationsView() {
return (
<main className="flex-1 p-4 transition-all duration-300 ease-in-out sm:p-6">
<div className="w-full space-y-4">
{/* 丰富美化后的搜索栏 */}
{/* 搜索栏 */}
<div className="flex flex-col bg-white p-4 rounded-md border mb-2">
<form
className={`grid gap-x-8 gap-y-4 items-center ${showAdvanced ? 'md:grid-cols-3' : 'md:grid-cols-3'} grid-cols-1`}
className="grid gap-x-8 gap-y-4 items-center md:grid-cols-3 grid-cols-1"
onSubmit={e => {
e.preventDefault();
handleQuery();
}}
>
{(showAdvanced ? [...firstRowFields, ...advancedFields] : firstRowFields).map(field => (
{/* 第一行字段 */}
{firstRowFields.map((field: SearchField) => (
<div className="flex flex-row items-center min-w-[200px] flex-1" key={field.key}>
<label
className="mr-2 text-sm font-medium text-foreground whitespace-nowrap text-right"
@ -298,14 +306,11 @@ export default function IMSConfigurationsView() {
{field.type === 'select' && (
<select
className="input h-10 rounded border border-border bg-background px-3 text-sm flex-1"
value={field.key === 'isDisabled' ? (isDisabled === undefined ? '' : isDisabled.toString()) :
field.key === 'pageSize' ? pageSize.toString() : ''}
value={field.key === 'isDisabled' ? (isDisabled === undefined ? '' : isDisabled.toString()) : ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
if (field.key === 'isDisabled') {
const value = e.target.value;
setIsDisabled(value === '' ? undefined : value === 'true');
} else if (field.key === 'pageSize') {
setPageSize(parseInt(e.target.value));
}
}}
>
@ -333,6 +338,37 @@ export default function IMSConfigurationsView() {
</button>
</div>
</form>
{/* 高级搜索字段 */}
{showAdvanced && (
<div className="grid gap-x-8 gap-y-4 items-center md:grid-cols-3 grid-cols-1 mt-4 pt-4 border-t">
{advancedFields.map((field: SearchField) => (
<div className="flex flex-row items-center min-w-[200px] flex-1" key={field.key}>
<label
className="mr-2 text-sm font-medium text-foreground whitespace-nowrap text-right"
style={{ width: 80, minWidth: 80 }}
>
{field.label}
</label>
{field.type === 'select' && (
<select
className="input h-10 rounded border border-border bg-background px-3 text-sm flex-1"
value={field.key === 'pageSize' ? pageSize.toString() : ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
if (field.key === 'pageSize') {
setPageSize(parseInt(e.target.value));
}
}}
>
{field.options.map(opt => (
<option value={opt.value} key={opt.value}>{opt.label}</option>
))}
</select>
)}
</div>
))}
</div>
)}
</div>
{/* 表格整体卡片区域,包括工具栏、表格、分页 */}
<div className="rounded-md border bg-background p-4">
@ -360,6 +396,7 @@ export default function IMSConfigurationsView() {
onEdit={handleEdit}
onDelete={handleDelete}
onStatusChange={handleStatusChange}
onViewContent={handleViewContent}
page={pageNumber}
pageSize={pageSize}
total={total}
@ -396,6 +433,13 @@ export default function IMSConfigurationsView() {
isEdit={true}
isSubmitting={isSubmitting}
/>
{/* 查看配置内容对话框 */}
<IMSConfigurationViewDialog
open={viewOpen}
onOpenChange={setViewOpen}
configuration={selectedConfiguration}
/>
</main>
);
}

125
src/X1.WebUI/src/pages/ran-configurations/RANConfigurationViewDialog.tsx

@ -0,0 +1,125 @@
import React from 'react';
import { RANConfiguration } from '@/services/ranConfigurationService';
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { X } from 'lucide-react';
import { useToast } from '@/components/ui/use-toast';
import ConfigContentViewer from '@/components/ui/ConfigContentViewer';
interface RANConfigurationViewDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
configuration: RANConfiguration | null;
}
export default function RANConfigurationViewDialog({
open,
onOpenChange,
configuration
}: RANConfigurationViewDialogProps) {
const { toast } = useToast();
// 复制配置内容到剪贴板
const handleCopyContent = async () => {
if (!configuration?.configContent) return;
try {
await navigator.clipboard.writeText(configuration.configContent);
toast({
title: "复制成功",
description: "配置内容已复制到剪贴板",
});
} catch (error) {
console.error('复制失败:', error);
toast({
title: "复制失败",
description: "无法复制到剪贴板,请手动复制",
variant: "destructive",
});
}
};
// 下载配置内容为文件
const handleDownloadContent = () => {
if (!configuration?.configContent) return;
const blob = new Blob([configuration.configContent], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${configuration.name || 'ran-config'}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
toast({
title: "下载成功",
description: "配置文件已下载",
});
};
if (!configuration) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl h-[80vh] flex flex-col">
<div className="flex items-center justify-between w-full mb-4 flex-shrink-0">
<DialogTitle className="text-lg font-semibold">
- {configuration.name}
</DialogTitle>
<Button
variant="ghost"
size="sm"
onClick={() => onOpenChange(false)}
className="h-8 w-8 p-0"
>
<X className="h-4 w-4" />
</Button>
</div>
<div className="flex flex-col flex-1 space-y-4 overflow-hidden min-h-0">
{/* 配置信息 */}
<div className="grid grid-cols-2 gap-4 text-sm flex-shrink-0">
<div>
<Label className="text-sm font-medium text-gray-600"></Label>
<div className="mt-1 text-gray-900">{configuration.name}</div>
</div>
<div>
<Label className="text-sm font-medium text-gray-600"></Label>
<div className="mt-1">
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
configuration.isDisabled
? 'bg-red-100 text-red-800'
: 'bg-green-100 text-green-800'
}`}>
{configuration.isDisabled ? '禁用' : '启用'}
</span>
</div>
</div>
{configuration.description && (
<div className="col-span-2">
<Label className="text-sm font-medium text-gray-600"></Label>
<div className="mt-1 text-gray-900">{configuration.description}</div>
</div>
)}
</div>
{/* 配置内容查看器 */}
<div className="flex flex-col flex-1 min-h-0">
<Label className="text-sm font-medium text-gray-600 mb-2 flex-shrink-0">
</Label>
<ConfigContentViewer
content={configuration.configContent || '无配置内容'}
className="flex-1 min-h-0"
onCopy={handleCopyContent}
onDownload={handleDownloadContent}
/>
</div>
</div>
</DialogContent>
</Dialog>
);
}

24
src/X1.WebUI/src/pages/ran-configurations/RANConfigurationsTable.tsx

@ -2,6 +2,7 @@ import React from 'react';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { RANConfiguration } from '@/services/ranConfigurationService';
import StatusSwitch from '@/components/ui/StatusSwitch';
import { Eye } from 'lucide-react';
interface RANConfigurationsTableProps {
ranConfigurations: RANConfiguration[];
@ -9,6 +10,7 @@ interface RANConfigurationsTableProps {
onEdit: (configuration: RANConfiguration) => void;
onDelete: (configuration: RANConfiguration) => void;
onStatusChange?: (configuration: RANConfiguration, newStatus: boolean) => void;
onViewContent?: (configuration: RANConfiguration) => void;
page: number;
pageSize: number;
total: number;
@ -56,13 +58,21 @@ const DateDisplay: React.FC<{ date?: string }> = ({ date }) => {
};
// 配置内容预览组件
const ConfigContentPreview: React.FC<{ content: string }> = ({ content }) => {
const ConfigContentPreview: React.FC<{
content: string;
onViewContent?: () => void;
}> = ({ content, onViewContent }) => {
const maxLength = 50;
const truncated = content.length > maxLength ? content.substring(0, maxLength) + '...' : content;
return (
<div className="max-w-xs truncate text-gray-600" title={content}>
{truncated}
<div
className="max-w-xs truncate text-gray-600 cursor-pointer hover:text-blue-600 hover:underline flex items-center gap-1 group"
onClick={onViewContent}
title="点击查看完整内容"
>
<span>{truncated}</span>
<Eye className="h-3 w-3 opacity-0 group-hover:opacity-100 transition-opacity" />
</div>
);
};
@ -73,6 +83,7 @@ export default function RANConfigurationsTable({
onEdit,
onDelete,
onStatusChange,
onViewContent,
page,
pageSize,
total,
@ -104,7 +115,12 @@ export default function RANConfigurationsTable({
</div>
);
case 'configContent':
return <ConfigContentPreview content={configuration.configContent} />;
return (
<ConfigContentPreview
content={configuration.configContent}
onViewContent={() => onViewContent?.(configuration)}
/>
);
case 'isDisabled':
return (
<StatusSwitchCell

17
src/X1.WebUI/src/pages/ran-configurations/RANConfigurationsView.tsx

@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { ranConfigurationService, RANConfiguration, GetRANConfigurationsRequest, CreateRANConfigurationRequest, UpdateRANConfigurationRequest } from '@/services/ranConfigurationService';
import RANConfigurationsTable from './RANConfigurationsTable';
import RANConfigurationDrawer from './RANConfigurationDrawer';
import RANConfigurationViewDialog from './RANConfigurationViewDialog';
import { Input } from '@/components/ui/input';
import PaginationBar from '@/components/ui/PaginationBar';
import TableToolbar, { DensityType } from '@/components/ui/TableToolbar';
@ -12,7 +13,7 @@ import { useToast } from '@/components/ui/use-toast';
const defaultColumns = [
{ key: 'name', title: '配置名称', visible: true },
{ key: 'description', title: '描述', visible: true },
{ key: 'configContent', title: '配置内容', visible: false },
{ key: 'configContent', title: '配置内容', visible: true },
{ key: 'isDisabled', title: '状态', visible: true },
{ key: 'createdAt', title: '创建时间', visible: true },
{ key: 'actions', title: '操作', visible: true }
@ -49,6 +50,7 @@ export default function RANConfigurationsView() {
// 表单对话框状态
const [open, setOpen] = useState(false);
const [editOpen, setEditOpen] = useState(false);
const [viewOpen, setViewOpen] = useState(false);
const [selectedConfiguration, setSelectedConfiguration] = useState<RANConfiguration | null>(null);
// 提交状态
@ -85,6 +87,11 @@ export default function RANConfigurationsView() {
setEditOpen(true);
};
const handleViewContent = (configuration: RANConfiguration) => {
setSelectedConfiguration(configuration);
setViewOpen(true);
};
const handleDelete = async (configuration: RANConfiguration) => {
if (confirm(`确定要删除RAN配置 "${configuration.name}" 吗?`)) {
try {
@ -335,6 +342,7 @@ export default function RANConfigurationsView() {
onEdit={handleEdit}
onDelete={handleDelete}
onStatusChange={handleStatusChange}
onViewContent={handleViewContent}
page={pageNumber}
pageSize={pageSize}
total={total}
@ -371,6 +379,13 @@ export default function RANConfigurationsView() {
isEdit={true}
isSubmitting={isSubmitting}
/>
{/* 查看配置内容对话框 */}
<RANConfigurationViewDialog
open={viewOpen}
onOpenChange={setViewOpen}
configuration={selectedConfiguration}
/>
</main>
);
}
Loading…
Cancel
Save