tdm_server_rust/service/
oss_service.rs1use crate::{
6 app::AppState,
7 entity::oss::{OssCredential, OssDto},
8 error::{ApiResult, AppError},
9 repository::oss_repo::OssRepository,
10};
11use bytes::Bytes;
12
13pub struct OssService;
15
16impl OssService {
17 #[tracing::instrument(skip_all, level = "debug")]
24 pub async fn upsert_oss(
25 state: &AppState,
26 dto: OssDto,
27 member_id: i32,
28 ) -> ApiResult<()> {
29 let cfg = (*state.config).clone();
30 OssRepository::new(state.db.clone(), cfg)
31 .upsert_oss(&dto, member_id)
32 .await?;
33 Ok(())
34 }
35
36 #[tracing::instrument(skip_all, level = "debug")]
47 pub async fn get_upload_credential(
48 state: &AppState,
49 episode_id: i32,
50 post_name: String,
51 filename: String,
52 ) -> ApiResult<OssCredential> {
53 let cfg = (*state.config).clone();
54 OssRepository::new(state.db.clone(), cfg)
55 .get_upload_credential(episode_id, &post_name, &filename)
56 .await
57 }
58
59 #[tracing::instrument(skip_all, level = "debug")]
61 pub async fn get_image_upload_credential(
62 state: &AppState,
63 image_type: String,
64 filename: String,
65 ) -> ApiResult<OssCredential> {
66 let cfg = (*state.config).clone();
67 OssRepository::new(state.db.clone(), cfg)
68 .get_image_upload_credential(&image_type, &filename)
69 .await
70 }
71
72 #[tracing::instrument(skip_all, level = "debug")]
82 pub async fn get_download_credential(
83 state: &AppState,
84 episode_id: i32,
85 post_name: String,
86 ) -> ApiResult<OssCredential> {
87 let cfg = (*state.config).clone();
88 OssRepository::new(state.db.clone(), cfg)
89 .get_download_credential(episode_id, &post_name)
90 .await
91 }
92
93 #[tracing::instrument(skip_all, level = "debug")]
103 pub async fn download_redirect_target(
104 state: &AppState,
105 episode_id: i32,
106 post_name: String,
107 ) -> ApiResult<(String, String)> {
108 let cred = Self::get_download_credential(state, episode_id, post_name).await?;
109 let filename = filename_from_credential(&cred);
110 Ok((cred.presigned_url, filename))
111 }
112
113 #[tracing::instrument(skip_all, level = "debug")]
127 pub async fn download_file_proxy(
128 state: &AppState,
129 episode_id: i32,
130 post_name: String,
131 ) -> ApiResult<(String, Bytes)> {
132 let cred = Self::get_download_credential(state, episode_id, post_name.clone()).await?;
133 let filename = filename_from_credential(&cred);
134
135 let resp = crate::telemetry::traced_http_execute(
136 &state.http_client,
137 state.http_client.get(&cred.presigned_url),
138 )
139 .await
140 .map_err(|e| AppError::Internal(format!("拉取 OSS 文件失败: {e}")))?;
141 if !resp.status().is_success() {
142 return Err(AppError::Oss {
143 code: None,
144 msg: format!("OSS 下载失败: HTTP {}", resp.status()),
145 });
146 }
147 let bytes = resp
148 .bytes()
149 .await
150 .map_err(|e| AppError::Internal(format!("读取 OSS 文件失败: {e}")))?;
151 crate::utils::agent_debug::log(
152 "H1",
153 "oss_service.rs:download_file_proxy",
154 "oss_proxy_ok",
155 serde_json::json!({
156 "episodeId": episode_id,
157 "postName": post_name,
158 "filename": filename,
159 "bytes": bytes.len()
160 }),
161 );
162 Ok((filename, bytes))
163 }
164}
165
166fn filename_from_credential(cred: &OssCredential) -> String {
168 cred.original_filename
169 .as_ref()
170 .filter(|s| !s.trim().is_empty())
171 .cloned()
172 .or_else(|| {
173 cred.object_key
174 .as_ref()
175 .and_then(|k| k.split('/').next_back())
176 .map(|s| s.to_string())
177 })
178 .unwrap_or_else(|| "download.bin".to_string())
179}