mirror of
https://github.com/go-admin-team/go-admin.git
synced 2026-10-04 06:22:02 +00:00
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, "修改成功".
113 lines
4.8 KiB
Go
113 lines
4.8 KiB
Go
package tools
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"go-admin/app/other/models/tools"
|
|
)
|
|
|
|
// 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 (
|
|
colWidthMin = 40
|
|
colWidthMax = 800
|
|
)
|
|
|
|
// expressionMarkers flags the "meant to be evaluated" shapes API契约.md §2.1
|
|
// says defaultValue must not carry: it is spliced into the generated
|
|
// defaultModel() as a literal and never evaluated, so anything that looks
|
|
// like a function call or a block is rejected outright rather than
|
|
// generating code that silently does nothing.
|
|
var expressionMarkers = []string{"(", ")", "{", "}", "`", ";", "=>"}
|
|
|
|
// validateAndSanitizeColumns enforces PRD 010 F10 on the columns carried by
|
|
// a table update (sys_tables.go:357's Update handler, the one bind-and-save
|
|
// path with no field-level validation at all - see API契约.md §1.2/§2.1,
|
|
// decision D6).
|
|
//
|
|
// jsonField and defaultValue problems reject the request outright: letting
|
|
// either through would corrupt the generated i18n file silently (a
|
|
// duplicate or malformed jsonField becomes a duplicate or invalid key in
|
|
// gen/{PackageName}/{BusinessName}.ts, see the lang-zh/lang-en templates).
|
|
// An out-of-range colWidth does not reject - §2.1 says it "falls back to
|
|
// the inferred value", so this resets it to the 0 sentinel in place and lets
|
|
// R2's inference take over, the same as if the field had never been set.
|
|
func validateAndSanitizeColumns(columns []tools.SysColumns) error {
|
|
seen := make(map[string]bool, len(columns))
|
|
for i := range columns {
|
|
col := &columns[i]
|
|
|
|
if !jsonFieldPattern.MatchString(col.JsonField) {
|
|
return fmt.Errorf("jsonField 格式不合法:%q,须以小写字母开头且只能包含英文字母", col.JsonField)
|
|
}
|
|
if seen[col.JsonField] {
|
|
return fmt.Errorf("jsonField 在同一张表内重复:%q", col.JsonField)
|
|
}
|
|
seen[col.JsonField] = true
|
|
|
|
if col.ColWidth != 0 && (col.ColWidth < colWidthMin || col.ColWidth > colWidthMax) {
|
|
col.ColWidth = 0
|
|
}
|
|
|
|
for _, marker := range expressionMarkers {
|
|
if strings.Contains(col.DefaultValue, marker) {
|
|
return fmt.Errorf("defaultValue 不允许包含表达式或函数调用内容:%q", col.DefaultValue)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateBusinessNameUnique enforces PRD 010 F10's other half: two tables
|
|
// sharing (packageName, businessName) write the same generated language
|
|
// pack path, gen/{PackageName}/{BusinessName}.ts (see gen.go's
|
|
// NOActionsGen), so the second one silently overwrites the first's
|
|
// translations. tableID excludes the row being saved, so a table updating
|
|
// its own unchanged name does not trip the check on itself.
|
|
//
|
|
// G10's other concern - colliding with the built-in admin/* i18n namespace -
|
|
// does not apply here anymore: D9 moved generated keys to their own gen/
|
|
// namespace, so this only has to guard generated tables against each other.
|
|
func validateBusinessNameUnique(db *gorm.DB, packageName, businessName string, tableID int) error {
|
|
var count int64
|
|
err := db.Table("sys_tables").
|
|
Where("package_name = ? AND business_name = ? AND table_id != ?", packageName, businessName, tableID).
|
|
Count(&count).Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count > 0 {
|
|
return fmt.Errorf("packageName=%q 下 businessName=%q 已被其它表使用", packageName, businessName)
|
|
}
|
|
return nil
|
|
}
|