springboot里使用websocket实现文件上传并且显示进度

首先,我们清晰一下思路

1)先实现文件上传,我们应该清楚,文件上传和进度走的是两条路线,即异步;

2)再使用文件上传解析器去获取文件进度信息,这个信息是保存在一个session域里的,会被实时刷新;

3)websocket定时遍历,实现点对点发送上传进度信息;

很简单,就这三步

接下来开始实现

所需的maven依赖

     
        
            commons-fileupload
            commons-fileupload
            1.3.3
        
     
            commons-io
            commons-io
            2.5
        
     
        
            org.springframework.boot
            spring-boot-starter-websocket
        

实现第一步和第二步

后台主要有下面几个类

1.FileuploadClass 文件上传配置信息,继承了CommonsMultipartResolver

import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * @ClassName FileUpload
 * @Description TODO
 * @Author fzj
 * @Date 2020-3-26 13:21
 * @Version 1.0.0
 **/
public class FileUploadClass extends CommonsMultipartResolver {
    private UploadProgressListener progressListener = new UploadProgressListener();

    public void setFileUploadProgressListener(
            UploadProgressListener progressListener) {
        this.progressListener = progressListener;
    }
    public MultipartParsingResult parseRequest(HttpServletRequest request)
            throws MultipartException {
        String encoding = determineEncoding(request);
        FileUpload fileUpload = prepareFileUpload(encoding);
        progressListener.setSession(request.getSession());
        fileUpload.setProgressListener(progressListener);
        try {
            @SuppressWarnings("unchecked")
            List fileItems = ((ServletFileUpload) fileUpload)
                    .parseRequest(request);
            return parseFileItems(fileItems, encoding);
        } catch (FileUploadBase.SizeLimitExceededException ex) {
            throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(),
                    ex);
        } catch (FileUploadException ex) {
            throw new MultipartException(
                    "Could not parse multipart servlet request", ex);
        }
    }
}

2.FileUploadConfig 配置类 用于springboot项目加载时注入文件上传配置文件的bean


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName FileUPDConfig
 * @Description TODO
 * @Author fzj
 * @Date 2020-4-1 8:19
 * @Version 1.0.0
 **/
@Configuration
public class FileUploadConfig {
    @Bean
    public FileUploadClass fileUploadConfig(){
        return new FileUploadClass();
    }
}

3.UploadProgressListener 文件上传状态监听器 实现了ProgressListener

import com.fzj.model.ProgressEntity;
import org.apache.commons.fileupload.ProgressListener;

import javax.servlet.http.HttpSession;

/**
 * @ClassName UploadProgressListener
 * @Description TODO
 * @Author fzj
 * @Date 2020-4-1 8:15
 * @Version 1.0.0
 **/
public class UploadProgressListener implements ProgressListener {
    private HttpSession session;

    public void setSession(HttpSession session) {
        this.session = session;
        ProgressEntity status = new ProgressEntity();// 保存上传状态
        session.setAttribute("status", status);
    }

    @Override
    public void update(long bytesRead, long contentLength, int items) {
        ProgressEntity status = (ProgressEntity) session.getAttribute("status");
        status.setBytesRead(bytesRead);// 已读取数据长度
        status.setContentLength(contentLength);// 文件总长度
        status.setItems(items);// 正在保存第几个文件

    }
}

4.ProgressEntity 进度实体类

/**
 * @ClassName ProgressEntity
 * @Description TODO
 * @Author fzj
 * @Date 2020-3-26 12:56
 * @Version 1.0.0
 **/
public class ProgressEntity {
    private long bytesRead;
    private long contentLength;
    private long items;
    private long startTime = System.currentTimeMillis(); // 开始上传时间,用于计算上传速率

    public ProgressEntity() {
    }

    public long getBytesRead() {
        return bytesRead;
    }

    public void setBytesRead(long bytesRead) {
        this.bytesRead = bytesRead;
    }

    public long getContentLength() {
        return contentLength;
    }

    public void setContentLength(long contentLength) {
        this.contentLength = contentLength;
    }

    public long getItems() {
        return items;
    }

    public void setItems(long items) {
        this.items = items;
    }

    public long getStartTime() {
        return startTime;
    }

    public void setStartTime(long startTime) {
        this.startTime = startTime;
    }
}

5.FileUploadCommonTool 文件上传通用操作类

import com.fzj.model.ProgressEntity;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName FileUploadCommonTool
 * @Description TODO
 * @Author fzj
 * @Date 2020-4-9 10:05
 * @Version 1.0.0
 **/
public class FileUploadCommonTool {
    public static Map upload(MultipartFile Fdata, String Sid, HttpServletRequest request) {
        String infilename = Fdata.getOriginalFilename();
        String endstring = infilename.substring(infilename.lastIndexOf("."));
        //这里写自己的文件名和文件夹即可
        String fromstring = SystemDateFormat.SdfForTimeString.format(new Date());
        String path = "C:\\Users\\Administrator\\Desktop\\_11_4_1临时文件夹\\20200326\\" + fromstring + endstring;
        Map map = new HashMap<>(1);
        InputStream fis = null;
        FileOutputStream fos = null;
        try {
            File fileo = new File(path);
            if (!fileo.exists()) {
                fileo.createNewFile();
            }
            fos = new FileOutputStream(fileo);
            fis = Fdata.getInputStream();
            byte[] bytes = new byte[1024];
            int aa = 0;
            while (true) {
                aa = fis.read(bytes, 0, bytes.length);
                if (aa == -1) {
                    break;
                }
                fos.write(bytes, 0, aa);
            }
        } catch (Exception e) {
            map.put("issuccess", false);

        } finally {
            try {
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                fis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return map;
    }

    public static Map getUploadInfo(HttpServletRequest request) {
        Map result = new HashMap<>();
        ProgressEntity status = (ProgressEntity) request.getSession(true)
                .getAttribute("status");// 从session中读取上传信息
        if (status == null) {
            result.put("error", "没发现上传文件!");
            return result;
        }
        long startTime = status.getStartTime(); // 上传开始时间
        long currentTime = System.currentTimeMillis(); // 现在时间
        long time = (currentTime - startTime) / 1000 + 1;// 已经传顺的时间 单位:s
        double velocity = status.getBytesRead() / time; // 传输速度:byte/s
        double totalTime = status.getContentLength() / velocity; // 估计总时间
        double timeLeft = totalTime - time; // 估计剩余时间
        int percent = (int) (100 * (double) status.getBytesRead() / (double) status
                .getContentLength()); // 百分比
        double length = status.getBytesRead() / 1024 / 1024; // 已完成数
        double totalLength = status.getContentLength() / 1024 / 1024; // 总长度 M
        result.put("startTime", startTime);
        result.put("currentTime", currentTime);
        result.put("time", time);
        result.put("velocity", velocity);
        result.put("totalTime", totalTime);
        result.put("timeLeft", timeLeft);
        result.put("percent", percent);
        result.put("length", length);
        result.put("totalLength", totalLength);
        if (length >= totalLength) {
            result.put("isfinish", 1);
        }
        return result;
    }
}

到这里,文件上传后端的所有工作已经做完了,现在你可以通过Controller里调用FileUploadCommonTool里的upload就可以实现页面上传文件了,同时可以通过页面轮训FileUploadCommonTool里的getUploadInfo获得文件上传的进度信息,当然了,所用的前端框架各不相同,我这里以miniui做个页面示例



    <%@ include file="head.jsp" %>
    




这样是能实现文件上传和进度显示,但是海量的轮询会给服务器增加很大压力,所以,就有了第三步websocket

实现第三步

第三步主要涉及到了以下几个类

1.WebsocketConfiguration 配置类,注入了org.springframework.web.socket.server.standard下的ServerEndpointExporter

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @ClassName WebsocketConfiguration
 * @Description TODO
 * @Author fzj
 * @Date 2020-4-8 14:32
 * @Version 1.0.0
 **/
@Configuration
public class WebsocketConfiguration {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

2.WebsocketEndPoint 节点类,所有的打开,关闭,错误,发送信息的方法都在这个类里,这样的东西网上的前辈们都写的各有特色,主要就是那几个方法,其他的根据自己的业务需求实现就可以了

/**
 * @ClassName WebsocketEndPoint
 * @Description TODO
 * @Author fzj
 * @Date 2020-4-8 13:20
 * @Version 1.0.0
 **/

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

@ServerEndpoint("/websocket/{sid}")
@Component
public class WebsocketEndPoint {
    static Log log = LogFactory.getLog(WebsocketEndPoint.class);
    //静态变量,用来记录当前在线连接数
    private static AtomicInteger onlineCount = new AtomicInteger(0);
    //用来存放每个客户端对应的WebsocketEndPoint对象
    private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    //接收sid
    private String sid = "";

    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        log.info("有新窗口开始监听:" + sid + ",当前在线人数为" + getOnlineCount());
        this.sid = sid;
        try {
            sendMessage("连接成功");
        } catch (IOException e) {
            log.error("websocket IO异常");
        }
    }

    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口" + sid + "的信息:" + message);
        //群发消息
        for (WebsocketEndPoint item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    public static synchronized Integer getOnlineCount() {
        return Integer.valueOf(onlineCount.toString());
    }

    public static synchronized void addOnlineCount() {
        WebsocketEndPoint.onlineCount.set(getOnlineCount() + 1);
    }

    public static synchronized void subOnlineCount() {
        WebsocketEndPoint.onlineCount.set(getOnlineCount() - 1);
    }

    public static CopyOnWriteArraySet getWebSocketSet() {
        return webSocketSet;
    }
    //提供外部可调用的方法获取sid,用于查找某个用户的文件上传信息
    public String getSid() {
        return sid;
    }
}

3.TimerConfig 定时器

package com.fzj.config;

import com.fzj.common.websocket.WebsocketEndPoint;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

@Configuration
@EnableScheduling
public class TimerConfig {
    //用于存储用户的request, 这里的concurrentHashMap和WebsocketEndPoint类里的Sid是一致的,这个值可以在FileUploadCommonTool里的上传方法里去put
    public static Map concurrentHashMap = new ConcurrentHashMap(1);
    public static Integer cou = 0;

    @Scheduled(cron = "0/1 * * * * *")
    public void sendmsg() {
        CopyOnWriteArraySet copyOnWriteArraySet = WebsocketEndPoint.getWebSocketSet();
        copyOnWriteArraySet.forEach(c -> {
            String s = "0";
            //取得的c表示该用户会话的类
            if (concurrentHashMap.containsKey(c.getSid())) {
                //此时当前执行文件上传操作的用户request集合里存在该用户
                //调用FileUploadCommonTool里的获取上传信息的方法
                //给s赋值为取得的进度
            if(//判断是否上传完成) {
            //上传完成删除concurrentHashMap里的该用户信息
                    concurrentHashMap.remove(c.getSid());
                }
            }
            try {
                //发送进度给客户端
                c.sendMessage(s);
            } catch (Exception e) {

            }
        });
    }
}

现在就不用页面上轮询去访问服务器去获取进度了,而是服务器定时发送给你进度,示例前端代码,将第一个页面定时轮询改为websocket

   var WebsocketUrl = "ws://ip地址:端口/上下文路径/websocket/445";
    var webs = new WebSocket(WebsocketUrl );
    //打开连接执行
    webs.onopen = function (ev) {
        alert(1);
    }
    //断开连接执行
    webs.onclose = function (ev) {
        alert(0);
    }
    //将接收到的进度值设置到进度条
    webs.onmessage = function (me) {
        bar2.setValue(me.data);
    }
    //发生错误执行
    webs.onerror = function (ev) {
        console.log(ev);
    }

好了,到这里就结束了

websocket是一种通信协议,只需要进行一次tcp的握手,然后可以实现信号的瞬时双向传输,实时性强,能耗低,是实现网页聊天功能的有效手段之一。

你可能感兴趣的:(Java学习,websocket)