92 lines
3.9 KiB
Python
92 lines
3.9 KiB
Python
"""物料目录的 MongoDB 存储:每个组件一条记录,目录版本和共享事件组放在元数据记录中,
|
|
多个物料共用的字段分组单独存放,每个分组一条记录"""
|
|
|
|
from loguru import logger
|
|
from pymongo.asynchronous.database import AsyncDatabase
|
|
|
|
from app.resources.component import set_catalog_cache
|
|
from app.schemas.component_resource import ComponentCatalog
|
|
|
|
# 元数据记录的 _id,组件记录的 _id 为组件 key
|
|
META_ID = "__catalog__"
|
|
|
|
|
|
class MongoMaterialRepository:
|
|
def __init__(self, db: AsyncDatabase, prefix: str = ""):
|
|
# 集合名前缀用于测试隔离
|
|
self._materials = db.get_collection(f"{prefix}materials")
|
|
self._field_groups = db.get_collection(f"{prefix}material_field_groups")
|
|
|
|
async def load_all(self) -> ComponentCatalog | None:
|
|
# 没有元数据记录视为尚未导入,由调用方决定回退方式
|
|
meta = await self._materials.find_one({"_id": META_ID})
|
|
if meta is None:
|
|
return None
|
|
if "event_groups" not in meta:
|
|
# 共享事件组之前的导入:事件写在各物料上,按现在的格式读出来每个物料都没有事件。
|
|
# 不兼容旧格式,按未导入处理,重新导入即可
|
|
logger.error(
|
|
"物料目录是旧格式导入的(元数据记录没有 event_groups),按未导入处理;"
|
|
"运行 uv run python -m scripts.seed_materials 重新导入后重启"
|
|
)
|
|
return None
|
|
documents = await self._materials.find({"_id": {"$ne": META_ID}}).to_list()
|
|
groups = await self._field_groups.find({}).to_list()
|
|
return ComponentCatalog.model_validate(
|
|
{
|
|
"version": meta["version"],
|
|
"components": _ordered(documents),
|
|
"field_groups": _ordered(groups),
|
|
"event_groups": meta["event_groups"],
|
|
}
|
|
)
|
|
|
|
async def replace_all(self, catalog: ComponentCatalog) -> None:
|
|
# 整体替换目录,供导入脚本使用;order 保持源文件中的顺序
|
|
for collection, items in (
|
|
(self._materials, catalog.components),
|
|
(self._field_groups, catalog.field_groups),
|
|
):
|
|
await collection.delete_many({})
|
|
if items:
|
|
await collection.insert_many(_documents(items))
|
|
# 事件组只有几项,跟版本一起放在元数据记录里
|
|
await self._materials.insert_one(
|
|
{
|
|
"_id": META_ID,
|
|
"version": catalog.version,
|
|
"event_groups": [
|
|
group.model_dump(mode="json") for group in catalog.event_groups
|
|
],
|
|
}
|
|
)
|
|
|
|
|
|
def _documents(items) -> list[dict]:
|
|
# 记录 _id 为物料或分组的 key
|
|
return [
|
|
{**item.model_dump(mode="json", by_alias=True), "_id": item.key, "order": index}
|
|
for index, item in enumerate(items)
|
|
]
|
|
|
|
|
|
def _ordered(documents: list[dict]) -> list[dict]:
|
|
return [
|
|
{key: value for key, value in item.items() if key not in ("_id", "order")}
|
|
for item in sorted(documents, key=lambda item: item.get("order", 0))
|
|
]
|
|
|
|
|
|
async def warm_material_cache(db: AsyncDatabase | None) -> ComponentCatalog | None:
|
|
"""启动时把物料目录从 MongoDB 载入进程缓存;未启用或尚未导入时目录为空,不回退读文件"""
|
|
catalog = await MongoMaterialRepository(db).load_all() if db is not None else None
|
|
if catalog is None:
|
|
logger.error(
|
|
"物料目录未载入:{},新增、更换组件等需要物料的修改将无法执行",
|
|
"未配置 MONGODB_URI" if db is None else "materials 集合尚未导入或格式过旧",
|
|
)
|
|
else:
|
|
logger.info("已从 MongoDB 加载物料 {} 个", len(catalog.components))
|
|
set_catalog_cache(catalog)
|
|
return catalog
|