diff --git a/internal/consts/http_metadata.go b/internal/consts/http_metadata.go deleted file mode 100644 index 8776f1a..0000000 --- a/internal/consts/http_metadata.go +++ /dev/null @@ -1,10 +0,0 @@ -package consts - -import ( - "github.com/gin-gonic/gin" -) - -type Request struct { - Http *gin.Context - User *UserTokenInfo -} diff --git a/internal/consts/user.go b/internal/consts/user.go index da36228..75cf125 100644 --- a/internal/consts/user.go +++ b/internal/consts/user.go @@ -23,4 +23,8 @@ type UserTokenInfo struct { Groups []string `json:"groups"` } +type User struct { + Token UserTokenInfo +} + const UserTokenInfoKey = "user.jwt" diff --git a/internal/http/controllers/user/main.go b/internal/http/controllers/user/main.go index 8364133..e04efe5 100644 --- a/internal/http/controllers/user/main.go +++ b/internal/http/controllers/user/main.go @@ -1,14 +1,22 @@ package user import ( + "fmt" "framework_v2/internal/consts" "github.com/gin-gonic/gin" - "net/http" ) -func CurrentUser(req *consts.Request) { - req.Http.JSON(http.StatusOK, gin.H{ - "req": req.User.Sub, - }) +//func CurrentUser(req *consts.Request) { +// req.Http.JSON(http.StatusOK, gin.H{ +// "req": req.User.Sub, +// }) +// +//} +func CurrentUser(c *gin.Context, user *consts.User) { + fmt.Println("CurrentUser", user) + c.JSON(200, gin.H{ + "IP": c.ClientIP(), + "User": user.Token.Sub, + }) } diff --git a/internal/providers/api_routes.go b/internal/providers/api_routes.go index 37b0782..e97ca18 100644 --- a/internal/providers/api_routes.go +++ b/internal/providers/api_routes.go @@ -5,5 +5,5 @@ import ( ) func InitApiRoutes() { - HandleRoute(GET, "/", user.CurrentUser, MiddlewareJWTAuth, MiddlewareJSONResponse) + GET("/", MiddlewareJSONResponse, user.CurrentUser) } diff --git a/internal/providers/gin.go b/internal/providers/gin.go index bd348ed..aef4b2e 100644 --- a/internal/providers/gin.go +++ b/internal/providers/gin.go @@ -1,10 +1,14 @@ package providers import ( + "fmt" "framework_v2/internal/access" "framework_v2/internal/consts" + "framework_v2/internal/helper" ginzap "github.com/gin-contrib/zap" "github.com/gin-gonic/gin" + "net/http" + "reflect" "time" ) @@ -18,43 +22,101 @@ func InitGin() { //access.Router.Use(ginzap.RecoveryWithZap(access.Logger, true)) } -type httpMethod int +//func HandleRoute(method httpMethod, relativePath string, controller HandlerFunc, middlewares ...gin.HandlerFunc) { +// access.Router.Handle(method.String(), relativePath, func(c *gin.Context) { +// for _, middleware := range middlewares { +// middleware(c) +// } +// +// if !c.IsAborted() { +// handleWithMetadata(c, controller) +// } +// }) +//} -var httpMethodStr = []string{"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"} +//func handleWithMetadata(c *gin.Context, controller HandlerFunc) { +// var metadata = &consts.Request{ +// Http: c, +// User: GetAuthFromCtx(c), +// } +// +// controller(metadata) +//} -func (h httpMethod) String() string { - return httpMethodStr[h-1] -} - -const ( - GET httpMethod = iota + 1 - POST - PUT - DELETE - PATCH - HEAD - OPTIONS -) - -type HandlerFunc func(metadata *consts.Request) - -func HandleRoute(method httpMethod, relativePath string, controller HandlerFunc, middlewares ...gin.HandlerFunc) { - access.Router.Handle(method.String(), relativePath, func(c *gin.Context) { - for _, middleware := range middlewares { - middleware(c) - } - - if !c.IsAborted() { - handleWithMetadata(c, controller) - } +func GET(relativePath string, handlers ...interface{}) { + access.Router.GET(relativePath, func(c *gin.Context) { + doHandler(c, handlers...) }) } -func handleWithMetadata(c *gin.Context, controller HandlerFunc) { - var metadata = &consts.Request{ - Http: c, - User: GetAuthFromCtx(c), +func POST(relativePath string, handlers ...interface{}) { + access.Router.POST(relativePath, func(c *gin.Context) { + doHandler(c, handlers...) + }) +} + +func PUT(relativePath string, handlers ...interface{}) { + access.Router.PUT(relativePath, func(c *gin.Context) { + doHandler(c, handlers...) + }) +} + +func PATCH(relativePath string, handlers ...interface{}) { + access.Router.PATCH(relativePath, func(c *gin.Context) { + doHandler(c, handlers...) + }) +} + +func DELETE(relativePath string, handlers ...interface{}) { + access.Router.DELETE(relativePath, func(c *gin.Context) { + doHandler(c, handlers...) + }) +} + +func doHandler(c *gin.Context, handlers ...interface{}) { + for _, handler := range handlers { + if c.IsAborted() { + // 是否已经响应 + if c.Writer.Written() { + return + } else { + helper.ResponseError(c, http.StatusBadRequest, ErrEmptyResponse) + } + + return + } + + wrapHandler(c, handler) + } +} + +func wrapHandler(c *gin.Context, f interface{}) { + fnValue := reflect.ValueOf(f) + fnType := fnValue.Type() + + var args []reflect.Value + + for i := 0; i < fnType.NumIn(); i++ { + argType := fnType.In(i) + + var argValue reflect.Value + switch argType { + case reflect.TypeOf((*gin.Context)(nil)): + argValue = reflect.ValueOf(c) + case reflect.TypeOf((*consts.User)(nil)): + userInfo := DIJWTAuth(c) + if userInfo == nil { + helper.ResponseError(c, http.StatusUnauthorized, ErrNotValidToken) + return + } + argValue = reflect.ValueOf(userInfo) + default: + helper.ResponseError(c, http.StatusBadRequest, fmt.Errorf("invalid argument type: %s", argType.String())) + return + } + + args = append(args, argValue) } - controller(metadata) + fnValue.Call(args) } diff --git a/internal/providers/gin_middleware.go b/internal/providers/gin_middleware.go index 4f80e8e..2edcc0f 100644 --- a/internal/providers/gin_middleware.go +++ b/internal/providers/gin_middleware.go @@ -14,58 +14,58 @@ var ( ErrNotValidToken = errors.New("无效的 JWT 令牌。") ErrJWTFormatError = errors.New("JWT 格式错误。") ErrNotBearerType = errors.New("不是 Bearer 类型。") + ErrEmptyResponse = errors.New("我们的服务器返回了空请求,可能某些环节出了问题。") ) const AnonymousUser = "anonymous" -func MiddlewareJWTAuth(c *gin.Context) { +// DIJWTAuth 用于注入到方法签名中。我觉得下面的代码以后可以优化。 +func DIJWTAuth(c *gin.Context) *consts.User { var sub = AnonymousUser - var jwtIdToken = &consts.UserTokenInfo{} + var jwtIdToken = &consts.User{} if Config.DebugMode.Enable { - jwtIdToken.Sub = sub + jwtIdToken.Token.Sub = sub } else { // get authorization header authorization := c.Request.Header.Get("Authorization") if authorization == "" { helper.ResponseError(c, http.StatusUnauthorized, ErrJWTFormatError) - return + return nil } authSplit := strings.Split(authorization, " ") if len(authSplit) != 2 { helper.ResponseError(c, http.StatusUnauthorized, ErrJWTFormatError) - return + return nil } if authSplit[0] != "Bearer" { helper.ResponseError(c, http.StatusUnauthorized, ErrNotBearerType) - return + return nil } token, err := ParseJWT(authSplit[1]) if err != nil { helper.ResponseError(c, http.StatusUnauthorized, ErrJWTFormatError) - return + return nil } sub, err = token.Claims.GetSubject() if err != nil { helper.ResponseError(c, http.StatusUnauthorized, ErrNotValidToken) - return + return nil } - err = mapstructure.Decode(token.Claims, &jwtIdToken) + err = mapstructure.Decode(token.Claims, &jwtIdToken.Token) if err != nil { Logger.Error("Failed to map token claims to JwtIDToken struct.\nError: " + err.Error()) helper.ResponseError(c, http.StatusUnauthorized, ErrNotValidToken) - return + return nil } } - c.Set(consts.UserTokenInfoKey, jwtIdToken) - - return + return jwtIdToken } func MiddlewareJSONResponse(c *gin.Context) { diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..9ef233d --- /dev/null +++ b/main_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "github.com/gin-gonic/gin" + "testing" +) + +type MyInterface interface{} + +type MyStruct struct{} + +func BenchmarkTypeAssertionSuccess(b *testing.B) { + var x MyInterface = &MyStruct{} + for n := 0; n < b.N; n++ { + if _, ok := x.(*MyStruct); !ok { + b.Fatal("Type assertion failed") + } + } +} + +func BenchmarkTypeAssertionFailure(b *testing.B) { + var x MyInterface = &MyStruct{} + for n := 0; n < b.N; n++ { + if _, ok := x.(*gin.Context); ok { + b.Fatal("Type assertion should fail") + } + } +}