feat: complete SOP execution workflow

This commit is contained in:
Eric 1549169735@qq.com
2026-08-09 00:29:26 +08:00
parent e32025d2ca
commit c5ab886b70
63 changed files with 1229 additions and 215 deletions

View File

@@ -19,6 +19,13 @@ func TestValidateFieldInput(t *testing.T) {
{name: "options null", input: fieldInput{FieldKey: "pet_note", FieldType: "text", Options: json.RawMessage(`null`)}, wantErr: true},
{name: "validation array", input: fieldInput{FieldKey: "pet_age", FieldType: "number", Validation: json.RawMessage(`[1]`)}, wantErr: true},
{name: "validation null", input: fieldInput{FieldKey: "pet_age", FieldType: "number", Validation: json.RawMessage(`null`)}, wantErr: true},
{name: "number range", input: fieldInput{FieldKey: "pet_age", FieldType: "number", Validation: json.RawMessage(`{"min":0,"max":50}`)}},
{name: "reversed number range", input: fieldInput{FieldKey: "pet_age", FieldType: "number", Validation: json.RawMessage(`{"min":50,"max":0}`)}, wantErr: true},
{name: "number rule on text", input: fieldInput{FieldKey: "pet_name", FieldType: "text", Validation: json.RawMessage(`{"min":1}`)}, wantErr: true},
{name: "text range", input: fieldInput{FieldKey: "pet_name", FieldType: "text", Validation: json.RawMessage(`{"min_length":1,"max_length":50}`)}},
{name: "fractional text length", input: fieldInput{FieldKey: "pet_name", FieldType: "text", Validation: json.RawMessage(`{"min_length":1.5}`)}, wantErr: true},
{name: "negative text length", input: fieldInput{FieldKey: "pet_name", FieldType: "text", Validation: json.RawMessage(`{"min_length":-1}`)}, wantErr: true},
{name: "duplicate options", input: fieldInput{FieldKey: "pet_type", FieldType: "select", Options: json.RawMessage(`["猫","猫"]`)}, wantErr: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {

View File

@@ -278,10 +278,16 @@ func validateFieldInput(input fieldInput) error {
if err := json.Unmarshal(input.Options, &options); err != nil || options == nil {
return errors.New("字段选项必须是文本数组")
}
seen := map[string]bool{}
for _, option := range options {
if strings.TrimSpace(option) == "" {
return errors.New("字段选项不能为空")
}
normalized := strings.TrimSpace(option)
if seen[normalized] {
return errors.New("字段选项不能重复")
}
seen[normalized] = true
}
if (input.FieldType == "select" || input.FieldType == "multiselect") && len(options) == 0 {
return errors.New("单选或多选字段至少需要一个选项")
@@ -290,14 +296,77 @@ func validateFieldInput(input fieldInput) error {
return errors.New("单选或多选字段至少需要一个选项")
}
if len(input.Validation) > 0 {
var validation map[string]interface{}
var validation map[string]json.RawMessage
if err := json.Unmarshal(input.Validation, &validation); err != nil || validation == nil {
return errors.New("字段校验规则必须是 JSON 对象")
}
if err := validateFieldRules(input.FieldType, validation); err != nil {
return err
}
}
return nil
}
func validateFieldRules(fieldType string, validation map[string]json.RawMessage) error {
allowed := map[string]bool{}
switch fieldType {
case "number":
allowed["min"], allowed["max"] = true, true
case "text", "textarea":
allowed["min_length"], allowed["max_length"] = true, true
}
for key := range validation {
if !allowed[key] {
return errors.New("当前字段类型不支持校验规则 " + key)
}
}
if fieldType == "number" {
min, hasMin, err := jsonNumber(validation["min"])
if err != nil {
return errors.New("最小值必须是数字")
}
max, hasMax, err := jsonNumber(validation["max"])
if err != nil {
return errors.New("最大值必须是数字")
}
if hasMin && hasMax && min > max {
return errors.New("最小值不能大于最大值")
}
}
if fieldType == "text" || fieldType == "textarea" {
min, hasMin, err := jsonInteger(validation["min_length"])
if err != nil || (hasMin && min < 0) {
return errors.New("最少字符必须是非负整数")
}
max, hasMax, err := jsonInteger(validation["max_length"])
if err != nil || (hasMax && max < 1) {
return errors.New("最多字符必须是正整数")
}
if hasMin && hasMax && min > max {
return errors.New("最少字符不能大于最多字符")
}
}
return nil
}
func jsonNumber(raw json.RawMessage) (float64, bool, error) {
if len(raw) == 0 {
return 0, false, nil
}
var value float64
err := json.Unmarshal(raw, &value)
return value, true, err
}
func jsonInteger(raw json.RawMessage) (int, bool, error) {
if len(raw) == 0 {
return 0, false, nil
}
var value int
err := json.Unmarshal(raw, &value)
return value, true, err
}
func idParam(c *gin.Context, key string) (uint64, bool) {
id, err := strconv.ParseUint(c.Param(key), 10, 64)
if err != nil || id == 0 {