From 43fa79686d99a7800f0bdc623bfc6807f7cd8f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E7=8A=80=E9=BA=9F?= Date: Mon, 24 Aug 2026 23:40:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor(api):=20=E5=90=8E=E5=8F=B0=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20API=20=E6=8C=89=20base/system/admin=20=E5=88=86?= =?UTF-8?q?=E7=BB=84=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - api/admin/v1 拆 base/system/admin 三子包,路由前缀 /admin/v1/{base,system,admin} - system: auth/menu/role;admin: 管理员;base: log - controller 改 import 子包;006_menu_paths_v2.sql 更新接口路径映射 - 权限码与路由分离,permission 标识不变 - 冒烟测试新路径 8 接口全通过 --- AGENTS.md | 19 +++++++++-------- api/admin/v1/{ => admin}/admin.go | 13 ++++++------ api/admin/v1/{ => base}/log.go | 7 ++++--- api/admin/v1/{ => system}/auth.go | 9 ++++---- api/admin/v1/{ => system}/menu.go | 4 ++-- api/admin/v1/{ => system}/menu_manage.go | 10 ++++----- api/admin/v1/{ => system}/role.go | 10 ++++----- docs/change-log/2026-08-24.md | 26 ++++++++++++++++++++++++ internal/controller/admin/admin.go | 2 +- internal/controller/admin/auth.go | 6 +++--- internal/controller/admin/log.go | 14 ++++++------- internal/controller/admin/menu.go | 24 +++++++++++----------- internal/controller/admin/profile.go | 22 ++++++++++---------- internal/controller/admin/role.go | 22 ++++++++++---------- manifest/sql/006_menu_paths_v2.sql | 19 +++++++++++++++++ 15 files changed, 129 insertions(+), 78 deletions(-) rename api/admin/v1/{ => admin}/admin.go (78%) rename api/admin/v1/{ => base}/log.go (75%) rename api/admin/v1/{ => system}/auth.go (68%) rename api/admin/v1/{ => system}/menu.go (88%) rename api/admin/v1/{ => system}/menu_manage.go (84%) rename api/admin/v1/{ => system}/role.go (80%) create mode 100644 manifest/sql/006_menu_paths_v2.sql diff --git a/AGENTS.md b/AGENTS.md index 8397daf..e27541c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,17 +44,20 @@ service.xpcool.com/ └── utility/ # (预留)跨切面辅助 ``` -## 后台管理 API(api/admin/v1) +## 后台管理 API(api/admin/v1,按 base/system/admin 分组) +- **分组规则**:`api/admin/v1/{base,system,admin}/` 三个子包,路由前缀 `/admin/v1/{base,system,admin}`: + - **base**(基础常规):`/base/log/*`(服务器日志监控) + - **system**(系统管理):`/system/auth/*`(登录/信息/权限码)、`/system/menu/*`(菜单路由+CRUD)、`/system/role/*`(角色 CRUD) + - **admin**(后台管理):`/admin`(管理员账号 CRUD) - 三层路由隔离(`internal/cmd/cmd.go`): - - **公开**(无鉴权):`POST /auth/login` - - **仅登录** `AdminAuthOnly`:`GET /auth/info`(个人资料)、`GET /auth/codes`(按钮权限码)、`GET /menu/routes`(vben 动态路由) + - **公开**(无鉴权):`POST /system/auth/login` + - **仅登录** `AdminAuthOnly`:`GET /system/auth/info`、`GET /system/auth/codes`、`GET /system/menu/routes` - **接口级鉴权** `AdminAuth`:RBAC 管理、日志等 -- **接口鉴权机制(重要)**:中间件按「请求方法+路径」从 `admin_menu`(type=2 行,path 存 `"METHOD /path"`,`{id}` 为动态段)反查所需权限码,再校验用户是否拥有。**前端无需传 X-Permission**;未配置映射的接口一律拒绝。 -- RBAC 管理:`/admins`、`/roles`、`/menus/tree`(权限码 `system:admin:*`、`system:role:*`、`system:menu:*`) -- 日志监控:`GET /log/files`、`GET /log/tail?file=&lines=&keyword=`(权限码 `monitor:log:*`;file 参数防路径穿越) -- 新增受保护接口三步:① `api/admin/v1/.go` 写 Req/Res;② `internal/controller/admin/.go` 加方法;③ 在 `admin_menu` 加 type=2 行:`permission` 填权限码、`path` 填 `"METHOD /路径"` 映射。 -- 迁移脚本:`003_schema_ext.sql`(admin_menu 加 icon/component/hidden)、`004_seed.sql`(初始 admin/admin123、super_admin、22 条菜单)、`005_menu_paths.sql`(按钮-接口路径映射)。 +- **接口鉴权机制(重要)**:中间件按「请求方法+路径」从 `admin_menu`(type=2 行,path 存 `"METHOD /路径"`,`{id}` 为动态段)反查所需权限码,再校验用户是否拥有。**前端无需传 X-Permission**;未配置映射的接口一律拒绝。 +- 权限码(permission)与路由分离:权限码保持 `system:admin:list` 等逻辑标识,路由路径按 base/system/admin 分组。 +- 新增受保护接口三步:① `api/admin/v1/{分组}/.go` 写 Req/Res;② `internal/controller/admin/.go` 加方法;③ 在 `admin_menu` 加 type=2 行:`permission` 填权限码、`path` 填 `"METHOD /路径"` 映射。 +- 迁移脚本:`003_schema_ext.sql`(admin_menu 加列)、`004_seed.sql`(初始账号/角色/菜单)、`005_menu_paths.sql`、`006_menu_paths_v2.sql`(按钮-接口路径映射,006 为分组重构后)。 ## 开放接口(api/open/v1,前端调用)与命名规则 diff --git a/api/admin/v1/admin.go b/api/admin/v1/admin/admin.go similarity index 78% rename from api/admin/v1/admin.go rename to api/admin/v1/admin/admin.go index df58927..55a17e9 100644 --- a/api/admin/v1/admin.go +++ b/api/admin/v1/admin/admin.go @@ -1,4 +1,5 @@ -package v1 +// Package admin 定义后台管理接口(管理员账号管理),路由前缀 /admin/v1/admin。 +package admin import "github.com/gogf/gf/v2/frame/g" @@ -15,7 +16,7 @@ type AdminItem struct { // AdminListReq pages the administrators. type AdminListReq struct { - g.Meta `path:"/admins" method:"get" tags:"Admin/Admin" summary:"Admin list"` + g.Meta `path:"/admin" method:"get" tags:"Admin/Admin" summary:"Admin list"` Page int `json:"page" d:"1" v:"min:1"` Size int `json:"size" d:"10" v:"min:1|max:100"` Keyword string `json:"keyword"` // matches username or nickname @@ -29,7 +30,7 @@ type AdminListRes struct { // AdminCreateReq creates an administrator. type AdminCreateReq struct { - g.Meta `path:"/admins" method:"post" tags:"Admin/Admin" summary:"Create admin"` + g.Meta `path:"/admin" method:"post" tags:"Admin/Admin" summary:"Create admin"` Username string `json:"username" v:"required"` Password string `json:"password" v:"required|min-length:6#password required|password too short"` Nickname string `json:"nickname"` @@ -43,7 +44,7 @@ type AdminCreateRes struct { // AdminUpdateReq updates profile/status/roles of an administrator. type AdminUpdateReq struct { - g.Meta `path:"/admins/{id}" method:"put" tags:"Admin/Admin" summary:"Update admin"` + g.Meta `path:"/admin/{id}" method:"put" tags:"Admin/Admin" summary:"Update admin"` Id uint64 `json:"id" in:"path" v:"required"` Nickname string `json:"nickname"` Status int `json:"status"` @@ -55,7 +56,7 @@ type AdminUpdateRes struct{} // AdminResetPwdReq resets an administrator's password. type AdminResetPwdReq struct { - g.Meta `path:"/admins/{id}/password" method:"put" tags:"Admin/Admin" summary:"Reset admin password"` + g.Meta `path:"/admin/{id}/password" method:"put" tags:"Admin/Admin" summary:"Reset admin password"` Id uint64 `json:"id" in:"path" v:"required"` Password string `json:"password" v:"required|min-length:6#password required|password too short"` } @@ -65,7 +66,7 @@ type AdminResetPwdRes struct{} // AdminDeleteReq deletes an administrator (soft delete). type AdminDeleteReq struct { - g.Meta `path:"/admins/{id}" method:"delete" tags:"Admin/Admin" summary:"Delete admin"` + g.Meta `path:"/admin/{id}" method:"delete" tags:"Admin/Admin" summary:"Delete admin"` Id uint64 `json:"id" in:"path" v:"required"` } diff --git a/api/admin/v1/log.go b/api/admin/v1/base/log.go similarity index 75% rename from api/admin/v1/log.go rename to api/admin/v1/base/log.go index 949ca65..f7e5bff 100644 --- a/api/admin/v1/log.go +++ b/api/admin/v1/base/log.go @@ -1,4 +1,5 @@ -package v1 +// Package base 定义基础常规接口(服务器日志监控等),路由前缀 /admin/v1/base。 +package base import "github.com/gogf/gf/v2/frame/g" @@ -12,7 +13,7 @@ type LogFile struct { // LogFilesReq lists the server log files. type LogFilesReq struct { - g.Meta `path:"/log/files" method:"get" tags:"Admin/Log" summary:"List server log files"` + g.Meta `path:"/base/log/files" method:"get" tags:"Admin/Base/Log" summary:"List server log files"` } // LogFilesRes is the response of LogFilesReq. @@ -23,7 +24,7 @@ type LogFilesRes struct { // LogTailReq reads the tail of a log file with optional keyword filter. type LogTailReq struct { - g.Meta `path:"/log/tail" method:"get" tags:"Admin/Log" summary:"Tail a server log file"` + g.Meta `path:"/base/log/tail" method:"get" tags:"Admin/Base/Log" summary:"Tail a server log file"` File string `json:"file" v:"required#file required"` // relative to the log dir; path traversal is rejected Lines int `json:"lines" d:"200" v:"min:1|max:2000"` Keyword string `json:"keyword"` // substring filter diff --git a/api/admin/v1/auth.go b/api/admin/v1/system/auth.go similarity index 68% rename from api/admin/v1/auth.go rename to api/admin/v1/system/auth.go index bfa4e62..b3fdaa1 100644 --- a/api/admin/v1/auth.go +++ b/api/admin/v1/system/auth.go @@ -1,9 +1,10 @@ -package v1 +// Package system 定义系统管理接口(认证、菜单、角色),路由前缀 /admin/v1/system。 +package system import "github.com/gogf/gf/v2/frame/g" type LoginReq struct { - g.Meta `path:"/auth/login" method:"post" tags:"Admin/Auth" summary:"Administrator login"` + g.Meta `path:"/system/auth/login" method:"post" tags:"Admin/System/Auth" summary:"Administrator login"` Username string `json:"username" v:"required"` Password string `json:"password" v:"required"` } @@ -16,7 +17,7 @@ type LoginRes struct { // InfoReq returns the current administrator profile (vben getUserInfo). type InfoReq struct { - g.Meta `path:"/auth/info" method:"get" tags:"Admin/Auth" summary:"Current admin info"` + g.Meta `path:"/system/auth/info" method:"get" tags:"Admin/System/Auth" summary:"Current admin info"` } // InfoRes is the response of InfoReq. Roles carries role codes for vben authority. @@ -30,7 +31,7 @@ type InfoRes struct { // CodesReq returns the button-level permission codes of the current admin // (vben getAccessCodes, backend access mode). type CodesReq struct { - g.Meta `path:"/auth/codes" method:"get" tags:"Admin/Auth" summary:"Current admin access codes"` + g.Meta `path:"/system/auth/codes" method:"get" tags:"Admin/System/Auth" summary:"Current admin access codes"` } // CodesRes is the response of CodesReq. diff --git a/api/admin/v1/menu.go b/api/admin/v1/system/menu.go similarity index 88% rename from api/admin/v1/menu.go rename to api/admin/v1/system/menu.go index 81a8bf3..33acdc6 100644 --- a/api/admin/v1/menu.go +++ b/api/admin/v1/system/menu.go @@ -1,4 +1,4 @@ -package v1 +package system import "github.com/gogf/gf/v2/frame/g" @@ -24,7 +24,7 @@ type RouteItem struct { // MenuRoutesReq returns the current admin's menu tree as vben routes // (backend access mode, login-only endpoint). type MenuRoutesReq struct { - g.Meta `path:"/menu/routes" method:"get" tags:"Admin/Menu" summary:"Current admin menu routes"` + g.Meta `path:"/system/menu/routes" method:"get" tags:"Admin/System/Menu" summary:"Current admin menu routes"` } // MenuRoutesRes is the response of MenuRoutesReq. diff --git a/api/admin/v1/menu_manage.go b/api/admin/v1/system/menu_manage.go similarity index 84% rename from api/admin/v1/menu_manage.go rename to api/admin/v1/system/menu_manage.go index 6d1fca1..3f62650 100644 --- a/api/admin/v1/menu_manage.go +++ b/api/admin/v1/system/menu_manage.go @@ -1,4 +1,4 @@ -package v1 +package system import "github.com/gogf/gf/v2/frame/g" @@ -20,7 +20,7 @@ type MenuItem struct { // MenuTreeReq returns the full menu tree for management. type MenuTreeReq struct { - g.Meta `path:"/menus/tree" method:"get" tags:"Admin/Menu" summary:"Full menu tree"` + g.Meta `path:"/system/menu/tree" method:"get" tags:"Admin/System/Menu" summary:"Full menu tree"` } // MenuTreeRes is the response of MenuTreeReq. @@ -30,7 +30,7 @@ type MenuTreeRes struct { // MenuCreateReq creates a menu or button node. type MenuCreateReq struct { - g.Meta `path:"/menus" method:"post" tags:"Admin/Menu" summary:"Create menu"` + g.Meta `path:"/system/menu" method:"post" tags:"Admin/System/Menu" summary:"Create menu"` ParentId uint64 `json:"parentId"` Name string `json:"name" v:"required"` Icon string `json:"icon"` @@ -50,7 +50,7 @@ type MenuCreateRes struct { // MenuUpdateReq updates a menu or button node. type MenuUpdateReq struct { - g.Meta `path:"/menus/{id}" method:"put" tags:"Admin/Menu" summary:"Update menu"` + g.Meta `path:"/system/menu/{id}" method:"put" tags:"Admin/System/Menu" summary:"Update menu"` Id uint64 `json:"id" in:"path" v:"required"` ParentId uint64 `json:"parentId"` Name string `json:"name" v:"required"` @@ -69,7 +69,7 @@ type MenuUpdateRes struct{} // MenuDeleteReq deletes a menu node (soft delete). type MenuDeleteReq struct { - g.Meta `path:"/menus/{id}" method:"delete" tags:"Admin/Menu" summary:"Delete menu"` + g.Meta `path:"/system/menu/{id}" method:"delete" tags:"Admin/System/Menu" summary:"Delete menu"` Id uint64 `json:"id" in:"path" v:"required"` } diff --git a/api/admin/v1/role.go b/api/admin/v1/system/role.go similarity index 80% rename from api/admin/v1/role.go rename to api/admin/v1/system/role.go index 163021b..44d4092 100644 --- a/api/admin/v1/role.go +++ b/api/admin/v1/system/role.go @@ -1,4 +1,4 @@ -package v1 +package system import "github.com/gogf/gf/v2/frame/g" @@ -14,7 +14,7 @@ type RoleItem struct { // RoleListReq pages the roles. type RoleListReq struct { - g.Meta `path:"/roles" method:"get" tags:"Admin/Role" summary:"Role list"` + g.Meta `path:"/system/role" method:"get" tags:"Admin/System/Role" summary:"Role list"` Page int `json:"page" d:"1" v:"min:1"` Size int `json:"size" d:"10" v:"min:1|max:100"` Keyword string `json:"keyword"` // matches code or name @@ -28,7 +28,7 @@ type RoleListRes struct { // RoleCreateReq creates a role and binds menu ids. type RoleCreateReq struct { - g.Meta `path:"/roles" method:"post" tags:"Admin/Role" summary:"Create role"` + g.Meta `path:"/system/role" method:"post" tags:"Admin/System/Role" summary:"Create role"` Code string `json:"code" v:"required"` Name string `json:"name" v:"required"` Status int `json:"status"` @@ -42,7 +42,7 @@ type RoleCreateRes struct { // RoleUpdateReq updates a role and rebinds menu ids. type RoleUpdateReq struct { - g.Meta `path:"/roles/{id}" method:"put" tags:"Admin/Role" summary:"Update role"` + g.Meta `path:"/system/role/{id}" method:"put" tags:"Admin/System/Role" summary:"Update role"` Id uint64 `json:"id" in:"path" v:"required"` Name string `json:"name" v:"required"` Status int `json:"status"` @@ -54,7 +54,7 @@ type RoleUpdateRes struct{} // RoleDeleteReq deletes a role (soft delete). type RoleDeleteReq struct { - g.Meta `path:"/roles/{id}" method:"delete" tags:"Admin/Role" summary:"Delete role"` + g.Meta `path:"/system/role/{id}" method:"delete" tags:"Admin/System/Role" summary:"Delete role"` Id uint64 `json:"id" in:"path" v:"required"` } diff --git a/docs/change-log/2026-08-24.md b/docs/change-log/2026-08-24.md index 8572112..855b40e 100644 --- a/docs/change-log/2026-08-24.md +++ b/docs/change-log/2026-08-24.md @@ -219,3 +219,29 @@ - **node 环境**:system node 23.0.0 未编译 `node:sqlite`(`ERR_UNKNOWN_BUILTIN_MODULE`),pnpm 11.16.0 依赖它;已用 managed node 22.22.2 + corepack wrapper 绕过。 - **`pnpm install` 卡住**:已配 `.npmrc`(npmmirror 镜像 + `node-linker=hoisted` + `store-dir=E:/.pnpm-store`),但 install 在 "added 27→98" 反复循环,疑似某 native 依赖 postinstall 失败重试。**dev server 未能启动验证**。 - 后续步骤:① 定位卡住的依赖(`pnpm install --reporter=append-only` 看具体包);② 或跳过 postinstall(`pnpm install --ignore-scripts` 后手动补 esbuild 等二进制);③ 完成 install 后 `pnpm dev:antd` 启动,浏览器验证登录/菜单/权限/日志。 + +--- + +## 追加(23:40):API 层 base/system/admin 分组重构 + +### 请求 + +优化 API 层设计:`/api/admin/v1/base`(基础常规)、`/api/admin/v1/system`(menu/role/auth 系统管理)、`/api/admin/v1/admin`(后台管理),并同步 service/controller 层。 + +### 变更 + +- `api/admin/v1/` 重组为三子包:`base/log.go`(日志)、`system/{auth,menu,menu_manage,role}.go`(认证+菜单+角色)、`admin/admin.go`(管理员) +- 路由前缀变更:`/auth/*`→`/system/auth/*`;`/menu/routes`→`/system/menu/routes`;`/menus`→`/system/menu`;`/roles`→`/system/role`;`/admins`→`/admin`;`/log/*`→`/base/log/*` +- `internal/controller/admin/*.go` 改 import 对应子包(basev1/systemv1/adminv1) +- `manifest/sql/006_menu_paths_v2.sql`:按钮-接口 path 映射更新;`system:role:assignMenu` 无独立接口,path 清空 +- 冒烟测试全通过:新路径 8 接口正常,旧路径 `/admins` 返回 Not Found + +### 决策与理由 + +- **权限码与路由分离**:permission 保持 `system:admin:list` 等逻辑标识不变,只改物理路由 path。管理员管理路由在 `/admin/v1/admin` 但权限码仍 `system:admin:*`(逻辑归属系统权限体系)。 +- **service/dao 层不硬拆子包**:service 保持 `internal/service` 单包按领域接口组织(GoFrame 惯例),dao 为生成代码按表组织;api/controller 体现 base/system/admin 分组即可。 +- `assignMenu` 权限码保留(前端按钮),但无独立后端接口(授权合并进 role update),path 置空不映射。 + +### 待办与风险 + +- 前端 API 路径需同步为 `/admin/v1/{base,system,admin}` 前缀(当前前端代码仍是旧路径)。 diff --git a/internal/controller/admin/admin.go b/internal/controller/admin/admin.go index fc2ae78..52c889a 100644 --- a/internal/controller/admin/admin.go +++ b/internal/controller/admin/admin.go @@ -3,7 +3,7 @@ package admin import ( "context" - adminv1 "service.xpcool.com/api/admin/v1" + adminv1 "service.xpcool.com/api/admin/v1/admin" "service.xpcool.com/internal/model/dto" "service.xpcool.com/internal/service" ) diff --git a/internal/controller/admin/auth.go b/internal/controller/admin/auth.go index c96eeef..7861a49 100644 --- a/internal/controller/admin/auth.go +++ b/internal/controller/admin/auth.go @@ -3,7 +3,7 @@ package admin import ( "context" - adminv1 "service.xpcool.com/api/admin/v1" + systemv1 "service.xpcool.com/api/admin/v1/system" "service.xpcool.com/internal/model/dto" "service.xpcool.com/internal/service" ) @@ -15,10 +15,10 @@ type AuthController struct{} func NewAuth() *AuthController { return &AuthController{} } // Login authenticates an administrator and issues a token pair. -func (c *AuthController) Login(ctx context.Context, req *adminv1.LoginReq) (res *adminv1.LoginRes, err error) { +func (c *AuthController) Login(ctx context.Context, req *systemv1.LoginReq) (res *systemv1.LoginRes, err error) { p, id, err := service.AdminAuth().Login(ctx, dto.AdminLoginInput{Username: req.Username, Password: req.Password}) if err != nil { return nil, err } - return &adminv1.LoginRes{AccessToken: p.AccessToken, RefreshToken: p.RefreshToken, ExpiresIn: p.ExpiresIn, AdminID: id}, nil + return &systemv1.LoginRes{AccessToken: p.AccessToken, RefreshToken: p.RefreshToken, ExpiresIn: p.ExpiresIn, AdminID: id}, nil } diff --git a/internal/controller/admin/log.go b/internal/controller/admin/log.go index 639f0d7..4330581 100644 --- a/internal/controller/admin/log.go +++ b/internal/controller/admin/log.go @@ -3,28 +3,28 @@ package admin import ( "context" - adminv1 "service.xpcool.com/api/admin/v1" + basev1 "service.xpcool.com/api/admin/v1/base" "service.xpcool.com/internal/service" ) // LogFiles lists the server log files. -func (c *Controller) LogFiles(ctx context.Context, req *adminv1.LogFilesReq) (res *adminv1.LogFilesRes, err error) { +func (c *Controller) LogFiles(ctx context.Context, req *basev1.LogFilesReq) (res *basev1.LogFilesRes, err error) { dir, files, err := service.LogManage().Files(ctx) if err != nil { return nil, err } - list := make([]*adminv1.LogFile, 0, len(files)) + list := make([]*basev1.LogFile, 0, len(files)) for _, f := range files { - list = append(list, &adminv1.LogFile{Name: f.Name, Path: f.Path, Size: f.Size, ModTime: f.ModTime}) + list = append(list, &basev1.LogFile{Name: f.Name, Path: f.Path, Size: f.Size, ModTime: f.ModTime}) } - return &adminv1.LogFilesRes{Dir: dir, Files: list}, nil + return &basev1.LogFilesRes{Dir: dir, Files: list}, nil } // LogTail reads the tail of a log file with optional keyword filter. -func (c *Controller) LogTail(ctx context.Context, req *adminv1.LogTailReq) (res *adminv1.LogTailRes, err error) { +func (c *Controller) LogTail(ctx context.Context, req *basev1.LogTailReq) (res *basev1.LogTailRes, err error) { lines, err := service.LogManage().Tail(ctx, req.File, req.Lines, req.Keyword) if err != nil { return nil, err } - return &adminv1.LogTailRes{Lines: lines}, nil + return &basev1.LogTailRes{Lines: lines}, nil } diff --git a/internal/controller/admin/menu.go b/internal/controller/admin/menu.go index f8dc5cf..6e5135c 100644 --- a/internal/controller/admin/menu.go +++ b/internal/controller/admin/menu.go @@ -3,26 +3,26 @@ package admin import ( "context" - adminv1 "service.xpcool.com/api/admin/v1" + systemv1 "service.xpcool.com/api/admin/v1/system" "service.xpcool.com/internal/model/dto" "service.xpcool.com/internal/service" ) // MenuTree returns the full menu tree (menus + button permissions). -func (c *Controller) MenuTree(ctx context.Context, req *adminv1.MenuTreeReq) (res *adminv1.MenuTreeRes, err error) { +func (c *Controller) MenuTree(ctx context.Context, req *systemv1.MenuTreeReq) (res *systemv1.MenuTreeRes, err error) { tree, err := service.MenuManage().Tree(ctx) if err != nil { return nil, err } - out := make([]*adminv1.MenuItem, 0, len(tree)) + out := make([]*systemv1.MenuItem, 0, len(tree)) for _, n := range tree { out = append(out, menuNodeToV1(n)) } - return &adminv1.MenuTreeRes{Tree: out}, nil + return &systemv1.MenuTreeRes{Tree: out}, nil } // MenuCreate creates a menu or button node. -func (c *Controller) MenuCreate(ctx context.Context, req *adminv1.MenuCreateReq) (res *adminv1.MenuCreateRes, err error) { +func (c *Controller) MenuCreate(ctx context.Context, req *systemv1.MenuCreateReq) (res *systemv1.MenuCreateRes, err error) { id, err := service.MenuManage().Create(ctx, dto.MenuCreateInput{ ParentId: req.ParentId, Name: req.Name, Icon: req.Icon, Type: req.Type, Path: req.Path, Component: req.Component, Permission: req.Permission, Sort: req.Sort, Status: req.Status, Hidden: req.Hidden, @@ -30,30 +30,30 @@ func (c *Controller) MenuCreate(ctx context.Context, req *adminv1.MenuCreateReq) if err != nil { return nil, err } - return &adminv1.MenuCreateRes{Id: id}, nil + return &systemv1.MenuCreateRes{Id: id}, nil } // MenuUpdate updates a menu or button node. -func (c *Controller) MenuUpdate(ctx context.Context, req *adminv1.MenuUpdateReq) (res *adminv1.MenuUpdateRes, err error) { +func (c *Controller) MenuUpdate(ctx context.Context, req *systemv1.MenuUpdateReq) (res *systemv1.MenuUpdateRes, err error) { if err = service.MenuManage().Update(ctx, dto.MenuUpdateInput{ Id: req.Id, ParentId: req.ParentId, Name: req.Name, Icon: req.Icon, Type: req.Type, Path: req.Path, Component: req.Component, Permission: req.Permission, Sort: req.Sort, Status: req.Status, Hidden: req.Hidden, }); err != nil { return nil, err } - return &adminv1.MenuUpdateRes{}, nil + return &systemv1.MenuUpdateRes{}, nil } // MenuDelete deletes a menu node. -func (c *Controller) MenuDelete(ctx context.Context, req *adminv1.MenuDeleteReq) (res *adminv1.MenuDeleteRes, err error) { +func (c *Controller) MenuDelete(ctx context.Context, req *systemv1.MenuDeleteReq) (res *systemv1.MenuDeleteRes, err error) { if err = service.MenuManage().Delete(ctx, req.Id); err != nil { return nil, err } - return &adminv1.MenuDeleteRes{}, nil + return &systemv1.MenuDeleteRes{}, nil } -func menuNodeToV1(n *dto.MenuNode) *adminv1.MenuItem { - item := &adminv1.MenuItem{ +func menuNodeToV1(n *dto.MenuNode) *systemv1.MenuItem { + item := &systemv1.MenuItem{ Id: n.Id, ParentId: n.ParentId, Name: n.Name, Icon: n.Icon, Type: n.Type, Path: n.Path, Component: n.Component, Permission: n.Permission, Sort: n.Sort, Status: n.Status, Hidden: n.Hidden, } diff --git a/internal/controller/admin/profile.go b/internal/controller/admin/profile.go index ade3edf..9128d0f 100644 --- a/internal/controller/admin/profile.go +++ b/internal/controller/admin/profile.go @@ -3,7 +3,7 @@ package admin import ( "context" - adminv1 "service.xpcool.com/api/admin/v1" + systemv1 "service.xpcool.com/api/admin/v1/system" "service.xpcool.com/internal/model/dto" "service.xpcool.com/internal/service" ) @@ -16,43 +16,43 @@ type ProfileController struct{} func NewProfile() *ProfileController { return &ProfileController{} } // Info returns the current administrator profile. -func (c *ProfileController) Info(ctx context.Context, req *adminv1.InfoReq) (res *adminv1.InfoRes, err error) { +func (c *ProfileController) Info(ctx context.Context, req *systemv1.InfoReq) (res *systemv1.InfoRes, err error) { info, err := service.AdminAuth().Info(ctx, adminID(ctx)) if err != nil { return nil, err } - return &adminv1.InfoRes{AdminID: info.AdminID, Username: info.Username, Nickname: info.Nickname, Roles: info.Roles}, nil + return &systemv1.InfoRes{AdminID: info.AdminID, Username: info.Username, Nickname: info.Nickname, Roles: info.Roles}, nil } // Codes returns the button-level permission codes of the current administrator. -func (c *ProfileController) Codes(ctx context.Context, req *adminv1.CodesReq) (res *adminv1.CodesRes, err error) { +func (c *ProfileController) Codes(ctx context.Context, req *systemv1.CodesReq) (res *systemv1.CodesRes, err error) { codes, err := service.AdminAuth().Codes(ctx, adminID(ctx)) if err != nil { return nil, err } - return &adminv1.CodesRes{Codes: codes}, nil + return &systemv1.CodesRes{Codes: codes}, nil } // Routes returns the current administrator's visible menu tree as vben routes. -func (c *ProfileController) Routes(ctx context.Context, req *adminv1.MenuRoutesReq) (res *adminv1.MenuRoutesRes, err error) { +func (c *ProfileController) Routes(ctx context.Context, req *systemv1.MenuRoutesReq) (res *systemv1.MenuRoutesRes, err error) { routes, err := service.AdminMenu().Routes(ctx, adminID(ctx)) if err != nil { return nil, err } - out := make([]*adminv1.RouteItem, 0, len(routes)) + out := make([]*systemv1.RouteItem, 0, len(routes)) for _, r := range routes { out = append(out, toV1Route(r)) } - return &adminv1.MenuRoutesRes{Routes: out}, nil + return &systemv1.MenuRoutesRes{Routes: out}, nil } // toV1Route converts a dto route tree into the v1 API shape. -func toV1Route(r *dto.RouteItem) *adminv1.RouteItem { - item := &adminv1.RouteItem{ +func toV1Route(r *dto.RouteItem) *systemv1.RouteItem { + item := &systemv1.RouteItem{ Name: r.Name, Path: r.Path, Component: r.Component, - Meta: adminv1.RouteMeta{ + Meta: systemv1.RouteMeta{ Title: r.Meta.Title, Icon: r.Meta.Icon, Order: r.Meta.Order, diff --git a/internal/controller/admin/role.go b/internal/controller/admin/role.go index 7eab956..56aed02 100644 --- a/internal/controller/admin/role.go +++ b/internal/controller/admin/role.go @@ -3,47 +3,47 @@ package admin import ( "context" - adminv1 "service.xpcool.com/api/admin/v1" + systemv1 "service.xpcool.com/api/admin/v1/system" "service.xpcool.com/internal/model/dto" "service.xpcool.com/internal/service" ) // RoleList pages the roles. -func (c *Controller) RoleList(ctx context.Context, req *adminv1.RoleListReq) (res *adminv1.RoleListRes, err error) { +func (c *Controller) RoleList(ctx context.Context, req *systemv1.RoleListReq) (res *systemv1.RoleListRes, err error) { items, total, err := service.RoleManage().List(ctx, dto.PageQuery{Page: req.Page, Size: req.Size, Keyword: req.Keyword}) if err != nil { return nil, err } - list := make([]*adminv1.RoleItem, 0, len(items)) + list := make([]*systemv1.RoleItem, 0, len(items)) for _, it := range items { - list = append(list, &adminv1.RoleItem{ + list = append(list, &systemv1.RoleItem{ Id: it.Id, Code: it.Code, Name: it.Name, Status: it.Status, MenuIds: it.MenuIds, CreatedAt: it.CreatedAt, }) } - return &adminv1.RoleListRes{List: list, Total: total}, nil + return &systemv1.RoleListRes{List: list, Total: total}, nil } // RoleCreate creates a role. -func (c *Controller) RoleCreate(ctx context.Context, req *adminv1.RoleCreateReq) (res *adminv1.RoleCreateRes, err error) { +func (c *Controller) RoleCreate(ctx context.Context, req *systemv1.RoleCreateReq) (res *systemv1.RoleCreateRes, err error) { id, err := service.RoleManage().Create(ctx, dto.RoleCreateInput{Code: req.Code, Name: req.Name, Status: req.Status, MenuIds: req.MenuIds}) if err != nil { return nil, err } - return &adminv1.RoleCreateRes{Id: id}, nil + return &systemv1.RoleCreateRes{Id: id}, nil } // RoleUpdate updates a role. -func (c *Controller) RoleUpdate(ctx context.Context, req *adminv1.RoleUpdateReq) (res *adminv1.RoleUpdateRes, err error) { +func (c *Controller) RoleUpdate(ctx context.Context, req *systemv1.RoleUpdateReq) (res *systemv1.RoleUpdateRes, err error) { if err = service.RoleManage().Update(ctx, dto.RoleUpdateInput{Id: req.Id, Name: req.Name, Status: req.Status, MenuIds: req.MenuIds}); err != nil { return nil, err } - return &adminv1.RoleUpdateRes{}, nil + return &systemv1.RoleUpdateRes{}, nil } // RoleDelete deletes a role. -func (c *Controller) RoleDelete(ctx context.Context, req *adminv1.RoleDeleteReq) (res *adminv1.RoleDeleteRes, err error) { +func (c *Controller) RoleDelete(ctx context.Context, req *systemv1.RoleDeleteReq) (res *systemv1.RoleDeleteRes, err error) { if err = service.RoleManage().Delete(ctx, req.Id); err != nil { return nil, err } - return &adminv1.RoleDeleteRes{}, nil + return &systemv1.RoleDeleteRes{}, nil } diff --git a/manifest/sql/006_menu_paths_v2.sql b/manifest/sql/006_menu_paths_v2.sql new file mode 100644 index 0000000..737f4d5 --- /dev/null +++ b/manifest/sql/006_menu_paths_v2.sql @@ -0,0 +1,19 @@ +-- 006_menu_paths_v2.sql +-- Re-map protected endpoints after API regrouping into base/system/admin. +-- permission codes stay unchanged; only the physical route paths change. +UPDATE admin_menu SET path='GET /admin/v1/admin' WHERE id=21; -- system:admin:list +UPDATE admin_menu SET path='POST /admin/v1/admin' WHERE id=22; -- system:admin:create +UPDATE admin_menu SET path='PUT /admin/v1/admin/{id}' WHERE id=23; -- system:admin:update +UPDATE admin_menu SET path='DELETE /admin/v1/admin/{id}' WHERE id=24; -- system:admin:delete +UPDATE admin_menu SET path='PUT /admin/v1/admin/{id}/password' WHERE id=25; -- system:admin:resetPwd +UPDATE admin_menu SET path='GET /admin/v1/system/role' WHERE id=31; -- system:role:list +UPDATE admin_menu SET path='POST /admin/v1/system/role' WHERE id=32; -- system:role:create +UPDATE admin_menu SET path='PUT /admin/v1/system/role/{id}' WHERE id=33; -- system:role:update +UPDATE admin_menu SET path='DELETE /admin/v1/system/role/{id}' WHERE id=34; -- system:role:delete +UPDATE admin_menu SET path='' WHERE id=35; -- system:role:assignMenu (no dedicated endpoint) +UPDATE admin_menu SET path='GET /admin/v1/system/menu/tree' WHERE id=41; -- system:menu:list +UPDATE admin_menu SET path='POST /admin/v1/system/menu' WHERE id=42; -- system:menu:create +UPDATE admin_menu SET path='PUT /admin/v1/system/menu/{id}' WHERE id=43; -- system:menu:update +UPDATE admin_menu SET path='DELETE /admin/v1/system/menu/{id}' WHERE id=44; -- system:menu:delete +UPDATE admin_menu SET path='GET /admin/v1/base/log/files' WHERE id=61; -- monitor:log:view +UPDATE admin_menu SET path='GET /admin/v1/base/log/tail' WHERE id=62; -- monitor:log:tail