feat: 新增客户自助查询 API (v1/available)

- wwwroot/api/v1/available.dspy: Bearer 认证,支持 product_type/product_name/request_amount 过滤
- init.py: get_available_vouchers_api 返回含 template_name 的完整数据
- README: 完善 API 文档,移除 dapi 依赖说明
- load_path.py: 注册 v1 端点权限
This commit is contained in:
yumoqing 2026-05-29 00:51:27 +08:00
parent 2c56aa904a
commit 2b9942f356
4 changed files with 100 additions and 13 deletions

View File

@ -108,21 +108,55 @@ accounting.record_order(order_id, total_amount,
balance_deducted=result['remaining'])
```
### dapi 模块(客户自助查询)
### 客户自助查询 APIvoucher 模块提供
```python
# 客户通过 API 查询自己的可用代金券
# dapi 端点调用 voucher 引擎
available = get_available_vouchers(sor, customer_id)
voucher 模块自身提供面向远端客户的查询 API无需通过 dapi 转发。
**接口**: `GET /voucher/api/v1/available.dspy`
**认证**: Bearer Token登录后用户
**参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| product_type | string | 否 | 产品类型过滤llm/image/video/audio |
| product_name | string | 否 | 具体产品名过滤(如 gpt-4 |
| request_amount | float | 否 | 预期消费金额(用于规则预校验) |
**返回**:
```json
{
"status": "success",
"data": [
{
"id": "xxx",
"code": "VCH-A1B2C3D4E5F6",
"face_value": 50.00,
"valid_from": "2026-01-01 00:00:00",
"valid_to": "2026-01-31 00:00:00",
"template_name": "新用户满减券"
}
],
"total": 1
}
```
**调用示例**:
```bash
# 查询所有可用券
curl -H "Authorization: Bearer <token>" \
https://ai.atvoe.com/voucher/api/v1/available.dspy
# 按产品类型过滤
curl -H "Authorization: Bearer <token>" \
"https://ai.atvoe.com/voucher/api/v1/available.dspy?product_type=llm&request_amount=100"
```
### 集成点总结
| 调用方 | 场景 | 调用函数 |
| 调用方 | 场景 | 调用方式 |
|--------|------|----------|
| llmage | 推理/生成时抵扣 | `apply_voucher()` |
| accounting | 月度账单结算 | `batch_apply_vouchers()` |
| dapi | 客户查询可用券 | `get_available_vouchers()` |
| llmage | 推理/生成时抵扣 | `apply_voucher()` 函数调用 |
| accounting | 月度账单结算 | `batch_apply_vouchers()` 函数调用 |
| 远端客户 | 自助查询可用券 | `GET /voucher/api/v1/available.dspy` HTTP API |
| 任意模块 | 新增规则类型 | `@register_rule` + 模板管理界面 |
---
@ -146,8 +180,10 @@ voucher/
│ ├── rule/ # 规则 CRUD
│ ├── instance/ # 实例 CRUD
│ ├── usage/ # 流水 CRUD
│ ├── v1/ # 客户自助查询 API
│ │ └── available.dspy # 查询可用券Bearer 认证)
│ ├── apply_voucher.dspy # 使用代金券
│ ├── get_available.dspy # 查询可用券
│ ├── get_available.dspy # 内部查询可用券
│ └── rule_types.dspy # 获取规则类型列表
├── models/ # 表定义 JSON
├── json/ # CRUD 定义 JSON

View File

@ -58,6 +58,7 @@ def main():
"/voucher/api/usage/voucher_usage_log_create.dspy",
"/voucher/api/usage/voucher_usage_log_update.dspy",
"/voucher/api/usage/voucher_usage_log_delete.dspy",
"/voucher/api/v1/available.dspy",
"/voucher/api/apply_voucher.dspy",
"/voucher/api/get_available.dspy",
"/voucher/api/rule_types.dspy",

View File

@ -171,16 +171,27 @@ async def apply_voucher_api(customer_id, order_id, voucher_ids, context):
async def get_available_vouchers_api(customer_id, context=None):
"""外部调用:查询可用代金券"""
"""外部调用:查询可用代金券(含模板名称)"""
from voucher.rules.engine import get_available_vouchers
sor = DBPools().sqlorContext(_get_dbname())
vouchers = get_available_vouchers(sor, customer_id, context)
# 预加载模板名称
template_names = {}
for v in vouchers:
tid = v.template_id if hasattr(v, 'template_id') else v.get('template_id', '')
if tid and tid not in template_names:
t = sor.R('voucher_template', {'id': tid}).first()
if t:
template_names[tid] = t.name if hasattr(t, 'name') else t.get('name', '')
items = []
for v in vouchers:
if hasattr(v, '__dict__'):
items.append(v.__dict__)
item = v.__dict__
else:
items.append(v)
item = dict(v)
tid = item.get('template_id', '')
item['template_name'] = template_names.get(tid, '')
items.append(item)
return {'status': 'success', 'data': items}

View File

@ -0,0 +1,39 @@
import json
# 获取当前登录用户的组织ID作为客户ID
customer_id = await get_userorgid()
# 构建查询上下文(可选过滤条件)
context = {}
if params_kw.get('product_type'):
context['product_type'] = params_kw.get('product_type')
if params_kw.get('product_name'):
context['product_name'] = params_kw.get('product_name')
if params_kw.get('request_amount'):
try:
context['request_amount'] = float(params_kw.get('request_amount'))
except (ValueError, TypeError):
pass
# 查询可用代金券
result = await get_available_vouchers_api(customer_id, context if context else None)
# 格式化输出
if result.get('status') == 'success':
vouchers = []
for v in result.get('data', []):
vouchers.append({
'id': v.get('id'),
'code': v.get('code'),
'face_value': float(v.get('face_value', 0)),
'valid_from': str(v.get('valid_from', '')),
'valid_to': str(v.get('valid_to', '')),
'template_name': v.get('template_name', ''),
})
return json.dumps({
'status': 'success',
'data': vouchers,
'total': len(vouchers)
})
else:
return json.dumps(result)