拖拽排序后端設(shè)計與實現(xiàn)

背景

最近在做課程后臺開發(fā)時,遇到一個很有意思的問題。問題是這樣的,有多節(jié)課程,運營人員可以根據(jù)需要調(diào)整課程的先后順序,從而更改課程的展示順序。

接到這個需求,立刻就想到了以前做首頁banner輪播圖的管理后臺。輪播圖中每一張圖有先后順序之分,為了進(jìn)行圖片排序,在設(shè)計banner實體時,設(shè)計了一個 weight (權(quán)重)字段。然后,讓運營人員給每張圖填寫1~500值,值越大,則排序越靠前。由于輪播圖展示的圖片有限(不超過5張),即使通過人工填值的方式管理順序,也不是很麻煩。但是,這種填權(quán)重管理順序的方式不適用于數(shù)據(jù)量較多的課程管理。那么,如何解決這種排序問題呢?

為了解決元素自定義排序問題,我們想了很多解決辦法。例如,

  • 增加上移/下移功能,更改當(dāng)前元素排序
  • 填權(quán)重,或增減權(quán)重值
  • 置頂排序
  • 拖拽排序

最后,綜合考慮了一下,認(rèn)為第四種最符合日常操作的習(xí)慣。針對拖拽排序進(jìn)行了后端方案設(shè)計。

image

需求描述

  • 允許更改元素的排序;
  • 允許新增數(shù)據(jù),并更新現(xiàn)有排序;
  • 允許刪除數(shù)據(jù),并更新現(xiàn)有排序。

解決辦法

方法一 全量更新元素位置法

適用場景:排序元素數(shù)量較少

原理:每個元素?fù)碛幸粋€字段,表示元素當(dāng)前排序的位置。通過前端排序,將排好的元素位置,一次性發(fā)送到后端。然后,后端統(tǒng)一更新所有元素的位置。

具體實現(xiàn)

實體設(shè)計:增加排序字段 sort,表示元素當(dāng)前的位置。例如,sort = 1,則表示元素處于第一位。

接口設(shè)計:

// 排序接口
POST /courses/sorting

// 格式:JSON
// 數(shù)組中為元素的ID
// 元素在數(shù)組中的序號,表示了元素的位置
// 例如,ID為3的元素,排序為 1,ID為2的元素,排序為2
[3, 2, 4, 1]

前端邏輯:當(dāng)前端排序后,或刪除元素后,將剩余元素ID,以數(shù)組的形式發(fā)送給后端。數(shù)組的索引序號,則表示元素當(dāng)前的排序。

后端實現(xiàn)邏輯:

  • 刪除不存在前端數(shù)組中的ID。將前端 ids,與服務(wù)器端的 ids 進(jìn)行對比。刪除服務(wù)器端存在但前端不存在的 ids
  • 按照數(shù)組的排序,更新所有元素排序。

總結(jié),此方法僅適用于排序元素較少(例如,總元素為5~15個)的場景。對于大量數(shù)據(jù)排序并不適用,適合首頁輪播圖管理、任務(wù)卡片管理。leangoo 的看板卡片管理就是采用這種方式。

方法二 取中值法(推薦)

原理與實現(xiàn)步驟:

  1. 創(chuàng)建元素時給元素賦默認(rèn)位置(pos字段記錄該值)。賦值規(guī)則為,當(dāng)創(chuàng)建第一個元素時,默認(rèn)位置賦值為65536,第二個元素為 2 * 65536 = 13172,增加第N個元素時,位置賦值為N*65536。
  2. 當(dāng)拖拽改變元素位置時,更新 pos。更新規(guī)則如下:
  • 調(diào)整一個元素到兩個元素中間時,(pre_item.pos + after_item.pos)/ 2 = pos
  • 調(diào)整一個元素到第一個元素時, old_first_item.pos / 2 = pos
  • 調(diào)整一個元素到最后一個元素時, old_last_item.post + 65536 = pos
  1. 當(dāng)前后兩個元素的數(shù)值,不滿足整數(shù)時,更新所有元素的排序。依次給每個元素的 pos賦新值。例如,第一位賦值65536,第二位為2 * 65536,第N位賦值N*65536。

通過取中值的方法,改變元素的位置。當(dāng)需要按序獲取時,只需要對 pos進(jìn)行排序,就可以獲取元素的位置。

關(guān)于中值重排的問題,解決方法有多種。例如,我們可以使用浮點數(shù)儲存 pos,但是需要考慮數(shù)據(jù)庫存儲的精度問題。而且,數(shù)值過小,會在前端丟失精度,元素排序會出現(xiàn)問題。當(dāng)然,如果在接口層,當(dāng)檢測到中值過小,則對所有元素進(jìn)行重排,接口相應(yīng)速度會存在問題(如果是后端管理系統(tǒng)就不用考慮性能問題了)。

有人提出,利用定時任務(wù)每天對所有元素定時重排,來解決單次接口的性能問題。個人覺得這個方法,還是存在問題。若定時任務(wù)不及時,那么排序由于精度問題,發(fā)生了排序錯亂的問題。那么,定時重排已經(jīng)無意義。

方法三 單表單列

每個元素,都有一個字段index,表示元素的排序信息。
規(guī)定元素從0開始遞增。
基本操作如下:

  • 增加數(shù)據(jù)。 新增元素時,序號為當(dāng)前元素數(shù)據(jù)總量值。
  • 刪除元素。刪除元素時,將大于該元素的序號,都減1。
  • 修改元素排序。當(dāng)元素從 x 移動到 y 時,
    • 若 x < y 時,則將(x, y)范圍內(nèi)的元素都減1
    • 若 x > y 時,則將(y, x)范圍內(nèi)的元素都加1
  • 查詢元素。展示列表時,按照 index 字段進(jìn)行排序即可。若需要查第n位元素時,元素位置為 index = n - 1

這種方式優(yōu)點是,查詢快,修改慢。而且,修改接口的邏輯較重,處理起來比較麻煩。我們很多項目都是采用這種模式。在接口設(shè)計方面,我們讓前端傳給后端是一個偏移值(offset),offset = y - x。當(dāng)元素向排序大的方向移動時,offset的為正值;若往排序小的方向移動時,offset`為負(fù)值。

總結(jié)

在選擇具體方案時,還需要根據(jù)具體的業(yè)務(wù)場景選擇。方案的選擇規(guī)則總結(jié)如下:

  • 若排序元素較少,采用方案一;
  • 若排序移動次數(shù)不是非常頻繁且對接口性能要求高,建議采用方案二;
  • 若排序移動非常頻繁且接口性能要求不高,可以采用方案三。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容