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:
iven
2026-04-11 23:25:43 +08:00
parent 1fec5e2cf2
commit 5d6e1dc394
32 changed files with 549 additions and 184 deletions

View File

@@ -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,
}
}