feat(product_interface): 实现 product_management 资源模块接口

参照 llmage/product_interface.py 范式:
- load_product_category_product(): 返回类别+产品标准数据,供产品导入
- get_product_display/check_product_availability/check_product_consumable
  /execute_product_service/execute_product_service_stream/calculate_product_cost
- load_xxx() 设置 env.product_interface
- calculate_product_cost 按 charge_mode/meter_mode 解析 ppid → buffered_charging 计费
- 延迟导入避免与 init 循环 import
This commit is contained in:
ymq 2026-08-26 15:39:02 +08:00
parent 846e2dec0d
commit 3fb367c61e
2 changed files with 168 additions and 0 deletions

View File

@ -361,6 +361,25 @@ def load_storage_resource():
env._resource_pricing_resolvers = existing
logger.info('产品层解析器注册表未就绪,已暂存 resolver: %s', RESOURCE_TYPE)
# 注册 product_management 资源模块接口env.product_interface
from .product_interface import (
get_product_display, check_product_availability, check_product_consumable,
execute_product_service, execute_product_service_stream, calculate_product_cost)
env.product_interface = {
'module_name': MODULE_NAME,
'get_product_display': get_product_display,
'check_product_availability': check_product_availability,
'check_product_consumable': check_product_consumable,
'execute_product_service': execute_product_service,
'execute_product_service_stream': execute_product_service_stream,
'calculate_product_cost': calculate_product_cost,
'load_product_category_product': load_product_category_product,
}
logger.info('storage_resource module loaded (v%s, resource_type=%s)',
MODULE_VERSION, RESOURCE_TYPE)
return True
# 产品类别/产品导入接口 —— 供 product_management 经 importlib.import_module 访问。
from .product_interface import load_product_category_product # noqa: E402

View File

@ -0,0 +1,149 @@
"""storage_resource 产品接口 — 供 product_management 调用。
标准资源模块接口参照 llmage/product_interface.py
resource_ref_id = storres_spec.id
定价按 spec × meter_mode 映射到 ppid与账号的 spec × charge_mode 不同
体现每类资源和 ppid 对应方式可不同但最终作用一样
"""
from ahserver.serverenv import ServerEnv
from .init import (
get_storage_spec,
storres_pricing_map_list,
get_storage_ppid,
)
async def _resolve_spec(resource_ref_id):
spec = await get_storage_spec(resource_ref_id)
if not spec:
return None, '存储规格 %s 不存在' % resource_ref_id
return spec, None
async def load_product_category_product(parent_category_id):
"""返回存储类别 + 存储产品(工作空间容量)。"""
specs = await storres_spec_list({'status': 'active'})
if not specs:
return {'success': False, 'error': '存储资源模块中没有启用规格'}
categories = [{
'source_id': 'storage_specs',
'name': '存储服务',
'description': '工作空间容量(按 GB月 计费)',
'product_type': 'workspace_storage',
'product_type_title': '存储容量',
'sort_order': 0,
}]
products = []
for s in specs:
products.append({
'source_category_id': 'storage_specs',
'resource_ref_id': getattr(s, 'id'),
'product_code': getattr(s, 'spec_code'),
'product_name': getattr(s, 'spec_name'),
'product_type': 'workspace_storage',
'brief_intro': getattr(s, 'description', '') or '',
'sort_order': int(getattr(s, 'sort_order', 0) or 0),
})
return {'success': True, 'categories': categories, 'products': products}
async def get_product_display(resource_ref_id):
spec, err = await _resolve_spec(resource_ref_id)
if err:
return {'success': False, 'message': err}
env = ServerEnv()
maps = await storres_pricing_map_list({'spec_id': getattr(spec, 'id')})
pricing_texts = []
for m in maps:
ppid = getattr(m, 'ppid', '') or ''
if not ppid:
continue
try:
pd = await env.get_pricing_display(ppid)
if pd:
txt = pd.get('display_text', '') if isinstance(pd, dict) else str(pd)
pricing_texts.append('%s: %s' % (getattr(m, 'meter_mode', ''), txt))
except Exception:
pricing_texts.append('%s: 定价暂不可用' % getattr(m, 'meter_mode', ''))
return {
'success': True,
'pricing_text': ' | '.join(pricing_texts) if pricing_texts else '未配置定价',
'pricing_detail': {},
'extra_info': {
'spec_code': getattr(spec, 'spec_code', ''),
'spec_name': getattr(spec, 'spec_name', ''),
},
}
async def check_product_availability(resource_ref_id, user_org_id=None):
spec, err = await _resolve_spec(resource_ref_id)
if err:
return {'available': False, 'reason': err}
if getattr(spec, 'status', '') != 'active':
return {'available': False, 'reason': '规格已下线'}
return {'available': True, 'reason': ''}
async def check_product_consumable(resource_ref_id, user_id, user_org_id):
spec, err = await _resolve_spec(resource_ref_id)
if err:
return {'consumable': False, 'reason': err,
'min_balance': 0, 'pricing_available': False}
maps = await storres_pricing_map_list({'spec_id': getattr(spec, 'id')})
pricing_available = any(getattr(m, 'ppid', '') for m in maps)
return {'consumable': True, 'reason': '',
'min_balance': 0, 'pricing_available': pricing_available}
async def execute_product_service(resource_ref_id, user_id, user_org_id, request_data):
"""存储容量开通/分配服务。实际配额分配后续接入。"""
spec, err = await _resolve_spec(resource_ref_id)
if err:
return {'success': False, 'message': err, 'status': 'FAILED'}
meter_mode = (request_data or {}).get('meter_mode', 'ondemand')
return {'success': True,
'message': '存储容量已开通配额分配逻辑待接入meter_mode=%s' % meter_mode,
'status': 'SUCCESS'}
async def execute_product_service_stream(resource_ref_id, user_id, user_org_id, request_data):
r = await execute_product_service(resource_ref_id, user_id, user_org_id, request_data)
async def _gen():
yield {'chunk': r, 'usage_data': None, 'done': True, 'error': None}
return _gen()
async def calculate_product_cost(resource_ref_id, usage_data, user_org_id=None):
"""计算存储费用。usage_data 含 meter_mode + storage_gb。
Returns:
{'success', 'amount', 'original_amount', 'cost', 'discount', 'pricing_program_id'}
"""
spec, err = await _resolve_spec(resource_ref_id)
if err:
return {'success': False, 'message': err}
usage_data = usage_data or {}
meter_mode = usage_data.get('meter_mode', 'ondemand')
ppid = await get_storage_ppid(getattr(spec, 'id'), meter_mode)
if not ppid:
return {'success': False, 'message': '无定价方案(ppid为空)'}
env = ServerEnv()
try:
prices = await env.buffered_charging(ppid, usage_data)
if prices is None:
return {'success': False, 'message': '定价计算返回空'}
except Exception as e:
return {'success': False, 'message': '定价计算失败: %s' % e}
amount = sum(getattr(p, 'amount', 0) for p in prices)
return {'success': True, 'amount': amount, 'original_amount': amount,
'cost': 0.0, 'discount': 1.0, 'pricing_program_id': ppid}