简介:这是一套基于.Net6.0的前后端分离权限管理及快速开发框架,定位面向中小型项目的开发者与技术选型者,用于解决通用权限、组织人员、定时任务等基础模块的重复建设问题。核心模块涵盖组织机构、角色用户、权限授权、多系统/多应用管理、定时任务、业务单据编码规则及代码生成器,底层整合Asp.Net Core MVC、EF、Dapper、WebAPI、Swagger与Vue等主流技术,整体架构清晰、易于扩展。压缩包共1332个文件,约5.8MB,以533个cs后端源码、319个svg图形资源、180个js与138个vue前端文件为主体,另含少量数据库脚本与工程配置文件,可直接展开后进行二次开发。当前已有1277人学习下载。借助这份资料,可以快速理解权限模型的落地方式、多应用隔离思路与代码生成机制,并复用仓储层、服务层和前端基础组件,显著缩短新项目的基础搭建周期。
1. 拿到 .Net6.0 前后端分离权限管理框架,先搞清楚它能替你省多少重复代码
拿到一套基于 .Net6.0 的前后端分离权限管理及快速开发框架,多数人第一反应是先把压缩包解压、跑起来,看看登录页、菜单、角色配置是不是开箱即用。这类框架解决的问题非常具体:不用再从零写用户登录、角色管理、菜单管理、接口鉴权这套所有中后台系统都绕不开的公共部分,业务模块往现成壳子里挂就行。适合正在做 .NET 中后台项目、又不想被权限细节拖住进度的从业者。下面按我拆这类项目的顺序讲:先看结构,再跑通前后端,把权限链路说透,最后落到避坑和加新模块的验证路径。
2. 框架结构拆解:先看后端三层与前端工程,再决定改哪里
2.1 后端分层:Controller 薄、Service 厚、Repository 只碰数据
压缩包解压后第一件事不是急着启动,而是先看解决方案结构。这类框架的后端基本逃不出四层:启动项目(Api)、业务逻辑(Application/Service)、数据访问(Repository/Infrastructure)、公共基础(Model/Common)。层与层之间单向引用,Api 引用 Service,Service 引用 Repository,Model 被各层共用。
| 项目/目录 | 职责 | 常见类 |
|---|---|---|
| Api | 控制器、过滤器、Swagger、JWT 鉴权配置 | Controllers/LoginController.cs, Program.cs |
| Application/Service | 业务逻辑、DTO、事务管理 | Services/LoginService.cs |
| Repository/Infrastructure | 数据访问、仓储接口、ORM 封装 | Repositories/UserRepository.cs |
| Model/Common | 实体、枚举、通用返回包装 | Models/User.cs, Result.cs |
一个登录接口的完整调用链最能说明分层价值:LoginController 只接收参数,参数合法性交给模型状态校验;LoginService 里做用户名密码校验、生成 JWT、写登录日志;UserRepository 只负责按用户名查用户。如果看到控制器里直接new Repository()或者直接拼 SQL,说明这个框架把分层当摆设,后期改起来会痛。我一般用“Controller 十行以内”作为快速判断标准。
// 控制器只做入参接收和结果返回,业务判断全部下沉到 Service [HttpPost("login")] [AllowAnonymous] public async Task<Result> Login(LoginDto input) { // ModelState 校验失败时请求不会进 Service,避免业务层到处写 if if (!ModelState.IsValid) return Result.Fail("参数不合法"); return await _loginService.LoginAsync(input); }入参校验这块,LoginDto 上的[Required]、[StringLength]这类数据注解会被框架自动执行,校验不过直接返回 400,逻辑很直观。Service 里返回值统一走Result包装,前端拿 body 里的 code 字段判断成功失败,而不是依赖 HTTP 状态码。这套约定不少权限框架都采用,好处是业务异常也能走 HTTP 200 返回,前端处理统一。框架里_loginService来自构造函数注入,生命周期常见配置是 Scoped,意味着同一个 HTTP 请求内拿到同一个实例,事务可以跨多个 Repository 共享。如果配成 Singleton,遇到 EF Core 或 SqlSugar 的并发上下文会踩线程安全坑。
2.2 前端工程:Vue 管理后台的目录约定与请求封装
后端看完看前端。这套框架的前端一般是 Vue + Element 系,具体是 Vue 2 + Element UI 还是 Vue 3 + Element Plus,打开 package.json 看 vue 版本字段就能确认,两者在目录组织上没有本质差别。真正要关注的是三块:请求封装、动态路由、页面目录。
| 路径 | 职责 |
|---|---|
| src/utils/request.js | axios 实例,请求拦截器带 token,响应拦截器统一处理错误 |
| src/api/login.js | 登录、登出、获取当前用户信息 |
| src/router/index.js | 静态路由 + 动态路由生成逻辑 |
| src/store/modules/user.js | 保存 token、用户信息、菜单权限 |
请求封装是所有前后端分离项目的命门,这套框架里通常长这样:
// src/utils/request.js 的 axios 拦截器,所有请求都会走到这里 service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) service.interceptors.response.use( res => { // 业务层统一返回的 code,401 表示登录态失效 if (res.data.code === 401) { localStorage.clear() window.location.href = '/login' } return res.data }, err => { // HTTP 层 401 通常是 token 过期或签名错误 return Promise.reject(err) } )这套封装是多数中后台的标配写法:请求拦截器统一把 localStorage 里的 token 塞进 Authorization 头,响应拦截器统一处理后端业务码。注意 HTTP 401 和业务码 401 是两套东西:JWT 过期通常返回 HTTP 401,业务校验失败是响应体里的 code 字段,具体以框架约定为准。前端 401 跳登录页只是体验兜底,真正的权限判断必须以后端为准,这是拆这类项目时最需要守住的原则。前端把菜单隐藏了,接口没拦住一样是安全事故。
2.3 技术选型为什么值得用:.NET 6 LTS、JWT、ORM 的取舍
为什么这类快速开发框架集中选 .NET 6 而不是老掉牙的 .NET Framework?因为 .NET 6 是 LTS 版本,从 2021 年发布到 2024 年 11 月官方支持期都很充足,部署到 Windows 服务器或 Linux 容器都有成熟方案。虽然现在新项目已经有人直接上 .NET 8,但存量框架和这类脚手架选 .NET 6 仍然是稳妥选择——生态组件兼容性好,网上踩坑记录多,出问题能搜到答案。
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| .NET Framework 4.x | 老项目兼容好 | 跨平台差、性能一般 | 遗留系统维护 |
| .NET Core 3.1 | 跨平台成熟 | 已停止支持 | 不推荐新项目 |
| .NET 6 | LTS 支持周期长 | 比 .NET 8 旧 | 存量框架、保守选型 |
| .NET 8 | 性能更好、新特性多 | 生态迁移中 | 全新项目 |
ORM 层面,这类框架用 SqlSugar 的偏多,因为“快速开发”四个字决定了开发效率优先。SqlSugar 语法接近 SQL,分页、连表写起来快,国内资料多;EF Core 的迁移能力和 LINQ 表达更完善,但约定比较多,新手容易在导航属性上翻车。我的看法是:如果你是改业务代码,不用纠结 ORM,跟着框架现有写法走。框架用 SqlSugar 你就写db.Queryable<User>().Where(...),用 EF Core 你就写context.Users.Where(...),保持一致最重要。
鉴权方案上,前后端分离场景下 JWT 是绝对主流。Session 依赖 Cookie 和服务器状态,跨端调用、移动端接入都不方便;JWT 无状态,校验时只验签名不查会话表,服务端压力小。代价是注销和权限变更做不到实时,一般配合 Redis 存用户权限版本来解决,后面第 4 章会展开讲。
3. 从零跑通:数据库脚本、后端启动、前端联调一条线
3.1 环境准备:版本不对,后面全是踩坑
跑这套框架最少需要四样东西:.NET SDK、Node.js、MySQL、Redis。版本上不需要追新,稳定为主。我本机环境是 .NET SDK 6.0.x、Node 16 LTS、MySQL 8.0、Redis 6,这套组合跑绝大多数权限框架都没问题。
| 工具 | 建议版本 | 用途 |
|---|---|---|
| .NET SDK | 6.0.x | 编译、运行后端项目 |
| Node.js | 14 LTS 或 16 LTS | 前端依赖安装与本地开发 |
| MySQL | 5.7 或 8.0 | 业务数据存储 |
| Redis | 5.x 及以上 | 缓存、验证码、权限缓存 |
| IDE | Visual Studio 2022 或 Rider | 调试 C# 代码 |
这里提前说一个新手常用的坑:装了 .NET 8 SDK 的机器可以直接编译目标框架为 net6.0 的项目,但运行时还是要装 6 的 runtime,或者给 dotnet 配置 roll-forward。最常见的情况是本地编译通过,发布到服务器后提示“You must install .NET runtime”,第 5 章避坑部分再展开。检查版本用一条命令:
dotnet --list-sdks dotnet --list-runtimes看到 6.0.x 的 SDK 和 runtime 都在列表里,就可以继续。如果只有 8.0,也能编译 net6.0 项目,但我会直接把两个运行时都装上,省得后续发布时再来一遍。
3.2 初始化数据库:脚本执行顺序与连接串修改
数据库脚本一般放在压缩包的 Databases 目录或 doc/db 目录下。脚本文件名如果带数字前缀,就严格按顺序执行:先建表结构,再灌初始数据。不要跳着执行,关联表和外键很依赖顺序。
mysql -uroot -p --default-character-set=utf8mb4 < Databases/01_schema.sql mysql -uroot -p --default-character-set=utf8mb4 < Databases/02_data.sql第一条命令建表结构,第二条灌初始数据。--default-character-set=utf8mb4必须带,否则中文乱码。执行完进入数据库检查两件事:一是表的数量是否和文档对得上,二是初始账号表里有没有数据。
USE admin_db; SHOW TABLES; SELECT * FROM t_user LIMIT 5;如果表数量明显缺失,基本可以断定脚本执行中断了,重跑时先用 DROP 清掉已建的表再执行。如果 t_user 表是空的,检查 02_data.sql 里是不是写死了数据库名,连接账号有没有对应库的写权限。初始账号一般是admin/123456,密码在数据库里是加密后的字符串,不要试图手动改成明文,后面验证登录还是要走后端加密逻辑。
3.3 启动后端:appsettings.json 三处必改参数
后端启动之前,先把 appsettings.json 里三处配置改掉:数据库连接串、Redis 地址、JWT 密钥。这三处错任何一个,启动能过但登录一定会出问题。
{ "ConnectionStrings": { "Default": "Server=127.0.0.1;Port=3306;Database=admin_db;User=root;Password=123456;Charset=utf8mb4" }, "Redis": { "Host": "127.0.0.1", "Port": 6379, "Password": "" }, "Jwt": { "Issuer": "AdminApi", "Audience": "AdminApp", "SecretKey": "replace-with-a-key-at-least-32-chars", "ExpiresMinutes": 120 } }连接串里 Server 用127.0.0.1比localhost更稳,能避开部分环境下的 socket 解析问题。Charset=utf8mb4保证中文和 emoji 不被截断。Redis 的 Password 本机没设密码就留空字符串,但生产环境必须设密码,否则 Redis 默认无防护很容易被扫描爆破。JWT 的 SecretKey 至少要 32 个字符,长度不足 HS256 签名算法会直接报错,这个坑在第 5 章还会遇到。
配置改完,用命令行启动后端:
cd 你的解压目录/src dotnet restore dotnet run --project AdminApi/AdminApi.csprojdotnet restore恢复 NuGet 包,第一次会慢一些,看到“已还原”字样说明依赖拉取正常。启动后控制台会输出监听的地址,一般是http://localhost:5000或https://localhost:5001。接着打开浏览器访问/swagger/index.html,能看到接口列表就说明后端起来了。如果端口被占用,去 Properties/launchSettings.json 里改 applicationUrl,前后端代理里的 target 也要同步改。
3.4 启动前端:依赖安装、代理配置与首次登录
后端跑起来后转到前端目录,一般是 web 或 AdminWeb。安装依赖时公司网络很容易卡住,直接指定镜像源:
cd web npm install --registry=https://registry.npmmirror.comnode_modules 装完启动开发服务器:
npm run dev默认端口一般是 8080。打开浏览器访问http://localhost:8080,这时候登录接口大概率还是调不通的,因为前端开发服务器的/api请求需要代理到后端。代理配置在 vue.config.js 或 vite.config.js:
// vue.config.js 常用写法 module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:5000', changeOrigin: true } } } }changeOrigin: true会把请求头里的 Host 改成 target 地址,避免后端做域名白名单时拒绝。改完代理配置必须重启前端,热更新不覆盖 devServer 配置。这一步做完,浏览器里用初始账号登录,能进到首页看到菜单和用户信息,前后端联调就算通了。如果提示密码错误,去 t_user 表确认初始账号有没有被脚本改过密码,或直接看 02_data.sql 里 INSERT 语句的明文。
4. 权限是怎么生效的:RBAC 表结构、JWT 链路与接口校验
4.1 RBAC 数据模型:五张表把用户、角色、权限串起来
权限管理框架的核心是 RBAC(Role-Based Access Control,基于角色的访问控制),这套代码里落地为三张主表和两张关联表。理解这五张表,整个权限体系就通了一半。
| 表 | 关键字段 | 作用 |
|---|---|---|
| t_user | id, username, password, status | 登录账号,密码加密存储 |
| t_role | id, name, code, is_super | 角色,is_super 标记超管 |
| t_menu | id, parent_id, type, name, path, perm | 菜单 + 按钮权限标识 |
| t_user_role | user_id, role_id | 用户和角色多对多关联 |
| t_role_menu | role_id, menu_id | 角色和菜单/按钮权限多对多关联 |
登录时后端做的事可以拆成三步:通过用户名查用户;拿 userId 去 t_user_role 查出角色;再拿角色去 t_role_menu 查出菜单和权限码。一个用户可以有多个角色,一个角色可以有多个菜单权限,这就是“多对多”的含义。菜单表里的type字段通常区分目录、菜单、按钮三类:目录只是导航层,菜单是页面路由,按钮是最细粒度的操作权限,比如“新增客户”“删除订单”这种动作。
权限校验的 bypass 逻辑也在这五张表里:t_role.is_super = 1的角色,后端会直接跳过权限码比对,所以超管能看到所有菜单、调所有接口。实际用的时候给内部运维账号开超管没问题,给业务用户误开超管,菜单全开只是一方面,接口层全部放行才是隐患。我见过不止一次生产环境普通账号被挂上 is_super,排查半天最后发现是初始化脚本里写死了角色编码。权限缓存一般会存进 Redis,key 设计为permission:{userId},用户改角色后要主动删掉对应缓存,否则权限变更要等 Redis 过期才生效。
4.2 JWT 登录签发与鉴权参数:哪些值不能改错
登录接口验证完密码后,后端要做两件事:生成 JWT 令牌,把用户信息存 Redis 或直接编码进令牌。JWT 生成的常见写法:
// 登录成功后构造 Claims 和签名参数 var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.UserName) }; var key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(jwtSettings.SecretKey)); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: jwtSettings.Issuer, audience: jwtSettings.Audience, claims: claims, expires: DateTime.Now.AddMinutes(jwtSettings.ExpiresMinutes), signingCredentials: credentials); return new JwtSecurityTokenHandler().WriteToken(token);Claims 是 JWT 的“载荷”,里面放什么有讲究。有的框架会把权限码列表new Claim("perms", string.Join(",", perms))塞进去,好处是校验时不用查 Redis,坏处是 token 变大、权限变更要等 token 过期才生效。另一派只在 token 里放 userId 和 name,每个请求从 Redis 按permission:{userId}拉权限,改角色立即生效。我拆过的框架里后者更常见,对权限频繁调整的后台系统更友好。
鉴权中间件配置决定了 token 怎么被校验:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtSettings.Issuer, ValidAudience = jwtSettings.Audience, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(jwtSettings.SecretKey)) }; });这里几个 Validate 开关在生产环境一个都不能关。ValidateAudience防止别人拿 A 系统的 token 调到 B 系统,ValidateIssuerSigningKey保证 token 签名密钥正确。SecretKey 和登录签发时必须是同一个值,改了密钥所有已签发 token 立即失效,用户得重新登录。ExpiresMinutes 一般设 120 分钟左右,太短影响体验,太长有泄漏风险。有刷新令牌机制的框架会把刷新 token 的过期时间设到 7 天甚至更长,没有刷新机制的就靠用户重新登录。
4.3 接口级权限与动态菜单:前后端如何对同一个权限码
RBAC 落到代码层面是接口权限和页面权限两层。接口权限靠自定义特性标记:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class PermissionAttribute : Attribute { public string Code { get; } public PermissionAttribute(string code) => Code = code; } // 控制器用法 [HttpGet("list")] [Authorize] [Permission("customer:list")] public async Task<Result> GetCustomerList() { return await _customerService.GetListAsync(); }PermissionAttribute标记的"customer:list"就是 t_menu 表里某条权限记录的perm字段。拦截器或过滤器拿到当前用户的权限集合,和"customer:list"比对,没有就返回 403。这里最容易踩的坑是权限码不统一:代码里写customer:list,菜单表里存customer/list,字符串格式对不上,权限永远不生效。我在加字段时会把权限码当作接口文档的一部分,前后端约定同一个字符串。
前端动态路由是另一条线:登录后根据用户菜单生成路由。router.addRoutes()拿到的是当前用户有权限的页面,没权限的菜单压根不会注册。按钮权限一般通过自定义指令v-permission="'customer:add'"控制,没权限就移除按钮 DOM。但按钮隐藏只是用户体验,接口必须用[Permission]拦住,否则有人直接调接口就能绕过页面限制。前端拦截是面子,后端校验是里子。
5. 避坑指南:环境、数据库、代理与权限的常见翻车现场
5.1 数据库脚本执行到一半报错,表建全了但数据灌不进去
现象:执行 01_schema.sql 提示Unknown collation: 'utf8mb4_0900_ai_ci',后面脚本直接中断,再跑就报“表已存在”。
原因:utf8mb4_0900_ai_ci是 MySQL 8.0 引入的排序规则,MySQL 5.7 不认识。框架作者在 8.0 上导出脚本,你的环境是 5.7,字符集定义直接崩了。
解决:把脚本里所有utf8mb4_0900_ai_ci替换成utf8mb4_general_ci,或者把本地数据库换成 MySQL 8.0。我习惯用版本对齐的方式,和框架作者的数据库大版本保持一致,后续尽量避免排序规则差异。改脚本前先备份,用 sed 批量替换比手改快:
sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' Databases/01_schema.sql5.2 后端启动即崩,报 Redis 连接超时
现象:dotnet run之后几秒钟,控制台抛出StackExchange.Redis.RedisConnectionException: It was not possible to connect to the Redis server(s),应用直接退出。
原因:框架登录、权限缓存、验证码都依赖 Redis,本机没装 Redis 或配置的 Host/Port 不对。这是前后端分离框架最常见的环境缺失问题,装了也没改 appsettings 里的 Redis 地址一样报错。
解决:本地快速起一个 Redis 容器,比编译安装省事:
docker run -d --name redis -p 6379:6379 redis:6起来后检查appsettings.json里的 Redis Host 是不是127.0.0.1、Port 是不是6379,改完重启后端。如果设置了 Redis 密码,Password字段必须填,否则鉴权握手失败。
5.3 登录接口返回 401,但账号密码确认是对的
现象:Swagger 调登录接口,输入初始账号密码,返回401 Unauthorized,翻看后端日志没有业务异常。
原因:JWT SecretKey 长度不足。HS256 算法要求密钥至少 128 bit(16 字节),很多默认模板里写的123456这种短字符串,运行时会静默失败或签名抛错。前端往往把这个错误当成用户名密码错误,实际上和后端密钥配置无关。
解决:把 appsettings.json 里 Jwt.SecretKey 换成一个 32 字符以上的随机串,改成和当前框架签发、校验共用同一份配置。改完重启后端,重新登录。这里也提醒:SecretKey 不要用容易被猜到的单词,生产环境用 GUID 拼接或随机数生成器产出。
5.4 前端代理配了还是 404,登录请求打到前端自己头上
现象:浏览器控制台 Network 里看到请求地址是http://localhost:8080/api/login,返回 404 或 504,后端 Swagger 里完全看不到这条请求记录。
原因:vue.config.js 的 devServer.proxy 没生效。最常见两种:一是改了配置没有重启 dev server,热更新不覆盖 proxy;二是代理匹配规则太严格,后端接口前缀是/api没错,但请求实际可能被 webpack 静态资源处理拦了,或者框架里实际前缀是/api/v1。
解决:先重启前端再测,还是不通就把代理匹配放宽:
proxy: { '/api': { target: 'http://localhost:5000', changeOrigin: true, pathRewrite: { '^/api': '' } } }pathRewrite要不要配取决于后端路由前缀。如果后端控制器路由是[Route("api/login")],不用重写;如果后端没有 api 前缀,就必须把/api去掉再转发。看后端 Swagger 里的路径就一目了然。
5.5 权限配置了但接口仍然 403,按钮时隐时现
现象:角色菜单里已经勾选了某个按钮权限,前端页面按钮也显示出来了,但后端接口一直返回 403。
原因:权限码不一致,或者权限缓存没有刷新。后端[Permission("customer:add")]的代码和 t_menu 表里存的perm字段差一个字符,比对永远失败;或者用户权限列表被缓存进 Redis,角色绑定新菜单后没删缓存,后端还在用旧权限集合做校验。
解决:第一步去数据库核对 t_menu 里那条记录的 perm 字段,把它复制到代码特性里,不要手动输入;第二步找到管理后台“刷新权限缓存”的入口,或者直接删 Redis 里的 key:
redis-cli KEYS "permission:*" redis-cli DEL "permission:{userId}"从那以后我做权限联调时,先确认权限码字符串完全一致,再考虑缓存,基本能避开这个坑。
6. 进阶:把一个新业务模块接进权限体系,并验证整条链路
6.1 最小模块代码与权限码绑定
跑通框架后,实际开发动作通常是往里面加业务模块。以一个“客户管理”为例,最小闭环是三样东西:实体表、后端接口、菜单权限记录。后端接口要做的不是写一大堆代码,而是把权限码挂上去:
[Route("api/customer")] [ApiController] public class CustomerController : ControllerBase { private readonly ICustomerService _customerService; public CustomerController(ICustomerService customerService) { _customerService = customerService; } [HttpGet] [Authorize] [Permission("customer:list")] public async Task<Result> GetList() { // 实际查询逻辑放在 Service 里,这里只做转发 return await _customerService.GetListAsync(); } }这里的"customer:list"就是权限码,和 t_menu 表里 perm 字段、前端按钮指令里的值必须是同一个字符串。菜单记录插入后要挂到角色上,才能看到菜单和按钮:
INSERT INTO t_menu(parent_id, type, name, path, perm, sort) VALUES (1, 1, '客户管理', '/customer', 'customer:list', 9);插入后去角色管理界面给目标角色勾上这个菜单,或者直接往 t_role_menu 插关联记录。我用 SQL 插入的方式做初始化,正式环境还是建议走页面配置,能少写不少关联查询。
6.2 验证权限闭环:普通账号 403,绑定后 200
验证路径要按“最小权限”原则走:创建一个没有任何角色的普通账号,登录后调GET /api/customer应该返回 403;给这个账号绑定已勾选菜单的角色,重新登录,再调接口返回 200。注意重新登录和清缓存要同时做,否则 Redis 里的旧权限还挂着,验证结果不准。我在本地会额外删一次permission:*的 key,确保权限集合是当前数据库状态的真实映射。整套走完,一个新模块接入权限体系的工作就收口了。
之前有次上线前我给测试账号直接开了超管角色,权限链路完全没验,结果正式环境里业务用户也能看到内部按钮,排查半天才发现是角色标记了is_super。从那以后我每次加模块都强制走一遍“普通账号登录 → 接口 403 → 绑定角色 → 重新登录 → 接口 200”的路径,顺手把权限码复制到菜单表和按钮指令里。先验证链路再谈功能开发,能省掉后面大量返工时间。这个习惯坚持下来,权限相关的线上事故几乎绝迹。希望帮到你。
本文还有配套的精品资源,点击获取