feat(server-rs): 接入真实短信验证码链路

This commit is contained in:
2026-04-23 00:09:51 +08:00
parent 1223f597d2
commit 9cb996b80e
11 changed files with 1274 additions and 40 deletions

View File

@@ -1291,7 +1291,10 @@ mod tests {
payload["expiresInSeconds"],
Value::Number(serde_json::Number::from(300))
);
assert_eq!(payload["providerRequestId"], Value::Null);
assert_eq!(
payload["providerRequestId"],
Value::String("mock-request-id".to_string())
);
}
#[tokio::test]

View File

@@ -25,6 +25,23 @@ pub struct AppConfig {
pub refresh_cookie_same_site: String,
pub refresh_session_ttl_days: u32,
pub sms_auth_enabled: bool,
pub sms_auth_provider: String,
pub sms_endpoint: String,
pub sms_access_key_id: Option<String>,
pub sms_access_key_secret: Option<String>,
pub sms_sign_name: String,
pub sms_template_code: String,
pub sms_template_param_key: String,
pub sms_country_code: String,
pub sms_scheme_name: Option<String>,
pub sms_code_length: u8,
pub sms_code_type: u8,
pub sms_valid_time_seconds: u64,
pub sms_interval_seconds: u64,
pub sms_duplicate_policy: u8,
pub sms_case_auth_policy: u8,
pub sms_return_verify_code: bool,
pub sms_mock_verify_code: String,
pub wechat_auth_enabled: bool,
pub wechat_auth_provider: String,
pub wechat_app_id: Option<String>,
@@ -79,6 +96,23 @@ impl Default for AppConfig {
refresh_cookie_same_site: "Lax".to_string(),
refresh_session_ttl_days: 30,
sms_auth_enabled: false,
sms_auth_provider: "mock".to_string(),
sms_endpoint: "dypnsapi.aliyuncs.com".to_string(),
sms_access_key_id: None,
sms_access_key_secret: None,
sms_sign_name: "速通互联验证码".to_string(),
sms_template_code: "100001".to_string(),
sms_template_param_key: "code".to_string(),
sms_country_code: "86".to_string(),
sms_scheme_name: None,
sms_code_length: 6,
sms_code_type: 1,
sms_valid_time_seconds: 300,
sms_interval_seconds: 60,
sms_duplicate_policy: 1,
sms_case_auth_policy: 1,
sms_return_verify_code: false,
sms_mock_verify_code: "123456".to_string(),
wechat_auth_enabled: false,
wechat_auth_provider: "mock".to_string(),
wechat_app_id: None,
@@ -194,6 +228,60 @@ impl AppConfig {
if let Some(sms_auth_enabled) = read_first_bool_env(&["SMS_AUTH_ENABLED"]) {
config.sms_auth_enabled = sms_auth_enabled;
}
if let Some(sms_auth_provider) = read_first_non_empty_env(&["SMS_AUTH_PROVIDER"]) {
config.sms_auth_provider = sms_auth_provider;
}
if let Some(sms_endpoint) = read_first_non_empty_env(&["ALIYUN_SMS_ENDPOINT"]) {
config.sms_endpoint = sms_endpoint;
}
config.sms_access_key_id = read_first_non_empty_env(&["ALIYUN_SMS_ACCESS_KEY_ID"]);
config.sms_access_key_secret = read_first_non_empty_env(&["ALIYUN_SMS_ACCESS_KEY_SECRET"]);
if let Some(sms_sign_name) = read_first_non_empty_env(&["ALIYUN_SMS_SIGN_NAME"]) {
config.sms_sign_name = sms_sign_name;
}
if let Some(sms_template_code) = read_first_non_empty_env(&["ALIYUN_SMS_TEMPLATE_CODE"]) {
config.sms_template_code = sms_template_code;
}
if let Some(sms_template_param_key) =
read_first_non_empty_env(&["ALIYUN_SMS_TEMPLATE_PARAM_KEY"])
{
config.sms_template_param_key = sms_template_param_key;
}
if let Some(sms_country_code) = read_first_non_empty_env(&["ALIYUN_SMS_COUNTRY_CODE"]) {
config.sms_country_code = sms_country_code;
}
config.sms_scheme_name = read_first_non_empty_env(&["ALIYUN_SMS_SCHEME_NAME"]);
if let Some(sms_code_length) = read_first_u8_env(&["ALIYUN_SMS_CODE_LENGTH"]) {
config.sms_code_length = sms_code_length;
}
if let Some(sms_code_type) = read_first_u8_env(&["ALIYUN_SMS_CODE_TYPE"]) {
config.sms_code_type = sms_code_type;
}
if let Some(sms_valid_time_seconds) =
read_first_duration_seconds_env(&["ALIYUN_SMS_VALID_TIME_SECONDS"])
{
config.sms_valid_time_seconds = sms_valid_time_seconds;
}
if let Some(sms_interval_seconds) =
read_first_duration_seconds_env(&["ALIYUN_SMS_INTERVAL_SECONDS"])
{
config.sms_interval_seconds = sms_interval_seconds;
}
if let Some(sms_duplicate_policy) = read_first_u8_env(&["ALIYUN_SMS_DUPLICATE_POLICY"]) {
config.sms_duplicate_policy = sms_duplicate_policy;
}
if let Some(sms_case_auth_policy) = read_first_u8_env(&["ALIYUN_SMS_CASE_AUTH_POLICY"]) {
config.sms_case_auth_policy = sms_case_auth_policy;
}
if let Some(sms_return_verify_code) =
read_first_bool_env(&["ALIYUN_SMS_RETURN_VERIFY_CODE"])
{
config.sms_return_verify_code = sms_return_verify_code;
}
if let Some(sms_mock_verify_code) = read_first_non_empty_env(&["SMS_AUTH_MOCK_VERIFY_CODE"])
{
config.sms_mock_verify_code = sms_mock_verify_code;
}
if let Some(wechat_auth_enabled) = read_first_bool_env(&["WECHAT_AUTH_ENABLED"]) {
config.wechat_auth_enabled = wechat_auth_enabled;
@@ -439,6 +527,11 @@ fn read_first_u64_env(keys: &[&str]) -> Option<u64> {
.find_map(|key| env::var(key).ok().and_then(|value| parse_u64(&value)))
}
fn read_first_u8_env(keys: &[&str]) -> Option<u8> {
keys.iter()
.find_map(|key| env::var(key).ok().and_then(|value| parse_u8(&value)))
}
fn read_first_positive_u16_env(keys: &[&str]) -> Option<u16> {
keys.iter().find_map(|key| {
env::var(key)
@@ -515,6 +608,10 @@ fn parse_u64(raw: &str) -> Option<u64> {
raw.trim().parse::<u64>().ok()
}
fn parse_u8(raw: &str) -> Option<u8> {
raw.trim().parse::<u8>().ok()
}
fn parse_positive_u16(raw: &str) -> Option<u16> {
let value = raw.trim().parse::<u16>().ok()?;
if value == 0 {

View File

@@ -46,6 +46,7 @@ pub async fn send_phone_code(
},
OffsetDateTime::now_utc(),
)
.await
.map_err(map_phone_auth_error)?;
Ok(json_success_body(

View File

@@ -16,6 +16,7 @@ use module_runtime::RuntimeSnapshotRecord;
use module_runtime::{SAVE_SNAPSHOT_VERSION, format_utc_micros};
use platform_auth::{
JwtConfig, JwtError, RefreshCookieConfig, RefreshCookieError, RefreshCookieSameSite,
SmsAuthConfig, SmsAuthProvider, SmsAuthProviderKind, SmsProviderError,
};
use platform_llm::{LlmClient, LlmConfig, LlmError};
use platform_oss::{OssClient, OssConfig, OssError};
@@ -54,6 +55,7 @@ pub struct AppState {
pub enum AppStateInitError {
Jwt(JwtError),
RefreshCookie(RefreshCookieError),
SmsProvider(SmsProviderError),
Oss(OssError),
Llm(LlmError),
}
@@ -78,9 +80,30 @@ impl AppState {
)?;
let oss_client = build_oss_client(&config)?;
let auth_store = InMemoryAuthStore::default();
let sms_provider = SmsAuthProvider::new(SmsAuthConfig::new(
SmsAuthProviderKind::parse(&config.sms_auth_provider).ok_or_else(|| {
SmsProviderError::InvalidConfig("短信 provider 配置非法".to_string())
})?,
config.sms_endpoint.clone(),
config.sms_access_key_id.clone(),
config.sms_access_key_secret.clone(),
config.sms_sign_name.clone(),
config.sms_template_code.clone(),
config.sms_template_param_key.clone(),
config.sms_country_code.clone(),
config.sms_scheme_name.clone(),
config.sms_code_length,
config.sms_code_type,
config.sms_valid_time_seconds,
config.sms_interval_seconds,
config.sms_duplicate_policy,
config.sms_case_auth_policy,
config.sms_return_verify_code,
config.sms_mock_verify_code.clone(),
)?)?;
let password_entry_service = PasswordEntryService::new(auth_store.clone());
let auth_user_service = AuthUserService::new(auth_store.clone());
let phone_auth_service = PhoneAuthService::new(auth_store.clone());
let phone_auth_service = PhoneAuthService::new(auth_store.clone(), sms_provider);
let wechat_auth_state_service =
WechatAuthStateService::new(auth_store.clone(), config.wechat_state_ttl_minutes);
let wechat_auth_service = WechatAuthService::new(auth_store.clone());
@@ -331,6 +354,7 @@ impl fmt::Display for AppStateInitError {
match self {
Self::Jwt(error) => write!(f, "{error}"),
Self::RefreshCookie(error) => write!(f, "{error}"),
Self::SmsProvider(error) => write!(f, "{error}"),
Self::Oss(error) => write!(f, "{error}"),
Self::Llm(error) => write!(f, "{error}"),
}
@@ -351,6 +375,12 @@ impl From<RefreshCookieError> for AppStateInitError {
}
}
impl From<SmsProviderError> for AppStateInitError {
fn from(value: SmsProviderError) -> Self {
Self::SmsProvider(value)
}
}
impl From<OssError> for AppStateInitError {
fn from(value: OssError) -> Self {
Self::Oss(value)