CHG: 移除管理员管理菜单、新增日志管理菜单(008_menu_rbac_v4.sql)
This commit is contained in:
parent
2ed31f012e
commit
78756a849f
74
api/admin/admin/admin/admin.go
Normal file
74
api/admin/admin/admin/admin.go
Normal file
@ -0,0 +1,74 @@
|
||||
// Package admin_admin_admin 定义后台管理接口(管理员账号管理),路由前缀 /admin。
|
||||
package admin_admin_admin
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// AdminItem 管理员列表中的一行。
|
||||
type AdminItem struct {
|
||||
Id uint64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Status int `json:"status"`
|
||||
RoleIds []uint64 `json:"roleIds"`
|
||||
RoleNames []string `json:"roleNames"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
// AdminListReq 分页查询管理员。
|
||||
type AdminListReq struct {
|
||||
g.Meta `path:"/admin/list" method:"post" tags:"Admin/Admin" summary:"管理员列表"`
|
||||
Page int `json:"page" d:"1" v:"min:1"`
|
||||
Size int `json:"size" d:"10" v:"min:1|max:100"`
|
||||
Keyword string `json:"keyword"` // 匹配用户名或昵称
|
||||
}
|
||||
|
||||
// AdminListRes 是 AdminListReq 的响应。
|
||||
type AdminListRes struct {
|
||||
List []*AdminItem `json:"list"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// AdminCreateReq 创建管理员。
|
||||
type AdminCreateReq struct {
|
||||
g.Meta `path:"/admin/create" method:"post" tags:"Admin/Admin" summary:"创建管理员"`
|
||||
Username string `json:"username" v:"required"`
|
||||
Password string `json:"password" v:"required|min-length:6#password required|password too short"`
|
||||
Nickname string `json:"nickname"`
|
||||
RoleIds []uint64 `json:"roleIds"`
|
||||
}
|
||||
|
||||
// AdminCreateRes 是 AdminCreateReq 的响应。
|
||||
type AdminCreateRes struct {
|
||||
Id uint64 `json:"id"`
|
||||
}
|
||||
|
||||
// AdminUpdateReq 更新管理员的资料、状态与角色。
|
||||
type AdminUpdateReq struct {
|
||||
g.Meta `path:"/admin/update/{id}" method:"post" tags:"Admin/Admin" summary:"更新管理员"`
|
||||
Id uint64 `json:"id" in:"path" v:"required"`
|
||||
Nickname string `json:"nickname"`
|
||||
Status int `json:"status"`
|
||||
RoleIds []uint64 `json:"roleIds"`
|
||||
}
|
||||
|
||||
// AdminUpdateRes 是 AdminUpdateReq 的响应。
|
||||
type AdminUpdateRes struct{}
|
||||
|
||||
// AdminResetPwdReq 重置管理员密码。
|
||||
type AdminResetPwdReq struct {
|
||||
g.Meta `path:"/admin/resetPwd/{id}" method:"post" tags:"Admin/Admin" summary:"重置管理员密码"`
|
||||
Id uint64 `json:"id" in:"path" v:"required"`
|
||||
Password string `json:"password" v:"required|min-length:6#password required|password too short"`
|
||||
}
|
||||
|
||||
// AdminResetPwdRes 是 AdminResetPwdReq 的响应。
|
||||
type AdminResetPwdRes struct{}
|
||||
|
||||
// AdminDeleteReq 删除管理员(软删除)。
|
||||
type AdminDeleteReq struct {
|
||||
g.Meta `path:"/admin/delete/{id}" method:"post" tags:"Admin/Admin" summary:"删除管理员"`
|
||||
Id uint64 `json:"id" in:"path" v:"required"`
|
||||
}
|
||||
|
||||
// AdminDeleteRes 是 AdminDeleteReq 的响应。
|
||||
type AdminDeleteRes struct{}
|
||||
33
api/admin/system/menu/menu.go
Normal file
33
api/admin/system/menu/menu.go
Normal file
@ -0,0 +1,33 @@
|
||||
// Package admin_system_menu 定义菜单路由接口(当前管理员可见菜单树),路由前缀 /admin。
|
||||
package admin_system_menu
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// RouteMeta 与 vben 后台动态路由元数据对应。
|
||||
type RouteMeta struct {
|
||||
Title string `json:"title"`
|
||||
Icon string `json:"icon"`
|
||||
Order int `json:"order"`
|
||||
Authority []string `json:"authority,omitempty"` // 角色码
|
||||
HideInMenu bool `json:"hideInMenu,omitempty"`
|
||||
KeepAlive bool `json:"keepAlive,omitempty"`
|
||||
}
|
||||
|
||||
// RouteItem vben 路由树中的一个节点。
|
||||
type RouteItem struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Component string `json:"component,omitempty"`
|
||||
Meta RouteMeta `json:"meta"`
|
||||
Children []*RouteItem `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
// MenuRoutesReq 获取当前管理员的菜单树(后端鉴权模式,登录后即可访问)。
|
||||
type MenuRoutesReq struct {
|
||||
g.Meta `path:"/system/menu/routes" method:"post" tags:"Admin/System/Menu" summary:"当前管理员菜单路由"`
|
||||
}
|
||||
|
||||
// MenuRoutesRes 是 MenuRoutesReq 的响应。
|
||||
type MenuRoutesRes struct {
|
||||
Routes []*RouteItem `json:"routes"`
|
||||
}
|
||||
78
api/admin/system/menu_manage/menu_manage.go
Normal file
78
api/admin/system/menu_manage/menu_manage.go
Normal file
@ -0,0 +1,78 @@
|
||||
// Package admin_system_menu_manage 定义菜单管理接口(菜单 + 按钮权限树),路由前缀 /admin。
|
||||
package admin_system_menu_manage
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// MenuItem 完整菜单树中的一个节点(type 1 菜单 / 2 按钮)。
|
||||
type MenuItem struct {
|
||||
Id uint64 `json:"id"`
|
||||
ParentId uint64 `json:"parentId"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
Type int `json:"type"` // 1 菜单, 2 按钮/接口
|
||||
Path string `json:"path"`
|
||||
Component string `json:"component"`
|
||||
Permission string `json:"permission"`
|
||||
Sort int `json:"sort"`
|
||||
Status int `json:"status"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Children []*MenuItem `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
// MenuTreeReq 获取完整菜单树(供管理)。
|
||||
type MenuTreeReq struct {
|
||||
g.Meta `path:"/system/menu/tree" method:"post" tags:"Admin/System/Menu" summary:"完整菜单树"`
|
||||
}
|
||||
|
||||
// MenuTreeRes 是 MenuTreeReq 的响应。
|
||||
type MenuTreeRes struct {
|
||||
Tree []*MenuItem `json:"tree"`
|
||||
}
|
||||
|
||||
// MenuCreateReq 创建菜单或按钮节点。
|
||||
type MenuCreateReq struct {
|
||||
g.Meta `path:"/system/menu/create" method:"post" tags:"Admin/System/Menu" summary:"创建菜单"`
|
||||
ParentId uint64 `json:"parentId"`
|
||||
Name string `json:"name" v:"required"`
|
||||
Icon string `json:"icon"`
|
||||
Type int `json:"type" v:"in:1,2#type must be 1 or 2"`
|
||||
Path string `json:"path"`
|
||||
Component string `json:"component"`
|
||||
Permission string `json:"permission" v:"required"`
|
||||
Sort int `json:"sort"`
|
||||
Status int `json:"status"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
||||
// MenuCreateRes 是 MenuCreateReq 的响应。
|
||||
type MenuCreateRes struct {
|
||||
Id uint64 `json:"id"`
|
||||
}
|
||||
|
||||
// MenuUpdateReq 更新菜单或按钮节点。
|
||||
type MenuUpdateReq struct {
|
||||
g.Meta `path:"/system/menu/update/{id}" method:"post" tags:"Admin/System/Menu" summary:"更新菜单"`
|
||||
Id uint64 `json:"id" in:"path" v:"required"`
|
||||
ParentId uint64 `json:"parentId"`
|
||||
Name string `json:"name" v:"required"`
|
||||
Icon string `json:"icon"`
|
||||
Type int `json:"type" v:"in:1,2#type must be 1 or 2"`
|
||||
Path string `json:"path"`
|
||||
Component string `json:"component"`
|
||||
Permission string `json:"permission" v:"required"`
|
||||
Sort int `json:"sort"`
|
||||
Status int `json:"status"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
||||
// MenuUpdateRes 是 MenuUpdateReq 的响应。
|
||||
type MenuUpdateRes struct{}
|
||||
|
||||
// MenuDeleteReq 删除菜单节点(软删除)。
|
||||
type MenuDeleteReq struct {
|
||||
g.Meta `path:"/system/menu/delete/{id}" method:"post" tags:"Admin/System/Menu" summary:"删除菜单"`
|
||||
Id uint64 `json:"id" in:"path" v:"required"`
|
||||
}
|
||||
|
||||
// MenuDeleteRes 是 MenuDeleteReq 的响应。
|
||||
type MenuDeleteRes struct{}
|
||||
63
api/admin/system/role/role.go
Normal file
63
api/admin/system/role/role.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Package admin_system_role 定义角色管理接口(角色及其菜单绑定),路由前缀 /admin。
|
||||
package admin_system_role
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// RoleItem 角色列表中的一行。
|
||||
type RoleItem struct {
|
||||
Id uint64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Status int `json:"status"`
|
||||
MenuIds []uint64 `json:"menuIds"` // 已绑定的菜单 id(编辑用)
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
// RoleListReq 分页查询角色。
|
||||
type RoleListReq struct {
|
||||
g.Meta `path:"/system/role/list" method:"post" tags:"Admin/System/Role" summary:"角色列表"`
|
||||
Page int `json:"page" d:"1" v:"min:1"`
|
||||
Size int `json:"size" d:"10" v:"min:1|max:100"`
|
||||
Keyword string `json:"keyword"` // 匹配角色码或名称
|
||||
}
|
||||
|
||||
// RoleListRes 是 RoleListReq 的响应。
|
||||
type RoleListRes struct {
|
||||
List []*RoleItem `json:"list"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// RoleCreateReq 创建角色并绑定菜单。
|
||||
type RoleCreateReq struct {
|
||||
g.Meta `path:"/system/role/create" method:"post" tags:"Admin/System/Role" summary:"创建角色"`
|
||||
Code string `json:"code" v:"required"`
|
||||
Name string `json:"name" v:"required"`
|
||||
Status int `json:"status"`
|
||||
MenuIds []uint64 `json:"menuIds"`
|
||||
}
|
||||
|
||||
// RoleCreateRes 是 RoleCreateReq 的响应。
|
||||
type RoleCreateRes struct {
|
||||
Id uint64 `json:"id"`
|
||||
}
|
||||
|
||||
// RoleUpdateReq 更新角色并重新绑定菜单。
|
||||
type RoleUpdateReq struct {
|
||||
g.Meta `path:"/system/role/update/{id}" method:"post" tags:"Admin/System/Role" summary:"更新角色"`
|
||||
Id uint64 `json:"id" in:"path" v:"required"`
|
||||
Name string `json:"name" v:"required"`
|
||||
Status int `json:"status"`
|
||||
MenuIds []uint64 `json:"menuIds"`
|
||||
}
|
||||
|
||||
// RoleUpdateRes 是 RoleUpdateReq 的响应。
|
||||
type RoleUpdateRes struct{}
|
||||
|
||||
// RoleDeleteReq 删除角色(软删除)。
|
||||
type RoleDeleteReq struct {
|
||||
g.Meta `path:"/system/role/delete/{id}" method:"post" tags:"Admin/System/Role" summary:"删除角色"`
|
||||
Id uint64 `json:"id" in:"path" v:"required"`
|
||||
}
|
||||
|
||||
// RoleDeleteRes 是 RoleDeleteReq 的响应。
|
||||
type RoleDeleteRes struct{}
|
||||
15
api/open/tools/doc.go
Normal file
15
api/open/tools/doc.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Package open_tools 承载开放工具接口(GET/POST /tools/*)。
|
||||
//
|
||||
// 命名规则:tools/ 下每个子功能一个独立目录,目录名即包名;Req/Res 契约放在
|
||||
// 目录内以功能命名的文件中,例如:
|
||||
//
|
||||
// api/open/tools/uuid/uuid.go - POST /tools/uuid
|
||||
// api/open/tools/md5/md5.go - POST /tools/md5
|
||||
// api/open/tools/random/random.go - POST /tools/random
|
||||
// api/open/tools/time/time.go - POST /tools/time
|
||||
// api/open/tools/ip/ip.go - POST /tools/ip
|
||||
//
|
||||
// 功能增长时,在其目录内新增文件(如 ocr/ 下加 idcard.go、invoice.go),
|
||||
// 而不是把起始文件写大。新增功能:创建 tools/<name>/<name>.go 并在
|
||||
// internal/controller/open/<name>.go 中增加对应方法,路由自动绑定。
|
||||
package open_tools
|
||||
15
api/open/tools/ip/ip.go
Normal file
15
api/open/tools/ip/ip.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Package open_tools_ip 定义 POST /tools/ip 接口契约。
|
||||
package open_tools_ip
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// IPReq 获取调用方 IP 信息。
|
||||
type IPReq struct {
|
||||
g.Meta `path:"/tools/ip" method:"post" tags:"Open/Tools" summary:"客户端 IP 信息"`
|
||||
}
|
||||
|
||||
// IPRes 是 IPReq 的响应。
|
||||
type IPRes struct {
|
||||
IP string `json:"ip"`
|
||||
Internal bool `json:"internal"` // 是否为内网/私网地址
|
||||
}
|
||||
15
api/open/tools/md5/md5.go
Normal file
15
api/open/tools/md5/md5.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Package open_tools_md5 定义 POST /tools/md5 接口契约。
|
||||
package open_tools_md5
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// MD5Req 计算文本的 MD5 摘要。
|
||||
type MD5Req struct {
|
||||
g.Meta `path:"/tools/md5" method:"post" tags:"Open/Tools" summary:"计算 MD5 摘要"`
|
||||
Text string `json:"text" v:"required#text required"`
|
||||
}
|
||||
|
||||
// MD5Res 是 MD5Req 的响应。
|
||||
type MD5Res struct {
|
||||
MD5 string `json:"md5"`
|
||||
}
|
||||
16
api/open/tools/random/random.go
Normal file
16
api/open/tools/random/random.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Package open_tools_random 定义 POST /tools/random 接口契约。
|
||||
package open_tools_random
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// RandomReq 生成随机字符串。
|
||||
type RandomReq struct {
|
||||
g.Meta `path:"/tools/random" method:"post" tags:"Open/Tools" summary:"生成随机字符串"`
|
||||
Length int `json:"length" d:"16" v:"min:1|max:128#length must be 1-128"`
|
||||
Type string `json:"type" d:"alnum" v:"in:alnum,digits,letters#unsupported type"`
|
||||
}
|
||||
|
||||
// RandomRes 是 RandomReq 的响应。
|
||||
type RandomRes struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
16
api/open/tools/time/time.go
Normal file
16
api/open/tools/time/time.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Package open_tools_time 定义 POST /tools/time 接口契约。
|
||||
package open_tools_time
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// TimeReq 获取当前服务器时间。
|
||||
type TimeReq struct {
|
||||
g.Meta `path:"/tools/time" method:"post" tags:"Open/Tools" summary:"当前服务器时间"`
|
||||
}
|
||||
|
||||
// TimeRes 是 TimeReq 的响应。
|
||||
type TimeRes struct {
|
||||
Timestamp int64 `json:"timestamp"` // Unix 秒
|
||||
DateTime string `json:"dateTime"` // 2006-01-02 15:04:05
|
||||
Date string `json:"date"` // 2006-01-02
|
||||
}
|
||||
15
api/open/tools/uuid/uuid.go
Normal file
15
api/open/tools/uuid/uuid.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Package open_tools_uuid 定义 POST /tools/uuid 接口契约。
|
||||
package open_tools_uuid
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// UUIDReq 生成唯一 ID。
|
||||
type UUIDReq struct {
|
||||
g.Meta `path:"/tools/uuid" method:"post" tags:"Open/Tools" summary:"生成唯一 ID"`
|
||||
Short bool `json:"short"` // true: 8 位短码;false: 32 位 ID
|
||||
}
|
||||
|
||||
// UUIDRes 是 UUIDReq 的响应。
|
||||
type UUIDRes struct {
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
33
api/user/auth/auth.go
Normal file
33
api/user/auth/auth.go
Normal file
@ -0,0 +1,33 @@
|
||||
// Package user_auth 定义用户端认证接口(登录/刷新),路由前缀 /api。
|
||||
package user_auth
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// LoginReq 用户登录请求(支持微信/手机验证码/账号密码三种方式)。
|
||||
type LoginReq struct {
|
||||
g.Meta `path:"/auth/login" method:"post" tags:"User/Auth" summary:"用户登录"`
|
||||
LoginType string `json:"loginType" v:"required|in:wechat,mobile,password#login type required|unsupported login type"`
|
||||
Code string `json:"code"` // 微信授权码
|
||||
Mobile string `json:"mobile"` // 手机号(mobile 登录方式)
|
||||
VerifyCode string `json:"verifyCode"` // 短信验证码
|
||||
Account string `json:"account"` // 账号(password 登录方式)
|
||||
Password string `json:"password"` // 密码
|
||||
Terminal string `json:"terminal" v:"required|in:mini,h5,app#terminal required|invalid terminal"`
|
||||
}
|
||||
|
||||
// LoginRes 用户登录响应。
|
||||
type LoginRes struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
ExpiresIn int64 `json:"expiresIn"`
|
||||
UserID uint64 `json:"userId"`
|
||||
}
|
||||
|
||||
// RefreshReq 使用刷新令牌轮换用户令牌对。
|
||||
type RefreshReq struct {
|
||||
g.Meta `path:"/auth/refresh" method:"post" tags:"User/Auth" summary:"轮换用户令牌"`
|
||||
RefreshToken string `json:"refreshToken" v:"required"`
|
||||
}
|
||||
|
||||
// RefreshRes 是 RefreshReq 的响应。
|
||||
type RefreshRes LoginRes
|
||||
158
internal/service/admin/admin/admin/admin.go
Normal file
158
internal/service/admin/admin/admin/admin.go
Normal file
@ -0,0 +1,158 @@
|
||||
package admin_admin_admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"service.xpcool.com/internal/consts"
|
||||
"service.xpcool.com/internal/dao"
|
||||
"service.xpcool.com/internal/library/response"
|
||||
"service.xpcool.com/internal/model/do"
|
||||
"service.xpcool.com/internal/model/dto"
|
||||
"service.xpcool.com/internal/model/entity"
|
||||
)
|
||||
|
||||
// IAdminManage 管理后台管理员账号。
|
||||
type IAdminManage interface {
|
||||
List(context.Context, dto.PageQuery) ([]*dto.AdminItem, int, error)
|
||||
Create(context.Context, dto.AdminCreateInput) (uint64, error)
|
||||
Update(context.Context, dto.AdminUpdateInput) error
|
||||
ResetPassword(context.Context, uint64, string) error
|
||||
Delete(context.Context, uint64) error
|
||||
}
|
||||
|
||||
type adminManage struct{}
|
||||
|
||||
var localAdminManage IAdminManage
|
||||
|
||||
func NewAdminManage() IAdminManage { return &adminManage{} }
|
||||
|
||||
func AdminManage() IAdminManage {
|
||||
if localAdminManage == nil {
|
||||
panic("AdminManage implementation not registered")
|
||||
}
|
||||
return localAdminManage
|
||||
}
|
||||
func RegisterAdminManage(i IAdminManage) { localAdminManage = i }
|
||||
|
||||
// ---------------------- Admin manage ----------------------
|
||||
|
||||
func (s *adminManage) List(ctx context.Context, q dto.PageQuery) ([]*dto.AdminItem, int, error) {
|
||||
total, err := dao.AdminUser.Ctx(ctx).Count()
|
||||
if err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "count admins")
|
||||
}
|
||||
if total == 0 {
|
||||
return nil, 0, nil
|
||||
}
|
||||
var list []entity.AdminUser
|
||||
if err = dao.AdminUser.Ctx(ctx).Page(q.Page, q.Size).OrderDesc("id").Scan(&list); err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "query admins")
|
||||
}
|
||||
items := make([]*dto.AdminItem, 0, len(list))
|
||||
for i := range list {
|
||||
a := &list[i]
|
||||
roleIds, roleNames, err := s.roles(ctx, a.Id)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
items = append(items, &dto.AdminItem{
|
||||
Id: a.Id, Username: a.Username, Nickname: a.Nickname, Status: a.Status,
|
||||
RoleIds: roleIds, RoleNames: roleNames,
|
||||
CreatedAt: a.CreatedAt.Layout("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func (s *adminManage) roles(ctx context.Context, adminID uint64) ([]uint64, []string, error) {
|
||||
var rels []entity.AdminUserRole
|
||||
if err := dao.AdminUserRole.Ctx(ctx).Where(do.AdminUserRole{AdminUserId: adminID}).Scan(&rels); err != nil {
|
||||
return nil, nil, gerror.Wrap(err, "query admin roles")
|
||||
}
|
||||
roleIds := make([]uint64, 0, len(rels))
|
||||
for _, r := range rels {
|
||||
roleIds = append(roleIds, r.RoleId)
|
||||
}
|
||||
if len(roleIds) == 0 {
|
||||
return roleIds, nil, nil
|
||||
}
|
||||
var roles []entity.AdminRole
|
||||
if err := dao.AdminRole.Ctx(ctx).WhereIn("id", roleIds).Scan(&roles); err != nil {
|
||||
return nil, nil, gerror.Wrap(err, "query roles")
|
||||
}
|
||||
names := make([]string, 0, len(roles))
|
||||
for _, r := range roles {
|
||||
names = append(names, r.Name)
|
||||
}
|
||||
return roleIds, names, nil
|
||||
}
|
||||
|
||||
func (s *adminManage) Create(ctx context.Context, in dto.AdminCreateInput) (uint64, error) {
|
||||
count, err := dao.AdminUser.Ctx(ctx).Where(do.AdminUser{Username: in.Username}).Count()
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "check username")
|
||||
}
|
||||
if count > 0 {
|
||||
return 0, response.Error(consts.CodeInvalidParam, "username already exists")
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "hash password")
|
||||
}
|
||||
id, err := dao.AdminUser.Ctx(ctx).Data(do.AdminUser{
|
||||
Username: in.Username, PasswordHash: string(hash), Nickname: in.Nickname, Status: 1,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "insert admin")
|
||||
}
|
||||
if err = bindAdminRoles(ctx, uint64(id), in.RoleIds); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint64(id), nil
|
||||
}
|
||||
|
||||
func (s *adminManage) Update(ctx context.Context, in dto.AdminUpdateInput) error {
|
||||
data := do.AdminUser{Nickname: in.Nickname, Status: in.Status}
|
||||
if _, err := dao.AdminUser.Ctx(ctx).Where(do.AdminUser{Id: in.Id}).Data(data).Update(); err != nil {
|
||||
return gerror.Wrap(err, "update admin")
|
||||
}
|
||||
return bindAdminRoles(ctx, in.Id, in.RoleIds)
|
||||
}
|
||||
|
||||
func (s *adminManage) ResetPassword(ctx context.Context, id uint64, password string) error {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "hash password")
|
||||
}
|
||||
if _, err = dao.AdminUser.Ctx(ctx).Where(do.AdminUser{Id: id}).Data(do.AdminUser{PasswordHash: string(hash)}).Update(); err != nil {
|
||||
return gerror.Wrap(err, "reset password")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *adminManage) Delete(ctx context.Context, id uint64) error {
|
||||
if _, err := dao.AdminUser.Ctx(ctx).Where(do.AdminUser{Id: id}).Delete(); err != nil {
|
||||
return gerror.Wrap(err, "delete admin")
|
||||
}
|
||||
// 关联关系物理删除,避免唯一键残留。
|
||||
if _, err := dao.AdminUserRole.Ctx(ctx).Unscoped().Where(do.AdminUserRole{AdminUserId: id}).Delete(); err != nil {
|
||||
return gerror.Wrap(err, "delete admin role bindings")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindAdminRoles 全量重绑管理员-角色关系。
|
||||
func bindAdminRoles(ctx context.Context, adminID uint64, roleIds []uint64) error {
|
||||
if _, err := dao.AdminUserRole.Ctx(ctx).Unscoped().Where(do.AdminUserRole{AdminUserId: adminID}).Delete(); err != nil {
|
||||
return gerror.Wrap(err, "clear admin roles")
|
||||
}
|
||||
for _, rid := range roleIds {
|
||||
if _, err := dao.AdminUserRole.Ctx(ctx).Data(do.AdminUserRole{AdminUserId: adminID, RoleId: rid}).Insert(); err != nil {
|
||||
return gerror.Wrap(err, "bind admin role")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
43
manifest/sql/004b_fill_seed.sql
Normal file
43
manifest/sql/004b_fill_seed.sql
Normal file
@ -0,0 +1,43 @@
|
||||
-- 004b_fill_seed.sql
|
||||
-- 补齐 004_seed.sql 中断后的缺失数据(admin_menu 菜单树 + super_admin 全绑定 + admin 用户角色绑定)。
|
||||
-- 背景:服务器首次导入 004 时因缺 icon 列在 admin_menu 处中断,仅写入了 admin_user/admin_role;
|
||||
-- 003 补列后再导入 004 会因 admin_user id=1 已存在而中断。本脚本以 INSERT IGNORE 幂等补齐。
|
||||
-- 依赖:003_schema_ext.sql 已执行(icon/component/hidden 列存在);007_menu_permissions_v3.sql 已执行(含 id=63)。
|
||||
|
||||
-- 1) 菜单树(type=1 菜单 + type=2 按钮,与 004_seed.sql 一致)
|
||||
INSERT IGNORE INTO admin_menu (id, parent_id, name, icon, type, path, component, permission, sort, status, hidden, created_at, updated_at) VALUES
|
||||
-- 系统管理
|
||||
(1, 0, '系统管理', 'mdi:settings-outline', 1, '/system', '', 'system', 1, 1, 0, NOW(), NOW()),
|
||||
-- 管理员管理
|
||||
(2, 1, '管理员管理', 'mdi:account-group-outline', 1, 'admin', 'system/admin/index', 'system:admin', 1, 1, 0, NOW(), NOW()),
|
||||
(21, 2, '查询', '', 2, '', '', 'system:admin:list', 1, 1, 0, NOW(), NOW()),
|
||||
(22, 2, '新增', '', 2, '', '', 'system:admin:create', 2, 1, 0, NOW(), NOW()),
|
||||
(23, 2, '编辑', '', 2, '', '', 'system:admin:update', 3, 1, 0, NOW(), NOW()),
|
||||
(24, 2, '删除', '', 2, '', '', 'system:admin:delete', 4, 1, 0, NOW(), NOW()),
|
||||
(25, 2, '重置密码', '', 2, '', '', 'system:admin:resetPwd', 5, 1, 0, NOW(), NOW()),
|
||||
-- 角色管理
|
||||
(3, 1, '角色管理', 'mdi:shield-account-outline', 1, 'role', 'system/role/index', 'system:role', 2, 1, 0, NOW(), NOW()),
|
||||
(31, 3, '查询', '', 2, '', '', 'system:role:list', 1, 1, 0, NOW(), NOW()),
|
||||
(32, 3, '新增', '', 2, '', '', 'system:role:create', 2, 1, 0, NOW(), NOW()),
|
||||
(33, 3, '编辑', '', 2, '', '', 'system:role:update', 3, 1, 0, NOW(), NOW()),
|
||||
(34, 3, '删除', '', 2, '', '', 'system:role:delete', 4, 1, 0, NOW(), NOW()),
|
||||
(35, 3, '分配菜单', '', 2, '', '', 'system:role:assignMenu', 5, 1, 0, NOW(), NOW()),
|
||||
-- 菜单管理
|
||||
(4, 1, '菜单管理', 'mdi:menu-outline', 1, 'menu', 'system/menu/index', 'system:menu', 3, 1, 0, NOW(), NOW()),
|
||||
(41, 4, '查询', '', 2, '', '', 'system:menu:list', 1, 1, 0, NOW(), NOW()),
|
||||
(42, 4, '新增', '', 2, '', '', 'system:menu:create', 2, 1, 0, NOW(), NOW()),
|
||||
(43, 4, '编辑', '', 2, '', '', 'system:menu:update', 3, 1, 0, NOW(), NOW()),
|
||||
(44, 4, '删除', '', 2, '', '', 'system:menu:delete', 4, 1, 0, NOW(), NOW()),
|
||||
-- 系统监控
|
||||
(5, 0, '系统监控', 'mdi:monitor-dashboard', 1, '/monitor', '', 'monitor', 2, 1, 0, NOW(), NOW()),
|
||||
-- 服务器日志
|
||||
(6, 5, '服务器日志', 'mdi:file-document-outline', 1, 'log', 'monitor/log/index', 'monitor:log', 1, 1, 0, NOW(), NOW()),
|
||||
(61, 6, '查看', '', 2, '', '', 'monitor:log:view', 1, 1, 0, NOW(), NOW()),
|
||||
(62, 6, '实时监控', '', 2, '', '', 'monitor:log:tail', 2, 1, 0, NOW(), NOW());
|
||||
|
||||
-- 2) super_admin 绑定全部菜单(含 007 新增的 63)
|
||||
INSERT IGNORE INTO admin_role_menu (role_id, menu_id, created_at, updated_at)
|
||||
SELECT 1, id, NOW(), NOW() FROM admin_menu WHERE deleted_at IS NULL;
|
||||
|
||||
-- 3) admin 用户绑定 super_admin 角色
|
||||
INSERT IGNORE INTO admin_user_role (admin_user_id, role_id, created_at, updated_at) VALUES (1, 1, NOW(), NOW());
|
||||
36
manifest/sql/007_menu_permissions_v3.sql
Normal file
36
manifest/sql/007_menu_permissions_v3.sql
Normal file
@ -0,0 +1,36 @@
|
||||
-- 007_menu_permissions_v3.sql
|
||||
-- 适配 2026-08-26 统一 API 前缀 /api/service + 全 POST 重构后的权限映射。
|
||||
-- 背景:005/006 的 path 仍为旧的 /admin/v1 路径,与最新路由不符;
|
||||
-- 本脚本将 admin_menu type=2 行的 path 全部更新为「METHOD /api/service/admin/...」格式,
|
||||
-- 并补充系统日志 system:log:list 权限映射(菜单 id=63,挂服务器日志 id=6 下)。
|
||||
-- 依赖:先执行 004_seed.sql(表结构已就绪时仅需 004 + 本脚本)。
|
||||
-- 幂等性:UPDATE 幂等;INSERT 63 用 INSERT IGNORE,可重复执行。
|
||||
|
||||
-- ============ 1) 权限路径映射(type=2 按钮行) ============
|
||||
-- 管理员管理(父 2)
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/admin/list' WHERE id=21; -- system:admin:list
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/admin/create' WHERE id=22; -- system:admin:create
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/admin/update/{id}' WHERE id=23; -- system:admin:update
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/admin/delete/{id}' WHERE id=24; -- system:admin:delete
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/admin/resetPwd/{id}' WHERE id=25; -- system:admin:resetPwd
|
||||
-- 角色管理(父 3)
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/system/role/list' WHERE id=31; -- system:role:list
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/system/role/create' WHERE id=32; -- system:role:create
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/system/role/update/{id}' WHERE id=33; -- system:role:update
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/system/role/delete/{id}' WHERE id=34; -- system:role:delete
|
||||
UPDATE admin_menu SET path='' WHERE id=35; -- system:role:assignMenu(无独立端点)
|
||||
-- 菜单管理(父 4,查询走 tree 接口)
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/system/menu/tree' WHERE id=41; -- system:menu:list
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/system/menu/create' WHERE id=42; -- system:menu:create
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/system/menu/update/{id}' WHERE id=43; -- system:menu:update
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/system/menu/delete/{id}' WHERE id=44; -- system:menu:delete
|
||||
-- 服务器日志(父 6)
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/base/log/files' WHERE id=61; -- monitor:log:view
|
||||
UPDATE admin_menu SET path='POST /api/service/admin/base/log/tail' WHERE id=62; -- monitor:log:tail
|
||||
|
||||
-- ============ 2) 系统日志权限(id=63,父 6,system:log:list) ============
|
||||
INSERT IGNORE INTO admin_menu (id, parent_id, name, icon, type, path, component, permission, sort, status, hidden, created_at, updated_at)
|
||||
VALUES (63, 6, '操作日志', '', 2, 'POST /api/service/admin/system/log', '', 'system:log:list', 3, 1, 0, NOW(), NOW());
|
||||
|
||||
-- super_admin 绑定新权限
|
||||
INSERT IGNORE INTO admin_role_menu (role_id, menu_id, created_at, updated_at) VALUES (1, 63, NOW(), NOW());
|
||||
23
manifest/sql/008_menu_rbac_v4.sql
Normal file
23
manifest/sql/008_menu_rbac_v4.sql
Normal file
@ -0,0 +1,23 @@
|
||||
-- 008_menu_rbac_v4.sql
|
||||
-- 2026-08-26 RBAC 界面调整:
|
||||
-- 1) 移除"管理员管理"菜单(id=2 及按钮行 21-25,软删 + 清理角色绑定);
|
||||
-- 后端 /admin/* 接口与权限码 system:admin:* 保留,仅前端不再出现该菜单。
|
||||
-- 2) 系统管理下新增"日志管理"菜单(id=7,type=1,组件 system/log/index);
|
||||
-- 原按钮行 63(system:log:list,POST /api/service/admin/system/log)改挂到 id=7 下。
|
||||
-- 依赖:先执行 004_seed.sql + 007_menu_permissions_v3.sql。
|
||||
-- 幂等性:UPDATE/INSERT IGNORE 可重复执行。
|
||||
|
||||
-- ============ 1) 移除管理员管理菜单 ============
|
||||
UPDATE admin_menu SET deleted_at=NOW() WHERE id IN (2,21,22,23,24,25) AND deleted_at IS NULL;
|
||||
DELETE FROM admin_role_menu WHERE menu_id IN (2,21,22,23,24,25);
|
||||
|
||||
-- ============ 2) 新增日志管理菜单 ============
|
||||
INSERT INTO admin_menu (id, parent_id, name, icon, type, path, component, permission, sort, status, hidden, created_at, updated_at)
|
||||
VALUES (7, 1, '日志管理', 'mdi:clipboard-text-outline', 1, 'log', 'system/log/index', 'system:log', 4, 1, 0, NOW(), NOW())
|
||||
ON DUPLICATE KEY UPDATE deleted_at=NULL, parent_id=1, name='日志管理', icon='mdi:clipboard-text-outline', path='log', component='system/log/index', permission='system:log', sort=4, status=1, hidden=0;
|
||||
|
||||
-- 原按钮行 63 挂到日志管理下
|
||||
UPDATE admin_menu SET parent_id=7, name='操作日志', sort=1, deleted_at=NULL WHERE id=63;
|
||||
|
||||
-- 超管绑定新菜单与按钮
|
||||
INSERT IGNORE INTO admin_role_menu (role_id, menu_id, created_at, updated_at) VALUES (1, 7, NOW(), NOW()), (1, 63, NOW(), NOW());
|
||||
Loading…
Reference in New Issue
Block a user