feat(space): 共享工作区增量3a —— Agent 编排按 Space 共享(纯 PG 打样)
引入 Space 中间容器(租户>Space>成员),资源作用域从 owner 改为 space_id,
让"个人私有/项目临时组队/整租户共享"出自同一模型(设计见 SPACE_DESIGN.md)。
先在纯 PG 的 Agent 上打样,零存储风险,验证协作+RBAC+切换 UX。
后端:
- 新表 Space{tenant_id,name,kind,creator,archived} + SpaceMember{space_id,user_id,role}
(如 Tenant 般不 isTenantScoped);User.ActiveSpaceID;Agent 作用域 owner→space_id,
owner 降级为创建人(供 UI 显示 / 删他人鉴权)
- store/space.go:个人空间幂等/活跃空间解析/切换/列表/建/成员CRUD/归档
- 迁移顺序坑:结构体只放非唯一 index,MigrateAgentSpaces 回填 space_id 后再建唯一
索引 idx_agent_sn + DROP 旧 idx_agent_on(否则存量空 space_id 撞车);启动序4步幂等
- 中间件 SpaceContext(注入 space_id) + RequireSpaceRole(照 RequireTenantRole)
- handler/space.go 空间端点 + 路由;agent.go 改空间作用域(删/覆盖他人需 admin)
- 计费零改动(Space 与 ResolveBillingTenantID 正交)
桌面端:
- api.ts space 接口;顶栏 SpaceSwitcher(含新建项目空间);StudioView 随空间切换
重拉编排 + viewer 禁保存;Agent 列表显示创建人 + 按 mine 控删除
验证:中间件6门控单测 + DB迁移(13个人空间/9 Agent全re-key/索引换新) + 后端HTTP全
场景(member见他人编排/删他人403、viewer存403、非成员切空间400+隔离、owner删他人200)
+ 浏览器实机(切换器3空间/Studio空间编排随切换隔离刷新/创建人显示/console无错)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ func New(db *store.Postgres, cache *store.Redis, bus *nats.Bus, blobStore *blob.
|
||||
r.Use(cors()) // 桌面端/浏览器跨源访问
|
||||
r.Use(middleware.Auth()) // 解析 Bearer JWT,注入已验证 userID(非阻断)——须在限流前,供按用户限流
|
||||
r.Use(middleware.TenantContext(db)) // 多租户:注入当前 tenant_id(已登录才解析;须在 Auth 之后)
|
||||
r.Use(middleware.SpaceContext(db)) // 共享工作区:注入当前 space_id(须在 TenantContext 之后)
|
||||
r.Use(middleware.RateLimit(cache)) // 已认证按用户限流,否则按 IP(企业网多人共享 IP 不再互相拖累)
|
||||
r.Use(middleware.Guardrail(db)) // Harness: Input Guardrail(命中落库 guardrail_event)
|
||||
|
||||
@@ -81,9 +82,20 @@ func New(db *store.Postgres, cache *store.Redis, bus *nats.Bus, blobStore *blob.
|
||||
p.GET("/kb/links", h.KbLinks) // 某库双链
|
||||
p.POST("/kb/note", h.KbSaveNote) // 新建/编辑笔记
|
||||
p.GET("/kb/graph", h.KbGraph) // 知识图谱三元组
|
||||
p.GET("/agents", h.AgentList) // 我的编排列表(owner 隔离)
|
||||
p.POST("/agents", h.AgentSave) // 保存/更新编排
|
||||
p.DELETE("/agents", h.AgentDelete) // 删除编排
|
||||
p.GET("/agents", h.AgentList) // 当前空间的编排列表(共享工作区,含创建人)
|
||||
p.POST("/agents", middleware.RequireSpaceRole(db, store.RoleMember), h.AgentSave) // 保存/更新编排:空间只读 viewer 拦下
|
||||
p.DELETE("/agents", middleware.RequireSpaceRole(db, store.RoleMember), h.AgentDelete) // 删除编排:viewer 拦下(删他人另需 admin,见 handler)
|
||||
|
||||
// 共享工作区(Space):切换 / 列表 / 建 / 成员管理(增量3)
|
||||
p.GET("/me/spaces", h.SpacesList) // 活跃租户内我所属的空间(供切换)
|
||||
p.POST("/me/space", h.SwitchSpace) // 切换活跃空间
|
||||
p.GET("/spaces/current", h.SpaceCurrent) // 当前空间上下文 + 我的角色
|
||||
p.POST("/spaces", middleware.RequireTenantRole(db, store.RoleMember), h.SpaceCreate) // 建空间:租户只读 viewer 拦下
|
||||
p.GET("/spaces/:id/members", h.SpaceMembers) // 空间成员列表
|
||||
p.POST("/spaces/:id/members", h.SpaceAddMember) // 拉人进空间(handler 内校验空间 admin)
|
||||
p.PUT("/spaces/:id/members/:uid", h.SpaceSetMemberRole) // 改空间成员角色
|
||||
p.DELETE("/spaces/:id/members/:uid", h.SpaceRemoveMember) // 移除空间成员
|
||||
p.POST("/spaces/:id/archive", h.SpaceArchive) // 归档空间
|
||||
p.POST("/reports", h.GenerateReport) // 报告生成
|
||||
p.GET("/billing", h.Billing)
|
||||
p.GET("/stats/overview", h.StatsOverview) // 工作台仪表盘聚合
|
||||
|
||||
Reference in New Issue
Block a user