uniapp:全局消息是推送,实现app在线更新,WebSocket,apk上传
全局消息是推送,实现app在线更新,WebSocket
- 1.在main.js中定义全局的WebSocket
- 2.java后端建立和发送WebSocket
- 3.通知所有用户更新
背景:
开发人员开发后app后打包成.apk文件,上传后通知厂区在线用户更新app。
那么没在线的怎么办?因为我们在上一篇博客中写了,在app打开的时候回去校验是否需要更新了,所以已经完成了闭环。
即时通讯首先想到的就是WebSocket
- 1.我们定义全局的WebSocket
- 2.在全局监听,当监听到指定消息的时候弹窗更新,下载逻辑也就是下载最新的apk,在上一篇博客写了,点击下方链接。
uniapp:实现手机端APP登录强制更新,从本地服务器下载新的apk更新,并使用WebSocket,实时强制在线用户更新
但是有一个问题,就是手持机少还可以,要是多的话,几百台连接还不是不太合适?,所以用登录的时候检测更新是最好的
使用post调用测试

@GetMapping("/sendAllUser")public void sendAllUser(){webSocketServer.sendAllMessage("updateApp");}
1.在main.js中定义全局的WebSocket
import App from './App'
import Vue from 'vue'
import uView from 'uni_modules/uview-ui'
import debounce from '@/utils/debounce'Vue.use(uView)
Vue.config.productionTip = false;
App.mpType = 'app'
Vue.prototype.$debounce = debounce;
import store from '@/store';const app = new Vue({store,...App
})// 创建全局的事件总线
Vue.prototype.$eventBus = new Vue();// 创建全局WebSocket连接
Vue.prototype.$socket = uni.connectSocket({url: 'ws://127.0.0.1:8080/webSocket',complete: (res) => {console.log('WebSocket connection completed:', res);},
});// 全局监听WebSocket消息
Vue.prototype.$socket.onMessage((res) => {console.log('Received WebSocket message:', res);if(res.data == 'updateApp'){uni.navigateTo({url: '/pages/index/upgrade'})}// 将消息传递给事件总线,以便在整个应用中进行处理// Vue.prototype.$eventBus.$emit('socketMessage', res);
});// 引入请求封装,将app参数传递到配置中
require('@/config/http.interceptor.js')(app)app.$mount()
当接受到updateApp的消息的时候,打开更新弹窗,详情见链接:
2.java后端建立和发送WebSocket
下面java后端建立和发送WebSocket 消息的地方,若依自带websocket连接代码,咕咕在其基础上做了一些修改,添加了部分方法
package com.qygx.framework.websocket;import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Semaphore;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.stereotype.Component;/*** websocket 消息处理* * @author ruoyi*/
//@ConditionalOnClass(value = WebSocketConfig.class)
@ServerEndpoint("/webSocket")
@Component
public class WebSocketServer
{/*** WebSocketServer 日志控制器*/private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);/*** 默认最多允许同时在线人数100*/public static int socketMaxOnlineCount = 100;private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount);private static CopyOnWriteArraySet<WebSocketServer> webSockets =new CopyOnWriteArraySet<>();//与某个客户端的连接会话,需要通过它来给客户端发送数据private Session session;/*** 用户ID*/private String userId;/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session) throws Exception{this.session = session;this.userId = userId;webSockets.add(this);boolean semaphoreFlag = false;// 尝试获取信号量semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore);if (!semaphoreFlag){// 未获取到信号量LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount);WebSocketUsers.sendMessageToUserByText(session, "当前在线人数超过限制数:" + socketMaxOnlineCount);session.close();}else{// 添加用户WebSocketUsers.put(session.getId(), session);LOGGER.info("\n 建立连接 - {}", session);LOGGER.info("\n 当前人数 - {}", WebSocketUsers.getUsers().size());WebSocketUsers.sendMessageToUserByText(session, "连接成功");}}/*** 连接关闭时处理*/@OnClosepublic void onClose(Session session){webSockets.remove(this);LOGGER.info("\n 关闭连接 - {}", session);// 移除用户WebSocketUsers.remove(session.getId());// 获取到信号量则需释放SemaphoreUtils.release(socketSemaphore);}/*** 抛出异常时处理*/@OnErrorpublic void onError(Session session, Throwable exception) throws Exception{if (session.isOpen()){// 关闭连接session.close();}String sessionId = session.getId();LOGGER.info("\n 连接异常 - {}", sessionId);LOGGER.info("\n 异常信息 - {}", exception);// 移出用户WebSocketUsers.remove(sessionId);// 获取到信号量则需释放SemaphoreUtils.release(socketSemaphore);}/*** 服务器接收到客户端消息时调用的方法*/@OnMessagepublic void onMessage(String message, Session session){String msg = message.replace("你", "我").replace("吗", "");WebSocketUsers.sendMessageToUserByText(session, msg);}// 此为广播消息public void sendAllMessage(String message) {for(WebSocketServer webSocket : webSockets) {try {if(webSocket.session.isOpen()) {webSocket.session.getAsyncRemote().sendText(message);}} catch (Exception e) {e.printStackTrace();}}}
}
3.通知所有用户更新
咕咕这里是在用户上传完,提交表单成功后发送消息的,大家可自定义。
/*** 新增app版本*/@PreAuthorize("@ss.hasPermi('system:appversion:add')")@Log(title = "app版本", businessType = BusinessType.INSERT)@PostMappingpublic AjaxResult add(@RequestBody SysAppVersion sysAppVersion){SysAppVersion newestVersion = sysAppVersionService.getNewestVersion();sysAppVersionService.insertSysAppVersion(sysAppVersion);if(newestVersion != null && newestVersion.getAppVersionCode() < sysAppVersion.getAppVersionCode()){webSocketServer.sendAllMessage("updateApp");}return toAjax(sysAppVersionService.insertSysAppVersion(sysAppVersion));}
下面的是,文件上传的代码,大家可以随意参考,基于若依的一些修改,本来若依也是开源的,所以这个也就不做删减了。
package com.qygx.mes.app.controller;import com.qygx.common.config.RuoYiConfig;
import com.qygx.common.core.domain.AjaxResult;
import com.qygx.common.utils.DateUtils;
import com.qygx.common.utils.StringUtils;
import com.qygx.common.utils.file.FileUploadUtils;
import com.qygx.common.utils.file.FileUtils;
import com.qygx.common.utils.file.MimeTypeUtils;
import com.qygx.common.utils.uuid.Seq;
import com.qygx.framework.config.ServerConfig;
import com.qygx.framework.websocket.WebSocketServer;
import com.qygx.mes.csm.domain.SysAppVersion;
import com.qygx.mes.csm.service.ISysAppVersionService;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Objects;@RestController
@RequestMapping("/app")
public class AppController {@Autowiredprivate WebSocketServer webSocketServer;@Autowiredprivate ServerConfig serverConfig;@Autowiredprivate ISysAppVersionService sysAppVersionService;@GetMapping("/download")public void download(String path, HttpServletResponse response) {try {//拿到最新的版本//http://127.0.0.1:8080/profile/upload/2023/12/28/__UNI__E832695__20231227183906_20231228110314A001.apkSysAppVersion newestVersion = sysAppVersionService.getNewestVersion();File file = new File("d://app//"+newestVersion.getFileName());String filename = file.getName();String ext = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();FileInputStream fileInputStream = new FileInputStream(file);InputStream fis = new BufferedInputStream(fileInputStream);byte[] buffer = new byte[fis.available()];fis.read(buffer);fis.close();response.reset();// 设置response的Headerresponse.setCharacterEncoding("UTF-8");//Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存//attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3"// filename表示文件的默认名称,因为网络传输只支持URL编码的相关支付,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));// 告知浏览器文件的大小response.addHeader("Content-Length", "" + file.length());OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());response.setContentType("application/octet-stream");outputStream.write(buffer);outputStream.flush();} catch (IOException ex) {ex.printStackTrace();}}/*** 通用上传请求(单个)*/@PostMapping("/upload")public AjaxResult uploadFile(MultipartFile file) throws Exception{try{String newName = extractFilename(file);String absPath = "D:\\app\\" + newName;file.transferTo(Paths.get(absPath));// 上传文件路径// 上传并返回新文件名称AjaxResult ajax = AjaxResult.success();ajax.put("url", absPath);ajax.put("fileName", "fileName");ajax.put("newFileName", newName);ajax.put("originalFilename", file.getOriginalFilename());return ajax;}catch (Exception e){return AjaxResult.error(e.getMessage());}}private String extractFilename(MultipartFile file){return StringUtils.format("{}_{}.{}",FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));}/*** 获取文件名的后缀** @param file 表单文件* @return 后缀名*/public static final String getExtension(MultipartFile file){String extension = FilenameUtils.getExtension(file.getOriginalFilename());if (StringUtils.isEmpty(extension)){extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));}return extension;}@GetMapping("/getNewestVersion")public AjaxResult getNewestVersion(){SysAppVersion newestVersion = sysAppVersionService.getNewestVersion();HashMap<String, Object> map = new HashMap<>();if(newestVersion != null ){map.put("newVersionName", newestVersion.getAppVersion());map.put("newVersionCode", newestVersion.getAppVersionCode());}else {map.put("newVersionName", "v");map.put("newVersionCode", 0);}return AjaxResult.success(map);}@GetMapping("/sendAllUser")public void sendAllUser(){webSocketServer.sendAllMessage("updateApp");}}
先上传文件嘛,然后将路径什么的,返回给表单提交,简单的。

相关文章:
uniapp:全局消息是推送,实现app在线更新,WebSocket,apk上传
全局消息是推送,实现app在线更新,WebSocket 1.在main.js中定义全局的WebSocket2.java后端建立和发送WebSocket3.通知所有用户更新 背景: 开发人员开发后app后打包成.apk文件,上传后通知厂区在线用户更新app。 那么没在线的怎么办&…...
ARM1.2作业
实现数码管不同位显示不同的数字 spi.h #ifndef __SPI_H__ #define __SPI_H__ #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h"//MOSI对应的引脚输入高低电平的信号PE14 #define MOSI_OUTPUT_H() do{GPIOE->ODR | (0x1 << 14);}whi…...
【算法专题】递归算法
递归 递归1. 汉诺塔问题2. 合并两个有序链表3. 反转链表4. 两两交换链表中的节点5. Pow(x, n) --- 快速幂 递归 在解决⼀个规模为 n 的问题时,如果满足以下条件,我们可以使用递归来解决: 问题可以被划分为规模更小的子问题,并且…...
不停止业务的情况下优化 Elasticsearch Reindex
在使用 Elasticsearch 时,我们总有需要修改索引映射的时候,这时我们只能进行 _reindex。事实上,这是一个相当昂贵的操作,因为根据数据量和分片数量,完整复制一个索引可能需要几个小时。 花费的时间不是大问题,但更严重的是,它会影响生产环境的性能甚至功能。 相信大家…...
PB 按Excel动态创建对应字段
/* > Function: w_cwjk_xhyy.wf_dw_init >-------------------------------------------------------------------- > 描述: 按excel表格列名,创建对应字段,用于部分接口对应字段导出文件 >-------------------------------------------------------------------- …...
数据结构——红黑树 and B-树
红黑树 根据平衡条件第4、5两点 最短路径,都是黑色 最长路径,红黑相间 最长是最短的两倍 B-树...
Android中线程间的通信-Handler
Handler机制在Android中主要用于线程间的通信,特别是处理从子线程向主线程(UI线程)传递消息和更新界面。 Handler中的四个关键对象及其作用: Message: Message 是在线程间传递的数据载体,它包含了需要处理…...
Spring Boot Admin健康检查引起的Spring Boot服务假死
问题现象 最近在spring boot项目中引入了 spring-boot-starter-actuator 后,测试环境开始出现服务假死的现象, 且这个问题十分怪异,只在多个微服务中的简称A的这个服务中出现,其他服务都没有出现这个问题, 之所以说…...
java企业人事信息管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目
一、源码特点 java Web企业人事信息管理系统是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境 为TOMCAT7.0,Myeclipse8.5开发,数据库为M…...
如何通过 useMemo 和 useCallback 提升你的 React 应用性能
背景 在 React 中,useMemo 和 useCallback 这两个 hook 是我们优化应用性能的有力工具。它们会返回 memoized 版本的值或函数,只在依赖项发生变化时才进行重新计算或定义。 Hook 介绍 useMemo useMemo 的作用是返回一个 memoized 值,它接…...
ArkTS - @Prop、@Link
一、作用 Prop 装饰器 和Link装饰器都是父组件向子组件传递参数,子组件接收父组件参数的时候用的,变量前边需要加上Prop或者Link装饰器即可。(跟前端vue中父组件向子组件传递参数类似) // 子组件 Component struct SonCom {Prop…...
Python中matplotlib库的使用1
1 matplotlib库简介 matplotlib是一个数学绘图库,可以将数据通过图形的方式显示出来,也就是数据可视化。 2 matplotlib库的安装 2.1 打开cmd窗口 点击键盘的“Win”“R”键,在弹出的“运行”对话框的“打开”栏中输入“cmd”,…...
位乘积计数-蓝桥
题目链接:1.位乘积计数 - 蓝桥云课 (lanqiao.cn) 解题思路:10的5次数量级暴力居然过了,看来测试样例很水,直接1遍历到n,再用一个循环判断每位数相乘乘机是否等于m即可。 下面是c代码: #include <iost…...
HCIA-Datacom题库(自己整理分类的)——OSPF协议判断
1.路由表中某条路由信息的Proto为OSPF则此路由的优先级一定为10。√ 2.如果网络管理员没有配置骨干区域,则路由器会自动创建骨干区域? 路由表中某条路由信息的Proto为OSPF,则此路由的优先级一定为10。 当两台OSPF路由器形成2-WAY邻居关系时࿰…...
【FPGA/verilog -入门学习16】fpga状态机实现
需求: 用两段式状态机设计序列码检测机。这个序列码检测机用于检索连续输入的 1bit 数据 (每个时钟周期输入 1bit),当检测到一串“101100”的输入数据时,产生一个时钟周期的 高脉冲指示信号 状态图 //实现状态机切…...
记chrome的hackbar无法post php://input的问题
尽管hackbar支持post请求体,但是当请求体里面没有等于号的时候,无法post出去,这样如果需要使用php://input绕过waf的时候就没法做。 在开发人员工具的网络里面可以看到不使用等于号的情况下没有荷载。 之后在这里看到了解决方法,…...
相机解析驱动小记
用过了几款相机,对使用相机也有了一点心得,在此记录。 当你得到一款相机,你需要做的: 第一件事:在datasheet中阅读配置单,知道怎么配置、配置完输出来是什么。 配置输出尺寸;传输模式…...
EasyExcel判断导入时是否符合给定模板
问题描述 在做系统的导入导出模块时需要在导入时判断用户导入的表格是否符合给定的模板,该系统导入导出使用的是EasyExcel,因此在实现该功能时是基于EasyExcel的 解决方案 创建Spring Boot项目,并添加如下依赖 <dependency><group…...
BDD - Python Behave Retry 机制
BDD - Python Behave Retry 机制 引言Behave RetryBehave Retry 应用feature 文件创建 step 文件Retry运行 Behave 并生成 rerun 文件重新运行失败的场景 引言 在日常运行测试用例,有时因为环境不稳定造成一些测试用例跑失败了,如果能将这些失败的测试用…...
链 表
3_1 删除链表中的节点 Answer-将被删节点下一个val复制到待删除节点,然后将待删节点直接连接到下下一个节点即可。 学到: /** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) …...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...
【iOS】 Block再学习
iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...
前端工具库lodash与lodash-es区别详解
lodash 和 lodash-es 是同一工具库的两个不同版本,核心功能完全一致,主要区别在于模块化格式和优化方式,适合不同的开发环境。以下是详细对比: 1. 模块化格式 lodash 使用 CommonJS 模块格式(require/module.exports&a…...
2025.6.9总结(利与弊)
凡事都有两面性。在大厂上班也不例外。今天找开发定位问题,从一个接口人不断溯源到另一个 接口人。有时候,不知道是谁的责任填。将工作内容分的很细,每个人负责其中的一小块。我清楚的意识到,自己就是个可以随时替换的螺丝钉&…...
