diff --git a/32-platform/platform/placeholder/name_form.html b/32-platform/platform/placeholder/name_form.html new file mode 100644 index 0000000..d915c99 --- /dev/null +++ b/32-platform/platform/placeholder/name_form.html @@ -0,0 +1,15 @@ +{{ layout "layout.html" }} + +
+
+ + +
+
+ + +
+
+ +
+
\ No newline at end of file diff --git a/32-platform/platform/placeholder/name_handler.go b/32-platform/platform/placeholder/name_handler.go index 22862ad..4c17be9 100644 --- a/32-platform/platform/placeholder/name_handler.go +++ b/32-platform/platform/placeholder/name_handler.go @@ -5,6 +5,7 @@ import ( "platform/http/actionresults" "platform/http/handing" "platform/logging" + "platform/validation" ) var names = []string{"Alice", "Bob", "charlie", "Dora"} @@ -12,10 +13,11 @@ var names = []string{"Alice", "Bob", "charlie", "Dora"} type NameHandler struct { logging.Logger handing.URLGenerator + validation.Validator } type NewName struct { - Name string + Name string `validation:"required,min:3"` InsertAtStart bool } @@ -35,8 +37,16 @@ func (n NameHandler) GetNames() actionresults.ActionResult { return actionresults.NewTemplateAction("simple_message.html", names) } +func (n NameHandler) GetFOrm() actionresults.ActionResult { + postUrl, _ := n.URLGenerator.GenerateUrl(NameHandler.PostName) + return actionresults.NewTemplateAction("name_form.html", postUrl) +} + func (n NameHandler) PostName(new NewName) actionresults.ActionResult { n.Logger.Debugf("PostName method invoked with argument: %v", new) + if ok, errs := n.Validator.Validate(&new); !ok { + return actionresults.NewTemplateAction("validation_errors.html", errs) + } if new.InsertAtStart { names = append([]string{new.Name}, names...) } else { diff --git a/32-platform/platform/placeholder/validation_errors.html b/32-platform/platform/placeholder/validation_errors.html new file mode 100644 index 0000000..ad720ad --- /dev/null +++ b/32-platform/platform/placeholder/validation_errors.html @@ -0,0 +1,8 @@ +{{ layout "layout.html" }} + +

Validation Errors

+ \ No newline at end of file diff --git a/32-platform/platform/services/services_default.go b/32-platform/platform/services/services_default.go index 3b4d434..3b14785 100644 --- a/32-platform/platform/services/services_default.go +++ b/32-platform/platform/services/services_default.go @@ -4,6 +4,7 @@ import ( "platform/config" "platform/logging" "platform/templates" + "platform/validation" ) func RegisterDefaultService() { @@ -39,4 +40,12 @@ func RegisterDefaultService() { if err != nil { panic(err) } + + // 注册验证服务 + err = AddSingleton(func() validation.Validator { + return validation.NewDefaultValidator() + }) + if err != nil { + panic(err) + } } diff --git a/32-platform/platform/validation/tag_validator.go b/32-platform/platform/validation/tag_validator.go new file mode 100644 index 0000000..8a8164a --- /dev/null +++ b/32-platform/platform/validation/tag_validator.go @@ -0,0 +1,62 @@ +package validation + +import ( + "reflect" + "strings" +) + +func NewDefaultValidator() Validator { + return NewValidator(DefaultValidators()) +} + +func NewValidator(validators map[string]ValidatorFunc) Validator { + return &TagValidator{ + validators: validators, + } +} + +type TagValidator struct { + validators map[string]ValidatorFunc +} + +func (tv *TagValidator) Validate(data interface{}) (ok bool, errs []ValidatorError) { + errs = []ValidatorError{} + + dataVal := reflect.ValueOf(data) + if dataVal.Kind() == reflect.Ptr { + dataVal = dataVal.Elem() + } + if dataVal.Kind() != reflect.Struct { + panic("Only structs can be validated") + } + for i := 0; i < dataVal.NumField(); i++ { + fieldType := dataVal.Type().Field(i) + validationTag, found := fieldType.Tag.Lookup("validate") + if !found { + continue + } + for _, v := range strings.Split(validationTag, ",") { + var name, arg string = "", "" + if strings.Contains(v, ":") { + nameAndArgs := strings.SplitN(v, ":", 2) + name = nameAndArgs[0] + arg = nameAndArgs[1] + } else { + name = v + } + if validator, ok := tv.validators[name]; ok { + valid, err := validator(fieldType.Name, dataVal.Field(i).Interface(), arg) + if !valid { + errs = append(errs, ValidatorError{ + FieldName: fieldType.Name, + Error: err, + }) + } else { + panic("Unknow validator:" + name) + } + } + } + } + ok = len(errs) == 0 + return +} diff --git a/32-platform/platform/validation/validator.go b/32-platform/platform/validation/validator.go new file mode 100644 index 0000000..88be384 --- /dev/null +++ b/32-platform/platform/validation/validator.go @@ -0,0 +1,56 @@ +package validation + +import ( + "fmt" + "strconv" +) + +type Validator interface { + Validate(data interface{}) (ok bool, errs []ValidatorError) +} + +// ValidatorError 验证错误 +type ValidatorError struct { + FieldName string + Error error +} +type ValidatorFunc func(fieldName string, value interface{}, arg string) (bool, error) + +func DefaultValidators() map[string]ValidatorFunc { + return map[string]ValidatorFunc{ + "required": required, + "min": minValue, + } +} + +func required(fieldName string, value interface{}, arg string) (valid bool, err error) { + if str, ok := value.(string); ok { + valid = str != "" + err = fmt.Errorf("字段 %v 不能为空", fieldName) + return + } else { + err = fmt.Errorf("字段 %v 必须为字符串", fieldName) + } + return +} + +func minValue(fieldName string, value interface{}, arg string) (valid bool, err error) { + minVal, err := strconv.Atoi(arg) + if err != nil { + err = fmt.Errorf("字段 %v 的配置参数 %v 必须为数字", fieldName, arg) + return + } + if iVal, iValOk := value.(int); iValOk { + valid = iVal >= minVal + err = fmt.Errorf("字段 %v 的值不能小于 %v", fieldName, minVal) + } else if fVal, fValOk := value.(float64); fValOk { + valid = fVal >= float64(minVal) + err = fmt.Errorf("字段 %v 的值不能小于 %v", fieldName, minVal) + } else if sVal, sValOk := value.(string); sValOk { + err = fmt.Errorf("字段 %v 的值长度不能小于 %v", fieldName, minVal) + valid = len(sVal) >= minVal + } else { + err = fmt.Errorf("字段 %v 的值类型必须为int,float,string", fieldName) + } + return +}