Files
ai_site/platform/internal/logic/applogic/agents.go
whm cb56e6847e feat: add Z12/Z13 bind APIs, stock import, and sync docs
Enable auto default sync channels on agent activate, bind-code/phone confirm flows, publish ALTER, and align admin/yuheng docs with the production bind path.
2026-08-05 11:47:20 +08:00

272 lines
7.2 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 (
"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
}