业务故事站
P4 Gotham

协同作战:项目、任务、评论把情报团队拧成一股绳

情报分析不只是一张图,更是团队作战:建项目把工作装进一个"作战室",按角色分权限、任务看板盯进度、在分析对象上评论 @同事、收站内通知、共享视图恢复分析现场。看完这 3 个故事,你就能用协作工作流把玄武集团专项的情报工作组织起来。

情报分析员 团队负责人 项目 任务看板 @提及 共 3 个故事

能 / 不能速览

✅ 这个主题能做
  • 项目:创建(Owner 自动成为 owner 成员)/ 我参与的列表 / 更新 / 归档(软删除)/ 删除(与归档同义:仅置 archived,不级联删成员 / 任务 / 评论)
  • 成员角色:owner / editor / viewer 三级,读=成员、写=owner/editor、成员管理仅 owner
  • 任务看板:todo → in_progress → review → done 状态机,非法流转 409;分配即发通知
  • 上下文评论:挂到 entity / relationship / alert / report / task,@用户名 自动解析并发通知
  • 活动流 + 通知:七种动作、五类通知,落库 + WebSocket 实时推送双通道,支持标记已读
  • 共享视图:保存 graph / map / timeline / cross_filter / report 视图 State,成员可恢复现场
⛔ 这个主题做不了
  • 评论不支持编辑(只有增 / 查 / 删)
  • 成员候选无枚举端点:后端无 /users 类接口,加成员只能手输 user_id;成员表搜索仅过滤已加载成员
  • 活动时间线为渲染层分页:后端仅 limit(上限 500)无 offset,一次取最近 200 条后本地翻页
  • 任务看板列表后端全量返回、仅渲染层分页(每列 20 条);关联实体只校验格式,不校验图节点是否存在
  • 共享视图创建时不自动发 project_share 通知(类型常量已定义,未触发)
  • 通知仅站内 + WebSocket,无邮件 / IM;协同编辑(多人同屏改图)属 V2
  • collab 无 demo seed,项目 / 任务 / 成员都要手动创建体验

适用角色

本主题面向两类角色:

  • 情报分析员:核心使用者,在项目里建任务、评论分析对象、@同事协作、收通知。
  • 团队负责人:建项目、管成员角色、看活动流掌握团队进展、归档结项。

项目内角色决定权限:owner 管人管项目,editor 可写任务评论,viewer 只能浏览。

能力速览(能做什么)

项目协作

七张 gotham_ 表支撑项目 / 成员 / 任务 / 评论 / 活动 / 通知 / 共享视图,Owner 创建即自动成为 owner 成员(事务保证)。

任务看板

四列看板 + 状态机流转,负责人(须为成员)、优先级、关联实体、截止时间;非法流转 409 GOTHAM_COLLAB_INVALID_TRANSITION;拖动失败可用「置为▾」下拉按后端合法目标流转(done 为终态不可选)。

上下文评论

评论挂到具体分析对象而非游离聊天;@用户名 反查用户表解析提及,@自己不触发通知。

实时通知

通知先落库再经 ws.Hub 实时推送;重连后可轮询补齐;服务端每 30s 心跳保活。

共享视图

State JSON 保存分析上下文,成员一键加载恢复;创建者可删(不必等 owner)。

界面操作指引(不写代码怎么用)

下面按「打开哪个页面 → 点哪里 → 填什么 → 看到什么」的顺序给出完整操作路径,业务用户照做即可。每步末尾的「API 等价」是给工程师对照用的,可以忽略。

1 打开「协同动态」页并选项目 Gotham 控制台 → 协同动态(/gotham/collab)
  • 怎么做:左侧边栏点「协同动态」,在页头项目下拉里选一个协作项目。
  • 会看到:上方状态条亮起绿点「实时通知已连接」,评论、活动、通知三块随之刷新。

API 等价:GET /api/v1/collab/projects

2 发表评论并用 @ 提及成员 协同动态 → 评论卡
  • 怎么做:在评论输入框打字,输入 @ 会浮出成员补全下拉(按登录名子串过滤、最多 8 条),点选成员回填 @username;再选「对象类型」、填「对象 ID」,点「发表」。
  • 会看到:评论立刻出现在列表,带作者 / 对象徽标 / 时间;评论项上出现「提及 N 人」,点开看被 @ 的成员。

API 等价:GET /api/v1/collab/projects/:id/members、POST /api/v1/collab/projects/:id/comments

3 翻看活动时间线 协同动态 → 活动时间线卡
  • 怎么做:在活动时间线卡片底部点「上一页 / 下一页」,切换每页 20 条。
  • 会看到:成员的操作动态流;进度条常驻提示「已取最近 N 条(后端仅 limit 无 offset)」。

API 等价:GET /api/v1/collab/projects/:id/activities

4 处理通知中心 协同动态 → 通知中心
  • 怎么做:勾选「仅看未读」只看未读,或直接点某个未读行操作列的「标记已读」;想一次清空点标题栏「全部已读」。
  • 会看到:通知表格状态列在未读 / 已读之间变化,条数随之减少。

API 等价:GET /api/v1/collab/notifications、POST /api/v1/collab/notifications/:id/read、POST /api/v1/collab/notifications/read-all

5 删除评论 / 归档项目 协同动态 → 评论项 / 项目列表
  • 怎么做:评论项点「删除」;项目列表的「删除」是软删除(归档),归档后从活动项目里移除。
  • 会看到:评论与项目状态更新。注意评论不支持编辑,卡片内常驻提示「如需修改请删除后重发」。

API 等价:DELETE /api/v1/collab/comments/:id、DELETE /api/v1/collab/projects/:id

提示:评论不能编辑,只能删除后重发;活动时间线是渲染层分页(后端无 offset);@ 提及按登录名(username)解析,成员 user_id 是 UUID 不参与匹配、@ 自己不触发通知。

调整指南(怎么调整)

  • 改成员权限:AddMember 时选 role(owner/editor/viewer);改角色用 PUT /collab/projects/:id/members/:user_id 原地更新(仅 owner),加成员前确认 user_id 已注册。
  • 改任务:建任务时 priority(low/medium/high)、assignee_id(须为项目成员)、related_entity_ids(关联图节点 rid)、due_date 都可填。
  • 改通知范围:/collab/notifications?unread_only=1 只看未读;单条 /read 与全部 /read-all 两种已读操作。
  • 改评论范围:ListComments 支持 target_type / target_id 过滤;评论 target 只能是 entity/relationship/alert/report/task 五类。
  • 改共享视图:view_type 支持 graph/map/timeline/cross_filter/report;State 存跨视图筛选参数。

做得好的场景

协作工作流最省事的几个场景:
  • 上下文不丢:评论直接挂在实体 / 关系 / 告警 / 报告 / 任务上,讨论跟分析对象绑死,不散在群里。
  • 权限分层清晰:viewer 只能看、editor 能写、owner 管人,权限判定收敛在服务层,误操作被 403 拦住。
  • 通知双通道:在线实时推送、离线重连轮询补齐,任务分配 / 提及 / 状态变更一个不漏。
  • 现场可恢复:共享视图存 State JSON,同事恢复你的分析上下文,不重头搭。

限制与不足

以下是明确的边界,使用前先知道:
  • 评论无编辑:AddComment / ListComments / DeleteComment 三件套,发错了只能删了重发(后端无评论更新端点)。
  • 项目删除 = 归档:DELETE /collab/projects/:id 与归档共用实现,只把状态置 archived、不级联清理成员 / 任务 / 评论,且列表不按 status 过滤,删除后项目仍在「我参与的项目」里。
  • 成员候选与活动流是前端兜底:后端无用户枚举端点,加成员只能手输 user_id(成员表搜索仅过滤已加载成员);活动时间线后端只有 limit(上限 500)、没有 offset,页面一次取最近 200 条后做渲染层分页,更早活动不在返回集内。
  • 任务看板列表是渲染层分页:后端任务列表按项目全量返回、无 limit / offset,页面每列 20 条本地切片;关联实体只校验「合法 JSON 数组 + 非空字符串元素」,不校验图节点是否存在(后端无批量节点端点)。
  • 通知仅站内:task_assigned / mention / task_status / alert / project_share 五类,无邮件 / IM 触达。
  • 协作编辑未实现:多人同屏协同编辑图(Yjs / OT / CRDT)属 V2;无审核工作流引擎。

场景故事

故事 1 周警官建"玄武集团资金链路专项"项目,按角色把三人小组分好权限
背景
2026 年 8 月,周警官牵头玄武集团资金链路专项,组里有李四(能写能改)和小王(只做复核)。他要在 Gotham 里建一个项目当作"作战室",把三人的角色一次性分好,避免后面权限混乱。
传统做法对比
以前靠微信群拉群 + Excel 分任务,谁有没有权限全凭自觉,出了纰漏互相扯皮。现在项目内 owner / editor / viewer 三级角色由服务层强制校验,读 / 写 / 管理边界清楚。
角色
周警官(owner,建项目并管成员);李四(editor)、小王(viewer)先注册用户再被加入。
操作步骤
  1. 进入 /gotham/projects,新建项目"玄武集团资金链路专项"
  2. 系统自动把周警官加为 owner 成员
  3. 添加成员:李四 role=editor、小王 role=viewer
  4. 在成员列表确认三人角色正确
系统响应
创建项目返回 201:
{ "code": 0, "data": { "id": 1 } }
成员列表返回:
{ "code": 0, "data": [
  { "project_id": 1, "user_id": "zhou", "role": "owner" },
  { "project_id": 1, "user_id": "lisi", "role": "editor" },
  { "project_id": 1, "user_id": "xiaowang", "role": "viewer" } ] }
项目 + owner 成员在同一个事务里写入,不会出现无主项目。
结果洞察
权限分层即刻生效:owner 才能加人 / 移除成员(且不能移除自己),editor 能建任务 / 评论,viewer 只能看。周警官验证了"添加不存在的用户会被 422 拒绝"——权限边界从源头就堵住了。
调整建议
想改某人角色,直接用 PUT /collab/projects/:id/members/:user_id 原地更新(仅 owner 可改角色);加成员前先确认对方已注册;重复添加同一用户会 409 DUPLICATE_ENTRY。
动手试一试
登录:admin / admin1,端口 18083。页面路径:/gotham/projects。预期结果:新建项目后成员列表自动含 owner(admin);再注册一个用户 bob 并添加为 editor,列表出现第二行。
限制提示
成员 user_id 必须真实存在(查 platform 用户表),不存在返回 422;角色只能是 owner/editor/viewer;collab 无 demo seed,项目、成员都要手动创建。
故事 2 任务看板流转:把"梳理玄武→天河资金链路"从待办推到审核中,李四同步收到通知
背景
周警官在项目里建了任务"梳理玄武→天河资金链路",分配给李四(assignee_id=lisi)。第二天李四把初稿做完,在 /gotham/tasks 把任务从 todo 流转到 in_progress、再推到 review,请周警官复核。
传统做法对比
以前口头派活,任务到哪一步靠打电话问;现在四列看板 + 状态机,谁做了什么、卡在哪一目了然,状态变更还自动通知负责人。
角色
周警官(owner,建任务);李四(editor,流转任务并收到分配 / 状态通知)。
操作步骤
  1. 周警官建任务:title=梳理玄武→天河资金链路、priority=high、assignee_id=lisi、related_entity_ids 关联玄武集团与天河贸易节点
  2. 李四把任务拖到 in_progress 列
  3. 李四完成后拖到 review 列(请复核)
  4. 周警官在通知中心看到 task_status 通知,进入任务查看详情
系统响应
建任务返回 201 与任务 id;分配时 assignee 收 task_assigned 通知。流转时李四试图把任务从 todo 直接拖到 done,返回 409:
{ "code": "GOTHAM_COLLAB_INVALID_TRANSITION",
  "error": "任务状态流转非法(todo→in_progress→review→done)" }
合法链路 todo→in_progress→review 通过,李四收到 task_status 通知。
结果洞察
状态机防住了跳步:todo 不能直接到 done,必须按 todo→in_progress→review→done 走。分配和状态变更都自动落库 + 实时推送,李四和周警官各收一条通知,进度不靠人肉同步。
调整建议
想过滤看板:GET /collab/projects/:id/tasks?status=review&assignee_id=lisi;任务可关联图节点(related_entity_ids 存 JSON 数组),配合"动手试一试"的评论形成闭环。
动手试一试
登录:admin / admin1。页面路径:/gotham/tasks。输入内容:建任务分配给 admin 自己,再从 todo 流转 in_progress(成功)、尝试 in_progress→done(409 被拒)。预期结果:合法流转通过、非法流转 409,通知中心出现 task_status 通知。
限制提示
assignee 必须是项目成员(否则 422);done 是终态不能再流转;删除任务无回收站,删除即不可恢复。
故事 3 在玄武集团实体上评论 @周警官,mention 通知实时推送 + 共享视图恢复分析现场
背景
李四在排查玄武集团实体(graph:org:xuanwu)时发现一笔 50 万转账存疑,想在图上直接 @周警官 复核。周警官当时正开着协同动态页,希望实时收到提醒,而不是事后查列表。
传统做法对比
以前要切到微信 / 邮件另说一遍,分析上下文和讨论两张皮;现在评论直接挂在实体上,@提及即发通知,讨论跟着分析对象走。
角色
李四(editor,写评论);周警官(被 @ 的人,通过 WebSocket 实时收通知);小王(viewer,只能读评论)。
操作步骤
  1. 李四在协同动态页选 target_type=entity、target_id=graph:org:xuanwu,写"@zhou 请复核这笔 50 万转账,怀疑与天河贸易有关"
  2. 周警官的协同动态页 ws-bar 状态栏显示在线,实时收到 notification 推送
  3. 周警官点开通知跳到实体讨论区回复
  4. 李四把当前筛选条件存成共享视图,成员可一键恢复
系统响应
评论创建成功,被 @ 用户收到 mention 通知(内容截断 80 字符):
{ "code": 0, "data": { "id": 3 } }

GET /api/v1/collab/notifications?unread_only=1
{ "code": 0, "data": [ { "type": "mention", "title": "评论提及",
  "content": "lisi 在评论中 @了你:@zhou 请复核这笔 50 万转账…", "is_read": false } ] }
WebSocket 实时推送载荷为 {"type":"notification","data":{...}};活动流新增 commented 记录。
结果洞察
讨论和实体绑在一起,复盘时点开实体就能看到全部评论;通知"落库 + 实时推送"双通道,在线秒收、离线可查;共享视图把李四的筛选条件存成 State JSON,周警官一键恢复分析现场,不用重头搭。
调整建议
评论列表用 target_type / target_id 过滤只看某一对象的讨论;通知中心支持单条 /read 与 /read-all;共享视图 view_type 可选 graph/map/timeline/cross_filter/report。
动手试一试
登录:admin / admin1。页面路径:/gotham/collab。输入内容:评论内容 "@admin 请复核这条链路"(@自己会被过滤,不产生通知)。预期结果:评论发表、活动时间线出现 commented;换一个真实存在的第二用户 @ 才产生 mention 通知。
限制提示
@自己不构成提及(作者过滤);通知仅站内,无邮件;共享视图创建当前不自动发 project_share 通知;评论不可编辑,发错只能删了重发。

常见问题

owner / editor / viewer 分别能做什么?

读操作(看项目 / 任务 / 评论 / 活动 / 共享视图)都要求是成员;写操作(建任务 / 评论 / 更新项目)要求 owner 或 editor;成员管理(加人 / 移除成员)仅 owner,且 owner 不能移除自己。viewer 只能读。

任务状态可以随便拖吗?

不行。状态机固定 todo→in_progress→review→done(含回退边 in_progress→todo、review→in_progress),done 是终态。非法流转返回 409 GOTHAM_COLLAB_INVALID_TRANSITION,比如 todo 直接到 done 会被拒。

@某人为什么没收到通知?

三种可能:@ 的用户名不存在(正则提取后按 username 反查用户表,查不到跳过);@ 的是评论作者自己(作者过滤);对方不在线且未订阅 WebSocket(但通知已落库,重连或刷新列表可见)。

通知有哪些类型?

五类:task_assigned(任务分配)、mention(评论提及)、task_status(任务状态变更)、alert(告警,经事件总线桥接广播)、project_share(共享视图,常量已定义但创建时暂未自动触发)。

评论能编辑吗?能删吗?

评论不支持编辑,只有添加 / 列表 / 删除三件套;删除评论要求 owner/editor 角色,删除不可恢复。发错了就删了重发。

项目「删除」会把成员、任务、评论一起清掉吗?

不会。DELETE /collab/projects/:id 与「归档」共用同一实现,只把项目状态置为 archived,不级联删除成员 / 任务 / 评论 / 共享视图;而且列表查询不按 status 过滤,删除后项目仍会出现在「我参与的项目」里。要真正清理关联数据需后端另行提供级联删除。

活动时间线能一直翻到更早的记录吗?

翻不到任意时点。后端 ListActivities 只支持 limit(默认 100、上限 500),没有 offset,页面固定取最近 200 条后在渲染层每页 20 条翻页,更早的活动不在返回集内。

主题小结

一句话:协同作战就三件事——建项目分角色、任务看板盯流转、评论 @人实时协作,共享视图保现场。记住几个边界:评论不可编辑(后端无更新端点)、项目删除与归档同义(只置 archived、不级联)、成员候选无枚举端点(加成员手输 user_id)、活动时间线为渲染层分页、通知仅站内、协同编辑属 V2。角色变更走原地更新(PUT 成员接口仅 owner);项目 + 任务 + 评论这一套,5 分钟就能跑通。