mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: restore persisted runtime schedules (#2231)
This commit is contained in:
@@ -716,7 +716,7 @@ DINGTALK_SECRET=
|
||||
#
|
||||
# 【通知路由策略】(Issue #1200 P3)
|
||||
# 默认留空:该类型通知发送到所有已配置渠道。填写后仅发送到列出的已配置渠道。
|
||||
# 允许值:wechat,feishu,telegram,email,pushover,ntfy,gotify,pushplus,serverchan3,custom,discord,slack,astrbot
|
||||
# 允许值:wechat,dingtalk,feishu,telegram,email,pushover,ntfy,gotify,pushplus,serverchan3,custom,discord,slack,astrbot
|
||||
# NOTIFICATION_REPORT_CHANNELS=
|
||||
# NOTIFICATION_ALERT_CHANNELS=
|
||||
# NOTIFICATION_SYSTEM_ERROR_CHANNELS=
|
||||
|
||||
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
<!-- 新条目格式:- [类型] 描述(类型取值:新功能/改进/修复/文档/测试/chore)-->
|
||||
<!-- 每条独立一行追加到本段末尾,无需分类标题,合并时冲突最小 -->
|
||||
- [修复] 已配置钉钉 Webhook 时不再误报“未配置通知渠道”;钉钉 Stream 仍仅用于交互,不作为定时静态推送渠道。
|
||||
- [修复] WebUI/API/Desktop 以 `--serve-only` 重启后会恢复已启用的定时任务,同时保持启动时不立即执行分析;通知路由示例补充钉钉 Webhook 渠道。
|
||||
- [改进] AIHubMix 注册与引流链接统一使用 inferera.com,改善中国大陆网络直连体验。
|
||||
- [修复] 单股推送模式在未配置通知渠道时仍会落盘本地个股报告;CLI 启动分析若因空股票列表、个股结果全失败或本地报告保存失败而未生成报告,会显式返回失败并记录原因。
|
||||
- [修复] 合并推送模式下即使个股汇总报告落盘失败,仍会先发送已有的合并通知;仅启用大盘复盘但最终未生成任何复盘内容时,分析任务会显式返回失败。
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
- WebUI 保存后会触发当前进程的配置重载;运行中的读取路径会同步使用最新写回的 `.env`,例如定时任务会继续热读取保存后的 `STOCK_LIST`
|
||||
- 如果容器启动命令里传入了这些同名环境变量(如 `--env-file .env`、`docker run -e ...` 或 Compose `environment:`),后续重启时仍可能以启动环境变量为准;要让 WebUI 保存值接管,请同步更新或移除这些同名 override
|
||||
- 如需持久化 WebUI 保存的配置,请将 `ENV_FILE` 指向 `/app/data/runtime.env` 等可写数据卷文件,不要把宿主机 `.env` 单文件挂载到 `/app/.env`
|
||||
- `SCHEDULE_ENABLED`、`SCHEDULE_TIME`、`SCHEDULE_TIMES` 保存后会让 WebUI/API/Desktop 长运行进程按新配置启停或重建 runtime scheduler
|
||||
- `SCHEDULE_ENABLED`、`SCHEDULE_TIME`、`SCHEDULE_TIMES` 保存后会让 WebUI/API/Desktop 长运行进程按新配置启停或重建 runtime scheduler;重启 `--serve-only` 或 Desktop 进程时会恢复已启用的 daily jobs,但不会在启动时立即执行分析
|
||||
- `SCHEDULE_RUN_IMMEDIATELY` 与 `RUN_IMMEDIATELY` 仍属于启动期/一次性运行配置,保存后不会立即触发一次分析
|
||||
3. **Docker 手工改 `.env` 后**:修改后仍建议重启容器
|
||||
```bash
|
||||
|
||||
@@ -101,7 +101,7 @@ This document compiles common issues encountered by users and their solutions.
|
||||
- Saving from WebUI triggers a config reload for the current process, and runtime reads continue from the latest persisted `.env`; for example, scheduled runs keep hot-reading the saved `STOCK_LIST`
|
||||
- If you pass the same keys as startup env vars (`--env-file .env`, `docker run -e ...`, or Compose `environment:`), those startup values can still win on later restarts; update or remove the same-name overrides if you want the WebUI-saved `.env` values to take over
|
||||
- To persist WebUI-saved config, point `ENV_FILE` at a writable data-volume file such as `/app/data/runtime.env`; do not bind-mount the host `.env` as a single file over `/app/.env`
|
||||
- Saving `SCHEDULE_ENABLED`, `SCHEDULE_TIME`, or `SCHEDULE_TIMES` starts, stops, or rebuilds the runtime scheduler in long-running WebUI/API/Desktop processes
|
||||
- Saving `SCHEDULE_ENABLED`, `SCHEDULE_TIME`, or `SCHEDULE_TIMES` starts, stops, or rebuilds the runtime scheduler in long-running WebUI/API/Desktop processes; restarting a `--serve-only` or Desktop process restores enabled daily jobs without immediately running an analysis at startup
|
||||
- `SCHEDULE_RUN_IMMEDIATELY` and `RUN_IMMEDIATELY` remain startup/one-shot settings; saving them does not immediately trigger an analysis run
|
||||
3. **Manual `.env` edits in Docker**: Restart the container after changes
|
||||
```bash
|
||||
|
||||
@@ -795,7 +795,7 @@ python main.py --schedule --no-run-immediately
|
||||
|
||||
> 说明:定时模式每次触发前都会重新读取当前保存的 `STOCK_LIST`。如果同时传入 `--stocks`,该参数不会锁定后续计划执行的股票列表;需要临时只跑指定股票时,请使用非定时的单次运行命令。
|
||||
>
|
||||
> 从 `python main.py --schedule` 或等价纯 CLI 调度模式启动后,WebUI 保存新的 `SCHEDULE_TIME` / `SCHEDULE_TIMES` 会在下一轮调度检查内自动重绑 daily jobs,无需重启进程;旧的执行时间不会继续保留。`python main.py --serve --schedule` 会由 Web/API runtime scheduler 接管定时任务,WebUI/API/Desktop 长运行进程保存 `SCHEDULE_ENABLED`、`SCHEDULE_TIME` 或 `SCHEDULE_TIMES` 后会按当前配置启停或重建 runtime scheduler。
|
||||
> 从 `python main.py --schedule` 或等价纯 CLI 调度模式启动后,WebUI 保存新的 `SCHEDULE_TIME` / `SCHEDULE_TIMES` 会在下一轮调度检查内自动重绑 daily jobs,无需重启进程;旧的执行时间不会继续保留。`python main.py --serve --schedule` 会由 Web/API runtime scheduler 接管定时任务,WebUI/API/Desktop 长运行进程保存 `SCHEDULE_ENABLED`、`SCHEDULE_TIME` 或 `SCHEDULE_TIMES` 后会按当前配置启停或重建 runtime scheduler。重启 `python main.py --serve-only` 或 Desktop 时,已启用的 daily jobs 会自动恢复,但服务启动本身不会立即执行分析。
|
||||
>
|
||||
> Web/API runtime scheduler 的立即执行入口只会在没有分析任务运行时接受请求;如果已有分析在执行,会返回忙碌状态而不是假装排队成功。
|
||||
|
||||
@@ -1561,7 +1561,7 @@ FastAPI 提供 RESTful API 服务,支持配置管理和触发分析。
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `python main.py --serve` | 启动 API 服务 + 执行一次完整分析 |
|
||||
| `python main.py --serve-only` | 仅启动 API 服务,手动触发分析 |
|
||||
| `python main.py --serve-only` | 仅启动 API 服务;可手动触发分析,并恢复配置中已启用的定时任务,但启动时不立即分析 |
|
||||
|
||||
### 功能特性
|
||||
|
||||
|
||||
@@ -709,7 +709,7 @@ crontab -e
|
||||
|
||||
> Note: Scheduled mode reloads the saved `STOCK_LIST` before each run. If you also pass `--stocks`, it will not pin future scheduled executions to the startup snapshot; use a normal one-off run when you want to analyze a temporary stock list.
|
||||
>
|
||||
> When the built-in scheduler is started via `python main.py --schedule` or an equivalent CLI-only mode, saving a new `SCHEDULE_TIME` / `SCHEDULE_TIMES` from the WebUI will rebind the daily jobs on the next scheduler poll without restarting the process. The previous trigger times are removed instead of being kept alongside the new ones. `python main.py --serve --schedule` is owned by the Web/API runtime scheduler, so long-running WebUI/API/Desktop processes start, stop, or rebuild the runtime scheduler after saving `SCHEDULE_ENABLED`, `SCHEDULE_TIME`, or `SCHEDULE_TIMES`.
|
||||
> When the built-in scheduler is started via `python main.py --schedule` or an equivalent CLI-only mode, saving a new `SCHEDULE_TIME` / `SCHEDULE_TIMES` from the WebUI will rebind the daily jobs on the next scheduler poll without restarting the process. The previous trigger times are removed instead of being kept alongside the new ones. `python main.py --serve --schedule` is owned by the Web/API runtime scheduler, so long-running WebUI/API/Desktop processes start, stop, or rebuild the runtime scheduler after saving `SCHEDULE_ENABLED`, `SCHEDULE_TIME`, or `SCHEDULE_TIMES`. Restarting `python main.py --serve-only` or Desktop restores enabled daily jobs, while service startup itself never runs an immediate analysis.
|
||||
>
|
||||
> The Web/API runtime scheduler run-now endpoint only accepts a request when no analysis is already running; if an analysis is in progress, it returns a busy response instead of reporting a queued run.
|
||||
|
||||
@@ -1400,7 +1400,7 @@ FastAPI provides RESTful API service for configuration management and triggering
|
||||
| Command | Description |
|
||||
|------|------|
|
||||
| `python main.py --serve` | Start API service + run full analysis once |
|
||||
| `python main.py --serve-only` | Start API service only, manually trigger analysis |
|
||||
| `python main.py --serve-only` | Start API service only; allow manual runs and restore enabled schedules without an immediate startup analysis |
|
||||
|
||||
### Features
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ Issue #1512 收口后,Web 设置页只展示后端配置注册表中的正式
|
||||
- `WEBUI_HOST`、`WEBUI_PORT`:监听地址和端口只在进程启动时绑定,保存后必须重启当前进程、Docker 容器或服务管理器才会生效。
|
||||
- `RUN_IMMEDIATELY`:非 schedule 模式启动期单次运行配置,保存后不会让已运行的 WebUI/API 进程立即触发分析。
|
||||
- Web 设置页不直接暴露 `SCHEDULE_TIME` / `SCHEDULE_TIMES` / `SCHEDULE_RUN_IMMEDIATELY` 等内部键;用户通过“定时任务”卡片维护启用状态、多个执行时间和立即执行一次。
|
||||
- `SCHEDULE_ENABLED`:WebUI/API/Desktop 长运行进程(包括 `python main.py --serve --schedule`)会在保存后按新值启动或停止 runtime scheduler;纯 CLI schedule 模式(`python main.py --schedule`)仍按启动时参数和配置运行。
|
||||
- `SCHEDULE_ENABLED`:WebUI/API/Desktop 长运行进程(包括 `python main.py --serve --schedule`)会在保存后按新值启动或停止 runtime scheduler;重启 `--serve-only` 或 Desktop 进程时也会恢复已启用的 daily jobs,但不会因此立即执行分析。纯 CLI schedule 模式(`python main.py --schedule`)仍按启动时参数和配置运行。
|
||||
- `SCHEDULE_TIME`、`SCHEDULE_TIMES`:不是重启必需项。`SCHEDULE_TIMES` 为空时使用 `SCHEDULE_TIME`;已运行的 scheduler 会按新时间重建 daily jobs。
|
||||
- `SCHEDULE_RUN_IMMEDIATELY`:schedule 模式启动行为,保存后不会让当前进程立即执行一次分析;手动执行请使用 runtime scheduler 的 run-now API。
|
||||
- runtime scheduler 的 run-now API 只会在没有分析任务运行时接受请求;如果已有分析在执行,会返回忙碌状态,Web 设置页会提示稍后重试。
|
||||
|
||||
15
main.py
15
main.py
@@ -1498,19 +1498,18 @@ def main() -> int:
|
||||
# This keeps Web settings, status, and run-now actions attached to the real
|
||||
# scheduler instead of a separate CLI loop.
|
||||
os.environ.pop(CLI_SCHEDULER_OWNER_ENV, None)
|
||||
if args.serve_only:
|
||||
os.environ[RUNTIME_SCHEDULER_SUPPRESS_START_ENV] = "true"
|
||||
else:
|
||||
os.environ.pop(RUNTIME_SCHEDULER_SUPPRESS_START_ENV, None)
|
||||
runtime_schedule_requested = not args.serve_only and (
|
||||
args.schedule or config.schedule_enabled
|
||||
)
|
||||
if not args.serve_only and args.schedule:
|
||||
runtime_schedule_requested = args.schedule or config.schedule_enabled
|
||||
if args.schedule:
|
||||
os.environ[RUNTIME_SCHEDULER_FORCE_ENABLED_ENV] = "true"
|
||||
else:
|
||||
os.environ.pop(RUNTIME_SCHEDULER_FORCE_ENABLED_ENV, None)
|
||||
if runtime_schedule_requested:
|
||||
runtime_run_immediately = config.schedule_run_immediately
|
||||
# ``--serve-only`` must restore persisted schedules, but it must not
|
||||
# turn service/Desktop startup into an immediate analysis run.
|
||||
runtime_run_immediately = (
|
||||
False if args.serve_only else config.schedule_run_immediately
|
||||
)
|
||||
if getattr(args, 'no_run_immediately', False):
|
||||
runtime_run_immediately = False
|
||||
os.environ[RUNTIME_SCHEDULER_RUN_IMMEDIATELY_ENV] = (
|
||||
|
||||
@@ -994,7 +994,7 @@ class MainScheduleModeTestCase(unittest.TestCase):
|
||||
self.assertEqual(run_immediately_seen_by_server, ["false"])
|
||||
run_with_schedule.assert_not_called()
|
||||
|
||||
def test_serve_only_suppresses_startup_scheduler_without_disabling_runtime_owner(self) -> None:
|
||||
def test_serve_only_restores_persisted_scheduler_without_running_immediately(self) -> None:
|
||||
from src.services.runtime_scheduler import (
|
||||
CLI_SCHEDULER_OWNER_ENV,
|
||||
RUNTIME_SCHEDULER_RUN_IMMEDIATELY_ENV,
|
||||
@@ -1028,8 +1028,8 @@ class MainScheduleModeTestCase(unittest.TestCase):
|
||||
|
||||
self.assertEqual(exit_code, 0)
|
||||
self.assertEqual(marker_seen_by_server, [None])
|
||||
self.assertEqual(suppress_seen_by_server, ["true"])
|
||||
self.assertEqual(run_immediately_seen_by_server, [None])
|
||||
self.assertEqual(suppress_seen_by_server, [None])
|
||||
self.assertEqual(run_immediately_seen_by_server, ["false"])
|
||||
start_bots.assert_called_once_with(config)
|
||||
run_with_schedule.assert_not_called()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user