C#插件开发实战:如何优雅地实现单据审核权限控制(附完整代码)

张开发
2026/4/10 20:39:12 15 分钟阅读

分享文章

C#插件开发实战:如何优雅地实现单据审核权限控制(附完整代码)
C#插件开发实战如何优雅地实现单据审核权限控制在企业级应用开发中单据审核流程的权限控制是一个常见但至关重要的需求。想象一下这样的场景财务部门的应付单需要特定人员审核采购订单需要部门主管审批而销售订单可能需要区域经理签字。如何在不修改核心业务代码的情况下通过插件机制实现灵活、可扩展的权限控制这正是我们今天要探讨的核心问题。对于C#开发者而言插件开发不仅是一种技术实现更是一种架构艺术。好的权限控制插件应该像乐高积木一样可以灵活组合、即插即用同时保持代码的整洁和可维护性。本文将带你从实际业务需求出发通过三个渐进式的实现方案最终构建一个既优雅又实用的权限控制体系。1. 权限控制基础理解插件架构与校验机制在深入代码之前我们需要明确几个关键概念。插件架构的本质是将应用程序的核心功能与可扩展功能分离通过定义良好的接口实现松耦合。而在单据审核场景中校验器Validator模式特别适合处理权限验证这类横切关注点。1.1 插件系统的基本组成一个典型的C#插件系统包含以下核心组件宿主应用程序提供基础运行环境和服务容器插件接口定义插件必须实现的契约插件实现具体的业务逻辑实现插件加载器负责发现、加载和管理插件生命周期// 一个简单的插件接口示例 public interface IValidationPlugin { bool Validate(Order order, out string errorMessage); int Priority { get; } // 用于控制插件执行顺序 }1.2 校验器的设计模式选择针对单据审核场景我们通常会采用以下几种设计模式责任链模式将多个校验器串联起来每个校验器决定是否处理请求或传递给下一个策略模式根据不同单据类型动态选择校验策略装饰器模式在不修改原有校验逻辑基础上添加权限控制// 责任链模式示例 public abstract class ApproveValidator { protected ApproveValidator _nextValidator; public void SetNext(ApproveValidator validator) { _nextValidator validator; } public abstract ValidationResult Validate(Invoice invoice); }2. 实现方案对比从简单到复杂的三种方式让我们通过实际代码示例比较三种不同复杂度的实现方案分析各自的适用场景和优缺点。2.1 方案一硬编码权限检查这是最直接但也最不灵活的方式适合快速验证概念或简单场景。public class BasicApprovalPlugin { public void ValidateApproval(Invoice invoice) { var currentUser GetCurrentUser(); if(invoice.Type InvoiceType.Purchase currentUser.Department ! Department.Purchasing) { throw new InvalidOperationException( $采购单必须由采购部门人员审核当前用户{currentUser.Name}); } // 其他硬编码规则... } }优点实现简单无需复杂架构代码直观易于理解缺点修改权限规则需要重新编译难以应对多变的业务需求代码重复度高维护困难2.2 方案二基于配置的权限控制通过外部配置如数据库或JSON文件定义权限规则提高灵活性。public class ConfigurableApprovalPlugin { private readonly ListApprovalRule _rules; public ConfigurableApprovalPlugin(IConfiguration config) { _rules config.GetSection(ApprovalRules) .GetListApprovalRule(); } public ValidationResult Validate(Invoice invoice) { var applicableRules _rules.Where(r r.DocumentType invoice.Type r.MinAmount invoice.TotalAmount r.MaxAmount invoice.TotalAmount); foreach(var rule in applicableRules) { if(!rule.AllowedRoles.Contains(CurrentUser.Role)) { return ValidationResult.Fail( $单据金额{invoice.TotalAmount}需要{rule.RequiredRole}角色审核); } } return ValidationResult.Success(); } }配套的JSON配置示例{ ApprovalRules: [ { DocumentType: Purchase, MinAmount: 10000, MaxAmount: 50000, AllowedRoles: [PurchasingManager, FinanceDirector] } ] }优点规则可配置无需重新编译支持更复杂的条件逻辑便于实现多环境差异化配置缺点配置复杂时难以维护缺乏编译时类型检查动态性仍然有限2.3 方案三基于校验器的插件化架构这是我们推荐的最终方案结合了插件架构的灵活性和强类型语言的优势。// 定义基础校验器抽象类 public abstract class InvoiceValidator { public abstract bool IsApplicable(Invoice invoice); public abstract ValidationResult Validate(Invoice invoice); protected User CurrentUser GetCurrentUser(); } // 实现具体的权限校验器 public class DepartmentApprovalValidator : InvoiceValidator { public override bool IsApplicable(Invoice invoice) { return invoice.Type InvoiceType.Purchase; } public override ValidationResult Validate(Invoice invoice) { if(CurrentUser.Department ! Department.Purchasing) { return ValidationResult.Fail( $采购单必须由采购部门人员审核当前用户{CurrentUser.Name}); } return ValidationResult.Success(); } } // 校验器执行引擎 public class ValidationEngine { private readonly IEnumerableInvoiceValidator _validators; public ValidationEngine(IEnumerableInvoiceValidator validators) { _validators validators; } public ValidationResult Validate(Invoice invoice) { var errors new Liststring(); foreach(var validator in _validators .Where(v v.IsApplicable(invoice))) { var result validator.Validate(invoice); if(!result.IsValid) { errors.Add(result.ErrorMessage); } } return errors.Any() ? ValidationResult.Fail(string.Join(Environment.NewLine, errors)) : ValidationResult.Success(); } }优点高度模块化每个校验器职责单一通过依赖注入轻松扩展新规则支持运行时动态加载校验器强类型检查减少运行时错误易于单元测试缺点初始架构复杂度较高需要一定的设计模式知识3. 高级技巧提升权限控制的灵活性与用户体验构建了基础架构后我们可以进一步优化权限控制系统的灵活性和用户体验。3.1 动态权限规则引擎对于需要频繁变更规则的场景可以引入规则引擎// 使用RulesEngine NuGet包示例 public class DynamicRulesValidator : InvoiceValidator { private readonly RulesEngine.RulesEngine _engine; public DynamicRulesValidator() { var workflow new WorkflowRules { WorkflowName InvoiceApproval, Rules new ListRule { new Rule { RuleName HighValuePurchase, Expression input.Amount 10000 user.Department Finance, ErrorMessage 高价值采购单需要财务部门审核 } } }; _engine new RulesEngine.RulesEngine(new[] { workflow }); } public override ValidationResult Validate(Invoice invoice) { var input new RuleParameter(input, invoice); var user new RuleParameter(user, CurrentUser); var results _engine.ExecuteAllRulesAsync( InvoiceApproval, input, user).Result; var errors results.Where(r !r.IsSuccess) .Select(r r.ErrorMessage); return errors.Any() ? ValidationResult.Fail(string.Join(, , errors)) : ValidationResult.Success(); } }3.2 友好的错误提示与批量验证改善用户体验的关键是提供清晰、具体的错误信息并支持批量处理public class ValidationError { public string FieldName { get; set; } public string ErrorCode { get; set; } public string Message { get; set; } public ErrorLevel Level { get; set; } } public enum ErrorLevel { Warning, Error, Critical } public class BatchValidator { public IReadOnlyCollectionValidationError ValidateBatch( IEnumerableInvoice invoices) { var errors new ListValidationError(); foreach(var invoice in invoices) { var result _validationEngine.Validate(invoice); if(!result.IsValid) { errors.Add(new ValidationError { FieldName Global, ErrorCode PERMISSION_DENIED, Message result.ErrorMessage, Level ErrorLevel.Error, RelatedDocumentId invoice.Id }); } } return errors; } }3.3 权限缓存与性能优化频繁的权限检查可能成为性能瓶颈合理的缓存策略至关重要public class CachedPermissionService { private readonly MemoryCache _cache new MemoryCache(new MemoryCacheOptions()); private readonly TimeSpan _cacheDuration TimeSpan.FromMinutes(5); public bool HasPermission(string userId, string permissionKey) { var cacheKey ${userId}_{permissionKey}; if(_cache.TryGetValue(cacheKey, out bool hasPermission)) { return hasPermission; } // 实际权限检查逻辑 hasPermission CheckPermissionInDatabase(userId, permissionKey); _cache.Set(cacheKey, hasPermission, _cacheDuration); return hasPermission; } }4. 实战完整插件实现与集成让我们将这些概念整合成一个完整的、可立即投入使用的解决方案。4.1 定义核心接口与模型首先定义我们的核心契约public interface IApprovalPlugin { string PluginName { get; } TaskValidationResult ValidateAsync(Invoice invoice); bool AppliesTo(InvoiceType invoiceType); } public class ValidationResult { public bool IsValid { get; } public string ErrorMessage { get; } public string ErrorCode { get; } private ValidationResult(bool isValid, string errorMessage, string errorCode) { IsValid isValid; ErrorMessage errorMessage; ErrorCode errorCode; } public static ValidationResult Success() new ValidationResult(true, null, null); public static ValidationResult Fail(string message, string code VALIDATION_ERROR) new ValidationResult(false, message, code); }4.2 实现具体权限插件然后实现几个具体的权限校验插件// 部门权限插件 public class DepartmentApprovalPlugin : IApprovalPlugin { public string PluginName DepartmentApproval; public bool AppliesTo(InvoiceType invoiceType) { return invoiceType InvoiceType.Purchase; } public async TaskValidationResult ValidateAsync(Invoice invoice) { var user await GetCurrentUserAsync(); if(user.Department ! Purchasing) { return ValidationResult.Fail( $当前用户{user.Name}无权审核采购单, DEPARTMENT_MISMATCH); } return ValidationResult.Success(); } } // 金额阈值插件 public class AmountThresholdPlugin : IApprovalPlugin { private readonly decimal _threshold; public AmountThresholdPlugin(decimal threshold) { _threshold threshold; } public string PluginName AmountThreshold; public bool AppliesTo(InvoiceType invoiceType) true; public async TaskValidationResult ValidateAsync(Invoice invoice) { if(invoice.TotalAmount _threshold) { var user await GetCurrentUserAsync(); if(!user.IsSeniorManager) { return ValidationResult.Fail( $金额超过{_threshold}的单据需要高级经理审核, AMOUNT_THRESHOLD_EXCEEDED); } } return ValidationResult.Success(); } }4.3 插件加载与执行引擎实现一个智能的插件执行引擎public class ApprovalPluginEngine { private readonly IEnumerableIApprovalPlugin _plugins; private readonly ILoggerApprovalPluginEngine _logger; public ApprovalPluginEngine( IEnumerableIApprovalPlugin plugins, ILoggerApprovalPluginEngine logger) { _plugins plugins; _logger logger; } public async TaskValidationResult ValidateInvoiceAsync(Invoice invoice) { var errors new ListValidationResult(); foreach(var plugin in _plugins.Where(p p.AppliesTo(invoice.Type))) { try { var result await plugin.ValidateAsync(invoice); if(!result.IsValid) { errors.Add(result); _logger.LogWarning( $插件{plugin.PluginName}验证失败: {result.ErrorMessage}); } } catch(Exception ex) { _logger.LogError(ex, $执行插件{plugin.PluginName}时发生异常); errors.Add(ValidationResult.Fail( $系统错误: {ex.Message}, PLUGIN_ERROR)); } } if(!errors.Any()) return ValidationResult.Success(); var errorMessages errors.Select(e e.ErrorMessage); return ValidationResult.Fail( string.Join(Environment.NewLine, errorMessages), MULTIPLE_VALIDATION_ERRORS); } }4.4 依赖注入配置最后在ASP.NET Core中配置我们的插件系统public static class ApprovalPluginExtensions { public static IServiceCollection AddApprovalPlugins(this IServiceCollection services) { // 自动发现并注册所有插件 var pluginTypes Assembly.GetExecutingAssembly() .GetTypes() .Where(t typeof(IApprovalPlugin).IsAssignableFrom(t) !t.IsAbstract); foreach(var type in pluginTypes) { services.AddTransient(typeof(IApprovalPlugin), type); } // 注册插件引擎 services.AddTransientApprovalPluginEngine(); return services; } }在Startup.cs中使用public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddApprovalPlugins(); }5. 测试策略与调试技巧任何优秀的插件系统都需要完善的测试保障。让我们探讨如何有效测试权限控制插件。5.1 单元测试插件逻辑为单个插件编写隔离测试[TestClass] public class DepartmentApprovalPluginTests { [TestMethod] public async Task ValidateAsync_ShouldFail_WhenUserNotInPurchasing() { // 准备 var plugin new DepartmentApprovalPlugin(); var invoice new Invoice { Type InvoiceType.Purchase }; var mockUserService new MockIUserService(); mockUserService.Setup(x x.GetCurrentUserAsync()) .ReturnsAsync(new User { Name Test, Department Finance }); // 执行 var result await plugin.ValidateAsync(invoice); // 断言 Assert.IsFalse(result.IsValid); Assert.AreEqual(DEPARTMENT_MISMATCH, result.ErrorCode); } }5.2 集成测试插件引擎测试多个插件的交互[TestClass] public class ApprovalPluginEngineTests { [TestMethod] public async Task ValidateInvoiceAsync_ShouldCombineMultipleErrors() { // 准备 var plugins new ListIApprovalPlugin { new DepartmentApprovalPlugin(), new AmountThresholdPlugin(10000m) }; var engine new ApprovalPluginEngine( plugins, Mock.OfILoggerApprovalPluginEngine()); var invoice new Invoice { Type InvoiceType.Purchase, TotalAmount 15000 }; // 执行 var result await engine.ValidateInvoiceAsync(invoice); // 断言 Assert.IsFalse(result.IsValid); StringAssert.Contains(result.ErrorMessage, 无权审核); StringAssert.Contains(result.ErrorMessage, 金额超过); } }5.3 调试技巧与日志记录当插件系统出现问题时有效的日志记录至关重要public class DiagnosticApprovalPlugin : IApprovalPlugin { private readonly IApprovalPlugin _inner; private readonly ILogger _logger; public DiagnosticApprovalPlugin( IApprovalPlugin inner, ILogger logger) { _inner inner; _logger logger; } public string PluginName _inner.PluginName; public bool AppliesTo(InvoiceType invoiceType) _inner.AppliesTo(invoiceType); public async TaskValidationResult ValidateAsync(Invoice invoice) { _logger.LogInformation($开始执行插件{PluginName}); var stopwatch Stopwatch.StartNew(); try { var result await _inner.ValidateAsync(invoice); _logger.LogInformation($插件{PluginName}执行完成耗时{stopwatch.ElapsedMilliseconds}ms结果: {result.IsValid}); return result; } catch(Exception ex) { _logger.LogError(ex, $插件{PluginName}执行失败); throw; } } }6. 性能优化与生产环境考量将插件系统投入生产环境前还需要考虑以下关键因素。6.1 插件加载优化避免每次请求都重新加载所有插件public class CachedPluginProvider : IApprovalPluginProvider { private readonly IReadOnlyCollectionIApprovalPlugin _plugins; public CachedPluginProvider(IEnumerableIApprovalPlugin plugins) { _plugins plugins.ToList().AsReadOnly(); } public IReadOnlyCollectionIApprovalPlugin GetPlugins() _plugins; }6.2 异步处理与并行执行对于独立的校验规则可以考虑并行执行public async TaskValidationResult ValidateInParallelAsync(Invoice invoice) { var applicablePlugins _plugins.Where(p p.AppliesTo(invoice.Type)).ToList(); var validationTasks applicablePlugins .Select(p SafeValidateAsync(p, invoice)) .ToList(); var results await Task.WhenAll(validationTasks); var errors results.Where(r !r.IsValid).ToList(); return errors.Any() ? ValidationResult.Fail(string.Join(Environment.NewLine, errors.Select(e e.ErrorMessage))) : ValidationResult.Success(); } private async TaskValidationResult SafeValidateAsync( IApprovalPlugin plugin, Invoice invoice) { try { return await plugin.ValidateAsync(invoice); } catch(Exception ex) { _logger.LogError(ex, $插件{plugin.PluginName}执行失败); return ValidationResult.Fail($验证系统错误: {ex.Message}); } }6.3 插件热重载实现运行时插件更新而不重启应用public class HotReloadPluginProvider : IApprovalPluginProvider, IDisposable { private readonly FileSystemWatcher _watcher; private readonly string _pluginsDirectory; private IReadOnlyCollectionIApprovalPlugin _plugins; public HotReloadPluginProvider(string pluginsDirectory) { _pluginsDirectory pluginsDirectory; _watcher new FileSystemWatcher(pluginsDirectory, *.dll); _watcher.Changed OnPluginChanged; _watcher.EnableRaisingEvents true; LoadPlugins(); } private void OnPluginChanged(object sender, FileSystemEventArgs e) { Thread.Sleep(500); // 等待文件写入完成 LoadPlugins(); } private void LoadPlugins() { var loader new PluginLoader(); _plugins loader.LoadPlugins(_pluginsDirectory); } public IReadOnlyCollectionIApprovalPlugin GetPlugins() _plugins; }7. 安全最佳实践权限控制系统本身也需要严格的安全防护。7.1 插件代码安全确保加载的插件代码可信public class SandboxedPluginLoader { public IApprovalPlugin LoadPlugin(string assemblyPath) { var setup new AppDomainSetup { ApplicationBase AppDomain.CurrentDomain.SetupInformation.ApplicationBase }; var domain AppDomain.CreateDomain( PluginDomain, null, setup); try { var loader (PluginProxy)domain.CreateInstanceAndUnwrap( typeof(PluginProxy).Assembly.FullName, typeof(PluginProxy).FullName); return loader.LoadPlugin(assemblyPath); } finally { AppDomain.Unload(domain); } } } [Serializable] public class PluginProxy : MarshalByRefObject { public IApprovalPlugin LoadPlugin(string assemblyPath) { var assembly Assembly.LoadFrom(assemblyPath); var pluginType assembly.GetTypes() .FirstOrDefault(t typeof(IApprovalPlugin).IsAssignableFrom(t)); if(pluginType null) throw new InvalidOperationException(未找到插件实现); return (IApprovalPlugin)Activator.CreateInstance(pluginType); } }7.2 权限规则最小化遵循最小权限原则设计校验规则public class LeastPrivilegeValidator : IApprovalPlugin { private readonly IRoleService _roleService; public LeastPrivilegeValidator(IRoleService roleService) { _roleService roleService; } public async TaskValidationResult ValidateAsync(Invoice invoice) { var requiredRole await _roleService.GetMinimumRequiredRoleAsync(invoice); var userRoles await _roleService.GetUserRolesAsync(CurrentUser.Id); if(!userRoles.Contains(requiredRole)) { return ValidationResult.Fail( $需要{requiredRole}角色才能审核此单据, INSUFFICIENT_PRIVILEGE); } return ValidationResult.Success(); } }7.3 审计日志记录所有权限决策以便后续审计public class AuditingApprovalPlugin : IApprovalPlugin { private readonly IApprovalPlugin _inner; private readonly IAuditLogger _auditLogger; public AuditingApprovalPlugin( IApprovalPlugin inner, IAuditLogger auditLogger) { _inner inner; _auditLogger auditLogger; } public async TaskValidationResult ValidateAsync(Invoice invoice) { var result await _inner.ValidateAsync(invoice); await _auditLogger.LogAsync(new AuditEntry { Timestamp DateTime.UtcNow, UserId CurrentUser.Id, InvoiceId invoice.Id, Action VALIDATE, IsAllowed result.IsValid, Reason result.IsValid ? null : result.ErrorMessage }); return result; } }

更多文章