增加在线脚本开发框架

This commit is contained in:
靳中伟 2025-09-12 16:15:13 +08:00
parent 674da470d3
commit df99d64864
230 changed files with 76112 additions and 4818 deletions

View File

@ -0,0 +1,198 @@
# VWED任务模块接口文档概览
## 文档说明
本目录包含VWED任务模块的完整接口文档涵盖了任务管理、脚本编辑、设备控制、数据管理等各个功能模块的API接口说明。
## 文档列表
### 核心功能模块
1. **[VWED任务管理接口文档](./VWED任务管理接口文档.md)**
- 任务的创建、查询、更新、删除操作
- 任务状态管理和生命周期控制
- 任务执行记录和监控
2. **[任务编辑接口文档](./任务编辑接口文档.md)**
- 任务流程设计器接口
- 任务块配置和管理
- 可视化任务编辑功能
3. **[任务记录接口文档](./任务记录接口文档.md)**
- 任务执行历史查询
- 执行状态监控
- 日志记录和追踪
4. **[任务模板管理接口文档](./任务模板管理接口文档.md)**
- 任务模板的创建和管理
- 模板复用和版本控制
- 模板导入导出功能
### 新增功能模块
5. **[在线脚本编辑接口文档](./在线脚本编辑接口文档.md)** ⭐ **新增**
- Python脚本在线编辑和管理
- 多脚本并发执行引擎
- 实时日志推送和监控
- 动态API注册和函数调用
- VWED统一对象系统
- 任务系统集成和脚本函数调用
### 设备和硬件接口
6. **[呼叫器设备接口文档](./呼叫器设备接口文档.md)**
- 呼叫器设备管理
- 设备状态监控
- 呼叫记录管理
7. **[机器人调度接口文档](./机器人调度接口文档.md)**
- AMR机器人调度管理
- 路径规划和导航
- 任务分配和执行
8. **[Modbus配置接口文档](./Modbus配置接口文档.md)**
- Modbus设备配置管理
- 设备通信参数设置
- 数据点位映射配置
### 数据管理接口
9. **[库位管理接口文档](./库位管理接口文档.md)**
- 仓储库位信息管理
- 库位状态监控
- 存储区域配置
10. **[地图数据推送接口文档](./地图数据推送接口文档.md)**
- 地图数据实时推送
- 位置信息更新
- 导航路径数据
11. **[外部任务接口文档](./外部任务接口文档.md)**
- 外部系统任务集成
- 第三方任务接口
- 数据交换协议
### 通信接口
12. **[WebSocket接口文档](./WebSocket接口文档.md)**
- 实时数据推送
- 双向通信协议
- 消息格式定义
## 接口规范
### 统一响应格式
所有API接口都遵循统一的响应格式
```json
{
"success": true, // 操作成功标识
"code": 200, // HTTP状态码
"message": "操作成功", // 响应消息
"data": {}, // 响应数据
"timestamp": "2025-09-11T12:00:00Z" // 响应时间戳
}
```
### 错误响应格式
```json
{
"success": false,
"code": 400,
"message": "请求参数错误",
"error": "详细错误信息",
"timestamp": "2025-09-11T12:00:00Z"
}
```
### 基础URL
- **开发环境**: `http://localhost:8000`
- **测试环境**: `http://test-server:8000`
- **生产环境**: `http://prod-server:8000`
### 认证方式
系统支持以下认证方式:
- **Token认证**: 在请求头中添加 `Authorization: Bearer <token>`
- **Session认证**: 基于会话的身份认证
- **API Key**: 在请求头中添加 `X-API-Key: <api-key>`
## 新增功能亮点
### 在线脚本编辑模块
**在线脚本编辑模块**是本次更新的重要功能提供了完整的Python脚本开发和运行环境
#### 🎯 核心特性
- **可视化脚本编辑**: 支持在线代码编辑、语法高亮、错误提示
- **多脚本并发**: 支持多个脚本同时运行,互不干扰
- **实时监控**: WebSocket实时推送脚本执行日志和状态
- **动态API注册**: 脚本可以动态注册HTTP API接口
- **函数调用**: 支持从VWED任务中调用脚本函数
- **统一对象系统**: 提供VWED统一API对象简化脚本开发
#### 🔧 技术架构
- **多层架构**: 数据层、服务层、接口层分离
- **异步处理**: 基于异步协程的高性能执行引擎
- **资源隔离**: 脚本间资源完全隔离,确保稳定性
- **数据持久化**: 完整的执行记录和统计数据存储
#### 🚀 使用场景
1. **自定义业务逻辑**: 通过脚本实现特定的业务处理逻辑
2. **数据处理**: 对传感器数据进行实时处理和分析
3. **系统集成**: 与第三方系统进行数据交互和接口对接
4. **任务扩展**: 为VWED任务系统提供自定义功能扩展
5. **API服务**: 快速构建RESTful API服务
#### 📋 脚本示例
```python
def boot():
"""脚本启动函数"""
VWED.log.sync_info("脚本服务启动中...")
# 注册API接口
@VWED.api.post("/calculate", description="计算服务")
def calculate(request_data):
a = request_data["body"].get("a", 0)
b = request_data["body"].get("b", 0)
return {"result": a + b}
# 注册自定义函数供VWED任务调用
@VWED.function.register("process_data", description="数据处理")
def process_data(args):
# 自定义数据处理逻辑
return {"processed": True, "count": len(args.get("data", []))}
# 设置定时任务
@VWED.timer.interval(60)
def monitor_system():
VWED.log.sync_info("系统状态检查")
VWED.log.sync_info("脚本服务启动完成")
```
## 版本信息
- **当前版本**: v2.0.0
- **最后更新**: 2025-09-11
- **更新内容**:
- 新增在线脚本编辑模块
- 完善WebSocket实时通信
- 优化任务系统集成
- 增强错误处理和日志记录
## 技术支持
如有疑问或需要技术支持,请参考:
1. 对应模块的详细接口文档
2. 代码示例和最佳实践
3. 错误码说明和解决方案
4. 性能优化建议
---
**注意**: 在线脚本编辑功能为新增模块,建议在开发和测试环境中充分验证后再部署到生产环境。

View File

@ -1,396 +0,0 @@
# 在线脚本管理接口文档
## 一、在线脚本模块概述
在线脚本模块是配合天风任务模块使用的核心功能允许用户创建、编辑、管理和执行Python脚本。该模块支持脚本版本控制运行脚本并查看执行日志等功能。
## 二、在线脚本模块接口
### 1. 获取脚本列表
#### 接口描述
获取系统中的脚本列表支持分页、排序和筛选。数据来源是vwed_script表。
#### 请求方式
- **HTTP方法**: GET
- **接口路径**: `/api/vwed-script/list`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| page | Integer | 否 | 页码默认为1 |
| pageSize | Integer | 否 | 每页记录数默认为20 |
| name | String | 否 | 脚本名称,支持模糊查询 |
| status | Integer | 否 | 脚本状态(1:启用, 0:禁用) |
| folderPath | String | 否 | 脚本所在目录路径 |
| tags | String | 否 | 标签,支持模糊查询 |
| isPublic | Integer | 否 | 是否公开(1:是, 0:否) |
| createdBy | String | 否 | 创建者 |
| startTime | String | 否 | 创建时间范围的开始时间格式yyyy-MM-dd HH:mm:ss |
| endTime | String | 否 | 创建时间范围的结束时间格式yyyy-MM-dd HH:mm:ss |
#### 响应参数
```json
{
"code": 200,
"message": "操作成功",
"data": {
"total": 15,
"list": [
{
"id": "script-001",
"name": "数据处理脚本",
"folderPath": "/数据处理",
"fileName": "data_processor.py",
"description": "用于处理CSV数据的脚本",
"version": 3,
"status": 1,
"isPublic": 1,
"tags": "数据,CSV,处理",
"createdBy": "admin",
"createdOn": "2025-04-10 09:30:45",
"updatedBy": "admin",
"updatedOn": "2025-04-12 15:20:10"
},
{
"id": "script-002",
"name": "邮件发送脚本",
"folderPath": "/通知",
"fileName": "email_sender.py",
"description": "自动发送邮件通知的脚本",
"version": 1,
"status": 1,
"isPublic": 1,
"tags": "邮件,通知",
"createdBy": "user1",
"createdOn": "2025-04-11 10:15:22",
"updatedBy": "user1",
"updatedOn": "2025-04-11 10:15:22"
}
]
}
}
```
### 2. 获取脚本详情
#### 接口描述
获取指定脚本的详细信息,包括脚本代码内容。
#### 请求方式
- **HTTP方法**: GET
- **接口路径**: `/api/vwed-script/{id}`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| id | String | 是 | 脚本ID路径参数 |
#### 响应参数
```json
{
"code": 200,
"message": "操作成功",
"data": {
"id": "script-001",
"name": "数据处理脚本",
"folderPath": "/数据处理",
"fileName": "data_processor.py",
"description": "用于处理CSV数据的脚本",
"code": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\ndef process_data(input_file, output_file):\n # 处理逻辑\n pass\n\nif __name__ == '__main__':\n process_data('input.csv', 'output.csv')",
"version": 3,
"status": 1,
"isPublic": 1,
"tags": "数据,CSV,处理",
"createdBy": "admin",
"createdOn": "2025-04-10 09:30:45",
"updatedBy": "admin",
"updatedOn": "2025-04-12 15:20:10",
"testParams": "{\"input_file\": \"test.csv\", \"output_file\": \"result.csv\"}"
}
}
```
### 3. 创建脚本
#### 接口描述
创建一个新的脚本。
#### 请求方式
- **HTTP方法**: POST
- **接口路径**: `/api/vwed-script`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| folderPath | String | 否 | 脚本所在目录路径 |
| fileName | String | 是 | 脚本文件名,必须以.py结尾 |
#### 请求示例
```json
{
"folderPath": "/数据处理",
"fileName": "data_processor.py",
}
```
#### 响应参数
```json
{
"code": 200,
"message": "创建成功",
"data": {
"id": "script-001"
}
}
```
### 4. 更新脚本
#### 接口描述
更新现有脚本的信息,包括代码内容。每次更新会自动生成新的版本记录。
#### 请求方式
- **HTTP方法**: PUT
- **接口路径**: `/api/vwed-script/{id}`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| id | String | 是 | 脚本ID路径参数 |
| folderPath | String | 是 | 脚本所在目录路径 |
| fileName | String | 是 | 脚本文件名,必须以.py结尾 |
| code | String | 是 | 脚本代码内容 |
#### 请求示例
```json
{
"code": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\ndef process_data(input_file, output_file):\n # 更新的处理逻辑\n with open(input_file, 'r') as f_in:\n data = f_in.read()\n \n # 处理数据\n processed_data = data.upper()\n \n with open(output_file, 'w') as f_out:\n f_out.write(processed_data)\n\nif __name__ == '__main__':\n process_data('input.csv', 'output.csv')",
"changeLog": "增加了文件读写功能"
}
```
#### 响应参数
```json
{
"code": 200,
"message": "更新成功",
"data": {
"version": 4
}
}
```
### 5. 删除脚本
#### 接口描述
删除指定的脚本,同时删除所有相关的版本记录。
#### 请求方式
- **HTTP方法**: DELETE
- **接口路径**: `/api/vwed-script/{id}`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| id | String | 是 | 脚本ID路径参数 |
#### 响应参数
```json
{
"code": 200,
"message": "删除成功",
"data": null
}
```
### 6. 运行脚本
#### 接口描述
执行指定的脚本,并返回执行结果。
#### 请求方式
- **HTTP方法**: POST
- **接口路径**: `/api/vwed-script/{id}/run`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| id | String | 是 | 脚本ID路径参数 |
#### 请求示例
```json
{
"id": "task-001",
}
```
#### 响应参数
```json
{
"code": 200,
"message": "执行成功",
"data": {
"logId": "log-001",
"result": {
"status": "success",
"output": {
"processed_rows": 100,
"duration": 0.25
}
},
"executionTime": 250
}
}
```
### 7. 停止脚本执行
#### 接口描述
停止正在执行的脚本。
#### 请求方式
- **HTTP方法**: POST
- **接口路径**: `/api/vwed-script/stop`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| logId | String | 是 | 脚本执行日志ID |
#### 请求示例
```json
{
"logId": "log-001"
}
```
#### 响应参数
```json
{
"code": 200,
"message": "已停止执行",
"data": null
}
```
### 12. 获取脚本执行日志详情
#### 接口描述
获取特定脚本执行日志的详细信息。
#### 请求方式
- **HTTP方法**: GET
- **接口路径**: `/api/vwed-script/log/{logId}`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| logId | String | 是 | 脚本执行日志ID路径参数 |
#### 响应参数
```json
{
"code": 200,
"message": "操作成功",
"data": {
"id": "log-003",
"scriptId": "script-001",
"scriptName": "数据处理脚本",
"version": 3,
"taskRecordId": "task-001",
"blockRecordId": "block-001",
"inputParams": "{\"input_file\":\"data.csv\",\"output_file\":\"result.csv\"}",
"outputResult": "{\"status\":\"success\",\"processed_rows\":100,\"duration\":0.25}",
"status": 1,
"errorMessage": null,
"executionTime": 250,
"startedOn": "2025-04-12 16:10:45",
"endedOn": "2025-04-12 16:10:45",
"code": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\ndef process_data(input_file, output_file):\n # 版本3的处理逻辑\n pass\n\nif __name__ == '__main__':\n process_data('input.csv', 'output.csv')"
}
}
```
### 13. 导出脚本
#### 接口描述
导出指定脚本的代码文件。
#### 请求方式
- **HTTP方法**: GET
- **接口路径**: `/api/vwed-script/{id}/export`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| id | String | 是 | 脚本ID路径参数 |
#### 响应参数
返回脚本文件的二进制内容Content-Type为application/octet-stream或text/plain。
### 14. 导入脚本
#### 接口描述
从Python文件导入脚本。
#### 请求方式
- **HTTP方法**: POST
- **接口路径**: `/api/vwed-script/import`
- **Content-Type**: multipart/form-data
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|-------|------|-----|------|
| file | File | 是 | 要导入的Python脚本文件 |
#### 响应参数
```json
{
"code": 200,
"message": "导入成功",
"data": {
"id": "script-010"
}
}
```
## 三、数据结构
### 1. 脚本状态
| 状态码 | 描述 |
|-------|------|
| 1 | 启用 |
| 0 | 禁用 |
### 2. 脚本执行状态
| 状态码 | 描述 |
|-------|------|
| 1 | 成功 |
| 0 | 失败 |

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -14,7 +14,6 @@ from data.models.blockrecord import VWEDBlockRecord
from data.models.tasktemplate import VWEDTaskTemplate
from data.models.datacache import VWEDDataCache
from data.models.datacachesplit import VWEDDataCacheSplit
from data.models.script import VWEDScript, VWEDScriptVersion, VWEDScriptLog
from data.models.modbusconfig import VWEDModbusConfig
from data.models.calldevice import VWEDCallDevice, VWEDCallDeviceButton
from data.models.interfacedef import InterfaceDefHistory
@ -25,13 +24,23 @@ from data.models.extended_property import ExtendedProperty, ExtendedPropertyType
from data.models.storage_location_log import StorageLocationLog
from data.models.external_task_record import VWEDExternalTaskRecord, ExternalTaskTypeEnum, ExternalTaskStatusEnum
# 脚本引擎相关模型
from data.models.script_project import VWEDScriptProject
from data.models.script_file import VWEDScriptFile
from data.models.script_execution_log import VWEDScriptExecutionLog
from data.models.script_api_registration import VWEDScriptAPIRegistration
from data.models.script_function_registration import VWEDScriptFunctionRegistration
from data.models.script_registry import VWEDScriptRegistry
# 导出所有模型供应用程序使用
__all__ = [
'BaseModel', 'VWEDTaskDef', 'VWEDTaskRecord', 'VWEDTaskLog',
'VWEDBlockRecord', 'VWEDTaskTemplate', 'VWEDDataCache', 'VWEDDataCacheSplit',
'VWEDScript', 'VWEDScriptVersion', 'VWEDScriptLog', 'VWEDModbusConfig',
'VWEDBlockRecord', 'VWEDTaskTemplate', 'VWEDDataCache', 'VWEDDataCacheSplit', 'VWEDModbusConfig',
'VWEDCallDevice', 'VWEDCallDeviceButton', 'InterfaceDefHistory',
'StorageArea', 'StorageAreaType', 'OperatePoint', 'OperatePointLayer',
'ExtendedProperty', 'ExtendedPropertyTypeEnum', 'StorageLocationLog',
'VWEDExternalTaskRecord', 'ExternalTaskTypeEnum', 'ExternalTaskStatusEnum'
'VWEDExternalTaskRecord', 'ExternalTaskTypeEnum', 'ExternalTaskStatusEnum',
# 脚本引擎模型
'VWEDScriptProject', 'VWEDScriptFile', 'VWEDScriptExecutionLog',
'VWEDScriptAPIRegistration', 'VWEDScriptFunctionRegistration', 'VWEDScriptRegistry'
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,110 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
在线脚本相关模型
包含脚本主表版本表和日志表
"""
import datetime
from sqlalchemy import Column, String, Integer, DateTime, Text, Boolean, ForeignKey
from sqlalchemy.dialects.mysql import LONGTEXT, BIT
from sqlalchemy.orm import relationship
from data.models.base import BaseModel
class VWEDScript(BaseModel):
"""
在线脚本模型
对应vwed_script表
功能存储系统中的Python脚本
"""
__tablename__ = 'vwed_script'
__table_args__ = {
'mysql_engine': 'InnoDB',
'mysql_charset': 'utf8mb4',
'mysql_collate': 'utf8mb4_general_ci',
'info': {'order_by': 'created_at DESC'}
}
id = Column(String(255), primary_key=True, nullable=False, comment='脚本唯一标识')
name = Column(String(255), nullable=False, comment='脚本名称')
folder_path = Column(String(255), default='/', comment='脚本所在目录路径')
file_name = Column(String(255), nullable=False, comment='脚本文件名')
description = Column(String(500), comment='脚本功能描述')
code = Column(LONGTEXT, nullable=False, comment='脚本代码内容')
version = Column(Integer, nullable=False, default=1, comment='当前版本号')
status = Column(Integer, nullable=False, default=1, comment='状态(1:启用, 0:禁用)')
is_public = Column(BIT(1), nullable=False, default=1, comment='是否公开')
tags = Column(String(255), comment='标签,用于分类查询')
created_by = Column(String(255), comment='创建者')
updated_by = Column(String(255), comment='最后更新者')
test_params = Column(LONGTEXT, comment='测试参数(JSON格式)')
# 关联关系
versions = relationship("VWEDScriptVersion", back_populates="script", cascade="all, delete-orphan")
logs = relationship("VWEDScriptLog", back_populates="script", cascade="all, delete-orphan")
def __repr__(self):
return f"<VWEDScript(id='{self.id}', name='{self.name}', version='{self.version}')>"
class VWEDScriptVersion(BaseModel):
"""
脚本版本模型
对应vwed_script_version表
功能保存脚本历史版本
"""
__tablename__ = 'vwed_script_version'
__table_args__ = {
'mysql_engine': 'InnoDB',
'mysql_charset': 'utf8mb4',
'mysql_collate': 'utf8mb4_general_ci'
}
id = Column(String(255), primary_key=True, nullable=False, comment='版本记录ID')
script_id = Column(String(255), ForeignKey('vwed_script.id', name='vwed_script_version_ibfk_1'), nullable=False, comment='关联的脚本ID')
version = Column(Integer, nullable=False, comment='版本号')
code = Column(LONGTEXT, nullable=False, comment='该版本代码')
change_log = Column(Text, comment='版本变更说明')
created_by = Column(String(255), comment='创建者')
# 关联关系
script = relationship("VWEDScript", back_populates="versions")
def __repr__(self):
return f"<VWEDScriptVersion(script_id='{self.script_id}', version='{self.version}')>"
class VWEDScriptLog(BaseModel):
"""
脚本执行日志模型
对应vwed_script_log表
功能记录脚本执行情况
"""
__tablename__ = 'vwed_script_log'
__table_args__ = {
'mysql_engine': 'InnoDB',
'mysql_charset': 'utf8mb4',
'mysql_collate': 'utf8mb4_general_ci'
}
id = Column(String(255), primary_key=True, nullable=False, comment='日志ID')
script_id = Column(String(255), ForeignKey('vwed_script.id'), nullable=False, comment='关联的脚本ID')
version = Column(Integer, nullable=False, comment='使用的脚本版本')
task_record_id = Column(String(255), comment='关联的任务记录ID')
block_record_id = Column(String(255), comment='关联的任务块记录ID')
input_params = Column(LONGTEXT, comment='输入参数(JSON格式)')
output_result = Column(LONGTEXT, comment='输出结果(JSON格式)')
status = Column(Integer, comment='执行状态(1:成功, 0:失败)')
error_message = Column(Text, comment='错误信息')
execution_time = Column(Integer, comment='执行耗时(毫秒)')
started_on = Column(DateTime, comment='开始时间')
ended_on = Column(DateTime, comment='结束时间')
# 关联关系
script = relationship("VWEDScript", back_populates="logs")
def __repr__(self):
return f"<VWEDScriptLog(id='{self.id}', script_id='{self.script_id}', status='{self.status}')>"

View File

@ -0,0 +1,76 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
API注册记录数据模型
记录脚本注册的API路由信息
"""
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, ForeignKey, JSON
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from .base import Base
class VWEDScriptAPIRegistration(Base):
"""API注册记录模型"""
__tablename__ = 'vwed_script_api_registration'
id = Column(Integer, primary_key=True, autoincrement=True, comment='注册记录ID')
script_id = Column(String(255), nullable=False, comment='脚本实例ID')
file_id = Column(Integer, ForeignKey('vwed_script_file.id'), nullable=False, comment='脚本文件ID')
route_path = Column(String(500), nullable=False, comment='路由路径')
http_method = Column(String(20), nullable=False, comment='HTTP方法: GET, POST, PUT, DELETE等')
handler_name = Column(String(255), nullable=False, comment='处理函数名')
description = Column(Text, comment='接口描述')
parameters = Column(JSON, comment='接口参数定义')
response_schema = Column(JSON, comment='响应格式定义')
is_active = Column(Boolean, default=True, comment='是否激活')
call_count = Column(Integer, default=0, comment='调用次数')
last_called_at = Column(DateTime, comment='最后调用时间')
average_response_time_ms = Column(Integer, comment='平均响应时间(毫秒)')
created_at = Column(DateTime, nullable=False, default=func.now(), comment='注册时间')
# 关联关系
script_file = relationship("VWEDScriptFile", backref="api_registrations")
def __repr__(self):
return f"<VWEDScriptAPIRegistration(id={self.id}, path='{self.route_path}', method='{self.http_method}')>"
def to_dict(self):
"""转换为字典格式"""
return {
'id': self.id,
'script_id': self.script_id,
'file_id': self.file_id,
'route_path': self.route_path,
'http_method': self.http_method,
'handler_name': self.handler_name,
'description': self.description,
'parameters': self.parameters,
'response_schema': self.response_schema,
'is_active': self.is_active,
'call_count': self.call_count,
'last_called_at': self.last_called_at.isoformat() if self.last_called_at else None,
'average_response_time_ms': self.average_response_time_ms,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
@property
def route_key(self):
"""生成路由键"""
return f"{self.http_method}:{self.route_path}"
def update_call_stats(self, response_time_ms):
"""更新调用统计"""
import datetime
self.call_count += 1
self.last_called_at = datetime.datetime.now()
if self.average_response_time_ms is None:
self.average_response_time_ms = response_time_ms
else:
# 计算新的平均响应时间
total_time = self.average_response_time_ms * (self.call_count - 1) + response_time_ms
self.average_response_time_ms = int(total_time / self.call_count)

View File

@ -0,0 +1,81 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
脚本执行日志数据模型
记录脚本执行过程结果和性能指标
"""
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, ForeignKey, Float, JSON
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from .base import Base
class VWEDScriptExecutionLog(Base):
"""脚本执行日志模型"""
__tablename__ = 'vwed_script_execution_log'
id = Column(Integer, primary_key=True, autoincrement=True, comment='日志ID')
script_id = Column(String(255), nullable=False, comment='脚本实例ID')
file_id = Column(Integer, ForeignKey('vwed_script_file.id'), nullable=False, comment='脚本文件ID')
execution_type = Column(String(50), nullable=False, comment='执行类型: start_service, stop_service, function_call, api_call')
function_name = Column(String(255), comment='执行的函数名')
input_params = Column(JSON, comment='输入参数')
output_result = Column(JSON, comment='输出结果')
status = Column(String(20), nullable=False, comment='执行状态: running, success, failed, timeout')
error_message = Column(Text, comment='错误信息')
error_traceback = Column(Text, comment='错误堆栈')
start_time = Column(DateTime, nullable=False, default=func.now(), comment='开始时间')
end_time = Column(DateTime, comment='结束时间')
duration_ms = Column(Integer, comment='执行时长(毫秒)')
memory_usage_mb = Column(Float, comment='内存使用(MB)')
cpu_usage_percent = Column(Float, comment='CPU使用率(%)')
log_level = Column(String(20), default='INFO', comment='日志级别: DEBUG, INFO, WARNING, ERROR')
tags = Column(JSON, comment='标签信息')
created_at = Column(DateTime, nullable=False, default=func.now(), comment='创建时间')
# 关联关系
script_file = relationship("VWEDScriptFile", backref="execution_logs")
def __repr__(self):
return f"<VWEDScriptExecutionLog(id={self.id}, script_id='{self.script_id}', status='{self.status}')>"
def to_dict(self):
"""转换为字典格式"""
return {
'id': self.id,
'script_id': self.script_id,
'file_id': self.file_id,
'execution_type': self.execution_type,
'function_name': self.function_name,
'input_params': self.input_params,
'output_result': self.output_result,
'status': self.status,
'error_message': self.error_message,
'error_traceback': self.error_traceback,
'start_time': self.start_time.isoformat() if self.start_time else None,
'end_time': self.end_time.isoformat() if self.end_time else None,
'duration_ms': self.duration_ms,
'memory_usage_mb': self.memory_usage_mb,
'cpu_usage_percent': self.cpu_usage_percent,
'log_level': self.log_level,
'tags': self.tags,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
def mark_completed(self, success=True, result=None, error=None):
"""标记执行完成"""
import datetime
self.end_time = datetime.datetime.now()
if self.start_time:
delta = self.end_time - self.start_time
self.duration_ms = int(delta.total_seconds() * 1000)
if success:
self.status = 'success'
self.output_result = result
else:
self.status = 'failed'
self.error_message = str(error) if error else '未知错误'

View File

@ -0,0 +1,67 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
脚本文件数据模型
管理脚本文件的内容版本和元数据
"""
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from .base import Base
class VWEDScriptFile(Base):
"""脚本文件模型"""
__tablename__ = 'vwed_script_file'
id = Column(Integer, primary_key=True, autoincrement=True, comment='文件ID')
project_id = Column(Integer, ForeignKey('vwed_script_project.id'), nullable=False, comment='所属项目ID')
file_name = Column(String(255), nullable=False, comment='文件名')
file_path = Column(String(500), nullable=False, comment='文件相对路径')
file_type = Column(String(20), nullable=False, default='python', comment='文件类型: python, text, json等')
content = Column(Text, comment='文件内容')
size = Column(Integer, default=0, comment='文件大小(字节)')
encoding = Column(String(20), default='utf-8', comment='文件编码')
is_directory = Column(Boolean, default=False, comment='是否为目录')
is_executable = Column(Boolean, default=False, comment='是否可执行')
has_boot_function = Column(Boolean, default=False, comment='是否包含boot函数')
status = Column(String(20), nullable=False, default='active', comment='文件状态: active, deleted')
created_by = Column(String(100), comment='创建者')
created_at = Column(DateTime, nullable=False, default=func.now(), comment='创建时间')
updated_at = Column(DateTime, nullable=False, default=func.now(), onupdate=func.now(), comment='更新时间')
# 关联关系
project = relationship("VWEDScriptProject", backref="files")
def __repr__(self):
return f"<VWEDScriptFile(id={self.id}, name='{self.file_name}', path='{self.file_path}')>"
def to_dict(self):
"""转换为字典格式"""
return {
'id': self.id,
'project_id': self.project_id,
'file_name': self.file_name,
'file_path': self.file_path,
'file_type': self.file_type,
'content': self.content,
'size': self.size,
'encoding': self.encoding,
'is_directory': self.is_directory,
'is_executable': self.is_executable,
'has_boot_function': self.has_boot_function,
'status': self.status,
'created_by': self.created_by,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
}
@property
def full_path(self):
"""获取完整文件路径"""
if self.project and self.project.project_path:
return f"{self.project.project_path}/{self.file_path}"
return self.file_path

View File

@ -0,0 +1,90 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
函数注册记录数据模型
记录脚本注册的自定义函数信息
"""
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, ForeignKey, JSON
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from .base import Base
class VWEDScriptFunctionRegistration(Base):
"""函数注册记录模型"""
__tablename__ = 'vwed_script_function_registration'
id = Column(Integer, primary_key=True, autoincrement=True, comment='注册记录ID')
script_id = Column(String(255), nullable=False, comment='脚本实例ID')
file_id = Column(Integer, ForeignKey('vwed_script_file.id'), nullable=False, comment='脚本文件ID')
function_name = Column(String(255), nullable=False, comment='函数名')
handler_name = Column(String(255), nullable=False, comment='处理函数名(实际函数名)')
description = Column(Text, comment='函数描述')
parameters = Column(JSON, comment='参数定义: [{"name": "param1", "type": "str", "required": true}]')
return_schema = Column(JSON, comment='返回值格式定义')
is_async = Column(Boolean, default=False, comment='是否为异步函数')
is_active = Column(Boolean, default=True, comment='是否激活')
call_count = Column(Integer, default=0, comment='调用次数')
last_called_at = Column(DateTime, comment='最后调用时间')
average_execution_time_ms = Column(Integer, comment='平均执行时间(毫秒)')
success_count = Column(Integer, default=0, comment='成功执行次数')
error_count = Column(Integer, default=0, comment='错误次数')
tags = Column(JSON, comment='函数标签')
created_at = Column(DateTime, nullable=False, default=func.now(), comment='注册时间')
# 关联关系
script_file = relationship("VWEDScriptFile", backref="function_registrations")
def __repr__(self):
return f"<VWEDScriptFunctionRegistration(id={self.id}, name='{self.function_name}')>"
def to_dict(self):
"""转换为字典格式"""
return {
'id': self.id,
'script_id': self.script_id,
'file_id': self.file_id,
'function_name': self.function_name,
'handler_name': self.handler_name,
'description': self.description,
'parameters': self.parameters,
'return_schema': self.return_schema,
'is_async': self.is_async,
'is_active': self.is_active,
'call_count': self.call_count,
'last_called_at': self.last_called_at.isoformat() if self.last_called_at else None,
'average_execution_time_ms': self.average_execution_time_ms,
'success_count': self.success_count,
'error_count': self.error_count,
'success_rate': self.success_rate,
'tags': self.tags,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
@property
def success_rate(self):
"""计算成功率"""
if self.call_count == 0:
return 0.0
return round((self.success_count / self.call_count) * 100, 2)
def update_call_stats(self, execution_time_ms, success=True):
"""更新调用统计"""
import datetime
self.call_count += 1
self.last_called_at = datetime.datetime.now()
if success:
self.success_count += 1
else:
self.error_count += 1
# 更新平均执行时间
if self.average_execution_time_ms is None:
self.average_execution_time_ms = execution_time_ms
else:
total_time = self.average_execution_time_ms * (self.call_count - 1) + execution_time_ms
self.average_execution_time_ms = int(total_time / self.call_count)

View File

@ -0,0 +1,42 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
脚本项目数据模型
管理脚本项目的基本信息和目录结构
"""
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean
from sqlalchemy.sql import func
from .base import Base
class VWEDScriptProject(Base):
"""脚本项目模型"""
__tablename__ = 'vwed_script_project'
id = Column(Integer, primary_key=True, autoincrement=True, comment='项目ID')
project_name = Column(String(100), nullable=False, comment='项目名称')
project_path = Column(String(500), nullable=False, unique=True, comment='项目路径')
description = Column(Text, comment='项目描述')
status = Column(String(20), nullable=False, default='active', comment='项目状态: active, archived, deleted')
created_by = Column(String(100), comment='创建者')
created_at = Column(DateTime, nullable=False, default=func.now(), comment='创建时间')
updated_at = Column(DateTime, nullable=False, default=func.now(), onupdate=func.now(), comment='更新时间')
def __repr__(self):
return f"<VWEDScriptProject(id={self.id}, name='{self.project_name}', path='{self.project_path}')>"
def to_dict(self):
"""转换为字典格式"""
return {
'id': self.id,
'project_name': self.project_name,
'project_path': self.project_path,
'description': self.description,
'status': self.status,
'created_by': self.created_by,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
}

View File

@ -0,0 +1,108 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
脚本注册中心数据模型
记录脚本实例的运行状态和注册信息
"""
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, ForeignKey, JSON
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from .base import Base
class VWEDScriptRegistry(Base):
"""脚本注册中心模型"""
__tablename__ = 'vwed_script_registry'
id = Column(Integer, primary_key=True, autoincrement=True, comment='注册记录ID')
script_id = Column(String(255), nullable=False, unique=True, comment='脚本实例ID')
file_id = Column(Integer, ForeignKey('vwed_script_file.id'), nullable=False, comment='脚本文件ID')
script_path = Column(String(500), nullable=False, comment='脚本相对路径')
status = Column(String(20), nullable=False, default='starting', comment='运行状态: starting, running, stopping, stopped, error')
process_id = Column(String(50), comment='进程ID')
host_info = Column(JSON, comment='主机信息')
start_params = Column(JSON, comment='启动参数')
resource_limits = Column(JSON, comment='资源限制配置')
performance_stats = Column(JSON, comment='性能统计信息')
error_message = Column(Text, comment='错误信息')
api_count = Column(Integer, default=0, comment='注册的API数量')
function_count = Column(Integer, default=0, comment='注册的函数数量')
event_count = Column(Integer, default=0, comment='注册的事件监听器数量')
timer_count = Column(Integer, default=0, comment='注册的定时任务数量')
last_heartbeat = Column(DateTime, comment='最后心跳时间')
started_at = Column(DateTime, nullable=False, default=func.now(), comment='启动时间')
stopped_at = Column(DateTime, comment='停止时间')
created_at = Column(DateTime, nullable=False, default=func.now(), comment='创建时间')
updated_at = Column(DateTime, nullable=False, default=func.now(), onupdate=func.now(), comment='更新时间')
# 关联关系
script_file = relationship("VWEDScriptFile", backref="registry_records")
def __repr__(self):
return f"<VWEDScriptRegistry(id={self.id}, script_id='{self.script_id}', status='{self.status}')>"
def to_dict(self):
"""转换为字典格式"""
return {
'id': self.id,
'script_id': self.script_id,
'file_id': self.file_id,
'script_path': self.script_path,
'status': self.status,
'process_id': self.process_id,
'host_info': self.host_info,
'start_params': self.start_params,
'resource_limits': self.resource_limits,
'performance_stats': self.performance_stats,
'error_message': self.error_message,
'registrations': {
'apis': self.api_count,
'functions': self.function_count,
'events': self.event_count,
'timers': self.timer_count
},
'last_heartbeat': self.last_heartbeat.isoformat() if self.last_heartbeat else None,
'started_at': self.started_at.isoformat() if self.started_at else None,
'stopped_at': self.stopped_at.isoformat() if self.stopped_at else None,
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
}
@property
def uptime_seconds(self):
"""计算运行时长(秒)"""
if not self.started_at:
return 0
import datetime
end_time = self.stopped_at or datetime.datetime.now()
delta = end_time - self.started_at
return int(delta.total_seconds())
@property
def is_running(self):
"""判断是否正在运行"""
return self.status in ['starting', 'running']
def update_heartbeat(self):
"""更新心跳时间"""
import datetime
self.last_heartbeat = datetime.datetime.now()
def update_registration_counts(self, apis=0, functions=0, events=0, timers=0):
"""更新注册统计"""
self.api_count = apis
self.function_count = functions
self.event_count = events
self.timer_count = timers
def mark_stopped(self, error_message=None):
"""标记为已停止"""
import datetime
self.status = 'error' if error_message else 'stopped'
self.stopped_at = datetime.datetime.now()
if error_message:
self.error_message = error_message

1723
docs/CODING_STANDARDS.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,773 @@
# VWED脚本注册声明示例文档
## 概述
本文档详细说明了在VWED在线脚本中如何注册和声明各种类型的内置函数包括API接口、自定义函数、事件监听器和定时任务。
---
## 1. API接口注册示例
### 1.1 基本GET接口
```python
def boot():
"""脚本启动函数"""
# 注册GET接口 - 传参数和定义执行逻辑分开
VWED.api.register_route(
path="/hello",
method="GET",
handler=hello_handler,
description="问候接口"
)
def hello_handler(request_data):
"""处理问候请求"""
name = request_data["query_params"].get("name", "World")
return {"message": f"Hello, {name}!"}
```
**请求示例**: `GET /api/dynamic/hello?name=张三`
**响应示例**:
```json
{
"message": "Hello, 张三!"
}
```
### 1.2 带参数定义的POST接口
```python
def boot():
# 注册POST接口简化参数定义 - 传参数和定义执行逻辑分开
VWED.api.register_route(
path="/calculate",
method="POST",
handler=calculate_handler,
description="数学计算接口",
params={"a": 0, "b": 0, "operation": "add"} # 简化的参数定义:键值对形式的默认值
)
def calculate_handler(a: int, b: int, operation: str):
"""数学计算处理器 - 直接接收参数"""
# 执行计算
if operation == "add":
result = a + b
elif operation == "subtract":
result = a - b
elif operation == "multiply":
result = a * b
elif operation == "divide":
result = a / b if b != 0 else None
else:
result = None
# 记录日志
VWED.log.sync_info(f"计算执行: {a} {operation} {b} = {result}")
return {
"result": result,
"operation": operation,
"operand_a": a,
"operand_b": b,
"timestamp": VWED.util.now()
}
```
**请求示例**:
```bash
POST /api/dynamic/calculate
Content-Type: application/json
{
"a": 10,
"b": 5,
"operation": "multiply"
}
```
### 1.3 数据处理接口
```python
def boot():
# 注册数据处理接口 - 传参数和定义执行逻辑分开
VWED.api.register_route(
path="/process-data",
method="POST",
handler=process_data_handler,
description="数据处理接口",
params={"data_list": [], "process_type": "filter"} # 简化的参数定义
)
def process_data_handler(data_list: list, process_type: str):
"""处理批量数据 - 直接接收参数"""
if process_type == "filter":
# 过滤正数
result = [x for x in data_list if isinstance(x, (int, float)) and x > 0]
elif process_type == "sort":
# 排序
result = sorted(data_list)
elif process_type == "sum":
# 求和
result = sum(x for x in data_list if isinstance(x, (int, float)))
else:
result = data_list
VWED.log.sync_info(f"数据处理完成: {process_type}, 输入{len(data_list)}项,输出{len(result) if isinstance(result, list) else 1}项")
return {
"original_data": data_list,
"processed_data": result,
"process_type": process_type,
"input_count": len(data_list),
"output_count": len(result) if isinstance(result, list) else 1
}
```
---
## 2. 自定义函数注册示例
### 2.1 简单计算函数
```python
def boot():
# 注册简单函数 - 传参数和定义执行逻辑分开
VWED.function.register(
name="simple_add",
handler=simple_add,
description="简单加法函数",
params={"a": 0, "b": 0} # 简化的参数定义
)
def simple_add(a: int, b: int):
"""供VWED任务调用的加法函数 - 直接接收参数"""
result = a + b
VWED.log.sync_info(f"执行加法: {a} + {b} = {result}")
return {"result": result}
```
**VWED任务中的调用方式**:
```json
{
"block_type": "script",
"execution_mode": "function_call",
"function_name": "simple_add",
"function_args": {
"a": 10,
"b": 20
}
}
```
### 2.2 带多个参数的函数
```python
def boot():
# 注册多参数函数 - 简化的参数定义
VWED.function.register(
name="data_statistics",
handler=data_statistics,
description="数据统计函数",
params={"numbers": [1, 2, 3], "operation": "sum", "round_digits": 2} # 简化的参数定义
)
def data_statistics(numbers: list, operation: str, round_digits: int):
"""数据统计函数 - 直接接收参数"""
if not numbers:
return {"error": "数字数组不能为空"}
# 执行计算
if operation == "sum":
result = sum(numbers)
elif operation == "avg":
result = sum(numbers) / len(numbers)
elif operation == "max":
result = max(numbers)
elif operation == "min":
result = min(numbers)
elif operation == "median":
sorted_numbers = sorted(numbers)
n = len(sorted_numbers)
if n % 2 == 0:
result = (sorted_numbers[n//2-1] + sorted_numbers[n//2]) / 2
else:
result = sorted_numbers[n//2]
else:
return {"error": f"不支持的运算类型: {operation}"}
# 四舍五入
if isinstance(result, float):
result = round(result, round_digits)
VWED.log.sync_info(f"数据统计: {operation}({numbers}) = {result}")
return {
"result": result,
"operation": operation,
"input_count": len(numbers),
"input_numbers": numbers
}
```
### 2.3 异步函数示例
```python
def boot():
# 注册异步函数 - 简化的参数定义
VWED.function.register(
name="async_data_fetch",
handler=async_data_fetch,
description="异步数据获取函数",
params={"source": "database", "delay": 1} # 简化的参数定义
)
async def async_data_fetch(source: str, delay: int):
"""异步获取和处理数据 - 直接接收参数"""
await VWED.log.info(f"开始异步数据获取: {source}")
# 模拟异步操作
await VWED.util.async_sleep(delay)
# 模拟数据获取
mock_data = {
"database": [{"id": 1, "name": "数据1"}, {"id": 2, "name": "数据2"}],
"api": [{"status": "success", "data": "API数据"}],
"file": [{"content": "文件内容", "size": 1024}]
}
data = mock_data.get(source, [])
await VWED.log.info(f"异步数据获取完成: {source}, 获取到{len(data)}条记录")
return {
"source": source,
"data": data,
"count": len(data),
"fetch_time": delay,
"completed_at": VWED.util.now()
}
```
### 2.4 数据验证函数
```python
def boot():
# 注册数据验证函数 - 简化的参数定义
VWED.function.register(
name="validate_user_data",
handler=validate_user_data,
description="用户数据验证函数",
params={"user_data": {"name": "", "age": 0, "email": ""}, "strict_mode": False} # 简化的参数定义
)
def validate_user_data(user_data: dict, strict_mode: bool):
"""验证用户数据 - 直接接收参数"""
errors = []
warnings = []
# 必填字段检查
if not user_data.get("name"):
errors.append("姓名是必填字段")
# 年龄验证
age = user_data.get("age")
if age is not None:
if not isinstance(age, int) or age < 0:
errors.append("年龄必须是非负整数")
elif age > 150:
warnings.append("年龄超过150岁请确认")
# 邮箱验证
email = user_data.get("email")
if email:
if "@" not in email or "." not in email:
if strict_mode:
errors.append("邮箱格式不正确")
else:
warnings.append("邮箱格式可能不正确")
# 记录验证结果
is_valid = len(errors) == 0
VWED.log.sync_info(f"用户数据验证: {'通过' if is_valid else '失败'}, 错误{len(errors)}个,警告{len(warnings)}个")
return {
"is_valid": is_valid,
"errors": errors,
"warnings": warnings,
"validated_fields": list(user_data.keys()),
"strict_mode": strict_mode
}
```
---
## 3. 事件监听器注册示例
### 3.1 基本事件监听器
```python
def boot():
# 注册基本事件监听器 - 传参数和定义执行逻辑分开
VWED.event.listen(
event_name="user_login",
handler=on_user_login
)
def on_user_login(event_data):
"""用户登录事件处理"""
user_id = event_data.get("user_id", "unknown")
login_time = event_data.get("login_time", VWED.util.now())
VWED.log.sync_info(f"用户登录: {user_id} at {login_time}")
# 更新登录统计
login_count = VWED.data.get("login_count", 0)
VWED.data.set("login_count", login_count + 1)
# 记录最近登录用户
recent_logins = VWED.data.get("recent_logins", [])
recent_logins.append({"user_id": user_id, "time": login_time})
# 只保留最近10条记录
if len(recent_logins) > 10:
recent_logins = recent_logins[-10:]
VWED.data.set("recent_logins", recent_logins)
```
### 3.2 高优先级事件监听器
```python
def boot():
# 注册高优先级事件监听器
@VWED.event.listen("system_error", priority=0) # 优先级0最高
def on_system_error(event_data):
"""系统错误事件处理(高优先级)"""
error_type = event_data.get("error_type", "unknown")
error_message = event_data.get("error_message", "")
VWED.log.sync_error(f"系统错误: {error_type} - {error_message}")
# 错误统计
error_count = VWED.data.get("error_count", 0)
VWED.data.set("error_count", error_count + 1)
# 如果是严重错误,触发告警
if error_type in ["database_down", "memory_overflow", "security_breach"]:
# 触发告警事件
asyncio.create_task(VWED.event.emit("critical_alert", {
"level": "critical",
"source": "system_monitor",
"error_type": error_type,
"message": error_message,
"timestamp": VWED.util.now()
}))
```
### 3.3 任务完成事件监听器
```python
def boot():
# 监听VWED任务完成事件
@VWED.event.listen("task_completed", priority=1)
def on_task_completed(event_data):
"""任务完成事件处理"""
task_id = event_data.get("task_id", "unknown")
task_status = event_data.get("status", "unknown")
execution_time = event_data.get("execution_time_ms", 0)
VWED.log.sync_info(f"任务完成: {task_id}, 状态: {task_status}, 耗时: {execution_time}ms")
# 更新任务统计
completed_tasks = VWED.data.get("completed_tasks", [])
completed_tasks.append({
"task_id": task_id,
"status": task_status,
"execution_time_ms": execution_time,
"completed_at": VWED.util.now()
})
# 只保留最近100条记录
if len(completed_tasks) > 100:
completed_tasks = completed_tasks[-100:]
VWED.data.set("completed_tasks", completed_tasks)
# 如果任务执行时间过长,记录预警
if execution_time > 30000: # 超过30秒
VWED.log.sync_warning(f"任务执行时间过长: {task_id} ({execution_time}ms)")
```
---
## 4. 定时任务注册示例
### 4.1 周期性定时任务
```python
def boot():
# 注册周期性定时任务 - 每分钟执行一次 - 传参数和定义执行逻辑分开
VWED.timer.interval(
interval=60, # 60秒间隔
handler=system_monitor
)
def system_monitor():
"""系统监控定时任务"""
# 获取系统统计信息
stats = {
"login_count": VWED.data.get("login_count", 0),
"error_count": VWED.data.get("error_count", 0),
"completed_tasks": len(VWED.data.get("completed_tasks", [])),
"check_time": VWED.util.now()
}
VWED.log.sync_info(f"系统监控: 登录{stats['login_count']}次, 错误{stats['error_count']}个, 完成任务{stats['completed_tasks']}个")
# 保存监控记录
monitor_history = VWED.data.get("monitor_history", [])
monitor_history.append(stats)
# 只保留最近24小时的记录
if len(monitor_history) > 1440: # 24*60分钟
monitor_history = monitor_history[-1440:]
VWED.data.set("monitor_history", monitor_history)
```
### 4.2 一次性延迟任务
```python
def boot():
# 注册一次性延迟任务 - 10秒后执行
@VWED.timer.once(delay=10)
def initialization_task():
"""初始化任务(延迟执行)"""
VWED.log.sync_info("执行初始化任务")
# 初始化数据结构
if not VWED.data.has("initialized"):
VWED.data.set("login_count", 0)
VWED.data.set("error_count", 0)
VWED.data.set("completed_tasks", [])
VWED.data.set("monitor_history", [])
VWED.data.set("initialized", True)
VWED.data.set("init_time", VWED.util.now())
VWED.log.sync_info("系统数据初始化完成")
```
### 4.3 数据清理定时任务
```python
def boot():
# 注册数据清理任务 - 每小时执行一次
@VWED.timer.interval(3600) # 3600秒 = 1小时
def data_cleanup():
"""数据清理定时任务"""
VWED.log.sync_info("开始执行数据清理")
cleanup_count = 0
# 清理过期的监控记录
monitor_history = VWED.data.get("monitor_history", [])
original_count = len(monitor_history)
# 只保留最近12小时的记录
monitor_history = monitor_history[-720:] # 12*60分钟
VWED.data.set("monitor_history", monitor_history)
cleanup_count += original_count - len(monitor_history)
# 清理过期的任务记录
completed_tasks = VWED.data.get("completed_tasks", [])
original_count = len(completed_tasks)
# 只保留最近50条记录
completed_tasks = completed_tasks[-50:]
VWED.data.set("completed_tasks", completed_tasks)
cleanup_count += original_count - len(completed_tasks)
# 清理过期的登录记录
recent_logins = VWED.data.get("recent_logins", [])
original_count = len(recent_logins)
# 只保留最近20条记录
recent_logins = recent_logins[-20:]
VWED.data.set("recent_logins", recent_logins)
cleanup_count += original_count - len(recent_logins)
VWED.log.sync_info(f"数据清理完成,清理了{cleanup_count}条过期记录")
# 记录清理统计
cleanup_stats = VWED.data.get("cleanup_stats", [])
cleanup_stats.append({
"cleanup_time": VWED.util.now(),
"cleaned_records": cleanup_count
})
# 只保留最近10次清理记录
if len(cleanup_stats) > 10:
cleanup_stats = cleanup_stats[-10:]
VWED.data.set("cleanup_stats", cleanup_stats)
```
---
## 5. 完整的脚本示例
以下是一个包含所有类型注册的完整脚本示例:
```python
def boot():
"""脚本启动函数 - 注册所有服务"""
VWED.log.sync_info("=== 开始注册脚本服务 ===")
# 注册API接口
register_api_services()
# 注册自定义函数
register_custom_functions()
# 注册事件监听器
register_event_listeners()
# 注册定时任务
register_timer_tasks()
VWED.log.sync_info("=== 脚本服务注册完成 ===")
def register_api_services():
"""注册API服务"""
VWED.log.sync_info("注册API接口...")
# 传参数和定义执行逻辑分开
VWED.api.register_route(
path="/status",
method="GET",
handler=get_system_status,
description="系统状态查询接口"
)
VWED.api.register_route(
path="/trigger-event",
method="POST",
handler=trigger_event,
description="手动触发事件接口"
)
def get_system_status(request_data):
return {
"status": "running",
"timestamp": VWED.util.now(),
"script_id": VWED.get_script_id(),
"stats": {
"login_count": VWED.data.get("login_count", 0),
"error_count": VWED.data.get("error_count", 0),
"task_count": len(VWED.data.get("completed_tasks", []))
}
}
def trigger_event(request_data):
event_name = request_data["body"].get("event_name")
event_data = request_data["body"].get("event_data", {})
if event_name:
# 异步触发事件
asyncio.create_task(VWED.event.emit(event_name, event_data))
return {"success": True, "event_triggered": event_name}
else:
return {"success": False, "error": "event_name is required"}
def register_custom_functions():
"""注册自定义函数"""
VWED.log.sync_info("注册自定义函数...")
# 传参数和定义执行逻辑分开
VWED.function.register(
name="get_statistics",
handler=get_statistics,
description="获取系统统计信息"
)
VWED.function.register(
name="reset_counters",
handler=reset_counters,
description="重置计数器"
)
def get_statistics(args):
return {
"login_count": VWED.data.get("login_count", 0),
"error_count": VWED.data.get("error_count", 0),
"completed_tasks": len(VWED.data.get("completed_tasks", [])),
"monitor_records": len(VWED.data.get("monitor_history", [])),
"initialized": VWED.data.get("initialized", False),
"init_time": VWED.data.get("init_time", None)
}
def reset_counters(args):
VWED.data.set("login_count", 0)
VWED.data.set("error_count", 0)
VWED.data.set("completed_tasks", [])
VWED.log.sync_info("计数器已重置")
return {"success": True, "reset_time": VWED.util.now()}
def register_event_listeners():
"""注册事件监听器"""
VWED.log.sync_info("注册事件监听器...")
# 传参数和定义执行逻辑分开
VWED.event.listen(
event_name="user_action",
handler=on_user_action
)
def on_user_action(event_data):
action = event_data.get("action", "unknown")
user_id = event_data.get("user_id", "unknown")
VWED.log.sync_info(f"用户操作: {user_id} 执行了 {action}")
# 记录用户操作
user_actions = VWED.data.get("user_actions", [])
user_actions.append({
"user_id": user_id,
"action": action,
"timestamp": VWED.util.now()
})
# 只保留最近50条记录
if len(user_actions) > 50:
user_actions = user_actions[-50:]
VWED.data.set("user_actions", user_actions)
def register_timer_tasks():
"""注册定时任务"""
VWED.log.sync_info("注册定时任务...")
# 传参数和定义执行逻辑分开
VWED.timer.interval(
interval=30, # 每30秒
handler=heartbeat
)
VWED.timer.once(
delay=5, # 5秒后执行一次
handler=delayed_initialization
)
def heartbeat():
heartbeat_count = VWED.data.get("heartbeat_count", 0)
VWED.data.set("heartbeat_count", heartbeat_count + 1)
if heartbeat_count % 10 == 0: # 每300秒5分钟记录一次
VWED.log.sync_info(f"系统心跳 #{heartbeat_count}")
def delayed_initialization():
VWED.log.sync_info("延迟初始化任务执行")
VWED.data.set("delayed_init_completed", True)
# 工具函数示例
def format_timestamp(timestamp_str):
"""格式化时间戳"""
try:
from datetime import datetime
dt = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
return dt.strftime("%Y-%m-%d %H:%M:%S")
except:
return timestamp_str
def calculate_average(numbers):
"""计算平均值"""
if not numbers:
return 0
return sum(numbers) / len(numbers)
```
---
## 6. 注册验证和调试技巧
### 6.1 验证注册状态
在脚本中可以添加验证代码来检查注册状态:
```python
def boot():
# ... 注册代码 ...
# 验证注册状态
verify_registrations()
def verify_registrations():
"""验证注册状态"""
# 可以通过API查询注册状态
# GET /api/script/registry/status
VWED.log.sync_info("验证注册状态...")
# 这里可以添加自定义验证逻辑
expected_functions = ["get_statistics", "reset_counters"]
expected_apis = ["/status", "/trigger-event"]
VWED.log.sync_info(f"预期注册函数: {expected_functions}")
VWED.log.sync_info(f"预期注册API: {expected_apis}")
```
### 6.2 调试技巧
```python
def boot():
try:
# 注册代码
register_all_services()
VWED.log.sync_info("✓ 所有服务注册成功")
except Exception as e:
VWED.log.sync_error(f"✗ 服务注册失败: {str(e)}")
# 可以根据需要添加详细的错误处理
def register_all_services():
"""集中注册所有服务"""
service_count = 0
# 注册API带计数
api_services = register_api_services()
service_count += api_services
# 注册函数(带计数)
function_services = register_custom_functions()
service_count += function_services
# 注册事件监听器(带计数)
event_services = register_event_listeners()
service_count += event_services
# 注册定时任务(带计数)
timer_services = register_timer_tasks()
service_count += timer_services
VWED.log.sync_info(f"总共注册了 {service_count} 个服务")
return service_count
```
---
## 总结
本文档提供了VWED脚本中各种注册声明的完整示例包括
1. **API接口注册** - 支持GET/POST/PUT/DELETE等HTTP方法
2. **自定义函数注册** - 供VWED任务系统调用的函数
3. **事件监听器注册** - 响应系统和自定义事件
4. **定时任务注册** - 周期性和一次性任务
每种类型都提供了从简单到复杂的多个示例帮助开发者快速掌握VWED脚本的注册机制。
关键要点:
- 所有注册都必须在 `boot()` 函数中进行
- 使用装饰器语法进行注册声明
- 支持详细的参数定义和文档描述
- 提供完整的错误处理和日志记录
- 支持异步和同步两种执行模式

13840
logs/app.log

File diff suppressed because it is too large Load Diff

3504
logs/app.log.2025-09-11 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,189 @@
"""创建脚本引擎相关表
Revision ID: 003
Revises: 002
Create Date: 2025-01-11 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '003'
down_revision = '002'
branch_labels = None
depends_on = None
def upgrade():
"""创建脚本引擎相关表"""
# 创建脚本项目表
op.create_table('vwed_script_project',
sa.Column('id', sa.Integer(), nullable=False, comment='项目ID'),
sa.Column('project_name', sa.String(100), nullable=False, comment='项目名称'),
sa.Column('project_path', sa.String(500), nullable=False, comment='项目路径'),
sa.Column('description', sa.Text(), nullable=True, comment='项目描述'),
sa.Column('status', sa.String(20), nullable=False, server_default='active', comment='项目状态'),
sa.Column('created_by', sa.String(100), nullable=True, comment='创建者'),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('NOW()'), comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('NOW() ON UPDATE NOW()'), comment='更新时间'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('project_path'),
comment='脚本项目表'
)
# 创建脚本文件表
op.create_table('vwed_script_file',
sa.Column('id', sa.Integer(), nullable=False, comment='文件ID'),
sa.Column('project_id', sa.Integer(), nullable=False, comment='所属项目ID'),
sa.Column('file_name', sa.String(255), nullable=False, comment='文件名'),
sa.Column('file_path', sa.String(500), nullable=False, comment='文件相对路径'),
sa.Column('file_type', sa.String(20), nullable=False, server_default='python', comment='文件类型'),
sa.Column('content', sa.Text(), nullable=True, comment='文件内容'),
sa.Column('size', sa.Integer(), nullable=True, server_default='0', comment='文件大小'),
sa.Column('encoding', sa.String(20), nullable=True, server_default='utf-8', comment='文件编码'),
sa.Column('is_directory', sa.Boolean(), nullable=True, server_default='0', comment='是否为目录'),
sa.Column('is_executable', sa.Boolean(), nullable=True, server_default='0', comment='是否可执行'),
sa.Column('has_boot_function', sa.Boolean(), nullable=True, server_default='0', comment='是否包含boot函数'),
sa.Column('status', sa.String(20), nullable=False, server_default='active', comment='文件状态'),
sa.Column('created_by', sa.String(100), nullable=True, comment='创建者'),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('NOW()'), comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('NOW() ON UPDATE NOW()'), comment='更新时间'),
sa.ForeignKeyConstraint(['project_id'], ['vwed_script_project.id'], ),
sa.PrimaryKeyConstraint('id'),
comment='脚本文件表'
)
# 创建脚本注册中心表
op.create_table('vwed_script_registry',
sa.Column('id', sa.Integer(), nullable=False, comment='注册记录ID'),
sa.Column('script_id', sa.String(255), nullable=False, comment='脚本实例ID'),
sa.Column('file_id', sa.Integer(), nullable=False, comment='脚本文件ID'),
sa.Column('script_path', sa.String(500), nullable=False, comment='脚本相对路径'),
sa.Column('status', sa.String(20), nullable=False, server_default='starting', comment='运行状态'),
sa.Column('process_id', sa.String(50), nullable=True, comment='进程ID'),
sa.Column('host_info', sa.JSON(), nullable=True, comment='主机信息'),
sa.Column('start_params', sa.JSON(), nullable=True, comment='启动参数'),
sa.Column('resource_limits', sa.JSON(), nullable=True, comment='资源限制配置'),
sa.Column('performance_stats', sa.JSON(), nullable=True, comment='性能统计信息'),
sa.Column('error_message', sa.Text(), nullable=True, comment='错误信息'),
sa.Column('api_count', sa.Integer(), nullable=True, server_default='0', comment='注册的API数量'),
sa.Column('function_count', sa.Integer(), nullable=True, server_default='0', comment='注册的函数数量'),
sa.Column('event_count', sa.Integer(), nullable=True, server_default='0', comment='注册的事件监听器数量'),
sa.Column('timer_count', sa.Integer(), nullable=True, server_default='0', comment='注册的定时任务数量'),
sa.Column('last_heartbeat', sa.DateTime(), nullable=True, comment='最后心跳时间'),
sa.Column('started_at', sa.DateTime(), nullable=False, server_default=sa.text('NOW()'), comment='启动时间'),
sa.Column('stopped_at', sa.DateTime(), nullable=True, comment='停止时间'),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('NOW()'), comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('NOW() ON UPDATE NOW()'), comment='更新时间'),
sa.ForeignKeyConstraint(['file_id'], ['vwed_script_file.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('script_id'),
comment='脚本注册中心表'
)
# 创建脚本执行日志表
op.create_table('vwed_script_execution_log',
sa.Column('id', sa.Integer(), nullable=False, comment='日志ID'),
sa.Column('script_id', sa.String(255), nullable=False, comment='脚本实例ID'),
sa.Column('file_id', sa.Integer(), nullable=False, comment='脚本文件ID'),
sa.Column('execution_type', sa.String(50), nullable=False, comment='执行类型'),
sa.Column('function_name', sa.String(255), nullable=True, comment='执行的函数名'),
sa.Column('input_params', sa.JSON(), nullable=True, comment='输入参数'),
sa.Column('output_result', sa.JSON(), nullable=True, comment='输出结果'),
sa.Column('status', sa.String(20), nullable=False, comment='执行状态'),
sa.Column('error_message', sa.Text(), nullable=True, comment='错误信息'),
sa.Column('error_traceback', sa.Text(), nullable=True, comment='错误堆栈'),
sa.Column('start_time', sa.DateTime(), nullable=False, server_default=sa.text('NOW()'), comment='开始时间'),
sa.Column('end_time', sa.DateTime(), nullable=True, comment='结束时间'),
sa.Column('duration_ms', sa.Integer(), nullable=True, comment='执行时长'),
sa.Column('memory_usage_mb', sa.Float(), nullable=True, comment='内存使用'),
sa.Column('cpu_usage_percent', sa.Float(), nullable=True, comment='CPU使用率'),
sa.Column('log_level', sa.String(20), nullable=True, server_default='INFO', comment='日志级别'),
sa.Column('tags', sa.JSON(), nullable=True, comment='标签信息'),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('NOW()'), comment='创建时间'),
sa.ForeignKeyConstraint(['file_id'], ['vwed_script_file.id'], ),
sa.PrimaryKeyConstraint('id'),
comment='脚本执行日志表'
)
# 创建API注册记录表
op.create_table('vwed_script_api_registration',
sa.Column('id', sa.Integer(), nullable=False, comment='注册记录ID'),
sa.Column('script_id', sa.String(255), nullable=False, comment='脚本实例ID'),
sa.Column('file_id', sa.Integer(), nullable=False, comment='脚本文件ID'),
sa.Column('route_path', sa.String(500), nullable=False, comment='路由路径'),
sa.Column('http_method', sa.String(20), nullable=False, comment='HTTP方法'),
sa.Column('handler_name', sa.String(255), nullable=False, comment='处理函数名'),
sa.Column('description', sa.Text(), nullable=True, comment='接口描述'),
sa.Column('parameters', sa.JSON(), nullable=True, comment='接口参数定义'),
sa.Column('response_schema', sa.JSON(), nullable=True, comment='响应格式定义'),
sa.Column('is_active', sa.Boolean(), nullable=True, server_default='1', comment='是否激活'),
sa.Column('call_count', sa.Integer(), nullable=True, server_default='0', comment='调用次数'),
sa.Column('last_called_at', sa.DateTime(), nullable=True, comment='最后调用时间'),
sa.Column('average_response_time_ms', sa.Integer(), nullable=True, comment='平均响应时间'),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('NOW()'), comment='注册时间'),
sa.ForeignKeyConstraint(['file_id'], ['vwed_script_file.id'], ),
sa.PrimaryKeyConstraint('id'),
comment='API注册记录表'
)
# 创建函数注册记录表
op.create_table('vwed_script_function_registration',
sa.Column('id', sa.Integer(), nullable=False, comment='注册记录ID'),
sa.Column('script_id', sa.String(255), nullable=False, comment='脚本实例ID'),
sa.Column('file_id', sa.Integer(), nullable=False, comment='脚本文件ID'),
sa.Column('function_name', sa.String(255), nullable=False, comment='函数名'),
sa.Column('handler_name', sa.String(255), nullable=False, comment='处理函数名'),
sa.Column('description', sa.Text(), nullable=True, comment='函数描述'),
sa.Column('parameters', sa.JSON(), nullable=True, comment='参数定义'),
sa.Column('return_schema', sa.JSON(), nullable=True, comment='返回值格式定义'),
sa.Column('is_async', sa.Boolean(), nullable=True, server_default='0', comment='是否为异步函数'),
sa.Column('is_active', sa.Boolean(), nullable=True, server_default='1', comment='是否激活'),
sa.Column('call_count', sa.Integer(), nullable=True, server_default='0', comment='调用次数'),
sa.Column('last_called_at', sa.DateTime(), nullable=True, comment='最后调用时间'),
sa.Column('average_execution_time_ms', sa.Integer(), nullable=True, comment='平均执行时间'),
sa.Column('success_count', sa.Integer(), nullable=True, server_default='0', comment='成功执行次数'),
sa.Column('error_count', sa.Integer(), nullable=True, server_default='0', comment='错误次数'),
sa.Column('tags', sa.JSON(), nullable=True, comment='函数标签'),
sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('NOW()'), comment='注册时间'),
sa.ForeignKeyConstraint(['file_id'], ['vwed_script_file.id'], ),
sa.PrimaryKeyConstraint('id'),
comment='函数注册记录表'
)
# 创建索引
op.create_index('idx_script_file_project_path', 'vwed_script_file', ['project_id', 'file_path'])
op.create_index('idx_script_registry_script_id', 'vwed_script_registry', ['script_id'])
op.create_index('idx_script_registry_status', 'vwed_script_registry', ['status'])
op.create_index('idx_execution_log_script_id', 'vwed_script_execution_log', ['script_id'])
op.create_index('idx_execution_log_status', 'vwed_script_execution_log', ['status'])
op.create_index('idx_api_registration_script_id', 'vwed_script_api_registration', ['script_id'])
op.create_index('idx_api_registration_route', 'vwed_script_api_registration', ['route_path', 'http_method'])
op.create_index('idx_function_registration_script_id', 'vwed_script_function_registration', ['script_id'])
op.create_index('idx_function_registration_name', 'vwed_script_function_registration', ['function_name'])
def downgrade():
"""删除脚本引擎相关表"""
# 删除索引
op.drop_index('idx_function_registration_name', table_name='vwed_script_function_registration')
op.drop_index('idx_function_registration_script_id', table_name='vwed_script_function_registration')
op.drop_index('idx_api_registration_route', table_name='vwed_script_api_registration')
op.drop_index('idx_api_registration_script_id', table_name='vwed_script_api_registration')
op.drop_index('idx_execution_log_status', table_name='vwed_script_execution_log')
op.drop_index('idx_execution_log_script_id', table_name='vwed_script_execution_log')
op.drop_index('idx_script_registry_status', table_name='vwed_script_registry')
op.drop_index('idx_script_registry_script_id', table_name='vwed_script_registry')
op.drop_index('idx_script_file_project_path', table_name='vwed_script_file')
# 删除表(按依赖关系逆序删除)
op.drop_table('vwed_script_function_registration')
op.drop_table('vwed_script_api_registration')
op.drop_table('vwed_script_execution_log')
op.drop_table('vwed_script_registry')
op.drop_table('vwed_script_file')
op.drop_table('vwed_script_project')

View File

@ -12,7 +12,6 @@ from routes.template_api import router as template_router
from routes.task_api import router as task_router
from routes.common_api import router as common_router
from routes.task_edit_api import router as task_edit_router
from routes.script_api import router as script_router
from routes.task_record_api import router as task_record_router
from routes.calldevice_api import router as calldevice_router
from routes.modbus_config_api import router as modbus_config_router
@ -20,6 +19,10 @@ from routes.websocket_api import router as websocket_router
from routes.map_data_api import router as map_data_router
from routes.operate_point_api import router as operate_point_router
from routes.external_task_api import router as external_task_router
from routes.script_api import router as script_router
from routes.script_websocket_api import router as script_websocket_router
from routes.dynamic_api import router as dynamic_api_router
from routes.map_converter_api import router as map_converter_router
# 路由列表,按照注册顺序排列
routers = [
@ -28,14 +31,19 @@ routers = [
template_router,
task_router,
task_edit_router,
script_router,
task_record_router,
calldevice_router,
modbus_config_router,
websocket_router,
map_data_router,
operate_point_router,
external_task_router
external_task_router,
script_router,
script_websocket_router,
# (script_router, "/api/script"), # 脚本管理API
# (script_websocket_router, "/api/script"), # 脚本WebSocket API
dynamic_api_router, # 动态API路由脚本注册的API,
map_converter_router
]
def register_routers(app):
@ -45,8 +53,14 @@ def register_routers(app):
Args:
app: FastAPI应用实例
"""
for router in routers:
app.include_router(router)
for router_config in routers:
if isinstance(router_config, tuple):
# 带前缀的路由
router, prefix = router_config
app.include_router(router, prefix=prefix)
else:
# 普通路由
app.include_router(router_config)
# from fastapi import FastAPI

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More