feat(validation): 添加表单验证功能并集成到名称处理器
- 新增基于结构体标签的验证器实现 - 实现必填和最小值验证规则- 在名称处理器中集成验证逻辑 - 添加验证错误页面模板- 注册默认验证服务 - 创建名称输入表单模板 - 为 NewName 结构体添加验证标签
This commit is contained in:
15
32-platform/platform/placeholder/name_form.html
Normal file
15
32-platform/platform/placeholder/name_form.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{{ layout "layout.html" }}
|
||||
|
||||
<form method="post" action="{{ . }}">
|
||||
<div style="padding:5px">
|
||||
<label for="name">Name:</label>
|
||||
<input id="name" name="name"/>
|
||||
</div>
|
||||
<div style="padding:5px">
|
||||
<label for="insertatstart">Insert at Front:</label>
|
||||
<input id="insertatstart" name="insertatstart" type="checkbox" value="true"/>
|
||||
</div>
|
||||
<div style="padding:5px">
|
||||
<input type="submit" value="Submit"/>
|
||||
</div>
|
||||
</form>
|
||||
@@ -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 {
|
||||
|
||||
8
32-platform/platform/placeholder/validation_errors.html
Normal file
8
32-platform/platform/placeholder/validation_errors.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{{ layout "layout.html" }}
|
||||
|
||||
<h3>Validation Errors</h3>
|
||||
<ul>
|
||||
{{ range . }}
|
||||
<li>{{ .FieldName }}:{{ .Error }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
62
32-platform/platform/validation/tag_validator.go
Normal file
62
32-platform/platform/validation/tag_validator.go
Normal file
@@ -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
|
||||
}
|
||||
56
32-platform/platform/validation/validator.go
Normal file
56
32-platform/platform/validation/validator.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user