背景
最近在做課程后臺開發(fā)時抠藕,遇到一個很有意思的問題饿肺。問題是這樣的,有多節(jié)課程盾似,運營人員可以根據需要調整課程的先后順序敬辣,從而更改課程的展示順序。
接到這個需求零院,立刻就想到了以前做首頁banner輪播圖的管理后臺溉跃。輪播圖中每一張圖有先后順序之分,為了進行圖片排序告抄,在設計banner實體時撰茎,設計了一個 weight
(權重)字段。然后打洼,讓運營人員給每張圖填寫1~500值龄糊,值越大,則排序越靠前拟蜻。由于輪播圖展示的圖片有限(不超過5張)绎签,即使通過人工填值的方式管理順序,也不是很麻煩酝锅。但是诡必,這種填權重管理順序的方式不適用于數據量較多的課程管理。那么搔扁,如何解決這種排序問題呢爸舒?
為了解決元素自定義排序問題,我們想了很多解決辦法稿蹲。例如扭勉,
- 增加上移/下移功能,更改當前元素排序
- 填權重苛聘,或增減權重值
- 置頂排序
- 拖拽排序
最后涂炎,綜合考慮了一下忠聚,認為第四種最符合日常操作的習慣。針對拖拽排序進行了后端方案設計唱捣。
需求描述
- 允許更改元素的排序两蟀;
- 允許新增數據,并更新現有排序震缭;
- 允許刪除數據赂毯,并更新現有排序。
解決辦法
方法一 全量更新元素位置法
適用場景:排序元素數量較少
原理:每個元素擁有一個字段拣宰,表示元素當前排序的位置党涕。通過前端排序,將排好的元素位置巡社,一次性發(fā)送到后端膛堤。然后,后端統一更新所有元素的位置重贺。
具體實現
實體設計:增加排序字段 sort
骑祟,表示元素當前的位置。例如气笙,sort = 1
次企,則表示元素處于第一位。
接口設計:
// 排序接口
POST /courses/sorting
// 格式:JSON
// 數組中為元素的ID
// 元素在數組中的序號潜圃,表示了元素的位置
// 例如缸棵,ID為3的元素,排序為 1谭期,ID為2的元素堵第,排序為2
[3, 2, 4, 1]
前端邏輯:當前端排序后,或刪除元素后隧出,將剩余元素ID踏志,以數組的形式發(fā)送給后端。數組的索引序號胀瞪,則表示元素當前的排序针余。
后端實現邏輯:
- 刪除不存在前端數組中的ID。將前端
ids
凄诞,與服務器端的ids
進行對比圆雁。刪除服務器端存在但前端不存在的ids
。 - 按照數組的排序帆谍,更新所有元素排序伪朽。
總結,此方法僅適用于排序元素較少(例如汛蝙,總元素為5~15個)的場景烈涮。對于大量數據排序并不適用朴肺,適合首頁輪播圖管理、任務卡片管理跃脊。leangoo 的看板卡片管理就是采用這種方式宇挫。
方法二 取中值法(推薦)
原理與實現步驟:
- 創(chuàng)建元素時給元素賦默認位置(
pos
字段記錄該值)。賦值規(guī)則為酪术,當創(chuàng)建第一個元素時,默認位置賦值為65536翠储,第二個元素為2 * 65536 = 13172
绘雁,增加第N個元素時,位置賦值為N*65536援所。 - 當拖拽改變元素位置時庐舟,更新
pos
。更新規(guī)則如下:
- 調整一個元素到兩個元素中間時住拭,
(pre_item.pos + after_item.pos)/ 2 = pos
- 調整一個元素到第一個元素時挪略,
old_first_item.pos / 2 = pos
- 調整一個元素到最后一個元素時,
old_last_item.post + 65536 = pos
- 當前后兩個元素的數值滔岳,不滿足整數時杠娱,更新所有元素的排序。依次給每個元素的
pos
賦新值谱煤。例如摊求,第一位賦值65536,第二位為2 * 65536
刘离,第N位賦值N*65536室叉。
通過取中值的方法,改變元素的位置硫惕。當需要按序獲取時茧痕,只需要對 pos
進行排序,就可以獲取元素的位置恼除。
關于中值重排的問題踪旷,解決方法有多種。例如缚柳,我們可以使用浮點數儲存 pos
埃脏,但是需要考慮數據庫存儲的精度問題。而且秋忙,數值過小彩掐,會在前端丟失精度,元素排序會出現問題灰追。當然堵幽,如果在接口層狗超,當檢測到中值過小,則對所有元素進行重排朴下,接口相應速度會存在問題(如果是后端管理系統就不用考慮性能問題了)努咐。
有人提出,利用定時任務每天對所有元素定時重排殴胧,來解決單次接口的性能問題渗稍。個人覺得這個方法,還是存在問題团滥。若定時任務不及時竿屹,那么排序由于精度問題,發(fā)生了排序錯亂的問題灸姊。那么拱燃,定時重排已經無意義。
方法三 單表單列
每個元素力惯,都有一個字段index
碗誉,表示元素的排序信息。
規(guī)定元素從0開始遞增父晶。
基本操作如下:
- 增加數據哮缺。 新增元素時,序號為當前元素數據總量值诱建。
- 刪除元素蝴蜓。刪除元素時,將大于該元素的序號俺猿,都減1茎匠。
- 修改元素排序。當元素從 x 移動到 y 時押袍,
- 若 x < y 時诵冒,則將(x, y)范圍內的元素都減1
- 若 x > y 時,則將(y, x)范圍內的元素都加1
- 查詢元素谊惭。展示列表時汽馋,按照
index
字段進行排序即可。若需要查第n位元素時圈盔,元素位置為index = n - 1
豹芯。
這種方式優(yōu)點是,查詢快驱敲,修改慢铁蹈。而且,修改接口的邏輯較重众眨,處理起來比較麻煩握牧。我們很多項目都是采用這種模式容诬。在接口設計方面,我們讓前端傳給后端是一個偏移值(offset
)沿腰,offset = y - x览徒。當元素向排序大的方向移動時,
offset的為正值颂龙;若往排序小的方向移動時习蓬,
offset`為負值。
總結
在選擇具體方案時措嵌,還需要根據具體的業(yè)務場景選擇友雳。方案的選擇規(guī)則總結如下:
- 若排序元素較少,采用方案一铅匹;
- 若排序移動次數不是非常頻繁且對接口性能要求高,建議采用方案二饺藤;
- 若排序移動非常頻繁且接口性能要求不高包斑,可以采用方案三。