From 2155c66a3bbf61a28141490276229e5cd9dd212f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E7=94=B7?= Date: Mon, 27 Apr 2026 23:20:52 +0800 Subject: [PATCH] fix(ws-org): enforce org-scoped lianxi relation lookups Require current_org_id for lianxi deletion and relation traversal, and add org-scoped relation lookups to prevent cross-organization hits in daka/jihua counters. Made-with: Cursor --- htyws/src/ws_lianxi.rs | 74 ++++++++++++++++----- htyws_models/src/models.rs | 36 +++++++++++ tmp_htyws_org_backfill.sql | 129 +++++++++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+), 15 deletions(-) create mode 100644 tmp_htyws_org_backfill.sql diff --git a/htyws/src/ws_lianxi.rs b/htyws/src/ws_lianxi.rs index 7620e46..b9288fc 100644 --- a/htyws/src/ws_lianxi.rs +++ b/htyws/src/ws_lianxi.rs @@ -112,24 +112,40 @@ pub async fn raw_create_lianxi( debug!("update lianxi_count"); if let Some(section_id) = &in_jihua_course_section_id { - let jihua_course_section = JihuaCourseSection::find_by_id( + let jihua_course_section = JihuaCourseSection::find_by_id_in_org( section_id, + &res_lianxi + .org_id + .clone() + .ok_or_else(|| anyhow!("lianxi.org_id is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let belonging_jihua = Jihua::find_by_id( + let belonging_jihua = Jihua::find_by_id_in_org( &jihua_course_section.jihua_id, + &res_lianxi + .org_id + .clone() + .ok_or_else(|| anyhow!("lianxi.org_id is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; let _ = belonging_jihua .update_count(extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; } else if let Some(section_id) = &in_daka_course_section_id { // in_daka_course_section_id is some - let daka_course_section = DakaCourseSection::find_by_id( + let daka_course_section = DakaCourseSection::find_by_id_in_org( section_id, + &res_lianxi + .org_id + .clone() + .ok_or_else(|| anyhow!("lianxi.org_id is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let belonging_daka = Daka::find_by_id( + let belonging_daka = Daka::find_by_id_in_org( &daka_course_section.daka_id, + &res_lianxi + .org_id + .clone() + .ok_or_else(|| anyhow!("lianxi.org_id is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; let _ = belonging_daka @@ -306,24 +322,40 @@ pub async fn raw_create_lianxi2( debug!("update lianxi_count"); if let Some(section_id) = &in_jihua_course_section_id { - let jihua_course_section = JihuaCourseSection::find_by_id( + let jihua_course_section = JihuaCourseSection::find_by_id_in_org( section_id, + &res_lianxi + .org_id + .clone() + .ok_or_else(|| anyhow!("lianxi.org_id is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let belonging_jihua = Jihua::find_by_id( + let belonging_jihua = Jihua::find_by_id_in_org( &jihua_course_section.jihua_id, + &res_lianxi + .org_id + .clone() + .ok_or_else(|| anyhow!("lianxi.org_id is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; let _ = belonging_jihua .update_count(extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; } else if let Some(section_id) = &in_daka_course_section_id { // in_daka_course_section_id is some - let daka_course_section = DakaCourseSection::find_by_id( + let daka_course_section = DakaCourseSection::find_by_id_in_org( section_id, + &res_lianxi + .org_id + .clone() + .ok_or_else(|| anyhow!("lianxi.org_id is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let belonging_daka = Daka::find_by_id( + let belonging_daka = Daka::find_by_id_in_org( &daka_course_section.daka_id, + &res_lianxi + .org_id + .clone() + .ok_or_else(|| anyhow!("lianxi.org_id is required"))?, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; let _ = belonging_daka @@ -340,12 +372,12 @@ pub async fn raw_create_lianxi2( } pub async fn delete_lianxi_by_id( - _root: HtySudoerTokenHeader, + root: HtySudoerTokenHeader, Path(id_delete): Path, State(db_pool): State>, ) -> Json> { debug!("delete_lianxi_by_id -> start here"); - match raw_delete_lianxi_by_id(&id_delete, db_pool).await { + match raw_delete_lianxi_by_id(&root, &id_delete, db_pool).await { Ok(ok) => wrap_json_ok_resp(ok), Err(e) => { error!("delete_lianxi_by_id -> failed to delete lianxi, e: {}", e); @@ -355,11 +387,14 @@ pub async fn delete_lianxi_by_id( } pub async fn raw_delete_lianxi_by_id( + root: &HtySudoerTokenHeader, id_delete: &String, db_pool: Arc, ) -> anyhow::Result { - let to_delete_lianxi = Lianxi::find_by_id( + let current_org_id = required_current_org_id_from_sudoer_token_str(&root.0)?; + let to_delete_lianxi = Lianxi::find_by_id_in_org( id_delete, + ¤t_org_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; let piyues = @@ -390,12 +425,14 @@ pub async fn raw_delete_lianxi_by_id( } if let Some(section_id) = &to_delete_lianxi.jihua_course_section_id { - let relation = JihuaCourseSection::find_by_id( + let relation = JihuaCourseSection::find_by_id_in_org( section_id, + ¤t_org_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let belonging_jihua = Jihua::find_by_id( + let belonging_jihua = Jihua::find_by_id_in_org( &relation.jihua_id, + ¤t_org_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; let _ = belonging_jihua.update_count(extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; @@ -406,12 +443,12 @@ pub async fn raw_delete_lianxi_by_id( } pub async fn delete_lianxi_by_id2( - _root: HtySudoerTokenHeader, + root: HtySudoerTokenHeader, Path(id_delete): Path, State(db_pool): State>, ) -> Json> { debug!("delete_lianxi_by_id2 -> start here"); - match raw_delete_lianxi_by_id2(&id_delete, db_pool).await { + match raw_delete_lianxi_by_id2(&root, &id_delete, db_pool).await { Ok(ok) => wrap_json_ok_resp(ok), Err(e) => { error!("delete_lianxi_by_id2 -> failed to delete lianxi, e: {}", e); @@ -421,11 +458,18 @@ pub async fn delete_lianxi_by_id2( } pub async fn raw_delete_lianxi_by_id2( + root: &HtySudoerTokenHeader, id_delete: &String, db_pool: Arc, ) -> anyhow::Result { // let to_delete_lianxi = Lianxi::find_by_id(id_delete, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; // let piyues = to_delete_lianxi.find_linked_piyues(extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; + let current_org_id = required_current_org_id_from_sudoer_token_str(&root.0)?; + let _ = Lianxi::find_by_id_in_org( + id_delete, + ¤t_org_id, + extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), + )?; let deleted_lianxi = Lianxi::logic_delete_by_id( id_delete, extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), diff --git a/htyws_models/src/models.rs b/htyws_models/src/models.rs index 34d0ae4..ba4e697 100644 --- a/htyws_models/src/models.rs +++ b/htyws_models/src/models.rs @@ -2150,6 +2150,24 @@ impl JihuaCourseSection { } } + pub fn find_by_id_in_org( + id_search: &String, + id_org: &String, + conn: &mut PgConnection, + ) -> anyhow::Result { + use crate::schema::jihua_course_section::columns::*; + use crate::schema::jihua_course_section::dsl::*; + jihua_course_section + .filter(id.eq(id_search).and(org_id.eq(id_org))) + .first::(conn) + .map_err(|e| { + anyhow!(HtyErr { + code: HtyErrCode::DbErr, + reason: Some(e.to_string()), + }) + }) + } + pub fn find_first_by_course_section_id( id_course_section: &String, conn: &mut PgConnection, @@ -3833,6 +3851,24 @@ impl DakaCourseSection { } } + pub fn find_by_id_in_org( + id_search: &String, + id_org: &String, + conn: &mut PgConnection, + ) -> anyhow::Result { + use crate::schema::daka_course_section::columns::*; + use crate::schema::daka_course_section::dsl::*; + daka_course_section + .filter(id.eq(id_search).and(org_id.eq(id_org))) + .first::(conn) + .map_err(|e| { + anyhow!(HtyErr { + code: HtyErrCode::DbErr, + reason: Some(e.to_string()), + }) + }) + } + pub fn find_by_daka_id_and_course_section_id( id_daka: &String, id_course_section: &String, diff --git a/tmp_htyws_org_backfill.sql b/tmp_htyws_org_backfill.sql new file mode 100644 index 0000000..85aa438 --- /dev/null +++ b/tmp_htyws_org_backfill.sql @@ -0,0 +1,129 @@ +-- htyws 多租户 org_id 历史数据回填(仅回填 NULL) +-- 使用方式(moicen): +-- psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f /path/to/tmp_htyws_org_backfill.sql + +BEGIN; + +-- 1) 先从已确定来源回填关系链: +-- jihua_course_section <- jihua +UPDATE jihua_course_section jcs +SET org_id = j.org_id +FROM jihua j +WHERE jcs.jihua_id = j.id + AND jcs.org_id IS NULL + AND j.org_id IS NOT NULL; + +-- daka_course_section <- daka +UPDATE daka_course_section dcs +SET org_id = d.org_id +FROM daka d +WHERE dcs.daka_id = d.id + AND dcs.org_id IS NULL + AND d.org_id IS NOT NULL; + +-- 2) 回填 lianxi.org_id(优先 jihua,再 daka) +UPDATE lianxi l +SET org_id = jcs.org_id +FROM jihua_course_section jcs +WHERE l.jihua_course_section_id = jcs.id + AND l.org_id IS NULL + AND jcs.org_id IS NOT NULL; + +UPDATE lianxi l +SET org_id = dcs.org_id +FROM daka_course_section dcs +WHERE l.daka_course_section_id = dcs.id + AND l.org_id IS NULL + AND dcs.org_id IS NOT NULL; + +-- 3) 回填 piyue / piyue_info +UPDATE piyue p +SET org_id = l.org_id +FROM lianxi l +WHERE p.lianxi_id = l.id + AND p.org_id IS NULL + AND l.org_id IS NOT NULL; + +UPDATE piyue_info pi +SET org_id = p.org_id +FROM piyue p +WHERE pi.piyue_id = p.id + AND pi.org_id IS NULL + AND p.org_id IS NOT NULL; + +-- 4) 回填 course_section.org_id +-- 4.1 course_section <- jihua_course_section +UPDATE course_section cs +SET org_id = jcs.org_id +FROM jihua_course_section jcs +WHERE jcs.course_section_id = cs.id + AND cs.org_id IS NULL + AND jcs.org_id IS NOT NULL; + +-- 4.2 course_section <- daka_course_section +UPDATE course_section cs +SET org_id = dcs.org_id +FROM daka_course_section dcs +WHERE dcs.course_section_id = cs.id + AND cs.org_id IS NULL + AND dcs.org_id IS NOT NULL; + +-- 4.3 course_section <- course +UPDATE course_section cs +SET org_id = c.org_id +FROM course c +WHERE cs.course_id = c.id + AND cs.org_id IS NULL + AND c.org_id IS NOT NULL; + +-- 5) 回填 course.org_id(来自已回填完的 section) +WITH section_org AS ( + SELECT cs.course_id, MIN(cs.org_id) AS resolved_org_id + FROM course_section cs + WHERE cs.course_id IS NOT NULL + AND cs.org_id IS NOT NULL + GROUP BY cs.course_id +) +UPDATE course c +SET org_id = so.resolved_org_id +FROM section_org so +WHERE c.id = so.course_id + AND c.org_id IS NULL; + +-- 6) 回填 jihua / daka(来自其关系表) +WITH jihua_rel_org AS ( + SELECT jcs.jihua_id, MIN(jcs.org_id) AS resolved_org_id + FROM jihua_course_section jcs + WHERE jcs.org_id IS NOT NULL + GROUP BY jcs.jihua_id +) +UPDATE jihua j +SET org_id = jro.resolved_org_id +FROM jihua_rel_org jro +WHERE j.id = jro.jihua_id + AND j.org_id IS NULL; + +WITH daka_rel_org AS ( + SELECT dcs.daka_id, MIN(dcs.org_id) AS resolved_org_id + FROM daka_course_section dcs + WHERE dcs.org_id IS NOT NULL + GROUP BY dcs.daka_id +) +UPDATE daka d +SET org_id = dro.resolved_org_id +FROM daka_rel_org dro +WHERE d.id = dro.daka_id + AND d.org_id IS NULL; + +COMMIT; + +-- 回填后建议检查: +-- SELECT 'course' tbl, count(*) FROM course WHERE org_id IS NULL +-- UNION ALL SELECT 'course_section', count(*) FROM course_section WHERE org_id IS NULL +-- UNION ALL SELECT 'lianxi', count(*) FROM lianxi WHERE org_id IS NULL +-- UNION ALL SELECT 'piyue', count(*) FROM piyue WHERE org_id IS NULL +-- UNION ALL SELECT 'piyue_info', count(*) FROM piyue_info WHERE org_id IS NULL +-- UNION ALL SELECT 'jihua', count(*) FROM jihua WHERE org_id IS NULL +-- UNION ALL SELECT 'daka', count(*) FROM daka WHERE org_id IS NULL +-- UNION ALL SELECT 'jihua_course_section', count(*) FROM jihua_course_section WHERE org_id IS NULL +-- UNION ALL SELECT 'daka_course_section', count(*) FROM daka_course_section WHERE org_id IS NULL;