feat: 优化web

This commit is contained in:
2026-04-23 18:58:13 +08:00
commit 544a2f3428
160 changed files with 27327 additions and 0 deletions
+326
View File
@@ -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
}