diff --git a/all/etc/default_dev.yaml b/all/etc/default_dev.yaml index 757a768..6369ca3 100644 --- a/all/etc/default_dev.yaml +++ b/all/etc/default_dev.yaml @@ -7,7 +7,7 @@ Server: BindIP: 0.0.0.0 Port: 12001 -# Dynamic JSON-to-protobuf unary RPC endpoint: POST /rpc/{full.service}.{method} +# Dynamic JSON-to-protobuf unary RPC endpoint: POST /rpc/{module}/{service}/{method} # An empty allow list denies every dynamic call. Use "*" to expose all # reflection-visible unary methods, or list full service/method names. DynamicRPC: diff --git a/all/internal/server/dynamic.go b/all/internal/server/dynamic.go index e2434b0..cbe9784 100644 --- a/all/internal/server/dynamic.go +++ b/all/internal/server/dynamic.go @@ -70,12 +70,15 @@ func reflectionTarget(addr string) string { func (g *dynamicGateway) Close() error { return g.conn.Close() } func (g *dynamicGateway) handle(c *gin.Context) { - fullMethod := strings.TrimSpace(c.Param("method")) - serviceName, methodName, ok := splitFullMethod(fullMethod) - if !ok { - writeDynamicError(c, status.Error(codes.InvalidArgument, "method must be {full.service}.{method}")) + moduleName := strings.TrimSpace(c.Param("module")) + serviceShortName := strings.TrimSpace(c.Param("service")) + methodName := strings.TrimSpace(c.Param("method")) + if moduleName == "" || serviceShortName == "" || methodName == "" { + writeDynamicError(c, status.Error(codes.InvalidArgument, "path must be /rpc/{module}/{service}/{method}")) return } + serviceName := moduleName + "." + serviceShortName + fullMethod := serviceName + "." + methodName if !g.isAllowed(serviceName, fullMethod) { writeDynamicError(c, status.Error(codes.PermissionDenied, "dynamic RPC method is not allowed")) return @@ -122,14 +125,6 @@ func (g *dynamicGateway) handle(c *gin.Context) { c.JSON(http.StatusOK, dynamicRPCResponse{Code: int32(codes.OK), Message: codes.OK.String(), Data: data}) } -func splitFullMethod(value string) (string, string, bool) { - separator := strings.LastIndexByte(value, '.') - if separator <= 0 || separator == len(value)-1 { - return "", "", false - } - return value[:separator], value[separator+1:], true -} - func (g *dynamicGateway) isAllowed(serviceName, fullMethod string) bool { for _, key := range []string{"*", serviceName, fullMethod} { if _, ok := g.allow[key]; ok { diff --git a/all/internal/server/dynamic_test.go b/all/internal/server/dynamic_test.go index b32ffc8..b84de95 100644 --- a/all/internal/server/dynamic_test.go +++ b/all/internal/server/dynamic_test.go @@ -41,8 +41,8 @@ func TestDynamicGatewayInvokesUnaryRPC(t *testing.T) { gin.SetMode(gin.TestMode) engine := gin.New() - engine.POST("/rpc/:method", gateway.handle) - request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1.Health.Check", strings.NewReader(`{"service":""}`)) + engine.POST("/rpc/:module/:service/:method", gateway.handle) + request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1/Health/Check", strings.NewReader(`{"service":""}`)) response := httptest.NewRecorder() engine.ServeHTTP(response, request) @@ -65,8 +65,8 @@ func TestDynamicGatewayDeniesMethodsByDefault(t *testing.T) { gateway := &dynamicGateway{allow: map[string]struct{}{}} gin.SetMode(gin.TestMode) engine := gin.New() - engine.POST("/rpc/:method", gateway.handle) - request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1.Health.Check", strings.NewReader(`{}`)) + engine.POST("/rpc/:module/:service/:method", gateway.handle) + request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1/Health/Check", strings.NewReader(`{}`)) response := httptest.NewRecorder() engine.ServeHTTP(response, request) if response.Code != http.StatusOK { diff --git a/all/internal/server/server.go b/all/internal/server/server.go index cb1285d..40ed434 100644 --- a/all/internal/server/server.go +++ b/all/internal/server/server.go @@ -52,7 +52,7 @@ func (s *Server) Start(grpcAddr, httpAddr string, allow []string) error { _ = httpListener.Close() return err } - s.HTTP.POST("/rpc/:method", s.dynamic.handle) + s.HTTP.POST("/rpc/:module/:service/:method", s.dynamic.handle) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { recorder := newBufferedResponse() diff --git a/module/base/fts/internal/routers/register.go b/module/base/fts/internal/routers/register.go index a176917..586a488 100644 --- a/module/base/fts/internal/routers/register.go +++ b/module/base/fts/internal/routers/register.go @@ -1,16 +1,17 @@ package routers import ( - "fmt" + "path" "bsm/full/module/base/fts/internal/logic" + "github.com/gin-gonic/gin" swaggerfiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" ) func Register(srvKey string, engine *gin.Engine) { - v1_key := fmt.Sprintf("/%s/%s", srvKey, "v1") + v1_key := path.Join("/rest", srvKey) registerAnonymous(v1_key, engine) registerUploader(v1_key, engine) } diff --git a/module/base/fts/internal/routers/register_test.go b/module/base/fts/internal/routers/register_test.go new file mode 100644 index 0000000..a57186b --- /dev/null +++ b/module/base/fts/internal/routers/register_test.go @@ -0,0 +1,34 @@ +package routers + +import ( + "strings" + "testing" + + "github.com/gin-gonic/gin" +) + +func TestRoutesUseRESTModulePrefix(t *testing.T) { + gin.SetMode(gin.TestMode) + engine := gin.New() + Register("fts", engine) + + expected := map[string]bool{ + "GET /rest/fts/v1/ping": false, + "GET /rest/fts/v1/config": false, + "POST /rest/fts/v1/uploader": false, + } + for _, route := range engine.Routes() { + key := route.Method + " " + route.Path + if _, ok := expected[key]; ok { + expected[key] = true + } + if strings.HasPrefix(route.Path, "/fts/") { + t.Fatalf("legacy route remains registered: %s", route.Path) + } + } + for route, found := range expected { + if !found { + t.Errorf("route not registered: %s", route) + } + } +} diff --git a/module/base/mgt/internal/routers/register.go b/module/base/mgt/internal/routers/register.go index 50fa723..f9fbc7c 100644 --- a/module/base/mgt/internal/routers/register.go +++ b/module/base/mgt/internal/routers/register.go @@ -1,6 +1,8 @@ package routers import ( + "path" + "bsm/full/module/base/mgt/internal/logic/application" "bsm/full/module/base/mgt/internal/logic/department" "bsm/full/module/base/mgt/internal/logic/hello" @@ -14,9 +16,9 @@ import ( "github.com/gin-gonic/gin" ) -// 完成请求地址: ip:port/srvKey/group/xxx +// 完成请求地址: ip:port/rest/srvKey/group/xxx func Register(srvKey string, engine *gin.Engine) { - base := "/" + srvKey + base := path.Join("/rest", srvKey) registerAnonymous(base, engine) registerRouters(base, engine) } diff --git a/module/base/mgt/internal/routers/register_test.go b/module/base/mgt/internal/routers/register_test.go new file mode 100644 index 0000000..ef1c753 --- /dev/null +++ b/module/base/mgt/internal/routers/register_test.go @@ -0,0 +1,35 @@ +package routers + +import ( + "strings" + "testing" + + "github.com/gin-gonic/gin" +) + +func TestRoutesUseRESTModulePrefix(t *testing.T) { + gin.SetMode(gin.TestMode) + engine := gin.New() + Register("mgt", engine) + + expected := map[string]bool{ + "GET /rest/mgt/ping": false, + "POST /rest/mgt/login": false, + "POST /rest/mgt/user/create": false, + "POST /rest/mgt/app/fetch": false, + } + for _, route := range engine.Routes() { + key := route.Method + " " + route.Path + if _, ok := expected[key]; ok { + expected[key] = true + } + if route.Path == "/mgt" || strings.HasPrefix(route.Path, "/mgt/") { + t.Fatalf("legacy route remains registered: %s", route.Path) + } + } + for route, found := range expected { + if !found { + t.Errorf("route not registered: %s", route) + } + } +}