更新计费

This commit is contained in:
zhanggangyong
2026-03-10 06:40:38 +00:00
parent 368198f53c
commit a540e6d61a
77 changed files with 5721 additions and 4788 deletions
@@ -0,0 +1,386 @@
# 计费管理三维度接口文档
## 接口信息
**接口路径**: `GET /api/admin/billing/overview`
**功能**: 获取计费管理三维度统计数据(渠道维度、租户维度、调用记录)
**认证**: 需要 Bearer Token
**权限**: 管理员角色(super_admin、billing_admin、operations_admin)
---
## 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| startTime | string | ✅ | 开始时间,ISO 8601格式,如 `2026-03-01T00:00:00Z` |
| endTime | string | ✅ | 结束时间,ISO 8601格式,如 `2026-03-31T23:59:59Z` |
| channelName | string | ❌ | 渠道名称筛选 |
| tenantName | string | ❌ | 租户名称筛选 |
| minCalls | int | ❌ | 最小调用次数筛选 |
| maxCalls | int | ❌ | 最大调用次数筛选 |
| export | string | ❌ | 导出格式:`excel`、`csv`、`pdf` |
---
## 响应数据结构
```json
{
"success": true,
"data": {
"channelStats": [...],
"tenantStats": [...],
"callRecords": [...]
}
}
```
---
## 一、渠道维度 (channelStats)
### 数据结构
```json
{
"channelStats": [
{
"channelId": "550e8400-e29b-41d4-a716-446655440001",
"channelName": "渠道A",
"calls": 1500,
"totalEU": 7500.0,
"totalCost": 750.00
},
{
"channelId": "550e8400-e29b-41d4-a716-446655440002",
"channelName": "渠道B",
"calls": 800,
"totalEU": 4000.0,
"totalCost": 400.00
}
]
}
```
### 字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| channelId | string | 渠道ID(UUID) |
| channelName | string | 渠道名称 |
| calls | int | 调用次数 |
| totalEU | float | 总EU消耗 |
| totalCost | float | 渠道总价(USD) |
### 前端汇总计算
```javascript
// 渠道总数
const channelCount = channelStats.length;
// 总计费额
const totalCost = channelStats.reduce((sum, c) => sum + c.totalCost, 0);
// 总EU消耗
const totalEU = channelStats.reduce((sum, c) => sum + c.totalEU, 0);
```
---
## 二、租户维度 (tenantStats)
### 数据结构
```json
{
"tenantStats": [
{
"tenantId": "b0d02105-55f8-41a7-b09a-bba8f49a9d62",
"tenantName": "xiaohei",
"channelName": "66",
"calls": 84,
"totalEU": 44197.14,
"totalCost": 12.2905,
"avgCost": 0.1463
},
{
"tenantId": "6b49508d-9c2c-4eac-99e1-c36263fd1699",
"tenantName": "cccccccc",
"channelName": "66",
"calls": 2,
"totalEU": 18.0,
"totalCost": 0.0046,
"avgCost": 0.0023
}
]
}
```
### 字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| tenantId | string | 租户ID(UUID) |
| tenantName | string | 租户名称 |
| channelName | string | 所属渠道名称(无渠道时显示"无渠道") |
| calls | int | 调用次数(Agent使用 + 模型调用) |
| totalEU | float | 总EU消耗 |
| totalCost | float | 用户总价(USD) |
| avgCost | float | 平均消费(totalCost / calls) |
### 前端汇总计算
```javascript
// 租户总数
const tenantCount = tenantStats.length;
// 用户总价
const userTotalCost = tenantStats.reduce((sum, t) => sum + t.totalCost, 0);
// 平均消费
const avgCost = tenantCount > 0 ? userTotalCost / tenantCount : 0;
```
---
## 三、调用记录 (callRecords)
调用记录包含两种类型:
- **agent**: Agent 使用记录(来自 AgentBillingRecord 表)
- **model**: 模型调用记录(来自 ModelBillingRecord 表,LiteLLM 回调数据)
### 数据结构
```json
{
"callRecords": [
{
"id": "7d984099-534f-4b8b-a892-74dead3fd5fd",
"type": "agent",
"timestamp": "2026-03-09T07:05:41.118384",
"channelName": "无渠道",
"tenantName": "xiaohei",
"agentName": "search-agent-b0d02105-21015e",
"modelName": "taiji/claude-sonnet-4-5",
"duration": 83430,
"eu": 8343,
"cost": 2.3175
},
{
"id": "d69e4da8-0852-4e35-94dc-ee2bd584e9ca",
"type": "model",
"timestamp": "2026-03-05T16:02:47.186423",
"channelName": "66",
"tenantName": "xiaohei",
"agentName": null,
"modelName": "openrouter/nousresearch/hermes-3-llama-3.1-405b",
"duration": 44.76,
"eu": 0.3862,
"cost": 0.003862,
"inputTokens": 2932,
"outputTokens": 930,
"totalTokens": 3862
}
]
}
```
### 字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| id | string | 调用ID(UUID) |
| type | string | 记录类型:`agent`(Agent使用)或 `model`(模型调用) |
| timestamp | string | 时间戳(ISO 8601格式) |
| channelName | string | 渠道名称(无渠道时显示"无渠道") |
| tenantName | string | 租户名称 |
| agentName | string | Agent名称(模型调用时为 null) |
| modelName | string | 模型名称(如 `taiji/claude-sonnet-4-5`) |
| duration | float | 时长(秒)- Agent为运行时长,模型为响应时间 |
| eu | float | EU消耗 |
| cost | float | 单次调用总价(USD) |
| inputTokens | int | 输入Token数(仅 type=model 时有值) |
| outputTokens | int | 输出Token数(仅 type=model 时有值) |
| totalTokens | int | 总Token数(仅 type=model 时有值) |
### EU计算规则
**Agent 使用(type=agent):**
```
1 EU = 10秒运行时间
不足10秒按1 EU计算
公式:EU = ceil(duration / 10)
示例:
- 5秒 → 1 EU
- 10秒 → 1 EU
- 15秒 → 2 EU
- 35秒 → 4 EU
```
**模型调用(type=model):**
```
EU 按 Token 数量计算
公式由 LiteLLM 回调提供
```
---
## 前端调用示例
### 请求示例
```javascript
const response = await fetch(
'/api/admin/billing/overview?' + new URLSearchParams({
startTime: '2026-03-01T00:00:00Z',
endTime: '2026-03-31T23:59:59Z'
}),
{
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
const data = await response.json();
```
### 数据处理示例
```javascript
if (data.success) {
const { channelStats, tenantStats, callRecords } = data.data;
// ========== 渠道维度汇总 ==========
const channelSummary = {
totalChannels: channelStats.length,
totalCost: channelStats.reduce((sum, c) => sum + c.totalCost, 0),
totalEU: channelStats.reduce((sum, c) => sum + c.totalEU, 0)
};
// ========== 租户维度汇总 ==========
const tenantSummary = {
totalTenants: tenantStats.length,
userTotalCost: tenantStats.reduce((sum, t) => sum + t.totalCost, 0),
avgCost: tenantStats.length > 0
? tenantStats.reduce((sum, t) => sum + t.totalCost, 0) / tenantStats.length
: 0
};
// ========== 渠道计费详情表格数据 ==========
const channelTableData = channelStats.map(c => ({
渠道名称: c.channelName,
调用次数: c.calls,
总EU: c.totalEU,
渠道总价: `$${c.totalCost.toFixed(2)}`
}));
// ========== 租户计费详情表格数据 ==========
const tenantTableData = tenantStats.map(t => ({
租户名称: t.tenantName,
所属渠道: t.channelName,
调用次数: t.calls,
总EU: t.totalEU,
用户总价: `$${t.totalCost.toFixed(2)}`
}));
// ========== 调用记录明细表格数据 ==========
const callTableData = callRecords.map(r => ({
调用ID: r.id,
租户: r.tenantName,
渠道: r.channelName,
调用时间: r.agentName,
'时长(秒)': r.duration,
EU: r.eu,
单次调用总价: `$${r.cost.toFixed(2)}`,
时间戳: r.timestamp
}));
}
```
---
## 前端页面字段映射
### 渠道维度卡片
| 前端显示 | 数据来源 |
|---------|---------|
| 渠道总数 | `channelStats.length` |
| 总计费额 | `channelStats.reduce((sum, c) => sum + c.totalCost, 0)` |
| 总EU消耗 | `channelStats.reduce((sum, c) => sum + c.totalEU, 0)` |
### 渠道计费详情表格
| 表头 | 字段 |
|------|------|
| 渠道名称 | `channelName` |
| 调用次数 | `calls` |
| 总EU | `totalEU` |
| 渠道总价 | `totalCost` |
### 租户维度卡片
| 前端显示 | 数据来源 |
|---------|---------|
| 租户总数 | `tenantStats.length` |
| 用户总价 | `tenantStats.reduce((sum, t) => sum + t.totalCost, 0)` |
| 平均消费 | `用户总价 / 租户总数` |
### 租户计费详情表格
| 表头 | 字段 |
|------|------|
| 租户名称 | `tenantName` |
| 所属渠道 | `channelName` |
| 调用次数 | `calls` |
| 总EU | `totalEU` |
| 用户总价 | `totalCost` |
### 调用记录明细表格
| 表头 | 字段 |
|------|------|
| 调用ID | `id` |
| 类型 | `type`(agent/model) |
| 租户 | `tenantName` |
| 渠道 | `channelName` |
| Agent名称 | `agentName` |
| 模型名称 | `modelName` |
| 时长(秒) | `duration` |
| EU | `eu` |
| 单次调用总价 | `cost` |
| 输入Token | `inputTokens`(仅模型调用) |
| 输出Token | `outputTokens`(仅模型调用) |
| 总Token | `totalTokens`(仅模型调用) |
| 时间戳 | `timestamp` |
---
## 数据来源说明
本接口从以下两个表聚合数据:
| 表名 | 说明 | 对应 type |
|------|------|----------|
| AgentBillingRecord | Agent 使用计费记录 | `agent` |
| ModelBillingRecord | 模型调用计费记录(LiteLLM 回调) | `model` |
---
## 注意事项
1. **时间格式**: 请求参数中的时间需要使用 ISO 8601 格式
2. **金额单位**: 所有金额字段单位为 USD(美元)
3. **调用记录限制**: 默认返回最近100条记录(Agent 50条 + 模型 50条),按时间倒序排列
4. **EU计算**: Agent 使用按 1 EU = 10秒计算;模型调用按 Token 数量计算
5. **渠道名称**: 如果租户未关联渠道,显示"无渠道"
6. **记录类型**: 通过 `type` 字段区分 Agent 使用记录和模型调用记录