This commit is contained in:
Windpicker-owo
2025-08-31 20:56:35 +08:00
12 changed files with 174 additions and 164 deletions

View File

@@ -64,8 +64,9 @@ class MonthlyPlanManager:
target_month = datetime.now().strftime("%Y-%m")
if not has_active_plans(target_month):
logger.info(f" {target_month} 没有任何有效的月度计划,将立即生成。")
return await self.generate_monthly_plans(target_month)
logger.info(f" {target_month} 没有任何有效的月度计划,将触发同步生成。")
generation_successful = await self._generate_monthly_plans_logic(target_month)
return generation_successful
else:
logger.info(f"{target_month} 已存在有效的月度计划。")
plans = get_active_plans_for_month(target_month)
@@ -74,8 +75,8 @@ class MonthlyPlanManager:
max_plans = global_config.monthly_plan_system.max_plans_per_month
if len(plans) > max_plans:
logger.warning(f"当前月度计划数量 ({len(plans)}) 超出上限 ({max_plans}),将自动删除多余的计划。")
# 按创建时间序排序(的在前),然后删除超出上限的部分(新的)
plans_to_delete = sorted(plans, key=lambda p: p.created_at, reverse=True)[: len(plans) - max_plans]
# 数据库查询结果已按创建时间序排序(的在前),直接截取超出上限的部分进行删除
plans_to_delete = plans[:len(plans)-max_plans]
delete_ids = [p.id for p in plans_to_delete]
delete_plans_by_ids(delete_ids)
# 重新获取计划列表
@@ -86,10 +87,21 @@ class MonthlyPlanManager:
logger.info(f"当前月度计划内容:\n{plan_texts}")
return True # 已经有计划,也算成功
async def generate_monthly_plans(self, target_month: Optional[str] = None) -> bool:
async def generate_monthly_plans(self, target_month: Optional[str] = None):
"""
生成指定月份的月度计划
启动月度计划生成。
"""
if self.generation_running:
logger.info("月度计划生成任务已在运行中,跳过重复启动")
return
logger.info(f"已触发 {target_month or '当前月份'} 的月度计划生成任务。")
await self._generate_monthly_plans_logic(target_month)
async def _generate_monthly_plans_logic(self, target_month: Optional[str] = None) -> bool:
"""
生成指定月份的月度计划的核心逻辑
:param target_month: 目标月份,格式为 "YYYY-MM"。如果为 None则为当前月份。
:return: 是否生成成功
"""
@@ -136,14 +148,6 @@ class MonthlyPlanManager:
finally:
self.generation_running = False
def trigger_generate_monthly_plans(self, target_month: Optional[str] = None):
"""
以非阻塞的方式启动月度计划生成任务。
这允许其他模块如ScheduleManager触发计划生成而无需等待其完成。
"""
logger.info(f"已触发 {target_month or '当前月份'} 的非阻塞月度计划生成任务。")
asyncio.create_task(self.generate_monthly_plans(target_month))
def _get_previous_month(self, current_month: str) -> str:
"""获取上个月的月份字符串"""
try:
@@ -287,8 +291,6 @@ class MonthlyPlanManager:
except Exception as e:
logger.error(f" 归档 {target_month} 月度计划时发生错误: {e}")
class MonthlyPlanGenerationTask(AsyncTask):
"""每月初自动生成新月度计划的任务"""
@@ -324,8 +326,8 @@ class MonthlyPlanGenerationTask(AsyncTask):
# 生成新月份的计划
current_month = next_month.strftime("%Y-%m")
logger.info(f" 到达月初,开始生成 {current_month} 的月度计划...")
await self.monthly_plan_manager.generate_monthly_plans(current_month)
await self.monthly_plan_manager._generate_monthly_plans_logic(current_month)
except asyncio.CancelledError:
logger.info(" 每月月度计划生成任务被取消。")
break

View File

@@ -159,42 +159,37 @@ class ScheduleManager:
schedule_record = session.query(Schedule).filter(Schedule.date == today_str).first()
if schedule_record:
logger.info(f"从数据库加载今天的日程 ({today_str})。")
try:
schedule_data = orjson.loads(str(schedule_record.schedule_data))
# 使用Pydantic验证日程数据
if self._validate_schedule_with_pydantic(schedule_data):
self.today_schedule = schedule_data
schedule_str = f"已成功加载今天的日程 ({today_str})\n"
if self.today_schedule:
for item in self.today_schedule:
schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n"
logger.info(schedule_str)
else:
logger.warning("数据库中的日程数据格式无效,将异步重新生成日程")
await self.generate_and_save_schedule()
except orjson.JSONDecodeError as e:
logger.error(f"日程数据JSON解析失败: {e},将异步重新生成日程")
await self.generate_and_save_schedule()
schedule_data = orjson.loads(str(schedule_record.schedule_data))
if self._validate_schedule_with_pydantic(schedule_data):
self.today_schedule = schedule_data
schedule_str = f"已成功加载今天的日程 ({today_str})\n"
if self.today_schedule:
for item in self.today_schedule:
schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n"
logger.info(schedule_str)
return # 成功加载,直接返回
else:
logger.warning("数据库中的日程数据格式无效,将重新生成日程")
else:
logger.info(f"数据库中未找到今天的日程 ({today_str}),将异步调用 LLM 生成。")
await self.generate_and_save_schedule()
logger.info(f"数据库中未找到今天的日程 ({today_str}),将调用 LLM 生成。")
# 仅在需要时生成
await self.generate_and_save_schedule()
except Exception as e:
logger.error(f"加载或生成日程时出错: {e}")
# 出错时也尝试异步生成
logger.info("尝试异步生成日程作为备用方案...")
logger.info("尝试生成日程作为备用方案...")
await self.generate_and_save_schedule()
async def generate_and_save_schedule(self):
"""启动异步日程生成任务,避免阻塞主程序"""
"""日程生成任务提交到后台执行"""
if self.schedule_generation_running:
logger.info("日程生成任务已在运行中,跳过重复启动")
return
# 创建异步任务进行日程生成,不阻塞主程序
asyncio.create_task(self._async_generate_and_save_schedule())
logger.info("已启动异步日程生成任务")
logger.info("检测到需要生成日程,已提交后台任务。")
task = OnDemandScheduleGenerationTask(self)
await async_task_manager.add_task(task)
async def _async_generate_and_save_schedule(self):
"""异步生成并保存日程的内部方法"""
@@ -234,12 +229,14 @@ class ScheduleManager:
logger.info("可用的月度计划已耗尽或不足,触发后台补充生成...")
from mmc.src.schedule.monthly_plan_manager import monthly_plan_manager
# 以非阻塞方式触发月度计划生成
monthly_plan_manager.trigger_generate_monthly_plans(current_month_str)
# 等待月度计划生成完成
await monthly_plan_manager.ensure_and_generate_plans_if_needed(current_month_str)
# 注意:这里不再等待生成结果,因此后续代码不会立即获得新计划
# 日程将基于当前可用的信息生成,新计划将在下一次日程生成时可用。
logger.info("月度计划的后台生成任务已启动,本次日程将不包含新计划。")
# 重新获取月度计划
sampled_plans = get_smart_plans_for_daily_schedule(
current_month_str, max_count=3, avoid_days=avoid_days
)
logger.info("月度计划补充生成完毕,继续日程生成任务。")
if sampled_plans:
plan_texts = "\n".join([f"- {plan.plan_text}" for plan in sampled_plans])
@@ -448,6 +445,20 @@ class ScheduleManager:
return True
class OnDemandScheduleGenerationTask(AsyncTask):
"""按需生成日程的后台任务"""
def __init__(self, schedule_manager: "ScheduleManager"):
task_name = f"OnDemandScheduleGenerationTask-{datetime.now().strftime('%Y%m%d%H%M%S')}"
super().__init__(task_name=task_name)
self.schedule_manager = schedule_manager
async def run(self):
logger.info(f"后台任务 {self.task_name} 开始执行日程生成。")
await self.schedule_manager._async_generate_and_save_schedule()
logger.info(f"后台任务 {self.task_name} 完成。")
class DailyScheduleGenerationTask(AsyncTask):
"""每日零点自动生成新日程的任务"""
@@ -471,9 +482,9 @@ class DailyScheduleGenerationTask(AsyncTask):
# 2. 等待直到零点
await asyncio.sleep(sleep_seconds)
# 3. 执行异步日程生成
logger.info("到达每日零点,开始异步生成新的一天日程...")
await self.schedule_manager.generate_and_save_schedule()
# 3. 执行日程生成
logger.info("到达每日零点,开始生成新的一天日程...")
await self.schedule_manager._async_generate_and_save_schedule()
except asyncio.CancelledError:
logger.info("每日日程生成任务被取消。")