first commit
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
import os
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from mako.lookup import TemplateLookup
|
||||
import black
|
||||
|
||||
class CodeGenerator:
|
||||
def __init__(self, template_root='templates'):
|
||||
self.template_root = template_root
|
||||
self.lookup = TemplateLookup(directories=[template_root], input_encoding='utf-8')
|
||||
|
||||
def generate_files(self, entity_config, prj_template_dir, params, language='python'):
|
||||
"""
|
||||
生成所有匹配的模板文件
|
||||
:param entity_config: 实体配置
|
||||
:param prj_template_dir: 项目对应的文件路径
|
||||
:param params: 生成参数
|
||||
:return: 生成的文件字典 {相对路径: 文件内容}
|
||||
"""
|
||||
entity_config = type("EntityConfig", (object,), entity_config)
|
||||
generated_files = []
|
||||
template_dir = os.path.join(self.template_root, prj_template_dir)
|
||||
|
||||
# 遍历模板目录
|
||||
for root, _, files in os.walk(template_dir):
|
||||
for file in files:
|
||||
if not file.endswith('.mako'): continue
|
||||
template_path = os.path.join(root, file)
|
||||
relative_path = os.path.relpath(template_path, self.template_root)
|
||||
|
||||
# 渲染模板
|
||||
template = self.lookup.get_template(relative_path)
|
||||
content = template.render(entity=entity_config, params=params, generator=self)
|
||||
|
||||
if relative_path.endswith('.py.mako'):
|
||||
content = self.post_process(content)
|
||||
|
||||
# 计算输出路径
|
||||
output_path = self._get_output_path(relative_path, entity_config, params)
|
||||
# generated_files[output_path] = content
|
||||
generated_files.append(dict(
|
||||
path=output_path,
|
||||
content=content
|
||||
))
|
||||
|
||||
return generated_files
|
||||
|
||||
def post_process(self, content):
|
||||
try:
|
||||
# 尝试使用 Black 格式化代码
|
||||
formatted_code = black.format_str(content, mode=black.Mode(line_length=100))
|
||||
return formatted_code
|
||||
except Exception as e:
|
||||
print(f"Black 格式化失败: {e}")
|
||||
return content
|
||||
|
||||
def _get_output_path(self, template_path, entity_config, params):
|
||||
"""
|
||||
根据模板路径计算输出路径
|
||||
:param template_path: 模板相对路径
|
||||
:return: 输出文件相对路径(无.mako后缀)
|
||||
"""
|
||||
# 移除.mako后缀
|
||||
if template_path.endswith('.mako'):
|
||||
output_path = template_path[:-5]
|
||||
else:
|
||||
output_path = template_path
|
||||
|
||||
# 替换实体名称占位符
|
||||
entity_name = entity_config.name
|
||||
output_path = output_path.replace('Entity', entity_name.capitalize())
|
||||
output_path = output_path.replace('entity', entity_name.lower())
|
||||
|
||||
return output_path
|
||||
|
||||
def _process_relations(self, entity):
|
||||
"""处理实体关系并返回处理后的结果"""
|
||||
if not hasattr(entity, 'relations') or not entity.relations:
|
||||
return []
|
||||
|
||||
processed = []
|
||||
for rel in entity.relations:
|
||||
# 确保关系有必要的字段
|
||||
rel.setdefault('through', None)
|
||||
rel.setdefault('description', '')
|
||||
|
||||
# 计算反向引用名称
|
||||
rel['reverse_name'] = self._get_reverse_relation_name(entity.name, rel)
|
||||
processed.append(rel)
|
||||
return processed
|
||||
|
||||
def _get_reverse_relation_name(self, entity_name, relation):
|
||||
"""生成反向引用名称"""
|
||||
if relation['type'] == 'one-to-many':
|
||||
return entity_name.lower()
|
||||
elif relation['type'] == 'many-to-one':
|
||||
return relation['name']
|
||||
elif relation['type'] == 'many-to-many':
|
||||
return f"{entity_name.lower()}s"
|
||||
return None
|
||||
|
||||
def create_zip(self, generated_files):
|
||||
"""
|
||||
将生成的文件打包为zip
|
||||
:param generated_files: {路径: 内容} 字典
|
||||
:return: zip文件字节流
|
||||
"""
|
||||
zip_buffer = BytesIO()
|
||||
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||
for item in generated_files:
|
||||
path, content = item.get('path'), item.get('content')
|
||||
zip_file.writestr(path, content)
|
||||
zip_buffer.seek(0)
|
||||
return zip_buffer
|
||||
|
||||
def create_local_code(self, generated_files, output_dir):
|
||||
save_dir = Path(output_dir)
|
||||
|
||||
for item in generated_files:
|
||||
# 确保输出目录存在
|
||||
path, content = item.get('path'), item.get('content')
|
||||
output_file = save_dir / path
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8', newline='') as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 使用示例
|
||||
generator = CodeGenerator()
|
||||
|
||||
default_frontend_config = {
|
||||
"editable": True,
|
||||
"listable": True,
|
||||
"detailable": True,
|
||||
"sortable": True,
|
||||
"filterable": True,
|
||||
"filter_operator": 'equal', # equal | contains 默认过滤操作符为包含
|
||||
"display_type": 'text', # 默认显示类型为文本
|
||||
}
|
||||
|
||||
entity_config = {
|
||||
"name": "user", # 实体名称(小写)
|
||||
"author": "Ly997", # 实体名称(小写)
|
||||
"description": "系统用户管理", # 实体描述
|
||||
"version": "1.0.0", # 版本号
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "Long",
|
||||
"primary_key": True,
|
||||
"description": "用户ID",
|
||||
"required": True
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"type": "String",
|
||||
"length": 50,
|
||||
"description": "用户名",
|
||||
"required": True,
|
||||
"unique": True,
|
||||
"validations": [
|
||||
{"type": "min_length", "value": 4},
|
||||
{"type": "max_length", "value": 20}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"type": "String",
|
||||
"description": "电子邮箱",
|
||||
"required": True,
|
||||
"validations": [
|
||||
{"type": "email"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "password_hash",
|
||||
"type": "String",
|
||||
"description": "密码哈希",
|
||||
"required": True,
|
||||
"secret": True # 标记为敏感字段
|
||||
},
|
||||
{
|
||||
"name": "is_active",
|
||||
"type": "Boolean",
|
||||
"description": "是否激活",
|
||||
"default": True
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"type": "DateTime",
|
||||
"description": "创建时间",
|
||||
"auto_now_add": True
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"type": "DateTime",
|
||||
"description": "更新时间",
|
||||
"auto_now": True
|
||||
}
|
||||
],
|
||||
"relations": [
|
||||
{
|
||||
"name": "roles",
|
||||
"type": "many-to-many",
|
||||
"target": "role",
|
||||
"through": "user_roles",
|
||||
"description": "用户角色关联"
|
||||
},
|
||||
{
|
||||
"name": "posts",
|
||||
"type": "one-to-many",
|
||||
"target": "post",
|
||||
"description": "用户发表的文章"
|
||||
}
|
||||
],
|
||||
"api": {
|
||||
"operations": ["create", "read", "update", "delete", "list"],
|
||||
"base_path": "/users",
|
||||
"auth_required": True,
|
||||
"permissions": {
|
||||
"create": ["admin", "manager"],
|
||||
"delete": ["admin"]
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"label": "用户管理",
|
||||
"icon": "user",
|
||||
"order": 10,
|
||||
"submenu": [
|
||||
{
|
||||
"label": "用户列表",
|
||||
"path": "/users",
|
||||
"icon": "list"
|
||||
},
|
||||
{
|
||||
"label": "角色管理",
|
||||
"path": "/roles",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
params = {
|
||||
"package": "com.example",
|
||||
"author": "John Doe"
|
||||
}
|
||||
|
||||
# 在代码中合并默认配置和字段的前端配置
|
||||
for field in entity_config["fields"]:
|
||||
frontend = {**default_frontend_config, **field}
|
||||
field.update(frontend)
|
||||
|
||||
import json
|
||||
with open('tmp.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(entity_config, f, ensure_ascii=False, indent=4)
|
||||
|
||||
# 生成所有文件
|
||||
generated_files = generator.generate_files(entity_config, 'relation-demo', params)
|
||||
# generated_files = generator.generate_files(entity_config, 'vue-fastapi-admin', params)
|
||||
generator.create_local_code(generated_files, 'tmp')
|
||||
# 打包为zip
|
||||
zip_data = generator.create_zip(generated_files)
|
||||
|
||||
file_path = './tmp.zip'
|
||||
with open(file_path, 'wb') as f:
|
||||
f.write(zip_data.getvalue())
|
||||
print(f"文件已保存到 {file_path}")
|
||||
|
||||
# 在Flask中返回zip文件示例
|
||||
# return send_file(zip_data, mimetype='application/zip', as_attachment=True, download_name='generated_code.zip')
|
||||
@@ -0,0 +1,133 @@
|
||||
|
||||
import random
|
||||
import string
|
||||
from hashlib import md5
|
||||
from typing import Any, Dict, List, Union, Optional, get_origin, get_args, Type
|
||||
from pydantic import BaseModel
|
||||
|
||||
def gen_random_str(length=10, prefix="", suffix=""):
|
||||
"""
|
||||
生成随机字符串,默认长度为10,可添加前缀和后缀
|
||||
"""
|
||||
random_str = prefix + ''.join(random.choices(string.ascii_letters + string.digits, k=length)) + suffix
|
||||
return md5(random_str.encode()).hexdigest()
|
||||
|
||||
|
||||
def split_generator(iterable, chunk_size=50):
|
||||
"""
|
||||
将可迭代对象拆分为多个子列表
|
||||
"""
|
||||
chunk = []
|
||||
for item in iterable:
|
||||
chunk.append(item)
|
||||
if len(chunk) == chunk_size:
|
||||
yield chunk
|
||||
chunk = []
|
||||
if chunk:
|
||||
yield chunk
|
||||
|
||||
async def async_split_generator(iterable, chunk_size=50):
|
||||
"""
|
||||
将可迭代对象拆分为多个子列表
|
||||
"""
|
||||
chunk = []
|
||||
async for item in iterable:
|
||||
chunk.append(item)
|
||||
if len(chunk) == chunk_size:
|
||||
yield chunk
|
||||
chunk = []
|
||||
if chunk:
|
||||
yield chunk
|
||||
|
||||
async def async_generator_to_list(iterable):
|
||||
"""
|
||||
将可迭代对象转换为列表
|
||||
"""
|
||||
return [item async for item in iterable]
|
||||
|
||||
TYPE_MAPPING = {
|
||||
"str": "string",
|
||||
"int": "number",
|
||||
"float": "number",
|
||||
"bool": "boolean",
|
||||
"list": "array",
|
||||
"dict": "object",
|
||||
"NoneType": "null", # 注意:None 的类型名是 'NoneType'
|
||||
}
|
||||
|
||||
def transform_pydantic_to_list(
|
||||
cls: Type[BaseModel],
|
||||
prefix: str = "" # 新增:当前路径前缀
|
||||
) -> List[Dict[str, Any]]:
|
||||
if not issubclass(cls, BaseModel):
|
||||
raise TypeError(f"{cls} is not a Pydantic BaseModel")
|
||||
|
||||
result = []
|
||||
exclude_filter = getattr(cls, "__exclude_filter__", [])
|
||||
meta_mapping = getattr(cls, "__meta_mapping__", {})
|
||||
|
||||
for field_name, field_info in cls.model_fields.items():
|
||||
if field_name in exclude_filter:
|
||||
continue
|
||||
|
||||
# 构建当前字段的完整路径
|
||||
current_path = f"{prefix}.{field_name}" if prefix else field_name
|
||||
|
||||
field_meta = meta_mapping.get(field_name, {}) or {}
|
||||
|
||||
raw_type = field_info.annotation
|
||||
children = []
|
||||
display_type_name = "any"
|
||||
|
||||
origin = get_origin(raw_type)
|
||||
args = get_args(raw_type)
|
||||
|
||||
# 处理 Optional[T](即 Union[T, None])
|
||||
if origin is Union and type(None) in args:
|
||||
# 提取非 None 的类型
|
||||
non_none_types = [arg for arg in args if arg is not type(None)]
|
||||
if len(non_none_types) == 1:
|
||||
inner_type = non_none_types[0]
|
||||
else:
|
||||
inner_type = raw_type # 多类型 Union,暂不深入处理
|
||||
else:
|
||||
inner_type = raw_type
|
||||
|
||||
# 判断是否为 List[BaseModel]
|
||||
list_origin = get_origin(inner_type)
|
||||
if list_origin is list:
|
||||
item_type = get_args(inner_type)[0] if get_args(inner_type) else Any
|
||||
if isinstance(item_type, type) and issubclass(item_type, BaseModel):
|
||||
children = transform_pydantic_to_list(item_type, prefix=current_path)
|
||||
display_type_name = "list"
|
||||
elif isinstance(inner_type, type) and issubclass(inner_type, BaseModel):
|
||||
children = transform_pydantic_to_list(inner_type, prefix=current_path)
|
||||
display_type_name = "object"
|
||||
else:
|
||||
# 基础类型:获取类型名
|
||||
if inner_type is type(None):
|
||||
display_type_name = "NoneType"
|
||||
elif hasattr(inner_type, '__name__'):
|
||||
display_type_name = inner_type.__name__
|
||||
else:
|
||||
display_type_name = str(inner_type)
|
||||
|
||||
frontend_type = TYPE_MAPPING.get(display_type_name, "string")
|
||||
|
||||
item = {
|
||||
"value": current_path, # ✅ 使用层级路径
|
||||
"label": field_info.description or field_name,
|
||||
"type": frontend_type,
|
||||
**field_meta,
|
||||
}
|
||||
if children:
|
||||
item["children"] = children
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(gen_random_str())
|
||||
print(gen_random_str())
|
||||
print(gen_random_str())
|
||||
print(gen_random_str())
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
import pymysql
|
||||
|
||||
|
||||
class DatabaseInfo:
|
||||
def __init__(self, host, user, port, password, database):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.database = database
|
||||
self.connection = None
|
||||
|
||||
def __enter__(self):
|
||||
self.connect()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.disconnect()
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
self.connection = pymysql.connect(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
user=self.user,
|
||||
password=self.password,
|
||||
database=self.database
|
||||
)
|
||||
except pymysql.Error as e:
|
||||
print(f"数据库连接失败: {e}")
|
||||
|
||||
def disconnect(self):
|
||||
if self.connection:
|
||||
self.connection.close()
|
||||
|
||||
def get_all_tables(self):
|
||||
if not self.connection:
|
||||
self.connect()
|
||||
try:
|
||||
with self.connection.cursor() as cursor:
|
||||
cursor.execute("SHOW TABLE STATUS")
|
||||
tables = cursor.fetchall()
|
||||
table_info_list = []
|
||||
for table in tables:
|
||||
table_info = {
|
||||
"tableName": table[0],
|
||||
"tableComment": table[17] # 表注释信息在第 19 列(索引为 18)
|
||||
}
|
||||
table_info_list.append(table_info)
|
||||
return table_info_list
|
||||
except pymysql.Error as e:
|
||||
print(f"获取表名失败: {e}")
|
||||
return []
|
||||
|
||||
def get_table_info(self, table_name):
|
||||
if not self.connection:
|
||||
self.connect()
|
||||
try:
|
||||
with self.connection.cursor() as cursor:
|
||||
cursor.execute("SHOW TABLE STATUS LIKE %s", (table_name,))
|
||||
table = cursor.fetchone()
|
||||
table_info = {
|
||||
"tableName": table[0],
|
||||
"tableComment": table[17] # 表注释信息在第 19 列(索引为 18)
|
||||
}
|
||||
return table_info
|
||||
except pymysql.Error as e:
|
||||
print(f"获取表名失败: {e}")
|
||||
return {}
|
||||
|
||||
def parse_type_and_validation(self, column_type):
|
||||
# 简单的类型解析,实际可能需要更复杂的逻辑
|
||||
if "int" in column_type.lower():
|
||||
return dict(type="int", validation="int")
|
||||
elif "varchar" in column_type.lower():
|
||||
number = column_type.split("(")[1].split(")")[0].strip()
|
||||
return dict(type="str", validations=[
|
||||
dict(type="str", min=1, max=int(number))
|
||||
])
|
||||
# elif "datetime" in column_type.lower():
|
||||
# return dict(type="str", validations=[
|
||||
# dict(type="str", format="%Y-%m-%d %H:%M:%S")
|
||||
# ])
|
||||
else:
|
||||
return dict(type=column_type)
|
||||
|
||||
def get_table_structure(self, table_name):
|
||||
if not self.connection:
|
||||
self.connect()
|
||||
try:
|
||||
table_info = self.get_table_info(table_name)
|
||||
with self.connection.cursor() as cursor:
|
||||
# 使用 SHOW FULL COLUMNS FROM 获取包含注释的列信息
|
||||
cursor.execute(f"SHOW FULL COLUMNS FROM {table_name}")
|
||||
columns = cursor.fetchall()
|
||||
structure = []
|
||||
for column in columns:
|
||||
column_info = {
|
||||
"name": column[0],
|
||||
"type": column[1],
|
||||
"required": column[2] is not None,
|
||||
"primary_key": column[4] == 'PRI',
|
||||
# "default": column[4],
|
||||
"description": column[8] # 注释信息在第 9 列(索引为 8)
|
||||
}
|
||||
column_info.update(self.parse_type_and_validation(column_info["type"])) # 添加解析后的类型和验证信息
|
||||
structure.append(column_info)
|
||||
table_info['fields'] = structure
|
||||
return table_info
|
||||
except pymysql.Error as e:
|
||||
print(f"获取表 {table_name} 结构失败: {e}")
|
||||
return {'fields': []}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 请根据实际情况修改数据库连接信息
|
||||
db_info = DatabaseInfo(
|
||||
host="lt.330770.xyz",
|
||||
port=3307,
|
||||
user="root",
|
||||
password="rap_sky",
|
||||
database="rpa"
|
||||
)
|
||||
tables = db_info.get_all_tables()
|
||||
print("所有表名:")
|
||||
for table in tables:
|
||||
print(table)
|
||||
tableName = table.get('TableName')
|
||||
structure = db_info.get_table_structure(tableName)
|
||||
print(f"表 {tableName} 的结构信息:")
|
||||
for column in structure:
|
||||
print(column)
|
||||
db_info.disconnect()
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
from typing import Dict, List, Callable, Any
|
||||
from fastapi import BackgroundTasks
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from enum import Enum
|
||||
import time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EventPriority(Enum):
|
||||
HIGH = 1
|
||||
MEDIUM = 2
|
||||
LOW = 3
|
||||
|
||||
class EventType:
|
||||
# 同步星云联系人信息
|
||||
SYNC_XINGYUN_CONTACT_INFO = "sync_xingyun_contact_info"
|
||||
# 绑定订单给用户
|
||||
BIND_ORDER_FOR_USER = "bind_order_for_user"
|
||||
# 打开个人聊天会话
|
||||
OPEN_PERSONAL_CHAT = "open_personal_chat"
|
||||
# 群聊变更
|
||||
GROUP_CHAT_UPDATED = "group_chat_updated"
|
||||
# 检查创建群聊
|
||||
CHECK_CREATE_GROUP_CHAT = "check_create_group_chat"
|
||||
# 客服领单
|
||||
CUSTOMER_SERVICE_ASSIGN_ORDER = "customer_service_assign_order"
|
||||
# 分配设计师
|
||||
DESIGNER_ASSIGN_ORDER = "designer_assign_order"
|
||||
# 客户复购
|
||||
OLD_CUSTOMER_REPEAT_PURCHASE = "old_customer_repeat_purchase"
|
||||
# 客户退货
|
||||
CUSTOMER_RETURN_ORDER = "customer_return_order"
|
||||
# 设计稿上传
|
||||
DESIGNER_UPLOAD_DESIGN = "designer_upload_design"
|
||||
# 客户在新店铺下单
|
||||
CUSTOMER_ORDER_IN_NEW_SHOP = "customer_order_in_new_shop"
|
||||
|
||||
|
||||
EventList = [
|
||||
{
|
||||
"value": EventType.SYNC_XINGYUN_CONTACT_INFO,
|
||||
"label": "同步星云联系人信息",
|
||||
"description": "当星云联系人信息发生变化时触发",
|
||||
"automation_event": False
|
||||
},
|
||||
{
|
||||
"value": EventType.BIND_ORDER_FOR_USER,
|
||||
"label": "绑定订单",
|
||||
"description": "当用户创建订单时触发",
|
||||
"automation_event": True
|
||||
},
|
||||
{
|
||||
"value": EventType.OPEN_PERSONAL_CHAT,
|
||||
"label": "打开个人聊天会话",
|
||||
"description": "当用户创建个人聊天时触发",
|
||||
"automation_event": False
|
||||
},
|
||||
{
|
||||
"value": EventType.GROUP_CHAT_UPDATED,
|
||||
"label": "群聊变更",
|
||||
"description": "当群聊信息发生变化时触发",
|
||||
"automation_event": True
|
||||
},
|
||||
{
|
||||
"value": EventType.CHECK_CREATE_GROUP_CHAT,
|
||||
"label": "群聊创建",
|
||||
"description": "当用户请求创建群聊时触发",
|
||||
"automation_event": True
|
||||
},
|
||||
{
|
||||
"value": EventType.CUSTOMER_SERVICE_ASSIGN_ORDER,
|
||||
"label": "客服领单",
|
||||
"description": "当客服领单时触发",
|
||||
"automation_event": True
|
||||
},
|
||||
{
|
||||
"value": EventType.DESIGNER_ASSIGN_ORDER,
|
||||
"label": "分配设计师",
|
||||
"description": "当分配设计师时触发",
|
||||
"automation_event": True
|
||||
},
|
||||
{
|
||||
"value": EventType.OLD_CUSTOMER_REPEAT_PURCHASE,
|
||||
"label": "客户复购",
|
||||
"description": "当老客复购时触发",
|
||||
"automation_event": True
|
||||
},
|
||||
{
|
||||
"value": EventType.CUSTOMER_RETURN_ORDER,
|
||||
"label": "客户退货",
|
||||
"description": "当客户退货时触发",
|
||||
"automation_event": True
|
||||
},
|
||||
{
|
||||
"value": EventType.DESIGNER_UPLOAD_DESIGN,
|
||||
"label": "设计稿上传",
|
||||
"description": "当设计师上传设计稿时触发",
|
||||
"automation_event": True
|
||||
},
|
||||
{
|
||||
"value": EventType.CUSTOMER_ORDER_IN_NEW_SHOP,
|
||||
"label": "客户在新店铺下单",
|
||||
"description": "当客户在新店铺下单时触发",
|
||||
"automation_event": True
|
||||
},
|
||||
]
|
||||
|
||||
class NonBlockingEventManager:
|
||||
def __init__(self):
|
||||
self.handlers: Dict[str, List[Callable]] = {}
|
||||
self.priority_handlers: Dict[EventPriority, List[Callable]] = {
|
||||
EventPriority.HIGH: [],
|
||||
EventPriority.MEDIUM: [],
|
||||
EventPriority.LOW: []
|
||||
}
|
||||
self.pretasks: List[Callable] = []
|
||||
self.automation_event_handlers: Dict[str, Any] = []
|
||||
|
||||
def subscribe(self, event_type: str, handler: Callable, priority: EventPriority = EventPriority.MEDIUM, input_model: Any = None):
|
||||
if event_type not in self.handlers:
|
||||
self.handlers[event_type] = []
|
||||
|
||||
for event in EventList:
|
||||
if event["value"] == event_type:
|
||||
event["field_tree"] = input_model
|
||||
self.automation_event_handlers.append(event)
|
||||
|
||||
logger.info(f"Subscribe {event_type} with priority {priority}")
|
||||
# print(f"Subscribe {event_type} with priority {priority}", flush=True)
|
||||
self.handlers[event_type].append(handler)
|
||||
|
||||
def subscribe_priority(self, handler: Callable, priority: EventPriority = EventPriority.MEDIUM):
|
||||
"""订阅所有事件类型的处理器,按优先级"""
|
||||
self.priority_handlers[priority].append(handler)
|
||||
|
||||
async def publish_async(self, event_type: str, data: Any, background_tasks: BackgroundTasks):
|
||||
"""异步发布事件,不阻塞主线程"""
|
||||
# 添加到后台任务,立即返回
|
||||
background_tasks.add_task(
|
||||
self._process_event_async,
|
||||
event_type,
|
||||
data
|
||||
)
|
||||
|
||||
def register_pretask(self, pretask: Callable):
|
||||
"""注册预任务"""
|
||||
self.pretasks.append(pretask)
|
||||
|
||||
async def trigger_event(self, event_type: str, event_data: Any):
|
||||
"""解析事件数据"""
|
||||
for pretask in self.pretasks:
|
||||
await pretask(event_type, event_data)
|
||||
|
||||
async def _process_event_async(self, event_type: str, data: Any):
|
||||
"""异步处理事件"""
|
||||
start_time = time.time()
|
||||
|
||||
# 触发事件系统
|
||||
try:
|
||||
await self.trigger_event(event_type, data)
|
||||
except Exception as e:
|
||||
logger.exception(f'处理事件失败,{e}')
|
||||
|
||||
# 处理优先级处理器(高优先级先执行)
|
||||
for priority in [EventPriority.HIGH, EventPriority.MEDIUM, EventPriority.LOW]:
|
||||
priority_tasks = []
|
||||
for handler in self.priority_handlers[priority]:
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(handler):
|
||||
task = handler(event_type, data)
|
||||
else:
|
||||
task = asyncio.to_thread(handler, event_type, data)
|
||||
priority_tasks.append(task)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in priority handler: {e}")
|
||||
|
||||
if priority_tasks:
|
||||
await asyncio.gather(*priority_tasks, return_exceptions=True)
|
||||
|
||||
# 处理特定事件类型的处理器
|
||||
if event_type in self.handlers:
|
||||
specific_tasks = []
|
||||
for handler in self.handlers[event_type]:
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(handler):
|
||||
task = handler(event_type, data)
|
||||
else:
|
||||
task = asyncio.to_thread(handler, event_type, data)
|
||||
specific_tasks.append(task)
|
||||
except Exception as e:
|
||||
logging.error(f"Error in specific handler: {e}")
|
||||
|
||||
if specific_tasks:
|
||||
await asyncio.gather(*specific_tasks, return_exceptions=True)
|
||||
|
||||
logging.info(f"Event {event_type} processed in {time.time() - start_time:.2f}s")
|
||||
|
||||
# 创建全局事件管理器
|
||||
event_manager = NonBlockingEventManager()
|
||||
|
||||
# # 定义事件处理器
|
||||
# async def log_event_handler(event_type: str, data: Any):
|
||||
# await asyncio.sleep(0.1) # 模拟异步日志记录
|
||||
# logging.info(f"Logged event: {event_type}")
|
||||
|
||||
# def update_cache_handler( Any):
|
||||
# import time
|
||||
# time.sleep(0.5) # 模拟缓存更新
|
||||
# logging.info(f"Cache updated with: {data}")
|
||||
|
||||
# # 注册处理器
|
||||
# event_manager.subscribe_priority(log_event_handler, EventPriority.HIGH)
|
||||
# event_manager.subscribe("order_created", update_cache_handler, EventPriority.MEDIUM)
|
||||
|
||||
# @app.post("/orders")
|
||||
# async def create_order_complete(order_ dict, background_tasks: BackgroundTasks):
|
||||
# # 执行主要业务逻辑
|
||||
# order_id = f"order_{hash(str(order_data))}"
|
||||
# order_data["order_id"] = order_id
|
||||
|
||||
# # 保存到数据库
|
||||
# # await save_order_to_db(order_data)
|
||||
|
||||
# # 立即返回响应
|
||||
# response = {"order_id": order_id, "status": "created"}
|
||||
|
||||
# # 异步发布事件,不阻塞响应
|
||||
# await event_manager.publish_async("order_created", order_data, background_tasks)
|
||||
|
||||
# return response
|
||||
|
||||
# # 启动时配置
|
||||
# @app.on_event("startup")
|
||||
# async def startup_event():
|
||||
# logging.basicConfig(level=logging.INFO)
|
||||
@@ -0,0 +1,34 @@
|
||||
# excel_utils.py
|
||||
import pandas as pd
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
def get_sheets_and_headers(
|
||||
file_path: str,
|
||||
header: int = 0,
|
||||
skiprows: int = 0,
|
||||
**kwargs
|
||||
) -> Dict[str, List[str]]:
|
||||
"""
|
||||
读取 Excel 文件,返回每个 sheet 的名称及其表头(列名)。
|
||||
|
||||
参数:
|
||||
file_path (str): Excel 文件路径(支持 .xlsx / .xls)
|
||||
header (int): 表头所在行索引(默认 0,即第一行)
|
||||
skiprows (int): 跳过的行数(在表头之前)
|
||||
**kwargs: 透传给 pd.read_excel 的其他参数(如 engine 等)
|
||||
|
||||
返回:
|
||||
Dict[str, List[str]]: {sheet_name: [col1, col2, ...]}
|
||||
"""
|
||||
# 使用 nrows=0 只读取表头,不加载数据,性能高
|
||||
all_sheets = pd.read_excel(
|
||||
file_path,
|
||||
sheet_name=None, # 读取所有 sheet
|
||||
header=header,
|
||||
skiprows=skiprows,
|
||||
nrows=0, # ⚡ 关键:只读表头
|
||||
**kwargs
|
||||
)
|
||||
return {name: df.columns.tolist() for name, df in all_sheets.items()}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import jwt
|
||||
|
||||
from app.schemas.login import JWTPayload
|
||||
from app.settings.config import settings
|
||||
|
||||
|
||||
def create_access_token(*, data: JWTPayload):
|
||||
payload = data.model_dump().copy()
|
||||
encoded_jwt = jwt.encode(payload, settings.SECRET_KEY, algorithm=settings.JWT_ALGORITHM)
|
||||
return encoded_jwt
|
||||
@@ -0,0 +1,16 @@
|
||||
from passlib import pwd
|
||||
from passlib.context import CryptContext
|
||||
|
||||
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
def generate_password() -> str:
|
||||
return pwd.genword()
|
||||
Submodule
+1
Submodule app/utils/some_sdk added at 2e8b24e312
Reference in New Issue
Block a user