feat(server): erp-ai 模块集成 — Config/State/路由注册
- 新增 AiConfig 到 AppConfig - 新增 FromRef<AppState> for AiState - 注册 AiModule 到 ModuleRegistry - 合并 AI protected routes - 修复 sync_module_permissions 只同步 health.% 的 bug Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ erp-workflow.workspace = true
|
||||
erp-message.workspace = true
|
||||
erp-plugin.workspace = true
|
||||
erp-health.workspace = true
|
||||
erp-ai.workspace = true
|
||||
anyhow.workspace = true
|
||||
uuid.workspace = true
|
||||
chrono.workspace = true
|
||||
|
||||
@@ -32,3 +32,13 @@ secret = "__MUST_SET_VIA_ENV__"
|
||||
[health]
|
||||
aes_key = "__MUST_SET_VIA_ENV__"
|
||||
hmac_key = "__MUST_SET_VIA_ENV__"
|
||||
|
||||
[ai]
|
||||
default_provider = "claude"
|
||||
api_key = ""
|
||||
base_url = "https://api.anthropic.com"
|
||||
model = "claude-sonnet-4-6"
|
||||
max_tokens = 2048
|
||||
temperature = 0.3
|
||||
cache_ttl_seconds = 604800
|
||||
rate_limit_patient_daily = 10
|
||||
|
||||
@@ -11,6 +11,7 @@ pub struct AppConfig {
|
||||
pub cors: CorsConfig,
|
||||
pub wechat: WechatConfig,
|
||||
pub health: HealthConfig,
|
||||
pub ai: AiConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
@@ -69,6 +70,18 @@ pub struct HealthConfig {
|
||||
pub hmac_key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AiConfig {
|
||||
pub default_provider: String,
|
||||
pub api_key: String,
|
||||
pub base_url: Option<String>,
|
||||
pub model: String,
|
||||
pub max_tokens: u32,
|
||||
pub temperature: f32,
|
||||
pub cache_ttl_seconds: u64,
|
||||
pub rate_limit_patient_daily: u32,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn load() -> anyhow::Result<Self> {
|
||||
let config = config::Config::builder()
|
||||
|
||||
@@ -343,13 +343,22 @@ async fn main() -> anyhow::Result<()> {
|
||||
"Health module initialized"
|
||||
);
|
||||
|
||||
// Initialize AI module
|
||||
let ai_module = erp_ai::AiModule;
|
||||
tracing::info!(
|
||||
module = ai_module.name(),
|
||||
version = ai_module.version(),
|
||||
"AI module initialized"
|
||||
);
|
||||
|
||||
// Initialize module registry and register modules
|
||||
let registry = ModuleRegistry::new()
|
||||
.register(auth_module)
|
||||
.register(config_module)
|
||||
.register(workflow_module)
|
||||
.register(message_module)
|
||||
.register(health_module);
|
||||
.register(health_module)
|
||||
.register(ai_module);
|
||||
tracing::info!(
|
||||
module_count = registry.modules().len(),
|
||||
"Modules registered"
|
||||
@@ -464,6 +473,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.merge(erp_message::MessageModule::protected_routes())
|
||||
.merge(erp_plugin::module::PluginModule::protected_routes())
|
||||
.merge(erp_health::HealthModule::protected_routes())
|
||||
.merge(erp_ai::AiModule::protected_routes())
|
||||
.merge(handlers::audit_log::audit_log_router())
|
||||
.layer(axum::middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
@@ -627,7 +637,7 @@ async fn sync_module_permissions(
|
||||
SELECT r.id, p.id, p.tenant_id, 'all', NOW(), NOW(), $1, $1, NULL, 1
|
||||
FROM permissions p
|
||||
JOIN roles r ON r.code = 'admin' AND r.tenant_id = p.tenant_id AND r.deleted_at IS NULL
|
||||
WHERE p.tenant_id = $2 AND p.code LIKE 'health.%'
|
||||
WHERE p.tenant_id = $2
|
||||
ON CONFLICT DO NOTHING"#,
|
||||
[system_user_id.into(), tenant_id.into()],
|
||||
)).await?;
|
||||
|
||||
@@ -115,3 +115,31 @@ impl FromRef<AppState> for erp_health::HealthState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow erp-ai handlers to extract their required state.
|
||||
impl FromRef<AppState> for erp_ai::AiState {
|
||||
fn from_ref(state: &AppState) -> Self {
|
||||
let mut provider = erp_ai::provider::claude::ClaudeProvider::new(
|
||||
state.config.ai.api_key.clone(),
|
||||
);
|
||||
if let Some(ref base_url) = state.config.ai.base_url {
|
||||
provider = provider.with_base_url(base_url.clone());
|
||||
}
|
||||
let db = state.db.clone();
|
||||
let event_bus = state.event_bus.clone();
|
||||
|
||||
let analysis = std::sync::Arc::new(
|
||||
erp_ai::service::analysis::AnalysisService::new(Box::new(provider), db.clone()),
|
||||
);
|
||||
let prompt = std::sync::Arc::new(erp_ai::service::prompt::PromptService::new(db.clone()));
|
||||
let usage = std::sync::Arc::new(erp_ai::service::usage::UsageService::new(db.clone()));
|
||||
|
||||
Self {
|
||||
db,
|
||||
event_bus,
|
||||
analysis,
|
||||
prompt,
|
||||
usage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user