一、系统架构设计
采用 "分层架构 + 事件驱动引擎" 模式,各层级职责与技术选型如下:
|
层级 |
核心职责 |
技术选型 |
|
前端层 |
用户交互(增删改查提醒)、实时接收提醒、处理确认操作 |
Vue/React + WebSocket 客户端 |
|
接口层 |
接收用户操作请求(CRUD 提醒)、转发事件触发结果 |
Spring Boot + REST API + WebSocket 服务端 |
|
事件调度层 |
管理提醒事件的注册、触发、重试,核心是 "时间精准调度" |
Quartz(分布式调度框架) |
|
业务逻辑层 |
解析用户规则、生成事件、处理触发逻辑、更新事件状态 |
Spring Bean + 事务管理 |
|
数据存储层 |
持久化提醒规则、事件状态、用户操作记录 |
MySQL(主数据) + Redis(临时状态) |
二、核心数据模型设计
1. 提醒规则表(user_reminder)
存储用户配置的提醒基本信息,关联事件调度的元数据:
CREATE TABLE user_reminder (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '提醒ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
content TEXT NOT NULL COMMENT '提醒内容',
rule_json JSON NOT NULL COMMENT '触发规则:{"type":"weekly","weekDay":1,"time":"09:00"}',
cron_expression VARCHAR(100) NOT NULL COMMENT '规则转换的Cron表达式(供调度器使用)',
status TINYINT DEFAULT 1 COMMENT '状态:1-启用 0-禁用',
create_time DATETIME DEFAULT NOW(),
update_time DATETIME DEFAULT NOW()
);
-- 索引:用户ID + 状态(加速查询用户有效提醒)
CREATE INDEX idx_user_status ON user_reminder(user_id, status);
2. 事件状态表(reminder_event)
追踪每个提醒事件的触发状态(解决 "触发后未确认" 的周期提醒问题):
CREATE TABLE reminder_event (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '事件ID',
reminder_id BIGINT NOT NULL COMMENT '关联提醒ID',
expected_trigger_time DATETIME NOT NULL COMMENT '预期触发时间',
actual_trigger_time DATETIME COMMENT '实际触发时间',
confirm_time DATETIME COMMENT '用户确认时间(null表示未确认)',
retry_count TINYINT DEFAULT 0 COMMENT '触发失败重试次数',
status TINYINT DEFAULT 0 COMMENT '状态:0-待触发 1-已触发 2-已确认 3-已过期',
FOREIGN KEY (reminder_id) REFERENCES user_reminder(id) ON DELETE CASCADE
);
-- 索引:预期触发时间 + 状态(加速调度器查询待触发事件)
CREATE INDEX idx_trigger_status ON reminder_event(expected_trigger_time, status);
三、核心流程设计
1. 新增提醒:规则解析 → 事件注册
用户创建提醒后,系统自动生成对应事件并注册到调度器:

sequenceDiagram
前端->>接口层: 提交提醒规则(如“每周一9:00提醒开会”)
接口层->>业务逻辑层: 解析规则
业务逻辑层->>业务逻辑层: 转换为Cron表达式(如“0 0 9 ? * MON”)
业务逻辑层->>数据存储层: 保存规则到user_reminder表
业务逻辑层->>事件调度层: 注册周期性事件(绑定Cron表达式)
事件调度层-->>业务逻辑层: 返回事件注册结果
业务逻辑层-->>前端: 提示“创建成功”
核心代码实现
规则转 Cron 表达式:
// 解析用户规则为Cron表达式(示例:每周一9:00)
public String ruleToCron(ReminderRule rule) {
String time = rule.getTime(); // 格式:"09:00"
String[] hm = time.split(":");
int hour = Integer.parseInt(hm[0]);
int minute = Integer.parseInt(hm[1]);
switch (rule.getType()) {
case "daily":
return String.format("0 %d %d * * ?", minute, hour); // 每天h:m
case "weekly":
int weekDay = rule.getWeekDay(); // 1=周一...7=周日(Quartz中1=周日,需转换)
int quartzWeekDay = weekDay == 7 ? 1 : weekDay + 1;
return String.format("0 %d %d ? * %d", minute, hour, quartzWeekDay); // 每周几h:m
case "monthly":
return String.format("0 %d %d %d * ?", minute, hour, rule.getDay()); // 每月d日h:m
default:
throw new IllegalArgumentException("不支持的规则类型");
}
}
事件注册到 Quartz:
// 为提醒创建周期性调度任务
public void registerReminderEvent(UserReminder reminder) {
// 1. 创建任务(触发时执行的逻辑)
JobDetail job = JobBuilder.newJob(ReminderTriggerJob.class)
.withIdentity("reminder_" + reminder.getId(), "user_" + reminder.getUserId())
.usingJobData("reminderId", reminder.getId()) // 传递提醒ID
.build();
// 2. 创建触发器(基于Cron表达式)
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger_" + reminder.getId(), "user_" + reminder.getUserId())
.withSchedule(CronScheduleBuilder.cronSchedule(reminder.getCronExpression()))
.startNow()
.build();
// 3. 注册到调度器
scheduler.scheduleJob(job, trigger);
}
2. 事件触发:精准执行 → 实时推送
当到达预期触发时间,调度器执行事件,推送提醒给用户:

sequenceDiagram
事件调度层-->>业务逻辑层: 触发事件(提醒ID=1001,预期时间=9:00)
业务逻辑层->>数据存储层: 新增reminder_event记录(状态=待触发)
业务逻辑层->>业务逻辑层: 检查用户在线状态(WebSocket连接)
业务逻辑层->>接口层: 推送提醒(通过WebSocket)
接口层-->>前端: 实时展示提醒弹窗
业务逻辑层->>数据存储层: 更新事件状态为“已触发”(actual_trigger_time=9:00)
核心代码实现
事件触发执行逻辑(Job 实现):
public class ReminderTriggerJob implements Job {
@Autowired
private ReminderService reminderService;
@Autowired
private WebSocketService webSocketService;
@Override
public void execute(JobExecutionContext context) {
Long reminderId = context.getJobDetail().getJobDataMap().getLong("reminderId");
// 1. 查询提醒详情和用户ID
UserReminder reminder = reminderService.getById(reminderId);
// 2. 创建事件记录(待触发)
ReminderEvent event = reminderService.createEvent(reminder);
// 3. 推送提醒给用户(通过WebSocket)
boolean pushSuccess = webSocketService.pushToUser(
reminder.getUserId(),
new ReminderMessage(event.getId(), reminder.getContent())
);
// 4. 更新事件状态(成功/失败)
if (pushSuccess) {
reminderService.updateEventStatus(event.getId(), EventStatus.TRIGGERED);
} else {
// 触发失败,记录重试次数(后续补偿任务处理)
reminderService.incrementRetryCount(event.getId());
}
}
}
3. 用户确认:更新状态 → 周期续期
用户点击 "知道了" 后,系统更新事件状态,若为周期性提醒,自动续期下一次事件:

sequenceDiagram
前端->>接口层: 提交确认(事件ID=5001)
接口层->>业务逻辑层: 处理确认
业务逻辑层->>数据存储层: 更新事件状态为“已确认”(confirm_time=当前时间)
业务逻辑层->>业务逻辑层: 判断是否为周期性提醒
alt 周期性提醒
业务逻辑层->>事件调度层: 无需额外操作(Quartz自动按Cron生成下一次事件)
else 一次性提醒
业务逻辑层->>事件调度层: 移除该提醒的调度任务
end
业务逻辑层-->>前端: 提示“已确认”
核心代码实现
确认处理逻辑:
@Transactional
public void confirmEvent(Long eventId) {
// 1. 更新事件状态为“已确认”
ReminderEvent event = eventMapper.selectById(eventId);
event.setConfirmTime(LocalDateTime.now());
event.setStatus(EventStatus.CONFIRMED);
eventMapper.updateById(event);
// 2. 若为一次性提醒,删除调度任务
UserReminder reminder = reminderMapper.selectById(event.getReminderId());
if (isOneTimeReminder(reminder.getRuleJson())) { // 判断是否为一次性规则
scheduler.unscheduleJob(
TriggerKey.triggerKey("trigger_" + reminder.getId(), "user_" + reminder.getUserId())
);
}
}
4. 修改 / 删除提醒:同步更新调度器
修改提醒流程
1. 删除旧调度任务
2. 更新数据库规则
3. 按新规则创建新任务
public void updateReminder(UserReminder newReminder) {
Long reminderId = newReminder.getId();
// 1. 删除旧调度任务
scheduler.unscheduleJob(
TriggerKey.triggerKey("trigger_" + reminderId, "user_" + newReminder.getUserId())
);
// 2. 更新数据库规则
newReminder.setCronExpression(ruleToCron(newReminder.getRuleJson()));
reminderMapper.updateById(newReminder);
// 3. 注册新调度任务
registerReminderEvent(newReminder);
}
删除提醒流程
1. 删除数据库user_reminder记录
2. 移除关联的调度任务
四、异常处理与可靠性设计
1. 触发失败重试机制
场景:WebSocket 推送失败(用户离线)、网络波动
处理:事件状态标记为 "触发失败",补偿任务每 5 分钟检查一次,重试推送(最多 3 次)
// 补偿任务(Quartz定时执行,每5分钟一次)
public class RetryFailedEventJob implements Job {
@Override
public void execute(JobExecutionContext context) {
// 查询触发失败且重试次数<3的事件
List<ReminderEvent> failedEvents = eventMapper.selectFailedEvents(3);
failedEvents.forEach(event -> {
webSocketService.pushToUser(
event.getUserId(),
new ReminderMessage(event.getId(), event.getContent())
);
eventMapper.incrementRetryCount(event.getId());
});
}
}
2. 分布式环境并发控制
● 多实例部署时,Quartz 通过数据库锁保证同一事件仅被一个实例触发
● 事件状态更新加行锁(SELECT ... FOR UPDATE),避免重复处理
3. 事件持久化
Quartz 配置 JDBC JobStore,将任务元数据存储到数据库,确保系统重启后任务不丢失:
# Quartz配置(application.properties)
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource=quartzDataSource
org.quartz.dataSource.quartzDataSource.URL=jdbc:mysql://localhost:3306/quartz
五、前端交互设计
1. 实时接收提醒
通过 WebSocket 连接后端,接收推送的提醒消息并弹窗:
// 初始化WebSocket
const ws = new WebSocket(`wss://${location.host}/ws/reminders?userId=${userId}`);
ws.onmessage = (event) => {
const reminder = JSON.parse(event.data);
// 显示提醒弹窗
showReminderModal(reminder.id, reminder.content);
};
// 确认提醒
function confirmReminder(eventId) {
fetch('/api/reminders/confirm', {
method: 'POST',
body: JSON.stringify({ eventId }),
headers: { 'Content-Type': 'application/json' }
}).then(() => {
closeReminderModal();
});
}
2. 提醒管理界面
支持用户新增(规则配置表单)、编辑(修改规则)、删除(一键移除):
<!-- 新增提醒表单 -->
<form @submit="addReminder">
<input v-model="content" placeholder="提醒内容" />
<select v-model="ruleType">
<option value="daily">每天</option>
<option value="weekly">每周</option>
</select>
<input v-model="time" type="time" />
<button type="submit">保存</button>
</form>
六、方案优势与扩展能力
优势
1. 无时间窗漏洞:基于 Quartz 的 Cron 调度,触发时间精准到秒,避免轮询的时间差
2. 实时性:WebSocket 推送 + 事件驱动,提醒到达延迟≤1 秒
3. 可扩展性:支持百万级用户(通过 Quartz 集群 + 数据库分表)
4. 状态透明:用户可查看提醒的 "待触发 / 已触发 / 已确认" 状态
扩展方向
● 多渠道提醒:触发时同时推送 WebSocket + 短信 / 邮件(针对离线用户)
● 智能暂停:用户可手动暂停提醒(更新user_reminder.status=0,调度器忽略)
● 负载均衡:Quartz 集群按用户 ID 分片,避免单实例压力过大
● 统计分析:增加提醒触发率、确认率统计,优化用户体验