fix: Z39 isolate module data by slug

Keep agent-bound modules in per-app schemas and prevent stale frontend routes from displaying another module's blueprint or rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-08-07 17:31:12 +08:00
parent 43dc708f1e
commit 473113682f
13 changed files with 402 additions and 207 deletions

View File

@@ -18,13 +18,15 @@ const Prefix = "AJZ1"
// Descriptor 智能体解密后才能看到的请求契约(前端不展示明文)。
type Descriptor struct {
Version string `json:"v"`
BaseURL string `json:"base_url"`
AppSlug string `json:"app_slug"`
TenantHint string `json:"tenant_hint,omitempty"`
Auth AuthSpec `json:"auth"`
Resources []ResourceSpec `json:"resources"`
Notes string `json:"notes,omitempty"`
Version string `json:"v"`
BaseURL string `json:"base_url"`
AppSlug string `json:"app_slug"`
TenantHint string `json:"tenant_hint,omitempty"`
SchemaName string `json:"schema_name,omitempty"`
BlueprintRevision string `json:"blueprint_revision,omitempty"`
Auth AuthSpec `json:"auth"`
Resources []ResourceSpec `json:"resources"`
Notes string `json:"notes,omitempty"`
}
type AuthSpec struct {
@@ -33,13 +35,14 @@ type AuthSpec struct {
}
type ResourceSpec struct {
Name string `json:"name"`
Path string `json:"path"`
Methods []string `json:"methods"`
Filters []string `json:"filters,omitempty"`
Sorts []string `json:"sorts,omitempty"`
Name string `json:"name"`
Path string `json:"path"`
Methods []string `json:"methods"`
Filters []string `json:"filters,omitempty"`
Sorts []string `json:"sorts,omitempty"`
Fields []FieldSpec `json:"fields,omitempty"`
PrimaryKey string `json:"primary_key"`
PrimaryKey string `json:"primary_key"`
Table string `json:"table,omitempty"`
}
type FieldSpec struct {

View File

@@ -1,6 +1,7 @@
package blueprint
import (
"crypto/sha256"
"encoding/json"
"fmt"
"regexp"
@@ -48,12 +49,12 @@ type Storage struct {
}
type Entity struct {
Name string `json:"name"`
Table string `json:"table"`
Label string `json:"label"`
PrimaryKey string `json:"primary_key"`
Fields []Field `json:"fields"`
Indexes []Index `json:"indexes,omitempty"`
Name string `json:"name"`
Table string `json:"table"`
Label string `json:"label"`
PrimaryKey string `json:"primary_key"`
Fields []Field `json:"fields"`
Indexes []Index `json:"indexes,omitempty"`
}
type Field struct {
@@ -271,10 +272,7 @@ func (bp *Blueprint) AssignSchemaName(tenantID int64) string {
bp.Storage.SchemaName = "public"
return "public"
}
name := fmt.Sprintf("app_t%d_%s", tenantID, bp.Meta.Slug)
if len(name) > 48 {
name = name[:48]
}
name := scopedIdent(fmt.Sprintf("app_t%d_%s", tenantID, bp.Meta.Slug))
bp.Storage.SchemaName = name
return name
}
@@ -284,11 +282,30 @@ func (bp *Blueprint) AssignDatabaseName(tenantID int64) string {
if bp.Storage.Mode != "database_per_app" {
return ""
}
name := fmt.Sprintf("appdb_t%d_%s", tenantID, bp.Meta.Slug)
if len(name) > 48 {
name = name[:48]
return scopedIdent(fmt.Sprintf("appdb_t%d_%s", tenantID, bp.Meta.Slug))
}
// scopedIdent 保留可读前缀,并为超长标识符附加内容哈希,避免不同 slug 截断后碰撞。
func scopedIdent(raw string) string {
if len(raw) <= 48 {
return raw
}
return name
sum := sha256.Sum256([]byte(raw))
suffix := fmt.Sprintf("_%x", sum[:6])
return raw[:48-len(suffix)] + suffix
}
// Revision 返回蓝图内容的稳定短哈希,用于发布回执、缓存隔离与诊断日志。
func (bp *Blueprint) Revision() string {
if bp == nil {
return ""
}
raw, err := json.Marshal(bp)
if err != nil {
return ""
}
sum := sha256.Sum256(raw)
return fmt.Sprintf("%x", sum[:8])
}
func checkIdent(field, v string) error {

View File

@@ -1,4 +1,4 @@
package blueprint
package blueprint
import "testing"
@@ -18,6 +18,32 @@ func TestNormalizeIdent(t *testing.T) {
}
}
func TestAssignSchemaNameKeepsLongSlugsDistinct(t *testing.T) {
prefix := "module_with_a_very_long_shared_slug_prefix_that_used_to_collide_"
a := &Blueprint{Meta: Meta{Slug: prefix + "a"}, Storage: Storage{Mode: "schema_per_app"}}
b := &Blueprint{Meta: Meta{Slug: prefix + "b"}, Storage: Storage{Mode: "schema_per_app"}}
gotA := a.AssignSchemaName(12)
gotB := b.AssignSchemaName(12)
if gotA == gotB {
t.Fatalf("different slugs resolved to the same schema: %q", gotA)
}
if len(gotA) > 48 || len(gotB) > 48 {
t.Fatalf("schema names exceed identifier limit: %q / %q", gotA, gotB)
}
if gotA != a.AssignSchemaName(12) {
t.Fatalf("schema name is not deterministic: %q", gotA)
}
}
func TestRevisionChangesWithSlug(t *testing.T) {
a := &Blueprint{Version: "1.0", Meta: Meta{Slug: "whm1"}}
b := &Blueprint{Version: "1.0", Meta: Meta{Slug: "whm11"}}
if a.Revision() == "" || a.Revision() == b.Revision() {
t.Fatalf("revision must include blueprint slug: %q / %q", a.Revision(), b.Revision())
}
}
func TestSanitizeFields(t *testing.T) {
bp := &Blueprint{
Version: "1.0",

View File

@@ -7,10 +7,10 @@ import (
"strconv"
"strings"
"aijianzhan/platform/internal/agentcap"
"aijianzhan/platform/internal/apidef"
"aijianzhan/platform/internal/audit"
"aijianzhan/platform/internal/authx"
"aijianzhan/platform/internal/agentcap"
"aijianzhan/platform/internal/logic/applogic"
"aijianzhan/platform/internal/meta"
"aijianzhan/platform/internal/svc"
@@ -1069,6 +1069,9 @@ func publicBlueprintHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
authx.WriteError(w, http.StatusNotFound, "blueprint missing")
return
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("X-AJZ-App-Slug", app.Slug)
w.Header().Set("X-AJZ-Blueprint-Revision", app.Blueprint.Revision())
httpx.OkJson(w, app.Blueprint)
}
}
@@ -1122,6 +1125,12 @@ func publicListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("X-AJZ-App-Slug", app.Slug)
w.Header().Set("X-AJZ-Resolved-Schema", app.SchemaName)
if app.Blueprint != nil {
w.Header().Set("X-AJZ-Blueprint-Revision", app.Blueprint.Revision())
}
httpx.OkJson(w, resp)
}
}

View File

@@ -309,12 +309,14 @@ func (l *CapsuleLogic) Build(slug string) (*types.CapsuleResp, error) {
base = fmt.Sprintf("http://127.0.0.1:%d", l.svcCtx.Config.Port)
}
desc := &agentcap.Descriptor{
Version: "1",
BaseURL: strings.TrimRight(base, "/"),
AppSlug: slug,
TenantHint: fmt.Sprintf("t%d", tenantID),
Auth: agentcap.AuthSpec{Type: "bearer_jwt", Header: "Authorization"},
Notes: "Decrypt with agent_key from /auth/token. Never expose plaintext API map in UI.",
Version: "1",
BaseURL: strings.TrimRight(base, "/"),
AppSlug: slug,
TenantHint: fmt.Sprintf("t%d", tenantID),
SchemaName: app.SchemaName,
BlueprintRevision: app.Blueprint.Revision(),
Auth: agentcap.AuthSpec{Type: "bearer_jwt", Header: "Authorization"},
Notes: "Decrypt with agent_key from /auth/token. Never expose plaintext API map in UI.",
}
for _, r := range app.Blueprint.Apis.Resources {
path := r.Path
@@ -337,11 +339,13 @@ func (l *CapsuleLogic) Build(slug string) (*types.CapsuleResp, error) {
}
var entityFields []agentcap.FieldSpec
pk := "id"
table := ""
for _, e := range app.Blueprint.Entities {
if e.Name != r.Entity {
continue
}
pk = e.PrimaryKey
table = e.Table
for _, f := range e.Fields {
entityFields = append(entityFields, agentcap.FieldSpec{Name: f.Name, Type: f.Type})
}
@@ -360,6 +364,7 @@ func (l *CapsuleLogic) Build(slug string) (*types.CapsuleResp, error) {
Sorts: sorts,
Fields: entityFields,
PrimaryKey: pk,
Table: app.SchemaName + "." + table,
})
}

View File

@@ -3,10 +3,12 @@ package applogic
import (
"context"
"fmt"
"log"
"strconv"
"aijianzhan/platform/internal/authx"
"aijianzhan/platform/internal/crud"
"aijianzhan/platform/internal/meta"
"aijianzhan/platform/internal/svc"
"aijianzhan/platform/internal/types"
)
@@ -40,7 +42,7 @@ func (l *CrudLogic) withOrgScope() context.Context {
func (l *CrudLogic) List(slug, resource string, page, pageSize int, filters map[string]string, sortBy string) (*types.PageResult, error) {
ctx := l.withOrgScope()
tenantID := authx.TenantID(ctx)
ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource)
ref, err := l.resolveResource(ctx, tenantID, slug, resource, "list")
if err != nil {
return nil, err
}
@@ -60,7 +62,7 @@ func (l *CrudLogic) List(slug, resource string, page, pageSize int, filters map[
func (l *CrudLogic) Get(slug, resource, id string) (map[string]any, error) {
ctx := l.withOrgScope()
tenantID := authx.TenantID(ctx)
ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource)
ref, err := l.resolveResource(ctx, tenantID, slug, resource, "get")
if err != nil {
return nil, err
}
@@ -71,7 +73,7 @@ func (l *CrudLogic) Create(slug, resource string, body map[string]any) (map[stri
ctx := l.withOrgScope()
tenantID := authx.TenantID(ctx)
userID := authx.UserID(ctx)
ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource)
ref, err := l.resolveResource(ctx, tenantID, slug, resource, "create")
if err != nil {
return nil, err
}
@@ -81,7 +83,7 @@ func (l *CrudLogic) Create(slug, resource string, body map[string]any) (map[stri
func (l *CrudLogic) Update(slug, resource, id string, body map[string]any) (map[string]any, error) {
ctx := l.withOrgScope()
tenantID := authx.TenantID(ctx)
ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource)
ref, err := l.resolveResource(ctx, tenantID, slug, resource, "update")
if err != nil {
return nil, err
}
@@ -91,13 +93,31 @@ func (l *CrudLogic) Update(slug, resource, id string, body map[string]any) (map[
func (l *CrudLogic) Delete(slug, resource, id string) error {
ctx := l.withOrgScope()
tenantID := authx.TenantID(ctx)
ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource)
ref, err := l.resolveResource(ctx, tenantID, slug, resource, "delete")
if err != nil {
return err
}
return l.svcCtx.CRUD.Delete(ctx, ref, tenantID, id)
}
func (l *CrudLogic) resolveResource(ctx context.Context, tenantID int64, slug, resource, operation string) (*meta.ResourceRef, error) {
ref, err := l.svcCtx.Meta.ResolveResource(ctx, tenantID, slug, resource)
if err != nil {
return nil, err
}
if ref == nil || ref.App == nil || ref.App.Slug != slug {
return nil, fmt.Errorf("resource isolation mismatch for app %s", slug)
}
if ref.App.SchemaName == "" || ref.Entity.Table == "" {
return nil, fmt.Errorf("resource storage mapping missing for app %s", slug)
}
log.Printf(
"app_resource op=%s tenant=%d requested_slug=%s resource=%s resolved_schema=%s resolved_table=%s blueprint_revision=%s",
operation, tenantID, slug, resource, ref.App.SchemaName, ref.Entity.Table, ref.App.Blueprint.Revision(),
)
return ref, nil
}
func (l *CrudLogic) GetBlueprint(slug string) (any, error) {
tenantID := authx.TenantID(l.ctx)
app, err := l.svcCtx.Meta.GetBySlug(l.ctx, tenantID, slug)

View File

@@ -230,11 +230,18 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi
mergeRes, err = blueprint.MergeInto(bp, incoming)
if err != nil {
if isNothingNewToPublish(err) {
return l.handleNoBlueprintDelta(existing, req, slug, tenantID, userID)
if needsSchemaIsolation(existing, authx.Role(l.ctx)) {
publishMode = "storage_isolated"
mergeRes = nil
} else {
return l.handleNoBlueprintDelta(existing, req, slug, tenantID, userID)
}
} else {
return nil, err
}
return nil, err
} else {
publishMode = "pages_added"
}
publishMode = "pages_added"
case mode == "replace":
bp = incoming
if exists {
@@ -253,11 +260,18 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi
mergeRes, err = blueprint.MergeInto(bp, incoming)
if err != nil {
if isNothingNewToPublish(err) {
return l.handleNoBlueprintDelta(existing, req, slug, tenantID, userID)
if needsSchemaIsolation(existing, authx.Role(l.ctx)) {
publishMode = "storage_isolated"
mergeRes = nil
} else {
return l.handleNoBlueprintDelta(existing, req, slug, tenantID, userID)
}
} else {
return nil, err
}
return nil, err
} else {
publishMode = "pages_added"
}
publishMode = "pages_added"
} else {
bp = incoming
publishMode = "created"
@@ -278,26 +292,37 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi
schemaName := bp.AssignSchemaName(tenantID)
dbName := bp.AssignDatabaseName(tenantID)
// Z8c智能体若绑定了合法 database_name新建模块优先落到该库database_per_app
boundAgentDatabase := false
// Z8c/Z39智能体绑定库可由多个模块共享因此库内仍须按 tenant+slug 分 Schema
// 不能把所有模块都放进 public 并按同名 entity table 复用。
// Z35中文昵称等非法名不得用于 EnsureDatabase回落 AssignDatabaseName
if authx.Role(l.ctx) == authx.RoleAgent && l.svcCtx.Agents != nil {
if aid := authx.AgentID(l.ctx); aid > 0 {
if acc, err := l.svcCtx.Agents.Get(l.ctx, tenantID, aid); err == nil && acc != nil {
if dn := strings.TrimSpace(acc.DatabaseName); dn != "" && schema.ValidDBName(dn) {
if !exists || existing.DatabaseName == "" {
bp.Storage.Mode = "database_per_app"
schemaName = bp.AssignSchemaName(tenantID)
dbName = dn
bp.Storage.Mode = "schema_per_app"
schemaName = bp.AssignSchemaName(tenantID)
dbName = dn
if exists && existing.DatabaseName != "" {
dbName = existing.DatabaseName
}
boundAgentDatabase = true
} else if dn != "" && !schema.ValidDBName(dn) {
// 历史脏数据(如 DisplayName 当库名):纠正智能体落点库名,避免反复 400
safeName := schema.SanitizeDBName(tenantID, slug)
_, _ = l.svcCtx.Agents.Update(l.ctx, tenantID, aid, agentstore.UpdateInput{
DatabaseName: &safeName,
})
bp.Storage.Mode = "database_per_app"
bp.Storage.Mode = "schema_per_app"
schemaName = bp.AssignSchemaName(tenantID)
dbName = safeName
boundAgentDatabase = true
} else if exists && existing.DatabaseName != "" {
// 已发布模块仍位于智能体绑定库;重发时一并升级旧 public Schema。
bp.Storage.Mode = "schema_per_app"
schemaName = bp.AssignSchemaName(tenantID)
dbName = existing.DatabaseName
boundAgentDatabase = true
}
}
}
@@ -314,7 +339,9 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi
appID = existing.AppID
now = existing.CreatedAt
republish = existing.Status == meta.StatusPublished
if existing.SchemaName != "" {
// Z39旧版把智能体同库下的所有模块放入 public导致同名资源串表。
// 重发时切到独立 app_t{tenant}_{slug};数据由既有双向同步重新灌入。
if existing.SchemaName != "" && !(boundAgentDatabase && existing.SchemaName == "public") {
schemaName = existing.SchemaName
bp.Storage.SchemaName = existing.SchemaName
}
@@ -415,6 +442,7 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi
detail := map[string]any{
"slug": slug, "schema": schemaName, "database": dbName, "user_id": userID,
"republish": republish, "publish_mode": publishMode,
"blueprint_revision": bp.Revision(), "resource_tables": resourceTables(bp),
}
if mergeRes != nil {
detail["added_pages"] = mergeRes.AddedPages
@@ -465,21 +493,23 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi
}
resp := &types.PublishResp{
AppID: rec.AppID,
Slug: slug,
SchemaName: schemaName,
DatabaseName: dbName,
Status: string(meta.StatusPublished),
Endpoints: endpoints,
DDL: ddl,
MemoryMode: l.svcCtx.MemoryMode,
PublishMode: publishMode,
ModuleName: moduleName,
PublishStyle: publishStyle,
AccessPath: accessPath,
AccessURL: accessURL,
PublishedAt: rec.UpdatedAt.UTC().Format(time.RFC3339),
OwnerID: ownerID,
AppID: rec.AppID,
Slug: slug,
SchemaName: schemaName,
DatabaseName: dbName,
Status: string(meta.StatusPublished),
Endpoints: endpoints,
DDL: ddl,
MemoryMode: l.svcCtx.MemoryMode,
PublishMode: publishMode,
ModuleName: moduleName,
PublishStyle: publishStyle,
AccessPath: accessPath,
AccessURL: accessURL,
PublishedAt: rec.UpdatedAt.UTC().Format(time.RFC3339),
OwnerID: ownerID,
BlueprintRevision: bp.Revision(),
ResourceTables: resourceTables(bp),
}
if mergeRes != nil {
resp.AddedPages = mergeRes.AddedPages
@@ -500,6 +530,13 @@ func isNothingNewToPublish(err error) bool {
return err != nil && strings.Contains(err.Error(), "nothing new to publish")
}
func needsSchemaIsolation(existing *meta.AppRecord, role string) bool {
return role == authx.RoleAgent &&
existing != nil &&
existing.DatabaseName != "" &&
existing.SchemaName == "public"
}
// handleNoBlueprintDelta Z36merge 无增量时,有 module_name 则仅更新 host_meta否则中文业务错误。
func (l *PublishLogic) handleNoBlueprintDelta(
existing *meta.AppRecord,
@@ -585,20 +622,22 @@ func (l *PublishLogic) publishHostMetaOnly(
endpoints = buildEndpoints(bp)
}
return &types.PublishResp{
AppID: rec.AppID,
Slug: slug,
SchemaName: rec.SchemaName,
DatabaseName: rec.DatabaseName,
Status: string(rec.Status),
Endpoints: endpoints,
MemoryMode: l.svcCtx.MemoryMode,
PublishMode: "host_meta_updated",
ModuleName: moduleName,
PublishStyle: publishStyle,
AccessPath: accessPath,
AccessURL: accessURL,
PublishedAt: rec.UpdatedAt.UTC().Format(time.RFC3339),
OwnerID: ownerID,
AppID: rec.AppID,
Slug: slug,
SchemaName: rec.SchemaName,
DatabaseName: rec.DatabaseName,
Status: string(rec.Status),
Endpoints: endpoints,
MemoryMode: l.svcCtx.MemoryMode,
PublishMode: "host_meta_updated",
ModuleName: moduleName,
PublishStyle: publishStyle,
AccessPath: accessPath,
AccessURL: accessURL,
PublishedAt: rec.UpdatedAt.UTC().Format(time.RFC3339),
OwnerID: ownerID,
BlueprintRevision: bp.Revision(),
ResourceTables: resourceTables(bp),
}, nil
}
@@ -635,3 +674,18 @@ func buildEndpoints(bp *blueprint.Blueprint) []string {
}
return out
}
func resourceTables(bp *blueprint.Blueprint) map[string]string {
out := make(map[string]string, len(bp.Apis.Resources))
entities := make(map[string]string, len(bp.Entities))
for _, entity := range bp.Entities {
entities[entity.Name] = entity.Table
}
for _, resource := range bp.Apis.Resources {
path := strings.TrimPrefix(resource.Path, "/")
if table := entities[resource.Entity]; path != "" && table != "" {
out[path] = bp.Storage.SchemaName + "." + table
}
}
return out
}

View File

@@ -15,31 +15,33 @@ type PublishReq struct {
}
type PublishHostMeta struct {
ModuleName string `json:"module_name,omitempty"` // 模块名称(宿主表格「应用/模块名称」)
PublishStyle string `json:"publish_style,omitempty"` // 如 immediate → 立即发布上线
HostBaseURL string `json:"host_base_url,omitempty"` // 宿主域名,如 https://whm123.yuheng.com
ModuleName string `json:"module_name,omitempty"` // 模块名称(宿主表格「应用/模块名称」)
PublishStyle string `json:"publish_style,omitempty"` // 如 immediate → 立即发布上线
HostBaseURL string `json:"host_base_url,omitempty"` // 宿主域名,如 https://whm123.yuheng.com
}
type PublishResp struct {
AppID string `json:"app_id"`
Slug string `json:"slug"`
SchemaName string `json:"schema_name"`
DatabaseName string `json:"database_name,omitempty"`
Status string `json:"status"`
Endpoints []string `json:"endpoints"`
DDL []string `json:"ddl,omitempty"`
MemoryMode bool `json:"memory_mode"`
PublishMode string `json:"publish_mode"` // created | pages_added | replaced | host_meta_updated
AddedPages []string `json:"added_pages,omitempty"`
AddedEntities []string `json:"added_entities,omitempty"`
AddedResources []string `json:"added_resources,omitempty"`
AppID string `json:"app_id"`
Slug string `json:"slug"`
SchemaName string `json:"schema_name"`
DatabaseName string `json:"database_name,omitempty"`
Status string `json:"status"`
Endpoints []string `json:"endpoints"`
DDL []string `json:"ddl,omitempty"`
MemoryMode bool `json:"memory_mode"`
PublishMode string `json:"publish_mode"` // created | pages_added | replaced | host_meta_updated | storage_isolated
AddedPages []string `json:"added_pages,omitempty"`
AddedEntities []string `json:"added_entities,omitempty"`
AddedResources []string `json:"added_resources,omitempty"`
BlueprintRevision string `json:"blueprint_revision,omitempty"`
ResourceTables map[string]string `json:"resource_tables,omitempty"`
// 宿主建站回执对应「AI 表格数据」)
ModuleName string `json:"module_name,omitempty"`
PublishStyle string `json:"publish_style,omitempty"`
AccessPath string `json:"access_path,omitempty"` // 加密文件路径,如 m/ajzm1_...
AccessURL string `json:"access_url,omitempty"` // 可打开的地址(宿主域名或平台公开地址)
PublishedAt string `json:"published_at,omitempty"` // RFC3339
OwnerID int64 `json:"owner_id,omitempty"` // 用于路径加密的用户/智能体 id
ModuleName string `json:"module_name,omitempty"`
PublishStyle string `json:"publish_style,omitempty"`
AccessPath string `json:"access_path,omitempty"` // 加密文件路径,如 m/ajzm1_...
AccessURL string `json:"access_url,omitempty"` // 可打开的地址(宿主域名或平台公开地址)
PublishedAt string `json:"published_at,omitempty"` // RFC3339
OwnerID int64 `json:"owner_id,omitempty"` // 用于路径加密的用户/智能体 id
}
type AppListItem struct {
@@ -85,24 +87,24 @@ type TokenReq struct {
}
type TokenResp struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresAt int64 `json:"expires_at"`
TenantID int64 `json:"tenant_id"`
UserID int64 `json:"user_id"`
Username string `json:"username,omitempty"`
Phone string `json:"phone,omitempty"`
UsernameLoginDisabled bool `json:"username_login_disabled,omitempty"`
DisplayName string `json:"display_name,omitempty"`
Role string `json:"role,omitempty"`
OrgUnitID int64 `json:"org_unit_id,omitempty"`
Status string `json:"status,omitempty"` // pending | active
AgentKey string `json:"agent_key"`
AgentID int64 `json:"agent_id,omitempty"`
Permissions []string `json:"permissions,omitempty"`
AppSlugs []string `json:"app_slugs,omitempty"`
Message string `json:"message,omitempty"`
TenantName string `json:"tenant_name,omitempty"` // 超管打开某公司管理视图时带回
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresAt int64 `json:"expires_at"`
TenantID int64 `json:"tenant_id"`
UserID int64 `json:"user_id"`
Username string `json:"username,omitempty"`
Phone string `json:"phone,omitempty"`
UsernameLoginDisabled bool `json:"username_login_disabled,omitempty"`
DisplayName string `json:"display_name,omitempty"`
Role string `json:"role,omitempty"`
OrgUnitID int64 `json:"org_unit_id,omitempty"`
Status string `json:"status,omitempty"` // pending | active
AgentKey string `json:"agent_key"`
AgentID int64 `json:"agent_id,omitempty"`
Permissions []string `json:"permissions,omitempty"`
AppSlugs []string `json:"app_slugs,omitempty"`
Message string `json:"message,omitempty"`
TenantName string `json:"tenant_name,omitempty"` // 超管打开某公司管理视图时带回
// Z12a同步落点智能体/已绑用户)
ChannelID string `json:"channel_id,omitempty"`
OnlineDBID string `json:"online_db_id,omitempty"`
@@ -154,7 +156,6 @@ type RoleUpdateReq struct {
Permissions *[]string `json:"permissions,omitempty"`
}
// AgentSelfRegisterReq 宿主首次连接自注册(公开)。
type AgentSelfRegisterReq struct {
Name string `json:"name"`
@@ -236,11 +237,11 @@ type ImportResp struct {
}
type AggregateResp struct {
Total int `json:"total"`
GroupBy string `json:"group_by,omitempty"`
Buckets []AggregateBucket `json:"buckets,omitempty"`
SumField string `json:"sum_field,omitempty"`
Sum float64 `json:"sum,omitempty"`
Total int `json:"total"`
GroupBy string `json:"group_by,omitempty"`
Buckets []AggregateBucket `json:"buckets,omitempty"`
SumField string `json:"sum_field,omitempty"`
Sum float64 `json:"sum,omitempty"`
}
type AggregateBucket struct {
@@ -250,10 +251,10 @@ type AggregateBucket struct {
}
type AuditListResp struct {
Items []any `json:"items"`
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
Items []any `json:"items"`
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
type UploadResp struct {