ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RuoYi框架自定义接口与权限验证:从认证机制到API签名实战

RuoYi框架自定义接口与权限验证:从认证机制到API签名实战 1. 为什么要在RuoYi里自己写接口和权限先聊点实在的。RuoYi这套框架在国内Java后台管理系统里属于装机量很高的那一类Gitee上星星多、社区活跃从中小公司的内部管理系统到外包项目的后台到处能看到它的影子。很多人拿它当脚手架用因为登录、用户、角色、菜单、部门、字典这些通用功能已经做好了拉下来改改就能跑。但真到了业务落地的时候你会发现一个很现实的问题框架自带的东西只解决通用解决不了你公司的具体业务。比如你要给移动端App提供一个查询订单的接口要给第三方系统推送数据要在后台加一个自定义报表的查询入口——这些都属于自定义接口的范畴。而接口写出来了紧接着就是第二个问题谁来调怎么确认调用者是谁权限怎么校验总不能把后台管理接口直接裸奔在公网上吧。这篇文章我就以RuoYi为例把自定义接口 权限验证这条链路完整走一遍。先说清楚框架本身的认证鉴权机制是怎么运转的再带你在不破坏原有架构的前提下加一个自己的业务接口最后把接口的权限验证方式讲透。适合谁看刚接触RuoYi想动手改代码的Java开发以及在公司里需要给RuoYi扩展业务接口但不想把框架搞乱的同学。我默认你已经能把RuoYi跑起来对Spring Boot和Spring Security有基本概念但不用特别熟关键点我会展开讲。2. RuoYi的认证与鉴权机制先搞懂再动手2.1 登录成功后你手里拿到的Token是什么在RuoYi里登录流程走的是Spring Security框架但RuoYi并没有完全照搬Spring Security那套Session机制而是用了Token。你输入用户名密码调用登录接口校验通过后服务端会生成一个Token返回给前端。这个Token分成两部分一部分是随机的UUID字符串另一部分是你登录用户的基本信息比如userId、用户名最终以Authorization: Bearer 前缀token的形式放在请求头里传给后端。很多新手第一次看RuoYi源码会困惑这个Token到底存在哪实际上是存在Redis里的。RuoYi用Redis缓存了登录用户的Token信息key是Token的UUID部分value是LoginUser对象包含用户基本信息、权限集合、角色集合等。也就是说Token本身只是一个钥匙串真正的用户信息在Redis里每次请求过来RuoYi会拿着Token去Redis查查到了就认定你是登录用户查不到就返回401。这套设计有几个好处。一是服务端可以主动让Token失效管理员把某个用户踢下线直接把Redis里对应的key删掉就行二是多实例部署时只要Redis是共享的用户在任何一个实例上登录其他实例都能识别不需要做Session同步三是请求本身不携带用户敏感信息万一Token泄露攻击者拿到的也只是UUID没有用户密码之类的核心数据。理解了这一点你就知道为什么RuoYi里改密码、退出登录这些操作都要清理Redis缓存了。2.2 PreAuthorize注解和权限字符串是怎么串起来的RuoYi的权限验证核心是Spring Security的PreAuthorize注解配合自定义的权限校验逻辑一起工作。你在Controller方法上看到类似PreAuthorize(ss.hasPermi(system:user:list))这样的写法意思就是调用这个方法之前先检查当前登录用户是否拥有system:user:list这个权限标识。这个权限标识从哪来答案是数据库。RuoYi的菜单表里每个菜单或按钮都可以配置一个权限字符串角色和菜单关联用户和角色关联最终捋下来就是用户 - 角色 - 菜单 - 权限字符串。用户登录时RuoYi会把这个用户所有角色对应的权限字符串汇总成一个Set放进LoginUser对象里缓存到Redis。请求进来时ss.hasPermi(system:user:list)会去当前的LoginUser对象里查这个Set里有没有对应的字符串有就放行没有就抛异常。这里有个关键点值得注意RuoYi的权限验证走的是注解 AOP不是拦截器。Spring Security的方法级权限控制通过AOP代理实现所以你在写自定义接口时PreAuthorize必须写在Spring管理的Bean方法上Controller方法当然算而且要确保这个Controller能被Spring Security的切面拦截到。如果你加了一个没被扫描到的Controller或者方法没走Spring代理注解就不会生效接口就裸奔了。这一点后面我会拿实际代码说明。2.3 匿名访问和登录访问的边界在哪RuoYi默认情况下除了登录接口和验证码接口是放行的其他接口都需要登录后才能访问。这个放行配置在Spring Security的配置类里通过SecurityConfig里的permitAll()方法指定。实际项目里如果你要加一个完全公开的接口比如服务健康检查、某些对外查询接口需要把它加入放行列表否则请求会被Spring Security挡在门外返回401。但注意放行不等于不做任何校验。放行只是不需要登录就能访问如果这个接口内部用了SecurityUtils.getUserId()之类的工具方法获取当前登录用户那就会拿到null或者抛异常因为匿名请求根本没有LoginUser。所以设计自定义接口时第一步要想清楚这个接口是给登录用户用的还是给第三方系统用的还是完全公开的这决定了你后面用哪套权限校验方案。我见过不少同学在接口能通和接口安全之间反复横跳根源就是对这一层边界没想清楚。3. 从零写一个带权限的自定义接口3.1 明确你要的接口场景和权限要求好理论知识铺垫完了现在开始实操。我以一个非常典型的场景为例给管理后台加一个设备信息查询接口要求只有拥有iot:device:query权限的用户才能调用并且接口内部需要拿到当前登录用户的ID用来做数据范围过滤比如普通用户只能看自己创建的设备管理员能看所有。这个场景在公司里很常见特别是接入了物联网设备或第三方硬件项目的时候。RuoYi本身不关心你的业务表长什么样它只负责谁有权限调这个接口业务逻辑完全由你写。所以这个例子既能演示如何扩展接口又能演示如何把RuoYi的权限体系用起来一举两得。先定义一下我们要干的事新建一张简单的设备表device字段包括设备编号、设备名称、所属用户ID、创建时间提供一个GET接口/iot/device/list支持分页和简单的关键字搜索接口必须要求登录且拥有iot:device:query权限普通用户查询时只能查到create_by等于自己的数据管理员拥有iot:device:all权限能查全部。这个需求看起来很常规但它涉及了RuoYi自定义接口的完整链路建表 - 写Mapper - 写Service - 写Controller - 配置权限标识 - 前端菜单授权。下面我一步步拆开讲。3.2 建表和实体层先搭好数据访问的地基先在数据库里执行建表语句。我习惯把业务表单独放一个库或加前缀区分避免和RuoYi的系统表混在一起。这里为了演示简单直接建在RuoYi同一个库里CREATE TABLE iot_device ( device_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 设备ID, device_code varchar(64) NOT NULL COMMENT 设备编号, device_name varchar(128) DEFAULT NULL COMMENT 设备名称, create_by varchar(64) DEFAULT NULL COMMENT 创建者, create_time datetime DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (device_id) ) ENGINEInnoDB AUTO_INCREMENT1 COMMENT物联网设备表;然后新建对应的实体类IotDevice.java。这里有个RuoYi的使用习惯框架自带代码生成器能根据表结构自动生成实体、Mapper、Service、Controller全套代码。用生成器能省不少事但生成出来的代码质量只能说能跑里面的注释、命名、逻辑都需要自己调整。我建议新手第一次写自定义接口时不要依赖生成器手写一遍这样你对每层代码的作用会有更深的体感。实体类我习惯继承RuoYi的BaseEntity因为BaseEntity里封装了createBy、createTime、updateBy、updateTime、remark这些公共字段还有分页参数params、beginTime、endTime等。继承之后你的实体天然支持RuoYi的公共字段和分页查询省去重复定义。public class IotDevice extends BaseEntity { private Long deviceId; private String deviceCode; private String deviceName; // getter/setter 略 }Mapper层我走的是MyBatis。RuoYi默认的数据访问层是MyBatisMapper接口配XML或注解都行。为了保持工程风格一致我建议用XML方式public interface IotDeviceMapper { public ListIotDevice selectDeviceList(IotDevice device); }对应的XML里写查询语句。这里的关键点在于数据权限过滤。刚才提到普通用户只能看自己的数据这个过滤条件可以写在SQL里也可以写在Service层。我个人的建议是如果只是简单的按创建人过滤SQL里直接判条件是最高效的如果涉及复杂的数据权限规则比如部门数据权限那就要结合RuoYi的数据权限注解DataScope来做了这个后面单独讲。3.3 Service和Controller接口的入口逻辑怎么写Service层是业务逻辑的核心也负责调用Mapper。RuoYi的Service接口加实现类的模式虽然显得啰嗦但在大项目里确实利于维护。ServiceImpl里用Service注解注册成Spring Bean同时因为有分页需求要调用startPage()方法开启PageHelper分页。Service public class IotDeviceServiceImpl implements IotDeviceService { Autowired private IotDeviceMapper iotDeviceMapper; Override public ListIotDevice selectDeviceList(IotDevice device) { return iotDeviceMapper.selectDeviceList(device); } }Controller是自定义接口的门面。RuoYi的Controller返回结果统一用AjaxResult包装分页数据用TableDataInfo包装。这里我不展开讲每个类的源码你只要知道AjaxResult是{code:200,msg:操作成功,data:...}这样的结构前端拿到之后统一处理保持风格一致。Controller的核心代码如下RestController RequestMapping(/iot/device) public class IotDeviceController extends BaseController { Autowired private IotDeviceService iotDeviceService; GetMapping(/list) PreAuthorize(ss.hasPermi(iot:device:query)) public TableDataInfo list(IotDevice device) { startPage(); ListIotDevice list iotDeviceService.selectDeviceList(device); return getDataTable(list); } }注意几个点继承BaseController是因为startPage()和getDataTable()这两个工具方法都定义在BaseController里。startPage()会从当前请求的ServletRequest里读取pageNum、pageSize、orderBy参数然后用PageHelper设置分页紧接着执行的第一条SQL会自动带上limitPreAuthorize(ss.hasPermi(iot:device:query))是权限验证的关键ss是Spring容器里一个名为ss的BeanPermissionServicehasPermi方法用来校验当前登录用户是否拥有指定权限接口路径用的/iot/device/list前缀、模块名按你自己项目来定没有硬性要求但建议和业务模块保持一致避免管理混乱。写完之后别忘了在启动类或配置里确认MapperScan扫描到了你的Mapper接口。RuoYi一般已经在启动类上加了MapperScan(com.ruoyi.**.mapper)如果你的包名符合规则一般没问题。但要是你新建的是业务模块包名不在扫描范围内那就要在启动类或Mapper接口上加Mapper注解否则启动报找不到Mapper的错误这个坑我踩过不止一次。3.4 前端菜单和权限标识的挂接接口写完后有个环节容易被忽略权限标识iot:device:query虽然在注解里写死了但这个权限标识本身并没有出现在RuoYi的菜单表里。也就是说现在你就算给某个用户配了超级管理员角色他调用这个接口也一样会被拦截因为他的权限集合里根本没有这个字符串。所以需要去系统管理 - 菜单管理里新增一个菜单项。菜单类型选按钮权限字符填iot:device:query路由地址和组件路径可以随便填一个占位或者不填也行对于按钮类型的权限前端主要靠权限字符做控制。这样做的目的是把这个权限标识纳入RuoYi的角色授权体系。你给某个角色勾选这个按钮权限该角色下的用户登录后权限集合里就会带上iot:device:query调用接口时才能通过校验。这是我见过的最常见的接口写好了但访问还是403的原因注解写了但数据库里的权限标识没配等于白写。如果你是在已有系统上临时加接口又不想让这个权限出现在菜单里有没有别的办法有可以直接给管理员角色的权限字段里手动插入一行数据或者写SQL关联菜单表和角色菜单表但这样做不太规范不利于后续维护。新增菜单是推荐做法。4. 权限验证的两条路线纯注解方案和API签名方案4.1 登录用户调用PreAuthorize是主力前文其实已经展示了一种权限验证方式面向登录用户的接口用PreAuthorize(ss.hasPermi(xxx:yyy:zzz))这是RuoYi体系内最标准、最常用的做法。它的验证流程是请求进入Controller方法之前Spring Security的切面拦截到PreAuthorize注解调用spEL表达式里指定的ss.hasPermi()方法。这个方法内部会拿当前请求的Token去Redis里查LoginUser然后遍历LoginUser里的permissions集合看有没有对应的权限字符串。通过就继续执行方法不通过就抛一个AccessDeniedException最终被RuoYi的全局异常处理器捕获返回HTTP 403和权限不足的提示。除了hasPermiRuoYi还提供了hasRole校验角色、hasAnyPermi多个权限任一满足即可等方法。实际项目里我更喜欢用hasAnyPermi来表达或者的场景比如某个接口允许设备管理员和系统管理员两个角色访问可以写成PreAuthorize(ss.hasAnyPermi(iot:device:query,iot:device:all))这样比在代码里写if (user.isAdmin())清晰得多权限规则对外可见、可配置后续要调整权限范围改数据库就行不用动代码。4.2 第三方系统调用不依赖登录态的API签名校验现实项目里还有一种更常见也更棘手的情况你的接口不是给前端登录用户调的而是给第三方系统调的。比如另一个Java服务要定时同步设备数据或者某个小程序后端要调用你的接口查询数据。这时候接下来的问题来了第三方系统没有用户名密码登录的概念它不能走RuoYi的登录接口拿Token但它又确实需要访问你的数据怎么办两个方向。方向一在RuoYi里创建一个虚拟用户比如叫第三方设备系统给这个用户配一个角色角色配权限第三方用这个虚拟用户的账号密码调登录接口拿Token后续请求带Token。这个方案简单粗暴RuoYi的权限体系完全复用坏处是虚拟用户多了以后管理混乱而且一旦账号密码泄露风险面比较大。方向二做API签名认证。这个方案更适合服务端对服务端的高频、自动化调用场景。思路是给每个第三方调用方分配一对 appId 和 appSecret调用方在请求时用 appSecret 对请求参数做签名服务端验证签名合法后放行。签名通常包含时间戳和随机数防止重放攻击。签名校验怎么实现可以写一个Spring MVC的HandlerInterceptor专门拦截/open/**这类对外开放的接口路径在preHandle里做验签逻辑。验签通过则返回true放行否则返回false并输出错误信息。我在实际项目里实现过一个简化版的验签流程分享给你参考public class ApiSignInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String appId request.getHeader(X-App-Id); String timestamp request.getHeader(X-Timestamp); String nonce request.getHeader(X-Nonce); String sign request.getHeader(X-Sign); // 1. 校验appId是否存在且有效 // 2. 校验时间戳是否在允许的偏差范围内比如正负5分钟 // 3. 校验nonce是否已使用存Redis防止重放 // 4. 用appSecret拼接请求参数计算md5/hmac对比sign return true; } }具体签名算法不唯一常见的做法是将请求参数按key字典序排列拼接成key1value1key2value2的形式再加上appSecret和时间戳做一次HMAC-SHA256或者MD5最后转成Hex字符串。服务端用相同的算法重算一遍比对是否一致。这里有一个细节容易被忽略三个header参数appId、timestamp、nonce也要参与签名计算否则攻击者可以任意替换header内容。严谨一点的做法是把除sign外的所有请求参数包括header里的认证参数都按约定拼接参与签名任何一项被篡改签名都对不上。这种API签名方案的好处是不依赖登录态不依赖Redis缓存用户信息非常适合对外开放的业务接口缺点是需要你自己管理appId和appSecret的分发、轮换还要处理时间偏差、重放攻击等细节比直接套用PreAuthorize复杂。所以我个人倾向于按场景区分内部管理端接口用PreAuthorize对外开放的服务间接口用API签名。4.3 一个接口同时支持两类调用方谨慎设计有些同学可能会想我能不能做一个接口既支持登录用户调用又支持第三方系统用API签名调用技术上当然可以但我不建议一开始就做这种双模式接口理由有两个。一是复杂度和安全边界容易模糊两类调用方的权限粒度、限流策略、审计日志需求都不一样硬凑在一起后期维护很痛苦。二是安全评审的时候一个接口同时支持两套认证方案会让审计方很难评估风险。如果业务上确实躲不开比较稳妥的做法是写两个不同的Controller入口一个走PreAuthorize登录态校验一个走自定义拦截器做API签名校验两个入口底层复用同一个Service。内部系统和第三方系统各走各的门数据权限在Service层根据调用来源做区分。这样既兼顾了灵活性又不会把认证逻辑搅成一锅粥。5. 实操过程中必踩的坑和排查姿势5.1 大白话解释为什么我加了接口但前端一直说401先说一个最容易踩的坑接口写在RuoYi工程里但Spring Security默认所有请求都必须登录。你加了一个接口没走放行配置也没有在请求头里带Token前端一调返回401你以为代码写错了其实只是请求没带Token。排查方法很简单用Postman请求你的接口Headers里加Authorization: Bearer 登录接口返回的token如果通了说明接口没问题是前端请求头的问题。如果加了Token还是401那就要看Token本身是否有效。RuoYi的Token有两个地方容易失效一是Redis里的缓存过期默认30分钟可配置二是服务端重启后Redis缓存被清掉。排查Token可以走登录接口重新登录一次拿新Token试。这里还有个经常被忽略的点如果你改过RuoYi的Redis配置或者Redis换了库要确认后端连接的是同一个Redis实例、同一个db index否则Token写入和读取不在一个地方登录永远不成功。5.2 权限不足提示背后的常见原因接口返回403或业务码是权限不足俗称没有权限这个定位起来一般分几步。第一步确认当前登录用户是否拥有对应的权限标识。最简单的验证方法用管理员账号登录系统在系统管理 - 用户管理里查看这个用户关联的角色再看角色关联的菜单里是否包含你要的那个权限标识。如果管理员本身是超级管理员角色RuoYi默认所有权限都放行但注意超级管理员放行逻辑是写死在框架里的并不代表你的iot:device:query被自动加到了他的权限集合里。有些同学用admin账号测试通过换个普通账号就403原因就在这——admin是框架的特殊逻辑其他用户必须按权限集合来。第二步确认注解是否真的生效。如果你确认用户权限没问题但请求还是403那排查方向就得转移到注解是否被Spring Security的切面拦截到了。一种情况是Controller类没有被Spring扫描类上忘了加RestController或Component另一种是配置了EnableGlobalMethodSecurity(prePostEnabled true)吗RuoYi默认是开启的但如果你在自定义配置类里不小心覆盖了这部分配置就可能失效。这两个点排查起来不复杂检查启动日志和配置类就行。5.3 分页查不到数据或数据范围不对怎么自检RuoYi自带的分页功能用起来简单但坑也不少。一个常见问题是分页参数无效。startPage()方法内部是通过PageHelper的拦截器实现的它必须在Mapper方法执行前调用而且只对紧接着的一次查询生效。如果你在startPage()和Mapper调用之间又多执行了一次查询比如查了个字典、查了个配置那分页可能作用在错误的SQL上结果就是前端传了pageNum但分页不生效或者查出来的总数不对。所以Service层里startPage()和实际的列表查询之间不要穿插任何其他数据库操作。另一个问题是数据权限。如果你在SQL里手写了and create_by #{createBy}这种条件那就要先拿到当前登录用户ID再塞进查询条件。RuoYi提供了SecurityUtils.getUserId()或getUsername()方法可以在Service层里获取当前用户。但要注意如果这个接口是第三方系统用API签名方式调用的那此时并没有登录用户SecurityUtils.getUserId()会返回null或者直接抛异常。这种场景下数据过滤逻辑不能依赖SecurityUtils要改成从请求头或参数里获取调用者身份信息。5.4 测试接口时的固定套路我个人测试自定义接口的固定流程是先用登录接口获取Token再拿Token去请求自定义接口中间任何一个环节有问题都能快速定位。具体来说# 第一步获取验证码如果开启了验证码登录 # 第二步登录获取token curl -X POST http://localhost:8080/login -H Content-Type: application/json -d {username:admin,password:admin123,code:xxx,uuid:xxx} # 第三步带token请求自定义接口 curl -X GET http://localhost:8080/iot/device/list?pageNum1pageSize10 -H Authorization: Bearer 上一步返回的token如果多实例部署还要测试一下Token在多个实例之间是否通用这其实就是验证Redis是否共享。这个点很多同学容易忽略开发环境单实例时一切正常部署到生产多实例后用户登录了但请求打到另一台机器上就401本质原因是Session/Token共享没做好。6. 数据权限进阶当查自己的数据不再是一句SQL能解决6.1 RuoYi的DataScope注解是什么前面我提到过一个场景普通用户只能看自己创建的设备管理员能看全部。如果只有这一条规则在SQL里写AND create_by 当前用户就够了。但真实项目的权限规则往往更复杂比如按部门过滤上级部门能看到下级部门的数据按自定义的数据范围仅本人、本部门、本部门及以下、全部过滤甚至某些角色能看到指定部门的数据。RuoYi对这类需求提供了DataScope注解支持。用法是在Service方法上加注解并限定别名DataScope(deptAlias d, userAlias u) public ListIotDevice selectDeviceList(IotDevice device) { return iotDeviceMapper.selectDeviceList(device); }对应的Mapper SQL里需要在表名处加上别名比如FROM iot_device u LEFT JOIN sys_dept d ON u.dept_id d.dept_id。RuoYi的AOP切面会解析DataScope注解根据当前用户的数据权限范围自动拼接一段SQL过滤条件追加到查询语句后面。这一段条件通常长这样AND (u.dept_id IN (某个部门及子部门) OR u.user_id 当前用户ID)如果是管理员则不加任何条件。6.2 DataScope的局限性DataScope很好用但它有个显著的局限它只对MyBatis XML里明确写出了相关别名表的情况有效。如果你的查询SQL比较复杂比如多表关联、子查询、聚合查询切面自动拼接的条件可能加错位置导致SQL语法错误或者过滤逻辑不对。这时候我更推荐的做法是在Service层显式地根据当前用户的数据权限范围手工拼装查询条件。虽然代码量大一点但可读性和可控性强很多。这里我给一个比较稳妥的建议如果项目里数据权限规则特别复杂不要硬套框架的DataScope而是自己封装一个权限上下文工具类提供当前用户可见的部门ID集合当前用户可见的用户ID集合等方法在SQL层用if标签动态拼接。RuoYi的DataScope适合够用就好的场景一旦规则超出它擅长的那几种自己写反而是更可靠的选择。7. 接口上线前除了权限你还得检查这些7.1 参数校验别偷懒RuoYi默认集成了Validated相关的参数校验能力但很多自定义接口根本没用到。我发现一个普遍问题自定义接口喜欢用实体类直接接收前端参数前端传什么后端就接什么几乎不校验。比如设备编号为空、设备名称超长、分页参数传负数这些都能让后端返回一些莫名其妙的结果。接口上线的底线是所有暴露给外部的接口入口参数必须做基础校验。用Java Bean Validation注解是最省事的方案public class IotDevice extends BaseEntity { NotNull(message 设备编号不能为空) private String deviceCode; Size(max 64, message 设备名称长度不能超过64) private String deviceName; }Controller方法上记得加Validated注解否则校验不生效。分页参数虽然由PageHelper处理但如果前端传了超大pageSize也可能拖垮数据库建议在网关或过滤器里做全局的pageSize上限限制。这个防护很多内部系统没有但一旦接口对外开放就非常致命。7.2 日志和审计不能少自定义接口涉及的业务数据很多时候比RuoYi自带的用户管理、角色管理更核心。设备数据、订单数据、财务数据这些接口的调用记录最好都打日志。RuoYi本身提供了操作日志记录Log注解可以记录操作人、操作类型、请求参数、响应结果、耗时等。自定义接口如果涉及增删改建议统一加上Log注解涉及查询但数据敏感的可以自己记录访问日志。日志的作用不只是排查问题更是安全追溯的依据。一旦出现数据泄露或越权操作审计日志能帮你定位到谁在什么时间调了哪个接口。这个环节别省钱哪怕先简单记到日志文件也比什么都没有好。7.3 接口文档同步更新RuoYi工程里很多人写接口不写文档联调全靠口口相传。如果你在团队里建议接口写完后顺手补一份简洁的接口说明包含请求路径、请求方式、请求参数、响应示例、权限要求、调用方类型。不用写得多华丽但一定要有。我见过太多项目上线后接口文档和实际代码对不上新来的同事接维护项目时只能读源码效率非常低。工具方面如果你用的是SpringDoc或OpenAPI可以在RuoYi里集成自动生成接口文档。这个集成本身不复杂但要注意RuoYi的全局响应体和统一认证机制可能让自动生成的文档不够直观需要额外配置一下。手工文档和自动文档各有优劣选一个适合团队节奏的就行。8. RuoYi自定义接口可以扩展的实用场景8.1 对接MQTT或硬件设备数据上报RuoYi常见的使用场景之一是企业内部信息化系统而物联网设备的接入越来越多。比如设备通过MQTT协议上报状态你需要在后端写一个消费MQTT消息的接口把数据写入RuoYi对应的业务表然后在后台展示。这时候RuoYi只是承载了数据展示和权限管理的职责真正的数据来源是MQTT消息。一种实现方式是在RuoYi后端集成MQTT客户端订阅指定topic在消息回调里解析数据、写入数据库。这个回调入口并不是HTTP接口所以不涉及PreAuthorize但写入数据库时要注意数据权限和归属人。比如设备上报的消息里如果不带用户ID你需要根据设备编号反查所属用户再把create_by字段填上否则后台按用户过滤数据时这些上报数据就消失了。8.2 对接泛微E9等OA系统的自定义接口很多公司有OA系统比如泛微E9业务系统需要和它交互。泛微E9本身提供了一些标准接口但实际项目里往往需要自定义接口来做单点登录、待办推送、数据同步等。这时候你写接口的思路和RuoYi自定义接口的思路完全一致先确定调用方向是RuoYi主动调E9还是E9回调RuoYi再确定认证方式。如果是E9回调RuoYi走API签名方案更合适如果是RuoYi主动调E9那重点反而是E9那边的接口怎么对接。这种情况不在RuoYi的权限体系内但你在RuoYi侧写的调用E9接口的Service同样要封装好参数和异常处理。我特别想提醒的是对接第三方系统时网络超时和重试机制一定要做好很多偶尔失败的问题就出在没设超时时间、没做重试。8.3 给RuoYi加一个定时任务RuoYi自带了定时任务功能支持通过后台页面动态管理cron表达式。这个功能非常适合定时同步数据的场景。比如每天凌晨从第三方系统拉取一次数据写入本地表或者每天定时清理过期的Token缓存。定时任务和自定义接口有什么关系关系在于定时任务里同样需要写业务逻辑同样需要访问数据库但它没有当前登录用户的概念。所以如果你在定时任务里调用了某个Service而这个Service里用了SecurityUtils.getUserId()那就会报错。这是新手很容易掉进去的坑。写定时任务时凡是涉及当前用户的逻辑要么传参、要么换个写法不要依赖SecurityUtils。我自己在项目里踩过这个坑凌晨跑批突然报一堆NullPointerException查了半天发现是任务方法里调了一个ServiceService里用了SecurityUtils获取当前用户。从那以后写公共方法时我都会检查一下这个方法会不会被没有登录上下文的场景调用如果可能参数就从外部传入。9. 最后补充自定义接口的几个约定和心得这套流程走下来关于RuoYi自定义接口和权限验证我个人的体会有几点放在这里供参考。第一接口路径和权限标识的命名要有规范。路径上尽量带模块前缀比如/iot/device、/cmdb/asset权限标识统一用模块:实体:操作的三段式比如iot:device:add、iot:device:edit、iot:device:remove。这样后续维护和权限分配都一目了然。我看到过一些项目权限标识起得随意比如queryDevice、d1时间一长根本没人知道那个权限对应哪个功能。第二权限验证要在多个层面都做到。注解校验是门禁但一个接口的安全不能只依赖这一层。如果接口对外开放建议在网关或过滤器层面再做一次IP白名单、访问频率控制如果接口涉及敏感数据Response里不要返回不该返回的字段比如createBy对应的用户手机号这一点在写VO视图对象时就要注意不要把实体直接序列化出去。第三不要过度设计。很多同学第一次接触RuoYi的权限体系时会想给每个接口都配上最复杂的权限逻辑结果权限规则复杂到连自己都维护不了。实际上80%的自定义接口只需要登录可见或单一权限标识就够了真正需要精细到角色、部门、数据级权限的接口其实很少。权限设计的关键不是做到最复杂而是让规则清晰、可解释、可扩展。第四RuoYi的安全配置是可以按项目需求微调的但改动前先看源码。RuoYi的SecurityConfig里有很多默认安全策略比如验证码开关、并发登录限制、Token有效期等。修改配置本身不难难的是理解这些配置之间的联动关系。比如你把登录验证码关了那登录接口的入参就少一个字段前端也要跟着改你把Token过期时间调长了Redis里缓存的数据就更容易变成旧数据。改配置之前建议先对照源码理解一下这个配置控的是什么改完有什么副作用。这个习惯能帮你避免不少上线后才发现的问题。最后还是那句话框架是死的业务是活的。RuoYi给你的是通用能力的地基它把登录、权限、菜单、日志这些脏活累活先干完了你能不能在这上面盖出适合自己的房子取决于你对它的机制理解得够不够深。希望这篇文章能帮你把自定义接口 权限验证这条链路理清楚少走几步弯路。如果你在实际操作中遇到特别奇怪的问题欢迎按文章里的排查思路走一遍大概率能找到答案。
返回列表