Appearance
项目完整指南
面向开发者与运维人员的综合参考:简介、快速开始、核心概念、模块详解与 FAQ。
目录
1. 项目简介
1.1 什么是 Automa Agent?
Automa Agent 是一款企业级桌面自动化工具,专为企业 ERP 数据采集与流程自动化设计,主要能力包括:
- 网页数据采集:页面 DOM 与接口数据的自动化抓取
- 用户操作模拟:鼠标点击、键盘输入、表单填写
- Excel 数据处理:读取、写入与批量转换
- 可视化流程编排:拖拽式工作流设计,降低使用门槛
- 定时调度:支持 Cron 表达式的无人值守执行
- AI 工作台:多模型对话、BizApi 自动执行、钉钉/飞书通道、截图分析与自定义 OpenAI Provider
1.2 核心优势
| 特性 | 说明 |
|---|---|
| 脚本驱动 | 脚本可以直接调用网站功能,如直接请求接口 |
| 流程隔离 | 每个流程在独立的浏览器窗口中执行,互不干扰 |
| 并发执行 | 支持多个流程同时运行 |
| 可视化编排 | 拖拽式工作流设计,无需编程基础 |
| 原生能力 | 基于 Rust 的原生模块,性能优异 |
1.3 应用场景
- 电商平台数据采集(生意参谋、店铺数据等)
- ERP 系统数据同步
- 批量表单填写
- 定时报表下载
- 网页自动化测试
2. 快速开始
2.1 环境要求
- Node.js: >= 18.x
- pnpm: >= 8.x
- 操作系统: Windows 10+, macOS 10.15+, Linux
2.2 安装依赖
bash
# 克隆项目
git clone git@codeup.aliyun.com:6627798cd833774c93cc0790/erp_automa.git
# 进入目录
cd erp_automa
# 安装依赖
pnpm install
# 重建原生模块(如果需要)
npm rebuild better-sqlite3 --runtime=electron --target=30.5.12.3 开发启动
bash
# 启动开发服务器
pnpm dev2.4 构建发布
bash
# Windows
pnpm build:win
# macOS
pnpm build:mac
# Linux
pnpm build:linux3. 核心概念
3.1 工作流 (Workflow)
工作流是一系列自动化操作的集合,由节点和连线组成。
text
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 触发器 │ ──▶ │ 打开窗口 │ ──▶ │ 点击按钮 │
└─────────┘ └─────────┘ └─────────┘
│
▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 结束 │ ◀── │ 保存数据 │ ◀── │ 获取数据 │
└─────────┘ └─────────┘ └─────────┘3.2 节点 (Node)
节点是工作流的基本执行单元,每个节点完成一个特定的操作。
节点分类:
| 分类 | 说明 | 示例 |
|---|---|---|
| 触发器 | 工作流的入口 | 手动触发、定时触发 |
| 鼠标键盘 | 模拟用户输入 | 鼠标点击、键盘输入 |
| 窗口自动化 | 浏览器窗口操作 | 打开窗口、执行脚本 |
| 数据处理 | 数据转换和处理 | JSON解析、变量设置 |
| 流程控制 | 控制执行流程 | 条件判断、循环 |
| 文件操作 | 文件读写 | 读取文件、写入文件 |
| 网络请求 | HTTP 请求 | GET请求、POST请求 |
3.3 变量 (Variable)
变量用于在节点之间传递数据。
javascript
// 设置变量
await setVariable('userName', '张三');
// 使用变量(在配置中)
${userName} // 输出: 张三
// 访问对象属性
${user.name} // 访问嵌套属性
${list[0]} // 访问数组元素3.4 执行上下文 (ExecutionContext)
执行上下文包含工作流运行时的所有状态信息:
typescript
interface ExecutionContext {
workflowId: string; // 工作流 ID
workerId: string; // 执行器 ID
variables: Record<string, unknown>; // 变量存储
logs: ExecutionLog[]; // 执行日志
startTime: Date; // 开始时间
}4. 技术架构
4.1 整体架构
text
┌────────────────────────────────────────────────────────────┐
│ 渲染进程 (Renderer) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 工作流设计器 │ │ 配置面板 │ │ 节点组件 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────────┬───────────────────────────────┘
│ IPC
┌────────────────────────────┴───────────────────────────────┐
│ 预加载脚本 (Preload) │
│ contextBridge / ipcRenderer │
└────────────────────────────┬───────────────────────────────┘
│ IPC
┌────────────────────────────┴───────────────────────────────┐
│ 主进程 (Main) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 控制器层 │ │ 引擎层 │ │ Bean 层 │ │
│ │ Controllers │ │ WorkflowEngine│ │ DesktopNative│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────────┬───────────────────────────────┘
│
┌────────────────────────────┴───────────────────────────────┐
│ 原生模块 (Native) │
│ Rust: desktop-selection-native │
└────────────────────────────────────────────────────────────┘4.2 进程模型
| 进程 | 职责 | 技术 |
|---|---|---|
| 主进程 | 应用生命周期、窗口管理、工作流执行 | Node.js + Electron |
| 渲染进程 | UI 界面、工作流设计器 | Vue 3 + TypeScript |
| 预加载脚本 | 进程通信桥接、安全 API 暴露 | contextBridge |
| BrowserWindow | 自动化目标窗口 | Chromium |
4.3 IoC 容器 (Summer)
项目使用自研的 IoC 容器框架,提供依赖注入能力:
typescript
// 定义 Bean
@Bean()
export class MyService {
@Autowired()
private readonly db: Db;
@PreConstruct()
async init() {
// 初始化逻辑
}
}
// 使用 Bean
@Bean()
export class MyController {
@Autowired()
private readonly myService: MyService;
}5. 模块详解
5.1 工作流引擎 (WorkflowEngine)
位置: src/main/engine/WorkflowEngine.ts
工作流引擎负责解析和执行工作流定义。
核心方法:
typescript
class WorkflowEngine {
// 注册节点执行器
registerExecutor(executor: INodeExecutor): void;
// 执行工作流
async execute(workflow: WorkflowDefinition, input?: Record<string, unknown>): Promise<WorkflowExecutionResult>;
// 暂停执行
pause(): void;
// 继续执行
resume(): void;
// 停止执行
stop(): void;
}执行流程:
- 查找 Trigger 节点作为入口
- 初始化执行上下文
- 循环执行节点,直到没有下一个节点
- 返回执行结果
5.2 节点执行器 (BaseNodeExecutor)
位置: src/main/engine/BaseNodeExecutor.ts
所有节点执行器的基类,提供通用功能。
核心 API:
| 方法 | 说明 |
|---|---|
execute(node, context) | 执行节点(抽象方法) |
success(output?, nextNodeId?) | 创建成功结果 |
failure(error) | 创建失败结果 |
getConfig<T>(node, key, defaultValue?) | 获取节点配置 |
getVariable<T>(context, name, defaultValue?) | 获取变量 |
setVariable(context, name, value) | 设置变量 |
resolveExpression(expression, context) | 解析变量表达式 |
delay(ms) | 延时 |
5.3 原生功能 (DesktopNative)
位置: src/main/beans/DesktopNative.ts
封装 Rust 原生模块,提供桌面自动化能力。
主要功能:
typescript
class DesktopNative {
// 截图
captureScreen(rect?: Rect): ScreenshotResult;
captureRegion(rect: Rect): ScreenshotResult;
// 鼠标操作
mouseMove(x: number, y: number): void;
mouseClick(button: 'left' | 'right' | 'middle'): void;
mouseScroll(deltaX: number, deltaY: number): void;
// 键盘操作
keyboardType(text: string): void;
keyboardPress(key: string, modifiers?: string[]): void;
// 元素定位
getElementAtPosition(x: number, y: number): AXElement;
findElementBySelector(selector: string): AXElement[];
}5.4 数据库 (Db)
位置: src/main/beans/Db.ts
使用 SQLite + TypeORM 进行数据持久化。
实体列表:
| 实体 | 说明 |
|---|---|
WorkflowEntity | 工作流定义 |
WorkflowWorkerEntity | 工作流执行实例 |
WorkflowVariablesEntity | 工作流变量 |
WorkflowLogEntity | 执行日志 |
CapturedElementEntity | 捕获的元素 |
ScreenshotEntity | 截图 |
FolderEntity | 文件夹 |
5.5 定时任务 (CronManager)
位置: src/main/beans/CronManager.ts
管理定时触发的工作流。
typescript
class CronManager {
// 启动定时任务管理
async start(): Promise<void>;
// 添加定时任务
addJob(workflowId: string, cronExpression: string): void;
// 移除定时任务
removeJob(workflowId: string): void;
}6. 工作流设计
6.1 设计器界面
工作流设计器基于 Vue Flow 实现,主要组件:
| 组件 | 位置 | 说明 |
|---|---|---|
WorkflowDesigner.vue | src/renderer/pages/workflow/ | 设计器主组件 |
ConfigPanel/ | src/renderer/pages/workflow/components/ | 配置面板 |
nodes/ | src/renderer/pages/workflow/nodes/ | 节点 UI 组件 |
edges/ | src/renderer/pages/workflow/edges/ | 连线组件 |
6.2 节点配置
每个节点都有对应的配置表单,定义在 src/automa/types/node-config.ts:
typescript
export const mouseClickFormConfig: DynamicFormI = {
showSide: false,
cols: 1,
fields: [
{
label: '点击类型',
key: 'clickType',
type: 'select',
span: 1,
tooltip: '单击、双击或右键点击',
options: [
{ label: '单击', value: 'single' },
{ label: '双击', value: 'double' },
{ label: '右键', value: 'right' },
],
},
// ... 更多字段
],
};6.3 工作流定义
工作流以 JSON 格式存储:
typescript
interface WorkflowDefinition {
id: string;
name: string;
nodes: NodeDefinition[];
edges: EdgeDefinition[];
variables?: Record<string, unknown>;
}
interface NodeDefinition {
id: string;
type: NodeKey;
name: string;
position: { x: number; y: number };
config: Record<string, unknown>;
}
interface EdgeDefinition {
id: string;
source: string;
target: string;
sourceHandle?: string;
targetHandle?: string;
}7. 节点开发指南
7.1 开发步骤
开发新节点需要完成以下 10 个步骤:
步骤 1: 添加命令定义
文件: src/automa/types/command-types.ts
typescript
export const CommandCategories = [
{
key: 'custom',
label: '自定义',
icon: 'icon-custom',
commands: [
{
id: 'my-node',
name: '我的节点',
type: NodeKey.MY_NODE,
icon: 'icon-custom',
desc: '节点描述'
}
],
},
];步骤 2: 添加节点键
文件: src/automa/types/node-config.ts
typescript
export enum NodeKey {
// ... 其他节点
MY_NODE = 'my-node',
}步骤 3: 添加节点 UI 配置
文件: src/automa/types/node-config.ts
typescript
export const NodeTypesConfig: Record<NodeKey, NodeTypeConfig> = {
// ... 其他节点
[NodeKey.MY_NODE]: {
label: '我的节点',
icon: 'icon-custom',
color: '#1890ff',
category: 'custom',
},
};步骤 4: 定义表单配置
文件: src/automa/types/node-config.ts
typescript
export const myNodeFormConfig: DynamicFormI = {
showSide: false,
cols: 1,
fields: [
{
label: '参数1',
key: 'param1',
type: 'text',
span: 1,
required: true,
tooltip: '参数说明',
},
],
};步骤 5: 定义节点类型
文件: src/automa/types/node-types.ts
typescript
export interface MyNodeDefinition extends BaseNodeDefinition {
type: NodeKey.MY_NODE;
config: {
param1: string;
// ... 其他配置
};
}步骤 6: 添加到联合类型
文件: src/automa/types/node-types.ts
typescript
export type SpecificNodeDefinition =
| TriggerNodeDefinition
| MyNodeDefinition // 添加这行
// ... 其他节点
;步骤 7: 添加到映射
文件: src/automa/types/node-types.ts
typescript
export interface NodeDefinitionMap {
[NodeKey.MY_NODE]: MyNodeDefinition;
// ... 其他节点
}步骤 8: 创建执行器
文件: src/main/engine/executors/MyNodeExecutor.ts
typescript
import { BaseNodeExecutor } from '../BaseNodeExecutor';
import type { ExecutionContext, NodeExecutionResult } from '@automa/types/engine-types';
import type { MyNodeDefinition } from '@automa/types/node-types';
import { NodeKey } from '@automa/types/node-config';
export default class MyNodeExecutor extends BaseNodeExecutor {
type = NodeKey.MY_NODE;
async execute(node: MyNodeDefinition, context: ExecutionContext): Promise<NodeExecutionResult> {
try {
// 获取配置
const param1 = this.getConfig<string>(node, 'param1', '');
// 解析变量
const resolvedParam1 = this.resolveExpression(param1, context);
// 执行逻辑
const result = await this.doSomething(resolvedParam1);
// 保存结果到变量
await this.setVariable(context, 'myResult', result);
return this.success({ result });
} catch (error) {
return this.failure(`执行失败: ${error}`);
}
}
private async doSomething(param: string): Promise<string> {
// 实现具体逻辑
return `处理结果: ${param}`;
}
}步骤 9: 创建节点 UI 组件
文件: src/renderer/pages/workflow/nodes/MyNodeNode.vue
vue
<template>
<div class="my-node">
<div class="node-header">
<i class="erpfont icon-custom"></i>
<span>{{ data.name || '我的节点' }}</span>
</div>
<div class="node-content">
<div class="param-item">
<span class="label">参数1:</span>
<span class="value">{{ data.config?.param1 || '-' }}</span>
</div>
</div>
<Handle type="target" :position="Position.Left" />
<Handle type="source" :position="Position.Right" />
</div>
</template>
<script setup lang="ts">
import { Handle, Position } from '@vue-flow/core';
defineProps<{
data: {
name?: string;
config?: {
param1?: string;
};
};
}>();
</script>
<style lang="scss" scoped>
@import './node-styles.scss';
</style>步骤 10: 注册表单配置
文件: src/renderer/pages/workflow/components/ConfigPanel/ConfigPanel.vue
typescript
import { myNodeFormConfig } from '@automa/types/node-config';
// 在 switch 语句中添加
case NodeKey.MY_NODE:
return myNodeFormConfig;7.2 执行器模板
typescript
import { BaseNodeExecutor } from '../BaseNodeExecutor';
import type { ExecutionContext, NodeExecutionResult } from '@automa/types/engine-types';
import type { CustomNodeDefinition } from '@automa/types/node-types';
import { NodeKey } from '@automa/types/node-config';
export default class CustomNodeExecutor extends BaseNodeExecutor {
type = NodeKey.CUSTOM_NODE;
async execute(node: CustomNodeDefinition, context: ExecutionContext): Promise<NodeExecutionResult> {
try {
// 1. 获取配置
const config1 = this.getConfig<string>(node, 'config1', '');
// 2. 解析变量表达式
const resolved = this.resolveExpression(config1, context);
// 3. 获取上下文变量
const existingVar = await this.getVariable<string>(context, 'someVar', '');
// 4. 执行业务逻辑
const result = await this.doBusinessLogic(resolved);
// 5. 设置输出变量
await this.setVariable(context, 'outputVar', result);
// 6. 返回成功结果
return this.success({ result });
} catch (error) {
// 7. 返回失败结果
return this.failure(`执行失败: ${error}`);
}
}
}8. API 开发指南
8.1 API 架构
text
Renderer (apiService.ts)
↓ IPC
Main (Controller)
↓
Service / Bean
↓
Native / Database8.2 添加新 API
步骤 1: 定义类型
文件: src/main/controller/NativeApiTypes.ts
typescript
export interface MyApiParams {
param1: string;
param2: number;
}
export interface MyApiResult {
success: boolean;
data?: any;
error?: string;
}步骤 2: 实现 Controller 方法
文件: src/main/controller/NativeApiController.ts
typescript
@Bean()
export class NativeApiController {
@Autowired()
private readonly myService: MyService;
public myApi(params: MyApiParams): ApiResultType<MyApiResult> {
return this.wrapAsync(async () => {
const result = await this.myService.doSomething(params);
return { success: true, data: result };
});
}
}步骤 3: 渲染进程调用
typescript
import { execNativeCommand } from '@renderer/common/apiService';
const result = await execNativeCommand('myApi', {
param1: 'value1',
param2: 123,
});8.3 API 命名规范
| 类型 | 命名 | 示例 |
|---|---|---|
| 获取数据 | getXxxApi | getWorkflowApi |
| 执行操作 | xxxApi | executeWorkflowApi |
| 启动功能 | startXxxApi | startRecordingApi |
| 停止功能 | stopXxxApi | stopRecordingApi |
9. 数据库设计
9.1 实体关系
text
FolderEntity (文件夹)
│
└── WorkflowEntity (工作流)
│
├── WorkflowWorkerEntity (执行实例)
│ │
│ └── WorkflowLogEntity (执行日志)
│
├── WorkflowVariablesEntity (变量)
│
├── WorkflowCookieEntity (Cookie)
│
└── WorkflowStatisticsEntity (统计)
CapturedElementEntity (捕获元素) - 独立
ScreenshotEntity (截图) - 独立9.2 主要实体
WorkflowEntity
typescript
@Entity('workflow')
export class WorkflowEntity extends BaseEntity {
@Column() name: string;
@Column() folderId: string;
@Column('simple-json') definition: WorkflowDefinition;
@Column() enabled: boolean;
}WorkflowWorkerEntity
typescript
@Entity('workflow_worker')
export class WorkflowWorkerEntity extends BaseEntity {
@Column() workflowId: string;
@Column() status: WorkerStatus;
@Column() currentNodeId: string;
@Column('simple-json') variables: Record<string, unknown>;
@Column() startTime: Date;
@Column() endTime: Date;
@Column() error: string;
}9.3 数据库操作
typescript
// 获取 Repository
const repo = this.db.ds.getRepository(WorkflowEntity);
// 查询
const workflow = await repo.findOne({ where: { id } });
const workflows = await repo.find({ where: { folderId } });
// 保存
await repo.save(workflow);
// 删除
await repo.delete({ id });10. 常见问题
10.1 开发相关
Q: 如何调试主进程?
A: 在 VSCode 中使用 Electron 调试配置,或在代码中使用 console.log,日志会输出到终端。
Q: 如何调试渲染进程?
A: 使用 Chrome DevTools,在应用窗口中按 Ctrl+Shift+I (Windows) 或 Cmd+Option+I (macOS)。
Q: 原生模块编译失败?
A: 运行以下命令重新编译:
bash
npm rebuild better-sqlite3 --runtime=electron --target=30.5.110.2 运行相关
Q: 工作流执行失败怎么排查?
A:
- 查看执行日志(数据库
workflow_log表) - 查看系统日志(
config/logs/automa_agent.log) - 检查变量值是否正确
Q: 定时任务不执行?
A:
- 检查 Cron 表达式是否正确
- 确认工作流已启用
- 查看 CronManager 日志
Q: 浏览器窗口无法打开?
A:
- 检查 URL 是否正确
- 检查网络连接
- 查看是否有安全策略阻止
10.3 部署相关
Q: 打包后应用无法启动?
A:
- 检查原生模块是否正确打包
- 检查配置文件路径
- 查看应用日志
Q: 如何配置开机自启?
A: 应用默认配置了开机自启,可在代码中修改:
typescript
app.setLoginItemSettings({
openAtLogin: true,
openAsHidden: true,
});附录
A. 组件库文档
- @erp/biz: https://lib.fukerp.com/
- @erp/common: https://lib.fukerp.com/
B. 相关资源
C. Git 规范
bash
# 查看最近提交
git --no-pager log --oneline -20
# 查看当前分支提交
git --no-pager log $(git merge-base HEAD origin/master)..HEAD --no-merges --oneline