任务三

This commit is contained in:
Yvan
2026-01-07 12:26:07 +08:00
parent a01fecdc11
commit 7df0450b1e
6 changed files with 2194 additions and 0 deletions
+268
View File
@@ -0,0 +1,268 @@
package system
import (
"context"
"errors"
"strings"
"kra/internal/biz/system"
"kra/internal/data/model"
"kra/internal/data/query"
"gorm.io/gorm"
)
type apiRepo struct {
db *gorm.DB
}
// NewApiRepo 创建API仓储
func NewApiRepo(db *gorm.DB) system.ApiRepo {
return &apiRepo{db: db}
}
func (r *apiRepo) Create(ctx context.Context, api *system.Api) error {
_, err := query.SysAPI.WithContext(ctx).Where(
query.SysAPI.Path.Eq(api.Path),
query.SysAPI.Method.Eq(api.Method),
).First()
if err == nil {
return errors.New("存在相同api")
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
m := toModelAPI(api)
return query.SysAPI.WithContext(ctx).Create(m)
}
func (r *apiRepo) Update(ctx context.Context, api *system.Api) error {
old, err := query.SysAPI.WithContext(ctx).Where(query.SysAPI.ID.Eq(int64(api.ID))).First()
if err != nil {
return err
}
if safeString(old.Path) != api.Path || safeString(old.Method) != api.Method {
dup, ferr := query.SysAPI.WithContext(ctx).Where(
query.SysAPI.Path.Eq(api.Path),
query.SysAPI.Method.Eq(api.Method),
).First()
if ferr == nil && dup.ID != int64(api.ID) {
return errors.New("存在相同api路径")
}
if ferr != nil && !errors.Is(ferr, gorm.ErrRecordNotFound) {
return ferr
}
}
m := toModelAPI(api)
_, err = query.SysAPI.WithContext(ctx).Where(query.SysAPI.ID.Eq(int64(api.ID))).Updates(m)
return err
}
func (r *apiRepo) Delete(ctx context.Context, id uint) error {
_, err := query.SysAPI.WithContext(ctx).Where(query.SysAPI.ID.Eq(int64(id))).Delete()
return err
}
func (r *apiRepo) DeleteByIds(ctx context.Context, ids []uint) error {
int64Ids := make([]int64, len(ids))
for i, id := range ids {
int64Ids[i] = int64(id)
}
_, err := query.SysAPI.WithContext(ctx).Where(query.SysAPI.ID.In(int64Ids...)).Delete()
return err
}
func (r *apiRepo) FindByID(ctx context.Context, id uint) (*system.Api, error) {
m, err := query.SysAPI.WithContext(ctx).Where(query.SysAPI.ID.Eq(int64(id))).First()
if err != nil {
return nil, err
}
return toBizAPI(m), nil
}
func (r *apiRepo) FindByPathMethod(ctx context.Context, path, method string) (*system.Api, error) {
m, err := query.SysAPI.WithContext(ctx).Where(
query.SysAPI.Path.Eq(path),
query.SysAPI.Method.Eq(method),
).First()
if err != nil {
return nil, err
}
return toBizAPI(m), nil
}
func (r *apiRepo) List(ctx context.Context, page, pageSize int, filters map[string]string, orderKey string, desc bool) ([]*system.Api, int64, error) {
q := query.SysAPI.WithContext(ctx)
if v, ok := filters["path"]; ok && v != "" {
q = q.Where(query.SysAPI.Path.Like("%" + v + "%"))
}
if v, ok := filters["description"]; ok && v != "" {
q = q.Where(query.SysAPI.Description.Like("%" + v + "%"))
}
if v, ok := filters["method"]; ok && v != "" {
q = q.Where(query.SysAPI.Method.Eq(v))
}
if v, ok := filters["api_group"]; ok && v != "" {
q = q.Where(query.SysAPI.APIGroup.Eq(v))
}
total, err := q.Count()
if err != nil {
return nil, 0, err
}
orderMap := map[string]bool{"id": true, "path": true, "api_group": true, "description": true, "method": true}
if orderKey != "" && orderMap[orderKey] {
switch orderKey {
case "id":
if desc {
q = q.Order(query.SysAPI.ID.Desc())
} else {
q = q.Order(query.SysAPI.ID)
}
case "path":
if desc {
q = q.Order(query.SysAPI.Path.Desc())
} else {
q = q.Order(query.SysAPI.Path)
}
case "api_group":
if desc {
q = q.Order(query.SysAPI.APIGroup.Desc())
} else {
q = q.Order(query.SysAPI.APIGroup)
}
case "description":
if desc {
q = q.Order(query.SysAPI.Description.Desc())
} else {
q = q.Order(query.SysAPI.Description)
}
case "method":
if desc {
q = q.Order(query.SysAPI.Method.Desc())
} else {
q = q.Order(query.SysAPI.Method)
}
}
} else {
q = q.Order(query.SysAPI.ID.Desc())
}
offset := (page - 1) * pageSize
list, err := q.Offset(offset).Limit(pageSize).Find()
if err != nil {
return nil, 0, err
}
apis := make([]*system.Api, len(list))
for i, m := range list {
apis[i] = toBizAPI(m)
}
return apis, total, nil
}
func (r *apiRepo) FindAll(ctx context.Context) ([]*system.Api, error) {
list, err := query.SysAPI.WithContext(ctx).Order(query.SysAPI.ID.Desc()).Find()
if err != nil {
return nil, err
}
apis := make([]*system.Api, len(list))
for i, m := range list {
apis[i] = toBizAPI(m)
}
return apis, nil
}
func (r *apiRepo) FindByIds(ctx context.Context, ids []uint) ([]*system.Api, error) {
int64Ids := make([]int64, len(ids))
for i, id := range ids {
int64Ids[i] = int64(id)
}
list, err := query.SysAPI.WithContext(ctx).Where(query.SysAPI.ID.In(int64Ids...)).Find()
if err != nil {
return nil, err
}
apis := make([]*system.Api, len(list))
for i, m := range list {
apis[i] = toBizAPI(m)
}
return apis, nil
}
func (r *apiRepo) GetApiGroups(ctx context.Context) ([]string, map[string]string, error) {
list, err := query.SysAPI.WithContext(ctx).Find()
if err != nil {
return nil, nil, err
}
groupMap := make(map[string]bool)
apiGroupMap := make(map[string]string)
var groups []string
for _, m := range list {
path := safeString(m.Path)
apiGroup := safeString(m.APIGroup)
if !groupMap[apiGroup] {
groupMap[apiGroup] = true
groups = append(groups, apiGroup)
}
if len(path) > 1 {
parts := strings.Split(path, "/")
if len(parts) > 1 {
apiGroupMap[parts[1]] = apiGroup
}
}
}
return groups, apiGroupMap, nil
}
func (r *apiRepo) CreateIgnoreApi(ctx context.Context, path, method string) error {
m := &model.SysIgnoreAPI{
Path: &path,
Method: &method,
}
return r.db.WithContext(ctx).Create(m).Error
}
func (r *apiRepo) DeleteIgnoreApi(ctx context.Context, path, method string) error {
return r.db.WithContext(ctx).Unscoped().Where("path = ? AND method = ?", path, method).Delete(&model.SysIgnoreAPI{}).Error
}
func (r *apiRepo) FindAllIgnoreApis(ctx context.Context) ([]*system.IgnoreApi, error) {
var list []model.SysIgnoreAPI
if err := r.db.WithContext(ctx).Find(&list).Error; err != nil {
return nil, err
}
result := make([]*system.IgnoreApi, len(list))
for i, m := range list {
result[i] = &system.IgnoreApi{
Path: safeString(m.Path),
Method: safeString(m.Method),
}
}
return result, nil
}
// 转换函数
func toModelAPI(a *system.Api) *model.SysAPI {
return &model.SysAPI{
ID: int64(a.ID),
Path: &a.Path,
Description: &a.Description,
APIGroup: &a.ApiGroup,
Method: &a.Method,
}
}
func toBizAPI(m *model.SysAPI) *system.Api {
return &system.Api{
ID: uint(m.ID),
Path: safeString(m.Path),
Description: safeString(m.Description),
ApiGroup: safeString(m.APIGroup),
Method: safeString(m.Method),
}
}
+485
View File
@@ -0,0 +1,485 @@
package system
import (
"context"
"errors"
"strconv"
"kra/internal/biz/system"
"kra/internal/data/model"
"gorm.io/gorm"
)
type menuRepo struct {
db *gorm.DB
}
// NewMenuRepo 创建菜单仓储
func NewMenuRepo(db *gorm.DB) system.MenuRepo {
return &menuRepo{db: db}
}
// CreateBaseMenu 创建基础菜单
func (r *menuRepo) CreateBaseMenu(ctx context.Context, menu *system.BaseMenu) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 检查name是否重复
var count int64
if err := tx.Model(&model.SysBaseMenu{}).Where("name = ?", menu.Name).Count(&count).Error; err != nil {
return err
}
if count > 0 {
return errors.New("存在重复name,请修改name")
}
if menu.ParentId != 0 {
// 检查父菜单是否存在
var parentMenu model.SysBaseMenu
if err := tx.First(&parentMenu, menu.ParentId).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("父菜单不存在")
}
return err
}
// 检查父菜单下现有子菜单数量
var existingChildrenCount int64
if err := tx.Model(&model.SysBaseMenu{}).Where("parent_id = ?", menu.ParentId).Count(&existingChildrenCount).Error; err != nil {
return err
}
// 如果父菜单原本是叶子菜单,需要清空其权限分配
if existingChildrenCount == 0 {
// 检查父菜单是否被其他角色设置为首页
var defaultRouterCount int64
if err := tx.Model(&model.SysAuthority{}).Where("default_router = ?", parentMenu.Name).Count(&defaultRouterCount).Error; err != nil {
return err
}
if defaultRouterCount > 0 {
return errors.New("父菜单已被其他角色的首页占用,请先释放父菜单的首页权限")
}
// 清空父菜单的所有权限分配
if err := tx.Where("sys_base_menu_id = ?", menu.ParentId).Delete(&model.SysAuthorityMenu{}).Error; err != nil {
return err
}
}
}
// 创建菜单
m := toModelBaseMenu(menu)
if err := tx.Create(m).Error; err != nil {
return err
}
// 创建参数
if len(menu.Parameters) > 0 {
for _, p := range menu.Parameters {
param := &model.SysBaseMenuParameter{
SysBaseMenuID: ptrInt64(int64(m.ID)),
Type: &p.Type,
Key: &p.Key,
Value: &p.Value,
}
if err := tx.Create(param).Error; err != nil {
return err
}
}
}
// 创建按钮
if len(menu.MenuBtn) > 0 {
for _, b := range menu.MenuBtn {
btn := &model.SysBaseMenuBtn{
SysBaseMenuID: ptrInt64(int64(m.ID)),
Name: &b.Name,
Desc: &b.Desc,
}
if err := tx.Create(btn).Error; err != nil {
return err
}
}
}
menu.ID = uint(m.ID)
return nil
})
}
// UpdateBaseMenu 更新基础菜单
func (r *menuRepo) UpdateBaseMenu(ctx context.Context, menu *system.BaseMenu) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var oldMenu model.SysBaseMenu
if err := tx.Where("id = ?", menu.ID).First(&oldMenu).Error; err != nil {
return err
}
// 检查name是否重复
if safeString(oldMenu.Name) != menu.Name {
var count int64
if err := tx.Model(&model.SysBaseMenu{}).Where("id <> ? AND name = ?", menu.ID, menu.Name).Count(&count).Error; err != nil {
return err
}
if count > 0 {
return errors.New("存在相同name修改失败")
}
}
// 删除旧参数和按钮
if err := tx.Unscoped().Where("sys_base_menu_id = ?", menu.ID).Delete(&model.SysBaseMenuParameter{}).Error; err != nil {
return err
}
if err := tx.Unscoped().Where("sys_base_menu_id = ?", menu.ID).Delete(&model.SysBaseMenuBtn{}).Error; err != nil {
return err
}
// 创建新参数
if len(menu.Parameters) > 0 {
for _, p := range menu.Parameters {
param := &model.SysBaseMenuParameter{
SysBaseMenuID: ptrInt64(int64(menu.ID)),
Type: &p.Type,
Key: &p.Key,
Value: &p.Value,
}
if err := tx.Create(param).Error; err != nil {
return err
}
}
}
// 创建新按钮
if len(menu.MenuBtn) > 0 {
for _, b := range menu.MenuBtn {
btn := &model.SysBaseMenuBtn{
SysBaseMenuID: ptrInt64(int64(menu.ID)),
Name: &b.Name,
Desc: &b.Desc,
}
if err := tx.Create(btn).Error; err != nil {
return err
}
}
}
// 更新菜单
updates := map[string]any{
"keep_alive": menu.KeepAlive,
"close_tab": menu.CloseTab,
"default_menu": menu.DefaultMenu,
"parent_id": menu.ParentId,
"path": menu.Path,
"name": menu.Name,
"hidden": menu.Hidden,
"component": menu.Component,
"title": menu.Title,
"active_name": menu.ActiveName,
"icon": menu.Icon,
"sort": menu.Sort,
"transition_type": menu.TransitionType,
}
return tx.Model(&oldMenu).Updates(updates).Error
})
}
// DeleteBaseMenu 删除基础菜单
func (r *menuRepo) DeleteBaseMenu(ctx context.Context, id uint) error {
// 检查是否有子菜单
var childCount int64
if err := r.db.WithContext(ctx).Model(&model.SysBaseMenu{}).Where("parent_id = ?", id).Count(&childCount).Error; err != nil {
return err
}
if childCount > 0 {
return errors.New("此菜单存在子菜单不可删除")
}
// 获取菜单信息
var menu model.SysBaseMenu
if err := r.db.WithContext(ctx).First(&menu, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("记录不存在")
}
return err
}
// 检查是否有角色将此菜单作为首页
var defaultRouterCount int64
if err := r.db.WithContext(ctx).Model(&model.SysAuthority{}).Where("default_router = ?", menu.Name).Count(&defaultRouterCount).Error; err != nil {
return err
}
if defaultRouterCount > 0 {
return errors.New("此菜单有角色正在作为首页,不可删除")
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Delete(&model.SysBaseMenu{}, "id = ?", id).Error; err != nil {
return err
}
if err := tx.Delete(&model.SysBaseMenuParameter{}, "sys_base_menu_id = ?", id).Error; err != nil {
return err
}
if err := tx.Delete(&model.SysBaseMenuBtn{}, "sys_base_menu_id = ?", id).Error; err != nil {
return err
}
if err := tx.Delete(&model.SysAuthorityBtn{}, "sys_menu_id = ?", id).Error; err != nil {
return err
}
if err := tx.Delete(&model.SysAuthorityMenu{}, "sys_base_menu_id = ?", id).Error; err != nil {
return err
}
return nil
})
}
// FindBaseMenuByID 根据ID获取基础菜单
func (r *menuRepo) FindBaseMenuByID(ctx context.Context, id uint) (*system.BaseMenu, error) {
var menu model.SysBaseMenu
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&menu).Error; err != nil {
return nil, err
}
// 获取参数
var params []model.SysBaseMenuParameter
r.db.WithContext(ctx).Where("sys_base_menu_id = ?", id).Find(&params)
// 获取按钮
var btns []model.SysBaseMenuBtn
r.db.WithContext(ctx).Where("sys_base_menu_id = ?", id).Find(&btns)
return toBizBaseMenu(&menu, params, btns), nil
}
// FindAllBaseMenus 获取所有基础菜单
func (r *menuRepo) FindAllBaseMenus(ctx context.Context) ([]*system.BaseMenu, error) {
var menus []model.SysBaseMenu
if err := r.db.WithContext(ctx).Order("sort").Find(&menus).Error; err != nil {
return nil, err
}
// 获取所有参数
var allParams []model.SysBaseMenuParameter
r.db.WithContext(ctx).Find(&allParams)
paramMap := make(map[int64][]model.SysBaseMenuParameter)
for _, p := range allParams {
if p.SysBaseMenuID != nil {
paramMap[*p.SysBaseMenuID] = append(paramMap[*p.SysBaseMenuID], p)
}
}
// 获取所有按钮
var allBtns []model.SysBaseMenuBtn
r.db.WithContext(ctx).Find(&allBtns)
btnMap := make(map[int64][]model.SysBaseMenuBtn)
for _, b := range allBtns {
if b.SysBaseMenuID != nil {
btnMap[*b.SysBaseMenuID] = append(btnMap[*b.SysBaseMenuID], b)
}
}
result := make([]*system.BaseMenu, len(menus))
for i, m := range menus {
result[i] = toBizBaseMenu(&m, paramMap[m.ID], btnMap[m.ID])
}
return result, nil
}
// FindBaseMenusByIds 根据ID列表获取基础菜单
func (r *menuRepo) FindBaseMenusByIds(ctx context.Context, ids []string) ([]*system.BaseMenu, error) {
var menus []model.SysBaseMenu
if err := r.db.WithContext(ctx).Where("id in (?)", ids).Order("sort").Find(&menus).Error; err != nil {
return nil, err
}
// 获取参数
var allParams []model.SysBaseMenuParameter
r.db.WithContext(ctx).Where("sys_base_menu_id in (?)", ids).Find(&allParams)
paramMap := make(map[int64][]model.SysBaseMenuParameter)
for _, p := range allParams {
if p.SysBaseMenuID != nil {
paramMap[*p.SysBaseMenuID] = append(paramMap[*p.SysBaseMenuID], p)
}
}
// 获取按钮
var allBtns []model.SysBaseMenuBtn
r.db.WithContext(ctx).Where("sys_base_menu_id in (?)", ids).Find(&allBtns)
btnMap := make(map[int64][]model.SysBaseMenuBtn)
for _, b := range allBtns {
if b.SysBaseMenuID != nil {
btnMap[*b.SysBaseMenuID] = append(btnMap[*b.SysBaseMenuID], b)
}
}
result := make([]*system.BaseMenu, len(menus))
for i, m := range menus {
result[i] = toBizBaseMenu(&m, paramMap[m.ID], btnMap[m.ID])
}
return result, nil
}
// FindAuthorityMenuIds 获取角色的菜单ID列表
func (r *menuRepo) FindAuthorityMenuIds(ctx context.Context, authorityId uint) ([]string, error) {
var authorityMenus []model.SysAuthorityMenu
if err := r.db.WithContext(ctx).Where("sys_authority_authority_id = ?", authorityId).Find(&authorityMenus).Error; err != nil {
return nil, err
}
menuIds := make([]string, len(authorityMenus))
for i, am := range authorityMenus {
menuIds[i] = strconv.FormatInt(am.SysBaseMenuID, 10)
}
return menuIds, nil
}
// FindAuthorityBtns 获取角色的按钮权限
func (r *menuRepo) FindAuthorityBtns(ctx context.Context, authorityId uint) ([]*system.AuthorityBtn, error) {
var btns []model.SysAuthorityBtn
if err := r.db.WithContext(ctx).Where("authority_id = ?", authorityId).Find(&btns).Error; err != nil {
return nil, err
}
// 获取按钮名称
var btnIds []int64
for _, b := range btns {
if b.SysBaseMenuBtnID != nil {
btnIds = append(btnIds, *b.SysBaseMenuBtnID)
}
}
btnNameMap := make(map[int64]string)
if len(btnIds) > 0 {
var menuBtns []model.SysBaseMenuBtn
r.db.WithContext(ctx).Where("id in (?)", btnIds).Find(&menuBtns)
for _, mb := range menuBtns {
btnNameMap[mb.ID] = safeString(mb.Name)
}
}
result := make([]*system.AuthorityBtn, len(btns))
for i, b := range btns {
result[i] = &system.AuthorityBtn{}
if b.AuthorityID != nil {
result[i].AuthorityId = uint(*b.AuthorityID)
}
if b.SysMenuID != nil {
result[i].SysMenuID = uint(*b.SysMenuID)
}
if b.SysBaseMenuBtnID != nil {
result[i].SysBaseMenuBtnID = uint(*b.SysBaseMenuBtnID)
result[i].BtnName = btnNameMap[*b.SysBaseMenuBtnID]
}
}
return result, nil
}
// SetMenuAuthority 设置角色菜单权限
func (r *menuRepo) SetMenuAuthority(ctx context.Context, authorityId uint, menuIds []uint) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 删除旧权限
if err := tx.Where("sys_authority_authority_id = ?", authorityId).Delete(&model.SysAuthorityMenu{}).Error; err != nil {
return err
}
// 创建新权限
for _, menuId := range menuIds {
am := &model.SysAuthorityMenu{
SysAuthorityAuthorityID: int64(authorityId),
SysBaseMenuID: int64(menuId),
}
if err := tx.Create(am).Error; err != nil {
return err
}
}
return nil
})
}
// 转换函数
func toModelBaseMenu(m *system.BaseMenu) *model.SysBaseMenu {
parentId := int64(m.ParentId)
sort := int64(m.Sort)
return &model.SysBaseMenu{
ID: int64(m.ID),
ParentID: &parentId,
Path: &m.Path,
Name: &m.Name,
Hidden: &m.Hidden,
Component: &m.Component,
Sort: &sort,
ActiveName: &m.ActiveName,
KeepAlive: &m.KeepAlive,
DefaultMenu: &m.DefaultMenu,
Title: &m.Title,
Icon: &m.Icon,
CloseTab: &m.CloseTab,
TransitionType: &m.TransitionType,
}
}
func toBizBaseMenu(m *model.SysBaseMenu, params []model.SysBaseMenuParameter, btns []model.SysBaseMenuBtn) *system.BaseMenu {
menu := &system.BaseMenu{
ID: uint(m.ID),
Path: safeString(m.Path),
Name: safeString(m.Name),
Component: safeString(m.Component),
ActiveName: safeString(m.ActiveName),
Title: safeString(m.Title),
Icon: safeString(m.Icon),
TransitionType: safeString(m.TransitionType),
}
if m.ParentID != nil {
menu.ParentId = uint(*m.ParentID)
}
if m.Sort != nil {
menu.Sort = int(*m.Sort)
}
if m.Hidden != nil {
menu.Hidden = *m.Hidden
}
if m.KeepAlive != nil {
menu.KeepAlive = *m.KeepAlive
}
if m.DefaultMenu != nil {
menu.DefaultMenu = *m.DefaultMenu
}
if m.CloseTab != nil {
menu.CloseTab = *m.CloseTab
}
// 转换参数
if len(params) > 0 {
menu.Parameters = make([]*system.MenuParameter, len(params))
for i, p := range params {
menu.Parameters[i] = &system.MenuParameter{
ID: uint(p.ID),
Type: safeString(p.Type),
Key: safeString(p.Key),
Value: safeString(p.Value),
}
if p.SysBaseMenuID != nil {
menu.Parameters[i].SysBaseMenuID = uint(*p.SysBaseMenuID)
}
}
}
// 转换按钮
if len(btns) > 0 {
menu.MenuBtn = make([]*system.MenuBtn, len(btns))
for i, b := range btns {
menu.MenuBtn[i] = &system.MenuBtn{
ID: uint(b.ID),
Name: safeString(b.Name),
Desc: safeString(b.Desc),
}
if b.SysBaseMenuID != nil {
menu.MenuBtn[i].SysBaseMenuID = uint(*b.SysBaseMenuID)
}
}
}
return menu
}
func ptrInt64(v int64) *int64 {
return &v
}