first commit

This commit is contained in:
2025-10-07 11:48:29 +08:00
commit ea18abdb04
555 changed files with 58225 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
package admin_menu
import (
"context"
"database/sql"
"encoding/json"
"time"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/app/main/model"
"hm-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateMenuLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateMenuLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateMenuLogic {
return &CreateMenuLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateMenuLogic) CreateMenu(req *types.CreateMenuReq) (resp *types.CreateMenuResp, err error) {
// 1. 参数验证
if req.Name == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("菜单名称不能为空"), "菜单名称不能为空")
}
if req.Type == "menu" && req.Component == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("组件路径不能为空"), "组件路径不能为空")
}
// 2. 检查名称和路径是否重复
exists, err := l.svcCtx.AdminMenuModel.FindOneByNamePath(l.ctx, req.Name, req.Path)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, err: %v", err)
}
if exists != nil {
return nil, errors.Wrapf(xerr.NewErrMsg("菜单名称或路径已存在"), "菜单名称或路径已存在")
}
// 3. 检查父菜单是否存在(如果不是根菜单)
if req.Pid > 0 {
parentMenu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Pid)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询父菜单失败, id: %d, err: %v", req.Pid, err)
}
if parentMenu == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "父菜单不存在, id: %d", req.Pid)
}
}
// 4. 将类型标签转换为值
typeValue, err := l.svcCtx.DictService.GetDictValue(l.ctx, "admin_menu_type", req.Type)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "菜单类型无效: %v", err)
}
// 5. 创建菜单记录
menu := &model.AdminMenu{
Pid: req.Pid,
Name: req.Name,
Path: req.Path,
Component: req.Component,
Redirect: sql.NullString{String: req.Redirect, Valid: req.Redirect != ""},
Status: req.Status,
Type: typeValue,
Sort: req.Sort,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
// 将Meta转换为JSON字符串
metaJson, err := json.Marshal(req.Meta)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Meta数据格式错误: %v", err)
}
menu.Meta = string(metaJson)
// 6. 保存到数据库
_, err = l.svcCtx.AdminMenuModel.Insert(l.ctx, nil, menu)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建菜单失败, err: %v", err)
}
return &types.CreateMenuResp{
Id: menu.Id,
}, nil
}

View File

@@ -0,0 +1,75 @@
package admin_menu
import (
"context"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteMenuLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeleteMenuLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteMenuLogic {
return &DeleteMenuLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteMenuLogic) DeleteMenu(req *types.DeleteMenuReq) (resp *types.DeleteMenuResp, err error) {
// 1. 参数验证
if req.Id <= 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.PARAM_VERIFICATION_ERROR),
"菜单ID必须大于0, id: %d", req.Id)
}
// 2. 查询菜单是否存在
menu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查找菜单失败, err: %v, id: %d", err, req.Id)
}
// 3. 检查是否有子菜单
childMenuBuilder := l.svcCtx.AdminMenuModel.SelectBuilder().Where("pid = ?", req.Id)
childMenus, err := l.svcCtx.AdminMenuModel.FindAll(l.ctx, childMenuBuilder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询子菜单失败, err: %v, parent_id: %d", err, req.Id)
}
if len(childMenus) > 0 {
return nil, errors.Wrapf(xerr.NewErrMsg("该菜单下还有子菜单,无法删除"),
"该菜单下还有子菜单,无法删除, id: %d", req.Id)
}
// 4. 检查是否有角色关联该菜单
roleMenuBuilder := l.svcCtx.AdminRoleMenuModel.SelectBuilder().Where("menu_id = ?", req.Id)
roleMenus, err := l.svcCtx.AdminRoleMenuModel.FindAll(l.ctx, roleMenuBuilder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查询角色菜单关联失败, err: %v, menu_id: %d", err, req.Id)
}
if len(roleMenus) > 0 {
return nil, errors.Wrapf(xerr.NewErrMsg("该菜单已被角色使用,无法删除"),
"该菜单已被角色使用,无法删除, id: %d", req.Id)
}
// 5. 执行软删除
err = l.svcCtx.AdminMenuModel.DeleteSoft(l.ctx, nil, menu)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"删除菜单失败, err: %v, id: %d", err, req.Id)
}
// 6. 返回成功结果
return &types.DeleteMenuResp{Success: true}, nil
}

View File

@@ -0,0 +1,250 @@
package admin_menu
import (
"context"
"sort"
"strconv"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/common/ctxdata"
"hm-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/bytedance/sonic"
"github.com/pkg/errors"
"github.com/samber/lo"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type GetMenuAllLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetMenuAllLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMenuAllLogic {
return &GetMenuAllLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetMenuAllLogic) GetMenuAll(req *types.GetMenuAllReq) (resp *[]types.GetMenuAllResp, err error) {
userId, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败, %+v", err)
}
// 使用MapReduceVoid并发获取用户角色
var roleIds []int64
var permissions []*struct {
RoleId int64
}
type UserRoleResult struct {
RoleId int64
}
err = mr.MapReduceVoid(
func(source chan<- interface{}) {
adminUserRoleBuilder := l.svcCtx.AdminUserRoleModel.SelectBuilder().Where(squirrel.Eq{"user_id": userId})
source <- adminUserRoleBuilder
},
func(item interface{}, writer mr.Writer[*UserRoleResult], cancel func(error)) {
builder := item.(squirrel.SelectBuilder)
result, err := l.svcCtx.AdminUserRoleModel.FindAll(l.ctx, builder, "role_id DESC")
if err != nil {
cancel(errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户角色信息失败, %+v", err))
return
}
for _, r := range result {
writer.Write(&UserRoleResult{RoleId: r.RoleId})
}
},
func(pipe <-chan *UserRoleResult, cancel func(error)) {
for item := range pipe {
permissions = append(permissions, &struct{ RoleId int64 }{RoleId: item.RoleId})
}
},
)
if err != nil {
return nil, err
}
for _, permission := range permissions {
roleIds = append(roleIds, permission.RoleId)
}
// 使用MapReduceVoid并发获取角色菜单
var menuIds []int64
var roleMenus []*struct {
MenuId int64
}
type RoleMenuResult struct {
MenuId int64
}
err = mr.MapReduceVoid(
func(source chan<- interface{}) {
getRoleMenuBuilder := l.svcCtx.AdminRoleMenuModel.SelectBuilder().Where(squirrel.Eq{"role_id": roleIds})
source <- getRoleMenuBuilder
},
func(item interface{}, writer mr.Writer[*RoleMenuResult], cancel func(error)) {
builder := item.(squirrel.SelectBuilder)
result, err := l.svcCtx.AdminRoleMenuModel.FindAll(l.ctx, builder, "id DESC")
if err != nil {
cancel(errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取角色菜单信息失败, %+v", err))
return
}
for _, r := range result {
writer.Write(&RoleMenuResult{MenuId: r.MenuId})
}
},
func(pipe <-chan *RoleMenuResult, cancel func(error)) {
for item := range pipe {
roleMenus = append(roleMenus, &struct{ MenuId int64 }{MenuId: item.MenuId})
}
},
)
if err != nil {
return nil, err
}
for _, roleMenu := range roleMenus {
menuIds = append(menuIds, roleMenu.MenuId)
}
// 使用MapReduceVoid并发获取菜单
type AdminMenuStruct struct {
Id int64
Pid int64
Name string
Path string
Component string
Redirect struct {
String string
Valid bool
}
Meta string
Sort int64
Type int64
Status int64
}
var menus []*AdminMenuStruct
err = mr.MapReduceVoid(
func(source chan<- interface{}) {
adminMenuBuilder := l.svcCtx.AdminMenuModel.SelectBuilder().Where(squirrel.Eq{"id": menuIds})
source <- adminMenuBuilder
},
func(item interface{}, writer mr.Writer[*AdminMenuStruct], cancel func(error)) {
builder := item.(squirrel.SelectBuilder)
result, err := l.svcCtx.AdminMenuModel.FindAll(l.ctx, builder, "sort ASC")
if err != nil {
cancel(errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取菜单信息失败, %+v", err))
return
}
for _, r := range result {
menu := &AdminMenuStruct{
Id: r.Id,
Pid: r.Pid,
Name: r.Name,
Path: r.Path,
Component: r.Component,
Redirect: r.Redirect,
Meta: r.Meta,
Sort: r.Sort,
Type: r.Type,
Status: r.Status,
}
writer.Write(menu)
}
},
func(pipe <-chan *AdminMenuStruct, cancel func(error)) {
for item := range pipe {
menus = append(menus, item)
}
},
)
if err != nil {
return nil, err
}
// 转换为types.Menu结构并存储到映射表
menuMap := make(map[string]types.GetMenuAllResp)
for _, menu := range menus {
// 只处理状态正常的菜单
if menu.Status != 1 {
continue
}
meta := make(map[string]interface{})
err = sonic.Unmarshal([]byte(menu.Meta), &meta)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析菜单Meta信息失败, %+v", err)
}
redirect := func() string {
if menu.Redirect.Valid {
return menu.Redirect.String
}
return ""
}()
menuId := strconv.FormatInt(menu.Id, 10)
menuMap[menuId] = types.GetMenuAllResp{
Name: menu.Name,
Path: menu.Path,
Redirect: redirect,
Component: menu.Component,
Sort: menu.Sort,
Meta: meta,
Children: make([]types.GetMenuAllResp, 0),
}
}
// 按ParentId将菜单分组
menuGroups := lo.GroupBy(menus, func(item *AdminMenuStruct) int64 {
return item.Pid
})
// 递归构建菜单树
var buildMenuTree func(parentId int64) []types.GetMenuAllResp
buildMenuTree = func(parentId int64) []types.GetMenuAllResp {
children := make([]types.GetMenuAllResp, 0)
childMenus, ok := menuGroups[parentId]
if !ok {
return children
}
// 按Sort排序
sort.Slice(childMenus, func(i, j int) bool {
return childMenus[i].Sort < childMenus[j].Sort
})
for _, childMenu := range childMenus {
menuId := strconv.FormatInt(childMenu.Id, 10)
if menu, exists := menuMap[menuId]; exists && childMenu.Status == 1 {
// 递归构建子菜单
menu.Children = buildMenuTree(childMenu.Id)
children = append(children, menu)
}
}
return children
}
// 从根菜单开始构建ParentId为0的是根菜单
menuTree := buildMenuTree(0)
return &menuTree, nil
}

View File

@@ -0,0 +1,30 @@
package admin_menu
import (
"context"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetMenuDetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetMenuDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMenuDetailLogic {
return &GetMenuDetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetMenuDetailLogic) GetMenuDetail(req *types.GetMenuDetailReq) (resp *types.GetMenuDetailResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -0,0 +1,109 @@
package admin_menu
import (
"context"
"encoding/json"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GetMenuListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetMenuListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMenuListLogic {
return &GetMenuListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetMenuListLogic) GetMenuList(req *types.GetMenuListReq) (resp []types.MenuListItem, err error) {
// 构建查询条件
builder := l.svcCtx.AdminMenuModel.SelectBuilder()
// 添加筛选条件
if len(req.Name) > 0 {
builder = builder.Where("name LIKE ?", "%"+req.Name+"%")
}
if len(req.Path) > 0 {
builder = builder.Where("path LIKE ?", "%"+req.Path+"%")
}
if req.Status != -1 {
builder = builder.Where("status = ?", req.Status)
}
if req.Type != "" {
builder = builder.Where("type = ?", req.Type)
}
// 排序但不分页,获取所有符合条件的菜单
builder = builder.OrderBy("sort ASC")
// 获取所有菜单
menus, err := l.svcCtx.AdminMenuModel.FindAll(l.ctx, builder, "id ASC")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, err: %v", err)
}
// 将菜单按ID存入map
menuMap := make(map[int64]types.MenuListItem)
for _, menu := range menus {
var meta map[string]interface{}
err := json.Unmarshal([]byte(menu.Meta), &meta)
if err != nil {
logx.Errorf("解析Meta字段失败: %v", err)
meta = make(map[string]interface{})
}
menuType, err := l.svcCtx.DictService.GetDictLabel(l.ctx, "admin_menu_type", menu.Type)
if err != nil {
logx.Errorf("获取菜单类型失败: %v", err)
menuType = ""
}
item := types.MenuListItem{
Id: menu.Id,
Pid: menu.Pid,
Name: menu.Name,
Path: menu.Path,
Component: menu.Component,
Redirect: menu.Redirect.String,
Meta: meta,
Status: menu.Status,
Type: menuType,
Sort: menu.Sort,
CreateTime: menu.CreateTime.Format("2006-01-02 15:04:05"),
Children: make([]types.MenuListItem, 0),
}
menuMap[menu.Id] = item
}
// 构建父子关系
for _, menu := range menus {
if menu.Pid > 0 {
// 找到父菜单
if parent, exists := menuMap[menu.Pid]; exists {
// 添加当前菜单到父菜单的子菜单列表
children := append(parent.Children, menuMap[menu.Id])
parent.Children = children
menuMap[menu.Pid] = parent
}
}
}
// 提取顶级菜单ParentId为0到响应列表
result := make([]types.MenuListItem, 0)
for _, menu := range menus {
if menu.Pid == 0 {
result = append(result, menuMap[menu.Id])
}
}
return result, nil
}

View File

@@ -0,0 +1,96 @@
package admin_menu
import (
"context"
"database/sql"
"encoding/json"
"hm-server/app/main/api/internal/svc"
"hm-server/app/main/api/internal/types"
"hm-server/app/main/model"
"hm-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateMenuLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpdateMenuLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateMenuLogic {
return &UpdateMenuLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateMenuLogic) UpdateMenu(req *types.UpdateMenuReq) (resp *types.UpdateMenuResp, err error) {
// 1. 检查菜单是否存在
menu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, id: %d, err: %v", req.Id, err)
}
if menu == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "菜单不存在, id: %d", req.Id)
}
// 2. 将类型标签转换为值
typeValue, err := l.svcCtx.DictService.GetDictValue(l.ctx, "admin_menu_type", req.Type)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "菜单类型无效: %v", err)
}
// 3. 检查父菜单是否存在(如果不是根菜单)
if req.Pid > 0 {
parentMenu, err := l.svcCtx.AdminMenuModel.FindOne(l.ctx, req.Pid)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询父菜单失败, id: %d, err: %v", req.Pid, err)
}
if parentMenu == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "父菜单不存在, id: %d", req.Pid)
}
}
// 4. 检查名称和路径是否重复
if req.Name != menu.Name || req.Path != menu.Path {
exists, err := l.svcCtx.AdminMenuModel.FindOneByNamePath(l.ctx, req.Name, req.Path)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询菜单失败, err: %v", err)
}
if exists != nil && exists.Id != req.Id {
return nil, errors.Wrapf(xerr.NewErrMsg("菜单名称或路径已存在"), "菜单名称或路径已存在")
}
}
// 5. 更新菜单信息
menu.Pid = req.Pid
menu.Name = req.Name
menu.Path = req.Path
menu.Component = req.Component
menu.Redirect = sql.NullString{String: req.Redirect, Valid: req.Redirect != ""}
menu.Status = req.Status
menu.Type = typeValue
menu.Sort = req.Sort
// 将Meta转换为JSON字符串
metaJson, err := json.Marshal(req.Meta)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Meta数据格式错误: %v", err)
}
menu.Meta = string(metaJson)
// 6. 保存更新
_, err = l.svcCtx.AdminMenuModel.Update(l.ctx, nil, menu)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新菜单失败, err: %v", err)
}
return &types.UpdateMenuResp{
Success: true,
}, nil
}