我用 AI,一天就把一个桌面提醒插件撸完了
这个插件是基于utools平台的, 标题虽然有点夸张,但在 AI 的加持下,这个插件的 MVP 版本,确实是我和 ChatGPT 搭档一天撸出来的, 感兴趣的可以看看哦
一、先说说 uTools:一个很适合"折腾"的效率神器
如果你还没用过 uTools,可以简单理解为:
一个跨平台的效率启动器 / 快速搜索工具,
再加上一整套插件生态 + 开发者支持。
具体可以看看官网:uTools 是一种高效工作方式,AI 时代的轻工具平台
它有几个特点特别适合做这种"小而精"的工具:
- 跨平台:Windows / macOS / Linux 都能跑;
- 插件机制:很多能力(窗口、托盘、系统通知等)都可以通过插件调用;
- 唤起方便:全局快捷键一呼即出,非常适合"查一下、记一下、记个提醒"这种场景;
- 开发门槛低:前端就是一套网页技术栈(我用的是 Vue),后端你爱怎么写怎么写。
对我这种常年写 Java 的后端来说,用 uTools 做一个贴合自己使用习惯的"小插件",成了一个很自然的选择。
二、为什么要做一个桌面提醒插件?
市面上提醒 / 待办类应用已经很多了,但一直有几个痛点让我很难坚持用下去:
-
提醒不在"当前设备"上
我工作时间基本都盯着电脑,手机的闹钟提醒太重了,也容易影响到同事,而插件提醒就是一个弹窗,随手一点就能关闭,很轻量,不扰人。
-
重复规则太简单
很多应用只支持:每天 / 每周几 / 每月几号。
但我经常需要一些更"奇怪"的规则,比如:
- 每月 1、15、28 号各提醒一次;
- 每天 09:00、14:00、21:30 都要提醒;
- 工作日 09:00--18:00,每隔 45 分钟提醒喝水。
-
多渠道通知不好用
有些事情我是真的不能错过,希望:
桌面弹窗 + 系统通知 + 企业微信 / 钉钉
能一起推送一遍,有了站外推送, 即便不在电脑前,或者插件退出了,也能收到提醒。
三、插件长什么样?先上效果 & 功能预览
- 1:主界面 + 提醒列表

-
2:新增 / 编辑提醒表单
- 标题、内容
- 时间设置(首个时间 + 多个额外时间点)
- 重复规则(每天 / 每周 / 每月 / 每年 / 自定义)
- 时间段循环提醒:起止时间 + 间隔分钟数


- 3:通知设置 + 多渠道配置

- 4:提醒效果
弹窗提醒+系统提醒 
当前插件已经支持的功能
简单列一下目前已经实现的功能:
-
✅ 各种重复类型
- 不重复 / 每天 / 每周(任意组合周几)
- 每月 / 每年(支持任意日期)
- 自定义模式(用药提醒、纪念日等)
-
✅ 多时间点提醒
- 每天可以设置多个时间点:
比如09:00 / 14:00 / 21:30各提醒一次
- 每天可以设置多个时间点:
-
✅ 时间段循环提醒
-
设置「时间段 + 间隔」,例如:
- 每天 09:00--18:00,每隔 45 分钟提醒一次
-
-
✅ Cron 表达式
- 给自己留了一个"高级玩法入口"
-
✅ AI 一句话创建提醒
- 比如:"明天下午三点提醒我交电费",后端解析出结构化提醒数据
-
✅ 多渠道通知
- 桌面弹窗(即使插件窗口隐藏)
- 系统通知
- Bark 推送
- 企业微信机器人
- 钉钉机器人
-
✅ 后端统一存储
- 提醒记录保存在后端,天然支持多平台同步(同一账号,多个设备一起用)
四、整体实现
主要分三块:
-
uTools 插件前端(Vue)
- 负责配置 / 展示 / 弹窗
- 通过 HTTP + WebSocket 和后端交互
-
Spring Boot 后端
- 负责存储提醒、计算下一次执行时间、触发提醒
-
通知适配层
- 把"提醒事件"统一分发到不同渠道:Bark、企微、钉钉、桌面弹窗、系统通知......
五、后端实现
5.1 表结构设计:为"复杂重复规则"留好余地
提醒系统的本质是一个"带规则的定时任务系统"。
我给每条提醒设计了大致这样的结构(简化版):
sql
CREATE TABLE t_user_reminder (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL COMMENT '用户ID',
template_id BIGINT DEFAULT NULL COMMENT '引用的模板ID,可为空',
title VARCHAR(128) NOT NULL COMMENT '提醒标题',
content TEXT COMMENT '提醒内容',
remind_time DATETIME NOT NULL COMMENT '下一次提醒时间',
repeat_rule VARCHAR(64) DEFAULT NULL COMMENT 'NONE, DAILY, WEEKLY, MONTHLY, YEARLY, WORKDAY, CUSTOM',
custom_mode VARCHAR(64) DEFAULT NULL COMMENT 'WEEKLY, YEARLY, MEDICINE, ANNIVERSARY 等',
repeat_interval INT NOT NULL DEFAULT 1 COMMENT '间隔数,如每2天/每2周',
repeat_weekdays SET('1','2','3','4','5','6','7') NULL COMMENT '每周的星期几',
repeat_month_days VARCHAR(256) NULL COMMENT '每月的哪几天,逗号分隔',
status TINYINT NOT NULL DEFAULT 1 COMMENT '1=启用 0=关闭 2=已结束',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL
);
几个关键点:
repeat_rule + custom_mode用来描述规则类型;repeat_weekdays/repeat_month_days支持"一条记录多个日期";remind_time始终存"下一次执行时间",触发一次就往后推一次。
调度时的伪逻辑:
- 定时任务定期扫描
remind_time <= now()且启用的记录; - 推送通知;
- 根据规则计算下一次
remind_time,更新回去; - 如果已经没有下一次了(比如单次提醒已过期),就把
status改为已结束。
5.2 Cron 表达式 & next 计算
为了给自己留个"高级玩法",我加了 Cron 表达式的支持。
现实问题:
用户可能写 5 段,也可能写 6 段(带秒),还可能是带 ? 的那种。
处理方式:
- 后端接收时先做规范化 :
缺秒就补秒,缺?就按默认规则填; - 统一交给一个
cron.next(baseTime)来算下一次执行时间。
baseTime 的选择上,我最后简化成这样:
ini
LocalDateTime base = LocalDateTime.now();
if (!isForSchedule
&& remind.getCronStartTime() != null
&& base.isBefore(remind.getCronStartTime())) {
base = remind.getCronStartTime();
}
LocalDateTime next = cron.next(base);
- 用于"预览"下次执行时间时,可能从
cronStartTime算; - 用于"调度"时,就是简单的
now()。
这一块我其实是和 AI 来回沟通了几轮,让它帮我重构逻辑、校验边界条件,省了不少脑细胞。
5.3 AI 一句话创建提醒 & Redis 限流
我给自己加了一个很爽的小功能:
在输入框里直接敲一句话:
"明天下午三点提醒我交电费",
后端调用 AI 解析出时间、标题、备注,直接生成提醒。
为了防止被恶意刷接口,用 Redis 做了一个非常简单的按天限流:
ini
public boolean checkAiLimit(Long userId) {
String key = "ai_time_" + userId;
Long count = redisTemplate.opsForValue().increment(key);
if (count != null && count == 1L) {
long seconds = calcSecondsUntilEndOfDay();
redisTemplate.expire(key, Duration.ofSeconds(seconds));
}
return count != null && count <= MAX_TIMES_PER_DAY;
}
- key 不带日期,过期时间设为当天剩余秒数;
- 第一次调用时设置失效时间,之后全部简单
increment; - 超过阈值直接拒绝,给出友好提示。
这段逻辑的方案设计,其实也是我先把想法和约束丢给 AI,让它帮我写完,再自己做一轮 review。
六、前端实现:复杂表单 + WebSocket + 独立提醒窗口
前端整体基于 Vue,核心页面包括:
- 提醒列表
- 新增 / 编辑提醒
- 即将提醒预览
- 通道配置弹窗
- 独立提醒弹窗窗口
6.1 复杂时间 / 重复设置表单
"新增提醒"这个表单是整个插件 UI 里最复杂的一块,要处理:
- 首次提醒时间(日期 + 时间)
- 多个额外时间点
extraTimes(仅时间) - 重复类型:不重复 / 每天 / 每周 / 每月 / 每年 / 自定义
- 自定义下的子模式:用药、纪念日等
- 时间段循环提醒:起止时间 + 间隔分钟
展示多时间点时,我搞了一个简化版的展示字段 displayTimesText:
kotlin
const displayTimesText = computed(() => {
const [date, time] = extractDateAndTimeFromFirstDateTime();
const times = [time, ...form.extraTimes.filter(Boolean)].filter(Boolean);
if (!times.length) return "未设置时间";
return times.join("、");
});
开发过程中,AI 在这里帮我做了很多琐碎事情,比如:
- 把"用户需求描述"翻译成结构化的 form 字段设计;
- 帮我生成初版的组件模板和样式;
- 排查 input[type="time"] 在不同浏览器下的一些小兼容问题;
- 优化滚动布局,解决双滚动条、按钮抖动等细节问题。
6.2 WebSocket 长连接 & 独立提醒窗口
为了保证 "人不在插件窗口,提醒也能到桌面" ,我前端用了 WebSocket + 独立提醒窗口的方案。
基本流程:
-
插件启动时,与后端建立 WebSocket 连接;
-
后端一旦有提醒触发,就推送一条消息;
-
前端收到消息后:
- 如果主窗口在前台,可以直接弹页面内弹窗;
- 如果主窗口隐藏,则使用 uTools 能力打开一个独立提醒窗口
remindWindow,显示提醒信息、播放提示音。
伪代码结构类似这样:
ini
let ws: WebSocket | null = null;
let heartbeatTimer: number | null = null;
let reconnectTimer: number | null = null;
let reconnectAttempts = 0;
const MAX_RECONNECT_ATTEMPTS = 5;
let remindWindow: any = null;
function initWebSocket() {
ws = new WebSocket(HTTP_BASE + "/ws/reminder");
ws.onopen = () => {
startHeartbeat();
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
handleReminder(msg);
};
ws.onclose = () => {
tryReconnect();
};
}
这里 AI 给我的主要帮助是:
- 一次性生成了 WebSocket + 心跳 + 重连的模板代码;
- 帮我梳理清楚"重连最大次数""心跳间隔""关闭连接时清理定时器"等边界情况;
- 根据我贴出来的报错信息,定位过几次逻辑问题。
6.3 多渠道通知设置 & 提示音试听
在插件设置页里,我提供了几种通知通道配置:
- Bark URL
- 企业微信机器人 Webhook
- 钉钉机器人 Webhook
- 是否开启桌面弹窗、系统通知
- 提示音选择 + 一键试听
提示音做法很简单:
- 将
default.mp3 / soft.mp3 / strong.mp3放在静态资源目录; - 选择不同值时,更新
form.soundName; - 点击"试听"按钮时,用
<audio>或 Audio 对象播放当前选择的声音。
这些交互细节基本都是我先丢一句话给 AI:"我有三个提示音文件,如何在选择时可以试听?"
它给我一段完整实现,我再根据项目结构稍微改一下就能用。
七、通知适配层:给未来扩展通道留个口子
后端有一个统一的 NotifyChannel 接口,所有通道都实现它:
java
public interface NotifyChannel {
boolean available(UserSettings settings);
void send(ReminderMsg msg, UserSettings settings);
}
然后分别有:
BarkNotifyChannelWeComRobotNotifyChannelDingTalkRobotNotifyChannel- ......(未来要扩的都可以继续加)
总的推送服务大概这样:
scss
@Service
public class ReminderPushService {
@Autowired
private List<NotifyChannel> notifyChannels;
public void pushReminder(TUserReminder reminder, UserSettings settings) {
ReminderMsg msg = buildMsg(reminder);
for (NotifyChannel channel : notifyChannels) {
if (channel.available(settings)) {
channel.send(msg, settings);
}
}
}
}
这种设计没什么炫技的地方,就是老老实实地面向接口编程 。
但对后面扩展通道非常友好
八、为什么说"我用 AI,一天就把这个插件撸完了"?
标题当然是带一点点"互联网文体"的 😂
准确一点说:
-
如果让我自己从 0 开始设计、踩坑,这个插件可能要拆成好几天甚至一两周的业余时间;
-
这次我把很多事情都丢给 AI 帮忙:
- 表结构初稿 & 字段命名建议;
- Cron 表达式兼容逻辑的实现 & 重构;
- Redis 限流的方案设计 + 代码;
- WebSocket + 心跳 + 重连的模板;
- Vue 表单的初版代码、样式优化建议;
- 各种细碎 bug:乱码、时间类型转换、滚动条问题......
结果就是:在已经很熟悉的 Java & Vue 基础上,
靠 AI 填了大量机械、重复、查文档的部分,核心 MVP 确实是一整天搞定的。
我更多地是在做:
- 需求决策:要不要这个功能?交互怎么做才顺手?
- 代码 Review:AI 给的代码哪里需要调整?
- 业务规则定义:这个提醒系统应该怎样才"顺人性"?
AI 更适合作为一个高强度不抱怨的结对编程伙伴,帮我把"我已经知道怎么做,但懒得敲"的那部分快速完成。
九、写在最后
到现在,「灵犀提醒」已经稳定地接管了我日常的很多小事:
- 每天上班打卡、喝水、写日报;
- 每月房租、信用卡还款、各种账单;
- 特定日期的生日、纪念日提醒;
- 工作上的一些关键 follow-up,会同时打到企业微信 / 钉钉。
这篇文章一方面是想分享下这套 "uTools + Spring Boot + AI 协作"的开发实践
如果你:
- 也常年对着电脑;
- 也老是忘东忘西;
- 也想找个机会实战一下"AI 辅助开发";
那不妨也找个周末,试试做一个属于你的"小插件"。
如果你对这个提醒插件感兴趣,或者想看更细的某一块实现(比如 Cron 核心调度逻辑、WebSocket 服务端实现、AI 解析那一块),可以在评论区告诉我,
如果,你也是个utools使用者, 不妨试用下插件哦
最后
如果文章对你有帮助,点个免费的赞鼓励一下吧!关注公众号:加瓦点灯, 每天推送干货知识!