菜单接口

This commit is contained in:
TsMask
2023-08-29 16:14:48 +08:00
parent 70250ce6e6
commit 005b12e60f
5 changed files with 1272 additions and 0 deletions

View File

@@ -0,0 +1,353 @@
package sysmenu
import (
"fmt"
"net/http"
"ems.agt/features/sys_menu/consts"
"ems.agt/features/sys_menu/model"
"ems.agt/features/sys_menu/service"
"ems.agt/lib/core/conf"
"ems.agt/lib/core/utils/ctx"
"ems.agt/lib/core/utils/regular"
"ems.agt/lib/core/vo/result"
"ems.agt/lib/midware"
"ems.agt/lib/services"
"ems.agt/restagent/config"
)
// 菜单接口添加到路由
func Routers() []services.RouterItem {
// 实例化控制层 SysMenuApi 结构体
var apis = &SysMenuApi{
sysMenuService: service.NewServiceSysMenu,
}
rs := [...]services.RouterItem{
{
Method: "GET",
Pattern: "/menuManage/{apiVersion}/list",
Handler: apis.List,
Middleware: midware.Authorize(map[string][]string{
"hasPerms": {"system:menu:list"},
}),
},
{
Method: "GET",
Pattern: "/menuManage/{apiVersion}/info/{menuId}",
Handler: apis.Info,
Middleware: midware.Authorize(map[string][]string{
"hasPerms": {"system:menu:query"},
}),
},
{
Method: "POST",
Pattern: "/menuManage/{apiVersion}/add",
Handler: apis.Add,
Middleware: midware.Authorize(map[string][]string{
"hasPerms": {"system:menu:add"},
}),
},
{
Method: "PUT",
Pattern: "/menuManage/{apiVersion}/edit",
Handler: apis.Edit,
Middleware: midware.Authorize(map[string][]string{
"hasPerms": {"system:menu:edit"},
}),
},
{
Method: "DELETE",
Pattern: "/menuManage/{apiVersion}/del/{menuId}",
Handler: apis.Remove,
Middleware: midware.Authorize(map[string][]string{
"hasPerms": {"system:menu:edit"},
}),
},
{
Method: "GET",
Pattern: "/menuManage/{apiVersion}/treeSelect",
Handler: apis.TreeSelect,
Middleware: midware.Authorize(map[string][]string{
"hasPerms": {"system:menu:list"},
}),
},
{
Method: "GET",
Pattern: "/menuManage/{apiVersion}/roleMenuTreeSelect/{roleId}",
Handler: apis.RoleMenuTreeSelect,
Middleware: midware.Authorize(map[string][]string{
"hasPerms": {"system:menu:list"},
}),
},
// 添加更多的 Router 对象...
}
// 生成两组前缀路由
rsPrefix := []services.RouterItem{}
for _, v := range rs {
path := v.Pattern
// 固定前缀
v.Pattern = config.DefaultUriPrefix + path
rsPrefix = append(rsPrefix, v)
// 可配置
v.Pattern = config.UriPrefix + path
rsPrefix = append(rsPrefix, v)
}
return rsPrefix
}
// // 实例化控制层 SysMenuApi 结构体
// var NewSysMenu = &SysMenuApi{
// sysMenuService: NewServiceSysMenu,
// }
// 菜单信息
//
// PATH /menuManage
type SysMenuApi struct {
// 菜单服务
sysMenuService *service.ServiceSysMenu
}
// 菜单列表
//
// GET /list
func (s *SysMenuApi) List(w http.ResponseWriter, r *http.Request) {
query := model.SysMenu{}
if v := ctx.GetQuery(r, "menuName"); v != "" {
query.MenuName = v
}
if v := ctx.GetQuery(r, "status"); v != "" {
query.Status = v
}
userId := ctx.LoginUserToUserID(r)
if conf.IsAdmin(userId) {
userId = "*"
}
data := s.sysMenuService.SelectMenuList(query, userId)
ctx.JSON(w, 200, result.OkData(data))
}
// 菜单信息
//
// GET /:menuId
func (s *SysMenuApi) Info(w http.ResponseWriter, r *http.Request) {
menuId := ctx.Param(r, "menuId")
if menuId == "" {
ctx.JSON(w, 400, result.CodeMsg(400, "参数错误"))
return
}
data := s.sysMenuService.SelectMenuById(menuId)
if data.MenuID == menuId {
ctx.JSON(w, 200, result.OkData(data))
return
}
ctx.JSON(w, 200, result.Err(nil))
}
// 菜单新增
//
// POST /
func (s *SysMenuApi) Add(w http.ResponseWriter, r *http.Request) {
var body model.SysMenu
err := ctx.ShouldBindJSON(r, &body)
if err != nil || body.MenuID != "" {
ctx.JSON(w, 400, result.CodeMsg(400, "参数错误"))
return
}
// 目录和菜单检查地址唯一
if consts.TYPE_DIR == body.MenuType || consts.TYPE_MENU == body.MenuType {
uniqueNenuPath := s.sysMenuService.CheckUniqueMenuPath(body.Path, "")
if !uniqueNenuPath {
msg := fmt.Sprintf("菜单新增【%s】失败菜单路由地址已存在", body.MenuName)
ctx.JSON(w, 200, result.ErrMsg(msg))
return
}
}
// 检查名称唯一
uniqueNenuName := s.sysMenuService.CheckUniqueMenuName(body.MenuName, body.ParentID, "")
if !uniqueNenuName {
msg := fmt.Sprintf("菜单新增【%s】失败菜单名称已存在", body.MenuName)
ctx.JSON(w, 200, result.ErrMsg(msg))
return
}
// 外链菜单需要符合网站http(s)开头
if body.IsFrame == "0" && !regular.ValidHttp(body.Path) {
msg := fmt.Sprintf("菜单新增【%s】失败非内部地址必须以http(s)://开头", body.MenuName)
ctx.JSON(w, 200, result.ErrMsg(msg))
return
}
body.CreateBy = ctx.LoginUserToUserName(r)
insertId := s.sysMenuService.InsertMenu(body)
if insertId != "" {
ctx.JSON(w, 200, result.Ok(nil))
return
}
ctx.JSON(w, 200, result.Err(nil))
}
// 菜单修改
//
// PUT /
func (s *SysMenuApi) Edit(w http.ResponseWriter, r *http.Request) {
var body model.SysMenu
err := ctx.ShouldBindJSON(r, &body)
if err != nil || body.MenuID == "" {
ctx.JSON(w, 400, result.CodeMsg(400, "参数错误"))
return
}
// 上级菜单不能选自己
if body.MenuID == body.ParentID {
msg := fmt.Sprintf("菜单修改【%s】失败上级菜单不能选择自己", body.MenuName)
ctx.JSON(w, 200, result.ErrMsg(msg))
return
}
// 检查数据是否存在
menuInfo := s.sysMenuService.SelectMenuById(body.MenuID)
if menuInfo.MenuID != body.MenuID {
ctx.JSON(w, 200, result.ErrMsg("没有权限访问菜单数据"))
return
}
// 父级ID不为0是要检查
if body.ParentID != "0" {
menuParent := s.sysMenuService.SelectMenuById(body.ParentID)
if menuParent.MenuID != body.ParentID {
ctx.JSON(w, 200, result.ErrMsg("没有权限访问菜单数据"))
return
}
}
// 目录和菜单检查地址唯一
if consts.TYPE_DIR == body.MenuType || consts.TYPE_MENU == body.MenuType {
uniqueNenuPath := s.sysMenuService.CheckUniqueMenuPath(body.Path, body.MenuID)
if !uniqueNenuPath {
msg := fmt.Sprintf("菜单修改【%s】失败菜单路由地址已存在", body.MenuName)
ctx.JSON(w, 200, result.ErrMsg(msg))
return
}
}
// 检查名称唯一
uniqueNenuName := s.sysMenuService.CheckUniqueMenuName(body.MenuName, body.ParentID, body.MenuID)
if !uniqueNenuName {
msg := fmt.Sprintf("菜单修改【%s】失败菜单名称已存在", body.MenuName)
ctx.JSON(w, 200, result.ErrMsg(msg))
return
}
// 外链菜单需要符合网站http(s)开头
if body.IsFrame == "0" && !regular.ValidHttp(body.Path) {
msg := fmt.Sprintf("菜单修改【%s】失败非内部地址必须以http(s)://开头", body.MenuName)
ctx.JSON(w, 200, result.ErrMsg(msg))
return
}
body.UpdateBy = ctx.LoginUserToUserName(r)
rows := s.sysMenuService.UpdateMenu(body)
if rows > 0 {
ctx.JSON(w, 200, result.Ok(nil))
return
}
ctx.JSON(w, 200, result.Err(nil))
}
// 菜单删除
//
// DELETE /:menuId
func (s *SysMenuApi) Remove(w http.ResponseWriter, r *http.Request) {
menuId := ctx.Param(r, "menuId")
if menuId == "" {
ctx.JSON(w, 400, result.CodeMsg(400, "参数错误"))
return
}
// 检查数据是否存在
menu := s.sysMenuService.SelectMenuById(menuId)
if menu.MenuID != menuId {
ctx.JSON(w, 200, result.ErrMsg("没有权限访问菜单数据!"))
return
}
// 检查是否存在子菜单
hasChild := s.sysMenuService.HasChildByMenuId(menuId)
if hasChild > 0 {
msg := fmt.Sprintf("不允许删除,存在子菜单数:%d", hasChild)
ctx.JSON(w, 200, result.ErrMsg(msg))
return
}
// 检查是否分配给角色
existRole := s.sysMenuService.CheckMenuExistRole(menuId)
if existRole > 0 {
msg := fmt.Sprintf("不允许删除,菜单已分配给角色数:%d", existRole)
ctx.JSON(w, 200, result.ErrMsg(msg))
return
}
rows := s.sysMenuService.DeleteMenuById(menuId)
if rows > 0 {
msg := fmt.Sprintf("删除成功:%d", rows)
ctx.JSON(w, 200, result.OkMsg(msg))
return
}
ctx.JSON(w, 200, result.Err(nil))
}
// 菜单树结构列表
//
// GET /treeSelect
func (s *SysMenuApi) TreeSelect(w http.ResponseWriter, r *http.Request) {
query := model.SysMenu{}
if v := ctx.GetQuery(r, "menuName"); v != "" {
query.MenuName = v
}
if v := ctx.GetQuery(r, "status"); v != "" {
query.Status = v
}
userId := ctx.LoginUserToUserID(r)
if conf.IsAdmin(userId) {
userId = "*"
}
data := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId)
ctx.JSON(w, 200, result.OkData(data))
}
// 菜单树结构列表(指定角色)
//
// GET /roleMenuTreeSelect/:roleId
func (s *SysMenuApi) RoleMenuTreeSelect(w http.ResponseWriter, r *http.Request) {
roleId := ctx.Param(r, "roleId")
if roleId == "" {
ctx.JSON(w, 400, result.CodeMsg(400, "参数错误"))
return
}
query := model.SysMenu{}
if v := ctx.GetQuery(r, "menuName"); v != "" {
query.MenuName = v
}
if v := ctx.GetQuery(r, "status"); v != "" {
query.Status = v
}
userId := ctx.LoginUserToUserID(r)
if conf.IsAdmin(userId) {
userId = "*"
}
menuTreeSelect := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId)
checkedKeys := s.sysMenuService.SelectMenuListByRoleId(roleId)
ctx.JSON(w, 200, result.OkData(map[string]any{
"menus": menuTreeSelect,
"checkedKeys": checkedKeys,
}))
}

View File

@@ -0,0 +1,24 @@
package consts
// 系统菜单常量信息
const (
// 组件布局类型-基础布局组件标识
COMPONENT_LAYOUT_BASIC = "BasicLayout"
// 组件布局类型-空白布局组件标识
COMPONENT_LAYOUT_BLANK = "BlankLayout"
// 组件布局类型-内链接布局组件标识
COMPONENT_LAYOUT_LINK = "LinkLayout"
)
const (
// 菜单类型-目录
TYPE_DIR = "D"
// 菜单类型-菜单
TYPE_MENU = "M"
// 菜单类型-按钮
TYPE_BUTTON = "B"
)
// 菜单内嵌地址标识-带/前缀
const PATH_INLINE = "/inline"

View File

@@ -0,0 +1,46 @@
package model
// SysMenu 菜单权限对象 sys_menu
type SysMenu struct {
// 菜单ID
MenuID string `json:"menuId"`
// 菜单名称
MenuName string `json:"menuName" binding:"required"`
// 父菜单ID 默认0
ParentID string `json:"parentId" binding:"required"`
// 显示顺序
MenuSort int `json:"menuSort"`
// 路由地址
Path string `json:"path"`
// 组件路径
Component string `json:"component"`
// 是否内部跳转0否 1是
IsFrame string `json:"isFrame"`
// 是否缓存0不缓存 1缓存
IsCache string `json:"isCache"`
// 菜单类型D目录 M菜单 B按钮
MenuType string `json:"menuType" binding:"required"`
// 是否显示0隐藏 1显示
Visible string `json:"visible"`
// 菜单状态0停用 1正常
Status string `json:"status"`
// 权限标识
Perms string `json:"perms"`
// 菜单图标(#无图标)
Icon string `json:"icon"`
// 创建者
CreateBy string `json:"createBy"`
// 创建时间
CreateTime int64 `json:"createTime"`
// 更新者
UpdateBy string `json:"updateBy"`
// 更新时间
UpdateTime int64 `json:"updateTime"`
// 备注
Remark string `json:"remark"`
// ====== 非数据库字段属性 ======
// 子菜单
Children []SysMenu `json:"children,omitempty"`
}

View File

@@ -0,0 +1,452 @@
package service
import (
"fmt"
"strings"
"time"
"ems.agt/features/sys_menu/consts"
"ems.agt/features/sys_menu/model"
"ems.agt/lib/core/datasource"
"ems.agt/lib/core/utils/parse"
"github.com/go-admin-team/go-admin-core/logger"
)
// 实例化数据层 RepoSysMenu 结构体
var NewRepoSysMenu = &RepoSysMenu{
selectSql: `select
m.menu_id, m.menu_name, m.parent_id, m.menu_sort, m.path, m.component, m.is_frame, m.is_cache, m.menu_type, m.visible, m.status, ifnull(m.perms,'') as perms, m.icon, m.create_time, m.remark
from sys_menu m`,
selectSqlByUser: `select distinct
m.menu_id, m.menu_name, m.parent_id, m.menu_sort, m.path, m.component, m.is_frame, m.is_cache, m.menu_type, m.visible, m.status, ifnull(m.perms,'') as perms, m.icon, m.create_time, m.remark
from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
left join sys_user_role ur on rm.role_id = ur.role_id
left join sys_role ro on ur.role_id = ro.role_id`,
resultMap: map[string]string{
"menu_id": "MenuID",
"menu_name": "MenuName",
"parent_name": "ParentName",
"parent_id": "ParentID",
"path": "Path",
"menu_sort": "MenuSort",
"component": "Component",
"is_frame": "IsFrame",
"is_cache": "IsCache",
"menu_type": "MenuType",
"visible": "Visible",
"status": "Status",
"perms": "Perms",
"icon": "Icon",
"create_by": "CreateBy",
"create_time": "CreateTime",
"update_by": "UpdateBy",
"update_time": "UpdateTime",
"remark": "Remark",
},
}
// RepoSysMenu 菜单表 数据层处理
type RepoSysMenu struct {
// 查询视图对象SQL
selectSql string
// 查询视图用户对象SQL
selectSqlByUser string
// 结果字段与实体映射
resultMap map[string]string
}
// convertResultRows 将结果记录转实体结果组
func (r *RepoSysMenu) convertResultRows(rows []map[string]any) []model.SysMenu {
arr := make([]model.SysMenu, 0)
for _, row := range rows {
sysMenu := model.SysMenu{}
for key, value := range row {
if keyMapper, ok := r.resultMap[key]; ok {
datasource.SetFieldValue(&sysMenu, keyMapper, value)
}
}
arr = append(arr, sysMenu)
}
return arr
}
// SelectMenuList 查询系统菜单列表
func (r *RepoSysMenu) SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu {
// 查询条件拼接
var conditions []string
var params []any
if sysMenu.MenuName != "" {
conditions = append(conditions, "m.menu_name like concat(?, '%')")
params = append(params, sysMenu.MenuName)
}
if sysMenu.Visible != "" {
conditions = append(conditions, "m.visible = ?")
params = append(params, sysMenu.Visible)
}
if sysMenu.Status != "" {
conditions = append(conditions, "m.status = ?")
params = append(params, sysMenu.Status)
}
fromSql := r.selectSql
// 个人菜单
if userId != "*" {
fromSql = r.selectSqlByUser
conditions = append(conditions, "ur.user_id = ?")
params = append(params, userId)
}
// 构建查询条件语句
whereSql := ""
if len(conditions) > 0 {
whereSql += " where " + strings.Join(conditions, " and ")
}
// 查询数据
orderSql := " order by m.parent_id, m.menu_sort"
querySql := fromSql + whereSql + orderSql
results, err := datasource.RawDB("", querySql, params)
if err != nil {
logger.Errorf("query err => %v", err)
return []model.SysMenu{}
}
// 转换实体
return r.convertResultRows(results)
}
// SelectMenuPermsByUserId 根据用户ID查询权限
func (r *RepoSysMenu) SelectMenuPermsByUserId(userId string) []string {
querySql := `select distinct m.perms as 'str' from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
left join sys_user_role ur on rm.role_id = ur.role_id
left join sys_role r on r.role_id = ur.role_id
where m.status = '1' and m.perms != '' and r.status = '1' and ur.user_id = ? `
// 查询结果
results, err := datasource.RawDB("", querySql, []any{userId})
if err != nil {
logger.Errorf("query err => %v", err)
return []string{}
}
// 读取结果
rows := make([]string, 0)
for _, m := range results {
rows = append(rows, fmt.Sprintf("%v", m["str"]))
}
return rows
}
// SelectMenuTreeByUserId 根据用户ID查询菜单
func (r *RepoSysMenu) SelectMenuTreeByUserId(userId string) []model.SysMenu {
var params []any
var querySql string
if userId == "*" {
// 管理员全部菜单
querySql = r.selectSql + ` where
m.menu_type in (?,?) and m.status = '1'
order by m.parent_id, m.menu_sort`
params = append(params, consts.TYPE_DIR)
params = append(params, consts.TYPE_MENU)
} else {
// 用户ID权限
querySql = r.selectSqlByUser + ` where
m.menu_type in (?, ?) and m.status = '1'
and ur.user_id = ? and ro.status = '1'
order by m.parent_id, m.menu_sort`
params = append(params, consts.TYPE_DIR)
params = append(params, consts.TYPE_MENU)
params = append(params, userId)
}
// 查询结果
results, err := datasource.RawDB("", querySql, params)
if err != nil {
logger.Errorf("query err => %v", err)
return []model.SysMenu{}
}
return r.convertResultRows(results)
}
// SelectMenuListByRoleId 根据角色ID查询菜单树信息
func (r *RepoSysMenu) SelectMenuListByRoleId(roleId string, menuCheckStrictly bool) []string {
querySql := `select m.menu_id as 'str' from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
where rm.role_id = ? `
var params []any
params = append(params, roleId)
// 展开
if menuCheckStrictly {
querySql += ` and m.menu_id not in
(select m.parent_id from sys_menu m
inner join sys_role_menu rm on m.menu_id = rm.menu_id
and rm.role_id = ?) `
params = append(params, roleId)
}
// 查询结果
results, err := datasource.RawDB("", querySql, params)
if err != nil {
logger.Errorf("query err => %v", err)
return []string{}
}
if len(results) > 0 {
ids := make([]string, 0)
for _, v := range results {
ids = append(ids, fmt.Sprintf("%v", v["str"]))
}
return ids
}
return []string{}
}
// SelectMenuByIds 根据菜单ID查询信息
func (r *RepoSysMenu) SelectMenuByIds(menuIds []string) []model.SysMenu {
placeholder := datasource.KeyPlaceholderByQuery(len(menuIds))
querySql := r.selectSql + " where m.menu_id in (" + placeholder + ")"
parameters := datasource.ConvertIdsSlice(menuIds)
results, err := datasource.RawDB("", querySql, parameters)
if err != nil {
logger.Errorf("query err => %v", err)
return []model.SysMenu{}
}
// 转换实体
return r.convertResultRows(results)
}
// HasChildByMenuId 存在菜单子节点数量
func (r *RepoSysMenu) HasChildByMenuId(menuId string) int64 {
querySql := "select count(1) as 'total' from sys_menu where parent_id = ?"
results, err := datasource.RawDB("", querySql, []any{menuId})
if err != nil {
logger.Errorf("query err => %v", err)
return 0
}
if len(results) > 0 {
return parse.Number(results[0]["total"])
}
return 0
}
// InsertMenu 新增菜单信息
func (r *RepoSysMenu) InsertMenu(sysMenu model.SysMenu) string {
// 参数拼接
params := make(map[string]any)
if sysMenu.MenuID != "" {
params["menu_id"] = sysMenu.MenuID
}
if sysMenu.ParentID != "" {
params["parent_id"] = sysMenu.ParentID
}
if sysMenu.MenuName != "" {
params["menu_name"] = sysMenu.MenuName
}
if sysMenu.MenuSort > 0 {
params["menu_sort"] = sysMenu.MenuSort
}
if sysMenu.Path != "" {
params["path"] = sysMenu.Path
}
if sysMenu.Component != "" {
params["component"] = sysMenu.Component
}
if sysMenu.IsFrame != "" {
params["is_frame"] = sysMenu.IsFrame
}
if sysMenu.IsCache != "" {
params["is_cache"] = sysMenu.IsCache
}
if sysMenu.MenuType != "" {
params["menu_type"] = sysMenu.MenuType
}
if sysMenu.Visible != "" {
params["visible"] = sysMenu.Visible
}
if sysMenu.Status != "" {
params["status"] = sysMenu.Status
}
if sysMenu.Perms != "" {
params["perms"] = sysMenu.Perms
}
if sysMenu.Icon != "" {
params["icon"] = sysMenu.Icon
} else {
params["icon"] = "#"
}
if sysMenu.Remark != "" {
params["remark"] = sysMenu.Remark
}
if sysMenu.CreateBy != "" {
params["create_by"] = sysMenu.CreateBy
params["create_time"] = time.Now().UnixMilli()
}
// 根据菜单类型重置参数
if sysMenu.MenuType == consts.TYPE_BUTTON {
params["component"] = ""
params["path"] = ""
params["icon"] = "#"
params["is_cache"] = "1"
params["is_frame"] = "1"
params["visible"] = "1"
params["status"] = "1"
}
if sysMenu.MenuType == consts.TYPE_DIR {
params["component"] = ""
params["perms"] = ""
}
// 构建执行语句
keys, placeholder, values := datasource.KeyPlaceholderValueByInsert(params)
sql := "insert into sys_menu (" + strings.Join(keys, ",") + ")values(" + placeholder + ")"
// 执行插入
rows, err := datasource.ExecDB("", sql, values)
if err != nil {
logger.Errorf("insert row : %v", err.Error())
return ""
}
return fmt.Sprint(rows)
}
// UpdateMenu 修改菜单信息
func (r *RepoSysMenu) UpdateMenu(sysMenu model.SysMenu) int64 {
// 参数拼接
params := make(map[string]any)
if sysMenu.MenuID != "" {
params["menu_id"] = sysMenu.MenuID
}
if sysMenu.ParentID != "" {
params["parent_id"] = sysMenu.ParentID
}
if sysMenu.MenuName != "" {
params["menu_name"] = sysMenu.MenuName
}
if sysMenu.MenuSort > 0 {
params["menu_sort"] = sysMenu.MenuSort
}
if sysMenu.Path != "" {
params["path"] = sysMenu.Path
}
if sysMenu.Component != "" {
params["component"] = sysMenu.Component
}
if sysMenu.IsFrame != "" {
params["is_frame"] = sysMenu.IsFrame
}
if sysMenu.IsCache != "" {
params["is_cache"] = sysMenu.IsCache
}
if sysMenu.MenuType != "" {
params["menu_type"] = sysMenu.MenuType
}
if sysMenu.Visible != "" {
params["visible"] = sysMenu.Visible
}
if sysMenu.Status != "" {
params["status"] = sysMenu.Status
}
if sysMenu.Perms != "" {
params["perms"] = sysMenu.Perms
}
if sysMenu.Icon != "" {
params["icon"] = sysMenu.Icon
} else {
params["icon"] = "#"
}
if sysMenu.Remark != "" {
params["remark"] = sysMenu.Remark
}
if sysMenu.UpdateBy != "" {
params["update_by"] = sysMenu.UpdateBy
params["update_time"] = time.Now().UnixMilli()
}
// 根据菜单类型重置参数
if sysMenu.MenuType == consts.TYPE_BUTTON {
params["component"] = ""
params["path"] = ""
params["icon"] = "#"
params["is_cache"] = "1"
params["is_frame"] = "1"
params["visible"] = "1"
params["status"] = "1"
}
if sysMenu.MenuType == consts.TYPE_DIR {
params["component"] = ""
params["perms"] = ""
}
// 构建执行语句
keys, values := datasource.KeyValueByUpdate(params)
sql := "update sys_menu set " + strings.Join(keys, ",") + " where menu_id = ?"
// 执行更新
values = append(values, sysMenu.MenuID)
rows, err := datasource.ExecDB("", sql, values)
if err != nil {
logger.Errorf("update row : %v", err.Error())
return 0
}
return rows
}
// DeleteMenuById 删除菜单管理信息
func (r *RepoSysMenu) DeleteMenuById(menuId string) int64 {
sql := "delete from sys_menu where menu_id = ?"
results, err := datasource.ExecDB("", sql, []any{menuId})
if err != nil {
logger.Errorf("delete err => %v", err)
return 0
}
return results
}
// CheckUniqueMenu 校验菜单是否唯一
func (r *RepoSysMenu) CheckUniqueMenu(sysMenu model.SysMenu) string {
// 查询条件拼接
var conditions []string
var params []any
if sysMenu.MenuName != "" {
conditions = append(conditions, "menu_name = ?")
params = append(params, sysMenu.MenuName)
}
if sysMenu.ParentID != "" {
conditions = append(conditions, "parent_id = ?")
params = append(params, sysMenu.ParentID)
}
if sysMenu.Path != "" {
conditions = append(conditions, "path = ?")
params = append(params, sysMenu.Path)
}
// 构建查询条件语句
whereSql := ""
if len(conditions) > 0 {
whereSql += " where " + strings.Join(conditions, " and ")
}
if whereSql == "" {
return ""
}
// 查询数据
querySql := "select menu_id as 'str' from sys_menu " + whereSql + " limit 1"
results, err := datasource.RawDB("", querySql, params)
if err != nil {
logger.Errorf("query err %v", err)
return ""
}
if len(results) > 0 {
return fmt.Sprintf("%v", results[0]["str"])
}
return ""
}

View File

@@ -0,0 +1,397 @@
package service
import (
"encoding/base64"
"strings"
"ems.agt/features/sys_menu/consts"
"ems.agt/features/sys_menu/model"
sysRoleService "ems.agt/features/sys_role/service"
sysrolemenu "ems.agt/features/sys_role_menu"
"ems.agt/lib/core/utils/parse"
"ems.agt/lib/core/utils/regular"
"ems.agt/lib/core/vo"
)
// 实例化服务层 ServiceSysMenu 结构体
var NewServiceSysMenu = &ServiceSysMenu{
sysMenuRepository: NewRepoSysMenu,
sysRoleMenuRepository: sysrolemenu.NewRepoSysRoleMenu,
sysRoleRepository: sysRoleService.NewRepoSysRole,
}
// ServiceSysMenu 菜单 服务层处理
type ServiceSysMenu struct {
// 菜单服务
sysMenuRepository *RepoSysMenu
// 角色与菜单关联服务
sysRoleMenuRepository *sysrolemenu.RepoSysRoleMenu
// 角色服务
sysRoleRepository *sysRoleService.RepoSysRole
}
// SelectMenuList 查询系统菜单列表
func (r *ServiceSysMenu) SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu {
return r.sysMenuRepository.SelectMenuList(sysMenu, userId)
}
// SelectMenuPermsByUserId 根据用户ID查询权限
func (r *ServiceSysMenu) SelectMenuPermsByUserId(userId string) []string {
return r.sysMenuRepository.SelectMenuPermsByUserId(userId)
}
// SelectMenuTreeByUserId 根据用户ID查询菜单
func (r *ServiceSysMenu) SelectMenuTreeByUserId(userId string) []model.SysMenu {
sysMenus := r.sysMenuRepository.SelectMenuTreeByUserId(userId)
return r.parseDataToTree(sysMenus)
}
// SelectMenuTreeSelectByUserId 根据用户ID查询菜单树结构信息
func (r *ServiceSysMenu) SelectMenuTreeSelectByUserId(sysMenu model.SysMenu, userId string) []vo.TreeSelect {
sysMenus := r.sysMenuRepository.SelectMenuList(sysMenu, userId)
menus := r.parseDataToTree(sysMenus)
tree := make([]vo.TreeSelect, 0)
for _, menu := range menus {
tree = append(tree, sysMenuTreeSelect(menu))
}
return tree
}
// sysMenuTreeSelect 使用给定的 SysMenu 对象解析为 TreeSelect 对象
func sysMenuTreeSelect(sysMenu model.SysMenu) vo.TreeSelect {
t := vo.TreeSelect{}
t.ID = sysMenu.MenuID
t.Label = sysMenu.MenuName
if len(sysMenu.Children) > 0 {
for _, menu := range sysMenu.Children {
child := sysMenuTreeSelect(menu)
t.Children = append(t.Children, child)
}
} else {
t.Children = []vo.TreeSelect{}
}
return t
}
// SelectMenuListByRoleId 根据角色ID查询菜单树信息 TODO
func (r *ServiceSysMenu) SelectMenuListByRoleId(roleId string) []string {
roles := r.sysRoleRepository.SelectRoleByIds([]string{roleId})
if len(roles) > 0 {
role := roles[0]
if role.RoleID == roleId {
return r.sysMenuRepository.SelectMenuListByRoleId(
role.RoleID,
role.MenuCheckStrictly == "1",
)
}
}
return []string{}
}
// SelectMenuById 根据菜单ID查询信息
func (r *ServiceSysMenu) SelectMenuById(menuId string) model.SysMenu {
if menuId == "" {
return model.SysMenu{}
}
menus := r.sysMenuRepository.SelectMenuByIds([]string{menuId})
if len(menus) > 0 {
return menus[0]
}
return model.SysMenu{}
}
// HasChildByMenuId 存在菜单子节点数量
func (r *ServiceSysMenu) HasChildByMenuId(menuId string) int64 {
return r.sysMenuRepository.HasChildByMenuId(menuId)
}
// CheckMenuExistRole 查询菜单是否存在角色
func (r *ServiceSysMenu) CheckMenuExistRole(menuId string) int64 {
return r.sysRoleMenuRepository.CheckMenuExistRole(menuId)
}
// InsertMenu 新增菜单信息
func (r *ServiceSysMenu) InsertMenu(sysMenu model.SysMenu) string {
return r.sysMenuRepository.InsertMenu(sysMenu)
}
// UpdateMenu 修改菜单信息
func (r *ServiceSysMenu) UpdateMenu(sysMenu model.SysMenu) int64 {
return r.sysMenuRepository.UpdateMenu(sysMenu)
}
// DeleteMenuById 删除菜单管理信息
func (r *ServiceSysMenu) DeleteMenuById(menuId string) int64 {
// 删除菜单与角色关联
r.sysRoleMenuRepository.DeleteMenuRole([]string{menuId})
return r.sysMenuRepository.DeleteMenuById(menuId)
}
// CheckUniqueMenuName 校验菜单名称是否唯一
func (r *ServiceSysMenu) CheckUniqueMenuName(menuName, parentId, menuId string) bool {
uniqueId := r.sysMenuRepository.CheckUniqueMenu(model.SysMenu{
MenuName: menuName,
ParentID: parentId,
})
if uniqueId == menuId {
return true
}
return uniqueId == ""
}
// CheckUniqueMenuPath 校验路由地址是否唯一(针对目录和菜单)
func (r *ServiceSysMenu) CheckUniqueMenuPath(path, menuId string) bool {
uniqueId := r.sysMenuRepository.CheckUniqueMenu(model.SysMenu{
Path: path,
})
if uniqueId == menuId {
return true
}
return uniqueId == ""
}
// BuildRouteMenus 构建前端路由所需要的菜单
func (r *ServiceSysMenu) BuildRouteMenus(sysMenus []model.SysMenu, prefix string) []vo.Router {
routers := []vo.Router{}
for _, item := range sysMenus {
router := vo.Router{}
router.Name = r.getRouteName(item)
router.Path = r.getRouterPath(item)
router.Component = r.getComponent(item)
router.Meta = r.getRouteMeta(item)
// 子项菜单 目录类型 非路径链接
cMenus := item.Children
if len(cMenus) > 0 && item.MenuType == consts.TYPE_DIR && !regular.ValidHttp(item.Path) {
// 获取重定向地址
redirectPrefix, redirectPath := r.getRouteRedirect(
cMenus,
router.Path,
prefix,
)
router.Redirect = redirectPath
// 子菜单进入递归
router.Children = r.BuildRouteMenus(cMenus, redirectPrefix)
} else if item.ParentID == "0" && item.IsFrame == "1" && item.MenuType == consts.TYPE_MENU {
// 父菜单 内部跳转 菜单类型
menuPath := "/" + item.MenuID
childPath := menuPath + r.getRouterPath(item)
children := vo.Router{
Name: r.getRouteName(item),
Path: childPath,
Component: item.Component,
Meta: r.getRouteMeta(item),
}
router.Meta.HideChildInMenu = true
router.Children = append(router.Children, children)
router.Name = item.MenuID
router.Path = menuPath
router.Redirect = childPath
router.Component = consts.COMPONENT_LAYOUT_BASIC
} else if item.ParentID == "0" && item.IsFrame == "1" && regular.ValidHttp(item.Path) {
// 父菜单 内部跳转 路径链接
menuPath := "/" + item.MenuID
childPath := menuPath + r.getRouterPath(item)
children := vo.Router{
Name: r.getRouteName(item),
Path: childPath,
Component: consts.COMPONENT_LAYOUT_LINK,
Meta: r.getRouteMeta(item),
}
router.Meta.HideChildInMenu = true
router.Children = append(router.Children, children)
router.Name = item.MenuID
router.Path = menuPath
router.Redirect = childPath
router.Component = consts.COMPONENT_LAYOUT_BASIC
}
routers = append(routers, router)
}
return routers
}
// getRouteName 获取路由名称 路径英文首字母大写
func (r *ServiceSysMenu) getRouteName(sysMenu model.SysMenu) string {
routerName := parse.FirstUpper(sysMenu.Path)
// 路径链接
if regular.ValidHttp(sysMenu.Path) {
return routerName[:5] + "Link" + sysMenu.MenuID
}
return routerName
}
// getRouterPath 获取路由地址
func (r *ServiceSysMenu) getRouterPath(sysMenu model.SysMenu) string {
routerPath := sysMenu.Path
// 显式路径
if routerPath == "" || strings.HasPrefix(routerPath, "/") {
return routerPath
}
// 路径链接 内部跳转
if regular.ValidHttp(routerPath) && sysMenu.IsFrame == "1" {
routerPath = regular.Replace(routerPath, `/^http(s)?:\/\/+/`, "")
routerPath = base64.StdEncoding.EncodeToString([]byte(routerPath))
}
// 父菜单 内部跳转
if sysMenu.ParentID == "0" && sysMenu.IsFrame == "1" {
routerPath = "/" + routerPath
}
return routerPath
}
// getComponent 获取组件信息
func (r *ServiceSysMenu) getComponent(sysMenu model.SysMenu) string {
// 内部跳转 路径链接
if sysMenu.IsFrame == "1" && regular.ValidHttp(sysMenu.Path) {
return consts.COMPONENT_LAYOUT_LINK
}
// 非父菜单 目录类型
if sysMenu.ParentID != "0" && sysMenu.MenuType == consts.TYPE_DIR {
return consts.COMPONENT_LAYOUT_BLANK
}
// 组件路径 内部跳转 菜单类型
if sysMenu.Component != "" && sysMenu.IsFrame == "1" && sysMenu.MenuType == consts.TYPE_MENU {
// 父菜单套外层布局
if sysMenu.ParentID == "0" {
return consts.COMPONENT_LAYOUT_BASIC
}
return sysMenu.Component
}
return consts.COMPONENT_LAYOUT_BASIC
}
// getRouteMeta 获取路由元信息
func (r *ServiceSysMenu) getRouteMeta(sysMenu model.SysMenu) vo.RouterMeta {
meta := vo.RouterMeta{}
if sysMenu.Icon == "#" {
meta.Icon = ""
} else {
meta.Icon = sysMenu.Icon
}
meta.Title = sysMenu.MenuName
meta.HideChildInMenu = false
meta.HideInMenu = sysMenu.Visible == "0"
meta.Cache = sysMenu.IsCache == "1"
meta.Target = ""
// 路径链接 非内部跳转
if regular.ValidHttp(sysMenu.Path) && sysMenu.IsFrame == "0" {
meta.Target = "_blank"
}
return meta
}
// getRouteRedirect 获取路由重定向地址(针对目录)
//
// cMenus 子菜单数组
// routerPath 当前菜单路径
// prefix 菜单重定向路径前缀
func (r *ServiceSysMenu) getRouteRedirect(cMenus []model.SysMenu, routerPath string, prefix string) (string, string) {
redirectPath := ""
// 重定向为首个显示并启用的子菜单
var firstChild *model.SysMenu
for _, item := range cMenus {
if item.IsFrame == "1" && item.Visible == "1" {
firstChild = &item
break
}
}
// 检查内嵌隐藏菜单是否可做重定向
if firstChild == nil {
for _, item := range cMenus {
if item.IsFrame == "1" && item.Visible == "1" && strings.Contains(item.Path, consts.PATH_INLINE) {
firstChild = &item
break
}
}
}
if firstChild != nil {
firstChildPath := r.getRouterPath(*firstChild)
if strings.HasPrefix(firstChildPath, "/") {
redirectPath = firstChildPath
} else {
// 拼接追加路径
if !strings.HasPrefix(routerPath, "/") {
prefix += "/"
}
prefix = prefix + routerPath
redirectPath = prefix + "/" + firstChildPath
}
}
return prefix, redirectPath
}
// parseDataToTree 将数据解析为树结构,构建前端所需要下拉树结构
func (r *ServiceSysMenu) parseDataToTree(sysMenus []model.SysMenu) []model.SysMenu {
// 节点分组
nodesMap := make(map[string][]model.SysMenu)
// 节点id
treeIds := []string{}
// 树节点
tree := []model.SysMenu{}
for _, item := range sysMenus {
parentID := item.ParentID
// 分组
mapItem, ok := nodesMap[parentID]
if !ok {
mapItem = []model.SysMenu{}
}
mapItem = append(mapItem, item)
nodesMap[parentID] = mapItem
// 记录节点ID
treeIds = append(treeIds, item.MenuID)
}
for key, value := range nodesMap {
// 选择不是节点ID的作为树节点
found := false
for _, id := range treeIds {
if id == key {
found = true
break
}
}
if !found {
tree = append(tree, value...)
}
}
for i, node := range tree {
iN := r.parseDataToTreeComponet(node, &nodesMap)
tree[i] = iN
}
return tree
}
// parseDataToTreeComponet 递归函数处理子节点
func (r *ServiceSysMenu) parseDataToTreeComponet(node model.SysMenu, nodesMap *map[string][]model.SysMenu) model.SysMenu {
id := node.MenuID
children, ok := (*nodesMap)[id]
if ok {
node.Children = children
}
if len(node.Children) > 0 {
for i, child := range node.Children {
icN := r.parseDataToTreeComponet(child, nodesMap)
node.Children[i] = icN
}
}
return node
}