ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java权限系统实战:RBAC模型到Spring Security集成与平台选型

Java权限系统实战:RBAC模型到Spring Security集成与平台选型 做Java后端的人几乎没有谁能绕开权限系统这道坎。刚入行时我自己处理得很粗暴——接口里写死if(user.getRole()1)角色一多就开始崩溃权限列表越堆越长、菜单树乱成一团。后来才意识到权限模型这件事从根上决定了项目能走多远。这几年我从零搭过权限模块也基于快速开发平台改过权限中心关于Java系统权限模型和开发平台选型算是趟出来了一些真实经验。这篇文章把整套思路摊开讲。先从RBAC这个最经典的授权模型开始说清楚为什么它能成为Java生态里的默认选择然后落到实际工程拆解用户、角色、菜单、关联关系的表设计以及Spring Security这类框架的集成细节最后聊到快速开发平台的选型我会把踩过坑之后的对比经验和判断标准一并写出来。适合正在自研权限模块的Java工程师、准备接手旧系统的后端开发以及打算选型中后台快速开发平台的团队参考。1. 内容整体设计与思路拆解为什么RBAC成了权限系统的默认答案1.1 认证与授权权限问题先拆成两件事权限系统的本质是两个问题你是谁Authentication你能做什么Authorization。前者解决登录和会话后者解决资源访问边界。很多权限混乱的局面根源就是把这两件事混在一起了。典型例子在User实体上直接加一个isAdmin字段登录后到处拿它判断能不能访问。这就是把授权嫁接到认证上看起来省事后续想加一个“运营角色”或者“访客角色”就只能疯狂加字段、疯狂写if else最后整个Controller层全是权限判断代码根本没法维护。RBAC的思路是引入角色这一中间层用户与角色关联角色与权限关联把“谁能干什么”的复杂度全部收敛在角色上。这个收敛非常关键。它意味着权限的管理从“写死在代码里”变成了“可配置的数据”。新来一个员工管理员在后台给他分配一个角色系统行为立刻改变开发人员完全不用动代码。这是RBAC能成为默认方案的第一个原因它把权限变更的成本从开发侧转移到了配置侧。1.2 RBAC模型家族RBAC0到RBAC3到底差在哪RBAC的完整理论框架由NIST提出实际工程中常听到的RBAC0、RBAC1、RBAC2、RBAC3其实是同一套模型的不同复杂度层级搞清楚这几个层级才能在面试和架构设计里把话说透。RBAC0是基础版核心就是用户、角色、权限三元组用户分配角色角色分配权限。绝大多数企业后台系统用到的就是这一层。RBAC1在RBAC0之上加入了角色继承比如“部门经理”角色自动继承“普通员工”角色的所有权限这样配置角色时不用重复勾选底层权限适合组织层级明显的企业。RBAC2则加入约束条件比如角色互斥一个人不能同时拥有财务和出纳两个角色、角色基数约束一个角色最多分配给多少用户这类约束通常对应真实的合规要求。RBAC3就是RBAC1加RBAC2的组合。我的建议是多数项目用RBAC0加上少量角色继承就足够不要为了追求模型完整而硬上RBAC2。角色互斥听着美好真做起来后台的管理复杂度会翻倍而且产品经理大概率说不清楚“到底哪些角色要互斥”。如果业务确实有硬性约束比如财务系统的职责分离再单独引入互斥机制即可。1.3 对比ACL和ABAC之后为什么还是RBAC最稳面试里经常被问到“为什么不用ACL、不用ABAC”这个问题其实没有标准答案只有适用场景的差别。ACL访问控制列表把权限直接挂在用户或对象上比如“文件A允许张三读、允许李四写”。好处是表达灵活坏处是维护成本爆炸每新增一个资源就要配置一堆用户的权限关系用户量上来后关系数据量级根本不可控。ABAC基于属性的访问控制用规则引擎做判断比如“部门等于销售部且职级大于P6才能访问”表达能力强到可以覆盖几乎所有复杂场景但代价是规则很难维护非开发人员基本看不懂策略配置运行效率也需要额外优化。RBAC恰好落在两者中间。对于企业内部管理系统、运营后台、SaaS管理端这类“权限边界相对稳定、角色划分清晰”的场景RBAC是性价比最高的选择。它既不像ACL那样在数据量上失控又不像ABAC那样给使用者增加理解成本。如果业务未来可能要做更精细的授权策略完全可以在RBAC基础上预留扩展位比如在权限表里增加属性条件字段等真正需要ABAC时再演进完全没必要一开始就上重型规则引擎。2. 核心细节解析与实操要点RBAC表设计与Java技术栈落地2.1 六张核心表怎么设计别再一张表打天下工程落地RBAC第一件事是表结构设计。网上的RBAC建表脚本五花八门但核心逻辑基本一致用户表、角色表、菜单权限表、用户角色关联表、角色菜单关联表这五张是标配如果涉及组织架构再加一张部门表。下面是一份可以直接使用的MySQL表结构我平时都是按这个模板起步。CREATE TABLE sys_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密码, nickname VARCHAR(50) DEFAULT NULL COMMENT 昵称, dept_id BIGINT DEFAULT NULL COMMENT 部门ID, status TINYINT DEFAULT 1 COMMENT 状态1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE sys_role ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 角色ID, role_key VARCHAR(50) NOT NULL COMMENT 角色标识如admin、manager, role_name VARCHAR(50) NOT NULL COMMENT 角色名称, sort INT DEFAULT 0 COMMENT 显示顺序, status TINYINT DEFAULT 1 COMMENT 状态1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_role_key (role_key) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT角色表; CREATE TABLE sys_menu ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 菜单ID, parent_id BIGINT DEFAULT 0 COMMENT 父菜单ID0表示根菜单, menu_name VARCHAR(50) NOT NULL COMMENT 菜单名称, menu_type CHAR(1) DEFAULT M COMMENT 类型M目录 C菜单 F按钮, path VARCHAR(200) DEFAULT NULL COMMENT 路由地址, perms VARCHAR(100) DEFAULT NULL COMMENT 权限标识如system:user:add, icon VARCHAR(50) DEFAULT NULL COMMENT 图标, sort INT DEFAULT 0 COMMENT 显示顺序, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜单权限表; CREATE TABLE sys_user_role ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户角色关联表; CREATE TABLE sys_role_menu ( role_id BIGINT NOT NULL, menu_id BIGINT NOT NULL, PRIMARY KEY (role_id, menu_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT角色菜单关联表;这套设计里有几个值得注意的点。sys_role表中我刻意加了role_key字段它才是代码里真正使用的角色标识主键id只负责关联。这样做的原因是主键不可变一旦在代码里大量硬编码roleId1后续数据库迁移或数据合并时会非常被动。2.2 权限标识符规范perms命名与通配匹配很多初学RBAC的人会误解菜单表的作用以为它就是存页面菜单的。实际上在成熟权限体系里sys_menu同时承载两件事菜单树的展示和接口权限的标识。一个按钮节点在菜单树里对应一个按钮它的perms字段就定义了访问该按钮对应接口所需的权限标识。权限标识符的命名建议统一采用“模块:子模块:操作”的三段式比如system:user:add、system:user:edit、system:user:delete。这样既能在前端做按钮级显隐也能在后端注解上精确匹配接口权限。采用三段式之后还有一个额外收益就是天然支持通配匹配。角色分配权限时如果不希望下属角色逐项勾选system:user:add、system:user:edit、system:user:delete可以直接给system:user:*代码里做权限校验时用AntPathMatcher匹配一下通配符即可。实测下来这种设计在需要快速给运营开一批“半开放权限”的场景里非常顺手。2.3 Spring Security、Shiro与轻量框架怎么选Java生态里RBAC的落地框架主流就是Spring Security和Apache Shiro近两年Sa-Token的使用率也明显上升。选型本质上是在“生态整合能力”和“上手成本”之间做权衡。维度Spring SecurityApache ShiroSa-TokenSpring生态集成原生整合自动化配置完善需手动整合Boot下略繁琐整合度高注解支持好OAuth2/OIDC支持内置完整方案需要额外扩展支持但生态较浅学习曲线偏陡概念较多平缓容易上手平缓API简洁社区活跃度极高中偏存量项目中国内社区活跃典型场景中大型单体、微服务、前后端分离传统单体后台、快速交付中小项目、内部系统Spring Boot 3.x全面普及之后新项目我基本默认Spring Security。因为Spring Boot已经把Security的过滤器链、OAuth2 Client、方法级安全都纳入自动化配置和Spring Cloud Gateway、OpenFeign这一套微服务体系的配合也最顺畅。Shiro在大量存量项目中依然活得很好胜在小巧直接对于“只做登录和角色判断”的简单需求学习成本确实低。但它和OAuth2、多租户体系整合时难受程度会直线上升。Sa-Token是国产轻量框架在内部工具、小项目里用起来非常舒服API设计更贴近业务直觉。选型一定要看团队情况如果你的团队已经对Spring Security很熟完全没必要为了“更简单”去引入另一套体系。2.4 数据权限行级权限RBAC之外必须补的一课RBAC解决的是“能不能访问这个接口”的问题但真实业务里还有一个高频需求同一个接口不同角色看到的数据范围不一样。这就是数据权限也叫行级权限。典型场景销售只能看自己的订单销售主管能看本部门所有订单销售总监能看全公司订单。如果RBAC只做到接口层那么销售登录后调用订单列表接口后端就得在Service层写一堆if else条件拼接逻辑。短期没问题时间一长每个列表接口都要写一遍部门范围判断代码重复率极高。工程上运行最多的方案是给角色表增加一个数据范围字段比如data_scope1表示全部数据、2表示本部门及以下、3表示本部门、4表示仅本人。然后在SQL查询层做统一拦截通过MyBatis拦截器在SQL编译期自动拼接dept_id条件。这种方案的通用性最强也是若依等快速开发平台默认采用的方式。这块的坑我会在后文专门写但这里先强调一点拼接数据权限条件时一定要处理好括号。and与or组合的SQL如果少了括号极容易产生数据越权漏洞而且这种漏洞在测试阶段往往发现不了。3. 实操过程与核心环节实现从建表到接口鉴权的完整链路3.1 建库建表和初始化管理员账号把第二章的表结构准备好之后需要初始化一个管理员角色和账号才能启动第一版权限验证。初始化SQL我通常这样写。-- 初始化管理员角色 INSERT INTO sys_role (role_key, role_name, sort, status) VALUES (admin, 超级管理员, 1, 1); -- 初始化管理员用户密码是BCrypt加密后的 admin123 INSERT INTO sys_user (username, password, nickname, status) VALUES (admin, $2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2, 系统管理员, 1); -- 建立关联 INSERT INTO sys_user_role (user_id, role_id) VALUES (1, 1); -- 将全部菜单权限授予管理员角色假设菜单id从1到100 INSERT INTO sys_role_menu (role_id, menu_id) SELECT 1, id FROM sys_menu WHERE id BETWEEN 1 AND 100;这里强烈建议一开始就使用BCrypt加密不要图省事用MD5。MD5加盐的成本远高于BCrypt而且Spring Security内置的BCryptPasswordEncoder直接就能用。密码散列的代价是每次登录多花几十毫秒对用户体验几乎没有影响但安全性完全不是一个级别。3.2 集成Spring Security时的版本细节Spring Boot 3.x下集成Spring Security核心是定义SecurityFilterChain。这里给一个前后端分离场景下最小可用配置登录接口放行其余接口全部走认证。Configuration EnableWebSecurity public class SecurityConfig { Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth - auth .requestMatchers(/auth/login).permitAll() .anyRequest().authenticated() ) .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }这里有个典型的版本坑Spring Boot 2.7之前用的是antMatchersSpring Boot 3.x开始改成requestMatchers同时WebSecurityConfigurerAdapter已被废弃。如果网上抄到旧代码直接粘大概率会在启动时报错。版本问题只能看官方文档不要依赖旧博客。配置里的jwtAuthFilter()是自定义的认证过滤器核心逻辑是拿到请求头里的Token解析出userId然后从缓存中查出用户信息封装成Authentication对象放进SecurityContext。Token的生成和校验建议直接使用Spring Security OAuth2 Resource Server的JWT支持或者引入jjwt这类轻量库不要自己手写加密签名逻辑。3.3 自定义RequirePermission注解完成接口鉴权Spring Security自带PreAuthorize写法是PreAuthorize(hasAuthority(system:user:add))字符串里嵌SpEL表达式。这种方式能跑但表达式的错误只有在运行时才能发现而且代码里一长串引号很难读。我更喜欢自定义一个注解把权限标识直接放注解参数里编译期就能检查切面逻辑也更可控。先定义注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequirePermission { String value(); }再定义切面Aspect Component public class PermissionAspect { Before(annotation(requirePermission)) public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) { Authentication authentication SecurityContextHolder.getContext().getAuthentication(); if (authentication null || !authentication.isAuthenticated()) { throw new AccessDeniedException(未登录); } Collection? extends GrantedAuthority authorities authentication.getAuthorities(); String required requirePermission.value(); boolean matched authorities.stream() .map(GrantedAuthority::getAuthority) .anyMatch(authority - matchPermission(required, authority)); if (!matched) { throw new AccessDeniedException(无操作权限); } } private boolean matchPermission(String required, String authority) { AntPathMatcher matcher new AntPathMatcher(); return matcher.match(required, authority) || matcher.match(authority, required); } }这里把Spring自带的AntPathMatcher拿来做了通配符匹配所以注解上写system:user:*也能匹配到用户拥有的system:user:add权限。接口层使用就非常干净了RestController RequestMapping(/system/user) public class SysUserController { PostMapping(/add) RequirePermission(system:user:add) public ApiResult addUser(RequestBody SysUser user) { // 业务逻辑 } }相比PreAuthorize这套自定义注解有两个实际收益。一是团队成员看到权限标识就是字符串参数不会因为SpEL写错导致线上权限失效二是切面逻辑完全在自己手里以后想加数据权限校验、操作日志记录都方便。3.4 权限缓存到Redis登录后权限的存取策略用户登录成功之后不要把整个用户对象和菜单列表全部塞进Session或Redis那样内存浪费极严重。更合理的做法是只缓存权限标识符集合比如ListString perms roleMenuService.selectPermsByUserId(userId); // 缓存key带版本号方便后续权限变更时统一失效 redisTemplate.opsForValue().set( login:perms: version : userId, perms, 2, TimeUnit.HOURS );缓存key里带上version这个字段是我后来才加上的经验。很多权限系统最头疼的问题不是认证而是权限变更后缓存刷新不及时。如果直接在key里拼一个权限版本号后台修改角色权限时只要把全局版本号加1所有用户的旧缓存自动失效下次请求就能强制重新加载权限。这种方式比用户主动删缓存、遍历key删缓存都要简单可靠。实际操作中可以给用户表或角色表增加一个perm_version字段每次权限相关变更都update一下版本号。JWT过滤器解析Token后拿缓存里的版本号和请求中的版本号比对不一致就重新加载权限。这个方案我在线上系统里实测很稳推荐直接采用。4. 常见问题与排查技巧实录权限失效、缓存错乱与拦截器误伤4.1 接口权限配置了却没生效这是权限系统上线后最常被开发怼的问题“注解我都加了为什么普通用户还是能调这个接口”排查顺序很重要。第一看SecurityConfig的放行规则如果配置里写了requestMatchers(/**).permitAll()那不管业务层注解怎么写请求都已经从过滤器链条上放过去了后面的AOP切面根本不会触发。这个属于配置范围过宽的经典错误需要把放行路径收敛到登录接口、静态资源、健康检查这几类白名单上。第二看切面是否真的被Spring管理。PermissionAspect如果忘了加Component或者注解类被final修饰导致CGLIB代理失效都会出现“注解不生效”的现象。排查时可以临时在切面里加一条日志看请求进来到底走没走切面。第三看权限标识符是否匹配。requires和用户拥有的权限字符串之间可能差一个字母比如权限表里存的是system:user:addxxx注解上写system:user:add通配匹配倒是能通过但精确匹配就会漏。这种问题排查最费时间我一般会写一个测试接口把当前用户拥有的权限列表直接打印出来比对。4.2 角色权限变更后用户权限不更新权限系统的第二个高频问题是“缓存滞后”。用户权限在登录时一次性加载进Redis管理人员在后台给用户加了角色但该用户不退出重新登录新权限就一直不生效。这个问题其实就是第三章提到的权限版本号方案解决的场景。如果项目已经有全局权限版本号那么权限变更时执行一次版本号更新并通过消息或请求间校验强制刷新就不需要逐个用户去清理Redis key。如果没有版本号只能退而求其次在角色修改逻辑里主动删除login:perms:userId对应的key。但要注意大版本批量调整岗位权限时这种遍历删除的方式性能堪忧而且容易漏删除。还有一个边缘情况用户的Redis缓存明明刷新了但SecurityContext里旧的Authentication对象还没被替换。这通常发生在同一次请求内做了权限变更和再次鉴权建议涉及权限变更的操作都强制要求用户重新登录一次避免上下文与新权限不一致。4.3 数据权限拦截器误伤普通查询与异步线程丢用户MyBatis拦截器实现行级权限的方案很强大但误伤概率也不低。最常见的问题是所有Mapper方法都被统一拦截连字典查询、统计查询也被强行拼上dept_id条件查询结果变得完全不可用。解决思路是建立一个“数据权限注解”给需要执行行级过滤的Mapper方法打上标记拦截器里通过反射判断方法上有没有该注解没有就直接放行。同时要做好白名单设计比如SysUserMapper.selectList这种系统级查询方法绝对不应该被数据权限规则影响。异步线程丢用户信息也是个极容易踩的坑。SecurityContextHolder默认使用ThreadLocal保存用户信息当你在线程池里执行异步任务时新线程拿不到主线程的用户上下文数据权限拦截器就会报“当前用户为空”。轻量解决方案是使用TransmittableThreadLocal做上下文传递或者在提交任务时手动把用户信息传给异步方法。这个问题在高并发导入、异步通知场景里尤其明显排查一次崩溃是真耽误事。4.4 权限问题排查速查表现象核心原因快速处理加了权限注解但接口仍可访问过滤器放行范围过宽 / 切面未生效收紧permitAll路径检查AOP代理配置修改角色权限后不生效登录时缓存的权限未刷新引入权限版本号并校验或主动删除用户权限缓存查询被错误过滤数据数据权限拦截器没有白名单用自定义注解标记数据权限方法系统方法直接放行异步线程数据权限报错SecurityContext未传递使用TransmittableThreadLocal或手动传递用户信息权限匹配时灵时不灵perm标识与权限表值不一致打印用户权限列表逐一比对统一perms命名规范趁踩坑的机会多说一句权限系统的日志审计一定要做全。谁在什么时间调用了哪个权限接口、被拒绝的请求又来自谁这些日志在事后排查和生产事故定责时价值极大。哪怕初期没有独立审计系统至少要在权限切面里把拒绝记录打到日志文件千万别省。5. 快速开发平台选型指南从自研到抄作业的思路转变5.1 自研一套权限模块到底要多久很多团队启动新项目时第一反应就是我全都要自己写包括权限模块。先别急着写代码可以理性估算一下时间。表结构设计、权限初始化、登录认证、菜单管理、数据权限、操作审计这一套完整做下来一个熟练后端工程师大概需要两周到三周左右。这还没算前端需要配套的角色管理页、菜单管理页、用户管理页更没算测试回归时间。等把权限模块打磨稳定又发现用户管理、字典管理、文件上传、通知公告这些基础功能也没着落项目排期肉眼可见要失控。所以要分清主次你的核心业务是什么如果系统是给内部使用的运营后台、管理平台权限相关能力属于基础底座直接选用成熟的开源快速开发平台把省下来的时间投入业务逻辑开发这才是性价比更高的路线。若依、芋道这类平台已经把权限底座打磨了很多年稳定度远高于临时自研。5.2 主流Java快速开发平台的横向对比下面这组对比不能代替深度调研但可以作为选型第一轮筛选的参考平台技术栈特点权限模型完整度社区与更新适用场景RuoYi若依单体/前后端分离双线Vue2/Vue3版本齐全RBAC数据权限经典稳定社区很大使用人数多中后台管理系统、内部工具、快速交付项目RuoYi-Vue-Plus若依增强版集成Sa-Token、MyBatis-PlusRBAC基础上增强了多租户与缓存设计维护频率高文档较分散需要更现代技术栈的新项目yudao芋道源码Spring Boot 3 MyBatis-Plus 多模块RBAC数据权限工作流支付功能丰富更新活跃需要较强业务模块扩展的交付项目JeecgBootSpring Boot Vue3智能化表单能力突出RBACOnline表单权限社区活跃但收费服务倾向明显业务系统、报表类需求多pig/pigxSpring Cloud微服务体系RBAC多租户企业级底座开源版与商业版分化微服务中后台、中大型团队5.3 容易被忽略的5个选型细节细节一授权协议必须仔细看。Apache-2.0协议下你可以自由修改、商用只需要保留版权声明而GPL类协议意味着如果修改后对外分发衍生代码也可能需要开源商用合规风险较高。很多平台在官网只字不提协议代码里藏着一个GPL-3.0的LICENSE文件等产品准备商业化时就麻烦了。细节二前端技术栈门槛往往被后端忽略。有些快速开发平台默认绑定Vue2整个生态已经停止维护新人不愿意学有些平台前端代码生成质量极差二次开发时改起来比从零写还痛苦。选型时一定让前端同学一起参与评审不要只看后端能力。细节三运维复杂度。有的平台默认集成了Redis、RabbitMQ、Elasticsearch、MinIO落地一个最小可用环境要装六个中间件小团队光部署就劝退了。按需裁剪模块的能力很重要别只看功能列表长不长。细节四代码生成器的质量。很多平台的代码生成器只生成基础CRUD数据权限、日志记录这些关键逻辑完全不生成交付代码的可读性也差。实际用下来代码生成器只能作为起点核心业务还是要手写Service层。细节五社区活跃度和文档质量。一个长期不更新的开源平台就是技术债的起点。选型前看近三个月是否有commit提交、Issue回复是否及时、文档更新是否与版本同步。5.4 我的实操体会选型最终要回到业务型态根据我个人经验选型之前不需要看太多花哨的功能对比先把业务型态想清楚。如果只是给企业内部做一个工具后台、内容管理平台直接选若依这类成熟稳定的单体快速开发项目就够了团队上手快、运维压力小。如果要交付给客户、做SaaS多租户产品一开始就考虑多租户体系和数据隔离方案yudao这类功能更全的平台或者商业授权框架更合适但一定要提前评估开源协议的风险。如果团队本身就有较强的Spring Cloud微服务建设能力那么基于Spring Boot Spring Security MyBatis-Plus自己搭一套RBAC底座再结合开源脚手架快速起项目反而比套一个重平台更可控。最后再分享一个细节技巧正式选型前把候选平台的源码拉到本地启动起来重点看它的权限表设计、菜单权限控制、数据权限拦截器这三个模块。网上Demo页面做得再好看源码里的权限管理逻辑混乱的话二次开发照样会让你怀疑人生。与其等项目组写完代码再发现问题不如在选型阶段就把权限模型这条路打通。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进