From 06eb129a2826875f031384940e8dcb3c9a5afbfa Mon Sep 17 00:00:00 2001 From: john Date: Tue, 30 Jun 2026 01:39:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(mindspace):=20=E4=BF=AE=E5=A4=8D=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=97=B6=E6=97=A0=E6=B3=95=E8=A7=A3=E6=9E=90=20MySQL?= =?UTF-8?q?=20JSON=20=E5=AF=BC=E8=87=B4=E9=A1=B5=E9=9D=A2=E5=A4=8D?= =?UTF-8?q?=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit source_snapshot_json 在 mysql2 中已是对象,JSON.parse 失败会使 relative_path 丢失、工作区 HTML 未清理;同步后页面重新出现。 Co-authored-by: Cursor --- mindspace-page-sync.mjs | 17 +++++++++++------ mindspace-pages.mjs | 22 ++++++++++++++++++---- mindspace-pages.test.mjs | 10 ++++++++++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/mindspace-page-sync.mjs b/mindspace-page-sync.mjs index e35a2c5..883c04e 100644 --- a/mindspace-page-sync.mjs +++ b/mindspace-page-sync.mjs @@ -52,6 +52,16 @@ async function findPageByWorkspaceRelativePath(pool, userId, relativePath) { return { id: row.id, updatedAt: Number(row.updated_at ?? 0) }; } +function parseJsonColumn(value, fallback = {}) { + if (value == null || value === '') return { ...fallback }; + if (typeof value === 'object' && !Buffer.isBuffer(value)) return value; + try { + return JSON.parse(String(value)); + } catch { + return { ...fallback }; + } +} + async function loadIndexedWorkspacePages(pool, userId) { const [rows] = await pool.query( `SELECT p.id, p.updated_at, p.source_asset_id, pv.source_snapshot_json @@ -62,12 +72,7 @@ async function loadIndexedWorkspacePages(pool, userId) { ); const byPath = new Map(); for (const row of rows) { - let snapshot = {}; - try { - snapshot = JSON.parse(row.source_snapshot_json ?? '{}'); - } catch { - snapshot = {}; - } + const snapshot = parseJsonColumn(row.source_snapshot_json); const relativePath = normalizePublicHtmlPath(snapshot.relative_path); if (relativePath) { byPath.set(relativePath, { diff --git a/mindspace-pages.mjs b/mindspace-pages.mjs index b1fbefb..411671c 100644 --- a/mindspace-pages.mjs +++ b/mindspace-pages.mjs @@ -29,6 +29,16 @@ function asNumber(value) { return Number(value ?? 0); } +export function parseJsonColumn(value, fallback = {}) { + if (value == null || value === '') return { ...fallback }; + if (typeof value === 'object' && !Buffer.isBuffer(value)) return value; + try { + return JSON.parse(String(value)); + } catch { + return { ...fallback }; + } +} + function pageError(message, code, details) { return Object.assign(new Error(message), { code, details }); } @@ -730,7 +740,7 @@ export function createPageService(pool, options = {}) { ); let snapshot = {}; try { - snapshot = JSON.parse(rows[0]?.source_snapshot_json ?? '{}'); + snapshot = parseJsonColumn(rows[0]?.source_snapshot_json); } catch { snapshot = {}; } @@ -1084,7 +1094,7 @@ export function createPageService(pool, options = {}) { LIMIT 1`, [pageId, userId], ); - const snapshot = JSON.parse(rows[0]?.source_snapshot_json ?? '{}'); + const snapshot = parseJsonColumn(rows[0]?.source_snapshot_json); workspaceHtmlRelativePath = snapshot.relative_path ?? null; } catch { workspaceHtmlRelativePath = null; @@ -1166,7 +1176,10 @@ export function createPageService(pool, options = {}) { publishDir: workspacePublishDir, htmlRelativePath: workspaceHtmlRelativePath, html: page.content ?? '', - }).catch(() => ({ removed: [], skipped: [] })); + }).catch((error) => { + console.warn('[MindSpace] workspace purge failed:', error?.message ?? error); + return { removed: [], skipped: [] }; + }); return { ...result, workspaceFilesRemoved: purgeResult.removed ?? [] }; }; @@ -1190,7 +1203,7 @@ export function createPageService(pool, options = {}) { const content = await fs.readFile(storagePath, 'utf8'); let snapshot = {}; try { - snapshot = JSON.parse(row.source_snapshot_json ?? '{}'); + snapshot = parseJsonColumn(row.source_snapshot_json); } catch { snapshot = {}; } @@ -1375,6 +1388,7 @@ export const pageInternals = { escapeHtml, normalizePageInput, normalizeListPageFilters, + parseJsonColumn, renderContent, renderPreviewHtml, renderHtmlPreview, diff --git a/mindspace-pages.test.mjs b/mindspace-pages.test.mjs index 5404dbb..6801a80 100644 --- a/mindspace-pages.test.mjs +++ b/mindspace-pages.test.mjs @@ -2,6 +2,16 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { pageInternals } from './mindspace-pages.mjs'; +test('parseJsonColumn accepts mysql json objects and strings', () => { + const objectValue = { relative_path: 'public/demo.html', auto_synced: true }; + assert.deepEqual(pageInternals.parseJsonColumn(objectValue), objectValue); + assert.deepEqual( + pageInternals.parseJsonColumn(JSON.stringify(objectValue)), + objectValue, + ); + assert.deepEqual(pageInternals.parseJsonColumn(null, { ok: true }), { ok: true }); +}); + test('normalizePageInput trims values and constrains templates', () => { const result = pageInternals.normalizePageInput({ title: ' 页面标题 ',