From 375fd462242043ddf4a25a447ede1692557b4668 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 8 May 2026 02:31:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=91=E7=AB=AF?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E8=AE=B0=E5=BD=95data=E4=B8=BAnull=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端: handleAddRecord增加recordData nil检查,避免存入null - 后端: 记录读取改用interface{}替代map[string]interface{}解析JSON - 后端: JSON解析失败时回退返回原始字符串 - 后端: API返回raw_data字段暴露数据库原始数据用于诊断 - 后端: app/cloud.go同步修复同样的JSON解析问题 - 前端: parseRecordData增加raw_data回退解析 - 前端: 添加数据诊断卡片显示原始数据结构 --- backend/internal/router/admin/cloud.go | 14 ++- backend/internal/router/app/cloud.go | 11 ++- backend/internal/router/app/dynamic.go | 19 ++-- .../admin/cloud-variables/[id]/records.vue | 89 ++++++++++++++----- 4 files changed, 98 insertions(+), 35 deletions(-) diff --git a/backend/internal/router/admin/cloud.go b/backend/internal/router/admin/cloud.go index 33a063e..d7f4184 100644 --- a/backend/internal/router/admin/cloud.go +++ b/backend/internal/router/admin/cloud.go @@ -1,4 +1,4 @@ -package admin +package admin import ( "crypto/md5" @@ -745,11 +745,17 @@ func handleGetCloudVariableRecords(c *gin.Context) { result := make([]gin.H, len(records)) for i, r := range records { - var data map[string]interface{} - json.Unmarshal([]byte(r.Data), &data) + var parsedData interface{} + if r.Data != "" { + json.Unmarshal([]byte(r.Data), &parsedData) + } + if parsedData == nil && r.Data != "" { + parsedData = r.Data + } record := gin.H{ "id": r.ID, - "data": data, + "data": parsedData, + "raw_data": r.Data, "created_at": r.CreatedAt, } if r.AppUserID != nil { diff --git a/backend/internal/router/app/cloud.go b/backend/internal/router/app/cloud.go index 7c14bfd..7862df4 100644 --- a/backend/internal/router/app/cloud.go +++ b/backend/internal/router/app/cloud.go @@ -818,11 +818,16 @@ func handleAppGetVariableRecords(c *gin.Context) { result := make([]gin.H, len(records)) for i, r := range records { - var data map[string]interface{} - json.Unmarshal([]byte(r.Data), &data) + var parsedData interface{} + if r.Data != "" { + json.Unmarshal([]byte(r.Data), &parsedData) + } + if parsedData == nil && r.Data != "" { + parsedData = r.Data + } result[i] = gin.H{ "id": r.ID, - "data": data, + "data": parsedData, "created_at": r.CreatedAt, } } diff --git a/backend/internal/router/app/dynamic.go b/backend/internal/router/app/dynamic.go index a673482..f0048b6 100644 --- a/backend/internal/router/app/dynamic.go +++ b/backend/internal/router/app/dynamic.go @@ -592,6 +592,9 @@ func handleAddRecord(appID uint, userID *uint, actionMap map[string]interface{}) if !ok { return fmt.Errorf("缺少record_data参数") } + if recordData == nil { + return fmt.Errorf("record_data不能为空") + } var variable model.CloudVariable if err := database.DB.Where("app_id = ? AND key = ?", appID, variableKey).First(&variable).Error; err != nil { @@ -684,13 +687,17 @@ func getRecords(appID uint, userID *uint, variableKey string, page, pageSize int var recordList []interface{} for _, record := range records { var data interface{} - if err := json.Unmarshal([]byte(record.Data), &data); err == nil { - recordList = append(recordList, map[string]interface{}{ - "id": record.ID, - "data": data, - "created_at": record.CreatedAt, - }) + if record.Data != "" { + json.Unmarshal([]byte(record.Data), &data) } + if data == nil && record.Data != "" { + data = record.Data + } + recordList = append(recordList, map[string]interface{}{ + "id": record.ID, + "data": data, + "created_at": record.CreatedAt, + }) } result["success"] = true diff --git a/frontend/src/pages/admin/cloud-variables/[id]/records.vue b/frontend/src/pages/admin/cloud-variables/[id]/records.vue index 7d8f99e..456e646 100644 --- a/frontend/src/pages/admin/cloud-variables/[id]/records.vue +++ b/frontend/src/pages/admin/cloud-variables/[id]/records.vue @@ -29,7 +29,8 @@ interface Variable { interface VariableRecord { id: number - data: Record | string + data: any + raw_data?: string user_id?: number user?: { id: number @@ -39,16 +40,53 @@ interface VariableRecord { } function parseRecordData(record: VariableRecord): Record { - if (!record.data) return {} - if (typeof record.data === 'string') { + let rawData = record.data + + if ((rawData == null || rawData === '') && record.raw_data) { try { - return JSON.parse(record.data) + rawData = JSON.parse(record.raw_data) + } + catch { + rawData = record.raw_data + } + } + + if (rawData == null || rawData === '') return {} + + if (typeof rawData === 'object') { + if (Array.isArray(rawData)) { + if (rawData.length > 0 && typeof rawData[0] === 'object' && rawData[0] !== null && !Array.isArray(rawData[0])) { + return rawData[0] as Record + } + const obj: Record = {} + rawData.forEach((item, index) => { obj[`item_${index}`] = item }) + return obj + } + return rawData as Record + } + + if (typeof rawData === 'string') { + try { + const parsed = JSON.parse(rawData) + if (parsed != null && typeof parsed === 'object') { + if (Array.isArray(parsed)) { + if (parsed.length > 0 && typeof parsed[0] === 'object' && parsed[0] !== null && !Array.isArray(parsed[0])) { + return parsed[0] as Record + } + const obj: Record = {} + parsed.forEach((item: any, index: number) => { obj[`item_${index}`] = item }) + return obj + } + return parsed as Record + } + return {} } catch { return {} } } - return record.data + + return {} } const route = useRoute() @@ -70,21 +108,25 @@ const deleteTargetId = ref(null) const batchDeleteDialogOpen = ref(false) const dynamicKeys = computed(() => { - console.log('Computing dynamicKeys, records count:', records.value.length) const keys = new Set() - records.value.forEach((record, index) => { - console.log(`Record ${index}:`, record) + records.value.forEach((record) => { const data = parseRecordData(record) - console.log(`Record ${index} parsed data:`, data, 'type:', typeof data) if (data && typeof data === 'object') { - const recordKeys = Object.keys(data) - console.log(`Record ${index} keys:`, recordKeys) - recordKeys.forEach(key => keys.add(key)) + Object.keys(data).forEach(key => keys.add(key)) } }) - const result = Array.from(keys).sort() - console.log('Final dynamicKeys:', result) - return result + return Array.from(keys).sort() +}) + +const debugInfo = computed(() => { + if (records.value.length === 0 || dynamicKeys.value.length > 0) return null + const first = records.value[0] + return { + dataType: first.data === null ? 'null' : first.data === undefined ? 'undefined' : typeof first.data, + dataValue: first.data === null ? 'null' : first.data === undefined ? 'undefined' : typeof first.data === 'object' ? JSON.stringify(first.data) : String(first.data), + rawDataField: first.raw_data === undefined ? '(字段不存在)' : first.raw_data === '' ? '(空字符串)' : first.raw_data || '(falsy)', + parsedKeys: (() => { const p = parseRecordData(first); return p && typeof p === 'object' ? Object.keys(p) : [] })(), + } }) const columns = computed[]>(() => { @@ -256,13 +298,6 @@ async function fetchRecords() { if (searchQuery.value) params.append('search', searchQuery.value) const data = await api.get<{ records: VariableRecord[], total: number }>(`/dev/cloud-variables/${variable.value.id}/records?${params}`) - console.log('API response data:', data) - console.log('Records:', data?.records) - if (data?.records?.length > 0) { - console.log('First record:', data.records[0]) - console.log('First record data:', data.records[0].data) - console.log('First record data type:', typeof data.records[0].data) - } records.value = data?.records || [] total.value = data?.total || 0 } @@ -405,6 +440,16 @@ onMounted(async () => { +
+

数据诊断 (Data Debug)

+
+
data 类型: {{ debugInfo.dataType }}
+
data 值: {{ debugInfo.dataValue.substring(0, 500) }}
+
raw_data (数据库原始): {{ debugInfo.rawDataField.substring(0, 500) }}
+
解析后 keys: {{ debugInfo.parsedKeys.length > 0 ? debugInfo.parsedKeys.join(', ') : '(空)' }}
+
+
+