1use crate::common::interface::{
2 DataMiddlewareHandle, DataStoreMiddlewareHandle, ModuleTrait, SyncBoxStream,
3};
4use crate::common::model::login_info::LoginInfo;
5use crate::common::model::message::TaskOutputEvent;
6use crate::common::model::scope::{AccountInfo, PlatformInfo};
7use crate::common::model::{Cookies, Headers, ModuleConfig, Request, Response};
8use crate::engine::task::module_dag_processor::ModuleDagProcessor;
9use crate::errors::RequestError;
10use crate::errors::Result;
11use futures::StreamExt;
12use log::warn;
13use serde::ser::SerializeStruct;
14use serde::{Serialize, Serializer};
15use serde_json::Map;
16use std::sync::Arc;
17use uuid::Uuid;
18
19#[derive(Clone)]
24pub struct Module {
25 pub config: Arc<ModuleConfig>,
27 pub account: AccountInfo,
29 pub platform: PlatformInfo,
31 pub error_times: u32,
33 pub finished: bool,
35 pub data_middleware: Vec<String>,
37 pub download_middleware: Vec<String>,
39 pub module: Arc<dyn ModuleTrait>,
41 pub locker: bool,
43 pub locker_ttl: u64,
45 pub processor: ModuleDagProcessor,
47 pub run_id: Uuid,
49 pub prefix_request: Uuid,
51 pub pending_ctx: Option<crate::common::model::ExecutionMark>,
53 pub bound_task_meta: Option<Map<String, serde_json::Value>>,
55 pub bound_login_info: Option<LoginInfo>,
57}
58impl Module {
59 pub fn bind_task_context(
61 &mut self,
62 task_meta: Map<String, serde_json::Value>,
63 login_info: Option<LoginInfo>,
64 ) {
65 self.bound_task_meta = Some(task_meta);
66 self.bound_login_info = login_info;
67 }
68
69 pub fn runtime_task_context(&self) -> (Map<String, serde_json::Value>, Option<LoginInfo>) {
71 (
72 self.bound_task_meta.clone().unwrap_or_default(),
73 self.bound_login_info.clone(),
74 )
75 }
76
77 pub async fn generate(
82 &self,
83 task_meta: Map<String, serde_json::Value>,
84 login_info: Option<LoginInfo>,
85 ) -> Result<SyncBoxStream<'static, Request>> {
86 if self.module.should_login() && login_info.is_none() {
87 return Err(RequestError::NotLogin("module need login".into()).into());
88 }
89 let request_stream = self
90 .processor
91 .execute_generate(
92 self.config.clone(),
93 task_meta.clone(),
94 login_info.clone(),
95 self.pending_ctx.clone(),
96 Some(self.prefix_request),
97 )
98 .await?;
99
100 let module_name = self.module.name().clone();
101 let platform_name = self.platform.name.clone();
102 let download_middleware = self.download_middleware.clone();
103 let data_middleware = self.data_middleware.clone();
104 let account_name = self.account.name.clone();
105 let finished = self.finished;
106 let limit_id = self
107 .config
108 .get_config_value("limit_id")
109 .and_then(|v| v.as_str())
110 .map(|s| s.to_string());
111 let headers = self.module.headers().await;
112 let cookies = self.module.cookies().await;
113 let run_id = self.run_id;
114 let prefix_request = self.prefix_request;
115 let config = self.config.clone();
116 let stream = request_stream.map(move |mut request| {
117 if request.id.is_nil() {
118 request.id = Uuid::now_v7();
119 }
120 request.module = module_name.clone();
121 request.platform = platform_name.clone();
122 let mut merged_download_middleware = request.download_middleware.clone();
123 merged_download_middleware.extend(download_middleware.clone());
124 merged_download_middleware.sort();
125 merged_download_middleware.dedup();
126 request.download_middleware = merged_download_middleware;
127
128 let mut merged_data_middleware = request.data_middleware.clone();
129 merged_data_middleware.extend(data_middleware.clone());
130 merged_data_middleware.sort();
131 merged_data_middleware.dedup();
132 request.data_middleware = merged_data_middleware;
133 request.account = account_name.clone();
134 request.task_finished = finished;
135 request.run_id = run_id;
136 request.prefix_request = prefix_request;
137
138 if request.headers.is_empty() && !headers.is_empty() {
139 request = request.with_headers(headers.clone());
140 }
141 if !cookies.is_empty() {
142 request = request.with_cookies(cookies.clone());
143 }
144
145 if let Some(ref info) = login_info {
146 let cookies = Cookies::from(info);
147 let headers = Headers::from(info);
148 request.headers.merge(&headers);
149 request.cookies.merge(&cookies);
150 request = request.with_login_info(info);
151 }
152 request.limit_id = limit_id.clone().unwrap_or(request.module_id());
153 request = request
154 .with_module_config(&config)
155 .with_task_config(task_meta.clone());
156 if let Some(downloader) = config.get_config::<String>("downloader") {
157 request.downloader = downloader;
158 } else {
159 request.downloader = "request_downloader".to_string();
160 }
161 log::debug!(
162 "[Module] request prepared: account={} platform={} module={} url={} request_id={}",
163 request.account,
164 request.platform,
165 request.module,
166 request.url,
167 request.id
168 );
169 request
170 });
171 Ok(Box::pin(stream))
172 }
173
174 pub async fn add_step(&self) {
179 if let Err(e) = self.module.pre_process(Some(self.config.clone())).await {
180 warn!(
181 "module pre_process failed: account={} platform={} module={} error={}",
182 self.account.name,
183 self.platform.name,
184 self.module.name(),
185 e
186 );
187 }
188
189 use crate::engine::task::module_dag_orchestrator::ModuleDagOrchestrator;
190 let definition = ModuleDagOrchestrator
191 .build_definition(self.module.clone())
192 .await;
193 self.processor.init_from_definition(&definition).await;
194 }
195
196 pub async fn parser(
198 &self,
199 response: Response,
200 config: Option<Arc<ModuleConfig>>,
201 ) -> Result<TaskOutputEvent> {
202 let cfg_for_post = config.clone();
203
204 let mut data = self.processor.execute_parse(response, config).await?;
205
206 for d in data.data.iter_mut() {
208 d.module = self.module.name();
209 d.account = self.account.name.clone();
210 d.platform = self.platform.name.clone();
211 }
212
213 let no_next_task = data.parser_task.is_empty();
215 if no_next_task {
216 self.module.post_process(cfg_for_post).await?;
217 }
218
219 if data.stop.unwrap_or(false) {
225 self.processor.delete_session_for_run(self.run_id).await;
226 }
227
228 Ok(data)
229 }
230
231 pub fn id(&self) -> String {
233 format!(
234 "{}-{}-{}",
235 self.account.name,
236 self.platform.name,
237 self.module.name()
238 )
239 }
240}
241
242impl Serialize for Module {
243 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
244 where
245 S: Serializer,
246 {
247 let mut state = serializer.serialize_struct("Module", 8)?;
248 state.serialize_field("config", &self.config)?;
249 state.serialize_field("account", &self.account)?;
250 state.serialize_field("platform", &self.platform)?;
251 state.serialize_field("error_times", &self.error_times)?;
252 state.serialize_field("data_middleware", &self.data_middleware)?;
253 state.serialize_field("download_middleware", &self.download_middleware)?;
254 state.serialize_field("module", &self.module.name())?;
255 state.end()
256 }
257}
258
259pub struct ModuleEntity {
261 pub module_work: Arc<dyn ModuleTrait>,
262 pub download_middleware: Vec<Arc<dyn ModuleTrait>>,
263 pub data_middleware: Vec<DataMiddlewareHandle>,
264 pub store_middleware: Vec<DataStoreMiddlewareHandle>,
265}
266
267impl From<Arc<dyn ModuleTrait>> for ModuleEntity {
268 fn from(module: Arc<dyn ModuleTrait>) -> Self {
269 ModuleEntity {
270 module_work: module,
271 download_middleware: vec![],
272 data_middleware: vec![],
273 store_middleware: vec![],
274 }
275 }
276}
277impl ModuleEntity {
278 pub fn add_download_middleware(mut self, middleware: Arc<dyn ModuleTrait>) -> Self {
280 self.download_middleware.push(middleware);
281 self
282 }
283
284 pub fn add_data_middleware(mut self, middleware: DataMiddlewareHandle) -> Self {
286 self.data_middleware.push(middleware);
287 self
288 }
289
290 pub fn add_store_middleware(mut self, middleware: DataStoreMiddlewareHandle) -> Self {
292 self.store_middleware.push(middleware);
293 self
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use crate::cacheable::CacheService;
301 use crate::common::interface::ModuleTrait;
302 use crate::engine::task::module_dag_processor::ModuleDagProcessor;
303 use async_trait::async_trait;
304
305 struct LoginRequiredTestModule;
306
307 #[async_trait]
308 impl ModuleTrait for LoginRequiredTestModule {
309 fn should_login(&self) -> bool {
310 true
311 }
312
313 fn name(&self) -> String {
314 "login_required_test".to_string()
315 }
316
317 fn version(&self) -> i32 {
318 1
319 }
320
321 fn default_arc() -> Arc<dyn ModuleTrait>
322 where
323 Self: Sized,
324 {
325 Arc::new(Self)
326 }
327 }
328
329 fn build_test_module(module_impl: Arc<dyn ModuleTrait>) -> Module {
330 Module {
331 config: Arc::new(ModuleConfig::default()),
332 account: AccountInfo {
333 id: 1,
334 name: "acc".to_string(),
335 config: serde_json::json!({}),
336 },
337 platform: PlatformInfo {
338 id: 1,
339 name: "pf".to_string(),
340 config: serde_json::json!({}),
341 },
342 error_times: 0,
343 finished: false,
344 data_middleware: vec![],
345 download_middleware: vec![],
346 module: module_impl,
347 locker: false,
348 locker_ttl: 0,
349 processor: ModuleDagProcessor::new(
350 "acc-pf-login_required_test".to_string(),
351 Arc::new(CacheService::new("test".to_string(), None, None)),
352 Uuid::now_v7(),
353 60,
354 ),
355 run_id: Uuid::now_v7(),
356 prefix_request: Uuid::nil(),
357 pending_ctx: None,
358 bound_task_meta: None,
359 bound_login_info: None,
360 }
361 }
362
363 #[test]
364 fn bind_task_context_roundtrip() {
365 let mut module = build_test_module(Arc::new(LoginRequiredTestModule));
366 let mut meta = Map::new();
367 meta.insert("k".to_string(), serde_json::json!("v"));
368 let login = LoginInfo::default();
369 module.bind_task_context(meta.clone(), Some(login.clone()));
370
371 let (bound_meta, bound_login) = module.runtime_task_context();
372 assert_eq!(bound_meta.get("k"), Some(&serde_json::json!("v")));
373 assert_eq!(
374 bound_login.as_ref().map(|x| x.useragent.clone()),
375 Some(login.useragent)
376 );
377 }
378
379 #[tokio::test]
380 async fn generate_returns_not_login_error_when_login_required_and_missing() {
381 let module = build_test_module(Arc::new(LoginRequiredTestModule));
382 match module.generate(Map::new(), None).await {
383 Ok(_) => panic!("should fail without login info"),
384 Err(err) => {
385 let msg = err.to_string();
386 assert!(
387 msg.contains("module need login"),
388 "unexpected error message: {msg}"
389 );
390 }
391 }
392 }
393}