chore: initial commit of ai site platform

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-07-31 10:31:17 +08:00
commit 4ca82fb58a
203 changed files with 45745 additions and 0 deletions

View File

@@ -0,0 +1,381 @@
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
}