docs(ai): 实施计划 Chunk 5 (Handler/State/Module + erp-server 集成)

This commit is contained in:
iven
2026-04-25 13:40:21 +08:00
parent 6158e79b9c
commit 41b17eedad

View File

@@ -1691,3 +1691,276 @@ git commit -m "feat(ai): AnalysisService 核心编排 + PromptService + UsageSer
```
---
## Chunk 5: Handler + State + Module + erp-server 集成
### Task 9: State + Module 定义
**Files:**
- Create: `crates/erp-ai/src/state.rs`
- Create: `crates/erp-ai/src/module.rs`
- [ ] **Step 1: 创建 state.rs**
```rust
// crates/erp-ai/src/state.rs
use std::sync::Arc;
use erp_core::EventBus;
use sea_orm::DatabaseConnection;
use crate::provider::AiProvider;
use crate::service::analysis::AnalysisService;
use crate::service::prompt::PromptService;
use crate::service::usage::UsageService;
#[derive(Clone)]
pub struct AiState {
pub db: DatabaseConnection,
pub event_bus: EventBus,
pub analysis: Arc<AnalysisService>,
pub prompt: Arc<PromptService>,
pub usage: Arc<UsageService>,
}
```
- [ ] **Step 2: 创建 module.rs — ErpModule + 路由注册**
```rust
// crates/erp-ai/src/module.rs
use async_trait::async_trait;
use axum::Router;
use erp_core::module::{ErpModule, ModuleContext, ModuleType, PermissionDescriptor};
use erp_core::AppResult;
use std::any::Any;
use uuid::Uuid;
use crate::handler;
pub struct AiModule;
#[async_trait]
impl ErpModule for AiModule {
fn name(&self) -> &str { "ai" }
fn module_type(&self) -> ModuleType { ModuleType::Builtin }
fn dependencies(&self) -> Vec<&str> { vec!["health"] }
async fn on_startup(&self, _ctx: &ModuleContext) -> AppResult<()> {
Ok(())
}
fn permissions(&self) -> Vec<PermissionDescriptor> {
vec![
PermissionDescriptor {
code: "ai.analysis.list".into(),
name: "查看分析历史".into(),
description: "查看 AI 分析结果历史记录".into(),
module: "ai".into(),
},
PermissionDescriptor {
code: "ai.analysis.manage".into(),
name: "请求分析".into(),
description: "发起 AI 分析请求".into(),
module: "ai".into(),
},
PermissionDescriptor {
code: "ai.prompt.list".into(),
name: "查看 Prompt".into(),
description: "查看 AI Prompt 模板列表".into(),
module: "ai".into(),
},
PermissionDescriptor {
code: "ai.prompt.manage".into(),
name: "管理 Prompt".into(),
description: "创建/编辑/激活/回滚 Prompt 模板".into(),
module: "ai".into(),
},
PermissionDescriptor {
code: "ai.usage.list".into(),
name: "查看用量".into(),
description: "查看 AI 用量统计".into(),
module: "ai".into(),
},
PermissionDescriptor {
code: "ai.provider.manage".into(),
name: "管理提供商".into(),
description: "管理 AI 提供商配置".into(),
module: "ai".into(),
},
]
}
fn as_any(&self) -> &dyn Any { self }
}
impl AiModule {
pub fn public_routes<S>() -> Router<S>
where
crate::state::AiState: axum::extract::FromRef<S>,
S: Clone + Send + Sync + 'static,
{
Router::new()
}
pub fn protected_routes<S>() -> Router<S>
where
crate::state::AiState: axum::extract::FromRef<S>,
S: Clone + Send + Sync + 'static,
{
Router::new()
.route("/ai/analyze/lab-report", axum::routing::post(handler::stream_lab_report::<S>))
.route("/ai/analyze/trends", axum::routing::post(handler::stream_trends::<S>))
.route("/ai/analyze/checkup-plan", axum::routing::post(handler::stream_checkup_plan::<S>))
.route("/ai/analyze/report-summary", axum::routing::post(handler::stream_report_summary::<S>))
.route("/ai/analysis/history", axum::routing::get(handler::list_analysis::<S>))
.route("/ai/analysis/{id}", axum::routing::get(handler::get_analysis::<S>))
}
}
```
- [ ] **Step 3: 更新 lib.rs 添加 re-export**
```rust
pub use module::AiModule;
pub use state::AiState;
```
以及 `pub mod module; pub mod state;`
- [ ] **Step 4: 验证编译 (handler 还没写,先创建 stub)**
创建 `crates/erp-ai/src/handler/mod.rs`:
```rust
// crates/erp-ai/src/handler/mod.rs — stubChunk 6 完善
use axum::response::IntoResponse;
pub async fn stream_lab_report<S>() -> impl IntoResponse { "stub" }
pub async fn stream_trends<S>() -> impl IntoResponse { "stub" }
pub async fn stream_checkup_plan<S>() -> impl IntoResponse { "stub" }
pub async fn stream_report_summary<S>() -> impl IntoResponse { "stub" }
pub async fn list_analysis<S>() -> impl IntoResponse { "stub" }
pub async fn get_analysis<S>() -> impl IntoResponse { "stub" }
```
```bash
cargo check -p erp-ai
```
- [ ] **Step 5: 提交**
```bash
git add crates/erp-ai/src/
git commit -m "feat(ai): AiState + AiModule (ErpModule impl + 权限 + 路由骨架)"
```
---
### Task 10: erp-server 集成 — Config + State + 路由注册
**Files:**
- Modify: `crates/erp-server/src/config.rs` — 添加 AiConfig
- Modify: `crates/erp-server/src/state.rs` — 添加 FromRef<AiState>
- Modify: `crates/erp-server/src/main.rs` — 注册模块 + 合并路由
- Modify: `crates/erp-server/Cargo.toml` — 添加 erp-ai 依赖
- Modify: `crates/erp-server/config/default.toml` — 添加 [ai] 段
- [ ] **Step 1: erp-server/Cargo.toml 添加 erp-ai**
```toml
erp-ai.workspace = true
```
- [ ] **Step 2: config.rs 添加 AiConfig**
```rust
// 在 AppConfig 结构体中添加:
pub ai: AiConfig,
// 新增结构体:
#[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,
}
```
- [ ] **Step 3: config/default.toml 添加 [ai] 段**
```toml
[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
```
- [ ] **Step 4: state.rs 添加 FromRef**
```rust
impl FromRef<AppState> for erp_ai::AiState {
fn from_ref(state: &AppState) -> Self {
// 从 config 构建 ClaudeProvider
let provider = erp_ai::provider::claude::ClaudeProvider::new(
state.config.ai.api_key.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 }
}
}
```
- [ ] **Step 5: main.rs 注册模块 + 路由**
```rust
// 1. 创建 AiModule
let ai_module = erp_ai::AiModule;
// 2. 注册到 registry
let registry = ModuleRegistry::new()
.register(auth_module)
// ...existing...
.register(ai_module);
// 3. 合并路由 (protected_routes 中)
.merge(erp_ai::AiModule::protected_routes())
```
- [ ] **Step 6: 验证全 workspace 编译**
```bash
cargo check --workspace
```
- [ ] **Step 7: 提交**
```bash
git add crates/erp-server/
git commit -m "feat(server): erp-ai 模块集成 — Config/State/路由注册"
```
---