增加项目的各个功能

This commit is contained in:
qiaoxinjiu
2026-05-07 19:21:19 +08:00
parent aba1618f89
commit ee6cd4ae66
121 changed files with 9346 additions and 43 deletions

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

@@ -0,0 +1,57 @@
# encoding: UTF-8
import random
import re
import string
class DataBuilderExecutor(object):
"""造数器同步执行器。
当前版本只做安全的模板渲染和内置随机函数,不执行用户脚本,避免引入任意代码执行风险。
"""
def __init__(self, builder_def, env=None):
# builder_def 对应 data_builder.definition 字段,约定为 JSON 对象。
self.builder_def = builder_def or {}
# 保留 steps 字段,后续扩展 http/db 流程编排时继续复用。
self.steps = self.builder_def.get('steps', [])
self.env = env or {}
# context 是模板变量来源,支持 {{env.xxx}} 和 {{param.xxx}}。
self.context = {'env': self.env}
self.results = []
def execute(self, params=None):
"""执行造数器定义并返回渲染后的 output。"""
params = params or {}
self.context['param'] = params
output = self.builder_def.get('output') or {}
# 如果未配置 output返回基础执行信息方便前端判断定义是否为空。
return self._render_template(output) if output else {'params': params, 'steps': len(self.steps)}
def _render_template(self, obj):
"""递归渲染字符串、字典、数组中的 {{变量}}。"""
if isinstance(obj, str):
return re.sub(r'\{\{([^}]+)\}\}', lambda m: str(self._get_value(m.group(1).strip())), obj)
if isinstance(obj, dict):
return {k: self._render_template(v) for k, v in obj.items()}
if isinstance(obj, list):
return [self._render_template(item) for item in obj]
return obj
def _get_value(self, expr):
"""获取模板表达式的值,支持内置随机函数和点路径取值。"""
if expr.startswith('random_string(') and expr.endswith(')'):
length = int(expr[len('random_string('):-1] or 8)
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
if expr == 'random_phone()':
return '1{}{}'.format(random.choice(['3', '5', '7', '8', '9']), ''.join(random.choice(string.digits) for _ in range(9)))
current = self.context
# 点路径示例param.amount、env.base_url。
for part in expr.split('.'):
if isinstance(current, dict):
current = current.get(part)
else:
current = getattr(current, part, None)
if current is None:
return ''
return current

View File

@@ -8,6 +8,7 @@ from const import sparkatp_sql_uri
from logger import logger
_ENGINE_CACHE = {}
_SESSION_FACTORY_CACHE = {}
"""
@@ -32,27 +33,30 @@ class SqlSession:
return f"postgresql+psycopg2://{user}:{urlquote(str(password))}@{host}:{port}/{database}"
def get_session(self):
engine = _ENGINE_CACHE.get(self.sql_uri)
if engine is None:
engine = create_engine(
self.sql_uri,
pool_size=5,
max_overflow=10,
pool_pre_ping=True,
pool_recycle=1800,
pool_timeout=30,
connect_args={
'connect_timeout': 20,
'options': '-c timezone=Asia/Shanghai'
}
)
_ENGINE_CACHE[self.sql_uri] = engine
Session = sessionmaker(bind=engine)
session = Session()
return session
session_factory = _SESSION_FACTORY_CACHE.get(self.sql_uri)
if session_factory is None:
engine = _ENGINE_CACHE.get(self.sql_uri)
if engine is None:
engine = create_engine(
self.sql_uri,
pool_size=20,
max_overflow=30,
pool_pre_ping=True,
pool_recycle=1200,
pool_timeout=60,
pool_use_lifo=True,
connect_args={
'connect_timeout': 10,
'options': '-c timezone=Asia/Shanghai'
}
)
_ENGINE_CACHE[self.sql_uri] = engine
session_factory = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False)
_SESSION_FACTORY_CACHE[self.sql_uri] = session_factory
return session_factory()
def query(self, obj):
return self._session.query(obj)
def query(self, *args):
return self._session.query(*args)
def add(self, added):
self._session.add(added)
@@ -69,6 +73,9 @@ class SqlSession:
def commit(self):
self._session.commit()
def rollback(self):
self._session.rollback()
def close(self):
self._session.close()