From c78be834d0a51778543160c0be62b6a1943c82d7 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Tue, 23 Jun 2026 14:47:12 +0800 Subject: [PATCH] refactor: import engine calls resource module's load_product_category_product - core.py: import_categories_and_products() now takes resource_module as first arg, uses importlib to load {resource_module}.init, calls load_product_category_product(parent_category_id) - Resource module owns the full import logic (read source + write product_management) - __init__.py: updated wrapper signature - import_category_products.dspy: updated call --- product_management/__init__.py | 6 +- product_management/core.py | 137 ++++------------------ wwwroot/api/import_category_products.dspy | 15 ++- 3 files changed, 32 insertions(+), 126 deletions(-) diff --git a/product_management/__init__.py b/product_management/__init__.py index aab9c9a..1d9d3ec 100644 --- a/product_management/__init__.py +++ b/product_management/__init__.py @@ -67,11 +67,11 @@ async def set_operator_config(category_id, config_name, config_json, user_id=Non return await manager.set_operator_config(category_id, config_name, config_json, user_id, org_id) -async def import_categories_and_products(org_id, parent_category_id, user_id, import_data): - """Generic import engine for sub-categories and products.""" +async def import_categories_and_products(resource_module, org_id, parent_category_id, user_id): + """Import sub-categories and products from a resource module.""" manager = get_manager() return await manager.import_categories_and_products( - org_id, parent_category_id, user_id, import_data + resource_module, org_id, parent_category_id, user_id ) diff --git a/product_management/core.py b/product_management/core.py index a887d16..3c1cd5a 100644 --- a/product_management/core.py +++ b/product_management/core.py @@ -1093,125 +1093,32 @@ class ProductManager: # ─── Product Import from Resource Modules ─── - async def import_categories_and_products(self, org_id, parent_category_id, - user_id, import_data): - """Generic import engine: create sub-categories and products from structured data. + async def import_categories_and_products(self, resource_module, org_id, + parent_category_id, user_id): + """Import sub-categories and products from a resource module. - import_data format: - { - 'categories': [ - {'name': '子类别名', 'source_id': 'ext_id', 'parent_source_id': None, - 'sort_order': 1, 'description': '', 'has_product': '1', 'product_type': 'llm_model'}, - ... - ], - 'products': [ - {'product_code': 'code1', 'product_name': '产品1', - 'category_source_id': 'ext_id', 'product_type': 'llm_model', - 'brief_intro': '', 'price': 0, 'currency': 'CNY', 'sort_order': 0}, - ... - ] - } - - source_id is the external module's ID, used to avoid duplicates. - Products link to categories via category_source_id matching a category's source_id. + Dynamically imports the resource module and calls its + load_product_category_product(parent_category_id) function, + which is responsible for both reading source data and writing + sub-categories + products under the given parent_category_id. """ - import time - dbname = self._get_dbname() - now = time.strftime('%Y-%m-%d %H:%M:%S') - created_cats = 0 - skipped_cats = 0 - created_prods = 0 - skipped_prods = 0 + import importlib - async with DBPools().sqlorContext(dbname) as sor: - # Phase 1: Create sub-categories under parent_category_id - # Map source_id -> new category id - source_to_id = {} + try: + mod = importlib.import_module(f'{resource_module}.init') + load_fn = getattr(mod, 'load_product_category_product', None) + except Exception as e: + return {'success': False, 'error': f'无法加载资源模块 "{resource_module}": {e}'} - for cat in import_data.get('categories', []): - source_id = cat.get('source_id', '') - # Check if already imported (by name + parent) - existing = await sor.sqlExe( - """SELECT id FROM product_category - WHERE name = ${name}$ AND parent_id = ${parent_id}$ AND org_id = ${org_id}$""", - {'name': cat['name'], 'parent_id': parent_category_id, 'org_id': org_id} - ) - if existing: - source_to_id[source_id] = existing[0]['id'] - skipped_cats += 1 - continue + if load_fn is None: + return {'success': False, 'error': f'资源模块 "{resource_module}" 未提供 load_product_category_product 函数'} - new_id = getID() - source_to_id[source_id] = new_id - # Determine parent: if parent_source_id is set, use that mapping - psrc = cat.get('parent_source_id') - actual_parent = source_to_id.get(psrc, parent_category_id) if psrc else parent_category_id + try: + result = await load_fn(parent_category_id) + except Exception as e: + return {'success': False, 'error': f'调用 {resource_module}.load_product_category_product() 失败: {e}'} - cat_data = { - 'id': new_id, - 'parent_id': actual_parent, - 'name': cat['name'], - 'description': cat.get('description', ''), - 'has_product': cat.get('has_product', '1'), - 'product_type': cat.get('product_type', ''), - 'product_type_title': cat.get('product_type_title', ''), - 'sort_order': str(cat.get('sort_order', 0)), - 'icon': cat.get('icon', ''), - 'status': '1', - 'resource_module': cat.get('resource_module', ''), - 'org_id': org_id, - 'created_by': user_id, - 'created_at': now, - 'updated_at': now - } - await sor.C('product_category', cat_data) - created_cats += 1 + if not result: + return {'success': False, 'error': f'资源模块 "{resource_module}" 未返回导入数据'} - # Phase 2: Create products under the mapped categories - for prod in import_data.get('products', []): - cat_source_id = prod.get('category_source_id', '') - target_cat_id = source_to_id.get(cat_source_id) - if not target_cat_id: - skipped_prods += 1 - continue - - # Check duplicate by product_code + org - existing_prod = await sor.sqlExe( - """SELECT id FROM product - WHERE product_code = ${code}$ AND org_id = ${org_id}$""", - {'code': prod['product_code'], 'org_id': org_id} - ) - if existing_prod: - skipped_prods += 1 - continue - - prod_id = getID() - prod_data = { - 'id': prod_id, - 'category_id': target_cat_id, - 'product_code': prod['product_code'], - 'product_name': prod['product_name'], - 'product_type': prod.get('product_type', ''), - 'brief_intro': prod.get('brief_intro', ''), - 'detail_intro': prod.get('detail_intro', ''), - 'extra_json': prod.get('extra_json', ''), - 'enabled_date': prod.get('enabled_date'), - 'expired_date': prod.get('expired_date'), - 'status': prod.get('status', '1'), - 'price_type': prod.get('price_type', '1'), - 'price': str(prod.get('price', 0)), - 'currency': prod.get('currency', 'CNY'), - 'sort_order': str(prod.get('sort_order', 0)), - 'org_id': org_id, - 'created_by': user_id, - 'created_at': now, - 'updated_at': now - } - await sor.C('product', prod_data) - created_prods += 1 - - return { - 'success': True, - 'message': f'导入完成: 新增 {created_cats} 个子类别, {created_prods} 个产品; ' - f'跳过 {skipped_cats} 个已存在类别, {skipped_prods} 个已存在产品' - } + return result diff --git a/wwwroot/api/import_category_products.dspy b/wwwroot/api/import_category_products.dspy index 667ccb7..8de959e 100644 --- a/wwwroot/api/import_category_products.dspy +++ b/wwwroot/api/import_category_products.dspy @@ -23,15 +23,14 @@ try: if not resource_module: raise ValueError('该类别未配置资源模块,请先在类别编辑中设置资源模块') - # Look up the registered import function by module name + # Call the import engine env = request._run_ns - import_fn_name = f'import_products_from_{resource_module}' - import_fn = getattr(env, import_fn_name, None) - if import_fn is None: - raise ValueError(f'资源模块 "{resource_module}" 未注册产品导入功能') - - # Call the resource module's import function - import_result = await import_fn(org_id=org_id, parent_category_id=category_id, user_id=user_id) + import_result = await env.import_categories_and_products( + resource_module=resource_module, + org_id=org_id, + parent_category_id=category_id, + user_id=user_id + ) if import_result.get('success'): msg = import_result.get('message', '导入完成')