多用户实时列表刷新方案咨询:SignalR可行性及优化建议
SignalR方案可行性及优化建议
SignalR完全是实现「用户编辑时自动从其他用户列表移除条目」需求的合适方案,你的技术方向没问题,但现有代码的逻辑和需求匹配度不高,下面是具体分析和优化建议:
现有代码的核心问题
- 触发时机错误:当前代码是在审批完成后发送刷新通知,但需求是用户开始编辑时就移除条目,逻辑触发点完全颠倒。
- 缺少编辑状态追踪:没有在数据库中标记条目是否被编辑,仅靠提交时的并发异常判断,无法提前阻止其他用户操作,也无法实时通知。
- 通知范围不合理:使用
Clients.All会给所有用户(包括当前编辑者)发通知,造成不必要的带宽浪费。 - 并发处理滞后:仅在保存数据时捕获并发异常,此时用户已经完成编辑操作,体验很差。
优化实现方案
1. 扩展实体字段
给MoveOrder添加编辑锁定相关字段,用于追踪状态:
public class MoveOrder { // 原有字段... public bool IsEditing { get; set; } // 是否正在编辑 public string? LockedByUserId { get; set; } // 锁定用户ID public DateTime? LockExpiryTime { get; set; } // 锁过期时间(防止永久锁定) }
2. 实现编辑锁定接口
当用户进入编辑页面时,调用此接口锁定条目,并实时通知其他用户移除该条目:
// Repository层 public async Task<(bool success, string message)> LockMoveOrder(string orderNo, string userId) { using (var transaction = await _context.Database.BeginTransactionAsync(IsolationLevel.Serializable)) { try { var existing = await _context.MoveOrders .FirstOrDefaultAsync(x => x.OrderNo == orderNo); if (existing == null) return (false, "订单不存在"); // 检查是否被其他用户锁定且未过期 if (existing.IsEditing && existing.LockExpiryTime > DateTime.Now) return (false, "该订单正在被其他用户编辑"); // 设置锁定状态 existing.IsEditing = true; existing.LockedByUserId = userId; existing.LockExpiryTime = DateTime.Now.AddMinutes(10); // 10分钟自动过期,可配置 await _context.SaveChangesAsync(); transaction.Commit(); // 通知除当前用户外的所有客户端移除条目 await _hub.Clients.Others.SendAsync("RemoveOrderFromList", orderNo); return (true, "锁定成功"); } catch (Exception ex) { transaction.Rollback(); return (false, $"锁定失败:{ex.Message}"); } } }
3. 实现编辑解锁接口
用户完成编辑或取消编辑时,解锁条目并通知其他用户恢复该条目:
// Repository层 public async Task<(bool success, string message)> UnlockMoveOrder(string orderNo, string userId) { using (var transaction = await _context.Database.BeginTransactionAsync(IsolationLevel.Serializable)) { try { var existing = await _context.MoveOrders .FirstOrDefaultAsync(x => x.OrderNo == orderNo && x.LockedByUserId == userId); if (existing == null) return (false, "订单不存在或不是你锁定的"); // 清除锁定状态 existing.IsEditing = false; existing.LockedByUserId = null; existing.LockExpiryTime = null; await _context.SaveChangesAsync(); transaction.Commit(); // 通知其他用户恢复条目 await _hub.Clients.Others.SendAsync("AddOrderToList", existing); return (true, "解锁成功"); } catch (Exception ex) { transaction.Rollback(); return (false, $"解锁失败:{ex.Message}"); } } }
4. 调整审批接口逻辑
审批前先检查条目是否被锁定,避免冲突;审批完成后通知其他用户更新条目状态(而非刷新整个列表):
// 修改原有ApprovalForMoveOrder方法 public async Task<(bool success, string message)> ApprovalForMoveOrder(MoveOrder moveorder) { using (var transaction = await _context.Database.BeginTransactionAsync(IsolationLevel.Serializable)) { try { var existing = await _context.MoveOrders .Where(x => x.OrderNo == moveorder.OrderNo) .ToListAsync(); if (!existing.Any()) return (false, "订单不存在"); // 检查是否有被锁定的条目 var lockedItem = existing.FirstOrDefault(x => x.IsEditing && x.LockExpiryTime > DateTime.Now); if (lockedItem != null) return (false, $"订单{lockedItem.OrderNo}正在被其他用户编辑"); foreach (var item in existing) { item.ApprovedDate = DateTime.Now; item.ApproveDateTempo = DateTime.Now; item.IsApprove = true; item.IsEditing = false; // 审批后自动解锁 item.LockedByUserId = null; item.LockExpiryTime = null; } await _context.SaveChangesAsync(); transaction.Commit(); // 通知其他用户更新该订单状态 await _hub.Clients.Others.SendAsync("UpdateOrderStatus", existing); return (true, "订单审批成功"); } catch (DbUpdateConcurrencyException) { transaction.Rollback(); return (false, "条目被其他用户锁定"); } catch (Exception ex) { transaction.Rollback(); return (false, $"审批失败:{ex.Message}"); } } }
额外建议
- 前端处理:
- 连接SignalR后,监听
RemoveOrderFromList、AddOrderToList、UpdateOrderStatus事件,实时更新列表。 - 处理SignalR断开重连场景,重连后重新拉取最新列表,避免数据不一致。
- 编辑页面定时刷新锁的过期时间(比如每5分钟调用一次延长锁定接口),防止用户编辑时锁过期。
- 连接SignalR后,监听
- 分布式场景:如果是多服务器部署,建议使用Redis分布式锁替代数据库事务锁,避免锁失效。
- 性能优化:通知时尽量只发送必要数据(比如
orderNo和状态字段),而非整个实体,减少带宽消耗。 - 过期锁清理:添加后台定时任务,定期清理过期的编辑锁,避免条目被永久锁定。
内容的提问来源于stack exchange,提问作者Aldrin Vega
相关产品推荐
相关产品推荐

