@@ -80,13 +80,14 @@ async def pager(
8080 if order_by and order_by != 'account' :
8181 select_columns .append (sort_field )
8282
83- # 相似度排序:精确匹配 > 前缀匹配 > 包含匹配
84- # 当有 keyword 时,将 similarity_score 加入 SELECT 列以满足 DISTINCT 约束
85- # 对 account、name、email 三个字段分别计算相似度,取最高(最小值)
86- similarity_score = None
83+ # 相似度排序:综合考虑匹配字段数量和相似度分数
84+ # 当有 keyword 时,将 match_count 和 total_score 加入 SELECT 列以满足 DISTINCT 约束
85+ # 匹配字段越多越靠前;相同匹配字段数时,总分越低(相似度越高)越靠前
86+ match_count = None
87+ total_score = None
8788 if keyword :
8889 from sqlalchemy import func
89- # 每个字段的相似度分数
90+ # 每个字段的相似度分数 (0=精确匹配, 1=前缀匹配, 2=包含匹配, 3=无匹配)
9091 account_score = case (
9192 (UserModel .account == keyword , 0 ),
9293 (UserModel .account .startswith (keyword ), 1 ),
@@ -105,9 +106,16 @@ async def pager(
105106 (UserModel .email .contains (keyword ), 2 ),
106107 else_ = 3
107108 )
108- # 取三个字段中的最小值(最高匹配度)
109- similarity_score = func .LEAST (account_score , name_score , email_score )
110- select_columns .append (similarity_score .label ('similarity_score' ))
109+ # 计算匹配字段数量(score < 3 表示有匹配):匹配字段越多越靠前
110+ match_count = (
111+ case ((account_score < 3 , 1 ), else_ = 0 ) +
112+ case ((name_score < 3 , 1 ), else_ = 0 ) +
113+ case ((email_score < 3 , 1 ), else_ = 0 )
114+ )
115+ # 总相似度分数:三个字段分数之和,越低越好
116+ total_score = account_score + name_score + email_score
117+ select_columns .append (match_count .label ('match_count' ))
118+ select_columns .append (total_score .label ('total_score' ))
111119
112120 origin_stmt = (
113121 select (* select_columns )
@@ -117,7 +125,8 @@ async def pager(
117125 )
118126 # 根据是否有 keyword 决定排序方式
119127 if keyword :
120- origin_stmt = origin_stmt .order_by (similarity_score , sort_clause )
128+ # 按匹配字段数降序、总分升序、再按用户选择的排序字段
129+ origin_stmt = origin_stmt .order_by (match_count .desc (), total_score .asc (), sort_clause )
121130 else :
122131 origin_stmt = origin_stmt .order_by (sort_clause )
123132
@@ -128,12 +137,14 @@ async def pager(
128137 if status is not None :
129138 origin_stmt = origin_stmt .where (UserModel .status == status )
130139 if keyword :
131- keyword_pattern = f"%{ keyword } %"
140+ # 转义 SQL LIKE 特殊字符(_ 匹配单个字符,% 匹配任意字符串)
141+ escaped_keyword = keyword .replace ('\\ ' , '\\ \\ ' ).replace ('_' , '\\ _' ).replace ('%' , '\\ %' )
142+ keyword_pattern = f"%{ escaped_keyword } %"
132143 origin_stmt = origin_stmt .where (
133144 or_ (
134- UserModel .account .ilike (keyword_pattern ),
135- UserModel .name .ilike (keyword_pattern ),
136- UserModel .email .ilike (keyword_pattern )
145+ UserModel .account .ilike (keyword_pattern , escape = ' \\ ' ),
146+ UserModel .name .ilike (keyword_pattern , escape = ' \\ ' ),
147+ UserModel .email .ilike (keyword_pattern , escape = ' \\ ' )
137148 )
138149 )
139150
@@ -169,8 +180,16 @@ async def pager(
169180 (UserModel .email .contains (keyword ), 2 ),
170181 else_ = 3
171182 )
172- similarity_score = func .LEAST (account_score , name_score , email_score )
173- stmt = stmt .order_by (similarity_score , sort_clause )
183+ # 计算匹配字段数量(score < 3 表示有匹配):匹配字段越多越靠前
184+ match_count = (
185+ case ((account_score < 3 , 1 ), else_ = 0 ) +
186+ case ((name_score < 3 , 1 ), else_ = 0 ) +
187+ case ((email_score < 3 , 1 ), else_ = 0 )
188+ )
189+ # 总相似度分数:三个字段分数之和,越低越好
190+ total_score = account_score + name_score + email_score
191+ # 排序:匹配字段数降序、总分升序、再按用户选择的排序字段
192+ stmt = stmt .order_by (match_count .desc (), total_score .asc (), sort_clause )
174193 else :
175194 stmt = stmt .order_by (sort_clause )
176195 user_workspaces = session .exec (stmt ).all ()
0 commit comments