diff --git a/core/async_task.py b/core/async_task.py index 0b34733..dc1d252 100644 --- a/core/async_task.py +++ b/core/async_task.py @@ -1,5 +1,6 @@ import asyncio import json +import aiofiles from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.interval import IntervalTrigger @@ -36,17 +37,22 @@ class AsyncTask: async with self._file_lock: try: self.file_path.parent.mkdir(parents=True, exist_ok=True) + if not self.file_path.exists(): local_data = {} else: - with open(self.file_path, 'r', encoding='utf-8') as f: - local_data = json.load(f) + async with aiofiles.open(self.file_path, "r", encoding="utf-8") as f: + content = await f.read() + local_data = json.loads(content) if content else {} local_data[key] = value - with open(self.file_path, 'w', encoding='utf-8') as f: - json.dump(local_data, f, ensure_ascii=False, indent=4) - except Exception as e: + async with aiofiles.open(self.file_path, "w", encoding="utf-8") as f: + await f.write( + json.dumps(local_data, ensure_ascii=False, indent=4) + ) + + except (OSError, json.JSONDecodeError) as e: logger.error(f"数据写入文件失败:{e}") async def get_local_data(self, key: str, default=None): @@ -54,10 +60,16 @@ class AsyncTask: try: if not self.file_path.exists(): return default - with open(self.file_path, 'r', encoding='utf-8') as f: - local_data = json.load(f) + + async with aiofiles.open(self.file_path, "r", encoding="utf-8") as f: + content = await f.read() + if not content: + return default + local_data = json.loads(content) + return local_data.get(key, default) - except Exception as e: + + except (OSError, json.JSONDecodeError) as e: logger.error(f"读取数据文件失败:{e}") return default @@ -65,9 +77,14 @@ class AsyncTask: async def _job_common(self, fetch_func, task_key: str, namefun: str): state = self.tasks[task_key] + try: data = await fetch_func() - state["state_new"] = data["status"] + + if not isinstance(data, dict): + raise ValueError("fetch_func 返回数据不是 dict") + + state["state_new"] = data.get("status") if state["state_old"] != state["state_new"]: message_chain = MessageChain().message(data.get("data")) @@ -78,8 +95,15 @@ class AsyncTask: await self.set_local_data(task_key, state["state_new"]) state["state_old"] = state["state_new"] + except asyncio.CancelledError: + # 调度器 shutdown 时的正常路径 + raise + + except (KeyError, TypeError, ValueError) as e: + logger.error(f"{namefun} 数据结构异常: {e}") + except Exception as e: - logger.error(f"{namefun}后台任务执行异常: {e}") + logger.exception(f"{namefun} 后台任务执行异常") """===================== 初始化任务 =====================""" @@ -137,13 +161,11 @@ class AsyncTask: logger.error(f"停止全部后台任务失败:{e}") async def destroy(self): - """ - 销毁整个调度器,适合插件卸载/重启时调用 - """ try: self.stop_all_tasks() if self.scheduler.running: self.scheduler.shutdown(wait=False) + self.tasks.clear() logger.info("后台调度器已销毁") except Exception as e: logger.error(f"销毁调度器失败:{e}") diff --git a/core/function_basic.py b/core/function_basic.py index c7cb6b7..b92bdb2 100644 --- a/core/function_basic.py +++ b/core/function_basic.py @@ -1,27 +1,19 @@ from pathlib import Path from datetime import datetime,date +import aiofiles -def load_template(template_name): +async def load_template(template_name: str) -> str: """ - 从模板文件加载模板内容 - - Args: - template_name: 模板文件名(不带路径) - - Returns: - str: 模板内容 + 异步加载模板内容(非阻塞) """ - # 获取模板文件路径 plugin_dir = Path(__file__).parent.parent template_path = plugin_dir / "templates" / template_name - - # 检查文件是否存在 + if not template_path.exists(): raise FileNotFoundError(f"模板文件不存在: {template_path}") - - # 读取模板内容 - with open(template_path, 'r', encoding='utf-8') as f: - return f.read() + + async with aiofiles.open(template_path, "r", encoding="utf-8") as f: + return await f.read() def gold_to_string(gold_amount): diff --git a/core/jx3_service.py b/core/jx3_service.py index 1baab3a..d841888 100644 --- a/core/jx3_service.py +++ b/core/jx3_service.py @@ -33,6 +33,13 @@ class JX3Service: logger.info(f"获取配置ticket成功。{self.ticket}") + async def close(self): + """释放底层 APIClient 资源""" + if self._api: + await self._api.close() + self._api = None + + def _init_return_data(self) -> Dict[str, Any]: """初始化标准的返回数据结构""" return { @@ -96,7 +103,7 @@ class JX3Service: # 加载模板 try: - return_data["temp"] = load_template("helps.html") + return_data["temp"] = await load_template("helps.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -198,7 +205,7 @@ class JX3Service: # 加载模板 try: - return_data["temp"] = load_template("richangyuche.html") + return_data["temp"] = await load_template("richangyuche.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -233,7 +240,7 @@ class JX3Service: # 加载模板 try: - return_data["temp"] = load_template("xingxiashijian.html") + return_data["temp"] = await load_template("xingxiashijian.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -361,7 +368,7 @@ class JX3Service: # 加载模板 try: - return_data["temp"] = load_template("qufuzhuangtai.html") + return_data["temp"] = await load_template("qufuzhuangtai.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -450,7 +457,7 @@ class JX3Service: return return_data # 加载模板 try: - return_data["temp"] = load_template("jinjia.html") + return_data["temp"] = await load_template("jinjia.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -489,7 +496,7 @@ class JX3Service: # 加载模板 try: - return_data["temp"] = load_template("qiyuliebiao.html") + return_data["temp"] = await load_template("qiyuliebiao.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -528,7 +535,7 @@ class JX3Service: # 5. 加载模板 try: - return_data["temp"] = load_template("wujia.html") + return_data["temp"] = await load_template("wujia.html") return_data["code"] = 200 except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") @@ -578,7 +585,7 @@ class JX3Service: # 5. 模板渲染 try: - return_data["temp"] = load_template("jiaoyihang.html") + return_data["temp"] = await load_template("jiaoyihang.html") return_data["code"] = 200 except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") @@ -661,7 +668,7 @@ class JX3Service: # 4. 加载模板 try: - return_data["temp"] = load_template("yanhuan.html") + return_data["temp"] = await load_template("yanhuan.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -697,7 +704,7 @@ class JX3Service: # 4. 加载模板 try: - return_data["temp"] = load_template("dilujilu.html") + return_data["temp"] = await load_template("dilujilu.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -732,7 +739,7 @@ class JX3Service: # 4. 加载模板 try: - return_data["temp"] = load_template("tuanduizhaomu.html") + return_data["temp"] = await load_template("tuanduizhaomu.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -772,7 +779,7 @@ class JX3Service: # 4. 加载模板 try: - return_data["temp"] = load_template("zhanji.html") + return_data["temp"] = await load_template("zhanji.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -820,7 +827,7 @@ class JX3Service: # 4. 加载模板 try: - return_data["temp"] = load_template("juesheqiyu.html") + return_data["temp"] = await load_template("juesheqiyu.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" @@ -853,7 +860,7 @@ class JX3Service: # 4. 加载模板 try: - return_data["temp"] = load_template("zhengyingpaimai.html") + return_data["temp"] = await load_template("zhengyingpaimai.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" diff --git a/core/request.py b/core/request.py index 8526a06..91830e7 100644 --- a/core/request.py +++ b/core/request.py @@ -32,6 +32,7 @@ class APIClient: """关闭 Session""" if self._session and not self._session.closed: await self._session.close() + self._session = None async def __aenter__(self): await self.get_session() diff --git a/main.py b/main.py index 07db79f..909ec59 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,7 @@ import json import shutil import pathlib +import asyncio from pathlib import Path from typing import Union @@ -47,26 +48,29 @@ class Jx3ApiPlugin(Star): """可选择实现异步的插件初始化方法,当实例化该插件类之后会自动调用该方法。""" # --- 调用函数完成检查和复制 --- try: - self.file_local_data = self.check_and_copy_db( - local_data_dir=self.local_data_dir, - db_filename="local_async.json", - default_db_dir=self.data_file_path + loop = asyncio.get_running_loop() + self.file_local_data = await loop.run_in_executor( + None, + self.check_and_copy_db, + self.local_data_dir, + "local_async.json", + self.data_file_path ) except FileNotFoundError as e: - # 处理默认文件丢失的严重错误 logger.critical(f"插件初始化失败:{e}") - raise # 中断初始化 - + raise + try: - self.jx3fun = JX3Service(self.api_config,self.conf) + self.jx3fun = JX3Service(self.api_config, self.conf) self.at = AsyncTask(self.context, self.conf, self.jx3fun) await self.at.init_tasks() except Exception as e: - await self.at.destroy() - logger.error(f"功能示例初始化失败: {e}") - return + if hasattr(self, "at"): + await self.at.destroy() + logger.error(f"功能模块初始化失败: {e}") + raise - logger.info("jx3api异步插件初始化完成") + logger.info("jx3api 异步插件初始化完成") def check_and_copy_db(self, local_data_dir: Union[str, Path], db_filename: str, default_db_dir: Union[str, Path]) -> pathlib.Path: @@ -501,8 +505,11 @@ class Jx3ApiPlugin(Star): async def terminate(self): """可选择实现异步的插件销毁方法,当插件被卸载/停用时会调用。""" - # 关闭数据库连接 - - # 后台z周期进程销毁 - await self.at.destroy() + if self.at: + await self.at.destroy() + self.at = None + + if self.jx3fun: + await self.jx3fun.close() + self.jx3fun = None logger.info("jx3api插件已卸载/停用") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3132c6a..3ba3119 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ aiomysql -matplotlib \ No newline at end of file +matplotlib +aiofiles \ No newline at end of file