場景分析
本次需要實現規則是根據用户點擊目錄左側——彈出排序選項,文件名A-Z:英文A-Z → 中文A-Z → 數字0-9 ,文件名Z-A:與A-Z相反,最近更新時間:文件夾按文件夾修改時間,文檔按文檔修改時間。圖片直達鏈接
如何實現?
1.改造接口增加參數
/**
* 排序類型: NAME_ASC(文件名A-Z), NAME_DESC(文件名Z-A), TIME_DESC(最近更新時間)
*/
@Schema(description = "排序類型: NAME_ASC(文件名A-Z), NAME_DESC(文件名Z-A), TIME_DESC(最近更新時間)")
private String sortType;
2.修改默認查詢子集排序規則
// 排序 - 默認按sort字段排序,如果需要其他排序規則會在數據庫層處理
this.children.sort(Comparator.comparing(KbPageTreeResp::getSort, Comparator.nullsLast(Comparator.naturalOrder())));
這個表達式是一個複合比較器,用於處理包含 null 值的排序場景。
1. Comparator.naturalOrder()
- 這是一個基礎比較器,用於對實現了
Comparable接口的對象進行自然排序 - 對於
Integer類型,自然排序就是數值升序(1, 2, 3, 4...) - 相當於調用對象的
compareTo()方法
2. Comparator.nullsLast(...)
- 這是一個裝飾器比較器,用於處理
null值 nullsLast表示:將null值排在最後- 它接受一個內部比較器作為參數,用於比較非
null值
3. 組合效果
Comparator.nullsLast(Comparator.naturalOrder()) 的排序規則是:
- 非
null值:按照自然排序(升序)排列 null值:統一排在最後面- 混合情況:非
null值在前面按升序排列,null值在最後
4. 實際例子
假設 sort 字段的值有:[3, null, 1, null, 2]
排序後的結果將是:[1, 2, 3, null, null]
// 示例代碼
List<Integer> sorts = Arrays.asList(3, null, 1, null, 2);
sorts.sort(Comparator.nullsLast(Comparator.naturalOrder()));
System.out.println(sorts); // 輸出: [1, 2, 3, null, null]
5. 在您代碼中的應用
在 KbPageTreeResp 類中,這個比較器用於:
this.children.sort(Comparator.comparing(KbPageTreeResp::getSort,
Comparator.nullsLast(Comparator.naturalOrder())));
Comparator.comparing(KbPageTreeResp::getSort, ...)提取每個對象的sort字段進行比較- 如果某些頁面的
sort字段為null,這些頁面會被排在最後 - 其他有
sort值的頁面按照數值升序排列
6. 為什麼需要處理 null?
在實際業務中,頁面的 sort 字段可能:
- 新創建的頁面還沒有設置排序值(
null) - 某些頁面被明確設置為不參與排序(
null) - 數據遷移或其他原因導致的空值
使用 nullsLast 可以確保程序不會因為 null 值而拋出 NullPointerException,同時提供合理的排序行為。
3.自定義排序規則
動態構建 SQL 排序語句(ORDER BY)*的 Java 方法。它的核心作用是根據*文件類型、標題、時間等多個維度,對一組文件/文件夾進行複合排序,並且支持多種排序策略(升序、降序、按時間、按名稱等)。
/**
* 構建ORDER BY語句
* 文件夾和文件分別排序,文件夾在前,文件在後
* xxxxx節點始終置頂
*
* @return ORDER BY語句
*/
private String buildOrderByClause() {
StringBuilder orderBy = new StringBuilder();
// 1. xxxxxx節點置頂
orderBy.append("ORDER BY CASE WHEN title = 'xxxxxx' THEN 0 ELSE 1 END, ");
// 2. 文件夾在前,文件在後
orderBy.append("CASE WHEN type = 'FOLDER' THEN 0 ELSE 1 END, ");
// 3. 根據排序類型進行排序
if (StrUtil.isNotBlank(this.sortType)) {
switch (this.sortType.toUpperCase()) {
case "NAME_ASC":
// 文件名A-Z:英文A-Z → 中文A-Z → 數字0-9
// 使用ASCII碼和字符判斷來實現優先級排序
orderBy.append("CASE ")
.append("WHEN ASCII(SUBSTRING(title, 1, 1)) BETWEEN 65 AND 90 OR ASCII(SUBSTRING(title, 1, 1)) BETWEEN 97 AND 122 THEN CONCAT('1', title) ") // 英文字母
.append("WHEN ASCII(SUBSTRING(title, 1, 1)) > 127 THEN CONCAT('2', title) ") // 中文字符
.append("WHEN ASCII(SUBSTRING(title, 1, 1)) BETWEEN 48 AND 57 THEN CONCAT('3', title) ") // 數字
.append("ELSE CONCAT('4', title) ") // 其他字符
.append("END ASC");
break;
case "NAME_DESC":
// 文件名Z-A:與A-Z相反
orderBy.append("CASE ")
.append("WHEN ASCII(SUBSTRING(title, 1, 1)) BETWEEN 65 AND 90 OR ASCII(SUBSTRING(title, 1, 1)) BETWEEN 97 AND 122 THEN CONCAT('1', title) ") // 英文字母
.append("WHEN ASCII(SUBSTRING(title, 1, 1)) > 127 THEN CONCAT('2', title) ") // 中文字符
.append("WHEN ASCII(SUBSTRING(title, 1, 1)) BETWEEN 48 AND 57 THEN CONCAT('3', title) ") // 數字
.append("ELSE CONCAT('4', title) ") // 其他字符
.append("END DESC");
break;
case "TIME_DESC":
// 最近更新時間:文件夾按文件夾修改時間,文檔按文檔修改時間
orderBy.append("modifier_time DESC");
break;
default:
// 默認排序:修改時間降序 + sort升序 + 標題升序
orderBy.append("modifier_time DESC, sort ASC, title ASC");
break;
}
} else {
// 默認排序:修改時間降序 + sort升序 + 標題升序
orderBy.append("modifier_time DESC, sort ASC, title ASC");
}
return orderBy.toString();
}
}
正確的排序代碼
/**
* 構建ORDER BY語句
* 文件夾和文件分別排序,文件夾在前,文件在後
* xxxxxx節點始終置頂
*
* @return ORDER BY語句
*/
private String buildOrderByClause() {
StringBuilder orderBy = new StringBuilder();
// 1. xxxxx沉澱節點置頂
orderBy.append("ORDER BY CASE WHEN title = 'xxxxxx' THEN 0 ELSE 1 END, ");
// 2. 文件夾在前,文件在後
orderBy.append("CASE WHEN type = 'FOLDER' THEN 0 ELSE 1 END, ");
// 3. 根據排序類型進行排序
if (StrUtil.isNotBlank(this.sortType)) {
switch (this.sortType.toUpperCase()) {
case "NAME_ASC":
// 文件名A-Z:英文A-Z → 中文A-Z → 數字0-9
// 統一使用gbk_chinese_ci排序規則避免collation衝突
orderBy.append("CASE ")
.append("WHEN title REGEXP '^[A-Za-z]' THEN 1 ") // 英文字母開頭
.append("WHEN title REGEXP '^[\\u4e00-\\u9fa5]' THEN 2 ") // 中文字符開頭
.append("WHEN title REGEXP '^[0-9]' THEN 3 ") // 數字開頭
.append("ELSE 4 ") // 其他字符
.append("END ASC, ")
.append("CONVERT(")
.append("CASE ")
.append("WHEN title REGEXP '^[A-Za-z]' THEN UPPER(title) ") // 英文按字母序,忽略大小寫
.append("WHEN title REGEXP '^[\\u4e00-\\u9fa5]' THEN title ") // 中文保持原樣
.append("WHEN title REGEXP '^[0-9]' THEN LPAD(title, 20, '0') ") // 數字按數值序,左補零確保正確排序
.append("ELSE title ") // 其他字符按默認序
.append("END USING gbk) COLLATE gbk_chinese_ci ASC");
break;
case "NAME_DESC":
// 文件名Z-A:數字9-0 → 中文Z-A → 英文Z-A
// 統一使用gbk_chinese_ci排序規則避免collation衝突
orderBy.append("CASE ")
.append("WHEN title REGEXP '^[0-9]' THEN 1 ") // 數字開頭優先
.append("WHEN title REGEXP '^[\\u4e00-\\u9fa5]' THEN 2 ") // 中文字符其次
.append("WHEN title REGEXP '^[A-Za-z]' THEN 3 ") // 英文字母最後
.append("ELSE 4 ") // 其他字符
.append("END ASC, ")
.append("CONVERT(")
.append("CASE ")
.append("WHEN title REGEXP '^[A-Za-z]' THEN UPPER(title) ") // 英文按字母序,忽略大小寫
.append("WHEN title REGEXP '^[\\u4e00-\\u9fa5]' THEN title ") // 中文保持原樣
.append("WHEN title REGEXP '^[0-9]' THEN LPAD(title, 20, '0') ") // 數字按數值序,左補零確保正確排序
.append("ELSE title ") // 其他字符按默認序
.append("END USING gbk) COLLATE gbk_chinese_ci DESC");
break;
case "TIME_DESC":
// 最近更新時間:文件夾按文件夾修改時間,文檔按文檔修改時間
orderBy.append("modifier_time DESC");
break;
default:
// 默認排序:修改時間降序 + sort升序 + 標題升序
orderBy.append("modifier_time DESC, sort ASC, CONVERT(title USING gbk) COLLATE gbk_chinese_ci ASC");
break;
}
} else {
// 默認排序:修改時間降序 + sort升序 + 標題升序(中文按拼音)
orderBy.append("modifier_time DESC, sort ASC, CONVERT(title USING gbk) COLLATE gbk_chinese_ci ASC");
}
return orderBy.toString();
}