262 lines
7.2 KiB
Go
262 lines
7.2 KiB
Go
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
|
|
}
|