P0-1: 微信模板 ID 从硬编码空字符串改为环境变量注入 - wechat-templates.ts 读取 process.env.TARO_APP_WX_TEMPLATE_* - defineConstants 新增 5 个模板 ID 编译时注入 P0-2: 积分商城 Tab 空白降级 - mall/index.tsx 在 currentPatient 为 null 时先调用 loadPatients() - 仍无档案才显示空状态引导,而非直接阻断 P0-3: 消除 erp-points 重复路由冲突 - 从 erp-server 移除 erp-points 模块注册和路由 merge - 积分功能统一由 erp-health /health/points/* 提供 - erp-points crate 保留但不参与编译 P0-4: 文章列表按角色过滤防止草稿泄露 - list_articles handler: 非管理权限强制 status=published - get_article service: 新增 is_admin 参数控制状态过滤
136 lines
4.4 KiB
Rust
136 lines
4.4 KiB
Rust
use axum::extract::FromRef;
|
||
use sea_orm::DatabaseConnection;
|
||
|
||
use crate::config::AppConfig;
|
||
use erp_core::events::EventBus;
|
||
use erp_core::module::ModuleRegistry;
|
||
|
||
/// Axum shared application state.
|
||
/// All handlers access database connections, configuration, etc. through `State<AppState>`.
|
||
#[derive(Clone)]
|
||
pub struct AppState {
|
||
pub db: DatabaseConnection,
|
||
pub config: AppConfig,
|
||
pub event_bus: EventBus,
|
||
pub module_registry: ModuleRegistry,
|
||
pub redis: redis::Client,
|
||
/// 实际的默认租户 ID,从数据库种子数据中获取。
|
||
pub default_tenant_id: uuid::Uuid,
|
||
/// 插件引擎
|
||
pub plugin_engine: erp_plugin::engine::PluginEngine,
|
||
/// 插件实体缓存
|
||
pub plugin_entity_cache: moka::sync::Cache<String, erp_plugin::state::EntityInfo>,
|
||
/// AI 模块状态(启动时构建,避免每次请求重建)
|
||
pub ai_state: erp_ai::AiState,
|
||
/// PII 加密服务(KEK + DEK 管理)
|
||
pub pii_crypto: erp_core::crypto::PiiCrypto,
|
||
}
|
||
|
||
/// Allow handlers to extract `DatabaseConnection` directly from `State<AppState>`.
|
||
impl FromRef<AppState> for DatabaseConnection {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
state.db.clone()
|
||
}
|
||
}
|
||
|
||
/// Allow handlers to extract `EventBus` directly from `State<AppState>`.
|
||
impl FromRef<AppState> for EventBus {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
state.event_bus.clone()
|
||
}
|
||
}
|
||
|
||
/// Allow erp-auth handlers to extract their required state without depending on erp-server.
|
||
///
|
||
/// This bridges the gap: erp-auth defines `AuthState` with the fields it needs,
|
||
/// and erp-server fills them from `AppState`.
|
||
impl FromRef<AppState> for erp_auth::AuthState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
use erp_auth::auth_state::parse_ttl;
|
||
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
jwt_secret: state.config.jwt.secret.clone(),
|
||
access_ttl_secs: parse_ttl(&state.config.jwt.access_token_ttl),
|
||
refresh_ttl_secs: parse_ttl(&state.config.jwt.refresh_token_ttl),
|
||
default_tenant_id: state.default_tenant_id,
|
||
wechat_appid: state.config.wechat.appid.clone(),
|
||
wechat_secret: state.config.wechat.secret.clone(),
|
||
wechat_dev_mode: state.config.wechat.dev_mode,
|
||
redis: Some(state.redis.clone()),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Allow erp-config handlers to extract their required state without depending on erp-server.
|
||
impl FromRef<AppState> for erp_config::ConfigState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Allow erp-workflow handlers to extract their required state without depending on erp-server.
|
||
impl FromRef<AppState> for erp_workflow::WorkflowState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Allow erp-message handlers to extract their required state without depending on erp-server.
|
||
impl FromRef<AppState> for erp_message::MessageState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Allow erp-plugin handlers to extract their required state.
|
||
impl FromRef<AppState> for erp_plugin::state::PluginState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
engine: state.plugin_engine.clone(),
|
||
entity_cache: state.plugin_entity_cache.clone(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Allow erp-health handlers to extract their required state.
|
||
impl FromRef<AppState> for erp_health::HealthState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
crypto: state.pii_crypto.clone(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Allow erp-ai handlers to extract their required state.
|
||
impl FromRef<AppState> for erp_ai::AiState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
state.ai_state.clone()
|
||
}
|
||
}
|
||
|
||
/// Allow erp-dialysis handlers to extract their required state.
|
||
impl FromRef<AppState> for erp_dialysis::DialysisState {
|
||
fn from_ref(state: &AppState) -> Self {
|
||
Self {
|
||
db: state.db.clone(),
|
||
event_bus: state.event_bus.clone(),
|
||
crypto: state.pii_crypto.clone(),
|
||
}
|
||
}
|
||
}
|