基于websocket 文件断点上传

对于打文件上传如果网突然断了,得重新来一遍,是不是很郁闷,怎么办,如果浏览器不支持FileAPI 那就只能使用 Flash来做断点上传,有了HTML5 基于Ajax或者WebSocket 的原生实现也很棒。

下面就是基于websocket  + spring 的一个实现例子 

 好了直接上代码

<!--

    文件上传例子

    使用websocket上传文件

    支持断点续传

-->

 

<!DOCTYPE html>

<html>

    <head>

        <title>文件上传</title>

        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

        <!--        <link href="../../res/css/bootstrap.css" rel="stylesheet"/>

                <script type="text/javascript" src="../../res/js/jquery-2.0.2.js"></script>

                <script type="text/javascript" src="../../res/js/bootstrap.js"></script>-->

        <link href="/bizplant/res/css/bootstrap.css" rel="stylesheet"/>

        <script type="text/javascript" src="/bizplant/res/js/jquery-2.0.2.js"></script>

        <!--                <script type="text/javascript" src="/bizplant/res/js/bootstrap.js"></script>-->

        <script type="text/javascript">

            ws = new WebSocket( "ws://localhost:8080/bizplant/document/receive.ws" );

            $( document ).ready( function() {

                $( "#disconnect" ).click( function() {

                    if ( ws != null ) {

                        ws.close();

                        ws = null;

                    }

                } );

                $( "#connect" ).click( function() {

                    if ( ws == null ) {

                        ws = new WebSocket( "ws://localhost:8080/bizplant/document/receive.ws" );

                    }

                } );

                $( "#continue" ).click( function() {

                            var id = $("#fileId").val();

                            if ( !ws ) {

                                ws = new WebSocket( "ws://localhost:8080/bizplant/document/receive.ws" );

                            }

                            $.post( "http://localhost:8080/bizplant/document/queryFileUploadState.json", { "id": id }, function( data ) {

                                if ( data ) {

                                    alert( "请选择续传文件" );

                                    $( "#uploadImage" ).change( function() {

                                        $( '<div file=' + data.fileUploadState.file.name + ' class="progress progress-striped active"><div class="progress-bar"  role="progressbar" aria-valuenow="'+data.fileUploadState.loadedSize+'" aria-valuemin="0" aria-valuemax="100" style="width: '+data.fileUploadState.loadedSize+'%"></div><div class="sr-only"><span class="file fileName" fileId="' + data.fileUploadState.file.id + '"> ' + data.fileUploadState.file.name + ' </span> <span>已经上传【</span><span class="fileLoadSize">'+data.fileUploadState.loadedSize+'</span><span>%】</span></div></div>' ).insertAfter( this );

                                        ws.send( JSON.stringify( data.fileUploadState.file ) );

                                        var files = $( this ).get( 0 ).files;

                                        var size = files[0].size;

                                        var total = data.fileUploadState.loadedSize;

                                        var unit = 8192;

                                        var buffer = null;

                                        log( "Info:" + data.fileUploadState.file.name + " 开始发送文件" );

                                        while ( total < size ) {

                                            if ( total + unit < size ) {

                                                buffer = files[0].slice( total, total + unit );

                                                total = total + unit;

                                            } else {

                                                buffer = files[0].slice( total, size );

                                                total = size;

                                            }

                                            ws.send( buffer );

                                        }

                                    } );

                                }

                            }, "json" );

 

                } );

                $( 'input[type="file"]' ).not("#uploadImage").change( function() {

                    var files = $( this ).get( 0 ).files;

                    var fileCount = files.length;

                    $( this ).hide();

                    $( this ).siblings( "input" ).hide();

                    for ( var i = 0; i < fileCount; i++ ) {

                        var file = { };

                        file.size = files[i].size;

                        file.name = files[i].name;

                        file.fileType = { };

                        file.fileType.extension = files[i].name.substring( files[i].name.lastIndexOf( "." ) + 1 );

                        file.fileType.mimeType = files[i].type;

                        $( '<div file=' + file.name + ' class="progress progress-striped active"><div class="progress-bar"  role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"></div><div class="sr-only"><span class="file fileName" fileId="' + file.id + '"> ' + file.name + ' </span> <span>已经上传【</span><span class="fileLoadSize"></span><span>%】</span></div></div>' ).insertAfter( this );

                        ws.send( JSON.stringify( file ) );

                        var size = files[i].size;

                        var total = 0;

                        var unit = 8192;

                        var buffer = null;

                        log( "Info:" + i + files[i].name + " 开始发送文件" );

                        while ( total < size ) {

                            if ( total + unit < size ) {

                                buffer = files[i].slice( total, total + unit );

                                total = total + unit;

                            } else {

                                buffer = files[i].slice( total, size );

                                total = size;

                            }

                            ws.send( buffer );

                        }

                    }

                } );

                ws.onopen = function() {

                    log( 'Info: connection opened.' );

                };

                ws.onmessage = function( event ) {

                    var loader = JSON.parse( event.data );

                    if ( parseInt( loader.load ) !== parseInt( loader.total ) ) {

                        $( 'div[file="' + loader.name + '"] .progress-bar' ).attr( "aria-valuenow", loader.percent ).css( "width", loader.percent + "%" );

                        $( 'div[file="' + loader.name + '"] .fileLoadSize' ).text( loader.percent );

                    } else {

                        $( 'div[file="' + loader.name + '"] .progress-bar' ).attr( "aria-valuenow", "100" ).css( "width", "100%" );

                        $( 'div[file="' + loader.name + '"] .fileLoadSize' ).text( "100" );

                        $( 'div[file="' + loader.name + '"]' ).removeClass( "active" );

                    }

                    log( 'Received: ' + event.data );

                };

                ws.onclose = function( event ) {

                    alert(event.reason);

                    var status = { "type": event.type, "code": event.code, "reason": event.reason, "time": new Date( event.timeStamp ) };

                    log( 'Info: connection closed.' );

                    log( JSON.stringify( status ) );

                };

                function log( message ) {

//                    var console = document.getElementById( 'console' );

//                    var p = document.createElement( 'p' );

//                    p.style.wordWrap = 'break-word';

//                    p.appendChild( document.createTextNode( message ) );

//                    console.appendChild( p );

//                    while ( console.childNodes.length > 10 ) {

//                        console.removeChild( console.firstChild );

//                    }

//                    console.scrollTop = console.scrollHeight;

                }

            } );

        </script>

    </head>

    <body>

        <button type="button" id="disconnect">断开</button>

        <button type="button" id="connect">连接</button>

        <button type="button" id="continue">续传</button>

        <input type="text" id="fileId"/>

        <input type="file" style="opacity: 100" id="uploadImage" name="file"/>

        <div class="container">

            <h2 id="forms-horizontal">用户注册</h2>

            <form class="form-horizontal">

                <div class="form-group">

                    <label for="account" class="col-lg-2 control-label">账号:</label>

                    <div class="col-lg-10 input string">

                        <input class="form-control" id="account" placeholder="账号" type="text">

                    </div>

                </div>

                <div class="form-group">

                    <label for="pswd" class="col-lg-2 control-label">密码</label>

                    <div class="col-lg-10 input string">

                        <input class="form-control" id="pswd" placeholder="密码" type="password">

                    </div>

                </div>

                <div class="form-group">

                    <label for="repswd" class="col-lg-2 control-label">再输密码</label>

                    <div class="col-lg-10">

                        <input class="form-control" id="repswd" placeholder="再输密码" type="password">

                    </div>

                </div>

                <div class="form-group">

                    <label for="name" class="col-lg-2 control-label">姓名</label>

                    <div class="col-lg-10">

                        <input class="form-control" id="name" placeholder="姓名" type="text">

                    </div>

                </div>

                <div class="form-group">

                    <label for="photo" class="col-lg-2 control-label">照片</label>

                    <div class="col-lg-10">

                        <input class="form-control" type="file">

                        <input class="form-control" id="photo" placeholder="照片" type="text">

                    </div>

                </div>

                <div class="form-group">

                    <label for="lifePhoto" class="col-lg-2 control-label">生活照片</label>

                    <div class="col-lg-10">

                        <input class="form-control" multiple = "multiple" type="file">

                        <input class="form-control" id="lifePhoto" placeholder="生活照片" type="text">

                    </div>

                </div>

                <div class="form-group">

                    <label for="age" class="col-lg-2 control-label">年龄</label>

                    <div class="col-lg-10">

                        <input class="form-control" id="lifePhoto" placeholder="年龄" type="number">

                    </div>

                </div>

                <div class="form-group">

                    <div class="col-lg-offset-2 col-lg-10">

                        <div class="checkbox">

                            <label>

                                <input type="checkbox"> Remember me

                            </label>

                        </div>

                    </div>

                </div>

                <div class="form-group">

                    <div class="col-lg-offset-2 col-lg-10">

                        <button type="submit" class="btn btn-default">Sign in</button>

                    </div>

                </div>

 

            </form>

        </div>

 

        <div class="input file">

            <img id="uploadPreview" style="width: 100px; height: 100px;" src="data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3Csvg%20width%3D%22153%22%20height%3D%22153%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%0A%20%3Cg%3E%0A%20%20%3Ctitle%3ENo%20image%3C/title%3E%0A%20%20%3Crect%20id%3D%22externRect%22%20height%3D%22150%22%20width%3D%22150%22%20y%3D%221.5%22%20x%3D%221.500024%22%20stroke-width%3D%223%22%20stroke%3D%22%23666666%22%20fill%3D%22%23e1e1e1%22/%3E%0A%20%20%3Ctext%20transform%3D%22matrix%286.66667%2C%200%2C%200%2C%206.66667%2C%20-960.5%2C%20-1099.33%29%22%20xml%3Aspace%3D%22preserve%22%20text-anchor%3D%22middle%22%20font-family%3D%22Fantasy%22%20font-size%3D%2214%22%20id%3D%22questionMark%22%20y%3D%22181.249569%22%20x%3D%22155.549819%22%20stroke-width%3D%220%22%20stroke%3D%22%23666666%22%20fill%3D%22%23000000%22%3E%3F%3C/text%3E%0A%20%3C/g%3E%0A%3C/svg%3E" alt="Image preview" />

            

            <button type="button">上传</button>

        </div>

        <div id="console-container">

            <div id="console"></div>

        </div>

    </body>

</html>

 

spring 配置

    <websocket:handlers>

        <websocket:mapping path="/document/receive.ws" handler="fileUploadSocketHandler"/>

        <websocket:handshake-interceptors>

            <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>

        </websocket:handshake-interceptors>

    </websocket:handlers>

    <bean name="fileUploadSocketHandler" class="com.chameleon.document.service.FileUploadSocketHandler"/>

    <bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">

        <property name="maxTextMessageBufferSize" value="8192"/>

        <property name="maxBinaryMessageBufferSize" value="8192"/>

    </bean>

 

FileUploadSocketHandler

 

import com.chameleon.document.DocumentBusiness;

import com.chameleon.document.model.File;

import com.chameleon.document.model.FileDiskContent;

import com.chameleon.document.model.FileUploadState;

import com.chameleon.util.Sequence;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.FileOutputStream;

import java.io.OutputStream;

import java.nio.ByteBuffer;

import java.text.DecimalFormat;

import java.util.HashMap;

import java.util.Map;

import javax.annotation.Resource;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.web.socket.BinaryMessage;

import org.springframework.web.socket.CloseStatus;

import org.springframework.web.socket.TextMessage;

import org.springframework.web.socket.WebSocketSession;

import org.springframework.web.socket.handler.AbstractWebSocketHandler;

/**

 *

 * @author Johnson.zhang

 */

public class FileUploadSocketHandler extends AbstractWebSocketHandler {

 

    private static final Logger logger = LoggerFactory.getLogger(FileUploadSocketHandler.class);

    private Map result = null;

    @Resource(name = "documentBusinessImpl")

    private DocumentBusiness documentBusiness;

    private ObjectMapper objectMapper = new ObjectMapper();

    private OutputStream outputStream;

    private long fileTotalSize;

    private long fileLoadSize;

    private DecimalFormat percentFormat = new DecimalFormat("0");

    private FileDiskContent fileDiskContent = null;

    @Resource(name = "systemFileUploadPath")

    private String uploadFilePath;

    @Resource(name = "sequenceSerialNumberImpl")

    private Sequence sequence;

 

    public void setUploadFilePath(String uploadFilePath) {

        this.uploadFilePath = uploadFilePath;

    }

 

    public void setObjectMapper(ObjectMapper objectMapper) {

        this.objectMapper = objectMapper;

    }

 

    public void setSequence(Sequence sequence) {

        this.sequence = sequence;

    }

 

    @Override

    public void afterConnectionEstablished(WebSocketSession session) throws Exception {

        String id = session.getId();

        logger.debug("WebSocket连接已经建立,连接ID为:" + id);

    }

 

    @Override

    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

        result = new HashMap();

        result.put("buffer", 8192);

        fileTotalSize = 0l;

        fileLoadSize = 0l;

        

        String msg = message.getPayload();

        fileDiskContent = objectMapper.readValue(msg, FileDiskContent.class);

        if (fileDiskContent.getId() != null && !fileDiskContent.getId().isEmpty()) {

            //续传文件

            CloseStatus status = null;

            String filePath = fileDiskContent.getPath();

            if (filePath == null || filePath.isEmpty()) {

                status = new CloseStatus(2001, "续传文件路径不能为空!");

            }

            java.io.File tempFile = new java.io.File(uploadFilePath + "/" + filePath);

            if (!tempFile.exists()) {

                status = new CloseStatus(2002, "续传文件不存在!");

            }

            if (!tempFile.isFile()) {

                status = new CloseStatus(2002, "续传文件不能是文件夹!");

            }

            if (status != null) {

                session.close(status);

                String rs = objectMapper.writeValueAsString(status);

                logger.debug("WebSocket 文件断点续传初始化发生错误: " + rs);

                return;

            }

            outputStream = new FileOutputStream(uploadFilePath + "/" + filePath, true);

            fileTotalSize = fileDiskContent.getSize();

            fileLoadSize = tempFile.length();

            if (fileTotalSize > 0) {

                double percent = ((double) fileLoadSize) / fileTotalSize * 100;

                result.put("percent", percentFormat.format(percent));

            }

            result.put("id", fileDiskContent.getId());

            result.put("name", fileDiskContent.getName());

            result.put("total", fileDiskContent.getSize());

            result.put("path", filePath);

            result.put("load", fileLoadSize);

            logger.debug("WebSocket 文件上传信息为: " + msg);

        } else {

            //新上传文件

            String filePath = sequence.getId();

            String tempPath = uploadFilePath + "/" + filePath;

            outputStream = new FileOutputStream(tempPath);

            fileDiskContent.setPath(filePath);

            fileDiskContent.setId(sequence.getId());

            fileTotalSize = fileDiskContent.getSize();

            

            result.put("id", fileDiskContent.getId());

            result.put("name", fileDiskContent.getName());

            result.put("total", fileDiskContent.getSize());

            result.put("path", filePath);

            result.put("load", fileLoadSize);

            result.put("percent", "0");

            logger.debug("WebSocket 文件上传信息为: " + msg);

            logger.debug("WebSocket 文件保存路径为: " + tempPath);

        }

        String rs = objectMapper.writeValueAsString(result);

        session.sendMessage(new TextMessage(rs));

    }

 

    @Override

    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {

        ByteBuffer msg = message.getPayload();

        byte[] buffer = msg.array();

        outputStream.write(buffer);

        fileLoadSize = fileLoadSize + buffer.length;

        logger.debug("WebSocket 文件已经上传了 [ " + fileLoadSize + " ] 字节");

        result.put("load", fileLoadSize);

        if (fileTotalSize > 0) {

            double percent = ((double) fileLoadSize) / fileTotalSize * 100;

            result.put("percent", percentFormat.format(percent));

        }

        if (fileLoadSize == fileTotalSize) {

            closeStream();

            if (fileDiskContent != null) {

                if (fileDiskContent.getMeta() == null) {

                    documentBusiness.saveFile(fileDiskContent);

                } else {

                    documentBusiness.deleteFileUploadStateByFile(fileDiskContent);

                }

            }

            logger.debug("WebSocket 文件已经保存到磁盘:【" + uploadFilePath + "/" + fileDiskContent.getPath() + "】");

            logger.debug("WebSocket 文件信息已经保存到数据库 文件ID为:【" + fileDiskContent.getId() + "】");

        }

        String rs = objectMapper.writeValueAsString(result);

        logger.debug("WebSocket 文件上传回显数据位 :【" + rs + "】");

        session.sendMessage(new TextMessage(rs));

    }

 

    @Override

    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {

        String rs = objectMapper.writeValueAsString(exception);

        if (session.isOpen()) {

            session.sendMessage(new TextMessage(rs));

        }

        logger.debug("WebSocket 程序发生异常 :【" + rs + "】");

    }

 

    @Override

    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {

        closeStream();

        saveFileUploadState();

        String rs = objectMapper.writeValueAsString(status);

        logger.debug("WebSocket 连接关闭 :【" + rs + "】");

    }

 

    private void closeStream() throws Exception {

        if (outputStream != null) {

            outputStream.close();

            outputStream = null;

        }

    }

 

    private void saveFileUploadState() {

        if (fileLoadSize < fileTotalSize) {

            if (fileDiskContent.getMeta() == null) {

                FileUploadState fileUploadState = new FileUploadState();

                fileUploadState.setId(sequence.getId());

                fileUploadState.setLoadedSize(fileLoadSize);

                fileUploadState.setFile(fileDiskContent);

                documentBusiness.saveFileUploadState(fileUploadState);

            } else {

                documentBusiness.updateFileUploadStateByFile(fileDiskContent, fileLoadSize);

            }

        }

    }

}

 

你可能感兴趣的:(文件上传,websocket,断点续传)