Files
ai_site/platform/internal/logic/applogic/platform.go
2026-07-31 10:31:17 +08:00

382 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}