135 lines
5.2 KiB
Go
135 lines
5.2 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"billing/internal/collection"
|
|
)
|
|
|
|
const usageDashboardUsersSQL = `
|
|
WITH today AS (
|
|
SELECT d.managed_key_id, COUNT(*) AS requests,
|
|
COALESCE(SUM(u.input_tokens),0) AS input_tokens,
|
|
COALESCE(SUM(u.output_tokens),0) AS output_tokens,
|
|
COALESCE(SUM(u.total_tokens),0) AS total_tokens,
|
|
COALESCE(SUM(u.cost_micros),0) AS cost_micros
|
|
FROM request_detail_index d INDEXED BY idx_request_detail_time
|
|
LEFT JOIN usage_records u ON u.id=d.usage_id
|
|
WHERE d.requested_at>=?
|
|
GROUP BY d.managed_key_id
|
|
), dashboard_keys AS (
|
|
SELECT id, name FROM managed_keys WHERE status <> 'archived' AND show_in_stats=1
|
|
UNION ALL
|
|
SELECT t.managed_key_id, '未识别'
|
|
FROM today t LEFT JOIN managed_keys k ON k.id=t.managed_key_id
|
|
WHERE k.id IS NULL
|
|
)
|
|
SELECT k.id, k.name,
|
|
COALESCE(t.requests,0), COALESCE(t.input_tokens,0), COALESCE(t.output_tokens,0),
|
|
COALESCE(t.total_tokens,0), COALESCE(t.cost_micros,0),
|
|
(SELECT recent.requested_at FROM request_detail_index recent
|
|
WHERE recent.managed_key_id=k.id
|
|
ORDER BY recent.requested_at DESC, recent.id DESC LIMIT 1)
|
|
FROM dashboard_keys k
|
|
LEFT JOIN today t ON t.managed_key_id=k.id
|
|
ORDER BY COALESCE(t.total_tokens,0) DESC, k.name COLLATE NOCASE`
|
|
|
|
func (r *SQLiteUsageRepository) UsageDashboard(ctx context.Context, today time.Time, days int) (collection.UsageDashboard, error) {
|
|
if days < 1 {
|
|
days = 7
|
|
}
|
|
start := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location())
|
|
dashboard := collection.UsageDashboard{}
|
|
if err := r.readDB.QueryRowContext(ctx, `
|
|
SELECT COUNT(*), COALESCE(SUM(u.input_tokens),0), COALESCE(SUM(u.output_tokens),0),
|
|
COALESCE(SUM(u.total_tokens),0), COALESCE(SUM(u.cost_micros),0)
|
|
FROM request_detail_index d LEFT JOIN usage_records u ON u.id=d.usage_id
|
|
WHERE d.requested_at>=?`, formatTime(start)).Scan(&dashboard.Today.Requests, &dashboard.Today.InputTokens, &dashboard.Today.OutputTokens, &dashboard.Today.TotalTokens, &dashboard.Today.CostMicros); err != nil {
|
|
return dashboard, fmt.Errorf("汇总今日用量: %w", err)
|
|
}
|
|
rows, err := r.readDB.QueryContext(ctx, usageDashboardUsersSQL, formatTime(start))
|
|
if err != nil {
|
|
return dashboard, fmt.Errorf("汇总用户用量: %w", err)
|
|
}
|
|
for rows.Next() {
|
|
var item collection.UserUsageSummary
|
|
var last sql.NullString
|
|
if err := rows.Scan(&item.KeyID, &item.KeyAlias, &item.Today.Requests, &item.Today.InputTokens, &item.Today.OutputTokens, &item.Today.TotalTokens, &item.Today.CostMicros, &last); err != nil {
|
|
_ = rows.Close()
|
|
return dashboard, fmt.Errorf("读取用户用量汇总: %w", err)
|
|
}
|
|
if last.Valid {
|
|
parsed, parseErr := time.Parse(time.RFC3339Nano, last.String)
|
|
if parseErr == nil {
|
|
item.LastUsedAt = &parsed
|
|
}
|
|
}
|
|
dashboard.Users = append(dashboard.Users, item)
|
|
}
|
|
_ = rows.Close()
|
|
|
|
dailyStart := start.AddDate(0, 0, -(days - 1))
|
|
rows, err = r.readDB.QueryContext(ctx, `
|
|
SELECT DATE(d.requested_at, '+8 hours'), COALESCE(SUM(u.total_tokens),0), COALESCE(SUM(u.cost_micros),0)
|
|
FROM request_detail_index d LEFT JOIN usage_records u ON u.id=d.usage_id
|
|
WHERE d.requested_at>=? GROUP BY DATE(d.requested_at, '+8 hours') ORDER BY 1`, formatTime(dailyStart))
|
|
if err != nil {
|
|
return dashboard, fmt.Errorf("汇总每日用量: %w", err)
|
|
}
|
|
byDate := make(map[string]int64)
|
|
costByDate := make(map[string]int64)
|
|
for rows.Next() {
|
|
var date string
|
|
var tokens, costMicros int64
|
|
if err := rows.Scan(&date, &tokens, &costMicros); err != nil {
|
|
_ = rows.Close()
|
|
return dashboard, fmt.Errorf("读取每日用量: %w", err)
|
|
}
|
|
byDate[date] = tokens
|
|
costByDate[date] = costMicros
|
|
}
|
|
_ = rows.Close()
|
|
for index := 0; index < days; index++ {
|
|
date := dailyStart.AddDate(0, 0, index)
|
|
key := date.Format("2006-01-02")
|
|
dashboard.Days = append(dashboard.Days, collection.DailyUsageSummary{Date: date, TotalTokens: byDate[key], CostMicros: costByDate[key]})
|
|
}
|
|
|
|
rows, err = r.readDB.QueryContext(ctx, `
|
|
SELECT d.managed_key_id, DATE(d.requested_at, '+8 hours'),
|
|
COALESCE(SUM(u.total_tokens),0), COALESCE(SUM(u.cost_micros),0)
|
|
FROM request_detail_index d LEFT JOIN usage_records u ON u.id=d.usage_id
|
|
WHERE d.requested_at>=?
|
|
GROUP BY d.managed_key_id, DATE(d.requested_at, '+8 hours')`, formatTime(dailyStart))
|
|
if err != nil {
|
|
return dashboard, fmt.Errorf("汇总用户每日用量: %w", err)
|
|
}
|
|
userDays := make(map[string]map[string]collection.DailyUsageSummary)
|
|
for rows.Next() {
|
|
var keyID, date string
|
|
var tokens, costMicros int64
|
|
if err := rows.Scan(&keyID, &date, &tokens, &costMicros); err != nil {
|
|
_ = rows.Close()
|
|
return dashboard, fmt.Errorf("读取用户每日用量: %w", err)
|
|
}
|
|
if userDays[keyID] == nil {
|
|
userDays[keyID] = make(map[string]collection.DailyUsageSummary)
|
|
}
|
|
userDays[keyID][date] = collection.DailyUsageSummary{TotalTokens: tokens, CostMicros: costMicros}
|
|
}
|
|
_ = rows.Close()
|
|
for index := range dashboard.Users {
|
|
for dayIndex := 0; dayIndex < days; dayIndex++ {
|
|
date := dailyStart.AddDate(0, 0, dayIndex)
|
|
key := date.Format("2006-01-02")
|
|
item := userDays[dashboard.Users[index].KeyID][key]
|
|
item.Date = date
|
|
dashboard.Users[index].Days = append(dashboard.Users[index].Days, item)
|
|
}
|
|
}
|
|
return dashboard, nil
|
|
}
|