# 监控大屏 API 接入文档 ## 1. 概述 监控大屏 API 提供树状层级的消费数据汇总,用于平台监控大屏展示。 **层级结构**:平台 → 超级管理员 → 租户 → 用户 每条消费记录包含三级折扣及对应金额:**用户折扣**、**企业折扣**、**超管折扣**。 --- ## 2. 部署准备 ### 2.1 执行数据库迁移 ```sql -- 迁移文件:migrations/060_create_super_admin_tenant_table.sql psql -d your_database -f migrations/060_create_super_admin_tenant_table.sql -- 迁移文件:migrations/063_create_super_admin_model_discount_table.sql psql -d your_database -f migrations/063_create_super_admin_model_discount_table.sql ``` ### 2.2 初始化超级管理员-租户关联数据 迁移执行后,需要往 `super_admin_tenant` 表写入关联关系: ```sql -- 示例:将租户 1、2、3 分配给超级管理员 1 INSERT INTO aigcspace.super_admin_tenant (super_admin_id, tenant_id) VALUES (1, 1), (1, 2), (1, 3); -- 将租户 4、5 分配给超级管理员 2 INSERT INTO aigcspace.super_admin_tenant (super_admin_id, tenant_id) VALUES (2, 4), (2, 5); ``` > **注意**:未关联到任何超级管理员的租户会归入"未分配租户"节点展示。 ### 2.3 同步超管折扣 超管折扣从 crawler API 同步到数据库,可通过爬虫自动同步或手动触发: ```bash # 手动同步(爬虫同步时会自动写入超管折扣表) python -c "from app.services.crawler_sync_service import _fetch_crawler_discounts_for_sync; print(_fetch_crawler_discounts_for_sync(db))" ``` 或通过超管后台接口手动同步: ``` POST /api/super/super-admin/discounts/sync ``` ### 2.4 重启后端服务 ```bash # 开发环境 python main.py # 生产环境 gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8010 ``` --- ## 3. API 接口 ### 3.1 获取监控大屏数据 ``` GET /api/public/monitoring/dashboard ``` > **公共接口,无需鉴权** #### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `start_date` | string | 否 | 查询开始日期,格式 `YYYY-MM-DD`,不传则查全部 | | `end_date` | string | 否 | 查询结束日期,格式 `YYYY-MM-DD`,不传则查全部 | | `super_admin_id` | integer | 否 | 指定某个超级管理员ID,不传则查全部 | #### 请求示例 ```bash # 查询全部 curl -X GET "http://localhost:8010/api/public/monitoring/dashboard" # 按时间范围查询 curl -X GET "http://localhost:8010/api/public/monitoring/dashboard?start_date=2026-01-01&end_date=2026-05-12" # 查询某个超级管理员 curl -X GET "http://localhost:8010/api/public/monitoring/dashboard?super_admin_id=1" ``` #### 响应结构 ```json { "overview": { "total_super_admins": 3, "total_tenants": 15, "total_users": 200, "total_consumption": "12500.0000", "total_tenant_charged": "9800.0000", "total_balance": "50000.0000" }, "super_admins": [ { "super_admin_id": 1, "username": "admin1", "nickname": "管理员A", "tenant_count": 5, "total_consumption": "8000.0000", "total_tenant_charged": "6400.0000", "tenants": [ { "tenant_id": 1, "company_name": "成都网讯", "subdomain": "wangxun", "total_consumption": "3000.0000", "total_tenant_charged": "2400.0000", "balance": "10000.0000", "user_count": 20, "users": [ { "user_id": "u001", "username": "zhangsan", "nickname": "张三", "total_consumption": "500.0000", "tenant_actual_total": "400.0000", "consumption_records": [ { "user_id": "u001", "username": "zhangsan", "tenant_name": "成都网讯", "order_no": "20260508194831_ai_conversation_xxx", "model_name": "通义千问Plus", "model_code": "qwen-plus", "amount": "0.0040", "created_at": "2026-05-08T19:48:31", "invoiced": false, "user_discount": "0.9000", "user_actual_price": "0.0040", "tenant_discount": "0.8000", "tenant_actual_price": "0.0036", "super_admin_discount": "0.8000", "super_admin_actual_price": "0.0036" } ] } ] } ] } ], "start_date": "2026-01-01", "end_date": "2026-05-12" } ``` --- ## 4. 字段说明 ### 4.1 平台汇总 (`overview`) | 字段 | 类型 | 说明 | |------|------|------| | `total_super_admins` | integer | 超级管理员数量 | | `total_tenants` | integer | 租户总数 | | `total_users` | integer | 用户总数 | | `total_consumption` | string(Decimal) | 平台总消费金额(元),即所有用户实际支付之和 | | `total_tenant_charged` | string(Decimal) | 平台向企业收取总额(元),即用户实际支付 / 用户折扣 × 企业折扣 | | `total_balance` | string(Decimal) | 所有企业当前余额合计(元) | ### 4.2 超级管理员节点 (`super_admins[]`) | 字段 | 类型 | 说明 | |------|------|------| | `super_admin_id` | integer | 超级管理员ID | | `username` | string | 管理员用户名 | | `nickname` | string | 管理员昵称 | | `tenant_count` | integer | 管辖的租户数量 | | `total_consumption` | string(Decimal) | 管辖范围内所有用户消费总额 | | `total_tenant_charged` | string(Decimal) | 管辖范围内平台向企业收取总额 | | `tenants` | array | 管辖的租户列表 | > **过滤规则**:该超管管辖范围内没有任何有消费记录的租户时,该节点仍会返回但 `tenants` 为空数组。 ### 4.3 租户节点 (`tenants[]`) | 字段 | 类型 | 说明 | |------|------|------| | `tenant_id` | integer | 租户ID | | `company_name` | string | 企业名称 | | `subdomain` | string | 二级域名前缀 | | `total_consumption` | string(Decimal) | 该租户下所有用户消费总额 | | `total_tenant_charged` | string(Decimal) | 平台向该企业收取的总额 | | `balance` | string(Decimal) | 企业当前余额 | | `user_count` | integer | 用户数量 | | `users` | array | 用户消费列表(仅包含有消费记录的用户) | > **过滤规则**:租户下所有用户在查询时间范围内均无消费记录时,该租户节点被过滤不返回。 ### 4.4 用户节点 (`users[]`) | 字段 | 类型 | 说明 | |------|------|------| | `user_id` | string | 用户ID | | `username` | string | 用户名 | | `nickname` | string | 用户昵称 | | `total_consumption` | string(Decimal) | 用户累计消费(用户实际支付金额) | | `tenant_actual_total` | string(Decimal) | 企业为该用户实际被平台收取的总额 | | `consumption_records` | array | 该用户的消费记录流水(扁平列表) | > **过滤规则**:用户在查询时间范围内无消费记录时,该用户节点被过滤不返回。 ### 4.5 消费记录流水 (`consumption_records[]`) 每条记录包含用户信息、消费明细和**三级折扣及金额**: | 字段 | 类型 | 说明 | |------|------|------| | `user_id` | string | 用户ID | | `username` | string | 用户名 | | `tenant_name` | string | 所属租户名称(NULL表示平台直属用户) | | `order_no` | string | 订单号(对应 balance_log.biz_order_no) | | `model_name` | string | 模型显示名称 | | `model_code` | string | 模型标识(如 `qwen-plus`) | | `amount` | string(Decimal) | 消费金额(用户实际支付) | | `created_at` | string(datetime) | 消费时间 | | `invoiced` | boolean | 是否已开票 | | `user_discount` | string(Decimal) | 用户折扣率(0~1,如 0.9 表示9折) | | `user_actual_price` | string(Decimal) | 用户实际支付金额 | | `tenant_discount` | string(Decimal) | 企业折扣率(0~1,如 0.8 表示8折) | | `tenant_actual_price` | string(Decimal) | 企业实际支付金额 | | `super_admin_discount` | string(Decimal) | 超级管理员折扣率(从 crawler API 同步) | | `super_admin_actual_price` | string(Decimal) | 平台向超管收取金额 | --- ## 5. 金额关系说明 ### 5.1 三级折扣体系 ``` 平台原价 │ ├── 超管折扣率 (super_admin_discount) → 平台向超管收取 (super_admin_actual_price) │ = amount / user_discount × super_admin_discount │ ├── 企业折扣率 (tenant_discount) → 企业实际支付 (tenant_actual_price) │ = amount / user_discount × tenant_discount │ └── 用户折扣率 (user_discount) → 用户实际支付 (user_actual_price) = amount(user_consumption.amount 已为用户折扣后的价格) ``` **计算公式**: | 层级 | 折扣率来源 | 实际支付计算 | |------|-----------|-------------| | 用户 | `user_model_discount` 表 | `user_actual_price = amount` | | 企业 | `tenant_model_discount` 表 | `tenant_actual_price = amount / user_discount × tenant_discount` | | 超管 | `super_admin_model_discount` 表(crawler 同步) | `super_admin_actual_price = amount / user_discount × super_admin_discount` | **关键区别**: - `user_actual_price`:用户实际掏的钱(用户余额扣减) - `tenant_actual_price`:平台从企业账户扣的钱(企业余额扣减) - `super_admin_actual_price`:平台向超管收取的金额 - 三者之差 = 各层级的折扣让利空间 ### 5.2 超管折扣同步 超管折扣存储在 `super_admin_model_discount` 表中,通过爬虫同步服务自动更新: - 全局折扣:`model_code = "*"` - 模型级折扣:`model_code = 具体模型标识` - 优先匹配具体模型,无匹配则使用全局折扣 `*` - 爬虫同步时写入,监控接口直接读库,响应速度快 --- ## 6. 前端接入示例 ### 6.1 Vue 3 数据获取 ```javascript import { ref, onMounted } from 'vue' const dashboardData = ref(null) async function fetchDashboard(startDate, endDate) { const params = new URLSearchParams() if (startDate) params.append('start_date', startDate) if (endDate) params.append('end_date', endDate) const res = await fetch(`/api/public/monitoring/dashboard?${params}`) dashboardData.value = await res.json() } ``` ### 6.2 消费记录表格渲染 ```javascript // 扁平化所有用户的消费记录 function flattenRecords(data) { const records = [] for (const sa of data.super_admins) { for (const tenant of sa.tenants) { for (const user of tenant.users) { for (const record of user.consumption_records) { records.push(record) } } } } return records.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)) } ``` ### 6.3 按模型聚合 ```javascript // 从消费记录中按模型聚合 function aggregateByModel(dashboardData) { const modelMap = {} for (const sa of dashboardData.super_admins) { for (const tenant of sa.tenants) { for (const user of tenant.users) { for (const record of user.consumption_records) { const key = record.model_code if (!modelMap[key]) { modelMap[key] = { model_code: record.model_code, model_name: record.model_name, total_amount: 0, user_total: 0, tenant_total: 0, super_admin_total: 0, count: 0 } } modelMap[key].total_amount += parseFloat(record.amount) modelMap[key].user_total += parseFloat(record.user_actual_price) modelMap[key].tenant_total += parseFloat(record.tenant_actual_price) modelMap[key].super_admin_total += parseFloat(record.super_admin_actual_price) modelMap[key].count += 1 } } } } return Object.values(modelMap).sort((a, b) => b.total_amount - a.total_amount) } ``` --- ## 7. 常见问题 ### Q: 为什么有些用户/租户不返回? A: 接口默认过滤 `total_consumption = 0` 的用户和租户。只有查询时间范围内有真实消费记录的节点才会返回。 ### Q: 未分配租户是什么意思? A: 如果某个租户没有在 `super_admin_tenant` 表中关联任何超级管理员,该租户会归入 `super_admin_id=0, nickname="未分配租户"` 的特殊节点。 ### Q: 折扣率是 1.0 代表什么? A: 折扣率 1.0 表示没有折扣(原价)。0.8 表示8折,用户只需支付原价的80%。 ### Q: 超管折扣如何更新? A: 超管折扣通过爬虫服务 `sync_from_crawler` 自动同步。爬虫每次同步模型数据时,会将折扣写入 `super_admin_model_discount` 表。也可通过超管后台 `POST /api/super/super-admin/discounts/sync` 手动触发同步。 ### Q: 时间范围如何影响数据? A: `start_date` 和 `end_date` 过滤 `user_consumption` 表的 `created_at` 字段。企业余额是实时快照不受时间过滤。 ### Q: 返回数据量很大怎么办? A: 可以通过 `super_admin_id` 参数只查某个管理员的数据,减少返回量。