138 lines
4.8 KiB
Go
138 lines
4.8 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
managedaccess "billing/internal/access"
|
|
)
|
|
|
|
type readOnlyManagedKeyDTO struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
MaskedSecret string `json:"masked_secret"`
|
|
Status string `json:"status"`
|
|
RouteMode string `json:"route_mode"`
|
|
UpstreamAccountID string `json:"upstream_account_id,omitempty"`
|
|
AllModels bool `json:"all_models"`
|
|
ShowInStats bool `json:"show_in_stats"`
|
|
Models []string `json:"models"`
|
|
Billing billingStateDTO `json:"billing"`
|
|
}
|
|
|
|
func (a *App) readOnlyResponse(query url.Values) ManagementResponse {
|
|
switch strings.TrimSpace(query.Get("view")) {
|
|
case "keys":
|
|
return a.readOnlyManagedKeys(query.Get("include_archived") == "1")
|
|
case "usage":
|
|
return a.readOnlyUsageResponse(query)
|
|
case "usage-summary":
|
|
return a.usageDashboardResponse()
|
|
case "prices":
|
|
return a.listPrices()
|
|
case "price-catalog":
|
|
return a.searchPriceCatalog(query)
|
|
case "upstreams":
|
|
return a.readOnlyUpstreamAccounts()
|
|
case "model-suggestions":
|
|
return a.modelSuggestions()
|
|
case "key-stats":
|
|
return a.readOnlyManagedKeyStats(strings.TrimSpace(query.Get("id")))
|
|
case "events":
|
|
return a.businessEvents(query)
|
|
default:
|
|
return managementError(http.StatusBadRequest, "invalid_view", "只读资源类型不存在")
|
|
}
|
|
}
|
|
|
|
func (a *App) readOnlyUsageResponse(query url.Values) ManagementResponse {
|
|
limited := url.Values{"page": {"1"}, "page_size": {"50"}}
|
|
response := a.usageResponse(limited)
|
|
if response.StatusCode != http.StatusOK {
|
|
return response
|
|
}
|
|
var payload usageListResponse
|
|
if err := json.Unmarshal(response.Body, &payload); err != nil {
|
|
return managementError(http.StatusInternalServerError, "response_error", "无法生成只读请求明细")
|
|
}
|
|
redactUsageItems(payload.Records)
|
|
return jsonManagementResponse(http.StatusOK, payload)
|
|
}
|
|
|
|
func (a *App) readOnlyManagedKeyStats(id string) ManagementResponse {
|
|
response := a.managedKeyStats(id)
|
|
if response.StatusCode != http.StatusOK {
|
|
return response
|
|
}
|
|
var payload managedKeyStatsResponse
|
|
if err := json.Unmarshal(response.Body, &payload); err != nil {
|
|
return managementError(http.StatusInternalServerError, "response_error", "无法生成只读 Key 统计")
|
|
}
|
|
redactUsageItems(payload.Recent)
|
|
return jsonManagementResponse(http.StatusOK, payload)
|
|
}
|
|
|
|
func redactUsageItems(items []usageListItem) {
|
|
for index := range items {
|
|
items[index].APIKey = ""
|
|
}
|
|
}
|
|
|
|
func (a *App) readOnlyManagedKeys(includeArchived bool) ManagementResponse {
|
|
store, ok := a.currentStore()
|
|
if !ok {
|
|
return managementError(http.StatusServiceUnavailable, "database_unavailable", "Key 数据库尚未初始化")
|
|
}
|
|
keys, err := store.ListManagedKeys(context.Background(), includeArchived)
|
|
if err != nil {
|
|
return managementError(http.StatusInternalServerError, "database_error", err.Error())
|
|
}
|
|
items := make([]readOnlyManagedKeyDTO, 0, len(keys))
|
|
for _, key := range keys {
|
|
state, stateErr := store.BillingState(context.Background(), key.ID, time.Now().UTC())
|
|
if stateErr != nil {
|
|
return managementError(http.StatusInternalServerError, "database_error", stateErr.Error())
|
|
}
|
|
items = append(items, readOnlyManagedKeyDTO{
|
|
ID: key.ID, Name: key.Name, MaskedSecret: maskManagedSecret(key.Secret), Status: key.Status,
|
|
RouteMode: key.RouteMode, UpstreamAccountID: key.UpstreamAccountID,
|
|
AllModels: key.AllModels, ShowInStats: key.ShowInStats, Models: key.Models, Billing: billingStateResponse(state),
|
|
})
|
|
}
|
|
return jsonManagementResponse(http.StatusOK, map[string]any{"keys": items})
|
|
}
|
|
|
|
func (a *App) readOnlyUpstreamAccounts() ManagementResponse {
|
|
store, ok := a.currentStore()
|
|
if !ok {
|
|
return managementError(http.StatusServiceUnavailable, "database_unavailable", "上游账号数据库尚未初始化")
|
|
}
|
|
accounts, err := store.ListUpstreamAccounts(context.Background())
|
|
if err != nil {
|
|
return managementError(http.StatusInternalServerError, "database_error", err.Error())
|
|
}
|
|
return jsonManagementResponse(http.StatusOK, map[string]any{"accounts": accounts})
|
|
}
|
|
|
|
func maskManagedSecret(secret string) string {
|
|
characters := []rune(secret)
|
|
if len(characters) < 6 {
|
|
return "******"
|
|
}
|
|
return string(characters[:2]) + "******" + string(characters[len(characters)-4:])
|
|
}
|
|
|
|
func billingStateResponse(state managedaccess.BillingState) billingStateDTO {
|
|
return billingStateDTO{
|
|
QuotaUSD: formatMicros(state.QuotaMicros), SpentUSD: formatMicros(state.SpentMicros),
|
|
LifetimeSpentUSD: formatMicros(state.LifetimeSpentMicros),
|
|
BalanceUSD: formatMicros(state.BalanceMicros), ResetPeriod: state.ResetPeriod,
|
|
NextResetAt: state.NextResetAt, MaxConcurrency: state.MaxConcurrency,
|
|
ActiveRequests: state.ActiveRequests, CycleStartedAt: state.CycleStartedAt,
|
|
}
|
|
}
|