diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index f830548..50e43aa 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -49,6 +49,10 @@ num_cpus = "1.16" tokio-tungstenite = "0.20" futures-util = "0.3" rand = "0.8" +hmac = "0.12.1" +sha2 = "0.10.9" +hex = "0.4.3" +url = "2.5.4" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["sysinfoapi"] } diff --git a/apps/desktop/src-tauri/src/business/services/volcano_video_service.rs b/apps/desktop/src-tauri/src/business/services/volcano_video_service.rs index 2dcf2a3..71c203d 100644 --- a/apps/desktop/src-tauri/src/business/services/volcano_video_service.rs +++ b/apps/desktop/src-tauri/src/business/services/volcano_video_service.rs @@ -4,6 +4,11 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use tracing::{error, info, warn}; use uuid::Uuid; +use chrono::{DateTime, Utc}; +use hmac::{Hmac, Mac}; +use sha2::{Digest, Sha256}; +use hex; +use url::Url; use crate::data::models::video_generation_record::{ VideoGenerationRecord, CreateVideoGenerationRequest, VideoGenerationQuery @@ -227,12 +232,10 @@ impl VolcanoVideoService { let api_url = "https://visual.volcengineapi.com?Action=CVSubmitTask&Version=2022-08-31"; // 构建认证头 - let timestamp = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() - .to_string(); - let auth_header = self.build_auth_header("POST", api_url, ×tamp, &request_body)?; + let now = Utc::now(); + let timestamp = now.format("%Y%m%dT%H%M%SZ").to_string(); + let date = now.format("%Y%m%d").to_string(); + let auth_header = self.build_auth_header("POST", api_url, ×tamp, &date, &request_body)?; let response = self.http_client .post(api_url) @@ -340,12 +343,10 @@ impl VolcanoVideoService { let api_url = "https://visual.volcengineapi.com?Action=CVGetResult&Version=2022-08-31"; // 构建认证头 - let timestamp = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() - .to_string(); - let auth_header = self.build_auth_header("POST", api_url, ×tamp, &request_body)?; + let now = Utc::now(); + let timestamp = now.format("%Y%m%dT%H%M%SZ").to_string(); + let date = now.format("%Y%m%d").to_string(); + let auth_header = self.build_auth_header("POST", api_url, ×tamp, &date, &request_body)?; let response = self.http_client .post(api_url) @@ -372,11 +373,28 @@ impl VolcanoVideoService { } /// 构建火山云API认证头 - fn build_auth_header(&self, _method: &str, _url: &str, _timestamp: &str, _body: &T) -> Result { - // 简化的认证实现,实际项目中需要按照火山云文档实现完整的签名算法 - // 这里先返回基本的认证头格式 - let auth_string = format!("HMAC-SHA256 Credential={}, SignedHeaders=content-type;host;x-date, Signature=placeholder", - self.vol_access_key); + fn build_auth_header(&self, method: &str, url: &str, timestamp: &str, date: &str, body: &T) -> Result { + // 1. 创建规范请求 (CanonicalRequest) + let canonical_request = self.create_canonical_request(method, url, timestamp, body)?; + + // 2. 创建待签名字符串 (StringToSign) + let credential_scope = format!("{}/cn-north-1/cv/request", date); + let string_to_sign = self.create_string_to_sign(timestamp, &credential_scope, &canonical_request)?; + + // 3. 派生签名密钥 (kSigning) + let signing_key = self.derive_signing_key(&self.vol_secret_key, date, "cn-north-1", "cv")?; + + // 4. 计算签名 (Signature) + let signature = self.calculate_signature(&signing_key, &string_to_sign)?; + + // 5. 构建Authorization头 + let auth_string = format!( + "HMAC-SHA256 Credential={}/{}, SignedHeaders=host;x-date, Signature={}", + self.vol_access_key, + credential_scope, + signature + ); + Ok(auth_string) } @@ -402,6 +420,120 @@ impl VolcanoVideoService { } Ok(()) } + + /// 创建规范请求 + fn create_canonical_request(&self, method: &str, url: &str, timestamp: &str, body: &T) -> Result { + // 解析URL获取host和path + let parsed_url = Url::parse(url)?; + let host = parsed_url.host_str().ok_or_else(|| anyhow!("无效的URL"))?; + let path = parsed_url.path(); + let query = parsed_url.query().unwrap_or(""); + + // 规范化查询字符串 + let canonical_query_string = self.canonicalize_query_string(query); + + // 规范化请求头 + let canonical_headers = format!("host:{}\nx-date:{}\n", host, timestamp); + + // 签名的请求头 + let signed_headers = "host;x-date"; + + // 计算请求体的哈希值 + let body_json = serde_json::to_string(body)?; + let body_hash = hex::encode(Sha256::digest(body_json.as_bytes())); + + // 构建规范请求 + let canonical_request = format!( + "{}\n{}\n{}\n{}\n{}\n{}", + method, + path, + canonical_query_string, + canonical_headers, + signed_headers, + body_hash + ); + + Ok(canonical_request) + } + + /// 规范化查询字符串 + fn canonicalize_query_string(&self, query: &str) -> String { + if query.is_empty() { + return String::new(); + } + + let mut params: Vec<(String, String)> = query + .split('&') + .filter_map(|param| { + let mut parts = param.splitn(2, '='); + let key = parts.next()?.to_string(); + let value = parts.next().unwrap_or("").to_string(); + Some((key, value)) + }) + .collect(); + + // 按参数名称的ASCII升序排序 + params.sort_by(|a, b| a.0.cmp(&b.0)); + + // 构建规范化查询字符串 + params + .into_iter() + .map(|(key, value)| format!("{}={}", key, value)) + .collect::>() + .join("&") + } + + /// 创建待签名字符串 + fn create_string_to_sign(&self, timestamp: &str, credential_scope: &str, canonical_request: &str) -> Result { + let canonical_request_hash = hex::encode(Sha256::digest(canonical_request.as_bytes())); + + let string_to_sign = format!( + "HMAC-SHA256\n{}\n{}\n{}", + timestamp, + credential_scope, + canonical_request_hash + ); + + Ok(string_to_sign) + } + + /// 派生签名密钥 + fn derive_signing_key(&self, secret_key: &str, date: &str, region: &str, service: &str) -> Result> { + type HmacSha256 = Hmac; + + // kDate = HMAC(kSecret, Date) + let mut mac = HmacSha256::new_from_slice(secret_key.as_bytes())?; + mac.update(date.as_bytes()); + let k_date = mac.finalize().into_bytes(); + + // kRegion = HMAC(kDate, Region) + let mut mac = HmacSha256::new_from_slice(&k_date)?; + mac.update(region.as_bytes()); + let k_region = mac.finalize().into_bytes(); + + // kService = HMAC(kRegion, Service) + let mut mac = HmacSha256::new_from_slice(&k_region)?; + mac.update(service.as_bytes()); + let k_service = mac.finalize().into_bytes(); + + // kSigning = HMAC(kService, "request") + let mut mac = HmacSha256::new_from_slice(&k_service)?; + mac.update(b"request"); + let k_signing = mac.finalize().into_bytes(); + + Ok(k_signing.to_vec()) + } + + /// 计算签名 + fn calculate_signature(&self, signing_key: &[u8], string_to_sign: &str) -> Result { + type HmacSha256 = Hmac; + + let mut mac = HmacSha256::new_from_slice(signing_key)?; + mac.update(string_to_sign.as_bytes()); + let signature = mac.finalize().into_bytes(); + + Ok(hex::encode(signature)) + } } // 实现Clone trait以支持异步任务