springboot集成websocket實現大文件分塊上傳


遇到一個上傳文件的問題,老大說使用http太慢了,因為http包含大量的請求頭,剛好項目本身又集成了websocket,想着就用websocket來做文件上傳。

相關技術

  • springboot
  • websocket
  • jdk1.8

創建springboot項目並集成websocket

先是創建一個spring boot項目

我們勾選了三個依賴
分別是 Lombok,web,websocket

這是當前完整的依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ccsert</groupId>
    <artifactId>websocketupload</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>websocketupload</name>
    <description>websocket Upload project</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

websocketupload包下創建config包,然后建一個配置類WebsocketConfig

然后實現ServletContextInitializer接口
代碼如下

package com.ccsert.websocketupload.config;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import org.springframework.web.util.WebAppRootListener;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

/**
 * ClassName: WebsocketConfig <br/>
 * Description: 開啟websocket支持 <br/>
 * date: 2020/2/4 10:58<br/>
 *
 * @author ccsert<br />
 * @since JDK 1.8
 */
@Configuration
@ComponentScan
@EnableAutoConfiguration
public class WebsocketConfig implements ServletContextInitializer {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    /**
     * 配置websocket文件接受的文件最大容量
     * @param servletContext    context域對象
     * @throws ServletException 拋出異常
     */
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.addListener(WebAppRootListener.class);
        servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize","51200000");
        servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize","51200000");
    }
}

websocketupload包下創建一個web包然后建立一個WebSocketServer

我們需要實現它的三個方法:OnOpen,OnClose,OnMessage

他們都會自動調用,類似於事件觸發,含義分別是,連接建立成功時調用的方法,連接關閉時調用的方法,最后一個是接收客戶端發來的消息

其中OnMessage我們后面會使用多種實現

我們先來玩一個例子,講websocket自然是喜聞樂見聊天室,貼一下WebSocketServer的代碼

package com.ccsert.websocketupload.web;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * ClassName: webSocketServer <br/>
 * Description: websocket服務處理類 <br/>
 * date: 2020/2/4 11:05<br/>
 *
 * @author ccsert<br />
 * @since JDK 1.8
 */
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {

    private static final Logger LOG = LoggerFactory.getLogger(WebSocketServer.class);
    /**
     * 靜態變量,用來記錄當前在線連接數。應該把它設計成線程安全的。
     */
    private static int onlineCount = 0;

    /**
     * concurrent包的線程安全Set,用來存放每個客戶端對應的MyWebSocket對象。
     */
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();

    /**
     * 與某個客戶端的連接會話,需要通過它來給客戶端發送數據
     */
    private Session session;

    /**
     * 連接建立成功時調用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        this.session = session;
        //加入set中
        webSocketSet.add(this);
        //在線人數加1
        addOnlineCount();
        LOG.info(sid + "連接成功" + "----當前在線人數為:" + onlineCount);
    }

    /**
     * 連接關閉時調用的方法
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        //在線人數減1
        subOnlineCount();
        //從set中刪除
        webSocketSet.remove(this);
        LOG.info(sid + "已關閉連接" + "----剩余在線人數為:" + onlineCount);
    }

    /**
     * 接收客戶端發送的消息時調用的方法
     *
     * @param message 接收的字符串消息
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        LOG.info(sid + "發送消息為:" + message);
        sendInfo(message, sid);
    }

    /**
     * 服務器主動提推送消息
     *
     * @param message 消息內容
     * @throws IOException io異常拋出
     */
    public void sendMessage(String message) throws IOException {

        this.session.getBasicRemote().sendText(message);
    }

    /**
     * 群發消息功能
     *
     * @param message 消息內容
     * @param sid     房間號
     */
    public static void sendInfo(String message, @PathParam("sid") String sid) {
        LOG.info("推送消息到窗口" + sid + ",推送內容:" + message);
        for (WebSocketServer item : webSocketSet) {
            try {
                //這里可以設定只推送給這個sid的,為null則全部推送
                item.sendMessage(message);
            } catch (IOException e) {
                LOG.error("消息發送失敗" + e.getMessage(), e);
                return;
            }
        }
    }

    /**
     * 原子性的++操作
     */
    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    /**
     * 原子性的--操作
     */
    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}

這里的代碼挺簡單沒什么好講的

然后在建立一個html
在static目錄下建立一個websocketDemo.html
代碼如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>chat room websocket</title>
    <link rel="stylesheet" href="bootstrap.css">
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</head>
<body class="container" style="width: 60%">
<div class="form-group"></br>
    <h5>聊天室</h5>
    <textarea id="message_content" class="form-control" readonly="readonly" cols="50"
              rows="10"></textarea>
</div>
<div class="form-group">
    <label for="in_user_name">⽤戶姓名 &nbsp;</label>
    <input id="in_user_name" value="" class="form-control"/></br>
    <label for="in_user_name">房間名 &nbsp;</label>
    <input id="in_room_id" value="" class="form-control">
    <button id="user_join" onclick="verificationValue()" class="btn btn-success">加入聊天室</button>
    <button id="user_exit" onclick="outRoom()" class="btn btn-warning">離開聊天室</button>
</div>
<div class="form-group">
    <label for="in_room_msg">群發消息 &nbsp;</label>
    <input id="in_room_msg" value="" class="form-control"/></br>
    <button id="user_send_all" onclick="sendInfo()" class="btn btn-info">發送消息</button>
</div>
<script>
    var socket;//websocket連接
    var webSocketUrl = 'ws://127.0.0.1:8033/websocket/';//websocketi連接地址
    var roomId = "";//房間號
    var userName = "";//用戶名
    //創建websocket連接
    function createWebSocketConnect(roomId) {
        if (!socket) {//避免重復連接
            console.log(roomId);
            socket = new WebSocket(webSocketUrl + roomId);
            socket.onopen = function () {
                console.log("websocket已連接");
                socket.send(userName + "已經成功加入房間");
            };
            socket.onmessage = function (e) {
                //服務端發送的消息
                $("#message_content").append(e.data + '\n');
            };
            socket.onclose = function () {
                socket.send(userName + "已經退出房間");
            }
        }
    }

    //驗證用戶名和房間號是否填寫
    function verificationValue() {
        roomId = $("#in_room_id").val();
        userName = $("#in_user_name").val();
        if (roomId === "" || userName === "") {
            alert("請填寫用戶名並填寫要加入的房間號");
            return;
        }
        createWebSocketConnect(roomId, userName);
    }

    //群發消息
    function sendInfo() {
        let msg = $('#in_room_msg').val();
        if (socket) {
            socket.send(userName + ":" + msg)
        }
    }

    //離開房間
    function outRoom() {
        if (socket) {
            socket.send(userName + "已退出");
            socket.close();
            $("#message_content").append(userName + "已退出");
        }
    }

</script>
</body>
</html>

這個也挺簡單,這里引用了bootstrap.css樣式和jq

然后我們就可以通過這樣的一個小demo測試下聊天室的功能了,文末附上代碼地址

使用websocket實現文件上傳功能

我們仿造剛才的WebSocketServer在寫一個websocket類

還是在web包下建立一個類,類名為WebSocketUploadServer

可以將原來WebSocketServer的代碼復制過來,然后稍微改造一下,其實我們實現文件上傳也可以直接在原來WebSocketServer的代碼里直接實現,但是現在為了讓代碼更清晰一些,我們先將就一下

原本聊天室的情況下,一個房間里是可以有多個客戶端連接的,但是文件上傳我們是不允許的,假如有多個人在同一個房間,那么消息就會傳到每個客戶端,因為我們要做分塊上傳,所以這里控制每個房間只能有一個人

我們把websocketDemo.html也復制一份,改名就叫uploadFileDemo.html

我們原先的房間號是手動輸入的,現在,我們保證每次都是不同的房間號所以,這次房間號就用隨機數

當然方法有很多種,我只是提供一種簡單的實現方式,不同的業務場景當然也需要不同的實現方式。不用太過死板

后台的核心代碼主要是接收字節流和json消息,我們將字符串消息格式化成了json消息

下面是接收字符串的OnMessage

	/**
     * 接收客戶端發送的消息時調用的方法
     *
     * @param message 接收的字符串消息。該消息應當為json字符串
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        //前端傳過來的消息都是一個json
        JSONObject jsonObject = JSON.parseObject(message);
        //消息類型
        String type = jsonObject.getString("type");
        //消息內容
        String data = jsonObject.getString("data");
        //判斷類型是否為文件名
        if ("fileName".equals(type)) {
            LOG.info("傳輸文件為:" + data);
            //此處的 “.”需要進行轉義
            /*String[] split = data.split("\\.");*/
            try {
                Map<String, Object> map = saveFileI.docPath(data);
                docUrl = (HashMap) map;
                this.sendMessage("ok");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        else if ("fileCount".equals(type)){
            LOG.info("傳輸第"+data+"份");
        }
        //判斷是否結束
        else if (endupload.equals(type)) {
            LOG.info("===============>傳輸成功");
            //返回一個文件下載地址
            String path = (String) docUrl.get("nginxPath");
            //返回客戶端文件地址
            try {
                this.sendMessage(path);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

然后是接收字節流的

    /**
     * 該方法用於接收字節流數組
     *
     * @param message 文件字節流數組
     * @param session 會話
     */
    @OnMessage
    public void onMessage(byte[] message, Session session) {
        //群發消息
        try {
            //將流寫入文件
            saveFileI.saveFileFromBytes(message,docUrl);
            //文件寫入成功,返回一個ok
            this.sendMessage("ok");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

單獨看肯定是看不懂的
業務邏輯我們放在了service層進行處理

在websocketupload包下建立service包
然后建立SaveFileI接口,接口里有兩個方法,一個是創建文件路徑的,一個是將流數據寫入文件的

package com.ccsert.websocketupload.service;

import java.util.Map;

/**
 * ClassName: saveFileI <br/>
 * Description: 保存文件接口 <br/>
 * date: 2020/2/4 17:39<br/>
 *
 * @author ccsert<br />
 * @since JDK 1.8
 */
public interface SaveFileI {
    /**
     * 生成文件路徑
     * @param fileName  接收文件名
     * @return  返回文件路徑
     */
    Map<String,Object> docPath(String fileName);

    /**
     * 將字節流寫入文件
     * @param b 字節流數組
     * @param map  文件路徑
     * @return  返回是否成功
     */
    boolean saveFileFromBytes(byte[] b, Map<String, Object> map);
}

具體的實現我直接貼出來

package com.ccsert.websocketupload.service.impl;

import com.ccsert.websocketupload.service.SaveFileI;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * ClassName: SaveFileImpl <br/>
 * Description: <br/>
 * date: 2020/2/4 19:01<br/>
 *
 * @author ccsert<br />
 * @since JDK 1.8
 */
@Service
public class SaveFileImpl implements SaveFileI {
    @Override
    public Map<String, Object> docPath(String fileName) {
        HashMap<String, Object> map = new HashMap<>();
        //根據時間生成文件夾路徑
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
        String docUrl = simpleDateFormat.format(date);
        //文件保存地址
        String path = "/data/images/" + docUrl;
        //創建文件
        File dest = new File(path+"/" + fileName);
        //如果文件已經存在就先刪除掉
        if (dest.getParentFile().exists()) {
            dest.delete();
        }
        map.put("dest", dest);
        map.put("path", path+"/" + fileName);
        map.put("nginxPath","/"+docUrl+"/"+fileName);
        return map;
    }

    @Override
    public boolean saveFileFromBytes(byte[] b, Map<String, Object> map) {
        //創建文件流對象
        FileOutputStream fstream = null;
        //從map中獲取file對象
        File file = (File) map.get("dest");
        //判斷路徑是否存在,不存在就創建
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        try {
            fstream = new FileOutputStream(file, true);
            fstream.write(b);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            if (fstream != null) {
                try {
                    fstream.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
        return true;
    }
}

我們在websocket服務里注入接口的時候要注意一點,因為spring是單例的,websocket在初始化的時候就實例化了spring的bean,但是當websocket創建一個新的連接的時候spring的bean會出現null的問題,也就是它只注入了一次。這里我們這樣注入可以解決這個問題。

    /**
     * 注入文件保存的接口
     */
    private static SaveFileI saveFileI;

    @Autowired
    public void setSaveFileI(SaveFileI saveFileI) {
        WebSocketUploadServer.saveFileI = saveFileI;
    }

我們貼一下WebSocketUploadServer完整的代碼

package com.ccsert.websocketupload.web;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ccsert.websocketupload.service.SaveFileI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * ClassName: WebSocketUploadServer <br/>
 * Description: <br/>
 * date: 2020/2/4 14:51<br/>
 *
 * @author ccsert<br />
 * @since JDK 1.8
 */
@ServerEndpoint("/upload/{sid}")
@Component
public class WebSocketUploadServer {
    private static final Logger LOG = LoggerFactory.getLogger(WebSocketUploadServer.class);

    /**
     * 靜態變量,用來記錄當前在線連接數。應該把它設計成線程安全的。
     */
    private static int onlineCount = 0;

    /**
     * concurrent包的線程安全Set,用來存放每個客戶端對應的MyWebSocket對象。
     */
    private static CopyOnWriteArraySet<WebSocketUploadServer> webSocketSet = new CopyOnWriteArraySet<>();

    /**
     * 與某個客戶端的連接會話,需要通過它來給客戶端發送數據
     */
    private Session session;

    /**
     * 注入文件保存的接口
     */
    private static SaveFileI saveFileI;

    @Autowired
    public void setSaveFileI(SaveFileI saveFileI) {
        WebSocketUploadServer.saveFileI = saveFileI;
    }

    /**
     * 保證文件對象和文件路徑的唯一性
     */
    private HashMap docUrl;

    /**
     * 結束標識判斷
     */
    private String endupload = "over";

    /**
     * 連接建立成功時調用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        this.session = session;
        //加入set中
        webSocketSet.add(this);
        //在線人數加1
        addOnlineCount();
        LOG.info(sid + "連接成功" + "----當前在線人數為:" + onlineCount);
    }

    /**
     * 連接關閉時調用的方法
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        //在線人數減1
        subOnlineCount();
        //從set中刪除
        webSocketSet.remove(this);
        LOG.info(sid + "已關閉連接" + "----剩余在線人數為:" + onlineCount);
    }

    /**
     * 接收客戶端發送的消息時調用的方法
     *
     * @param message 接收的字符串消息。該消息應當為json字符串
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        //前端傳過來的消息都是一個json
        JSONObject jsonObject = JSON.parseObject(message);
        //消息類型
        String type = jsonObject.getString("type");
        //消息內容
        String data = jsonObject.getString("data");
        //判斷類型是否為文件名
        if ("fileName".equals(type)) {
            LOG.info("傳輸文件為:" + data);
            //此處的 “.”需要進行轉義
            /*String[] split = data.split("\\.");*/
            try {
                Map<String, Object> map = saveFileI.docPath(data);
                docUrl = (HashMap) map;
                this.sendMessage("ok");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        else if ("fileCount".equals(type)){
            LOG.info("傳輸第"+data+"份");
        }
        //判斷是否結束
        else if (endupload.equals(type)) {
            LOG.info("===============>傳輸成功");
            //返回一個文件下載地址
            String path = (String) docUrl.get("nginxPath");
            //返回客戶端文件地址
            try {
                this.sendMessage(path);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 該方法用於接收字節流數組
     *
     * @param message 文件字節流數組
     * @param session 會話
     */
    @OnMessage
    public void onMessage(byte[] message, Session session) {
        //群發消息
        try {
            //將流寫入文件
            saveFileI.saveFileFromBytes(message,docUrl);
            //文件寫入成功,返回一個ok
            this.sendMessage("ok");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服務器主動提推送消息
     *
     * @param message 消息內容
     * @throws IOException io異常拋出
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /**
     * 群發消息功能
     *
     * @param message 消息內容
     * @param sid     房間號
     */
    public static void sendInfo(String message, @PathParam("sid") String sid) {
        LOG.info("推送消息到窗口" + sid + ",推送內容:" + message);
        for (WebSocketUploadServer item : webSocketSet) {
            try {
                //這里可以設定只推送給這個sid的,為null則全部推送
                item.sendMessage(message);
            } catch (IOException e) {
                LOG.error("消息發送失敗" + e.getMessage(), e);
                return;
            }
        }
    }

    /**
     * 原子性的++操作
     */
    public static synchronized void addOnlineCount() {
        WebSocketUploadServer.onlineCount++;
    }

    /**
     * 原子性的--操作
     */
    public static synchronized void subOnlineCount() {
        WebSocketUploadServer.onlineCount--;
    }

}

接着就是前端的一些處理了

前端我使用了很多打標記的思想

我就直接貼代碼了

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>chat room websocket</title>
    <link rel="stylesheet" href="bootstrap.css">
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</head>
<body class="container" style="width: 60%">
<div class="form-group"></br>
    <a href="javascript:;" class="file">選擇文件
        <input type="file" name="" onchange="fileOnchange()" id="fileId"> <span id="filename" style="color: red"></span>
    </a>
    <a href="javascript:;" onclick="uploadFileFun()" class="file">
        上傳
    </a>
</div>
<div class="progress">
    <div id="speedP" class="progress-bar" role="progressbar" aria-valuenow="60"
         aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
    </div>
</div>

<div class="form-group"></br>
    <h5>傳輸信息</h5>
    <textarea id="message_content" class="form-control" readonly="readonly" cols="50"
              rows="10"></textarea>
</div>

<script>
    var socket;//websocket連接
    var webSocketUrl = 'ws://127.0.0.1:8033/upload/';//websocketi連接地址
    var roomId = Number(Math.random().toString().substr(3, 3) + Date.now()).toString(36);//房間號,生成唯一的id
    var SpeedOfProgress = "";//進度
    var fileObject;//文件對象
    var uploadFlag = true;//文件上傳的標識
    var paragraph = 10485760;//文件分塊上傳大小
    var startSize, endSize = 0;//文件的起始大小和文件的結束大小
    var i = 0;//第幾部分文件
    createWebSocketConnect(roomId);//自動調用
    //創建websocket連接
    function createWebSocketConnect(roomId) {
        if (!socket) {//避免重復連接
            console.log(roomId);
            socket = new WebSocket(webSocketUrl + roomId);
            socket.onopen = function () {
                console.log("websocket已連接");
            };
            socket.onmessage = function (e) {
                if (uploadFlag) {
                    //服務端發送的消息
                    $("#message_content").append(e.data + '\n');
                }
            };
            socket.onclose = function () {
                console.log("websocket已斷開");
            }
        }
    }

    //文件上傳核心方法
    function uploadFileFun() {
        //文件對象賦值
        let filedata = fileObject;
        //切換保存標識的狀態
        uploadFlag = false;
        //先向后台傳輸文件名
        let fileName = fileObject.name;
        //后台只接收字符串類型,我們定義一個字符串的json對象給后台解析
        let fileJson = {
            type: "fileName",
            data: fileName
        };

        //后台接收到文件名以后會正式開始傳輸文件
        socket.send(JSON.stringify(fileJson));

        //此處為文件上傳的核心中的核心,涉及分塊上傳
        socket.onmessage = function (msg) {
            if (uploadFlag === false) {
                //開始上傳文件
                if (msg.data === 'ok') {
                    //判斷結束大小是否大於文件大小
                    if (endSize < filedata.size) {
                        $("#message_content").append("file.size:" + filedata.size+ '\n');
                        startSize = endSize;
                        endSize += paragraph;
                        $("#message_content").append("file.size:" + filedata.size+ '\n');
                        $("#message_content").append("startSize:" + startSize+'\n');
                        $("#message_content").append("endSize:" + endSize+'\n');
                        SpeedOfProgress = Math.round(startSize / filedata.size * 10000) / 100.00 + "%";
                        $("#speedP").css("width",SpeedOfProgress);
                        $("#message_content").append("Slice--->"+'\n');
                        var blob = filedata.slice(startSize, endSize);
                        var reader = new FileReader();
                        reader.readAsArrayBuffer(blob);
                        reader.onload = function loaded(evt) {
                            var ArrayBuffer = evt.target.result;
                            $("#message_content").append("發送文件第" + (i++) + "部分"+'\n');
                            let fileObjJson={
                                type: "fileCount",
                                data:i
                            };
                            socket.send(JSON.stringify(fileObjJson));
                            socket.send(ArrayBuffer);
                        }
                    } else {
                        $("#speedP").css("width","100%");
                        $("#message_content").append("endSize >= file.size-->" + msg.data + "<---"+'\n');
                        $("#message_content").append("endSize >= file.size-->endSize:" + endSize+'\n');
                        $("#message_content").append("endSize >= file.size-->file.size:" + filedata.size+'\n');
                        startSize = endSize = 0;
                        i = 0;
                        $("#message_content").append("發送" + filedata.name + "完畢"+'\n');
                        $("#message_content").append("發送文件完畢"+'\n');
                        socketmess={
                            type:"over",
                            message:filedata.name
                        };
                        socket.send(JSON.stringify(socketmess));//告訴socket文件傳輸完畢,清空計數器
                    }
                } else {
                    //此處獲取
                    $("#message_content").append("文件路徑為:"+msg.data+'\n');
                }
            }
        }


    }

    //監聽file域對象的變化,然后用於回顯文件名
    function fileOnchange() {
        //從file域對象獲取文件對象
        let files = $("#fileId")[0].files;
        //存儲文件對象
        fileObject = files[0];
        //回顯文件名
        $("#filename").html(fileObject.name);
    }

</script>
<style>
    .file {
        position: relative;
        display: inline-block;
        background: #D0EEFF;
        border: 1px solid #99D3F5;
        border-radius: 4px;
        padding: 4px 12px;
        overflow: hidden;
        color: #1E88C7;
        text-decoration: none;
        text-indent: 0;
        line-height: 20px;
    }

    .file input {
        position: absolute;
        font-size: 100px;
        right: 0;
        top: 0;
        opacity: 0;
    }

    .file:hover {
        background: #AADFFD;
        border-color: #78C3F3;
        color: #004974;
        text-decoration: none;
    }
</style>
</body>
</html>

加了點樣式加了個進度條,大部分代碼都有注釋所以也不多做解釋了

最后返回的這個文件地址,你可以在后台存放到文件服務器后返回給前端展示,這里我就不做過多的操作了,我沒加網絡地址那么文件就存在項目所在盤符的根路徑下的日期格式目錄下。

最后附上源碼
GitHub地址:
git@github.com:ccsert/websocketUpload.git
gitee地址:
https://gitee.com/ccsert/websocketUpload.git


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM