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 依赖关系拓扑
光用文字说还不够直观。咱们画一张依赖关系图,看完你心里就有谱了。
这张图里,
permission-common 被三个模块依赖,但它自己谁也不依赖。我把它放在最底下偏右,特意跟其他模块隔开,就是为了强调:公共模块必须零依赖。
3.5 关键依赖规则
光看拓扑还不够,实际编码时得遵守几条硬规则。我拿项目经历给你说几个例子。
- 禁止跨层跳过依赖:业务适配层不能直接调用基础设施层的
CacheTemplate。必须通过策略核心层提供的PolicyEngine间接访问。为什么?因为一旦跳过,缓存失效策略、降级逻辑全绕过去了,权限判断结果就不一致。 - 基础设施层内部可以互相调用:比如
permission-remote获取用户角色后,可以在内部写入permission-cache。但要注意,这种调用只能通过接口抽象,不能硬编码实现类。我用 Spring 的@Autowired注入时吃过亏——换成 Guice 后,直接报 NoSuchBeanDefinitionException。 - 公共层只放不变的东西: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> 保证顺序。
好了,这一章的结构就这些。你按照这个分层去搭,模块之间的边界清清楚楚,后面改什么都不会手忙脚乱。