feat: 优化web
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"giter.top/smart/internal/iam/entity"
|
||||
"giter.top/smart/internal/iam/repository"
|
||||
"giter.top/smart/pkg/utils/id"
|
||||
)
|
||||
|
||||
// MenuService 菜单(全局资源)
|
||||
type MenuService interface {
|
||||
Create(ctx context.Context, req *CreateMenuRequest, isPlatform bool) (*entity.Menu, error)
|
||||
Update(ctx context.Context, mid string, req *UpdateMenuRequest, isPlatform bool) (*entity.Menu, error)
|
||||
Delete(ctx context.Context, ids []string, isPlatform bool) error
|
||||
Get(ctx context.Context, mid string) (*entity.Menu, error)
|
||||
Tree(ctx context.Context, menuType *int16) ([]MenuNode, error)
|
||||
NavForUser(ctx context.Context, userID string) ([]MenuNode, error)
|
||||
PermsForUser(ctx context.Context, userID string) ([]string, error)
|
||||
}
|
||||
|
||||
type CreateMenuRequest struct {
|
||||
ParentID string `json:"parent_id"`
|
||||
MenuName string `json:"menu_name" binding:"required,max=128"`
|
||||
MenuType int16 `json:"menu_type" binding:"required"`
|
||||
Perms string `json:"perms"`
|
||||
Path string `json:"path"`
|
||||
Component string `json:"component"`
|
||||
Icon string `json:"icon"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
IsVisible bool `json:"is_visible"`
|
||||
IsBuiltin bool `json:"is_builtin"`
|
||||
ExternalLink string `json:"external_link"`
|
||||
}
|
||||
|
||||
type UpdateMenuRequest struct {
|
||||
ParentID *string `json:"parent_id"`
|
||||
MenuName *string `json:"menu_name"`
|
||||
SortOrder *int `json:"sort_order"`
|
||||
IsVisible *bool `json:"is_visible"`
|
||||
Path *string `json:"path"`
|
||||
Component *string `json:"component"`
|
||||
Icon *string `json:"icon"`
|
||||
ExternalLink *string `json:"external_link"`
|
||||
Status *int16 `json:"status"`
|
||||
}
|
||||
|
||||
// MenuNode 菜单树节点
|
||||
type MenuNode struct {
|
||||
entity.Menu
|
||||
Children []MenuNode `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type menuService struct {
|
||||
menus repository.MenuRepository
|
||||
roles repository.RoleRepository
|
||||
users repository.UserRepository
|
||||
}
|
||||
|
||||
func NewMenuService(menus repository.MenuRepository, roles repository.RoleRepository, users repository.UserRepository) MenuService {
|
||||
return &menuService{menus: menus, roles: roles, users: users}
|
||||
}
|
||||
|
||||
func normalizeMenuParent(pid string) string {
|
||||
if pid == "0" {
|
||||
return ""
|
||||
}
|
||||
return pid
|
||||
}
|
||||
|
||||
func (s *menuService) Create(ctx context.Context, req *CreateMenuRequest, isPlatform bool) (*entity.Menu, error) {
|
||||
if !isPlatform {
|
||||
return nil, repository.ErrForbidden
|
||||
}
|
||||
if req.Perms != "" {
|
||||
ok, err := s.menus.ExistsPerms(ctx, req.Perms, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
return nil, fmt.Errorf("权限标识已存在")
|
||||
}
|
||||
}
|
||||
m := &entity.Menu{
|
||||
ID: id.New(),
|
||||
ParentID: normalizeMenuParent(req.ParentID),
|
||||
MenuName: req.MenuName,
|
||||
MenuType: req.MenuType,
|
||||
Perms: req.Perms,
|
||||
Path: req.Path,
|
||||
Component: req.Component,
|
||||
Icon: req.Icon,
|
||||
SortOrder: req.SortOrder,
|
||||
IsVisible: req.IsVisible,
|
||||
IsBuiltin: req.IsBuiltin,
|
||||
ExternalLink: req.ExternalLink,
|
||||
Status: 1,
|
||||
}
|
||||
if err := s.menus.Create(ctx, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (s *menuService) Update(ctx context.Context, mid string, req *UpdateMenuRequest, isPlatform bool) (*entity.Menu, error) {
|
||||
if !isPlatform {
|
||||
return nil, repository.ErrForbidden
|
||||
}
|
||||
m, err := s.menus.GetByID(ctx, mid)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
return nil, fmt.Errorf("菜单不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if m.IsBuiltin {
|
||||
return nil, fmt.Errorf("系统内置菜单禁止修改")
|
||||
}
|
||||
if req.MenuName != nil {
|
||||
m.MenuName = *req.MenuName
|
||||
}
|
||||
if req.SortOrder != nil {
|
||||
m.SortOrder = *req.SortOrder
|
||||
}
|
||||
if req.IsVisible != nil {
|
||||
m.IsVisible = *req.IsVisible
|
||||
}
|
||||
if req.Path != nil {
|
||||
m.Path = *req.Path
|
||||
}
|
||||
if req.Component != nil {
|
||||
m.Component = *req.Component
|
||||
}
|
||||
if req.Icon != nil {
|
||||
m.Icon = *req.Icon
|
||||
}
|
||||
if req.ExternalLink != nil {
|
||||
m.ExternalLink = *req.ExternalLink
|
||||
}
|
||||
if req.Status != nil {
|
||||
m.Status = *req.Status
|
||||
}
|
||||
if err := s.menus.Update(ctx, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (s *menuService) Delete(ctx context.Context, ids []string, isPlatform bool) error {
|
||||
if !isPlatform {
|
||||
return repository.ErrForbidden
|
||||
}
|
||||
for _, mid := range ids {
|
||||
m, err := s.menus.GetByID(ctx, mid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.IsBuiltin {
|
||||
return fmt.Errorf("系统内置菜单禁止删除")
|
||||
}
|
||||
n, err := s.menus.CountChildren(ctx, mid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
return fmt.Errorf("存在子菜单,无法删除")
|
||||
}
|
||||
rn, err := s.menus.CountRoleRefs(ctx, mid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rn > 0 {
|
||||
return fmt.Errorf("菜单仍被角色引用")
|
||||
}
|
||||
if err := s.menus.Delete(ctx, mid); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *menuService) Get(ctx context.Context, mid string) (*entity.Menu, error) {
|
||||
return s.menus.GetByID(ctx, mid)
|
||||
}
|
||||
|
||||
func (s *menuService) Tree(ctx context.Context, menuType *int16) ([]MenuNode, error) {
|
||||
rows, err := s.menus.ListByType(ctx, menuType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildMenuTreeRows(rows), nil
|
||||
}
|
||||
|
||||
func buildMenuTreeRows(rows []entity.Menu) []MenuNode {
|
||||
byParent := map[string][]entity.Menu{}
|
||||
for _, m := range rows {
|
||||
pid := normalizeMenuParent(m.ParentID)
|
||||
byParent[pid] = append(byParent[pid], m)
|
||||
}
|
||||
for k := range byParent {
|
||||
sort.Slice(byParent[k], func(i, j int) bool {
|
||||
if byParent[k][i].SortOrder != byParent[k][j].SortOrder {
|
||||
return byParent[k][i].SortOrder < byParent[k][j].SortOrder
|
||||
}
|
||||
return byParent[k][i].ID < byParent[k][j].ID
|
||||
})
|
||||
}
|
||||
var walk func(pid string) []MenuNode
|
||||
walk = func(pid string) []MenuNode {
|
||||
list := byParent[pid]
|
||||
out := make([]MenuNode, 0, len(list))
|
||||
for _, m := range list {
|
||||
out = append(out, MenuNode{Menu: m, Children: walk(m.ID)})
|
||||
}
|
||||
return out
|
||||
}
|
||||
return walk("")
|
||||
}
|
||||
|
||||
func (s *menuService) NavForUser(ctx context.Context, userID string) ([]MenuNode, error) {
|
||||
rids, err := s.users.ListRoleIDs(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
menuIDs, err := s.roles.ListMenuIDsByRoles(ctx, rids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowed := map[string]struct{}{}
|
||||
for _, mid := range menuIDs {
|
||||
allowed[mid] = struct{}{}
|
||||
}
|
||||
pub, err := s.menus.ListByPerms(ctx, entity.PublicOverviewPerms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range pub {
|
||||
allowed[m.ID] = struct{}{}
|
||||
}
|
||||
all, err := s.menus.ListAll(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byID := map[string]entity.Menu{}
|
||||
for _, m := range all {
|
||||
byID[m.ID] = m
|
||||
}
|
||||
for _, m := range all {
|
||||
if _, ok := allowed[m.ID]; !ok {
|
||||
continue
|
||||
}
|
||||
cur := m
|
||||
for {
|
||||
pid := normalizeMenuParent(cur.ParentID)
|
||||
if pid == "" {
|
||||
break
|
||||
}
|
||||
p, ok := byID[pid]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
allowed[p.ID] = struct{}{}
|
||||
cur = p
|
||||
}
|
||||
}
|
||||
filtered := make([]entity.Menu, 0)
|
||||
for _, m := range all {
|
||||
if _, ok := allowed[m.ID]; ok && m.Status == 1 && m.IsVisible {
|
||||
filtered = append(filtered, m)
|
||||
}
|
||||
}
|
||||
tree := buildMenuTreeRows(filtered)
|
||||
return pruneEmptyDirs(tree), nil
|
||||
}
|
||||
|
||||
func pruneEmptyDirs(nodes []MenuNode) []MenuNode {
|
||||
out := make([]MenuNode, 0, len(nodes))
|
||||
for _, n := range nodes {
|
||||
ch := pruneEmptyDirs(n.Children)
|
||||
if n.MenuType == 1 && len(ch) == 0 {
|
||||
continue
|
||||
}
|
||||
n.Children = ch
|
||||
out = append(out, n)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *menuService) PermsForUser(ctx context.Context, userID string) ([]string, error) {
|
||||
rids, err := s.users.ListRoleIDs(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mids, err := s.roles.ListMenuIDsByRoles(ctx, rids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
all, err := s.menus.ListAll(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idset := map[string]struct{}{}
|
||||
for _, mid := range mids {
|
||||
idset[mid] = struct{}{}
|
||||
}
|
||||
var perms []string
|
||||
for _, m := range all {
|
||||
if _, ok := idset[m.ID]; !ok {
|
||||
continue
|
||||
}
|
||||
if m.Perms != "" {
|
||||
perms = append(perms, m.Perms)
|
||||
}
|
||||
}
|
||||
return perms, nil
|
||||
}
|
||||
Reference in New Issue
Block a user