服务器代码

  1. const express = require('express'),
  2. fs = require('fs'),
  3. bodyParser = require('body-parser'),
  4. multiparty = require('multiparty'),
  5. SparkMD5 = require('spark-md5');
  6. /*-CREATE SERVER-*/
  7. const app = express(),
  8. PORT = 8888,
  9. HOST = 'http://127.0.0.1',
  10. HOSTNAME = `${HOST}:${PORT}`;
  11. app.listen(PORT, () => {
  12. console.log(`THE WEB SERVICE IS CREATED SUCCESSFULLY AND IS LISTENING TO THE PORT${PORT},YOU CAN VISIT${HOSTNAME}`);
  13. });
  14. /*-中间件-*/
  15. app.use((req, res, next) => {
  16. res.header("Access-Control-Allow-Origin", "*");
  17. req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next();
  18. });
  19. app.use(bodyParser.urlencoded({
  20. extended: false,
  21. limit: '1024mb'
  22. }));
  23. /*-API-*/
  24. // 延迟函数
  25. const delay = function delay(interval) {
  26. typeof interval !== "number" ? interval = 1000 : null;
  27. return new Promise(resolve => {
  28. setTimeout(() => {
  29. resolve();
  30. }, interval);
  31. });
  32. };
  33. // 检测文件是否存在
  34. const exists = function exists(path) {
  35. return new Promise(resolve => {
  36. fs.access(path, fs.constants.F_OK, err => {
  37. if (err) {
  38. resolve(false);
  39. return;
  40. }
  41. resolve(true);
  42. });
  43. });
  44. };
  45. // 创建文件并写入到指定的目录 & 返回客户端结果
  46. const writeFile = function writeFile(res, path, file, filename, stream) {
  47. return new Promise((resolve, reject) => {
  48. if (stream) {
  49. try {
  50. let readStream = fs.createReadStream(file.path),
  51. writeStream = fs.createWriteStream(path);
  52. readStream.pipe(writeStream);
  53. readStream.on('end', () => {
  54. resolve();
  55. fs.unlinkSync(file.path);
  56. res.send({
  57. code: 0,
  58. codeText: 'upload success',
  59. originalFilename: filename,
  60. servicePath: path.replace(__dirname, HOSTNAME)
  61. });
  62. });
  63. } catch (err) {
  64. reject(err);
  65. res.send({
  66. code: 1,
  67. codeText: err
  68. });
  69. }
  70. return;
  71. }
  72. fs.writeFile(path, file, err => {
  73. if (err) {
  74. reject(err);
  75. res.send({
  76. code: 1,
  77. codeText: err
  78. });
  79. return;
  80. }
  81. resolve();
  82. res.send({
  83. code: 0,
  84. codeText: 'upload success',
  85. originalFilename: filename,
  86. servicePath: path.replace(__dirname, HOSTNAME)
  87. });
  88. });
  89. });
  90. };
  91. // 基于multiparty插件实现文件上传处理 & form-data解析
  92. const uploadDir = `${__dirname}/upload`;
  93. const multiparty_upload = function multiparty_upload(req, auto) {
  94. typeof auto !== "boolean" ? auto = false : null;
  95. let config = {
  96. maxFieldsSize: 200 * 1024 * 1024,
  97. };
  98. if (auto) config.uploadDir = uploadDir;
  99. return new Promise(async (resolve, reject) => {
  100. await delay();
  101. new multiparty.Form(config)
  102. .parse(req, (err, fields, files) => {
  103. if (err) {
  104. reject(err);
  105. return;
  106. }
  107. resolve({
  108. fields,
  109. files
  110. });
  111. });
  112. });
  113. };
  114. // 单文件上传处理「FORM-DATA」
  115. app.post('/upload_single', async (req, res) => {
  116. try {
  117. let {
  118. files
  119. } = await multiparty_upload(req, true);
  120. let file = (files.file && files.file[0]) || {};
  121. res.send({
  122. code: 0,
  123. codeText: 'upload success',
  124. originalFilename: file.originalFilename,
  125. servicePath: file.path.replace(__dirname, HOSTNAME)
  126. });
  127. } catch (err) {
  128. res.send({
  129. code: 1,
  130. codeText: err
  131. });
  132. }
  133. });
  134. app.post('/upload_single_name', async (req, res) => {
  135. try {
  136. let {
  137. fields,
  138. files
  139. } = await multiparty_upload(req);
  140. let file = (files.file && files.file[0]) || {},
  141. filename = (fields.filename && fields.filename[0]) || "",
  142. path = `${uploadDir}/${filename}`,
  143. isExists = false;
  144. // 检测是否存在
  145. isExists = await exists(path);
  146. if (isExists) {
  147. res.send({
  148. code: 0,
  149. codeText: 'file is exists',
  150. originalFilename: filename,
  151. servicePath: path.replace(__dirname, HOSTNAME)
  152. });
  153. return;
  154. }
  155. writeFile(res, path, file, filename, true);
  156. } catch (err) {
  157. res.send({
  158. code: 1,
  159. codeText: err
  160. });
  161. }
  162. });
  163. // 单文件上传处理「BASE64」
  164. app.post('/upload_single_base64', async (req, res) => {
  165. let file = req.body.file,
  166. filename = req.body.filename,
  167. spark = new SparkMD5.ArrayBuffer(),
  168. suffix = /\.([0-9a-zA-Z]+)$/.exec(filename)[1],
  169. isExists = false,
  170. path;
  171. file = decodeURIComponent(file);
  172. file = file.replace(/^data:image\/\w+;base64,/, "");
  173. file = Buffer.from(file, 'base64');
  174. spark.append(file);
  175. path = `${uploadDir}/${spark.end()}.${suffix}`;
  176. await delay();
  177. // 检测是否存在
  178. isExists = await exists(path);
  179. if (isExists) {
  180. res.send({
  181. code: 0,
  182. codeText: 'file is exists',
  183. originalFilename: filename,
  184. servicePath: path.replace(__dirname, HOSTNAME)
  185. });
  186. return;
  187. }
  188. writeFile(res, path, file, filename, false);
  189. });
  190. // 大文件切片上传 & 合并切片
  191. const merge = function merge(HASH, count) {
  192. return new Promise(async (resolve, reject) => {
  193. let path = `${uploadDir}/${HASH}`,
  194. fileList = [],
  195. suffix,
  196. isExists;
  197. isExists = await exists(path);
  198. if (!isExists) {
  199. reject('HASH path is not found!');
  200. return;
  201. }
  202. fileList = fs.readdirSync(path);
  203. if (fileList.length < count) {
  204. reject('the slice has not been uploaded!');
  205. return;
  206. }
  207. fileList.sort((a, b) => {
  208. let reg = /_(\d+)/;
  209. return reg.exec(a)[1] - reg.exec(b)[1];
  210. }).forEach(item => {
  211. !suffix ? suffix = /\.([0-9a-zA-Z]+)$/.exec(item)[1] : null;
  212. fs.appendFileSync(`${uploadDir}/${HASH}.${suffix}`, fs.readFileSync(`${path}/${item}`));
  213. fs.unlinkSync(`${path}/${item}`);
  214. });
  215. fs.rmdirSync(path);
  216. resolve({
  217. path: `${uploadDir}/${HASH}.${suffix}`,
  218. filename: `${HASH}.${suffix}`
  219. });
  220. });
  221. };
  222. app.post('/upload_chunk', async (req, res) => {
  223. try {
  224. let {
  225. fields,
  226. files
  227. } = await multiparty_upload(req);
  228. let file = (files.file && files.file[0]) || {},
  229. filename = (fields.filename && fields.filename[0]) || "",
  230. path = '',
  231. isExists = false;
  232. // 创建存放切片的临时目录
  233. let [, HASH] = /^([^_]+)_(\d+)/.exec(filename);
  234. path = `${uploadDir}/${HASH}`;
  235. !fs.existsSync(path) ? fs.mkdirSync(path) : null;
  236. // 把切片存储到临时目录中
  237. path = `${uploadDir}/${HASH}/${filename}`;
  238. isExists = await exists(path);
  239. if (isExists) {
  240. res.send({
  241. code: 0,
  242. codeText: 'file is exists',
  243. originalFilename: filename,
  244. servicePath: path.replace(__dirname, HOSTNAME)
  245. });
  246. return;
  247. }
  248. writeFile(res, path, file, filename, true);
  249. } catch (err) {
  250. res.send({
  251. code: 1,
  252. codeText: err
  253. });
  254. }
  255. });
  256. app.post('/upload_merge', async (req, res) => {
  257. let {
  258. HASH,
  259. count
  260. } = req.body;
  261. try {
  262. let {
  263. filename,
  264. path
  265. } = await merge(HASH, count);
  266. res.send({
  267. code: 0,
  268. codeText: 'merge success',
  269. originalFilename: filename,
  270. servicePath: path.replace(__dirname, HOSTNAME)
  271. });
  272. } catch (err) {
  273. res.send({
  274. code: 1,
  275. codeText: err
  276. });
  277. }
  278. });
  279. app.get('/upload_already', async (req, res) => {
  280. let {
  281. HASH
  282. } = req.query;
  283. let path = `${uploadDir}/${HASH}`,
  284. fileList = [];
  285. try {
  286. fileList = fs.readdirSync(path);
  287. fileList = fileList.sort((a, b) => {
  288. let reg = /_(\d+)/;
  289. return reg.exec(a)[1] - reg.exec(b)[1];
  290. });
  291. res.send({
  292. code: 0,
  293. codeText: '',
  294. fileList: fileList
  295. });
  296. } catch (err) {
  297. res.send({
  298. code: 0,
  299. codeText: '',
  300. fileList: fileList
  301. });
  302. }
  303. });
  304. app.use(express.static('./'));
  305. app.use((req, res) => {
  306. res.status(404);
  307. res.send('NOT FOUND!');
  308. });

axios代码

  1. let instance = axios.create();
  2. instance.defaults.baseURL = 'http://127.0.0.1:8888';
  3. instance.defaults.headers['Content-Type'] = 'multipart/form-data';
  4. instance.defaults.transformRequest = (data, headers) => {
  5. const contentType = headers['Content-Type'];
  6. if (contentType === "application/x-www-form-urlencoded") return Qs.stringify(data);
  7. return data;
  8. };
  9. instance.interceptors.response.use(response => {
  10. return response.data;
  11. });

基于FORM-DATA实现文件上传

  1. let formData = new FormData();
  2. formData.append('file', _file);
  3. formData.append('filename', _file.name);
  4. instance.post('/upload_single', formData).then(data => {
  5. if (+data.code === 0) {
  6. alert(`文件已经上传成功~~,您可以基于 ${data.servicePath} 访问这个资源~~`);
  7. return;
  8. }
  9. return Promise.reject(data.codeText);
  10. }).catch(reason => {
  11. alert('文件上传失败,请您稍后再试~~');
  12. })
  13. //限制大小
  14. // 获取用户选中的文件对象
  15. // + name:文件名
  16. // + size:文件大小 B
  17. // + type:文件的MIME类型
  18. let file = upload_inp.files[0];
  19. if (!file) return;
  20. // 限制文件上传的格式「方案一」
  21. /* if (!/(PNG|JPG|JPEG)/i.test(file.type)) {
  22. alert('上传的文件只能是 PNG/JPG/JPEG 格式的~~');
  23. return;
  24. } */
  25. // 限制文件上传的大小
  26. if (file.size > 2 * 1024 * 1024) {
  27. alert('上传的文件不能超过2MB~~');
  28. return;
  29. }

基于BASE64实现文件上传

  1. // 把选择的文件读取成为BASE64
  2. const changeBASE64 = file => {
  3. return new Promise(resolve => {
  4. let fileReader = new FileReader();
  5. fileReader.readAsDataURL(file);
  6. fileReader.onload = ev => resolve(ev.target.result);
  7. });
  8. };
  9. //上传相关代码
  10. let file = upload_inp.files[0],
  11. BASE64,
  12. data;
  13. if (!file) return;
  14. if (file.size > 2 * 1024 * 1024) {
  15. alert('上传的文件不能超过2MB~~');
  16. return;
  17. }
  18. BASE64 = await changeBASE64(file);
  19. try {
  20. data = await instance.post('/upload_single_base64', {
  21. file: encodeURIComponent(BASE64),
  22. filename: file.name
  23. }, {
  24. headers: {
  25. 'Content-Type': 'application/x-www-form-urlencoded'
  26. }
  27. });
  28. if (+data.code === 0) {
  29. alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 地址去访问~~`);
  30. return;
  31. }
  32. throw data.codeText;
  33. } catch (err) {
  34. alert('很遗憾,文件上传失败,请您稍后再试~~');
  35. }

文件缩略图 & 自动生成名字

  1. // 把选择的文件读取成为BASE64
  2. const changeBASE64 = file => {
  3. return new Promise(resolve => {
  4. let fileReader = new FileReader();
  5. fileReader.readAsDataURL(file);
  6. fileReader.onload = ev => {
  7. resolve(ev.target.result);
  8. };
  9. });
  10. };
  11. // 生成文件的HASH名字
  12. const changeBuffer = file => {
  13. return new Promise(resolve => {
  14. let fileReader = new FileReader();
  15. fileReader.readAsArrayBuffer(file);
  16. fileReader.onload = ev => {
  17. let buffer = ev.target.result,
  18. spark = new SparkMD5.ArrayBuffer(),
  19. HASH,
  20. suffix;
  21. spark.append(buffer);
  22. HASH = spark.end();
  23. suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1];
  24. resolve({
  25. buffer,
  26. HASH,
  27. suffix,
  28. filename: `${HASH}.${suffix}`
  29. });
  30. };
  31. });
  32. };
  33. //上传相关代码
  34. let {
  35. filename
  36. } = await changeBuffer(_file);
  37. let formData = new FormData();
  38. formData.append('file', _file);
  39. formData.append('filename', filename);
  40. instance.post('/upload_single_name', formData).then(data => {
  41. if (+data.code === 0) {
  42. alert(`文件已经上传成功~~,您可以基于 ${data.servicePath} 访问这个资源~~`);
  43. return;
  44. }
  45. return Promise.reject(data.codeText);
  46. }).catch(reason => {
  47. alert('文件上传失败,请您稍后再试~~');
  48. })
  49. // 文件预览,就是把文件对象转换为BASE64,赋值给图片的SRC属性即可
  50. let file = upload_inp.files[0],
  51. BASE64;
  52. BASE64 = await changeBASE64(file);
  53. upload_abbre_img.src = BASE64;

进度管控

  1. let file = upload_inp.files[0],
  2. data;
  3. try {
  4. let formData = new FormData();
  5. formData.append('file', file);
  6. formData.append('filename', file.name);
  7. data = await instance.post('/upload_single', formData, {
  8. // 文件上传中的回调函数 xhr.upload.onprogress
  9. onUploadProgress(ev) {
  10. let {
  11. loaded,
  12. total
  13. } = ev;
  14. upload_progress.style.display = 'block';
  15. upload_progress_value.style.width = `${loaded / total * 100}%`;
  16. }
  17. });
  18. if (+data.code === 0) {
  19. upload_progress_value.style.width = `100%`;
  20. await delay(300);
  21. alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`);
  22. return;
  23. }
  24. throw data.codeText;
  25. } catch (err) {
  26. alert('很遗憾,文件上传失败,请您稍后再试~~');
  27. }

多文件上传

  1. // 循环发送请求
  2. let _files = [];
  3. let upload_list_arr = Array.from(upload_list.querySelectorAll('li'));
  4. _files = _files.map(item => {
  5. let fm = new FormData,
  6. curLi = upload_list_arr.find(liBox => liBox.getAttribute('key') === item.key),
  7. curSpan = curLi ? curLi.querySelector('span:nth-last-child(1)') : null;
  8. fm.append('file', item.file);
  9. fm.append('filename', item.filename);
  10. return instance.post('/upload_single', fm, {
  11. onUploadProgress(ev) {
  12. // 检测每一个的上传进度
  13. if (curSpan) {
  14. curSpan.innerHTML = `${(ev.loaded / ev.total * 100).toFixed(2)}%`;
  15. }
  16. }
  17. }).then(data => {
  18. if (+data.code === 0) {
  19. if (curSpan) {
  20. curSpan.innerHTML = `100%`;
  21. }
  22. return;
  23. }
  24. return Promise.reject();
  25. });
  26. });
  27. // 等待所有处理的结果
  28. Promise.all(_files).then(() => {
  29. alert('恭喜您,所有文件都上传成功~~');
  30. }).catch(() => {
  31. alert('很遗憾,上传过程中出现问题,请您稍后再试~~');
  32. }).finally(() => {
  33. changeDisable(false);
  34. _files = [];
  35. upload_list.innerHTML = '';
  36. upload_list.style.display = 'none';
  37. });

拖拽上传

  1. const uploadFile = async file => {
  2. if (isRun) return;
  3. isRun = true;
  4. upload_mark.style.display = 'block';
  5. try {
  6. let fm = new FormData,
  7. data;
  8. fm.append('file', file);
  9. fm.append('filename', file.name);
  10. data = await instance.post('/upload_single', fm);
  11. if (+data.code === 0) {
  12. alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`);
  13. return;
  14. }
  15. throw data.codeText;
  16. } catch (err) {
  17. alert(`很遗憾,文件上传失败,请您稍后再试~~`);
  18. } finally {
  19. upload_mark.style.display = 'none';
  20. isRun = false;
  21. }
  22. };
  23. upload.addEventListener('dragover', function (ev) {
  24. ev.preventDefault();
  25. });
  26. upload.addEventListener('drop', function (ev) {
  27. ev.preventDefault();
  28. let file = ev.dataTransfer.files[0];
  29. if (!file) return;
  30. uploadFile(file);
  31. });

大文件上传

  1. //文件重命名
  2. const changeBuffer = file => {
  3. return new Promise(resolve => {
  4. let fileReader = new FileReader();
  5. fileReader.readAsArrayBuffer(file);
  6. fileReader.onload = ev => {
  7. let buffer = ev.target.result,
  8. spark = new SparkMD5.ArrayBuffer(),
  9. HASH,
  10. suffix;
  11. spark.append(buffer);
  12. HASH = spark.end();
  13. suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1];
  14. resolve({
  15. buffer,
  16. HASH,
  17. suffix,
  18. filename: `${HASH}.${suffix}`
  19. });
  20. };
  21. });
  22. };
  23. //文件上传
  24. let file = upload_inp.files[0];
  25. // 获取文件的HASH
  26. let already = [],
  27. data = null,
  28. {
  29. HASH,
  30. suffix
  31. } = await changeBuffer(file);
  32. try {
  33. data = await instance.get('/upload_already', {
  34. params: {
  35. HASH
  36. }
  37. });
  38. if (+data.code === 0) {
  39. console.log('文件信息-------',data,'---------end');
  40. already = data.fileList;
  41. }
  42. } catch (err) { }
  43. // 实现文件切片处理 「固定数量 & 固定大小」
  44. let max = 1024 * 100,
  45. count = Math.ceil(file.size / max),
  46. index = 0,
  47. chunks = [];
  48. if (count > 100) {
  49. max = file.size / 100;
  50. count = 100;
  51. }
  52. while (index < count) {
  53. chunks.push({
  54. file: file.slice(index * max, (index + 1) * max),
  55. filename: `${HASH}_${index + 1}.${suffix}`
  56. });
  57. index++;
  58. }
  59. //上传后成功的回调函数
  60. const complate = async () => {
  61. // 管控进度条
  62. index++;
  63. upload_progress_value.style.width = `${index / count * 100}%`;
  64. // 当所有切片都上传成功,我们合并切片
  65. if (index < count) return;
  66. upload_progress_value.style.width = `100%`;
  67. try {
  68. data = await instance.post('/upload_merge', {
  69. HASH,
  70. count
  71. }, {
  72. headers: {
  73. 'Content-Type': 'application/x-www-form-urlencoded'
  74. }
  75. });
  76. if (+data.code === 0) {
  77. alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`);
  78. clear();
  79. return;
  80. }
  81. throw data.codeText;
  82. } catch (err) {
  83. alert('切片合并失败,请您稍后再试~~');
  84. clear();
  85. }
  86. };
  87. // 把每一个切片都上传到服务器上
  88. chunks.forEach(chunk => {
  89. // 已经上传的无需在上传
  90. if (already.length > 0 && already.includes(chunk.filename)) {
  91. complate();
  92. return;
  93. }
  94. let fm = new FormData;
  95. fm.append('file', chunk.file);
  96. fm.append('filename', chunk.filename);
  97. instance.post('/upload_chunk', fm).then(data => {
  98. if (+data.code === 0) {
  99. complate();
  100. return;
  101. }
  102. return Promise.reject(data.codeText);
  103. }).catch(() => {
  104. alert('当前切片上传失败,请您稍后再试~~');
  105. clear();
  106. });
  107. });