feat(auth,mp): 患者登录流程优化 — 智能合并 + 角色冻结 + 页面冻结
- 智能合并:微信注册时用手机号盲索引匹配已有患者档案,避免重复建 档(AuthState 添加 PiiCrypto + ensure_patient_record 增加盲索引查询) - 角色冻结:小程序仅允许患者角色登录,医护角色被拦截 (auth_service.rs 添加反向拦截 + 登录页移除 credential login 表单) - 页面冻结:10 个非核心页面替换为 FrozenPage 占位组件(用药/知情同意 /透析/家属/诊断/事件),移除 profile 导航入口,移除医生端预加载 - 医生端代码保留,仅隐藏入口,后续可零成本恢复 讨论记录:docs/discussions/2026-05-23-account-registration-login-flow.md
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use erp_core::crypto::PiiCrypto;
|
||||
use erp_core::events::EventBus;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use uuid::Uuid;
|
||||
@@ -25,6 +26,7 @@ pub struct AuthState {
|
||||
pub wechat_secret: String,
|
||||
pub wechat_dev_mode: bool,
|
||||
pub redis: Option<redis::Client>,
|
||||
pub crypto: PiiCrypto,
|
||||
}
|
||||
|
||||
/// Parse a human-readable TTL string (e.g. "15m", "7d", "1h", "900s") into seconds.
|
||||
|
||||
@@ -127,6 +127,12 @@ impl AuthService {
|
||||
return Err(AuthError::Forbidden("患者账号请使用小程序登录".to_string()));
|
||||
}
|
||||
|
||||
// 小程序端仅允许患者角色登录,医护角色请使用管理端
|
||||
let has_patient_role = roles.iter().any(|r| r == "patient");
|
||||
if is_miniprogram && !has_patient_role {
|
||||
return Err(AuthError::Forbidden("医护账号请使用管理端登录".to_string()));
|
||||
}
|
||||
|
||||
let permissions = TokenService::get_user_permissions(user_model.id, tenant_id, db).await?;
|
||||
|
||||
// 6. Sign tokens
|
||||
|
||||
@@ -151,7 +151,8 @@ impl WechatService {
|
||||
return Err(AuthError::Validation("该微信已绑定账号".to_string()));
|
||||
}
|
||||
|
||||
let user_id = Self::find_or_create_user_by_phone(&state.db, tenant_id, &phone).await?;
|
||||
let user_id =
|
||||
Self::find_or_create_user_by_phone(&state.db, tenant_id, &phone, &state.crypto).await?;
|
||||
|
||||
let now = Utc::now();
|
||||
let wu = wechat_user::ActiveModel {
|
||||
@@ -189,6 +190,7 @@ impl WechatService {
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
tenant_id: Uuid,
|
||||
phone: &str,
|
||||
crypto: &erp_core::crypto::PiiCrypto,
|
||||
) -> AuthResult<Uuid> {
|
||||
use crate::entity::user;
|
||||
|
||||
@@ -234,7 +236,7 @@ impl WechatService {
|
||||
Self::assign_patient_role(db, tenant_id, user_id).await?;
|
||||
|
||||
// 自动创建或关联 patient 记录
|
||||
Self::ensure_patient_record(db, tenant_id, user_id, phone).await?;
|
||||
Self::ensure_patient_record(db, tenant_id, user_id, phone, crypto).await?;
|
||||
|
||||
Ok(user_id)
|
||||
}
|
||||
@@ -282,12 +284,14 @@ impl WechatService {
|
||||
/// 自动创建或关联 patient 记录。
|
||||
///
|
||||
/// 1. 如果已有 user_id 关联的 patient → 跳过
|
||||
/// 2. 否则 → 创建新的 patient 记录
|
||||
/// 2. 如果手机号盲索引匹配到未绑定的已有患者 → 合并(关联 user_id)
|
||||
/// 3. 否则 → 创建新的 patient 记录
|
||||
async fn ensure_patient_record(
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
phone: &str,
|
||||
crypto: &erp_core::crypto::PiiCrypto,
|
||||
) -> AuthResult<()> {
|
||||
use sea_orm::{ConnectionTrait, Statement};
|
||||
|
||||
@@ -306,6 +310,40 @@ impl WechatService {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 智能合并:用手机号盲索引查找未绑定的已有患者(管理员/护士建档)
|
||||
let phone_hash = erp_core::crypto::hmac_hash(crypto.hmac_key(), phone);
|
||||
let blind_match: Option<sea_orm::QueryResult> = db
|
||||
.query_one(Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
r#"SELECT bi.entity_id AS patient_id
|
||||
FROM blind_index bi
|
||||
JOIN patient p ON p.id = bi.entity_id AND p.tenant_id = $2 AND p.deleted_at IS NULL
|
||||
WHERE bi.entity_type = 'patient'
|
||||
AND bi.field_name = 'emergency_contact_phone'
|
||||
AND bi.blind_hash = $1
|
||||
AND bi.tenant_id = $2
|
||||
AND p.user_id IS NULL
|
||||
LIMIT 1"#,
|
||||
[phone_hash.as_str().into(), tenant_id.into()],
|
||||
))
|
||||
.await
|
||||
.map_err(|e| AuthError::DbError(e.to_string()))?;
|
||||
|
||||
if let Some(row) = blind_match {
|
||||
let patient_id: Uuid = row
|
||||
.try_get("", "patient_id")
|
||||
.map_err(|e| AuthError::DbError(format!("blind_index parse: {}", e)))?;
|
||||
db.execute(Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"UPDATE patient SET user_id = $1, updated_at = NOW(), updated_by = $1 WHERE id = $2 AND user_id IS NULL",
|
||||
[user_id.into(), patient_id.into()],
|
||||
))
|
||||
.await
|
||||
.map_err(|e| AuthError::DbError(e.to_string()))?;
|
||||
tracing::info!(%user_id, %patient_id, "手机号盲索引合并 patient");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let suffix = &phone[phone.len().saturating_sub(4)..];
|
||||
let patient_id = Uuid::now_v7();
|
||||
let now = Utc::now();
|
||||
|
||||
Reference in New Issue
Block a user