88 lines
8.2 KiB
Markdown
88 lines
8.2 KiB
Markdown
# 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 配置为准)。
|
||
|
||
宿主集成:
|
||
|
||
```python
|
||
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)
|
||
|
||
```python
|
||
# 规格 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(commit 3e6f448 已修)。存储定价 YAML 示例:`pricings: [{price_factors: storage_gb, unit_prices: 2.8}]`。
|
||
|
||
## 部署注意事项
|
||
|
||
1. **库名**:一律 `env.get_module_dbname('storage_resource')`(pipeline-app 返回 'pipeline'),禁写死。
|
||
2. **建表**:models/*.json 交宿主建表流程;生产库勿跑完整 create_tables.py(product_management 会 DROP 重建丢数据)。
|
||
3. **种子导入**:init/data.json 由 import_init 导入;导完**回填 ppid**(先建 pricing_program + pricing_program_timing.pricing_data)。
|
||
4. **采样定时任务**:需宿主配周期扫盘任务调 `record_storage_sample`(容量事实可来自 filemgr 的 file.filesize 汇总 / 工作空间实际占用),再按月 `summarize_samples` → `storage_charging`。
|
||
5. **RBAC**:宿主根目录执行 `py3/bin/python pkgs/storage_resource/scripts/load_path.py`。
|
||
6. **meter_mode 语义**:ondemand(按量,均值)/package(包月)/peak(峰值)三种都是设计值;`summarize_samples` 里 AVG/MAX 返回 Decimal,必须 float() 否则 JSON 序列化炸(已在代码处理)。
|
||
7. **依赖**:sqlor、bricks_for_python;运行时依赖宿主提供 ahserver/appPublic/pricing/product_management。i18n/{zh,en,jp,ko}/msg.txt 已备词条(存储规格/容量采样/计量方式/计费容量/峰值容量等)。
|
||
8. `init.py` 尾部延迟 import `load_product_category_product`(避免与 product_interface 循环导入),勿上移。
|