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:
@@ -30,6 +30,7 @@ pub struct MessageResp {
|
||||
pub sent_at: Option<DateTime<Utc>>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
/// 发送消息请求
|
||||
@@ -162,6 +163,7 @@ pub struct MessageSubscriptionResp {
|
||||
pub dnd_end: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
/// 更新消息订阅偏好请求
|
||||
@@ -172,4 +174,5 @@ pub struct UpdateSubscriptionReq {
|
||||
pub dnd_enabled: Option<bool>,
|
||||
pub dnd_start: Option<String>,
|
||||
pub dnd_end: Option<String>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ pub enum MessageError {
|
||||
|
||||
#[error("渲染失败: {0}")]
|
||||
TemplateRenderError(String),
|
||||
|
||||
#[error("版本冲突: 数据已被其他操作修改,请刷新后重试")]
|
||||
VersionMismatch,
|
||||
}
|
||||
|
||||
impl From<MessageError> for AppError {
|
||||
@@ -23,6 +26,7 @@ impl From<MessageError> for AppError {
|
||||
MessageError::NotFound(msg) => AppError::NotFound(msg),
|
||||
MessageError::DuplicateTemplateCode(msg) => AppError::Conflict(msg),
|
||||
MessageError::TemplateRenderError(msg) => AppError::Internal(msg),
|
||||
MessageError::VersionMismatch => AppError::VersionMismatch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,9 +218,11 @@ impl MessageService {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let current_version = model.version;
|
||||
let mut active: message::ActiveModel = model.into();
|
||||
active.is_read = Set(true);
|
||||
active.read_at = Set(Some(Utc::now()));
|
||||
active.version = Set(current_version + 1);
|
||||
active.updated_at = Set(Utc::now());
|
||||
active.updated_by = Set(user_id);
|
||||
active
|
||||
@@ -275,7 +277,9 @@ impl MessageService {
|
||||
));
|
||||
}
|
||||
|
||||
let current_version = model.version;
|
||||
let mut active: message::ActiveModel = model.into();
|
||||
active.version = Set(current_version + 1);
|
||||
active.deleted_at = Set(Some(Utc::now()));
|
||||
active.updated_at = Set(Utc::now());
|
||||
active.updated_by = Set(user_id);
|
||||
@@ -308,6 +312,7 @@ impl MessageService {
|
||||
sent_at: m.sent_at,
|
||||
created_at: m.created_at,
|
||||
updated_at: m.updated_at,
|
||||
version: m.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use uuid::Uuid;
|
||||
use crate::dto::{MessageSubscriptionResp, UpdateSubscriptionReq};
|
||||
use crate::entity::message_subscription;
|
||||
use crate::error::{MessageError, MessageResult};
|
||||
use erp_core::error::check_version;
|
||||
|
||||
/// 消息订阅偏好服务。
|
||||
pub struct SubscriptionService;
|
||||
@@ -46,6 +47,9 @@ impl SubscriptionService {
|
||||
let now = Utc::now();
|
||||
|
||||
if let Some(model) = existing {
|
||||
let current_version = model.version;
|
||||
let next_ver = check_version(req.version, current_version)
|
||||
.map_err(|_| MessageError::VersionMismatch)?;
|
||||
let mut active: message_subscription::ActiveModel = model.into();
|
||||
if let Some(types) = &req.notification_types {
|
||||
active.notification_types = Set(Some(types.clone()));
|
||||
@@ -64,6 +68,7 @@ impl SubscriptionService {
|
||||
}
|
||||
active.updated_at = Set(now);
|
||||
active.updated_by = Set(user_id);
|
||||
active.version = Set(next_ver);
|
||||
|
||||
let updated = active
|
||||
.update(db)
|
||||
@@ -112,6 +117,7 @@ impl SubscriptionService {
|
||||
dnd_end: m.dnd_end.clone(),
|
||||
created_at: m.created_at,
|
||||
updated_at: m.updated_at,
|
||||
version: m.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user