feat: Iteration 1 — 审计日志IP记录、文件上传、医护端API、小程序角色切换
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

Iteration 1 六项任务全部完成:

1. 审计日志IP记录 — task_local RequestInfo 自动注入 IP/user_agent
2. 文件上传服务 — multipart 上传 + ServeDir 静态文件服务
3. 医护端后端API — 医生工作台仪表盘 + 患者标签CRUD + 会话已读
4. 小程序角色切换 — 登录后根据角色跳转医护台/患者首页
5. 小程序安全加固 — secure-storage 开发模式警告
6. 讨论记录归档 — docs/discussions/
This commit is contained in:
iven
2026-04-26 13:13:25 +08:00
parent 1326b3e504
commit a0b72b0f73
21 changed files with 679 additions and 12 deletions

View File

@@ -188,3 +188,64 @@ where
.await?;
Ok(Json(ApiResponse::ok(result)))
}
/// 标记会话消息为已读。
#[utoipa::path(
put,
path = "/consultation-sessions/{id}/read",
responses(
(status = 200, description = "标记成功"),
(status = 404, description = "会话不存在"),
),
tag = "咨询管理",
)]
pub async fn mark_session_read<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(id): Path<Uuid>,
) -> Result<Json<ApiResponse<()>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.manage")?;
let is_doctor = crate::entity::doctor_profile::Entity::find()
.filter(crate::entity::doctor_profile::Column::UserId.eq(ctx.user_id))
.filter(crate::entity::doctor_profile::Column::TenantId.eq(ctx.tenant_id))
.filter(crate::entity::doctor_profile::Column::DeletedAt.is_null())
.one(&state.db)
.await
.map_err(|e| AppError::Internal(e.to_string()))?
.is_some();
let role = if is_doctor { "doctor" } else { "patient" };
consultation_service::mark_session_read(
&state, ctx.tenant_id, id, ctx.user_id, role,
)
.await?;
Ok(Json(ApiResponse::ok(())))
}
/// 获取当前医生的仪表盘数据。
#[utoipa::path(
get,
path = "/doctor/dashboard",
responses(
(status = 200, description = "仪表盘数据"),
),
tag = "医护端",
)]
pub async fn get_doctor_dashboard<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<consultation_service::DoctorDashboard>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.list")?;
let result = consultation_service::get_doctor_dashboard(
&state, ctx.tenant_id, ctx.user_id,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}

View File

@@ -319,3 +319,74 @@ where
let tags = patient_service::list_tags(&state, ctx.tenant_id).await?;
Ok(Json(ApiResponse::ok(tags)))
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct CreateTagReq {
pub name: String,
pub color: Option<String>,
pub description: Option<String>,
}
pub async fn create_tag<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Json(req): Json<CreateTagReq>,
) -> Result<Json<ApiResponse<patient_service::TagResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.manage")?;
let result = patient_service::create_tag(
&state, ctx.tenant_id, Some(ctx.user_id),
patient_service::CreateTagReq {
name: req.name, color: req.color, description: req.description,
},
).await?;
Ok(Json(ApiResponse::ok(result)))
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct UpdateTagWithVersion {
pub name: Option<String>,
pub color: Option<String>,
pub description: Option<String>,
pub version: i32,
}
pub async fn update_tag<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(id): Path<Uuid>,
Json(req): Json<UpdateTagWithVersion>,
) -> Result<Json<ApiResponse<patient_service::TagResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.manage")?;
let result = patient_service::update_tag(
&state, ctx.tenant_id, id, Some(ctx.user_id),
patient_service::UpdateTagReq {
name: req.name, color: req.color, description: req.description, version: req.version,
},
).await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn delete_tag<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(id): Path<Uuid>,
Json(req): Json<DeleteWithVersion>,
) -> Result<Json<ApiResponse<()>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.patient.manage")?;
patient_service::delete_tag(
&state, ctx.tenant_id, id, Some(ctx.user_id), req.version,
).await?;
Ok(Json(ApiResponse::ok(())))
}