feat: 新增睿美云对接模块
All checks were successful
continuous-integration/drone/push Build is passing

- 扩展 ToolConfig 配置类型,新增 external_api 类型
- 实现接口注册表,包含 90+ 睿美云开放接口定义
- 实现 TPOS SHA256WithRSA 签名鉴权
- 实现睿美云 API 客户端,支持多租户配置
- 新增代理路由 /api/ruimeiyun/call/{api_name}
- 支持接口权限控制和健康检查
This commit is contained in:
2026-01-30 17:27:58 +08:00
parent c1ba17f809
commit afcf30b519
23 changed files with 6290 additions and 4648 deletions

View File

@@ -1,358 +1,358 @@
# 定时任务系统文档
## 功能概述
平台定时任务系统,支持 Python 脚本或 Webhook 定时执行,执行结果可自动推送到钉钉/企微机器人。
**核心能力**
- 脚本执行:安全沙箱运行 Python 脚本,内置 AI、HTTP、数据库等 SDK
- 调度方式:指定时间点(多选)或 CRON 表达式
- 消息推送:支持钉钉/企微机器人所有消息格式markdown、actionCard、feedCard 等)
- 失败处理:支持重试和告警通知
---
## 数据库表
### platform_scheduled_tasks定时任务表
```sql
CREATE TABLE platform_scheduled_tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
tenant_id VARCHAR(50) COMMENT '租户ID空为全局任务',
task_name VARCHAR(100) NOT NULL COMMENT '任务名称',
task_desc VARCHAR(500) COMMENT '任务描述',
schedule_type ENUM('simple', 'cron') NOT NULL DEFAULT 'simple',
time_points JSON COMMENT '时间点列表 ["08:00", "12:00"]',
cron_expression VARCHAR(100) COMMENT 'CRON表达式',
timezone VARCHAR(50) DEFAULT 'Asia/Shanghai',
execution_type ENUM('webhook', 'script') NOT NULL DEFAULT 'script',
webhook_url VARCHAR(500),
script_content TEXT COMMENT 'Python脚本内容',
script_deps TEXT COMMENT '脚本依赖',
input_params JSON COMMENT '输入参数',
retry_count INT DEFAULT 0,
retry_interval INT DEFAULT 60,
alert_on_failure TINYINT(1) DEFAULT 0,
alert_webhook VARCHAR(500),
notify_channels JSON COMMENT '通知渠道ID列表',
notify_wecom_app_id INT COMMENT '企微应用ID',
is_enabled TINYINT(1) DEFAULT 1,
last_run_at DATETIME,
last_run_status ENUM('success', 'failed', 'running'),
last_run_message TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
### platform_task_notify_channels通知渠道表
```sql
CREATE TABLE platform_task_notify_channels (
id INT AUTO_INCREMENT PRIMARY KEY,
tenant_id VARCHAR(50) NOT NULL COMMENT '租户ID',
channel_name VARCHAR(100) NOT NULL COMMENT '渠道名称',
channel_type ENUM('dingtalk_bot', 'wecom_bot') NOT NULL,
webhook_url VARCHAR(500) NOT NULL,
sign_secret VARCHAR(200) COMMENT '钉钉加签密钥',
description VARCHAR(255),
is_enabled TINYINT(1) DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
### platform_task_logs执行日志表
```sql
CREATE TABLE platform_task_logs (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
task_id INT NOT NULL,
tenant_id VARCHAR(50),
trace_id VARCHAR(100),
status ENUM('running', 'success', 'failed'),
started_at DATETIME,
finished_at DATETIME,
duration_ms INT,
output TEXT,
error TEXT,
retry_count INT DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
---
## 后端文件结构
```
backend/app/
├── models/
│ ├── scheduled_task.py # ScheduledTask, TaskLog, ScriptVar, Secret 模型
│ └── notification_channel.py # TaskNotifyChannel 模型
├── routers/
│ ├── tasks.py # 定时任务 API (/api/scheduled-tasks)
│ └── notification_channels.py # 通知渠道 API (/api/notification-channels)
└── services/
├── scheduler.py # APScheduler 调度服务
├── script_executor.py # 脚本执行器(安全沙箱)
└── script_sdk.py # 脚本内置 SDK
```
---
## 脚本 SDK 文档
### 内置函数
```python
# 日志
log(message) # 记录日志
print(message) # 打印输出
# AI 调用
ai(prompt, system=None, model='deepseek-chat') # 调用 AI
# 通知发送(直接发送,不走 result
dingtalk(webhook_url, content, title='通知')
wecom(webhook_url, content)
# HTTP 请求
http_get(url, headers=None, params=None)
http_post(url, data=None, json=None, headers=None)
# 数据库查询(只读)
db_query(sql, params=None)
# 变量存储(跨执行持久化)
get_var(key, default=None)
set_var(key, value)
del_var(key)
# 任务参数
get_param(key, default=None) # 获取单个参数
get_params() # 获取所有参数
# 租户相关
get_tenants() # 获取所有租户
get_tenant_config(tenant_id, app_code, key) # 获取租户配置
get_all_tenant_configs(app_code, key) # 获取所有租户的配置
# 密钥
get_secret(key) # 获取密钥
```
### 内置变量
```python
task_id # 当前任务ID
tenant_id # 当前租户ID可能为空
trace_id # 追踪ID
```
### 内置模块(无需 import
```python
datetime # datetime.now(), datetime.strptime()
date # date.today()
timedelta # timedelta(days=1)
time # time.sleep(), time.time()
json # json.dumps(), json.loads()
re # re.search(), re.match()
math # math.ceil(), math.floor()
random # random.randint(), random.choice()
hashlib # hashlib.md5()
base64 # base64.b64encode()
```
---
## 消息格式result 变量)
### 基础格式(默认 markdown
```python
result = {
'content': 'Markdown 内容',
'title': '消息标题'
}
```
### 钉钉 ActionCard交互卡片
```python
result = {
'msg_type': 'actionCard',
'title': '卡片标题',
'content': '''### 正文内容
| 列1 | 列2 |
|:---:|:---:|
| A | B |
''',
'btn_orientation': '1', # 0-竖向 1-横向
'buttons': [
{'title': '按钮1', 'url': 'https://...'},
{'title': '按钮2', 'url': 'https://...'}
]
}
```
### 钉钉 FeedCard信息流
```python
result = {
'msg_type': 'feedCard',
'links': [
{'title': '标题1', 'url': 'https://...', 'pic_url': 'https://...'},
{'title': '标题2', 'url': 'https://...', 'pic_url': 'https://...'}
]
}
```
### 钉钉 Link链接消息
```python
result = {
'msg_type': 'link',
'title': '链接标题',
'content': '链接描述',
'url': 'https://...',
'pic_url': 'https://...'
}
```
### 企微 News图文消息
```python
result = {
'msg_type': 'news',
'articles': [
{
'title': '文章标题',
'description': '文章描述',
'url': 'https://...',
'picurl': 'https://...'
}
]
}
```
### 企微 Template Card模板卡片
```python
result = {
'msg_type': 'template_card',
'card_type': 'text_notice', # text_notice / news_notice / button_interaction
'title': '卡片标题',
'content': '卡片内容',
'horizontal_list': [
{'keyname': '申请人', 'value': '张三'},
{'keyname': '金额', 'value': '¥5,000'}
],
'jump_list': [
{'type': 1, 'title': '查看详情', 'url': 'https://...'}
]
}
```
---
## API 端点
### 定时任务
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/scheduled-tasks | 任务列表 |
| GET | /api/scheduled-tasks/{id} | 任务详情 |
| POST | /api/scheduled-tasks | 创建任务 |
| PUT | /api/scheduled-tasks/{id} | 更新任务 |
| DELETE | /api/scheduled-tasks/{id} | 删除任务 |
| POST | /api/scheduled-tasks/{id}/toggle | 启用/禁用 |
| POST | /api/scheduled-tasks/{id}/run | 立即执行 |
| GET | /api/scheduled-tasks/{id}/logs | 执行日志 |
| POST | /api/scheduled-tasks/test-script | 测试脚本 |
| GET | /api/scheduled-tasks/sdk-docs | SDK 文档 |
### 通知渠道
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/notification-channels | 渠道列表 |
| POST | /api/notification-channels | 创建渠道 |
| PUT | /api/notification-channels/{id} | 更新渠道 |
| DELETE | /api/notification-channels/{id} | 删除渠道 |
| POST | /api/notification-channels/{id}/test | 测试渠道 |
---
## 前端文件
```
frontend/src/views/
├── scheduled-tasks/
│ └── index.vue # 定时任务管理页面
└── notification-channels/
└── index.vue # 通知渠道管理页面
```
---
## 示例脚本
### 基础示例
```python
# 无需 import模块已内置
log('任务开始执行')
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
prompt = get_param('prompt', '默认提示词')
content = ai(prompt, system='你是一个助手')
result = {
'title': '每日推送',
'content': f'**生成时间**: {now}\n\n{content}'
}
log('任务执行完成')
```
### 复杂卡片示例
```python
log('生成销售日报')
now = datetime.now()
today = now.strftime('%Y年%m月%d')
# 模拟数据
revenue = random.randint(50000, 150000)
result = {
'msg_type': 'actionCard',
'title': f'销售日报 | {today}',
'content': f'''### 今日业绩
| 指标 | 数值 |
|:---:|:---:|
| 销售额 | **¥{revenue:,}** |
| 订单数 | **{random.randint(40, 80)}** |
> 点击查看详情
''',
'buttons': [
{'title': '查看详情', 'url': 'https://example.com/report'}
]
}
```
---
## 部署信息
- **测试环境**: https://platform.test.ai.ireborn.com.cn
- **数据库**: new_qiqi (测试) / new_platform_prod (生产)
- **Docker 容器**: platform-backend-test / platform-frontend-test
# 定时任务系统文档
## 功能概述
平台定时任务系统,支持 Python 脚本或 Webhook 定时执行,执行结果可自动推送到钉钉/企微机器人。
**核心能力**
- 脚本执行:安全沙箱运行 Python 脚本,内置 AI、HTTP、数据库等 SDK
- 调度方式:指定时间点(多选)或 CRON 表达式
- 消息推送:支持钉钉/企微机器人所有消息格式markdown、actionCard、feedCard 等)
- 失败处理:支持重试和告警通知
---
## 数据库表
### platform_scheduled_tasks定时任务表
```sql
CREATE TABLE platform_scheduled_tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
tenant_id VARCHAR(50) COMMENT '租户ID空为全局任务',
task_name VARCHAR(100) NOT NULL COMMENT '任务名称',
task_desc VARCHAR(500) COMMENT '任务描述',
schedule_type ENUM('simple', 'cron') NOT NULL DEFAULT 'simple',
time_points JSON COMMENT '时间点列表 ["08:00", "12:00"]',
cron_expression VARCHAR(100) COMMENT 'CRON表达式',
timezone VARCHAR(50) DEFAULT 'Asia/Shanghai',
execution_type ENUM('webhook', 'script') NOT NULL DEFAULT 'script',
webhook_url VARCHAR(500),
script_content TEXT COMMENT 'Python脚本内容',
script_deps TEXT COMMENT '脚本依赖',
input_params JSON COMMENT '输入参数',
retry_count INT DEFAULT 0,
retry_interval INT DEFAULT 60,
alert_on_failure TINYINT(1) DEFAULT 0,
alert_webhook VARCHAR(500),
notify_channels JSON COMMENT '通知渠道ID列表',
notify_wecom_app_id INT COMMENT '企微应用ID',
is_enabled TINYINT(1) DEFAULT 1,
last_run_at DATETIME,
last_run_status ENUM('success', 'failed', 'running'),
last_run_message TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
### platform_task_notify_channels通知渠道表
```sql
CREATE TABLE platform_task_notify_channels (
id INT AUTO_INCREMENT PRIMARY KEY,
tenant_id VARCHAR(50) NOT NULL COMMENT '租户ID',
channel_name VARCHAR(100) NOT NULL COMMENT '渠道名称',
channel_type ENUM('dingtalk_bot', 'wecom_bot') NOT NULL,
webhook_url VARCHAR(500) NOT NULL,
sign_secret VARCHAR(200) COMMENT '钉钉加签密钥',
description VARCHAR(255),
is_enabled TINYINT(1) DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
### platform_task_logs执行日志表
```sql
CREATE TABLE platform_task_logs (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
task_id INT NOT NULL,
tenant_id VARCHAR(50),
trace_id VARCHAR(100),
status ENUM('running', 'success', 'failed'),
started_at DATETIME,
finished_at DATETIME,
duration_ms INT,
output TEXT,
error TEXT,
retry_count INT DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
---
## 后端文件结构
```
backend/app/
├── models/
│ ├── scheduled_task.py # ScheduledTask, TaskLog, ScriptVar, Secret 模型
│ └── notification_channel.py # TaskNotifyChannel 模型
├── routers/
│ ├── tasks.py # 定时任务 API (/api/scheduled-tasks)
│ └── notification_channels.py # 通知渠道 API (/api/notification-channels)
└── services/
├── scheduler.py # APScheduler 调度服务
├── script_executor.py # 脚本执行器(安全沙箱)
└── script_sdk.py # 脚本内置 SDK
```
---
## 脚本 SDK 文档
### 内置函数
```python
# 日志
log(message) # 记录日志
print(message) # 打印输出
# AI 调用
ai(prompt, system=None, model='deepseek-chat') # 调用 AI
# 通知发送(直接发送,不走 result
dingtalk(webhook_url, content, title='通知')
wecom(webhook_url, content)
# HTTP 请求
http_get(url, headers=None, params=None)
http_post(url, data=None, json=None, headers=None)
# 数据库查询(只读)
db_query(sql, params=None)
# 变量存储(跨执行持久化)
get_var(key, default=None)
set_var(key, value)
del_var(key)
# 任务参数
get_param(key, default=None) # 获取单个参数
get_params() # 获取所有参数
# 租户相关
get_tenants() # 获取所有租户
get_tenant_config(tenant_id, app_code, key) # 获取租户配置
get_all_tenant_configs(app_code, key) # 获取所有租户的配置
# 密钥
get_secret(key) # 获取密钥
```
### 内置变量
```python
task_id # 当前任务ID
tenant_id # 当前租户ID可能为空
trace_id # 追踪ID
```
### 内置模块(无需 import
```python
datetime # datetime.now(), datetime.strptime()
date # date.today()
timedelta # timedelta(days=1)
time # time.sleep(), time.time()
json # json.dumps(), json.loads()
re # re.search(), re.match()
math # math.ceil(), math.floor()
random # random.randint(), random.choice()
hashlib # hashlib.md5()
base64 # base64.b64encode()
```
---
## 消息格式result 变量)
### 基础格式(默认 markdown
```python
result = {
'content': 'Markdown 内容',
'title': '消息标题'
}
```
### 钉钉 ActionCard交互卡片
```python
result = {
'msg_type': 'actionCard',
'title': '卡片标题',
'content': '''### 正文内容
| 列1 | 列2 |
|:---:|:---:|
| A | B |
''',
'btn_orientation': '1', # 0-竖向 1-横向
'buttons': [
{'title': '按钮1', 'url': 'https://...'},
{'title': '按钮2', 'url': 'https://...'}
]
}
```
### 钉钉 FeedCard信息流
```python
result = {
'msg_type': 'feedCard',
'links': [
{'title': '标题1', 'url': 'https://...', 'pic_url': 'https://...'},
{'title': '标题2', 'url': 'https://...', 'pic_url': 'https://...'}
]
}
```
### 钉钉 Link链接消息
```python
result = {
'msg_type': 'link',
'title': '链接标题',
'content': '链接描述',
'url': 'https://...',
'pic_url': 'https://...'
}
```
### 企微 News图文消息
```python
result = {
'msg_type': 'news',
'articles': [
{
'title': '文章标题',
'description': '文章描述',
'url': 'https://...',
'picurl': 'https://...'
}
]
}
```
### 企微 Template Card模板卡片
```python
result = {
'msg_type': 'template_card',
'card_type': 'text_notice', # text_notice / news_notice / button_interaction
'title': '卡片标题',
'content': '卡片内容',
'horizontal_list': [
{'keyname': '申请人', 'value': '张三'},
{'keyname': '金额', 'value': '¥5,000'}
],
'jump_list': [
{'type': 1, 'title': '查看详情', 'url': 'https://...'}
]
}
```
---
## API 端点
### 定时任务
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/scheduled-tasks | 任务列表 |
| GET | /api/scheduled-tasks/{id} | 任务详情 |
| POST | /api/scheduled-tasks | 创建任务 |
| PUT | /api/scheduled-tasks/{id} | 更新任务 |
| DELETE | /api/scheduled-tasks/{id} | 删除任务 |
| POST | /api/scheduled-tasks/{id}/toggle | 启用/禁用 |
| POST | /api/scheduled-tasks/{id}/run | 立即执行 |
| GET | /api/scheduled-tasks/{id}/logs | 执行日志 |
| POST | /api/scheduled-tasks/test-script | 测试脚本 |
| GET | /api/scheduled-tasks/sdk-docs | SDK 文档 |
### 通知渠道
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/notification-channels | 渠道列表 |
| POST | /api/notification-channels | 创建渠道 |
| PUT | /api/notification-channels/{id} | 更新渠道 |
| DELETE | /api/notification-channels/{id} | 删除渠道 |
| POST | /api/notification-channels/{id}/test | 测试渠道 |
---
## 前端文件
```
frontend/src/views/
├── scheduled-tasks/
│ └── index.vue # 定时任务管理页面
└── notification-channels/
└── index.vue # 通知渠道管理页面
```
---
## 示例脚本
### 基础示例
```python
# 无需 import模块已内置
log('任务开始执行')
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
prompt = get_param('prompt', '默认提示词')
content = ai(prompt, system='你是一个助手')
result = {
'title': '每日推送',
'content': f'**生成时间**: {now}\n\n{content}'
}
log('任务执行完成')
```
### 复杂卡片示例
```python
log('生成销售日报')
now = datetime.now()
today = now.strftime('%Y年%m月%d')
# 模拟数据
revenue = random.randint(50000, 150000)
result = {
'msg_type': 'actionCard',
'title': f'销售日报 | {today}',
'content': f'''### 今日业绩
| 指标 | 数值 |
|:---:|:---:|
| 销售额 | **¥{revenue:,}** |
| 订单数 | **{random.randint(40, 80)}** |
> 点击查看详情
''',
'buttons': [
{'title': '查看详情', 'url': 'https://example.com/report'}
]
}
```
---
## 部署信息
- **测试环境**: https://platform.test.ai.ireborn.com.cn
- **数据库**: new_qiqi (测试) / new_platform_prod (生产)
- **Docker 容器**: platform-backend-test / platform-frontend-test