feat: 优化web
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"giter.top/smart/internal/iam/entity"
|
||||
"giter.top/smart/internal/iam/repository"
|
||||
"giter.top/smart/pkg/utils/id"
|
||||
)
|
||||
|
||||
// DeptService 部门
|
||||
type DeptService interface {
|
||||
Tree(ctx context.Context, tenantID string, keyword string, leaderID *string) ([]DeptNode, error)
|
||||
Create(ctx context.Context, tenantID string, req *CreateDeptRequest) (*entity.Dept, error)
|
||||
Update(ctx context.Context, tenantID string, id string, req *UpdateDeptRequest) (*entity.Dept, error)
|
||||
Delete(ctx context.Context, tenantID string, ids []string) error
|
||||
Get(ctx context.Context, tenantID string, id string) (*entity.Dept, error)
|
||||
}
|
||||
|
||||
type CreateDeptRequest struct {
|
||||
ParentID string `json:"parent_id"`
|
||||
DeptName string `json:"dept_name" binding:"required,max=128"`
|
||||
LeaderID *string `json:"leader_id"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
}
|
||||
|
||||
type UpdateDeptRequest struct {
|
||||
ParentID *string `json:"parent_id"`
|
||||
DeptName *string `json:"dept_name" binding:"omitempty,max=128"`
|
||||
LeaderID *string `json:"leader_id"`
|
||||
SortOrder *int `json:"sort_order"`
|
||||
}
|
||||
|
||||
// DeptNode 树节点
|
||||
type DeptNode struct {
|
||||
entity.Dept
|
||||
Children []DeptNode `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type deptService struct {
|
||||
depts repository.DeptRepository
|
||||
users repository.UserRepository
|
||||
}
|
||||
|
||||
func NewDeptService(depts repository.DeptRepository, users repository.UserRepository) DeptService {
|
||||
return &deptService{depts: depts, users: users}
|
||||
}
|
||||
|
||||
func isDeptRoot(d *entity.Dept) bool {
|
||||
return d.ParentID == "" || d.ParentID == "0"
|
||||
}
|
||||
|
||||
func (s *deptService) Tree(ctx context.Context, tenantID string, keyword string, leaderID *string) ([]DeptNode, error) {
|
||||
rows, err := s.depts.ListByTenant(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filtered := rows
|
||||
if keyword != "" || leaderID != nil {
|
||||
filtered = make([]entity.Dept, 0)
|
||||
for _, d := range rows {
|
||||
if keyword != "" && !strings.Contains(d.DeptName, keyword) {
|
||||
continue
|
||||
}
|
||||
if leaderID != nil && (d.LeaderID == nil || *d.LeaderID != *leaderID) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, d)
|
||||
}
|
||||
if keyword != "" || leaderID != nil {
|
||||
filtered = s.includeAncestors(rows, filtered)
|
||||
}
|
||||
}
|
||||
return buildDeptTree(filtered, ""), nil
|
||||
}
|
||||
|
||||
func (s *deptService) includeAncestors(all []entity.Dept, matched []entity.Dept) []entity.Dept {
|
||||
idSet := map[string]struct{}{}
|
||||
byID := map[string]entity.Dept{}
|
||||
for _, d := range all {
|
||||
byID[d.ID] = d
|
||||
}
|
||||
for _, d := range matched {
|
||||
cur := d
|
||||
for {
|
||||
idSet[cur.ID] = struct{}{}
|
||||
if isDeptRoot(&cur) {
|
||||
break
|
||||
}
|
||||
p, ok := byID[cur.ParentID]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
cur = p
|
||||
}
|
||||
}
|
||||
out := make([]entity.Dept, 0, len(idSet))
|
||||
for _, d := range all {
|
||||
if _, ok := idSet[d.ID]; ok {
|
||||
out = append(out, d)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func buildDeptTree(rows []entity.Dept, parentID string) []DeptNode {
|
||||
children := map[string][]entity.Dept{}
|
||||
for _, d := range rows {
|
||||
pid := d.ParentID
|
||||
if d.ParentID == "0" {
|
||||
pid = ""
|
||||
}
|
||||
children[pid] = append(children[pid], d)
|
||||
}
|
||||
var walk func(pid string) []DeptNode
|
||||
walk = func(pid string) []DeptNode {
|
||||
list := children[pid]
|
||||
out := make([]DeptNode, 0, len(list))
|
||||
for _, d := range list {
|
||||
out = append(out, DeptNode{Dept: d, Children: walk(d.ID)})
|
||||
}
|
||||
return out
|
||||
}
|
||||
return walk(parentID)
|
||||
}
|
||||
|
||||
func (s *deptService) Create(ctx context.Context, tenantID string, req *CreateDeptRequest) (*entity.Dept, error) {
|
||||
parentKey := req.ParentID
|
||||
if parentKey == "0" {
|
||||
parentKey = ""
|
||||
}
|
||||
ok, err := s.depts.ExistsSiblingName(ctx, tenantID, parentKey, req.DeptName, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
return nil, fmt.Errorf("同级部门名称已存在")
|
||||
}
|
||||
d := &entity.Dept{
|
||||
ID: id.New(),
|
||||
TenantID: tenantID,
|
||||
ParentID: parentKey,
|
||||
DeptName: req.DeptName,
|
||||
LeaderID: req.LeaderID,
|
||||
SortOrder: req.SortOrder,
|
||||
Status: 1,
|
||||
}
|
||||
if err := s.depts.Create(ctx, d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := fmt.Sprintf("/%s/", d.ID)
|
||||
if parentKey != "" {
|
||||
p, err := s.depts.GetByID(ctx, parentKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.TenantID != tenantID {
|
||||
return nil, fmt.Errorf("父部门不属于当前租户")
|
||||
}
|
||||
base := p.DeptPath
|
||||
if base == "" {
|
||||
base = fmt.Sprintf("/%s/", p.ID)
|
||||
}
|
||||
path = base + fmt.Sprintf("%s/", d.ID)
|
||||
}
|
||||
if err := s.depts.UpdatePath(ctx, d.ID, path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.DeptPath = path
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (s *deptService) Update(ctx context.Context, tenantID string, id string, req *UpdateDeptRequest) (*entity.Dept, error) {
|
||||
d, err := s.depts.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
return nil, fmt.Errorf("部门不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if d.TenantID != tenantID {
|
||||
return nil, fmt.Errorf("部门不属于当前租户")
|
||||
}
|
||||
if isDeptRoot(d) {
|
||||
if req.ParentID != nil && *req.ParentID != "" && *req.ParentID != "0" {
|
||||
return nil, fmt.Errorf("根部门禁止移动")
|
||||
}
|
||||
if req.DeptName != nil && *req.DeptName != "" && *req.DeptName != d.DeptName {
|
||||
return nil, fmt.Errorf("根部门禁止重命名")
|
||||
}
|
||||
}
|
||||
curParent := d.ParentID
|
||||
if curParent == "0" {
|
||||
curParent = ""
|
||||
}
|
||||
var newParentForName string
|
||||
if req.ParentID != nil {
|
||||
np := *req.ParentID
|
||||
if np == "0" {
|
||||
np = ""
|
||||
}
|
||||
newParentForName = np
|
||||
} else {
|
||||
newParentForName = curParent
|
||||
}
|
||||
if req.DeptName != nil && *req.DeptName != "" {
|
||||
ok, err := s.depts.ExistsSiblingName(ctx, tenantID, newParentForName, *req.DeptName, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
return nil, fmt.Errorf("同级部门名称已存在")
|
||||
}
|
||||
d.DeptName = *req.DeptName
|
||||
}
|
||||
if req.ParentID != nil {
|
||||
npID := *req.ParentID
|
||||
if npID == "0" {
|
||||
npID = ""
|
||||
}
|
||||
if npID != curParent {
|
||||
if npID == id {
|
||||
return nil, fmt.Errorf("不能将部门移动到自身之下")
|
||||
}
|
||||
if npID != "" {
|
||||
if s.isDescendant(ctx, id, npID) {
|
||||
return nil, fmt.Errorf("禁止移动至子部门(防环)")
|
||||
}
|
||||
np, err := s.depts.GetByID(ctx, npID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("父部门无效")
|
||||
}
|
||||
if np.TenantID != tenantID {
|
||||
return nil, fmt.Errorf("父部门不属于当前租户")
|
||||
}
|
||||
d.ParentID = npID
|
||||
base := np.DeptPath
|
||||
if base == "" {
|
||||
base = fmt.Sprintf("/%s/", np.ID)
|
||||
}
|
||||
d.DeptPath = base + fmt.Sprintf("%s/", d.ID)
|
||||
} else {
|
||||
d.ParentID = ""
|
||||
d.DeptPath = fmt.Sprintf("/%s/", d.ID)
|
||||
}
|
||||
_ = s.depts.UpdatePath(ctx, d.ID, d.DeptPath)
|
||||
}
|
||||
}
|
||||
if req.LeaderID != nil {
|
||||
d.LeaderID = req.LeaderID
|
||||
}
|
||||
if req.SortOrder != nil {
|
||||
d.SortOrder = *req.SortOrder
|
||||
}
|
||||
if err := s.depts.Update(ctx, d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (s *deptService) isDescendant(ctx context.Context, rootID, nodeID string) bool {
|
||||
if nodeID == rootID {
|
||||
return true
|
||||
}
|
||||
cur, err := s.depts.GetByID(ctx, nodeID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < 64 && cur.ParentID != "" && cur.ParentID != "0"; i++ {
|
||||
if cur.ParentID == rootID {
|
||||
return true
|
||||
}
|
||||
cur, err = s.depts.GetByID(ctx, cur.ParentID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *deptService) Delete(ctx context.Context, tenantID string, ids []string) error {
|
||||
for _, did := range ids {
|
||||
d, err := s.depts.GetByID(ctx, did)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.TenantID != tenantID {
|
||||
return fmt.Errorf("部门 %s 不属于当前租户", did)
|
||||
}
|
||||
if isDeptRoot(d) {
|
||||
return fmt.Errorf("根部门禁止删除")
|
||||
}
|
||||
n, err := s.depts.CountChildren(ctx, did)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
return fmt.Errorf("部门 %s 存在子部门", did)
|
||||
}
|
||||
uc, err := s.users.CountByDept(ctx, did)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if uc > 0 {
|
||||
return fmt.Errorf("部门 %s 仍存在用户", did)
|
||||
}
|
||||
if err := s.depts.Delete(ctx, did); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *deptService) Get(ctx context.Context, tenantID string, id string) (*entity.Dept, error) {
|
||||
d, err := s.depts.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.TenantID != tenantID {
|
||||
return nil, fmt.Errorf("部门不属于当前租户")
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
Reference in New Issue
Block a user