hujiarui 1 hónapja
szülő
commit
c3a978fda1

+ 23 - 5
booker_builtin.py

@@ -86,13 +86,20 @@ class BuiltinBookerGCO:
                 now = time.time()
                 for apt_type in self.m_cfg.appointment_types:
                     redis_key = self._get_redis_key(apt_type.routing_key)
-                    if not self.redis_client.get(redis_key):
+                    raw_data = self.redis_client.get(redis_key)
+                    if not raw_data:
                         continue
                     
-                    data = json.loads(self.redis_client.get(redis_key))
-                    query_result = VSQueryResult.model_validate(data['query_result'])
-                    query_result.apt_type = AppointmentType.model_validate(data['apt_type'])
+                    try:
+                        data = json.loads(raw_data)
+                        query_result = VSQueryResult.model_validate(data['query_result'])
+                        query_result.apt_type = AppointmentType.model_validate(data['apt_type'])
+                    except Exception as parse_err:
+                        self._log(f"Data parsing error for {redis_key}: {parse_err}. Deleting corrupted signal.")
+                        self.redis_client.delete(redis_key)
+                        continue
                     
+                    matching_tasks = []
                     with self.m_lock:
                         for task in self.m_tasks:
                             if now < task.next_run or not task.book_allowed:
@@ -102,7 +109,18 @@ class BuiltinBookerGCO:
                             
                             self._log(f"🚀 Triggering BOOK for {apt_type.routing_key}")
                             task.next_run = now + self.m_cfg.booker.booking_cooldown
-                            ThreadPool.getInstance().enqueue(self._execute_book_job, task, query_result)
+                            matching_tasks.append(task)
+                    
+                    if matching_tasks:
+                        threads = []
+                        for task in matching_tasks:
+                            self._log(f"🚀 Triggering BOOK for {apt_type.routing_key}")
+                            t = threading.Thread(target=self._execute_book_job, args=(task, query_result))
+                            threads.append(t)
+                            t.start()
+                        
+                        for t in threads:
+                            t.join() 
             except Exception as e:
                 self._log(f"Trigger loop error: {e}")
                 time.sleep(2)

+ 44 - 25
booker_order.py

@@ -54,28 +54,28 @@ class OrderBookerGCO:
     def _get_redis_key(self, routing_key: str) -> str:
         return f"vs:signal:{routing_key}"
 
-    def _safe_return_task(self, task_id: int, reason: str = ""):
+    def _safe_return_task(self, order_id: str, reason: str = ""):
         """安全地将订单归还给云端队列,防止复活已被取消或已抢成功的订单"""
-        if not task_id:
+        if not order_id:
             return
             
         try:
-            task_data = VSCloudApi.Instance().get_vas_task(task_id)
+            task_data = VSCloudApi.Instance().get_vas_task(order_id)
             if not task_data:
-                self._log(f"Task {task_id} not found in cloud, cannot return.")
+                self._log(f"Task {order_id} not found in cloud, cannot return.")
                 return
-                
+            task_id = task_data['id']
             current_status = task_data.get('status', '')
             # 如果订单已经被客户取消,或者已经成功,绝对不能还回队列!
             if current_status in ['cancelled', 'grabbed', 'success']:
-                self._log(f"Task {task_id} is already '{current_status}'. Skipping return.")
+                self._log(f"Task {order_id} is already '{current_status}'. Skipping return.")
                 return
                 
-            self._log(f"Returning task {task_id} to queue. Reason: {reason}")
+            self._log(f"Returning task {order_id} to queue. Reason: {reason}")
             VSCloudApi.Instance().return_vas_task_to_queue(task_id)
             
         except Exception as ex:
-            self._log(f"Failed to safely return task {task_id}: {ex}")
+            self._log(f"Failed to safely return task for order_id {order_id}: {ex}")
 
     def _maintain_loop(self):
         self._log("Maintain loop started.")
@@ -121,12 +121,19 @@ class OrderBookerGCO:
                 now = time.time()
                 for apt_type in self.m_cfg.appointment_types:
                     redis_key = self._get_redis_key(apt_type.routing_key)
-                    if not self.redis_client.get(redis_key): continue
-                    
-                    data = json.loads(self.redis_client.get(redis_key))
-                    query_result = VSQueryResult.model_validate(data['query_result'])
-                    query_result.apt_type = AppointmentType.model_validate(data['apt_type'])
+                    raw_data = self.redis_client.get(redis_key)
+                    if not raw_data:
+                        continue
+                    try:
+                        data = json.loads(raw_data)
+                        query_result = VSQueryResult.model_validate(data['query_result'])
+                        query_result.apt_type = AppointmentType.model_validate(data['apt_type'])
+                    except Exception as parse_err:
+                        self._log(f"Data parsing error for {redis_key}: {parse_err}. Deleting corrupted signal.")
+                        self.redis_client.delete(redis_key)
+                        continue
                     
+                    matching_tasks = []
                     with self.m_lock:
                         for task in self.m_tasks:
                             if now < task.next_run or not task.book_allowed:
@@ -134,27 +141,38 @@ class OrderBookerGCO:
                             if apt_type.routing_key not in task.acceptable_routing_keys:
                                 continue
                             
-                            self._log(f"🚀 Triggering BOOK for {apt_type.routing_key}")
                             task.next_run = now + self.m_cfg.booker.booking_cooldown
-                            ThreadPool.getInstance().enqueue(self._execute_book_job, task, query_result)
+                            matching_tasks.append(task)
+                            
+                    if matching_tasks:
+                        threads = []
+                        for task in matching_tasks:
+                            self._log(f"🚀 Triggering BOOK for {apt_type.routing_key} | Order Ref: {task.task_ref}")
+                            t = threading.Thread(target=self._execute_book_job, args=(task, query_result))
+                            threads.append(t)
+                            t.start()
+                        
+                        for t in threads:
+                            t.join() 
+                    
             except Exception as e:
                 self._log(f"Trigger loop error: {e}")
                 time.sleep(2)
 
     def _execute_book_job(self, task: Task, query_result: VSQueryResult):
-        task_id = task.task_ref
-        if not task_id:
+        order_id = task.task_ref
+        if not order_id:
             return
 
         try:
-            task_data = VSCloudApi.Instance().get_vas_task(task_id)
+            task_data = VSCloudApi.Instance().get_vas_task(order_id)
             if not task_data or task_data.get('status') in ['grabbed', 'cancelled']:
-                self._log(f"Bound Task {task_id} is no longer valid or already processed. Removing instance.")
+                self._log(f"Bound Task {order_id} is no longer valid or already processed. Removing instance.")
                 with self.m_lock:
                     if task in self.m_tasks: self.m_tasks.remove(task)
                 return
             
-            order_id = task_data.get('order_id')
+            task_id = task_data.get('id')
             user_input = task_data.get('user_inputs', {})
 
             book_res = task.instance.book(query_result, user_input)
@@ -201,7 +219,7 @@ class OrderBookerGCO:
             
         def _job():
             success = False
-            task_id = None
+            order_id = None
             try:
                 queue_name = f"auto.{target_routing_key}"
                 task_data = VSCloudApi.Instance().get_vas_task_pop(queue_name)
@@ -209,6 +227,7 @@ class OrderBookerGCO:
                     return 
                 
                 task_id = task_data['id']
+                order_id = task_data['order_id']
                 user_inputs = task_data.get('user_inputs', {})
                 
                 plg_cfg = VSPlgConfig()
@@ -242,9 +261,9 @@ class OrderBookerGCO:
                             instance=instance,
                             qw_cfg=self.m_cfg.query_wait,
                             next_run=time.time(), 
-                            task_ref=task_id,
+                            task_ref=order_id,
                             acceptable_routing_keys=acceptable_keys, 
-                            ource_queue=target_routing_key,
+                            source_queue=target_routing_key,
                             book_allowed=True
                         )
                     )
@@ -261,7 +280,7 @@ class OrderBookerGCO:
                     self.m_pending_order_by_queue[target_routing_key] = max(0, self.m_pending_order_by_queue[target_routing_key] - 1)
                 
                 # 创建/登录失败,调用安全归还函数
-                if not success and task_id is not None:
-                    self._safe_return_task(task_id, reason="Instance spawn/login failed")
+                if not success and order_id is not None:
+                    self._safe_return_task(order_id, reason="Instance spawn/login failed")
                     
         ThreadPool.getInstance().enqueue(_job)

+ 4 - 1
plugins/grc_plugin.py

@@ -42,7 +42,10 @@ class GrcPlugin(IVSPlg):
         self.free_config = config.free_config or {}
         
     def keep_alive(self):
-        pass
+        home_page = "https://www.supersaas.com/schedule/GreekEmbassyInDublin/Visas"
+        resp = self._perform_request("GET", home_page)
+        if f'Signed in as {self.config.account.username.lower()}' not in resp.text:
+            self.is_healthy = False
 
     def health_check(self) -> bool:
         if not self.is_healthy:

+ 49 - 41
plugins/tls_plugin.py

@@ -368,7 +368,6 @@ class TlsPlugin(IVSPlg):
         apt_config = self.free_config.get('apt_config', {})
         group_num = self.travel_group['group_number']
         
-        available_dates = [da.date for da in slot_info.availability]
         exp_start = user_inputs.get('expected_start_date', '')
         exp_end = user_inputs.get('expected_end_date', '')
         support_pta = user_inputs.get('support_pta', True)
@@ -376,33 +375,51 @@ class TlsPlugin(IVSPlg):
         target_labels = ['']
         if support_pta:
             target_labels.append('pta')
+
+        # 获取所有可用的日期字符串用于过滤
         available_dates_str = [
             da.date.strftime("%Y-%m-%d")
-            for da in slot_info.availability
+            for da in slot_info.availability if da.date
         ]
-        valid_dates = self._filter_dates(available_dates_str, exp_start, exp_end)
-        if not valid_dates:
+        
+        # 1. 过滤出符合用户日期范围要求的日期
+        valid_dates_list = self._filter_dates(available_dates_str, exp_start, exp_end)
+        if not valid_dates_list:
             raise NotFoundError(message="No dates match user constraints")
         
-        selected_date = None
-        selected_time = None
-        selected_label = None
-        
-        for d in valid_dates:
-            for da in slot_info.availability:
-                if da.date == d:
-                    for t in da.times:
-                        if t.label in target_labels:
-                            selected_date = d
-                            selected_time = t
-                            selected_label = t.label
-                            break
-            if selected_date: break
+        # --- 修改部分:计算所有可行的 slot ---
+        all_possible_slots = []
+        
+        for da in slot_info.availability:
+            if not da.date:
+                continue
+                
+            date_str = da.date.strftime("%Y-%m-%d")
             
-        if not selected_date:
-             raise NotFoundError(message="No suitable slot found")
-
-        # 2. 解决 ReCaptcha V3 (Action: book)
+            # 如果该日期在用户过滤后的合法日期列表中
+            if date_str in valid_dates_list:
+                for t in da.times:
+                    # 检查标签是否符合要求
+                    if t.label in target_labels:
+                        all_possible_slots.append({
+                            "date": date_str,
+                            "time_obj": t,
+                            "label": t.label
+                        })
+        
+        if not all_possible_slots:
+            raise NotFoundError(message="No suitable slot found (after label filtering)")
+
+        # 随机选择一个 slot
+        selected_slot = random.choice(all_possible_slots)
+        selected_date = selected_slot["date"]
+        selected_time = selected_slot["time_obj"]  # 这是一个 TimeSlot 对象
+        selected_label = selected_slot["label"]
+
+        self._log(f"Found {len(all_possible_slots)} valid slots. Randomly selected: {selected_date} {selected_time.time}")
+        # --- 修改结束 ---
+
+        # 2. 解决 ReCaptcha V3
         page_url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking?location={apt_config["code"]}&month={selected_date[:7]}'
         
         api_token = self.free_config.get("capsolver_key", "")
@@ -412,20 +429,16 @@ class TlsPlugin(IVSPlg):
             "action": "book", 
             "siteKey": "6LcTpXcfAAAAAM3VojNhyV-F1z92ADJIvcSZ39Y9",
             "apiToken": api_token,
-            "proxy": self._get_proxy_url() # ProxyLess
+            "proxy": self._get_proxy_url() 
         }
         g_token = self._solve_recaptcha(rc_params)
 
         # 3. 构造 Next.js Payload
-        # 注意:在 JS 中构造 FormData 比在 Python 中拼 Multipart 更容易且不易出错
-        ACTION_ID = "60d0616946df1fc4e7c094ca6a7a04f134d0be3d53"
+        ACTION_ID = "6043cfd107081bc817cbb11a8c0db17d3a063401be"
         url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking'
         
-        # State Tree 字符串
         router_state = '%5B%22%22%2C%7B%22children%22%3A%5B%5B%22lang%22%2C%22en-us%22%2C%22d%22%5D%2C%7B%22children%22%3A%5B%5B%22groupId%22%2C%22'+str(group_num)+'%22%2C%22d%22%5D%2C%7B%22children%22%3A%5B%22workflow%22%2C%7B%22children%22%3A%5B%22appointment-booking%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%2Ctrue%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%2Ctrue%5D%7D%2Cnull%2Cnull%5D'
 
-        # 构造 JS 代码执行 fetch
-        # 使用 FormData 对象来处理 multipart
         js_script = f"""
         const url = "{url}";
         const formData = new FormData();
@@ -470,29 +483,24 @@ class TlsPlugin(IVSPlg):
         resp = BrowserResponse(res_dict)
 
         # 4. 结果判定
-        # Next.js Server Action 重定向通常是 303,但 fetch 可能会自动跟随
-        # 如果 fetch 跟随了,url 会变;如果没跟随(Redirect mode: manual),status 是 303
-        
         if resp.status_code == 303 or (resp.status_code == 200 and "appointment-confirmation" in resp.url):
-             self._log(f"Booking Success! URL: {resp.url}")
-             res.success = True
-             res.book_date = selected_date
-             res.book_time = selected_time
-             return res
+            self._log(f"Booking Success! URL: {resp.url}")
+            res.success = True
+            res.book_date = selected_date
+            res.book_time = selected_time
+            return res
 
         if resp.status_code == 200:
             if "APPOINTMENT_LIMIT_REACHED" in resp.text:
-                 self._log("Failed: Appointment Limit Reached")
+                self._log("Failed: Appointment Limit Reached")
             elif "Invalid captcha" in resp.text:
-                 self._log("Failed: Invalid Captcha")
+                self._log("Failed: Invalid Captcha")
             else:
-                 self._log(f"Booking Failed (Unknown 200): {resp.text[:200]}")
+                self._log(f"Booking Failed (Unknown 200): {resp.text[:200]}")
         else:
             self._log(f"Booking Failed. Status: {resp.status_code}")
 
         return res
-
-    # --- 辅助方法 ---
     
     def _get_proxy_url(self):
         # 构造代理

+ 6 - 6
plugins/vfs_plugin.py

@@ -1185,9 +1185,9 @@ class VfsPlugin(IVSPlg):
             "urn": "",
             "arn": "",
             "loginUser": self.config.account.username,
-            "firstName": str(user_inputs.get("first_name", "")).upper(),
+            "firstName": str(user_inputs.get("first_name", "")).strip().upper(),
             "middleName": "",
-            "lastName": str(user_inputs.get("last_name", "")).upper(),
+            "lastName": str(user_inputs.get("last_name", "")).strip().upper(),
             "employerFirstName": "",
             "employerLastName": "",
             "salutation": "",
@@ -1197,13 +1197,13 @@ class VfsPlugin(IVSPlg):
             "dateOfApplication": None,
             "selectedSubvisaCategory": None,
             "gender": gender_code,
-            "contactNumber": str(user_inputs.get("phone", "")).lstrip("0"),
+            "contactNumber": str(user_inputs.get("phone", "")).strip().lstrip("0"),
             "dialCode": dial_code,
             "employerContactNumber": "",
             "employerDialCode": "",
-            "emailId": str(user_inputs.get("alias_email", "")).upper(),
+            "emailId": str(user_inputs.get("alias_email", "")).strip().upper(),
             "employerEmailId": "",
-            "passportNumber": str(user_inputs.get("passport_no", "")).upper(),
+            "passportNumber": str(user_inputs.get("passport_no", "")).strip().upper(),
             "confirmPassportNumber": "",
             "passportExpirtyDate": ppt_exp,
             "dateOfBirth": dob,
@@ -1250,7 +1250,7 @@ class VfsPlugin(IVSPlg):
         }
 
         if enable_ref:
-            applicant["referenceNumber"] = str(user_inputs.get("cover_letter_id", ""))
+            applicant["referenceNumber"] = str(user_inputs.get("cover_letter_id", "")).strip()
         else:
             applicant["referenceNumber"] = None
 

+ 1 - 3
requirements.txt

@@ -3,7 +3,7 @@ bs4
 redis
 cryptography
 curl-cffi>=0.7.0
-ddddocr==1.4.11
+ddddocr
 fastapi
 numpy
 pydantic
@@ -13,7 +13,5 @@ uvicorn
 psutil
 loguru
 mitmproxy>=10.0.0
-# 建议安装 CPU 版本的 torch 以节省约 2GB 空间,
-# 如果必须用 GPU 版,请直接写 torch torchvision
 torch --index-url https://download.pytorch.org/whl/cpu
 torchvision --index-url https://download.pytorch.org/whl/cpu

+ 14 - 3
toolkit/vs_cloud_api.py

@@ -76,10 +76,21 @@ class VSCloudApi:
         else:
             raise BizLogicError(message=f"Get vas task pop biz error: {result.get('message')}")
     
-    def get_vas_task(self, task_id: int) -> dict:
+    def get_vas_task(self, order_id: str) -> dict:
         # 例如:请求 GET /api/v1/tasks/{task_id}
-        # 返回包含 user_inputs 的完整字典数据
-        pass
+        # curl -X 'GET' \
+        # 'http://45.137.220.138:8888/api/vas/task/get_by_order?order_id=ORD-20260306212306-7e604df8' \
+        # -H 'accept: application/json' \
+        # -H 'Authorization: Bearer tok_c9be86aa78274939a3c008db31ce9d22'
+        url = f"{self.base_url}/api/vas/task/get_by_order"
+        params = {"order_id": order_id}
+        headers = self._get_headers()
+        resp = self._perform_request('GET', url, params=params, headers=headers)
+        result = resp.json()
+        if result.get("code") == 0:
+            return result.get("data", {})
+        else:
+            raise BizLogicError(message=f"Get Task for order_id={order_id} error: {result.get('message')}")
 
     def update_vas_task(self, 
                         task_id: int, 

+ 67 - 27
vfs_registration_bot.py

@@ -18,7 +18,6 @@ from cryptography.hazmat.backends import default_backend
 from DrissionPage import ChromiumPage, ChromiumOptions
 from vs_types import RateLimiteddError, BizLogicError 
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
-from toolkit.vs_cloud_api import VSCloudApi
 
 # --- 配置日志 ---
 logging.basicConfig(
@@ -278,7 +277,7 @@ class VFSRegistrationBot:
         logger.info("Attempting to refresh Turnstile token...")
         try:
             self.page.run_js('try{window.turnstile.reset()}catch(e){}')
-            cf_bypasser = CloudflareBypasser(self.page, log=False)
+            cf_bypasser = CloudflareBypasser(self.page, log=True)
             
             for i in range(30):
                 time.sleep(1)
@@ -292,7 +291,7 @@ class VFSRegistrationBot:
                 
                 if i > 5:
                     try:
-                        cf_bypasser.click_verification_button(is_dfs=(i > 15))
+                        cf_bypasser.click_verification_button(is_dfs=False)
                     except:
                         pass
         except Exception as e:
@@ -306,27 +305,66 @@ class VFSRegistrationBot:
         
         master_email = self.config.get("master_email", "visafly666@gmail.com")
         
+        # 配置代理
+        proxies = {
+            "http": self.proxy_url,
+            "https": self.proxy_url,
+        } if self.proxy_url else None
+
         while time.time() - start_time < max_wait_sec:
             try:
                 utc_now_str = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
-                # 调用邮件 API
-                content = VSCloudApi.Instance().fetch_mail_content(
-                    master_email, 
-                    'donotreply at vfsglobal.com',
-                    username, 
-                    'Welcome', 
-                    'ActivateAccount', 
-                    utc_now_str, 
-                    expiry=60
-                )
                 
-                if content:
-                    soup = BeautifulSoup(content, "html.parser")
-                    link = soup.find("a", string="ActivateAccount")
-                    if link:
-                        raw_link = link["href"]
-                        clean_url = raw_link.replace(" ", "").replace("\n", "").replace("\r", "").strip()
-                        return clean_url
+                params = {
+                    "email": master_email,
+                    "sender": 'donotreply at vfsglobal.com',
+                    "recipient": username,
+                    "subjectKeywords": 'Welcome',
+                    "bodyKeywords": 'ActivateAccount',
+                    "sentDate": utc_now_str,
+                    "expiry": str(60)
+                }
+        
+                url = f"https://visafly.top/api/email-authorizations/fetch"
+                headers =  {
+                    "Authorization": "Bearer tok_e946329a60ff45ba807f3f41b0e8b7fc",
+                    "Content-Type": "application/json",
+                    "Accept": "application/json, text/plain, */*"
+                }
+
+                # 发送请求,添加代理和超时
+                # 注意:如果 body 为空,建议传 json={} 而不是 data=""
+                resp = requests.post(url, headers=headers, params=params, json={}, proxies=proxies, timeout=60)
+                
+                # --- 关键改进:先检查状态码 ---
+                if resp.status_code != 200:
+                    logger.warning(f"Email API returned status {resp.status_code}. Body: {resp.text[:100]}")
+                    time.sleep(15)
+                    continue
+
+                # --- 关键改进:安全解析 JSON ---
+                try:
+                    result = resp.json()
+                except json.JSONDecodeError:
+                    logger.error(f"Failed to decode JSON. Response was: {resp.text[:200]}")
+                    time.sleep(15)
+                    continue
+
+                if result.get('code') != 0:
+                    # 这里的错误通常是业务逻辑错误(如:邮件还没到)
+                    logger.debug(f"API Message: {result.get('message')}")
+                else:
+                    data = result.get('data', {})
+                    content = data.get('body', "")
+        
+                    if content:
+                        soup = BeautifulSoup(content, "html.parser")
+                        link = soup.find("a", string="ActivateAccount")
+                        if link:
+                            raw_link = link["href"]
+                            clean_url = raw_link.replace(" ", "").replace("\n", "").replace("\r", "").strip()
+                            return clean_url
+                
             except Exception as e:
                 logger.warning(f"Error fetching email: {e}")
             
@@ -378,13 +416,14 @@ class VFSRegistrationBot:
                     ele = self.page.ele('@name=cf-turnstile-response')
                     if ele and ele.value:
                         cf_token = ele.value
-                        break
+                        if cf_token:
+                            break
                 except:
                     pass
                 
                 # 尝试点击
                 try:
-                    cf_bypasser.click_verification_button()
+                    cf_bypasser.click_verification_button(is_dfs=False)
                 except:
                     pass
             
@@ -496,15 +535,16 @@ def generate_account_details(config, pool_name):
 def main():
     # 配置
     config = {
-        "pool_name": "gb_at",
+        "pool_name": "ie.at.booker",
+        "account_prefix": "ie_at",
         "email_domain": "gmail-app", 
         "master_email": "visafly666@gmail.com",
         "proxy_url": "http://127.0.0.1:7890",
-        "target_count": 30,
+        "target_count": 5,
         "phone_country_code": 353,
-        "country_code": "gbr",
+        "country_code": "irl",
         "mission_code": "aut",
-        "website": "https://visa.vfsglobal.com/gbr/en/aut/register",
+        "website": "https://visa.vfsglobal.com/irl/en/aut/register",
     }
     
     bot = VFSRegistrationBot(config)
@@ -513,7 +553,7 @@ def main():
     print(">>> Starting Registration Bot <<<")
     
     while len(success_accounts) < config['target_count']:
-        account = generate_account_details(config, config['pool_name'])
+        account = generate_account_details(config, config['account_prefix'])
         logger.info(f"Processing Account: {account['username']}")
         
         is_success = bot.register(account)

+ 1 - 1
vs_types.py

@@ -199,7 +199,7 @@ class Task(BaseModel):
     next_run: float = 0.0      
     book_allowed: bool = True
     # 订单模式下,保存绑定的 Task ID
-    task_ref: Optional[int] = None
+    task_ref: Optional[str] = None
     # 允许预订的 routing_key 列表
     acceptable_routing_keys: List[str] = Field(default_factory=list)
     # 来源标识(用于配额统计)