package service import ( "context" "errors" "fmt" "time" "giter.top/smart/internal/iam/entity" "giter.top/smart/internal/iam/repository" "giter.top/smart/pkg/utils/id" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // TenantService 租户 type TenantService interface { Create(ctx context.Context, req *CreateTenantRequest) (*entity.Tenant, error) Update(ctx context.Context, id string, req *UpdateTenantRequest) (*entity.Tenant, error) Delete(ctx context.Context, ids []string) error Get(ctx context.Context, id string) (*entity.Tenant, error) List(ctx context.Context, name, code string, status *int16, page, pageSize int) (*TenantListResponse, error) } type CreateTenantRequest struct { TenantCode string `json:"tenant_code" binding:"required,max=64"` TenantName string `json:"tenant_name" binding:"required,max=128"` AdminUserName string `json:"admin_user_name" binding:"required,max=64"` AdminPassword string `json:"admin_password" binding:"required,min=6,max=64"` AdminRealName string `json:"admin_real_name" binding:"max=64"` } type UpdateTenantRequest struct { TenantName *string `json:"tenant_name"` TenantCode *string `json:"tenant_code" binding:"omitempty,max=64"` Status *int16 `json:"status"` ExpireTime *string `json:"expire_time"` // RFC3339 } type TenantListItem struct { entity.Tenant UserCount int64 `json:"user_count"` DeptCount int64 `json:"dept_count"` } type TenantListResponse struct { Items []TenantListItem `json:"items"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` TotalPages int `json:"total_pages"` } type tenantService struct { db *gorm.DB tenants repository.TenantRepository depts repository.DeptRepository users repository.UserRepository roles repository.RoleRepository menus repository.MenuRepository } func NewTenantService( db *gorm.DB, tenants repository.TenantRepository, depts repository.DeptRepository, users repository.UserRepository, roles repository.RoleRepository, menus repository.MenuRepository, ) TenantService { return &tenantService{db: db, tenants: tenants, depts: depts, users: users, roles: roles, menus: menus} } func (s *tenantService) Create(ctx context.Context, req *CreateTenantRequest) (*entity.Tenant, error) { ok, err := s.tenants.ExistsCode(ctx, req.TenantCode, "") if err != nil { return nil, err } if ok { return nil, fmt.Errorf("租户编码已存在") } hash, err := bcrypt.GenerateFromPassword([]byte(req.AdminPassword), bcrypt.DefaultCost) if err != nil { return nil, err } var out *entity.Tenant err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { t := &entity.Tenant{ ID: id.New(), TenantCode: req.TenantCode, TenantName: req.TenantName, Status: 1, } if err := tx.Create(t).Error; err != nil { return err } var ucount int64 if err := tx.Model(&entity.User{}).Where("tenant_id = ? AND user_name = ?", t.ID, req.AdminUserName).Count(&ucount).Error; err != nil { return err } if ucount > 0 { return fmt.Errorf("管理员账号已存在") } root := &entity.Dept{ ID: id.New(), TenantID: t.ID, ParentID: "", DeptName: req.TenantName, SortOrder: 0, Status: 1, } if err := tx.Create(root).Error; err != nil { return err } path := fmt.Sprintf("/%s/", root.ID) if err := tx.Model(&entity.Dept{}).Where("id = ?", root.ID).Update("dept_path", path).Error; err != nil { return err } admin := &entity.User{ ID: id.New(), TenantID: t.ID, DeptID: &root.ID, UserName: req.AdminUserName, RealName: req.AdminRealName, PasswordHash: string(hash), Status: 1, } if err := tx.Create(admin).Error; err != nil { return err } if err := tx.Create(&entity.UserDept{ID: id.New(), UserID: admin.ID, DeptID: root.ID, IsPrimary: true}).Error; err != nil { return err } role := &entity.Role{ ID: id.New(), TenantID: t.ID, RoleCode: DefaultTenantAdminRoleCode, RoleName: "超级管理员", DataScope: entity.DataScopeAll, Description: "租户初始化角色", IsBuiltin: true, Status: 1, } if err := tx.Create(role).Error; err != nil { return err } var allMenus []entity.Menu if err := tx.Find(&allMenus).Error; err != nil { return err } for _, m := range allMenus { if err := tx.Create(&entity.RoleMenu{ID: id.New(), RoleID: role.ID, MenuID: m.ID}).Error; err != nil { return err } } if err := tx.Create(&entity.UserRole{ID: id.New(), UserID: admin.ID, RoleID: role.ID}).Error; err != nil { return err } aid := admin.ID if err := tx.Model(t).Update("admin_user_id", aid).Error; err != nil { return err } t.AdminUserID = &aid out = t return nil }) if err != nil { return nil, err } return out, nil } func (s *tenantService) Update(ctx context.Context, id string, req *UpdateTenantRequest) (*entity.Tenant, error) { t, err := s.tenants.GetByID(ctx, id) if err != nil { if errors.Is(err, repository.ErrNotFound) { return nil, fmt.Errorf("租户不存在") } return nil, err } if req.TenantName != nil && *req.TenantName != "" { t.TenantName = *req.TenantName if root, err := s.depts.FindRoot(ctx, t.ID); err == nil { root.DeptName = *req.TenantName _ = s.depts.Update(ctx, root) } } if req.TenantCode != nil && *req.TenantCode != "" { ok, err := s.tenants.ExistsCode(ctx, *req.TenantCode, id) if err != nil { return nil, err } if ok { return nil, fmt.Errorf("租户编码已存在") } t.TenantCode = *req.TenantCode } if req.Status != nil { t.Status = *req.Status } if req.ExpireTime != nil && *req.ExpireTime != "" { et, err := time.Parse(time.RFC3339, *req.ExpireTime) if err != nil { return nil, fmt.Errorf("到期时间格式无效: %w", err) } t.ExpireTime = &et if et.Before(time.Now()) { t.Status = 0 } } if err := s.tenants.Update(ctx, t); err != nil { return nil, err } return t, nil } func (s *tenantService) Delete(ctx context.Context, ids []string) error { for _, tid := range ids { n, err := s.tenants.CountUsers(ctx, tid) if err != nil { return err } if n > 0 { return fmt.Errorf("租户 %s 仍存在用户,无法删除", tid) } } for _, tid := range ids { if err := s.db.WithContext(ctx).Delete(&entity.Tenant{}, "id = ?", tid).Error; err != nil { return err } } return nil } func (s *tenantService) Get(ctx context.Context, id string) (*entity.Tenant, error) { return s.tenants.GetByID(ctx, id) } func (s *tenantService) List(ctx context.Context, name, code string, status *int16, page, pageSize int) (*TenantListResponse, error) { if page <= 0 { page = 1 } if pageSize <= 0 { pageSize = 10 } rows, total, err := s.tenants.List(ctx, name, code, status, page, pageSize) if err != nil { return nil, err } items := make([]TenantListItem, 0, len(rows)) for _, t := range rows { uc, _ := s.tenants.CountUsers(ctx, t.ID) dc, _ := s.tenants.CountDepts(ctx, t.ID) items = append(items, TenantListItem{Tenant: t, UserCount: uc, DeptCount: dc}) } tp := int(total) / pageSize if int(total)%pageSize != 0 { tp++ } return &TenantListResponse{Items: items, Total: total, Page: page, PageSize: pageSize, TotalPages: tp}, nil }