You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多用户实时列表刷新方案咨询:SignalR可行性及优化建议

SignalR方案可行性及优化建议

SignalR完全是实现「用户编辑时自动从其他用户列表移除条目」需求的合适方案,你的技术方向没问题,但现有代码的逻辑和需求匹配度不高,下面是具体分析和优化建议:

现有代码的核心问题

  1. 触发时机错误:当前代码是在审批完成后发送刷新通知,但需求是用户开始编辑时就移除条目,逻辑触发点完全颠倒。
  2. 缺少编辑状态追踪:没有在数据库中标记条目是否被编辑,仅靠提交时的并发异常判断,无法提前阻止其他用户操作,也无法实时通知。
  3. 通知范围不合理:使用Clients.All会给所有用户(包括当前编辑者)发通知,造成不必要的带宽浪费。
  4. 并发处理滞后:仅在保存数据时捕获并发异常,此时用户已经完成编辑操作,体验很差。

优化实现方案

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分钟调用一次延长锁定接口),防止用户编辑时锁过期。
  • 分布式场景:如果是多服务器部署,建议使用Redis分布式锁替代数据库事务锁,避免锁失效。
  • 性能优化:通知时尽量只发送必要数据(比如orderNo和状态字段),而非整个实体,减少带宽消耗。
  • 过期锁清理:添加后台定时任务,定期清理过期的编辑锁,避免条目被永久锁定。

内容的提问来源于stack exchange,提问作者Aldrin Vega

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 22:20:41