chore: 提交所有工作进度 — SaaS 后端增强、Admin UI、桌面端集成

包含大量 SaaS 平台改进、Admin 管理后台更新、桌面端集成完善、
文档同步、测试文件重构等内容。为 QA 测试准备干净工作树。
This commit is contained in:
iven
2026-03-29 10:46:26 +08:00
parent 9a5fad2b59
commit 5fdf96c3f5
268 changed files with 22011 additions and 3886 deletions

View File

@@ -7,16 +7,23 @@ use axum::{
use crate::state::AppState;
use crate::error::SaasResult;
use crate::auth::types::AuthContext;
use crate::auth::handlers::check_permission;
use crate::auth::handlers::{check_permission, log_operation};
use crate::common::PaginatedResponse;
use super::{types::*, service};
/// GET /api/v1/config/items?category=xxx&source=xxx
/// GET /api/v1/config/items?category=xxx&source=xxx&page=1&page_size=20
pub async fn list_config_items(
State(state): State<AppState>,
Query(query): Query<ConfigQuery>,
_ctx: Extension<AuthContext>,
) -> SaasResult<Json<Vec<ConfigItemInfo>>> {
service::list_config_items(&state.db, &query).await.map(Json)
) -> SaasResult<Json<PaginatedResponse<ConfigItemInfo>>> {
let filter_query = ConfigQuery {
category: query.category.clone(),
source: query.source.clone(),
page: None,
page_size: None,
};
service::list_config_items(&state.db, &filter_query, query.page, query.page_size).await.map(Json)
}
/// GET /api/v1/config/items/:id
@@ -36,10 +43,11 @@ pub async fn create_config_item(
) -> SaasResult<(StatusCode, Json<ConfigItemInfo>)> {
check_permission(&ctx, "config:write")?;
let item = service::create_config_item(&state.db, &req).await?;
log_operation(&state.db, &ctx.account_id, "config.create", "config_item", &item.id, None, ctx.client_ip.as_deref()).await?;
Ok((StatusCode::CREATED, Json(item)))
}
/// PUT /api/v1/config/items/:id (admin only)
/// PATCH /api/v1/config/items/:id (admin only)
pub async fn update_config_item(
State(state): State<AppState>,
Path(id): Path<String>,
@@ -47,7 +55,9 @@ pub async fn update_config_item(
Json(req): Json<UpdateConfigItemRequest>,
) -> SaasResult<Json<ConfigItemInfo>> {
check_permission(&ctx, "config:write")?;
service::update_config_item(&state.db, &id, &req).await.map(Json)
let item = service::update_config_item(&state.db, &id, &req).await?;
log_operation(&state.db, &ctx.account_id, "config.update", "config_item", &id, None, ctx.client_ip.as_deref()).await?;
Ok(Json(item))
}
/// DELETE /api/v1/config/items/:id (admin only)
@@ -58,6 +68,7 @@ pub async fn delete_config_item(
) -> SaasResult<Json<serde_json::Value>> {
check_permission(&ctx, "config:write")?;
service::delete_config_item(&state.db, &id).await?;
log_operation(&state.db, &ctx.account_id, "config.delete", "config_item", &id, None, ctx.client_ip.as_deref()).await?;
Ok(Json(serde_json::json!({"ok": true})))
}
@@ -76,32 +87,95 @@ pub async fn seed_config(
) -> SaasResult<Json<serde_json::Value>> {
check_permission(&ctx, "config:write")?;
let count = service::seed_default_config_items(&state.db).await?;
log_operation(&state.db, &ctx.account_id, "config.seed", "config_item", "batch", Some(serde_json::json!({"count": count})), ctx.client_ip.as_deref()).await?;
Ok(Json(serde_json::json!({"created": count})))
}
/// POST /api/v1/config/sync
/// POST /api/v1/config/sync (需要 config:write 权限)
pub async fn sync_config(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
Json(req): Json<SyncConfigRequest>,
) -> SaasResult<Json<super::service::ConfigSyncResult>> {
super::service::sync_config(&state.db, &ctx.account_id, &req).await.map(Json)
// 权限检查:仅 config:write 可推送配置
check_permission(&ctx, "config:write")?;
let result = super::service::sync_config(&state.db, &ctx.account_id, &req).await?;
// 审计日志
log_operation(
&state.db,
&ctx.account_id,
"config.sync",
"config",
"batch",
Some(serde_json::json!({
"client_fingerprint": req.client_fingerprint,
"action": req.action,
"config_count": req.config_keys.len(),
})),
ctx.client_ip.as_deref(),
).await.ok();
Ok(Json(result))
}
/// POST /api/v1/config/diff
/// 计算客户端与 SaaS 端的配置差异 (不修改数据)
pub async fn config_diff(
State(state): State<AppState>,
Extension(_ctx): Extension<AuthContext>,
Extension(ctx): Extension<AuthContext>,
Json(req): Json<SyncConfigRequest>,
) -> SaasResult<Json<ConfigDiffResponse>> {
// diff 操作虽然不修改数据,但涉及敏感配置信息,仍需认证用户
service::compute_config_diff(&state.db, &req).await.map(Json)
}
/// GET /api/v1/config/sync-logs
/// GET /api/v1/config/sync-logs?page=1&page_size=20
pub async fn list_sync_logs(
State(state): State<AppState>,
Extension(ctx): Extension<AuthContext>,
) -> SaasResult<Json<Vec<ConfigSyncLogInfo>>> {
service::list_sync_logs(&state.db, &ctx.account_id).await.map(Json)
Query(params): Query<std::collections::HashMap<String, String>>,
) -> SaasResult<Json<crate::common::PaginatedResponse<ConfigSyncLogInfo>>> {
let page: u32 = params.get("page").and_then(|v| v.parse().ok()).unwrap_or(1).max(1);
let page_size: u32 = params.get("page_size").and_then(|v| v.parse().ok()).unwrap_or(20).min(100);
service::list_sync_logs(&state.db, &ctx.account_id, page, page_size).await.map(Json)
}
/// GET /api/v1/config/pull?since=2026-03-28T00:00:00Z
/// 批量拉取配置(供桌面端启动时一次性拉取)
/// 返回扁平的 key-value map可选 since 参数过滤仅返回该时间之后更新的配置
pub async fn pull_config(
State(state): State<AppState>,
_ctx: Extension<AuthContext>,
Query(params): Query<std::collections::HashMap<String, String>>,
) -> SaasResult<Json<serde_json::Value>> {
let since = params.get("since").cloned();
let items = service::fetch_all_config_items(
&state.db,
&ConfigQuery { category: None, source: None, page: None, page_size: None },
).await?;
let mut configs: Vec<serde_json::Value> = Vec::new();
for item in items {
// 如果指定了 since只返回 updated_at > since 的配置
if let Some(ref since_val) = since {
if item.updated_at <= *since_val {
continue;
}
}
configs.push(serde_json::json!({
"key": item.key_path,
"category": item.category,
"value": item.current_value,
"value_type": item.value_type,
"default": item.default_value,
"updated_at": item.updated_at,
}));
}
Ok(Json(serde_json::json!({
"configs": configs,
"pulled_at": chrono::Utc::now().to_rfc3339(),
})))
}