当前位置: 首页 > 工具软件 > go-parse > 使用案例 >

Go-zero学习 api如何了解到我们希望如何读取post请求数据

荀博
2023-12-01

如官网所描述的

type (
	LoginReq {
		Username string `json:"username"`
		Password string `json:"password""`
	}

	LoginReply {
		Id           int64  `json:"id"`
		Name         string `json:"name"`
		Gender       string `json:"gender"`
		AccessToken  string `json:"accessToken"`
		AccessExpire int64  `json:"accessExpire"`
		RefreshAfter int64  `json:"refreshAfter"`
	}
)

service user-api {
	@handler login
	post /user/login (LoginReq) returns (LoginReply)
}

编写了一个.api文件
生成之后,发现了一个奇怪的问题,post方法有很多形式,那么go-zero究竟支持哪些形式呢?查看源码找到了go-zero的处理方式

// api/internal/handler/loginhandler.go
func loginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req types.LoginReq
		if err := httpx.Parse(r, &req); err != nil {
			httpx.Error(w, err)
			return
		}

		l := logic.NewLoginLogic(r.Context(), svcCtx)
		resp, err := l.Login(&req)
		if err != nil {
			httpx.Error(w, err)
		} else {
			httpx.OkJson(w, resp)
		}
	}
}

可以看到go-zero帮我们把传参方式做了一个go-zero形式的包装httpx.Parse。对准这个函数ctrl-b,进入这个httpx.parse,可以看到

func Parse(r *http.Request, v interface{}) error {
	if err := ParsePath(r, v); err != nil {
		return err
	}

	if err := ParseForm(r, v); err != nil {
		return err
	}

	if err := ParseHeaders(r, v); err != nil {
		return err
	}

	return ParseJsonBody(r, v)
}

有意思,4种读参方式顺序执行。我立刻产生了一种想法,如果真的是顺序运行,那么真正有效的只有最后了一个啊,显然不可能这样,只有一种解释就是,四个函数内部都设置了一个唯一条件的判断,保证一个条件只能正常执行其中之一。
那先按顺序看看ctrl-b这个ParsePath

// Like http://localhost/bag/:name
func ParsePath(r *http.Request, v interface{}) error {
	vars := pathvar.Vars(r)
	m := make(map[string]interface{}, len(vars))
	for k, v := range vars {
		m[k] = v
	}

	return pathUnmarshaler.Unmarshal(m, v)
}

顶端注释给我们简单的解释了这种传参方式,在restful api规范了解过一点点,在url中传参,这可不是我想要的传参方法啊,前面的逻辑很经典的处理了path参数,如果使用真的使用这个方式传参数,是可以看到m最终是包含了通过path传来的参数的,然而最终退出httpx.Parse这个函数,是可以看到req中是没有path方式传来的,显然问题出在这个pathUnmarshaler.Unmarshal函数,进去看看
经过一系列的跳转来到了这个函数

// github.com/zeromicro/go-zero@v1.3.2/core/mapping/unmarshaler.go
func (u *Unmarshaler) unmarshalWithFullName(m Valuer, v interface{}, fullName string) error {
	rv := reflect.ValueOf(v)
	if err := ValidatePtr(&rv); err != nil {
		return err
	}

	rte := reflect.TypeOf(v).Elem()
	if rte.Kind() != reflect.Struct {
		return errValueNotStruct
	}

	rve := rv.Elem()
	numFields := rte.NumField()
	for i := 0; i < numFields; i++ {
		field := rte.Field(i)
		if usingDifferentKeys(u.key, field) {
			continue
		}

		if err := u.processField(field, rve.Field(i), m, fullName); err != nil {
			return err
		}
	}

	return nil
}

简单的调试后发现,这个判断条件总为真

if usingDifferentKeys(u.key, field) {
	continue
}

我目测是将读出的path的参数写入req的逻辑在于这个u.processField函数中,而这个continue跳过了这个过程。那这个usingDifferentKeys是什么意思呢?首先看参数u.key是什么意思,那必须要知道u这个实例是什么,找了一下就是上面的ParsePath函数中的这个pathUnmarshaler,这个实例是一个全局实例,在文件顶上创建出来的,

var (
	formUnmarshaler = mapping.NewUnmarshaler(formKey, mapping.WithStringValues())
	pathUnmarshaler = mapping.NewUnmarshaler(pathKey, mapping.WithStringValues())
)

这个pathKey是个const值,即“path”,也在这个文件的顶部,具体不说明了,总是实例中的u.key被初始化为“path”
进入这个函数看看

// github.com/zeromicro/go-zero@v1.3.2/core/mapping/utils.go
func usingDifferentKeys(key string, field reflect.StructField) bool {
	if len(field.Tag) > 0 {
		if _, ok := field.Tag.Lookup(key); !ok {
			return true
		}
	}

	return false
}

即查看types.LoginReq类型中该字段的tag第一个是否是u.key也就是那个“path”
也就是说,我们在.api文件中写的tag就代表了我们希望以什么形式获取这个请求数据。

这样似乎我们就可以更加多样化一些,不同的参数可以通过不同的方式传。

 类似资料: