merge: 合并热重载功能分支

合并 feature-ms-v0.0.1-SNAPSHOOT-20260319 的热重载实现:
- 使用 notify-debouncer-mini 监听 mocks 目录变化
- AppState 使用 RwLock<MockRouter> 支持并发读写
- 200ms 防抖避免编辑器保存时的多次触发

保留当前分支的功能:
- 文件上传功能 (upload.rs)
- 请求体 body 匹配支持
- 完整的测试覆盖 (43个测试)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 22:20:20 +08:00
5 changed files with 163 additions and 39 deletions

View File

@@ -5,14 +5,14 @@ use axum::{
response::{IntoResponse, Response},
};
use std::collections::HashMap;
use std::sync::Arc;
use tokio_util::io::ReaderStream; // 需在 Cargo.toml 确认有 tokio-util
use std::sync::{Arc, RwLock};
use tokio_util::io::ReaderStream;
use crate::router::MockRouter;
/// 共享的应用状态
/// 共享的应用状态router 由 RwLock 保护以支持热重载
pub struct AppState {
pub router: MockRouter,
pub router: RwLock<MockRouter>,
}
/// 全局统一请求处理函数
@@ -26,7 +26,7 @@ pub async fn mock_handler(
let path = req.uri().path().to_string();
let method_str = method.as_str().to_string();
// 将 Axum HeaderMap 转换为简单的 HashMap 供 Router 使用
// 1. 将需要的数据克隆出来,断开与 req 的借用关系
let body_bytes = match axum::body::to_bytes(req.into_body(), 10 * 1024 * 1024).await {
Ok(bytes) => bytes,
Err(_) => {
@@ -39,40 +39,41 @@ pub async fn mock_handler(
let incoming_json: Option<serde_json::Value> = serde_json::from_slice(&body_bytes).ok();
// 2. 将 Axum HeaderMap 转换为简单的 HashMap
let mut req_headers = HashMap::new();
for (name, value) in headers.iter() {
if let Ok(v) = value.to_str() {
req_headers.insert(name.as_str().to_string(), v.to_string());
}
}
// 2. 执行匹配逻辑
if let Some(rule) =
state
.router
.match_rule(&method_str, &path, &params, &req_headers, &incoming_json)
{
// 3. 处理模拟延迟
// 3. 执行匹配逻辑:先获取读锁 (Read Lock)
let maybe_rule = {
let router = state.router.read().expect("Failed to acquire read lock");
router.match_rule(&method_str, &path, &params, &req_headers, &incoming_json).cloned()
// 使用 .cloned() 以便尽早释放读锁,避免阻塞热重载写锁
};
if let Some(rule) = maybe_rule {
// 4. 处理模拟延迟
if let Some(ref settings) = rule.settings {
if let Some(delay) = settings.delay_ms {
tokio::time::sleep(std::time::Duration::from_millis(delay)).await;
}
}
// 4. 构建响应基础信息
// 5. 构建响应
let status = StatusCode::from_u16(rule.response.status).unwrap_or(StatusCode::OK);
let mut response_builder = Response::builder().status(status);
// 注入 YAML 定义的 Header
if let Some(ref h) = rule.response.headers {
for (k, v) in h {
// println!("{}:{}",k.clone(), v.clone());
response_builder = response_builder.header(k, v);
}
}
// 5. 执行 Smart Body 协议逻辑
// 6. Smart Body 逻辑
if let Some(file_path) = rule.response.get_file_path() {
// A. 文件模式:异步打开文件并转换为流,实现低内存占用
match tokio::fs::File::open(file_path).await {
Ok(file) => {
let stream = ReaderStream::new(file);
@@ -88,13 +89,13 @@ pub async fn mock_handler(
.unwrap(),
}
} else {
// B. 内联模式:直接返回字符串内容
// 内联模式:直接返回字符串内容
response_builder
.body(Body::from(rule.response.body.clone()))
.unwrap()
}
} else {
println!("请求头{:?}", req_headers.clone());
println!("请求头{:?}", req_headers);
// 匹配失败返回 404
Response::builder()
.status(StatusCode::NOT_FOUND)