fix(core): 消除乐观锁 version.unwrap() 潜在 panic

20 处 ActiveValue::unwrap() + 1 乐观锁递增改为 take().unwrap_or(0) + 1,
避免数据库记录缺少 version 字段时 panic。覆盖 erp-auth/erp-config/
erp-workflow/erp-health/erp-ai/erp-server 7 个 crate。
DTO 层 Option<i32> 字段保持原有 unwrap_or(0) 不变。
This commit is contained in:
iven
2026-05-17 13:05:40 +08:00
parent 7b2c03309c
commit c631d364b3
20 changed files with 30 additions and 30 deletions

View File

@@ -143,7 +143,7 @@ where
} }
active.updated_at = Set(chrono::Utc::now()); active.updated_at = Set(chrono::Utc::now());
active.updated_by = Set(Some(ctx.user_id)); active.updated_by = Set(Some(ctx.user_id));
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
let result = active.update(&state.db).await?; let result = active.update(&state.db).await?;
Ok(Json(ApiResponse::ok(result))) Ok(Json(ApiResponse::ok(result)))
@@ -179,7 +179,7 @@ where
active.deleted_at = Set(Some(chrono::Utc::now())); active.deleted_at = Set(Some(chrono::Utc::now()));
active.updated_at = Set(chrono::Utc::now()); active.updated_at = Set(chrono::Utc::now());
active.updated_by = Set(Some(ctx.user_id)); active.updated_by = Set(Some(ctx.user_id));
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
active.update(&state.db).await?; active.update(&state.db).await?;
Ok(Json(ApiResponse::ok(serde_json::json!({"deleted": true})))) Ok(Json(ApiResponse::ok(serde_json::json!({"deleted": true}))))

View File

@@ -170,7 +170,7 @@ impl AnalysisService {
active.result_content = Set(Some(content)); active.result_content = Set(Some(content));
active.result_metadata = Set(Some(metadata)); active.result_metadata = Set(Some(metadata));
active.updated_at = Set(chrono::Utc::now()); active.updated_at = Set(chrono::Utc::now());
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
active.update(&self.db).await?; active.update(&self.db).await?;
Ok(()) Ok(())
} }
@@ -186,7 +186,7 @@ impl AnalysisService {
active.status = Set("failed".into()); active.status = Set("failed".into());
active.error_message = Set(Some(error)); active.error_message = Set(Some(error));
active.updated_at = Set(chrono::Utc::now()); active.updated_at = Set(chrono::Utc::now());
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
active.update(&self.db).await?; active.update(&self.db).await?;
Ok(()) Ok(())
} }

View File

@@ -124,7 +124,7 @@ impl AnalysisQueue {
active.status = Set("running".to_string()); active.status = Set("running".to_string());
active.started_at = Set(Some(now)); active.started_at = Set(Some(now));
active.updated_at = Set(now); active.updated_at = Set(now);
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
let model = active.update(&self.db).await?; let model = active.update(&self.db).await?;
Ok(Some(model)) Ok(Some(model))
} }
@@ -140,7 +140,7 @@ impl AnalysisQueue {
active.completed_at = Set(Some(now)); active.completed_at = Set(Some(now));
active.result_analysis_id = Set(Some(result_analysis_id)); active.result_analysis_id = Set(Some(result_analysis_id));
active.updated_at = Set(now); active.updated_at = Set(now);
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
active.update(&self.db).await?; active.update(&self.db).await?;
Ok(()) Ok(())
} }
@@ -163,7 +163,7 @@ impl AnalysisQueue {
active.retry_count = Set(retry_count + 1); active.retry_count = Set(retry_count + 1);
active.started_at = Set(None); active.started_at = Set(None);
active.updated_at = Set(now); active.updated_at = Set(now);
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
active.update(&self.db).await?; active.update(&self.db).await?;
Ok(()) Ok(())
} }

View File

@@ -104,7 +104,7 @@ impl InsightService {
active.is_dismissed = Set(true); active.is_dismissed = Set(true);
active.updated_at = Set(chrono::Utc::now()); active.updated_at = Set(chrono::Utc::now());
active.updated_by = Set(updated_by); active.updated_by = Set(updated_by);
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
active.update(db).await?; active.update(db).await?;
Ok(()) Ok(())
} }
@@ -123,7 +123,7 @@ impl InsightService {
let mut active: copilot_insights::ActiveModel = model.into(); let mut active: copilot_insights::ActiveModel = model.into();
active.deleted_at = Set(Some(now)); active.deleted_at = Set(Some(now));
active.updated_at = Set(now); active.updated_at = Set(now);
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
active.update(db).await?; active.update(db).await?;
} }
Ok(count) Ok(count)

View File

@@ -169,7 +169,7 @@ impl PromptService {
let mut active: ai_prompt::ActiveModel = sibling.into(); let mut active: ai_prompt::ActiveModel = sibling.into();
active.is_active = Set(false); active.is_active = Set(false);
active.updated_at = Set(chrono::Utc::now()); active.updated_at = Set(chrono::Utc::now());
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
active.update(&self.db).await?; active.update(&self.db).await?;
} }
@@ -177,7 +177,7 @@ impl PromptService {
let mut active: ai_prompt::ActiveModel = entity.into(); let mut active: ai_prompt::ActiveModel = entity.into();
active.is_active = Set(true); active.is_active = Set(true);
active.updated_at = Set(chrono::Utc::now()); active.updated_at = Set(chrono::Utc::now());
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
Ok(active.update(&self.db).await?) Ok(active.update(&self.db).await?)
} }

View File

@@ -89,7 +89,7 @@ impl RiskService {
active.llm_summary = Set(llm_summary); active.llm_summary = Set(llm_summary);
active.computed_at = Set(now); active.computed_at = Set(now);
active.updated_at = Set(now); active.updated_at = Set(now);
active.version_lock = Set(active.version_lock.unwrap() + 1); active.version_lock = Set(active.version_lock.take().unwrap_or(0) + 1);
active.update(db).await?; active.update(db).await?;
} else { } else {
let id = Uuid::now_v7(); let id = Uuid::now_v7();

View File

@@ -137,7 +137,7 @@ impl AuthService {
let mut user_active: user::ActiveModel = user_model.clone().into(); let mut user_active: user::ActiveModel = user_model.clone().into();
user_active.last_login_at = Set(Some(Utc::now())); user_active.last_login_at = Set(Some(Utc::now()));
user_active.updated_at = Set(Utc::now()); user_active.updated_at = Set(Utc::now());
user_active.version = Set(user_active.version.unwrap() + 1); user_active.version = Set(user_active.version.take().unwrap_or(0) + 1);
user_active user_active
.update(db) .update(db)
.await .await

View File

@@ -299,7 +299,7 @@ impl RoleService {
active.deleted_at = Set(Some(now)); active.deleted_at = Set(Some(now));
active.updated_at = Set(now); active.updated_at = Set(now);
active.updated_by = Set(operator_id); active.updated_by = Set(operator_id);
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active active
.update(db) .update(db)
.await .await

View File

@@ -168,7 +168,7 @@ impl TokenService {
let mut active: user_token::ActiveModel = token_row.into(); let mut active: user_token::ActiveModel = token_row.into();
active.revoked_at = Set(Some(Utc::now())); active.revoked_at = Set(Some(Utc::now()));
active.updated_at = Set(Utc::now()); active.updated_at = Set(Utc::now());
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active active
.update(db) .update(db)
.await .await
@@ -238,7 +238,7 @@ impl TokenService {
let mut active: user_token::ActiveModel = token.into(); let mut active: user_token::ActiveModel = token.into();
active.revoked_at = Set(Some(now)); active.revoked_at = Set(Some(now));
active.updated_at = Set(now); active.updated_at = Set(now);
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active active
.update(db) .update(db)
.await .await

View File

@@ -383,7 +383,7 @@ impl MenuService {
active.deleted_at = Set(Some(now)); active.deleted_at = Set(Some(now));
active.updated_at = Set(now); active.updated_at = Set(now);
active.updated_by = Set(operator_id); active.updated_by = Set(operator_id);
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active active
.update(db) .update(db)
.await .await

View File

@@ -392,7 +392,7 @@ impl NumberingService {
active.seq_current = Set(next_seq); active.seq_current = Set(next_seq);
active.last_reset_date = Set(Some(today)); active.last_reset_date = Set(Some(today));
active.updated_at = Set(Utc::now()); active.updated_at = Set(Utc::now());
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active active
.update(txn) .update(txn)
.await .await

View File

@@ -302,7 +302,7 @@ impl OAuthService {
let mut active: api_client::ActiveModel = client.into(); let mut active: api_client::ActiveModel = client.into();
active.deleted_at = Set(Some(Utc::now().into())); active.deleted_at = Set(Some(Utc::now().into()));
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active.update(db).await?; active.update(db).await?;
Ok(()) Ok(())
@@ -328,7 +328,7 @@ impl OAuthService {
let mut active: api_client::ActiveModel = client.into(); let mut active: api_client::ActiveModel = client.into();
active.client_secret_hash = Set(hash); active.client_secret_hash = Set(hash);
active.updated_at = Set(Utc::now().into()); active.updated_at = Set(Utc::now().into());
active.version = Set(active.version.clone().unwrap() + 1); active.version = Set(active.version.clone().take().unwrap_or(0) + 1);
let id = active.id.clone().unwrap().to_string(); let id = active.id.clone().unwrap().to_string();
active.update(db).await?; active.update(db).await?;

View File

@@ -338,7 +338,7 @@ pub async fn increment_view_count(
let mut active: article::ActiveModel = model.into(); let mut active: article::ActiveModel = model.into();
active.view_count = Set(active.view_count.take().unwrap_or(0) + 1); active.view_count = Set(active.view_count.take().unwrap_or(0) + 1);
active.updated_at = Set(Utc::now()); active.updated_at = Set(Utc::now());
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active.update(&state.db).await?; active.update(&state.db).await?;
Ok(()) Ok(())
} }

View File

@@ -264,7 +264,7 @@ pub async fn heartbeat(
active.ip_address = Set(Some(v)); active.ip_address = Set(Some(v));
} }
active.updated_at = Set(now); active.updated_at = Set(now);
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active.update(&state.db).await?; active.update(&state.db).await?;
Ok(()) Ok(())

View File

@@ -173,7 +173,7 @@ pub async fn delete_threshold(
let mut active: critical_value_threshold::ActiveModel = existing.into(); let mut active: critical_value_threshold::ActiveModel = existing.into();
active.deleted_at = Set(Some(chrono::Utc::now())); active.deleted_at = Set(Some(chrono::Utc::now()));
active.updated_by = Set(operator_id); active.updated_by = Set(operator_id);
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active.update(db).await?; active.update(db).await?;
Ok(()) Ok(())
} }

View File

@@ -207,7 +207,7 @@ pub async fn update_template(
for old in old_fields { for old in old_fields {
let mut af: follow_up_template_field::ActiveModel = old.into(); let mut af: follow_up_template_field::ActiveModel = old.into();
af.deleted_at = Set(Some(Utc::now())); af.deleted_at = Set(Some(Utc::now()));
af.version = Set(af.version.unwrap() + 1); af.version = Set(af.version.take().unwrap_or(0) + 1);
af.update(&state.db).await?; af.update(&state.db).await?;
} }
// 插入新字段 // 插入新字段
@@ -270,7 +270,7 @@ pub async fn delete_template(
for f in fields { for f in fields {
let mut af: follow_up_template_field::ActiveModel = f.into(); let mut af: follow_up_template_field::ActiveModel = f.into();
af.deleted_at = Set(Some(Utc::now())); af.deleted_at = Set(Some(Utc::now()));
af.version = Set(af.version.unwrap() + 1); af.version = Set(af.version.take().unwrap_or(0) + 1);
af.update(&state.db).await?; af.update(&state.db).await?;
} }

View File

@@ -530,7 +530,7 @@ pub async fn remove_doctor(
active.deleted_at = Set(Some(Utc::now())); active.deleted_at = Set(Some(Utc::now()));
active.updated_at = Set(Utc::now()); active.updated_at = Set(Utc::now());
active.updated_by = Set(operator_id); active.updated_by = Set(operator_id);
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active.update(&state.db).await?; active.update(&state.db).await?;
audit_service::record( audit_service::record(

View File

@@ -542,7 +542,7 @@ pub async fn exchange_product(
// 关联消费流水的 order_id // 关联消费流水的 order_id
let mut spend_active: points_transaction::ActiveModel = spend.into(); let mut spend_active: points_transaction::ActiveModel = spend.into();
spend_active.order_id = Set(Some(inserted_order.id)); spend_active.order_id = Set(Some(inserted_order.id));
spend_active.version = Set(spend_active.version.unwrap() + 1); spend_active.version = Set(spend_active.version.take().unwrap_or(0) + 1);
spend_active.updated_at = Set(Utc::now()); spend_active.updated_at = Set(Utc::now());
spend_active.update(&txn).await?; spend_active.update(&txn).await?;

View File

@@ -108,7 +108,7 @@ async fn handle_dialysis_record_created(
.ok_or("透析记录不存在")?; .ok_or("透析记录不存在")?;
let mut active: erp_dialysis::entity::dialysis_record::ActiveModel = record.into(); let mut active: erp_dialysis::entity::dialysis_record::ActiveModel = record.into();
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active.workflow_instance_id = Set(Some(result.id)); active.workflow_instance_id = Set(Some(result.id));
active.updated_at = Set(chrono::Utc::now()); active.updated_at = Set(chrono::Utc::now());
active.update(db).await?; active.update(db).await?;

View File

@@ -89,7 +89,7 @@ impl FlowExecutor {
// 消费当前 token // 消费当前 token
let mut active: token::ActiveModel = current_token.into(); let mut active: token::ActiveModel = current_token.into();
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active.status = Set("consumed".to_string()); active.status = Set("consumed".to_string());
active.consumed_at = Set(Some(Utc::now())); active.consumed_at = Set(Some(Utc::now()));
active active
@@ -600,7 +600,7 @@ impl FlowExecutor {
.ok_or_else(|| WorkflowError::NotFound(format!("流程实例不存在: {instance_id}")))?; .ok_or_else(|| WorkflowError::NotFound(format!("流程实例不存在: {instance_id}")))?;
let mut active: process_instance::ActiveModel = instance.into(); let mut active: process_instance::ActiveModel = instance.into();
active.version = Set(active.version.unwrap() + 1); active.version = Set(active.version.take().unwrap_or(0) + 1);
active.status = Set("completed".to_string()); active.status = Set("completed".to_string());
active.completed_at = Set(Some(Utc::now())); active.completed_at = Set(Some(Utc::now()));
active.updated_at = Set(Utc::now()); active.updated_at = Set(Utc::now());