From 5e96d15888e8c74e196627bf409ccf263814d17b Mon Sep 17 00:00:00 2001 From: Hermes CI Fix Date: Thu, 3 Sep 2026 23:09:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(knowledge):=20articles=E5=85=B1=E4=BA=AB?= =?UTF-8?q?=E5=B1=82=E8=AF=AD=E4=B9=89=E5=AF=B9=E9=BD=90=E8=AF=84=E5=AE=A1?= =?UTF-8?q?=E6=96=B9=E6=A1=88=E2=80=94=E2=80=94entity=5Fid=3D0=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=85=B1=E4=BA=AB(=E6=9B=BF=E4=BB=A3NULL)=20(entity-m?= =?UTF-8?q?emory-partition=20=E5=A4=8D=E6=A0=B8=E5=AF=B9=E9=BD=90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/knowledge_articles.py | 8 ++++---- backend/app/models/knowledge_article.py | 2 +- backend/scripts/verify_knowledge_partition_e2e.py | 14 +++++++------- backend/tests/test_security_knowledge_partition.py | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/backend/app/api/knowledge_articles.py b/backend/app/api/knowledge_articles.py index fa783f5d..88cdd04e 100644 --- a/backend/app/api/knowledge_articles.py +++ b/backend/app/api/knowledge_articles.py @@ -37,17 +37,17 @@ def list_articles( db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id), ): - """查询知识文章(本企业专属 ∪ 全局共享),可按关联页面或分类筛选 + """查询知识文章(本企业专属 ∪ 系统共享),可按关联页面或分类筛选 隔离语义(2026-09-03 多租户分区): - - entity_id IS NULL → 全局共享(CMA 系统教学知识,所有企业可见) + - entity_id = 0 → 系统共享(CMA 教学知识,所有企业可见) - entity_id == 当前账套 → 企业专属(只本企业可见) - 其他企业的专属文章不可见 """ q = db.query(KnowledgeArticle).filter( or_( KnowledgeArticle.entity_id == entity_id, - KnowledgeArticle.entity_id.is_(None), + KnowledgeArticle.entity_id == 0, ) ) if related_page: @@ -72,6 +72,6 @@ def get_article( ) if not a: raise HTTPException(404, "文章不存在") - if a.entity_id is not None and a.entity_id != entity_id: + if a.entity_id != 0 and a.entity_id != entity_id: raise HTTPException(404, "文章不存在") return article_to_dict(a) diff --git a/backend/app/models/knowledge_article.py b/backend/app/models/knowledge_article.py index ab91d73c..ef136054 100644 --- a/backend/app/models/knowledge_article.py +++ b/backend/app/models/knowledge_article.py @@ -12,7 +12,7 @@ class KnowledgeArticle(Base): __tablename__ = "knowledge_articles" id = Column(Integer, primary_key=True, index=True) - entity_id = Column(Integer, nullable=True, index=True, comment="企业ID: NULL=全局共享(系统教学), 有值=企业专属 (多租户隔离 2026-09-03)") + entity_id = Column(Integer, default=0, nullable=False, index=True, comment="企业ID: 0=系统共享(CMA教学知识), >0=企业专属 (多租户隔离 2026-09-03)") title = Column(String(200), nullable=False, comment="文章标题") summary = Column(String(500), nullable=True, comment="一句话摘要") content = Column(Text, nullable=False, comment="文章正文(支持Markdown)") diff --git a/backend/scripts/verify_knowledge_partition_e2e.py b/backend/scripts/verify_knowledge_partition_e2e.py index 54a4e0bf..cd42ec3e 100644 --- a/backend/scripts/verify_knowledge_partition_e2e.py +++ b/backend/scripts/verify_knowledge_partition_e2e.py @@ -39,11 +39,11 @@ for t in ['knowledge_events', 'knowledge_summaries', 'knowledge_articles']: cur.execute(f"SHOW INDEX FROM {t} WHERE Column_name='entity_id'") has_idx = cur.fetchone() is not None check(f"{t}.entity_id 列+索引", has_col and has_idx) -# 存量全局文章(NULL)在加列后保持 38 篇 -cur.execute("SELECT COUNT(*) FROM knowledge_articles WHERE entity_id IS NULL") +# 存量全局文章(entity_id=0 系统共享)在加列后保持 38 篇 +cur.execute("SELECT COUNT(*) FROM knowledge_articles WHERE entity_id = 0") g = cur.fetchone()[0] -print(f" 存量全局文章(NULL): {g} 篇") -check("存量全局教学文章保持全局共享", g == 38, f"(期望38)") +print(f" 系统共享文章(entity_id=0): {g} 篇") +check("存量系统共享教学文章保持", g == 38, f"(期望38)") # 1. 插临时验证文章(标题带标记,source 专属 entity) cur.execute("DELETE FROM knowledge_articles WHERE title LIKE %s", (f"%{MARK}%",)) @@ -56,7 +56,7 @@ cur.execute( (2, f"{MARK}E2专属越权测试", "s", "c", "term")) cur.execute( "INSERT INTO knowledge_articles (entity_id, title, summary, content, category) VALUES (%s,%s,%s,%s,%s)", - (None, f"{MARK}全局共享测试", "s", "c", "term")) + (0, f"{MARK}系统共享测试", "s", "c", "term")) conn.commit() cur.execute("SELECT id, entity_id, title FROM knowledge_articles WHERE title LIKE %s", (f"%{MARK}%",)) rows = cur.fetchall() @@ -98,13 +98,13 @@ if not t1 or not t2: st, body = api("GET", "/api/cma/knowledge-articles", token=t1) d1 = _json.loads(body) titles1 = {i["title"] for i in d1.get("data", [])} -check("E1 见全局共享文章", f"{MARK}全局共享测试" in titles1) +check("E1 见系统共享文章", f"{MARK}系统共享测试" in titles1) check("E1 见自己专属文章", f"{MARK}E1专属越权测试" in titles1) check("E1 不见 E2 专属文章", f"{MARK}E2专属越权测试" not in titles1, "(核心越权1)") # E2 视角 st, body = api("GET", "/api/cma/knowledge-articles", token=t2) titles2 = {i["title"] for i in _json.loads(body).get("data", [])} -check("E2 见全局共享文章", f"{MARK}全局共享测试" in titles2) +check("E2 见系统共享文章", f"{MARK}系统共享测试" in titles2) check("E2 见自己专属文章", f"{MARK}E2专属越权测试" in titles2) check("E2 不见 E1 专属文章", f"{MARK}E1专属越权测试" not in titles2, "(核心越权2)") diff --git a/backend/tests/test_security_knowledge_partition.py b/backend/tests/test_security_knowledge_partition.py index ecbc7d6a..79294486 100644 --- a/backend/tests/test_security_knowledge_partition.py +++ b/backend/tests/test_security_knowledge_partition.py @@ -139,7 +139,7 @@ class TestKnowledgeArticlesGlobalUnion: def test_articles_global_union_scope(self, client: TestClient, db: Session): """文章查询 = 本企业专属 ∪ 全局共享;他企专属不可见""" _ensure_entity(db, 2) - g = KnowledgeArticle(title="全局教学文章", content="c", category="term", entity_id=None) + g = KnowledgeArticle(title="全局教学文章", content="c", category="term", entity_id=0) e1 = KnowledgeArticle(title="E1专属文章", content="c", category="term", entity_id=1) e2 = KnowledgeArticle(title="E2专属文章", content="c", category="term", entity_id=2) db.add_all([g, e1, e2]) @@ -166,7 +166,7 @@ class TestKnowledgeArticlesGlobalUnion: def test_get_article_cross_entity_404(self, client: TestClient, db: Session): """越权:entity1 直取 entity2 专属文章 → 404;全局文章两账套可取""" _ensure_entity(db, 2) - g = KnowledgeArticle(title="全局文章", content="c", entity_id=None) + g = KnowledgeArticle(title="全局文章", content="c", entity_id=0) e2 = KnowledgeArticle(title="E2秘密文章", content="c", entity_id=2) db.add_all([g, e2]) db.commit()