#[cfg(feature = "ban-manager")]
use crate::BanManager;
#[cfg(feature = "circuit-breaker")]
use crate::CircuitBreaker;
use crate::Governor;
#[cfg(feature = "quota-control")]
use crate::QuotaController;
use super::config::AdminApiConfig;
use super::routes;
use axum::Router;
use std::sync::Arc;
use tokio::net::TcpListener;
#[derive(Clone)]
pub struct AppState {
pub governor: Arc<Governor>,
#[cfg(feature = "ban-manager")]
pub ban_manager: Option<Arc<BanManager>>,
#[cfg(feature = "quota-control")]
pub quota_controller: Option<Arc<QuotaController>>,
#[cfg(feature = "circuit-breaker")]
pub circuit_breaker: Option<Arc<CircuitBreaker>>,
}
pub struct AdminServer {
state: AppState,
config: AdminApiConfig,
}
impl AdminServer {
pub fn new(governor: Arc<Governor>, config: AdminApiConfig) -> Self {
Self {
state: AppState {
governor,
#[cfg(feature = "ban-manager")]
ban_manager: None,
#[cfg(feature = "quota-control")]
quota_controller: None,
#[cfg(feature = "circuit-breaker")]
circuit_breaker: None,
},
config,
}
}
#[cfg(feature = "ban-manager")]
pub fn with_ban_manager(mut self, ban_manager: Arc<BanManager>) -> Self {
self.state.ban_manager = Some(ban_manager);
self
}
#[cfg(feature = "quota-control")]
pub fn with_quota_controller(mut self, quota_controller: Arc<QuotaController>) -> Self {
self.state.quota_controller = Some(quota_controller);
self
}
#[cfg(feature = "circuit-breaker")]
pub fn with_circuit_breaker(mut self, circuit_breaker: Arc<CircuitBreaker>) -> Self {
self.state.circuit_breaker = Some(circuit_breaker);
self
}
pub async fn start(&self) -> Result<(), Box<dyn std::error::Error>> {
if !self.config.enabled {
log::info!(target: "admin-api", "管理API已禁用");
return Ok(());
}
self.config.validate()?;
let router = routes::create_router(self.state.clone(), &self.config);
let listener = TcpListener::bind(&self.config.address()).await?;
let address = listener.local_addr()?;
log::info!(
target: "admin-api",
"管理API服务器已启动: http://{}",
address
);
axum::serve(listener, router).await?;
Ok(())
}
pub fn into_router(self) -> Router {
routes::create_router(self.state.clone(), &self.config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::admin::AdminApiConfig;
use crate::admin::make_governor;
#[tokio::test]
async fn test_admin_server_new() {
let governor = Arc::new(make_governor().await);
let config = AdminApiConfig::new("test-api-key-16chars!!");
let server = AdminServer::new(governor, config);
assert!(Arc::strong_count(&server.state.governor) >= 1);
#[cfg(feature = "ban-manager")]
assert!(server.state.ban_manager.is_none());
#[cfg(feature = "quota-control")]
assert!(server.state.quota_controller.is_none());
#[cfg(feature = "circuit-breaker")]
assert!(server.state.circuit_breaker.is_none());
}
#[cfg(feature = "ban-manager")]
#[tokio::test]
async fn test_admin_server_with_ban_manager() {
use crate::BanManager;
let governor = Arc::new(make_governor().await);
let ban_manager = Arc::new(BanManager::new().await.unwrap());
let config = AdminApiConfig::new("test-api-key-16chars!!");
let server = AdminServer::new(governor, config).with_ban_manager(ban_manager);
assert!(server.state.ban_manager.is_some());
}
#[cfg(feature = "quota-control")]
#[tokio::test]
async fn test_admin_server_with_quota_controller() {
use crate::QuotaController;
use crate::cache::CacheQuotaStorage;
use crate::quota::QuotaConfig;
use crate::storage::QuotaStorage;
use oxcache::backend::memory::DashMapMemoryBackend;
let governor = Arc::new(make_governor().await);
let storage: Arc<dyn QuotaStorage> = Arc::new(CacheQuotaStorage::new(Arc::new(
DashMapMemoryBackend::new(),
)));
let quota_controller = Arc::new(QuotaController::with_dependencies(
storage,
QuotaConfig::default(),
));
let config = AdminApiConfig::new("test-api-key-16chars!!");
let server = AdminServer::new(governor, config).with_quota_controller(quota_controller);
assert!(server.state.quota_controller.is_some());
}
#[cfg(feature = "circuit-breaker")]
#[tokio::test]
async fn test_admin_server_with_circuit_breaker() {
use crate::circuit::{CircuitBreaker, CircuitBreakerConfig};
let governor = Arc::new(make_governor().await);
let cb = Arc::new(CircuitBreaker::with_dependencies(
CircuitBreakerConfig::default(),
));
let config = AdminApiConfig::new("test-api-key-16chars!!");
let server = AdminServer::new(governor, config).with_circuit_breaker(cb);
assert!(server.state.circuit_breaker.is_some());
}
#[tokio::test]
async fn test_admin_server_start_disabled() {
let governor = Arc::new(make_governor().await);
let config = AdminApiConfig::default();
let server = AdminServer::new(governor, config);
let result = server.start().await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_admin_server_into_router() {
let governor = Arc::new(make_governor().await);
let config = AdminApiConfig::new("test-api-key-16chars!!");
let server = AdminServer::new(governor, config);
let _router = server.into_router();
}
#[cfg(feature = "ban-manager")]
#[tokio::test]
async fn test_admin_server_chained_builders() {
use crate::cache::CacheQuotaStorage;
use crate::circuit::{CircuitBreaker, CircuitBreakerConfig};
use crate::quota::QuotaConfig;
use crate::storage::QuotaStorage;
use crate::{BanManager, QuotaController};
use oxcache::backend::memory::DashMapMemoryBackend;
let governor = Arc::new(make_governor().await);
let ban_manager = Arc::new(BanManager::new().await.unwrap());
let storage: Arc<dyn QuotaStorage> = Arc::new(CacheQuotaStorage::new(Arc::new(
DashMapMemoryBackend::new(),
)));
let quota_controller = Arc::new(QuotaController::with_dependencies(
storage,
QuotaConfig::default(),
));
let cb = Arc::new(CircuitBreaker::with_dependencies(
CircuitBreakerConfig::default(),
));
let config = AdminApiConfig::new("test-api-key-16chars!!");
let server = AdminServer::new(governor, config)
.with_ban_manager(ban_manager)
.with_quota_controller(quota_controller)
.with_circuit_breaker(cb);
assert!(server.state.ban_manager.is_some());
assert!(server.state.quota_controller.is_some());
assert!(server.state.circuit_breaker.is_some());
}
}