整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          css input[type=file]樣式美化

          nput[type=file]是上傳文件離不開的標(biāo)簽元素,但是默認(rèn)樣式太丑,完全不符合前端人對審美的需求,今天就來教大家如何改造默認(rèn)樣式。

          首先給input[type=file]標(biāo)簽包裝一個外圍容器,這里我使用span元素,當(dāng)然其他元素也可以。


          代碼如下:

          <span id="uploadBtn">選擇文件

          <input type="file" class="file">

          </span>


          接下來的事情交給css來搞定

          代碼如下:

          #uploadBtn{

          display:inline-block;

          position: relative;

          color: #fff;

          background-color: #1989fa;

          border-color: #1989fa;

          padding: 0 30px;

          font-size: 14px;

          line-height: 42px;

          font-weight: 500;

          -webkit-transition: all .15s;

          transition: all .15s;

          padding: 0 15px;

          line-height: 32px;

          border-radius: 4px;

          }

          .file{

          position: absolute;

          width: 100%;

          height: 100%;

          left: 0;

          top: 0;

          opacity: 0;

          }


          接下來就是效果展示了:

          大功告成,快去嘗試吧~


          者:藍(lán)色的秋風(fēng)

          轉(zhuǎn)發(fā)鏈接:https://mp.weixin.qq.com/s/cruL9JGZNZQFrMSrzJJWiQ

          近期的項目里使用了這樣一個項目架構(gòu): 前端 -> nodejs -> java

          • 前端負(fù)責(zé)實現(xiàn)業(yè)務(wù)邏輯的展示和交互
          • nodejs 包括維護(hù)某些數(shù)據(jù)和接口轉(zhuǎn)發(fā)
          • java 負(fù)責(zé)維護(hù)剩下的數(shù)據(jù)

          在 nodejs 的接口轉(zhuǎn)發(fā)中攔截一部分接口,再對請求的方法進(jìn)行區(qū)分,請求后臺數(shù)據(jù)后,再進(jìn)行返回。現(xiàn)有的接口中基本只用到了 get 和 post 兩種,但是在文件上傳的時候遇到了問題。

          node 層使用 eggjs ,一般的 post 的請求直接在 ctx.body 就能拿到請求的參數(shù),但是 /upload 的接口就不行,拿到的 body 是 {} ,下面我們來逐步分析。

          js 中的文件

          web 中的 Blob 、File 和 Formdate

          一個 Blob ( Binary Large Object ) 對象表示一個不可變的, 原始數(shù)據(jù)的類似文件對象。Blob表示的數(shù)據(jù)不一定是一個JavaScript原生格式。 File 接口基于Blob,繼承 Blob 功能并將其擴(kuò)展為支持用戶系統(tǒng)上的文件。

          前端上傳文件的方式無非就是使用:1、表單自動上傳;2、使用 ajax 上傳。我們可以使用以下代碼創(chuàng)建一個 Form,并打印出 file

          <form method="POST" id="uploadForm" enctype="multipart/form-data">
           <input type="file" id="file" name="file" />
          </form>
          <button id="submit">submit</button>
          <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
          <script>
           $("#submit").click(function() {
           console.log($("#file")[0].files[0])
           });
          </script>
          復(fù)制代碼
          

          從 F12 中可以看出 File 原型鏈上是 Blob。簡單地說 Blob 可以理解為 Web 中的二進(jìn)制文件。 而 File 是基于 Blob 實現(xiàn)的一個類,新增了關(guān)于文件有關(guān)的一些信息。

          FormData 對象的作用就類似于 Jq 的 serialize() 方法,不過 FormData 是瀏覽器原生的,且支持二進(jìn)制文件。 ajax 通過 FormData 這個對象發(fā)送表單請求,無論是原生的 XMLHttpRequest 、jq 的 ajax 方法、 axios 都是在 data 里直接指定上傳 formData 類型的數(shù)據(jù),fetch api 是在 body 里上傳。

          forData 數(shù)據(jù)有兩種方式生成,如下 formData 和 formData2 的區(qū)別,而 formData2 可以通過傳入一個 element 的方式進(jìn)行初始化,初始化之后依然可以調(diào)用 formData 的 append 方法。

          <!DOCTYPE html>
          <html>
           <form method="POST" id="uploadForm" name="uploadFormName" enctype="multipart/form-data">
           <input type="file" id="fileImag" name="configFile" />
           </form>
           <div id="show"></div>
           <button id="submit">submit</button>
           <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
          </html>
          <script>
           $("#submit").click(function() {
           const file = $("#fileImag")[0].files[0];
           
           const formData = new FormData();
           
           formData.append("fileImag", file);
           console.log(formData.getAll("fileImag"));
           const formData2 = new FormData(document.querySelector("#uploadForm"));
           // const formData2 = new FormData(document.forms.namedItem("uploadFormName"););
           console.log(formData2.get("configFile"));
           
           });
          </script>
          復(fù)制代碼
          

          node 中的 Buffer 、 Stream 、fs

          Buffer 和 Stream 是 node 為了讓 js 在后端擁有處理二進(jìn)制文件而出現(xiàn)的數(shù)據(jù)結(jié)構(gòu)。

          通過名字可以看出 buffer 是緩存的意思。存儲在內(nèi)存當(dāng)中,所以大小有限,buffer 是 C++ 層面分配的,所得內(nèi)存不在 V8 內(nèi)。

          stream 可以用水流形容數(shù)據(jù)的流動,在文件 I/O、網(wǎng)絡(luò) I/O中數(shù)據(jù)的傳輸都可以稱之為流。

          通過兩個 fs 的 api 看出,readFile 不指定字符編碼默認(rèn)返回 buffer 類型,而 createReadStream 將文件轉(zhuǎn)化為一個 stream , nodejs 中的 stream 通過 data 事件能夠一點一點地拿到文件內(nèi)容,直到 end 事件響應(yīng)為止。

          const fs = require("fs");
          fs.readFile("./package.json", function(err, buffer) {
           if (err) throw err;
           console.log("buffer", buffer);
          });
          function readLines(input, func) {
           var remaining = "";
           input.on("data", function(data) {
           remaining += data;
           var index = remaining.indexOf("\n");
           var last = 0;
           while (index > -1) {
           var line = remaining.substring(last, index);
           last = index + 1;
           func(line);
           index = remaining.indexOf("\n", last);
           }
           remaining = remaining.substring(last);
           });
           input.on("end", function() {
           if (remaining.length > 0) {
           func(remaining);
           }
           });
          }
          function func(data) {
           console.log("Line: " + data);
          }
          var input = fs.createReadStream("./package.json");
          input.setEncoding("binary");
          readLines(input, func);
          復(fù)制代碼
          

          fs.readFile() 函數(shù)會緩沖整個文件。 為了最小化內(nèi)存成本,盡可能通過 fs.createReadStream() 進(jìn)行流式傳輸。

          使用 nodejs 創(chuàng)建 uoload api

          http 協(xié)議中的文件上傳

          在 http 的請求頭中 Content-type 是 multipart/form-data 時,請求的內(nèi)容如下:

          POST / HTTP/1.1
          Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoMwe4OxVN0Iuf1S4
          Origin: http://localhost:3000
          Referer: http://localhost:3000/upload
          Sec-Fetch-Mode: navigate
          Sec-Fetch-User: ?1
          Upgrade-Insecure-Requests: 1
          User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
          ------WebKitFormBoundaryoqBx9oYBhx4SF1YQ
          Content-Disposition: form-data; name="upload"
          http://localhost:3000
          ------WebKitFormBoundaryoMwe4OxVN0Iuf1S4
          Content-Disposition: form-data; name="upload"; filename="IMG_9429.JPG"
          Content-Type: image/jpeg
          ????JFIF??C // 文件的二進(jìn)制數(shù)據(jù)
          ……
          --------WebKitFormBoundaryoMwe4OxVN0Iuf1S4--
          復(fù)制代碼
          

          根據(jù) WebKitFormBoundaryoMwe4OxVN0Iuf1S4 可以分割出文件的二進(jìn)制內(nèi)容

          原生 node

          使用原生的 node 寫一個文件上傳的 demo

          const http = require("http");
          const fs = require("fs");
          const util = require("util");
          const querystring = require("querystring");
          //用http模塊創(chuàng)建一個http服務(wù)端
          http
           .createServer(function(req, res) {
           if (req.url == "/upload" && req.method.toLowerCase() === "get") {
           
           //顯示一個用于文件上傳的form
           res.writeHead(200, { "content-type": "text/html" });
           res.end(
           '<form action="/upload" enctype="multipart/form-data" method="post">' +
           '<input type="file" name="upload" multiple="multiple" />' +
           '<input type="submit" value="Upload" />' +
           "</form>"
           );
           } else if (req.url == "/upload" && req.method.toLowerCase() === "post") {
           if (req.headers["content-type"].indexOf("multipart/form-data") !== -1)
           parseFile(req, res);
           } else {
           res.end("pelease upload img");
           }
           })
           .listen(3000);
          function parseFile(req, res) {
           req.setEncoding("binary");
           let body = ""; // 文件數(shù)據(jù)
           let fileName = ""; // 文件名
           
           // 邊界字符串 ----WebKitFormBoundaryoMwe4OxVN0Iuf1S4
           const boundary = req.headers["content-type"]
           .split("; ")[1]
           .replace("boundary=", "");
           
           
           req.on("data", function(chunk) {
           body += chunk;
           });
           req.on("end", function() {
           const file = querystring.parse(body, "\r\n", ":");
           // 只處理圖片文件;
           if (file["Content-Type"].indexOf("image") !== -1) {
           //獲取文件名
           var fileInfo = file["Content-Disposition"].split("; ");
           for (value in fileInfo) {
           if (fileInfo[value].indexOf("filename=") != -1) {
           fileName = fileInfo[value].substring(10, fileInfo[value].length - 1);
           if (fileName.indexOf("\\") != -1) {
           fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
           }
           console.log("文件名: " + fileName);
           }
           }
           // 獲取圖片類型(如:image/gif 或 image/png))
           const entireData = body.toString();
           const contentTypeRegex = /Content-Type: image\/.*/;
           contentType = file["Content-Type"].substring(1);
           //獲取文件二進(jìn)制數(shù)據(jù)開始位置,即contentType的結(jié)尾
           const upperBoundary = entireData.indexOf(contentType) + contentType.length;
           const shorterData = entireData.substring(upperBoundary);
           // 替換開始位置的空格
           const binaryDataAlmost = shorterData
           .replace(/^\s\s*/, "")
           .replace(/\s\s*$/, "");
           // 去除數(shù)據(jù)末尾的額外數(shù)據(jù),即: "--"+ boundary + "--"
           const binaryData = binaryDataAlmost.substring(
           0,
           binaryDataAlmost.indexOf("--" + boundary + "--")
           );
           // console.log("binaryData", binaryData);
           const bufferData = new Buffer.from(binaryData, "binary");
           console.log("bufferData", bufferData);
           // fs.writeFile(fileName, binaryData, "binary", function(err) {
           // res.end("sucess");
           // });
           fs.writeFile(fileName, bufferData, function(err) {
           res.end("sucess");
           });
           } else {
           res.end("reupload");
           }
           });
          }
          復(fù)制代碼
          
          • 通過 req.setEncoding("binary"); 拿到圖片的二進(jìn)制數(shù)據(jù)。可以通過以下兩種方式處理二進(jìn)制數(shù)據(jù),寫入文件。
          fs.writeFile(fileName, binaryData, "binary", function(err) {
           res.end("sucess");
          });
          復(fù)制代碼
          fs.writeFile(fileName, bufferData, function(err) {
           res.end("sucess");
          });
          復(fù)制代碼
          

          koa

          在 koa 中使用 koa-body 可以通過 ctx.request.files 拿到上傳的 file 對象。下面是例子。

          'use strict';
          const Koa = require('koa');
          const app = new Koa();
          const router = require('koa-router')();
          const koaBody = require('../index')({multipart:true});
          router.post('/users', koaBody,
           (ctx) => {
           console.log(ctx.request.body);
           // => POST body
           ctx.body = JSON.stringify(ctx.request.body, null, 2);
           }
          );
          router.get('/', (ctx) => {
           ctx.set('Content-Type', 'text/html');
           ctx.body = `
          <!doctype html>
          <html>
           <body>
           <form action="/" enctype="multipart/form-data" method="post">
           <input type="text" name="username" placeholder="username"><br>
           <input type="text" name="title" placeholder="tile of film"><br>
           <input type="file" name="uploads" multiple="multiple"><br>
           <button type="submit">Upload</button>
           </body>
          </html>`;
          });
          router.post('/', koaBody,
           (ctx) => {
           console.log('fields: ', ctx.request.body);
           // => {username: ""} - if empty
           console.log('files: ', ctx.request.files);
           /* => {uploads: [
           {
           "size": 748831,
           "path": "/tmp/f7777b4269bf6e64518f96248537c0ab.png",
           "name": "some-image.png",
           "type": "image/png",
           "mtime": "2014-06-17T11:08:52.816Z"
           },
           {
           "size": 379749,
           "path": "/tmp/83b8cf0524529482d2f8b5d0852f49bf.jpeg",
           "name": "nodejs_rulz.jpeg",
           "type": "image/jpeg",
           "mtime": "2014-06-17T11:08:52.830Z"
           }
           ]}
           */
           ctx.body = JSON.stringify(ctx.request.body, null, 2);
           }
          )
          app.use(router.routes());
          const port = process.env.PORT || 3333;
          app.listen(port);
          console.log('Koa server with `koa-body` parser start listening to port %s', port);
          console.log('curl -i http://localhost:%s/users -d "user=admin"', port);
          console.log('curl -i http://localhost:%s/ -F "source=@/path/to/file.png"', port);
          復(fù)制代碼
          

          我們來看一下 koa-body 的實現(xiàn)

          const forms = require('formidable');
          function requestbody(opts) {
           opts = opts || {};
           ...
           opts.multipart = 'multipart' in opts ? opts.multipart : false;
           opts.formidable = 'formidable' in opts ? opts.formidable : {};
           ...
           // @todo: next major version, opts.strict support should be removed
           if (opts.strict && opts.parsedMethods) {
           throw new Error('Cannot use strict and parsedMethods options at the same time.')
           }
           if ('strict' in opts) {
           console.warn('DEPRECATED: opts.strict has been deprecated in favor of opts.parsedMethods.')
           if (opts.strict) {
           opts.parsedMethods = ['POST', 'PUT', 'PATCH']
           } else {
           opts.parsedMethods = ['POST', 'PUT', 'PATCH', 'GET', 'HEAD', 'DELETE']
           }
           }
           opts.parsedMethods = 'parsedMethods' in opts ? opts.parsedMethods : ['POST', 'PUT', 'PATCH']
           opts.parsedMethods = opts.parsedMethods.map(function (method) { return method.toUpperCase() })
           return function (ctx, next) {
           var bodyPromise;
           // only parse the body on specifically chosen methods
           if (opts.parsedMethods.includes(ctx.method.toUpperCase())) {
           try {
           if (opts.json && ctx.is(jsonTypes)) {
           bodyPromise = buddy.json(ctx, {
           encoding: opts.encoding,
           limit: opts.jsonLimit,
           strict: opts.jsonStrict,
           returnRawBody: opts.includeUnparsed
           });
           } else if (opts.multipart && ctx.is('multipart')) {
           bodyPromise = formy(ctx, opts.formidable);
           }
           } catch (parsingError) {
           if (typeof opts.onError === 'function') {
           opts.onError(parsingError, ctx);
           } else {
           throw parsingError;
           }
           }
           }
           bodyPromise = bodyPromise || Promise.resolve({});
           
          /**
           * Check if multipart handling is enabled and that this is a multipart request
           *
           * @param {Object} ctx
           * @param {Object} opts
           * @return {Boolean} true if request is multipart and being treated as so
           * @api private
           */
          function isMultiPart(ctx, opts) {
           return opts.multipart && ctx.is('multipart');
          }
          /**
           * Donable formidable
           *
           * @param {Stream} ctx
           * @param {Object} opts
           * @return {Promise}
           * @api private
           */
          function formy(ctx, opts) {
           return new Promise(function (resolve, reject) {
           var fields = {};
           var files = {};
           var form = new forms.IncomingForm(opts);
           form.on('end', function () {
           return resolve({
           fields: fields,
           files: files
           });
           }).on('error', function (err) {
           return reject(err);
           }).on('field', function (field, value) {
           if (fields[field]) {
           if (Array.isArray(fields[field])) {
           fields[field].push(value);
           } else {
           fields[field] = [fields[field], value];
           }
           } else {
           fields[field] = value;
           }
           }).on('file', function (field, file) {
           if (files[field]) {
           if (Array.isArray(files[field])) {
           files[field].push(file);
           } else {
           files[field] = [files[field], file];
           }
           } else {
           files[field] = file;
           }
           });
           if (opts.onFileBegin) {
           form.on('fileBegin', opts.onFileBegin);
           }
           form.parse(ctx.req);
           });
          }
          復(fù)制代碼
          

          代碼中刪除了影響有關(guān)文件上傳的相關(guān)邏輯

          • 首先 multipart 為 true 是開啟文件上傳的關(guān)鍵。
          • 然后 formy 函數(shù)處理了 http 解析和保存的一系列過程,最終將 files 拋出進(jìn)行統(tǒng)一處理。代碼中依賴了 formidable 這個庫,我們其實也可以直接使用這個庫對文件進(jìn)行處理。(上面的原生 node upload 只是簡單地處理了一下)
          • opts.formidable 是 formidable 的 config 可以設(shè)置文件大小,保存的文件路徑等等。

          eggjs

          使用 eggjs 進(jìn)行文件上傳需要現(xiàn)在配置文件中開啟

           config.multipart = { mode: "file", fileSize: "600mb" };
          復(fù)制代碼
          

          然后通過 ctx.request.files[0] 就能取到文件信息。

          文件上傳接口的轉(zhuǎn)發(fā)

          一千個觀眾眼中有一千個哈姆雷特,通過以上知識點的梳理,我相信你也有了自己得想法。在這里說一下我是怎么處理的。 在 egg 中我使用了 request-promise 去做接口轉(zhuǎn)發(fā),通過查看 api 和 ctx.request.files[0] 拿到的信息,我做了以下處理。

           if (method === "POST") {
           options.body = request.body;
           options.json = true;
           if (url === uploadeUrl) {
           delete options.body;
           options.formData = {
           // Like <input type="text" name="name">
           name: "file",
           // Like <input type="file" name="file">
           file: {
           value: fs.createReadStream(ctx.request.files[0].filepath),
           options: {
           filename: ctx.request.files[0].filename,
           contentType: ctx.get("content-type")
           }
           }
           };
           }
           } else {
           options.qs = query;
           }
          復(fù)制代碼
          

          總結(jié)

          • http 中的文件上傳第一步就是設(shè)置 Content-type 為 multipart/form-data 的 header。
          • 區(qū)分好 web 端 js 和 node 端處理文件的方式有所不同。
          • 有些 npm 模塊的 readme 并不是很清晰,可以直接下源碼去看 example ,或者直接讀源碼,就比如 koa-body 中 formidable 的用法并未在他的 reademe 中寫出,直接看源碼是你會發(fā)現(xiàn)更多用法。
          • 文中的知識點很多知識稍微提及,可以進(jìn)一步深入了解與他相關(guān)的知識。
          • 最后如果文中有任何錯誤和疑問請及時指出。

          主站蜘蛛池模板: 韩国精品一区视频在线播放| 国产成人精品一区二三区在线观看| 久久精品国产一区二区电影| 亚洲一区二区三区高清在线观看 | 亚洲毛片αv无线播放一区| 蜜桃AV抽搐高潮一区二区| 日本一区二区三区在线视频观看免费 | 无码一区二区波多野结衣播放搜索| 国产高清不卡一区二区| 色久综合网精品一区二区| 国产精品自拍一区| 国产免费一区二区三区| 在线免费一区二区| 日韩亚洲一区二区三区| 国产高清一区二区三区| 丝袜人妻一区二区三区网站| 国产精品无码亚洲一区二区三区| 国产在线视频一区二区三区 | 亚洲熟妇无码一区二区三区| 日韩精品免费一区二区三区 | 韩国福利影视一区二区三区| 国产主播在线一区| 一区二区三区四区电影视频在线观看| 国产精品被窝福利一区| 玩弄放荡人妻一区二区三区| 国产成人一区在线不卡| 亚洲国产精品一区第二页| 亚洲乱色熟女一区二区三区蜜臀| 国产福利电影一区二区三区久久久久成人精品综合 | 伊人久久精品一区二区三区| 国产av福利一区二区三巨| 福利一区二区在线| 欧洲精品一区二区三区在线观看| 日本精品一区二区三本中文| 成人精品一区二区三区电影 | 无码国产精品一区二区免费I6| 久久er99热精品一区二区| 99精品国产一区二区三区不卡| 91香蕉福利一区二区三区| 人妻体内射精一区二区三四| 在线观看午夜亚洲一区|