Files
users/app/api/api-keys/billing/route.ts

210 lines
7.6 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
// 调用日志接口
interface CallLog {
id: string
keyId: string
keyName: string
endpoint: string
method: string
status: number
credits: number
responseTime: number
timestamp: string
ip: string
requestFields?: string[]
}
// 计费明细接口
interface BillingDetail {
category: string
callCount: number
avgPrice: number
totalCredits: number
percentage: number
}
// 内存存储调用日志
const callLogs: CallLog[] = [
{ id: 'log_1', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/user', method: 'GET', status: 200, credits: 5, responseTime: 123, timestamp: '2026-01-31 14:32:15', ip: '123.45.67.89' },
{ id: 'log_2', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/users/batch', method: 'POST', status: 200, credits: 40, responseTime: 856, timestamp: '2026-01-31 14:30:02', ip: '123.45.67.89' },
{ id: 'log_3', keyId: 'key_2', keyName: '点了码-测试环境', endpoint: '/api/shensheshou/ai/chat', method: 'POST', status: 200, credits: 5, responseTime: 2341, timestamp: '2026-01-31 14:28:45', ip: '98.76.54.32' },
{ id: 'log_4', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/tags', method: 'GET', status: 200, credits: 0.5, responseTime: 45, timestamp: '2026-01-31 14:25:18', ip: '123.45.67.89' },
{ id: 'log_5', keyId: 'key_2', keyName: '点了码-测试环境', endpoint: '/api/shensheshou/user', method: 'GET', status: 403, credits: 0, responseTime: 12, timestamp: '2026-01-31 14:20:33', ip: '98.76.54.32' },
{ id: 'log_6', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/ai/analyze', method: 'POST', status: 200, credits: 10, responseTime: 5623, timestamp: '2026-01-31 14:15:00', ip: '123.45.67.89' },
{ id: 'log_7', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/packages/create', method: 'POST', status: 200, credits: 5, responseTime: 1234, timestamp: '2026-01-31 14:10:22', ip: '123.45.67.89' },
{ id: 'log_8', keyId: 'key_2', keyName: '点了码-测试环境', endpoint: '/api/shensheshou/ingest', method: 'POST', status: 429, credits: 0, responseTime: 8, timestamp: '2026-01-31 14:05:11', ip: '98.76.54.32' },
]
// API分类映射
const ENDPOINT_CATEGORIES: Record<string, string> = {
'/api/shensheshou/user': '用户查询',
'/api/shensheshou/users/batch': '用户查询',
'/api/shensheshou/tags': '标签服务',
'/api/shensheshou/tags/apply': '标签服务',
'/api/shensheshou/ai/chat': 'AI服务',
'/api/shensheshou/ai/analyze': 'AI服务',
'/api/shensheshou/ai/tag': 'AI服务',
'/api/shensheshou/sources': '数据服务',
'/api/shensheshou/ingest': '数据服务',
'/api/shensheshou/report/generate': '报告服务',
'/api/shensheshou/packages': '流量包',
'/api/shensheshou/packages/create': '流量包',
'/api/shensheshou/packages/export': '流量包',
}
// GET: 获取调用日志和计费明细
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const action = searchParams.get('action')
const keyId = searchParams.get('keyId')
const startDate = searchParams.get('startDate')
const endDate = searchParams.get('endDate')
const page = parseInt(searchParams.get('page') || '1')
const pageSize = parseInt(searchParams.get('pageSize') || '20')
// 筛选日志
let filteredLogs = [...callLogs]
if (keyId) {
filteredLogs = filteredLogs.filter(log => log.keyId === keyId)
}
if (startDate) {
filteredLogs = filteredLogs.filter(log => log.timestamp >= startDate)
}
if (endDate) {
filteredLogs = filteredLogs.filter(log => log.timestamp <= endDate)
}
// 获取计费明细
if (action === 'billing') {
const categoryStats: Record<string, { count: number; credits: number }> = {}
let totalCredits = 0
filteredLogs.forEach(log => {
const category = ENDPOINT_CATEGORIES[log.endpoint] || '其他'
if (!categoryStats[category]) {
categoryStats[category] = { count: 0, credits: 0 }
}
categoryStats[category].count++
categoryStats[category].credits += log.credits
totalCredits += log.credits
})
const billingDetails: BillingDetail[] = Object.entries(categoryStats).map(([category, stats]) => ({
category,
callCount: stats.count,
avgPrice: stats.count > 0 ? Number((stats.credits / stats.count).toFixed(2)) : 0,
totalCredits: stats.credits,
percentage: totalCredits > 0 ? Number(((stats.credits / totalCredits) * 100).toFixed(1)) : 0
}))
return NextResponse.json({
success: true,
data: {
details: billingDetails.sort((a, b) => b.totalCredits - a.totalCredits),
totalCredits,
totalCalls: filteredLogs.length,
period: {
start: startDate || filteredLogs[filteredLogs.length - 1]?.timestamp?.split(' ')[0],
end: endDate || filteredLogs[0]?.timestamp?.split(' ')[0]
}
}
})
}
// 获取统计概览
if (action === 'stats') {
const today = new Date().toISOString().split('T')[0]
const todayLogs = filteredLogs.filter(log => log.timestamp.startsWith(today))
const successLogs = filteredLogs.filter(log => log.status === 200)
return NextResponse.json({
success: true,
data: {
today: {
calls: todayLogs.length,
credits: todayLogs.reduce((sum, log) => sum + log.credits, 0),
avgResponseTime: todayLogs.length > 0
? Math.round(todayLogs.reduce((sum, log) => sum + log.responseTime, 0) / todayLogs.length)
: 0
},
total: {
calls: filteredLogs.length,
credits: filteredLogs.reduce((sum, log) => sum + log.credits, 0),
successRate: filteredLogs.length > 0
? Number(((successLogs.length / filteredLogs.length) * 100).toFixed(1))
: 0
}
}
})
}
// 分页返回日志
const startIndex = (page - 1) * pageSize
const paginatedLogs = filteredLogs.slice(startIndex, startIndex + pageSize)
return NextResponse.json({
success: true,
data: paginatedLogs,
pagination: {
page,
pageSize,
total: filteredLogs.length,
totalPages: Math.ceil(filteredLogs.length / pageSize)
}
})
} catch (error) {
console.error('获取计费信息失败:', error)
return NextResponse.json({
success: false,
error: '获取计费信息失败'
}, { status: 500 })
}
}
// POST: 记录API调用
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { keyId, keyName, endpoint, method, status, credits, responseTime, ip, requestFields } = body
const newLog: CallLog = {
id: `log_${Date.now()}`,
keyId,
keyName,
endpoint,
method,
status,
credits: credits || 0,
responseTime: responseTime || 0,
timestamp: new Date().toISOString().replace('T', ' ').substring(0, 19),
ip: ip || 'unknown',
requestFields
}
callLogs.unshift(newLog) // 添加到开头
// 保持日志数量在合理范围
if (callLogs.length > 10000) {
callLogs.splice(10000)
}
return NextResponse.json({
success: true,
data: newLog
})
} catch (error) {
console.error('记录调用日志失败:', error)
return NextResponse.json({
success: false,
error: '记录调用日志失败'
}, { status: 500 })
}
}