feat:对接中心平台,完善接口鉴权
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""读取前端组件目录:启动时载入进程内存缓存,未载入时回退读取本地 JSON 文件"""
|
||||
"""查询前端组件目录:目录只来自启动时从 MongoDB 载入的进程内存缓存,没有文件回退"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
@@ -8,8 +8,6 @@ from app.schemas.component_resource import (
|
||||
ComponentResult,
|
||||
)
|
||||
|
||||
DEFAULT_CATALOG_PATH = Path(__file__).resolve().parent / "catalog" / "components.json"
|
||||
|
||||
# 进程内共享的组件目录,由启动流程载入;agent 与前端接口读取同一份
|
||||
_cached_catalog: ComponentCatalog | None = None
|
||||
|
||||
@@ -23,36 +21,24 @@ def get_catalog_cache() -> ComponentCatalog | None:
|
||||
return _cached_catalog
|
||||
|
||||
|
||||
def load_catalog_file(path: str | Path | None = None) -> ComponentCatalog | None:
|
||||
def load_catalog_file(path: str | Path) -> ComponentCatalog:
|
||||
"""读取导出的目录文件,只供导入脚本使用,运行时不读取文件"""
|
||||
# 如果传入的是相对路径,以项目根目录为基准,避免受启动时工作目录影响
|
||||
target = Path(path) if path is not None else DEFAULT_CATALOG_PATH
|
||||
target = Path(path)
|
||||
if not target.is_absolute():
|
||||
target = Path(__file__).resolve().parents[2] / target
|
||||
try:
|
||||
# utf-8-sig 同时兼容带 BOM 和不带 BOM 的 UTF-8 文件
|
||||
content = target.read_text(encoding="utf-8-sig")
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
# 文件损坏或定义不合法直接报错,不能伪装成查无结果
|
||||
return ComponentCatalog.model_validate_json(content)
|
||||
# utf-8-sig 同时兼容带 BOM 和不带 BOM 的 UTF-8 文件;损坏或不合法直接报错
|
||||
return ComponentCatalog.model_validate_json(target.read_text(encoding="utf-8-sig"))
|
||||
|
||||
|
||||
class ComponentProvider:
|
||||
def __init__(self, catalog_path: str | Path | None = None):
|
||||
# 指定路径时始终读取该文件;未指定时优先使用启动缓存,缓存为空再读默认文件
|
||||
self.catalog_path = catalog_path
|
||||
|
||||
def query(self, query: ComponentQuery) -> ComponentResult:
|
||||
catalog = (
|
||||
_cached_catalog
|
||||
if self.catalog_path is None and _cached_catalog is not None
|
||||
else load_catalog_file(self.catalog_path)
|
||||
)
|
||||
catalog = _cached_catalog
|
||||
if catalog is None:
|
||||
# 如果目录文件不存在,返回未支持状态和原因,由调用方决定失败处理方式
|
||||
# 启动时未载入目录,返回未支持状态和原因,由调用方决定失败处理方式
|
||||
return ComponentResult(
|
||||
status="unsupported",
|
||||
message="尚未配置前端组件目录,请先导出真实组件定义",
|
||||
message="物料目录未载入,请确认已配置 MongoDB 并导入物料",
|
||||
)
|
||||
|
||||
# 如果提供关键词,去除首尾空白并统一大小写,未提供时不启用关键词筛选
|
||||
@@ -92,9 +78,3 @@ class ComponentProvider:
|
||||
catalog_version=catalog.version,
|
||||
message="已找到组件定义" if matches else "已查询组件目录,未找到匹配的组件",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
provider = ComponentProvider()
|
||||
result = provider.query(ComponentQuery())
|
||||
print(result.model_dump_json(indent=2))
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""从物料目录读取组件的可编辑字段,供动作生成理解字段含义,并供校验约束属性结构"""
|
||||
|
||||
from app.resources.component import ComponentProvider
|
||||
from app.schemas.component_resource import ComponentDefinition, ComponentQuery
|
||||
from app.schemas.screen import ScreenNode
|
||||
|
||||
# 属性面板的 key 以区域开头,只有这两个区域可以被 update 修改
|
||||
EDITABLE_SECTIONS = ("props", "style")
|
||||
|
||||
|
||||
def _materials(node_type: str) -> list[ComponentDefinition]:
|
||||
# 多个物料预设可以共用同一前端 type,按 type 取全部预设
|
||||
result = ComponentProvider().query(ComponentQuery(type=node_type))
|
||||
return result.components if result.status == "resolved" else []
|
||||
|
||||
|
||||
def _split_key(key: str) -> tuple[str, list[str | int]] | None:
|
||||
# 前端点路径中的纯数字段是数组下标,转换为与 UpdateAction.path 一致的整数
|
||||
section, *path = key.split(".")
|
||||
if section not in EDITABLE_SECTIONS or not path:
|
||||
return None
|
||||
return section, [int(part) if part.isdigit() else part for part in path]
|
||||
|
||||
|
||||
def editable_fields(node_type: str) -> list[dict]:
|
||||
"""返回组件属性面板中的字段:用户可见名称、区域、路径及可选值,按出现顺序去重"""
|
||||
fields: dict[str, dict] = {}
|
||||
for material in _materials(node_type):
|
||||
for setter in material.setters:
|
||||
parsed = _split_key(setter.key)
|
||||
if parsed is None or setter.key in fields:
|
||||
continue
|
||||
section, path = parsed
|
||||
field = {"label": setter.label, "section": section, "path": path}
|
||||
if options := setter.props.get("options"):
|
||||
field["options"] = options
|
||||
fields[setter.key] = field
|
||||
return list(fields.values())
|
||||
|
||||
|
||||
def known_prop_keys(node: ScreenNode) -> set[str] | None:
|
||||
"""props 第一层是组件与渲染器的约定,只能是节点已有或物料默认配置中的字段;
|
||||
目录中找不到该类型时约定未知,返回 None 表示不做限制"""
|
||||
materials = _materials(node.type)
|
||||
if not materials:
|
||||
return None
|
||||
keys = set(node.props)
|
||||
for material in materials:
|
||||
keys.update(material.schema_.props)
|
||||
return keys
|
||||
Reference in New Issue
Block a user