面试知识库

21 · 权限系统对齐报告(企业文档 Agentic RAG 权限工程实践对标)#

目的:审查 DocMind 现有权限/访问控制系统,对标主流企业文档 Agentic RAG(Glean、Microsoft Azure AI Search / Copilot、Google Vertex AI Search / Agentspace、Milvus 多租户、MCP 2025-11 规范、OWASP LLM Top 10 2025)的真实工程实践,给出一份如何对齐的报告。

配套:本篇是 19-对标主流产品改造报告 「P2-2 权限下推」与 20-改造实现设计 「Stage D(暂缓)」的深化。15-RBAC 权限系统升级方案 是应用层 RBAC 的落地设计,本篇负责把它与”检索层权限”和”Agentic/MCP 安全”统一收口

调研时间:2026-06。来源见文末。


0 · 执行摘要#

DocMind 当前的权限模型是 「JWT 认证 + 单 role 字段 + URL 前缀拦截」的简化版,且存在一个被设计文档忽略的关键事实:RBAC 四张表(sys_role/sys_permission/sys_user_role/sys_role_permission)已在数据库建好并灌入种子数据(3 角色 / 15 权限码),但应用层一行都没读——UserDetailsServiceImpl 仍只把 sys_user.role 单字段拼成 ROLE_xxx。即 DB 层 RBAC ≠ 应用层 RBAC,二者完全脱节

与主流企业 Agentic RAG 对照,最核心的差距只有一条:

主流是”权限在检索层强制(permission-at-retrieval)“——ACL 与内容一起索引,查询期按用户身份过滤,受限内容根本不进入 LLM 上下文(“The LLM cannot leak what it never receives”)。DocMind 是”权限只在 UI/接口前缀挡一下”——检索层完全无用户隔离,kbIds 由请求参数任意指定且不校验归属,MCP 端点裸奔无鉴权。

但在动手前,有一个必须先做的产品决策(也是 [20] 把 Stage D 暂缓的真正原因):

DocMind 的知识库到底是”全局共享单语料库”还是”按用户/团队隔离”? 现状代码是全局共享KnowledgeBaseController.list 不按 user_id 过滤,种子数据全归 user_id=1,管理员写、所有人读)。“按 owner 隔离”是 [15] 的设计假设,但与现状冲突。这个语义不定,“kbIds 归属校验”就无从谈起。

核心结论(按优先级)#

优先级对齐项一句话是否依赖产品决策
P0MCP 端点鉴权/mcp/** 裸奔 → 至少 API-Key/JWT 拦截,对齐 MCP OAuth2.1 资源服务器范式
P0Memory userId 收口recall/store_memory 不再接受外部传 userId,强制从 auth context 注入
P0RBAC 接线(DB→应用层)把已建的四表接进 UserDetailsService/@PreAuthorize,消灭”建了不用”
P0决策:共享 vs 隔离kb_knowledge_basevisibility,定义 KB 可见性语义是(先决)
P1检索层权限下推ACL 字段与 chunk 同索引,检索期按用户/可见性过滤(security trimming)
P1Agentic 工具最小权限 + provenance工具 read-only、按用户裁剪、chunk 打来源/信任标签,防 confused deputy部分
P2审计 + 实时权限 + 向量库多租户sys_audit_log、权限热更新、Milvus partition-key/collection 隔离

1 · 现状盘点(诚实清单,带代码位置)#

1.1 认证 Authentication —— 基本健全#

  • JWT(HMAC SHA-256),claims = userId / username / role单值字符串),默认 24h,JwtUtils.java
  • JwtAuthenticationFilter.java:token 优先取 Authorization: Bearer,其次 URL ?token=(SSE 流式接口需要)。
  • 白名单 SecurityConfig.java:48-61:login/register/refresh/forgot、swagger、actuator、uploads、/sse以及致命的 /mcp + /mcp/**(完全放行,无任何鉴权)
  • userId 一律由 userService.getCurrentUserId() 从 SecurityContext 派生(✓ 正确,会话类接口都做了 conv.getUserId().equals(userId) 归属校验)。

1.2 授权 Authorization —— RBAC 表”建了不用”#

  • 关键发现UserDetailsServiceImpl.java:44 仍是
    .authorities(List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().toUpperCase())))
    java
    即权限完全来自 sys_user.role 单字段。docs/docmind.sql:274-385 已建好 sys_role/sys_permission/sys_user_role/sys_role_permission 四表 + 3 角色(SUPER_ADMIN/KB_ADMIN/USER)+ 15 权限码 + 映射,但没有任何 PermissionService 去读它们。[15] 的 Phase 2~4(perms 进 JWT、hasAuthority('kb:upload')@OwnerCheck 切面、热更新)均未实施
  • 鉴权点:SecurityConfig.java:73 /api/admin/** → hasRole("ADMIN");外加散落的 @PreAuthorize("hasRole('ADMIN')")(KB 写操作 KnowledgeBaseController.java:31/49/87/97/108/118、用户管理、统计)。全是硬编码 'ADMIN' 字面量的二元判断。

1.3 知识库(KB)级权限 —— 全局共享,读侧无隔离#

  • kb_knowledge_baseuser_id(仅记录创建者),visibility/owner_acl/shared_with 字段docs/docmind.sql)。
  • KnowledgeBaseController.java:65-71 listlistKnowledge(current,size,category,status)不传 userId → 任意登录用户拿到全部 KB 列表
  • DocMindChatController.java:74-84kbIds 来自请求参数 KbIdsParser.parse,userId 从 JWT 派生(✓),但二者从不交叉校验 → 用户可传 kbIds=任意值 检索他人 KB。
  • 净语义:管理员写、所有登录用户读全部——一个全局共享语料库。写操作(upload/url/delete/reprocess)已被 hasRole('ADMIN') 保护,但读/检索完全放开

1.4 文档 / chunk 级权限 —— 零#

1.5 MCP 工具权限 —— 内部安全、外部裸奔#

  • 6 工具(doc_search/keyword_search/web_search/recall_memory/store_memory/kb_meta)经 spring-ai-starter-mcp-server-webmvc/mcp 暴露,外部端点无鉴权
  • 内部路径安全:agentic 循环 / Worker 调用时,AgentToolContextisActive()强制用会话 kbIds 覆盖 LLM 传入DocSearchTool.searchDocs),且 agentic 白名单硬排除 store_memory/kb_meta——这是做得很好的防御设计。
  • 外部路径越权:直接 POST /mcp 时,kbIds 以调用方传入为准(无限制);MemoryTool.resolveUserId 优先用外部传入的 userId可读写任意用户的记忆

1.6 Memory 权限 —— 逻辑隔离对、信任源错#

  • MemoryStore:Redis key 前缀 user:memory:v2:{userId} + Milvus user_id == "..." 过滤,逻辑隔离正确
  • 但外部路径 userId 来自参数而非 auth → 隔离形同虚设(见 1.5)。

越权风险点汇总#

风险点位置影响严重性
/mcp/** 无鉴权SecurityConfig:59-60匿名调 6 个工具
外部 MCP 可传任意 kbIdsDocSearchTool(非 active 分支)读任意 KB
外部 MCP 可传任意 userId 读写记忆MemoryTool.resolveUserId串改他人记忆
kbIds 请求参数无归属校验DocMindChatController:74登录用户读他人 KB中(取决于共享语义)
KB 列表不按 user 过滤KnowledgeBaseController:65列举全部 KB中(同上)
检索层无 per-user 过滤VectorRetriever/BM25Retriever无法做 security trimming
RBAC 四表建了不用UserDetailsServiceImpl:44细粒度授权失效、维护误导

注:4/5 的”严重性”取决于 §4 的共享 vs 隔离决策——若产品定位就是”全局共享 demo”,它们不算漏洞;若要按用户隔离,它们是高危。这正是必须先决策的原因。


2 · 主流企业文档 Agentic RAG 的权限工程实践(调研结论)#

范式 A:权限在检索层强制(permission-at-retrieval / security trimming)——最核心共识#

三大主流产品的实现高度一致:ACL 检查发生在内容进入 LLM 之前

  • Glean:索引期同时爬”内容 + ACL”,查询期先召回再按用户权限过滤,只把”安全子集”喂给 LLM——“The LLM cannot leak information it never receives”。强调实时权限(源系统权限一变,搜索结果立刻更新)+ 最小权限。
  • Azure AI Search:query-time security trimming。两条路线:(1) 2025-05 起原生 Entra ACL/RBAC;(2) 通用 security string filter——索引里建 group_ids 可过滤字段,查询期 OData group_ids/any(g: search.in(g, '用户的组'))这条最适合非微软体系、可直接照搬到 Milvus 标量过滤。
  • Vertex AI Search / Agentspace:JSONL 里带 acl_info、data store 设 aclEnabled:true,查询期把认证主体(google.subject/group)匹配文档 ACL,同一 query 不同用户结果不同。明确警告:别依赖 UI 层过滤做安全;必须后端强制 + 严格 metadata filter

学术界同样收敛到此:要”确定性消除 RAG 泄露”,必须在任何内容喂给 LLM 前显式校验所有相关方的访问权限(arXiv 2509.14608)。

范式 B:ACL 与内容同索引 + 身份只来自 auth context#

  • 把”谁能看”(principal/group ids)作为可过滤字段与 chunk 一起入库,而非检索后再查库。
  • 身份只能来自认证上下文,绝不接受前端/模型/调用方传值;从后端服务发起检索(backend mediation),不要让浏览器直连检索引擎。

范式 C:向量库多租户(Milvus 四级隔离)#

级别隔离强度上限RBAC适用
Database最强(物理)64强合规、少租户
Collection强(物理)65536强隔离、中等租户
Partition1024/coll聚合分析
Partition Key弱(逻辑)百万级海量租户、性能最好
  • 关键安全事实:RBAC 只在 DB/Collection 级生效;partition-key 是逻辑隔离,安全完全依赖应用层每次查询都正确带上过滤条件——查询构造一旦出 bug 就跨租户泄露。
  • 最简单的”单 collection + tenant 字段过滤”可行,但过滤性能可能成为 ANN 瓶颈(Milvus 官方明确提示)。
  • 运维红线:Milvus metrics 端口 9091 未授权访问漏洞(GO-2026-4481),勿暴露、勤打补丁。

范式 D:MCP 安全(2025-11 规范已成熟)#

  • MCP server = OAuth 2.1 资源服务器PKCE 强制;认证委托外部 IdP,server 只验 token(issuer/audience/expiry/scope)。
  • Resource Indicators(RFC 8707) 绑定 token 到特定 server,防跨服务盗用;RFC 9728 PRM 发现端点 /.well-known/oauth-protected-resource
  • 2026 起支持角色化工具访问@RolesAllowed 限制单个工具)。
  • 内部/服务间场景:可用 mTLS / M2M client credentials 替代用户登录流。
  • 真实事故:OAuth proxy 共享 client_id → consent 缓存绕过(Obsidian Security, 2025)。绝不能匿名暴露工具端点——正是 DocMind 现状。

范式 E:Agentic 特有风险(OWASP LLM Top 10 2025)#

  • LLM01 Prompt Injection:Agentic RAG 尤其暴露于间接注入(恶意指令藏在被检索的文档里);每一轮迭代检索都是一次新的注入机会,risk 比单轮 RAG 更高。投毒 5 篇文档即可 90% 成功率操纵答案。
  • LLM06 Excessive Agency + Confused Deputy:Agent 持有用户没有的”高权”(API key/DB 访问),被注入操纵后替攻击者越权行事;根因是 LLM 无法在架构层区分”控制指令 vs 数据”。
  • 防御共识(控制必须在模型之外)
    • 工具最小权限:默认 read-only,按”请求用户自身的权限”裁剪工具能力(“a tool’s access must be limited to what the requesting user is allowed to do”),不要让工具跑在比用户更高的服务账号权限上
    • provenance 标记:每个检索 chunk 打”来源 + 信任级别”标签随上下文传下去,下游据此施加不同审查;“把模型输出当不可信输入”。
    • action budget、高危操作 human-in-loop、对抗性渗透测试。

DocMind 已做对的两件事,正好命中此范式:① agentic 循环只暴露 3 个 read 工具、硬排除 store_memory(写工具走 DocMindAgent.stageMemoryWrite() 直连 Java);② AgentToolContext 强制覆盖 kbIds(防 LLM 篡改)。这是教科书式的”最小权限 + 模型外强制”,应在面试中重点讲。


3 · 差距对照表#

维度主流怎么做DocMind 现状差距
权限强制位置检索层(content 前置过滤)UI/接口前缀检索层零隔离
ACL 存储与内容同索引的可过滤字段缺字段
身份来源仅 auth context内部✓ / 外部 MCP 接受传值外部路径可伪造
KB 可见性语义owner/group ACL + 实时全局共享、无 visibility语义未定义
应用层授权RBAC/ABAC 细粒度单 role + 硬编码 ADMIN(四表建了不用)RBAC 脱节
MCP 端点OAuth2.1 资源服务器匿名放行裸奔
Agentic 工具最小权限 + provenance工具最小权限✓ / 无 provenance部分对齐
向量库租户DB/Collection/PartitionKey单 collection 无 tenant 字段无多租户
审计全量审计 + 实时回收缺失

4 · 对齐方案(分层 · 优先级 · 决策点)#

每条结构:主流怎么做 → 现状 → 怎么改 → 收益 → 风险 → 落地文件。先决策、再速赢、后下推。

【决策点 · 先决】KB 可见性语义:共享 vs 隔离#

这是 [20] Stage D 暂缓的根因,必须先拍板。给 kb_knowledge_base 加一个 visibility 字段统一三种形态,存量数据默认 PUBLIC 保持现状不破坏 demo

选项语义适用改动量
A 全局共享(现状)所有人读全部纯演示,定位”统一企业知识库”仅补 MCP 鉴权即可,不动检索
B 共享 + 私有(推荐)visibility ∈ {PUBLIC, PRIVATE},PUBLIC 全员可读、PRIVATE 仅 ownerdemo 可演示”隔离”又不破坏存量中:加字段 + 检索过滤 + 列表过滤
C 严格按 owner/team 隔离每 KB 归属 user/team,跨域需显式授权真多租户 SaaS大:ACL 表 + Milvus 多租户

推荐 B:一个 visibility 列 + 检索期 WHERE visibility='PUBLIC' OR user_id=:me 就能既对齐 security trimming 范式、又演示隔离能力、还零破坏存量,性价比最高。面试讲”为什么不直接上 C”——ACL 矩阵对 demo 过重,先用 visibility + owner 覆盖 80% 场景,留 C 作为多租户演进方向。

【P0 · 速赢,不依赖决策】#

P0-1 MCP 端点鉴权(范式 D)#

  • 现状/mcp/mcp/**SecurityConfig.java:59-60 白名单裸奔。
  • 怎么改:从白名单移除 /mcp/**,加 JWT 或独立 API-Key 过滤(MVP 阶段 API-Key 即可,注释里讲清”生产应升级为 OAuth2.1 资源服务器 + RFC 8707 资源指示符”对齐主流)。
  • 收益:堵掉最高危的匿名越权;面试可讲 MCP 2025-11 安全演进。
  • 风险:外部 MCP 客户端需带凭证——demo 无外部消费者,无影响。
  • 落地SecurityConfig.java(+ 可选 McpApiKeyFilter)。

P0-2 Memory userId 收口(范式 B)#

  • 现状MemoryTool.resolveUserId 优先用外部传入 userId。
  • 怎么改recall/store_memory 的 userId 只从 AgentToolContext,忽略/拒绝外部传值(外部 MCP 无 active context 时直接拒绝记忆类工具)。
  • 收益:消灭跨用户记忆读写。风险:无(内部路径本就用 context)。
  • 落地MemoryTool.javaMemoryStore 调用点。

P0-3 RBAC 接线:让”建了的四表”真正生效(对齐 [15])#

  • 现状UserDetailsServiceImpl.java:44 只读 sys_user.role
  • 怎么改:实现 [15] Phase 2 —— 新增 PermissionService.loadPermissionCodes(userId)(查四表 + Redis 缓存),UserDetailsService 把权限码作为 authority,散落的 hasRole('ADMIN') 渐改 hasAuthority('kb:upload') 等。JWT 可塞 perms([15] §3.1,注意体积降级)。
  • 收益:消灭”建了不用”的认知负债;细粒度、可审计、可热更新;面试 STAR 现成(影响 7 controller/30+ 接口)。
  • 风险:注解散落易遗漏 → 用 ArchUnit 强制 controller 每个 public 方法必须有 @PreAuthorize/@PermitAll([15] §8)。
  • 落地:见 [15] §5 清单。

【P1 · 检索层权限下推(依赖决策 B/C)】#

P1-1 Security trimming:ACL 字段与 chunk 同索引(范式 A+B)#

  • 主流:Azure group_ids/any(g:search.in(...))、Vertex acl_info
  • 怎么改(选 B 时最轻):
    1. kb_chunk 与 Milvus collection 增 owner_id + visibility 标量字段(随切片写入,从所属 KB 继承)。
    2. VectorRetriever/BM25Retriever 检索时强制注入 Milvus 过滤表达式 visibility == "PUBLIC" || owner_id == {ctxUserId}(userId 来自 AgentToolContext,模型不可篡改——复用现有 kbIds 覆盖范式)。
    3. kbIds 归属校验下推:抽 KbAccessGuard.filterOwned(userId, kbIds)([20] Stage D 已设计),在 DocMindChatController.stream 入口剔除越权 kbId。
    4. KnowledgeBaseController.list 传 userId,列表只回”PUBLIC + 自己的 PRIVATE”。
  • 收益:受限内容根本不进 LLM 上下文,对齐主流抗泄露架构;语义/答案缓存的跨用户风险也一并解决。
  • 风险:Milvus 标量过滤性能(范式 C 提示)→ 数据量大时考虑 partition-key by owner。
  • 落地docmind.sqlMilvusService、两个 Retriever、DocMindChatController、新增 KbAccessGuard

【P1 · Agentic 工具最小权限 + provenance(范式 E)】#

  • 已对齐:read-only 工具白名单、store_memory 硬排除、kbIds 强制覆盖(保留并在面试强调)。
  • 补强:① 检索 chunk 注入 provenance(source/trust level)随 prompt 传下,KB 内部内容与 web 抓取内容信任级别区分(web 内容更易藏间接注入);② 给 agentic 循环加 action budget(已有 max_iterations,可补”重复 query 去抖”);③ 文档摄入期对 web/上传内容做基础注入检测(可选)。
  • 收益:抗间接 prompt injection / confused deputy;面试可讲 OWASP LLM01/06。
  • 落地SourcePayloadFactory/PromptAssembler(provenance 字段)、AgenticSearchOrchestrator

【P2 · 企业级加固(按运营需要)】#

  • 审计sys_audit_log + AOP([15] Phase 4),记录登录/角色变更/越权拒绝/MCP 调用。
  • 实时权限(范式 A 的”killer feature”):Redis user:perm:revoke:{id}=ts,JWT 校验比对签发时间([15] §3.5),权限变更分钟级生效。
  • 向量库多租户演进(范式 C,仅选 C 时):单 collection + owner_id 标量过滤 → 数据增长后迁 partition-key by tenant;强合规场景上 collection 级 + Milvus RBAC。修复 9091 端口暴露。

5 · 落地顺序(先堵洞、再决策、后下推)#

  1. P0-1 / P0-2(MCP 鉴权 + Memory 收口)——独立、低风险、堵高危洞,先做
  2. 决策点(共享 vs 隔离,推荐 B)——产品/自己拍板,gate 后续
  3. P0-3 RBAC 接线——独立于决策,可并行,落地 [15]。
  4. P1-1 检索层权限下推——依赖决策 B/C,是与主流对齐的核心一刀
  5. P1 Agentic provenance + P2 审计/实时/多租户——按需排期。

6 · 面试讲故事角度#

  • “权限为什么不只在接口挡,要下推到检索层?” —— 因为 Agentic RAG 里 LLM 会把召回内容综合进答案,接口挡不住”内容已进上下文”;主流(Glean/Azure/Vertex)一致做法是 permission-at-retrieval——“模型收不到就泄不出”。我把 ACL 作为可过滤字段与 chunk 同索引,检索期按 auth-context 注入过滤。
  • “你怎么防 Agent 越权(confused deputy)?” —— 三层:① 工具最小权限(只暴露 3 个 read 工具、写工具不给 LLM);② kbIds/userId 由 AgentToolContext 强制覆盖,模型传啥都不算数;③ chunk 打 provenance,区分 KB 与 web 信任级。控制全在模型之外。
  • “RBAC 你不是建表了吗?” —— 诚实讲:DB 层四表先行落地,但应用层接线(PermissionService/hasAuthority/OwnerCheck 切面)是这次对齐补齐的——“建了表 ≠ 有了权限系统”,这正是审查发现的认知负债
  • “为什么不直接上多租户/ACL 矩阵?” —— 用 visibility + owner 覆盖 80% 场景,ACL 矩阵对当前规模过重;留 partition-key 多租户作为演进方向(YAGNI + 可演进)。

6.5 · 实施状态(2026-06-07 落地)#

决策已拍板:内部系统,不上多租户;功能权限走 RBAC、数据权限按「KB 级 + 部门 + 公开/私有」;上传默认 PUBLIC(沿用现状全员可读),存量一律 PUBLIC(零破坏)。

数据权限部分已实施mvn test:129 单测全过,唯一失败 contextLoads 为 Milvus 基础设施未启动,与改动无关):

落地
Schemadocmind.sql 追加:sys_dept(4 部门种子)+ sys_user.dept_id + kb_knowledge_base.dept_id/visibility;存量 UPDATE 归部门、可见性默认 PUBLIC
实体SysUser.deptIdKbKnowledgeBase.deptId/visibility
收口类新增 support/KbAccessGuardresolveAccessible(deptId) / filterAccessible(deptId, requested) / assertAccessible,规则 visibility='PUBLIC' OR dept_id=:dept
聊天入口DocMindChatController.stream 进 agent 前 filterAccessible 收敛 kbIds(空≠全库,改为「全部可访问」)
列表listKnowledgedeptId 入参 + 部门过滤;UserService.getCurrentUserDeptId()
零改动VectorRetriever/BM25Retriever/MilvusService/AgentToolContext/JwtUtils —— KB 级粒度只需收敛 kbIds,检索链自动只在授权范围召回(security trimming)

P0 续:RBAC 接线已实施(让已建四表真正驱动鉴权):

落地
权限解析新增 service/PermissionService:user→role→permission 三段式,产出「权限码 + ROLE_<角色码>」,无 RBAC 映射时按 legacy sys_user.role 兜底
认证接线UserDetailsServiceImpl 改为加载 PermissionService 授权 + 保留 legacy ROLE_<role>/api/admin/** 兼容);JwtAuthenticationFilter 走 UserDetailsService 故全链自动生效
鉴权迁移hasRole('ADMIN')hasAuthority('<perm>'):KB 写操作 kb:upload/delete/reprocess、Stats stats:read、User user:read/user:manage(AiConfig 仍由 /api/admin/** URL 守卫,兼容保留)
验证活动 DB 实测:admin→ROLE_SUPER_ADMIN+全 15 权限码(可上传/删除/管理用户);simon→ROLE_USER+仅 kb:read/chat:use/chat:export(上传 403)。行为同改造前但已由 RBAC 表驱动

P0 续:MCP 端点鉴权已实施

落地
过滤器新增 security/McpApiKeyAuthFilter/mcp/**/sse 校验 X-API-Key(定长比较),合法注入 ROLE_MCP_CLIENT,缺失/错误→401;配置键 docmind.mcp.api-key(默认 dev key,env MCP_API_KEY
收口SecurityConfig 从白名单移除 /mcp//mcp/**//sse,注册该过滤器;内部 agentic 路径走 Java 直调 @Tool,不受影响
测试McpApiKeyAuthFilterTest(4:合法 key 认证 / 缺 key 匿名 / 错误 key 匿名 / 非 MCP 路径不处理)

P0 续:Memory userId 收口 + 上传归属部门 已实施

落地
① Memory userId 收口MemoryTool.resolveUserId 改为只取 AgentToolContext 的 userId,彻底忽略外部/LLM 传入;无上下文(外部 MCP 直连)→ 空 → 记忆操作 no-op。recall/store_memoryuserId @ToolParam 标注「自动注入,传入忽略」。测试 MemoryToolTest 更新为 context 注入并断言外部 attacker-id 被忽略(7 过)
④ 上传归属部门uploadDocument/addUrlDocumentdeptId 入参,KnowledgeBaseControllergetCurrentUser() 注入上传者部门;visibility 仍由 DB 默认 PUBLIC 兜底(沿用全员可读)

仍待办(演进项,非必须):② MCP 升级 OAuth2.1 资源服务器(对外开放/合规时);③ PermissionService 接 Redis 缓存(流量上来后;当前实时查库反而无权限陈旧问题);④ 前端 PUBLIC/PRIVATE 可见性切换(仅”通过 UI 端到端建私有库”才需要,演示可 SQL 翻 visibility)。

可选后续:上传时设归属部门 + 前端可见性切换(当前默认 PUBLIC 故非必需);新注册用户默认部门(当前 null → 仅见 PUBLIC,因全 PUBLIC 故行为一致)。


7 · 同步面试文档(落地后)#


附 · 调研来源#