chore: checkpoint local workspace changes

This commit is contained in:
2026-04-23 12:45:15 +08:00
parent 3eb9390e8f
commit a6cd9afcbb
47 changed files with 2154 additions and 529 deletions

View File

@@ -8,6 +8,7 @@ license.workspace = true
platform-auth = { path = "../platform-auth" }
shared-kernel = { path = "../shared-kernel" }
time = { version = "0.3", features = ["formatting", "parsing"] }
tracing = "0.1"
uuid = { version = "1", features = ["v4"] }
[dev-dependencies]

View File

@@ -14,6 +14,7 @@ use shared_kernel::{
normalize_optional_string, normalize_required_string, parse_rfc3339,
};
use time::{Duration, OffsetDateTime};
use tracing::{info, warn};
const USERNAME_MIN_LENGTH: usize = 3;
const USERNAME_MAX_LENGTH: usize = 24;
@@ -90,6 +91,10 @@ pub struct SendPhoneCodeResult {
pub cooldown_seconds: u64,
pub expires_in_seconds: u64,
pub provider_request_id: Option<String>,
pub provider_out_id: Option<String>,
pub provider: String,
pub scene: String,
pub phone_number_masked: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -102,6 +107,9 @@ pub struct PhoneLoginInput {
pub struct PhoneLoginResult {
pub user: AuthUser,
pub created: bool,
pub provider: String,
pub provider_out_id: Option<String>,
pub phone_number_masked: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -577,7 +585,18 @@ impl PhoneAuthService {
input: SendPhoneCodeInput,
now: OffsetDateTime,
) -> Result<SendPhoneCodeResult, PhoneAuthError> {
let scene = input.scene.clone();
let normalized_phone = normalize_mainland_china_phone_number(&input.phone_number)?;
let national_phone_number = build_national_phone_number(&normalized_phone.e164)?;
info!(
scene = scene.as_str(),
provider = self.sms_provider.kind().as_str(),
phone_e164_masked = mask_phone_number(&normalized_phone.e164).as_str(),
phone_national_masked = normalized_phone.masked_national_number.as_str(),
"手机号验证码发送准备调用 provider"
);
self.store
.ensure_phone_code_not_cooling_down(&normalized_phone.e164, &scene, now)?;
let expires_at = now
.checked_add(Duration::minutes(SMS_CODE_TTL_MINUTES))
.ok_or_else(|| PhoneAuthError::Store("短信验证码过期时间计算溢出".to_string()))?;
@@ -588,16 +607,27 @@ impl PhoneAuthService {
let provider_result = self
.sms_provider
.send_code(SmsSendCodeRequest {
national_phone_number: build_national_phone_number(&normalized_phone.e164)?,
national_phone_number,
scene: input.scene.as_str().to_string(),
})
.await
.map_err(map_sms_provider_error_to_phone_error)?;
info!(
scene = scene.as_str(),
provider = self.sms_provider.kind().as_str(),
phone_e164_masked = mask_phone_number(&normalized_phone.e164).as_str(),
phone_national_masked = normalized_phone.masked_national_number.as_str(),
cooldown_seconds = provider_result.cooldown_seconds,
expires_in_seconds = provider_result.expires_in_seconds,
provider_request_id = provider_result.provider_request_id.as_deref().unwrap_or("unknown"),
provider_out_id = provider_result.provider_out_id.as_deref().unwrap_or("unknown"),
"手机号验证码 provider 调用成功,准备写入本地快照"
);
self.store.upsert_phone_code(
StoredPhoneCode {
phone_number: normalized_phone.e164.clone(),
scene: input.scene,
scene,
expires_at,
last_sent_at: format_rfc3339(now).map_err(|message| {
PhoneAuthError::Store(format!("短信验证码发送时间格式化失败:{message}"))
@@ -612,6 +642,10 @@ impl PhoneAuthService {
cooldown_seconds: provider_result.cooldown_seconds,
expires_in_seconds: provider_result.expires_in_seconds,
provider_request_id: provider_result.provider_request_id,
provider_out_id: provider_result.provider_out_id,
provider: self.sms_provider.kind().as_str().to_string(),
scene: input.scene.as_str().to_string(),
phone_number_masked: normalized_phone.masked_national_number,
})
}
@@ -656,6 +690,9 @@ impl PhoneAuthService {
..user
},
created: false,
provider: self.sms_provider.kind().as_str().to_string(),
provider_out_id,
phone_number_masked: normalized_phone.masked_national_number,
});
}
@@ -671,6 +708,9 @@ impl PhoneAuthService {
Ok(PhoneLoginResult {
user: created_user,
created: true,
provider: self.sms_provider.kind().as_str().to_string(),
provider_out_id,
phone_number_masked: normalized_phone.masked_national_number,
})
}
@@ -1205,7 +1245,7 @@ impl InMemoryAuthStore {
fn upsert_phone_code(
&self,
code: StoredPhoneCode,
now: OffsetDateTime,
_now: OffsetDateTime,
) -> Result<(), PhoneAuthError> {
let mut state = self
.inner
@@ -1213,26 +1253,49 @@ impl InMemoryAuthStore {
.map_err(|_| PhoneAuthError::Store("短信验证码仓储锁已中毒".to_string()))?;
// 手机号和业务场景共同决定同一份验证码快照,重复发送时直接覆盖旧值。
let key = build_phone_code_key(&code.phone_number, &code.scene);
if let Some(stored) = state.phone_codes_by_key.get(&key).cloned() {
let expires_at = parse_phone_code_time(&stored.expires_at, "过期时间")?;
if expires_at > now {
let last_sent_at = parse_phone_code_time(&stored.last_sent_at, "发送时间")?;
let cooling_until = last_sent_at
.checked_add(Duration::seconds(SMS_CODE_COOLDOWN_SECONDS as i64))
.ok_or_else(|| {
PhoneAuthError::Store("短信验证码冷却时间计算溢出".to_string())
})?;
if cooling_until > now {
return Err(PhoneAuthError::SendCoolingDown {
retry_after_seconds: seconds_until(now, cooling_until),
});
}
}
}
state.phone_codes_by_key.insert(key, code);
Ok(())
}
fn ensure_phone_code_not_cooling_down(
&self,
phone_number: &str,
scene: &PhoneAuthScene,
now: OffsetDateTime,
) -> Result<(), PhoneAuthError> {
let state = self
.inner
.lock()
.map_err(|_| PhoneAuthError::Store("短信验证码仓储锁已中毒".to_string()))?;
let key = build_phone_code_key(phone_number, scene);
let Some(stored) = state.phone_codes_by_key.get(&key).cloned() else {
return Ok(());
};
drop(state);
let expires_at = parse_phone_code_time(&stored.expires_at, "过期时间")?;
if expires_at <= now {
return Ok(());
}
let last_sent_at = parse_phone_code_time(&stored.last_sent_at, "发送时间")?;
let cooling_until = last_sent_at
.checked_add(Duration::seconds(SMS_CODE_COOLDOWN_SECONDS as i64))
.ok_or_else(|| PhoneAuthError::Store("短信验证码冷却时间计算溢出".to_string()))?;
if cooling_until <= now {
return Ok(());
}
let retry_after_seconds = seconds_until(now, cooling_until);
warn!(
scene = scene.as_str(),
phone_masked = mask_phone_number(phone_number).as_str(),
retry_after_seconds,
"手机号验证码发送命中本地冷却限制"
);
Err(PhoneAuthError::SendCoolingDown {
retry_after_seconds,
})
}
fn assert_phone_code_active(
&self,
phone_number: &str,