服务端高效处理拖拽排序

在这里插入图片描述在这里插入图片描述
目的:减少数据库压力

传统解决方案

获取拖拽后元素的前一个元素的位置,重新排序后面的所有元素,如果没有前一个元素,则统一全排。
例如:将item1拖拽到item6之后,按顺序排列可以不处理item6及之前的数据,但是必须重新处理item1及之后所有的数据。
在这里插入图片描述

位置间隔法

获取拖拽后前一个元素和后一个元素,设置当前顺序为中间数值,不用修改其他元素,如果顺序过于紧密则出发重平衡机制。
这里需要几个参数:初始值(BASE_SCORE),间隔(INITIAL_INTERVAL),平衡参数(MIN_GAP )。
1.第一个元素作为初始值
2.当新添加一个元素时,取最大值+间隔
3.当修改顺序时,计算相邻项目的中间值作为新元素顺序值。
4.当新元素值与前后元素间隔小于平衡参数时,触发重平衡。

初始数据

@Entity
public class SortableItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    // 使用Double类型存储分数(也可用BigDecimal提高精度)
    private Double sortScore;
    
    // 其他业务字段
    private String name;
    private boolean visible;
    
    // 初始分数分配
    @PrePersist
    public void initializeSortScore() {
        if (this.sortScore == null) {
            // 获取当前最大分数
            Double maxScore = repository.findMaxSortScore();
            this.sortScore = (maxScore != null) ? maxScore + INITIAL_INTERVAL : BASE_SCORE;
        }
    }
    
    // 常量定义
    private static final Double BASE_SCORE = 1000000.0;
    private static final Double INITIAL_INTERVAL = 10000.0;
}

插入移动实现

@Service
public class FractionalSortService {
    // 最小允许间隔(避免浮点精度问题)
    private static final Double MIN_GAP = 0.0001;
    
    @Transactional
    public void moveItem(Long itemId, Long prevId, Long nextId) {
        SortableItem movingItem = itemRepository.findById(itemId).orElseThrow();
        SortableItem prevItem = prevId != null ? itemRepository.findById(prevId).orElse(null) : null;
        SortableItem nextItem = nextId != null ? itemRepository.findById(nextId).orElse(null) : null;
        
        // 计算新分数
        Double newScore = calculateNewScore(prevItem, nextItem);
        
        // 检查是否需要重平衡
        if (requiresRebalance(prevItem, newScore, nextItem)) {
            rebalanceItems(prevItem, nextItem);
            // 重新计算新位置
            newScore = calculateNewScore(prevItem, nextItem);
        }
        
        // 更新分数
        movingItem.setSortScore(newScore);
        itemRepository.save(movingItem);
    }
    
    private Double calculateNewScore(SortableItem prev, SortableItem next) {
        Double prevScore = (prev != null) ? prev.getSortScore() : 0.0;
        Double nextScore = (next != null) ? next.getSortScore() : prevScore + 2 * INITIAL_INTERVAL;
    
        // 简单情况:有足够间隔
        if (nextScore - prevScore > MIN_GAP) {
            return (prevScore + nextScore) / 2.0;
        }
        
        // 复杂情况:使用扩展范围算法
        return extendedRangeCalculation(prevScore, nextScore);
    }
    
    private Double extendedRangeCalculation(Double prevScore, Double nextScore) {
        // 使用对数刻度扩展范围
        double range = nextScore - prevScore;
        double logRange = Math.log(range);
        
        // 在指数空间计算中点
        double midLog = (Math.log(prevScore) + Math.log(nextScore)) / 2;
        return Math.exp(midLog);
    }
    
    private boolean requiresRebalance(SortableItem prev, Double newScore, SortableItem next) {
        if (prev != null && (newScore - prev.getSortScore()) < MIN_GAP) {
            return true;
        }
        if (next != null && (next.getSortScore() - newScore) < MIN_GAP) {
            return true;
        }
        return false;
    }
}

重平衡实现

private void rebalanceItems(SortableItem startItem, SortableItem endItem) {
    // 获取需要重平衡的范围
    List<SortableItem> items = findItemsBetween(startItem, endItem);
    
    if (items.size() < 2) return;
    
    // 计算总范围和步长
    double startScore = items.get(0).getSortScore();
    double endScore = items.get(items.size()-1).getSortScore();
    double totalRange = endScore - startScore;
    double step = totalRange / (items.size() + 1);
    
    // 分配新分数
    for (int i = 0; i < items.size(); i++) {
        SortableItem item = items.get(i);
        item.setSortScore(startScore + step * (i + 1));
    }
    
    // 批量保存
    itemRepository.saveAll(items);
}

private List<SortableItem> findItemsBetween(SortableItem start, SortableItem end) {
    // 根据业务需求确定范围大小
    int neighborCount = 50; // 前后各取50个项目
    
    List<SortableItem> before = itemRepository.findPreviousItems(
        start != null ? start.getId() : null, 
        neighborCount
    );
    
    List<SortableItem> after = itemRepository.findNextItems(
        end != null ? end.getId() : null,
        neighborCount
    );
    
    // 合并结果
    List<SortableItem> result = new ArrayList<>();
    result.addAll(before);
    if (start != null) result.add(start);
    if (end != null) result.add(end);
    result.addAll(after);
    
    // 按分数排序
    result.sort(Comparator.comparingDouble(SortableItem::getSortScore));
    return result;
}

重平衡策略选择:

局部重平衡:仅重平衡受影响区域(推荐)
全局重平衡:定期重排整个列表(维护时使用)
局部更新可以选择传入位置前后50条重新等间隔排序。

在这里插入图片描述
此文助力马上行计划管理WEB端四象限拖拽处理。

欢迎体验微信小程序:马上行计划管理
在这里插入图片描述

WEB端在紧锣密鼓的开发中敬请期待

参考
codesandbox.io
react-beautiful-dnd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值