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:
夏犀麟 2026-08-24 23:40:39 +08:00
parent 1d632065fe
commit 43fa79686d
15 changed files with 129 additions and 78 deletions

View File

@ -44,17 +44,20 @@ service.xpcool.com/
utility/ # 预留跨切面辅助
```
## 后台管理 APIapi/admin/v1
## 后台管理 APIapi/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/admin123super_admin22 条菜单`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前端调用与命名规则

View File

@ -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"`
}

View File

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

View File

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

View File

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

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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:40API 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 updatepath 置空不映射
### 待办与风险
- 前端 API 路径需同步为 `/admin/v1/{base,system,admin}` 前缀当前前端代码仍是旧路径

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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