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

@@ -7,6 +7,7 @@ use uuid::Uuid;
use crate::dto::SettingResp;
use crate::entity::setting;
use crate::error::{ConfigError, ConfigResult};
use erp_core::error::check_version;
use erp_core::events::EventBus;
use erp_core::types::Pagination;
@@ -89,11 +90,17 @@ impl SettingService {
.map_err(|e| ConfigError::Validation(e.to_string()))?;
if let Some(model) = existing {
// Update existing record
// Update existing record — 乐观锁校验
let next_version = match params.version {
Some(v) => check_version(v, model.version).map_err(|_| ConfigError::VersionMismatch)?,
None => model.version + 1,
};
let mut active: setting::ActiveModel = model.into();
active.setting_value = Set(params.value.clone());
active.updated_at = Set(Utc::now());
active.updated_by = Set(operator_id);
active.version = Set(next_version);
let updated = active
.update(db)
@@ -181,12 +188,14 @@ impl SettingService {
}
/// Soft-delete a setting by setting the `deleted_at` timestamp.
/// Performs optimistic locking via version check.
pub async fn delete(
key: &str,
scope: &str,
scope_id: &Option<Uuid>,
tenant_id: Uuid,
operator_id: Uuid,
version: i32,
db: &sea_orm::DatabaseConnection,
) -> ConfigResult<()> {
let model = setting::Entity::find()
@@ -205,10 +214,14 @@ impl SettingService {
))
})?;
let next_version =
check_version(version, model.version).map_err(|_| ConfigError::VersionMismatch)?;
let mut active: setting::ActiveModel = model.into();
active.deleted_at = Set(Some(Utc::now()));
active.updated_at = Set(Utc::now());
active.updated_by = Set(operator_id);
active.version = Set(next_version);
active
.update(db)
.await
@@ -283,6 +296,7 @@ impl SettingService {
scope_id: model.scope_id,
setting_key: model.setting_key.clone(),
setting_value: model.setting_value.clone(),
version: model.version,
}
}
}