From 05661e2f3e055db7211bd92912812a19388366e7 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Sat, 19 Sep 2026 20:37:01 +0800 Subject: [PATCH] =?UTF-8?q?fix=F0=9F=90=9B:=20jsonField=20format=20check?= =?UTF-8?q?=20relaxed=20to=20any=20legal=20identifier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jsonFieldPattern copied businessName's rule (^[a-z][A-Za-z]+$: at least two letters, no digits) on the theory that jsonField should tighten to the same identifier shape. That does not hold: businessName is typed by a person on genInfoForm.vue, so a strict pattern is a reasonable guardrail on human input. jsonField is computed by the importer from the column name (sys_tables.go's namelist/JsonField loop) - nobody types it, so the same pattern only rejected names the importer legitimately produces: a single-letter column ("x") or one whose last segment ends in a digit ("address2", "a1") both collapse to a single camelCase word with nothing left to re-capitalize, and both failed the old check. The blast radius is wider than "this one column can't be edited": validateAndSanitizeColumns runs over every column on every Update, so a table that merely contains one such column could not save any config change at all, including edits with nothing to do with that column. Relaxed to ^[a-z][A-Za-z0-9]*$ - any legal JS/TS identifier starting with a lowercase letter. Still rejects what has to be rejected: empty, whitespace/punctuation, and leading-digit names, since those cannot be unquoted object keys in the generated interface/lang file at all. Uniqueness and the expression-content check on defaultValue are unchanged - defaultValue is genuinely user-typed (F6's config page), so tightening it was the right call to begin with; this was the only place a human-input rule had been copied onto machine-generated data. Verified against the real import path, not hand-typed jsonField values: built a table with columns id/x/address2/a1 in a fake information_schema, ran it through the real SysTable.Insert, confirmed the importer computes exactly jsonField x/address2/a1, then submitted an update through the real SysTable.Update changing only tableComment (nothing about those columns). Confirmed red first - 500, "jsonField 格式不合法:\"x\"" - a change unrelated to any of the three columns was rejected solely because they existed on the table. Restored the fix - 200, "修改成功". --- app/other/apis/tools/sys_tables_validate.go | 30 +++++++++++++++---- .../apis/tools/sys_tables_validate_test.go | 12 ++++++-- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/app/other/apis/tools/sys_tables_validate.go b/app/other/apis/tools/sys_tables_validate.go index c9353b32..067f6aaa 100644 --- a/app/other/apis/tools/sys_tables_validate.go +++ b/app/other/apis/tools/sys_tables_validate.go @@ -10,11 +10,31 @@ import ( "go-admin/app/other/models/tools" ) -// jsonFieldPattern mirrors genInfoForm.vue's businessName rule -// (`/^[a-z][A-Za-z]+$/`) - jsonField has never had a format rule of its own, -// unlike businessName/tableName/className, and API契约.md §1.2 recommends -// tightening it to the same identifier shape the other three already use. -var jsonFieldPattern = regexp.MustCompile(`^[a-z][A-Za-z]+$`) +// jsonFieldPattern accepts any legal JS/TS identifier that starts with a +// lowercase letter - not businessName's rule. +// +// This used to be businessName's own pattern (^[a-z][A-Za-z]+$, requiring at +// least two letters and no digits), copied over on the theory that jsonField +// "should tighten to the same identifier shape". That theory does not hold: +// businessName is typed by a person on genInfoForm.vue, so a strict pattern +// is a reasonable guardrail on human input. jsonField is computed by the +// importer from the column name (sys_tables.go's namelist/JsonField loop) - +// nobody types it, so the same pattern only rejects names the importer +// legitimately produces. A one-letter column ("x") or a column ending in a +// digit ("address2", "a1") both import to a single camelCase word with no +// separators to re-capitalize, and both used to fail this check - meaning a +// table that merely contained such a column could never save any config +// again, unrelated columns included, since this check runs over every +// column on every Update. +// +// What still has to be rejected is a jsonField that cannot be a raw object +// key at all: empty, containing whitespace/punctuation, or leading with a +// digit (`2faEnabled: 1` is not valid JS - identifiers cannot start with a +// digit, and this is what lands as the property name in gen.go's generated +// interface / lang file, both unquoted). Hence still anchoring on a +// lowercase letter first, but no longer requiring a second character or +// forbidding digits after it. +var jsonFieldPattern = regexp.MustCompile(`^[a-z][A-Za-z0-9]*$`) // colWidthMin/colWidthMax are API契约.md §2.1's suggested range for colWidth. const ( diff --git a/app/other/apis/tools/sys_tables_validate_test.go b/app/other/apis/tools/sys_tables_validate_test.go index f2a31c77..0d80643e 100644 --- a/app/other/apis/tools/sys_tables_validate_test.go +++ b/app/other/apis/tools/sys_tables_validate_test.go @@ -17,9 +17,17 @@ func TestValidateAndSanitizeColumns_JsonFieldFormat(t *testing.T) { }{ {"lower camelCase", "userName", false}, {"two-letter lowercase", "id", false}, + // The importer's own output (sys_tables.go's namelist/JsonField + // loop), not made up: a single-letter column ("x"), and a column + // whose last name segment ends in a digit ("address2", "a1") both + // produce a jsonField with no separator left to re-capitalize. + // These three used to be rejected - the whole point of this fix. + {"single letter, real importer output for a column named x", "x", false}, + {"letters then a trailing digit, real importer output for address2", "address2", false}, + {"two letters then a digit, real importer output for a1", "a1", false}, {"leading underscore rejected", "_id", true}, - {"leading digit rejected", "1name", true}, - {"snake_case rejected", "user_name", true}, + {"leading digit rejected (not a legal identifier start)", "1name", true}, + {"snake_case rejected (importer never emits an underscore)", "user_name", true}, {"dot rejected, would break the gen/{pkg}/{biz}.ts key path", "user.name", true}, {"empty rejected", "", true}, }