feat: mock配置迁移至JSON格式并修复body匹配
- 将mock配置从YAML格式迁移到JSON格式 - 修复JSON字符串格式body匹配失败问题 - 添加MCP功能模块 - 更新mock-spec.md规范文档 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
122
src/main.rs
122
src/main.rs
@@ -1,47 +1,136 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use axum::{routing::any, Router};
|
||||
use notify_debouncer_mini::{new_debouncer, notify::*};
|
||||
use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode};
|
||||
|
||||
use mock_server::loader::MockLoader;
|
||||
use mock_server::router::MockRouter;
|
||||
use mock_server::handler::{mock_handler, AppState};
|
||||
|
||||
fn print_usage() {
|
||||
println!("Mock Server - A mock API server with hot-reload support");
|
||||
println!();
|
||||
println!("Usage: mock_server [OPTIONS]");
|
||||
println!();
|
||||
println!("Options:");
|
||||
println!(" --mcp Run as MCP server (stdio transport)");
|
||||
println!(" --mocks <DIR> Mocks directory path (default: ./mocks)");
|
||||
println!(" --port <PORT> Server port (default: 8080)");
|
||||
println!(" --help Show this help message");
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let mocks_dir = Path::new("./mocks");
|
||||
if !mocks_dir.exists() {
|
||||
std::fs::create_dir_all(mocks_dir).unwrap();
|
||||
// 解析命令行参数
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let mut mocks_dir = PathBuf::from("./mocks");
|
||||
let mut port: u16 = 8080;
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
let mut mcp_mode = false;
|
||||
|
||||
let mut i = 1;
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"--help" | "-h" => {
|
||||
print_usage();
|
||||
return;
|
||||
}
|
||||
"--mcp" => {
|
||||
#[cfg(feature = "mcp")]
|
||||
{
|
||||
mcp_mode = true;
|
||||
}
|
||||
#[cfg(not(feature = "mcp"))]
|
||||
{
|
||||
eprintln!("MCP feature not enabled. Rebuild with --features mcp");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
"--mocks" => {
|
||||
if i + 1 < args.len() {
|
||||
mocks_dir = PathBuf::from(&args[i + 1]);
|
||||
i += 1;
|
||||
} else {
|
||||
eprintln!("--mocks requires a directory path");
|
||||
return;
|
||||
}
|
||||
}
|
||||
"--port" => {
|
||||
if i + 1 < args.len() {
|
||||
match args[i + 1].parse::<u16>() {
|
||||
Ok(p) => port = p,
|
||||
Err(_) => {
|
||||
eprintln!("Invalid port number: {}", args[i + 1]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
} else {
|
||||
eprintln!("--port requires a port number");
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Unknown option: {}", args[i]);
|
||||
print_usage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// 1. 初始加载
|
||||
if !mocks_dir.exists() {
|
||||
std::fs::create_dir_all(&mocks_dir).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
if mcp_mode {
|
||||
run_mcp_server(mocks_dir).await;
|
||||
return;
|
||||
}
|
||||
|
||||
run_http_server(mocks_dir, port).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
async fn run_mcp_server(mocks_dir: PathBuf) {
|
||||
println!("Starting MCP server...");
|
||||
let manager = Arc::new(mock_server::manager::MockManager::new(mocks_dir));
|
||||
println!("Loaded {} groups", manager.list_groups().len());
|
||||
|
||||
match mock_server::mcp::run_mcp_server(manager).await {
|
||||
Ok(_) => println!("MCP server stopped"),
|
||||
Err(e) => eprintln!("MCP server error: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_http_server(mocks_dir: PathBuf, port: u16) {
|
||||
println!("Scanning mocks directory...");
|
||||
let index = MockLoader::load_all_from_dir(mocks_dir);
|
||||
let index = MockLoader::load_all_from_dir(&mocks_dir);
|
||||
let shared_state = Arc::new(AppState {
|
||||
router: RwLock::new(MockRouter::new(index)),
|
||||
router: std::sync::RwLock::new(MockRouter::new(index)),
|
||||
});
|
||||
|
||||
// 2. 设置热加载监听器
|
||||
// 设置热加载监听器
|
||||
let state_for_watcher = shared_state.clone();
|
||||
let watch_path = mocks_dir.to_path_buf();
|
||||
let watch_path = mocks_dir.clone();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
// 200ms 防抖,防止编辑器保存文件时产生多次干扰
|
||||
let mut debouncer = new_debouncer(Duration::from_millis(200), tx).unwrap();
|
||||
debouncer.watcher().watch(&watch_path, RecursiveMode::Recursive).unwrap();
|
||||
|
||||
// 启动异步任务监听文件变动
|
||||
tokio::spawn(async move {
|
||||
while let Ok(res) = rx.recv() {
|
||||
match res {
|
||||
Ok(_) => {
|
||||
println!("🔄 Detecting changes in mocks/, reloading...");
|
||||
let new_index = MockLoader::load_all_from_dir(&watch_path);
|
||||
// 获取写锁 (Write Lock) 更新索引
|
||||
let mut writer = state_for_watcher.router.write().expect("Failed to acquire write lock");
|
||||
*writer = MockRouter::new(new_index);
|
||||
println!("✅ Mocks reloaded successfully.");
|
||||
@@ -51,14 +140,13 @@ async fn main() {
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 配置 Axum 路由
|
||||
let app = Router::new()
|
||||
.fallback(any(mock_handler))
|
||||
.with_state(shared_state);
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||
println!("🚀 Server running at http://{}", addr);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user