# EasyJob
**Repository Path**: duanzt/EasyJob
## Basic Information
- **Project Name**: EasyJob
- **Description**: EasyJob 让所有的Job都变得简单。
多种任务类型 — SQL 执行任务、ETL 数据同步、SQL 监控通知、飞书表格同步
可视化工作流引擎 — 基于 DAG 的拖拽式工作流设计器,支持 10 种节点类型(数据源/SQL执行/HTTP/C#/JavaScript/条件分支/循环/分片查询/通知/AI),支持分支跳过、级联跳过、多入边合并、失败策略(Continue/Abort/Fallbac
- **Primary Language**: C#
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 7
- **Created**: 2026-10-12
- **Last Updated**: 2026-10-12
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
[English](README.en.md) | 中文
# EasyJob
**基于 ASP.NET Core + Vue.js 的分布式任务调度与 ETL 数据同步平台**
[](https://dotnet.microsoft.com/)
[](https://vuejs.org/)
[](https://www.typescriptlang.org/)
[](https://vitejs.dev/)
[](https://element-plus.org/)
[](https://www.quartz-scheduler.net/)
[](LICENSE)
---
## ✨ 功能特性
- **多种任务类型** — SQL 执行任务、ETL 数据同步、SQL 监控通知、飞书表格同步、HTTP 接口任务
- **可视化工作流引擎** — 基于 DAG 的拖拽式工作流设计器,支持 11 种节点类型(数据源/SQL执行/HTTP/C#/JavaScript/条件分支/循环/分片查询/通知/AI/结果输出),支持分支跳过、级联跳过、多入边合并、失败策略(Continue/Abort/Fallback)
- **AI 节点集成** — 工作流中可直接调用 AI 大模型(OpenAI/Azure/Ollama/DeepSeek/通义千问/智谱/OneAPI/自定义),8 种预设配置
- **灵活的定时调度** — 基于 Quartz.NET,支持 Cron 表达式自定义调度,线程池并发可配(默认 50)
- **三种 ETL 同步模式** — 全量同步、增量同步、聚合同步
- **多数据库支持** — SQL Server、MySQL、PostgreSQL、SQLite、Oracle
- **多渠道通知** — 邮件通知、飞书通知、钉钉通知、企业微信通知
- **任务重试机制** — 支持任务超时设置和失败自动重试
- **分布式部署高可用** — 多副本部署下保证“同一任务同一时刻只被一台机器执行、且每个触发窗口恰好执行一次”(详见下文[分布式部署与高可用](#-分布式部署与高可用))
- **安全防护** — SQL 安全校验、C#/JS 代码节点沙箱隔离、登录限流防暴力破解(5 次/分钟/IP)、审计日志全操作可追溯
- **RBAC 权限控制** — 基于角色的访问控制,支持用户组管理
- **Windows 服务** — 原生支持以 Windows Service 方式运行
---






## 🛠 技术栈
### 后端
| 技术 | 版本 | 说明 |
|------|------|------|
| ASP.NET Core | 10.0 | Web 框架 |
| Entity Framework Core | 10.0 | ORM(多数据库支持) |
| Dapper | 2.1 | 高性能数据查询 |
| Quartz.NET | 4.4 | 任务调度引擎(支持集群模式) |
| StackExchange.Redis | 3.2 | Redis 分布式锁客户端 |
| JWT | — | 身份认证 |
| BCrypt | 4.2 | 密码加密 |
| Serilog | 4.3 | 结构化日志 |
| MailKit | 4.16 | 邮件发送 |
| FeishuNetSdk | 4.1 | 飞书集成 |
| ClosedXML | 0.105 | Excel 导出 |
| Swashbuckle | 10.3 | Swagger UI(OpenAPI 文档由 .NET 10 内置生成) |
### 前端
| 技术 | 版本 | 说明 |
|------|------|------|
| Vue.js | 3.5 | 前端框架 |
| TypeScript | 5.8 | 类型安全 |
| Vite | 6.3 | 构建工具 |
| Element Plus | 2.9 | UI 组件库 |
| Pinia | 3.0 | 状态管理 |
| Vue Router | 4.5 | 路由管理 |
| Axios | 1.9 | HTTP 请求 |
| Monaco Editor | 0.52 | SQL 编辑器 |
| ECharts | 6.0 | 图表可视化 |
### 数据库
SQL Server | MySQL | PostgreSQL | SQLite(默认,零配置) | Oracle
---
## 🔧 工作流引擎
EasyJob 内置一套基于 DAG(有向无环图)的可视化工作流引擎,支持拖拽式编排复杂的数据处理流水线,节点间通过 `VariablePool` 自动传递数据。
### 节点类型
| 节点 | 类型字符串 | 用途 | 输出契约 |
|------|-----------|------|----------|
| 数据源查询 | `DataSource` | 执行 SELECT 查询返回数据行 | `List>` |
| SQL 执行 | `SqlExecute` | 执行 INSERT/UPDATE/DELETE,返回影响行数 | `{ affectedRows }` |
| HTTP 请求 | `Http` | 调用外部 API,支持 GET/POST/PUT/DELETE 等 | `{ statusCode, body, headers }` |
| C# 代码 | `CSharpCode` | 用 Roslyn 执行 C# 脚本,支持 NuGet 包引用 | 任意(脚本返回值) |
| JavaScript 代码 | `JavaScript` | 用 Jint 执行 JS 脚本(沙箱) | 任意(run 函数返回值) |
| 条件分支 | `Condition` | 按 cases 顺序匹配,首个命中决定 Branch | `{ branch, matchedCase, field, value }` |
| 循环展开 | `Loop` | 将上游 List 展开为可迭代的 records 输出 | `{ count, records, itemVariableName }` |
| 分片查询 | `ShardingDataSource` | 支持 Modulo/Time/Database 三种分片模式 | `List>` |
| 通知发送 | `Notification` | 飞书/钉钉/企业微信/邮件多渠道并发 | `{ sent, channels[] }` |
| AI 调用 | `AI` | 调用大模型生成文本,支持 8 种预设 | `{ response }` |
| 结果输出 | `Output` | return / database / file 三种输出模式 | 取决于 mode |
### 数据流与变量插值
- **节点输出**:每个节点执行成功后,其 `Output` 写入 `VariablePool`,下游节点可按 `nodeId` 引用
- **模板插值**:配置中支持 `{{nodeId.fieldName}}` 语法,引擎自动从 VariablePool 取值并替换
- **工作流输入**:执行工作流时可通过 `InputJson` 传参,在节点配置中用 `input.fieldName` 引用(如 `input.branchSelector`)
- **C# 节点 input**:单上游 → `input` 即上游输出;多上游 → `input` 是 `Dictionary`
- **JS 节点 input**:始终 `input.{上游nodeId}.xxx` 访问(更直观的字典访问)
### 分支与跳过机制
- **Condition 节点**:按 cases 数组顺序匹配,首个命中的 case.label 作为返回的 Branch;都不匹配走 `defaultBranch`
- **边分支绑定**:Condition 节点的出边可设置 `branch` 字段,仅当节点实际返回的 Branch 与边的 branch 一致时该边激活
- **级联跳过**:被跳过的节点不会写入 `VariablePool`,其无条件下游(branch=null 的入边)也会因上游无输出而级联跳过,避免空输入执行
- **多入边合并**:节点有多个入边时,只要任意一条入边激活即可执行;`final_out` 类合并节点天然支持三分支汇合
- **失败策略**:每个节点可配置 `FailureStrategy`:
- `Abort`(默认)— 节点失败立即终止工作流
- `Continue` — 跳过失败节点,下游不含其输出
- `Fallback` — 注入 `{ error }` 作为输出,按 `FallbackBranch` 走指定分支
### 安全保障
- **SQL 安全校验**:所有 SQL 节点强制过 `SqlSecurityValidator`,禁止 DDL(CREATE/DROP/ALTER/TRUNCATE),SELECT 强制只读
- **C# 沙箱**:C# 节点通过 `AssemblyLoadContext` 隔离 NuGet 包版本,超时控制,使用 `InvariantCulture` 避免中文乱码
- **JS 沙箱**:Jint 解释器无文件/网络 API,限制递归深度 1000,超时控制
- **标识符校验**:表名/列名严格正则校验,防 SQL 注入
---
## 🌐 分布式部署与高可用
多副本部署(如 50 台服务器跑同一个定时任务)时,EasyJob 通过**四层机制**保证任务"不重复执行、且必须执行一次":
| 防线 | 机制 | 拦截的异常场景 |
|------|------|----------------|
| ① 分布式锁互斥 | Redis `SET NX EX` 或数据库主键锁(二选一,`DistributedLock:Provider` 配置) | 多节点同一时刻并发触发 |
| ② 锁持有者校验(CAS) | 续期/释放必须校验持有者身份(Redis Lua 原子脚本 / SQL 条件更新) | 长任务锁过期易主后误删他人锁,引发三节点级联并发 |
| ③ 心跳中止 | 锁续期连续 3 次失败即主动中止本次执行,交由重试机制补跑 | 锁存储抖动/网络分区期间防止双节点同时执行 |
| ④ 同窗口幂等复查 | 以 Quartz 触发时间为基准,5 秒容差内已执行过则跳过 | 毫秒级快任务释放锁后,同窗口延迟触发的节点串行重复执行 |
节点重启场景同样安全:启动时只回收**已过期**的锁,绝不误删其他节点正在持有的有效锁;调度注册采用幂等跳过,50 节点同时启动互不冲突。
### 可选:Quartz 集群模式
开启后调度层由集群统一裁决触发权(单点触发 + 节点故障自动转移 + misfire 自动补跑),与业务层锁构成双保险:
```json
"Quartz": {
"EnableClustering": true,
"ClusterCheckinIntervalSeconds": 10,
"MisfireThresholdSeconds": 60,
"ThreadPoolSize": 50
}
```
启用步骤:
1. 所有节点的主库切换为 SQL Server / MySQL / PostgreSQL(SQLite 多节点共享文件不可靠,Oracle 需自行导入 Quartz 官方 QuRTZ 建表脚本,二者都会自动降级回单机模式并在日志中提示)
2. 在主库执行 `database/quartz_tables_sqlserver.sql` 创建 Quartz 集群表
3. 所有节点设置 `EnableClustering: true` 并连接同一主库
`ThreadPoolSize`(默认 50)控制本节点并行执行的任务数上限;集群模式下同时是本节点可从集群领取的触发器数——调小可把任务摊到更多节点,调大则提升单节点吞吐。启动日志 `Using thread pool ... size: N` 可确认实际生效值。
> 集群模式采用"先到先得"的竞争式任务分配(非负载均衡器):节点只会领取不超过其空闲线程数的触发器,忙节点自动让出、闲节点多接活,任务量大时长期统计趋近均衡;正确性(不重复、恰好一次)与分布无关。
---
## 📁 项目结构
```
EasyJob/
├── backend/ # 后端 .NET 项目
│ ├── EasyJob.Api/ # API 层(控制器、启动配置、前端静态文件托管)
│ ├── EasyJob.Core/ # 核心层(实体、DTO、接口)
│ ├── EasyJob.Infrastructure/ # 基础设施层(数据访问、服务实现、调度)
│ ├── EasyJob.ScriptHost/ # C# 代码节点独立进程沙箱宿主
│ └── EasyJob.Tests/ # 单元测试(含数据库迁移约定校验)
├── frontend/ # 前端 Vue.js 项目
│ └── src/
│ ├── api/ # API 请求封装
│ ├── components/ # 公共组件(含 workflow/ 工作流设计器组件)
│ ├── layouts/ # 布局组件
│ ├── router/ # 路由配置
│ ├── stores/ # 状态管理
│ └── views/ # 页面视图
├── database/ # 数据库初始化脚本(含 Quartz 集群建表 SQL、迁移备案 SQL)
├── docs/ # 技术文档
├── docker-compose.yml # Docker 部署配置
└── start.bat # 一键启动脚本(开发模式)
```
> **前后端产物集成**:`frontend` 执行 `npm run build` 时,Vite 会直接把产物输出到 `backend/EasyJob.Api/Views`(见 `vite.config.ts` 的 `outDir`),由后端统一托管,无需 nginx、无需手工拷贝。
---
## 🚀 快速开始
### 环境要求
- .NET 10.0 SDK 或更高版本
- Node.js 18+
- 数据库:SQLite(默认,零配置)/ SQL Server / MySQL / PostgreSQL / Oracle
### 方式一:本地开发
```bash
# 启动后端(默认监听 http://localhost:8080,由 appsettings.json 的 Kestrel 配置决定)
cd backend
dotnet run --project EasyJob.Api
# 启动前端(Vite 开发服务器,/api 自动代理到后端 8080)
cd frontend
npm install
npm run dev
```
也可以直接双击 `start.bat` 一键启动前后端。
### 方式二:Docker 部署
```bash
# 1. 准备环境变量(三项均为必填,缺失时 compose 会拒绝启动,拒绝带着公开默认密钥跑生产)
cp .env.example .env
# 编辑 .env,填写 SA_PASSWORD / JWT_KEY / ADMIN_INITIAL_PASSWORD
# 2. 启动
docker-compose up -d
```
部署包含以下服务(前端产物由 ASP.NET Core 直接托管,无需独立 nginx 容器):
| 服务 | 说明 | 端口 |
|------|------|------|
| easyjob-api | 后端 API + 前端静态资源 | 5000 |
| sqlserver | SQL Server 2022 | 1433 |
> ⚠️ `dataprotection_keys` 卷存储数据保护密钥(库内凭据加密依赖),**千万不能删除**,否则已加密的连接串/AppSecret/ApiKey 将无法解密。
### 方式三:Windows 服务部署
后端已内置 `UseWindowsService()` 支持,标准部署流程:
```powershell
# 1. 发布(前端产物已在 Views 目录时,直接发布后端即可)
dotnet publish backend/EasyJob.Api -c Release -o C:\EasyJob
# 2. 安装并启动服务(管理员 PowerShell)
sc.exe create EasyJob binPath= "C:\EasyJob\EasyJob.Api.exe" start= auto
sc.exe start EasyJob
```
服务默认监听 `http://0.0.0.0:8080`(由 `appsettings.json` 的 `Kestrel:Endpoints:Http:Url` 配置,对 Windows 服务 / Docker / 直接运行均生效),修改配置后重启服务即可。
---
## 🔑 默认账户
| 项目 | 值 |
|------|------|
| 用户名 | `admin` |
| 密码 | `Admin@123` |
> ⚠️ 该密码已在仓库中公开,部署后请**立即登录修改**,生产环境请配合强密码与 HTTPS 使用。
---
## 🌐 访问地址
| 服务 | 地址 |
|------|------|
| 前端(开发模式) | http://localhost:5173 |
| 前端 + API(发布模式 / Windows 服务) | http://localhost:8080 |
| 前端 + API(Docker) | http://localhost:5000 |
| Swagger 文档 | http://localhost:8080/swagger |
> 说明:`dotnet run` 时 launchSettings.json 的 5271 会被 `appsettings.json` 的 Kestrel 端点配置覆盖(启动日志会打印 `Overriding address(es)`),实际以 8080 为准。
---
## 📡 API 接口
| 模块 | 路径 | 说明 |
|------|------|------|
| 认证 | `/api/auth` | 登录、注册、刷新 Token |
| 任务管理 | `/api/tasks` | 任务 CRUD、执行、调度 |
| 工作流 | `/api/workflows` | 工作流 CRUD、手动执行、实例查询 |
| 数据库连接 | `/api/dbconnections` | 数据库连接管理 |
| 邮件发送者 | `/api/emailsenders` | 邮件发送账号管理 |
| AI 供应商 | `/api/aiproviders` | AI 大模型供应商配置(8 种预设) |
| 飞书通知 | `/api/feishunotify` | 飞书 Webhook 通知配置 |
| 飞书应用 | `/api/feishuappconfig` | 飞书应用配置管理 |
| 钉钉通知 | `/api/dingtalknotify` | 钉钉 Webhook 通知配置 |
| 企业微信通知 | `/api/wecomnotify` | 企业微信 Webhook 通知配置 |
| 用户管理 | `/api/users` | 用户 CRUD、角色分配 |
| 用户组 | `/api/usergroups` | 用户组管理 |
| 审计日志 | `/api/auditlogs` | 操作日志查询 |
| 执行日志 | `/api/executionlogs` | 任务执行日志查询、清理 |
| 仪表盘 | `/api/dashboard` | 统计数据、运行状态 |
---
## 🖥 功能页面
| 页面 | 路径 | 说明 |
|------|------|------|
| 仪表盘 | `/` | 任务运行统计、系统概览 |
| 任务管理 | `/tasks` | 任务列表,支持筛选和搜索 |
| 创建 SQL 任务 | `/tasks/create-sql` | SQL 执行任务配置 |
| 创建 ETL 任务 | `/tasks/create-etl` | ETL 数据同步任务配置 |
| 创建通知任务 | `/tasks/create-notification` | SQL 监控通知任务配置 |
| 创建 HTTP 任务 | `/tasks/create-http` | HTTP 接口调用任务配置 |
| 创建飞书表格任务 | `/tasks/create-feishu-sheet` | 飞书表格同步任务配置 |
| 任务详情 | `/tasks/:id` | 任务执行记录和日志 |
| 工作流列表 | `/workflows` | 工作流管理 |
| 工作流设计器 | `/workflows/create`、`/workflows/:id/designer` | 拖拽式 DAG 编排 |
| 工作流实例 | `/workflows/:id/instances` | 执行实例历史与详情 |
| 数据库连接 | `/connections` | 管理目标数据库连接信息 |
| 邮件发送者 | `/email-senders` | 邮件发送账号管理 |
| AI 供应商 | `/ai-providers` | AI 大模型供应商配置 |
| 飞书通知 | `/feishu-notify` | 飞书 Webhook 配置 |
| 飞书应用 | `/feishu-apps` | 飞书应用凭证管理 |
| 钉钉通知 | `/dingtalk-notify` | 钉钉 Webhook 配置 |
| 企业微信通知 | `/wecom-notify` | 企业微信 Webhook 配置 |
| 用户管理 | `/users` | 用户账户和角色管理 |
| 用户组 | `/user-groups` | 用户分组管理 |
| 审计日志 | `/audit-logs` | 系统操作记录查询 |
| 执行日志 | `/execution-logs` | 任务执行日志查询与清理 |
---
## ⚙️ 配置说明
后端配置位于 `backend/EasyJob.Api/appsettings.json`:
| 配置项 | 说明 |
|--------|------|
| `DatabaseProvider` | 数据库类型(Sqlite / SqlServer / MySql / PostgreSql / Oracle) |
| `ConnectionStrings` | 各数据库连接字符串 |
| `Kestrel:Endpoints:Http:Url` | 监听地址(默认 `http://0.0.0.0:8080`,对开发/服务/Docker 均生效,优先级高于 launchSettings) |
| `Jwt:Key` | Token 签名密钥(生产环境用环境变量 `Jwt__Key` 注入随机值) |
| `Jwt:Issuer` | Token 颁发者 |
| `Jwt:Audience` | Token 受众 |
| `DistributedLock:Provider` | 分布式锁提供者:`Database`(默认)/ `Redis`(多副本推荐) |
| `DistributedLock:Redis:ConnectionString` | Redis 连接串(Provider=Redis 时必填),支持单机/哨兵 |
| `Quartz:EnableClustering` | 是否启用 Quartz 集群调度(默认 false),详见[分布式部署与高可用](#-分布式部署与高可用) |
| `Quartz:ThreadPoolSize` | 线程池并发上限(代码默认 50,当前配置 30) |
| `Quartz:ClusterCheckinIntervalSeconds` | 集群节点报到间隔(默认 10 秒) |
| `Quartz:MisfireThresholdSeconds` | 触发错过补跑阈值(默认 60 秒) |
| `Workflow:AllowedNuGetPackages` | C# 代码节点允许引用的 NuGet 包白名单(默认空 = 全部拒绝) |
| `Feishu:AppId` | 飞书应用 AppId |
| `Feishu:AppSecret` | 飞书应用 AppSecret |
| `Notification:Email` | 邮件发件账号(SMTP) |
| `Notification:Alert` | 系统告警接收邮箱/飞书 Webhook |
| `Oracle:Compatibility` | Oracle 兼容级别(19 默认 / 21 / 23),详见 [docs/Oracle-支持说明.md](docs/Oracle-支持说明.md) |
| `Oracle:MaxIdentifierLength` | 可选,连 12.1 及更老 Oracle 时设 30 |
---
## 📄 License
[MIT](LICENSE)