权限校验必须放在中间件里,因其天然支持基于路由名的精准控制、便于统一处理游客跳转和超管放行逻辑;需预加载权限至session、用字符串code标识、配合can()函数支持通配符与多级表达式,且文件系统chmod与业务权限须分层实现。

权限校验必须放在中间件里,不能写在控制器方法开头
直接在每个控制器方法里调用 checkPermission() 容易漏掉新接口、难以统一处理游客跳转或超管放行逻辑,维护成本高。ThinkPHP 等主流框架已明确要求用中间件——它在路由匹配完成后、控制器执行前拦截请求,天然支持基于路由名的精准控制。
关键点:
- 中间件注册位置要靠后,比如在
app/middleware.php的'http'数组末尾(确保$request->rule()->getName()能拿到有效路由名) - 校验依据必须是
$request->rule()->getName(),不是拼接$request->controller() . '/' . $request->action()——后者受大小写转换、多应用前缀、驼峰规则影响,极易错配 - 超管逻辑(如
isSuperAdmin())应放在中间件内统一判断,避免每个接口重复写
用户权限数据必须预加载进 session,别每次查库
登录成功后,立刻从数据库查出该用户所有权限标识(如 ['post.create', 'user.delete']),存入 $_SESSION['permissions']。后续所有校验都基于这个数组,否则每请求一次就查一次库,性能崩得快。
注意细节:
- 权限字段建议用字符串 code(如
setting.edit),不用 ID——语义清晰、便于调试、避免关联表 JOIN 复杂化 - 查询 SQL 必须用
DISTINCT防止角色继承导致重复权限 - 用户角色变更后,必须主动清除其 session 或更新
$_SESSION['permissions'],否则缓存脏数据
校验函数要用 can() 封装,且支持多级权限表达式
裸写 in_array('post.edit', $_SESSION['permissions']) 不够灵活。实际业务常需「或」逻辑(如编辑自己文章 or 有全局编辑权)、「前缀匹配」(如 post.* 匹配所有文章操作)。
推荐 can() 实现要点:
- 支持通配符:
can('post.*')应匹配post.create、post.edit - 支持数组参数:
can(['post.edit', 'post.delete'])表示「至少满足一个」 - 返回布尔值,不抛异常——异常留给业务层决定如何响应(403 / 重定向 / 提示)
- 不要在
can()里查数据库,它只做内存比对
文件系统权限和代码权限要分层,别混为一谈
chmod() 和用户角色权限是两套东西:前者控制进程能否读写磁盘文件,后者控制用户能否触发某段 PHP 逻辑。枚举类(如 enum Permission: int)只能提升代码可读性,不能替代 chmod() 的整型参数。
常见踩坑:
- 把
Permission::ReadWriteAll直接传给chmod($file, Permission::ReadWriteAll)→ 报Fatal error: Uncaught TypeError - 用户提交的权限字符串
'0644'被(int)强转成十进制644,而非八进制 → 必须用octdec('0644')或定义枚举时写case ReadWriteAll = 0644;(开头带0) - 误以为给目录设了
0755就等于用户能访问对应路由——其实只是 PHP 进程能读取该目录下的脚本,跟用户权限无关
真正复杂的是权限组合场景:比如某个 API 接口既要校验用户是否有 file.upload 权限,又要确保上传目标目录的 chmod 允许当前 PHP 进程写入。这两层检查缺一不可,但必须分开实现、分开报错。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











