docs(ai): 实施计划 Chunk 5 (Handler/State/Module + erp-server 集成)
This commit is contained in:
@@ -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 — stub,Chunk 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/路由注册"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user