导读:本期聚焦于小伙伴创作的《如何高效获取用户被授权的分级结构,包括F、E、D列表且F列表支持分页?》,敬请观看详情。在权限系统开发中,常遇到需要一次性拉取用户可见的F、E、D三层授权数据,同时F层数据量庞大必须分页的场景。若直接嵌套查询或多次远程调用,容易引发慢查询和内存溢出。合理做法是在服务端建立统一的授权视图模型,将D、E作为轻量关联集合随首屏返回,F列表通过独立游标分页接口按需加载。采用冗余缓存与批量预取能进一步降低数据库压力。下文将基于关系型表结构,给出可落地的查询方案与代码实现,并比较不同分页策略在真实业务中的表现差异。

在后台权限管理系统中,用户往往拥有多层级的资源授权关系。以常见的三级结构为例,D代表最细粒度的操作权限,E代表中间的业务模块,F代表顶层的业务线或组织单元。实际接口设计中,前端需要在进入页面时立刻拿到完整的D与E列表用于渲染树形菜单,而F由于数量可能上万,必须支持分页拉取。如何在一次会话中高效完成这件事,是本文要解决的问题。

如何高效获取用户被授权的分级结构,包括F、E、D列表且F列表支持分页?

一、分级授权数据的存储模型

多数系统采用关系型数据库保存授权记录。我们设计三张核心表:表auth_f存储顶层授权,表auth_e存储模块授权,表auth_d存储操作授权。它们通过外键形成层级引用。用户与授权的绑定关系存放在独立的映射表中,以便支持用户组继承等复杂场景。

下面给出简化后的表结构定义。注意这里只保留与查询相关的字段,真实环境还需加入租户ID、失效时间等控制字段。通过这种结构,我们可以在一次联表查询中拿到某个用户拥有的全部D和E,而F则单独处理。

CREATE TABLE auth_f (
  id BIGINT PRIMARY KEY,
  name VARCHAR(64) NOT NULL,
  owner_user_id BIGINT NOT NULL
);

CREATE TABLE auth_e (
  id BIGINT PRIMARY KEY,
  f_id BIGINT NOT NULL,
  name VARCHAR(64) NOT NULL
);

CREATE TABLE auth_d (
  id BIGINT PRIMARY KEY,
  e_id BIGINT NOT NULL,
  code VARCHAR(64) NOT NULL
);

CREATE TABLE user_auth_map (
  user_id BIGINT NOT NULL,
  f_id BIGINT,
  e_id BIGINT,
  d_id BIGINT
);

二、D与E列表的批量获取方案

由于D和E的数据量相对可控,通常采用单次查询返回。我们利用用户映射表做入口,通过LEFT JOIN把三层关系拼成扁平结果,再在内存中按f_id、e_id分组。这种方式只需一次数据库往返,避免了循环查询带来的N+1问题。

在代码层面,建议使用MyBatis或类似框架的嵌套结果映射,直接将数据反序列化为树形对象。如果框架不支持自动嵌套,也可以在Service层手动组装。下面的Java示例展示了如何执行查询并组装成层级结构。

public AuthStructure loadDE(Long userId) {
    List<RawAuthRow> rows = authMapper.selectUserAuth(userId);
    Map<Long, AuthF> fMap = new LinkedHashMap<>();
    Map<Long, AuthE> eMap = new LinkedHashMap<>();
    for (RawAuthRow r : rows) {
        AuthF f = fMap.computeIfAbsent(r.getFId(), id -> new AuthF(id, r.getFName()));
        AuthE e = eMap.computeIfAbsent(r.getEId(), id -> new AuthE(id, r.getEName()));
        f.getEList().add(e);
        e.getDList().add(r.getDCode());
    }
    return new AuthStructure(new ArrayList<>(fMap.values()));
}

这种方案的优点是延迟低、逻辑直观。缺点在于如果用户跨了大量F和E,单次返回的行数会膨胀。此时可考虑对D列表做按需加载,即首屏只返回E的ID,D通过单独接口按E的ID批量查询。

三、F列表的分页实现

F列表分页不能简单使用LIMIT OFFSET,因为在大数据量下深翻页会导致性能陡降。推荐基于游标(Cursor)或最后主键(Seek Method)分页。我们让前端首次请求不带游标,后续携带上次最后一条记录的ID,查询时使用WHERE id > lastId ORDER BY id LIMIT size。

游标分页要求排序字段具备唯一性且存在索引。若F的展示顺序依赖权重而非主键,可改用(weight, id)的复合游标。以下SQL演示了基于主键的Seek分页,能有效利用B+树索引,避免OFFSET扫描。

SELECT id, name
FROM auth_f
WHERE owner_user_id = #{userId}
  AND id > #{lastId}
ORDER BY id ASC
LIMIT #{size};

在接口设计上,F分页应独立成Controller方法,与D、E的加载解耦。前端在渲染完树骨架后,监听滚动事件逐步加载F的更多节点。这样既保证了首屏速度,也控制了单次内存占用。

@GetMapping("/auth/f/page")
public PageResult<AuthF> pageF(@RequestParam Long userId,
                               @RequestParam(required = false) Long lastId,
                               @RequestParam int size) {
    List<AuthF> list = authMapper.pageFByUser(userId, lastId, size);
    Long nextCursor = list.size() < size ? null : list.get(list.size() - 1).getId();
    return new PageResult<>(list, nextCursor);
}

四、缓存与性能优化建议

D和E的授权关系变更频率低,适合使用Redis缓存整个结构,缓存Key为用户ID加版本号。当管理员调整授权时,递增版本号即可让旧缓存失效。F列表由于分页独立,可按页缓存,但需注意用户授权变更时的批量清理。

另一个常见优化是冗余用户可访问的F的ID集合到用户画像中。这样分页查询可直接用IN或JOIN缩小范围,不必每次扫描全表。下表对比了三种分页策略在百万级F数据下的表现:

分页方式深翻页性能实现复杂度适用场景
OFFSET分页差,随页数线性退化数据量小后台
主键游标稳定O(log n)顺序展示列表
权重复合游标稳定O(log n)自定义排序大列表

综合来看,将D、E做一次性轻量返回,F走游标分页,并辅以合理的缓存,能够在保证开发效率的同时满足高效获取用户分级授权结构的需求。落地时请根据业务实际的数据规模和变更频率微调批大小与缓存时长。

授权分级分页查询数据结构设计修改时间:2026-08-10 14:42:59

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。