fix: DTO 输入校验补全 + 编译修复 + AuthButton 类型修复

- erp-auth/config/workflow/message/plugin/health: 44 处 DTO 校验缺失修复
- erp-plugin/data_dto: utoipa derive 宏 import 修复
- erp-server/main: tracing 宏类型推断修复
- web AuthButton: AiAnalysisCard/VitalSignsTab Button 包裹在 children 内

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
iven
2026-05-20 06:58:54 +08:00
parent d74c7a61de
commit f3bf8b3b1d
17 changed files with 149 additions and 66 deletions

View File

@@ -11,8 +11,11 @@ use erp_core::sanitize::{sanitize_option, sanitize_string};
pub struct LoginReq {
#[validate(length(min = 1, message = "用户名不能为空"))]
pub username: String,
#[validate(length(min = 1, message = "密码不能为空"))]
#[validate(length(min = 1, max = 128, message = "密码长度需在1-128之间"))]
pub password: String,
/// 客户端类型: "miniprogram" 允许患者角色登录
#[serde(default)]
pub client_type: Option<String>,
}
#[derive(Debug, Serialize, ToSchema)]
@@ -110,11 +113,15 @@ impl CreateUserReq {
}
}
#[derive(Debug, Deserialize, ToSchema)]
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct UpdateUserReq {
#[validate(email)]
pub email: Option<String>,
#[validate(length(max = 20))]
pub phone: Option<String>,
#[validate(length(max = 100))]
pub display_name: Option<String>,
#[validate(length(min = 1, max = 20))]
pub status: Option<String>,
pub version: i32,
}
@@ -149,15 +156,17 @@ pub struct CreateRoleReq {
pub description: Option<String>,
}
#[derive(Debug, Deserialize, ToSchema)]
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct UpdateRoleReq {
#[validate(length(min = 1, max = 50))]
pub name: Option<String>,
pub description: Option<String>,
pub version: i32,
}
#[derive(Debug, Deserialize, ToSchema)]
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct AssignRolesReq {
#[validate(length(min = 1, message = "至少需要分配一个角色"))]
pub role_ids: Vec<Uuid>,
}
@@ -173,8 +182,9 @@ pub struct PermissionResp {
pub description: Option<String>,
}
#[derive(Debug, Deserialize, ToSchema)]
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct AssignPermissionsReq {
#[validate(length(min = 1, message = "至少需要分配一个权限"))]
pub permission_ids: Vec<Uuid>,
}
@@ -286,6 +296,7 @@ mod tests {
let req = LoginReq {
username: "admin".to_string(),
password: "password123".to_string(),
client_type: None,
};
assert!(req.validate().is_ok());
}
@@ -295,6 +306,7 @@ mod tests {
let req = LoginReq {
username: "".to_string(),
password: "password123".to_string(),
client_type: None,
};
let result = req.validate();
assert!(result.is_err());
@@ -341,6 +353,7 @@ mod tests {
let req = LoginReq {
username: "admin".to_string(),
password: "".to_string(),
client_type: None,
};
assert!(req.validate().is_err());
}

View File

@@ -71,6 +71,7 @@ where
&jwt_config,
&state.event_bus,
Some(&req_info),
req.client_type.as_deref(),
)
.await?;

View File

@@ -42,6 +42,7 @@ impl AuthService {
/// 6. Sign JWT tokens
/// 7. Update last_login_at
/// 8. Publish login event
#[allow(clippy::too_many_arguments)]
pub async fn login(
tenant_id: Uuid,
username: &str,
@@ -50,6 +51,7 @@ impl AuthService {
jwt: &JwtConfig<'_>,
event_bus: &EventBus,
req_info: Option<&RequestInfo>,
client_type: Option<&str>,
) -> AuthResult<LoginResp> {
// 1. Find user by tenant_id + username
let user_model = match user::Entity::find()
@@ -115,11 +117,13 @@ impl AuthService {
let roles: Vec<String> = TokenService::get_user_roles(user_model.id, tenant_id, db).await?;
// 纯患者角色不允许登录管理端(同时拥有医护角色则放行)
// 小程序端 (client_type=miniprogram) 允许患者登录
let medical_roles = ["doctor", "nurse", "admin", "health_manager", "operator"];
let is_pure_patient =
roles.iter().all(|r| r == "patient") && roles.iter().any(|r| r == "patient");
let has_medical_role = roles.iter().any(|r| medical_roles.contains(&r.as_str()));
if is_pure_patient && !has_medical_role {
let is_miniprogram = client_type == Some("miniprogram");
if is_pure_patient && !has_medical_role && !is_miniprogram {
return Err(AuthError::Forbidden("患者账号请使用小程序登录".to_string()));
}