尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
C# WinForms 日程提醒器实战:从Timer轮询到JSON存储的桌面应用开发
简介一套基于C#与WinForms打造的日程提醒器源码资源面向Windows桌面开发初学者、需要完成课程设计或练手项目的开发者覆盖事件新增、列表查看、自定义提前提醒、倒计时显示和弹窗通知等核心功能。资源共47个文件压缩包约317KB以15个cs源码文件为主体配合5个resx界面资源、5个resources编译资源、窗体设计器文件及日期提醒DataSet数据库脚本并含可直接运行的exe与配套dll便于对照学习界面布局与数据交互。已有395人学习下载。项目代码按窗体拆分包括FormMain、FormManage、FormLogin、FormRegister等模块可掌握WinForms窗体设计、Timer定时器触发、日期时间计算、数据集绑定及登录注册逻辑等要点结构清晰适合按模块逐步阅读或二次扩展。1. 从一次错过会议说起这个日程提醒器能救什么你有没有过这种经历早上到公司打开电脑弹窗提醒“10分钟后开会”你抬手关掉然后一头扎进代码里再抬头时会议已经过了半小时。我用WinForms写过一个小工具专门治这种“刚关提醒就忘事”的毛病——它不是一次性闹钟而是一个能管理日期、倒计时、循环日程的桌面提醒器。界面是典型的C# WinForm不依赖任何外部服务双击就能跑。它适合谁除了像我一样记性差的开发者也适合需要定时记录工时、跟踪项目节点、给生日设置倒计时的普通用户。下面拆解这个项目的完整玩法从架构到改代码一步步来。2. 项目架构拆解WinForm界面、提醒内核与数据存储三条线一个看起来简单的日程提醒器实际拆开是三层逻辑UI层负责展示和录入数据层负责持久化内核层负责每秒检查“有没有日程该响了”。这三条线各自独立又通过事件和定时器串联理解了这个结构后面改功能就顺了。2.1 目录结构与工程分层整个项目没有用复杂的分层框架而是按职责分了三个文件夹加一个入口根目录下是FormMain.cs主窗体、ReminderCore.cs提醒内核、DataStore.cs数据读写辅助的还有Models文件夹存放日程实体类。它的优点是每个文件都能单独打开看不用翻一堆目录。ScheduleReminder/ ├── FormMain.cs // 主界面日程列表、添加/编辑/删除、托盘图标 ├── FormMain.Designer.cs // 界面布局WinForm设计器自动生成 ├── ReminderCore.cs // 提醒核心Timer轮询、触发事件、响声与气泡 ├── DataStore.cs // 数据持久化读写JSON文件启动加载、变更保存 ├── Models/ │ └── ScheduleItem.cs // 日程实体标题、时间、重复规则、是否启用 ├── Program.cs // 应用入口加载主窗体 └── data/ └── schedules.json // 运行后自动生成的日程数据文件这里最关键的ScheduleItem类我需要多说一句。它上面不是只挂了“标题和日期”两个字段而是用枚举定义了重复类型一次性、每天、每周、每月。这个设计直接影响内核的触发判断如果后面你想加“每个工作日”或“每年生日提醒”只需要扩展枚举和判断逻辑即可。数据层用JSON而不是SQLite是因为日程数据量很小一个文件扛得住而且用户能直接打开文本修改对非专业用户更友好。2.2 数据层设计用JSON存日程不装数据库的理由给桌面小工具装数据库属于杀鸡用牛刀。这里用JSON文件存储序列化用的是C#自带的System.Text.Json代码干净不需要引入外部依赖。每次添加或修改日程就调用一次SaveChanges()把整个列表写回文件启动时则读文件反序列化如果文件不存在就用空列表兜底。public class DataStore { private readonly string _filePath Path.Combine(AppDomain.CurrentDomain.BaseDirectory, data, schedules.json); public ListScheduleItem Load() { try { if (!File.Exists(_filePath)) return new ListScheduleItem(); var json File.ReadAllText(_filePath); return JsonSerializer.DeserializeListScheduleItem(json) ?? new ListScheduleItem(); } catch (Exception ex) { // 解析失败不能让程序崩溃返回空列表并在日志中记录 File.AppendAllText(error.log, ${DateTime.Now}: {ex.Message}\n); return new ListScheduleItem(); } } public void Save(ListScheduleItem items) { Directory.CreateDirectory(Path.GetDirectoryName(_filePath)); var json JsonSerializer.Serialize(items, new JsonSerializerOptions { WriteIndented true }); File.WriteAllText(_filePath, json); } }逻辑说明Load()做了三件事——检查文件是否存在、读取并反序列化、异常兜底。我特意在异常时写错误日志而不是直接弹窗是因为这种工具常驻后台一个坏的日程文件不应该让用户措手不及。Save()每次全量写入效率不高但胜在实现简单也符合数据量小的场景。参数说明JsonSerializerOptions.WriteIndented true让JSON带缩进用户用记事本打开也能看懂AppDomain.CurrentDomain.BaseDirectory取的是程序运行目录比如 Debug 或 Release 下的路径。注意这个路径和 exe 所在目录一致很多人把它和Environment.CurrentDirectory搞混后者可能因为启动方式不同而变化踩过坑的都知道。数据文件放data子目录也方便备份。2.3 提醒内核Timer驱动的检查循环提醒内核是整个项目的心脏。WinForms里自带的System.Windows.Forms.Timer是UI线程控件Tick事件能直接操作界面控件不会出现跨线程异常。这里设置Interval 1000也就是每秒触发一次检查。为什么用1秒而不是1分钟因为倒计时显示要精确到秒而且日程安排可能精确到某一个分钟每秒轮询的CPU开销其实很低。public class ReminderCore { private System.Windows.Forms.Timer _timer; private ListScheduleItem _items; private readonly HashSetint _triggeredIds new HashSetint(); // 记录已触发过的ID防止重复弹出 public event ActionScheduleItem ReminderDue; // 到点后向外抛事件 public void Start(ListScheduleItem items) { _items items; _timer new System.Windows.Forms.Timer { Interval 1000 }; _timer.Tick OnTick; _timer.Start(); } private void OnTick(object sender, EventArgs e) { var now DateTime.Now; foreach (var item in _items) { if (!item.Enabled) continue; if (item.RemindOnce _triggeredIds.Contains(item.Id)) continue; if (IsDue(item, now)) { _triggeredIds.Add(item.Id); ReminderDue?.Invoke(item); // 一次性任务触发后自动禁用 if (item.RepeatType RepeatType.Once) item.Enabled false; } } } private bool IsDue(ScheduleItem item, DateTime now) { // 简化版判断当前时间在计划的“已到时间”附近 var diff now - item.ScheduledTime; return diff.TotalSeconds 0 diff.TotalSeconds 60; } }逻辑说明Start()创建定时器并订阅OnTick每一秒遍历所有日程。IsDue()判断“当前时间落在日程预定时间后的60秒窗口内”这么做是为了避免因为几秒钟的轮询延迟导致漏触。_triggeredIds这个集合是关键它保证同一条日程不会因为每条都在窗口期内而反复弹窗一次性任务触发后还会设置Enabled false从根源上断电。参数说明diff.TotalSeconds 0 60这个窗口可以根据你的需求调整。比如用户设置了“提前10分钟提醒”那判断条件就是diff.TotalSeconds -600 diff.TotalSeconds 600吗不对更合理的写法是now - item.AlertTime。实际上我在改造时把“提醒时间”和“日程时间”拆成两个字段界面上用户可以独立设置提前量这样内核逻辑不变只是把比较对象从ScheduledTime换成AlertTime后面第6章细说。3. 核心模块实战日期倒计时、到期触发与通知弹出的完整链路这一章把用户最关心的三条链路端到端打通主界面上怎么显示“距某日还有多少天”、到点后系统怎么知道该提醒了、提醒时用什么样的交互不打扰人。每一条链路都配合代码讲清。3.1 倒计时计算TimeSpan与本地时区处理倒计时的朴素做法是DateTime直接相减得到TimeSpan再取Days属性。但这有个隐蔽的坑如果用户设置的日期是“2026年1月1日”值默认是零点零分零秒而“今天”的当前时刻是下午三点相减会得到0天 15小时界面上显示“剩余0天”就很不人性化。我的处理是先把目标日期的时分秒清零再按“今天零点”做基准。private string GetCountdownText(DateTime target) { var targetDay target.Date; // 丢弃时分秒只留日期 var today DateTime.Now.Date; // 今天零点 var diff targetDay - today; if (diff.TotalDays 0) return 已过期; if (diff.TotalDays 1) return $今天 {target:HH:mm}; if (diff.TotalDays 2) return $明天 {target:HH:mm}; return $剩余 {diff.Days} 天; }逻辑说明target.Date和DateTime.Now.Date都把时间部分讨平这样减法得到的TotalDays就是纯天数差。小于1天时直接显示“今天/明天”避免像“0天23小时”这种不直观的文案。这个方法被绑定到列表控件的自定义绘制事件里每次界面刷新、列表更新都会调用。参数说明这里有个细节值得注意——TimeSpan有个Days属性和TotalDays属性前者是整数值后者是带小数的浮点数。我们比较TotalDays时用 2而不是Days 2因为Days在跨天时会有截断误差。比如现在是2025-07-01 23:00目标日期是2025-07-02 10:00TotalDays约等于0.46Days则直接是0看起来没问题但如果是提前2天零23小时Days可能变成1显示“剩余1天”用户会以为算错了。所以干这行浮点数比较一定要用TotalDays。3.2 提醒触发轮询与一次性/重复任务的调度前面内核里的轮询代码是基础版真正要支撑起“每天重复提醒”和“每月提醒”需要细化判断逻辑。我的做法是给ScheduleItem增加RepeatType枚举然后在内核里做分支匹配。需要注意月重复里的一种特殊情况用户选了每月30号但某个月只有28天这时候不能漏掉也不能报错我选择跳过该月等下个月再判断。public enum RepeatType { Once, // 一次性 Daily, // 每天 Weekly, // 每周固定星期几 Monthly // 每月固定几号 } // 在 IsDue 中补充重复判断 private bool IsRepeatedDue(ScheduleItem item, DateTime now) { switch (item.RepeatType) { case RepeatType.Daily: return now.Hour item.ScheduledTime.Hour now.Minute item.ScheduledTime.Minute now.Second 0; case RepeatType.Weekly: return now.DayOfWeek item.ScheduledTime.DayOfWeek now.Hour item.ScheduledTime.Hour now.Minute item.ScheduledTime.Minute; case RepeatType.Monthly: // 跳过不存在的日期 if (item.ScheduledTime.Day DateTime.DaysInMonth(now.Year, now.Month)) return false; return now.Day item.ScheduledTime.Day now.Hour item.ScheduledTime.Hour now.Minute item.ScheduledTime.Minute; case RepeatType.Once: default: var diff now - item.ScheduledTime; return diff.TotalSeconds 0 diff.TotalSeconds 60; } }逻辑说明对于每日/每周/每月这种重复型日程不能用“时间差小于60秒”的窗口去判断。因为每天提醒一次昨天的“今天10:00”已经过去十几个小时了时间差早就超过窗口。所以这里的思路是拆出“日、时、分”三个维度精确匹配当前时刻的时和分再分别匹配日维度。唯一例外的是一口 SQL 能触发的While逻辑代码中Once分支继续走时间差。参数说明DaysInMonth是.NET内置的静态方法返回指定年月的天数。2月要判断闰年这个函数已经处理了你不用自己写闰年算法。另外注意now.Second 0这个条件在Daily里看起来多余实际是让触发窗口在整个分钟都有效——否则秒数正好等于0才有可能命中丢概率。你可以把Second也参与比较比如小于等30但我一般不管让它整分钟内都算数。3.3 托盘与气泡通知一个能独立运行的提醒器日程提醒器不太适合一直霸占任务栏。用户往往开着它但希望它缩在右下角。这里用NotifyIcon实现托盘图标单击图标还原主窗体双击也可以右键菜单包含“显示主界面”和“退出”。到点弹出气泡通知时用ShowBalloonTip是最省力的方案系统自带样式用户一看就懂。另外还配了一段蜂鸣音防止用户没注意气泡。private void NotifyReminder(ScheduleItem item) { // 托盘气泡显示标题和正文5000毫秒后自动消失 _notifyIcon.ShowBalloonTip(5000, 日程提醒, item.Title, ToolTipIcon.Info); // 如果主窗体最小化或隐藏把它闪一下任务栏提示用户有事件 if (WindowState FormWindowState.Minimized || !Visible) { WindowState FormWindowState.Normal; FlashWindow(this.Handle, true); // 调用系统API闪烁窗口 } // 连续响三声每声间隔300ms避免像闹钟一样吵 for (int i 0; i 3; i) { System.Media.SystemSounds.Exclamation.Play(); System.Threading.Thread.Sleep(300); } }逻辑说明ShowBalloonTip的第一个参数是气泡显示毫秒数在Windows 10及以上系统实际显示时长受系统设置影响但至少在5秒。FlashWindow是原生API需要DllImport(user32.dll)引用它的作用是让任务栏图标闪烁提醒用户日程到了。蜂鸣音用了SystemSounds.Exclamation是Windows内置的声音不会在代码里嵌入音频文件省体积。参数说明如果不想在触发时打断用户写代码可以把蜂鸣音改成SystemSounds.Asterisk声音更轻。还有一层细节如果用户正在全屏打游戏或演示PPT弹出气泡也可能被忽略。我一般在触发的瞬间记录一条日志到remind_log.txt这样用户事后能查“刚才弹了没”也算给工具加了一双后悔药。4. 运行与调试从源码到可执行文件的几步路拿到这个WinForm项目的源码第一件事不是读代码是先把它跑起来。这里按我平时的流程来走环境准备、编译、运行配置、打包。每一步都有可复现的命令和参数照着做基本不会翻车。4.1 环境准备与编译开发WinForm应用理论上只要装了.NET SDK就行不需要装完整的Visual Studio。我用的是.NET 6.0以上的版本因为从.NET Core 3.1开始WinForms就被官方支持了。如果你的操作系统是Windows 10或11用dotnet命令行就能编译。# 查看已安装的SDK版本 dotnet --list-sdks # 还原NuGet依赖项目里默认没有第三方包但执行一下无妨 dotnet restore # 编译-c Release生成发布版 dotnet build -c Release逻辑说明dotnet restore会读取.csproj文件中的PackageReference由于这个项目只用了系统库所以这一步会很快。dotnet build的输出在bin/Release/net6.0-windows/目录下里面包含.exe和一堆.dll直接运行里面的 exe 就能看到界面。参数说明.csproj文件里需要重点关注两处。一处是TargetFrameworknet6.0-windows/TargetFramework后面必须带-windows否则WinForms项目编译不过另一处是UseWindowsFormstrue/UseWindowsForms这是Windows桌面应用的开关。如果你用的是Visual Studio新建项目时选“Windows Forms应用”模板这两项会自动生成。4.2 首次运行配置双击 exe 后的初始状态是个空列表界面上方有“添加日程”按钮下方是 ListView 展示的日程列表。添加日程的对话框长这样标题输入框、日期选择器DateTimePicker、时间选择器、重复类型下拉框、是否启用复选框。录制后点确定数据写入JSON同时列表刷新。// 添加按钮事件中的核心逻辑 private void BtnAdd_Click(object sender, EventArgs e) { var dlg new FormScheduleEdit(); // 编辑对话框 if (dlg.ShowDialog() DialogResult.OK) { var newItem dlg.CreatedItem; _items.Add(newItem); _dataStore.Save(_items); // 立即持久化 RefreshList(); // 刷新ListBox _core.Restart(_items.ToList()); // 重启内核让新日程马上生效 } }逻辑说明ShowDialog()是模态对话框用户点“确定”才会返回DialogResult.OK。CreatedItem是从对话框里拿到的日程实例里面已经用DateTimePicker.Value合并了日期和时间。注意_core.Restart这步不能省——如果内核还在用旧的_items引用新添加的日程永远不会被检查。重启内核就是停掉旧Timer重新Start(new List)。参数说明DateTimePicker有两个模式设置Format DateTimePickerFormat.Custom和CustomFormat yyyy-MM-dd HH:mm就可以同时输入日期和时间不需要放两个控件。这个细节很多新手不知道默认的Long格式只选日期时间永远是12:00我最早做这个工具时就翻在这上面。4.3 打包成单文件exe的小技巧给同事用的时候扔一堆dll给别人不专业也容易漏文件。我用.csproj里的PublishSingleFile属性一行配置就能发布成单文件同事拿过去直接双击不需要安装运行库前提是目标机器装了相应.NET运行时。PropertyGroup OutputTypeWinExe/OutputType TargetFrameworknet6.0-windows/TargetFramework UseWindowsFormstrue/UseWindowsForms PublishSingleFiletrue/PublishSingleFile SelfContainedfalse/SelfContained /PropertyGroup发布命令用的还是dotnet publishdotnet publish -c Release -r win-x64 --self-contained false逻辑说明--self-contained false让exe依赖用户机器上的 .NET 运行时体积大约2-3MB适合在公司内部已经装了运行环境的机器间分发。如果要发给从没装过.NET的亲戚朋友那就改成--self-contained true体积会到50MB以上但人家不用装任何东西。参数说明发布后exe文件里面会包含配置文件和数据目录吗不会。data/schedules.json是运行时动态创建的所以第一次在新机器上启动程序当前目录下才会出现data文件夹。如果你改了JSON路径为固定目录比如C:\ProgramData\...那又涉及到权限问题我建议还是用AppDomain.CurrentDomain.BaseDirectory省心。5. 避坑指南Windows定时、时区与休眠那些坑写桌面工具最头疼的不是功能逻辑而是操作系统层面的“意外”。我总结了几条真实踩过的坑每条都是现象、原因、解决三步走照着排查能少走弯路。5.1 系统休眠后提醒不触发现象笔记本电脑合盖再打开发现之前设的提醒一个都没响。原因System.Windows.Forms.Timer依赖UI消息循环而休眠时整个进程挂起Timer的Tick事件根本不会执行。等唤醒后Timer继续走但时间窗口已经错过了。解决改用System.Threading.Timer或System.Timers.Timer它们基于线程池在系统唤醒后能继续触发但仍然无法在网络休眠期间唤醒系统。更狠的方案是任务计划程序定一个每分钟跑一次的任务来唤起程序但这个小工具不值得这么复杂。我的常见做法是启动时扫描未触发的提醒如果发现“已过时间且没有触发记录”补一次提醒并打日志相当于补发。// 启动时补检查找当前时间已超过提醒时间但未触发过的日程 foreach (var item in _items) { if (item.Enabled item.ScheduledTime DateTime.Now !_triggeredIds.Contains(item.Id)) { ReminderDue?.Invoke(item); _triggeredIds.Add(item.Id); } }5.2 日期差一天UTC与本地时区混用现象程序在美国同事的电脑上跑日程提醒总是比实际时间早/晚8个小时。原因DateTime.Now返回本地时间而DateTime.UtcNow返回UTC时间。如果JSON中保存的字符串带了Z后缀UTC标志反序列化后DateTime的Kind属性会变成Utc与本地时间比较时就会自动换算时区导致判断偏移。解决统一使用DateTime.Now作为唯一的存储和比较基准序列化时不用相对或绝对格式。在ScheduleItem中使用[JsonConverter(typeof(LocalDateTimeConverter))]强制按本地时间处理或者最简单粗暴的做法是存储时调用ToLocalTime()读取时再调用一次ToLocalTime()宁可多此一举也不要让UTC混进来。5.3 重复提醒每天多次触发现象设了“每天10:00提醒”结果一天弹了七八次。原因IsRepeatedDue里对“分钟”窗口判断太宽用了diff.TotalSeconds 0 3600导致10:00到11:00这段时间内每秒都命中。解决重复型提醒的窗口缩到1分钟以内并且要用_triggeredIds做当天粒度去重。我的方案是把_triggeredIds的值从int改成string存成“日程ID_日期”比如3_2025-07-01这样每天第一次触发后记录后续都不会再弹第二天日期变了又能正常触发。5.4 高DPI下界面错乱现象在笔记本电脑默认125%或150%缩放下控件挤在一起按钮文字被截断。原因WinForm的默认 DPI 感知模式是不感知系统会对整个窗口进行缩放导致布局计算混乱。特别是在AutoScaleMode未正确设置时。解决在FormMain的构造函数前加上[STAThread]之外还要在Program.cs入口或app.manifest中声明PerMonitorV2感知模式。同时在设计器里把所有容器的AutoScaleMode设为Dpi或Font这样字体缩放按钮位置会联动调整。如果你的主窗体用了固定像素的Location和Size建议改成TableLayoutPanel或FlowLayoutPanel让布局自适应。5.5 路径问题发布后找不到数据文件现象通过快捷方式启动程序后日程列表变成空的但点击exe直接启动又正常。原因Environment.CurrentDirectory在快捷方式启动时可能指向的是快捷方式所在的旧路径而不是exe所在目录。我最初用File.ReadAllText(schedules.json)这种相对路径写法所以翻车了。解决强制使用AppDomain.CurrentDomain.BaseDirectory拼接文件路径前面数据结构里已经这样做了。另外如果需要让数据文件单独存放到用户文档目录用Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)拼接也是同样思路。从那以后我每次写桌面工具第一件事就是定义BaseDir所有文件操作都从这里派生。6. 二次开发指南加“提前N分钟提醒”和“农历日期”功能到这里主流程你已经能跑通了。想把它变成真正能用的私人助理还需两个高频功能提前提醒和农历支持。前者是内核改造后者是数据校验和显示扩展。我用两个实际案例带你走一遍。先看“提前N分钟提醒”。这个功能的用户需求是日程定在下午3点开会希望提前15分钟弹窗。传统设计是让用户手动计算得到一个提前时间点但更友好的是给ScheduleItem增加一个AlertOffsetMinutes字段默认0表示“准点提醒”。界面上一枚数字输入框用户填5或10在保存时把提醒时间 日程时间 - 偏移分钟数。// ScheduleItem新增字段 public int AlertOffsetMinutes { get; set; } 0; // 界面保存时计算 item.AlertTime item.ScheduledTime.AddMinutes(-item.AlertOffsetMinutes); // 兼容旧数据如果没有设置偏移AlertTime就是ScheduledTime if (item.AlertOffsetMinutes 0) item.AlertTime item.ScheduledTime;内核判断时把原来的ScheduledTime全部换成AlertTime即可。注意对于重复型任务AlertTime也需要根据当前周期动态计算。比如“每天10:00提醒提前5分钟”那么判断时看的是now是否等于今天9:55。这个逻辑可以封装成方法private DateTime GetNextAlertTime(ScheduleItem item, DateTime from) { switch (item.RepeatType) { case RepeatType.Daily: return from.Date.Add(item.ScheduledTime.TimeOfDay).AddMinutes(-item.AlertOffsetMinutes); case RepeatType.Weekly: var daysUntil ((int)item.ScheduledTime.DayOfWeek - (int)from.DayOfWeek 7) % 7; return from.Date.AddDays(daysUntil).Add(item.ScheduledTime.TimeOfDay).AddMinutes(-item.AlertOffsetMinutes); // ...其余类型类似 default: return item.ScheduledTime.AddMinutes(-item.AlertOffsetMinutes); } }这段代码的返回值就是内核里要比较的“目标时间”。前一章提到的“每次启动补检”同样用GetNextAlertTime去跟当前时间比较逻辑就统一了。再来看农历日期功能。WinForm本身没有农历类库但 .NET 自带的ChineseLunisolarCalendar可以做公历转农历只是它不直接告诉我们“今天初几”。我的做法是封装一个转换函数得到农历的年月日字符串然后存到ScheduleItem.LunarDate字段中。用户在编辑界面勾选“按农历日期提醒”保存时就先用公历换算农历把农历月日存起来。内核判断时每轮询一次都判断当天农历月日是否匹配。private static string GetLunarMonthDay(DateTime solar) { var calendar new System.Globalization.ChineseLunisolarCalendar(); var month calendar.GetMonth(solar); var day calendar.GetDayOfMonth(solar); return ${month}-{day}; } // 在Monthly重复分支里扩展 case RepeatType.Monthly: if (item.UseLunarCalendar) { var lunarToday GetLunarMonthDay(now); return lunarToday item.LunarDate now.Hour item.ScheduledTime.Hour now.Minute item.ScheduledTime.Minute; } // 原来的公历逻辑...实现后怎么验证我会单独写一个测试小函数遍历从今天到过年每一天检查GetLunarMonthDay是否和传统日历App显示一致。注意你无法自动对比只能手动抽查几个已知日期比如2025年春节的公历日期对应正月初一。改造完这些功能后一个日程提醒器其实已经比系统自带日历工具更贴合日常需求了。从那以后我每次做这类小工具都强制自己先画一遍“触发链路图”用户输入 → 存储 → 轮询 → 判断 → 通知 → 防重复。只要这条链路上每一步都设了参数和扩展点后面加再奇怪的需求都不至于推翻重来。希望这次的拆解能帮你把这套代码跑起来再改出自己的版本。本文还有配套的精品资源点击获取
RELATED

相关推荐

线性回归求解全解析:梯度下降与正规方程的底层原理与选型

线性回归求解全解析:梯度下降与正规方程的底层原理与选型

简介:这是一份面向Java开发者与机器学习初学者的线性回归实践代码包,演示了如何通过梯度下降与正态方程两种方式拟合数据点的最佳直线。项目来源于Andrew Ng的Coursera机器学习课程示例数据,通过给定训练集可自动求解yMxB中的参数M与B&#x…

📅 2026/10/23 22:29:54
macOS上Python环境管理:Homebrew与pyenv多版本切换实践

macOS上Python环境管理:Homebrew与pyenv多版本切换实践

简介:这份资源是一份面向macOS用户的Python安装方法图文指南,覆盖三种主流方式:通过Homebrew安装、官网下载.pkg安装包,以及使用pyenv进行多版本管理。适用人群从刚接触Python的初学者到需要维护多个开发环境的进阶开发者&#xf…

📅 2026/10/23 22:24:54
MQL4编程入门:从基础语法到EA与自定义指标实战

MQL4编程入门:从基础语法到EA与自定义指标实战

简介:面向外汇交易者与编程初学者的MT4 MQL4编程入门资料,系统梳理MetaTrader 4平台内置编程语言的基础用法,帮助读者从零开始建立编写自定义指标与Expert Advisor(EA)的能力。内容以文档形式呈现,围绕自定…

📅 2026/10/23 22:24:54
MORE NEWS

更多资讯

📰

时间与时区:系统稳定性的隐形地基与全栈治理指南

1. 为什么“时间、时区”不是技术配角,而是系统稳定性的隐形地基你有没有遇到过这样的情况:后台日志里两条相邻记录,时间戳却倒着走?明明是用户刚提交的订单,数据库里显示的创建时间却比服务器当前时间早了8小时&#…

📰

CircleBug Java项目拆解:从随机游走到碰撞检测的动画实战

简介:这是一个基于Java ActorWorld环境的迷你actor行为观察示例,适合刚接触GridWorld或面向对象编程的初学者,也适合需要快速理解runner类与重载add方法放置actor的读者。整个压缩包约4KB,包含9个文件,以java源代码与c…

📰

Spotify押注AI代理:让音乐服务成为智能体首选

当生成式AI从聊天窗口走向能替用户执行任务的智能体,数字服务的入口正在发生迁移。TechCrunch报道,Spotify联合CEO Gustav Sderstrm近日解释了公司当前的AI判断:AI代理的崛起,可能对音乐服务是个好消息。Spotify真正押注的&#x…

📰

Uber 3万员工全员接入ChatGPT Work

编者按:企业AI正在经历从“部门试用”到“全员标配”的关键跨越。Uber的这次决定,或许会成为2026年企业软件采购风向的标志性事件。 3万人一起用:Uber把ChatGPT Work推向全员 据TechCrunch报道,出行巨头Uber宣布将ChatGPT Work部署…

📰

信号检测与估计核心概念解析:从噪声建模到最优判决的工程实践

简介:这份资料是电子科技大学《信号检测与估计理论与应用》关欣译教材的配套课后答案,面向通信工程、信号处理等专业的学生与自学者,用于在噪声背景下理解信号检测与参数估计的核心方法,巩固课堂所学并排查知识盲点。压缩包共95个…

📰

MFC哈夫曼压缩工具实战:从Huffman.zip到二进制文件压缩解压

简介:这份资源是一个基于哈夫曼编码实现的文件压缩与解压工具,面向学习数据结构、算法课程设计或C桌面开发的学生与开发者,帮助理解哈夫曼树构造、编码生成及二进制文件读写的完整流程。压缩包共53个文件,约109.76MB,包…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬