feat: standardize REST and RPC routes

This commit is contained in:
2026-08-11 18:55:36 +08:00
parent 53e953c3fc
commit eed82bcf38
8 changed files with 89 additions and 22 deletions

View File

@@ -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:

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}

View File

@@ -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)
}
}
}