fix: 修复测试发现的 7 个问题 + 全 workspace clippy 清零
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

功能修复:
1. 患者创建空名称验证:后端添加 name.trim().is_empty() 检查
2. 仪表盘统计容错:单个查询失败返回零值而非 500
3. FHIR 路由修复:从 /fhir 移到 /api/v1/fhir 保持一致
4. 冻结模块后端中间件:新增 frozen_module_middleware 拦截冻结路径
5. 积分端点权限码:health.health-data.list → health.points.list
6. 角色权限迁移:护士补充 devices.list,运营补充 points.list/manage
7. 测试结果文档:R01-R05 角色测试 + T00/T10 结果归档

Clippy 全 workspace 清零(14→0 errors):
- erp-core: 修复 empty doc line、collapsible if、redundant closure 等 9 处
- erp-health: 修复 too_many_arguments、unused var、unnecessary parens 等 58 处
- erp-ai: 修复 dead_code、unused import 等 11 处
- erp-plugin: 修复 too_many_arguments、wildcard pattern 等 11 处
- erp-server-migration: 修复 enum_variant_names 5 处
- erp-auth/config/workflow/message: 各 1-3 处

工程改进:
- lint-staged 配置迁移到 .lintstagedrc.js(函数式避免文件列表传给 clippy)
- cargo fmt 统一格式化
This commit is contained in:
iven
2026-05-07 23:43:14 +08:00
parent 786f57c151
commit 6d5a711d2c
323 changed files with 15662 additions and 6603 deletions

View File

@@ -64,11 +64,10 @@ impl AuthService {
None => {
// 审计:用户不存在(登录失败)
audit_service::record(
AuditLog::new(tenant_id, None, "user.login_failed", "user")
.with_request_info(
req_info.as_ref().and_then(|r| r.ip.clone()),
req_info.as_ref().and_then(|r| r.user_agent.clone()),
),
AuditLog::new(tenant_id, None, "user.login_failed", "user").with_request_info(
req_info.as_ref().and_then(|r| r.ip.clone()),
req_info.as_ref().and_then(|r| r.user_agent.clone()),
),
db,
)
.await;

View File

@@ -317,13 +317,7 @@ const DEFAULT_PERMISSIONS: &[(&str, &str, &str, &str, &str)] = &[
"admin",
"管理插件全生命周期",
),
(
"plugin.list",
"查看插件",
"plugin",
"list",
"查看插件列表",
),
("plugin.list", "查看插件", "plugin", "list", "查看插件列表"),
];
/// Indices of read-only (list/read) permissions within DEFAULT_PERMISSIONS.

View File

@@ -153,7 +153,11 @@ impl TokenService {
/// Revoke a specific refresh token by database ID.
/// Verifies that the token belongs to the specified user for security.
pub async fn revoke_token(token_id: Uuid, user_id: Uuid, db: &DatabaseConnection) -> AuthResult<()> {
pub async fn revoke_token(
token_id: Uuid,
user_id: Uuid,
db: &DatabaseConnection,
) -> AuthResult<()> {
let token_row = user_token::Entity::find_by_id(token_id)
.filter(user_token::Column::UserId.eq(user_id))
.one(db)

View File

@@ -406,8 +406,7 @@ impl UserService {
.unwrap_or_default()
};
let role_map: HashMap<Uuid, &role::Model> =
roles.iter().map(|r| (r.id, r)).collect();
let role_map: HashMap<Uuid, &role::Model> = roles.iter().map(|r| (r.id, r)).collect();
// 3. 按 user_id 分组
let mut result: HashMap<Uuid, Vec<RoleResp>> = HashMap::new();

View File

@@ -1,10 +1,8 @@
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
use aes::cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7};
use base64::Engine;
use chrono::Utc;
use cbc::Decryptor;
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set,
};
use chrono::Utc;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::LazyLock;
@@ -59,9 +57,13 @@ impl WechatService {
code = %code,
"fetch_session 开始"
);
let session =
fetch_session(&state.wechat_appid, &state.wechat_secret, code, state.wechat_dev_mode)
.await?;
let session = fetch_session(
&state.wechat_appid,
&state.wechat_secret,
code,
state.wechat_dev_mode,
)
.await?;
let openid = session
.openid
@@ -69,18 +71,18 @@ impl WechatService {
.ok_or_else(|| AuthError::Validation("微信登录失败:未获取到 openid".to_string()))?;
// 缓存 session_keyRedis 优先,内存降级)
if let Some(sk) = &session.session_key {
if let Err(e) = Self::store_session_key_redis(&state.redis, &openid, sk).await {
tracing::warn!(openid = %openid, error = %e, "Redis session_key 存储失败,降级内存");
let mut cache = MEMORY_FALLBACK.lock().await;
cache.insert(
openid.clone(),
SessionEntry {
session_key: sk.clone(),
created_at: Instant::now(),
},
);
}
if let Some(sk) = &session.session_key
&& let Err(e) = Self::store_session_key_redis(&state.redis, &openid, sk).await
{
tracing::warn!(openid = %openid, error = %e, "Redis session_key 存储失败,降级内存");
let mut cache = MEMORY_FALLBACK.lock().await;
cache.insert(
openid.clone(),
SessionEntry {
session_key: sk.clone(),
created_at: Instant::now(),
},
);
}
let existing = wechat_user::Entity::find()
@@ -141,8 +143,7 @@ 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).await?;
let now = Utc::now();
let wu = wechat_user::ActiveModel {
@@ -248,22 +249,19 @@ impl WechatService {
Ok(())
}
async fn get_session_key(
redis: &Option<redis::Client>,
openid: &str,
) -> AuthResult<String> {
async fn get_session_key(redis: &Option<redis::Client>, openid: &str) -> AuthResult<String> {
// 1. 尝试 Redis
if let Some(client) = redis {
if let Ok(mut conn) = client.get_multiplexed_async_connection().await {
let key = format!("{}{}", REDIS_KEY_PREFIX, openid);
let result: Option<String> = redis::cmd("GETDEL")
.arg(&key)
.query_async::<Option<String>>(&mut conn)
.await
.unwrap_or(None);
if let Some(sk) = result {
return Ok(sk);
}
if let Some(client) = redis
&& let Ok(mut conn) = client.get_multiplexed_async_connection().await
{
let key = format!("{}{}", REDIS_KEY_PREFIX, openid);
let result: Option<String> = redis::cmd("GETDEL")
.arg(&key)
.query_async::<Option<String>>(&mut conn)
.await
.unwrap_or(None);
if let Some(sk) = result {
return Ok(sk);
}
}
@@ -285,11 +283,7 @@ impl WechatService {
}
/// AES-128-CBC 解密微信手机号
fn decrypt_phone_number(
session_key: &str,
encrypted_data: &str,
iv: &str,
) -> AuthResult<String> {
fn decrypt_phone_number(session_key: &str, encrypted_data: &str, iv: &str) -> AuthResult<String> {
let engine = base64::engine::general_purpose::STANDARD;
let key_bytes = engine
@@ -303,9 +297,7 @@ fn decrypt_phone_number(
.map_err(|e| AuthError::Validation(format!("encrypted_data base64 解码失败: {}", e)))?;
if key_bytes.len() != 16 {
return Err(AuthError::Validation(
"session_key 长度不正确".to_string(),
));
return Err(AuthError::Validation("session_key 长度不正确".to_string()));
}
if iv_bytes.len() != 16 {
return Err(AuthError::Validation("iv 长度不正确".to_string()));
@@ -319,8 +311,8 @@ fn decrypt_phone_number(
.decrypt_padded_mut::<Pkcs7>(&mut buf)
.map_err(|e| AuthError::Validation(format!("AES 解密失败: {}", e)))?;
let plaintext =
String::from_utf8(decrypted.to_vec()).map_err(|_| AuthError::Validation("解密结果非 UTF-8".to_string()))?;
let plaintext = String::from_utf8(decrypted.to_vec())
.map_err(|_| AuthError::Validation("解密结果非 UTF-8".to_string()))?;
// 微信返回的 JSON 包含 watermark 等字段,提取 phone_number
let info: serde_json::Value = serde_json::from_str(&plaintext)
@@ -358,14 +350,9 @@ async fn build_login_resp(
jwt.secret,
jwt.access_ttl_secs,
)?;
let (refresh_token, _) = TokenService::sign_refresh_token(
user_id,
tenant_id,
db,
jwt.secret,
jwt.refresh_ttl_secs,
)
.await?;
let (refresh_token, _) =
TokenService::sign_refresh_token(user_id, tenant_id, db, jwt.secret, jwt.refresh_ttl_secs)
.await?;
let role_resps = AuthService::get_user_role_resps(user_id, tenant_id, db).await?;
@@ -424,15 +411,15 @@ async fn fetch_session(
.await
.map_err(|e| AuthError::Validation(format!("微信 API 响应解析失败: {}", e)))?;
if let Some(errcode) = session.errcode {
if errcode != 0 {
let msg = session.errmsg.clone().unwrap_or_default();
tracing::error!(errcode, errmsg = %msg, "微信 jscode2session 返回错误");
return Err(AuthError::Validation(format!(
"微信登录失败 ({}): {}",
errcode, msg
)));
}
if let Some(errcode) = session.errcode
&& errcode != 0
{
let msg = session.errmsg.clone().unwrap_or_default();
tracing::error!(errcode, errmsg = %msg, "微信 jscode2session 返回错误");
return Err(AuthError::Validation(format!(
"微信登录失败 ({}): {}",
errcode, msg
)));
}
tracing::info!(