跳至主要内容

用量与分析

ONE SDK 提供了专用的 UsageService,用于跟踪 API 消耗、监控配额和查看用量分析。您可以在仪表板中查看用量数据,也可以通过编程方式查询。

创建 UsageService

SDK 提供了两个工厂函数来创建该服务。

createUsageService

使用显式配置创建新的 UsageService 实例。

import { createUsageService } from '@one_deploy/sdk';

const usageService = createUsageService({
engineUrl: process.env.ONE_ENGINE_URL!,
clientId: process.env.ONE_CLIENT_ID!,
secretKey: process.env.ONE_SECRET_KEY!,
projectId: 'proj_abc123',
});

getUsageService

获取一个单例 UsageService 实例,它会复用当前 OneEngineClient 上下文中的配置。

import { getUsageService } from '@one_deploy/sdk';

const usageService = getUsageService();
提示

在 React 组件中使用 getUsageService() 时,服务会自动从最近的 OneProvider 继承配置。无需额外设置。

类型

UsageCategory

对正在跟踪的 API 调用类型进行分类。

type UsageCategory =
| 'auth'
| 'wallet'
| 'swap'
| 'onramp'
| 'offramp'
| 'ai_trading'
| 'forex'
| 'contracts'
| 'nfts'
| 'billing'
| 'staking'
| 'bridge'
| 'gas'
| 'price'
| 'webhooks'
| 'admin'
| 'project';

DisplayCategory

用于仪表板 UI 图表和表格的更高级别分组。

type DisplayCategory =
| 'Authentication'
| 'Wallet & Assets'
| 'Payments'
| 'Trading'
| 'Infrastructure'
| 'Management';

UsageRecord

表示一个按时间分桶的计数数据点。

interface UsageRecord {
id: string;
projectId: string;
category: UsageCategory;
count: number;
timestamp: string; // ISO-8601
metadata?: Record<string, unknown>;
}

UsageSummary

指定时间范围内项目的汇总用量总计。

interface UsageSummary {
projectId: string;
totalCalls: number;
byCategory: Record<UsageCategory, number>;
byDisplayCategory: Record<DisplayCategory, number>;
periodStart: string; // ISO-8601
periodEnd: string; // ISO-8601
quotaLimit: number;
quotaUsed: number;
quotaRemaining: number;
}

UsageActivity

用量动态的带时间戳的活动条目。

interface UsageActivity {
id: string;
projectId: string;
category: UsageCategory;
action: string; // e.g. "swap.execute", "wallet.getBalance"
timestamp: string; // ISO-8601
statusCode: number;
durationMs: number;
metadata?: Record<string, unknown>;
}

UsageResponse

用量查询方法返回的标准封装。

interface UsageResponse<T> {
success: boolean;
data?: T;
error?: string;
meta?: {
page: number;
limit: number;
total: number;
};
}

跟踪 API 用量

获取用量摘要

获取项目在指定时间范围内的汇总摘要。

const summary = await usageService.getUsageSummary({
projectId: 'proj_abc123',
from: '2025-01-01T00:00:00Z',
to: '2025-01-31T23:59:59Z',
});

if (summary.success) {
const data: UsageSummary = summary.data;
console.log('Total API calls:', data.totalCalls);
console.log('Quota used:', data.quotaUsed, '/', data.quotaLimit);
console.log('Remaining:', data.quotaRemaining);

// Breakdown by category
for (const [category, count] of Object.entries(data.byCategory)) {
console.log(` ${category}: ${count} calls`);
}
}

获取用量记录

获取细粒度的按时间分桶的用量记录,用于图表和详细分析。

const records = await usageService.getUsageRecords({
projectId: 'proj_abc123',
category: 'ai_trading',
from: '2025-01-01T00:00:00Z',
to: '2025-01-31T23:59:59Z',
granularity: 'day', // 'hour' | 'day' | 'week' | 'month'
page: 1,
limit: 31,
});

if (records.success) {
for (const record of records.data) {
console.log(`${record.timestamp}: ${record.count} calls`);
}
}

获取用量活动动态

获取按时间排序的单个 API 调用动态,用于调试和审计。

const activity = await usageService.getUsageActivity({
projectId: 'proj_abc123',
category: 'wallet',
page: 1,
limit: 50,
});

if (activity.success) {
for (const entry of activity.data) {
console.log(
`[${entry.timestamp}] ${entry.action} -- ${entry.statusCode} (${entry.durationMs}ms)`
);
}
}

在仪表板中查看分析

dashboard.one23.io 上的仪表板提供了可视化分析视图,包括:

  • 调用量图表 -- 选定时间范围内的每日和每小时 API 调用量。
  • 分类分布 -- 饼图或条形图,显示各 DisplayCategory 组的调用分布。
  • 热门端点 -- 最常调用的 API 方法的排名列表。
  • 错误率 -- 返回 4xx 或 5xx 状态码的调用百分比。
  • 延迟百分位 -- 所有端点的 p50、p95 和 p99 响应时间。

配额与速率限制

每个项目计划都有每月 API 调用配额和每秒速率限制。

计划月度配额速率限制 (req/s)
Free10,00010
Pro500,000100
Enterprise自定义自定义

检查配额状态

const summary = await usageService.getUsageSummary({
projectId: 'proj_abc123',
from: new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString(),
to: new Date().toISOString(),
});

if (summary.success) {
const { quotaLimit, quotaUsed, quotaRemaining } = summary.data;
const usagePercent = ((quotaUsed / quotaLimit) * 100).toFixed(1);

console.log(`Usage: ${quotaUsed} / ${quotaLimit} (${usagePercent}%)`);
console.log(`Remaining: ${quotaRemaining}`);

if (quotaRemaining < quotaLimit * 0.1) {
console.warn('Warning: Less than 10% of your monthly quota remains.');
}
}

速率限制响应头

当请求被速率限制时,API 返回 429 Too Many Requests 状态,并附带以下响应头:

响应头说明
X-RateLimit-Limit您的计划允许的每秒最大请求数。
X-RateLimit-Remaining当前窗口内剩余的请求数。
X-RateLimit-Reset速率限制窗口重置的 Unix 时间戳(秒)。
Retry-After重试前需要等待的秒数。

处理速率限制

async function callWithRetry<T>(
fn: () => Promise<UsageResponse<T>>,
maxRetries: number = 3
): Promise<UsageResponse<T>> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fn();

if (res.success || res.error !== 'rate_limited') {
return res;
}

// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, attempt) * 1000;
console.warn(`Rate limited. Retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}

return { success: false, error: 'Max retries exceeded' };
}

完整示例

usage-report.ts
import { createUsageService } from '@one_deploy/sdk';

const usageService = createUsageService({
engineUrl: process.env.ONE_ENGINE_URL!,
clientId: process.env.ONE_CLIENT_ID!,
secretKey: process.env.ONE_SECRET_KEY!,
projectId: 'proj_abc123',
});

async function generateMonthlyReport() {
const now = new Date();
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);

// 1. Summary
const summary = await usageService.getUsageSummary({
projectId: 'proj_abc123',
from: monthStart.toISOString(),
to: now.toISOString(),
});

if (!summary.success) {
console.error('Failed to fetch summary:', summary.error);
return;
}

console.log('=== Monthly Usage Report ===');
console.log(`Period: ${summary.data.periodStart} to ${summary.data.periodEnd}`);
console.log(`Total calls: ${summary.data.totalCalls}`);
console.log(`Quota: ${summary.data.quotaUsed} / ${summary.data.quotaLimit}`);

// 2. Category breakdown
console.log('\n--- By Category ---');
for (const [cat, count] of Object.entries(summary.data.byCategory)) {
if (count > 0) {
console.log(` ${cat}: ${count}`);
}
}

// 3. Daily records for the top category
const topCategory = Object.entries(summary.data.byCategory)
.sort(([, a], [, b]) => b - a)[0];

if (topCategory) {
const records = await usageService.getUsageRecords({
projectId: 'proj_abc123',
category: topCategory[0] as any,
from: monthStart.toISOString(),
to: now.toISOString(),
granularity: 'day',
});

if (records.success) {
console.log(`\n--- Daily Breakdown: ${topCategory[0]} ---`);
for (const record of records.data) {
console.log(` ${record.timestamp}: ${record.count}`);
}
}
}
}

generateMonthlyReport();

下一步