feat(auth): 小程序登录采集微信昵称

This commit is contained in:
2026-06-06 23:59:15 +08:00
parent caa65bf15f
commit b74440373f
16 changed files with 432 additions and 58 deletions

View File

@@ -65,12 +65,14 @@ pub struct BindWechatPhoneInput {
pub user_id: String,
pub phone_number: String,
pub verify_code: String,
pub wechat_display_name: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BindWechatVerifiedPhoneInput {
pub user_id: String,
pub phone_number: String,
pub wechat_display_name: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]

View File

@@ -111,11 +111,7 @@ fn hydrate_private_auth_fields(
.find(|identity| identity.user_id == hydrated.user.id);
if hydrated.user.wechat_display_name.is_none() {
hydrated.user.wechat_display_name = hydrated_wechat_identity
.and_then(|identity| identity.display_name.clone())
.or_else(|| {
(hydrated.user.login_method == AuthLoginMethod::Wechat)
.then(|| hydrated.user.display_name.clone())
});
.and_then(|identity| normalize_optional_string(identity.display_name.clone()));
}
if hydrated.user.wechat_account.is_none() {
hydrated.user.wechat_account =
@@ -655,9 +651,11 @@ impl PhoneAuthService {
return Err(PhoneAuthError::UserStateMismatch);
}
let (merged_user, activated_new_user) = self
.store
.bind_wechat_phone_to_user(&input.user_id, normalized_phone)?;
let (merged_user, activated_new_user) = self.store.bind_wechat_phone_to_user(
&input.user_id,
normalized_phone,
input.wechat_display_name,
)?;
Ok(BindWechatPhoneResult {
user: merged_user,
@@ -711,9 +709,11 @@ impl PhoneAuthService {
return Err(PhoneAuthError::UserStateMismatch);
}
let (merged_user, activated_new_user) = self
.store
.bind_wechat_phone_to_user(&input.user_id, normalized_phone)?;
let (merged_user, activated_new_user) = self.store.bind_wechat_phone_to_user(
&input.user_id,
normalized_phone,
input.wechat_display_name,
)?;
Ok(BindWechatPhoneResult {
user: merged_user,
@@ -1365,8 +1365,7 @@ impl InMemoryAuthStore {
.filter(|value| !value.is_empty())
.unwrap_or("微信旅人")
.to_string();
let wechat_display_name = normalize_optional_string(profile.display_name.clone())
.or_else(|| Some(display_name.clone()));
let wechat_display_name = normalize_optional_string(profile.display_name.clone());
let username = build_wechat_username(&display_name, &profile.provider_uid);
let provider_uid = normalize_required_string(&profile.provider_uid).unwrap_or_default();
let user = AuthUser {
@@ -1758,11 +1757,13 @@ impl InMemoryAuthStore {
&self,
pending_user_id: &str,
phone_number: PhoneNumberSnapshot,
wechat_display_name: Option<String>,
) -> Result<(AuthUser, bool), PhoneAuthError> {
let mut state = self
.inner
.lock()
.map_err(|_| PhoneAuthError::Store("用户仓储锁已中毒".to_string()))?;
let submitted_wechat_display_name = normalize_optional_string(wechat_display_name);
let existing_phone_user_id =
Self::resolve_phone_user_locked(&mut state, &phone_number.e164)
@@ -1777,20 +1778,24 @@ impl InMemoryAuthStore {
.cloned()
.ok_or(PhoneAuthError::UserStateMismatch)?;
let pending_wechat_account = pending_wechat_identity.provider_uid.clone();
let pending_wechat_display_name = pending_wechat_identity.display_name.clone();
let pending_username = state
let pending_user = state
.users_by_username
.values()
.find(|stored| stored.user.id == pending_user_id)
.map(|stored| stored.user.username.clone())
.cloned()
.ok_or(PhoneAuthError::UserNotFound)?;
let pending_username = pending_user.user.username.clone();
let pending_wechat_display_name = submitted_wechat_display_name
.clone()
.or_else(|| normalize_optional_string(pending_wechat_identity.display_name.clone()))
.or_else(|| normalize_optional_string(pending_user.user.wechat_display_name));
state.users_by_username.remove(&pending_username);
state.wechat_identity_by_provider_uid.insert(
pending_wechat_identity.provider_uid.clone(),
StoredWechatIdentity {
user_id: target_user_id.clone(),
display_name: pending_wechat_display_name.clone(),
..pending_wechat_identity.clone()
},
);
@@ -1825,11 +1830,31 @@ impl InMemoryAuthStore {
.values()
.find(|identity| identity.user_id == pending_user_id)
.map(|identity| identity.provider_uid.clone());
let bound_wechat_display_name = state
.wechat_identity_by_provider_uid
.values()
.find(|identity| identity.user_id == pending_user_id)
.and_then(|identity| identity.display_name.clone());
let bound_wechat_display_name = submitted_wechat_display_name.clone().or_else(|| {
state
.wechat_identity_by_provider_uid
.values()
.find(|identity| identity.user_id == pending_user_id)
.and_then(|identity| normalize_optional_string(identity.display_name.clone()))
.or_else(|| {
state
.users_by_username
.values()
.find(|stored| stored.user.id == pending_user_id)
.and_then(|stored| {
normalize_optional_string(stored.user.wechat_display_name.clone())
})
})
});
if let Some(display_name) = bound_wechat_display_name.clone()
&& let Some(identity) = state
.wechat_identity_by_provider_uid
.values_mut()
.find(|identity| identity.user_id == pending_user_id)
{
identity.display_name = Some(display_name);
}
let stored_user = state
.users_by_username
@@ -3584,6 +3609,7 @@ mod tests {
user_id: wechat_user.id.clone(),
phone_number: "13800138000".to_string(),
verify_code: "123456".to_string(),
wechat_display_name: None,
},
now + Duration::seconds(3),
)
@@ -3619,4 +3645,97 @@ mod tests {
Some("已归并微信用户")
);
}
#[tokio::test]
async fn bind_wechat_phone_keeps_account_marker_when_identity_has_no_display_name() {
let store = build_store();
let phone_service = build_phone_service(store.clone());
let wechat_service = WechatAuthService::new(store.clone());
let now = OffsetDateTime::now_utc();
phone_service
.send_code(
SendPhoneCodeInput {
phone_number: "13800138031".to_string(),
scene: PhoneAuthScene::Login,
},
now,
)
.await
.expect("phone login code should send");
let phone_user = phone_service
.login(
PhoneLoginInput {
phone_number: "13800138031".to_string(),
verify_code: "123456".to_string(),
},
now + Duration::seconds(1),
)
.await
.expect("phone login should succeed")
.user;
let wechat_user = wechat_service
.resolve_login(ResolveWechatLoginInput {
profile: WechatIdentityProfile {
provider_uid: "wx-openid-mini-bind".to_string(),
provider_union_id: Some("wx-union-mini-bind".to_string()),
display_name: None,
avatar_url: None,
session_key: Some("mini-session-key".to_string()),
},
})
.await
.expect("mini program wechat login should succeed")
.user;
assert_eq!(wechat_user.wechat_display_name, None);
assert_eq!(
wechat_user.wechat_account.as_deref(),
Some("wx-openid-mini-bind")
);
assert_ne!(wechat_user.id, phone_user.id);
phone_service
.send_code(
SendPhoneCodeInput {
phone_number: "13800138031".to_string(),
scene: PhoneAuthScene::BindPhone,
},
now + Duration::seconds(2),
)
.await
.expect("bind phone code should send");
let merged = phone_service
.bind_wechat_phone(
BindWechatPhoneInput {
user_id: wechat_user.id.clone(),
phone_number: "13800138031".to_string(),
verify_code: "123456".to_string(),
wechat_display_name: None,
},
now + Duration::seconds(3),
)
.await
.expect("bind phone should succeed");
assert_eq!(merged.user.id, phone_user.id);
assert!(merged.user.wechat_bound);
assert_eq!(merged.user.wechat_display_name, None);
assert_eq!(
merged.user.wechat_account.as_deref(),
Some("wx-openid-mini-bind")
);
let restored_user = build_password_service(store)
.get_user_by_id(&phone_user.id)
.expect("user lookup should succeed")
.expect("merged user should exist")
.user;
assert_eq!(restored_user.wechat_display_name, None);
assert_eq!(
restored_user.wechat_account.as_deref(),
Some("wx-openid-mini-bind")
);
}
}