| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 |
- package service
- import (
- "cold-delivery/app/admin/model"
- "cold-delivery/app/admin/service/dto"
- "cold-delivery/common/actions"
- cDto "cold-delivery/common/dto"
- "cold-delivery/common/global"
- "errors"
- "fmt"
- "strings"
- "github.com/casbin/casbin/v2"
- "gogs.baozhida.cn/zoie/OAuth-core/sdk"
- "gogs.baozhida.cn/zoie/OAuth-core/sdk/config"
- "gogs.baozhida.cn/zoie/OAuth-core/service"
- "gorm.io/gorm"
- )
- type SysPost struct {
- service.Service
- }
- // GetPage 分页查询岗位列表
- func (e *SysPost) GetPage(c *dto.SysPostGetPageReq, list *[]model.SysPost, count *int64, p *actions.DataPermission) error {
- var err error
- var data model.SysPost
- db := e.Orm.Model(&data).
- Scopes(
- cDto.MakeCondition(c.GetNeedSearch()),
- cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
- )
- // 超级管理员(deptId=0)可以看到所有部门的岗位
- if p.DeptId != 0 {
- db = db.Scopes(actions.Permission(data.TableName(), p))
- }
- err = db.Find(list).Limit(-1).Offset(-1).
- Count(count).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- return global.GetFailedErr
- }
- return nil
- }
- // Get 获取单个岗位详情(含权限菜单ID列表)
- func (e *SysPost) Get(d *dto.SysPostGetReq, postModel *model.SysPost, p *actions.DataPermission) error {
- db := e.Orm
- // 超级管理员可以看到所有部门的岗位
- if p.DeptId != 0 {
- db = db.Scopes(actions.Permission(postModel.TableName(), p))
- }
- err := db.First(postModel, d.GetId()).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- if errors.Is(err, gorm.ErrRecordNotFound) {
- return global.GetNotFoundOrNoPermissionErr
- }
- return global.GetFailedErr
- }
- postModel.MenuIds, err = e.GetPostMenuIds(postModel.Id)
- if err != nil {
- e.Log.Errorf("get menuIds error, %s", err)
- return global.GetFailedErr
- }
- return nil
- }
- // Insert 创建岗位(含菜单权限分配)
- func (e *SysPost) Insert(c *dto.SysPostInsertReq) error {
- var err error
- var data model.SysPost
- tx := e.Orm.Begin()
- defer func() {
- if err != nil {
- tx.Rollback()
- } else {
- tx.Commit()
- }
- }()
- // 检查岗位编码在同一个部门下是否已存在
- var count int64
- err = tx.Model(&data).Where("post_code = ? AND dept_id = ?", c.PostCode, c.DeptId).Count(&count).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- return global.CreateFailedErr
- }
- if count > 0 {
- return errors.New("该部门下岗位编码已存在")
- }
- // 创建岗位
- c.Generate(&data)
- err = tx.Create(&data).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- return global.CreateFailedErr
- }
- c.Id = data.Id
- // 添加岗位菜单关联
- if len(c.MenuIds) > 0 {
- var menuList []model.SysMenu
- err = tx.Where("id in (?)", c.MenuIds).Find(&menuList).Error
- if err != nil {
- return global.GetFailedErr
- }
- if len(menuList) == 0 {
- return errors.New("菜单不存在!")
- }
- var postMenuList []model.SysPostMenu
- for _, v := range menuList {
- postMenuObj := model.SysPostMenu{
- PostId: data.Id,
- MenuId: v.Id,
- }
- postMenuObj.SetCreateBy(c.CreateBy)
- postMenuList = append(postMenuList, postMenuObj)
- }
- err = tx.Save(&postMenuList).Error
- if err != nil {
- e.Log.Errorf("save menuIds error, %s", err)
- return global.UpdateFailedErr
- }
- // 同步写入 Casbin 规则
- err = e.syncCasbinRules(tx, data, menuList)
- if err == nil {
- cb := sdk.Runtime.GetCasbinKey(config.ApplicationConfig.Host)
- if cb != nil {
- cb.LoadPolicy()
- }
- }
- if err != nil {
- e.Log.Errorf("sync casbin rules error, %s", err)
- return global.UpdateFailedErr
- }
- }
- return nil
- }
- // Update 修改岗位(含菜单权限重新分配)
- func (e *SysPost) Update(c *dto.SysPostUpdateReq, p *actions.DataPermission, cb *casbin.SyncedEnforcer) error {
- var err error
- tx := e.Orm.Begin()
- defer func() {
- if err != nil {
- tx.Rollback()
- } else {
- tx.Commit()
- }
- }()
- var postModel = model.SysPost{}
- // 查询岗位是否存在
- db := e.Orm
- if p.DeptId != 0 {
- db = db.Scopes(actions.Permission(postModel.TableName(), p))
- }
- err = db.First(&postModel, c.GetId()).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- if errors.Is(err, gorm.ErrRecordNotFound) {
- return global.UpdateNotFoundOrNoPermissionErr
- }
- return global.UpdateFailedErr
- }
- // 更新岗位基本信息
- c.Generate(&postModel)
- // deptId 由 Generate 方法从更新请求中设置,允许修改部门
- err = tx.Save(&postModel).Error
- if err != nil {
- if errors.Is(err, model.ErrForbidUpdateSysPost) {
- return err
- }
- e.Log.Errorf("db error: %s", err)
- return global.UpdateFailedErr
- }
- // 清除旧的 Casbin 规则(用旧的 postCode)
- _, _ = cb.RemoveFilteredPolicy(0, "role:"+postModel.PostCode)
- _, _ = cb.RemoveFilteredPolicy(0, postModel.PostCode) // 清除旧格式规则(无 role: 前缀)
- // 删除旧的菜单关联
- err = tx.Where("post_id = ?", postModel.Id).Delete(&model.SysPostMenu{}).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- return global.UpdateFailedErr
- }
- // 重新添加菜单关联
- if len(c.MenuIds) > 0 {
- var menuList []model.SysMenu
- err = e.Orm.Where("id in (?)", c.MenuIds).Find(&menuList).Error
- if err != nil {
- return global.GetFailedErr
- }
- if len(menuList) == 0 {
- return errors.New("菜单不存在!")
- }
- var postMenuList []model.SysPostMenu
- for _, v := range menuList {
- postMenuObj := model.SysPostMenu{
- PostId: postModel.Id,
- MenuId: v.Id,
- }
- postMenuObj.SetCreateBy(c.UpdateBy)
- postMenuList = append(postMenuList, postMenuObj)
- }
- err = tx.Save(&postMenuList).Error
- if err != nil {
- e.Log.Errorf("save menuIds error, %s", err)
- return global.UpdateFailedErr
- }
- // 同步写入 Casbin 规则
- err = e.syncCasbinRules(tx, postModel, menuList)
- if err == nil {
- cb.LoadPolicy()
- }
- if err != nil {
- e.Log.Errorf("sync casbin rules error, %s", err)
- return global.UpdateFailedErr
- }
- }
- // 清除缓存
- _ = model.DeletePostMenuCache(postModel.Id)
- c.Id = postModel.Id
- return nil
- }
- // Remove 删除岗位(逻辑删除)
- func (e *SysPost) Remove(c *dto.SysPostDeleteReq, p *actions.DataPermission, cb *casbin.SyncedEnforcer) error {
- var err error
- tx := e.Orm.Begin()
- defer func() {
- if err != nil {
- tx.Rollback()
- } else {
- tx.Commit()
- }
- }()
- // 检查是否有用户关联该岗位
- var userCount int64
- err = tx.Table("sys_user").Where("post_id = ? AND deleted_at IS NULL", c.Id).Count(&userCount).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- return global.DeleteFailedErr
- }
- if userCount > 0 {
- return errors.New(fmt.Sprintf("有 %d 个用户关联了该岗位,禁止删除!", userCount))
- }
- var postModel model.SysPost
- rdb := e.Orm
- if p.DeptId != 0 {
- rdb = rdb.Scopes(actions.Permission(postModel.TableName(), p))
- }
- err = rdb.First(&postModel, c.GetId()).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- if errors.Is(err, gorm.ErrRecordNotFound) {
- return global.DeleteNotFoundOrNoPermissionErr
- }
- return global.DeleteFailedErr
- }
- // GORM 逻辑删除(自动设置 deleted_at)
- db := tx.Delete(&postModel)
- if err = db.Error; err != nil {
- if errors.Is(err, model.ErrForbidDeleteSysPost) {
- return err
- }
- e.Log.Errorf("db error: %s", err)
- return global.DeleteFailedErr
- }
- if db.RowsAffected == 0 {
- return global.DeleteNotFoundOrNoPermissionErr
- }
- // 删除岗位关联的菜单
- err = tx.Where("post_id = ?", postModel.Id).Delete(&model.SysPostMenu{}).Error
- if err != nil {
- return global.UpdateFailedErr
- }
- // 清除 Casbin 规则
- _, _ = cb.RemoveFilteredPolicy(0, "role:"+postModel.PostCode)
- _, _ = cb.RemoveFilteredPolicy(0, postModel.PostCode) // 清除旧格式规则(无 role: 前缀)
- // 清除缓存
- _ = model.DeletePostMenuCache(postModel.Id)
- return nil
- }
- // GetAll 获取所有岗位(下拉列表用)
- func (e *SysPost) GetAll(deptId int, list *[]model.SysPost, p *actions.DataPermission) error {
- var err error
- var data model.SysPost
- db := e.Orm.Model(&data)
- // 超级管理员可以看到所有部门的岗位
- if p.DeptId != 0 {
- db = db.Scopes(actions.Permission(data.TableName(), p))
- }
- if deptId > 0 {
- db = db.Where("dept_id = ?", deptId)
- }
- err = db.Order("sort asc").Find(list).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- return global.GetFailedErr
- }
- return nil
- }
- // GetPostMenuIds 获取岗位已分配的菜单ID列表
- func (e *SysPost) GetPostMenuIds(postId int) ([]int, error) {
- var menuIds []int
- err := e.Orm.Model(&model.SysPostMenu{}).Where("post_id = ?", postId).Pluck("menu_id", &menuIds).Error
- if err != nil {
- return []int{}, global.GetFailedErr
- }
- return menuIds, nil
- }
- // GetMenuTree 获取完整菜单树(用于权限分配界面)
- func (e *SysPost) GetMenuTree() ([]dto.MenuLabel, error) {
- // 直接查询所有菜单,不依赖 SysMenu.GetList 的复杂搜索逻辑
- var menuList []model.SysMenu
- err := e.Orm.Order("sort asc").Find(&menuList).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- return nil, global.GetFailedErr
- }
- // 构建菜单树
- tree := make([]dto.MenuLabel, 0)
- for i := 0; i < len(menuList); i++ {
- if menuList[i].ParentId != 0 {
- continue
- }
- label := dto.MenuLabel{}
- label.Id = menuList[i].Id
- label.Label = menuList[i].Title
- buildMenuLabelTree(&menuList, &label)
- tree = append(tree, label)
- }
- return tree, nil
- }
- // buildMenuLabelTree 递归构建菜单标签树
- func buildMenuLabelTree(menuList *[]model.SysMenu, parent *dto.MenuLabel) {
- list := *menuList
- children := make([]dto.MenuLabel, 0)
- for j := 0; j < len(list); j++ {
- if parent.Id != list[j].ParentId {
- continue
- }
- child := dto.MenuLabel{}
- child.Id = list[j].Id
- child.Label = list[j].Title
- child.Children = []dto.MenuLabel{}
- if list[j].MenuType != "F" {
- buildMenuLabelTree(menuList, &child)
- }
- children = append(children, child)
- }
- if len(children) > 0 {
- parent.Children = children
- } else {
- parent.Children = nil
- }
- }
- // FilterMenuTree 根据允许的菜单ID过滤菜单树
- func (e *SysPost) FilterMenuTree(fullTree []dto.MenuLabel, allowedIds []int) []dto.MenuLabel {
- if len(allowedIds) == 0 {
- return nil
- }
- allowedMap := make(map[int]bool)
- for _, id := range allowedIds {
- allowedMap[id] = true
- }
- var filter func(nodes []dto.MenuLabel) []dto.MenuLabel
- filter = func(nodes []dto.MenuLabel) []dto.MenuLabel {
- result := make([]dto.MenuLabel, 0)
- for _, node := range nodes {
- // Only include if allowed or has allowed children
- if allowedMap[node.Id] || hasAllowedChild(node.Children, allowedMap) {
- child := dto.MenuLabel{
- Id: node.Id,
- Label: node.Label,
- Children: filter(node.Children),
- }
- result = append(result, child)
- }
- }
- return result
- }
- return filter(fullTree)
- }
- func hasAllowedChild(children []dto.MenuLabel, allowedMap map[int]bool) bool {
- for _, c := range children {
- if allowedMap[c.Id] || hasAllowedChild(c.Children, allowedMap) {
- return true
- }
- }
- return false
- }
- // syncCasbinRules 同步岗位权限到 Casbin 规则表
- func (e *SysPost) syncCasbinRules(tx *gorm.DB, post model.SysPost, menuList []model.SysMenu) error {
- // 获取完整的菜单信息(包括父菜单的路由权限)
- var allMenus []model.SysMenu
- menuIds := make([]int, 0)
- for _, m := range menuList {
- menuIds = append(menuIds, m.Id)
- }
- // 递归获取所有父级菜单的权限路径
- err := e.resolveMenuPaths(menuIds, &allMenus)
- if err != nil {
- return err
- }
- // 为每个菜单路径写入 Casbin 规则
- // Casbin 规则格式: p = postCode, domain, path, method
- // 在 Casbin 中: sub = postCode, dom = service::api, obj = path, act = method
- for _, menu := range allMenus {
- if menu.Path == "" {
- continue
- }
- var methods []string
- if menu.MenuType == "C" {
- methods = []string{"GET"}
- } else if menu.MenuType == "M" {
- continue
- } else if menu.MenuType == "F" {
- methods = mapButtonToMethods(menu.Title)
- if len(methods) == 0 {
- continue
- }
- }
- for _, method := range methods {
- // 使用原始 SQL 插入避免与 Casbin adapter 冲突
- // 检查是否已存在
- var count int64
- sub := "role:" + post.PostCode
- tx.Table("sys_casbin_rule").
- Where("p_type = 'p' AND v0 = ? AND v1 = ? AND v2 = ? AND v3 = ?",
- sub, "service::api", menu.Path, method).
- Count(&count)
- if count == 0 {
- err = tx.Exec(
- "INSERT INTO sys_casbin_rule (p_type, v0, v1, v2, v3, v4, v5) VALUES ('p', ?, ?, ?, ?, '', '')",
- sub, "service::api", menu.Path, method,
- ).Error
- if err != nil {
- return err
- }
- }
- }
- }
- // 写入 g 规则(Casbin RBAC 角色继承)
- sub := "role:" + post.PostCode
- var gCount int64
- tx.Table("sys_casbin_rule").
- Where("p_type = 'g' AND v0 = ? AND v1 = ? AND v2 = ?", sub, sub, "service::api").
- Count(&gCount)
- if gCount == 0 {
- err = tx.Exec(
- "INSERT INTO sys_casbin_rule (p_type, v0, v1, v2, v3, v4, v5) VALUES ('g', ?, ?, ?, '', '', '')",
- sub, sub, "service::api",
- ).Error
- if err != nil {
- return err
- }
- }
- return nil
- }
- // mapButtonToMethods 将按钮名称映射为 HTTP 方法
- func mapButtonToMethods(title string) []string {
- title = strings.TrimSpace(title)
- switch {
- case strings.Contains(title, "查询"):
- return []string{"GET"}
- case strings.Contains(title, "新增") || strings.Contains(title, "导入"):
- return []string{"POST"}
- case strings.Contains(title, "编辑") || strings.Contains(title, "修改"):
- return []string{"PUT"}
- case strings.Contains(title, "删除"):
- return []string{"DELETE"}
- case strings.Contains(title, "重置") || strings.Contains(title, "审核"):
- return []string{"PUT"}
- case strings.Contains(title, "分配"):
- return []string{"POST"}
- default:
- return []string{"GET"}
- }
- }
- // resolveMenuPaths 递归获取菜单及其所有父菜单
- func (e *SysPost) resolveMenuPaths(menuIds []int, result *[]model.SysMenu) error {
- if len(menuIds) == 0 {
- return nil
- }
- var menus []model.SysMenu
- err := e.Orm.Where("id in (?)", menuIds).Find(&menus).Error
- if err != nil {
- return err
- }
- parentIds := make([]int, 0)
- seen := make(map[int]bool)
- for _, m := range *result {
- seen[m.Id] = true
- }
- for _, m := range menus {
- if !seen[m.Id] {
- *result = append(*result, m)
- seen[m.Id] = true
- }
- if m.ParentId != 0 && !seen[m.ParentId] {
- parentIds = append(parentIds, m.ParentId)
- }
- }
- if len(parentIds) > 0 {
- return e.resolveMenuPaths(parentIds, result)
- }
- return nil
- }
- // GetWithName 根据岗位名称查询
- func (e *SysPost) GetWithName(name string, deptId int, postModel *model.SysPost) *SysPost {
- db := e.Orm.Where("post_name = ?", name)
- if deptId > 0 {
- db = db.Where("dept_id = ?", deptId)
- }
- err := db.First(postModel).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- if errors.Is(err, gorm.ErrRecordNotFound) {
- err = global.GetNotFoundOrNoPermissionErr
- } else {
- err = global.GetFailedErr
- }
- _ = e.AddError(err)
- return e
- }
- return e
- }
- // GetPostNamesByIds 根据岗位ID列表获取岗位名称映射
- func (e *SysPost) GetPostNamesByIds(ids []int) (map[int]string, error) {
- if len(ids) == 0 {
- return map[int]string{}, nil
- }
- var posts []model.SysPost
- err := e.Orm.Where("id in (?)", ids).Find(&posts).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- return nil, global.GetFailedErr
- }
- result := make(map[int]string)
- for _, p := range posts {
- result[p.Id] = p.PostName
- }
- return result, nil
- }
- // GetPostById 通过岗位ID获取菜单权限标识列表
- func (e *SysPost) GetPostById(postId int) ([]string, error) {
- permissions := make([]string, 0)
- postModel := model.SysPost{}
- postModel.Id = postId
- err := e.Orm.Model(&postModel).First(&postModel).Error
- if err != nil {
- e.Log.Errorf("db error: %s", err)
- if errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, global.GetNotFoundOrNoPermissionErr
- }
- return nil, global.GetFailedErr
- }
- // 查询岗位关联的菜单权限标识
- var menuPerms []string
- err = e.Orm.Model(&model.SysMenu{}).
- Joins("JOIN sys_post_menu ON sys_post_menu.menu_id = sys_menu.id").
- Where("sys_post_menu.post_id = ? AND sys_menu.permission != ''", postId).
- Pluck("sys_menu.permission", &menuPerms).Error
- if err != nil {
- return nil, global.GetFailedErr
- }
- permissions = append(permissions, menuPerms...)
- return permissions, nil
- }
|