Когда я недавно изучал джин, то обнаружил, что проверка параметров запроса была очень хлопотной, и было много повторяющихся кодов.После некоторого размышления и практики я нашел метод с использованием рефлексии, который реализовал автоматическое извлечение параметров запроса на функцию контроллера для указанной структуры и автоматически использовать проверку для проверки.
причина
Как показано ниже, это очень распространенный код для обработки входа в систему, получения параметров запроса, проверки правильности параметров и возврата кода ошибки, если они неверны.Этот тип кода очень распространен в проектах и очень часто повторяется. такой код всегда раздражает.
type LoginParam struct {
Account string `validate:"required" json:"account"`
Password string `validate:"required" json:"password"`
}
func main(){
g := gin.New()
g.POST("login", LoginHandlerFunc)
_ = g.Run(":8080")
}
// HanlderFunc
func LoginHandlerFunc(ctx *gin.Context) {
param := LonginParam{}
err := ctx.ShouldBind(¶m)
if err != nil {
ctx.String(400, "请求参数异常")
return
}
secc, msg := Validate(¶m)
if !secc {
ctx.String(400, msg)
return
}
// ... CRUD
}
Далее идет валидатор, опускающий код типа регистрации переводчика.
import (
"github.com/go-playground/validator/v10"
)
var v = validator.New()
func Validate(param interface{}) (bool, string) {
err := v.Struct(param)
errs := err.(validator.ValidationErrors)
if len(errs) > 0 {
err := errs[0]
return false, err.Translate(that.trans)
}
return true, ""
}
Цель
Параметры отображения и параметры проверки фиксированы, и цель состоит в том, чтобыLoginHandlerFunc
Оптимизировано, как показано ниже.
func LoginHandlerFunc(ctx *gin.Context, params *LonginParam) {
// ... CRUD
}
идеи
Вопрос 1: Как реализовать автоматическое создание на основе параметров функции
Я не могу знать конкретный тип структуры, отображаемой параметрами запроса, поэтому я могу использовать отражение только для получения параметров функции путем отражения.Конкретная практика заключается в следующем.
func reflectHandlerFunc(handlerFunc interface{}){
funcType := reflect.TypeOf(handlerFunc)
// 判断是否 Func
if funcType.Kind() != reflect.Func {
panic("the route handlerFunc must be a function")
}
// 获取第二个参数的类型
typeParam := funcType.In(1).Elem()
// 创建实例
instance := reflect.New(typeParam).Interface()
}
Как и выше, вы можете получить его, отразив handlerFunc, а затем получив второй параметр функцииType
Затем создайте экземпляр с помощью отражения.
Вопрос 2: Как сопоставить параметры и проверить
Оптимизируйте код в вопросе 1.
func proxyHandlerFunc(ctx *gin.Context, handlerFunc interface{}){
funcType := reflect.TypeOf(handlerFunc)
funcValue := reflect.ValueOf(handlerFunc)
// 判断是否 Func
if funcType.Kind() != reflect.Func {
panic("the route handlerFunc must be a function")
}
// 获取第二个参数的类型
typeParam := funcType.In(1).Elem()
// 创建实例
param := reflect.New(typeParam).Interface()
// 绑定参数到 struct
err := ctx.ShouldBind(¶m)
if err != nil {
ctx.String(400, "请求参数异常")
return
}
// 验证参数
succ, msg := Validate(¶m)
if !succ {
ctx.String(400, msg)
return
}
// 调用真实 HandlerFunc
reflect.Call(valOf(ctx, param))
}
func valOf(i ...interface{}) []reflect.Value {
var rt []reflect.Value
for _, i2 := range i {
rt = append(rt, reflect.ValueOf(i2))
}
return rt
}
Таким образом, работа прокси для HandlerFunc завершена, пока мы обертываем настоящий HandlerFunc при регистрации маршрута.
// ...
g.POST("login", getHandlerFunc(LoginHandlerFunc))
// ...
func getHandlerFunc(handlerFunc interface{}) func(*gin.Context) {
return func(context *gin.Context){
proxyHandlerFunc(context, handlerFunc)
}
}
// ...
func LoginHandlerFunc(ctx *gin.Context, param *LoginParam){
// ... CRUD
}
Код и оптимизация производительности
0. Совместимость с оригинальной HandlerFunc
Предполагая, что проект находится на полпути, и я не могу реорганизовать все HandlerFunc за один шаг, мне нужно быть совместимым с исходным методом, который должен только добавить простое суждение.
func getHandlerFunc(handlerFunc interface{}) func(*gin.Context) {
// 获取参数数量
paramNum := reflect.TypeOf(handlerFunc).NumIn()
valueFunc := reflect.ValueOf(handlerFunc)
return func(context *gin.Context){
// 只有一个参数说明是未重构的 HandlerFunc
if paramNum == 1 {
valueFunc.Call(valOf(context))
return
}
proxyHandlerFunc(context, handlerFunc)
}
}
1. Вручную привязать и проверить определенные параметры
В реальной разработке некоторые интерфейсы могут быть формами, некоторые интерфейсы — JSON, а некоторые — другими типами.Приведенный выше код может использоваться толькоgin.ShouldBind
Автоматически обрабатывать процесс привязки к структуре, для этой проблемы дайтеParam
Достаточно реализовать определенный интерфейс.Если он реализован, мы используем метод интерфейса для привязки.Конкретная реализация выглядит следующим образом.
type Deserialzer interface {
DeserializeFrom(ctx *gin.Context) error
}
type LoginParam struct {
Account string `validate:"required" json:"account"`
Password string `validate:"required" json:"password"`
}
func (that *LoginParam) DeserializeFrom(ctx *gin.Context) error {
return ctx.ShouldBindWith(that, binding.FormPost)
}
После приведенного выше преобразования мы передаем конкретный процесс привязки самой конкретной структуре для всех реализаций.Dserializer
Все структуры интерфейса связаны пользовательскими настройками, и тогда толькоproxyHandlerFunc
Для достижения этой адаптации можно внести небольшое изменение.
func proxyHandlerFunc(ctx *gin.Context, handlerFunc interface{}){
// ...
// 创建实例
param := reflect.New(typeParam).Interface()
deser, ok := param.(Deserialzer)
// 如果未实现 Deserializer 接口则说明该 struct 使用默认绑定过程即可.
if !ok {
// 绑定参数到 struct
err := ctx.ShouldBind(¶m)
if err != nil {
ctx.String(400, "请求参数异常")
return
}
} else {
// 绑定请求参数
err := deser.DeserializeFrom(ctx)
if err != nil {
ctx.String(400, "请求参数异常")
return
}
param = reflect.ValueOf(deser).Interface()
}
// 验证参数
succ, msg := Validate(¶m)
// ...
}
Тот же метод можно использовать для процесса проверки пользовательских параметров.
оптимизация производительности
Влияние отражения в GO на производительность огромно, поэтому старайтесь избегать использования отражения в HandleFunc. Разница во времени между приведенными выше функциями, использующими отражение и не использующими отражение, составляет примерно 300 раз.Type
, Value
Отражение можно выполнять при регистрации маршрута, а предварительное отражение позволяет избежать отражения каждый раз, когда оно используется.
Как показано ниже, мы заранее размышляем над реальной функцией handlerFunc и типом параметра.
func GetHandlerFunc(handlerFunc interface{}) func(*gin.Context) {
// 提前反射
paramNum := reflect.TypeOf(handlerFunc).NumIn()
funcValue := reflect.ValueOf(handlerFunc)
funcType := reflect.TypeOf(handleFunc)
paramType := funcType.In(1).Elem()
// 判断是否 Func
if funcType.Kind() != reflect.Func {
panic("the route handlerFunc must be a function")
}
// ... 还可以做一些其他校验确保无误
return func(context *gin.Context){
// 只有一个参数说明是未重构的 HandlerFunc
if paramNum == 1 {
funcValue.Call(valOf(context))
return
}
proxyHandlerFunc(context, funcValue, paramType)
}
}
func proxyHandlerFunc(ctx *gin.Context, funcValue reflect.Value, typeParam reflect.Type){
// 创建实例
param := reflect.New(typeParam).Interface()
// ...
// 调用真实 HandlerFunc
reflect.Call(valOf(ctx, param))
}
func valOf(i ...interface{}) []reflect.Value {
var rt []reflect.Value
for _, i2 := range i {
rt = append(rt, reflect.ValueOf(i2))
}
return rt
}
Тестирование производительности
Используйте Baenchmark для тестирования производительности.
func BenchmarkNormalHandleFunc(b *testing.B) {
router := gin.New()
router.POST("login", func(ctx *gin.Context) {
p := validates.RegisterParams{}
validator := LoginParam{}
if !validator.Validate(wrap.Context(ctx), &p) {
return
}
})
config.Router = router
go func() {
_ = router.Run(":8081")
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
testPost("8081")
}
}
func BenchmarkReflectHandleFunc(b *testing.B) {
router := gin.New()
handlerFunc := func(ctx *gin.Context, params *LoginParam) {
//...
}
router.POST("login", GetHandlerFunc(handleFunc))
go func() {
_ = router.Run(":8082")
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
testPost("8082")
}
}
func testPost(port string) {
params := struct {
Account string
Password string
Email string
Captcha string
}{
Account: "account",
Password: "1231ljasd",
Email: "email@exmpale.com",
Captcha: "12345",
}
paramsByte, _ := json.Marshal(params)
r, _ := http.Post("http://127.0.0.1:"+port+"/login", "application/json", bytes.NewReader(paramsByte))
if r.StatusCode != 200 {
fmt.Println("errr")
}
}
Результаты испытаний следующие.
E:\go> go test -bench="." -count=5 -benchmem
goos: windows
goarch: amd64
pkg: go
BenchmarkNormalHandleFunc-12 19603 59545 ns/op 7321 B/op 83 allocs/op
BenchmarkNormalHandleFunc-12 17412 66923 ns/op 7306 B/op 83 allocs/op
BenchmarkNormalHandleFunc-12 20368 57849 ns/op 7349 B/op 83 allocs/op
BenchmarkNormalHandleFunc-12 20542 60086 ns/op 7395 B/op 83 allocs/op
BenchmarkNormalHandleFunc-12 20577 58671 ns/op 7361 B/op 83 allocs/op
BenchmarkReflectHandleFunc-12 20613 58374 ns/op 7493 B/op 85 allocs/op
BenchmarkReflectHandleFunc-12 20230 62594 ns/op 7456 B/op 85 allocs/op
BenchmarkReflectHandleFunc-12 19764 59617 ns/op 7441 B/op 85 allocs/op
BenchmarkReflectHandleFunc-12 20684 58899 ns/op 7461 B/op 85 allocs/op
BenchmarkReflectHandleFunc-12 19796 58712 ns/op 7421 B/op 85 allocs/op
PASS
ok go 18.177s
Потери производительности почти нет.
Вышеуказанные методы я практикую в конкретных проектах:GitHub