Skip to Content
Week 11Day 1 - 角色与权限

Day 1 - 角色与权限

建议用时:180-210 分钟

你将学会什么

  • 认证和授权有什么区别
  • 角色和权限有什么区别
  • 为什么隐藏按钮不等于权限控制
  • 怎么定义稳定的权限名
  • 怎么在服务层检查权限
  • 没有权限时怎么返回失败结果

本页重点是:权限判断必须放在真正执行动作之前。界面可以根据权限隐藏按钮,但服务层仍然要再次检查。否则只要绕过界面,关键动作仍然可能被执行。

本页固定顺序

  1. 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
  2. 再学第二部分:把刚学的知识组合成一个完整例子。
  3. 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
  4. 最后做第四部分:先独立完成作业,再用完整答案检查。

学习衔接

上一页学习的是“复盘与巩固”,今天继续学习“角色与权限”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。

今天的最低通过线

第一次学习不要求背完整页。完成下面 3 项,就可以继续:

  • 能用自己的话说明“角色与权限”解决什么问题。
  • 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
  • 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。

第一部分:先学原理和最小知识

这一部分从最小知识开始。先读解释,再把紧跟着的短例子敲一遍。权限的核心不是“谁能看到按钮”,而是“谁能真正执行动作”。

认证和授权的区别

名词解决的问题例子
认证你是谁Alice 已登录
授权你能做什么Alice 能不能删除商品

登录成功只说明系统知道你是谁。

能不能删除商品,还要看权限。

角色和权限的区别

角色是一组权限的集合。

权限是具体动作。

例如:

角色权限
ViewerProduct.Read
EditorProduct.ReadProduct.CreateProduct.Update
AdminProduct.ReadProduct.CreateProduct.UpdateProduct.Delete

这样设计的好处是:

  • 用户只需要分配角色。
  • 权限可以统一维护。
  • 新增权限时不需要改每个用户。

为什么权限名要稳定

权限名最好集中定义:

public static class PermissionNames { public const string ProductDelete = "Product.Delete"; }

不要到处手写字符串:

"Product.Delete"

原因是字符串写错不会自动报错,例如写成:

"Product.Delet"

程序还能编译,但权限判断会失败。

隐藏按钮不等于权限控制

界面可以这样做:

CanDelete = permissionService.HasPermission(user, PermissionNames.ProductDelete);

然后根据 CanDelete 显示或隐藏删除按钮。

但这只是界面体验。

真正保护动作的是服务层:

if (!permissionService.HasPermission(user, PermissionNames.ProductDelete)) { return OperationResult.Fail("没有删除权限。"); }

因为按钮隐藏了,不代表删除方法不会被调用。

为什么权限检查放在服务层

删除商品这个动作属于业务动作。

服务层负责业务规则,所以删除前必须在服务层检查权限。

数据层只负责删除数据,不应该判断谁能删。

界面层只负责显示按钮,不应该成为唯一防线。

权限失败应该怎么处理

权限失败是可预期的业务失败。

本页不抛异常,而是返回:

OperationResult.Fail("Alice 没有删除权限。")

这样界面可以显示提示,程序也能继续运行。

真正意外的问题,例如文件损坏、数据库连接失败,才适合异常和日志。

角色与权限常用写法速查

需求写法说明
定义角色enum UserRoleViewer、Editor、Admin
定义权限名static class PermissionNames避免魔法字符串
判断角色user.Role == UserRole.Admin简单规则
判断权限Can(user, permission)集中权限逻辑
权限集合HashSet<string>判断是否包含权限
拒绝操作返回失败结果不继续执行业务

权限判断流程:

先确认当前用户 -> 再确认要做的动作 -> 查角色是否拥有权限 -> 没权限直接返回失败 -> 有权限才执行业务

第二部分:把知识组合成完整例子

今天做一个角色权限控制程序。

它会验证:

  • Viewer 只能查看,不能删除。
  • Editor 可以新增和编辑,但不能删除。
  • Admin 可以删除。
  • 删除商品时,服务层会检查 Product.Delete 权限。
  • 没有权限时返回失败,不执行删除。

完整 Program.cs

using System; using System.Collections.Generic; using System.Linq; var repository = new ProductRepository(); var permissionService = new PermissionService(); var productService = new ProductService(repository, permissionService); var viewer = new User("Alice", Role.Viewer); var editor = new User("Bob", Role.Editor); var admin = new User("Cindy", Role.Admin); void Print(OperationResult result) { Console.WriteLine($"{result.Success} - {result.Message}"); } Print(productService.DeleteProduct(viewer, 1)); Print(productService.DeleteProduct(editor, 1)); Print(productService.DeleteProduct(admin, 1)); Print(productService.DeleteProduct(admin, 99)); Console.WriteLine($"剩余商品数量:{repository.Count}"); public enum Role { Viewer, Editor, Admin } public sealed record User(string Name, Role Role); public static class PermissionNames { public const string ProductRead = "Product.Read"; public const string ProductCreate = "Product.Create"; public const string ProductUpdate = "Product.Update"; public const string ProductDelete = "Product.Delete"; } public sealed class PermissionService { private static readonly Dictionary<Role, HashSet<string>> RolePermissions = new() { [Role.Viewer] = new HashSet<string> { PermissionNames.ProductRead }, [Role.Editor] = new HashSet<string> { PermissionNames.ProductRead, PermissionNames.ProductCreate, PermissionNames.ProductUpdate }, [Role.Admin] = new HashSet<string> { PermissionNames.ProductRead, PermissionNames.ProductCreate, PermissionNames.ProductUpdate, PermissionNames.ProductDelete } }; public bool HasPermission(User user, string permission) { return RolePermissions.TryGetValue(user.Role, out HashSet<string>? permissions) && permissions.Contains(permission); } } public sealed class ProductService { private readonly ProductRepository repository; private readonly PermissionService permissionService; public ProductService(ProductRepository repository, PermissionService permissionService) { this.repository = repository; this.permissionService = permissionService; } public OperationResult DeleteProduct(User user, int productId) { if (!permissionService.HasPermission(user, PermissionNames.ProductDelete)) { return OperationResult.Fail($"{user.Name} 没有删除权限。"); } if (!repository.Exists(productId)) { return OperationResult.Fail($"商品 {productId} 不存在。"); } repository.Delete(productId); return OperationResult.Ok($"{user.Name} 删除商品 {productId} 成功。"); } } public sealed class ProductRepository { private readonly List<Product> products = new() { new Product(1, "Keyboard"), new Product(2, "Mouse") }; public int Count => products.Count; public bool Exists(int id) { return products.Any(product => product.Id == id); } public void Delete(int id) { products.RemoveAll(product => product.Id == id); } } public sealed record Product(int Id, string Name); public sealed class OperationResult { public bool Success { get; init; } public string Message { get; init; } = ""; public static OperationResult Ok(string message) { return new OperationResult { Success = true, Message = message }; } public static OperationResult Fail(string message) { return new OperationResult { Success = false, Message = message }; } }

正常运行结果

False - Alice 没有删除权限。 False - Bob 没有删除权限。 True - Cindy 删除商品 1 成功。 False - 商品 99 不存在。 剩余商品数量:1

这段程序说明:权限检查不是界面装饰,而是服务层执行动作前的必要判断。

第三部分:跟着敲代码

从这里开始动手。先用控制台项目验证权限规则。

第 1 步:创建控制台项目

dotnet new console -n RolePermissionDemo cd RolePermissionDemo

打开 Program.cs,清空原内容。

第 2 步:定义角色

public enum Role { Viewer, Editor, Admin }

解释:

  • Viewer 只查看。
  • Editor 能新增和编辑。
  • Admin 能执行全部动作。

第 3 步:定义用户

public sealed record User(string Name, Role Role);

解释:

  • 用户有名字和角色。
  • 这里先不做登录,只模拟当前用户。

第 4 步:定义权限名

public static class PermissionNames { public const string ProductRead = "Product.Read"; public const string ProductCreate = "Product.Create"; public const string ProductUpdate = "Product.Update"; public const string ProductDelete = "Product.Delete"; }

解释:

  • 权限名集中管理。
  • 后面检查权限时不要散落字符串。

第 5 步:定义角色权限表

private static readonly Dictionary<Role, HashSet<string>> RolePermissions = new() { [Role.Viewer] = new HashSet<string> { PermissionNames.ProductRead }, [Role.Editor] = new HashSet<string> { PermissionNames.ProductRead, PermissionNames.ProductCreate, PermissionNames.ProductUpdate }, [Role.Admin] = new HashSet<string> { PermissionNames.ProductRead, PermissionNames.ProductCreate, PermissionNames.ProductUpdate, PermissionNames.ProductDelete } };

解释:

  • 每个角色对应一组权限。
  • HashSet 适合判断是否包含某个权限。

第 6 步:写权限检查方法

public bool HasPermission(User user, string permission) { return RolePermissions.TryGetValue(user.Role, out HashSet<string>? permissions) && permissions.Contains(permission); }

解释:

  • 先找到用户角色对应的权限集合。
  • 再判断集合里有没有目标权限。

第 7 步:写 ProductRepository

public sealed class ProductRepository { private readonly List<Product> products = new() { new Product(1, "Keyboard"), new Product(2, "Mouse") }; public int Count => products.Count; public bool Exists(int id) { return products.Any(product => product.Id == id); } public void Delete(int id) { products.RemoveAll(product => product.Id == id); } }

解释:

  • Repository 只管数据。
  • 它不知道谁有权限。

第 8 步:写 ProductService 删除方法

public OperationResult DeleteProduct(User user, int productId) { if (!permissionService.HasPermission(user, PermissionNames.ProductDelete)) { return OperationResult.Fail($"{user.Name} 没有删除权限。"); } if (!repository.Exists(productId)) { return OperationResult.Fail($"商品 {productId} 不存在。"); } repository.Delete(productId); return OperationResult.Ok($"{user.Name} 删除商品 {productId} 成功。"); }

解释:

  • 先检查权限。
  • 再检查商品是否存在。
  • 最后才真正删除。

第 9 步:写验证流程

var viewer = new User("Alice", Role.Viewer); var editor = new User("Bob", Role.Editor); var admin = new User("Cindy", Role.Admin); Print(productService.DeleteProduct(viewer, 1)); Print(productService.DeleteProduct(editor, 1)); Print(productService.DeleteProduct(admin, 1)); Print(productService.DeleteProduct(admin, 99));

解释:

  • Viewer 删除失败。
  • Editor 删除失败。
  • Admin 删除成功。
  • Admin 删除不存在商品失败。

常见错误和修法

错误为什么错修法
只在按钮上隐藏功能只是界面看不到,逻辑仍可能被调用服务层也要检查权限
角色名到处写字符串拼错后很难发现用常量或枚举集中管理
权限判断分散在每个页面后期难维护PermissionService 统一判断
没有默认低权限角色新账号可能权限过大默认使用普通角色,只给必要权限
提示只写“失败”不知道是权限问题还是系统错误权限不足时明确提示“没有权限执行此操作”

小白重复敲写训练

权限先练纯 C# 判断,再绑定到按钮。

训练 1:按角色判断

bool CanDelete(string role) { return role == "Admin"; } Console.WriteLine(CanDelete("Admin")); Console.WriteLine(CanDelete("Viewer"));

训练 2:使用枚举角色

enum UserRole { Viewer, Editor, Admin } bool CanEdit(UserRole role) { return role is UserRole.Editor or UserRole.Admin; }

遍历三个角色并输出结果。

训练 3:绑定按钮权限

[ObservableProperty] [NotifyPropertyChangedFor(nameof(CanDelete))] private UserRole _currentRole; public bool CanDelete => CurrentRole == UserRole.Admin;
<Button Content="删除" IsEnabled="{Binding CanDelete}" />

第三遍切换角色,确认按钮状态变化。

每日小测

做完本页后,用这 5 题检查是否真的掌握。

1. 判断题

本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。

答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。

2. 填空题

本页主题是:角色与权限。今天至少要掌握的 3 个点是:

1. 认证和授权有什么区别 2. 角色和权限有什么区别 3. 为什么隐藏按钮不等于权限控制

答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。

3. 流程题

遇到本页相关功能时,先按什么顺序处理?

答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。

4. 找错误题

如果本页代码运行失败,第一步应该做什么?

答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。

5. 改需求题

在本页完整例子跑通后,至少改一个小需求。

可选改法:

  • 改一个字段名称。
  • 多加一个校验条件。
  • 多输出一行结果。
  • 把固定数据改成用户输入。
  • 把一次处理改成多条数据处理。

答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:认证和授权有什么区别。

上位机专项练习

操作员、工程师和管理员权限不同。权限控制要放在命令和服务层,不能只把按钮藏起来。

下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。

专项例子 1:定义角色

public enum UserRole { Operator, Engineer, Administrator }

运行结果或界面效果:

三种角色有明确名称

改动任务: 写出每种角色可执行的操作。

专项例子 2:检查修改权限

public bool CanEditConfiguration(User user) { return user.Role is UserRole.Engineer or UserRole.Administrator; }

运行结果或界面效果:

操作员不能修改配置

改动任务: 增加只有管理员能管理用户的规则。

专项例子 3:界面绑定权限

<Button Content="修改设备配置" IsVisible="{Binding CanEditConfiguration}" Command="{Binding EditConfigurationCommand}" />

运行结果或界面效果:

无权限时入口不可见

改动任务: 命令执行处也再次检查权限。

第四部分:作业完整答案

作业要求:写一个角色权限控制程序,包含 Viewer、Editor、Admin,删除商品前必须检查 Product.Delete 权限。

答案文件:Program.cs

using System; using System.Collections.Generic; using System.Linq; var repository = new ProductRepository(); var permissionService = new PermissionService(); var productService = new ProductService(repository, permissionService); var viewer = new User("Alice", Role.Viewer); var editor = new User("Bob", Role.Editor); var admin = new User("Cindy", Role.Admin); void Print(OperationResult result) { Console.WriteLine($"{result.Success} - {result.Message}"); } Print(productService.DeleteProduct(viewer, 1)); Print(productService.DeleteProduct(editor, 1)); Print(productService.DeleteProduct(admin, 1)); Print(productService.DeleteProduct(admin, 99)); Console.WriteLine($"剩余商品数量:{repository.Count}"); public enum Role { Viewer, Editor, Admin } public sealed record User(string Name, Role Role); public static class PermissionNames { public const string ProductRead = "Product.Read"; public const string ProductCreate = "Product.Create"; public const string ProductUpdate = "Product.Update"; public const string ProductDelete = "Product.Delete"; } public sealed class PermissionService { private static readonly Dictionary<Role, HashSet<string>> RolePermissions = new() { [Role.Viewer] = new HashSet<string> { PermissionNames.ProductRead }, [Role.Editor] = new HashSet<string> { PermissionNames.ProductRead, PermissionNames.ProductCreate, PermissionNames.ProductUpdate }, [Role.Admin] = new HashSet<string> { PermissionNames.ProductRead, PermissionNames.ProductCreate, PermissionNames.ProductUpdate, PermissionNames.ProductDelete } }; public bool HasPermission(User user, string permission) { return RolePermissions.TryGetValue(user.Role, out HashSet<string>? permissions) && permissions.Contains(permission); } } public sealed class ProductService { private readonly ProductRepository repository; private readonly PermissionService permissionService; public ProductService(ProductRepository repository, PermissionService permissionService) { this.repository = repository; this.permissionService = permissionService; } public OperationResult DeleteProduct(User user, int productId) { if (!permissionService.HasPermission(user, PermissionNames.ProductDelete)) { return OperationResult.Fail($"{user.Name} 没有删除权限。"); } if (!repository.Exists(productId)) { return OperationResult.Fail($"商品 {productId} 不存在。"); } repository.Delete(productId); return OperationResult.Ok($"{user.Name} 删除商品 {productId} 成功。"); } } public sealed class ProductRepository { private readonly List<Product> products = new() { new Product(1, "Keyboard"), new Product(2, "Mouse") }; public int Count => products.Count; public bool Exists(int id) { return products.Any(product => product.Id == id); } public void Delete(int id) { products.RemoveAll(product => product.Id == id); } } public sealed record Product(int Id, string Name); public sealed class OperationResult { public bool Success { get; init; } public string Message { get; init; } = ""; public static OperationResult Ok(string message) { return new OperationResult { Success = true, Message = message }; } public static OperationResult Fail(string message) { return new OperationResult { Success = false, Message = message }; } }

验收结果

运行后必须看到:

  1. Viewer 删除失败。
  2. Editor 删除失败。
  3. Admin 删除成功。
  4. 删除不存在商品失败。
  5. 最后剩余商品数量是 1。

为什么这个答案是对的

这个答案把权限控制拆成了清楚的层:

代码作用
用户User表示当前操作的人
角色Role表示用户属于哪一类
权限名PermissionNames稳定定义动作名称
权限表PermissionService判断角色是否拥有权限
业务动作ProductService.DeleteProduct删除前再次检查权限
数据层ProductRepository真正删除数据

权限控制的关键是:界面可以隐藏按钮,但服务层必须检查权限;Repository 只执行数据操作,不决定谁能操作。