From 4df3c1232809e0c12adda3f49ec8dbb13f29512f Mon Sep 17 00:00:00 2001 From: huang Date: Tue, 6 Jan 2026 11:07:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=91=E6=8F=90=E4=BA=A4=E7=9A=84=E4=B8=9C?= =?UTF-8?q?=E8=A5=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/routes/admin.go | 22 +++++++ internal/routes/permission.go | 54 ++++++++++++--- internal/routes/registry.go | 43 ++++++++++++ internal/routes/role.go | 69 ++++++++++++++++--- internal/routes/routes.go | 22 ++----- pkg/openapi/generator.go | 84 ++++++++++++++++++++++++ tests/integration/account_test.go | 30 ++++----- tests/integration/api_regression_test.go | 56 ++++++++-------- tests/integration/permission_test.go | 24 +++---- tests/integration/role_test.go | 24 +++---- 10 files changed, 328 insertions(+), 100 deletions(-) create mode 100644 internal/routes/admin.go create mode 100644 internal/routes/registry.go create mode 100644 pkg/openapi/generator.go diff --git a/internal/routes/admin.go b/internal/routes/admin.go new file mode 100644 index 0000000..6ae791a --- /dev/null +++ b/internal/routes/admin.go @@ -0,0 +1,22 @@ +package routes + +import ( + "github.com/gofiber/fiber/v2" + + "github.com/break/junhong_cmp_fiber/internal/bootstrap" + "github.com/break/junhong_cmp_fiber/pkg/openapi" +) + +// RegisterAdminRoutes 注册管理后台相关路由 +func RegisterAdminRoutes(router fiber.Router, handlers *bootstrap.Handlers, doc *openapi.Generator, basePath string) { + if handlers.Account != nil { + registerAccountRoutes(router, handlers.Account, doc, basePath) + } + if handlers.Role != nil { + registerRoleRoutes(router, handlers.Role, doc, basePath) + } + if handlers.Permission != nil { + registerPermissionRoutes(router, handlers.Permission, doc, basePath) + } + // TODO: Task routes? +} diff --git a/internal/routes/permission.go b/internal/routes/permission.go index ed3cdd8..1bfb1e0 100644 --- a/internal/routes/permission.go +++ b/internal/routes/permission.go @@ -3,18 +3,56 @@ package routes import ( "github.com/gofiber/fiber/v2" - "github.com/break/junhong_cmp_fiber/internal/handler" + "github.com/break/junhong_cmp_fiber/internal/handler/admin" + "github.com/break/junhong_cmp_fiber/internal/model" + "github.com/break/junhong_cmp_fiber/pkg/openapi" ) // registerPermissionRoutes 注册权限相关路由 -func registerPermissionRoutes(api fiber.Router, h *handler.PermissionHandler) { +func registerPermissionRoutes(api fiber.Router, h *admin.PermissionHandler, doc *openapi.Generator, basePath string) { permissions := api.Group("/permissions") + groupPath := basePath + "/permissions" // 权限 CRUD - permissions.Post("", h.Create) // POST /api/v1/permissions - permissions.Get("", h.List) // GET /api/v1/permissions - permissions.Get("/tree", h.GetTree) // GET /api/v1/permissions/tree (注意:放在 :id 之前避免路由冲突) - permissions.Get("/:id", h.Get) // GET /api/v1/permissions/:id - permissions.Put("/:id", h.Update) // PUT /api/v1/permissions/:id - permissions.Delete("/:id", h.Delete) // DELETE /api/v1/permissions/:id + Register(permissions, doc, groupPath, "POST", "", h.Create, RouteSpec{ + Summary: "创建权限", + Tags: []string{"Permission"}, + Input: new(model.CreatePermissionRequest), + Output: new(model.PermissionResponse), + }) + + Register(permissions, doc, groupPath, "GET", "", h.List, RouteSpec{ + Summary: "权限列表", + Tags: []string{"Permission"}, + Input: new(model.PermissionListRequest), + Output: new(model.PermissionPageResult), + }) + + Register(permissions, doc, groupPath, "GET", "/tree", h.GetTree, RouteSpec{ + Summary: "获取权限树", + Tags: []string{"Permission"}, + Input: nil, // 无参数或 Query 参数 + Output: new([]*model.PermissionTreeNode), + }) + + Register(permissions, doc, groupPath, "GET", "/:id", h.Get, RouteSpec{ + Summary: "获取权限详情", + Tags: []string{"Permission"}, + Input: new(model.IDReq), + Output: new(model.PermissionResponse), + }) + + Register(permissions, doc, groupPath, "PUT", "/:id", h.Update, RouteSpec{ + Summary: "更新权限", + Tags: []string{"Permission"}, + Input: new(model.UpdatePermissionParams), + Output: new(model.PermissionResponse), + }) + + Register(permissions, doc, groupPath, "DELETE", "/:id", h.Delete, RouteSpec{ + Summary: "删除权限", + Tags: []string{"Permission"}, + Input: new(model.IDReq), + Output: nil, + }) } diff --git a/internal/routes/registry.go b/internal/routes/registry.go new file mode 100644 index 0000000..46c353f --- /dev/null +++ b/internal/routes/registry.go @@ -0,0 +1,43 @@ +package routes + +import ( + "regexp" + + "github.com/gofiber/fiber/v2" + + "github.com/break/junhong_cmp_fiber/pkg/openapi" +) + +// RouteSpec 定义接口文档元数据 +type RouteSpec struct { + Summary string + Input interface{} // 请求参数结构体 (Query/Path/Body) + Output interface{} // 响应参数结构体 + Tags []string + Auth bool // 是否需要认证图标 (预留) +} + +// pathParamRegex 用于匹配 Fiber 的路径参数格式 /:param +var pathParamRegex = regexp.MustCompile(`/:([a-zA-Z0-9_]+)`) + +// Register 封装后的注册函数 +// router: Fiber 路由组 +// doc: 文档生成器 (如果在运行 Web 服务时为 nil,在生成文档时为非 nil) +// basePath: 当前路由组的基础路径 (用于文档生成) +// method, path: HTTP 方法和路径 +// handler: Fiber Handler +// spec: 文档元数据 +func Register(router fiber.Router, doc *openapi.Generator, basePath, method, path string, handler fiber.Handler, spec RouteSpec) { + // 1. 注册实际的 Fiber 路由 + router.Add(method, path, handler) + + // 2. 注册文档 (如果 doc 不为空 - 也就是在生成文档模式下) + if doc != nil { + // 简单的路径拼接 + fullPath := basePath + path + // 将 Fiber 路由参数格式 /:id 转换为 OpenAPI 格式 /{id} + openapiPath := pathParamRegex.ReplaceAllString(fullPath, "/{$1}") + + doc.AddOperation(method, openapiPath, spec.Summary, spec.Input, spec.Output, spec.Tags...) + } +} diff --git a/internal/routes/role.go b/internal/routes/role.go index d582454..7f0bd51 100644 --- a/internal/routes/role.go +++ b/internal/routes/role.go @@ -3,22 +3,71 @@ package routes import ( "github.com/gofiber/fiber/v2" - "github.com/break/junhong_cmp_fiber/internal/handler" + "github.com/break/junhong_cmp_fiber/internal/handler/admin" + "github.com/break/junhong_cmp_fiber/internal/model" + "github.com/break/junhong_cmp_fiber/pkg/openapi" ) // registerRoleRoutes 注册角色相关路由 -func registerRoleRoutes(api fiber.Router, h *handler.RoleHandler) { +func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Generator, basePath string) { roles := api.Group("/roles") + groupPath := basePath + "/roles" // 角色 CRUD - roles.Post("", h.Create) // POST /api/v1/roles - roles.Get("", h.List) // GET /api/v1/roles - roles.Get("/:id", h.Get) // GET /api/v1/roles/:id - roles.Put("/:id", h.Update) // PUT /api/v1/roles/:id - roles.Delete("/:id", h.Delete) // DELETE /api/v1/roles/:id + Register(roles, doc, groupPath, "POST", "", h.Create, RouteSpec{ + Summary: "创建角色", + Tags: []string{"Role"}, + Input: new(model.CreateRoleRequest), + Output: new(model.RoleResponse), + }) + + Register(roles, doc, groupPath, "GET", "", h.List, RouteSpec{ + Summary: "角色列表", + Tags: []string{"Role"}, + Input: new(model.RoleListRequest), + Output: new(model.RolePageResult), + }) + + Register(roles, doc, groupPath, "GET", "/:id", h.Get, RouteSpec{ + Summary: "获取角色详情", + Tags: []string{"Role"}, + Input: new(model.IDReq), + Output: new(model.RoleResponse), + }) + + Register(roles, doc, groupPath, "PUT", "/:id", h.Update, RouteSpec{ + Summary: "更新角色", + Tags: []string{"Role"}, + Input: new(model.UpdateRoleParams), + Output: new(model.RoleResponse), + }) + + Register(roles, doc, groupPath, "DELETE", "/:id", h.Delete, RouteSpec{ + Summary: "删除角色", + Tags: []string{"Role"}, + Input: new(model.IDReq), + Output: nil, + }) // 角色-权限关联 - roles.Post("/:id/permissions", h.AssignPermissions) // POST /api/v1/roles/:id/permissions - roles.Get("/:id/permissions", h.GetPermissions) // GET /api/v1/roles/:id/permissions - roles.Delete("/:role_id/permissions/:perm_id", h.RemovePermission) // DELETE /api/v1/roles/:role_id/permissions/:perm_id + Register(roles, doc, groupPath, "POST", "/:id/permissions", h.AssignPermissions, RouteSpec{ + Summary: "分配权限", + Tags: []string{"Role"}, + Input: new(model.AssignPermissionsParams), + Output: nil, + }) + + Register(roles, doc, groupPath, "GET", "/:id/permissions", h.GetPermissions, RouteSpec{ + Summary: "获取角色权限", + Tags: []string{"Role"}, + Input: new(model.IDReq), + Output: new([]model.Permission), + }) + + Register(roles, doc, groupPath, "DELETE", "/:role_id/permissions/:perm_id", h.RemovePermission, RouteSpec{ + Summary: "移除权限", + Tags: []string{"Role"}, + Input: new(model.RemovePermissionParams), + Output: nil, + }) } diff --git a/internal/routes/routes.go b/internal/routes/routes.go index d8b5ee0..43920f4 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -9,21 +9,13 @@ import ( // RegisterRoutes 路由注册总入口 // 按业务模块调用各自的路由注册函数 func RegisterRoutes(app *fiber.App, handlers *bootstrap.Handlers) { - // API 路由组 - api := app.Group("/api/v1") - - // 注册各模块路由 + // 1. 全局路由 registerHealthRoutes(app) - registerTaskRoutes(api) - // RBAC 路由 - if handlers.Account != nil { - registerAccountRoutes(api, handlers.Account) - } - if handlers.Role != nil { - registerRoleRoutes(api, handlers.Role) - } - if handlers.Permission != nil { - registerPermissionRoutes(api, handlers.Permission) - } + // 2. Admin 域 (挂载在 /api/admin) + adminGroup := app.Group("/api/admin") + RegisterAdminRoutes(adminGroup, handlers, nil, "/api/admin") + + // 任务相关路由 (归属于 Admin 域) + registerTaskRoutes(adminGroup) } diff --git a/pkg/openapi/generator.go b/pkg/openapi/generator.go new file mode 100644 index 0000000..18de685 --- /dev/null +++ b/pkg/openapi/generator.go @@ -0,0 +1,84 @@ +package openapi + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/swaggest/openapi-go/openapi3" + "gopkg.in/yaml.v3" +) + +// Generator OpenAPI 文档生成器 +type Generator struct { + Reflector *openapi3.Reflector +} + +// NewGenerator 创建一个新的生成器实例 +func NewGenerator(title, version string) *Generator { + reflector := openapi3.Reflector{} + reflector.Spec = &openapi3.Spec{ + Openapi: "3.0.3", + Info: openapi3.Info{ + Title: title, + Version: version, + }, + } + return &Generator{Reflector: &reflector} +} + +// AddOperation 向 OpenAPI 规范中添加一个操作 +func (g *Generator) AddOperation(method, path, summary string, input interface{}, output interface{}, tags ...string) { + op := openapi3.Operation{ + Summary: &summary, + Tags: tags, + } + + // 反射输入 (请求参数/Body) + if input != nil { + // SetRequest 根据结构体标签自动检测 Body、Query 或 Path 参数 + if err := g.Reflector.SetRequest(&op, input, method); err != nil { + panic(err) // 生成过程中出错直接 panic,以便快速发现问题 + } + } + + // 反射输出 (响应 Body) + if output != nil { + if err := g.Reflector.SetJSONResponse(&op, output, 200); err != nil { + panic(err) + } + } + + // 将操作添加到规范中 + if err := g.Reflector.Spec.AddOperation(method, path, op); err != nil { + panic(err) + } +} + +// Save 将规范导出为 YAML 文件 +func (g *Generator) Save(filename string) error { + // 确保目录存在 + dir := filepath.Dir(filename) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + // 安全的方法:MarshalJSON -> Unmarshal -> MarshalYAML + // 这确保了我们遵守 openapi3 库中定义的 `json` 标签 + jsonBytes, err := g.Reflector.Spec.MarshalJSON() + if err != nil { + return err + } + + var obj interface{} + if err := json.Unmarshal(jsonBytes, &obj); err != nil { + return err + } + + yamlBytes, err := yaml.Marshal(obj) + if err != nil { + return err + } + + return os.WriteFile(filename, yamlBytes, 0644) +} \ No newline at end of file diff --git a/tests/integration/account_test.go b/tests/integration/account_test.go index 9beee1f..9c765cc 100644 --- a/tests/integration/account_test.go +++ b/tests/integration/account_test.go @@ -22,7 +22,7 @@ import ( "gorm.io/gorm/logger" "github.com/break/junhong_cmp_fiber/internal/bootstrap" - "github.com/break/junhong_cmp_fiber/internal/handler" + "github.com/break/junhong_cmp_fiber/internal/handler/admin" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/routes" accountService "github.com/break/junhong_cmp_fiber/internal/service/account" @@ -107,7 +107,7 @@ func setupTestEnv(t *testing.T) *testEnv { accService := accountService.New(accountStore, roleStore, accountRoleStore) // 初始化 Handler - accountHandler := handler.NewAccountHandler(accService) + accountHandler := admin.NewAccountHandler(accService) // 创建 Fiber App app := fiber.New(fiber.Config{ @@ -191,7 +191,7 @@ func TestAccountAPI_Create(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/v1/accounts", bytes.NewReader(jsonBody)) + req := httptest.NewRequest("POST", "/api/admin/accounts", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -231,7 +231,7 @@ func TestAccountAPI_Create(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/v1/accounts", bytes.NewReader(jsonBody)) + req := httptest.NewRequest("POST", "/api/admin/accounts", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -253,7 +253,7 @@ func TestAccountAPI_Create(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/v1/accounts", bytes.NewReader(jsonBody)) + req := httptest.NewRequest("POST", "/api/admin/accounts", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -290,7 +290,7 @@ func TestAccountAPI_Get(t *testing.T) { createTestAccount(t, env.db, testAccount) t.Run("成功获取账号详情", func(t *testing.T) { - req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d", testAccount.ID), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/accounts/%d", testAccount.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -302,7 +302,7 @@ func TestAccountAPI_Get(t *testing.T) { }) t.Run("账号不存在时返回错误", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/accounts/99999", nil) + req := httptest.NewRequest("GET", "/api/admin/accounts/99999", nil) resp, err := env.app.Test(req) require.NoError(t, err) @@ -313,7 +313,7 @@ func TestAccountAPI_Get(t *testing.T) { }) t.Run("无效ID返回错误", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/accounts/invalid", nil) + req := httptest.NewRequest("GET", "/api/admin/accounts/invalid", nil) resp, err := env.app.Test(req) require.NoError(t, err) @@ -354,7 +354,7 @@ func TestAccountAPI_Update(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/accounts/%d", testAccount.ID), bytes.NewReader(jsonBody)) + req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/accounts/%d", testAccount.ID), bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -392,7 +392,7 @@ func TestAccountAPI_Delete(t *testing.T) { } createTestAccount(t, env.db, testAccount) - req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/accounts/%d", testAccount.ID), nil) + req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/admin/accounts/%d", testAccount.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -431,7 +431,7 @@ func TestAccountAPI_List(t *testing.T) { } t.Run("成功获取账号列表", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/accounts?page=1&page_size=10", nil) + req := httptest.NewRequest("GET", "/api/admin/accounts?page=1&page_size=10", nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -443,7 +443,7 @@ func TestAccountAPI_List(t *testing.T) { }) t.Run("分页功能正常", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/accounts?page=1&page_size=2", nil) + req := httptest.NewRequest("GET", "/api/admin/accounts?page=1&page_size=2", nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -487,7 +487,7 @@ func TestAccountAPI_AssignRoles(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/roles", testAccount.ID), bytes.NewReader(jsonBody)) + req := httptest.NewRequest("POST", fmt.Sprintf("/api/admin/accounts/%d/roles", testAccount.ID), bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -542,7 +542,7 @@ func TestAccountAPI_GetRoles(t *testing.T) { env.db.Create(accountRole) t.Run("成功获取账号角色", func(t *testing.T) { - req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/roles", testAccount.ID), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/accounts/%d/roles", testAccount.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -595,7 +595,7 @@ func TestAccountAPI_RemoveRole(t *testing.T) { env.db.Create(accountRole) t.Run("成功移除角色", func(t *testing.T) { - req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/accounts/%d/roles/%d", testAccount.ID, testRole.ID), nil) + req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/admin/accounts/%d/roles/%d", testAccount.ID, testRole.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) diff --git a/tests/integration/api_regression_test.go b/tests/integration/api_regression_test.go index 738cc74..bae154b 100644 --- a/tests/integration/api_regression_test.go +++ b/tests/integration/api_regression_test.go @@ -20,7 +20,7 @@ import ( "gorm.io/gorm/logger" "github.com/break/junhong_cmp_fiber/internal/bootstrap" - "github.com/break/junhong_cmp_fiber/internal/handler" + "github.com/break/junhong_cmp_fiber/internal/handler/admin" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/routes" accountService "github.com/break/junhong_cmp_fiber/internal/service/account" @@ -108,9 +108,9 @@ func setupRegressionTestEnv(t *testing.T) *regressionTestEnv { permSvc := permissionService.New(permStore) // 初始化所有 Handler - accountHandler := handler.NewAccountHandler(accService) - roleHandler := handler.NewRoleHandler(roleSvc) - permHandler := handler.NewPermissionHandler(permSvc) + accountHandler := admin.NewAccountHandler(accService) + roleHandler := admin.NewRoleHandler(roleSvc) + permHandler := admin.NewPermissionHandler(permSvc) // 创建 Fiber App app := fiber.New(fiber.Config{ @@ -168,17 +168,17 @@ func TestAPIRegression_AllEndpointsAccessible(t *testing.T) { {"GET", "/health/ready", "Readiness check"}, // Account endpoints - {"GET", "/api/v1/accounts", "List accounts"}, - {"GET", "/api/v1/accounts/1", "Get account"}, + {"GET", "/api/admin/accounts", "List accounts"}, + {"GET", "/api/admin/accounts/1", "Get account"}, // Role endpoints - {"GET", "/api/v1/roles", "List roles"}, - {"GET", "/api/v1/roles/1", "Get role"}, + {"GET", "/api/admin/roles", "List roles"}, + {"GET", "/api/admin/roles/1", "Get role"}, // Permission endpoints - {"GET", "/api/v1/permissions", "List permissions"}, - {"GET", "/api/v1/permissions/1", "Get permission"}, - {"GET", "/api/v1/permissions/tree", "Get permission tree"}, + {"GET", "/api/admin/permissions", "List permissions"}, + {"GET", "/api/admin/permissions/1", "Get permission"}, + {"GET", "/api/admin/permissions/tree", "Get permission tree"}, } for _, ep := range endpoints { @@ -214,13 +214,13 @@ func TestAPIRegression_RouteModularization(t *testing.T) { env.db.Create(account) // 测试获取账号 - req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d", account.ID), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/accounts/%d", account.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) // 测试获取角色列表 - req = httptest.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/roles", account.ID), nil) + req = httptest.NewRequest("GET", fmt.Sprintf("/api/admin/accounts/%d/roles", account.ID), nil) resp, err = env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -236,13 +236,13 @@ func TestAPIRegression_RouteModularization(t *testing.T) { env.db.Create(role) // 测试获取角色 - req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/roles/%d", role.ID), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/roles/%d", role.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) // 测试获取权限列表 - req = httptest.NewRequest("GET", fmt.Sprintf("/api/v1/roles/%d/permissions", role.ID), nil) + req = httptest.NewRequest("GET", fmt.Sprintf("/api/admin/roles/%d/permissions", role.ID), nil) resp, err = env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -259,13 +259,13 @@ func TestAPIRegression_RouteModularization(t *testing.T) { env.db.Create(perm) // 测试获取权限 - req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/permissions/%d", perm.ID), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/permissions/%d", perm.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) // 测试获取权限树 - req = httptest.NewRequest("GET", "/api/v1/permissions/tree", nil) + req = httptest.NewRequest("GET", "/api/admin/permissions/tree", nil) resp, err = env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -280,20 +280,20 @@ func TestAPIRegression_ErrorHandling(t *testing.T) { t.Run("资源不存在返回正确错误码", func(t *testing.T) { // 账号不存在 - req := httptest.NewRequest("GET", "/api/v1/accounts/99999", nil) + req := httptest.NewRequest("GET", "/api/admin/accounts/99999", nil) resp, err := env.app.Test(req) require.NoError(t, err) // 应该返回业务错误,不是 404 assert.NotEqual(t, fiber.StatusNotFound, resp.StatusCode) // 角色不存在 - req = httptest.NewRequest("GET", "/api/v1/roles/99999", nil) + req = httptest.NewRequest("GET", "/api/admin/roles/99999", nil) resp, err = env.app.Test(req) require.NoError(t, err) assert.NotEqual(t, fiber.StatusNotFound, resp.StatusCode) // 权限不存在 - req = httptest.NewRequest("GET", "/api/v1/permissions/99999", nil) + req = httptest.NewRequest("GET", "/api/admin/permissions/99999", nil) resp, err = env.app.Test(req) require.NoError(t, err) assert.NotEqual(t, fiber.StatusNotFound, resp.StatusCode) @@ -301,7 +301,7 @@ func TestAPIRegression_ErrorHandling(t *testing.T) { t.Run("无效参数返回正确错误码", func(t *testing.T) { // 无效账号 ID - req := httptest.NewRequest("GET", "/api/v1/accounts/invalid", nil) + req := httptest.NewRequest("GET", "/api/admin/accounts/invalid", nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.NotEqual(t, fiber.StatusInternalServerError, resp.StatusCode) @@ -328,20 +328,20 @@ func TestAPIRegression_Pagination(t *testing.T) { t.Run("分页参数正常工作", func(t *testing.T) { // 第一页 - req := httptest.NewRequest("GET", "/api/v1/accounts?page=1&page_size=10", nil) + req := httptest.NewRequest("GET", "/api/admin/accounts?page=1&page_size=10", nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) // 第二页 - req = httptest.NewRequest("GET", "/api/v1/accounts?page=2&page_size=10", nil) + req = httptest.NewRequest("GET", "/api/admin/accounts?page=2&page_size=10", nil) resp, err = env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) }) t.Run("默认分页参数工作", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/accounts", nil) + req := httptest.NewRequest("GET", "/api/admin/accounts", nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -355,7 +355,7 @@ func TestAPIRegression_ResponseFormat(t *testing.T) { defer env.redisCleanup() t.Run("成功响应包含正确字段", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/accounts", nil) + req := httptest.NewRequest("GET", "/api/admin/accounts", nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -382,9 +382,9 @@ func TestAPIRegression_ServicesIntegration(t *testing.T) { // 验证所有模块路由都已注册 endpoints := []string{ "/health", - "/api/v1/accounts", - "/api/v1/roles", - "/api/v1/permissions", + "/api/admin/accounts", + "/api/admin/roles", + "/api/admin/permissions", } for _, ep := range endpoints { diff --git a/tests/integration/permission_test.go b/tests/integration/permission_test.go index bef318d..fabf0d3 100644 --- a/tests/integration/permission_test.go +++ b/tests/integration/permission_test.go @@ -20,7 +20,7 @@ import ( "gorm.io/gorm/logger" "github.com/break/junhong_cmp_fiber/internal/bootstrap" - "github.com/break/junhong_cmp_fiber/internal/handler" + "github.com/break/junhong_cmp_fiber/internal/handler/admin" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/routes" permissionService "github.com/break/junhong_cmp_fiber/internal/service/permission" @@ -81,7 +81,7 @@ func setupPermTestEnv(t *testing.T) *permTestEnv { permSvc := permissionService.New(permStore) // 初始化 Handler - permHandler := handler.NewPermissionHandler(permSvc) + permHandler := admin.NewPermissionHandler(permSvc) // 创建 Fiber App app := fiber.New(fiber.Config{ @@ -130,7 +130,7 @@ func TestPermissionAPI_Create(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/v1/permissions", bytes.NewReader(jsonBody)) + req := httptest.NewRequest("POST", "/api/admin/permissions", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -166,7 +166,7 @@ func TestPermissionAPI_Create(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/v1/permissions", bytes.NewReader(jsonBody)) + req := httptest.NewRequest("POST", "/api/admin/permissions", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -197,7 +197,7 @@ func TestPermissionAPI_Create(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/v1/permissions", bytes.NewReader(jsonBody)) + req := httptest.NewRequest("POST", "/api/admin/permissions", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -235,7 +235,7 @@ func TestPermissionAPI_Get(t *testing.T) { env.db.Create(testPerm) t.Run("成功获取权限详情", func(t *testing.T) { - req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/permissions/%d", testPerm.ID), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/permissions/%d", testPerm.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -247,7 +247,7 @@ func TestPermissionAPI_Get(t *testing.T) { }) t.Run("权限不存在时返回错误", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/permissions/99999", nil) + req := httptest.NewRequest("GET", "/api/admin/permissions/99999", nil) resp, err := env.app.Test(req) require.NoError(t, err) @@ -287,7 +287,7 @@ func TestPermissionAPI_Update(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/permissions/%d", testPerm.ID), bytes.NewReader(jsonBody)) + req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/permissions/%d", testPerm.ID), bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -324,7 +324,7 @@ func TestPermissionAPI_Delete(t *testing.T) { } env.db.Create(testPerm) - req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/permissions/%d", testPerm.ID), nil) + req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/admin/permissions/%d", testPerm.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -362,7 +362,7 @@ func TestPermissionAPI_List(t *testing.T) { } t.Run("成功获取权限列表", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/permissions?page=1&page_size=10", nil) + req := httptest.NewRequest("GET", "/api/admin/permissions?page=1&page_size=10", nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -374,7 +374,7 @@ func TestPermissionAPI_List(t *testing.T) { }) t.Run("按类型过滤权限", func(t *testing.T) { - req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/permissions?perm_type=%d", constants.PermissionTypeMenu), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/permissions?perm_type=%d", constants.PermissionTypeMenu), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -425,7 +425,7 @@ func TestPermissionAPI_GetTree(t *testing.T) { env.db.Create(grandchildPerm) t.Run("成功获取权限树", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/permissions/tree", nil) + req := httptest.NewRequest("GET", "/api/admin/permissions/tree", nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) diff --git a/tests/integration/role_test.go b/tests/integration/role_test.go index bac9fd8..8dc42a6 100644 --- a/tests/integration/role_test.go +++ b/tests/integration/role_test.go @@ -22,7 +22,7 @@ import ( "gorm.io/gorm/logger" "github.com/break/junhong_cmp_fiber/internal/bootstrap" - "github.com/break/junhong_cmp_fiber/internal/handler" + "github.com/break/junhong_cmp_fiber/internal/handler/admin" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/routes" roleService "github.com/break/junhong_cmp_fiber/internal/service/role" @@ -107,7 +107,7 @@ func setupRoleTestEnv(t *testing.T) *roleTestEnv { roleSvc := roleService.New(roleStore, permissionStore, rolePermissionStore) // 初始化 Handler - roleHandler := handler.NewRoleHandler(roleSvc) + roleHandler := admin.NewRoleHandler(roleSvc) // 创建 Fiber App app := fiber.New(fiber.Config{ @@ -171,7 +171,7 @@ func TestRoleAPI_Create(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/v1/roles", bytes.NewReader(jsonBody)) + req := httptest.NewRequest("POST", "/api/admin/roles", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -195,7 +195,7 @@ func TestRoleAPI_Create(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/v1/roles", bytes.NewReader(jsonBody)) + req := httptest.NewRequest("POST", "/api/admin/roles", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -230,7 +230,7 @@ func TestRoleAPI_Get(t *testing.T) { env.db.Create(testRole) t.Run("成功获取角色详情", func(t *testing.T) { - req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/roles/%d", testRole.ID), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/roles/%d", testRole.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -242,7 +242,7 @@ func TestRoleAPI_Get(t *testing.T) { }) t.Run("角色不存在时返回错误", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/roles/99999", nil) + req := httptest.NewRequest("GET", "/api/admin/roles/99999", nil) resp, err := env.app.Test(req) require.NoError(t, err) @@ -281,7 +281,7 @@ func TestRoleAPI_Update(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("PUT", fmt.Sprintf("/api/v1/roles/%d", testRole.ID), bytes.NewReader(jsonBody)) + req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/roles/%d", testRole.ID), bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -317,7 +317,7 @@ func TestRoleAPI_Delete(t *testing.T) { } env.db.Create(testRole) - req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/roles/%d", testRole.ID), nil) + req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/admin/roles/%d", testRole.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -354,7 +354,7 @@ func TestRoleAPI_List(t *testing.T) { } t.Run("成功获取角色列表", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/v1/roles?page=1&page_size=10", nil) + req := httptest.NewRequest("GET", "/api/admin/roles?page=1&page_size=10", nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -402,7 +402,7 @@ func TestRoleAPI_AssignPermissions(t *testing.T) { } jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", fmt.Sprintf("/api/v1/roles/%d/permissions", testRole.ID), bytes.NewReader(jsonBody)) + req := httptest.NewRequest("POST", fmt.Sprintf("/api/admin/roles/%d/permissions", testRole.ID), bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") resp, err := env.app.Test(req) @@ -454,7 +454,7 @@ func TestRoleAPI_GetPermissions(t *testing.T) { env.db.Create(rolePerm) t.Run("成功获取角色权限", func(t *testing.T) { - req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/roles/%d/permissions", testRole.ID), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/roles/%d/permissions", testRole.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode) @@ -504,7 +504,7 @@ func TestRoleAPI_RemovePermission(t *testing.T) { env.db.Create(rolePerm) t.Run("成功移除权限", func(t *testing.T) { - req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/roles/%d/permissions/%d", testRole.ID, testPerm.ID), nil) + req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/admin/roles/%d/permissions/%d", testRole.ID, testPerm.ID), nil) resp, err := env.app.Test(req) require.NoError(t, err) assert.Equal(t, fiber.StatusOK, resp.StatusCode)