fix: 将 HTTP 桥接改为独立的开发服务器(端口 3030)

## 问题

之前将 dev_bridge 路由合并到主应用服务器(端口 8999),
但用户需要一个独立的开发桥接端口,并且服务器启动后会立即关闭。

## 变更

- 重构 dev_bridge.rs 为独立的服务器实现
- 服务器在后台持续运行(不使用 graceful_shutdown)
- 在服务器启动时同步启动 dev bridge 服务器
- 添加 get_network_info 命令支持
- 修复 get_default_provider 返回格式(返回字符串而非对象)

## 架构

- 端口 8999: 主应用 API(生产/通用)
- 端口 3030: 专用开发桥接(仅 dev 模式)

## 验证

- [DevBridge] HTTP 桥接成功: get_server_status
- [DevBridge] HTTP 桥接成功: get_config
- [DevBridge] HTTP 桥接成功: get_default_provider
- [DevBridge] HTTP 桥接成功: get_network_info
This commit is contained in:
lwmacct
2026-01-11 23:54:33 +08:00
parent 329c699ae8
commit e77bb2a5ec
3 changed files with 123 additions and 18 deletions
+89 -14
View File
@@ -1,6 +1,8 @@
//! HTTP 桥接模块
//!
//! 仅在开发模式下启用,允许浏览器 dev server 通过 HTTP 调用 Tauri 命令。
//!
//! 这是一个独立的开发服务器,运行在 3030 端口,与主应用服务器(8999)分离。
#[cfg(debug_assertions)]
pub mod dispatcher;
@@ -13,12 +15,18 @@ use axum::{
routing::post,
Json, Router,
};
#[cfg(debug_assertions)]
use serde::{Deserialize, Serialize};
#[cfg(debug_assertions)]
use std::sync::Arc;
#[cfg(debug_assertions)]
use tokio::sync::RwLock;
#[cfg(debug_assertions)]
use tower_http::cors::CorsLayer;
// 使用 Axum 服务器的 AppState
use crate::server::AppState;
#[cfg(debug_assertions)]
#[derive(Debug, Deserialize)]
pub struct InvokeRequest {
pub cmd: String,
@@ -26,28 +34,86 @@ pub struct InvokeRequest {
pub args: Option<serde_json::Value>,
}
#[cfg(debug_assertions)]
#[derive(Debug, Serialize)]
pub struct InvokeResponse {
pub result: Option<serde_json::Value>,
pub error: Option<String>,
}
/// 创建 HTTP 桥接路由
///
/// 仅在 dev 模式下被添加到 Axum 服务器
pub fn dev_bridge_routes() -> Router<AppState> {
Router::new().route("/invoke", post(invoke_command)).layer(
// CORS 配置 - 允许 localhost:1420 访问
CorsLayer::new()
.allow_origin("http://localhost:1420".parse::<HeaderValue>().unwrap())
.allow_methods([axum::http::Method::POST])
.allow_headers([axum::http::header::CONTENT_TYPE]),
)
/// 开发桥接服务器配置
#[cfg(debug_assertions)]
pub struct DevBridgeConfig {
/// 监听地址
pub host: String,
/// 监听端口
pub port: u16,
}
async fn invoke_command(State(state): State<AppState>, Json(req): Json<InvokeRequest>) -> Response {
#[cfg(debug_assertions)]
impl Default for DevBridgeConfig {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 3030,
}
}
}
/// 开发桥接服务器
#[cfg(debug_assertions)]
pub struct DevBridgeServer;
#[cfg(debug_assertions)]
impl DevBridgeServer {
/// 启动开发桥接服务器
///
/// 这是一个独立的 HTTP 服务器,仅用于开发模式,
/// 允许浏览器 dev server 通过 HTTP 调用 Tauri 命令。
///
/// 服务器会在后台持续运行,直到应用退出。
pub async fn start(
app_state: Arc<RwLock<AppState>>,
config: Option<DevBridgeConfig>,
) -> Result<(), Box<dyn std::error::Error>> {
let config = config.unwrap_or_default();
let app = Router::new()
.route("/invoke", post(invoke_command))
.route("/health", post(health_check))
.layer(
// CORS 配置 - 允许 localhost:1420 访问
CorsLayer::new()
.allow_origin("http://localhost:1420".parse::<HeaderValue>().unwrap())
.allow_methods([axum::http::Method::POST, axum::http::Method::GET])
.allow_headers([axum::http::header::CONTENT_TYPE]),
)
.with_state(app_state);
let addr = format!("{}:{}", config.host, config.port);
let listener = tokio::net::TcpListener::bind(&addr).await?;
eprintln!("[DevBridge] 正在监听: http://{}", addr);
// 直接运行服务器(不使用 graceful_shutdown)
// 服务器将持续运行直到应用退出
tokio::spawn(async move {
axum::serve(listener, app).await.ok();
});
Ok(())
}
}
#[cfg(debug_assertions)]
async fn invoke_command(
State(state): State<Arc<RwLock<AppState>>>,
Json(req): Json<InvokeRequest>,
) -> Response {
// 获取 AppState 的读锁
let state_ref = state.read().await;
// 调用命令分发器
match dispatcher::handle_command(&state, &req.cmd, req.args).await {
match dispatcher::handle_command(&state_ref, &req.cmd, req.args).await {
Ok(result) => Json(InvokeResponse {
result: Some(result),
error: None,
@@ -60,3 +126,12 @@ async fn invoke_command(State(state): State<AppState>, Json(req): Json<InvokeReq
.into_response(),
}
}
#[cfg(debug_assertions)]
async fn health_check() -> impl IntoResponse {
Json(serde_json::json!({
"status": "ok",
"service": "DevBridge",
"version": "1.0.0"
}))
}
+12 -1
View File
@@ -32,7 +32,8 @@ pub async fn handle_command(
"get_default_provider" => {
let provider = state.default_provider.read().await.clone();
Ok(serde_json::json!({ "provider": provider }))
// 直接返回字符串值,不是对象
Ok(serde_json::json!(provider))
}
"get_endpoint_providers" => {
@@ -177,6 +178,16 @@ pub async fn handle_command(
}))
}
// ========== 网络信息 ==========
"get_network_info" => {
// 返回网络信息
Ok(serde_json::json!({
"localhost": "127.0.0.1",
"lan_ip": null,
"all_ips": ["127.0.0.1"]
}))
}
_ => Err(format!(
"[DevBridge] 未知命令: '{}'. 如需此命令,请将其添加到 dispatcher.rs 的 handle_command 函数中。",
cmd
+22 -3
View File
@@ -866,6 +866,28 @@ async fn run_server(
api_key_service,
};
// ========== 开发模式:启动独立的 HTTP 桥接服务器 ==========
// 仅在 debug 模式下,启动一个独立的开发服务器在端口 3030
// 允许浏览器 dev server 通过 HTTP 调用 Tauri 命令
#[cfg(debug_assertions)]
{
eprintln!("[DevBridge] ===== 准备启动开发桥接服务器 =====");
use tokio::sync::RwLock as TokioRwLock;
let dev_bridge_state = Arc::new(TokioRwLock::new(state.clone()));
eprintln!("[DevBridge] 状态已克隆,准备启动");
tokio::spawn(async move {
eprintln!("[DevBridge] spawn 任务开始执行");
match crate::dev_bridge::DevBridgeServer::start(dev_bridge_state, None).await {
Ok(_) => {
eprintln!("[DevBridge] 启动完成");
}
Err(e) => {
eprintln!("[DevBridge] 启动失败: {}", e);
}
}
});
}
// 启动配置文件监控
let _file_watcher = if let Some(path) = config_path {
start_config_watcher(
@@ -986,9 +1008,6 @@ async fn run_server(
.merge(kiro_api_routes)
// 凭证 API 路由(用于 aster Agent 集成)
.merge(credentials_api_routes)
// 开发模式 HTTP 桥接路由(仅在 debug 模式启用)
// 允许浏览器 dev server 通过 HTTP 调用 Tauri 命令
.merge(crate::dev_bridge::dev_bridge_routes())
.layer(DefaultBodyLimit::max(body_limit))
.with_state(state);