gin_scaffold
gin_scaffold 是一个基于 Gin 框架打造的企业级开发脚手架,旨在加速Go语言(Golang)Web应用的开发过程。Gin本身是一个轻量级且高性能的Web框架,以其高效的路由处理能力、简洁的API设计而受到广泛好评。gin_scaffold在此基础上进一步集成了一系列最佳实践和常用功能模块,使得开发者能够更专注于业务逻辑的实现,而不是基础设施的搭建。
请求参数绑定
这是gin_scaffold框架中public包下params.go文件中的一个用于绑定请求参数的方法
func DefaultGetValidParams(c *gin.Context, params interface{}) error {
// 关键之处!!!!
if err := c.ShouldBind(params); err != nil {
return err
}
//获取验证器
valid, err := GetValidator(c)
if err != nil {
return err
}
//获取翻译器
trans, err := GetTranslation(c)
if err != nil {
return err
}
err = valid.Struct(params)
if err != nil {
errs := err.(validator.ValidationErrors)
sliceErrs := []string{}
for _, e := range errs {
sliceErrs = append(sliceErrs, e.Translate(trans))
}
return errors.New(strings.Join(sliceErrs, ","))
}
return nil
}
查看ShouldBind方法
func (c *Context) ShouldBind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.ShouldBindWith(obj, b)
}
//=========>ShouldBindWith
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
return b.Bind(c.Request, obj)
}
//=========>Binding
type Binding interface {
Name() string
Bind(*http.Request, interface{}) error
}
binding.Default()方法返回了当前请求包含数据的对应的处理类。以下的处理类都实现了Bind接口
var (
JSON = jsonBinding{}
XML = xmlBinding{}
Form = formBinding{}
Query = queryBinding{}
FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
YAML = yamlBinding{}
Uri = uriBinding{}
Header = headerBinding{}
)
//根据请求方法和内容的类型来返回参数绑定的方法
func Default(method, contentType string) Binding {
if method == http.MethodGet {
return Form
}
switch contentType {
case MIMEJSON:
return JSON
case MIMEXML, MIMEXML2:
return XML
case MIMEPROTOBUF:
return ProtoBuf
case MIMEMSGPACK, MIMEMSGPACK2:
return MsgPack
case MIMEYAML:
return YAML
case MIMEMultipartPOSTForm:
return FormMultipart
default: // case MIMEPOSTForm:
return Form
}
}
formPostBinding为例
type formPostBinding struct{}
func (formPostBinding) Name() string {
return "form-urlencoded"
}
func (formPostBinding) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
//用于将转换后的PostForm与obj中的字段意义对应
if err := mapForm(obj, req.PostForm); err != nil {
return err
}
return validate(obj)
}
ParseForm
func (r *Request) ParseForm() error {
var err error
if r.PostForm == nil {
if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
r.PostForm, err = parsePostForm(r)
}
if r.PostForm == nil {
r.PostForm = make(url.Values)
}
}
....
return err
}
//==========>这段代码从Request中的body读取数据
func parsePostForm(r *Request) (vs url.Values, err error) {
if r.Body == nil {
err = errors.New("missing form body")
return
}
ct := r.Header.Get("Content-Type")
// RFC 7231, section 3.1.1.5 - empty type
// MAY be treated as application/octet-stream
if ct == "" {
ct = "application/octet-stream"
}
ct, _, err = mime.ParseMediaType(ct)
switch {
case ct == "application/x-www-form-urlencoded":
var reader io.Reader = r.Body
maxFormSize := int64(1<<63 - 1)
if _, ok := r.Body.(*maxBytesReader); !ok {
maxFormSize = int64(10 << 20) // 10 MB is a lot of text.
reader = io.LimitReader(r.Body, maxFormSize+1)
}
b, e := io.ReadAll(reader)
if e != nil {
if err == nil {
err = e
}
break
}
if int64(len(b)) > maxFormSize {
err = errors.New("http: POST too large")
return
}
vs, e = url.ParseQuery(string(b))
if err == nil {
err = e
}
case ct == "multipart/form-data":
// handled by ParseMultipartForm (which is calling us, or should be)
// TODO(bradfitz): there are too many possible
// orders to call too many functions here.
// Clean this up and write more tests.
// request_test.go contains the start of this,
// in TestParseMultipartFormOrder and others.
}
return
}
mapForm
func mapForm(ptr interface{}, form map[string][]string) error {
return mapFormByTag(ptr, form, "form")
}
// 这段代码中根据定义实体时定义的tag来进行相应的映射
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
// Check if ptr is a map
ptrVal := reflect.ValueOf(ptr)
var pointed interface{}
if ptrVal.Kind() == reflect.Ptr {
ptrVal = ptrVal.Elem()
pointed = ptrVal.Interface()
}
if ptrVal.Kind() == reflect.Map &&
ptrVal.Type().Key().Kind() == reflect.String {
if pointed != nil {
ptr = pointed
}
return setFormMap(ptr, form)
}
return mappingByPtr(ptr, formSource(form), tag)
}
映射实体
我们在声明DTO对象的时候需要进行一些必要的注解,以保证绑定映射时能够被正确识别对应。
type AdminLoginInput struct {
UserName string `json:"username" form:"username" comment:"姓名" example:"admin" validate:"required,is_valid_username"`
Password string `json:"password" form:"password" comment:"密码" example:"123456" validate:"required"`
}