做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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; CREATE TABLE sys_menu ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '菜单ID', parent_id BIGINT DEFAULT 0 COMMENT '父菜单ID,0表示根菜单', 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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单权限表'; CREATE TABLE sys_user_role ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表'; CREATE TABLE sys_role_menu ( role_id BIGINT NOT NULL, menu_id BIGINT NOT NULL, PRIMARY KEY (role_id, menu_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色菜单关联表';这套设计里有几个值得注意的点。sys_role表中我刻意加了role_key字段,它才是代码里真正使用的角色标识,主键id只负责关联。这样做的原因是主键不可变,一旦在代码里大量硬编码roleId=1,后续数据库迁移或数据合并时会非常被动。
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 Security | Apache Shiro | Sa-Token |
|---|---|---|---|
| Spring生态集成 | 原生整合,自动化配置完善 | 需手动整合,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_scope:1表示全部数据、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之前用的是antMatchers,Spring 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,那样内存浪费极严重。更合理的做法是只缓存权限标识符集合,比如:
List<String> 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-Plus | RBAC基础上增强了多租户与缓存设计 | 维护频率高,文档较分散 | 需要更现代技术栈的新项目 |
| yudao(芋道源码) | Spring Boot 3 + MyBatis-Plus + 多模块 | RBAC+数据权限+工作流+支付 | 功能丰富,更新活跃 | 需要较强业务模块扩展的交付项目 |
| JeecgBoot | Spring Boot + Vue3,智能化表单能力突出 | RBAC+Online表单权限 | 社区活跃但收费服务倾向明显 | 业务系统、报表类需求多 |
| pig/pigx | Spring 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页面做得再好看,源码里的权限管理逻辑混乱的话,二次开发照样会让你怀疑人生。与其等项目组写完代码再发现问题,不如在选型阶段就把权限模型这条路打通。