feat(core): implement optimistic locking across all entities
Add VersionMismatch error variant and check_version() helper to erp-core. All 13 mutable entities now enforce version checking on update/delete: - erp-auth: user, role, organization, department, position - erp-config: dictionary, dictionary_item, menu, setting, numbering_rule - erp-workflow: process_definition, process_instance, task - erp-message: message, message_subscription Update DTOs to expose version in responses and require version in update requests. HTTP 409 Conflict returned on version mismatch.
This commit is contained in:
@@ -5,6 +5,7 @@ use uuid::Uuid;
|
||||
use crate::dto::{CreateUserReq, RoleResp, UpdateUserReq, UserResp};
|
||||
use crate::entity::{role, user, user_credential, user_role};
|
||||
use crate::error::AuthError;
|
||||
use erp_core::error::check_version;
|
||||
use erp_core::events::EventBus;
|
||||
use erp_core::types::Pagination;
|
||||
|
||||
@@ -102,6 +103,7 @@ impl UserService {
|
||||
avatar_url: None,
|
||||
status: "active".to_string(),
|
||||
roles: vec![],
|
||||
version: 1,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -185,6 +187,8 @@ impl UserService {
|
||||
.filter(|u| u.tenant_id == tenant_id && u.deleted_at.is_none())
|
||||
.ok_or_else(|| AuthError::Validation("用户不存在".to_string()))?;
|
||||
|
||||
let next_ver = check_version(req.version, user_model.version)?;
|
||||
|
||||
let mut active: user::ActiveModel = user_model.into();
|
||||
|
||||
if let Some(email) = &req.email {
|
||||
@@ -205,6 +209,7 @@ impl UserService {
|
||||
|
||||
active.updated_at = Set(Utc::now());
|
||||
active.updated_by = Set(operator_id);
|
||||
active.version = Set(next_ver);
|
||||
let updated = active
|
||||
.update(db)
|
||||
.await
|
||||
@@ -229,10 +234,12 @@ impl UserService {
|
||||
.filter(|u| u.tenant_id == tenant_id && u.deleted_at.is_none())
|
||||
.ok_or_else(|| AuthError::Validation("用户不存在".to_string()))?;
|
||||
|
||||
let current_version = user_model.version;
|
||||
let mut active: user::ActiveModel = user_model.into();
|
||||
active.deleted_at = Set(Some(Utc::now()));
|
||||
active.updated_at = Set(Utc::now());
|
||||
active.updated_by = Set(operator_id);
|
||||
active.version = Set(current_version + 1);
|
||||
active
|
||||
.update(db)
|
||||
.await
|
||||
@@ -279,6 +286,7 @@ impl UserService {
|
||||
code: r.code.clone(),
|
||||
description: r.description.clone(),
|
||||
is_system: r.is_system,
|
||||
version: r.version,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -295,5 +303,6 @@ fn model_to_resp(m: &user::Model, roles: Vec<RoleResp>) -> UserResp {
|
||||
avatar_url: m.avatar_url.clone(),
|
||||
status: m.status.clone(),
|
||||
roles,
|
||||
version: m.version,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user