"""物料目录的 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 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), } ) 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}) 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