问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

若依定时任务推送消息(每天早上八点发送上班提醒)

创作时间:
作者:
@小白创作中心

若依定时任务推送消息(每天早上八点发送上班提醒)

引用
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:任务数据映射,用于传递任务执行时所需的数据。

定时提醒工作流程

  1. WorkReminderTask定时执行,从SysStaffController获取员工列表
  2. Redis中创建提醒信息
  3. 前端通过notice.js调用SysReminderApi的接口
  4. 获取到提醒后,显示提醒界面给用户

这种设计实现了:

  • 定时任务与接口分离
  • 前后端分离
  • 数据的临时存储(Redis)
  • 模块化的代码结构

前端调用流程

  1. App.vue只在早上7:55到8:05期间进行轮询检查提醒状态,前端定期检查的作用:
  • 检查后端是否有为当前用户创建的提醒
  • 只有在用户打开应用时才会执行
  • 只能处理当前登录用户的提醒
  1. 如果有未读提醒触发,跳转到notice.vue页面
  2. 用户查看提醒后,点击按钮调用markWorkReminderAsRead()标记已读

后端调用流程

  1. 后端定时器(WorkReminderTask):定时:通过远程调用remoteStaffService目的是获取所有要提醒的用户
    并且主动创建(每天早上八点创建)提醒数据,将提醒信息写入Redis。
  2. remoteStaffService远程调用controller中的getStaffIdsWithWorkReminder方法,通过SecurityUtils获得所有要提醒的用户,SecurityUtils中可以获取当前用户的身份信息。
    Long staffId = SecurityUtils.getLoginUser().getSysStaff().getStaffId();
    
  3. 然后创建api可以让前端使用,一共有两个方法:1.获取上班提醒状态2.标记上班提醒为已读

为什么用openfeign?因为定时器WorkReminderTask在job模块中,它调不到system中的service

整体工作流程

  1. 定时任务系统在每天早上八点触发WorkReminderTask.executeWorkReminder()
  2. 任务通过RemoteStaffService获取需要提醒的员工列表
  3. 为每个员工在Redis中创建提醒记录
  4. 前端App定期调用SysReminderApi.getWorkReminderStatus()检查是否有提醒
  5. 如果有未读提醒,前端显示提醒页面
  6. 用户确认后,前端调用SysReminderApi.markWorkReminderAsRead()标记为已读

这种设计利用了微服务架构和Redis缓存,实现了高效、可靠的上班提醒功能。

- 后端定时器负责"生产"提醒数据
- 前端定期检查负责"消费"提醒数据

如果没有后端定时器,前端检查时将找不到任何提醒数据。如果没有前端检查,用户将看不到后端创建的提醒。

具体文件说明

这几个文件共同构成了一个上班提醒系统,实现了每天早上八点自动提醒员工上班打卡的功能。下面是各个文件的作用:

  1. WorkReminderTask.java
    这是一个定时任务类,主要功能是在每天早上八点执行上班提醒:
  • @Component("workReminderTask")注解将其注册为Spring组件,可以在定时任务配置中引用
  • executeWorkReminder()方法是定时任务的执行入口
  • 通过RemoteStaffService获取需要提醒的员工ID列表
  • 为每个员工创建提醒信息,并存储到Redis中,设置24小时过期时间
    redisCache.setCacheObject(reminderKey, reminderInfo, 24 * 60 * 60);
    
  • 提醒信息包含标题、内容、时间和已读状态
  1. SysReminderApi.java
    这是一个REST API控制器,提供前端访问提醒相关功能的接口:
  • @RequestMapping("/api/reminder")定义了API的基础路径
  • getWorkReminderStatus()方法:获取当前登录用户的上班提醒状态
  • SecurityUtils
  • 获取当前用户的身份信息。
  • 检查用户是否有某个角色或权限。
  • 简化安全操作的代码,提供统一的接口。
    Long staffId = SecurityUtils.getLoginUser().getSysStaff().getStaffId();
    
  • markWorkReminderAsRead()方法:标记提醒为已读
  • testWorkReminder()方法:用于测试,手动创建一条提醒
  1. RemoteStaffService.java
    这是一个Feign客户端接口,用于微服务间的远程调用:
    因为定时任务WorkReminderTask.java中需要利用openfeign来进行服务间的调用使用controller中的getStaffIdsWithWorkReminder
  • @FeignClient注解指定了调用的目标服务
  • getStaffIdsWithWorkReminder()方法用于获取需要上班提醒的员工ID列表
  • 定时任务通过这个接口获取需要提醒的员工信息
  1. RedisConfig.java
    这是Redis配置类,为定时任务模块配置Redis操作相关的Bean:
  • 配置了RedisTemplate,用于Redis数据操作
  • 配置了序列化方式为StringRedisSerializer
  • 创建了RedisCacheBean,提供更便捷的Redis操作方法

定时任务配置

在系统监控--》定时任务中新增

component里面是实例,后面是具体的定时方法

前端 UniApp 接收后端推送信息的方法

  1. 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>  
  1. 轮询方式(已实现)
    我使用的方法通过定时调用接口检查是否有新提醒。这种方式简单可靠,但不够实时。

  2. 消息推送方式

// 在 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>
  
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号