简介这是一套基于 layui2.5.6 前端组件库与 ThinkPHP6.0.2 后端框架搭建的权限管理后台源码面向需要快速构建企业级管理系统的 PHP 开发者也适合初学者理解前后端协同开发流程。资源包共 1098 个文件约 6.1MB以 481 个 php 业务与框架代码、139 个 js 脚本、36 个 html 页面、30 个 css 样式为主另含图片、字体、配置与依赖锁文件完整覆盖前端界面、后端逻辑与工程配置。项目内置 RBAC 权限控制模型可实现用户角色分配与权限校验并通过 .env 管理数据库等环境配置借助 Composer 统一依赖版本目录结构清晰、便于二次开发。目前已有 2283 人学习下载读者可据此掌握权限后台的搭建思路、模块组织方式与前后端协作要点并在此基础上扩展自己的业务功能。1. 接手一个后台项目最怕权限菜单全是硬编码前阵子帮朋友看一个后台系统需求很明确角色不同左侧菜单不一样按钮级别的操作也要控住。他之前那套是用纯前端路由表写死的加一个角色就得改一次代码运维同事已经快疯了。后来我翻到这个基于 layui2.5.6 加 ThinkPHP6.0.2 的权限管理后台才算把「角色—菜单—按钮」这条链路真正跑通。它适合两类人一类是刚接触 TP6 想找个完整后台练手的另一类是手里有老 layui 项目、想低成本补上 RBAC 权限的。整套东西不花哨但胜在结构清楚改起来不玄学。2. 技术选型与目录结构为什么是 layui 配 TP62.1 前后端各管一摊的边界这个项目最舒服的一点是它没有强行上前后端分离。layui 负责页面渲染和交互TP6 负责数据、权限判定和接口输出两边通过模板变量和少量 ajax 对接。对于权限后台这种以表格、表单为主的场景这种组合反而比 Vue 接口来得快——菜单树、角色分配这些页面用 layui 的 tree 和 table 组件直接就能拼出来不用再单独维护一套前端工程。TP6 这边用的是标准的 MVC 分层但权限相关的逻辑被单独抽了出来。常见做法是在应用目录下建一个admin模块控制器只做参数接收和视图返回真正的权限校验放在中间件和自定义的 Auth 服务里。这样做的直接好处是以后要加一个「运营」角色只需要在数据库里配好规则代码一行不用动。2.2 目录结构长什么样下面是我按这个项目实际跑起来后的结构整理的和官方骨架基本一致只是多了权限相关的几个目录project/ ├── app/ │ ├── admin/ # 后台模块 │ │ ├── controller/ # 控制器Index、User、Role、Menu、Node │ │ ├── middleware/ # 权限中间件 CheckAuth │ │ └── view/ # layui 模板文件 │ ├── common/ # 公共模块 │ │ ├── model/ # User、Role、Menu、Node 模型 │ │ └── service/ # Auth 权限服务类 │ └── provider.php # 容器绑定 ├── config/ │ ├── database.php # 数据库配置 │ └── app.php # 应用配置 ├── public/ │ ├── static/ │ │ ├── layui/ # layui2.5.6 静态资源 │ │ └── admin/ # 后台自定义 css/js │ └── index.php # 入口文件 ├── route/ │ └── route.php # 路由定义 └── vendor/ # composer 依赖提示layui 的静态资源建议放在public/static/layui下模板里用__STATIC__常量引用换域名或加 CDN 时只改一处。2.3 权限数据表的设计思路RBAC 的核心就四张表用户表、角色表、菜单/节点表、用户角色关联表。这个项目在此基础上多了一张角色节点关联表用来控制「某个角色能看到哪些菜单、能点哪些按钮」。字段设计上节点表里有一个is_menu字段区分是菜单还是按钮还有一个pid做无限级分类。这样前端渲染菜单树和后端校验按钮权限用的是同一份数据。-- 节点表菜单 按钮 CREATE TABLE node ( id int unsigned NOT NULL AUTO_INCREMENT, pid int unsigned NOT NULL DEFAULT 0 COMMENT 父级ID, title varchar(50) NOT NULL COMMENT 名称, name varchar(50) NOT NULL COMMENT 控制器/方法标识, is_menu tinyint(1) NOT NULL DEFAULT 0 COMMENT 1菜单 0按钮, sort int NOT NULL DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 角色节点关联表 CREATE TABLE role_node ( role_id int unsigned NOT NULL, node_id int unsigned NOT NULL, PRIMARY KEY (role_id,node_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明name字段存的是控制器/方法这种格式比如User/index中间件校验时直接拿当前请求的控制器和方法去比对比存 URL 更稳改路由也不会失效。is_menu为 1 的节点才会出现在左侧菜单里为 0 的只用于按钮级校验。3. 核心模块拆解登录、鉴权、菜单渲染一条龙3.1 登录与 session 写入登录逻辑在app/admin/controller/Login.php里流程很直白接收账号密码查用户表验证密码写 session返回跳转地址。TP6 默认用 session 存登录态对于后台这种同域场景完全够用。public function doLogin() { $username $this-request-post(username); $password $this-request-post(password); $user UserModel::where(username, $username)-find(); if (!$user || md5($password . $user[salt]) ! $user[password]) { return json([code 0, msg 账号或密码错误]); } // 写入 session后续中间件靠这个判断 session(admin_id, $user[id]); session(admin_name, $user[username]); return json([code 1, msg 登录成功, url /admin/index]); }逻辑说明密码用了md5 salt的方式虽然现在看不算最强但在这个项目定位下够用改造成password_hash也很容易。关键是 session 里只存了用户 ID 和用户名没有把权限数据塞进去——权限每次请求实时查避免改了角色还要重新登录才生效。3.2 权限中间件 CheckAuth这是整个后台的闸门。所有后台控制器都挂上这个中间件请求进来先判断有没有登录再判断当前控制器/方法有没有在角色的节点列表里。public function handle($request, \Closure $next) { $adminId session(admin_id); if (!$adminId) { return redirect(/admin/login); } // 超级管理员直接放行 if ($adminId 1) { return $next($request); } $controller $request-controller(); $action $request-action(); $node strtolower($controller . / . $action); // 查当前用户角色对应的所有节点 $auth new AuthService(); $nodes $auth-getNodesByAdmin($adminId); if (!in_array($node, $nodes)) { return json([code 0, msg 无权限操作]); } return $next($request); }参数说明$request-controller()和$request-action()是 TP6 自带的拿到的是当前请求的控制器和方法名。getNodesByAdmin会先查用户角色再查角色关联的节点最后返回一个name字段的数组。这里有个细节节点名统一转小写比对避免大小写导致的玄学问题。3.3 菜单树的递归渲染左侧菜单不是写死的而是根据当前登录用户的权限动态生成。后端把节点查出来递归拼成树前端用 layui 的element组件渲染。public function getMenuTree($adminId) { $auth new AuthService(); $nodes $auth-getMenusByAdmin($adminId); // 只取 is_menu1 的 return $this-buildTree($nodes, 0); } private function buildTree($list, $pid) { $tree []; foreach ($list as $item) { if ($item[pid] $pid) { $children $this-buildTree($list, $item[id]); if ($children) { $item[children] $children; } $tree[] $item; } } return $tree; }逻辑说明buildTree是一个标准的无限级递归传入父级 ID找出所有子节点再对每个子节点继续递归。前端拿到这个树后用 layui 的layui.element.render(nav)就能渲染出可折叠的侧边栏。按钮级权限则是在模板里用{if in_array(User/del, $nodes)}这种方式控制显示后端中间件再兜底一次。4. 跑起来环境、配置、初始化三步走4.1 环境要求与依赖安装这个项目对环境的门槛不高PHP 7.1 以上、MySQL 5.7 以上、Composer 装好就行。TP6.0.2 本身要求 PHP 7.1.0layui2.5.6 是纯静态资源不挑环境。# 1. 进入项目目录 cd project # 2. 安装 PHP 依赖 composer install # 3. 复制环境配置文件 cp .env.example .env注意如果composer install卡住先检查 PHP 的proc_open和proc_get_status有没有被禁用TP6 的某些依赖会用到。4.2 数据库配置与导入打开.env文件改数据库连接信息。TP6 支持在.env里覆盖config/database.php的配置改这里比直接改配置文件更安全不会被 git 提交上去。[DATABASE] TYPE mysql HOSTNAME 127.0.0.1 DATABASE admin_db USERNAME root PASSWORD your_password HOSTPORT 3306 CHARSET utf8mb4 DEBUG true参数说明DEBUG true在开发阶段打开能看到详细的 SQL 和错误堆栈上线后记得改成false不然报错页面会暴露表结构。数据库建好后把项目根目录下的admin_db.sql导入里面包含了建表语句和一条默认管理员数据。4.3 入口与伪静态TP6 的入口文件在public/index.php所以 Web 服务器的根目录要指向public不是项目根目录。Nginx 下配一个伪静态把请求都转发到入口文件。location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } }配置完成后访问http://你的域名/admin/login用默认账号登录。如果页面样式全丢检查public/static/layui目录是否存在以及模板里的__STATIC__常量有没有被正确替换。5. 避坑与常见问题那几次翻车记录5.1 登录后一直跳回登录页现象输入正确账号密码提示登录成功但跳转后又回到登录页。原因TP6 的 session 默认是文件存储如果runtime目录没有写权限session 写不进去。解决给runtime目录 755 权限或者检查config/session.php里的存储路径是否可写。5.2 菜单渲染出来但点击没反应现象左侧菜单能显示但点击后右侧 iframe 不加载内容。原因layui 的element组件需要layui.use(element, function(){...})初始化如果 JS 报错导致初始化中断菜单就只是静态 HTML。解决打开浏览器控制台看有没有layui is not defined检查 layui 的引入路径是否正确。5.3 按钮权限前端隐藏了但接口还能调现象模板里用{if}隐藏了删除按钮但直接请求删除接口依然成功。原因只做了前端隐藏中间件没有覆盖到该控制器。解决确认所有后台控制器都挂上了CheckAuth中间件TP6 里可以在app/admin/middleware.php里全局注册也可以在每个控制器里单独protected $middleware [CheckAuth::class];。5.4 修改角色权限后不生效现象在角色管理里勾选了新菜单但用户刷新页面还是旧菜单。原因权限数据查的是 session 里的用户 ID但角色节点关联表更新后没有清缓存。解决这个项目没有用缓存所以直接刷新即可如果自己加了缓存记得在保存角色权限时删掉对应的缓存 key。5.5 数据库导入报外键错误现象导入 SQL 时提示Cannot add or update a child row。原因建表顺序不对关联表先于主表创建。解决按user→role→node→role_node的顺序导入或者临时关闭外键检查SET FOREIGN_KEY_CHECKS0;。6. 二次开发从改一个菜单到加一套新模块6.1 加一个新菜单的完整流程假设要加一个「文章管理」模块步骤是先在node表插入一条is_menu1的记录name填Article/index再插入两个按钮节点name分别填Article/add和Article/delpid指向菜单节点然后去角色管理里给对应角色勾上这几个节点最后建Article控制器和对应模板。整个过程不用改权限中间件的代码这就是 RBAC 的好处。6.2 把权限校验从中间件下沉到服务层如果项目变大中间件里查一次、模板里查一次会有重复查询。我一般会把当前用户的节点列表在中间件里查出来后塞到$request对象里控制器和模板直接取避免重复查库。// 中间件里 $request-nodes $nodes; // 控制器里 $nodes $this-request-nodes; // 模板里 {if in_array(Article/del, $request-nodes)}这样一次请求只查一次权限数据性能上会好很多。改的时候注意$request是对象别当成数组用。6.3 验证改造是否成功的三个检查点改完权限相关代码后我习惯按这三步验一遍第一用超级管理员登录看所有菜单和按钮是否正常第二用一个普通角色登录看菜单是否只显示勾选的直接访问未授权接口是否返回无权限第三改一次角色权限不重新登录刷新页面看是否立即生效。这三步走完基本不会翻车。6.4 一个容易忽略的细节节点 name 的大小写TP6 的$request-controller()返回的是驼峰格式比如UserGroup而数据库里存的可能是usergroup。如果不统一转小写权限校验就会失败。我一般在中间件里统一strtolower数据库里也存小写两边对齐。从那以后我每次加新节点都强制走一遍「插入节点 → 分配角色 → 清缓存 → 双角色验证」的流程再也没出现过权限对不上的情况。希望帮到你。本文还有配套的精品资源点击获取