待办事项提醒系统落地:数据库设计、调度器与幂等发送
简介这份待办事项提醒系统实现源码包面向计算机、软件工程等专业的课程设计、期末大作业与毕设场景提供一套可直接运行的完整项目参考。资源包含全部源码、项目说明文档与数据库脚本采用前后端分离结构后端以Java实现业务逻辑与接口前端使用Vue组件化开发并配有SQL建表与初始化数据便于快速理解任务管理、提醒调度等核心功能的实现思路。压缩包共602个文件以252个java、95个vue、88个svg、77个js为主另含xml配置、scss样式、yml与bat脚本等整体约1.49MB目录划分清晰方便按模块查阅与二次调试。目前已有306人学习下载适合希望借鉴成熟项目结构、对照源码梳理开发流程并在此基础上扩展功能的读者参考使用。1. 待办事项提醒系统从建表到提醒触发的完整落地路径很多人第一次做待办提醒系统都会把它想成“一个列表加一个闹钟”。真动手才发现难点根本不在界面而在三件事任务怎么存、提醒什么时候触发、重复任务怎么算下一次时间。我见过不少 Demo 把任务塞进一个 JSON 文件跑两天就乱套——改一条任务要重写整个文件多端同步直接冲突提醒时间一改就丢。所以这个标题真正要解决的问题是用一套可持久化、可查询、可扩展的结构把“待办”和“提醒”这两件事拆开又串起来。它适合两类人一类是想把课程设计或练手项目做扎实的开发者另一类是需要在内部工具里加一个轻量提醒模块的工程师。核心思路是数据库负责状态调度器负责时间通知层负责触达。下面按建表、写接口、跑调度、避坑、进阶的顺序讲每一步都能直接抄。2. 数据库设计三张表撑起任务、提醒与通知记录2.1 为什么不是一张表搞定新手最容易犯的错是把任务和提醒时间放在同一张表里字段大概长这样id, title, due_time, remind_time, status。单次任务没问题但一旦出现“每周一上午十点提醒我交周报”这张表就崩了——你没法存重复规则也没法记录每次提醒是否已经发过。常见做法是拆成三张表任务表存“要做什么”提醒表存“什么时候提醒”通知记录表存“提醒发没发出去”。这样重复任务只需要在提醒表里挂一条规则任务本身不用复制多份。2.2 建表 SQL 与字段说明-- 任务表只存任务本身的状态 CREATE TABLE todo_task ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, description TEXT, status TINYINT NOT NULL DEFAULT 0, -- 0待办 1完成 2取消 priority TINYINT NOT NULL DEFAULT 1, -- 1低 2中 3高 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 提醒表一条任务可以有多个提醒支持重复规则 CREATE TABLE todo_reminder ( id BIGINT PRIMARY KEY AUTO_INCREMENT, task_id BIGINT NOT NULL, remind_at DATETIME NOT NULL, -- 首次提醒时间 repeat_rule VARCHAR(50) DEFAULT NULL, -- 如 DAILY / WEEKLY / NONE repeat_until DATETIME DEFAULT NULL, -- 重复截止时间 enabled TINYINT NOT NULL DEFAULT 1, INDEX idx_remind_at (remind_at, enabled), FOREIGN KEY (task_id) REFERENCES todo_task(id) ON DELETE CASCADE ); -- 通知记录表防止同一次提醒重复发送 CREATE TABLE todo_notification_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, reminder_id BIGINT NOT NULL, fire_time DATETIME NOT NULL, channel VARCHAR(20) NOT NULL, -- sms / email / push result TINYINT NOT NULL, -- 0失败 1成功 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_reminder_fire (reminder_id, fire_time) );逻辑说明todo_reminder上的联合索引(remind_at, enabled)是给调度器扫描用的调度器每次只查“到点且启用”的提醒。todo_notification_log的唯一键是后悔药——调度器可能因为重试或并发重复触发唯一键直接挡住重复写入。参数上repeat_rule用字符串而不是枚举是为了后续加MONTHLY或EVERY_2_DAYS时不用改表结构。2.3 重复任务的下一次时间怎么算重复规则不要存“下一次时间”而是每次触发后根据repeat_rule和repeat_until算出新的remind_at。这样即使服务停了一天重启后也能从当前时间往后推不会补发一堆过期提醒。常见做法是写一个纯函数from datetime import datetime, timedelta def next_remind_time(current: datetime, rule: str) - datetime | None: if rule DAILY: return current timedelta(days1) if rule WEEKLY: return current timedelta(weeks1) if rule MONTHLY: # 简化处理按月加遇到月末用下月最后一天 month current.month 1 year current.year (month - 1) // 12 month (month - 1) % 12 1 day min(current.day, 28) # 避免2月溢出实际项目用日历库 return current.replace(yearyear, monthmonth, dayday) return None # NONE 表示不重复参数说明current是本次实际触发时间不是原计划时间这样能避免任务堆积时连续补发。repeat_until的判断放在调用方如果算出的下一次时间超过截止时间就把enabled置 0。3. 提醒调度器轮询、触发与幂等发送3.1 轮询间隔与扫描窗口怎么定调度器最朴素的做法是每秒查一次数据库SELECT * FROM todo_reminder WHERE remind_at NOW() AND enabled 1。任务量小的时候没问题但任务上万后每秒全表扫会拖垮数据库。我一般会加一个扫描窗口比如每 30 秒跑一次查remind_at在[now, now 30s]之间的记录配合索引命中率很高。窗口大小要略大于轮询间隔否则会漏掉边界任务。import time from datetime import datetime, timedelta POLL_INTERVAL 30 # 秒 WINDOW 35 # 略大于轮询间隔防止边界漏扫 def scan_reminders(db): now datetime.now() end now timedelta(secondsWINDOW) rows db.query( SELECT id, task_id, remind_at, repeat_rule, repeat_until FROM todo_reminder WHERE enabled 1 AND remind_at BETWEEN %s AND %s, (now, end) ) for row in rows: fire_reminder(db, row) def fire_reminder(db, row): # 先写通知记录唯一键冲突说明已发过直接跳过 try: db.execute( INSERT INTO todo_notification_log (reminder_id, fire_time, channel, result) VALUES (%s, %s, push, 1), (row[id], row[remind_at]) ) except DuplicateKeyError: return send_push(row[task_id], row[remind_at]) # 计算下一次时间 nxt next_remind_time(row[remind_at], row[repeat_rule]) if nxt and (row[repeat_until] is None or nxt row[repeat_until]): db.execute(UPDATE todo_reminder SET remind_at %s WHERE id %s, (nxt, row[id])) else: db.execute(UPDATE todo_reminder SET enabled 0 WHERE id %s, (row[id],))逻辑说明先写日志再发送是为了让“发送”这个动作有幂等保护。如果先发送再写日志进程在中间崩溃就会重复发。参数上POLL_INTERVAL和WINDOW要根据任务密度调任务密集时把窗口调小、轮询调快任务稀疏时反过来减少空转。3.2 多实例部署时怎么避免重复触发单机跑调度器没问题一旦部署两个实例两个进程会同时扫到同一条提醒。常见做法有两种一是用数据库行锁SELECT ... FOR UPDATE SKIP LOCKED抢到锁的实例才处理二是用一张调度锁表按时间片加唯一键。我一般用第一种简单且不引入额外组件。-- 抢锁式扫描MySQL 8.0 / PostgreSQL 支持 SKIP LOCKED SELECT id, task_id, remind_at, repeat_rule, repeat_until FROM todo_reminder WHERE enabled 1 AND remind_at BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 35 SECOND) FOR UPDATE SKIP LOCKED;注意SKIP LOCKED在事务里才生效处理完一条要尽快提交否则锁持有太久会拖慢其他实例。如果数据库不支持退而求其次用UPDATE ... SET locked_by ? WHERE locked_by IS NULL的方式打标记。3.3 通知层怎么解耦发送短信、邮件、推送的代码不要写在调度循环里否则一个通道超时会把整个调度器卡住。我一般把通知任务丢进队列调度器只负责写队列消费者负责实际发送。没有消息队列时用数据库表当队列也行todo_notification_log里result 0的记录就是待重试的。def send_push(task_id, fire_time): # 实际项目里这里投递到队列Demo 里直接调用 payload {task_id: task_id, fire_time: fire_time.isoformat()} queue.put(notify, payload)参数说明fire_time要带上消费者用它做去重和日志关联。队列的消费者要设置最大重试次数超过后把result置为失败并告警不要无限重试。4. 避坑与排查五个真实翻车现场4.1 提醒时间存了本地时间换时区后全乱现象服务器从一台机器迁到另一台所有提醒提前或延后了 8 小时。原因remind_at存的是本地时间字符串没有时区信息。解决数据库统一存 UTC展示层再转本地时区。建表时用DATETIME存 UTC或者直接用TIMESTAMP并设置连接时区。4.2 重复任务补发了一整屏通知现象服务停了两天重启后用户收到几十条过期提醒。原因调度器扫描时把remind_at now的历史记录全查出来了。解决扫描窗口用BETWEEN now AND now window对于已经过期的重复任务直接把remind_at推到下一个未来时间点不补发。4.3 唯一键冲突导致正常提醒被吞现象某条提醒明明到点了但用户没收到。原因todo_notification_log的唯一键是(reminder_id, fire_time)而fire_time用了秒级精度同一秒内重试被当成重复。解决fire_time用本次计划触发时间而不是实际发送时间并且重试时不要改这个字段。4.4 任务删除后提醒还在跑现象用户删了任务提醒照样发。原因删任务时没有级联删除提醒或者用了软删除但调度器没过滤。解决外键加ON DELETE CASCADE软删除时在调度查询里加task.status ! 2的条件。4.5 调度器单点卡死没人发现现象提醒延迟了半小时才发出来。原因调度循环里某个通知发送阻塞了整个循环停住。解决通知发送必须异步化调度循环里只做数据库操作同时加心跳监控超过两个轮询周期没跑就告警。5. 进阶技巧用最小验证集确认提醒链路真的通了5.1 造一批边界任务来压测不要只测“一分钟后提醒我”这种顺风局。我一般会造这几类任务刚好在扫描窗口边界的、重复规则跨月的、repeat_until就在下一次触发当天的、任务被删除但提醒未清理的。跑一遍看通知记录表确认没有重复、没有遗漏、没有补发。-- 检查是否有重复发送 SELECT reminder_id, fire_time, COUNT(*) AS cnt FROM todo_notification_log GROUP BY reminder_id, fire_time HAVING cnt 1; -- 检查是否有到点未发送的提醒 SELECT r.id, r.remind_at FROM todo_reminder r LEFT JOIN todo_notification_log l ON l.reminder_id r.id AND l.fire_time r.remind_at WHERE r.enabled 1 AND r.remind_at NOW() AND l.id IS NULL;这两条 SQL 是我每次上线前必跑的第一条查重第二条查漏。参数上第二条的NOW()可以换成具体时间点用来回溯某次故障。5.2 用日志字段定位问题在todo_notification_log里加一个trace_id调度器每次扫描生成一个同一次扫描处理的所有提醒共享。出问题时按trace_id一拉就能看出是扫描没扫到、还是发送失败了。5.3 我自己的习惯我习惯在调度器启动时先打一行日志把当前时间、扫描窗口、待处理数量打出来。别小看这一行很多“提醒没发”的问题看一眼启动日志就知道是窗口设错了还是时区不对。另外重复规则我从来不用自然语言解析只支持几种固定枚举省得给自己挖坑。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

泼冷水:语义 if 会不会把代码变成『玄学』?上线前先想清楚这两件事

泼冷水:语义 if 会不会把代码变成『玄学』?上线前先想清楚这两件事

泼冷水:语义 if 会不会把代码变成『玄学』?上线前先想清楚这两件事 【免费下载链接】SemIf-OpenJev Semantic ifs from open models, on a 3090 at home. Independent; not affiliated with Jev or TypeSafe. 项目地址: https://gitcode.com/gh_mirror…

2026/10/13 14:24:37 阅读更多 →
不止是 OpenClaw:把 Gemini 的 Gems 接进科研工作流,24 小时被无限延长

不止是 OpenClaw:把 Gemini 的 Gems 接进科研工作流,24 小时被无限延长

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 9:08:01 阅读更多 →
MCP 模型上下文协议理论篇1:从架构视角拆解客户端-服务器与 LLM 协作,TaoToken 统一 Key 通道怎么接

MCP 模型上下文协议理论篇1:从架构视角拆解客户端-服务器与 LLM 协作,TaoToken 统一 Key 通道怎么接

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 9:08:12 阅读更多 →

最新新闻

掌握新一代AI工具:从提示词设计到工作流搭建的硬技能

掌握新一代AI工具:从提示词设计到工作流搭建的硬技能

1. 为什么“会用AI工具”正在变成一项硬技能 这两年我身边做开发的朋友、做设计的朋友、甚至做运营和行政的朋友,都在聊同一件事:AI工具怎么用才能真正落到工作里。不是那种“打开网页问一句、复制答案”的浅层玩法,而是把它当成一个能持续产…

2026/10/13 15:08:40 阅读更多 →
TNT Unicode Controls 2.3.0 解决Delphi乱码

TNT Unicode Controls 2.3.0 解决Delphi乱码

简介:这是一套基于 Delphi 开发的第三方 Unicode 控件套装,核心价值在于补足 VCL 组件对字符集支持不足的缺陷,使中文、日文、韩文乃至欧洲多语言文本能在同一界面中流畅呈现,特别适合承担国际化改造任务的桌面程序项目。压缩包共…

2026/10/13 15:08:40 阅读更多 →
Video.js源码级改造:实现RTMP-WS与HLS双协议统一播放

Video.js源码级改造:实现RTMP-WS与HLS双协议统一播放

简介:本资源是一套基于Video.js实现RTMP推流与HLS直播播放的轻量级HTML5原生播放器实践方案,面向前端开发者、音视频初学者及Web直播功能集成需求者,解决在不依赖大型框架前提下快速搭建兼容性好、可定制性强的网页端流媒体播放能力问题。压缩…

2026/10/13 15:08:40 阅读更多 →
多传感器融合的电力系统入侵检测:Python多级融合实战

多传感器融合的电力系统入侵检测:Python多级融合实战

简介:面向具备一定Python编程基础并熟悉数据处理与机器学习算法的科研人员,以及电力系统和工业控制安全方向的研究生、工程师,该资源围绕多源数据融合技术,给出针对电力系统网络入侵的检测方案。方案覆盖数据预处理、特征提取、融…

2026/10/13 15:08:40 阅读更多 →
全流程供应链系统源码拆包:从采购到结算链路与二次开发避坑指南

全流程供应链系统源码拆包:从采购到结算链路与二次开发避坑指南

简介:这是一套面向零售与供应链领域开发者、技术团队及企业信息化选型人员的高度可定制供应链中台基础系统,全部源码开放,可用于商用目的。系统覆盖零售管理、电子商务、供应链、财务、车队、仓库、人员、产品、订单、会员、连锁与加盟等业务…

2026/10/13 15:08:40 阅读更多 →
【CStackGUI 实战】我用自研的 Win32 GUI 框架写了个「工具箱启动器」:8 页签、真注册表操作、右键菜单管理

【CStackGUI 实战】我用自研的 Win32 GUI 框架写了个「工具箱启动器」:8 页签、真注册表操作、右键菜单管理

文章目录一、这是什么二、功能介绍1. 八页签工具启动器 全局搜索2. 我的工具:改一个 ini 就能加工具,不用重编译3. 右键菜单管理:扫描、批量禁用、备份删除、一键还原4. 成对开关:模式照抄原版,落点绝不越界5. 日志与…

2026/10/13 15:07:39 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →