docs: 战略规划和管理架构优化
- 新增 STRATEGY.md: 三年战略规划、技术路线、团队策略 - 新增 MILESTONES.md: 详细里程碑和时间表(M1.1-M1.4) - 新增 CODE_REVIEW.md: 代码审核标准和流程 - 组建管理班子: 新增 PM 刘建国,优化管理架构 - 丰富团队成员背景: 补充所有成员的教育经历、工作经验、技能树 - 解锁多线程思考能力: 团队成员可使用 kilo 命令并行探索 - 更新工作流程: CEO → PM → 开发团队,两级审核制度 - 修正 kilo 调用方式: 不使用 -f 参数,在消息中指示读取文件
This commit is contained in:
412
src/plugins/http/routes.rs
Normal file
412
src/plugins/http/routes.rs
Normal file
@@ -0,0 +1,412 @@
|
||||
use super::HttpState;
|
||||
use crate::core::config;
|
||||
use crate::core::message::{Destination, Envelope, Message, PlayerCommand, WifiCommand};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use std::convert::Infallible;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::time::{Duration, Instant};
|
||||
use warp::http::StatusCode;
|
||||
use warp::{Filter, Reply};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct WifiConnectRequest {
|
||||
ssid: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct WifiApStartRequest {
|
||||
ssid: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ApiMessage<'a> {
|
||||
status: &'a str,
|
||||
message: String,
|
||||
}
|
||||
|
||||
pub(crate) fn build_routes(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
state: Arc<HttpState>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
let api = play_route(tx.clone())
|
||||
.or(pause_route(tx.clone()))
|
||||
.or(next_route(tx.clone()))
|
||||
.or(previous_route(tx.clone()))
|
||||
.or(goto_route(tx.clone()))
|
||||
.or(trigger_route(tx.clone()))
|
||||
.or(scene_route(tx.clone()))
|
||||
.or(status_route(Arc::clone(&state)))
|
||||
.or(config_get_route(Arc::clone(&state)))
|
||||
.or(config_post_route(tx.clone(), Arc::clone(&state)))
|
||||
.or(wifi_status_route(tx.clone(), Arc::clone(&state)))
|
||||
.or(wifi_scan_route(tx.clone(), Arc::clone(&state)))
|
||||
.or(wifi_connect_route(tx.clone(), Arc::clone(&state)))
|
||||
.or(wifi_ap_start_route(tx.clone(), Arc::clone(&state)))
|
||||
.or(wifi_ap_stop_route(tx, state));
|
||||
|
||||
let cors = warp::cors()
|
||||
.allow_any_origin()
|
||||
.allow_headers(["content-type"])
|
||||
.allow_methods(["GET", "POST", "OPTIONS"]);
|
||||
|
||||
root_route().or(api).with(cors)
|
||||
}
|
||||
|
||||
fn root_route() -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path::end().and(warp::get()).map(|| {
|
||||
warp::reply::html(
|
||||
"<!doctype html><html><head><meta charset=\"utf-8\"><title>ShowenV2 HTTP API</title></head><body><h1>ShowenV2 HTTP API</h1><p>HTTP API is running.</p></body></html>",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn play_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "play")
|
||||
.and(warp::post())
|
||||
.and(with_tx(tx))
|
||||
.and_then(|tx| command_reply(tx, Message::PlayerCommand(PlayerCommand::Play), "开始播放"))
|
||||
}
|
||||
|
||||
fn pause_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "pause")
|
||||
.and(warp::post())
|
||||
.and(with_tx(tx))
|
||||
.and_then(|tx| command_reply(tx, Message::PlayerCommand(PlayerCommand::Pause), "已暂停"))
|
||||
}
|
||||
|
||||
fn next_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "next")
|
||||
.and(warp::post())
|
||||
.and(with_tx(tx))
|
||||
.and_then(|tx| command_reply(tx, Message::PlayerCommand(PlayerCommand::Next), "切换到下一个视频"))
|
||||
}
|
||||
|
||||
fn previous_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "previous")
|
||||
.and(warp::post())
|
||||
.and(with_tx(tx))
|
||||
.and_then(|tx| command_reply(tx, Message::PlayerCommand(PlayerCommand::Previous), "切换到上一个视频"))
|
||||
}
|
||||
|
||||
fn goto_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "goto" / usize)
|
||||
.and(warp::post())
|
||||
.and(with_tx(tx))
|
||||
.and_then(|index, tx| {
|
||||
command_reply(
|
||||
tx,
|
||||
Message::PlayerCommand(PlayerCommand::Goto(index)),
|
||||
format!("跳转到视频 {index}"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn trigger_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "trigger" / String / String)
|
||||
.and(warp::post())
|
||||
.and(with_tx(tx))
|
||||
.and_then(|name, value, tx| {
|
||||
let message = format!("触发器 '{name}' 已发送,值: {value}");
|
||||
command_reply(tx, Message::Trigger { name, value }, message)
|
||||
})
|
||||
}
|
||||
|
||||
fn scene_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "scene" / String)
|
||||
.and(warp::post())
|
||||
.and(with_tx(tx))
|
||||
.and_then(|name, tx| {
|
||||
let message = format!("切换到场景: {name}");
|
||||
command_reply(tx, Message::PlayerCommand(PlayerCommand::ChangeScene(name)), message)
|
||||
})
|
||||
}
|
||||
|
||||
fn status_route(
|
||||
state: Arc<HttpState>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "status")
|
||||
.and(warp::get())
|
||||
.and(with_state(state))
|
||||
.and_then(status_reply)
|
||||
}
|
||||
|
||||
fn config_get_route(
|
||||
state: Arc<HttpState>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "config")
|
||||
.and(warp::get())
|
||||
.and(with_state(state))
|
||||
.and_then(config_get_reply)
|
||||
}
|
||||
|
||||
fn config_post_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
state: Arc<HttpState>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "config")
|
||||
.and(warp::post())
|
||||
.and(warp::body::content_length_limit(1024 * 64))
|
||||
.and(warp::body::bytes())
|
||||
.and(with_tx(tx))
|
||||
.and(with_state(state))
|
||||
.and_then(handle_config_post)
|
||||
}
|
||||
|
||||
fn wifi_status_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
state: Arc<HttpState>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "wifi" / "status")
|
||||
.and(warp::get())
|
||||
.and(with_tx(tx))
|
||||
.and(with_state(state))
|
||||
.and_then(|tx, state| wifi_reply(tx, state, WifiCommand::Status))
|
||||
}
|
||||
|
||||
fn wifi_scan_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
state: Arc<HttpState>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "wifi" / "scan")
|
||||
.and(warp::get())
|
||||
.and(with_tx(tx))
|
||||
.and(with_state(state))
|
||||
.and_then(|tx, state| wifi_reply(tx, state, WifiCommand::Scan))
|
||||
}
|
||||
|
||||
fn wifi_connect_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
state: Arc<HttpState>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "wifi" / "connect")
|
||||
.and(warp::post())
|
||||
.and(warp::body::json())
|
||||
.and(with_tx(tx))
|
||||
.and(with_state(state))
|
||||
.and_then(|req: WifiConnectRequest, tx, state| {
|
||||
wifi_reply(
|
||||
tx,
|
||||
state,
|
||||
WifiCommand::Connect {
|
||||
ssid: req.ssid,
|
||||
password: req.password,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn wifi_ap_start_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
state: Arc<HttpState>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "wifi" / "ap" / "start")
|
||||
.and(warp::post())
|
||||
.and(warp::body::json())
|
||||
.and(with_tx(tx))
|
||||
.and(with_state(state))
|
||||
.and_then(|req: WifiApStartRequest, tx, state| {
|
||||
wifi_reply(
|
||||
tx,
|
||||
state,
|
||||
WifiCommand::ApStart {
|
||||
ssid: req.ssid,
|
||||
password: req.password,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn wifi_ap_stop_route(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
state: Arc<HttpState>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "wifi" / "ap" / "stop")
|
||||
.and(warp::post())
|
||||
.and(with_tx(tx))
|
||||
.and(with_state(state))
|
||||
.and_then(|tx, state| wifi_reply(tx, state, WifiCommand::ApStop))
|
||||
}
|
||||
|
||||
async fn status_reply(state: Arc<HttpState>) -> Result<warp::reply::Response, Infallible> {
|
||||
Ok(warp::reply::json(&json!({
|
||||
"player": state.player_status(),
|
||||
"ble_ready": state.ble_ready(),
|
||||
}))
|
||||
.into_response())
|
||||
}
|
||||
|
||||
async fn config_get_reply(state: Arc<HttpState>) -> Result<warp::reply::Response, Infallible> {
|
||||
Ok(warp::reply::json(state.config().as_ref()).into_response())
|
||||
}
|
||||
|
||||
async fn handle_config_post(
|
||||
body: bytes::Bytes,
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
state: Arc<HttpState>,
|
||||
) -> Result<warp::reply::Response, Infallible> {
|
||||
let current_config = state.config();
|
||||
let raw = match std::str::from_utf8(&body) {
|
||||
Ok(raw) => raw,
|
||||
Err(_) => return Ok(error_json(StatusCode::BAD_REQUEST, "请求体不是有效的 UTF-8")),
|
||||
};
|
||||
|
||||
if let Err(error) = config::parse_str(raw, ¤t_config.source_path) {
|
||||
return Ok(error_json(
|
||||
StatusCode::BAD_REQUEST,
|
||||
&format!("配置验证失败: {error}"),
|
||||
));
|
||||
}
|
||||
|
||||
if let Err(error) = std::fs::write(¤t_config.source_path, raw) {
|
||||
return Ok(error_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("写入配置文件失败: {error}"),
|
||||
));
|
||||
}
|
||||
|
||||
if let Err(error) = tx.send(Envelope {
|
||||
from: "http",
|
||||
to: Destination::Manager,
|
||||
message: Message::ConfigReloadRequest,
|
||||
}) {
|
||||
return Ok(error_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("发送配置重载请求失败: {error}"),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(success_json("配置已保存并请求重载"))
|
||||
}
|
||||
|
||||
async fn command_reply(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
message: Message,
|
||||
success_message: impl Into<String>,
|
||||
) -> Result<warp::reply::Response, Infallible> {
|
||||
match tx.send(Envelope {
|
||||
from: "http",
|
||||
to: Destination::Plugin("video"),
|
||||
message,
|
||||
}) {
|
||||
Ok(()) => Ok(success_json(success_message)),
|
||||
Err(error) => Ok(error_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("发送命令失败: {error}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn wifi_reply(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
state: Arc<HttpState>,
|
||||
command: WifiCommand,
|
||||
) -> Result<warp::reply::Response, Infallible> {
|
||||
let version = match state.wifi_response.lock() {
|
||||
Ok(guard) => guard.version,
|
||||
Err(_) => {
|
||||
return Ok(error_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"WiFi 响应状态锁已损坏",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(error) = tx.send(Envelope {
|
||||
from: "http",
|
||||
to: Destination::Plugin("wifi"),
|
||||
message: Message::WifiCommand(command),
|
||||
}) {
|
||||
return Ok(error_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&format!("发送 WiFi 命令失败: {error}"),
|
||||
));
|
||||
}
|
||||
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
let mut guard = match state.wifi_response.lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(_) => {
|
||||
return Ok(error_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"WiFi 响应状态锁已损坏",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
while guard.version == version {
|
||||
let now = Instant::now();
|
||||
if now >= deadline {
|
||||
return Ok(error_json(StatusCode::GATEWAY_TIMEOUT, "等待 WiFi 响应超时"));
|
||||
}
|
||||
|
||||
let timeout = deadline.saturating_duration_since(now);
|
||||
let (next_guard, wait_result) = match state.wifi_response_cv.wait_timeout(guard, timeout) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
return Ok(error_json(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"等待 WiFi 响应失败",
|
||||
));
|
||||
}
|
||||
};
|
||||
guard = next_guard;
|
||||
|
||||
if wait_result.timed_out() && guard.version == version {
|
||||
return Ok(error_json(StatusCode::GATEWAY_TIMEOUT, "等待 WiFi 响应超时"));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(warp::reply::with_status(guard.payload.clone().unwrap_or_default(), StatusCode::OK).into_response())
|
||||
}
|
||||
|
||||
fn with_tx(
|
||||
tx: mpsc::Sender<Envelope>,
|
||||
) -> impl Filter<Extract = (mpsc::Sender<Envelope>,), Error = Infallible> + Clone {
|
||||
warp::any().map(move || tx.clone())
|
||||
}
|
||||
|
||||
fn with_state(
|
||||
state: Arc<HttpState>,
|
||||
) -> impl Filter<Extract = (Arc<HttpState>,), Error = Infallible> + Clone {
|
||||
warp::any().map(move || Arc::clone(&state))
|
||||
}
|
||||
|
||||
fn success_json(message: impl Into<String>) -> warp::reply::Response {
|
||||
warp::reply::with_status(
|
||||
warp::reply::json(&ApiMessage {
|
||||
status: "ok",
|
||||
message: message.into(),
|
||||
}),
|
||||
StatusCode::OK,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
fn error_json(status: StatusCode, message: &str) -> warp::reply::Response {
|
||||
warp::reply::with_status(
|
||||
warp::reply::json(&json!({
|
||||
"status": "error",
|
||||
"message": message,
|
||||
})),
|
||||
status,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
Reference in New Issue
Block a user