8.2 KiB
storage_resource — 存储资源模块
模块定位
工作空间容量(GB/月)类资源的规格 / 容量采样 / 定价映射模块,宿主为 pipeline-app(也可装 sage)。参照 llmage 范式,但 ppid 挂法与账号资源不同:
account_resource: acctres_spec × charge_mode(月/年/试用) → ppid
storage_resource: storres_spec × meter_mode(按量/包月/峰值) → ppid
存储比账号多一层「采样」:容量是连续占用量,必须周期采样再按 GB月 汇总;账号是一次性/周期性开通事件。两者都通过 resolve_ppid(resource_ref_id, ctx) 对外统一,产品层不必知道内部差异。RESOURCE_TYPE = 'workspace_storage'。业务定价口径 1 元/GB/月(示例定价 unit_prices: 2.8 见链路技能,实际以 pricing_program 配置为准)。
宿主集成:
from storage_resource.init import load_storage_resource
load_storage_resource()
表清单(models/*.json,共 4 张)
| 表 | 用途 | 关键字段 |
|---|---|---|
storres_spec |
存储规格(资源主表) | id(pk32), spec_code(唯一64), spec_name, storage_class(standard…), min_gb(起售), max_gb(单次上限), step_gb(购买步长), capacity_total(可售总量,-1=不限), capacity_used(已分配), backup_enabled('0'/'1'), attr_json, sort_order, status(active) |
storres_pricing_map |
规格×计量方式 → 定价项目 | spec_id, meter_mode(唯一键 spec_id+meter_mode: ondemand/package/peak), meter_unit(GB月), settle_period(month), ppid(→pricing_program), is_default('1'=缺省), status |
storres_sample |
容量采样(存储特有) | spec_id, subscription_id, target_type(计量对象类型), target_id, userid/userorgid, sample_date/sample_time, used_gb(实测), quota_gb(配额), over_gb(超额=used-quota,≥0), source(scan…) |
storres_usage |
存储资源用量(计费落账凭证) | spec_id, meter_mode, subscription_id, userid/userorgid, use_date/use_time(出账), period_start/end, sample_count(采样点数), avg_gb/max_gb/billable_gb(计费容量), usages(JSON), transno, amount+amount_currency+amount_base, cost+cost_currency+cost_base, discount_rate, accounting_status(pending→accounted) |
种子数据 init/data.json:1 条规格(WS-STORAGE-STD,min_gb=1,capacity_total=-1 不限)+ 2 条定价映射(ondemand is_default='1'、package)。种子 ppid 为空串——建好 pricing_program 后必须回填,否则计费金额记 0;meter_mode='peak' 若业务要用峰值计费也必须补映射条目,否则记 0。
对外 API(load_storage_resource() 注册到 ServerEnv)
# 规格 CRUD
env.storres_spec_list(ns) # ns: {status, storage_class}
env.storres_spec_create/update/delete(data)
env.get_storage_spec(spec_id_or_code)
env.check_capacity_available(spec_id_or_code, need_gb) # → (bool, msg);capacity_total<0=不限
# 定价映射 CRUD
env.storres_pricing_map_list(ns) # ns: {spec_id};LEFT JOIN 带出 spec_code/spec_name
env.storres_pricing_map_create/update/delete(data)
env.get_storage_ppid(spec_id_or_code, meter_mode=None) # meter_mode空→取is_default='1'
# 采样
env.record_storage_sample(spec_id, target_type, target_id, used_gb,
userid='', userorgid='', subscription_id='',
quota_gb=None, source='scan') # 定时扫盘后调;over_gb自动算
env.summarize_samples(subscription_id, period_start, period_end) # → (采样点数, 均值GB, 峰值GB)
# 出账
env.storage_charging(spec_id_or_code, meter_mode, userid, userorgid,
subscription_id, period_start, period_end, discount_rate=None)
# 汇总采样 → billable_gb = max_gb if meter_mode=='peak' else avg_gb
# → 解析ppid → env.buffered_charging → 原价×discount_rate → 写 storres_usage(pending)
env.storage_direct_charging(spec_id_or_code, storage_gb, userid, userorgid,
valid_months=1, subscription_id='', discount_rate=None)
# 一次性直购(购买即记账,选A模式): 不依赖采样; 按 package 定价(无则缺省);
# 金额 = 单价(GB月) × storage_gb × valid_months; sample_count=0 标记直购; meter_mode='direct'
# 记账对接
env.get_accounting_storres_usages(limit=200) # pending 用量,供 accounting 出账扫描
env.mark_storres_accounted(usage_id, status='accounted')
resolve_ppid(resource_ref_id, ctx)(ctx: {'meter_mode': 'ondemand'|'package'|'peak'})作为统一定价解析器注册:优先 env.register_resource_pricing_resolver('workspace_storage', resolve_ppid),产品层未就绪时兜底暂存 env._resource_pricing_resolvers。
页面/CRUD(json/*.json)
4 个 xls2ui 格式 Bricks CRUD 定义:storres_spec、storres_pricing_map、storres_sample、storres_usage(editable 指向 ./add_*.dspy 等——这些 dspy 端点不在本仓库,由宿主 wwwroot 提供)。scripts/load_path.py 注册 RBAC 路径(全部 logined):/storage_resource、/storage_resource/{storres_spec,storres_pricing_map,storres_sample,storres_usage}/index.ui。
与产品/计费链路的集成关系
- product_management:
env.product_interface指向本模块product_interface.py(module_name / get_product_display / check_product_availability / check_product_consumable / execute_product_service(_stream) / calculate_product_cost / load_product_category_product)。resource_ref_id = storres_spec.id。load_product_category_product:导出「存储服务」类别 + 每规格一条 product_type='workspace_storage' 产品。execute_product_service:request_data 取meter_mode(默认ondemand)/storage_gb(默认 min_gb),返回 entitlement(容量配额);实际配额分配后续接入。calculate_product_cost(resource_ref_id, usage_data, user_org_id=None):只算原价(discount 恒 1.0);meter_mode='direct' 复用 package 定价,valid_months>1 时按 N 倍计;客户/分销商折扣由产品层 product_management.core 精确计算。
- accounting(计费落账链):
storage_charging/storage_direct_charging写 storres_usage(pending) → 产品层storage_resource_accounting(usage_record)按 spec_id→product_id → product_accounting_generic → biz_order/biz_orderdetail → consume_accounting 复式记账(acc_balance/acc_detail/bill_detail)→mark_storres_accounted置 accounted。前置:客户已开户 + 余额充足,否则 AccountIdNone/AccountOverDraw。 - 定价引擎(pricing):铁律——用
env.buffered_charging(ppid, data)两参、返回价格对象列表,金额sum(getattr(p,'amount',0) for p in prices);用三参pricing_program_charging报缺参且静默记 0(commit3e6f448已修)。存储定价 YAML 示例:pricings: [{price_factors: storage_gb, unit_prices: 2.8}]。
部署注意事项
- 库名:一律
env.get_module_dbname('storage_resource')(pipeline-app 返回 'pipeline'),禁写死。 - 建表:models/*.json 交宿主建表流程;生产库勿跑完整 create_tables.py(product_management 会 DROP 重建丢数据)。
- 种子导入:init/data.json 由 import_init 导入;导完回填 ppid(先建 pricing_program + pricing_program_timing.pricing_data)。
- 采样定时任务:需宿主配周期扫盘任务调
record_storage_sample(容量事实可来自 filemgr 的 file.filesize 汇总 / 工作空间实际占用),再按月summarize_samples→storage_charging。 - RBAC:宿主根目录执行
py3/bin/python pkgs/storage_resource/scripts/load_path.py。 - meter_mode 语义:ondemand(按量,均值)/package(包月)/peak(峰值)三种都是设计值;
summarize_samples里 AVG/MAX 返回 Decimal,必须 float() 否则 JSON 序列化炸(已在代码处理)。 - 依赖:sqlor、bricks_for_python;运行时依赖宿主提供 ahserver/appPublic/pricing/product_management。i18n/{zh,en,jp,ko}/msg.txt 已备词条(存储规格/容量采样/计量方式/计费容量/峰值容量等)。
init.py尾部延迟 importload_product_category_product(避免与 product_interface 循环导入),勿上移。