You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
31 KiB
31 KiB
WebSocketMiddleware 修改记录
1. 初始分析
- 分析了
_channelPool
的使用情况 - 发现 Channel 对象池的实现和潜在问题
2. 问题识别
- Channel 对象频繁创建和销毁,可能导致 GC 压力
- 缺乏对 Channel 状态的验证
- 没有监控机制来跟踪对象池的效率
3. 初步修复
- 实现了
ChannelPooledObjectPolicy
来管理 Channel 对象池 - 添加了状态验证和错误处理
- 创建了
ChannelPoolMonitor
来监控对象池性能
4. 并发问题修复
- 识别并修复了快速重连时的竞态条件
- 引入了
ConcurrentDictionary<string, ChannelInfo> _activeChannels
来跟踪活跃的 Channel - 确保每个连接 ID 只有一个活跃的 Channel
5. 异步任务生命周期问题
- 分析了
StartMessageProcessingLoop
异步任务的取消机制 - 发现异步任务可能在使用中的 Channel 被归还后仍在运行
6. 进一步优化
- 增强了
ChannelInfo
类,包含CancellationTokenSource
和ProcessingTask
- 改进了
ReturnMessageChannelAsync
方法,确保任务完成后再归还 Channel
7. 代码重构优化
- 提取了
GetMessageChannel
和ReturnMessageChannelAsync
方法 - 改进了代码结构和可读性
- 添加了详细的日志记录
8. Channel 状态分析
- 深入分析了 Channel 的完成状态和重用限制
- 确认了 .NET Channel 一旦关闭就无法重用的设计
9. 最终修复方案
- 完善了 Channel 对象池的归还逻辑
- 确保了异步任务的正确取消和等待
- 优化了资源管理和清理流程
10. CancellationTokenSource 统一修复
- 问题识别:发现
HandleWebSocketConnection
和GetMessageChannel
创建了两个不同的CancellationTokenSource
- 风险分析:
StartMessageProcessingLoop
使用HandleWebSocketConnection
中的cts.Token
ReturnMessageChannelAsync
取消的是ChannelInfo.CancellationTokenSource
- 两个不同的实例导致任务无法正确取消
- 修复方案:
- 修改
HandleWebSocketConnection
使用ChannelInfo.CancellationTokenSource.Token
- 确保所有异步任务使用同一个
CancellationTokenSource
- 正确设置
channelInfo.ProcessingTask = processTask
- 修改
- 修复效果:
- ✅ 统一了 CancellationTokenSource 的使用
- ✅ 确保任务可以被正确取消
- ✅ 避免了资源泄漏风险
- ✅ 提高了代码的一致性和可维护性
11. Channel 状态验证修复
- 问题识别:发现
ObjectPool.Get()
可能返回已关闭的 Channel(Items = 0, Closed = true) - 问题根源:
ObjectPool.Return()
方法返回void
,不关心IPooledObjectPolicy.Return()
的返回值- 即使
ChannelPooledObjectPolicy.Return()
返回false
,ObjectPool 仍可能将 Channel 放回池中 - 下次
ObjectPool.Get()
时,可能返回一个已关闭的 Channel
- 修复方案:
- 在
GetMessageChannel
中添加IsChannelUsable()
验证方法 - 实现重试机制(最多3次),避免获取到不可用的 Channel
- 如果对象池获取失败或获取的 Channel 不可用,创建新的 Channel
- 将不可用的 Channel 归还到池中(虽然它不会被重用)
- 在
- 验证逻辑:
private bool IsChannelUsable(Channel<WebSocketMessage> channel) { // 检查 Channel 是否已关闭 if (channel.Reader.Completion.IsCompleted) return false; // 检查是否有未处理的消息 if (channel.Reader.TryPeek(out _)) return false; // 检查写入器状态 if (!channel.Writer.TryComplete()) return false; return true; }
- 修复效果:
- ✅ 确保不会使用已关闭的 Channel
- ✅ 提高了对象池的可靠性
- ✅ 添加了重试机制,提高成功率
- ✅ 增强了错误处理和日志记录
12. Channel 对象池根本问题修复 (2024-12-19)
- 问题识别:用户发现
messageChannel = _channelPool.Get()
返回的 Channel 状态为Items = 0, Closed = true
,无法使用 - 问题根源分析:
_channelPool.Return(messageChannel)
执行完成后,Channel 被标记为关闭状态- 当新的连接请求到达时,对象池可能返回一个已经关闭的 Channel
ChannelPooledObjectPolicy.Return()
方法中的逻辑有问题,没有正确处理 Channel 的重用
- 修复方案:
-
改进 ChannelPooledObjectPolicy.Return() 方法:
- 添加异常处理,确保方法稳定性
- 改进 Channel 状态检查逻辑
- 只有当 Channel 未关闭时才返回 true(可重用)
-
添加 Channel 获取验证机制:
- 新增
GetValidMessageChannel()
方法,实现重试机制 - 新增
IsChannelValid()
方法,验证 Channel 状态 - 新增
ReturnMessageChannel()
方法,安全地返回 Channel 到对象池
- 新增
-
重试机制:
- 最多重试3次获取有效 Channel
- 如果对象池无法提供有效 Channel,创建新的
- 添加详细的日志记录,便于问题排查
-
- 修复效果:
- ✅ 确保获取到的 Channel 始终可用
- ✅ 避免了使用已关闭 Channel 的问题
- ✅ 提高了对象池的可靠性
- ✅ 增强了错误处理和恢复机制
- ✅ 添加了详细的日志记录
13. 当前状态
- ✅ Channel 对象池正常工作
- ✅ 并发问题已解决
- ✅ 异步任务生命周期管理正确
- ✅ CancellationTokenSource 统一使用
- ✅ Channel 状态验证完善
- ✅ 资源管理和清理完善
- ✅ 监控和日志记录完整
- ✅ Channel 对象池根本问题已修复
14. 剩余风险评估
修复后的系统现在具有以下特点:
✅ 已解决的风险
- CancellationTokenSource 统一:所有异步任务使用同一个 CancellationTokenSource
- 任务取消正确:
ReturnMessageChannelAsync
可以正确取消所有相关任务 - Channel 状态验证:确保不会使用已关闭的 Channel
- 资源管理完善:Channel 和 CancellationTokenSource 都被正确释放
- 并发安全:使用
ConcurrentDictionary
确保线程安全 - 对象池可靠性:Channel 对象池现在能够正确处理重用和状态验证
⚠️ 仍需关注的风险
- 性能影响:Channel 状态验证和重试机制可能增加少量性能开销
- 重试延迟:在极端情况下,重试机制可能导致连接建立延迟
- 配置调优:可能需要根据实际使用情况调整重试次数和超时时间
🎯 建议
- 监控 Channel 状态验证的成功率和重试次数
- 根据实际使用情况调整重试参数(当前设置为3次重试)
- 定期审查对象池的性能指标
- 监控连接建立时间,确保重试机制不会显著影响用户体验
15. ChannelPooledObjectPolicy 日志跟踪功能 (2024-12-19)
- 功能添加:为
ChannelPooledObjectPolicy
添加了详细的跟踪日志 - 日志内容:
- Create() 方法:记录新 Channel 实例的创建,包含 Channel ID(HashCode)
- Return() 方法:记录 Channel 重用状态检查的详细过程
- Channel 完全关闭时的日志
- Channel 仍然可用时的日志
- Channel 已关闭时的日志
- 异常情况的错误日志
- 技术实现:
- 添加了
ILogger<ChannelPooledObjectPolicy>
依赖注入 - 修改了
WebSocketMiddleware
构造函数,添加ILoggerFactory
参数 - 使用
loggerFactory.CreateLogger<ChannelPooledObjectPolicy>()
创建专用 logger
- 添加了
- 日志级别:
- Debug 级别:Channel 创建和状态检查的详细信息
- Error 级别:异常情况的错误信息
- 监控价值:
- 可以跟踪 Channel 对象的生命周期
- 监控对象池的重用效率
- 快速定位 Channel 状态问题
- 分析对象池性能瓶颈
16. IsChannelValid 方法日志跟踪功能 (2024-12-19)
- 功能添加:为
IsChannelValid
方法添加了详细的跟踪日志 - 日志内容:
- 方法开始:记录开始检查 Channel 有效性的日志,包含 Channel ID
- 读取器状态检查:记录 Channel 读取器的完成状态
- 写入器状态检查:记录 Channel 写入器的完成状态
- 检查结果:记录 Channel 是否有效的最终结果
- 异常处理:记录检查过程中发生的异常
- 技术实现:
- 使用
channel.GetHashCode()
作为 Channel ID 进行标识 - 分别检查
channel.Reader.Completion.IsCompleted
和channel.Writer.TryComplete()
- 在每个检查步骤都添加了详细的 Debug 级别日志
- 改进了异常处理,捕获具体的异常信息
- 使用
- 日志级别:
- Debug 级别:Channel 状态检查的详细信息
- Error 级别:检查过程中发生的异常
- 监控价值:
- 可以详细跟踪 Channel 状态验证过程
- 快速定位 Channel 无效的具体原因
- 监控 Channel 对象池的验证成功率
- 帮助分析 Channel 重用失败的原因
17. Channel 对象池简化移除 (2024-12-19)
- 问题识别:用户指出既然每次都是创建新的 Channel,应该简化移除
_channelPool
- 问题分析:
- 对象池无意义:每次
GetValidMessageChannel()
最终都是创建新的 Channel - 逻辑复杂:对象池的重试机制和验证逻辑增加了复杂性
- 性能开销:对象池的获取、验证、返回操作增加了不必要的开销
- 对象池无意义:每次
- 解决方案:完全移除 Channel 对象池,直接创建新的 Channel
- 修复内容:
- 移除对象池相关代码:
- 删除
_channelPool
字段 - 删除
ChannelPooledObjectPolicy
类 - 删除
GetValidMessageChannel()
方法 - 删除
IsChannelValid()
方法 - 删除
ReturnMessageChannel()
方法
- 删除
- 简化 Channel 创建:
- 新增
CreateNewMessageChannel()
方法 - 直接创建新的 Channel 实例,无需验证和重试
- 移除对象池相关的复杂逻辑
- 新增
- 清理依赖:
- 移除
Microsoft.Extensions.ObjectPool
引用 - 移除
ILoggerFactory
参数(不再需要创建 ChannelPooledObjectPolicy logger)
- 移除
- 移除对象池相关代码:
- 修复效果:
- ✅ 简化了代码逻辑,提高了可维护性
- ✅ 消除了对象池的复杂性和潜在问题
- ✅ 减少了不必要的性能开销
- ✅ 确保每个连接都有全新的 Channel
- ✅ 解决了重连客户端无法使用的问题
- 性能影响:
- 轻微的性能开销:每次连接创建新的 Channel
- 但避免了对象池的复杂性和验证开销
- 总体而言,简化带来的好处大于性能开销
18. WebSocket Channel 管理重构 (2024-12-19)
- 问题识别:用户要求将
CreateNewMessageChannel
提取为单独的类来管理 Channel 的创建、记录和释放 - 命名优化:将通用的
ChannelManager
重命名为WebSocketChannelManager
,避免命名冲突 - 文件结构优化:创建了清晰的目录结构
src/X1.WebSocket/Services/ChannelManagement/
- 功能增强:
- Channel 生命周期管理:
- 创建:
CreateNewMessageChannel(string connectionId)
- 释放:
ReleaseMessageChannel(Channel<WebSocketMessage> channel, string connectionId)
- 统计:
GetStatistics()
返回总创建数、总释放数、活跃数
- 创建:
- 详细统计信息:
- 每个 Channel 的消息处理数量
- 每个 Channel 的总字节处理量
- Channel 的创建时间和释放时间
- Channel 的存活时间统计
- 监控功能:
- 每创建100个 Channel 自动记录统计信息
- 提供活跃 Channel 列表查询
- 支持按连接ID查找 Channel 信息
- 清理已释放的 Channel 信息
- Channel 生命周期管理:
- 技术实现:
- 线程安全:使用
ConcurrentDictionary<int, WebSocketChannelInfo>
管理活跃 Channel - 原子操作:使用
Interlocked
进行计数器的线程安全操作 - 字段优化:将
MessageCount
和TotalBytesProcessed
改为字段以支持ref
操作 - 异常处理:完善的异常处理和日志记录
- 线程安全:使用
- 代码重构:
- 分离关注点:将 Channel 管理逻辑从
WebSocketMiddleware
中分离 - 依赖注入:通过构造函数注入
ILogger
- 命名空间清晰:使用
CellularManagement.WebSocket.Services.ChannelManagement
命名空间 - 接口设计:提供清晰的公共接口,便于扩展和维护
- 分离关注点:将 Channel 管理逻辑从
- 日志增强:
- 创建日志:记录 Channel ID、连接ID、总创建数
- 释放日志:记录存活时间、处理消息数、总字节数
- 统计日志:定期记录 Channel 统计信息
- 错误日志:详细的异常处理和错误记录
- 修复效果:
- ✅ 代码结构更清晰,职责分离明确
- ✅ 避免了命名冲突,使用更具体的类名
- ✅ 提供了完整的 Channel 生命周期管理
- ✅ 增强了监控和统计功能
- ✅ 改进了日志记录和错误处理
- ✅ 便于后续扩展和维护
2024-12-19 - WebSocket Channel管理器优化
修改文件
src/X1.WebSocket/Services/ChannelManagement/WebSocketChannelManager.cs
主要改进
1. 连接级别的锁机制
- 添加了
ConcurrentDictionary<string, SemaphoreSlim> _connectionLocks
来管理连接级别的锁 - 确保同一连接的Channel操作串行化,避免并发问题
- 实现了
CleanupConnectionLock
方法来清理连接锁资源
2. 异步操作统一
- 移除了同步版本的Channel操作方法
- 统一使用异步方法
CreateNewMessageChannel
和ReleaseMessageChannel
- 提高了系统的响应性和并发处理能力
3. 增强的Channel生命周期管理
- 改进了Channel创建时的重复检查逻辑
- 添加了超时机制,避免死锁情况
- 增强了Channel释放时的错误处理和日志记录
4. 统计信息增强
- 添加了连接状态检查方法
GetConnectionStatus
- 新增连接统计方法
GetConnectionStatistics
- 提供了更详细的Channel和连接管理信息
5. 资源清理优化
- 实现了
CleanupReleasedChannels
方法来清理已释放的Channel信息 - 添加了连接锁的自动清理机制
- 改进了内存管理和资源释放
技术特性
- 使用
ConcurrentDictionary
确保线程安全 - 实现了原子操作来更新统计信息
- 添加了详细的日志记录用于调试和监控
- 支持Channel的存活时间统计和消息处理统计
性能优化
- 使用连接级别的锁减少锁竞争
- 实现了Channel的批量统计记录(每100个记录一次)
- 优化了Channel查找和清理算法
监控和调试
- 提供了丰富的统计信息接口
- 添加了详细的日志记录
- 支持连接状态和Channel状态的实时查询
2024-12-19 WebSocket超时判断逻辑分析
问题分析
检查 WebSocketMiddleware.cs
中第320-321行的超时判断逻辑:
// 检查消息是否超时
if (IsMessageTimeout(messageStartTime))
发现的问题
-
超时判断逻辑不合理:
messageStartTime
在ProcessWebSocketMessages
方法开始时设置为连接开始时间- 在每次处理有效消息后,
messageStartTime
被更新为当前时间(第350行) - 但是超时判断使用的是
MessageSendTimeout
(30秒),这个配置项名称与实际用途不符
-
配置项命名错误:
MessageSendTimeout
实际上用于控制消息接收超时,而不是发送超时- 应该重命名为
MessageReceiveTimeout
或MessageProcessingTimeout
-
超时逻辑缺陷:
- 当前逻辑会在每次消息处理后重置
messageStartTime
- 这意味着如果客户端在30秒内发送任何消息,超时计时器就会重置
- 这可能导致连接永远不会因为超时而关闭,即使客户端长时间不活跃
- 当前逻辑会在每次消息处理后重置
-
阻塞接收问题:
webSocket.ReceiveAsync()
是阻塞等待,如果客户端长时间不发送消息,代码会一直阻塞- 超时检查永远不会执行,导致连接永远不会被关闭
- 存在严重的资源泄漏风险
已完成的修复
-
重命名配置项:
- 将
WebSocketOptions.MessageSendTimeout
重命名为MessageReceiveTimeout
- 更新了配置项的注释说明
- 将
-
修改超时判断逻辑:
- 分离了连接超时和消息接收超时的检查
- 添加了
IsConnectionTimeout()
方法检查整个连接的超时(5分钟) - 添加了
IsMessageReceiveTimeout()
方法检查消息接收超时(30秒) - 使用
lastMessageTime
来跟踪最后一次接收消息的时间
-
更新相关方法:
- 修改了
ProcessWebSocketMessages
方法签名,添加了lastMessageTime
参数 - 修改了
ProcessMessage
方法签名,移除了不必要的messageStartTime
参数 - 添加了
HandleConnectionTimeout()
和HandleMessageReceiveTimeout()
方法 - 更新了
WebSocketMessageHandlerAdapter
中的超时配置引用
- 修改了
-
改进的超时机制:
- 连接超时:基于连接开始时间,超过5分钟关闭连接
- 消息接收超时:基于最后消息时间,超过30秒没有新消息则关闭连接
- 这样可以确保连接在真正超时时能够正确关闭
-
修复阻塞接收问题:
- 添加了
ReceiveMessageWithTimeout()
方法,实现带超时的消息接收 - 使用
CancellationTokenSource
创建带超时的取消令牌 - 动态计算剩余超时时间,使用较小的超时值
- 在超时时正确关闭连接并返回null
- 确保即使客户端长时间不发送消息,连接也能在超时后正确关闭
- 添加了
移除超时判断的风险分析
低风险场景:
- 正常的长连接应用:如聊天、实时监控、推送通知
- 客户端主动管理连接:客户端会定期发送心跳或主动关闭
- 网络环境稳定:连接质量好,很少出现网络问题
高风险场景:
- 僵尸连接:客户端异常断开但服务器不知道
- 资源泄漏:大量无效连接占用服务器资源
- 网络异常:网络中断但连接状态未更新
- 客户端崩溃:客户端进程异常退出
具体风险:
-
资源泄漏风险 ⚠️
- 每个连接占用内存缓冲区、文件描述符、线程资源
- 大量僵尸连接可能导致服务器内存耗尽
-
性能下降风险 ⚠️
- 服务器需要维护连接列表、心跳检测、状态同步
- 无效连接越多,性能越差
-
连接数限制风险 ⚠️
- 僵尸连接占用连接数,新连接无法建立
- 影响系统的可扩展性
建议的解决方案:
- 保留超时机制:但使用更合理的超时时间(如5-10分钟)
- 实现心跳机制:客户端定期发送心跳包
- 连接状态检查:定期检查WebSocket连接的实际状态
- 应用层超时:在应用层处理业务超时,而不是传输层
当前配置值
MessageReceiveTimeout
: 30秒ConnectionTimeout
: 5分钟
结论
已成功修复了超时判断逻辑的设计缺陷和阻塞接收问题,现在连接能够正确地在超时后关闭,避免了资源泄漏问题。新的实现确保了:
- 连接超时检查正常工作
- 消息接收超时检查正常工作
- 不会因为阻塞等待而忽略超时
- 资源能够及时释放
建议保留超时机制,但可以根据实际业务需求调整超时时间,或者实现更智能的心跳机制。
2024-12-19 - .gitignore 文件更新
- 修改内容:将
X1.WebAPI.logs/
添加到.gitignore
文件中 - 修改位置:在
# Node modules
部分后添加了# Logs
部分 - 修改原因:避免将日志文件提交到版本控制系统
- 修改效果:
- ✅ 日志文件不会被 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: 更新处理器逻辑,支持同时删除绑定关系
- CreateNetworkStackConfigCommand.cs: 将
-
NetworkStackConfigs Queries
- GetNetworkStackConfigByIdResponse.cs: 将
StackId
改为NetworkStackName
,添加StackCoreIMSBindingResponseItem
列表 - GetNetworkStackConfigByIdQueryHandler.cs: 更新处理器逻辑,使用导航属性优化性能
- GetNetworkStackConfigsResponse.cs: 将
StackId
改为NetworkStackName
,添加StackCoreIMSBindingResponseItem
列表 - GetNetworkStackConfigsQueryHandler.cs: 更新处理器逻辑,使用导航属性优化性能
- GetNetworkStackConfigByIdResponse.cs: 将
-
StackCoreIMSBindings Commands
- CreateStackCoreIMSBindingCommand.cs: 将
StackId
改为NetworkStackConfigId
- CreateStackCoreIMSBindingResponse.cs: 将
StackId
改为NetworkStackConfigId
- CreateStackCoreIMSBindingCommandHandler.cs: 更新处理器逻辑
- UpdateStackCoreIMSBindingResponse.cs: 将
StackId
改为NetworkStackConfigId
- UpdateStackCoreIMSBindingCommandHandler.cs: 更新处理器逻辑
- DeleteStackCoreIMSBindingCommandHandler.cs: 更新日志信息
- CreateStackCoreIMSBindingCommand.cs: 将
-
StackCoreIMSBindings Queries
- GetStackCoreIMSBindingByIdResponse.cs: 将
StackId
改为NetworkStackConfigId
- GetStackCoreIMSBindingByIdQueryHandler.cs: 更新处理器逻辑
- GetStackCoreIMSBindingsResponse.cs: 将
StackId
改为NetworkStackConfigId
- GetStackCoreIMSBindingsQueryHandler.cs: 更新处理器逻辑
- GetStackCoreIMSBindingsQuery.cs: 将
StackId
改为NetworkStackConfigId
- GetStackCoreIMSBindingByIdResponse.cs: 将
新增功能
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
- 列表查询包含绑定关系
- 减少数据库往返: 从多次查询优化为单次查询
修改原因
- 简化设计:移除冗余的
StackId
字段,直接使用Id
作为关联键 - 减少维护成本:不需要维护两个标识符
- 避免数据不一致:消除了
Id
和StackId
可能不一致的风险 - 提高性能:减少索引维护开销,优化查询性能
- 增加业务语义:添加
NetworkStackName
提供更好的业务标识 - 提升用户体验:支持一次性操作网络栈配置及其绑定关系,减少API调用次数
- 数据完整性:确保删除操作级联删除相关数据
- 查询性能:使用导航属性避免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