3. 权限改造SDK整体架构设计:分层架构、模块划分、依赖关系

这一章咱们要聊的是骨架——SDK 的整体架构。说白了,就是搞清楚这堆代码怎么分层、分几个模块、谁依赖谁。

我接手第一个权限改造项目时,代码全堆在一个类里,两千多行。后来要加个数据权限,改一个地方崩三处。那种痛苦,经历过就知道——所以架构设计,真的不能省。

3.1 为什么需要分层?

你想想看,权限改造不是写个 if else 就完事的。它要对接用户系统、解析策略、拦截请求、缓存结果……全搅在一起,维护成本直线飙升。

分层的目的就一条:让每一层只关心自己的事。上层调用下层,下层不感知上层。改动底层缓存,不会影响上层的拦截逻辑;换一种策略存储后端,也不用改权限判断的代码。

我个人习惯:先画出一个最简的分层图,再往里填模块。哪怕一开始只分两层,也比没有强。

3.2 四层架构总览

我最终在项目中沉淀下来的结构是四层。咱们从下往上说。

层次 名称 职责 关键产出
Layer 1 基础设施层 封装底层能力:缓存、RPC、数据库、配置读取 CacheProvider、RemoteFetcher
Layer 2 策略核心层 解析RBAC策略、构建权限树、缓存管理 PolicyEngine、PermissionTree
Layer 3 业务适配层 对接具体业务场景:接口、菜单、数据行 ApiGuard、MenuFilter
Layer 4 用户接口层 暴露给使用方:注解、配置类、SDK初始化入口 @RequirePermission、PermissionStarter

注意这里:依赖方向只能从上往下。用户接口层依赖业务适配层,业务适配层依赖策略核心层,策略核心层依赖基础设施层。反过来不行。

曾经有个同事直接把 Redis 调用写到了用户接口层的注解解析器里,结果换缓存中间件时,注解类也要改。这就是典型的分层违规。

3.3 模块划分与职责

四层再往下拆,就是具体的模块。我习惯按功能粒度划分 Maven/Gradle 子模块。下面这张表格列出了主要模块和它们之间的关系。

模块名 所属层次 对外暴露 依赖模块
permission-core 策略核心层 PolicyEngine、PermissionContext permission-common
permission-cache 基础设施层 CacheTemplate 无(或依赖第三方缓存客户端)
permission-remote 基础设施层 UserRoleFetcher permission-common
permission-api 用户接口层 注解、配置类 permission-core、permission-common
permission-spring-boot-starter 用户接口层 自动装配、配置属性 permission-api、permission-core
permission-common 工具层 DTO、常量、异常、工具类 无
特别注意 permission-common 不能依赖任何业务模块。它只放公共定义。我见过有人把常量类和 JSON 工具类放一起,结果工具类又引了 httpclient……循环依赖的噩梦就这么来的。

3.4 依赖关系拓扑

光用文字说还不够直观。咱们画一张依赖关系图,看完你心里就有谱了。

权限改造SDK模块依赖拓扑图 权限改造SDK模块依赖拓扑图 依赖方向:上层 → 下层(箭头指向被依赖方) 用户接口层 permission-api 注解、配置类、上下文 permission-spring-boot-starter 自动装配、属性绑定 业务适配层 permission-adapter ApiGuard, MenuFilter permission-core PolicyEngine, PermissionTree, 缓存管理 permission-cache CacheTemplate permission-remote UserRoleFetcher permission-common DTO、常量、异常
这张图里,permission-common 被三个模块依赖,但它自己谁也不依赖。我把它放在最底下偏右,特意跟其他模块隔开,就是为了强调:公共模块必须零依赖。

3.5 关键依赖规则

光看拓扑还不够,实际编码时得遵守几条硬规则。我拿项目经历给你说几个例子。

  1. 禁止跨层跳过依赖:业务适配层不能直接调用基础设施层的 CacheTemplate。必须通过策略核心层提供的 PolicyEngine 间接访问。为什么?因为一旦跳过,缓存失效策略、降级逻辑全绕过去了,权限判断结果就不一致。
  2. 基础设施层内部可以互相调用:比如 permission-remote 获取用户角色后,可以在内部写入 permission-cache。但要注意,这种调用只能通过接口抽象,不能硬编码实现类。我用 Spring 的 @Autowired 注入时吃过亏——换成 Guice 后,直接报 NoSuchBeanDefinitionException。
  3. 公共层只放不变的东西:DTO 结构、枚举、异常类、常量。不要放业务逻辑。我曾经把策略串行化的逻辑放到 permission-common 里,结果每次升级策略格式都要改公共层,所有模块都要重新编译——太蠢了。
如果你用 Maven,我建议在父 pom 里统一管理版本,子模块通过 <dependency> 的 scope(如 compile、runtime、provided)控制可见性。这样编译期就能拦住违规依赖。

3.6 初始化与配置依赖

最后聊一聊启动时的依赖顺序。SDK 被 Spring 加载时,各个模块的初始化不能乱。

顺序 模块 初始化动作 前置条件
1 permission-common 加载常量、注册错误码 无
2 permission-cache 初始化 Redis/本地缓存连接池 配置文件就绪
3 permission-remote 初始化 RPC 客户端、建立长连接 缓存模块就绪(用于回退缓存)
4 permission-core 加载 RBAC 策略、构建权限树 cache 和 remote 可用
5 permission-adapter 注册拦截器、过滤器、注入 PolicyEngine core 初始化完毕
6 permission-api / starter 暴露注解、配置类、自动装配 所有下层模块就绪
我曾经遇到的坑:在 @PostConstruct 里直接调用了 permission-core 的方法,结果 core 的 Bean 还没初始化完成,报 NullPointerException。解决方案:使用 @DependsOn 或 ApplicationListener<ContextRefreshedEvent> 保证顺序。

好了,这一章的结构就这些。你按照这个分层去搭,模块之间的边界清清楚楚,后面改什么都不会手忙脚乱。