From e0052ea99b7a92d4fede8e9ff82f695472459753 Mon Sep 17 00:00:00 2001 From: iven Date: Wed, 3 Jun 2026 01:03:57 +0800 Subject: [PATCH] =?UTF-8?q?fix(diary):=20=E6=B7=BB=E5=8A=A0=E4=BA=8B?= =?UTF-8?q?=E5=8A=A1=20=E2=80=94=20create=5Fclass/join=5Fclass/parent=20?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=8E=9F=E5=AD=90=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - create_class: 班级创建 + 老师成员插入包裹在 db.transaction() 中 - join_class: 成员插入 + member_count 更新包裹在事务中 - delete_child_data: PIPL 删除权 — 逐条软删除包裹在事务中(避免部分删除) - DiaryError: 添加 From> 支持事务闭包 审计 ID: B-07, B-11, 8a-C03 --- crates/erp-diary/src/error.rs | 12 ++ crates/erp-diary/src/service/class_service.rs | 132 ++++++++++-------- .../erp-diary/src/service/parent_service.rs | 27 ++-- 3 files changed, 102 insertions(+), 69 deletions(-) diff --git a/crates/erp-diary/src/error.rs b/crates/erp-diary/src/error.rs index f8051d7..cf719b0 100644 --- a/crates/erp-diary/src/error.rs +++ b/crates/erp-diary/src/error.rs @@ -106,6 +106,18 @@ impl From for DiaryError { } } +/// 支持 db.transaction() 闭包的错误转换 +impl From> for DiaryError { + fn from(err: sea_orm::TransactionError) -> Self { + match err { + sea_orm::TransactionError::Connection(db_err) => { + DiaryError::Internal(db_err.to_string()) + } + sea_orm::TransactionError::Transaction(inner) => inner, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/erp-diary/src/service/class_service.rs b/crates/erp-diary/src/service/class_service.rs index a198758..c51874c 100644 --- a/crates/erp-diary/src/service/class_service.rs +++ b/crates/erp-diary/src/service/class_service.rs @@ -3,7 +3,7 @@ use chrono::{Months, Utc}; use sea_orm::{ ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, - Set, + Set, TransactionTrait, }; use uuid::Uuid; @@ -37,42 +37,49 @@ impl ClassService { // 过期时间:6 个月后 let expires_at = now.checked_add_months(Months::new(6)); - // 创建班级记录 - let class_model = school_class::ActiveModel { - id: Set(id), - tenant_id: Set(tenant_id), - name: Set(name), - school_name: Set(school_name), - teacher_id: Set(teacher_id), - class_code: Set(class_code.clone()), - member_count: Set(1), // 老师自动计入 - is_active: Set(true), - expires_at: Set(expires_at), - created_at: Set(now), - updated_at: Set(now), - created_by: Set(teacher_id), - updated_by: Set(teacher_id), - deleted_at: Set(None), - version: Set(1), - }; - let inserted_class = class_model.insert(db).await?; + // 事务:创建班级 + 老师自动加入(原子操作,保证一致性) + let inserted_class = db + .transaction::<_, school_class::Model, DiaryError>(|txn| { + Box::pin(async move { + let class_model = school_class::ActiveModel { + id: Set(id), + tenant_id: Set(tenant_id), + name: Set(name), + school_name: Set(school_name), + teacher_id: Set(teacher_id), + class_code: Set(class_code), + member_count: Set(1), // 老师自动计入 + is_active: Set(true), + expires_at: Set(expires_at), + created_at: Set(now), + updated_at: Set(now), + created_by: Set(teacher_id), + updated_by: Set(teacher_id), + deleted_at: Set(None), + version: Set(1), + }; + let inserted = class_model.insert(txn).await?; - // 自动将老师加入成员表 - let member_model = class_member::ActiveModel { - class_id: Set(id), - user_id: Set(teacher_id), - tenant_id: Set(tenant_id), - role: Set("teacher".to_string()), - nickname: Set(None), - joined_at: Set(now), - created_at: Set(now), - updated_at: Set(now), - created_by: Set(teacher_id), - updated_by: Set(teacher_id), - deleted_at: Set(None), - version: Set(1), - }; - member_model.insert(db).await?; + let member_model = class_member::ActiveModel { + class_id: Set(id), + user_id: Set(teacher_id), + tenant_id: Set(tenant_id), + role: Set("teacher".to_string()), + nickname: Set(None), + joined_at: Set(now), + created_at: Set(now), + updated_at: Set(now), + created_by: Set(teacher_id), + updated_by: Set(teacher_id), + deleted_at: Set(None), + version: Set(1), + }; + member_model.insert(txn).await?; + + Ok(inserted) + }) + }) + .await?; // 发布 ClassCreated 事件 event_bus @@ -175,31 +182,38 @@ impl ClassService { return Err(DiaryError::BadRequest("已是班级成员".to_string())); } - // 5. 创建成员记录 - let member_model = class_member::ActiveModel { - class_id: Set(class_id), - user_id: Set(user_id), - tenant_id: Set(tenant_id), - role: Set("student".to_string()), - nickname: Set(nickname), - joined_at: Set(now), - created_at: Set(now), - updated_at: Set(now), - created_by: Set(user_id), - updated_by: Set(user_id), - deleted_at: Set(None), - version: Set(1), - }; - member_model.insert(db).await?; - - // 6. 更新 member_count + // 5. 事务:创建成员记录 + 更新 member_count(原子操作) let current_count = class_model.member_count; let current_version = class_model.version; - let mut active_class: school_class::ActiveModel = class_model.into(); - active_class.member_count = Set(current_count + 1); - active_class.updated_at = Set(now); - active_class.version = Set(current_version + 1); - let updated_class = active_class.update(db).await?; + let updated_class = db + .transaction::<_, school_class::Model, DiaryError>(|txn| { + Box::pin(async move { + let member_model = class_member::ActiveModel { + class_id: Set(class_id), + user_id: Set(user_id), + tenant_id: Set(tenant_id), + role: Set("student".to_string()), + nickname: Set(nickname), + joined_at: Set(now), + created_at: Set(now), + updated_at: Set(now), + created_by: Set(user_id), + updated_by: Set(user_id), + deleted_at: Set(None), + version: Set(1), + }; + member_model.insert(txn).await?; + + let mut active_class: school_class::ActiveModel = class_model.into(); + active_class.member_count = Set(current_count + 1); + active_class.updated_at = Set(now); + active_class.version = Set(current_version + 1); + let updated = active_class.update(txn).await?; + + Ok(updated) + }) + }) + .await?; // 7. 成功加入 → 清除错误计数 if let Some(redis_client) = redis { diff --git a/crates/erp-diary/src/service/parent_service.rs b/crates/erp-diary/src/service/parent_service.rs index 60d98ed..8b37941 100644 --- a/crates/erp-diary/src/service/parent_service.rs +++ b/crates/erp-diary/src/service/parent_service.rs @@ -3,7 +3,7 @@ use chrono::Utc; use sea_orm::{ ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, PaginatorTrait, - QueryFilter, QueryOrder, Set, + QueryFilter, QueryOrder, Set, TransactionTrait, }; use uuid::Uuid; @@ -176,15 +176,22 @@ impl ParentService { let count = journals.len(); let now = Utc::now(); - for journal in journals { - let current_version = journal.version; - let mut active: journal_entry::ActiveModel = journal.into(); - active.deleted_at = Set(Some(now)); - active.updated_at = Set(now); - active.updated_by = Set(parent_id); - active.version = Set(current_version + 1); - active.update(db).await?; - } + // 事务:软删除所有日记(PIPL 删除权 — 原子操作,避免部分删除) + db.transaction::<_, (), DiaryError>(|txn| { + Box::pin(async move { + for journal in journals { + let current_version = journal.version; + let mut active: journal_entry::ActiveModel = journal.into(); + active.deleted_at = Set(Some(now)); + active.updated_at = Set(now); + active.updated_by = Set(parent_id); + active.version = Set(current_version + 1); + active.update(txn).await?; + } + Ok(()) + }) + }) + .await?; event_bus .publish(