任务三
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Api API实体
|
||||
type Api struct {
|
||||
ID uint
|
||||
Path string
|
||||
Description string
|
||||
ApiGroup string
|
||||
Method string
|
||||
}
|
||||
|
||||
// IgnoreApi 忽略的API
|
||||
type IgnoreApi struct {
|
||||
Path string
|
||||
Method string
|
||||
}
|
||||
|
||||
// CasbinRule Casbin规则
|
||||
type CasbinRule struct {
|
||||
Path string
|
||||
Method string
|
||||
}
|
||||
|
||||
// ApiRepo API仓储接口
|
||||
type ApiRepo interface {
|
||||
Create(ctx context.Context, api *Api) error
|
||||
Update(ctx context.Context, api *Api) error
|
||||
Delete(ctx context.Context, id uint) error
|
||||
DeleteByIds(ctx context.Context, ids []uint) error
|
||||
FindByID(ctx context.Context, id uint) (*Api, error)
|
||||
FindByPathMethod(ctx context.Context, path, method string) (*Api, error)
|
||||
List(ctx context.Context, page, pageSize int, filters map[string]string, orderKey string, desc bool) ([]*Api, int64, error)
|
||||
FindAll(ctx context.Context) ([]*Api, error)
|
||||
FindByIds(ctx context.Context, ids []uint) ([]*Api, error)
|
||||
GetApiGroups(ctx context.Context) ([]string, map[string]string, error)
|
||||
CreateIgnoreApi(ctx context.Context, path, method string) error
|
||||
DeleteIgnoreApi(ctx context.Context, path, method string) error
|
||||
FindAllIgnoreApis(ctx context.Context) ([]*IgnoreApi, error)
|
||||
}
|
||||
|
||||
// CasbinRepo Casbin仓储接口
|
||||
type CasbinRepo interface {
|
||||
ClearCasbin(v int, p ...string) bool
|
||||
UpdateCasbinApi(oldPath, newPath, oldMethod, newMethod string) error
|
||||
GetPolicyPathByAuthorityId(authorityId uint) []CasbinRule
|
||||
FreshCasbin() error
|
||||
}
|
||||
|
||||
// ApiUsecase API用例
|
||||
type ApiUsecase struct {
|
||||
repo ApiRepo
|
||||
casbinRepo CasbinRepo
|
||||
authRepo AuthorityRepo
|
||||
useStrictAuth bool
|
||||
routers []RouterInfo // 内存中的路由信息
|
||||
}
|
||||
|
||||
// RouterInfo 路由信息
|
||||
type RouterInfo struct {
|
||||
Path string
|
||||
Method string
|
||||
}
|
||||
|
||||
// NewApiUsecase 创建API用例
|
||||
func NewApiUsecase(repo ApiRepo, casbinRepo CasbinRepo, authRepo AuthorityRepo, useStrictAuth bool) *ApiUsecase {
|
||||
return &ApiUsecase{
|
||||
repo: repo,
|
||||
casbinRepo: casbinRepo,
|
||||
authRepo: authRepo,
|
||||
useStrictAuth: useStrictAuth,
|
||||
routers: make([]RouterInfo, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// SetRouters 设置内存中的路由信息
|
||||
func (uc *ApiUsecase) SetRouters(routers []RouterInfo) {
|
||||
uc.routers = routers
|
||||
}
|
||||
|
||||
// CreateApi 创建API
|
||||
func (uc *ApiUsecase) CreateApi(ctx context.Context, api *Api) error {
|
||||
return uc.repo.Create(ctx, api)
|
||||
}
|
||||
|
||||
// UpdateApi 更新API
|
||||
func (uc *ApiUsecase) UpdateApi(ctx context.Context, api *Api) error {
|
||||
// 获取旧记录
|
||||
old, err := uc.repo.FindByID(ctx, api.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果路径或方法变更,更新Casbin
|
||||
if old.Path != api.Path || old.Method != api.Method {
|
||||
if err := uc.casbinRepo.UpdateCasbinApi(old.Path, api.Path, old.Method, api.Method); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return uc.repo.Update(ctx, api)
|
||||
}
|
||||
|
||||
// DeleteApi 删除API
|
||||
func (uc *ApiUsecase) DeleteApi(ctx context.Context, id uint) error {
|
||||
api, err := uc.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := uc.repo.Delete(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除Casbin规则
|
||||
uc.casbinRepo.ClearCasbin(1, api.Path, api.Method)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteApisByIds 批量删除API
|
||||
func (uc *ApiUsecase) DeleteApisByIds(ctx context.Context, ids []uint) error {
|
||||
apis, err := uc.repo.FindByIds(ctx, ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := uc.repo.DeleteByIds(ctx, ids); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除Casbin规则
|
||||
for _, api := range apis {
|
||||
uc.casbinRepo.ClearCasbin(1, api.Path, api.Method)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetApiById 根据ID获取API
|
||||
func (uc *ApiUsecase) GetApiById(ctx context.Context, id uint) (*Api, error) {
|
||||
return uc.repo.FindByID(ctx, id)
|
||||
}
|
||||
|
||||
// GetAPIInfoList 分页获取API列表
|
||||
func (uc *ApiUsecase) GetAPIInfoList(ctx context.Context, page, pageSize int, filters map[string]string, orderKey string, desc bool) ([]*Api, int64, error) {
|
||||
// 验证排序字段
|
||||
if orderKey != "" {
|
||||
orderMap := map[string]bool{"id": true, "path": true, "api_group": true, "description": true, "method": true}
|
||||
if !orderMap[orderKey] {
|
||||
return nil, 0, errors.New("非法的排序字段: " + orderKey)
|
||||
}
|
||||
}
|
||||
return uc.repo.List(ctx, page, pageSize, filters, orderKey, desc)
|
||||
}
|
||||
|
||||
// GetAllApis 获取所有API(根据权限过滤)
|
||||
func (uc *ApiUsecase) GetAllApis(ctx context.Context, authorityID uint) ([]*Api, error) {
|
||||
parentAuthorityID, err := uc.authRepo.GetParentAuthorityID(ctx, authorityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apis, err := uc.repo.FindAll(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果是顶级角色或不使用严格权限,返回所有API
|
||||
if parentAuthorityID == 0 || !uc.useStrictAuth {
|
||||
return apis, nil
|
||||
}
|
||||
|
||||
// 根据Casbin规则过滤
|
||||
paths := uc.casbinRepo.GetPolicyPathByAuthorityId(authorityID)
|
||||
var authApis []*Api
|
||||
for _, api := range apis {
|
||||
for _, rule := range paths {
|
||||
if rule.Path == api.Path && rule.Method == api.Method {
|
||||
authApis = append(authApis, api)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return authApis, nil
|
||||
}
|
||||
|
||||
// GetApiGroups 获取API分组
|
||||
func (uc *ApiUsecase) GetApiGroups(ctx context.Context) ([]string, map[string]string, error) {
|
||||
return uc.repo.GetApiGroups(ctx)
|
||||
}
|
||||
|
||||
// SyncApi 同步API
|
||||
func (uc *ApiUsecase) SyncApi(ctx context.Context) (newApis, deleteApis, ignoreApis []*Api, err error) {
|
||||
newApis = make([]*Api, 0)
|
||||
deleteApis = make([]*Api, 0)
|
||||
ignoreApis = make([]*Api, 0)
|
||||
|
||||
// 获取数据库中的API
|
||||
apis, err := uc.repo.FindAll(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// 获取忽略的API
|
||||
ignores, err := uc.repo.FindAllIgnoreApis(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
for _, ig := range ignores {
|
||||
ignoreApis = append(ignoreApis, &Api{
|
||||
Path: ig.Path,
|
||||
Method: ig.Method,
|
||||
})
|
||||
}
|
||||
|
||||
// 过滤掉忽略的路由
|
||||
var cacheApis []*Api
|
||||
for _, r := range uc.routers {
|
||||
ignored := false
|
||||
for _, ig := range ignores {
|
||||
if ig.Path == r.Path && ig.Method == r.Method {
|
||||
ignored = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ignored {
|
||||
cacheApis = append(cacheApis, &Api{
|
||||
Path: r.Path,
|
||||
Method: r.Method,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 对比:内存中有但数据库没有的 -> 新增
|
||||
for _, cache := range cacheApis {
|
||||
found := false
|
||||
for _, api := range apis {
|
||||
if cache.Path == api.Path && cache.Method == api.Method {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
newApis = append(newApis, &Api{
|
||||
Path: cache.Path,
|
||||
Method: cache.Method,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 对比:数据库有但内存没有的 -> 删除
|
||||
for _, api := range apis {
|
||||
found := false
|
||||
for _, cache := range cacheApis {
|
||||
if cache.Path == api.Path && cache.Method == api.Method {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
deleteApis = append(deleteApis, api)
|
||||
}
|
||||
}
|
||||
|
||||
return newApis, deleteApis, ignoreApis, nil
|
||||
}
|
||||
|
||||
// IgnoreApi 忽略/取消忽略API
|
||||
func (uc *ApiUsecase) IgnoreApi(ctx context.Context, path, method string, flag bool) error {
|
||||
if flag {
|
||||
return uc.repo.CreateIgnoreApi(ctx, path, method)
|
||||
}
|
||||
return uc.repo.DeleteIgnoreApi(ctx, path, method)
|
||||
}
|
||||
|
||||
// EnterSyncApi 确认同步API
|
||||
func (uc *ApiUsecase) EnterSyncApi(ctx context.Context, newApis, deleteApis []*Api) error {
|
||||
// 创建新API
|
||||
for _, api := range newApis {
|
||||
if err := uc.repo.Create(ctx, api); err != nil {
|
||||
// 忽略重复错误
|
||||
if !strings.Contains(err.Error(), "存在相同api") {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除旧API
|
||||
for _, api := range deleteApis {
|
||||
uc.casbinRepo.ClearCasbin(1, api.Path, api.Method)
|
||||
existing, err := uc.repo.FindByPathMethod(ctx, api.Path, api.Method)
|
||||
if err == nil && existing != nil {
|
||||
if err := uc.repo.Delete(ctx, existing.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FreshCasbin 刷新Casbin缓存
|
||||
func (uc *ApiUsecase) FreshCasbin() error {
|
||||
return uc.casbinRepo.FreshCasbin()
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// BaseMenu 基础菜单实体
|
||||
type BaseMenu struct {
|
||||
ID uint
|
||||
ParentId uint
|
||||
Path string
|
||||
Name string
|
||||
Hidden bool
|
||||
Component string
|
||||
Sort int
|
||||
ActiveName string
|
||||
KeepAlive bool
|
||||
DefaultMenu bool
|
||||
Title string
|
||||
Icon string
|
||||
CloseTab bool
|
||||
TransitionType string
|
||||
Parameters []*MenuParameter
|
||||
MenuBtn []*MenuBtn
|
||||
Children []*BaseMenu
|
||||
}
|
||||
|
||||
// MenuParameter 菜单参数
|
||||
type MenuParameter struct {
|
||||
ID uint
|
||||
SysBaseMenuID uint
|
||||
Type string
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// MenuBtn 菜单按钮
|
||||
type MenuBtn struct {
|
||||
ID uint
|
||||
SysBaseMenuID uint
|
||||
Name string
|
||||
Desc string
|
||||
}
|
||||
|
||||
// Menu 菜单(带权限)
|
||||
type Menu struct {
|
||||
BaseMenu
|
||||
AuthorityId uint
|
||||
MenuId uint
|
||||
Btns map[string]uint
|
||||
Children []*Menu
|
||||
}
|
||||
|
||||
// AuthorityBtn 角色按钮权限
|
||||
type AuthorityBtn struct {
|
||||
AuthorityId uint
|
||||
SysMenuID uint
|
||||
SysBaseMenuBtnID uint
|
||||
BtnName string
|
||||
}
|
||||
|
||||
// MenuRepo 菜单仓储接口
|
||||
type MenuRepo interface {
|
||||
CreateBaseMenu(ctx context.Context, menu *BaseMenu) error
|
||||
UpdateBaseMenu(ctx context.Context, menu *BaseMenu) error
|
||||
DeleteBaseMenu(ctx context.Context, id uint) error
|
||||
FindBaseMenuByID(ctx context.Context, id uint) (*BaseMenu, error)
|
||||
FindAllBaseMenus(ctx context.Context) ([]*BaseMenu, error)
|
||||
FindBaseMenusByIds(ctx context.Context, ids []string) ([]*BaseMenu, error)
|
||||
FindAuthorityMenuIds(ctx context.Context, authorityId uint) ([]string, error)
|
||||
FindAuthorityBtns(ctx context.Context, authorityId uint) ([]*AuthorityBtn, error)
|
||||
SetMenuAuthority(ctx context.Context, authorityId uint, menuIds []uint) error
|
||||
}
|
||||
|
||||
// MenuUsecase 菜单用例
|
||||
type MenuUsecase struct {
|
||||
repo MenuRepo
|
||||
authRepo AuthorityRepo
|
||||
useStrictAuth bool
|
||||
}
|
||||
|
||||
// NewMenuUsecase 创建菜单用例
|
||||
func NewMenuUsecase(repo MenuRepo, authRepo AuthorityRepo, useStrictAuth bool) *MenuUsecase {
|
||||
return &MenuUsecase{
|
||||
repo: repo,
|
||||
authRepo: authRepo,
|
||||
useStrictAuth: useStrictAuth,
|
||||
}
|
||||
}
|
||||
|
||||
// AddBaseMenu 添加基础菜单
|
||||
func (uc *MenuUsecase) AddBaseMenu(ctx context.Context, menu *BaseMenu) error {
|
||||
return uc.repo.CreateBaseMenu(ctx, menu)
|
||||
}
|
||||
|
||||
// UpdateBaseMenu 更新基础菜单
|
||||
func (uc *MenuUsecase) UpdateBaseMenu(ctx context.Context, menu *BaseMenu) error {
|
||||
return uc.repo.UpdateBaseMenu(ctx, menu)
|
||||
}
|
||||
|
||||
// DeleteBaseMenu 删除基础菜单
|
||||
func (uc *MenuUsecase) DeleteBaseMenu(ctx context.Context, id uint) error {
|
||||
return uc.repo.DeleteBaseMenu(ctx, id)
|
||||
}
|
||||
|
||||
// GetBaseMenuById 根据ID获取基础菜单
|
||||
func (uc *MenuUsecase) GetBaseMenuById(ctx context.Context, id uint) (*BaseMenu, error) {
|
||||
return uc.repo.FindBaseMenuByID(ctx, id)
|
||||
}
|
||||
|
||||
// GetMenuTree 获取动态菜单树
|
||||
func (uc *MenuUsecase) GetMenuTree(ctx context.Context, authorityId uint) ([]*Menu, error) {
|
||||
treeMap, err := uc.getMenuTreeMap(ctx, authorityId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
menus := treeMap[0]
|
||||
for i := range menus {
|
||||
uc.getChildrenList(menus[i], treeMap)
|
||||
}
|
||||
return menus, nil
|
||||
}
|
||||
|
||||
func (uc *MenuUsecase) getMenuTreeMap(ctx context.Context, authorityId uint) (map[uint][]*Menu, error) {
|
||||
treeMap := make(map[uint][]*Menu)
|
||||
|
||||
// 获取角色的菜单ID
|
||||
menuIds, err := uc.repo.FindAuthorityMenuIds(ctx, authorityId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取基础菜单
|
||||
baseMenus, err := uc.repo.FindBaseMenusByIds(ctx, menuIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取按钮权限
|
||||
btns, err := uc.repo.FindAuthorityBtns(ctx, authorityId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构建按钮map
|
||||
btnMap := make(map[uint]map[string]uint)
|
||||
for _, b := range btns {
|
||||
if btnMap[b.SysMenuID] == nil {
|
||||
btnMap[b.SysMenuID] = make(map[string]uint)
|
||||
}
|
||||
btnMap[b.SysMenuID][b.BtnName] = authorityId
|
||||
}
|
||||
|
||||
// 构建菜单树
|
||||
for _, base := range baseMenus {
|
||||
menu := &Menu{
|
||||
BaseMenu: *base,
|
||||
AuthorityId: authorityId,
|
||||
MenuId: base.ID,
|
||||
Btns: btnMap[base.ID],
|
||||
}
|
||||
treeMap[base.ParentId] = append(treeMap[base.ParentId], menu)
|
||||
}
|
||||
|
||||
return treeMap, nil
|
||||
}
|
||||
|
||||
func (uc *MenuUsecase) getChildrenList(menu *Menu, treeMap map[uint][]*Menu) {
|
||||
menu.Children = treeMap[menu.MenuId]
|
||||
for i := range menu.Children {
|
||||
uc.getChildrenList(menu.Children[i], treeMap)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBaseMenuTree 获取基础菜单树
|
||||
func (uc *MenuUsecase) GetBaseMenuTree(ctx context.Context, authorityID uint) ([]*BaseMenu, error) {
|
||||
treeMap, err := uc.getBaseMenuTreeMap(ctx, authorityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
menus := treeMap[0]
|
||||
for i := range menus {
|
||||
uc.getBaseChildrenList(menus[i], treeMap)
|
||||
}
|
||||
return menus, nil
|
||||
}
|
||||
|
||||
func (uc *MenuUsecase) getBaseMenuTreeMap(ctx context.Context, authorityID uint) (map[uint][]*BaseMenu, error) {
|
||||
parentAuthorityID, err := uc.authRepo.GetParentAuthorityID(ctx, authorityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var allMenus []*BaseMenu
|
||||
treeMap := make(map[uint][]*BaseMenu)
|
||||
|
||||
// 当开启了严格的树角色并且父角色不为0时需要进行菜单筛选
|
||||
if uc.useStrictAuth && parentAuthorityID != 0 {
|
||||
menuIds, err := uc.repo.FindAuthorityMenuIds(ctx, authorityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allMenus, err = uc.repo.FindBaseMenusByIds(ctx, menuIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
allMenus, err = uc.repo.FindAllBaseMenus(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range allMenus {
|
||||
treeMap[m.ParentId] = append(treeMap[m.ParentId], m)
|
||||
}
|
||||
return treeMap, nil
|
||||
}
|
||||
|
||||
func (uc *MenuUsecase) getBaseChildrenList(menu *BaseMenu, treeMap map[uint][]*BaseMenu) {
|
||||
menu.Children = treeMap[menu.ID]
|
||||
for i := range menu.Children {
|
||||
uc.getBaseChildrenList(menu.Children[i], treeMap)
|
||||
}
|
||||
}
|
||||
|
||||
// GetInfoList 获取菜单列表(树形)
|
||||
func (uc *MenuUsecase) GetInfoList(ctx context.Context, authorityID uint) ([]*BaseMenu, error) {
|
||||
return uc.GetBaseMenuTree(ctx, authorityID)
|
||||
}
|
||||
|
||||
// AddMenuAuthority 为角色增加菜单权限
|
||||
func (uc *MenuUsecase) AddMenuAuthority(ctx context.Context, menus []*BaseMenu, adminAuthorityID, authorityId uint) error {
|
||||
// 检查权限
|
||||
parentAuthorityID, err := uc.authRepo.GetParentAuthorityID(ctx, adminAuthorityID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 当开启了严格的树角色并且父角色不为0时需要进行菜单筛选
|
||||
if uc.useStrictAuth && parentAuthorityID != 0 {
|
||||
menuIds, err := uc.repo.FindAuthorityMenuIds(ctx, adminAuthorityID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, menu := range menus {
|
||||
hasMenu := false
|
||||
idStr := strconv.Itoa(int(menu.ID))
|
||||
for _, mid := range menuIds {
|
||||
if idStr == mid {
|
||||
hasMenu = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasMenu {
|
||||
return errors.New("添加失败,请勿跨级操作")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置菜单权限
|
||||
menuIds := make([]uint, len(menus))
|
||||
for i, m := range menus {
|
||||
menuIds[i] = m.ID
|
||||
}
|
||||
return uc.repo.SetMenuAuthority(ctx, authorityId, menuIds)
|
||||
}
|
||||
|
||||
// GetMenuAuthority 获取角色的菜单权限
|
||||
func (uc *MenuUsecase) GetMenuAuthority(ctx context.Context, authorityId uint) ([]*Menu, error) {
|
||||
menuIds, err := uc.repo.FindAuthorityMenuIds(ctx, authorityId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseMenus, err := uc.repo.FindBaseMenusByIds(ctx, menuIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
menus := make([]*Menu, len(baseMenus))
|
||||
for i, base := range baseMenus {
|
||||
menus[i] = &Menu{
|
||||
BaseMenu: *base,
|
||||
AuthorityId: authorityId,
|
||||
MenuId: base.ID,
|
||||
}
|
||||
}
|
||||
return menus, nil
|
||||
}
|
||||
Reference in New Issue
Block a user