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调用测试

uniapp:全局消息是推送,实现app在线更新,WebSocket,apk上传_第1张图片

@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;

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public 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, "连接成功");
        }
    }

    /**
     * 连接关闭时处理
     */
    @OnClose
    public void onClose(Session session)
    {
        webSockets.remove(this);
        LOGGER.info("\n 关闭连接 - {}", session);
        // 移除用户
        WebSocketUsers.remove(session.getId());
        // 获取到信号量则需释放
        SemaphoreUtils.release(socketSemaphore);
    }

    /**
     * 抛出异常时处理
     */
    @OnError
    public 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);
    }

    /**
     * 服务器接收到客户端消息时调用的方法
     */
    @OnMessage
    public 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)
    @PostMapping
    public 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 {
    @Autowired
    private WebSocketServer webSocketServer;
    @Autowired
    private ServerConfig serverConfig;
    @Autowired
    private 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.apk
            SysAppVersion 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的Header
            response.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上传_第2张图片

你可能感兴趣的:(uni-app,websocket,网络协议)