refactor(api): 后台管理 API 按 base/system/admin 分组重构
- 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 接口全通过
This commit is contained in:
parent
1d632065fe
commit
43fa79686d
19
AGENTS.md
19
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/<xxx>.go` 写 Req/Res;② `internal/controller/admin/<xxx>.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/{分组}/<xxx>.go` 写 Req/Res;② `internal/controller/admin/<xxx>.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,前端调用)与命名规则
|
||||
|
||||
|
||||
@ -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"`
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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"`
|
||||
}
|
||||
|
||||
@ -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"`
|
||||
}
|
||||
|
||||
@ -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}` 前缀(当前前端代码仍是旧路径)。
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
19
manifest/sql/006_menu_paths_v2.sql
Normal file
19
manifest/sql/006_menu_paths_v2.sql
Normal file
@ -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
|
||||
Loading…
Reference in New Issue
Block a user