Работа над загрузкой файла на сервер

This commit is contained in:
Book Pauk
2019-02-04 20:03:36 +07:00
parent 170c06fa3f
commit b33911b8ec
12 changed files with 207 additions and 22 deletions

View File

@@ -1,6 +1,7 @@
import axios from 'axios'; import axios from 'axios';
import {sleep} from '../share/utils'; import {sleep} from '../share/utils';
const maxFileUploadSize = 10*1024*1024;
const api = axios.create({ const api = axios.create({
baseURL: '/api/reader' baseURL: '/api/reader'
}); });
@@ -65,6 +66,36 @@ class Reader {
//загрузка //загрузка
return await axios.get(url, options); return await axios.get(url, options);
} }
async uploadFile(file, callback) {
if (file.size > maxFileUploadSize)
throw new Error(`Размер файла превышает ${maxFileUploadSize} байт`);
let formData = new FormData();
formData.append('file', file);
const options = {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: progress => {
const total = (progress.total ? progress.total : progress.loaded + 200000);
if (callback)
callback({state: 'upload', progress: Math.round((progress.loaded*100)/total)});
}
};
let response = await api.post('/upload-file', formData, options);
if (response.data.state == 'error')
throw new Error(response.data.error);
const url = response.data.url;
if (!url)
throw new Error('Неверный ответ api');
return url;
}
} }
export default new Reader(); export default new Reader();

View File

@@ -10,7 +10,8 @@
<el-button slot="append" icon="el-icon-check" @click="submitUrl"></el-button> <el-button slot="append" icon="el-icon-check" @click="submitUrl"></el-button>
</el-input> </el-input>
<div class="space"></div> <div class="space"></div>
<el-button size="mini" @click="loadFle"> <input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
<el-button size="mini" @click="loadFileClick">
Загрузить файл с диска Загрузить файл с диска
</el-button> </el-button>
<div class="space"></div> <div class="space"></div>
@@ -66,7 +67,13 @@ class LoaderPage extends Vue {
} }
} }
loadFle() { loadFileClick() {
this.$refs.file.click();
}
loadFile() {
const file = this.$refs.file.files[0];
this.$emit('load-file', {file});
} }
openHelp() { openHelp() {

View File

@@ -21,6 +21,7 @@ const ruMessage = {
'convert': 'конвертирование', 'convert': 'конвертирование',
'loading': 'загрузка', 'loading': 'загрузка',
'parse': 'обработка', 'parse': 'обработка',
'upload': 'отправка',
}; };
export default @Component({ export default @Component({

View File

@@ -50,6 +50,7 @@
<keep-alive> <keep-alive>
<component ref="page" :is="activePage" <component ref="page" :is="activePage"
@load-book="loadBook" @load-book="loadBook"
@load-file="loadFile"
@book-pos-changed="bookPosChanged" @book-pos-changed="bookPosChanged"
@tool-bar-toggle="toolBarToggle" @tool-bar-toggle="toolBarToggle"
@full-screen-toogle="fullScreenToggle" @full-screen-toogle="fullScreenToggle"
@@ -647,6 +648,29 @@ class Reader extends Vue {
}); });
} }
loadFile(opts) {
this.progressActive = true;
this.$nextTick(async() => {
const progress = this.$refs.page;
try {
progress.show();
progress.setState({state: 'upload'});
const url = await readerApi.uploadFile(opts.file, (state) => {
progress.setState(state);
});
this.loadBook(url);
progress.hide(); this.progressActive = false;
} catch (e) {
progress.hide(); this.progressActive = false;
this.loaderActive = true;
this.$alert(e.message, 'Ошибка', {type: 'error'});
}
});
}
blinkCachedLoadMessage() { blinkCachedLoadMessage() {
this.blinkCount = 30; this.blinkCount = 30;
if (!this.inBlink) { if (!this.inBlink) {

96
package-lock.json generated
View File

@@ -619,6 +619,11 @@
"normalize-path": "^2.1.1" "normalize-path": "^2.1.1"
} }
}, },
"append-field": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
},
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
@@ -1840,8 +1845,7 @@
"buffer-from": { "buffer-from": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
"dev": true
}, },
"buffer-xor": { "buffer-xor": {
"version": "1.0.3", "version": "1.0.3",
@@ -1855,6 +1859,38 @@
"integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
"dev": true "dev": true
}, },
"busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
"integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
"requires": {
"dicer": "0.2.5",
"readable-stream": "1.1.x"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
"byline": { "byline": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz",
@@ -2379,7 +2415,6 @@
"version": "1.6.2", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"dev": true,
"requires": { "requires": {
"buffer-from": "^1.0.0", "buffer-from": "^1.0.0",
"inherits": "^2.0.3", "inherits": "^2.0.3",
@@ -3251,6 +3286,38 @@
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
}, },
"dicer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
"integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
"requires": {
"readable-stream": "1.1.x",
"streamsearch": "0.1.2"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
"diffie-hellman": { "diffie-hellman": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@@ -6521,6 +6588,21 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}, },
"multer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.1.tgz",
"integrity": "sha512-zzOLNRxzszwd+61JFuAo0fxdQfvku12aNJgnla0AQ+hHxFmfc/B7jBVuPr5Rmvu46Jze/iJrFpSOsD7afO8SDw==",
"requires": {
"append-field": "^1.0.0",
"busboy": "^0.2.11",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.1",
"object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
}
},
"multistream": { "multistream": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/multistream/-/multistream-2.1.1.tgz", "resolved": "https://registry.npmjs.org/multistream/-/multistream-2.1.1.tgz",
@@ -10405,6 +10487,11 @@
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
"dev": true "dev": true
}, },
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
@@ -11036,8 +11123,7 @@
"typedarray": { "typedarray": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
"dev": true
}, },
"uglify-js": { "uglify-js": {
"version": "3.4.9", "version": "3.4.9",

View File

@@ -70,6 +70,7 @@
"iconv-lite": "^0.4.24", "iconv-lite": "^0.4.24",
"localforage": "^1.7.3", "localforage": "^1.7.3",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"multer": "^1.4.1",
"path-browserify": "^1.0.0", "path-browserify": "^1.0.0",
"sql-template-strings": "^2.2.2", "sql-template-strings": "^2.2.2",
"sqlite": "^3.0.0", "sqlite": "^3.0.0",

View File

@@ -13,6 +13,7 @@ module.exports = {
tempDir: `${dataDir}/tmp`, tempDir: `${dataDir}/tmp`,
logDir: `${dataDir}/log`, logDir: `${dataDir}/log`,
publicDir: `${execDir}/public`, publicDir: `${execDir}/public`,
uploadDir: `${execDir}/public/upload`,
dbFileName: 'db.sqlite', dbFileName: 'db.sqlite',
loggingEnabled: true, loggingEnabled: true,

View File

@@ -10,6 +10,7 @@ module.exports = Object.assign({}, base, {
tempDir: `${dataDir}/tmp`, tempDir: `${dataDir}/tmp`,
logDir: `${dataDir}/log`, logDir: `${dataDir}/log`,
publicDir: `${execDir}/public`, publicDir: `${execDir}/public`,
uploadDir: `${execDir}/public/upload`,
servers: [ servers: [
{ {

View File

@@ -14,19 +14,25 @@ class ReaderController extends BaseController {
const request = req.body; const request = req.body;
let error = ''; let error = '';
try { try {
if (!request.type || !(request.type == 'url' || request.type == 'file')) if (!request.url)
throw new Error(`key 'type' is wrong`); throw new Error(`key 'url' is empty`);
const workerId = this.readerWorker.loadBookUrl(request.url);
const state = workerState.getState(workerId);
return (state ? state : {});
} catch (e) {
error = e.message;
}
//bad request
res.status(400).send({error});
return false;
}
if (request.type == 'file') async uploadFile(req, res) {
throw new Error(`file loading is not supported yet`); const file = req.file;
let error = '';
if (request.type == 'url') { try {
if (!request.url) const url = await this.readerWorker.saveFile(file);
throw new Error(`key 'url' is empty`); return ({url});
const workerId = this.readerWorker.loadBookUrl(request.url);
const state = workerState.getState(workerId);
return (state ? state : {});
}
} catch (e) { } catch (e) {
error = e.message; error = e.message;
} }

View File

@@ -88,6 +88,10 @@ class ReaderWorker {
return workerId; return workerId;
} }
async saveFile(file) {
return `file://${file.filename}`;
}
} }
module.exports = ReaderWorker; module.exports = ReaderWorker;

View File

@@ -13,6 +13,7 @@ const SqliteConnectionPool = require('./core/SqliteConnectionPool');
async function init() { async function init() {
await fs.ensureDir(config.dataDir); await fs.ensureDir(config.dataDir);
await fs.ensureDir(config.uploadDir);
await fs.ensureDir(config.tempDir); await fs.ensureDir(config.tempDir);
await fs.emptyDir(config.tempDir); await fs.emptyDir(config.tempDir);
} }

View File

@@ -1,4 +1,6 @@
const c = require('./controllers'); const c = require('./controllers');
const utils = require('./core/utils');
const multer = require('multer');
function initRoutes(app, connPool, config) { function initRoutes(app, connPool, config) {
const misc = new c.MiscController(connPool, config); const misc = new c.MiscController(connPool, config);
@@ -9,16 +11,35 @@ function initRoutes(app, connPool, config) {
const [aAll, aNormal, aSite, aReader, aOmnireader] = // eslint-disable-line no-unused-vars const [aAll, aNormal, aSite, aReader, aOmnireader] = // eslint-disable-line no-unused-vars
[config.mode, 'normal', 'site', 'reader', 'omnireader']; [config.mode, 'normal', 'site', 'reader', 'omnireader'];
//multer
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, config.uploadDir);
},
filename: (req, file, cb) => {
cb(null, utils.randomHexString(30));
}
});
const upload = multer({ storage, limits: {fileSize: 10*1024*1024} });
//routes //routes
const routes = [ const routes = [
['POST', '/api/config', misc.getConfig.bind(misc), [aAll], {}], ['POST', '/api/config', misc.getConfig.bind(misc), [aAll], {}],
['POST', '/api/reader/load-book', reader.loadBook.bind(reader), [aAll], {}], ['POST', '/api/reader/load-book', reader.loadBook.bind(reader), [aAll], {}],
['POST', '/api/reader/upload-file', [upload.single('file'), reader.uploadFile.bind(reader)], [aAll], {}],
['POST', '/api/worker/get-state', worker.getState.bind(worker), [aAll], {}], ['POST', '/api/worker/get-state', worker.getState.bind(worker), [aAll], {}],
]; ];
//to app //to app
for (let route of routes) { for (let route of routes) {
let [httpMethod, path, controller, access, options] = route; let callbacks = [];
let [httpMethod, path, controllers, access, options] = route;
let controller = controllers;
if (Array.isArray(controllers)) {
controller = controllers[controllers.length - 1];
callbacks = controllers.slice(0, -1);
}
access = new Set(access); access = new Set(access);
let callback = () => {}; let callback = () => {};
@@ -38,13 +59,14 @@ function initRoutes(app, connPool, config) {
res.status(403); res.status(403);
}; };
} }
callbacks.push(callback);
switch (httpMethod) { switch (httpMethod) {
case 'GET' : case 'GET' :
app.get(path, callback); app.get(path, ...callbacks);
break; break;
case 'POST': case 'POST':
app.post(path, callback); app.post(path, ...callbacks);
break; break;
default: default:
throw new Error(`initRoutes error: unknown httpMethod: ${httpMethod}`); throw new Error(`initRoutes error: unknown httpMethod: ${httpMethod}`);