拖拽排序 + 置顶和置底逻辑的实现

最近在实现拖拽排序、置顶和置底逻辑时遇到了比较恶心的问题。以下为记录

背景

我们系统的排序是基于数据库中的 sort_num 字段进行的,sort_num 值越大,数据排位越靠前。新增数据时,其 sort_num 默认为其 id 值。假设当前有 10 条数据,最大 id 为 10,新增数据 id=11,sort_num 也为 11,因此新增的数据会排在最前面。

拖拽排序

最简单的拖拽排序方法是对当前页面的数据重新排序。假设当前页面有 5 条数据,排序顺序为:

1
2
3
4
5

当用户将 id=3 的数据拖动到第一位时,前端会传递新的排序顺序:

3
1
2
4
5

服务端会获取这些数据中最大

ID  排序号
3   5
1   4
2   3
4   2
5   1

这种方式可以实现拖拽排序。

置顶和置底逻辑

置顶:获取sort_num最大的一条记录,把当前需要置顶的id的sort_num改为和最大记录一致。
置底:获取sort_num最大的一条记录,把当前需要指于地步

为解决此问题,我们引入了多字段排序。即在 sort_num 相同的情况下,使用 update_time 作为第二排序字段。每次更新数据时,update_time 会更新为当前时间,因此可以通过 ORDER BY sort_num DESC, update_time DESC 实现正确排序。

置底逻辑

置底逻辑与置顶类似,不同之处在于我们会找到当前最小的排序值,并将其减 1 作为新的排序值。然而,由于 sort_num 字段是无符号类型,当最小排序值为 0 时,再减 1 仍然是 0。例如:

【id=4,sort_num=0,update_time=2024-07-09 17:00:00】
【id=5,sort_num=0,update_time=2024-07-09 17:01:00】
此时 id=4 在最底部,id=5 倒数第二,显然不符合预期。

解决方案

将 sort_num 字段改为有符号类型,使其支持负数。这样在置底逻辑中,当最小排序值为 0 时,我们可以继续向负数方向延伸,避免排序冲突。

func (s *adminRewardService) AdminRewardActivitySetSortNum(ctx context.Context, req *api.AdminRewardActivitySetSortNumReq) (resp *api.AdminRewardActivitySetSortNumResp, err error) {
	ids := req.GetId()
	idsCopy := make([]int64, len(ids))
	copy(idsCopy, ids)
	resp = new(api.AdminRewardActivitySetSortNumResp)
	var wg sync.WaitGroup
	var mu sync.Mutex 

	// 置顶:找到最顶部的数据的排序,然后把当前需要置顶的id改为最顶部的数据的id一致,两者一致通过updated_time排序
	if req.GetTopId() > 0 {
		topRecord, err := userIntegralRedeemActivityDao.GetTopRecord(ctx, s.Dao.DB)
		if err != nil {
			return nil, ecode.UnknownError
		}
		if err = userIntegralRedeemActivityDao.UpdateSortNum(ctx, req.GetTopId(), topRecord.SortNum, s.Dao.DB); err != nil {
			zlog.WithContext(ctx).Sugar().Errorf("SetTopSortNumError:%v", err)
		}
		return resp, ecode.SuccessCode
	}

	// 置底排序:找到最底部数据排序,然后把当前数据排序改为最底部排序-1
	if req.GetBottomId() > 0 {
		bottomRecord, err := userIntegralRedeemActivityDao.GetBottomRecord(ctx, s.Dao.DB)
		if err != nil {
			return nil, ecode.UnknownError
		}
		if err = userIntegralRedeemActivityDao.UpdateSortNum(ctx, req.GetBottomId(), bottomRecord.SortNum-1, s.Dao.DB); err != nil {
			zlog.WithContext(ctx).Sugar().Errorf("SetBottomSortNumError:%v", err)
		}
		return resp, ecode.SuccessCode
	}

	// 如果就一个元素,不排序
	if len(req.GetId()) <= 1 {
		return resp, ecode.SuccessCode
	}

	// 拖拽排序逻辑
	maxSortId := req.GetId()[0]
	//minSortId := maxSortId - int64(len(ids))

	maxSort, _ := userIntegralRedeemActivityDao.GetUserIntegralRedeemActivitySortNumById(ctx, maxSortId, s.Dao.DB)
	minSort := maxSort - int64(len(req.GetId()))

	mark := 0
	// 这是准备排序的index
	for index := maxSort; index > minSort; index-- {
		wg.Add(1)
		currentId := ids[mark]

		go func(goCtx context.Context, id int64, index int64) {
			defer wg.Done()
			if updateError := userIntegralRedeemActivityDao.UpdateSortNum(ctx, id, index, s.Dao.DB); updateError != nil {
				mu.Lock()
				err = updateError
				mu.Unlock()
			}
		}(utils.GetTraceCtx(ctx), currentId, index)
		mark++
	}

	wg.Wait()

	if err != nil {
		zlog.WithContext(ctx).Sugar().Errorf("拖拽排序失败, err = [%s]", err)
		return resp, ecode.UnknownError
	}

	return resp, ecode.SuccessCode
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值