若依定时任务推送消息(每天早上八点发送上班提醒)
创作时间:
作者:
@小白创作中心
若依定时任务推送消息(每天早上八点发送上班提醒)
引用
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>
热门推荐
二拇指关节处硬疙瘩的可能原因及建议
餐桌挑选方法及风格搭配技巧
郁金香怎么种植土培(郁金香土培的正确方法)
抽1根等于白练3天?抽烟对健身的影响——别让香烟害了你!
我是"湾村明白人":老支书有新目标 化解矛盾护和谐
主食罐头可以代替猫粮吗?混合喂食需谨慎
OpenAI推出"成人模式":AI内容生成政策的重大转向
电生理检查是怎么做的
动脉粥样硬化吃什么能让血管恢复弹性
捡到万元现金,聊城一小学生寒风中原地等待1小时归还失主
香附子的两面性:毒性与安全使用须知
第一次坐火车所有流程,需要注意什么
八字命理:财星配印是富贵八字吗?深度解析
贵州茅台进入买方市场了吗?
2025年新工科专业!这4大热门专业先了解,抢占未来就业先机
社工+志愿者!银川市为民服务模式上新
民族团结专题课件:土尔扈特人的东归之路
武夷山“金骏眉”茶叶知识
如何在自助餐中健康美味全兼顾:从海鲜到甜点的选择攻略
根据身材选裤子 怎么穿都显瘦
福清到海南有多少公里及海口距离与动车票价
一年十二个月对应的应季时令水果蔬菜有哪些?
熬夜必备!男生熬夜食谱,让你恢复活力!
髋关节疼痛如何缓解
9万亿股份行行长落定,70后芦苇接班中信银行,曾是中信最年轻副行长之一
USS Enterprise CV(N)-6—— 企业号 "灰色幽灵"(上)
如何利用国漫3D模型技术提升动画作品的视觉效果
夫妻冷暴力怎么解决
门静脉高压临床表现是哪些
8万股东踩雷!深市“老五股”之一ST星源终止上市!