通过.NET平台进行机器人流程自动化RPA开发与执行引擎插图

从零到一:在.NET生态中构建你自己的RPA引擎

大家好,作为一名在自动化领域摸爬滚打多年的开发者,我经历过从录制回放工具到脚本,再到如今需要高度定制化RPA(机器人流程自动化)引擎的整个过程。市面上UiPath、Blue Prism等工具固然强大,但面对一些特殊的业务场景、复杂的遗留系统集成,或是出于成本与自主可控的考虑,我们常常需要自己动手。今天,我就和大家分享一下,如何利用我们熟悉的.NET平台,从核心原理出发,搭建一个轻量级但功能完整的RPA执行引擎。这不仅仅是一个教程,更是我过去项目中诸多“踩坑”与“填坑”经验的总结。

一、理解核心:一个RPA引擎需要什么?

在动手写代码之前,我们必须想清楚目标。一个可用的RPA引擎,至少需要三大核心模块:流程设计器(或脚本解析器)活动(Activity)库执行引擎(Runtime)。对于入门,我们可以先从控制台应用驱动一个简单的活动库开始,这能让我们快速抓住本质。

我的第一个“坑”就在这里:一开始就想做一个图形化设计器,结果在UI交互上浪费了大量时间,反而忽略了执行逻辑的健壮性。所以,我建议从定义活动模型和执行引擎开始

二、搭建基石:定义活动(Activity)抽象模型

活动是RPA的原子操作,比如“点击按钮”、“输入文本”、“读取表格”。我们首先定义一个所有活动的基类。这里,我设计了一个包含同步和异步执行方法的抽象类,因为很多UI操作需要等待。

namespace MyRPA.Core
{
    public abstract class Activity
    {
        public string Id { get; set; } = Guid.NewGuid().ToString();
        public string Name { get; set; }

        // 核心执行方法
        public abstract Task ExecuteAsync(ActivityContext context);

        // 一个简单的上下文,用于在活动间传递数据
        public class ActivityContext
        {
            public Dictionary Variables { get; } = new Dictionary();
            public CancellationToken CancellationToken { get; set; }
            // 可以添加日志记录器、错误处理器等
            public ILogger Logger { get; set; }
        }
    }
}

这个ActivityContext非常重要,它相当于整个流程执行的“记忆体”和“传令官”。我曾在早期版本中让活动直接访问全局静态变量,结果在并行执行多个流程时发生了灾难性的数据混乱。

三、实现关键活动:以UI自动化为核心

RPA最常打交道的就是桌面UI。在.NET中,我们可以使用 System.Windows.Automation 命名空间(UIAutomation)或第三方库如 FlaUI。我个人更推荐 FlaUI,它对UIAutomation进行了非常友好的封装。下面实现一个“输入文本”活动。

首先,通过NuGet安装 FlaUI.UIA3

using FlaUI.UIA3;
using MyRPA.Core;

namespace MyRPA.Activities.UIA
{
    public class TypeIntoActivity : Activity
    {
        // 属性:目标元素选择器、要输入的文本
        public string ElementSelector { get; set; } // 例如:“name:txtUserName”
        public string Text { get; set; }

        public override async Task ExecuteAsync(ActivityContext context)
        {
            context.Logger?.LogInformation($"活动【{Name}】开始:向'{ElementSelector}'输入文本。");
            
            // 解析选择器(这里简化处理,实际需要完整的解析逻辑)
            var (by, value) = ParseSelector(ElementSelector);
            
            using (var automation = new UIA3Automation())
            {
                var desktop = automation.GetDesktop();
                // 查找元素(此处为简化示例,真实场景需处理查找失败、超时)
                var element = desktop.FindFirstDescendant(cf => cf.ByAutomationId(value)); 

                if (element != null)
                {
                    element.Patterns.Value.Pattern.SetValue(Text); // 使用Value模式输入
                    await Task.Delay(100); // 短暂延迟,模拟人工操作间隔
                    context.Logger?.LogInformation($"输入成功。");
                }
                else
                {
                    throw new InvalidOperationException($"未找到元素:{ElementSelector}");
                }
            }
        }

        private (string by, string value) ParseSelector(string selector)
        {
            // 简易解析,例如 “id:submitBtn” -> (“id”, “submitBtn”)
            var parts = selector.Split(new[] { ':' }, 2);
            return parts.Length == 2 ? (parts[0], parts[1]) : (“name”, selector);
        }
    }
}

踩坑提示:UI自动化最大的敌人是“时机”。元素可能还没加载出来,或者窗口失去焦点。所以,一个健壮的活动必须包含重试机制和超时控制。上面的示例省略了这些,但在生产代码中,你需要用 Retry.WhileNull(()=> FindElement(...), timeout) 这样的逻辑包裹查找过程。

四、构建引擎核心:流程执行器

有了活动,我们需要一个能够按顺序执行它们的引擎。这个引擎还需要处理异常、记录日志、支持条件分支和循环(这需要更复杂的流程结构,如 SequenceActivity, IfActivity)。我们先实现一个最简单的线性执行器。

namespace MyRPA.Core
{
    public class WorkflowEngine
    {
        private readonly List _activities;
        private readonly Activity.ActivityContext _context;

        public WorkflowEngine(List activities, ILogger logger)
        {
            _activities = activities;
            _context = new Activity.ActivityContext { Logger = logger };
        }

        public async Task ExecuteAsync(CancellationToken cancellationToken = default)
        {
            _context.CancellationToken = cancellationToken;
            _context.Logger.LogInformation($"工作流开始执行,共{_activities.Count}个活动。");

            foreach (var activity in _activities)
            {
                if (_context.CancellationToken.IsCancellationRequested)
                {
                    _context.Logger.LogWarning("工作流被用户取消。");
                    break;
                }

                try
                {
                    _context.Logger.LogInformation($">> 执行活动:{activity.Name}");
                    await activity.ExecuteAsync(_context);
                }
                catch (Exception ex)
                {
                    _context.Logger.LogError(ex, $"执行活动【{activity.Name}】时发生错误。");
                    // 策略:可以抛出异常终止流程,或记录后继续执行下一个
                    // 这里我们选择抛出,让调用者决定如何处理
                    throw new WorkflowExecutionException($"活动'{activity.Name}'执行失败。", ex);
                }
            }
            _context.Logger.LogInformation("工作流执行完毕。");
        }
    }

    public class WorkflowExecutionException : Exception
    {
        public WorkflowExecutionException(string message, Exception inner) : base(message, inner) { }
    }
}

这个引擎虽然简单,但已经具备了执行、异常处理和取消响应的骨架。在我的项目中,我在此基础上增加了活动执行前后的钩子(Hooks),用于统一的日志、性能监控和截图,这对后期调试至关重要。

五、组装与运行:一个完整的控制台示例

现在,让我们把上面所有的部分组合起来,实现一个自动打开记事本并输入“Hello, RPA!”的流程。

using Microsoft.Extensions.Logging;
using MyRPA.Activities.UIA;
using MyRPA.Core;
using System.Diagnostics;

class Program
{
    static async Task Main(string[] args)
    {
        // 使用内置日志
        using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
        var logger = loggerFactory.CreateLogger();

        // 1. 启动记事本
        Process.Start("notepad.exe");
        await Task.Delay(1000); // 等待程序启动

        // 2. 构建活动列表(这就是我们的“流程”)
        var activities = new List
        {
            new TypeIntoActivity 
            { 
                Name = “在记事本中输入问候语”, 
                ElementSelector = “name:文本编辑器”, // 记事本主编辑区的自动化ID
                Text = “Hello, RPA from .NET!” 
            }
        };

        // 3. 创建引擎并执行
        var engine = new WorkflowEngine(activities, logger);
        try
        {
            await engine.ExecuteAsync();
            Console.WriteLine(“流程执行成功!”);
        }
        catch (WorkflowExecutionException ex)
        {
            Console.WriteLine($“流程执行失败: {ex.Message}”);
        }
    }
}

运行这个程序,你会看到控制台输出日志,并且记事本中自动出现了我们设定的文本。虽然简陋,但一个RPA引擎的核心闭环已经完成了!

六、展望与进阶

走到这一步,你已经拥有了一个可工作的内核。但要把它变成一个真正实用的RPA平台,还有很长的路要走,这也是最有趣的部分:

  1. 丰富活动库:实现点击、读取、Excel/数据库操作、HTTP请求、OCR识别等。
  2. 引入流程控制:实现 If/Else, While, ForEach 等容器活动,这需要设计更复杂的活动树结构。
  3. 设计流程定义语言:用JSON或XML来描述流程,让引擎可以解析并动态创建活动树,从而实现与设计器的解耦。
  4. 构建设计器:可以考虑使用WPF或Blazor,通过拖拽方式生成上一步的流程定义文件。
  5. 增强鲁棒性:全局异常处理、流程快照与恢复、更完善的元素查找等待策略。
  6. 加入调度与监控:开发一个Web管理后台,用于部署流程、设置定时任务、查看执行日志和报表。

回顾整个历程,在.NET上构建RPA引擎的优势非常明显:强大的生态系统(FlaUI, Tesseract for OCR, ClosedXML for Excel)、出色的性能以及你能完全掌控每一行代码。挑战则在于对Windows UI底层细节的深入理解和异常处理复杂性的把控。

希望这篇从实战出发的指南,能为你打开一扇门。从这个小内核开始,逐步扩展,你完全有能力打造出一个贴合自身业务、独具特色的自动化工具。编码愉快!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。