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, } }