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
+261
View File
@@ -0,0 +1,261 @@
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
}