docs: 补充用户与开发者文档

- 新增 README.md,面向用户说明功能、运行要求、快速开始、触发器和常见问题。
- 新增 DEVELOPMENT.md,面向开发者说明项目结构、构建运行、发布流程、提交规范和检查清单。
- 将 artifacts/ 加入忽略规则,避免误提交本地发布产物。
- 修正托盘图标加载方式,改为读取 exe 内嵌图标,匹配不包含外置 app.ico 的发布包结构。
This commit is contained in:
2026-05-21 11:44:38 +08:00
parent 03d7c9cbd0
commit 45d3ca2f83
4 changed files with 448 additions and 2 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ obj/
.vs/ .vs/
*.user *.user
*.suo *.suo
artifacts/

280
DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,280 @@
# OmniScheduler 开发指导
本文档面向参与 OmniScheduler 开发、构建和发布的维护者。
## 技术栈
- 语言C#
- 运行时:.NET 8
- 框架WPF
- 目标平台Windows
- 项目类型WinExe
- 项目文件:`OmniScheduler/OmniScheduler.csproj`
项目启用了:
- `UseWPF`
- `UseWindowsForms`
其中 Windows Forms 主要用于系统托盘 `NotifyIcon`
## 目录结构
```text
.
├── OmniScheduler/
│ ├── App.xaml
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ ├── Models.cs
│ ├── Services.cs
│ ├── SettingsWindow.xaml
│ ├── SettingsWindow.xaml.cs
│ ├── TaskEditorWindow.xaml
│ ├── TaskEditorWindow.xaml.cs
│ └── OmniScheduler.csproj
├── app.ico
├── README.md
├── DEVELOPMENT.md
└── PRD.md
```
## 主要模块
### `Models.cs`
定义核心数据模型:
- `SchedulerState`:应用整体状态,包含任务、日志和设置。
- `AppSettings`:全局设置,例如 OmniNotify API 地址和日志保留策略。
- `ScheduledTask`:单个调度任务。
- `TaskTrigger`:任务触发器。
- `ExecutionLog`:执行日志。
触发器类型由 `TriggerKind` 定义:
- `OneTime`
- `Interval`
- `Daily`
- `Weekly`
- `Monthly`
- `Cron`
### `Services.cs`
包含应用服务层:
- `StateStore`:负责读取和保存 `%LOCALAPPDATA%\OmniScheduler\state.json`
- `NotifyClient`:负责向 OmniNotify API 发送 JSON 请求。
- `SchedulerService`:调度循环、任务触发、手动触发和补偿策略处理。
- `NextRunCalculator`:计算下次执行时间和未来执行预览。
### `MainWindow`
主界面,负责:
- 任务列表展示
- 日志列表展示
- 新建、编辑、删除、克隆、手动触发任务
- 全局暂停/恢复
- 托盘菜单与隐藏窗口行为
### `TaskEditorWindow`
任务编辑窗口,包含三个页签:
- 常规
- 触发器
- 消息动作
触发器页会根据 `TriggerKind` 动态切换配置区域。时间输入应优先使用日期选择器、下拉框、按钮等结构化控件,避免要求用户手写时间格式。
### `SettingsWindow`
全局设置窗口。当前支持:
- OmniNotify API 地址
- 日志保留天数
- 最大日志条数
为了降低对用户系统的影响,项目不提供开机自启功能,也不写入 Windows 启动项。
## 本地开发
### 环境要求
- Windows
- .NET 8 SDK
- 支持 WPF 的开发环境,例如 Visual Studio、Rider 或 VS Code + .NET SDK
### 还原与构建
```powershell
dotnet restore .\OmniScheduler\OmniScheduler.csproj
dotnet build .\OmniScheduler\OmniScheduler.csproj
```
如果本地正在运行 `OmniScheduler.exe`,构建可能因为 `bin` 目录文件被锁定而失败。可以先退出应用,或临时输出到其他目录:
```powershell
dotnet build .\OmniScheduler\OmniScheduler.csproj -p:OutDir=.\obj\codex-verify\
```
### 运行
```powershell
dotnet run --project .\OmniScheduler\OmniScheduler.csproj
```
也可以直接运行构建后的 exe。
## 数据存储
运行时数据保存在:
```text
%LOCALAPPDATA%\OmniScheduler\state.json
```
`StateStore` 会在文件不存在或读取失败时创建默认状态。默认任务用于 OmniNotify 连通性测试,并保持禁用,避免首次启动自动发送消息。
## 调度规则开发注意事项
- 所有下次执行时间计算应集中在 `NextRunCalculator`
- UI 里的触发器摘要来自 `TaskTrigger.Summary`
- 修改触发器模型时,需要同步检查:
- 克隆逻辑:`TaskTrigger.Clone`
- 摘要逻辑:`TaskTrigger.Summary`
- 下次执行计算:`NextRunCalculator.NextRun`
- 任务编辑窗口加载与保存:`TaskEditorWindow`
- 对用户可见的时间输入,优先使用结构化控件,不新增要求用户记忆格式的文本输入。
## OmniNotify API 约定
`NotifyClient` 发送 `POST` 请求Content-Type 为 `application/json`
```json
{
"channel": "default",
"title": "标题",
"body": "内容"
}
```
发送前会替换以下变量:
- `{CurrentTime}`
- `{TaskName}`
- `{TriggerType}`
响应状态码非 2xx 时,日志级别记为 `ERROR`。如果响应中包含 `IllegalChannel`,错误消息会提示检查频道名。
## 托盘图标
项目图标由根目录 `app.ico` 提供:
- `ApplicationIcon` 用于嵌入 exe 图标。
- `Resource Include="..\app.ico"` 用于 WPF 窗口图标资源。
- 托盘图标从当前进程 exe 的关联图标读取,不依赖发布目录中的独立 `app.ico` 文件。
发布包中不应包含外置 `app.ico`
## 发布构建
首次发布使用 framework-dependent、多文件包不内置运行时不启用 single-file
```powershell
dotnet publish .\OmniScheduler\OmniScheduler.csproj `
-c Release `
-r win-x64 `
--self-contained false `
-p:PublishSingleFile=false `
-p:PublishReadyToRun=false `
-p:DebugType=none `
-p:DebugSymbols=false `
-p:PublishDir=..\artifacts\OmniScheduler-v0.1.0-win-x64\
```
压缩包命名建议遵循 GitHub Release 常见格式:
```text
OmniScheduler-v0.1.0-win-x64.zip
```
压缩:
```powershell
Compress-Archive `
-Path .\artifacts\OmniScheduler-v0.1.0-win-x64 `
-DestinationPath .\artifacts\OmniScheduler-v0.1.0-win-x64.zip
```
计算校验值:
```powershell
Get-FileHash .\artifacts\OmniScheduler-v0.1.0-win-x64.zip -Algorithm SHA256
```
发布包预期包含:
```text
OmniScheduler.exe
OmniScheduler.dll
OmniScheduler.deps.json
OmniScheduler.runtimeconfig.json
```
## Git 与提交规范
提交信息使用 Conventional Commits
```text
<type>[optional scope]: <description>
[optional body]
```
示例:
```text
feat: 优化触发器配置体验
- 根据触发器类型动态展示对应配置区域。
- 增加单次执行的延后快捷设置。
```
常用 type
- `feat`:新增功能
- `fix`:修复问题
- `docs`:文档变更
- `refactor`:重构
- `chore`:构建、发布、工具或维护性变更
## 版本标签
创建版本标签:
```powershell
git tag -a v0.1.0 -m "v0.1.0"
git push origin v0.1.0
```
如果发布内容修正后需要移动尚未正式发布的标签,应明确确认后再执行:
```powershell
git tag -fa v0.1.0 -m "v0.1.0"
git push --force origin v0.1.0
```
## 质量检查清单
提交前建议确认:
- `dotnet build` 通过。
- 触发器编辑窗口能正常切换各触发类型。
- 单次执行快捷设置能正确回填日期时间。
- Cron 预览可显示未来执行时间或明确错误提示。
- 发送测试消息不会要求保存任务。
- 发布包不包含 `app.ico`、PDB、运行时目录或其他多余文件。
- 程序关闭后能隐藏到托盘,托盘菜单可打开、暂停和退出。

View File

@@ -66,10 +66,10 @@ public partial class MainWindow : Window, INotifyPropertyChanged
}); });
menu.Items.Add("退出", null, (_, _) => ExitApplication()); menu.Items.Add("退出", null, (_, _) => ExitApplication());
var iconPath = System.IO.Path.Combine(AppContext.BaseDirectory, "app.ico");
var icon = new Forms.NotifyIcon var icon = new Forms.NotifyIcon
{ {
Icon = new System.Drawing.Icon(iconPath), Icon = System.Drawing.Icon.ExtractAssociatedIcon(Environment.ProcessPath ?? string.Empty)
?? System.Drawing.SystemIcons.Application,
Text = "OmniScheduler", Text = "OmniScheduler",
Visible = true, Visible = true,
ContextMenuStrip = menu ContextMenuStrip = menu

165
README.md Normal file
View File

@@ -0,0 +1,165 @@
# OmniScheduler
OmniScheduler 是一款面向 Windows 的本地任务调度与消息推送工具。你可以创建定时、间隔、每周、每月或 Cron 规则,在规则触发时向本机 OmniNotify API 发送结构化通知消息。
它适合用来做本地提醒、周期性消息推送、脚本外部通知入口,以及需要可视化管理触发规则的轻量自动化场景。
## 功能概览
- 任务管理:创建、编辑、删除、克隆、启用/禁用任务。
- 多触发器:一个任务可以配置多个触发器。
- 触发类型支持单次执行、固定间隔、每日定时、每周定时、每月定时、Cron 表达式。
- 便捷时间设置:通过日期选择器和时分秒下拉框设置时间,无需手动输入时间格式。
- 单次快捷执行:支持设置“几分钟/几小时/几天后执行”,也提供常用快捷按钮。
- 触发预览:实时预览未来 5 次执行时间,便于检查规则是否符合预期。
- 消息模板:支持 `{CurrentTime}``{TaskName}``{TriggerType}` 动态变量。
- 执行日志:记录请求 JSON、响应内容、状态码、耗时和错误信息。
- 托盘运行:关闭窗口时默认隐藏到系统托盘,避免打断工作流。
- 低系统侵入:不提供开机自启功能,不写入 Windows 启动项。
## 系统要求
- Windows 10/11 x64
- [.NET 8 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/8.0)(发布包不内置运行时)
- 本机可访问的 OmniNotify API默认地址为
```text
http://127.0.0.1:19845/notify
```
## 下载与运行
1. 从仓库 Release 页面下载:
```text
OmniScheduler-v0.1.0-win-x64.zip
```
2. 解压到任意目录。
3. 双击运行 `OmniScheduler.exe`
4. 如系统提示缺少 .NET 运行时,请安装 .NET 8 Desktop Runtime 后再次运行。
发布包采用 framework-dependent、多文件形式不内置运行时也不启用单文件打包以减少体积并降低安全软件误报概率。
## 快速开始
1. 打开 OmniScheduler。
2. 点击“新建任务”。
3. 在“常规”页填写任务名称,并确认任务已启用。
4. 在“触发器”页添加触发器:
- 单次执行选择具体日期时间或使用“xx 时间后执行”的快捷设置。
- 固定间隔:设置每隔多少秒、分钟、小时或天执行。
- 每日/每周/每月:选择执行时间和对应日期条件。
- Cron 表达式:输入高级规则并查看未来执行时间预览。
5. 在“消息动作”页填写频道、标题和内容。
6. 点击“发送测试消息”确认 OmniNotify 能正常接收。
7. 保存任务。
## 消息格式
触发任务时OmniScheduler 会向配置的 OmniNotify API 地址发送 JSON
```json
{
"channel": "default",
"title": "提醒标题",
"body": "提醒内容"
}
```
频道名需要与 OmniNotify 中已创建的频道一致,否则 OmniNotify 可能拒绝请求并返回 `IllegalChannel`
## 动态变量
可以在频道、标题和内容中使用以下变量:
| 变量 | 含义 |
| --- | --- |
| `{CurrentTime}` | 当前触发时间,格式为 `yyyy-MM-dd HH:mm:ss` |
| `{TaskName}` | 当前任务名称 |
| `{TriggerType}` | 当前触发器摘要或触发来源 |
示例:
```text
标题:{TaskName} 已触发
内容:触发时间:{CurrentTime},触发方式:{TriggerType}
```
## 触发器说明
### 单次执行
在指定日期和时间执行一次。适合一次性提醒或临时任务。
单次执行支持快捷设置,例如:
- 5 分钟后
- 30 分钟后
- 1 小时后
- 明天此时
- 自定义 N 分钟/小时/天后执行
### 固定间隔
按固定间隔重复执行,例如每 10 分钟、每 2 小时或每 1 天执行一次。可以额外设置起始生效时间和结束失效时间。
### 每日、每周、每月
适合日历类周期任务:
- 每日:每天固定时间执行。
- 每周:选择星期几,并在固定时间执行。
- 每月:选择每月某日,或每月最后一个工作日执行。
### Cron 表达式
适合高级用户配置更复杂的时间规则。编辑时可以查看未来 5 次执行时间,用于校验表达式是否符合预期。
## 错过触发补偿策略
当系统休眠、关机或程序未运行导致错过计划时间时,可以选择:
- 忽略,等待下一次:不补发错过的任务。
- 立即补偿一次:恢复运行后补发一次,然后继续后续计划。
## 日志与数据存储
任务、设置和日志保存在当前用户目录:
```text
%LOCALAPPDATA%\OmniScheduler\state.json
```
你可以在全局设置中调整:
- OmniNotify API 地址
- 日志保留天数
- 最大日志条数
## 常见问题
### 为什么 release 包里没有 .NET 运行时?
为了减小体积,并降低杀毒软件对自包含单文件程序的误报概率。请在系统中安装 .NET 8 Desktop Runtime。
### 为什么没有开机自启?
为了降低对用户系统的影响OmniScheduler 不会写入 Windows 启动项。如果需要开机运行,建议由用户自行通过系统任务计划程序或快捷方式管理。
### 关闭窗口后程序为什么还在运行?
OmniScheduler 是调度工具,关闭主窗口时默认隐藏到系统托盘。需要完全退出时,请在托盘菜单中选择“退出”。
### 发送失败怎么办?
请优先检查:
- OmniNotify 是否正在运行。
- OmniNotify API 地址是否正确。
- 频道名是否存在并拼写一致。
- 执行日志中的响应内容和错误信息。
## 许可证
当前仓库暂未声明许可证。使用、分发或二次开发前,请先与维护者确认授权方式。