package applogic import ( "fmt" "strings" "time" "aijianzhan/platform/internal/authx" "aijianzhan/platform/internal/invitestore" "aijianzhan/platform/internal/types" "aijianzhan/platform/internal/userstore" ) type PlatformLogic struct { *AuthLogic } type CreateTenantResult struct { Tenant *userstore.TenantInfo `json:"tenant"` AdminAccount *AdminAccountOut `json:"admin_account,omitempty"` AdminInvite *invitestore.Invite `json:"admin_invite,omitempty"` } // AdminAccountOut 新建公司时下发的公司管理员凭据(密码仅此一次明文返回)。 type AdminAccountOut struct { UserID int64 `json:"user_id"` Username string `json:"username"` Password string `json:"password,omitempty"` Phone string `json:"phone,omitempty"` UsernameLoginDisabled bool `json:"username_login_disabled,omitempty"` DisplayName string `json:"display_name"` Role string `json:"role"` } type AdminInfo struct { UserID int64 `json:"user_id"` Username string `json:"username"` Phone string `json:"phone"` UsernameLoginDisabled bool `json:"username_login_disabled"` DisplayName string `json:"display_name"` Role string `json:"role"` Status string `json:"status"` } func NewPlatformLogic(l *AuthLogic) *PlatformLogic { return &PlatformLogic{AuthLogic: l} } func (l *PlatformLogic) requireSuper() (*userstore.User, error) { if !authx.IsPlatformAdmin(authx.Role(l.ctx)) { return nil, fmt.Errorf("需要超级管理员") } u, err := l.svcCtx.Users.GetByID(l.ctx, authx.UserID(l.ctx)) if err != nil { return nil, err } if !u.IsPlatformAdmin() { return nil, fmt.Errorf("需要超级管理员") } return u, nil } func (l *PlatformLogic) ListTenants() ([]userstore.TenantInfo, error) { if _, err := l.requireSuper(); err != nil { return nil, err } return l.svcCtx.Users.ListTenants(l.ctx) } func (l *PlatformLogic) CreateTenant(name, slug string, withAdminInvite bool, adminPhone string) (*CreateTenantResult, error) { super, err := l.requireSuper() if err != nil { return nil, err } phone := strings.TrimSpace(adminPhone) if l.phoneLoginOnly() && phone == "" { return nil, fmt.Errorf("本部署仅手机号登录,请填写管理员手机号") } t, err := l.svcCtx.Users.CreateTenant(l.ctx, strings.TrimSpace(name), strings.TrimSpace(slug)) if err != nil { return nil, err } if l.svcCtx.Roles != nil { _ = l.svcCtx.Roles.EnsureDefaults(l.ctx, t.TenantID) } if l.svcCtx.TenantPerm != nil { _ = l.svcCtx.TenantPerm.EnsureDefault(l.ctx, t.TenantID) } out := &CreateTenantResult{Tenant: t} acc, aerr := l.createCompanyAdmin(t.TenantID, strings.TrimSpace(name), phone) if aerr != nil { return nil, fmt.Errorf("创建公司成功,但管理员账号生成失败: %v", aerr) } out.AdminAccount = acc if withAdminInvite && l.svcCtx.Invites != nil { inv, ierr := l.svcCtx.Invites.Create(l.ctx, t.TenantID, super.UserID, invitestore.CreateInput{ Role: authx.Role管理员, MaxUses: 1, ExpiresIn: 7 * 24 * time.Hour, }) if ierr == nil { out.AdminInvite = inv } } return out, nil } // createCompanyAdmin 生成随机唯一管理员;phone 非空时绑定手机。 func (l *PlatformLogic) createCompanyAdmin(tenantID int64, companyName, phone string) (*AdminAccountOut, error) { uname, err := userstore.AllocUniqueUsername(l.ctx, l.svcCtx.Users.UsernameExists) if err != nil { return nil, err } display := strings.TrimSpace(companyName) if display == "" { display = "公司" } display += "管理员" u, plain, err := l.svcCtx.Users.CreateMember(l.ctx, tenantID, uname, "", display, authx.Role管理员, 0) if err != nil { if strings.Contains(err.Error(), "already exists") { uname2, e2 := userstore.AllocUniqueUsername(l.ctx, l.svcCtx.Users.UsernameExists) if e2 != nil { return nil, e2 } u, plain, err = l.svcCtx.Users.CreateMember(l.ctx, tenantID, uname2, "", display, authx.Role管理员, 0) } if err != nil { return nil, err } } out := &AdminAccountOut{ UserID: u.UserID, Username: u.Username, Password: plain, DisplayName: u.DisplayName, Role: u.Role, } phone = strings.TrimSpace(phone) if phone == "" { return out, nil } ns, nerr := userstore.NormalizePhone(phone) if nerr != nil { return nil, nerr } nu, berr := l.svcCtx.Users.BindPhone(l.ctx, u.UserID, ns) if berr != nil { return nil, fmt.Errorf("账号已创建但绑定手机失败: %v", berr) } out.Phone = nu.Phone return out, nil } // IssueAdminAccount 为已有公司再发一个管理员账号;有期限控制时手机号可选。 func (l *PlatformLogic) IssueAdminAccount(tenantID int64, adminPhone string) (*AdminAccountOut, error) { if _, err := l.requireSuper(); err != nil { return nil, err } if l.phoneLoginOnly() && strings.TrimSpace(adminPhone) == "" { return nil, fmt.Errorf("本部署仅手机号登录,请填写管理员手机号") } t, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID) if err != nil { return nil, err } return l.createCompanyAdmin(t.TenantID, t.Name, adminPhone) } // ListCompanyAdmins 列出该公司人类管理员账号(用户名唯一不可改)。 func (l *PlatformLogic) ListCompanyAdmins(tenantID int64) ([]AdminInfo, error) { if _, err := l.requireSuper(); err != nil { return nil, err } if _, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID); err != nil { return nil, err } members, err := l.svcCtx.Users.ListMembers(l.ctx, tenantID) if err != nil { return nil, err } out := make([]AdminInfo, 0) for _, u := range members { if !authx.IsCompanyAdmin(u.Role) { continue } out = append(out, AdminInfo{ UserID: u.UserID, Username: u.Username, Phone: u.Phone, UsernameLoginDisabled: u.UsernameLoginDisabled, DisplayName: u.DisplayName, Role: u.Role, Status: u.Status, }) } return out, nil } // UpdateCompanyAdmin 平台超管重置该公司管理员密码 / 绑定手机 / 禁用用户名登录(用户名不变)。 func (l *PlatformLogic) UpdateCompanyAdmin(tenantID, userID int64, resetPassword bool, password string, phone *string, disableUsernameLogin *bool) (*AdminAccountOut, error) { if _, err := l.requireSuper(); err != nil { return nil, err } if _, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID); err != nil { return nil, err } u, err := l.svcCtx.Users.GetByID(l.ctx, userID) if err != nil { return nil, err } if u.TenantID != tenantID { return nil, fmt.Errorf("成员不属于该公司") } if !authx.IsCompanyAdmin(u.Role) { return nil, fmt.Errorf("仅可管理公司管理员账号") } if authx.IsPlatformAdmin(u.Role) { return nil, fmt.Errorf("cannot edit platform admin") } out := &AdminAccountOut{ UserID: u.UserID, Username: u.Username, Phone: u.Phone, UsernameLoginDisabled: u.UsernameLoginDisabled, DisplayName: u.DisplayName, Role: u.Role, } if resetPassword { plain, err := l.svcCtx.Users.SetPassword(l.ctx, userID, password) if err != nil { return nil, err } out.Password = plain } if phone != nil { if l.phoneLoginOnly() && strings.TrimSpace(*phone) == "" { return nil, fmt.Errorf("本部署仅支持手机号登录,不可解绑手机号") } nu, err := l.svcCtx.Users.BindPhone(l.ctx, userID, *phone) if err != nil { return nil, err } out.Phone = nu.Phone out.DisplayName = nu.DisplayName out.UsernameLoginDisabled = nu.UsernameLoginDisabled } if disableUsernameLogin != nil { nu, err := l.svcCtx.Users.SetUsernameLoginDisabled(l.ctx, userID, *disableUsernameLogin) if err != nil { return nil, err } out.Phone = nu.Phone out.UsernameLoginDisabled = nu.UsernameLoginDisabled } return out, nil } func (l *PlatformLogic) UpdateTenant(tenantID int64, name, slug string) (*userstore.TenantInfo, error) { if _, err := l.requireSuper(); err != nil { return nil, err } return l.svcCtx.Users.UpdateTenant(l.ctx, tenantID, name, slug) } func (l *PlatformLogic) GetTenantPerms(tenantID int64) ([]string, error) { if _, err := l.requireSuper(); err != nil { return nil, err } if l.svcCtx.TenantPerm == nil { return authx.CompanyPermCatalog(), nil } if _, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID); err != nil { return nil, err } return l.svcCtx.TenantPerm.Get(l.ctx, tenantID) } func (l *PlatformLogic) SetTenantPerms(tenantID int64, perms []string) ([]string, error) { if _, err := l.requireSuper(); err != nil { return nil, err } if l.svcCtx.TenantPerm == nil { return nil, fmt.Errorf("tenant perm store unavailable") } if _, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID); err != nil { return nil, err } if err := l.svcCtx.TenantPerm.Set(l.ctx, tenantID, perms); err != nil { return nil, err } return l.svcCtx.TenantPerm.Get(l.ctx, tenantID) } func (l *PlatformLogic) IssueAdminInvite(tenantID int64) (*invitestore.Invite, error) { super, err := l.requireSuper() if err != nil { return nil, err } if l.svcCtx.Invites == nil { return nil, fmt.Errorf("invite store unavailable") } if _, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID); err != nil { return nil, err } return l.svcCtx.Invites.Create(l.ctx, tenantID, super.UserID, invitestore.CreateInput{ Role: authx.Role管理员, MaxUses: 1, ExpiresIn: 7 * 24 * time.Hour, }) } // EnterTenant 进入某公司上下文(JWT 仍为超级管理员,但带上 tenant_id)。 func (l *PlatformLogic) EnterTenant(tenantID int64) (*types.TokenResp, error) { super, err := l.requireSuper() if err != nil { return nil, err } if tenantID <= 0 { return nil, fmt.Errorf("tenant_id required") } t, err := l.svcCtx.Users.GetTenant(l.ctx, tenantID) if err != nil { return nil, err } resp, err := l.issue(t.TenantID, super.UserID, authx.Role超级管理员, super.Username, super.DisplayName, 0) if err != nil { return nil, err } resp.Phone = super.Phone resp.Status = userstore.StatusActive resp.TenantName = t.Name resp.Message = fmt.Sprintf("已打开「%s」的管理视图(你仍是平台超管,不是该公司账号)", t.Name) return resp, nil } // ExitTenant 退出公司管理视图,回到平台工作台(tenant_id=0)。 func (l *PlatformLogic) ExitTenant() (*types.TokenResp, error) { super, err := l.requireSuper() if err != nil { return nil, err } resp, err := l.issue(0, super.UserID, authx.Role超级管理员, super.Username, super.DisplayName, 0) if err != nil { return nil, err } resp.Phone = super.Phone resp.Status = userstore.StatusActive resp.Message = "已回到平台工作台" return resp, nil } // CompanyEntitlements 本公司可用权限。 func (l *AuthLogic) CompanyEntitlements() ([]string, []authx.PermModule, error) { tid := authx.TenantID(l.ctx) if tid <= 0 { return nil, nil, fmt.Errorf("missing tenant") } var perms []string var err error // 超管进入公司后看全量权限模块,便于代管(不受该公司额度裁剪展示) if authx.IsPlatformAdmin(authx.Role(l.ctx)) { perms = authx.CompanyPermCatalog() } else if l.svcCtx.TenantPerm != nil { perms, err = l.svcCtx.TenantPerm.Get(l.ctx, tid) if err != nil { return nil, nil, err } } else { perms = authx.CompanyPermCatalog() } allow := map[string]struct{}{} for _, p := range perms { allow[p] = struct{}{} } var modules []authx.PermModule for _, m := range authx.PermModules() { var items []authx.PermItem for _, it := range m.Items { if _, ok := allow[it.Perm]; ok { items = append(items, it) } } if len(items) > 0 { modules = append(modules, authx.PermModule{Title: m.Title, Items: items}) } } return perms, modules, nil }