修复排序bug
This commit is contained in:
@@ -37,8 +37,6 @@ import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.persistence.criteria.Join;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.criteria.Subquery;
|
||||
@@ -267,11 +265,13 @@ public class SysUserService implements ISysUserService {
|
||||
public Page<SysUserVo> list(SysUserQuery query, Pageable pageable) {
|
||||
Specification<SysUser> spec = buildSpecification(query);
|
||||
Page<SysUser> page = sysUserRepository.findAll(spec, pageable);
|
||||
// 把当前组维度的 sortNo 带到 Vo(仅在具体组上下文有意义;根节点/未选组时为 null)
|
||||
// 把当前组维度的 sortNo 带到 Vo(仅在具体组上下文有意义;根节点/未选组时为 null)。
|
||||
// 组范围须与列表排序一致:含子组时取自身+子孙组范围内的最小 sort_no,避免「排序按范围、展示按单组」的不一致。
|
||||
if (query.getGroupId() != null) {
|
||||
List<Integer> groupIds = resolveGroupIds(query.getGroupId(), query.getWithSub());
|
||||
Map<Integer, Integer> sortNoMap = resolveUserSortNoInGroup(
|
||||
page.getContent().stream().map(SysUser::getId).collect(Collectors.toList()),
|
||||
query.getGroupId());
|
||||
groupIds);
|
||||
return page.map(u -> convertToVo(u, sortNoMap.get(u.getId())));
|
||||
}
|
||||
return page.map(u -> convertToVo(u, null));
|
||||
@@ -596,9 +596,10 @@ public class SysUserService implements ISysUserService {
|
||||
/**
|
||||
* 组装用户列表查询条件:组范围(含子组)+ 关键字(账号/姓名/身份证模糊、手机号精确)+ 启用状态。<br>
|
||||
* 排序按「是否选中具体组」分治:<br>
|
||||
* - 选中具体组(groupId != null):JOIN 关联表,按组内 sort_no 正序(NULLS LAST),其次 user_id 正序;
|
||||
* 含子组时同一用户可能命中多组,启用 distinct 去重(同时保证导出不去重重复行)。<br>
|
||||
* - 根节点 / 未选组(groupId == null):不 JOIN 关联表,直接按主键 id 正序(聚簇索引顺序,无 filesort),
|
||||
* - 选中具体组(groupId != null):用 EXISTS 子查询按组范围过滤成员,用「组内最小 sort_no」标量子查询排序
|
||||
* (NULLS LAST),其次 user_id 正序;不使用 JOIN+distinct,规避 MySQL 的 DISTINCT+ORDER BY 限制,
|
||||
* 且含子组时同一用户命中多组也不会产生重复行。<br>
|
||||
* - 根节点 / 未选组(groupId == null):不关联组表,直接按主键 id 正序(聚簇索引顺序,无 filesort),
|
||||
* 平台总用户量不影响查询性能。
|
||||
*/
|
||||
private Specification<SysUser> buildSpecification(SysUserQuery query) {
|
||||
@@ -606,18 +607,35 @@ public class SysUserService implements ISysUserService {
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
if (query.getGroupId() != null) {
|
||||
// 具体组上下文:JOIN 关联表以按组内 sort_no 排序
|
||||
// 具体组上下文:按组内 sort_no 排序、按组范围过滤成员。
|
||||
// 注意:不能 root.join(rel) + distinct 后再 ORDER BY rel.sortNo ——
|
||||
// MySQL 在 DISTINCT 下禁止 ORDER BY 未选中列(报 "incompatible with DISTINCT"),
|
||||
// 且 WITH SUB 时同一用户命中多组会因 JOIN 产生重复行。
|
||||
// 改用「成员资格 EXISTS 子查询」过滤 + 「组内最小 sort_no 标量子查询」排序:
|
||||
// 不产生重复行,也规避 DISTINCT+ORDER BY 限制,分页/排序语义保持不变。
|
||||
List<Integer> groupIds = resolveGroupIds(query.getGroupId(), query.getWithSub());
|
||||
Join<SysUser, SysUserGroupRel> rel = root.join("userGroupRelList", JoinType.INNER);
|
||||
predicates.add(rel.get("groupId").in(groupIds));
|
||||
cbQuery.distinct(true);
|
||||
// 排序规则:已设排序号者排最前(NULLS LAST),其次 sort_no 升序,最后 user_id 升序兜底
|
||||
// 注:Hibernate 5.6 的 AST 解析器不支持把 IS NULL 表达式直接放进 ORDER BY,
|
||||
// 改用 CASE WHEN sort_no IS NULL THEN 1 ELSE 0 END(产出普通整数,语义等价 NULLS LAST)。
|
||||
// rel.get("sortNo") 返回 Path<Object>,直接作为 Expression<?> 传入 orderBy 即可。
|
||||
|
||||
// 成员过滤:用户须归属于 groupIds 中的某一组
|
||||
Subquery<Integer> memberSq = cbQuery.subquery(Integer.class);
|
||||
Root<SysUserGroupRel> memberRoot = memberSq.from(SysUserGroupRel.class);
|
||||
memberSq.select(memberRoot.get("userId"));
|
||||
memberSq.where(
|
||||
memberRoot.get("groupId").in(groupIds),
|
||||
cb.equal(memberRoot.get("userId"), root.get("id"))
|
||||
);
|
||||
predicates.add(cb.exists(memberSq));
|
||||
|
||||
// 排序:取该用户在组范围内的「最小 sort_no」作为排序键(NULLS LAST),其次 user_id 兜底
|
||||
Subquery<Integer> sortSq = cbQuery.subquery(Integer.class);
|
||||
Root<SysUserGroupRel> sortRoot = sortSq.from(SysUserGroupRel.class);
|
||||
sortSq.select(cb.min(sortRoot.<Integer>get("sortNo")));
|
||||
sortSq.where(
|
||||
sortRoot.get("groupId").in(groupIds),
|
||||
cb.equal(sortRoot.get("userId"), root.get("id"))
|
||||
);
|
||||
cbQuery.orderBy(
|
||||
cb.asc(cb.selectCase().when(cb.isNull(rel.get("sortNo")), 1).otherwise(0)),
|
||||
cb.asc(rel.get("sortNo")),
|
||||
cb.asc(cb.selectCase().when(cb.isNull(sortSq), 1).otherwise(0)),
|
||||
cb.asc(sortSq),
|
||||
cb.asc(root.get("id"))
|
||||
);
|
||||
} else {
|
||||
@@ -645,16 +663,21 @@ public class SysUserService implements ISysUserService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量取「用户在某组下的排序号」,用于列表 Vo 携带当前组维度 sortNo(仅具体组上下文调用)。
|
||||
* 批量取「用户在组范围内」的排序号(组内最小 sort_no),用于列表 Vo 携带当前组维度 sortNo。<br>
|
||||
* 与列表排序键(组内最小 sort_no)严格一致:含子组时 groupIds 为自身+子孙组,用户跨多组命中时取最小,
|
||||
* 因此「排序顺序」与「展示的排序号」完全对应。
|
||||
*/
|
||||
private Map<Integer, Integer> resolveUserSortNoInGroup(List<Integer> userIds, Integer groupId) {
|
||||
private Map<Integer, Integer> resolveUserSortNoInGroup(List<Integer> userIds, List<Integer> groupIds) {
|
||||
Map<Integer, Integer> sortNoMap = new HashMap<>();
|
||||
if (userIds.isEmpty()) {
|
||||
if (userIds.isEmpty() || groupIds.isEmpty()) {
|
||||
return sortNoMap;
|
||||
}
|
||||
List<SysUserGroupRel> rels = sysUserGroupRelRepository.findByUserIdInAndGroupId(userIds, groupId);
|
||||
for (SysUserGroupRel rel : rels) {
|
||||
sortNoMap.put(rel.getUserId(), rel.getSortNo());
|
||||
List<Object[]> rows = sysUserGroupRelRepository.findMinSortNoByUserIdInAndGroupIdIn(userIds, groupIds);
|
||||
for (Object[] row : rows) {
|
||||
Integer userId = (Integer) row[0];
|
||||
Object minObj = row[1]; // MIN(sort_no) 可能为空(该用户范围内均未设置排序号)
|
||||
Integer sortNo = minObj == null ? null : ((Number) minObj).intValue();
|
||||
sortNoMap.put(userId, sortNo);
|
||||
}
|
||||
return sortNoMap;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user