iT邦幫忙

0

利用原生node.js編寫圖片伺服器(含圖片上傳和圖片回傳)

  • 分享至 

  • xImage
  •  

廢話區

寫了一個論壇會用到圖片上傳功能, 本來是採用imgur的api來串接,
但後來發現其實其api有所限制,包含短時間不能傳太多, 甚至一個月不能超過幾張,
雖然數量非常大, 到達極限的可能性很低, 但反正手癢,
於是決定簡單寫一個圖片伺服器來解決

正文

思考何謂網頁伺服器

首先我定義了這傢伙有以下幾個功能

  1. 接收前端傳來的圖片
  2. 儲存前端傳來的圖片
  3. 返還圖片網址
  4. 使圖片網址可以從伺服器取得圖片
    為了達到上述功能,server必須涵蓋以下功能
  • 接收api
  • 存api中的圖片
  • 回傳字串(為圖片網址)
  • 依照圖片網址回傳圖片

決定架構

首先因為我個人比較熟悉node,
所以就直接開始思考要用甚麼node的框架來做,
包含express,Koa,.....等等

其中收api,回傳字串算是比較普遍的功能,所以跳過

接著是圖片存儲以及回傳,這貌似就比較複雜
印象中以前用幾個框架做過
不外乎是引用一個他們公司開發的用於該框架的npm套件,
接著按照文件的說法用個函數,填個設定,填個參數
就可以完成任務。

但問題來了, 我今天只不過是要完成這麼點小事,
有必要引用這些框架來協助開發嗎 ?
感覺結果就是MVC裡,M塞滿了東西,但其他地方空空如也
在強迫症發作下我開始想,
原生node是否也擁有兩個套件來完成對應的這兩件事呢?

一查資料, 還真的有, 我就來用用看吧!

建置

我採用visual studio直接建立一個原生node,
以此避免麻煩的指令輸入。
https://ithelp.ithome.com.tw/upload/images/20200925/20131164YEgpOmLefY.png
->點擊空白node.js web 應用

搭建架構

既然要接收圖片=>post圖片過來吧
既然要讓任何人call到我的圖片=>get我的圖片吧
所以一個post 一個get
順手設定跨域許可,讓別台主機可以call你(屏蔽處是設定只允許誰call你)
post用一下api 看起來比較專業

'use strict';
var http = require('http');
var port = process.env.PORT || 1337;

http.createServer(function (req, res) {
    res.setHeader('Access-Control-Allow-Origin', /***/ '*');
    if (req.method == 'GET') {
        
    }
    else if(req.url == '/getImg' && req.method == 'POST') {
        
    }
}).listen(port);

挑選方案與引用

此處要活用之前框架提供的功能帶給我的經驗
總之就是以下三步

  1. 找有該功能的npm套件裝上
  2. 複製使用該功能的代碼(翻套件github example, 或是別人的教學)
  3. 貼上代碼後略為修改

圖片回傳

我採用 npm install node-static
不廢話,寫程式三步驟=>安裝,複製,貼上
//多引用
var nStatic = require('node-static');
var fileServer = new nStatic.Server('./uploadImg');
//get 裡加
fileServer.serve(req, res);

所以變成以下情況:

'use strict';
var http = require('http');
var nStatic = require('node-static');
//以下決定哪個資料夾當成靜態資料服務器的儲存處,要先建立
var fileServer = new nStatic.Server('./uploadImg');
var port = process.env.PORT || 1337;

http.createServer(function (req, res) {
    res.setHeader('Access-Control-Allow-Origin', /*'140.116.*'*/ '*');
    if (req.method == 'GET') {
        //直接建立靜態server,其可使所有get指令可以直接提取上方選定資料夾中的檔案
        //, 且可包含資料夾做分類
        fileServer.serve(req, res);
    }
    else if(req.url == '/getImg' && req.method == 'POST') {
        
    }
}).listen(port);

圖片儲存

我採用了 formidable 這個npm 套件
其為原生node存圖片的套件
安裝,複製,貼上 三連招一用
//多引用
var formidable = require('formidable');
var path = require('path');//取得副檔名,改存檔時會用到
var fs = require('fs');//改檔名,因為存下來沒副檔名,檔名也是很長的亂碼
//下面加
略,直接看源碼

else if (req.url == '/getImg' && req.method == 'POST') {
        //存下傳來的form
        var form = formidable.IncomingForm();
        // 設定存哪
        form.uploadDir = '.\\uploadImg\\' ;
        form.parse(req, function (err, fields, files) {//存檔
            // all information of picture are in files.file
            //console.log(files)
            if (err) { throw err; }
            // 如果有存到東西
            if (files.file != null) {
                //取得原始下載下來的檔案
                var oldpath = __dirname + "\\" + files.file.path;
                // 取得副檔名,改名用
                var extname = path.extname(files.file.name);
                // 設定新名子為savedata.xxx
                var newpath = __dirname + '\\uploadImg\\savedata' + extname;
                //回傳位置就是新名子的位置 前面是網域
                var returnpath = 'http:\\127.0.0.1:1337\\savedata'  + extname;
                //改名,不然本來是亂碼,也沒有附檔名,根本無法讀
                fs.rename(oldpath, newpath, function (err) {
                    if (err) {//錯誤處理
                        console.log(err.message);
                        res.end("error");
                        throw Error("false");
                    }
                    //回傳get要call的網址
                    res.end(returnpath);
                });
            }
            else
                res.end("error");
        });
    }

進階功能

到上面為止已經完成基本目的,但事實是上面的代碼根本不能用,原因有以下幾點

  1. 檔名變更後會重複
  2. 只有一個資料夾,到後來圖片取得會越來越慢
    為了解決以上兩個問題要使用以下兩個方法
  • 在新檔名中加入日期,避免重複
  • 建立多個子資料夾,隨機進入一個資料夾
    因此最終的代碼為:
'use strict';
var http = require('http');
var formidable = require('formidable');
var path = require('path');
var fs = require('fs');
var nStatic = require('node-static');
var fileServer = new nStatic.Server('./uploadImg');
var port = process.env.PORT || 1337;
//共有幾個子資料夾 須先建立,名稱為:0,1,2,3,.....16
const hashnumber = 17;
//伺服器所在網域
const baseURL = 'http://localhost:1337';

http.createServer(function (req, res) {
    res.setHeader('Access-Control-Allow-Origin', /*'140.116.*'*/ '*');
    if (req.method == 'GET') {
        fileServer.serve(req, res);
    }
    else if (req.url == '/getImg' && req.method == 'POST') {
        var form = formidable.IncomingForm();
        //隨機數取餘數,得知檔案被分配到哪個資料夾
        var random = parseInt(Math.random() * 10000);
        var place = (random % hashnumber).toString();
        form.uploadDir = '.\\uploadImg\\' + place;
        form.parse(req, function (err, fields, files) {
            if (err) { throw err; }
            if (files.file != null) {
                var oldpath = __dirname + "\\" + files.file.path;
                //  取得時間,等下加到新檔名
                var time = +new Date();
                var extname = path.extname(files.file.name);
                // 新檔名的path有多分配到的子資料夾,和時間,隨機數,副檔名組成的名稱
                var newpath = __dirname + '\\uploadImg\\' + place + '\\' + time + random + extname;
                //回傳字串當然要包含子資料夾和新名稱
                var returnpath = '\\' + place + '\\' + time + random + extname;
                fs.rename(oldpath, newpath, function (err) {
                    if (err) {
                        console.log(err.message);
                        res.end("error");
                        throw Error("false");
                    }
                    res.end(baseURL + returnpath);
                });
            }
            else
                res.end("error");
        });
    }
}).listen(port);

前端寫法補充

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <title></title>
</head>
<body>
    <input type="file" />
    <script>
	// api
    var api = 'http://127.0.0.1:1337/getImg';
	$("document").ready(function () {
		$('input[type=file]').on("change", function () {

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

			if ($files.length) {

				// Reject big files
				if ($files[0].size > $(this).data("max-size") * 1024) {
					console.log("Please select a smaller file");
					return false;
				}
                var formData = new FormData();
                formData.append('file', $files[0]);
				var settings = {
					"async": true,
					"crossDomain": true,
					"url": api,
					"type": "POST",
                    'contentType': false, //required
                    'processData': false, // required
                    'mimeType': 'multipart/form-data',
                    'data': formData,
					beforeSend: function (xhr) {
					},
					success: function (res) {
						console.log(res);
					},
					error: function () {
						alert("Failed | 上傳失敗");
					}
				}
				$.ajax(settings).done(function (response) {
					console.log("Done | 完成");
				});
			}
		});
	});
    </script>
</body>
</html>

用到的npm套件

https://ithelp.ithome.com.tw/upload/images/20200926/20131164J7xkbVgwkN.png

完成

建好擺資料的資料夾後,node server.js了一下
https://ithelp.ithome.com.tw/upload/images/20200926/20131164nlnNrjvmCa.png
順利完成了


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
arguskao
iT邦新手 3 級 ‧ 2022-11-17 08:37:37

剛好是我需要的,謝謝!

很高興可以幫到忙

我要留言

立即登入留言