diff --git a/Utils/Dy/DyUtils.py b/Utils/Dy/DyUtils.py index cf52fec..2ff79bf 100644 --- a/Utils/Dy/DyUtils.py +++ b/Utils/Dy/DyUtils.py @@ -685,6 +685,12 @@ class DouYinMessageHandler: self.sign_engine_initialized = False self.sign_ctx = None self.js_engine = None + + # 🧹 启动时清理超过24小时的旧临时文件 + self._cleanup_old_temp_files(max_age_hours=24) + + # 🧹 设置定期清理任务(每6小时清理一次) + self._setup_periodic_cleanup() # 打印实例创建信息 print(f"[DY Handler] 创建实例 {self.instance_id} for store {store_id}") @@ -2079,8 +2085,9 @@ class DouYinMessageHandler: continue return None, None - # 保存到临时文件 - temp_dir = os.path.join(os.path.dirname(__file__), "temp_uploads") + # 保存到临时文件(使用系统临时目录,兼容打包环境) + import tempfile + temp_dir = os.path.join(tempfile.gettempdir(), "shuidrop_temp_uploads") os.makedirs(temp_dir, exist_ok=True) # 🔥 修复:智能提取文件扩展名 @@ -2113,7 +2120,7 @@ class DouYinMessageHandler: return None, None def _cleanup_temp_file(self, file_path): - """清理临时文件""" + """清理单个临时文件""" try: if file_path and os.path.exists(file_path): os.remove(file_path) @@ -2121,6 +2128,76 @@ class DouYinMessageHandler: except Exception as e: self._log(f"⚠️ [DY上传] 删除临时文件失败: {e}", "WARNING") + def _cleanup_old_temp_files(self, max_age_hours=24): + """ + 清理超过指定时间的旧临时文件 + + Args: + max_age_hours: 文件最大保留时间(小时),默认24小时 + """ + try: + import tempfile + import time + + temp_dir = os.path.join(tempfile.gettempdir(), "shuidrop_temp_uploads") + if not os.path.exists(temp_dir): + return + + current_time = time.time() + max_age_seconds = max_age_hours * 3600 + cleaned_count = 0 + total_size = 0 + + # 遍历临时目录 + for filename in os.listdir(temp_dir): + file_path = os.path.join(temp_dir, filename) + + # 跳过目录 + if not os.path.isfile(file_path): + continue + + try: + # 检查文件修改时间 + file_mtime = os.path.getmtime(file_path) + file_age = current_time - file_mtime + + # 如果文件超过保留时间,删除 + if file_age > max_age_seconds: + file_size = os.path.getsize(file_path) + os.remove(file_path) + cleaned_count += 1 + total_size += file_size + self._log(f"🗑️ [清理] 删除过期临时文件: {filename} (已存在{file_age/3600:.1f}小时)", "DEBUG") + + except Exception as e: + self._log(f"⚠️ [清理] 删除文件失败 {filename}: {e}", "WARNING") + continue + + if cleaned_count > 0: + self._log(f"✅ [清理] 已清理 {cleaned_count} 个过期临时文件,释放空间: {total_size/(1024*1024):.2f}MB", "INFO") + else: + self._log(f"✅ [清理] 无需清理(所有临时文件都在保留期内)", "DEBUG") + + except Exception as e: + self._log(f"⚠️ [清理] 清理旧临时文件失败: {e}", "WARNING") + + def _setup_periodic_cleanup(self): + """设置定期清理任务(每6小时清理一次超过24小时的文件)""" + import threading + + def periodic_cleanup_task(): + import time + while self.is_running: + # 每6小时清理一次 + time.sleep(6 * 3600) # 6小时 + if self.is_running: + self._cleanup_old_temp_files(max_age_hours=24) + + # 在后台线程中运行定期清理任务 + cleanup_thread = threading.Thread(target=periodic_cleanup_task, daemon=True, name="TempFileCleanup") + cleanup_thread.start() + self._log("🧹 [清理] 定期清理任务已启动(每6小时执行一次)", "DEBUG") + async def _get_upload_token(self, upload_type="image"): """获取上传Token(图片或视频)""" try: @@ -2209,14 +2286,15 @@ class DouYinMessageHandler: if not upload_id: return None - success = await self._upload_file_part(store_uri, authorization, image_path, step=2, upload_id=upload_id) + # 🔥 优化:只读取一次文件,计算CRC32并传递给step2和step3 + with open(image_path, 'rb') as f: + file_content = f.read() + crc32_hex = format(zlib.crc32(file_content), '08x') + + success = await self._upload_file_part(store_uri, authorization, image_path, step=2, upload_id=upload_id, file_content=file_content, crc=crc32_hex) if not success: return None - with open(image_path, 'rb') as f: - content = f.read() - crc32_hex = format(zlib.crc32(content), '08x') - success = await self._upload_file_part(store_uri, authorization, image_path, step=3, upload_id=upload_id, crc=crc32_hex) if not success: return None @@ -2299,8 +2377,12 @@ class DouYinMessageHandler: self._log(f"❌ [DY图片] {action}失败: {e}", "ERROR") return None - async def _upload_file_part(self, uri, authorization, file_path, step, crc=None, upload_id=None): - """上传图片文件分片(带超时和重试)""" + async def _upload_file_part(self, uri, authorization, file_path, step, crc=None, upload_id=None, file_content=None): + """上传图片文件分片(带超时和重试) + + Args: + file_content: 文件内容(step=2时直接使用,避免重复读取) + """ try: headers = { "Authorization": authorization, @@ -2323,9 +2405,19 @@ class DouYinMessageHandler: return response.json().get("payload", {}).get("uploadID") elif step == 2: - with open(file_path, 'rb') as f: - content = f.read() - headers["Content-Crc32"] = format(zlib.crc32(content), '08x') + # 🔥 优化:如果传入了file_content,直接使用;否则读取文件(兼容旧代码) + if file_content is None: + with open(file_path, 'rb') as f: + content = f.read() + else: + content = file_content + + # 使用传入的crc或重新计算(优先使用传入的) + if crc: + headers["Content-Crc32"] = crc + else: + headers["Content-Crc32"] = format(zlib.crc32(content), '08x') + headers["Content-Length"] = str(len(content)) headers["Content-Type"] = 'application/octet-stream' params = {"partNumber": "1", "uploadID": upload_id}