package applogic import ( "context" "fmt" "strings" "aijianzhan/platform/internal/agentcap" "aijianzhan/platform/internal/agentstore" "aijianzhan/platform/internal/authx" "aijianzhan/platform/internal/svc" "aijianzhan/platform/internal/tenantperm" "aijianzhan/platform/internal/types" ) type AgentAdminLogic struct { ctx context.Context svcCtx *svc.ServiceContext } func NewAgentAdminLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AgentAdminLogic { return &AgentAdminLogic{ctx: ctx, svcCtx: svcCtx} } func (l *AgentAdminLogic) store() (agentstore.Store, error) { if l.svcCtx.Agents == nil { return nil, fmt.Errorf("agent store unavailable") } return l.svcCtx.Agents, nil } func (l *AgentAdminLogic) List() ([]agentstore.Account, error) { st, err := l.store() if err != nil { return nil, err } items, err := st.List(l.ctx, authx.TenantID(l.ctx)) if err != nil { return nil, err } for i := range items { l.attachRole(&items[i]) } return items, nil } func (l *AgentAdminLogic) Get(agentID int64) (*agentstore.Account, error) { st, err := l.store() if err != nil { return nil, err } acc, err := st.Get(l.ctx, authx.TenantID(l.ctx), agentID) if err != nil { return nil, err } l.attachRole(acc) return acc, nil } func (l *AgentAdminLogic) attachRole(acc *agentstore.Account) { if acc == nil || acc.RoleID <= 0 || l.svcCtx.Roles == nil { return } role, err := l.svcCtx.Roles.Get(l.ctx, authx.TenantID(l.ctx), acc.RoleID) if err != nil { return } acc.RoleCode = role.Code acc.RoleName = role.Name } func (l *AgentAdminLogic) resolvePerms(roleID int64, fallback []string) (int64, []string, error) { if roleID <= 0 { return 0, authx.NormalizePerms(fallback), nil } if l.svcCtx.Roles == nil { return 0, nil, fmt.Errorf("role store unavailable") } role, err := l.svcCtx.Roles.Get(l.ctx, authx.TenantID(l.ctx), roleID) if err != nil { return 0, nil, err } return role.RoleID, authx.NormalizePerms(append([]string{}, role.Permissions...)), nil } func (l *AgentAdminLogic) Create(req *types.AgentCreateReq) (*types.AgentCreateResp, error) { st, err := l.store() if err != nil { return nil, err } roleID, perms, err := l.resolvePerms(req.RoleID, req.Permissions) if err != nil { return nil, err } if roleID <= 0 && len(perms) == 0 { return nil, fmt.Errorf("role_id or permissions required") } if err := tenantperm.MustAllow(l.ctx, l.svcCtx.TenantPerm, authx.TenantID(l.ctx), perms); err != nil { return nil, err } acc, secret, err := st.Create(l.ctx, authx.TenantID(l.ctx), authx.UserID(l.ctx), agentstore.CreateInput{ Name: req.Name, Perms: perms, AppSlugs: req.AppSlugs, Status: agentstore.StatusActive, RoleID: roleID, ChannelID: req.ChannelID, OnlineDBID: req.OnlineDBID, DatabaseName: req.DatabaseName, }) if err != nil { return nil, err } if acc2, err := l.ensureAgentSyncBind(acc); err == nil && acc2 != nil { acc = acc2 } else if err != nil { // 自动绑通道失败不阻断创建,但返回提示 l.attachRole(acc) return &types.AgentCreateResp{Account: *acc, ClientSecret: secret}, nil } l.attachRole(acc) return &types.AgentCreateResp{Account: *acc, ClientSecret: secret}, nil } func (l *AuthLogic) SelfRegisterAgent(req *types.AgentSelfRegisterReq) (*types.AgentSelfRegisterResp, error) { if l.svcCtx.Agents == nil { return nil, fmt.Errorf("agent store unavailable") } want := l.svcCtx.Config.Agent.RegisterSecret if want == "" { want = l.svcCtx.Config.Auth.IssueSecret } if want == "" { want = l.svcCtx.JWT.AccessSecret } if req.RegisterSecret == "" || req.RegisterSecret != want { return nil, fmt.Errorf("invalid register secret") } tenantID := req.TenantID if tenantID <= 0 { tenantID = 1 } acc, secret, reused, err := l.svcCtx.Agents.Register(l.ctx, tenantID, req.Name, req.HostKey) if err != nil { return nil, err } msg := "已登记为 pending,请管理员在控制台分配角色并启用后再换票" if reused { msg = "已存在 pending 登记,已轮换 client_secret;仍须管理员分配角色并启用" } return &types.AgentSelfRegisterResp{ Account: *acc, ClientSecret: secret, Reused: reused, Message: msg, }, nil } func (l *AgentAdminLogic) Update(agentID int64, req *types.AgentUpdateReq) (*agentstore.Account, error) { st, err := l.store() if err != nil { return nil, err } in := agentstore.UpdateInput{} if req.Name != nil { in.Name = req.Name } if req.Status != nil { in.Status = req.Status } if req.AppSlugs != nil { in.AppSlugs = req.AppSlugs } if req.ChannelID != nil { in.ChannelID = req.ChannelID } if req.OnlineDBID != nil { in.OnlineDBID = req.OnlineDBID } if req.DatabaseName != nil { in.DatabaseName = req.DatabaseName } if req.RoleID != nil { roleID, perms, err := l.resolvePerms(*req.RoleID, nil) if err != nil { return nil, err } if err := tenantperm.MustAllow(l.ctx, l.svcCtx.TenantPerm, authx.TenantID(l.ctx), perms); err != nil { return nil, err } in.RoleID = &roleID in.Perms = &perms } else if req.Permissions != nil { n := authx.NormalizePerms(*req.Permissions) if err := tenantperm.MustAllow(l.ctx, l.svcCtx.TenantPerm, authx.TenantID(l.ctx), n); err != nil { return nil, err } in.Perms = &n } acc, err := st.Update(l.ctx, authx.TenantID(l.ctx), agentID, in) if err != nil { return nil, err } // Z12c:启用为 active 且无通道时自动绑默认同步通道 if acc.Status == agentstore.StatusActive { if acc2, err := l.ensureAgentSyncBind(acc); err == nil && acc2 != nil { acc = acc2 } } l.attachRole(acc) return acc, nil } func (l *AgentAdminLogic) Rotate(agentID int64) (*types.AgentSecretResp, error) { st, err := l.store() if err != nil { return nil, err } secret, err := st.RotateSecret(l.ctx, authx.TenantID(l.ctx), agentID) if err != nil { return nil, err } acc, err := st.Get(l.ctx, authx.TenantID(l.ctx), agentID) if err != nil { return nil, err } return &types.AgentSecretResp{ClientID: acc.ClientID, ClientSecret: secret}, nil } func (l *AgentAdminLogic) Delete(agentID int64) error { st, err := l.store() if err != nil { return err } return st.Delete(l.ctx, authx.TenantID(l.ctx), agentID) } func (l *AuthLogic) IssueClientCredentials(clientID, clientSecret string) (*types.TokenResp, error) { if l.svcCtx.Agents == nil { return nil, fmt.Errorf("agent store unavailable") } acc, err := l.svcCtx.Agents.Authenticate(l.ctx, strings.TrimSpace(clientID), clientSecret) if err != nil { return nil, err } token, exp, err := authx.IssueAgentToken(l.svcCtx.JWT, acc.TenantID, acc.AgentID, acc.Perms) if err != nil { return nil, err } _ = l.svcCtx.Agents.TouchToken(l.ctx, acc.AgentID) secret := l.svcCtx.Config.Agent.CapsuleSecret if secret == "" { secret = l.svcCtx.JWT.AccessSecret } resp := &types.TokenResp{ AccessToken: token, TokenType: "Bearer", ExpiresAt: exp, TenantID: acc.TenantID, UserID: acc.AgentID, Username: acc.ClientID, DisplayName: acc.Name, Role: authx.RoleAgent, AgentKey: agentcap.PublicAgentKey(secret, acc.TenantID, acc.AgentID), AgentID: acc.AgentID, Permissions: append([]string{}, acc.Perms...), AppSlugs: append([]string{}, acc.AppSlugs...), } fillAgentSyncOnToken(resp, acc) return resp, nil }