fix(diary): 添加事务 — create_class/join_class/parent 删除原子化
- create_class: 班级创建 + 老师成员插入包裹在 db.transaction() 中 - join_class: 成员插入 + member_count 更新包裹在事务中 - delete_child_data: PIPL 删除权 — 逐条软删除包裹在事务中(避免部分删除) - DiaryError: 添加 From<TransactionError<DiaryError>> 支持事务闭包 审计 ID: B-07, B-11, 8a-C03
This commit is contained in:
@@ -106,6 +106,18 @@ impl From<sea_orm::DbErr> for DiaryError {
|
||||
}
|
||||
}
|
||||
|
||||
/// 支持 db.transaction() 闭包的错误转换
|
||||
impl From<sea_orm::TransactionError<DiaryError>> for DiaryError {
|
||||
fn from(err: sea_orm::TransactionError<DiaryError>) -> 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::*;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user