fix: SaaS Admin + Tauri 一致性审查修复
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

- 删除 webhook 死代码模块 (4 文件 + worker,未注册未挂载)
- 删除孤立组件 StatusTag.tsx (从未被导入)
- authStore 权限模型补全 (scheduler/knowledge/billing 6+ permission key)
- authStore 硬编码 logout URL 改为 env 变量
- 清理未使用 service 方法 (agent-templates/billing/roles)
- Logs.tsx 代码重复消除 (本地常量 → @/constants/status)
- TRUTH.md 数字校准 (Tauri 177→183, SaaS API 131→130)
This commit is contained in:
iven
2026-04-07 01:53:54 +08:00
parent ae55ad6dc4
commit 2fd6d08899
12 changed files with 16 additions and 787 deletions

View File

@@ -1,175 +0,0 @@
//! Webhook 投递 Worker
//!
//! 从 webhook_deliveries 表取出待投递记录,向目标 URL 发送 HTTP POST。
//! 使用 HMAC-SHA256 签名 payload接收方可用 X-Webhook-Signature 头验证。
//! 投递失败时指数退避重试(最多 3 次)。
use async_trait::async_trait;
use sqlx::PgPool;
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use hmac::{Hmac, Mac};
use crate::error::SaasResult;
use super::Worker;
type HmacSha256 = Hmac<Sha256>;
/// Worker 参数(当前未使用 — Worker 通过 poll DB 工作)
#[derive(Debug, Serialize, Deserialize)]
pub struct WebhookDeliveryArgs {
pub delivery_id: String,
}
/// 最大投递尝试次数
const MAX_ATTEMPTS: i32 = 3;
/// 每轮轮询获取的最大投递数
const POLL_LIMIT: i32 = 50;
pub struct WebhookDeliveryWorker;
#[async_trait]
impl Worker for WebhookDeliveryWorker {
type Args = WebhookDeliveryArgs;
fn name(&self) -> &str {
"webhook_delivery"
}
async fn perform(&self, db: &PgPool, args: Self::Args) -> SaasResult<()> {
let pending = crate::webhook::service::fetch_pending_deliveries(
db, MAX_ATTEMPTS, 1,
).await?;
for delivery in pending {
if delivery.id != args.delivery_id {
continue;
}
deliver_one(db, &delivery).await?;
}
Ok(())
}
}
/// 投递所有待处理的 webhooks由 Scheduler 调用)
pub async fn deliver_pending_webhooks(db: &PgPool) -> SaasResult<u32> {
let pending = crate::webhook::service::fetch_pending_deliveries(
db, MAX_ATTEMPTS, POLL_LIMIT,
).await?;
let count = pending.len() as u32;
for delivery in &pending {
if let Err(e) = deliver_one(db, delivery).await {
tracing::warn!(
delivery_id = %delivery.id,
url = %delivery.url,
"Webhook delivery failed: {}", e
);
}
}
if count > 0 {
tracing::info!("Webhook delivery batch: {} pending processed", count);
}
Ok(count)
}
/// 投递单个 webhook
async fn deliver_one(
db: &PgPool,
delivery: &crate::webhook::service::PendingDelivery,
) -> SaasResult<()> {
let payload_str = serde_json::to_string(&delivery.payload)?;
// 计算 HMAC-SHA256 签名
let signature = compute_hmac_signature(&delivery.secret, &payload_str);
// 构建 webhook payload envelope
let envelope = serde_json::json!({
"event": delivery.event,
"timestamp": chrono::Utc::now().to_rfc3339(),
"data": delivery.payload,
"delivery_id": delivery.id,
});
let body = serde_json::to_string(&envelope)?;
// 发送 HTTP POST
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| crate::error::SaasError::Internal(format!("reqwest client 创建失败: {}", e)))?;
let result = client
.post(&delivery.url)
.header("Content-Type", "application/json")
.header("X-Webhook-Signature", format!("sha256={}", signature))
.header("X-Webhook-Delivery-ID", &delivery.id)
.header("X-Webhook-Event", &delivery.event)
.body(body)
.send()
.await;
match result {
Ok(resp) => {
let status = resp.status().as_u16() as i32;
let resp_body = resp.text().await.unwrap_or_default();
let success = (200..300).contains(&status);
let resp_body_truncated = if resp_body.len() > 1024 {
&resp_body[..1024]
} else {
&resp_body
};
crate::webhook::service::record_delivery_result(
db,
&delivery.id,
Some(status),
Some(resp_body_truncated),
success,
).await?;
if success {
tracing::debug!(
delivery_id = %delivery.id,
url = %delivery.url,
status = status,
"Webhook delivered successfully"
);
} else {
tracing::warn!(
delivery_id = %delivery.id,
url = %delivery.url,
status = status,
"Webhook target returned non-2xx status"
);
}
}
Err(e) => {
tracing::warn!(
delivery_id = %delivery.id,
url = %delivery.url,
"Webhook HTTP request failed: {}", e
);
crate::webhook::service::record_delivery_result(
db,
&delivery.id,
None,
Some(&e.to_string()),
false,
).await?;
}
}
Ok(())
}
/// 计算 HMAC-SHA256 签名
fn compute_hmac_signature(secret: &str, payload: &str) -> String {
let decoded_secret = hex::decode(secret).unwrap_or_else(|_| secret.as_bytes().to_vec());
let mut mac = HmacSha256::new_from_slice(&decoded_secret)
.expect("HMAC can take any key size");
mac.update(payload.as_bytes());
let result = mac.finalize();
hex::encode(result.into_bytes())
}