diff --git a/collectors/output/last_update.pkl b/collectors/output/last_update.pkl new file mode 100644 index 0000000..f8d691c Binary files /dev/null and b/collectors/output/last_update.pkl differ diff --git a/logs/application.log b/logs/application.log index 7c3be69..b12c50c 100644 --- a/logs/application.log +++ b/logs/application.log @@ -128805,3 +128805,392 @@ → module: 'NewsAPIClient' 2025-09-18 15:57:08.084 | INFO | news_api:301 - 本次最新更新时间: 2025-09-18 07:53:49 → module: 'NewsAPIClient' +2025-09-19 13:57:30.261 | INFO | news_api:38 - 新闻API客户端初始化完成,已连接到数据库 + → module: 'NewsAPIClient' +2025-09-19 13:57:30.313 | DEBUG | mysql_agent:591 - 查询执行完成 + → module: 'MySQLAgent(Windows)' + → 行数: 1 +2025-09-19 13:57:30.321 | DEBUG | mysql_agent:591 - 查询执行完成 + → module: 'MySQLAgent(Windows)' + → 行数: 8 +2025-09-19 13:57:30.322 | INFO | news_api:76 - 数据库表结构验证通过,当前字段:['id', '文章标题', '文章链接', '文章摘要', '发布时间', '来源URL', '创建时间', '更新时间'] + → module: 'NewsAPIClient' +2025-09-19 13:57:30.323 | DEBUG | news_api:90 - 加载上次更新时间: 2025-09-18 07:53:49 + → module: 'NewsAPIClient' +2025-09-19 13:57:30.323 | INFO | news_api:283 - 上次更新时间: 2025-09-18 07:53:49 + → module: 'NewsAPIClient' +2025-09-19 13:57:30.323 | INFO | news_api:286 - 开始获取RSS源数据... + → module: 'NewsAPIClient' +2025-09-19 13:57:30.765 | DEBUG | news_api:126 - 成功获取 https://www.chinanews.com.cn/rss/finance.xml 的RSS数据 + → module: 'NewsAPIClient' +2025-09-19 13:57:30.777 | DEBUG | news_api:126 - 成功获取 https://www.chinanews.com.cn/rss/china.xml 的RSS数据 + → module: 'NewsAPIClient' +2025-09-19 13:57:30.786 | DEBUG | news_api:126 - 成功获取 https://www.chinanews.com.cn/rss/world.xml 的RSS数据 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.083 | DEBUG | news_api:126 - 成功获取 https://www.chinanews.com.cn/rss/scroll-news.xml 的RSS数据 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.085 | INFO | news_api:153 - RSS源获取完成,成功获取 4/4 个源 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.086 | INFO | news_api:289 - 获取完成,耗时: 0.76秒 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.086 | INFO | news_api:210 - 开始处理 RSS 源: https://www.chinanews.com.cn/rss/finance.xml + → module: 'NewsAPIClient' +2025-09-19 13:57:31.086 | DEBUG | news_api:227 - 处理条目 1: 双节假期临近 消费板块是否迎来上涨机遇? + → module: 'NewsAPIClient' +2025-09-19 13:57:31.087 | DEBUG | news_api:227 - 处理条目 2: “中国罗非鱼之都”广东茂名罗非鱼出口同比增逾两成 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.087 | DEBUG | news_api:227 - 处理条目 3: 今年以来广西单个最大新增用海面积项目获批 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.087 | DEBUG | news_api:227 - 处理条目 4: 首超10亿人次!河南文旅破圈背后的创新密码 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.087 | DEBUG | news_api:227 - 处理条目 5: 2025中国国际消费电子博览会在青岛举办 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.087 | DEBUG | news_api:227 - 处理条目 6: 四川经济总量连续跨过两个万亿元大关 实现历史性晋位 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.088 | DEBUG | news_api:227 - 处理条目 7: 广西最大工业城市抢抓东盟机遇 推进制造强市建设 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.088 | DEBUG | news_api:227 - 处理条目 8: 时令蔬果丰富居民“菜篮子” “小果子”撑起乡村振兴“大产业” + → module: 'NewsAPIClient' +2025-09-19 13:57:31.088 | DEBUG | news_api:227 - 处理条目 9: AI点亮东博会 中国-东盟合作进入“数智时代” + → module: 'NewsAPIClient' +2025-09-19 13:57:31.088 | DEBUG | news_api:227 - 处理条目 10: (活力中国调研行)盐城港滨海港区:“港口+产业+能源”三位一体打造沿海综合枢纽港 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.088 | DEBUG | news_api:227 - 处理条目 11: 【两点一存耀华夏】甘肃庆阳:革命老区插上数字化“翅膀” + → module: 'NewsAPIClient' +2025-09-19 13:57:31.088 | DEBUG | news_api:227 - 处理条目 12: (活力中国调研行)探访全国最大的液化天然气能源枢纽站:10个“气墩墩”绿色能源送四方 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.089 | DEBUG | news_api:227 - 处理条目 13: 我国科技创新能力稳步提升 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.089 | DEBUG | news_api:227 - 处理条目 14: 厦门机场今年出入境客流破300万人次 外籍旅客超71万人次 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.089 | DEBUG | news_api:227 - 处理条目 15: 中外记者走进山东济南未来产业 体验科技力量 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.089 | DEBUG | news_api:227 - 处理条目 16: 中巴(西)财金分委会第十一次会议举行 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.089 | DEBUG | news_api:227 - 处理条目 17: 小米汽车科技有限公司召回部分SU7标准版电动汽车 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.089 | DEBUG | news_api:227 - 处理条目 18: 【新思想引领新征程·非凡“十四五”】从跟跑到领跑 中国新能源汽车迈上新台阶 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.090 | DEBUG | news_api:227 - 处理条目 19: 9部门发文支持一刻钟便民生活圈建设扩围升级 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.090 | DEBUG | news_api:227 - 处理条目 20: 何猷君:积极参与中国—东盟电竞发展浪潮 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.090 | DEBUG | news_api:227 - 处理条目 21: 广西北部湾港再添一条至中东的滚装新航线 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.090 | DEBUG | news_api:227 - 处理条目 22: 数智赋能发展 第22届东博会签约155个项目 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.091 | DEBUG | news_api:227 - 处理条目 23: 中国AI企业共拓东盟人工智能市场“蓝海” + → module: 'NewsAPIClient' +2025-09-19 13:57:31.091 | DEBUG | news_api:227 - 处理条目 24: 工信部有关负责人解读《轻工业稳增长工作方案(2025—2026年)》 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.091 | DEBUG | news_api:227 - 处理条目 25: 三部门印发轻工业稳增长工作方案 助力行业持续回升向好 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.091 | DEBUG | news_api:227 - 处理条目 26: 今年前8个月全国制造业税收收入同比增长5%以上 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.091 | DEBUG | news_api:227 - 处理条目 27: 辽宁省数据局副局长刘洋:数据要素应用让群众跑腿变数据跑路 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.093 | DEBUG | news_api:227 - 处理条目 28: 海南自贸港数据安全有序流动 游戏、短剧出海迎国际化发展 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.093 | DEBUG | news_api:227 - 处理条目 29: 浙非深化电商合作 “数字丝路”推动互利共赢 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.093 | DEBUG | news_api:227 - 处理条目 30: 首届老中电力技术论坛在老挝纳塞通变电站举办 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.111 | DEBUG | mysql_agent:164 - 已建立连接,准备插入数据到 collector_rss_subscriptions + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.113 | DEBUG | mysql_agent:170 - 表 collector_rss_subscriptions 包含以下列:['id', '文章标题', '文章链接', '文章摘要', '发布时间', '来源URL', '创建时间', '更新时间'] + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.121 | DEBUG | mysql_agent:196 - 表 collector_rss_subscriptions 的过滤后DataFrame:共 30 行待插入 + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.326 | INFO | mysql_agent:282 - 表 collector_rss_subscriptions 插入结果汇总 + → module: 'MySQLAgent(Windows)' + → total_to_insert: 30 + → total_inserted: 30 + → total_duplicates: 0 + → total_failed: 0 + → failed_records_count: 0 +2025-09-19 13:57:31.326 | INFO | news_api:251 - 成功写入 30/30 条记录 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.326 | INFO | news_api:210 - 开始处理 RSS 源: https://www.chinanews.com.cn/rss/china.xml + → module: 'NewsAPIClient' +2025-09-19 13:57:31.327 | DEBUG | news_api:227 - 处理条目 1: 国庆中秋假期临近 多地创新文旅消费新场景 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.327 | DEBUG | news_api:227 - 处理条目 2: 生态环境部:将重点打击非法拆解废动力电池等“新三样”固体废物 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.327 | DEBUG | news_api:227 - 处理条目 3: 生态环境部:我国已建成全球规模最大的生态环境监测网络 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.327 | DEBUG | news_api:227 - 处理条目 4: 中央生态环保督察受理转办35万件群众举报 绝大多数已办结或阶段性办结 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.327 | DEBUG | news_api:227 - 处理条目 5: 习近平致中国致公党成立100周年的贺信 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.327 | DEBUG | news_api:227 - 处理条目 6: 习近平致信祝贺中国致公党成立100周年 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.327 | DEBUG | news_api:227 - 处理条目 7: 存在偷逃税等问题,1名主播、两家MCN被通报! + → module: 'NewsAPIClient' +2025-09-19 13:57:31.328 | DEBUG | news_api:227 - 处理条目 8: 生态环境部:风电、太阳能发电装机总量已提前完成2030年国家自主贡献目标 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.328 | DEBUG | news_api:227 - 处理条目 9: 生态环境部:加强准入管理 推动产业结构优化调整 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.328 | DEBUG | news_api:227 - 处理条目 10: 今年以来,全国生态环境质量延续持续向好态势 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.328 | DEBUG | news_api:227 - 处理条目 11: 生态环境部:基本建立了全域覆盖的生态环境分区管控体系 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.329 | DEBUG | news_api:227 - 处理条目 12: 我国陆域生态保护红线面积占比已超30% + → module: 'NewsAPIClient' +2025-09-19 13:57:31.329 | DEBUG | news_api:227 - 处理条目 13: 广西—文莱经济走廊建设推动各领域合作持续拓展 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.329 | DEBUG | news_api:227 - 处理条目 14: 生态环境部:“十四五”期间淘汰高排放车辆近2000万辆 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.329 | DEBUG | news_api:227 - 处理条目 15: 我国建成全球规模最大的碳排放权交易市场 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.330 | DEBUG | news_api:227 - 处理条目 16: 汇聚国内外资深专家学者 香山论坛首设“高端对话” + → module: 'NewsAPIClient' +2025-09-19 13:57:31.330 | DEBUG | news_api:227 - 处理条目 17: “人类和平与发展的崇高事业必将胜利!”——习近平总书记重要讲话揭示历史前进的必然逻辑 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.330 | DEBUG | news_api:227 - 处理条目 18: 上海:发挥统一战线法宝作用 助力民营经济发展壮大 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.330 | DEBUG | news_api:227 - 处理条目 19: 东盟秘书长高金洪:中国与东盟双边贸易额将实现大幅跨越 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.331 | DEBUG | news_api:227 - 处理条目 20: 学习新语|九一八,铭记白山黑水间的抗联英雄 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.331 | DEBUG | news_api:227 - 处理条目 21: 贵州省自然资源厅党委委员、副厅长高玉平接受审查调查 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.331 | DEBUG | news_api:227 - 处理条目 22: 河南省信阳市政府原副市长杨淑萍接受审查调查 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.331 | DEBUG | news_api:227 - 处理条目 23: 湖南省高速公路集团原副总经理王辉扬被开除党籍 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.332 | DEBUG | news_api:227 - 处理条目 24: 互联网大厂持续反腐 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.332 | DEBUG | news_api:227 - 处理条目 25: 为困难退役军人排忧解难 “情暖老兵·关爱尊崇”专项行动启动 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.332 | DEBUG | news_api:227 - 处理条目 26: 跳水运动员遭网暴案被公安部列为打击整治网络违法犯罪典型案例 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.332 | DEBUG | news_api:227 - 处理条目 27: 【同心声影】侨海报国百年路 肝胆相照启新程 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.333 | DEBUG | news_api:227 - 处理条目 28: 我国现行地方性法规已达1.4万余件 地方立法“小快灵”带来哪些改变 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.333 | DEBUG | news_api:227 - 处理条目 29: “台北快轮”迎1000航次往返 畅通岚台“黄金通道” + → module: 'NewsAPIClient' +2025-09-19 13:57:31.334 | DEBUG | news_api:227 - 处理条目 30: 天津打造《信仰的颜色》艺术“大思政课” + → module: 'NewsAPIClient' +2025-09-19 13:57:31.336 | DEBUG | mysql_agent:164 - 已建立连接,准备插入数据到 collector_rss_subscriptions + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.338 | DEBUG | mysql_agent:170 - 表 collector_rss_subscriptions 包含以下列:['id', '文章标题', '文章链接', '文章摘要', '发布时间', '来源URL', '创建时间', '更新时间'] + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.340 | DEBUG | mysql_agent:196 - 表 collector_rss_subscriptions 的过滤后DataFrame:共 30 行待插入 + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.518 | INFO | mysql_agent:282 - 表 collector_rss_subscriptions 插入结果汇总 + → module: 'MySQLAgent(Windows)' + → total_to_insert: 30 + → total_inserted: 30 + → total_duplicates: 0 + → total_failed: 0 + → failed_records_count: 0 +2025-09-19 13:57:31.519 | INFO | news_api:251 - 成功写入 30/30 条记录 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.519 | INFO | news_api:210 - 开始处理 RSS 源: https://www.chinanews.com.cn/rss/world.xml + → module: 'NewsAPIClient' +2025-09-19 13:57:31.519 | DEBUG | news_api:227 - 处理条目 1: 国际最新研究:南美发现首批保存昆虫琥珀沉积物 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.520 | DEBUG | news_api:227 - 处理条目 2: 巴基斯坦西南部发生两起袭击事件造成8死23伤 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.520 | DEBUG | news_api:227 - 处理条目 3: 美媒:美政府请求最高法院允许其解雇美联储理事库克 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.520 | DEBUG | news_api:227 - 处理条目 4: 意大利车手在军舰甲板上创造“最快船载汽车”世界纪录 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.520 | DEBUG | news_api:227 - 处理条目 5: 加沙地带和约旦河西岸多名以军士兵遇袭伤亡 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.520 | DEBUG | news_api:227 - 处理条目 6: 金正恩指导无人武器装备性能试验 强调发展人工智能技术 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.520 | DEBUG | news_api:227 - 处理条目 7: 最新气候变化研究:野火烟雾造成超额和过早死亡人数预计会增加 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.521 | DEBUG | news_api:227 - 处理条目 8: 2025中比运河城市文化交流对话活动在布鲁塞尔举行 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.521 | DEBUG | news_api:227 - 处理条目 9: 法国超50万人举行罢工 反对政府财政紧缩方案 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.521 | DEBUG | news_api:227 - 处理条目 10: 电影《731》澳大利亚首映活动在悉尼举行 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.521 | DEBUG | news_api:227 - 处理条目 11: 中国常驻日内瓦代表团举办人人享有无障碍对话会 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.521 | DEBUG | news_api:227 - 处理条目 12: 《铭记和平——纪念世界反法西斯战争暨中国人民抗战胜利80周年、韩国光复80周年中韩书画展》在首尔开幕 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.522 | DEBUG | news_api:227 - 处理条目 13: 中国驻纽约总领馆举行国庆76周年招待会 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.522 | DEBUG | news_api:227 - 处理条目 14: 李在明支持率反弹至60% + → module: 'NewsAPIClient' +2025-09-19 13:57:31.522 | DEBUG | news_api:227 - 处理条目 15: 俄总统办公厅副主任被免职 曾是普京最亲密顾问之一 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.522 | DEBUG | news_api:227 - 处理条目 16: 美国佛罗里达州一男子在乘坐过山车后死亡 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.523 | DEBUG | news_api:227 - 处理条目 17: 法国发生多行业罢工 旨在反对政府财政紧缩计划 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.523 | DEBUG | news_api:227 - 处理条目 18: 未取得明显成果 韩国贸易代表结束访美回国 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.523 | DEBUG | news_api:227 - 处理条目 19: 南非央行维持基准利率在7%不变 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.523 | DEBUG | news_api:227 - 处理条目 20: 美参议院一次性表决通过48名特朗普政府提名人选 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.524 | DEBUG | news_api:227 - 处理条目 21: 特朗普反对英国承认巴勒斯坦国计划 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.524 | DEBUG | news_api:227 - 处理条目 22: 特朗普结束访英返美前 因直升机故障改乘备用机 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.524 | DEBUG | news_api:227 - 处理条目 23: 安理会未通过加沙停火决议草案 中方:非常失望 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.524 | DEBUG | news_api:227 - 处理条目 24: 美国再次一票否决加沙停火决议草案 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.525 | DEBUG | news_api:227 - 处理条目 25: 堪察加东岸附近海域发生7.8级地震 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.525 | DEBUG | news_api:227 - 处理条目 26: 泰国总理阿努廷承诺4个月内推动经济见效 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.525 | DEBUG | news_api:227 - 处理条目 27: 马来西亚交通部长:“空中丝路”助力马打造区域货运枢纽 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.525 | DEBUG | news_api:227 - 处理条目 28: 示威平息后雅加达经贸与文化活动再度升温 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.525 | DEBUG | news_api:227 - 处理条目 29: 世卫称加沙北部医院濒临崩溃 人道灾难必须立即结束 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.525 | DEBUG | news_api:227 - 处理条目 30: 外媒:乌军称袭击俄罗斯伏尔加格勒炼油厂 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.529 | DEBUG | mysql_agent:164 - 已建立连接,准备插入数据到 collector_rss_subscriptions + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.530 | DEBUG | mysql_agent:170 - 表 collector_rss_subscriptions 包含以下列:['id', '文章标题', '文章链接', '文章摘要', '发布时间', '来源URL', '创建时间', '更新时间'] + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.531 | DEBUG | mysql_agent:196 - 表 collector_rss_subscriptions 的过滤后DataFrame:共 30 行待插入 + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.691 | INFO | mysql_agent:282 - 表 collector_rss_subscriptions 插入结果汇总 + → module: 'MySQLAgent(Windows)' + → total_to_insert: 30 + → total_inserted: 30 + → total_duplicates: 0 + → total_failed: 0 + → failed_records_count: 0 +2025-09-19 13:57:31.692 | INFO | news_api:251 - 成功写入 30/30 条记录 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.692 | INFO | news_api:210 - 开始处理 RSS 源: https://www.chinanews.com.cn/rss/scroll-news.xml + → module: 'NewsAPIClient' +2025-09-19 13:57:31.692 | DEBUG | news_api:227 - 处理条目 1: “增肌神器”暗藏激素陷阱 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.693 | DEBUG | news_api:227 - 处理条目 2: 宋人茶肆里的世俗生活 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.693 | DEBUG | news_api:227 - 处理条目 3: 流感疫苗怎么选、何时打? + → module: 'NewsAPIClient' +2025-09-19 13:57:31.693 | DEBUG | news_api:227 - 处理条目 4: 国庆中秋假期临近 多地创新文旅消费新场景 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.693 | DEBUG | news_api:227 - 处理条目 5: 侨批:漂洋过海的信与银 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.693 | DEBUG | news_api:227 - 处理条目 6: 加强一体联动 推进技能型高校产教融合 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.693 | DEBUG | news_api:227 - 处理条目 7: 生态环境部:将重点打击非法拆解废动力电池等“新三样”固体废物 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.693 | DEBUG | news_api:227 - 处理条目 8: 国际最新研究:南美发现首批保存昆虫琥珀沉积物 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.693 | DEBUG | news_api:227 - 处理条目 9: 云海肴“85后”创始人赵晗去世 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.693 | DEBUG | news_api:227 - 处理条目 10: 在教室私装监控 警惕“育人”变“控人” + → module: 'NewsAPIClient' +2025-09-19 13:57:31.694 | DEBUG | news_api:227 - 处理条目 11: 生态环境部:我国已建成全球规模最大的生态环境监测网络 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.694 | DEBUG | news_api:227 - 处理条目 12: 深圳鼓励外卖员随手拍“黑餐馆” + → module: 'NewsAPIClient' +2025-09-19 13:57:31.694 | DEBUG | news_api:227 - 处理条目 13: 教室装监控被举报 该有的边界感要重拾 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.695 | DEBUG | news_api:227 - 处理条目 14: 戏曲正破圈而来 以另一种方式被打开 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.695 | DEBUG | news_api:227 - 处理条目 15: 金融知识小课堂丨警惕防不胜防的移动应用消费陷阱 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.695 | DEBUG | news_api:227 - 处理条目 16: 大闸蟹销售“熔断机制”,直击行业假冒痛点 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.695 | DEBUG | news_api:227 - 处理条目 17: 金融知识小课堂丨识破虚假投资理财 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.696 | DEBUG | news_api:227 - 处理条目 18: 在历史时空中重温台山籍侨胞的浓厚乡情 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.696 | DEBUG | news_api:227 - 处理条目 19: 得了脂肪肝要怎么吃?营养专家来支招 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.696 | DEBUG | news_api:227 - 处理条目 20: 青海海西州就“一‘矿霸’非法填埋万吨危废”事件成立调查组 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.696 | DEBUG | news_api:227 - 处理条目 21: 双节假期临近 消费板块是否迎来上涨机遇? + → module: 'NewsAPIClient' +2025-09-19 13:57:31.697 | DEBUG | news_api:227 - 处理条目 22: 中经评论:“人工智能+制造”不是简单叠加 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.697 | DEBUG | news_api:227 - 处理条目 23: 巴基斯坦西南部发生两起袭击事件造成8死23伤 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.697 | DEBUG | news_api:227 - 处理条目 24: 金观平:因城施策破解千城一面 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.697 | DEBUG | news_api:227 - 处理条目 25: 书香引入商圈,除了“种草”还有什么 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.698 | DEBUG | news_api:227 - 处理条目 26: 海外中文学校帮华裔青年“寻根” 为外国学生“开窗” + → module: 'NewsAPIClient' +2025-09-19 13:57:31.698 | DEBUG | news_api:227 - 处理条目 27: 中央生态环保督察受理转办35万件群众举报 绝大多数已办结或阶段性办结 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.698 | DEBUG | news_api:227 - 处理条目 28: 致命的“化骨水”中毒,如何防范和急救? + → module: 'NewsAPIClient' +2025-09-19 13:57:31.699 | DEBUG | news_api:227 - 处理条目 29: 习近平致中国致公党成立100周年的贺信 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.699 | DEBUG | news_api:227 - 处理条目 30: “中国罗非鱼之都”广东茂名罗非鱼出口同比增逾两成 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.701 | DEBUG | mysql_agent:164 - 已建立连接,准备插入数据到 collector_rss_subscriptions + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.703 | DEBUG | mysql_agent:170 - 表 collector_rss_subscriptions 包含以下列:['id', '文章标题', '文章链接', '文章摘要', '发布时间', '来源URL', '创建时间', '更新时间'] + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.704 | DEBUG | mysql_agent:196 - 表 collector_rss_subscriptions 的过滤后DataFrame:共 30 行待插入 + → module: 'MySQLAgent(Windows)' +2025-09-19 13:57:31.724 | WARNING | mysql_agent:241 - 表 collector_rss_subscriptions 中跳过重复记录 + → module: 'MySQLAgent(Windows)' + → index: 3 + → error_message: "Duplicate entry '国庆中秋假期临近 多地创新文旅消费新场景-2025-' for key 'collector_rss_subscriptions.idx_title_pubtime'" + → record: {'文章标题': '国庆中秋假期临近 多地创新文旅消费新场景...', '文章链接': 'http://www.chinanews.com/gn/2025/09-19/10485196.shtml...', '文章摘要': '国庆中秋假期临近 多地创新文旅消费新场景 迎接客流高峰...', '发布时间': '2025-09-19 05:51:25...', '来源URL': 'https:/... +2025-09-19 13:57:31.737 | WARNING | mysql_agent:241 - 表 collector_rss_subscriptions 中跳过重复记录 + → module: 'MySQLAgent(Windows)' + → index: 6 + → error_message: "Duplicate entry '生态环境部:将重点打击非法拆解废动力电池等â' for key 'collector_rss_subscriptions.idx_title_pubtime'" + → record: {'文章标题': '生态环境部:将重点打击非法拆解废动力电池等“新三样”固体废物...', '文章链接': 'http://www.chinanews.com/gn/2025/09-19/10485183.shtml...', '文章摘要': '中新网9月19日电 国新办19日举行“高质量完成‘十四五’规划”系列主题新闻发布会,介绍以生态环境高水平保护推动高质量发展情况。...', '发布时... +2025-09-19 13:57:31.740 | WARNING | mysql_agent:241 - 表 collector_rss_subscriptions 中跳过重复记录 + → module: 'MySQLAgent(Windows)' + → index: 7 + → error_message: "Duplicate entry '国际最新研究:南美发现首批保存昆虫琥珀沉积ç' for key 'collector_rss_subscriptions.idx_title_pubtime'" + → record: {'文章标题': '国际最新研究:南美发现首批保存昆虫琥珀沉积物...', '文章链接': 'http://www.chinanews.com/gj/2025/09-19/10485194.shtml...', '文章摘要': '中新网北京9月19日电 (记者 孙自法)施普林格·自然旗下专业学术期刊《通讯-地球与环境》最新发表一篇古生物学论文指出,研究人员在厄瓜多尔一个采石场发现南美首批含有... +2025-09-19 13:57:31.753 | WARNING | mysql_agent:241 - 表 collector_rss_subscriptions 中跳过重复记录 + → module: 'MySQLAgent(Windows)' + → index: 10 + → error_message: "Duplicate entry '生态环境部:我国已建成全球规模最大的生态环å' for key 'collector_rss_subscriptions.idx_title_pubtime'" + → record: {'文章标题': '生态环境部:我国已建成全球规模最大的生态环境监测网络...', '文章链接': 'http://www.chinanews.com/gn/2025/09-19/10485174.shtml...', '文章摘要': '中新网9月19日电 国新办19日举行“高质量完成‘十四五’规划”系列主题新闻发布会,介绍以生态环境高水平保护推动高质量发展情况。...', '发布时间': ... +2025-09-19 13:57:31.802 | WARNING | mysql_agent:241 - 表 collector_rss_subscriptions 中跳过重复记录 + → module: 'MySQLAgent(Windows)' + → index: 20 + → error_message: "Duplicate entry '双节假期临近 消费板块是否迎来上涨机遇?-2025-' for key 'collector_rss_subscriptions.idx_title_pubtime'" + → record: {'文章标题': '双节假期临近 消费板块是否迎来上涨机遇?...', '文章链接': 'http://www.chinanews.com/cj/2025/09-19/10485187.shtml...', '文章摘要': '央广网北京9月19日消息(记者蔡军)据中央广播电视总台经济之声《交易实况》报道,9月18日,消费股中的餐饮旅游板块尾盘拉升,曲江文旅、云南旅游涨停,西安饮食、西安旅游、... +2025-09-19 13:57:31.810 | WARNING | mysql_agent:241 - 表 collector_rss_subscriptions 中跳过重复记录 + → module: 'MySQLAgent(Windows)' + → index: 22 + → error_message: "Duplicate entry '巴基斯坦西南部发生两起袭击事件造成8死23伤-202' for key 'collector_rss_subscriptions.idx_title_pubtime'" + → record: {'文章标题': '巴基斯坦西南部发生两起袭击事件造成8死23伤...', '文章链接': 'http://www.chinanews.com/gj/2025/09-19/10485170.shtml...', '文章摘要': '中新社北京9月19日电 伊斯兰堡消息:巴基斯坦官员18日称,该国西南部俾路支省当日发生两起袭击事件,造成8人死亡、23人受伤。...', '发布时间': '2025... +2025-09-19 13:57:31.827 | WARNING | mysql_agent:241 - 表 collector_rss_subscriptions 中跳过重复记录 + → module: 'MySQLAgent(Windows)' + → index: 26 + → error_message: "Duplicate entry '中央生态环保督察受理转办35万件群众举报 绝大å' for key 'collector_rss_subscriptions.idx_title_pubtime'" + → record: {'文章标题': '中央生态环保督察受理转办35万件群众举报 绝大多数已办结或阶段性办结...', '文章链接': 'http://www.chinanews.com/gn/2025/09-19/10485172.shtml...', '文章摘要': '中新网9月19日电 国新办19日举行“高质量完成‘十四五’规划”系列主题新闻发布会,介绍以生态环境高水平保护推动高质量发展情况。会上,生态环... +2025-09-19 13:57:31.836 | WARNING | mysql_agent:241 - 表 collector_rss_subscriptions 中跳过重复记录 + → module: 'MySQLAgent(Windows)' + → index: 28 + → error_message: "Duplicate entry '习近平致中国致公党成立100周年的贺信-2025-09-19 0' for key 'collector_rss_subscriptions.idx_title_pubtime'" + → record: {'文章标题': '习近平致中国致公党成立100周年的贺信...', '文章链接': 'http://www.chinanews.com/gn/2025/09-19/10485171.shtml...', '文章摘要': '新华社北京9月19日电...', '发布时间': '2025-09-19 05:05:34...', '来源URL': 'https://www.chinanews.co... +2025-09-19 13:57:31.839 | WARNING | mysql_agent:241 - 表 collector_rss_subscriptions 中跳过重复记录 + → module: 'MySQLAgent(Windows)' + → index: 29 + → error_message: "Duplicate entry '“中国罗非鱼之都”广东茂名罗非鱼出口同比增é' for key 'collector_rss_subscriptions.idx_title_pubtime'" + → record: {'文章标题': '“中国罗非鱼之都”广东茂名罗非鱼出口同比增逾两成...', '文章链接': 'http://www.chinanews.com/cj/2025/09-19/10485167.shtml...', '文章摘要': '中新社广东茂名9月19日电 (记者 梁盛)记者19日从广东茂名市农业农村局了解到,今年上半年,该市罗非鱼产量14.48万吨,同比增长7.12%,罗非鱼及其产品出... +2025-09-19 13:57:31.839 | INFO | mysql_agent:282 - 表 collector_rss_subscriptions 插入结果汇总 + → module: 'MySQLAgent(Windows)' + → total_to_insert: 30 + → total_inserted: 21 + → total_duplicates: 9 + → total_failed: 0 + → failed_records_count: 9 +2025-09-19 13:57:31.840 | ERROR | mysql_agent:293 - 表 collector_rss_subscriptions 插入失败记录详情 + → module: 'MySQLAgent(Windows)' + → failed_records_summary: [{'index': 3, 'type': 'duplicate', 'error_code': 1062, 'error_message': "Duplicate entry '国庆中秋假期临近 多地创新文旅消费新场景-2025-' for key 'collector_rss_subscriptions.idx_title_pubtime'"}, {'index': 6, 'type':... + → detailed_failed_records: [{'index': 3, 'type': 'duplicate', 'error_code': 1062, 'error_message': "Duplicate entry '国庆中秋假期临近 多地创新文旅消费新场景-2025-' for key 'collector_rss_subscriptions.idx_title_pubtime'", 'record': {'文章标题': '国... +2025-09-19 13:57:31.841 | INFO | news_api:251 - 成功写入 21/30 条记录 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.842 | DEBUG | news_api:106 - 已保存本次更新时间: 2025-09-19 05:54:27 + → module: 'NewsAPIClient' +2025-09-19 13:57:31.842 | INFO | news_api:301 - 本次最新更新时间: 2025-09-19 05:54:27 + → module: 'NewsAPIClient' diff --git a/logs/errors.log b/logs/errors.log index 6f98f25..0cf2909 100644 --- a/logs/errors.log +++ b/logs/errors.log @@ -65258,3 +65258,5 @@ 2025-09-18 15:46:27.640 | ERROR | news_api:259 - 数据库写入失败: MySQLAgent.insert_from_df() got an unexpected keyword argument 'replace' +2025-09-19 13:57:31.840 | ERROR | mysql_agent:293 - 表 collector_rss_subscriptions 插入失败记录详情 + diff --git a/tools/task_manager.ipynb b/tools/task_manager.ipynb index 8194221..6dd56ef 100644 --- a/tools/task_manager.ipynb +++ b/tools/task_manager.ipynb @@ -12,8 +12,8 @@ "metadata": { "collapsed": true, "ExecuteTime": { - "end_time": "2025-09-12T06:19:31.379133Z", - "start_time": "2025-09-12T06:19:30.001155Z" + "end_time": "2025-09-18T09:06:59.024834Z", + "start_time": "2025-09-18T09:06:58.966690Z" } }, "source": [ @@ -324,19 +324,18 @@ ], "outputs": [ { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'system_management.scheduler.task_manager'", + "ename": "NameError", + "evalue": "name 'null' is not defined", "output_type": "error", "traceback": [ "\u001B[31m---------------------------------------------------------------------------\u001B[39m", - "\u001B[31mModuleNotFoundError\u001B[39m Traceback (most recent call last)", - "\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[1]\u001B[39m\u001B[32m, line 8\u001B[39m\n\u001B[32m 6\u001B[39m \u001B[38;5;66;03m# 导入系统组件\u001B[39;00m\n\u001B[32m 7\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01msystem_management\u001B[39;00m\u001B[34;01m.\u001B[39;00m\u001B[34;01mscheduler\u001B[39;00m\u001B[34;01m.\u001B[39;00m\u001B[34;01mtask_scheduler\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m TaskScheduler\n\u001B[32m----> \u001B[39m\u001B[32m8\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01msystem_management\u001B[39;00m\u001B[34;01m.\u001B[39;00m\u001B[34;01mscheduler\u001B[39;00m\u001B[34;01m.\u001B[39;00m\u001B[34;01mtask_management\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m TaskManager\n\u001B[32m 9\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mconfig\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m Config\n\u001B[32m 11\u001B[39m \u001B[38;5;66;03m# 初始化配置和管理器\u001B[39;00m\n", - "\u001B[36mFile \u001B[39m\u001B[32mD:\\Idea Project\\intelligence_system\\system_management\\scheduler\\task_management.py:4\u001B[39m\n\u001B[32m 2\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mdatetime\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m datetime\n\u001B[32m 3\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01msystem_management\u001B[39;00m\u001B[34;01m.\u001B[39;00m\u001B[34;01mscheduler\u001B[39;00m\u001B[34;01m.\u001B[39;00m\u001B[34;01mtask_scheduler\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m TaskScheduler\n\u001B[32m----> \u001B[39m\u001B[32m4\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01msystem_management\u001B[39;00m\u001B[34;01m.\u001B[39;00m\u001B[34;01mscheduler\u001B[39;00m\u001B[34;01m.\u001B[39;00m\u001B[34;01mtask_manager\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m TaskManager\n\u001B[32m 5\u001B[39m \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mconfig\u001B[39;00m\u001B[34;01m.\u001B[39;00m\u001B[34;01mconfig\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mimport\u001B[39;00m ConfigManager\n\u001B[32m 8\u001B[39m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34mmain\u001B[39m():\n\u001B[32m 9\u001B[39m \u001B[38;5;66;03m# 初始化配置和组件\u001B[39;00m\n", - "\u001B[31mModuleNotFoundError\u001B[39m: No module named 'system_management.scheduler.task_manager'" + "\u001B[31mNameError\u001B[39m Traceback (most recent call last)", + "\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[4]\u001B[39m\u001B[32m, line 13\u001B[39m\n\u001B[32m 1\u001B[39m {\n\u001B[32m 2\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mcells\u001B[39m\u001B[33m\"\u001B[39m: [\n\u001B[32m 3\u001B[39m {\n\u001B[32m 4\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mcell_type\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mmarkdown\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 5\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmetadata\u001B[39m\u001B[33m\"\u001B[39m: {},\n\u001B[32m 6\u001B[39m \u001B[33m\"\u001B[39m\u001B[33msource\u001B[39m\u001B[33m\"\u001B[39m: [\n\u001B[32m 7\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m# TaskManager 任务管理类\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 8\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m任务管理核心组件,负责任务的CRUD、状态切换、手动执行等操作\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 9\u001B[39m ]\n\u001B[32m 10\u001B[39m },\n\u001B[32m 11\u001B[39m {\n\u001B[32m 12\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mcell_type\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mcode\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m---> \u001B[39m\u001B[32m13\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mexecution_count\u001B[39m\u001B[33m\"\u001B[39m: \u001B[43mnull\u001B[49m,\n\u001B[32m 14\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmetadata\u001B[39m\u001B[33m\"\u001B[39m: {},\n\u001B[32m 15\u001B[39m \u001B[33m\"\u001B[39m\u001B[33moutputs\u001B[39m\u001B[33m\"\u001B[39m: [],\n\u001B[32m 16\u001B[39m \u001B[33m\"\u001B[39m\u001B[33msource\u001B[39m\u001B[33m\"\u001B[39m: [\n\u001B[32m 17\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mimport pandas as pd\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 18\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mfrom datetime import datetime\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 19\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mfrom typing import Dict, List, Optional, Any\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 20\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mimport pytz\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 21\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mimport croniter\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 22\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 23\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mfrom utils.mysql_agent import MySQLAgent\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 24\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mfrom utils.logger import CrossPlatformLog\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 25\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mfrom system_management.scheduler.task_scheduler import TaskScheduler\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m\n\u001B[32m 26\u001B[39m ]\n\u001B[32m 27\u001B[39m },\n\u001B[32m 28\u001B[39m {\n\u001B[32m 29\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mcell_type\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mcode\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 30\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mexecution_count\u001B[39m\u001B[33m\"\u001B[39m: null,\n\u001B[32m 31\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmetadata\u001B[39m\u001B[33m\"\u001B[39m: {},\n\u001B[32m 32\u001B[39m \u001B[33m\"\u001B[39m\u001B[33moutputs\u001B[39m\u001B[33m\"\u001B[39m: [],\n\u001B[32m 33\u001B[39m \u001B[33m\"\u001B[39m\u001B[33msource\u001B[39m\u001B[33m\"\u001B[39m: [\n\u001B[32m 34\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mclass TaskManager:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 35\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m def __init__(self, scheduler: TaskScheduler):\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 36\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m初始化任务管理器\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 37\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Args:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 38\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m scheduler: 任务调度器实例\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 39\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 40\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.scheduler = scheduler\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 41\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.db = scheduler.db # 复用调度器的数据库连接\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 42\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log = CrossPlatformLog.get_logger(\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33mTaskManager\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 43\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 44\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m def get_all_tasks(self, active_only: bool = False) -> List[Dict[str, Any]]:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 45\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m获取所有任务列表\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 46\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Args:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 47\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m active_only: 是否只返回活跃任务\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 48\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Returns:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 49\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m 任务字典列表\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 50\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 51\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m try:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 52\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m query = \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33mSELECT * FROM main_task\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 53\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m params = []\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 54\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m if active_only:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 55\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m query += \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m WHERE is_active = 1\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 56\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m query += \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m ORDER BY task_id\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 57\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 58\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m tasks_df = self.db.query_to_df(query, params=params)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 59\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return tasks_df.to_dict(\u001B[39m\u001B[33m'\u001B[39m\u001B[33mrecords\u001B[39m\u001B[33m'\u001B[39m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 60\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m except Exception as e:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 61\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.error(\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m获取任务列表失败\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m, exc_info=True)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 62\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return []\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 63\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 64\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m def get_task_by_id(self, task_id: int) -> Optional[Dict[str, Any]]:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 65\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m通过ID获取任务详情\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 66\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Args:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 67\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task_id: 任务ID\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 68\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Returns:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 69\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m 任务字典,不存在则返回None\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 70\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 71\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m try:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 72\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task_df = self.db.query_to_df(\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 73\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33mSELECT * FROM main_task WHERE task_id = \u001B[39m\u001B[38;5;132;01m%s\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m,\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 74\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m params=(task_id,)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 75\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m )\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 76\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m if task_df.empty:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 77\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return None\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 78\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return task_df.iloc[0].to_dict()\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 79\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m except Exception as e:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 80\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.error(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m获取任务详情失败 (ID: \u001B[39m\u001B[38;5;132;01m{task_id}\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 81\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return None\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 82\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 83\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m def update_task(self, task_id: int, updates: Dict[str, Any]) -> bool:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 84\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m更新任务属性\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 85\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Args:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 86\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task_id: 任务ID\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 87\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m updates: 需要更新的字段字典\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 88\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Returns:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 89\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m 更新是否成功\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 90\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 91\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m if not updates:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 92\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.warning(\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m未提供更新字段\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 93\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return False\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 94\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 95\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m try:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 96\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m # 处理Cron表达式更新(需重新计算下次运行时间)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 97\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m if \u001B[39m\u001B[33m'\u001B[39m\u001B[33mcron_expression\u001B[39m\u001B[33m'\u001B[39m\u001B[33m in updates or \u001B[39m\u001B[33m'\u001B[39m\u001B[33mtime_zone\u001B[39m\u001B[33m'\u001B[39m\u001B[33m in updates:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 98\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task = self.get_task_by_id(task_id)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 99\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m if not task:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 100\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return False\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 101\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 102\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m cron_expr = updates.get(\u001B[39m\u001B[33m'\u001B[39m\u001B[33mcron_expression\u001B[39m\u001B[33m'\u001B[39m\u001B[33m, task[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mcron_expression\u001B[39m\u001B[33m'\u001B[39m\u001B[33m])\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 103\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m time_zone = updates.get(\u001B[39m\u001B[33m'\u001B[39m\u001B[33mtime_zone\u001B[39m\u001B[33m'\u001B[39m\u001B[33m, task[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mtime_zone\u001B[39m\u001B[33m'\u001B[39m\u001B[33m])\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 104\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m updates[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mnext_run_time\u001B[39m\u001B[33m'\u001B[39m\u001B[33m] = self.scheduler._calculate_next_run_time(cron_expr, time_zone)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 105\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 106\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m # 执行更新\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 107\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.scheduler._update_task_status(task_id, updates)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 108\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.info(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m任务更新成功 (ID: \u001B[39m\u001B[38;5;132;01m{task_id}\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 109\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return True\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 110\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m except Exception as e:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 111\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.error(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m任务更新失败 (ID: \u001B[39m\u001B[38;5;132;01m{task_id}\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 112\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return False\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 113\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 114\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m def toggle_task_status(self, task_id: int, activate: bool) -> bool:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 115\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m切换任务激活状态\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 116\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Args:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 117\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task_id: 任务ID\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 118\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m activate: True=激活, False=禁用\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 119\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Returns:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 120\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m 操作是否成功\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 121\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 122\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m try:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 123\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m status = 1 if activate else 0\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 124\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m # 激活时重新计算下次运行时间\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 125\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m updates = \u001B[39m\u001B[33m{\u001B[39m\u001B[33m'\u001B[39m\u001B[33mis_active\u001B[39m\u001B[33m'\u001B[39m\u001B[33m: status}\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 126\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m if activate:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 127\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task = self.get_task_by_id(task_id)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 128\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m if task:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 129\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m updates[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mnext_run_time\u001B[39m\u001B[33m'\u001B[39m\u001B[33m] = self.scheduler._calculate_next_run_time(\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 130\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mcron_expression\u001B[39m\u001B[33m'\u001B[39m\u001B[33m], task[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mtime_zone\u001B[39m\u001B[33m'\u001B[39m\u001B[33m]\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 131\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m )\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 132\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 133\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.scheduler._update_task_status(task_id, updates)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 134\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.info(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m任务\u001B[39m\u001B[33m{\u001B[39m\u001B[33m'\u001B[39m\u001B[33m激活\u001B[39m\u001B[33m'\u001B[39m\u001B[33m if activate else \u001B[39m\u001B[33m'\u001B[39m\u001B[33m禁用\u001B[39m\u001B[33m'\u001B[39m\u001B[33m}成功 (ID: \u001B[39m\u001B[38;5;132;01m{task_id}\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 135\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return True\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 136\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m except Exception as e:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 137\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.error(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m任务状态切换失败 (ID: \u001B[39m\u001B[38;5;132;01m{task_id}\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 138\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return False\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 139\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 140\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m def delete_task(self, task_id: int) -> bool:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 141\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m删除任务\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 142\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Args:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 143\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task_id: 任务ID\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 144\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Returns:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 145\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m 删除是否成功\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 146\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 147\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m try:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 148\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m # 检查任务是否存在\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 149\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m if not self.get_task_by_id(task_id):\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 150\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.warning(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m任务不存在 (ID: \u001B[39m\u001B[38;5;132;01m{task_id}\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 151\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return False\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 152\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 153\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m # 执行删除\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 154\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.db.execute_sql(\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 155\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33mDELETE FROM main_task WHERE task_id = \u001B[39m\u001B[38;5;132;01m%s\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m,\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 156\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m params=(task_id,)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 157\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m )\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 158\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.info(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m任务删除成功 (ID: \u001B[39m\u001B[38;5;132;01m{task_id}\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 159\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return True\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 160\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m except Exception as e:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 161\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.error(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m任务删除失败 (ID: \u001B[39m\u001B[38;5;132;01m{task_id}\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 162\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return False\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 163\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 164\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m def run_task_manually(self, task_id: int) -> bool:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 165\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m手动执行任务\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 166\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Args:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 167\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task_id: 任务ID\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 168\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Returns:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 169\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m 执行是否成功\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 170\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 171\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m try:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 172\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task = self.get_task_by_id(task_id)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 173\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m if not task:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 174\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.warning(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m任务不存在 (ID: \u001B[39m\u001B[38;5;132;01m{task_id}\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 175\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return False\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 176\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 177\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m # 标记任务为运行中\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 178\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.scheduler._update_task_status(task_id, \u001B[39m\u001B[33m{\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 179\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[33m'\u001B[39m\u001B[33mis_running\u001B[39m\u001B[33m'\u001B[39m\u001B[33m: 1,\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 180\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[33m'\u001B[39m\u001B[33mlast_run_time\u001B[39m\u001B[33m'\u001B[39m\u001B[33m: datetime.now()\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 181\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m })\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 182\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 183\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m # 执行任务逻辑\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 184\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.scheduler._execute_task_logic(task)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 185\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 186\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m # 更新任务状态\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 187\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m next_run_time = self.scheduler._calculate_next_run_time(\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 188\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m task[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mcron_expression\u001B[39m\u001B[33m'\u001B[39m\u001B[33m], task[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mtime_zone\u001B[39m\u001B[33m'\u001B[39m\u001B[33m]\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 189\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m )\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 190\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.scheduler._update_task_status(task_id, \u001B[39m\u001B[33m{\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 191\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[33m'\u001B[39m\u001B[33mis_running\u001B[39m\u001B[33m'\u001B[39m\u001B[33m: 0,\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 192\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[33m'\u001B[39m\u001B[33mlast_run_status\u001B[39m\u001B[33m'\u001B[39m\u001B[33m: \u001B[39m\u001B[33m'\u001B[39m\u001B[33msuccess\u001B[39m\u001B[33m'\u001B[39m\u001B[33m,\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 193\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[33m'\u001B[39m\u001B[33mrun_count\u001B[39m\u001B[33m'\u001B[39m\u001B[33m: task[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mrun_count\u001B[39m\u001B[33m'\u001B[39m\u001B[33m] + 1,\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 194\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[33m'\u001B[39m\u001B[33mnext_run_time\u001B[39m\u001B[33m'\u001B[39m\u001B[33m: next_run_time\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 195\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m })\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 196\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return True\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 197\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m except Exception as e:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 198\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.scheduler._update_task_status(task_id, \u001B[39m\u001B[33m{\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 199\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[33m'\u001B[39m\u001B[33mis_running\u001B[39m\u001B[33m'\u001B[39m\u001B[33m: 0,\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 200\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[33m'\u001B[39m\u001B[33mlast_run_status\u001B[39m\u001B[33m'\u001B[39m\u001B[33m: \u001B[39m\u001B[33m'\u001B[39m\u001B[33mfailed\u001B[39m\u001B[33m'\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 201\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m })\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 202\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m self.log.error(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m任务手动执行失败 (ID: \u001B[39m\u001B[38;5;132;01m{task_id}\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 203\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return False\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 204\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 205\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m def print_task_table(self, tasks: List[Dict[str, Any]]) -> None:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 206\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m格式化打印任务列表\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 207\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m Args:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 208\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m tasks: 任务列表\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 209\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 210\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m if not tasks:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 211\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m print(\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m没有任务数据\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 212\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m return\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 213\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 214\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m # 转换为DataFrame并筛选显示字段\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 215\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m df = pd.DataFrame(tasks)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 216\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m display_cols = [\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 217\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[33m'\u001B[39m\u001B[33mtask_id\u001B[39m\u001B[33m'\u001B[39m\u001B[33m, \u001B[39m\u001B[33m'\u001B[39m\u001B[33mtask_name\u001B[39m\u001B[33m'\u001B[39m\u001B[33m, \u001B[39m\u001B[33m'\u001B[39m\u001B[33mtask_type\u001B[39m\u001B[33m'\u001B[39m\u001B[33m, \u001B[39m\u001B[33m'\u001B[39m\u001B[33mcron_expression\u001B[39m\u001B[33m'\u001B[39m\u001B[33m,\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 218\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m \u001B[39m\u001B[33m'\u001B[39m\u001B[33mis_active\u001B[39m\u001B[33m'\u001B[39m\u001B[33m, \u001B[39m\u001B[33m'\u001B[39m\u001B[33mlast_run_status\u001B[39m\u001B[33m'\u001B[39m\u001B[33m, \u001B[39m\u001B[33m'\u001B[39m\u001B[33mnext_run_time\u001B[39m\u001B[33m'\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 219\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m ]\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 220\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m # 处理状态显示\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 221\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m df[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mis_active\u001B[39m\u001B[33m'\u001B[39m\u001B[33m] = df[\u001B[39m\u001B[33m'\u001B[39m\u001B[33mis_active\u001B[39m\u001B[33m'\u001B[39m\u001B[33m].map(\u001B[39m\u001B[33m{\u001B[39m\u001B[33m1: \u001B[39m\u001B[33m'\u001B[39m\u001B[33m活跃\u001B[39m\u001B[33m'\u001B[39m\u001B[33m, 0: \u001B[39m\u001B[33m'\u001B[39m\u001B[33m禁用\u001B[39m\u001B[33m'\u001B[39m\u001B[33m})\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 222\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m print(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m共 \u001B[39m\u001B[33m{\u001B[39m\u001B[33mlen(df)} 个任务\u001B[39m\u001B[38;5;130;01m\\\\\u001B[39;00m\u001B[33mn\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 223\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m print(df[display_cols].to_string(index=False))\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 224\u001B[39m ]\n\u001B[32m 225\u001B[39m },\n\u001B[32m 226\u001B[39m {\n\u001B[32m 227\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mcell_type\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mmarkdown\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 228\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmetadata\u001B[39m\u001B[33m\"\u001B[39m: {},\n\u001B[32m 229\u001B[39m \u001B[33m\"\u001B[39m\u001B[33msource\u001B[39m\u001B[33m\"\u001B[39m: [\n\u001B[32m 230\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m## 使用示例\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 231\u001B[39m ]\n\u001B[32m 232\u001B[39m },\n\u001B[32m 233\u001B[39m {\n\u001B[32m 234\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mcell_type\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mcode\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 235\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mexecution_count\u001B[39m\u001B[33m\"\u001B[39m: null,\n\u001B[32m 236\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmetadata\u001B[39m\u001B[33m\"\u001B[39m: {},\n\u001B[32m 237\u001B[39m \u001B[33m\"\u001B[39m\u001B[33moutputs\u001B[39m\u001B[33m\"\u001B[39m: [],\n\u001B[32m 238\u001B[39m \u001B[33m\"\u001B[39m\u001B[33msource\u001B[39m\u001B[33m\"\u001B[39m: [\n\u001B[32m 239\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m# 初始化示例\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 240\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mfrom config.config import ConfigManager\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 241\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 242\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m# 加载配置\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 243\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mconfig = ConfigManager()\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 244\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mscheduler = TaskScheduler(config.get(\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33mdatabase\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m))\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 245\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmanager = TaskManager(scheduler)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 246\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 247\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m# 1. 列出所有任务\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 248\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mall_tasks = manager.get_all_tasks()\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 249\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmanager.print_task_table(all_tasks)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 250\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 251\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m# 2. 获取单个任务详情\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 252\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mtask = manager.get_task_by_id(1)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 253\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mif task:\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 254\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m print(\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;130;01m\\\\\u001B[39;00m\u001B[33mn任务详情:\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 255\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m for k, v in task.items():\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 256\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m print(f\u001B[39m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[38;5;132;01m{k}\u001B[39;00m\u001B[33m: \u001B[39m\u001B[38;5;132;01m{v}\u001B[39;00m\u001B[38;5;130;01m\\\"\u001B[39;00m\u001B[33m)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 257\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 258\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m# 3. 更新任务Cron表达式\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 259\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmanager.update_task(1, \u001B[39m\u001B[33m{\u001B[39m\u001B[33m'\u001B[39m\u001B[33mcron_expression\u001B[39m\u001B[33m'\u001B[39m\u001B[33m: \u001B[39m\u001B[33m'\u001B[39m\u001B[33m0 */2 * * *\u001B[39m\u001B[33m'\u001B[39m\u001B[33m})\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 260\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 261\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m# 4. 激活任务\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 262\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmanager.toggle_task_status(1, activate=True)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 263\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 264\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m# 5. 手动执行任务\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 265\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmanager.run_task_manually(1)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 266\u001B[39m \u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 267\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m# 6. 删除任务(谨慎操作)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 268\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m# manager.delete_task(1)\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 269\u001B[39m ]\n\u001B[32m 270\u001B[39m },\n\u001B[32m 271\u001B[39m {\n\u001B[32m 272\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mcell_type\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mmarkdown\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 273\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmetadata\u001B[39m\u001B[33m\"\u001B[39m: {},\n\u001B[32m 274\u001B[39m \u001B[33m\"\u001B[39m\u001B[33msource\u001B[39m\u001B[33m\"\u001B[39m: [\n\u001B[32m 275\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m## 核心功能说明\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 276\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m- 依赖 `TaskScheduler` 实现底层调度逻辑与数据库交互\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 277\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m- 支持任务全生命周期管理(查询/更新/激活/执行/删除)\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 278\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m- 内置日志记录与错误处理\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m\"\u001B[39m,\n\u001B[32m 279\u001B[39m \u001B[33m\"\u001B[39m\u001B[33m- 兼容Windows/macOS/Linux多平台\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 280\u001B[39m ]\n\u001B[32m 281\u001B[39m }\n\u001B[32m 282\u001B[39m ],\n\u001B[32m 283\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmetadata\u001B[39m\u001B[33m\"\u001B[39m: {\n\u001B[32m 284\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mkernelspec\u001B[39m\u001B[33m\"\u001B[39m: {\n\u001B[32m 285\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mdisplay_name\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mPython 3\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 286\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mlanguage\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mpython\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 287\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mname\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mpython3\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 288\u001B[39m },\n\u001B[32m 289\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mlanguage_info\u001B[39m\u001B[33m\"\u001B[39m: {\n\u001B[32m 290\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mcodemirror_mode\u001B[39m\u001B[33m\"\u001B[39m: {\n\u001B[32m 291\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mname\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mipython\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 292\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mversion\u001B[39m\u001B[33m\"\u001B[39m: \u001B[32m3\u001B[39m\n\u001B[32m 293\u001B[39m },\n\u001B[32m 294\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mfile_extension\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33m.py\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 295\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mmimetype\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mtext/x-python\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 296\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mname\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mpython\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 297\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mnbconvert_exporter\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mpython\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 298\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mpygments_lexer\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33mipython3\u001B[39m\u001B[33m\"\u001B[39m,\n\u001B[32m 299\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mversion\u001B[39m\u001B[33m\"\u001B[39m: \u001B[33m\"\u001B[39m\u001B[33m3.8.10\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 300\u001B[39m }\n\u001B[32m 301\u001B[39m },\n\u001B[32m 302\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mnbformat\u001B[39m\u001B[33m\"\u001B[39m: \u001B[32m4\u001B[39m,\n\u001B[32m 303\u001B[39m \u001B[33m\"\u001B[39m\u001B[33mnbformat_minor\u001B[39m\u001B[33m\"\u001B[39m: \u001B[32m4\u001B[39m\n\u001B[32m 304\u001B[39m }\n", + "\u001B[31mNameError\u001B[39m: name 'null' is not defined" ] } ], - "execution_count": 1 + "execution_count": 4 }, { "metadata": {}, @@ -345,10 +344,13 @@ "id": "8271189cef3b5f17" }, { - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-09-18T09:06:51.924812Z", + "start_time": "2025-09-18T09:06:51.885005Z" + } + }, "cell_type": "code", - "outputs": [], - "execution_count": null, "source": [ "# 列出所有任务(包括已禁用的)\n", "def list_tasks(active_only=True):\n", @@ -390,7 +392,22 @@ "# 或者:只列出活跃任务\n", "# list_tasks(active_only=True)" ], - "id": "7b020af55972643" + "id": "7b020af55972643", + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'manager' is not defined", + "output_type": "error", + "traceback": [ + "\u001B[31m---------------------------------------------------------------------------\u001B[39m", + "\u001B[31mNameError\u001B[39m Traceback (most recent call last)", + "\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[3]\u001B[39m\u001B[32m, line 36\u001B[39m\n\u001B[32m 33\u001B[39m \u001B[38;5;28;01mreturn\u001B[39;00m df\n\u001B[32m 35\u001B[39m \u001B[38;5;66;03m# 执行:列出所有任务(包括已禁用)\u001B[39;00m\n\u001B[32m---> \u001B[39m\u001B[32m36\u001B[39m \u001B[43mlist_tasks\u001B[49m\u001B[43m(\u001B[49m\u001B[43mactive_only\u001B[49m\u001B[43m=\u001B[49m\u001B[38;5;28;43;01mFalse\u001B[39;49;00m\u001B[43m)\u001B[49m\n\u001B[32m 38\u001B[39m \u001B[38;5;66;03m# 或者:只列出活跃任务\u001B[39;00m\n\u001B[32m 39\u001B[39m \u001B[38;5;66;03m# list_tasks(active_only=True)\u001B[39;00m\n", + "\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[3]\u001B[39m\u001B[32m, line 3\u001B[39m, in \u001B[36mlist_tasks\u001B[39m\u001B[34m(active_only)\u001B[39m\n\u001B[32m 2\u001B[39m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34mlist_tasks\u001B[39m(active_only=\u001B[38;5;28;01mTrue\u001B[39;00m):\n\u001B[32m----> \u001B[39m\u001B[32m3\u001B[39m tasks = \u001B[43mmanager\u001B[49m.get_all_tasks(active_only)\n\u001B[32m 4\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m tasks:\n\u001B[32m 5\u001B[39m display(Markdown(\u001B[33m\"\u001B[39m\u001B[33m### 没有找到任务\u001B[39m\u001B[33m\"\u001B[39m))\n", + "\u001B[31mNameError\u001B[39m: name 'manager' is not defined" + ] + } + ], + "execution_count": 3 }, { "metadata": {}, diff --git a/utils/mysql_agent.py b/utils/mysql_agent.py index d891ed1..0a1472e 100644 --- a/utils/mysql_agent.py +++ b/utils/mysql_agent.py @@ -65,7 +65,7 @@ class MySQLAgent: def get_connection(self) -> pymysql.connections.Connection: """获取数据库连接(原有逻辑完全保留)""" try: - conn = pymysql.connect(**self.config) + conn = pymysql.connect(** self.config) # 为连接添加 character_set_name 方法 if not hasattr(conn, 'character_set_name'): @@ -78,16 +78,16 @@ class MySQLAgent: if platform.system() == 'Darwin' and self.config.get('ssl'): conn.ping(reconnect=True) - self.log.trace("Database connection obtained") + self.log.trace("获取数据库连接成功") return conn except Exception as e: error_msg = str(e) if platform.system() == 'Windows' and "timed out" in error_msg: - self.log.warning("Windows connection timeout, retrying...") + self.log.warning("Windows连接超时,正在重试...") return self._retry_connection() - self.log.error("Connection failed", error=error_msg, exc_info=True) + self.log.error("连接失败", error=error_msg, exc_info=True) raise def _retry_connection(self, max_retries: int = 3) -> Any | None: @@ -95,7 +95,7 @@ class MySQLAgent: for attempt in range(max_retries): try: conn = pymysql.connect(**self.config) - self.log.info(f"Connection established after {attempt + 1} attempts") + self.log.info(f"经过 {attempt + 1} 次尝试后成功建立连接") return conn except Exception: if attempt == max_retries - 1: @@ -107,7 +107,7 @@ class MySQLAgent: parse_dates: Union[List[str], bool] = True) -> pd.DataFrame: """执行SQL查询并返回DataFrame(原有逻辑完全保留)""" try: - self.log.debug("Executing SQL query", sql=sql) + self.log.debug("执行SQL查询", sql=sql) # 获取连接并确保字符集方法存在 conn = self.get_connection() @@ -124,49 +124,50 @@ class MySQLAgent: # 执行查询 df = pd.read_sql(sql, engine, params=params, parse_dates=parse_dates) - self.log.info("Query executed successfully", rows=len(df)) + self.log.info("查询执行成功", 行数=len(df)) return df except Exception as e: - self.log.error("SQL query failed", sql=sql, params=params, error=str(e), exc_info=True) + self.log.error("SQL查询失败", sql=sql, params=params, error=str(e), exc_info=True) raise finally: if 'engine' in locals(): engine.dispose() def insert_from_df(self, table_name: str, df: pd.DataFrame, - chunk_size: int = 1000, replace: bool = False, # 保留replace参数 - ignore_duplicates: bool = None) -> int: # 新增ignore_duplicates参数 + chunk_size: int = 1000, replace: bool = False, + ignore_duplicates: bool = None) -> int: """ 兼容旧接口的通用插入方法:保留replace参数,同时支持新的ignore_duplicates - 自动处理重复数据,对所有数据源通用 + 自动处理重复数据,对所有数据源通用,插入失败的数据会通过日志记录 """ - # 【兼容性处理】如果未指定ignore_duplicates,用replace参数推导(replace=True时不忽略重复) + # 【兼容性处理】如果未指定ignore_duplicates,用replace参数推导 if ignore_duplicates is None: ignore_duplicates = not replace # 旧逻辑中replace=True表示替换,即不忽略重复 if df.empty: - self.log.warning("Attempted to insert empty DataFrame", table=table_name) + self.log.warning("尝试插入空的DataFrame", table=table_name) return 0 conn = None cursor = None total_inserted = 0 - total_duplicated = 0 + total_duplicates = 0 total_failed = 0 + failed_records = [] # 存储所有失败的记录 try: # 1. 建立数据库连接 conn = self.get_connection() cursor = conn.cursor() - self.log.debug(f"Established connection for inserting into {table_name}") + self.log.debug(f"已建立连接,准备插入数据到 {table_name}") # 2. 获取数据库表的实际列名 cursor.execute(f"SHOW COLUMNS FROM `{table_name}`") columns_info = cursor.fetchall() db_columns = [col[0] for col in columns_info] - self.log.debug(f"Table {table_name} has columns: {db_columns}") + self.log.debug(f"表 {table_name} 包含以下列:{db_columns}") # 3. 数据预处理:统一处理空值 cleaned_df = df.replace( @@ -181,19 +182,19 @@ class MySQLAgent: if unmatched_columns: self.log.warning( - f"Table {table_name} dropping unmatched columns", + f"表 {table_name} 中存在不匹配的列,已自动丢弃", unmatched_columns=unmatched_columns, count=len(unmatched_columns) ) if not matched_columns: - self.log.warning(f"No matched columns for {table_name}, abort insertion") + self.log.warning(f"表 {table_name} 没有匹配的列,终止插入操作") return 0 filtered_df = cleaned_df[matched_columns].copy() total_to_insert = len(filtered_df) self.log.debug( - f"Filtered DataFrame for {table_name}: {total_to_insert} rows to insert" + f"表 {table_name} 的过滤后DataFrame:共 {total_to_insert} 行待插入" ) # 5. 处理复杂类型(dict/list转JSON) @@ -203,7 +204,7 @@ class MySQLAgent: ).any() if has_complex_type: - self.log.debug(f"Column {col} in {table_name} has complex type, converting to JSON") + self.log.debug(f"表 {table_name} 中的 {col} 列包含复杂类型,正在转换为JSON") filtered_df.loc[:, col] = filtered_df[col].apply( lambda x: json.dumps(x, ensure_ascii=False) if x is not None else x ) @@ -212,7 +213,7 @@ class MySQLAgent: columns_str = ', '.join([f"`{col}`" for col in filtered_df.columns]) placeholders = ', '.join(['%s'] * len(filtered_df.columns)) insert_sql = f"INSERT INTO `{table_name}` ({columns_str}) VALUES ({placeholders})" - self.log.trace(f"Generated insert SQL for {table_name}: {insert_sql}") + self.log.trace(f"为表 {table_name} 生成的插入SQL:{insert_sql}") # 7. 逐条插入(确保能捕获单条重复错误) records = filtered_df.to_dict('records') @@ -226,34 +227,50 @@ class MySQLAgent: if (i + 1) % 100 == 0: self.log.trace( - f"Inserted {i + 1}/{total_to_insert} rows into {table_name}" + f"已向表 {table_name} 插入 {i + 1}/{total_to_insert} 行数据" ) except MySQLError as e: # 8. 捕获重复错误(MySQL错误码1062) if e.args[0] == 1062: - total_duplicated += 1 + total_duplicates += 1 short_record = { k: (str(v)[:100] + '...') if isinstance(v, (str, dict, list)) else v for k, v in record.items() } self.log.warning( - f"Skipped duplicate record in {table_name}", + f"表 {table_name} 中跳过重复记录", index=idx, - error_msg=e.args[1], + error_message=e.args[1], record=short_record ) + # 记录重复的记录 + failed_records.append({ + 'index': idx, + 'type': 'duplicate', + 'error_code': e.args[0], + 'error_message': e.args[1], + 'record': record + }) if not ignore_duplicates: raise else: # 其他数据库错误 total_failed += 1 + # 记录失败的记录详情 + failed_records.append({ + 'index': idx, + 'type': 'error', + 'error_code': e.args[0], + 'error_message': e.args[1], + 'record': record + }) self.log.error( - f"Failed to insert record in {table_name}", + f"表 {table_name} 插入记录失败", index=idx, error_code=e.args[0], - error_msg=e.args[1], - record=record + error_message=e.args[1], + record=record # 完整记录写入日志 ) if not ignore_duplicates: raise @@ -261,21 +278,44 @@ class MySQLAgent: # 提交事务 conn.commit() - # 9. 插入结果统计 + # 9. 插入结果统计,包括失败记录汇总 self.log.info( - f"Insertion summary for {table_name}", + f"表 {table_name} 插入结果汇总", total_to_insert=total_to_insert, total_inserted=total_inserted, - total_duplicated=total_duplicated, - total_failed=total_failed + total_duplicates=total_duplicates, + total_failed=total_failed, + failed_records_count=len(failed_records) ) + # 单独记录所有失败的数据详情 + if failed_records: + self.log.error( + f"表 {table_name} 插入失败记录详情", + failed_records_summary=[ + { + 'index': r['index'], + 'type': r['type'], + 'error_code': r['error_code'], + 'error_message': r['error_message'] + } for r in failed_records + ], + # 完整记录可以作为调试信息单独记录,避免日志过大 + detailed_failed_records=failed_records + ) + return total_inserted except Exception as e: if conn: conn.rollback() - self.log.error(f"Batch insertion failed for {table_name}", error=str(e), exc_info=True) + self.log.error(f"表 {table_name} 批量插入失败", error=str(e), exc_info=True) + # 记录事务回滚时的失败记录 + if failed_records: + self.log.error( + f"表 {table_name} 事务回滚,已失败的记录", + failed_records=failed_records + ) raise finally: if cursor: @@ -296,7 +336,7 @@ class MySQLAgent: result = cursor.fetchone() return result[0] if result else None except Exception as e: - self.log.warning(f"Failed to get primary key for {table_name}", error=str(e)) + self.log.warning(f"获取表 {table_name} 的主键失败", error=str(e)) return None def _get_table_detailed_info(self, table_name: str) -> Dict[str, Dict[str, Any]]: @@ -319,7 +359,7 @@ class MySQLAgent: # 强制转换为列表,避免游标类型导致的解析问题 result_list = list(result) if not result_list: - self.log.error("No columns found in table", table=table_name) + self.log.error("未在表中找到任何列", 表=table_name) return {} schema = {} @@ -334,16 +374,16 @@ class MySQLAgent: 'max_length': max_length } - self.log.debug("Successfully fetched table schema", - table=table_name, - columns=list(schema.keys())) + self.log.debug("成功获取表结构信息", + 表=table_name, + 列=list(schema.keys())) return schema finally: cursor.close() conn.close() except Exception as e: - self.log.error("Failed to get table detailed info", - table=table_name, + self.log.error("获取表详细信息失败", + 表=table_name, error=str(e)) raise @@ -358,10 +398,10 @@ class MySQLAgent: invalid_columns = [col for col in df_columns if col not in table_columns] if invalid_columns: - self.log.warning("Dropping invalid columns not present in table", - table=table_name, - invalid_columns=invalid_columns, - count=len(invalid_columns)) + self.log.warning("丢弃表中不存在的无效列", + 表=table_name, + 无效列=invalid_columns, + 数量=len(invalid_columns)) cleaned_df = df[valid_columns].copy() if cleaned_df.empty: @@ -378,11 +418,11 @@ class MySQLAgent: # 根据字段类型设置默认值 default_value = '' if data_type in ['varchar', 'char', 'text'] else None cleaned_df[col].fillna(default_value, inplace=True) - self.log.debug("Replaced null values", - table=table_name, - column=col, - default_value=default_value, - count=cleaned_df[col].isnull().sum()) + self.log.debug("替换空值", + 表=table_name, + 列=col, + 默认值=default_value, + 数量=cleaned_df[col].isnull().sum()) # 2.2 处理字符串类型的超长字段 if data_type in ['varchar', 'char'] and max_length: @@ -392,11 +432,11 @@ class MySQLAgent: too_long_mask = cleaned_df[col].str.len() > max_length if too_long_mask.any(): cleaned_df.loc[too_long_mask, col] = cleaned_df.loc[too_long_mask, col].str.slice(0, max_length) - self.log.warning("Truncated overlength values", - table=table_name, - column=col, - max_length=max_length, - count=too_long_mask.sum()) + self.log.warning("截断超长值", + 表=table_name, + 列=col, + 最大长度=max_length, + 数量=too_long_mask.sum()) # 2.3 处理日期时间类型 if data_type in ['datetime', 'timestamp']: @@ -404,10 +444,10 @@ class MySQLAgent: # 尝试转换为datetime类型 cleaned_df[col] = pd.to_datetime(cleaned_df[col]) except Exception as e: - self.log.warning("Failed to convert to datetime, using current time", - table=table_name, - column=col, - error=str(e)) + self.log.warning("转换为datetime失败,使用当前时间替代", + 表=table_name, + 列=col, + 错误=str(e)) # 转换失败的用当前时间替代 invalid_mask = pd.to_datetime(cleaned_df[col], errors='coerce').isna() cleaned_df.loc[invalid_mask, col] = datetime.now() @@ -418,19 +458,19 @@ class MySQLAgent: key_columns: Union[str, List[str]]) -> int: """使用DataFrame数据更新数据库表(原有逻辑完全保留)""" if df.empty: - self.log.warning("Attempted to update with empty DataFrame", table=table_name) + self.log.warning("尝试使用空的DataFrame进行更新", 表=table_name) return 0 - self.log.debug("Preparing to update table from DataFrame", - table=table_name, - key_columns=key_columns, - rows=len(df)) + self.log.debug("准备从DataFrame更新表数据", + 表=table_name, + 关键字列=key_columns, + 行数=len(df)) try: if isinstance(key_columns, str): key_columns = [key_columns] - total_updated = 0 + 总更新数 = 0 with self.get_connection() as conn: with conn.cursor() as cursor: # 获取表结构信息 @@ -442,11 +482,11 @@ class MySQLAgent: where_clause = ' AND '.join([f"{col}=%s" for col in key_columns]) if not set_clause: - self.log.warning("No columns to update", table=table_name) + self.log.warning("没有可更新的列", 表=table_name) return 0 update_sql = f"UPDATE {table_name} SET {set_clause} WHERE {where_clause}" - self.log.trace("Generated update SQL", sql=update_sql) + self.log.trace("生成的更新SQL", sql=update_sql) # 准备数据 update_data = [] @@ -457,17 +497,17 @@ class MySQLAgent: # 执行批量更新 cursor.executemany(update_sql, update_data) - total_updated = cursor.rowcount + 总更新数 = cursor.rowcount conn.commit() - self.log.info("Data updated successfully", - table=table_name, - rows_updated=total_updated) - return total_updated + self.log.info("数据更新成功", + 表=table_name, + 更新行数=总更新数) + return 总更新数 except Exception as e: - self.log.error("Data update failed", - table=table_name, + self.log.error("数据更新失败", + 表=table_name, error=str(e), exc_info=True) raise @@ -488,20 +528,20 @@ class MySQLAgent: dtype_str = str(dtype) sql_types[col] = type_mapping.get(dtype_str, 'TEXT') - self.log.debug("Mapped DataFrame types to SQL types", - mappings=sql_types) + self.log.debug("将DataFrame类型映射为SQL类型", + 映射关系=sql_types) return sql_types def create_table_from_df(self, table_name: str, df: pd.DataFrame, primary_key: Union[str, List[str], None] = None) -> bool: """根据DataFrame结构创建表(原有逻辑完全保留)""" if self.table_exists(table_name): - self.log.warning("Table already exists", table=table_name) + self.log.warning("表已存在", 表=table_name) return False - self.log.debug("Creating new table from DataFrame schema", - table=table_name, - columns=list(df.columns)) + self.log.debug("根据DataFrame结构创建新表", + 表=table_name, + 列=list(df.columns)) try: sql_types = self.df_to_sql_type(df) @@ -517,19 +557,19 @@ class MySQLAgent: pk_columns = [col for col in primary_key if col in sql_types] if pk_columns: columns_sql.append(f"PRIMARY KEY ({', '.join(pk_columns)})") - self.log.trace("Set primary key", - table=table_name, - primary_key=pk_columns) + self.log.trace("设置主键", + 表=table_name, + 主键=pk_columns) create_sql = f"CREATE TABLE {table_name} (\n {',\n '.join(columns_sql)}\n)" self.execute_sql(create_sql) - self.log.info("Table created successfully", table=table_name) + self.log.info("表创建成功", 表=table_name) return True except Exception as e: - self.log.error("Failed to create table", - table=table_name, + self.log.error("创建表失败", + 表=table_name, error=str(e), exc_info=True) return False @@ -548,16 +588,16 @@ class MySQLAgent: if fetch: result = cursor.fetchall() - self.log.debug("Query executed", rows=len(result)) + self.log.debug("查询执行完成", 行数=len(result)) return result else: affected_rows = cursor.rowcount conn.commit() # 立即提交 - self.log.debug("Update executed", affected_rows=affected_rows) + self.log.debug("更新执行完成", 受影响行数=affected_rows) return affected_rows except Exception as e: - self.log.error("SQL execution failed", + self.log.error("SQL执行失败", sql=sql, params=params, error=str(e), @@ -578,9 +618,9 @@ class MySQLAgent: try: result = self.execute_sql(sql, params, fetch=True) exists = result[0][0] > 0 # 适配元组结果 - self.log.debug("Checked table existence", - table=table_name, - exists=exists) + self.log.debug("检查表是否存在", + 表=table_name, + 存在=exists) return exists except Exception: return False @@ -588,16 +628,16 @@ class MySQLAgent: def drop_table(self, table_name: str) -> bool: """删除表(原有逻辑完全保留)""" if not self.table_exists(table_name): - self.log.warning("Table does not exist", table=table_name) + self.log.warning("表不存在", 表=table_name) return False try: self.execute_sql(f"DROP TABLE {table_name}") - self.log.info("Table dropped successfully", table=table_name) + self.log.info("表删除成功", 表=table_name) return True except Exception as e: - self.log.error("Failed to drop table", - table=table_name, + self.log.error("删除表失败", + 表=table_name, error=str(e), exc_info=True) return False @@ -641,7 +681,7 @@ def get_default_config(): 'ssl': {'ca': '/usr/local/etc/openssl/cert.pem'} } else: # Linux和其他平台 - return {**base_config, + return {** base_config, 'connect_timeout': 15, 'read_timeout': 60, 'write_timeout': 60 @@ -654,10 +694,10 @@ if __name__ == "__main__": # 测试连接 if db.validate_connection(): - print("Database connection successful") + print("数据库连接成功") # 获取数据库版本 version = db.query_to_df("SELECT VERSION() as version") - print(f"Database version: {version['version'].iloc[0]}") + print(f"数据库版本: {version['version'].iloc[0]}") else: - print("Failed to connect to database") \ No newline at end of file + print("连接数据库失败")