若依定时任务推送消息(每天早上八点发送上班提醒)
创作时间:
作者:
@小白创作中心
若依定时任务推送消息(每天早上八点发送上班提醒)
引用
CSDN
1.
https://blog.csdn.net/qq_62859013/article/details/146375861
若依框架是一个基于Spring Boot的快速开发平台,广泛应用于企业级应用开发。本文将详细介绍如何在若依框架中使用Quartz实现每天早上八点发送上班提醒的功能。
Quartz定时任务调度框架
在若依框架中,定时任务使用的是Quartz框架,若依提供了可视化页面,使得定时任务的管理变得非常方便。
Quartz的核心功能
- 任务调度:定义任务的执行计划,并在指定时间或周期性执行任务。
- 任务管理:管理和控制任务的生命周期,如启动、暂停、删除等。
- 持久化:支持将任务的状态持久化到数据库,以便在应用重启后恢复任务状态。
Quartz的主要组件
- Scheduler:调度器,是Quartz的核心,用于管理和调度任务。
- Job:任务接口,定义任务的执行逻辑。所有Quartz任务必须实现这个接口。
- JobDetail:任务细节对象,定义了任务的具体实现和执行参数。
- Trigger:触发器,定义了任务的触发条件,如时间、周期等。
- JobDataMap:任务数据映射,用于传递任务执行时所需的数据。
定时提醒工作流程
- WorkReminderTask定时执行,从SysStaffController获取员工列表
- 在Redis中创建提醒信息
- 前端通过notice.js调用SysReminderApi的接口
- 获取到提醒后,显示提醒界面给用户
这种设计实现了:
- 定时任务与接口分离
- 前后端分离
- 数据的临时存储(Redis)
- 模块化的代码结构
前端调用流程
- App.vue只在早上7:55到8:05期间进行轮询检查提醒状态,前端定期检查的作用:
- 检查后端是否有为当前用户创建的提醒
- 只有在用户打开应用时才会执行
- 只能处理当前登录用户的提醒
- 如果有未读提醒触发,跳转到notice.vue页面
- 用户查看提醒后,点击按钮调用markWorkReminderAsRead()标记已读
后端调用流程
- 后端定时器(WorkReminderTask):定时:通过远程调用remoteStaffService目的是获取所有要提醒的用户
并且主动创建(每天早上八点创建)提醒数据,将提醒信息写入Redis。 - remoteStaffService远程调用controller中的getStaffIdsWithWorkReminder方法,通过SecurityUtils获得所有要提醒的用户,SecurityUtils中可以获取当前用户的身份信息。
Long staffId = SecurityUtils.getLoginUser().getSysStaff().getStaffId();
- 然后创建api可以让前端使用,一共有两个方法:1.获取上班提醒状态2.标记上班提醒为已读
为什么用openfeign?因为定时器WorkReminderTask在job模块中,它调不到system中的service
整体工作流程
- 定时任务系统在每天早上八点触发WorkReminderTask.executeWorkReminder()
- 任务通过RemoteStaffService获取需要提醒的员工列表
- 为每个员工在Redis中创建提醒记录
- 前端App定期调用SysReminderApi.getWorkReminderStatus()检查是否有提醒
- 如果有未读提醒,前端显示提醒页面
- 用户确认后,前端调用SysReminderApi.markWorkReminderAsRead()标记为已读
这种设计利用了微服务架构和Redis缓存,实现了高效、可靠的上班提醒功能。
- 后端定时器负责"生产"提醒数据
- 前端定期检查负责"消费"提醒数据
如果没有后端定时器,前端检查时将找不到任何提醒数据。如果没有前端检查,用户将看不到后端创建的提醒。
具体文件说明
这几个文件共同构成了一个上班提醒系统,实现了每天早上八点自动提醒员工上班打卡的功能。下面是各个文件的作用:
- WorkReminderTask.java
这是一个定时任务类,主要功能是在每天早上八点执行上班提醒:
- @Component("workReminderTask")注解将其注册为Spring组件,可以在定时任务配置中引用
- executeWorkReminder()方法是定时任务的执行入口
- 通过RemoteStaffService获取需要提醒的员工ID列表
- 为每个员工创建提醒信息,并存储到Redis中,设置24小时过期时间
redisCache.setCacheObject(reminderKey, reminderInfo, 24 * 60 * 60);
- 提醒信息包含标题、内容、时间和已读状态
- SysReminderApi.java
这是一个REST API控制器,提供前端访问提醒相关功能的接口:
- @RequestMapping("/api/reminder")定义了API的基础路径
- getWorkReminderStatus()方法:获取当前登录用户的上班提醒状态
- SecurityUtils:
- 获取当前用户的身份信息。
- 检查用户是否有某个角色或权限。
- 简化安全操作的代码,提供统一的接口。
Long staffId = SecurityUtils.getLoginUser().getSysStaff().getStaffId();
- markWorkReminderAsRead()方法:标记提醒为已读
- testWorkReminder()方法:用于测试,手动创建一条提醒
- RemoteStaffService.java
这是一个Feign客户端接口,用于微服务间的远程调用:
因为定时任务WorkReminderTask.java中需要利用openfeign来进行服务间的调用使用controller中的getStaffIdsWithWorkReminder
- @FeignClient注解指定了调用的目标服务
- getStaffIdsWithWorkReminder()方法用于获取需要上班提醒的员工ID列表
- 定时任务通过这个接口获取需要提醒的员工信息
- RedisConfig.java
这是Redis配置类,为定时任务模块配置Redis操作相关的Bean:
- 配置了RedisTemplate,用于Redis数据操作
- 配置了序列化方式为StringRedisSerializer
- 创建了RedisCacheBean,提供更便捷的Redis操作方法
定时任务配置
在系统监控--》定时任务中新增
component里面是实例,后面是具体的定时方法
前端 UniApp 接收后端推送信息的方法
- WebSocket 方式
<script>
import config from './config'
import store from '@/store'
import { getToken } from '@/utils/auth'
import { getWorkReminderStatus, markWorkReminderAsRead } from '@/api/notice/notice'
export default {
data() {
return {
lastCheckTime: 0,
reminderTimer: null,
morningCheckDone: false, // 记录今天是否已执行过早上八点检查
socketTask: null, // WebSocket 连接对象
}
},
onLaunch: function() {
this.initApp()
// 检查是否有上班提醒
this.checkWorkReminder(true) // 首次启动强制检查
// 清除可能存在的旧定时器
if (this.reminderTimer) {
clearInterval(this.reminderTimer)
}
// 设置定时器,每分钟检查一次是否到了早上八点
this.reminderTimer = setInterval(() => {
// ... 现有代码 ...
}, 60000) // 每分钟检查一次
// 初始化 WebSocket 连接
this.initWebSocket()
},
// ... 现有代码 ...
methods: {
// ... 现有方法 ...
// 初始化 WebSocket 连接
initWebSocket() {
if (getToken()) {
// 关闭已存在的连接
if (this.socketTask) {
this.socketTask.close()
}
// 创建新连接
this.socketTask = uni.connectSocket({
url: config.baseUrl.replace('http', 'ws') + '/websocket/' + getToken(),
success: () => {
console.log('WebSocket 连接已建立')
}
})
// 监听连接打开
this.socketTask.onOpen(() => {
console.log('WebSocket 连接已打开')
})
// 监听接收消息
this.socketTask.onMessage((res) => {
console.log('收到推送消息:', res)
try {
const message = JSON.parse(res.data)
if (message.type === 'reminder') {
// 处理提醒消息
this.handleReminderMessage(message)
}
} catch (e) {
console.error('解析消息失败:', e)
}
})
// 监听连接关闭
this.socketTask.onClose(() => {
console.log('WebSocket 连接已关闭')
// 尝试重新连接
setTimeout(() => {
this.initWebSocket()
}, 5000)
})
// 监听连接错误
this.socketTask.onError((error) => {
console.error('WebSocket 连接错误:', error)
})
}
},
// 处理提醒消息
handleReminderMessage(message) {
// 如果有未读提醒,跳转到提醒页面
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
// 如果当前不在提醒页面,才进行跳转
if (!currentPage || currentPage.route !== 'pages/notice/notice') {
uni.navigateTo({
url: '/pages/notice/notice?title=' + encodeURIComponent(message.title || '系统提醒') +
'&content=' + encodeURIComponent(message.content || '您有新的提醒'),
success: () => {
console.log('成功跳转到提醒页面')
},
fail: (err) => {
console.error('跳转提醒页面失败:', err)
}
})
}
}
}
}
</script>
轮询方式(已实现)
我使用的方法通过定时调用接口检查是否有新提醒。这种方式简单可靠,但不够实时。消息推送方式
// 在 onLaunch 方法中添加
onLaunch: function() {
// ... 现有代码 ...
// 初始化推送服务
this.initPush()
},
// 在 methods 中添加
methods: {
// ... 现有方法 ...
// 初始化推送服务
initPush() {
// #ifdef APP-PLUS
// 获取客户端推送标识
const clientInfo = plus.push.getClientInfo()
if (clientInfo) {
// 向服务器发送推送标识,用于后续推送
this.registerPushId(clientInfo)
// 监听接收透传消息事件
plus.push.addEventListener('receive', (msg) => {
console.log('收到推送消息:', msg)
// 处理推送消息
this.handlePushMessage(msg)
})
}
// #endif
},
// 注册推送ID到服务器
registerPushId(clientInfo) {
// 调用接口将推送ID注册到服务器
uni.request({
url: config.baseUrl + '/system/api/push/register',
method: 'POST',
data: {
clientId: clientInfo.clientid,
appId: clientInfo.appid,
appVersion: clientInfo.appversion,
platform: uni.getSystemInfoSync().platform
},
header: {
'Authorization': 'Bearer ' + getToken()
},
success: (res) => {
console.log('推送ID注册成功')
},
fail: (err) => {
console.error('推送ID注册失败:', err)
}
})
},
// 处理推送消息
handlePushMessage(msg) {
try {
const message = JSON.parse(msg.content)
if (message.type === 'reminder') {
// 处理提醒消息
uni.navigateTo({
url: '/pages/notice/notice?title=' + encodeURIComponent(message.title || '系统提醒') +
'&content=' + encodeURIComponent(message.content || '您有新的提醒')
})
}
} catch (e) {
console.error('解析推送消息失败:', e)
}
}
}
总结:WebSocket 方式适合需要实时交互的场景,而推送服务则更适合移动应用的后台通知。
测试代码
有固定间隔检查。
<script>
import config from './config'
import store from '@/store'
import { getToken } from '@/utils/auth'
import { getWorkReminderStatus, markWorkReminderAsRead } from '@/api/notice/notice'
export default {
data() {
return {
lastCheckTime: 0,
reminderTimer: null
}
},
onLaunch: function() {
this.initApp()
// 检查是否有上班提醒
this.checkWorkReminder(true) // 首次启动强制检查
// 清除可能存在的旧定时器
if (this.reminderTimer) {
clearInterval(this.reminderTimer)
}
// 设置新定时器,每5分钟检查一次
this.reminderTimer = setInterval(() => {
console.log('定时检查提醒,间隔:5分钟')
this.checkWorkReminder(true) // 定时器触发的检查强制执行
}, 10000)
},
onShow: function() {
// 只在应用从后台切换到前台时检查,且与上次检查间隔超过1分钟才执行
const now = Date.now()
if (!this.lastCheckTime || now - this.lastCheckTime > 60000) {
console.log('App Show - 执行提醒检查')
this.checkWorkReminder(false)
} else {
console.log('App Show - 最近已检查过,跳过本次检查')
}
},
// 添加页面卸载时的清理
onUnload: function() {
if (this.reminderTimer) {
clearInterval(this.reminderTimer)
this.reminderTimer = null
}
},
methods: {
// 初始化应用
initApp() {
// 初始化应用配置
this.initConfig()
// 检查用户登录状态
//#ifdef H5
this.checkLogin()
//#endif
},
initConfig() {
this.globalData.config = config
},
checkLogin() {
if (!getToken()) {
this.$tab.reLaunch('/pages/login')
}
},
// 检查是否有上班提醒
checkWorkReminder(forceCheck = false) {
// 添加节流控制,防止频繁调用
const now = Date.now()
if (!forceCheck && this.lastCheckTime && now - this.lastCheckTime < 60000) { // 至少间隔1分钟
// 不输出日志,避免控制台污染
return
}
this.lastCheckTime = now
console.log('检查上班提醒============')
// 确保用户已登录
if (getToken()) {
// 获取上班提醒状态
getWorkReminderStatus().then(res => {
console.log('提醒检查响应:', res)
if (res.code === 200 && res.data && res.data !== 'noReminder' && !res.data.read) {
// 如果有未读提醒,跳转到提醒页面
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
// 如果当前不在提醒页面,才进行跳转
if (!currentPage || currentPage.route !== 'pages/notice/notice') {
uni.navigateTo({
url: '/pages/notice/notice?title=' + encodeURIComponent(res.data.title || '上班提醒') +
'&content=' + encodeURIComponent(res.data.content || '测试提醒'),
success: () => {
console.log('成功跳转到提醒页面')
// 标记提醒为已读
markWorkReminderAsRead().then(() => {
console.log('提醒已标记为已读')
}).catch(err => {
console.error('标记已读失败:', err)
})
},
fail: (err) => {
console.error('跳转提醒页面失败:', err)
}
})
}
}
}).catch(err => {
console.error('获取提醒失败', err)
})
}
}
}
}
</script>
<style lang="scss">
@import '@/static/scss/index.scss'
/* 在 App.vue 的 style 标签中添加 */
</style>
热门推荐
旋转矩阵的性质分析:为什么它是完美的正交矩阵?
风水学核心要素与现代应用解析
伤官生财能够大富吗
如何避免团队甩锅
甄姬历史形象演变研究
植物神经紊乱维生素小贴士
如何制定科学的体能训练计划表?
青年人群高血压患病率超10%:加强血压健康管理 倡导良好生活方式
公积金不按照实际工资缴纳去哪投诉
三分钟教你区分纯棉与人造棉
5种绿色食物让你越吃越白!皮肤从此变得有光泽
不同咖啡豆种,风味有啥不同?(必看干货)
男宝宝取名宥字搭配寓意
日本料理咖喱的魅力与历史:从英国传入到国民美食
高考志愿平行志愿和顺序志愿的区别是什么 有什么不同
如何解梦?告诉你梦的所有知识
Qt数据库重新连接机制详解
长期不感冒,免疫力真的会变差吗?
罕见!铜仁惊现濒危物种鸿雁!
房屋维修基金的用途范围及管理方式解析
王者荣耀后手法师角色推荐 怎么选择合适的法师
L3级智驾元年来临!多家车企宣布计划 新能源汽车将迎来变革
汽车轮胎有正反吗?装反了有什么影响?
韩泰K胎是否有正反面之分?
破产法律框架是什么
“种养肥”三产融合 推进绿色种养循环
安息角测试计的测试方法及原理
五行八字最好的命局:揭秘最佳命格奥秘
深圳保租房摇号大揭秘:家庭申请数翻倍,公证摇号全程直播
八字算命教程之阴阳五行学说