fix(response):修复响应结构错误问题

This commit is contained in:
qian committed 2026-09-19 13:18:39 +08:00
1 parent 4889477452
commit 22bdb23c8d
21 files changed
+208 -65

No files matched your search

+12 -3
View File
@@ -1,6 +1,10 @@
package v1 package v1
import "github.com/gogf/gf/v2/frame/g" import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
)
// 渠道调试日志:调试开关开启的渠道,per-attempt 记录客户端↔系统↔上游四段完整报文。 // 渠道调试日志:调试开关开启的渠道,per-attempt 记录客户端↔系统↔上游四段完整报文。
// 端点全部为 GET/DELETE,路径在 /channels/ 下,自动命中 rbac 前缀规则 // 端点全部为 GET/DELETE,路径在 /channels/ 下,自动命中 rbac 前缀规则
@@ -50,9 +54,14 @@ type ChannelDebugLogDetailReq struct {
ID int64 `json:"id" in:"path" v:"required" dc:"记录ID"` ID int64 `json:"id" in:"path" v:"required" dc:"记录ID"`
} }
// ChannelDebugLogDetailRes 渠道调试日志详情响应 // ChannelDebugLogDetailRes 渠道调试日志详情响应:详情对象平铺进统一响应的 data 字段
// (json:"-" + 自定义 MarshalJSON),避免统一包装后再多包一层 data
type ChannelDebugLogDetailRes struct { type ChannelDebugLogDetailRes struct {
Data map[string]any `json:"data"` Data map[string]any `json:"-"`
}
func (r *ChannelDebugLogDetailRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
// ChannelDebugLogDeleteReq 删除单条调试日志请求(硬删除) // ChannelDebugLogDeleteReq 删除单条调试日志请求(硬删除)
+12 -2
View File
@@ -1,6 +1,10 @@
package v1 package v1
import "github.com/gogf/gf/v2/frame/g" import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
)
// === 渠道错误监控 === // === 渠道错误监控 ===
@@ -61,8 +65,14 @@ type ChannelErrorCategoriesReq struct {
g.Meta `path:"/monitor/channel-errors/categories" method:"get" mime:"json" tags:"管理后台-监控" summary:"错误分类选项"` g.Meta `path:"/monitor/channel-errors/categories" method:"get" mime:"json" tags:"管理后台-监控" summary:"错误分类选项"`
} }
// ChannelErrorCategoriesRes 分类选项平铺进统一响应的 data 字段(json:"-" + 自定义
// MarshalJSON),避免统一包装后再多包一层 data
type ChannelErrorCategoriesRes struct { type ChannelErrorCategoriesRes struct {
Data []map[string]string `json:"data"` Data []map[string]string `json:"-"`
}
func (r *ChannelErrorCategoriesRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
// ChannelErrorClearReq 清空全部渠道错误事件(硬删除,用于错误大量堆积时快速释放数据库空间) // ChannelErrorClearReq 清空全部渠道错误事件(硬删除,用于错误大量堆积时快速释放数据库空间)
+57 -8
View File
@@ -1,6 +1,8 @@
package v1 package v1
import ( import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/os/gtime"
) )
@@ -142,6 +144,54 @@ type AdminUsageLogListReq struct {
AdminUsageLogFilter `json:",inline"` // 嵌入共用筛选条件 AdminUsageLogFilter `json:",inline"` // 嵌入共用筛选条件
} }
// AdminUsageLogListItem 用量日志列表项:仅表格列与悬浮明细(Token / 费用分解)所需字段。
// 纯关联 ID(user_id / project_id / channel_id)与排查大字段(billing_snapshot、
// user_agent、error_message、request_id 等)不进列表,统一经详情接口按需获取。
type AdminUsageLogListItem struct {
Id int64 `json:"id"`
TenantId int64 `json:"tenant_id"`
TenantName string `json:"tenant_name"`
Username string `json:"username"`
ProjectName string `json:"project_name"`
ApiKeyId int64 `json:"api_key_id"`
ApiKeyName string `json:"api_key_name"`
ChannelName string `json:"channel_name"`
ModelName string `json:"model_name"`
UpstreamModel string `json:"upstream_model"`
RequestType int `json:"request_type"`
BillingMode string `json:"billing_mode"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
CacheCreationTokens int `json:"cache_creation_tokens"`
CacheReadTokens int `json:"cache_read_tokens"`
CacheCreation5mTokens int `json:"cache_creation_5m_tokens"`
CacheCreation1hTokens int `json:"cache_creation_1h_tokens"`
ReasoningTokens int `json:"reasoning_tokens"`
AudioInputTokens int `json:"audio_input_tokens"`
AudioOutputTokens int `json:"audio_output_tokens"`
ImageOutputTokens int `json:"image_output_tokens"`
InputCost float64 `json:"input_cost"`
OutputCost float64 `json:"output_cost"`
CacheCreationCost float64 `json:"cache_creation_cost"`
CacheReadCost float64 `json:"cache_read_cost"`
TotalCost float64 `json:"total_cost"`
ActualCost float64 `json:"actual_cost"`
RateMultiplier float64 `json:"rate_multiplier"`
LatencyMs int `json:"latency_ms"`
FirstTokenMs int `json:"first_token_ms"`
Status string `json:"status"`
RetryIndex int `json:"retry_index"`
CreatedAt *gtime.Time `json:"created_at"`
}
type AdminUsageLogListRes struct {
List []*AdminUsageLogListItem `json:"list"`
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// AdminUsageLogItem 用量日志全量字段结构体(详情接口专用,列表用上方的精简 ListItem)
type AdminUsageLogItem struct { type AdminUsageLogItem struct {
Id int64 `json:"id"` Id int64 `json:"id"`
TenantId int64 `json:"tenant_id"` TenantId int64 `json:"tenant_id"`
@@ -204,13 +254,6 @@ type AdminUsageLogItem struct {
CreatedAt *gtime.Time `json:"created_at"` CreatedAt *gtime.Time `json:"created_at"`
} }
type AdminUsageLogListRes struct {
List []*AdminUsageLogItem `json:"list"`
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// AdminUsageLogDetailReq 用量日志详情(列表只返回表格展示字段, // AdminUsageLogDetailReq 用量日志详情(列表只返回表格展示字段,
// billing_snapshot / billing_summary / user_agent / error_message 等大字段经此接口按需获取) // billing_snapshot / billing_summary / user_agent / error_message 等大字段经此接口按需获取)
type AdminUsageLogDetailReq struct { type AdminUsageLogDetailReq struct {
@@ -218,8 +261,14 @@ type AdminUsageLogDetailReq struct {
Id int64 `json:"id" in:"path" v:"min:1" dc:"用量记录ID"` Id int64 `json:"id" in:"path" v:"min:1" dc:"用量记录ID"`
} }
// AdminUsageLogDetailRes 详情对象平铺进统一响应的 data 字段(json:"-" + 自定义 MarshalJSON,
// 与 RequestAuditLogDetailRes 同一模式),避免统一包装后再多包一层 data
type AdminUsageLogDetailRes struct { type AdminUsageLogDetailRes struct {
Data *AdminUsageLogItem `json:"data"` Data *AdminUsageLogItem `json:"-"`
}
func (r *AdminUsageLogDetailRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
// AdminUsageLogSummaryReq 用量日志统计请求(复用筛选条件,不包含分页) // AdminUsageLogSummaryReq 用量日志统计请求(复用筛选条件,不包含分页)
+7 -1
View File
@@ -58,8 +58,14 @@ type ErrorLogStatsReq struct {
g.Meta `path:"/error-logs/stats" method:"get" mime:"json" tags:"管理后台-系统错误" summary:"错误统计"` g.Meta `path:"/error-logs/stats" method:"get" mime:"json" tags:"管理后台-系统错误" summary:"错误统计"`
} }
// ErrorLogStatsRes 统计对象平铺进统一响应的 data 字段(json:"-" + 自定义 MarshalJSON),
// 避免统一包装后再多包一层 data
type ErrorLogStatsRes struct { type ErrorLogStatsRes struct {
Data map[string]any `json:"data"` Data map[string]any `json:"-"`
}
func (r *ErrorLogStatsRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
// ErrorLogClearReq 清空全部错误日志(硬删除,用于错误大量堆积时快速释放数据库空间) // ErrorLogClearReq 清空全部错误日志(硬删除,用于错误大量堆积时快速释放数据库空间)
+42 -8
View File
@@ -1,6 +1,10 @@
package v1 package v1
import "github.com/gogf/gf/v2/frame/g" import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
)
// === 监控仪表盘 === // === 监控仪表盘 ===
@@ -9,8 +13,14 @@ type MonitorDashboardReq struct {
Minutes int `json:"minutes" in:"query" d:"5"` Minutes int `json:"minutes" in:"query" d:"5"`
} }
// MonitorDashboardRes 数据对象平铺进统一响应的 data 字段(json:"-" + 自定义 MarshalJSON),
// 避免统一包装后再多包一层 data(monitor 包下带 Data 的 Res 均为此模式)
type MonitorDashboardRes struct { type MonitorDashboardRes struct {
Data map[string]any `json:"data"` Data map[string]any `json:"-"`
}
func (r *MonitorDashboardRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
type MonitorTrafficReq struct { type MonitorTrafficReq struct {
@@ -19,7 +29,11 @@ type MonitorTrafficReq struct {
} }
type MonitorTrafficRes struct { type MonitorTrafficRes struct {
Data any `json:"data"` Data any `json:"-"`
}
func (r *MonitorTrafficRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
type MonitorTrafficFlowReq struct { type MonitorTrafficFlowReq struct {
@@ -30,7 +44,11 @@ type MonitorTrafficFlowReq struct {
} }
type MonitorTrafficFlowRes struct { type MonitorTrafficFlowRes struct {
Data any `json:"data"` Data any `json:"-"`
}
func (r *MonitorTrafficFlowRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
type MonitorModelPerformanceReq struct { type MonitorModelPerformanceReq struct {
@@ -102,7 +120,11 @@ type MonitorLatencyReq struct {
} }
type MonitorLatencyRes struct { type MonitorLatencyRes struct {
Data map[string]any `json:"data"` Data map[string]any `json:"-"`
}
func (r *MonitorLatencyRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
type MonitorSystemReq struct { type MonitorSystemReq struct {
@@ -111,7 +133,11 @@ type MonitorSystemReq struct {
} }
type MonitorSystemRes struct { type MonitorSystemRes struct {
Data map[string]any `json:"data"` Data map[string]any `json:"-"`
}
func (r *MonitorSystemRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
// MonitorDispatchReq 渠道调度引擎指标请求 // MonitorDispatchReq 渠道调度引擎指标请求
@@ -176,7 +202,11 @@ type MonitorRealtimeReq struct {
} }
type MonitorRealtimeRes struct { type MonitorRealtimeRes struct {
Data any `json:"data"` Data any `json:"-"`
}
func (r *MonitorRealtimeRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
// === 告警规则 === // === 告警规则 ===
@@ -202,7 +232,11 @@ type AlertOptionsReq struct {
} }
type AlertOptionsRes struct { type AlertOptionsRes struct {
Data map[string]any `json:"data"` Data map[string]any `json:"-"`
}
func (r *AlertOptionsRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
type AlertRuleCreateReq struct { type AlertRuleCreateReq struct {
+9 -1
View File
@@ -1,6 +1,8 @@
package v1 package v1
import ( import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/os/gtime"
) )
@@ -59,8 +61,14 @@ type PlanDetailReq struct {
Id int64 `json:"id" in:"path" v:"required|min:1"` Id int64 `json:"id" in:"path" v:"required|min:1"`
} }
// PlanDetailRes 详情对象平铺进统一响应的 data 字段(json:"-" + 自定义 MarshalJSON),
// 避免统一包装后再多包一层 data
type PlanDetailRes struct { type PlanDetailRes struct {
Data *PlanItem `json:"data"` Data *PlanItem `json:"-"`
}
func (r *PlanDetailRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
type PlanUpdateReq struct { type PlanUpdateReq struct {
+9 -1
View File
@@ -1,6 +1,8 @@
package v1 package v1
import ( import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
) )
@@ -62,6 +64,12 @@ type TenantRequestAuditLogDetailReq struct {
Id int64 `json:"id" in:"path" v:"required|min:1"` Id int64 `json:"id" in:"path" v:"required|min:1"`
} }
// TenantRequestAuditLogDetailRes 详情对象平铺进统一响应的 data 字段(json:"-" + 自定义
// MarshalJSON),避免统一包装后再多包一层 data(与 admin 端审计详情接口同一模式)
type TenantRequestAuditLogDetailRes struct { type TenantRequestAuditLogDetailRes struct {
Data map[string]any `json:"data"` Data map[string]any `json:"-"`
}
func (r *TenantRequestAuditLogDetailRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
+12 -2
View File
@@ -1,6 +1,10 @@
package v1 package v1
import "github.com/gogf/gf/v2/frame/g" import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
)
// === 租户用量与账单 === // === 租户用量与账单 ===
@@ -31,8 +35,14 @@ type TenantUsageLogDetailReq struct {
Id int64 `json:"id" in:"path" v:"min:1" dc:"用量记录ID"` Id int64 `json:"id" in:"path" v:"min:1" dc:"用量记录ID"`
} }
// TenantUsageLogDetailRes 详情对象平铺进统一响应的 data 字段(json:"-" + 自定义
// MarshalJSON),避免统一包装后再多包一层 data(与 admin 端详情接口同一模式)
type TenantUsageLogDetailRes struct { type TenantUsageLogDetailRes struct {
Data map[string]any `json:"data"` Data map[string]any `json:"-"`
}
func (r *TenantUsageLogDetailRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
// TenantUsageLogsSummaryReq 用量日志统计请求(复用列表筛选条件,不含分页) // TenantUsageLogsSummaryReq 用量日志统计请求(复用列表筛选条件,不含分页)
+9 -1
View File
@@ -1,6 +1,8 @@
package v1 package v1
import ( import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/os/gtime"
) )
@@ -124,8 +126,14 @@ type TenantProjectUsageStatsReq struct {
Id int64 `json:"id" in:"path" v:"required|min:1"` Id int64 `json:"id" in:"path" v:"required|min:1"`
} }
// TenantProjectUsageStatsRes 统计对象平铺进统一响应的 data 字段(json:"-" + 自定义
// MarshalJSON),避免统一包装后再多包一层 data
type TenantProjectUsageStatsRes struct { type TenantProjectUsageStatsRes struct {
Data map[string]any `json:"data"` Data map[string]any `json:"-"`
}
func (r *TenantProjectUsageStatsRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
} }
type TenantProjectUsageLogsReq struct { type TenantProjectUsageLogsReq struct {
+6 -1
View File
@@ -355,7 +355,8 @@ func (s *sAdmin) ListRequestAuditLogs(ctx context.Context, req *v1.RequestAuditL
} }
where := strings.Join(conditions, " AND ") where := strings.Join(conditions, " AND ")
// 列表白名单:表格列 + EnrichAuditRecords 回填名称所需的关联 ID。 // 列表白名单:表格列 + EnrichAuditRecords 回填名称所需的关联键。
// user_id / project_id 仅作关联查询键(username / project_name 据此回填),表格不展示,出参剔除;
// request_id / query_params / user_agent 等列表不展示的字段经详情接口获取。 // request_id / query_params / user_agent 等列表不展示的字段经详情接口获取。
auditFields := "id, tenant_id, user_id, api_key_id, project_id, method, path, model_name, status_code, client_ip, latency_ms, first_token_ms, audit_level, created_at" auditFields := "id, tenant_id, user_id, api_key_id, project_id, method, path, model_name, status_code, client_ip, latency_ms, first_token_ms, audit_level, created_at"
@@ -363,6 +364,10 @@ func (s *sAdmin) ListRequestAuditLogs(ctx context.Context, req *v1.RequestAuditL
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, item := range items {
delete(item, "user_id")
delete(item, "project_id")
}
return &v1.RequestAuditLogListRes{ return &v1.RequestAuditLogListRes{
List: items, List: items,
+10 -7
View File
@@ -455,19 +455,22 @@ func buildUsageLogFilter(f v1.AdminUsageLogFilter) (where string, args []any) {
const usageLogJoinedFrom = "bil_usage_logs u LEFT JOIN tnt_users t ON u.user_id = t.id AND u.tenant_id = t.tenant_id LEFT JOIN tnt_projects p ON u.project_id = p.id LEFT JOIN tnt_tenants tn ON u.tenant_id = tn.id LEFT JOIN api_keys ak ON u.api_key_id = ak.id" const usageLogJoinedFrom = "bil_usage_logs u LEFT JOIN tnt_users t ON u.user_id = t.id AND u.tenant_id = t.tenant_id LEFT JOIN tnt_projects p ON u.project_id = p.id LEFT JOIN tnt_tenants tn ON u.tenant_id = tn.id LEFT JOIN api_keys ak ON u.api_key_id = ak.id"
// usageLogListFields 列表展示字段白名单:表格列 + 悬浮明细(Token / 费用分解)所需。 // usageLogListFields 列表展示字段白名单:表格列 + 悬浮明细(Token / 费用分解)所需。
// 大字段(billing_snapshot、billing_summary、user_agent、error_message 等)只进详情接口, // 纯关联 ID(user_id / project_id / channel_id)列表不展示(名称经 JOIN 回填,
// tenant_id / api_key_id 保留作名称为空时的兜底展示),只进详情接口;
// 大字段(billing_snapshot、billing_summary、user_agent、error_message 等)同样只进详情,
// 禁止改回全字段 —— 列表每页最多 100 行,快照 JSONB 会成倍放大响应体积。 // 禁止改回全字段 —— 列表每页最多 100 行,快照 JSONB 会成倍放大响应体积。
const usageLogListFields = `u.id, u.tenant_id, COALESCE(tn.name, '') AS tenant_name, u.user_id, COALESCE(t.username, '') AS username, const usageLogListFields = `u.id, u.tenant_id, COALESCE(tn.name, '') AS tenant_name, COALESCE(t.username, '') AS username,
u.project_id, COALESCE(p.name, '') AS project_name, u.api_key_id, COALESCE(ak.name, '') AS api_key_name, COALESCE(p.name, '') AS project_name, u.api_key_id, COALESCE(ak.name, '') AS api_key_name,
u.channel_id, u.channel_name, u.model_name, u.upstream_model, u.request_type, u.billing_mode, u.channel_name, u.model_name, u.upstream_model, u.request_type, u.billing_mode,
u.input_tokens, u.output_tokens, u.cache_creation_tokens, u.cache_read_tokens, u.input_tokens, u.output_tokens, u.cache_creation_tokens, u.cache_read_tokens,
u.cache_creation_5m_tokens, u.cache_creation_1h_tokens, u.reasoning_tokens, u.cache_creation_5m_tokens, u.cache_creation_1h_tokens, u.reasoning_tokens,
u.audio_input_tokens, u.audio_output_tokens, u.image_output_tokens, u.audio_input_tokens, u.audio_output_tokens, u.image_output_tokens,
u.input_cost, u.output_cost, u.cache_creation_cost, u.cache_read_cost, u.total_cost, u.actual_cost, u.input_cost, u.output_cost, u.cache_creation_cost, u.cache_read_cost, u.total_cost, u.actual_cost,
u.rate_multiplier, u.latency_ms, u.first_token_ms, u.status, u.retry_index, u.created_at` u.rate_multiplier, u.latency_ms, u.first_token_ms, u.status, u.retry_index, u.created_at`
// usageLogDetailFields 详情字段:列表白名单 + 仅详情展示的大字段(错误信息、UA、计费快照等) // usageLogDetailFields 详情字段:列表白名单 + 纯关联 ID + 仅详情展示的大字段(错误信息、UA、计费快照等)
const usageLogDetailFields = usageLogListFields + `, const usageLogDetailFields = usageLogListFields + `,
u.user_id, u.project_id, u.channel_id,
u.channel_type, u.requested_model, u.relay_mode, u.currency, u.billing_source, u.channel_type, u.requested_model, u.relay_mode, u.currency, u.billing_source,
u.error_message, u.client_ip, u.user_agent, u.service_tier, u.reasoning_effort, u.stream_end_reason, u.error_message, u.client_ip, u.user_agent, u.service_tier, u.reasoning_effort, u.stream_end_reason,
u.image_count, u.image_size, u.pre_deduct_amount, u.refund_amount, u.supplement_amount, u.image_count, u.image_size, u.pre_deduct_amount, u.refund_amount, u.supplement_amount,
@@ -498,9 +501,9 @@ func (s *sAdmin) GetAllUsageLogs(ctx context.Context, req *v1.AdminUsageLogListR
return nil, err return nil, err
} }
logs := make([]*v1.AdminUsageLogItem, 0, len(result)) logs := make([]*v1.AdminUsageLogListItem, 0, len(result))
for _, row := range result { for _, row := range result {
item := &v1.AdminUsageLogItem{} item := &v1.AdminUsageLogListItem{}
if err := row.Struct(item); err != nil { if err := row.Struct(item); err != nil {
continue continue
} }
+12 -18
View File
@@ -45,21 +45,19 @@ func TestPlanCRUD(t *testing.T) {
detailResp.AssertSuccess(t) detailResp.AssertSuccess(t)
var detail struct { var detail struct {
Data struct { Id int64 `json:"id"`
Id int64 `json:"id"` Name string `json:"name"`
Name string `json:"name"` Identifier string `json:"identifier"`
Identifier string `json:"identifier"` Status string `json:"status"`
Status string `json:"status"`
} `json:"data"`
} }
detailResp.DecodeData(t, &detail) detailResp.DecodeData(t, &detail)
if detail.Data.Id != planID { if detail.Id != planID {
t.Fatalf("expected plan id=%d, got %d", planID, detail.Data.Id) t.Fatalf("expected plan id=%d, got %d", planID, detail.Id)
} }
t.Logf("Plan detail: name=%s, identifier=%s, status=%s", t.Logf("Plan detail: name=%s, identifier=%s, status=%s",
detail.Data.Name, detail.Data.Identifier, detail.Data.Status) detail.Name, detail.Identifier, detail.Status)
// Update // Update
updateResp := client.Put(fmt.Sprintf("/api/admin/plans/%d", planID), map[string]any{ updateResp := client.Put(fmt.Sprintf("/api/admin/plans/%d", planID), map[string]any{
@@ -76,15 +74,13 @@ func TestPlanCRUD(t *testing.T) {
verifyResp.AssertSuccess(t) verifyResp.AssertSuccess(t)
var verifyData struct { var verifyData struct {
Data struct { Description string `json:"description"`
Description string `json:"description"`
} `json:"data"`
} }
verifyResp.DecodeData(t, &verifyData) verifyResp.DecodeData(t, &verifyData)
if verifyData.Data.Description != "updated by integration test" { if verifyData.Description != "updated by integration test" {
t.Fatalf("expected description 'updated by integration test', got %q", t.Fatalf("expected description 'updated by integration test', got %q",
verifyData.Data.Description) verifyData.Description)
} }
t.Logf("Plan update verified") t.Logf("Plan update verified")
@@ -105,13 +101,11 @@ func TestPlanToggleRecommend(t *testing.T) {
detailResp.AssertSuccess(t) detailResp.AssertSuccess(t)
var detail struct { var detail struct {
Data struct { IsRecommended bool `json:"is_recommended"`
IsRecommended bool `json:"is_recommended"`
} `json:"data"`
} }
detailResp.DecodeData(t, &detail) detailResp.DecodeData(t, &detail)
t.Logf("Plan %d is_recommended=%v after first toggle", planID, detail.Data.IsRecommended) t.Logf("Plan %d is_recommended=%v after first toggle", planID, detail.IsRecommended)
// Toggle again to revert // Toggle again to revert
resp2 := client.Put(fmt.Sprintf("/api/admin/plans/%d/toggle-recommend", planID), nil) resp2 := client.Put(fmt.Sprintf("/api/admin/plans/%d/toggle-recommend", planID), nil)
+1 -1
View File
@@ -612,7 +612,7 @@ async function openDebugDetail(row: any) {
debugDetail.value = null debugDetail.value = null
try { try {
const res: any = await request.get(`/admin/channels/${channelId}/debug-logs/${row.id}`) const res: any = await request.get(`/admin/channels/${channelId}/debug-logs/${row.id}`)
debugDetail.value = res.data?.data?.data || null debugDetail.value = res.data?.data || null
} catch { } catch {
debugDetail.value = null debugDetail.value = null
} finally { } finally {
@@ -128,7 +128,7 @@ const columns: TableColumnData[] = [
async function fetchCategories() { async function fetchCategories() {
try { try {
const res = await request.get('/admin/monitor/channel-errors/categories') const res = await request.get('/admin/monitor/channel-errors/categories')
categoryOptions.value = res.data?.data?.data || [] categoryOptions.value = res.data?.data || []
} catch { } catch {
categoryOptions.value = [] categoryOptions.value = []
} }
@@ -142,7 +142,7 @@ async function fetchStats() {
statsLoading.value = true statsLoading.value = true
try { try {
const res = await request.get('/admin/error-logs/stats') const res = await request.get('/admin/error-logs/stats')
stats.value = res.data?.data?.data stats.value = res.data?.data
} catch { } catch {
// error auto-shown // error auto-shown
} finally { } finally {
+3 -3
View File
@@ -173,7 +173,7 @@ function updateCharts() {
async function fetchDashboard() { async function fetchDashboard() {
try { try {
const res = await request.get('/admin/monitor/dashboard', { params: { minutes: timeRange.value } }) const res = await request.get('/admin/monitor/dashboard', { params: { minutes: timeRange.value } })
dashboardData.value = res.data?.data?.data || {} dashboardData.value = res.data?.data || {}
} catch { } catch {
// error auto-shown by Axios interceptor // error auto-shown by Axios interceptor
} }
@@ -182,7 +182,7 @@ async function fetchDashboard() {
async function fetchSystemHistory() { async function fetchSystemHistory() {
try { try {
const res = await request.get('/admin/monitor/system', { params: { minutes: timeRange.value } }) const res = await request.get('/admin/monitor/system', { params: { minutes: timeRange.value } })
systemHistory.value = res.data?.data?.data?.history || [] systemHistory.value = res.data?.data?.history || []
} catch { } catch {
// keep previous data // keep previous data
} }
@@ -191,7 +191,7 @@ async function fetchSystemHistory() {
async function fetchTraffic() { async function fetchTraffic() {
try { try {
const res = await request.get('/admin/monitor/traffic', { params: { minutes: timeRange.value } }) const res = await request.get('/admin/monitor/traffic', { params: { minutes: timeRange.value } })
trafficData.value = res.data?.data?.data || [] trafficData.value = res.data?.data || []
} catch { } catch {
// keep previous data // keep previous data
} }
@@ -25,7 +25,7 @@ const gcPauseOption = shallowRef({})
async function fetchRealtime() { async function fetchRealtime() {
try { try {
const res = await request.get('/admin/monitor/realtime') const res = await request.get('/admin/monitor/realtime')
data.value = res.data?.data?.data || {} data.value = res.data?.data || {}
lastUpdated.value = new Date() lastUpdated.value = new Date()
updateCharts() updateCharts()
} catch { } catch {
@@ -95,7 +95,7 @@ async function fetchFlow() {
params.end_date = dateRange.value[1] params.end_date = dateRange.value[1]
} }
const res = await request.get('/admin/monitor/traffic-flow', { params }) const res = await request.get('/admin/monitor/traffic-flow', { params })
const payload = res.data?.data?.data || {} const payload = res.data?.data || {}
nodes.value = payload.nodes || [] nodes.value = payload.nodes || []
links.value = payload.links || [] links.value = payload.links || []
if (nodes.value.length > 0) { if (nodes.value.length > 0) {
@@ -245,7 +245,7 @@ async function handleSubmit() {
? `/tenant/projects/${props.projectId}/api-keys` ? `/tenant/projects/${props.projectId}/api-keys`
: '/tenant/api-keys' : '/tenant/api-keys'
const res: any = await request.post(url, body) const res: any = await request.post(url, body)
createdKey.value = (res.data?.data?.data || res.data?.data)?.key || '' createdKey.value = res.data?.data?.key || ''
} else { } else {
const body: any = { const body: any = {
name: form.name, name: form.name,
@@ -189,7 +189,7 @@ async function fetchDetail(id: number) {
detailRecord.value = null detailRecord.value = null
try { try {
const res: any = await request.get(`/tenant/audit/request-logs/${id}`) const res: any = await request.get(`/tenant/audit/request-logs/${id}`)
detailRecord.value = res.data?.data?.data || null detailRecord.value = res.data?.data || null
} catch { } catch {
detailRecord.value = null detailRecord.value = null
} finally { } finally {
@@ -329,8 +329,7 @@ async function fetchUsageStats() {
usageLoading.value = true usageLoading.value = true
try { try {
const res: any = await request.get(`/tenant/projects/${projectId.value}/usage-stats`) const res: any = await request.get(`/tenant/projects/${projectId.value}/usage-stats`)
const raw = res.data?.data usageStats.value = res.data?.data || null
usageStats.value = raw?.data || raw
} catch { } catch {
usageStats.value = null usageStats.value = null
} finally { } finally {