PK vV}X/W server.jslet http = require('http'); let static = require('node-static'); let fileServer = new static.Server('.'); let path = require('path'); let fs = require('fs'); let debug = require('debug')('example:resume-upload'); let uploads = Object.create(null); function onUpload(req, res) { let fileId = req.headers['x-file-id']; let startByte = +req.headers['x-start-byte']; if (!fileId) { res.writeHead(400, "No file id"); res.end(); } // 我们将“无处”保存文件 let filePath = '/dev/null'; // 可以改用真实路径,例如 // let filePath = path.join('/tmp', fileId); debug("onUpload fileId: ", fileId); // 初始化一个新上传 if (!uploads[fileId]) uploads[fileId] = {}; let upload = uploads[fileId]; debug("bytesReceived:" + upload.bytesReceived + " startByte:" + startByte) let fileStream; // 如果 startByte 为 0 或者没设置,创建一个新文件,否则检查大小并附加到现有的大小 if (!startByte) { upload.bytesReceived = 0; fileStream = fs.createWriteStream(filePath, { flags: 'w' }); debug("New file created: " + filePath); } else { // 我们也可以检查磁盘上的文件大小以确保 if (upload.bytesReceived != startByte) { res.writeHead(400, "Wrong start byte"); res.end(upload.bytesReceived); return; } // 附加到现有文件 fileStream = fs.createWriteStream(filePath, { flags: 'a' }); debug("File reopened: " + filePath); } req.on('data', function(data) { debug("bytes received", upload.bytesReceived); upload.bytesReceived += data.length; }); // 将 request body 发送到文件 req.pipe(fileStream); // 当请求完成,并且其所有数据都以写入完成 fileStream.on('close', function() { if (upload.bytesReceived == req.headers['x-file-size']) { debug("Upload finished"); delete uploads[fileId]; // 可以在这里对上传的文件进行其他操作 res.end("Success " + upload.bytesReceived); } else { // 连接断开,我们将未完成的文件保留在周围 debug("File unfinished, stopped at " + upload.bytesReceived); res.end(); } }); // 如果发生 I/O error —— 完成请求 fileStream.on('error', function(err) { debug("fileStream error"); res.writeHead(500, "File error"); res.end(); }); } function onStatus(req, res) { let fileId = req.headers['x-file-id']; let upload = uploads[fileId]; debug("onStatus fileId:", fileId, " upload:", upload); if (!upload) { res.end("0") } else { res.end(String(upload.bytesReceived)); } } function accept(req, res) { if (req.url == '/status') { onStatus(req, res); } else if (req.url == '/upload' && req.method == 'POST') { onUpload(req, res); } else { fileServer.serve(req, res); } } // ----------------------------------- if (!module.parent) { http.createServer(accept).listen(8080); console.log('Server listening at port 8080'); } else { exports.accept = accept; }PK vV}X~H[ uploader.jsclass Uploader { constructor({file, onProgress}) { this.file = file; this.onProgress = onProgress; // 创建唯一标识文件的 fileId // 我们还可以添加用户会话标识符(如果有的话),以使其更具唯一性 this.fileId = file.name + '-' + file.size + '-' + file.lastModified; } async getUploadedBytes() { let response = await fetch('status', { headers: { 'X-File-Id': this.fileId } }); if (response.status != 200) { throw new Error("Can't get uploaded bytes: " + response.statusText); } let text = await response.text(); return +text; } async upload() { this.startByte = await this.getUploadedBytes(); let xhr = this.xhr = new XMLHttpRequest(); xhr.open("POST", "upload", true); // 发送文件 id,以便服务器知道要恢复哪个文件 xhr.setRequestHeader('X-File-Id', this.fileId); // 发送我们要从哪个字节开始恢复,因此服务器知道我们正在恢复 xhr.setRequestHeader('X-Start-Byte', this.startByte); xhr.upload.onprogress = (e) => { this.onProgress(this.startByte + e.loaded, this.startByte + e.total); }; console.log("send the file, starting from", this.startByte); xhr.send(this.file.slice(this.startByte)); // return // true —— 如果上传成功, // false —— 如果被中止 // 出现 error 时将其抛出 return await new Promise((resolve, reject) => { xhr.onload = xhr.onerror = () => { console.log("upload end status:" + xhr.status + " text:" + xhr.statusText); if (xhr.status == 200) { resolve(true); } else { reject(new Error("Upload failed: " + xhr.statusText)); } }; // onabort 仅在 xhr.abort() 被调用时触发 xhr.onabort = () => resolve(false); }); } stop() { if (this.xhr) { this.xhr.abort(); } } }PK vV}X index.html