1.
第1章 模塊化編程 2019.2.19 13:30'
1.1.
1.1初識模塊化編程
1.1.1.
模塊化是一種設計思想,把一個很是複雜的系統結構細化到具體的功能點,每一個功能點看作一個模塊,而後經過某種規則把這些小的模塊組合到一塊兒,構成模塊化系統。
1.1.2.
模塊化編程可有效解決命名衝突問題和文件依賴的關係和順序問題。
1.2.
1.2模塊化編程的演變
1.2.1.
1.2.1全局函數
1.2.1.1.
//定義用於計算的函數:全部的變量和函數都暴露在全局,沒法保證全局變量與其餘模塊的變量發生衝突,也看不出全局變量與模塊成員之間的直接關係。 function add(x, y) { return parseInt(x) + parseInt(y); } function subtract(x, y) { return parseInt(x) - parseInt(y); } function multiply(x, y) { return parseInt(x) * parseInt(y); } function divide(x, y) { return parseInt(x) / parseInt(y); } //引用:result = add(x, y);//加
1.2.2.
1.2.2對象的命名空間
1.2.2.1.
/* * 對象命名空間:內部成員的狀態能夠隨意被外部改寫,不安全。代碼可讀性隨着子命名空間延長可讀性差。 * 只是從理論意義上減小了命名衝突的問題,可是命名衝突仍是存在 */ var calculator = {}; //加法 calculator.add = function(x, y) { return parseInt(x) + parseInt(y); } //減法 calculator.subtract = function(x, y) { return parseInt(x) - parseInt(y); } //乘法 calculator.multiply = function(x, y) { return parseInt(x) * parseInt(y); } //除法 calculator.divide = function(x, y) { return parseInt(x) / parseInt(y); } //引用:result = calculator.add(x, y);
1.2.3.
1.2.3函數的做用域(閉包)
1.2.3.1.
/*函數的做用域(閉包):經過封裝函數的私有空間可讓一些屬性和方法私有化。經過匿名自執行函數,進行私有變量隔離。 利用匿名自執行函數造成的封閉的函數做用域空間,達到自優化的目的*/ var calculator = ( function () { function add(x,y) { return parseInt(x)+parseInt(y); } function subtract(x,y) { return parseInt(x)-parseInt(y); } function multiply(x,y) { return parseInt(x)*parseInt(y); } function divide(x,y) { return parseInt(x)/parseInt(y); } return { add:add, subtract:subtract, multiply:multiply, divide:divide } })(); //引用與命名空間相同:result=calculator.add(x,y);
1.2.4.
1.2.4維護和擴展
1.2.4.1.
//若是有第三方依賴的時候,可經過參數的形式將原來的模塊和第三方庫傳遞進去。 //傳遞參數cal var calculator = (function(cal) { //加法 function add(x, y) { return parseInt(x) + parseInt(y); } // 減法 function subtract(x, y) { return parseInt(x) - parseInt(y); } //乘法 function multiply(x, y) { return parseInt(x) * parseInt(y); } //除法 function divide(x, y) { return parseInt(x) / parseInt(y); } cal.add = add; cal.subtract = subtract; cal.multiply = multiply; cal.divide = divide; return cal; })(calculator || {}); //當擴展該模塊時,優先查找要擴展的對象是否已存在 // 從代碼上來看:下面的 calculator 已經把上面的 calculator 給覆蓋掉了 // 注意:在進行擴展的時候,優先查找要擴展的對象是否已存在 // 若是已存在,就使用已經存在的 // 若是不存在,就建立一個新的 // 最大的好處:加載的時候不用考慮順序了 var calculator = (function(cal) { //取餘方法 cal.mod = function(x, y) { return x % y; } return cal; })(calculator || {}); //引用:result = calculator.add(x, y);
2.
第2章 初識Node.js 2019.2.22 17:30'
2.1.
2.1 Node.js概述
2.1.1.
有了Node.js,用JavaScript既能夠客戶端開發,又能夠服務器端開發,還能夠與數據庫交互。減小學習成本,快速打造全棧工程師。
2.1.2.
客戶端將用戶請求發送給服務器端。服務器端根據用戶的請求進行邏輯處理、數據處理並將結果響應給客戶端。如今,用Node.js來代替傳統的服務器語言,開發服務器端的Web框架。
2.1.3.
JavaScript是一種腳本語言,通常運行在客戶端,而Node.js可以使JavaScript運行在服務器端。
2.1.4.
JavaScript組成
2.1.4.1.
核心語法是ECMAScript
2.1.4.2.
DOM是HTML的應用程序接口,是文檔對象模型
2.1.4.3.
BOM是瀏覽器對象模型,能夠對瀏覽器窗口進行訪問和操做
2.1.5.
JavaScript做用
2.1.5.1.
在客戶端主要用來處理頁面交互
2.1.5.1.1.
解析:依賴瀏覽器提供的JavaScript引擎解析執行
2.1.5.1.2.
操做對象:對瀏覽器提供的DOM、BOM的解析進行操做
2.1.5.1.3.
常見操做:用戶交互、動畫特效、表單驗證、Ajax請求等
2.1.5.2.
在服務器端主要用來處理數據交互
2.1.5.2.1.
解析:由特定的JavaScript引擎解析執行,如Node.js
2.1.5.2.2.
G不依賴瀏覽器,不操做DOM和BOM。
2.1.5.2.3.
主要操做:客戶端作不到的事情,如操做數據庫和文件等
2.2.
2.2 Node.js簡介
2.2.1.
概念
2.2.1.1.
在服務器端的運行環境或運行時平臺
2.2.1.2.
解析和執行JavaScript代碼
2.2.1.3.
提供一些功能性的API,如文件操做和網絡通訊API等
2.2.1.4.
2009年5月由RyanDahl把Chrome的V8引擎移植出來,在其上加上API
2.2.2.
特色和優點
2.2.2.1.
它是一個JavaScript運行環境,可脫離瀏覽器在服務器端單獨執行,代碼可共用。
2.2.2.2.
依賴ChromeV8引擎,在非瀏覽器下解析JavaScript代碼
2.2.2.3.
事件驅動Event-Driven
2.2.2.4.
非阻塞I/O(non-blocking I/O):使用事件回調的方式避免阻塞I/O所需的等待
2.2.2.5.
輕量、可伸縮,適用於實時數據交互,Socket可實現雙向通訊
2.2.2.6.
單進程單線程,異步編程模式,實現非阻塞I/O
2.3.
2.3 Node.js安裝和配置
2.3.1.
下載和安裝
2.3.2.
CMD命令臺
2.3.3.
Path環境變量
2.3.4.
快速體驗Node.js
2.3.4.1.
輸出內容到終端: 創建文件:demo2-1.js console.log('hello world'); 執行: node demo2-1.js
2.3.4.2.
輸出內容到網頁: 創建文件demo2-2.js //加載http模塊 var http = require('http'); //建立http服務器 http.createServer(function(req, res) { //響應結束 res.end('hello world'); //監聽網址127.0.0.1 端口號3000 }).listen(3000,'127.0.0.1'); 在命令行執行:node demo2-2.js 光標閃爍.... 打開瀏覽器,輸入網址:http://127.0.0.1:3000 便會看到輸出的內容。
2.4.
2.4 Node.js基礎入門
2.4.1.
REPL運行環境
2.4.1.1.
node 【Enter】 >
2.4.1.2.
打開Chrome,按【F12】打開Console控制檯 >
2.4.2.
global對象和模塊做用域
2.4.2.1.
//demo2-3.js global對象和模塊的做用域 var foo = 'bar'; console.log(foo); //global對象這時是沒有foo屬性的 console.log('global:foo '+global.foo); //爲global對象掛載一個foo變量,並將該文件模塊中foo的值賦值給它 global.foo = foo; //這是global.foo的值爲'bar' console.log('global:foo '+global.foo);
2.4.2.2.
//require()、exports、module exports
2.4.2.2.1.
//require()、exports、module exports //require()從外部獲取一個模塊的接口./是相對路徑,默認js文件 //demo2-4.js //加載模塊 var myModule = require('./info'); console.log(myModule); //輸出模塊中的變量值 console.log('name:'+myModule.name); console.log('type:'+myModule.type); console.log('age:'+myModule.age); //調用模塊的方法 myModule.sayHello(); //info.js被加載模塊 //向外暴漏變量name exports.name = 'itcast'; exports.type='edu'; //向外暴漏變量age module.exports.age='10'; //向外暴漏函數 module.exports.sayHello= function () { console.log('hello'); }
2.4.2.2.2.
//demo2-5.js //加載模塊 var myModule = require('./test'); console.log(myModule); //輸出數組長度 console.log('length:'+myModule.length); //test.js被加載模塊 //使用module.exports能夠單獨定義數組,併成功對外開放 // module.exports=['name','type','age']; //使用exports不能單獨定義 exports=['name','type','age'];
2.4.3.
全局可用變量、函數和對象
2.4.3.1.
Node.js v10.15.1 Documentation Table of Contents Global Objects ******************************** Class: Buffer __dirname __filename clearImmediate(immediateObject) clearInterval(intervalObject) clearTimeout(timeoutObject) console exports global module process require() setImmediate(callback[, ...args]) setInterval(callback, delay[, ...args]) setTimeout(callback, delay[, ...args]) URL URLSearchParams WebAssembly
2.4.3.2.
_dirname和_filename變量
2.4.3.2.1.
// 輸出全局變量 __dirname 的值 console.log('文件的目錄是:'+ __dirname ); // 輸出全局變量 __filename 的值 console.log('文件的絕對路徑是:'+__filename );
2.4.3.3.
全局函數
2.4.3.3.1.
setImmediate(callback[, ...args]) setInterval(callback, delay[, ...args]) setTimeout(callback, delay[, ...args])
clearImmediate(immediateObject)
clearInterval(intervalObject)
clearTimeout(timeoutObject)
2.4.3.4.
console對象
2.4.3.4.1.
console.log([data][, ...args])
2.4.3.4.2.
onsole.info([data][, ...args])
2.4.3.4.3.
console.error([data][, ...args])
2.4.3.4.4.
console.dir(obj[, options])
2.4.3.4.5.
console.time([label])
2.4.3.4.6.
console.timeEnd([label])
2.4.3.4.7.
console.trace([message][, ...args])
2.4.3.4.8.
console.assert(value[, ...message])
2.4.4.
Node.js模塊化重寫計算器案例
2.4.4.1.
add.js
2.4.4.1.1.
//加法 module.exports = function (x, y) { return parseInt(x) + parseInt(y) }
2.4.4.2.
subtract.js
2.4.4.2.1.
//減法 module.exports = function (x, y) { return parseInt(x) - parseInt(y) }
2.4.4.3.
multiply.js
2.4.4.3.1.
//乘法 module.exports = function (x, y) { return parseInt(x) * parseInt(y) }
2.4.4.4.
divide.js
2.4.4.4.1.
//除法 module.exports = function (x, y) { return parseInt(x) / parseInt(y) }
2.4.4.5.
index.js
2.4.4.5.1.
//入口模塊 module.exports = { add: require('./add'), subtract: require('./subtract'), multiply: require('./multiply'), divide: require('./divide') }
2.4.4.6.
testCal.js
2.4.4.6.1.
//測試計算器功能 var cal = require('./index'); //在終端輸出計算結果 console.log(cal.add(1, 2)); // => 3 console.log(cal.subtract(1, 2)) ;// => -1 console.log(cal.multiply(1, 2)); // => 2 console.log(cal.divide(1, 2)) ;// => 0.5
2.4.4.7.
node testCal.js
2.4.5.
require()模塊的加載規則
2.4.5.1.
文件模塊的加載
2.4.5.1.1.
/開頭爲根路徑 ./../爲相對路徑 .js擴展名可不加 查找順序.js.json.node
2.4.5.2.
核心模塊的加載
2.4.5.2.1.
Node.js提供的基本API 保存在lib目錄的源碼文件 可直接加載,不用路徑
全局對象
經常使用工具
事件機制
文件訪問系統
HTTP服務器與客戶端
2.4.5.2.2.
//demo2-7.js // 核心模塊就是一個固定標識 // 若是寫錯,就沒法加載 var os = require('os'); //輸出CPU信息 console.log(os.cpus());
2.4.6.
模塊的緩存 require.cache
2.4.6.1.
foo.js
2.4.6.1.1.
console.log("foo模塊被加載了"); //清除緩存 delete require.cache[module.filename] ;
2.4.6.2.
demo2-7.js
2.4.6.2.1.
// 對於同一個模塊標識,node 在第一次加載完成以後就會緩存該模塊 // 下次繼續加載該模塊的時候,直接從緩存中獲取 require('./foo'); require('./foo'); require('./foo'); require('./foo');
3.
第3章 異步編程和包資源管理 2019.2.22 21.30'
3.1.
異步編程
3.1.1.
同步和異步
3.1.1.1.
/** * 同步代碼 */ console.log('起牀'); console.log('背單詞'); //吃早餐 function eatBreakfast() { console.log('早餐吃完了'); } eatBreakfast(); console.log('去上學');
3.1.1.2.
/** * 異步代碼 */ console.log('起牀'); console.log('背單詞'); function eatBreakfast() { console.log('開始吃早餐了'); // setTimeout 執行的時候,不會阻塞後面代碼的繼續執行 setTimeout(function () { //異步函數 console.log('早餐吃完了'); }, 0); } eatBreakfast() console.log('去上學');
3.1.2.
回調函數
3.1.2.1.
概念:是指函數能夠被傳遞到 另外一個函數中,而後被調用的形式。
3.1.2.2.
同步代碼中使用 try...catch處理異常
3.1.2.2.1.
/** * 同步代碼處理異常 */ function parseJsonStrToObj(str) { return JSON.parse(str) } // 對於同步代碼,咱們可使用 try-catch 來捕獲代碼執行可能出現的異常 try { var obj = parseJsonStrToObj('foo') console.log(obj) } catch (e) { console.log('轉換失敗了') }
3.1.2.3.
異步代碼中沒法使用 try...catch處理異常
3.1.2.3.1.
/** *異步代碼沒法使用try-catch處理異常 */ function parseJsonStrToObj(str) { setTimeout(function() { return JSON.parse(str); }, 0); } //對於異步代碼的執行來講,try-catch 是沒法捕獲異步代碼中出現的異常的 try { var obj = parseJsonStrToObj('foo'); console.log('執行結果是:' + obj); } catch (e) { console.log('轉換失敗了'); }
3.1.2.4.
使用回調函數接收 異步代碼的執行結果
3.1.2.4.1.
/** * try-catch寫在異步代碼中 */ function parseJsonStrToObj(str) { setTimeout(function() { try{ return JSON.parse(str); }catch(e){ console.log('轉換失敗了'); } }, 0); } //調用方法輸出結果 var obj = parseJsonStrToObj('foo'); //這種寫法沒法接收到第7行的返回值 console.log('執行結果是:' + obj);
3.1.2.4.2.
//經過回調函數來接收異步代碼執行的處理結果 function parseJsonStrToObj(str,callback) { setTimeout(function() { try { var obj = JSON.parse(str); callback(null, obj); } catch (e) { callback(e, null); } }, 0); } //注意區分錯誤信息和正確的數據信息 parseJsonStrToObj('{"foo":"bar"}',function (err, result) { if (err) { return console.log('轉換失敗了'); } console.log('數據轉換成功,沒有問題能夠直接使用了:' + result); });
3.1.2.4.3.
回調函數:即當使用異步代碼去作一件事時,不能預測這件事何時作完,其餘的事情還在繼續,這時,可給異步代碼準備一個包裹,當異步代碼有了執行結果時能夠將結果放到這個包裹裏,須要在哪裏使用這個結果就從包裹取出。 回調函數的三個約定: 1、函數名稱一般爲callback,在封裝異步執行代碼時,優先把callback做爲函數的最後一個參數出現: function 函數名 (arg1,arg2,callback){} 2、把代碼中出現的錯誤做爲callback回調函數的第一個參數進行傳遞:callback(err,result); 3、把真正的返回的結果數據,傳遞給callback的第二個參數。callback(err,result);
理解異步編程的「事件驅動」思路: 在異步編程中,當異步函數執行時,不肯定什麼時候執行完畢,回調函數會被壓入到一個事件循環(EventLoop)的隊列,而後往下執行其餘代碼,直到異步函數執行完成後,纔會開始處理事件循環,調用相應的回調函數。這個事件循環隊列是一個先進先出的隊列,這說明回調是按照它們被加入隊列的順序執行的。
3.2.
Node.js的包和NPM
3.2.1.
包的概念
3.2.1.1.
包是在模塊的基礎上更進一步的組織JavaScript代碼的目錄,有出口模塊,遵循CommonJS規範
3.2.1.1.1.
目錄結構
package.json在頂層目錄的包描述文件,說明文件
name
description
version
keywords
author
dependencies
scripts
bin 存放可執行二進制文件的目錄
lib 存放JavaScript文件的目錄
doc 存放文檔的目錄
test 存放單元測試用例的代碼
3.2.2.
NPM的概念
3.2.2.1.
全稱是Node.js Package Manage,
3.2.2.1.1.
含義一:是Node.js的開放模塊登記和管理系統,是一個NPM網站,http://www.npmjs.com,是全球最大的模塊生態系統,裏面的包都是經過Node.js實現的,開源免費,即查即用。
3.2.2.1.2.
含義二:是Node.js的包管理工具,一個命令行下的軟件,提供了一些命令用於快速安裝和管理模塊。
npm init[-y] 初始化一個package.json文件
npm install 包名 安裝一個包
npm install -save 包名 將安裝的包添加到package.json的依賴中
npm install -g 包名 安裝一個命令行工具
npm docs 包名 查看包的文檔
npm root -g 查看全局包安裝路徑
npm config set prefix "路徑" 修改全局包安裝路徑
npm list 查看當前目錄下安裝的全部包
npm list -g 查看全局包的安裝路徑下全部的包
npm uninstall 包名 卸載當前目錄下的某個包
npm uninstall -g 包名 卸載全局安裝路徑下的某個包
npm update 包名 更新當前目錄下的某個包
3.2.2.1.3.
包管理的使用場景
從NPM服務器下載別人編寫的第三方包到本地使用
從NPM服務器下載並安裝別人編寫的命令行程序到本地使用
容許將本身編寫的包或命令行程序上傳到NPM 服務器供別人使用
3.2.3.
NPM的基本應用
3.2.3.1.
安裝npm install markdown
3.2.3.2.
node-moudules目錄自動建立。存放第三方包,目錄名和內容不能修改 除了markdown,另外的包是其依賴包
3.2.4.
包模塊加載規則
3.2.4.1.
1、在加載時,Node.js會默認當作核心模塊去加載。若是發現標識名不是核心模塊,就會在當前目錄的node_modules目錄下尋找。若是沒有,則從當前目錄的父目錄的node_modules裏搜索,遞歸下去直到根目錄。
3.2.4.2.
2、若是找到了該標識名的子目錄,Node.js將會找到該子目錄下的package.json文件,獲取其main屬性的值,根據main屬性指定的路徑值進行加載。用戶不用關心入口模塊是哪個文件。
4.
第4章 Node.js 文件操做 2019.2.23 2:40'
4.1.
基本文件操做
4.1.1.
文件寫入
4.1.1.1.
加載fs(FileSystem)模塊 var fs=require('fs');
4.1.1.2.
//同步寫入 fs.writeFileSync(file,data[,option]); //異步寫入 fs.writeFile(file,data[,option],callback);
4.1.1.2.1.
/* * 同步方式寫入文件 */ var fs = require('fs'); // 在進行文件操做的時候,若是是同步 API,必須使用 try-catch 來捕獲異常 // 防止程序由於異常退出,致使後續代碼沒法繼續執行 try { console.log('寫入文件...') fs.writeFileSync('D:/a.txt', '傳智播客'); } catch (e) { console.log('很差意思,文件寫入失敗了') }
4.1.1.2.2.
/* * 異步方式寫入文件 */ var fs = require('fs'); console.log(1); //該方法中回調函數的第一個參數爲錯誤對象 fs.writeFile('d:/b.txt', '傳智播客', function(err) { //判斷是否出現錯誤,進行錯誤提示 if (err) { console.log('很差意思,文件寫入失敗了'); } console.log(2); }) console.log(3);
4.1.2.
向文件追加內容
4.1.2.1.
appendFile(file,data[,option],callback); option默認爲:utf8,0o666,'a'
4.1.2.1.1.
/* * 向文件追加內容 */ var fs = require('fs'); //定義須要追加的數據 var data = '歡迎您'; //調用文件追加函數 fs.appendFile('D:/a.txt', data, function(err) { if (err) { // 出錯的狀況下,回調函數中的代碼就不要繼續日後執行了 // 因此可使用return 的方式,阻止代碼繼續執行 return console.log('文件追加失敗了'); } // 但願在文件追加成功以後作一些事情 console.log('文件追加成功了'); });
4.1.3.
文件讀取
4.1.3.1.
/* * 文件讀取 */ var fs = require('fs'); //讀取文件 fs.readFile('D:/a.txt', function(err, data) { if (err) { return console.log('文件讀取失敗'); } // 由於計算機中全部的數據最終保存的都是 二進制 數據 // 因此能夠經過調用 toString() 方法將二進制數據轉換爲人類能夠識別的字符 console.log(data.toString()); });
4.1.4.
文件複製
4.1.4.1.
/* * 文件複製案例 */ var fs = require('fs'); //讀取a.txt文件數據 fs.readFile('D:/a.txt', function(err, data) { if (err) { return console.log('讀取文件失敗了'); } //將數據寫入c.txt文件 fs.writeFile('D:/c.txt', data.toString(), function(err) { if (err) { return console.log('寫入文件失敗了'); } }); console.log('文件複製成功了'); });
4.1.4.2.
//封裝copy再複製 //建立封裝文件demo4-6.js /* * 文件複製模塊 */ var fs = require('fs'); /*定義文件複製函數copy() * src:須要讀取的文件 * dist:目標文件 * callback:回調函數 * */ function copy(src, dist, callback) { //讀取文件 fs.readFile(src, function(err, data) { if (err) { return callback(err); } //寫入文件 fs.writeFile(dist, data.toString(), function(err) { if (err) { return callback(err); } callback(null); }); }); } module.exports = copy; //測試文件複製test.js /* * 測試文件複製 */ //加載封裝好的文件複製功能模塊 var copy = require('./demo4-6'); //調用copy()函數 copy('D:/a.txt','D:/d.txt',function(err){ if(err){ return console.log('文件複製失敗了'); } console.log('文件複製成功了'); });
4.1.5.
獲取文件信息
4.1.5.1.
var fs = require('fs'); fs.stat('D:/a.txt', function (err, stats) { //判斷是不是文件 console.log("是不是文件:"+stats.isFile()); console.log(stats); //輸出文件信息 })
4.2.
案例-控制歌詞滾動
4.2.1.
var fs = require('fs'); //讀取歌詞文件 fs.readFile('./lrc.txt', function(err, data) { if (err) { return console.log('讀取歌詞文件失敗了'); } data = data.toString(); var lines = data.split('\n'); // 遍歷全部行,經過正則匹配裏面的時間,解析出毫秒 // 須要裏面的時間和裏面的內容 var reg = /\[(\d{2})\:(\d{2})\.(\d{2})\]\s*(.+)/; for (var i = 0; i < lines.length; i++) { (function(index) { var line = lines[index]; var matches = reg.exec(line); if (matches) { // 獲取分 var m = parseFloat(matches[1]); // 獲取秒 var s = parseFloat(matches[2]); // 獲取毫秒 var ms = parseFloat(matches[3]); // 獲取定時器中要輸出的內容 var content = matches[4]; // 將分+秒+毫秒轉換爲毫秒 var time = m * 60 * 1000 + s * 1000 + ms; //使用定時器,讓每行內容在指定的時間輸出 setTimeout(function() { console.log(content); }, time); } })(i); } });
4.2.1.1.
[ti:我想] [ar:張傑] [t_time:(03:46)] [00:00.00] 我想 - 張傑 [00:02.00] 詞:王笠人 [00:04.00] 曲:王笠人 [00:06.00] 編曲:梁思樺 [00:08.00] 歌詞編輯:果果 [00:10.00] QQ:765708831 [00:13.00] 中文歌詞庫 www.cnLyric.com [00:18.23] 每件事情都有發生的理由 [00:21.72] 可沒法解釋 碰見你 [00:26.15] 再多的心理準備 都抵抗不了 [00:29.89] 被現實戰勝的愛情 [00:34.27] 咱們都習慣了同一個溫度 [00:38.08] 你說這叫幸福 [00:42.32] 同時也忽略了一種殘酷 [00:45.39] 我以爲 好無助 [00:50.69] 我想 我想 [00:53.54] 我想一塊兒越過全部困難和阻擋 [00:57.64] 而咱們 卻不同 [01:01.58] 雖然都有共同的理想 [01:06.10] 窗外 有陽光 [01:09.76] 透過了一絲縫隙照亮了一點但願 [01:14.07] 而晚上 的月亮 [01:17.94] 讓咱們再次陷入了彷徨 [01:25.66] 你問爲何喜歡拍照記錄 [01:29.27] 答案我卻說不出 [01:33.17] 怕若是咱們走了不一樣方向 [01:37.06] 有照片讓我回顧 [01:41.36] 回憶咱們去過的每個地方 [01:45.41] 和時而停頓的腳步 [01:49.43] 就這麼停停頓頓一步接一步 [01:53.75] 直到沒有路 [01:57.91] 我想 我想 [02:00.86] 我想一塊兒越過全部困難和阻擋 [02:04.95] 而咱們 卻不同 [02:08.74] 雖然都有共同的理想 [02:13.15] 窗外 有陽光 [02:16.83] 透過了一絲縫隙照亮了一點但願 [02:21.01] 而晚上 的月亮 [02:25.30] 讓咱們再次陷入了彷徨 [02:30.44] 若是每個清晨 [02:33.82] 在你的溫度裏甦醒 [02:37.82] 閉眼聆聽 有節奏的呼吸 [02:41.63] 哪怕只是一瞬間 [02:43.68] 哪怕只是一場夢 [02:50.74] 我想 我想 [02:53.60] 我想一塊兒越過全部困難和阻擋 [02:57.73] 而咱們 卻不同 [03:01.43] 雖然都有共同的理想 [03:06.16] 窗外 有陽光 [03:09.81] 透過了一絲縫隙照亮了一點但願 [03:13.72] 而晚上 的月亮 [03:18.04] 讓咱們再次陷入了彷徨 [03:23.35] 每件事情都有發生的理由 [03:26.88] 可沒法解釋 碰見你 [03:31.17] 再多的心理準備 都抵抗不了 [03:35.11] 被命運安排的相遇
4.3.
文件相關操做
4.3.1.
路徑字符串操做
4.3.1.1.
獲取路徑模塊 var path=require('path');
4.3.1.2.
> str='D:\Node.js' 'D:Node.js' > path.basename(str) 'Node.js' > path.dirname(str) 'D:' > path.extname(str) '.js' >
4.3.1.3.
拼接路徑字符串和轉換標準路徑path.join()
4.3.2.
目錄操做
4.3.2.1.
建立目錄fs.mkdir(path[,model],callback);
4.3.2.1.1.
/* *建立目錄,必須逐級建立目錄,若是越級則出錯 */ var fs = require('fs'); console.log('在C:/Course目錄下建立目錄testDemo4-8'); fs.mkdir('D:/Course/testDemo4-8/',function(err){ if (err) { return console.error(err); } console.log("目錄建立成功。"); });
4.3.2.2.
讀取目錄
4.3.2.2.1.
/* *讀取目錄 */ var fs = require('fs'); console.log('查看/testDemo4-8目錄'); fs.readdir('/Node.js/code/',function(err, files){ if (err) { return console.error(err); } //遍歷全部文件 files.forEach( function (file){ // 輸出文件名 console.log( file ); }); });
4.3.2.3.
刪除目錄
4.3.2.3.1.
/* *刪除目錄 */ var fs=require('fs'); console.log('讀取 /testDemo4-8 目錄'); fs.readdir('/Course/testDemo4-8/',function(err, files){ if (err) { return console.error(err); } //遍歷全部文件 files.forEach( function (file){ // 輸出文件名 console.log( file ); //刪除文件 fs.unlink('/Course/testDemo4-8/'+file, function(err) { if (err) { return console.error(err); } console.log(file+'文件刪除成功!'); }); }); console.log('準備刪除/testDemo4-8目錄'); fs.rmdir('/Course/testDemo4-8/',function(err){ if (err) { return console.error(err); } console.log("目錄刪除成功!"); }); });
5.
第5章 Node.js中處理數據I/O 2019.2.23 12:00'
5.1.
Buffer緩衝區 限制大小1GB
5.1.1.
二進制數據和亂碼
5.1.1.1.
亂碼是指計算機二進制數據在轉換字符的過程當中,使用了不合適的字符集,而形成的部分或全部的字符沒法被閱讀。
5.1.2.
Buffer的構造函數
5.1.2.1.
緩衝區是在內容中操做數據的容器。Node.js的Buffer模塊是全局性的,不須要require()函數來加載
5.1.2.2.
建立緩衝區
5.1.2.2.1.
傳入字節:var buf=new Buffer(size);
5.1.2.2.2.
傳入數組:var buf=new Buffer([10,20,30,40,50]);
5.1.2.2.3.
傳入字符串和編碼:var buf=new Buffer("hello","utf-8");
5.1.3.
寫入緩衝區
5.1.3.1.
/* * 寫入緩衝區:格式 buf.write(string[,offset[,length]][,encoding]); */ //建立一個能夠存儲 5 個字節的內存空間對象 var buf = new Buffer(5); // 經過 buffer 對象的 length 屬性能夠獲取 buffer 緩存中的字節大小 console.log(buf.length); //向緩衝區寫入a buf.write('a'); //輸出緩衝區數據 console.log(buf); //向緩衝區寫入b buf.write('b', 1, 1, 'ascii'); //輸出緩衝區數據 console.log(buf);
5.1.4.
從緩衝區讀取數據
5.1.4.1.
/* * 讀取緩衝區:格式:buf.toString([encoding[,start[,end]]]); */ //建立一個能夠存儲26個字節的內存空間對象 var buf = new Buffer(26); //像buffer數組中存入26個字母對應的編碼 for (var i = 0 ; i < 26 ; i++) { buf[i] = i + 97; } //輸出所有字母 console.log( buf.toString('ascii')); // 輸出: abcdefghijklmnopqrstuvwxyz //輸出前五個字母 console.log( buf.toString('ascii',0,5)); // 輸出: abcde // 輸出: 'abcde' console.log(buf.toString('utf8',0,5)); // 輸出: 'abcde', 默認編碼爲 'utf8' console.log(buf.toString(undefined,0,5));
5.1.5.
拼接緩衝區
5.1.5.1.
/* * 拼接緩衝區:buf.concat(list[,totalLength]); */ //建立兩個緩衝區 var buf = new Buffer('世上無難事,'); var buf1 = new Buffer('只怕有心人'); //執行拼接操做 var buf2= Buffer.concat([buf,buf1]); //輸出拼接後緩衝區的內容 console.log("buf2 內容: " + buf2.toString());
5.2.
Stream文件流
5.2.1.
文件流的概念
5.2.1.1.
四種流類型
5.2.1.1.1.
Readable可讀操做可讀流
5.2.1.1.2.
Writable可寫操做可寫流
5.2.1.1.3.
Duplex可讀可寫操做雙向流、雙工流
5.2.1.1.4.
Transform操做被寫入數據,而後讀出結果(變換流)
5.2.1.2.
全部的Stream對象都是EventEmitter(時間觸發器)的實例
5.2.1.2.1.
事件
data當有數據可讀時觸發
end沒有更多的數據可讀時觸發
error在接收和寫入發生錯誤時觸發
finish全部數據已被寫入到底層系統時觸發
5.2.2.
Node.js的可讀流和可寫流
5.2.2.1.
可讀流
5.2.2.1.1.
/** * 從流中讀取數據 */ var fs = require("fs"); var total = ''; // 建立可讀流 var readableStream = fs.createReadStream('input.txt'); // 設置編碼爲 utf8。 readableStream.setEncoding('UTF8'); // 處理流事件 data\end\and\error //綁定data事件,附加回調函數,流開始流動 readableStream.on('data', function(chunk) { total += chunk; }); //讀取結束後,輸出total readableStream.on('end',function(){ console.log(total); }); //若是出錯,輸出提示信息 readableStream.on('error', function(err){ console.log(err.stack); }); console.log("程序執行完畢");
5.2.2.2.
可寫流
5.2.2.2.1.
/** * 使用文件流進行文件拷貝 */ var fs = require('fs'); //建立可讀流 var readableStream = fs.createReadStream('input.txt'); //建立可寫流 var writableStream = fs.createWriteStream('output.txt'); readableStream.setEncoding('utf8'); readableStream.on('data', function(chunk){ //將讀出的數據塊寫入可寫流 writableStream.write(chunk); }); readableStream.on('error', function(err){ console.log(err.stack); }); readableStream.on('end',function(){ //將剩下的數據所有寫入,而且關閉寫入的文件 writableStream.end(); }); writableStream.on('error', function(err){ console.log(err.stack); });
5.2.3.
使用pipe()處理大文件
5.2.3.1.
/** * 使用pipe()進行文件拷貝 */ var fs = require('fs') //源文件路徑 var srcPath = 'd:/node.js/code/chapter05/demo5-7/input.txt'; //目標文件路徑 var distPath = 'd:/node.js/code/chapter05/demo5-7/input.txtoutput.txt'; var readableStream = fs.createReadStream(srcPath); var writableStream = fs.createWriteStream(distPath); // 能夠經過使用可讀流 的函數 pipe ()接入到可寫流中 // pipe()是一個很高效的數據處理方式 if(readableStream.pipe(writableStream)){ console.log('文件複製成功了') }else{ console.log('文件複製失敗了') }
6.
第6章 Node.js網絡編程 2019.2.23 16:20’
6.1.
6.1Node.js網絡編程基礎
6.1.1.
IP地址和端口號
6.1.1.1.
IP地址是用來定位一臺計算機的,既能夠是服務器,也能夠是客戶端 端口號是用來定位應用程序的
6.1.2.
套接字Socket簡單模型
6.1.2.1.
TCP/IP協議
6.1.2.1.1.
TCP:Transfer Control Protocol,傳輸控制協議,是一種穩定可靠的傳送方式。TCP負責發現傳輸的問題,一有問題就發出信號,要求從新傳輸,直到全部數據安全正確地傳輸到目的地爲止。
6.1.2.1.2.
IP是給互聯網的每一臺聯網設備規定一個地址。
6.1.2.1.3.
TCP/IP協議包含因特網整個TCP/IP協議簇
應用層面:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet協議等。
6.1.2.2.
Socket孔插座,可理解爲接口對象,網絡編程中也稱套接字,經常使用於描述IP地址和端口等。
6.1.2.3.
Socket是支持TCP/IP的網絡通訊的基本操做單元,能夠看作是不一樣主機之間的進程進行雙向通訊的端點,簡單的說就是通訊兩方的一種約定。
6.1.2.4.
Socket就是對TCP/IP協議的封裝,Socket自己並非協議,而是一個調用接口(API)。
6.1.2.5.
Socket進行網絡通訊必須的5種信息
6.1.2.5.1.
鏈接使用的協議
6.1.2.5.2.
客戶端設備的IP地址
6.1.2.5.3.
客戶端的端口號
6.1.2.5.4.
服務期端的IP地址
6.1.2.5.5.
服務器端口
6.1.2.6.
套接字地址就是IP地址和端口號的組合
6.1.2.6.1.
套接字服務不需處理get和post請求,而是採用點對點的傳輸數據方式,是一個輕量級的網絡通訊解決方案
6.1.2.7.
套接字服務
6.1.2.7.1.
服務器用來監聽鏈接,客戶端用來打開一個到服務器的鏈接。
6.1.2.8.
Node.js自己就是一個服務器
6.2.
6.2Node.js中實現套接字服務
6.2.1.
Net模塊的API
6.2.1.1.
net.createServer([options][, connectionListener])建立一個TCP服務器
6.2.1.2.
net.connect(options[, connectListener])
6.2.1.3.
net.createConnection(options[, connectListener])
6.2.1.4.
net.connect(port[, host][, connectListener])
6.2.1.5.
net.createConnection(port[, host][, connectListener])
6.2.1.6.
net.connect(path[, connectListener])
6.2.1.7.
net.createConnection(path[, connectListener])
6.2.1.8.
net.isIP(input)
6.2.1.9.
net.isIPv4(input)
6.2.1.10.
net.isIPv6(input)
6.2.2.
Net.Server對象
6.2.2.1.
建立Net.Server對象: var server = net.createServer([options][, connectionListener])
6.2.2.1.1.
Net.Server對象函數
server.listen([port[, host[, backlog]]][, callback])
server.listen(path[, backlog][, callback])
server.listen(handle[, backlog][, callback])
server.listen(options[, callback])
server.close([callback])
server.address()
server.ref()
server.unref()
server.getConnections(callback)
6.2.2.1.2.
Server對象的事件
'listening' 事件
'connection' 事件
'close' 事件
'error' 事件
6.2.2.2.
示例程序
6.2.2.2.1.
第1步:建立服務器端文件demo6-1.js
/** * Net.Servet建立服務器 */ // 1. 加載 manychat 核心模塊 var net = require('net'); // 2. 建立一個服務應用程序,獲得一個服務器實例對象 var server = net.createServer(); // 3. 監聽客戶端的鏈接事件connection,鏈接成功就會執行回調處理函數 server.on('connection',function () { console.log('有客戶端鏈接上來了'); }); // 5. 服務器有一個事件叫作 listening ,表示開啓監聽成功以後回調處理函數 server.on('listening',function () { console.log('服務器開啓監聽成功了,正在等待客戶端鏈接'); }); // 4. 啓動服務器,開啓監聽 // 監聽 127.0.0.1:3000 只能被本機所訪問 server.listen(3000,'127.0.0.1');
6.2.2.2.2.
第2步:在命令行窗口運行:node demo6-1.js
6.2.2.2.3.
第3步:安裝Telnet
設置->應用->右側 相關設置:程序和功能->左側 啓動或關閉windows功能 在Telnet客戶端前的複選框裏打鉤按肯定
6.2.2.2.4.
第4步:另開終端窗口運行:telnet 127.0.0.1 3000 記住:端口3000前面是空格而不是冒號
這時運行demo6-1窗口會顯示「有客戶鏈接上來了」 表示客戶端與服務器端鏈接成功!!!
6.2.3.
Net.Socket對象
6.2.3.1.
Duplex(雙工)流接口,是可讀可寫流
6.2.3.2.
Socket事件
6.2.3.2.1.
lookup
6.2.3.2.2.
connect
6.2.3.2.3.
data
6.2.3.2.4.
end
6.2.3.2.5.
timeout
6.2.3.2.6.
drain
6.2.3.2.7.
error
6.2.3.2.8.
close
6.2.3.3.
Socket屬性
6.2.3.3.1.
socket.bufferSize
6.2.3.3.2.
socket.remoteAddress
6.2.3.3.3.
socket.remoteFamily
6.2.3.3.4.
socket.remotePort
6.2.3.3.5.
socket.localAddress
6.2.3.3.6.
socket.localPort
6.2.3.3.7.
socket.bytesRead
6.2.3.3.8.
socket.bytesWritten
6.2.3.4.
Socket函數
6.2.3.4.1.
new net.Socket([options])
6.2.3.4.2.
socket.connect(port[, host][, connectListener])
6.2.3.4.3.
socket.connect(path[, connectListener])
6.2.3.4.4.
socket.setEncoding([encoding])
6.2.3.4.5.
socket.write(data[, encoding][, callback])
6.2.3.4.6.
socket.destroy([exception])
6.2.3.4.7.
socket.pause()
6.2.3.4.8.
socket.resume()
6.2.3.4.9.
socket.setTimeout(timeout[, callback])
6.2.3.4.10.
socket.setNoDelay([noDelay])
6.2.3.4.11.
socket.setKeepAlive([enable][, initialDelay])
6.2.3.4.12.
socket.address()
6.2.3.4.13.
socket.ref()
6.2.3.4.14.
socket.unref()
6.2.3.5.
服務器向客戶端發送消息
6.2.3.5.1.
/** * 在服務器端使用Socket */ // 1. 加載 manychat 核心模塊 var net = require('net'); // 2. 建立一個服務應用程序,獲得一個服務器實例對象 var server = net.createServer(); // 3. 監聽客戶端的鏈接事件,鏈接成功就會執行回調處理函數 // 每次回調函數被調用,就會有一個新的 socket 對象在回調函數中 server.on('connection',function (socket) { console.log('有客戶端鏈接上來了'); //在服務端能夠獲取到客戶端的IP地址等信息 console.log('客戶端IP地址:' + socket.remoteAddress + '鏈接到了當前服務器'); // 當前鏈接成功以後的客戶端發送一個 hello world socket.write('hello world'); }); // 5. 服務器有一個事件叫作 listening ,表示開啓監聽成功以後回調處理函數 server.on('listening',function () { console.log('服務器開啓監聽成功了,正在等待客戶端鏈接'); }); // 4. 啓動服務器,開啓監聽 server.listen(3000,'127.0.0.1');
6.2.3.5.2.
服務器端運行node demo6-1
6.2.3.5.3.
客戶端運行 telnet 127.0.0.1 3000
6.2.3.6.
統計在線人數
6.2.3.6.1.
/** * 服務端統計在線人數 */ var net = require('net'); var server = net.createServer(); var count = 0; server.on('connection', function(socket) { count++; console.log('welcome , 當前在線人數:' + count); socket.write('remoteAddress'+socket.remoteAddress+'\n'); socket.write('remotePort'+socket.remotePort); }); server.listen(3000, '127.0.0.1', function() { console.log('server listening at port 3000'); });
6.2.3.6.2.
服務器端運行 node demo6-3.js 打開多個命令窗口分別運行 telnet 127.0.0.1 3000
6.2.3.7.
客戶端與服務器端雙向通訊
6.2.3.7.1.
建立客戶端
/* * 雙向通訊-客戶端 */ var net = require('net'); // 當調用 createConnection 以後,就會獲得一個與服務器進行通訊的 socket 對象 // 該對象中包含當前客戶端與服務器通訊的 ip地址和端口號 var client = net.createConnection({ port: 3000 }); // 何時客戶端和服務器鏈接成功了 // 能夠經過監聽 client 的 connect 事件來處理 client.on('connect',function () { // 客戶端與服務器鏈接成功了 console.log('客戶端與服務器鏈接成功了'); client.write('你吃了嗎?'); }); client.on('data',function (data) { //輸出服務器發送給當前客戶端的數據 console.log(data.toString()); });
6.2.3.7.2.
建立服務器端
/** * 雙向通訊-服務器 */ var net = require('net'); var server = net.createServer(); // 每個客戶端與服務器創建鏈接成功以後,都會觸發一次 connection 事件 server.on('connection', function(socket) { /*如下部分應用於雙向通訊*/ //經過監聽 socket 對象的 data 事件來獲取客戶端發送給服務器的數據 socket.on('data', function(data) { console.log(data.toString()); socket.write('我吃的小豆包'); }); }); server.listen(3000, '127.0.0.1', function() { console.log('server listening at port 3000'); });
6.2.3.7.3.
若是在服務器端啓動telnet 127.0.0.1 3000 則可即時通信
6.3.
6.3Node.js進程管理
6.3.1.
Process模塊獲取終端輸入
6.3.1.1.
/** * 測試獲取終端輸入 */ // 經過下面的方式就能夠獲取用戶的輸入 process.stdin.on('data',function (data) { console.log(data.toString().trim()); });
6.3.2.
多人廣播消息
6.3.2.1.
1、建立目錄manychat,並建立兩個文件server.js和client.js
6.3.2.1.1.
//server.js /** * 多人廣播聊天服務端端 */ var net = require('net'); var server = net.createServer(); //該數組用來封裝全部客戶端的Socket var users = []; server.on('connection', function(socket) { users.push(socket); socket.on('data', function(data) { data = data.toString().trim(); users.forEach(function(client) { if (client !== socket) { //因爲同一臺計算機上不一樣客戶端端口號不一樣,因此能夠經過端口號來區分是誰說的話 client.write(socket.remotePort+ ':' + data); } }); }); // 當有客戶端異常退出的時候,就會觸發該函數 // 若是不監聽客戶端異常退出就會致使服務器崩潰 socket.on('error',function () { console.log('有客戶端異常退出了'); }); }) server.listen(3000, '127.0.0.1', function() { console.log('server listening at port 3000'); });
6.3.2.1.2.
//client.js /** * 多人廣播聊天客戶端 */ var net = require('net') //向服務端建立鏈接 var client = net.createConnection({ port:3000, host:'127.0.0.1' }); //監聽鏈接成功事件connent client.on('connect',function () { // 經過當前進程的標準輸入的 data 事件獲取終端中的輸入 process.stdin.on('data',function (data) { data = data.toString().trim(); client.write(data); }); }); //監聽data事件輸入服務器返回的數據 client.on('data',function (data) { console.log(data.toString()); });
6.4.
6.4案例--終端聊天室
6.4.1.
配置文件:config
6.4.1.1.
module.exports = { "port": 3000, "host": "127.0.0.1" }
6.4.2.
客戶端文件:client.js
6.4.2.1.
/** * 終端聊天室客戶端 */ var net = require('net'); var config = require('./config'); var client = net.createConnection({ port: config.port, host: config.host }) //用戶註冊成功後爲該屬性賦值 var username; client.on('connect', function() { console.log('請輸入用戶名:'); process.stdin.on('data', function(data) { data = data.toString().trim(); // 當用戶註冊成功以後,下面的數據格式就不能再使用了 // 判斷一下是否已經有用戶名了,若是已經有了,則表示用戶要發送聊天數據 // 若是沒有,則表示用戶要發送註冊數據 if (!username) { var send = { protocal: 'signup', username: data } client.write(JSON.stringify(send)); return; } //判斷是廣播消息仍是點對點消息 // name:內容 var regex = /(.{1,18}):(.+)/; var matches = regex.exec(data); if (matches) { var from = username; var to = matches[1]; var message = matches[2]; var send = { protocal: 'p2p', from: username, to: to, message: message } client.write(JSON.stringify(send)); } else { var send = { protocal: 'broadcast', from: username, message: data } client.write(JSON.stringify(send)); } }); }); client.on('data', function(data) { data = JSON.parse(data); switch (data.protocal) { case 'signup': var code = data.code; switch (code) { case 1000: username = data.username; console.log(data.message); break; case 1001: console.log(data.message); break; default: break; } break; case 'broadcast': console.log(data.message); break; case 'p2p': var code = data.code; switch (code) { case 2000: var from = data.from; var message = data.message; message = from + '對你說:' + message; console.log(message); break; case 2001: console.log(data.message); break; default: break; } break; default: break; }; });
6.4.3.
服務器端文件:server.js
6.4.3.1.
/** * 終端聊天室服務端 */ var net = require('net'); var config = require('./config'); var broadcast=require('./broadcast.js'); var p2p=require('./p2p.js'); var signup=require('./signup.js'); var server = net.createServer(); var users = {}; server.on('connection', function(socket) { socket.on('data', function(data) { // 解析客戶端發送的數據 data = JSON.parse(data); // 根據客戶端發送的數據類型,作對應的操做 switch (data.protocal) { case 'signup': //處理用戶註冊 signup.signup(socket,data,users); break; //處理廣播消息 case 'broadcast': broadcast.broadcast(data,users); break; case 'p2p': // 處理點對點消息 p2p.p2p(socket, data,users); break; default: break; } }); socket.on('error', function() { console.log('有客戶端異常退出了'); }); }); // 3. 啓動服務器,開啓監聽 server.listen(config.port, config.host, function() { console.log('server listening at port ' + config.port); });
6.4.4.
用戶註冊模塊:signup.js
6.4.4.1.
/** * 用戶註冊 * @param socket * @param data 用戶名 * {protocal: 'signup', username: '小明'} * @param users 用戶組 */ exports.signup=function (socket,data,users) { // 處理用戶註冊請求 var username = data.username; // 若是用戶名不存在,則將該用戶名和它的Socket地址保存起來 if (!users[username]) { users[username] = socket; var send = { protocal: 'signup', code: 1000, username: username, message: '註冊成功' } socket.write(JSON.stringify(send)); } else { var send = { protocal: 'signup', code: 1001, message: '用戶名已被佔用,請從新輸入用戶名:' } socket.write(JSON.stringify(send)); } }
6.4.5.
廣播消息模塊:broadcast.js
6.4.5.1.
/** * 廣播消息 * @param data 廣播消息發送過來的JSON數據 * { "protocal": "broadcast",//消息類型爲廣播 "from": "小紅",//發送消息的用戶 "message": "你們早上好"//用戶發送的消息內容 } */ exports.broadcast= function(data,users) { var from = data.from; var message = data.message message = from + '說:' + message; var send = { protocal: 'broadcast', message: message } send = new Buffer(JSON.stringify(send)); for (var username in users) { var tmpSocket = users[username]; tmpSocket.write(send); } }
6.4.6.
點對點消息模塊:p2p.js
6.4.6.1.
/** * 點對點消息 * @param socket * @param data 點對點消息的JSON數據 * { "protocal": "p2p", //消息類型爲點對點 "from": "小紅", //發送消息的用戶 "to": "小明", "message": "你早上吃的什麼" } * @param users 用戶組 */ exports.p2p=function (socket,data,users) { var from = data.from; var to = data.to; var message = data.message; // 找到要發送給某我的的 Socket 地址對象 var receiver = users[to]; // 若是接收人不存在,告訴客戶端沒有該用戶 if (!receiver) { var send = { protocal: 'p2p', code: 2001, message: '用戶名不存在' } socket.write(new Buffer(JSON.stringify(send))); } else { // xxx 對你說: xxx var send = { protocal: 'p2p', code: 2000, from: data.from, message: message } receiver.write(new Buffer(JSON.stringify(send))); } // 若是接收人存在,則將消息發送給該用戶 }
7.
第7章 Node.js中實現HTTP服務 2019.2.23 22:35'
7.1.
7.1HTTP協議
7.1.1.
HTTP協議簡介
7.1.1.1.
HTTP:Hyper Text Transfer Protocol 超文本傳輸協議
7.1.1.1.1.
1990年提出
7.1.1.1.2.
用於從WWW服務器傳輸超文本到本地瀏覽器的傳輸協議
7.1.1.1.3.
基於TCP的鏈接方式
7.1.1.1.4.
開放系統互連參考模型OSI/RM通訊協議七層
應用層Application Layer
協議有HTTP、FTP、SMTP等
表示層Presentation Layer
ASCII碼、JPEG、MPEG、 WAV等文件轉換
會話層Session Layer
負責訪問次序的安排等
傳輸層Transport Layer
協議有TCP、UDP等
網絡層Network Layer
三層交換機、路由器等 協議有IP、SPX
數據鏈路層Data Link Layer
二層交換機、網橋網卡等
物理層Physics Layer
集線器、中繼器和傳輸線路等
7.1.1.1.5.
HTTP由請求和響應構成, 是一個標準的客戶端服務器模型, 也是一個無狀態的協議。 各大瀏覽器普遍基於HTTP1.1
7.1.1.1.6.
HTTP協議特色
支持客戶/服務器模式
簡單快速:GET/HEAD/POST
靈活:容許任意類型數據
無鏈接:處理完一個請求和響應即斷開鏈接
無狀態:對事務處理沒有記憶能力
7.1.2.
HTTP請求響應流程
7.1.2.1.
URL由幾部分組成:協議+域名+具體地址
7.1.2.1.1.
HTTP、POP3、FTP
7.1.2.1.2.
域名或IP地址及端口號:www.itheima.com
經過DNS解析
7.1.2.1.3.
具體地址:index.html通常加密或不顯示
7.1.2.2.
請求request,把URL地址等封裝成HTTP請求報文,存放在客戶端Socket對象中
7.1.2.3.
響應response,把數據封裝在HTTP響應報文,並存放在Socket對象中
7.1.3.
HTTP的請求報文和響應報文
7.1.3.1.
報文是有必定格式的字符串,查看報文須要藉助工具,例如chrome內核版本 65.0.3325.181
7.1.3.2.
請求報文request
7.1.3.2.1.
GET / HTTP/1.1
7.1.3.2.2.
Host: www.itheima.com
7.1.3.2.3.
Connection: keep-alive
7.1.3.2.4.
Cache-Control: max-age=0
7.1.3.2.5.
Upgrade-Insecure-Requests: 1
7.1.3.2.6.
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
7.1.3.2.7.
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
7.1.3.2.8.
Accept-Encoding: gzip, deflate
7.1.3.2.9.
Accept-Language: zh-CN,zh;q=0.9
7.1.3.2.10.
Cookie: UM_distinctid=1691db04
7.1.3.3.
響應報文response
7.1.3.3.1.
HTTP/1.1 200 OK
7.1.3.3.2.
Server: Tengine
7.1.3.3.3.
Content-Type: text/html; charset=UTF-8
7.1.3.3.4.
Transfer-Encoding: chunked
7.1.3.3.5.
Connection: keep-alive
7.1.3.3.6.
Date: Sun, 24 Feb 2019 04:11:14 GMT
7.1.3.3.7.
Accept-Ranges: bytes
7.1.3.3.8.
Ali-Swift-Global-Savetime: 1550981474
7.1.3.3.9.
Via: cache4.l2et15[26,200-0,M], cache14.l2et15[27,0], kunlun5.cn1259[77,200-0,M], kunlun5.cn1259[77,0]
7.1.3.3.10.
X-Cache: MISS TCP_MISS dirn:-2:-2
7.1.3.3.11.
X-Swift-SaveTime: Sun, 24 Feb 2019 04:11:14 GMT
7.1.3.3.12.
X-Swift-CacheTime: 0
7.1.3.3.13.
Timing-Allow-Origin: *
7.1.3.3.14.
EagleId: da5ed29915509814747132159e
7.2.
7.2Node.js的HTTP服務
7.2.1.
HTTP模塊經常使用API
7.2.1.1.
加載語法:var http = require('http');
7.2.1.2.
http.Server
7.2.1.2.1.
HTTP服務器指的就是http.Server對象
7.2.1.2.2.
Node.js的全部基於HTTP協議的系統都是基於http.Server實現的
如網站、社交應用、代理服務器等
7.2.1.2.3.
建立語法:var server = http.createServer();
7.2.1.2.4.
函數
server.close([callback])
server.listen(port[,hostname][,backlog][,callback])
server.listen(handle[,callback])/server.listen(path[,callback])
7.2.1.2.5.
事件
request
connection
close
7.2.1.3.
http.IncomingMessage可讀流req
7.2.1.3.1.
函數&屬性
message.headers
message.httpVersion
Message.method
message.setTimeout(msecs,callback)
message.socket
message.url
7.2.1.4.
http.ServerResponse可寫流res
7.2.1.4.1.
函數&屬性
response.writeHead(statusCode,[headers])
response.write(data,[enconding])
response.end([data],[enconding])
response.addTrailers(headers)
response.finished
response.getHeader(name)
response.headersSent
response.removeHeader(name)
response.sendDate
response.setHeader(name, value)
response.setTimeout(msecs[, callback])
response.statusCode
response.statusMessage
response.writeContinue()
7.2.2.
使用HTTP模塊構建Web服務器
7.2.2.1.
/** * 使用HTTP構建Web服務器 */ var http = require('http'); // 1. 建立一個 HTTP 服務器 var server = http.createServer(); // 2. 監聽 請求(request) 事件 // request 就是一個可讀流,用來 獲取 當前與服務器鏈接的客戶端的一些請求報文數據 // response 就是一個可寫流,用來 給 客戶端 Socket 發送消息的,或者用來發送響應報文的 server.on('request',function (request, response) { // 使用 HTTP 發送響應數據的時候,HTTP 服務器會自動把數據經過 HTTP 協議包裝爲一個響應報文而後發送到Socket response.write('hello world'); // 在結束響應以前,咱們能夠屢次向 客戶端 發送數據 response.write('hello itheima'); // 對於 HTTP 請求響應模型來講,它們的請求和響應是一次性的 // 也就是說,每一次請求都必須結束響應, // 標識斷開當前鏈接 response.end(); // 在一次 HTTP 請求響應模型中,當結束了響應,就不能繼續發送數據了,如下消息不會顯示 }); // 3. 開啓啓動,監聽端口 server.listen(3000,function () { console.log('server is listening at port 3000'); });
7.2.3.
03_http.js
7.2.3.1.
var http = require('http') var server = http.createServer(function (req,res) { res.end('<h1> hello world </h1>') }) server.listen(3000)
7.2.4.
04_瀏覽器的本質.js
7.2.4.1.
+++++++++++++++++var net = require('manychat') var server = net.createServer() server.on('connection',function (socket) { socket.on('data',function (data) { console.log(data.toString()) // 對於 客戶端來講, 這個時候,服務器發送給本身的消息,有沒有發送完畢,不肯定 // 因此 瀏覽器客戶端 保持了掛起的狀態,繼續等待 服務器給本身傳輸消息數據 socket.write('HTTP/1.1 200 成功了\n\nhello world') // 若是想告訴客戶端,本次的數據已經給你發送完畢了,不用等了 ,結束響應 // 結束響應就意味着,本次請求響應鏈接斷開,。 socket.end() }) }) server.listen(3000)
7.3.
7.3HTTP服務請求處理
7.3.1.
根據不一樣的URL發送不一樣響應消息
7.3.1.1.
/** * 根據不一樣URL響應不一樣消息 */ var http = require('http'); //建立服務器 var server = http.createServer(); //監聽request事件 server.on('request', function(request, response) { //獲取資源路徑,默認爲'/' var url = request.url; //經過判斷獲取到的資源路徑,發送指定響應消息 if (url === '/') { response.end('hello index'); } else if (url === '/login') { response.end('hello login'); } else if (url === '/register') { response.end('hello register'); }else { //若是路資源徑找不到,提示錯誤信息 response.end('404 Not Found!'); } }); //開啓啓動,監聽端口 server.listen(3000, function() { console.log('server is listening at port 3000'); });
7.3.2.
HTTP處理靜態資源服務
7.3.2.1.
主程序demo7-3.js
7.3.2.1.1.
/** * 使用HTTP提供靜態資源服務 */ var http = require('http'); var fs = require('fs');//用於讀取靜態資源 var path = require('path');//用於作路徑拼接 var server = http.createServer(); server.on('request', function(request, response) { //獲取靜態資源路徑 var url = request.url; if (url === '/') { //讀取相應靜態資源內容 fs.readFile(path.join(__dirname, 'static/index.html'), 'utf8', function(err, data) { //若是出現異常拋出異常 if (err) { throw err; } //將讀取的靜態資源數據響應給瀏覽器 response.end(data); }); } else if (url === '/login') { fs.readFile(path.join(__dirname, 'static/login.html'), 'utf8', function(err, data) { if (err) { throw err; } response.end(data); }); } else if (url === '/register') { fs.readFile(path.join(__dirname, 'static/register.html'), 'utf8', function(err, data) { if (err) { throw err; } response.end(data); }); } else if (url === '/login.html') { fs.readFile(path.join(__dirname, 'static/404.html'), 'utf8', function(err, data) { if (err) { throw err } response.end(data); }); //若是有圖片、CSS文件等,瀏覽器會從新發送請求獲取靜態資源 } else if (url === '/css/main.css') { var cssPath = path.join(__dirname, 'static/css/main.css') fs.readFile(cssPath, 'utf8', function(err, data) { if (err) { throw err } response.end(data); }); } else if (url === '/images/01.jpg') { var imgPath = path.join(__dirname,'static/images/01.jpg') fs.readFile(imgPath, function(err, data) { if (err) { throw err } response.end(data); }); } else { fs.readFile(path.join(__dirname, 'static/404.html'), 'utf8', function(err, data) { if (err) { throw err } response.end(data); }); } }); server.listen(3000, function() { console.log('server is listening at port 3000'); });
7.3.2.2.
index.html
7.3.2.2.1.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首頁</title> <link rel="stylesheet" href="css/main.css"> </head> <body> <h1>首頁</h1> <img src="images/01.jpg" alt=""> </body> </html>
7.3.2.3.
login.html
7.3.2.3.1.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登陸</title> <link rel="stylesheet" href="css/main.css"> </head> <body> <h1>登陸</h1> <img src="images/01.jpg" alt=""> </body> </html>
7.3.2.4.
register.html
7.3.2.4.1.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>註冊</title> <link rel="stylesheet" href="css/main.css"> </head> <body> <h1>註冊</h1> <img src="images/01.jpg" alt=""> </body> </html>
7.3.2.5.
404.html
7.3.2.5.1.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>404</title> </head> <style> body { background-color:pink; } </style> <body> <h1>404 Not Found.</h1> </body> </html>
7.3.2.6.
main.css
7.3.2.6.1.
body { background-color: pink; }
7.3.2.7.
01.jpg
7.3.2.7.1.
Topic1
7.3.3.
動態處理靜態資源請求
7.3.3.1.
demo7-4.js
7.3.3.1.1.
/** * 動態處理靜態資源請求 */ var http = require('http'); var fs = require('fs'); var path = require('path'); var server = http.createServer(); server.on('request', function(req, res) { // 當用戶訪問 / 的時候,默認讓用戶訪問 index.html var url = req.url; console.log(url);//每次請求獲取資源路徑在服務端輸出。 var fullPath = path.join(__dirname,'static',url); if (url==='/') { fullPath = path.join(__dirname,'static/index.html'); } fs.readFile(fullPath,function (err,data) { if (err) { // 在進行web開發的時候,若是發生了錯誤,咱們能夠直接把該錯誤消息輸出到 客戶端 return res.end(err.message); } res.end(data); }); }); server.listen(3000, function() { console.log('server is runnig at port 3000'); });
7.4.
Underscore的模板引擎template
7.4.1.
template用於將JavaScript模板編譯爲能夠用於頁面呈現的函數,經過JSON數據源生成複雜的HTML並呈現出來
7.4.2.
語法:_.template(templateString,[settings])
7.4.3.
賦值:
7.4.3.1.
var compiled = _.template("hello:<%=name%>"); compiled({name:'moe'}); =>"hello:moe"
7.4.4.
須要轉義:
7.4.4.1.
var template = _.template("<b><%- value %></b>"); template({value:'<script>'}); =>"<b><script></b>"
7.4.5.
在Node.js中使用Underscore須要用NPM安裝
7.4.5.1.
在static-server目錄下初始化:npm init -y
7.4.5.1.1.
package.json
{ "name": "static-server", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "underscore": "^1.8.3" } }
7.4.5.2.
在此目錄下繼續輸入:npm install underscore --save
7.4.5.3.
在此目錄下建立index.heml,並添加代碼
7.4.5.3.1.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <body> <ul> <% arr.forEach(function(item){ %> <li> <%= item.name %> </li> <%}) %> </ul> <h1><%= title %></h1> </body> 備註:在服務器端定義了一個數組arr,遍歷該數組的每一個元素, 爲元素掛着name屬性。再定義一個變量title。這個頁面是做爲 一個網頁的模板使用,在服務器端會得到這個模板併爲模板中的 變量賦值,最後,將完整的數據做爲一個字符串響應給瀏覽器端, 由瀏覽器進行頁面渲染和呈現
7.4.5.4.
在此目錄下建立文件app.js,並添加服務器端代碼
7.4.5.4.1.
/** * 服務端代碼 */ var http = require('http'); var fs = require('fs'); var path = require('path'); var _ = require('underscore'); var server = http.createServer(); server.on('request', function(req, res) { var url = req.url; if (url === '/') { //備註:讀取index.html模板的內容,並將返回的data傳入模板函數_.template,傳入後可使用compiled()函數向該模板的數組和變量注入數據,最後將完整的HTML數據響應給客戶端瀏覽器!!! fs.readFile(path.join(__dirname, 'index.html'), 'utf8', function(err, data) { if (err) { return res.end(err.message); } // 如今的 data 就是 字符串 // 我把 html 字符串 總體的當成 模板字符串 var compiled = _.template(data); var htmlStr = compiled({ title: 'hello world', arr: [ { name: 'Jack' }, { name: 'rose' }, { name: 'mike' } ] }); res.end(htmlStr); }); } }); server.listen(3000, function() { console.log('server is runnig at port 3000'); });
子主題
7.4.5.5.
最後,啓動服務器:node app.js 客戶端打開瀏覽器查看
8.
第8章 綜合項目--個人音樂 2019.2.24 16:00
8.1.
項目簡介
8.1.1.
項目功能展現
8.1.2.
項目開發流程
8.1.2.1.
1、產品創意
8.1.2.2.
2、產品原型
8.1.2.3.
3、美工設計
8.1.2.4.
4、前端實現
8.1.2.5.
5、後端實現
8.1.2.6.
6、測試、試運行、上線
8.1.3.
需求分析
8.1.3.1.
1、數據模型分析
8.1.3.2.
2、路由設計
8.1.3.3.
3、功能開發
8.1.3.3.1.
展現歌曲信息
8.1.3.3.2.
添加歌曲
8.1.3.3.3.
編輯歌曲信息
8.1.3.3.4.
刪除歌曲
8.1.4.
項目結構
8.1.4.1.
1、render.js解析模板標記語法
8.1.4.2.
2、music.js封裝音樂文件相關的邏輯處理函數
8.1.4.3.
3、node_modules文件夾下的bootstrap響應式前端框架
8.1.4.4.
4、node_modules文件夾下的underscore:模板引擎用於注入後臺數據
8.1.4.5.
5、node_modules文件夾下的formidate:用於表單的數據處理,尤爲是表單的文件上傳處理
8.1.4.6.
6、uploads文件夾:用於存放MP3音頻文件和jpg圖片文件
8.1.4.7.
7、views文件夾:用於存放頁面
8.1.4.8.
8、app.js:項目的入口文件
8.1.4.9.
9、config.js配置端口
8.1.4.10.
10、package.json項目的說明文件
8.1.4.11.
11、router.js:路由模塊,根據用戶的請求判斷路徑,而後將請求分發到具體的處理函數
8.2.
項目實現
8.2.1.
項目初始化
8.2.1.1.
項目說明文件package.json
8.2.1.1.1.
{ "name": "music-player", "version": "1.0.0", "description": "一個簡單的音樂播放器", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node app.js" }, "author": "iroc <mail@lipengzhou.com> (https://github.com/iroc)", "license": "MIT", "dependencies": { "bootstrap": "^3.3.6", "formidable": "^1.0.17", "underscore": "^1.8.3" } }
8.2.1.1.2.
"start": "nodemon app.js" 當不知道入口文件時,會經過下面的命令自動找到start指令: npm start
8.2.1.1.3.
"dependencies": { "bootstrap": "^3.3.6", "formidable": "^1.0.17", "underscore": "^1.8.3" }須要選裝第三方依賴包
npm install --save bootstrap
npm install --save formidable
8.2.1.2.
入口文件app.js
8.2.1.2.1.
var http = require('http') var config = require('./config') var router = require('./router') var render = require('./common/render') var server = http.createServer() server.on('request', function(req, res) { // 首先動態的給 Response 對象掛載了一個 render 方法,該方法用來讀取一個模板文件,注入數據,而後響應給當前請求 render(res) // 在進入 router 模塊以前 // 咱們就已經給 res 對象加了一個屬性方法叫作:render // 而後請求和響應被傳遞到一個路由的模塊中 // 該模塊就是專門用來對不一樣的請求路徑分發到具體的請求處理函數 router(req, res) }) server.listen(config.port, config.host, function() { console.log('server is listening at port ' + config.port) console.log('pleast visit http://' + config.host + ':' + config.port) })
8.2.1.3.
配置文件config.js
8.2.1.3.1.
var path = require('path') module.exports = { port: 3000, host: '127.0.0.1', viewPath: path.join(__dirname, 'views'), uploadPath: path.join(__dirname, 'uploads') }
8.2.1.4.
路由文件router.js
8.2.1.4.1.
/** * 路由模塊:負責把具體的請求路徑分發的具體的請求處理函數 * 分發到具體的業務處理邏輯 * 用戶的每個請求都會對應後臺的一個具體的請求處理函數 */ var fs = require('fs') var path = require('path') var _ = require('underscore') var handler = require('./handler') var musicController = require('./controllers/music') var userController = require('./controllers/user') var url = require('url') module.exports = function(req, res) { // 首頁 / 呈遞音樂列表頁面 // 添加音樂頁面 /add 呈遞添加音樂頁面 // 編輯音樂頁面 /edit 呈遞編輯音樂頁面 // 刪除音樂 /remove 處理刪除音樂請求 // 當使用 url核心模塊的 parse方法以後,該方法會自動幫你把路徑部分解析到 pathname 屬性中 // 同時他會把 查詢字符串部分 解析到 query 屬性 // 對於咱們的 url.parse 方法來講,它還有第二個參數,咱們能夠給它指定爲 true,那麼這個時候它會自動幫咱們把 query 屬性查詢字符串轉換爲一個對象 var urlObj = url.parse(req.url, true) req.query = urlObj.query console.log(urlObj.query) // 獲取當前請求路徑 // pathname 不包含查詢字符串 var pathname = urlObj.pathname var method = req.method console.log(method) if (method === 'GET' && pathname === '/') { musicController.showIndex(req, res) } else if (method === 'GET' && pathname === '/index.html') { musicController.showIndex(req, res) } else if (method === 'GET' && pathname.startsWith('/node_modules/')) { var staticPath = path.join(__dirname, pathname) fs.readFile(staticPath, 'utf8', function(err, data) { if (err) { return res.end(err.message) } res.end(data) }) } else if (method === 'GET' && pathname === '/add') { musicController.showAdd(req, res) } else if (method === 'GET' && pathname === '/edit') { musicController.showEdit(req, res) } else if (method === 'GET' && pathname === '/login') { userController.showLogin(req, res) } else if (method === 'GET' && pathname === '/register') { userController.showRegister(req, res) } else if (method === 'POST' && pathname === '/add') { musicController.doAdd(req, res) } else if (method === 'GET' && pathname ==='/remove') { musicController.doRemove(req, res) } else if (method === 'POST' && pathname === '/edit') { musicController.doEdit(req, res) } } // res.render('path',{}) // render('path',{ // title: 'Index' // },res) // function render(viewPath, obj, res) { // fs.readFile(viewPath, 'utf8', function(err, data) { // if (err) { // return res.end(err.message) // } // var compiled = _.template(data) // var htmlStr = compiled(obj) // res.end(htmlStr) // }) // }
8.2.1.5.
解析模板標記語法render.js
8.2.1.5.1.
var fs = require('fs') var path = require('path') var _ = require('underscore') var config = require('../config') module.exports = function(res) { res.render = function(viewName, obj) { fs.readFile(path.join(config.viewPath, viewName) + '.html', 'utf8', function(err, data) { if (err) { return res.end(err.message) } var compiled = _.template(data) var htmlStr = compiled(obj || {}) res.end(htmlStr) }) } }
8.2.1.6.
封裝處理函數模塊music.js
8.2.1.6.1.
var qstring = require('querystring') var formidable = require('formidable') var config = require('../config') var path = require('path') var storage = [ { id: 1, title: '富士山下', singer: '陳奕迅', music: '陳奕迅 - 富士山下.mp3', poster: '陳奕迅.jpg' }, { id: 2, title: '石頭記', singer: '達明一派', music: '達明一派 - 石頭記.mp3', poster: '達明一派.jpg' }, { id: 3, title: '青城山下白素貞', singer: '好妹妹樂隊', music: '好妹妹樂隊 - 青城山下白素貞.mp3', poster: '好妹妹樂隊.jpg' }, { id: 4, title: '友情歲月', singer: '黃耀明', music: '黃耀明 - 友情歲月.mp3', poster: '黃耀明.jpg' }, { id: 5, title: '夢裏水鄉', singer: '江珊', music: '江珊 - 夢裏水鄉.mp3', poster: '江珊.jpg' }, { id: 6, title: 'Blowing In The Wind', singer: '南方二重唱', music: '南方二重唱 - Blowing In The Wind.mp3', poster: '南方二重唱.jpg' }, { id: 7, title: '女兒情', singer: '萬曉利', music: '萬曉利 - 女兒情.mp3', poster: '萬曉利.jpg' }, { id: 8, title: '王馨平', singer: '別問我是誰', music: '王馨平 - 別問我是誰.mp3', poster: '王馨平.jpg' }, { id: 9, title: '五環之歌', singer: '岳雲鵬', music: '岳雲鵬,MC Hotdog - 五環之歌.mp3', poster: '岳雲鵬.jpg' } ] exports.showIndex = function(req, res) { res.render('index', { title: '首頁', musicList: storage }) } exports.showAdd = function(req, res) { res.render('add', { title: '添加音樂' }) } exports.doAdd = function(req, res) { // 表單post提交的時候,沒有enctype的狀況下,可使用下面這種方式來接收和處理post提交的數據 // var data = '' // req.on('data',function (chunk) { // data += chunk // }) // req.on('end',function () { // var postBody = qstring.parse(data) // console.log(postBody) // res.end(JSON.stringify(postBody)) // }) // 若是要處理有文件的表單,那麼可使用社區提供的一個包:formidable var form = new formidable.IncomingForm() form.uploadDir = config.uploadPath form.keepExtensions = true form.parse(req, function(err, fields, files) { if (err) { return res.end(err.message) } var title = fields.title var singer = fields.singer var music = path.basename(files.music.path) var poster = path.basename(files.poster.path) var id = 0 storage.forEach(function(item) { if (item.id > id) { id = item.id } }) storage.push({ id: id + 1, title: title, singer: singer, music: music, poster: poster }) res.writeHead(302, { 'Location': 'http://127.0.0.1:3000/' }) res.end() }) } exports.showEdit = function(req, res) { var id = req.query.id var music = {} // 根據 id 查詢出該id在數組中對應的項 storage.forEach(function(item, index) { if (item.id == id) { music = item } }) res.render('edit', { title: '編輯音樂', music: music }) } exports.doEdit = function(req, res) { console.log('doedit 被執行了') var id = req.query.id // 獲取用戶提交的數據 var data = '' req.on('data', function(chunk) { data += chunk }) req.on('end', function() { var postBody = qstring.parse(data) // 根據id找到數據中該項的索引 var music_index = 0 storage.forEach(function (item, index) { if (item.id == id) { music_index = index } }) storage[music_index].title = postBody.title storage[music_index].singer = postBody.singer res.writeHead(302, { 'Location': 'http://127.0.0.1:3000/' }) res.end() }) // 而後作修改操做 } // 如何獲取和解析get請求提價的查詢字符串中參數 url模塊的parse方法的第二個參數 // 如何從數組中刪除一個元素 splice exports.doRemove = function(req, res) { // 獲取查詢字符串中的 id var id = req.query.id var music_index = 0 // 經過該 id 找到數組中的該項 storage.forEach(function(item, index) { if (item.id == id) { music_index = index } }) // 而後進行真正的刪除操做,根據索引下標進行刪除 storage.splice(music_index, 1) res.writeHead(302, { 'Location': 'http://127.0.0.1:3000/' }) res.end() }
8.2.1.7.
頁面顯示文件夾view下的index.html
8.2.1.7.1.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css"> </head> <body> <div class="container"> <div class="page-header"> <h1><a href="/">個人音樂</a> <small><%= title %></small></h1> </div> <a class="btn btn-success" href="/add">添加歌曲</a> <table class="table"> <thead> <tr> <th>編號</th> <th>標題</th> <th>歌手</th> <th>音樂名稱</th> <th>海報</th> <th>操做</th> </tr> </thead> <tbody> <% musicList.forEach(function(music){ %> <tr> <td><%= music.id %></td> <td><%= music.title %></td> <td><%= music.singer %></td> <td><%= music.music %></td> <td><%= music.poster %></td> <td> <a href="/edit?id=<%= music.id %>">修改</a> <a href="/remove?id=<%= music.id %>">刪除</a> </td> </tr> <% }) %> </tbody> </table> </div> </body> </html>
8.2.1.8.
添加歌曲頁面add.html
8.2.1.8.1.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css"> </head> <body> <div class="container"> <div class="page-header"> <h1><a href="/">個人音樂</a> <small><%= title %></small></h1> </div> <!-- 表單提交三要素: action 用來指定提交到那個請求地址 method 默認是get方式,當表單提交的時候,表單會把表單內的全部具備 name 屬性的 input 元素的值 以 name=value&name=value 的格式放到 url 地址中,而後發出請求 若是指定 method 爲 post 提交方式, 表單一樣的將表單內全部具備name屬性的input元素的值以 name=value&name=value 的格式 放到請求報問體 中,而後發出請求 若是要使用表單來上傳文件,那麼必須指定兩點: 1. 表單提交方法必須爲 post 2. 必定要 指定表單的 enctype 屬性 爲 multipart/form-data 上面兩點缺一不可,不然後臺收不到提交的文件數據 若是表單提交沒有file類型的input元素,那麼就不要手動的指定 enctype 若是有file類型的input元素,則必須指定enctype 屬性 爲 multipart/form-data --> <form action="/add" method="post" enctype="multipart/form-data"> <div class="form-group"> <label for="title">標題</label> <input type="text" class="form-control" id="title" name="title" placeholder="請輸入音樂標題"> </div> <div class="form-group"> <label for="artist">歌手</label> <input type="text" class="form-control" id="singer" name="singer" placeholder="請輸入歌手名稱"> </div> <div class="form-group"> <label for="music_file">音樂</label> <input type="file" id="music" name="music" accept="audio/*"> <p class="help-block">請選擇要上傳的音樂文件.</p> </div> <div class="form-group"> <label for="image_file">海報</label> <input type="file" id="poster" name="poster" accept="image/*"> <p class="help-block">請選擇要上傳的音樂海報.</p> </div> <button type="submit" class="btn btn-success">點擊添加</button> </form> </div> </body> </html>
8.2.1.9.
編輯歌曲頁面edit.html
8.2.1.9.1.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css"> </head> <body> <div class="container"> <div class="page-header"> <h1><a href="/">個人音樂</a> <small><%= title %></small></h1> </div> <form action="/edit?id=<%= music.id %>" method="post"> <div class="form-group"> <label for="title">標題</label> <input type="text" class="form-control" id="title" name="title" placeholder="請輸入音樂標題" value="<%= music.title %>"> </div> <div class="form-group"> <label for="artist">歌手</label> <input type="text" class="form-control" id="artist" name="singer" placeholder="請輸入歌手名稱" value="<%= music.singer %>"> </div> <!-- <div class="form-group"> <label for="music_file">音樂</label> <input type="file" id="music_file"> <p class="help-block">請選擇要上傳的音樂文件.</p> </div> <div class="form-group"> <label for="image_file">海報</label> <input type="file" id="image_file"> <p class="help-block">請選擇要上傳的音樂海報.</p> </div> --> <button type="submit" class="btn btn-success">肯定修改</button> </form> </div> </body> </html>
《Node.js核心技術教程》第1章 模塊化編程2019.2.19 13:30'1.1初識模塊化編程1.2模塊化編程的演變模塊化是一種設計思想,把一個很是複雜的系統結構細化到具體的功能點,每一個功能點看作一個模塊,而後經過某種規則把這些小的模塊組合到一塊兒,構成模塊化系統。模塊化編程可有效解決命名衝突問題和文件依賴的關係和順序問題。1.2.1全局函數1.2.2對象的命名空間 /* * 對象命名空間:內部成員的狀態能夠隨意被外部改寫,不安全。代碼可讀性隨着子命名空間延長可讀性差。 * 只是從理論意義上減小了命名衝突的問題,可是命名衝突仍是存在 */ var calculator = {}; //加法 calculator.add = function(x, y) { return parseInt(x) + parseInt(y); } //減法 calculator.subtract = function(x, y) { return parseInt(x) - parseInt(y); } //乘法 calculator.multiply = function(x, y) { return parseInt(x) * parseInt(y); } //除法 calculator.divide = function(x, y) { return parseInt(x) / parseInt(y); }//引用:result = calculator.add(x, y); //定義用於計算的函數:全部的變量和函數都暴露在全局,沒法保證全局變量與其餘模塊的變量發生衝突,也看不出全局變量與模塊成員之間的直接關係。 function add(x, y) { return parseInt(x) + parseInt(y); } function subtract(x, y) { return parseInt(x) - parseInt(y); } function multiply(x, y) { return parseInt(x) * parseInt(y); } function divide(x, y) { return parseInt(x) / parseInt(y); }//引用:result = add(x, y);//加1.2.3函數的做用域(閉包)/*函數的做用域(閉包):經過封裝函數的私有空間可讓一些屬性和方法私有化。經過匿名自執行函數,進行私有變量隔離。利用匿名自執行函數造成的封閉的函數做用域空間,達到自優化的目的*/ var calculator = ( function () { function add(x,y) { return parseInt(x)+parseInt(y); } function subtract(x,y) { return parseInt(x)-parseInt(y); } function multiply(x,y) { return parseInt(x)*parseInt(y); } function divide(x,y) { return parseInt(x)/parseInt(y); } return { add:add, subtract:subtract, multiply:multiply, divide:divide } })();//引用與命名空間相同:result=calculator.add(x,y);1.2.4維護和擴展//若是有第三方依賴的時候,可經過參數的形式將原來的模塊和第三方庫傳遞進去。 //傳遞參數cal var calculator = (function(cal) { //加法 function add(x, y) { return parseInt(x) + parseInt(y); } // 減法 function subtract(x, y) { return parseInt(x) - parseInt(y); } //乘法 function multiply(x, y) { return parseInt(x) * parseInt(y); } //除法 function divide(x, y) { return parseInt(x) / parseInt(y); } cal.add = add; cal.subtract = subtract; cal.multiply = multiply; cal.divide = divide; return cal; })(calculator || {}); //當擴展該模塊時,優先查找要擴展的對象是否已存在 // 從代碼上來看:下面的 calculator 已經把上面的 calculator 給覆蓋掉了 // 注意:在進行擴展的時候,優先查找要擴展的對象是否已存在 // 若是已存在,就使用已經存在的 // 若是不存在,就建立一個新的 // 最大的好處:加載的時候不用考慮順序了 var calculator = (function(cal) { //取餘方法 cal.mod = function(x, y) { return x % y; } return cal; })(calculator || {}); //引用:result = calculator.add(x, y);第2章 初識Node.js2019.2.22 17:30'2.1 Node.js概述2.2 Node.js簡介2.3 Node.js安裝和配置2.4 Node.js基礎入門有了Node.js,用JavaScript既能夠客戶端開發,又能夠服務器端開發,還能夠與數據庫交互。減小學習成本,快速打造全棧工程師。客戶端將用戶請求發送給服務器端。服務器端根據用戶的請求進行邏輯處理、數據處理並將結果響應給客戶端。如今,用Node.js來代替傳統的服務器語言,開發服務器端的Web框架。JavaScript是一種腳本語言,通常運行在客戶端,而Node.js可以使JavaScript運行在服務器端。JavaScript組成核心語法是ECMAScriptDOM是HTML的應用程序接口,是文檔對象模型BOM是瀏覽器對象模型,能夠對瀏覽器窗口進行訪問和操做JavaScript做用在客戶端主要用來處理頁面交互在服務器端主要用來處理數據交互解析:依賴瀏覽器提供的JavaScript引擎解析執行操做對象:對瀏覽器提供的DOM、BOM的解析進行操做常見操做:用戶交互、動畫特效、表單驗證、Ajax請求等解析:由特定的JavaScript引擎解析執行,如Node.jsG不依賴瀏覽器,不操做DOM和BOM。主要操做:客戶端作不到的事情,如操做數據庫和文件等概念在服務器端的運行環境或運行時平臺解析和執行JavaScript代碼提供一些功能性的API,如文件操做和網絡通訊API等2009年5月由RyanDahl把Chrome的V8引擎移植出來,在其上加上API特色和優點它是一個JavaScript運行環境,可脫離瀏覽器在服務器端單獨執行,代碼可共用。依賴ChromeV8引擎,在非瀏覽器下解析JavaScript代碼事件驅動Event-Driven非阻塞I/O(non-blocking I/O):使用事件回調的方式避免阻塞I/O所需的等待輕量、可伸縮,適用於實時數據交互,Socket可實現雙向通訊單進程單線程,異步編程模式,實現非阻塞I/O下載和安裝CMD命令臺Path環境變量快速體驗Node.js輸出內容到終端:創建文件:demo2-1.js console.log('hello world');執行: node demo2-1.js輸出內容到網頁:創建文件demo2-2.js//加載http模塊var http = require('http');//建立http服務器http.createServer(function(req, res) { //響應結束 res.end('hello world'); //監聽網址127.0.0.1 端口號3000}).listen(3000,'127.0.0.1');在命令行執行:node demo2-2.js 光標閃爍....打開瀏覽器,輸入網址:http://127.0.0.1:3000便會看到輸出的內容。REPL運行環境node 【Enter】>打開Chrome,按【F12】打開Console控制檯>global對象和模塊做用域//demo2-3.js global對象和模塊的做用域var foo = 'bar';console.log(foo);//global對象這時是沒有foo屬性的console.log('global:foo '+global.foo);//爲global對象掛載一個foo變量,並將該文件模塊中foo的值賦值給它global.foo = foo;//這是global.foo的值爲'bar'console.log('global:foo '+global.foo);//require()、exports、module exports//require()從外部獲取一個模塊的接口./是相對路徑,默認js文件//demo2-4.js//加載模塊var myModule = require('./info');console.log(myModule);//輸出模塊中的變量值console.log('name:'+myModule.name);console.log('type:'+myModule.type);console.log('age:'+myModule.age);//調用模塊的方法myModule.sayHello();//info.js被加載模塊//向外暴漏變量nameexports.name = 'itcast';exports.type='edu';//向外暴漏變量agemodule.exports.age='10';//向外暴漏函數module.exports.sayHello= function () { console.log('hello');}//require()、exports、module exports//demo2-5.js//加載模塊var myModule = require('./test');console.log(myModule);//輸出數組長度console.log('length:'+myModule.length);//test.js被加載模塊//使用module.exports能夠單獨定義數組,併成功對外開放// module.exports=['name','type','age'];//使用exports不能單獨定義exports=['name','type','age'];全局可用變量、函數和對象Node.js v10.15.1 DocumentationTable of ContentsGlobal Objects********************************Class: Buffer__dirname__filenameclearImmediate(immediateObject)clearInterval(intervalObject)clearTimeout(timeoutObject)consoleexportsglobalmoduleprocessrequire()setImmediate(callback[, ...args])setInterval(callback, delay[, ...args])setTimeout(callback, delay[, ...args])URLURLSearchParamsWebAssembly_dirname和_filename變量// 輸出全局變量 __dirname 的值console.log('文件的目錄是:'+ __dirname );// 輸出全局變量 __filename 的值console.log('文件的絕對路徑是:'+__filename );全局函數setImmediate(callback[, ...args])setInterval(callback, delay[, ...args])setTimeout(callback, delay[, ...args])clearImmediate(immediateObject)clearInterval(intervalObject)clearTimeout(timeoutObject)console對象console.log([data][, ...args])onsole.info([data][, ...args])console.error([data][, ...args])console.dir(obj[, options])console.time([label])console.timeEnd([label])console.trace([message][, ...args])console.assert(value[, ...message])Node.js模塊化重寫計算器案例add.jssubtract.jsmultiply.jsdivide.jsindex.jstestCal.jsnode testCal.js//加法module.exports = function (x, y) { return parseInt(x) + parseInt(y)}//減法module.exports = function (x, y) { return parseInt(x) - parseInt(y)}//乘法module.exports = function (x, y) { return parseInt(x) * parseInt(y)}//除法module.exports = function (x, y) { return parseInt(x) / parseInt(y)}//入口模塊module.exports = { add: require('./add'), subtract: require('./subtract'), multiply: require('./multiply'), divide: require('./divide')}//測試計算器功能var cal = require('./index');//在終端輸出計算結果console.log(cal.add(1, 2)); // => 3console.log(cal.subtract(1, 2)) ;// => -1console.log(cal.multiply(1, 2)); // => 2console.log(cal.divide(1, 2)) ;// => 0.5require()模塊的加載規則文件模塊的加載/開頭爲根路徑./../爲相對路徑.js擴展名可不加查找順序.js.json.node核心模塊的加載Node.js提供的基本API保存在lib目錄的源碼文件可直接加載,不用路徑全局對象經常使用工具事件機制文件訪問系統HTTP服務器與客戶端//demo2-7.js// 核心模塊就是一個固定標識// 若是寫錯,就沒法加載var os = require('os');//輸出CPU信息console.log(os.cpus());模塊的緩存require.cachefoo.jsconsole.log("foo模塊被加載了");//清除緩存delete require.cache[module.filename] ;// 對於同一個模塊標識,node 在第一次加載完成以後就會緩存該模塊// 下次繼續加載該模塊的時候,直接從緩存中獲取require('./foo');require('./foo');require('./foo');require('./foo');demo2-7.js第3章 異步編程和包資源管理2019.2.22 21.30'異步編程Node.js的包和NPM同步和異步回調函數包的概念NPM的概念NPM的基本應用包模塊加載規則/** * 同步代碼 */console.log('起牀');console.log('背單詞');//吃早餐function eatBreakfast() { console.log('早餐吃完了');}eatBreakfast();console.log('去上學');/** * 異步代碼 */console.log('起牀');console.log('背單詞');function eatBreakfast() { console.log('開始吃早餐了'); // setTimeout 執行的時候,不會阻塞後面代碼的繼續執行 setTimeout(function () { //異步函數 console.log('早餐吃完了'); }, 0);}eatBreakfast()console.log('去上學');概念:是指函數能夠被傳遞到另外一個函數中,而後被調用的形式。同步代碼中使用try...catch處理異常/** * 同步代碼處理異常 */function parseJsonStrToObj(str) { return JSON.parse(str)}// 對於同步代碼,咱們可使用 try-catch 來捕獲代碼執行可能出現的異常try { var obj = parseJsonStrToObj('foo') console.log(obj)} catch (e) { console.log('轉換失敗了')}異步代碼中沒法使用try...catch處理異常/** *異步代碼沒法使用try-catch處理異常 */function parseJsonStrToObj(str) { setTimeout(function() { return JSON.parse(str); }, 0);}//對於異步代碼的執行來講,try-catch 是沒法捕獲異步代碼中出現的異常的try { var obj = parseJsonStrToObj('foo'); console.log('執行結果是:' + obj);} catch (e) { console.log('轉換失敗了');}使用回調函數接收異步代碼的執行結果/** * try-catch寫在異步代碼中 */function parseJsonStrToObj(str) { setTimeout(function() { try{ return JSON.parse(str); }catch(e){ console.log('轉換失敗了'); } }, 0);}//調用方法輸出結果var obj = parseJsonStrToObj('foo'); //這種寫法沒法接收到第7行的返回值console.log('執行結果是:' + obj);//經過回調函數來接收異步代碼執行的處理結果function parseJsonStrToObj(str,callback) { setTimeout(function() { try { var obj = JSON.parse(str); callback(null, obj); } catch (e) { callback(e, null); } }, 0);}//注意區分錯誤信息和正確的數據信息parseJsonStrToObj('{"foo":"bar"}',function (err, result) { if (err) { return console.log('轉換失敗了'); } console.log('數據轉換成功,沒有問題能夠直接使用了:' + result);});回調函數:即當使用異步代碼去作一件事時,不能預測這件事何時作完,其餘的事情還在繼續,這時,可給異步代碼準備一個包裹,當異步代碼有了執行結果時能夠將結果放到這個包裹裏,須要在哪裏使用這個結果就從包裹取出。回調函數的三個約定:一、函數名稱一般爲callback,在封裝異步執行代碼時,優先把callback做爲函數的最後一個參數出現: function 函數名 (arg1,arg2,callback){}二、把代碼中出現的錯誤做爲callback回調函數的第一個參數進行傳遞:callback(err,result);三、把真正的返回的結果數據,傳遞給callback的第二個參數。callback(err,result);理解異步編程的「事件驅動」思路:在異步編程中,當異步函數執行時,不肯定什麼時候執行完畢,回調函數會被壓入到一個事件循環(EventLoop)的隊列,而後往下執行其餘代碼,直到異步函數執行完成後,纔會開始處理事件循環,調用相應的回調函數。這個事件循環隊列是一個先進先出的隊列,這說明回調是按照它們被加入隊列的順序執行的。包是在模塊的基礎上更進一步的組織JavaScript代碼的目錄,有出口模塊,遵循CommonJS規範目錄結構package.json在頂層目錄的包描述文件,說明文件bin 存放可執行二進制文件的目錄lib 存放JavaScript文件的目錄doc 存放文檔的目錄test 存放單元測試用例的代碼namedescriptionversionkeywordsauthordependenciesscripts全稱是Node.js Package Manage,含義一:是Node.js的開放模塊登記和管理系統,是一個NPM網站,http://www.npmjs.com,是全球最大的模塊生態系統,裏面的包都是經過Node.js實現的,開源免費,即查即用。含義二:是Node.js的包管理工具,一個命令行下的軟件,提供了一些命令用於快速安裝和管理模塊。npm init[-y] 初始化一個package.json文件npm install 包名 安裝一個包npm install -save 包名 將安裝的包添加到package.json的依賴中npm install -g 包名 安裝一個命令行工具npm docs 包名 查看包的文檔npm root -g 查看全局包安裝路徑npm config set prefix "路徑" 修改全局包安裝路徑npm list 查看當前目錄下安裝的全部包npm list -g 查看全局包的安裝路徑下全部的包npm uninstall 包名 卸載當前目錄下的某個包npm uninstall -g 包名 卸載全局安裝路徑下的某個包npm update 包名 更新當前目錄下的某個包包管理的使用場景從NPM服務器下載別人編寫的第三方包到本地使用從NPM服務器下載並安裝別人編寫的命令行程序到本地使用容許將本身編寫的包或命令行程序上傳到NPM 服務器供別人使用安裝npm install markdownnode-moudules目錄自動建立。存放第三方包,目錄名和內容不能修改除了markdown,另外的包是其依賴包一、在加載時,Node.js會默認當作核心模塊去加載。若是發現標識名不是核心模塊,就會在當前目錄的node_modules目錄下尋找。若是沒有,則從當前目錄的父目錄的node_modules裏搜索,遞歸下去直到根目錄。二、若是找到了該標識名的子目錄,Node.js將會找到該子目錄下的package.json文件,獲取其main屬性的值,根據main屬性指定的路徑值進行加載。用戶不用關心入口模塊是哪個文件。第4章 Node.js 文件操做2019.2.23 2:40'基本文件操做案例-控制歌詞滾動文件相關操做路徑字符串操做目錄操做文件寫入向文件追加內容文件讀取文件複製獲取文件信息加載fs(FileSystem)模塊var fs=require('fs');//同步寫入fs.writeFileSync(file,data[,option]);//異步寫入fs.writeFile(file,data[,option],callback);/* * 同步方式寫入文件 */var fs = require('fs');// 在進行文件操做的時候,若是是同步 API,必須使用 try-catch 來捕獲異常// 防止程序由於異常退出,致使後續代碼沒法繼續執行 try { console.log('寫入文件...') fs.writeFileSync('D:/a.txt', '傳智播客'); } catch (e) { console.log('很差意思,文件寫入失敗了') }/* * 異步方式寫入文件 */var fs = require('fs');console.log(1);//該方法中回調函數的第一個參數爲錯誤對象fs.writeFile('d:/b.txt', '傳智播客', function(err) { //判斷是否出現錯誤,進行錯誤提示 if (err) { console.log('很差意思,文件寫入失敗了'); } console.log(2);})console.log(3);appendFile(file,data[,option],callback);option默認爲:utf8,0o666,'a'/** 向文件追加內容*/var fs = require('fs');//定義須要追加的數據var data = '歡迎您';//調用文件追加函數fs.appendFile('D:/a.txt', data, function(err) { if (err) { // 出錯的狀況下,回調函數中的代碼就不要繼續日後執行了 // 因此可使用return 的方式,阻止代碼繼續執行 return console.log('文件追加失敗了'); } // 但願在文件追加成功以後作一些事情 console.log('文件追加成功了');});/* * 文件讀取 */var fs = require('fs');//讀取文件 fs.readFile('D:/a.txt', function(err, data) { if (err) { return console.log('文件讀取失敗'); } // 由於計算機中全部的數據最終保存的都是 二進制 數據 // 因此能夠經過調用 toString() 方法將二進制數據轉換爲人類能夠識別的字符 console.log(data.toString()); });/* * 文件複製案例 */var fs = require('fs');//讀取a.txt文件數據 fs.readFile('D:/a.txt', function(err, data) { if (err) { return console.log('讀取文件失敗了'); } //將數據寫入c.txt文件 fs.writeFile('D:/c.txt', data.toString(), function(err) { if (err) { return console.log('寫入文件失敗了'); } }); console.log('文件複製成功了'); });//封裝copy再複製//建立封裝文件demo4-6.js/* * 文件複製模塊 */var fs = require('fs');/*定義文件複製函數copy()* src:須要讀取的文件* dist:目標文件* callback:回調函數* */function copy(src, dist, callback) { //讀取文件 fs.readFile(src, function(err, data) { if (err) { return callback(err); } //寫入文件 fs.writeFile(dist, data.toString(), function(err) { if (err) { return callback(err); } callback(null); }); });}module.exports = copy;//測試文件複製test.js/* * 測試文件複製 *///加載封裝好的文件複製功能模塊var copy = require('./demo4-6');//調用copy()函數 copy('D:/a.txt','D:/d.txt',function(err){ if(err){ return console.log('文件複製失敗了'); } console.log('文件複製成功了'); });var fs = require('fs');fs.stat('D:/a.txt', function (err, stats) { //判斷是不是文件 console.log("是不是文件:"+stats.isFile()); console.log(stats); //輸出文件信息})var fs = require('fs');//讀取歌詞文件fs.readFile('./lrc.txt', function(err, data) { if (err) { return console.log('讀取歌詞文件失敗了'); } data = data.toString(); var lines = data.split('\n'); // 遍歷全部行,經過正則匹配裏面的時間,解析出毫秒 // 須要裏面的時間和裏面的內容 var reg = /\[(\d{2})\:(\d{2})\.(\d{2})\]\s*(.+)/; for (var i = 0; i < lines.length; i++) { (function(index) { var line = lines[index]; var matches = reg.exec(line); if (matches) { // 獲取分 var m = parseFloat(matches[1]); // 獲取秒 var s = parseFloat(matches[2]); // 獲取毫秒 var ms = parseFloat(matches[3]); // 獲取定時器中要輸出的內容 var content = matches[4]; // 將分+秒+毫秒轉換爲毫秒 var time = m * 60 * 1000 + s * 1000 + ms; //使用定時器,讓每行內容在指定的時間輸出 setTimeout(function() { console.log(content); }, time); } })(i); }});[ti:我想] [ar:張傑] [t_time:(03:46)] [00:00.00] 我想 - 張傑[00:02.00] 詞:王笠人[00:04.00] 曲:王笠人[00:06.00] 編曲:梁思樺[00:08.00] 歌詞編輯:果果 [00:10.00] QQ:765708831 [00:13.00] 中文歌詞庫 www.cnLyric.com[00:18.23] 每件事情都有發生的理由[00:21.72] 可沒法解釋 碰見你[00:26.15] 再多的心理準備 都抵抗不了[00:29.89] 被現實戰勝的愛情[00:34.27] 咱們都習慣了同一個溫度[00:38.08] 你說這叫幸福[00:42.32] 同時也忽略了一種殘酷[00:45.39] 我以爲 好無助[00:50.69] 我想 我想[00:53.54] 我想一塊兒越過全部困難和阻擋[00:57.64] 而咱們 卻不同[01:01.58] 雖然都有共同的理想[01:06.10] 窗外 有陽光[01:09.76] 透過了一絲縫隙照亮了一點但願[01:14.07] 而晚上 的月亮[01:17.94] 讓咱們再次陷入了彷徨[01:25.66] 你問爲何喜歡拍照記錄[01:29.27] 答案我卻說不出[01:33.17] 怕若是咱們走了不一樣方向[01:37.06] 有照片讓我回顧[01:41.36] 回憶咱們去過的每個地方[01:45.41] 和時而停頓的腳步[01:49.43] 就這麼停停頓頓一步接一步[01:53.75] 直到沒有路[01:57.91] 我想 我想[02:00.86] 我想一塊兒越過全部困難和阻擋[02:04.95] 而咱們 卻不同[02:08.74] 雖然都有共同的理想[02:13.15] 窗外 有陽光[02:16.83] 透過了一絲縫隙照亮了一點但願[02:21.01] 而晚上 的月亮[02:25.30] 讓咱們再次陷入了彷徨[02:30.44] 若是每個清晨[02:33.82] 在你的溫度裏甦醒[02:37.82] 閉眼聆聽 有節奏的呼吸[02:41.63] 哪怕只是一瞬間[02:43.68] 哪怕只是一場夢[02:50.74] 我想 我想[02:53.60] 我想一塊兒越過全部困難和阻擋[02:57.73] 而咱們 卻不同[03:01.43] 雖然都有共同的理想[03:06.16] 窗外 有陽光[03:09.81] 透過了一絲縫隙照亮了一點但願[03:13.72] 而晚上 的月亮[03:18.04] 讓咱們再次陷入了彷徨[03:23.35] 每件事情都有發生的理由[03:26.88] 可沒法解釋 碰見你[03:31.17] 再多的心理準備 都抵抗不了[03:35.11] 被命運安排的相遇獲取路徑模塊var path=require('path');> str='D:\Node.js''D:Node.js'> path.basename(str)'Node.js'> path.dirname(str)'D:'> path.extname(str)'.js'>拼接路徑字符串和轉換標準路徑path.join()建立目錄fs.mkdir(path[,model],callback);/* *建立目錄,必須逐級建立目錄,若是越級則出錯 */var fs = require('fs');console.log('在C:/Course目錄下建立目錄testDemo4-8');fs.mkdir('D:/Course/testDemo4-8/',function(err){ if (err) { return console.error(err); } console.log("目錄建立成功。");});讀取目錄/* *讀取目錄 */var fs = require('fs');console.log('查看/testDemo4-8目錄');fs.readdir('/Node.js/code/',function(err, files){ if (err) { return console.error(err); } //遍歷全部文件 files.forEach( function (file){ // 輸出文件名 console.log( file ); });});刪除目錄/* *刪除目錄 */var fs=require('fs'); console.log('讀取 /testDemo4-8 目錄'); fs.readdir('/Course/testDemo4-8/',function(err, files){ if (err) { return console.error(err); } //遍歷全部文件 files.forEach( function (file){ // 輸出文件名 console.log( file ); //刪除文件 fs.unlink('/Course/testDemo4-8/'+file, function(err) { if (err) { return console.error(err); } console.log(file+'文件刪除成功!'); }); }); console.log('準備刪除/testDemo4-8目錄'); fs.rmdir('/Course/testDemo4-8/',function(err){ if (err) { return console.error(err); } console.log("目錄刪除成功!"); }); });第5章 Node.js中處理數據I/O2019.2.23 12:00'Buffer緩衝區限制大小1GB二進制數據和亂碼Buffer的構造函數從緩衝區讀取數據拼接緩衝區Stream文件流文件流的概念Node.js的可讀流和可寫流使用pipe()處理大文件亂碼是指計算機二進制數據在轉換字符的過程當中,使用了不合適的字符集,而形成的部分或全部的字符沒法被閱讀。緩衝區是在內容中操做數據的容器。Node.js的Buffer模塊是全局性的,不須要require()函數來加載建立緩衝區傳入字節:var buf=new Buffer(size);傳入數組:var buf=new Buffer([10,20,30,40,50]);傳入字符串和編碼:var buf=new Buffer("hello","utf-8");寫入緩衝區/* * 寫入緩衝區:格式 buf.write(string[,offset[,length]][,encoding]); *///建立一個能夠存儲 5 個字節的內存空間對象 var buf = new Buffer(5); // 經過 buffer 對象的 length 屬性能夠獲取 buffer 緩存中的字節大小 console.log(buf.length);//向緩衝區寫入a buf.write('a');//輸出緩衝區數據 console.log(buf);//向緩衝區寫入b buf.write('b', 1, 1, 'ascii');//輸出緩衝區數據 console.log(buf);/* * 讀取緩衝區:格式:buf.toString([encoding[,start[,end]]]); *///建立一個能夠存儲26個字節的內存空間對象 var buf = new Buffer(26);//像buffer數組中存入26個字母對應的編碼for (var i = 0 ; i < 26 ; i++) { buf[i] = i + 97;}//輸出所有字母console.log( buf.toString('ascii')); // 輸出: abcdefghijklmnopqrstuvwxyz//輸出前五個字母console.log( buf.toString('ascii',0,5)); // 輸出: abcde// 輸出: 'abcde'console.log(buf.toString('utf8',0,5));// 輸出: 'abcde', 默認編碼爲 'utf8'console.log(buf.toString(undefined,0,5));/* * 拼接緩衝區:buf.concat(list[,totalLength]); *///建立兩個緩衝區 var buf = new Buffer('世上無難事,'); var buf1 = new Buffer('只怕有心人');//執行拼接操做var buf2= Buffer.concat([buf,buf1]);//輸出拼接後緩衝區的內容console.log("buf2 內容: " + buf2.toString());四種流類型Readable可讀操做可讀流Writable可寫操做可寫流Duplex可讀可寫操做雙向流、雙工流Transform操做被寫入數據,而後讀出結果(變換流)全部的Stream對象都是EventEmitter(時間觸發器)的實例事件data當有數據可讀時觸發end沒有更多的數據可讀時觸發error在接收和寫入發生錯誤時觸發finish全部數據已被寫入到底層系統時觸發可讀流/** * 從流中讀取數據 */var fs = require("fs");var total = '';// 建立可讀流var readableStream = fs.createReadStream('input.txt');// 設置編碼爲 utf8。readableStream.setEncoding('UTF8');// 處理流事件 data\end\and\error//綁定data事件,附加回調函數,流開始流動readableStream.on('data', function(chunk) { total += chunk;});//讀取結束後,輸出totalreadableStream.on('end',function(){ console.log(total);});//若是出錯,輸出提示信息readableStream.on('error', function(err){ console.log(err.stack);});console.log("程序執行完畢");可寫流/** * 使用文件流進行文件拷貝 */var fs = require('fs');//建立可讀流var readableStream = fs.createReadStream('input.txt');//建立可寫流var writableStream = fs.createWriteStream('output.txt');readableStream.setEncoding('utf8');readableStream.on('data', function(chunk){ //將讀出的數據塊寫入可寫流 writableStream.write(chunk);});readableStream.on('error', function(err){ console.log(err.stack);});readableStream.on('end',function(){ //將剩下的數據所有寫入,而且關閉寫入的文件 writableStream.end();});writableStream.on('error', function(err){ console.log(err.stack);});/** * 使用pipe()進行文件拷貝 */var fs = require('fs')//源文件路徑var srcPath = 'd:/node.js/code/chapter05/demo5-7/input.txt';//目標文件路徑var distPath = 'd:/node.js/code/chapter05/demo5-7/input.txtoutput.txt';var readableStream = fs.createReadStream(srcPath);var writableStream = fs.createWriteStream(distPath);// 能夠經過使用可讀流 的函數 pipe ()接入到可寫流中// pipe()是一個很高效的數據處理方式if(readableStream.pipe(writableStream)){ console.log('文件複製成功了')}else{ console.log('文件複製失敗了')}第6章 Node.js網絡編程2019.2.23 16:20’6.1Node.js網絡編程基礎IP地址和端口號套接字Socket簡單模型6.2Node.js中實現套接字服務Net.Server對象Net.Socket對象6.3Node.js進程管理Process模塊獲取終端輸入多人廣播消息6.4案例--終端聊天室IP地址是用來定位一臺計算機的,既能夠是服務器,也能夠是客戶端端口號是用來定位應用程序的TCP/IP協議TCP:Transfer Control Protocol,傳輸控制協議,是一種穩定可靠的傳送方式。TCP負責發現傳輸的問題,一有問題就發出信號,要求從新傳輸,直到全部數據安全正確地傳輸到目的地爲止。IP是給互聯網的每一臺聯網設備規定一個地址。TCP/IP協議包含因特網整個TCP/IP協議簇應用層面:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet協議等。Socket孔插座,可理解爲接口對象,網絡編程中也稱套接字,經常使用於描述IP地址和端口等。Socket是支持TCP/IP的網絡通訊的基本操做單元,能夠看作是不一樣主機之間的進程進行雙向通訊的端點,簡單的說就是通訊兩方的一種約定。Socket就是對TCP/IP協議的封裝,Socket自己並非協議,而是一個調用接口(API)。Socket進行網絡通訊必須的5種信息鏈接使用的協議客戶端設備的IP地址客戶端的端口號服務期端的IP地址服務器端口套接字地址就是IP地址和端口號的組合套接字服務不需處理get和post請求,而是採用點對點的傳輸數據方式,是一個輕量級的網絡通訊解決方案套接字服務服務器用來監聽鏈接,客戶端用來打開一個到服務器的鏈接。Node.js自己就是一個服務器Net模塊的APInet.createServer([options][, connectionListener])建立一個TCP服務器net.connect(options[, connectListener])net.createConnection(options[, connectListener])net.connect(port[, host][, connectListener])net.createConnection(port[, host][, connectListener]) net.connect(path[, connectListener])net.createConnection(path[, connectListener])net.isIP(input)net.isIPv4(input)net.isIPv6(input)建立Net.Server對象:var server = net.createServer([options][, connectionListener])Net.Server對象函數Server對象的事件server.listen(handle[, backlog][, callback])server.listen(options[, callback])server.listen(path[, backlog][, callback])server.listen([port[, host[, backlog]]][, callback])server.address()server.close([callback])server.ref()server.unref()server.getConnections(callback)'close' 事件'connection' 事件'error' 事件'listening' 事件第3步:安裝Telnet設置->應用->右側 相關設置:程序和功能->左側 啓動或關閉windows功能在Telnet客戶端前的複選框裏打鉤按肯定示例程序第1步:建立服務器端文件demo6-1.js/** * Net.Servet建立服務器 */// 1. 加載 manychat 核心模塊var net = require('net');// 2. 建立一個服務應用程序,獲得一個服務器實例對象var server = net.createServer();// 3. 監聽客戶端的鏈接事件connection,鏈接成功就會執行回調處理函數server.on('connection',function () { console.log('有客戶端鏈接上來了');});// 5. 服務器有一個事件叫作 listening ,表示開啓監聽成功以後回調處理函數server.on('listening',function () { console.log('服務器開啓監聽成功了,正在等待客戶端鏈接');});// 4. 啓動服務器,開啓監聽// 監聽 127.0.0.1:3000 只能被本機所訪問server.listen(3000,'127.0.0.1');第2步:在命令行窗口運行:node demo6-1.js第4步:另開終端窗口運行:telnet 127.0.0.1 3000記住:端口3000前面是空格而不是冒號這時運行demo6-1窗口會顯示「有客戶鏈接上來了」表示客戶端與服務器端鏈接成功!!!Duplex(雙工)流接口,是可讀可寫流Socket事件Socket屬性Socket函數lookupconnectdataendtimeoutdrainerrorclosesocket.bufferSizesocket.remoteAddresssocket.remoteFamilysocket.remotePortsocket.localAddresssocket.localPortsocket.bytesReadsocket.bytesWrittennew net.Socket([options])socket.connect(path[, connectListener])socket.connect(port[, host][, connectListener])socket.setEncoding([encoding])socket.write(data[, encoding][, callback])socket.destroy([exception])socket.pause()socket.resume()socket.setTimeout(timeout[, callback])socket.setKeepAlive([enable][, initialDelay])socket.setNoDelay([noDelay])socket.address()socket.ref()socket.unref()服務器向客戶端發送消息/** * 在服務器端使用Socket */// 1. 加載 manychat 核心模塊var net = require('net');// 2. 建立一個服務應用程序,獲得一個服務器實例對象var server = net.createServer();// 3. 監聽客戶端的鏈接事件,鏈接成功就會執行回調處理函數// 每次回調函數被調用,就會有一個新的 socket 對象在回調函數中server.on('connection',function (socket) { console.log('有客戶端鏈接上來了'); //在服務端能夠獲取到客戶端的IP地址等信息 console.log('客戶端IP地址:' + socket.remoteAddress + '鏈接到了當前服務器'); // 當前鏈接成功以後的客戶端發送一個 hello world socket.write('hello world');});// 5. 服務器有一個事件叫作 listening ,表示開啓監聽成功以後回調處理函數server.on('listening',function () { console.log('服務器開啓監聽成功了,正在等待客戶端鏈接');});// 4. 啓動服務器,開啓監聽server.listen(3000,'127.0.0.1');服務器端運行node demo6-1客戶端運行 telnet 127.0.0.1 3000統計在線人數/** * 服務端統計在線人數 */var net = require('net');var server = net.createServer();var count = 0;server.on('connection', function(socket) { count++; console.log('welcome , 當前在線人數:' + count); socket.write('remoteAddress'+socket.remoteAddress+'\n'); socket.write('remotePort'+socket.remotePort);});server.listen(3000, '127.0.0.1', function() { console.log('server listening at port 3000');});服務器端運行 node demo6-3.js打開多個命令窗口分別運行 telnet 127.0.0.1 3000客戶端與服務器端雙向通訊建立客戶端/* * 雙向通訊-客戶端 */var net = require('net');// 當調用 createConnection 以後,就會獲得一個與服務器進行通訊的 socket 對象// 該對象中包含當前客戶端與服務器通訊的 ip地址和端口號var client = net.createConnection({ port: 3000});// 何時客戶端和服務器鏈接成功了// 能夠經過監聽 client 的 connect 事件來處理client.on('connect',function () { // 客戶端與服務器鏈接成功了 console.log('客戶端與服務器鏈接成功了'); client.write('你吃了嗎?');});client.on('data',function (data) { //輸出服務器發送給當前客戶端的數據 console.log(data.toString());});建立服務器端/** * 雙向通訊-服務器 */var net = require('net');var server = net.createServer();// 每個客戶端與服務器創建鏈接成功以後,都會觸發一次 connection 事件server.on('connection', function(socket) { /*如下部分應用於雙向通訊*/ //經過監聽 socket 對象的 data 事件來獲取客戶端發送給服務器的數據 socket.on('data', function(data) { console.log(data.toString()); socket.write('我吃的小豆包'); });});server.listen(3000, '127.0.0.1', function() { console.log('server listening at port 3000');});若是在服務器端啓動telnet 127.0.0.1 3000 則可即時通信/** * 測試獲取終端輸入 */// 經過下面的方式就能夠獲取用戶的輸入process.stdin.on('data',function (data) { console.log(data.toString().trim());});一、建立目錄manychat,並建立兩個文件server.js和client.js//server.js/** * 多人廣播聊天服務端端 */var net = require('net');var server = net.createServer();//該數組用來封裝全部客戶端的Socketvar users = [];server.on('connection', function(socket) { users.push(socket); socket.on('data', function(data) { data = data.toString().trim(); users.forEach(function(client) { if (client !== socket) { //因爲同一臺計算機上不一樣客戶端端口號不一樣,因此能夠經過端口號來區分是誰說的話 client.write(socket.remotePort+ ':' + data); } }); }); // 當有客戶端異常退出的時候,就會觸發該函數 // 若是不監聽客戶端異常退出就會致使服務器崩潰 socket.on('error',function () { console.log('有客戶端異常退出了'); });})server.listen(3000, '127.0.0.1', function() { console.log('server listening at port 3000');});//client.js/** * 多人廣播聊天客戶端 */var net = require('net')//向服務端建立鏈接var client = net.createConnection({ port:3000, host:'127.0.0.1'});//監聽鏈接成功事件connentclient.on('connect',function () { // 經過當前進程的標準輸入的 data 事件獲取終端中的輸入 process.stdin.on('data',function (data) { data = data.toString().trim(); client.write(data); });});//監聽data事件輸入服務器返回的數據client.on('data',function (data) { console.log(data.toString());});配置文件:configmodule.exports = { "port": 3000, "host": "127.0.0.1"}客戶端文件:client.js/** * 終端聊天室客戶端 */var net = require('net');var config = require('./config');var client = net.createConnection({ port: config.port, host: config.host})//用戶註冊成功後爲該屬性賦值var username;client.on('connect', function() { console.log('請輸入用戶名:'); process.stdin.on('data', function(data) { data = data.toString().trim(); // 當用戶註冊成功以後,下面的數據格式就不能再使用了 // 判斷一下是否已經有用戶名了,若是已經有了,則表示用戶要發送聊天數據 // 若是沒有,則表示用戶要發送註冊數據 if (!username) { var send = { protocal: 'signup', username: data } client.write(JSON.stringify(send)); return; } //判斷是廣播消息仍是點對點消息 // name:內容 var regex = /(.{1,18}):(.+)/; var matches = regex.exec(data); if (matches) { var from = username; var to = matches[1]; var message = matches[2]; var send = { protocal: 'p2p', from: username, to: to, message: message } client.write(JSON.stringify(send)); } else { var send = { protocal: 'broadcast', from: username, message: data } client.write(JSON.stringify(send)); } });});client.on('data', function(data) { data = JSON.parse(data); switch (data.protocal) { case 'signup': var code = data.code; switch (code) { case 1000: username = data.username; console.log(data.message); break; case 1001: console.log(data.message); break; default: break; } break; case 'broadcast': console.log(data.message); break; case 'p2p': var code = data.code; switch (code) { case 2000: var from = data.from; var message = data.message; message = from + '對你說:' + message; console.log(message); break; case 2001: console.log(data.message); break; default: break; } break; default: break; };});服務器端文件:server.js/** * 終端聊天室服務端 */var net = require('net');var config = require('./config');var broadcast=require('./broadcast.js');var p2p=require('./p2p.js');var signup=require('./signup.js');var server = net.createServer();var users = {};server.on('connection', function(socket) { socket.on('data', function(data) { // 解析客戶端發送的數據 data = JSON.parse(data); // 根據客戶端發送的數據類型,作對應的操做 switch (data.protocal) { case 'signup': //處理用戶註冊 signup.signup(socket,data,users); break; //處理廣播消息 case 'broadcast': broadcast.broadcast(data,users); break; case 'p2p': // 處理點對點消息 p2p.p2p(socket, data,users); break; default: break; } }); socket.on('error', function() { console.log('有客戶端異常退出了'); });});// 3. 啓動服務器,開啓監聽server.listen(config.port, config.host, function() { console.log('server listening at port ' + config.port);});用戶註冊模塊:signup.js/** * 用戶註冊 * @param socket * @param data 用戶名 * {protocal: 'signup', username: '小明'} * @param users 用戶組 */exports.signup=function (socket,data,users) { // 處理用戶註冊請求 var username = data.username; // 若是用戶名不存在,則將該用戶名和它的Socket地址保存起來 if (!users[username]) { users[username] = socket; var send = { protocal: 'signup', code: 1000, username: username, message: '註冊成功' } socket.write(JSON.stringify(send)); } else { var send = { protocal: 'signup', code: 1001, message: '用戶名已被佔用,請從新輸入用戶名:' } socket.write(JSON.stringify(send)); }}廣播消息模塊:broadcast.js/** * 廣播消息 * @param data 廣播消息發送過來的JSON數據 * { "protocal": "broadcast",//消息類型爲廣播 "from": "小紅",//發送消息的用戶 "message": "你們早上好"//用戶發送的消息內容} */exports.broadcast= function(data,users) { var from = data.from; var message = data.message message = from + '說:' + message; var send = { protocal: 'broadcast', message: message } send = new Buffer(JSON.stringify(send)); for (var username in users) { var tmpSocket = users[username]; tmpSocket.write(send); }}點對點消息模塊:p2p.js/** * 點對點消息 * @param socket * @param data 點對點消息的JSON數據 * { "protocal": "p2p", //消息類型爲點對點 "from": "小紅", //發送消息的用戶 "to": "小明", "message": "你早上吃的什麼"} * @param users 用戶組 */exports.p2p=function (socket,data,users) { var from = data.from; var to = data.to; var message = data.message; // 找到要發送給某我的的 Socket 地址對象 var receiver = users[to]; // 若是接收人不存在,告訴客戶端沒有該用戶 if (!receiver) { var send = { protocal: 'p2p', code: 2001, message: '用戶名不存在' } socket.write(new Buffer(JSON.stringify(send))); } else { // xxx 對你說: xxx var send = { protocal: 'p2p', code: 2000, from: data.from, message: message } receiver.write(new Buffer(JSON.stringify(send))); } // 若是接收人存在,則將消息發送給該用戶}第7章 Node.js中實現HTTP服務2019.2.23 22:35'7.1HTTP協議HTTP協議簡介HTTP請求響應流程HTTP的請求報文和響應報文7.2Node.js的HTTP服務HTTP模塊經常使用API使用HTTP模塊構建Web服務器7.3HTTP服務請求處理根據不一樣的URL發送不一樣響應消息HTTP處理靜態資源服務動態處理靜態資源請求HTTP:Hyper Text Transfer Protocol 超文本傳輸協議1990年提出用於從WWW服務器傳輸超文本到本地瀏覽器的傳輸協議基於TCP的鏈接方式開放系統互連參考模型OSI/RM通訊協議七層應用層Application Layer協議有HTTP、FTP、SMTP等表示層Presentation Layer會話層Session Layer傳輸層Transport Layer網絡層Network Layer數據鏈路層Data Link Layer物理層Physics LayerASCII碼、JPEG、MPEG、WAV等文件轉換負責訪問次序的安排等協議有TCP、UDP等三層交換機、路由器等協議有IP、SPX二層交換機、網橋網卡等集線器、中繼器和傳輸線路等HTTP由請求和響應構成,是一個標準的客戶端服務器模型,也是一個無狀態的協議。各大瀏覽器普遍基於HTTP1.1HTTP協議特色支持客戶/服務器模式簡單快速:GET/HEAD/POST靈活:容許任意類型數據無鏈接:處理完一個請求和響應即斷開鏈接無狀態:對事務處理沒有記憶能力URL由幾部分組成:協議+域名+具體地址HTTP、POP三、FTP域名或IP地址及端口號:www.itheima.com具體地址:index.html通常加密或不顯示經過DNS解析請求request,把URL地址等封裝成HTTP請求報文,存放在客戶端Socket對象中響應response,把數據封裝在HTTP響應報文,並存放在Socket對象中報文是有必定格式的字符串,查看報文須要藉助工具,例如chrome內核版本 65.0.3325.181請求報文requestGET / HTTP/1.1Host: www.itheima.comConnection: keep-aliveCache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: UM_distinctid=1691db04響應報文responseHTTP/1.1 200 OKServer: TengineContent-Type: text/html; charset=UTF-8Transfer-Encoding: chunkedConnection: keep-aliveDate: Sun, 24 Feb 2019 04:11:14 GMTAccept-Ranges: bytesAli-Swift-Global-Savetime: 1550981474Via: cache4.l2et15[26,200-0,M], cache14.l2et15[27,0], kunlun5.cn1259[77,200-0,M], kunlun5.cn1259[77,0]X-Cache: MISS TCP_MISS dirn:-2:-2X-Swift-SaveTime: Sun, 24 Feb 2019 04:11:14 GMTX-Swift-CacheTime: 0Timing-Allow-Origin: *EagleId: da5ed29915509814747132159e加載語法:var http = require('http');http.ServerHTTP服務器指的就是http.Server對象Node.js的全部基於HTTP協議的系統都是基於http.Server實現的如網站、社交應用、代理服務器等建立語法:var server = http.createServer();函數server.close([callback])server.listen(port[,hostname][,backlog][,callback])server.listen(handle[,callback])/server.listen(path[,callback])事件requestconnectionclosehttp.IncomingMessage可讀流req函數&屬性message.headersmessage.urlmessage.httpVersionMessage.methodmessage.setTimeout(msecs,callback)message.sockethttp.ServerResponse可寫流res函數&屬性response.writeHead(statusCode,[headers])response.write(data,[enconding])response.end([data],[enconding])response.addTrailers(headers)response.finishedresponse.getHeader(name)response.headersSentresponse.removeHeader(name)response.sendDateresponse.setHeader(name, value)response.setTimeout(msecs[, callback])response.statusCoderesponse.statusMessageresponse.writeContinue()/** * 使用HTTP構建Web服務器 */var http = require('http');// 1. 建立一個 HTTP 服務器var server = http.createServer();// 2. 監聽 請求(request) 事件// request 就是一個可讀流,用來 獲取 當前與服務器鏈接的客戶端的一些請求報文數據// response 就是一個可寫流,用來 給 客戶端 Socket 發送消息的,或者用來發送響應報文的server.on('request',function (request, response) { // 使用 HTTP 發送響應數據的時候,HTTP 服務器會自動把數據經過 HTTP 協議包裝爲一個響應報文而後發送到Socket response.write('hello world'); // 在結束響應以前,咱們能夠屢次向 客戶端 發送數據 response.write('hello itheima'); // 對於 HTTP 請求響應模型來講,它們的請求和響應是一次性的 // 也就是說,每一次請求都必須結束響應, // 標識斷開當前鏈接 response.end(); // 在一次 HTTP 請求響應模型中,當結束了響應,就不能繼續發送數據了,如下消息不會顯示});// 3. 開啓啓動,監聽端口server.listen(3000,function () { console.log('server is listening at port 3000');});/** * 根據不一樣URL響應不一樣消息 */var http = require('http');//建立服務器var server = http.createServer();//監聽request事件server.on('request', function(request, response) { //獲取資源路徑,默認爲'/' var url = request.url; //經過判斷獲取到的資源路徑,發送指定響應消息 if (url === '/') { response.end('hello index'); } else if (url === '/login') { response.end('hello login'); } else if (url === '/register') { response.end('hello register'); }else { //若是路資源徑找不到,提示錯誤信息 response.end('404 Not Found!'); }});//開啓啓動,監聽端口server.listen(3000, function() { console.log('server is listening at port 3000');});主程序demo7-3.js/** * 使用HTTP提供靜態資源服務 */var http = require('http');var fs = require('fs');//用於讀取靜態資源var path = require('path');//用於作路徑拼接var server = http.createServer();server.on('request', function(request, response) { //獲取靜態資源路徑 var url = request.url; if (url === '/') { //讀取相應靜態資源內容 fs.readFile(path.join(__dirname, 'static/index.html'), 'utf8', function(err, data) { //若是出現異常拋出異常 if (err) { throw err; } //將讀取的靜態資源數據響應給瀏覽器 response.end(data); }); } else if (url === '/login') { fs.readFile(path.join(__dirname, 'static/login.html'), 'utf8', function(err, data) { if (err) { throw err; } response.end(data); }); } else if (url === '/register') { fs.readFile(path.join(__dirname, 'static/register.html'), 'utf8', function(err, data) { if (err) { throw err; } response.end(data); }); } else if (url === '/login.html') { fs.readFile(path.join(__dirname, 'static/404.html'), 'utf8', function(err, data) { if (err) { throw err } response.end(data); }); //若是有圖片、CSS文件等,瀏覽器會從新發送請求獲取靜態資源 } else if (url === '/css/main.css') { var cssPath = path.join(__dirname, 'static/css/main.css') fs.readFile(cssPath, 'utf8', function(err, data) { if (err) { throw err } response.end(data); }); } else if (url === '/images/01.jpg') { var imgPath = path.join(__dirname,'static/images/01.jpg') fs.readFile(imgPath, function(err, data) { if (err) { throw err } response.end(data); }); } else { fs.readFile(path.join(__dirname, 'static/404.html'), 'utf8', function(err, data) { if (err) { throw err } response.end(data); }); }});server.listen(3000, function() { console.log('server is listening at port 3000');});index.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>首頁</title> <link rel="stylesheet" href="css/main.css"></head><body> <h1>首頁</h1><img src="images/01.jpg" alt=""></body></html>login.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>登陸</title> <link rel="stylesheet" href="css/main.css"></head><body> <h1>登陸</h1> <img src="images/01.jpg" alt=""></body></html>register.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>註冊</title> <link rel="stylesheet" href="css/main.css"></head><body> <h1>註冊</h1> <img src="images/01.jpg" alt=""></body></html>404.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>404</title></head><style> body { background-color:pink; }</style><body> <h1>404 Not Found.</h1></body></html>main.cssbody { background-color: pink;}01.jpgdemo7-4.js/** * 動態處理靜態資源請求 */var http = require('http');var fs = require('fs');var path = require('path');var server = http.createServer();server.on('request', function(req, res) { // 當用戶訪問 / 的時候,默認讓用戶訪問 index.html var url = req.url; console.log(url);//每次請求獲取資源路徑在服務端輸出。 var fullPath = path.join(__dirname,'static',url); if (url==='/') { fullPath = path.join(__dirname,'static/index.html'); } fs.readFile(fullPath,function (err,data) { if (err) { // 在進行web開發的時候,若是發生了錯誤,咱們能夠直接把該錯誤消息輸出到 客戶端 return res.end(err.message); } res.end(data); });});server.listen(3000, function() { console.log('server is runnig at port 3000');});Underscore的模板引擎templatetemplate用於將JavaScript模板編譯爲能夠用於頁面呈現的函數,經過JSON數據源生成複雜的HTML並呈現出來語法:_.template(templateString,[settings])賦值:var compiled = _.template("hello:<%=name%>");compiled({name:'moe'});=>"hello:moe"須要轉義:var template = _.template("<b><%- value %></b>");template({value:'<script>'});=>"<b><script></b>"在Node.js中使用Underscore須要用NPM安裝在static-server目錄下初始化:npm init -y在此目錄下繼續輸入:npm install underscore --save在此目錄下建立index.heml,並添加代碼package.json<!DOCTYPE html><html><head> <meta charset="utf-8"> <body> <ul> <% arr.forEach(function(item){ %> <li> <%= item.name %> </li> <%}) %> </ul> <h1><%= title %></h1> </body>備註:在服務器端定義了一個數組arr,遍歷該數組的每一個元素,爲元素掛着name屬性。再定義一個變量title。這個頁面是做爲一個網頁的模板使用,在服務器端會得到這個模板併爲模板中的變量賦值,最後,將完整的數據做爲一個字符串響應給瀏覽器端,由瀏覽器進行頁面渲染和呈現{ "name": "static-server", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "underscore": "^1.8.3" }}/** * 服務端代碼 */var http = require('http');var fs = require('fs');var path = require('path');var _ = require('underscore');var server = http.createServer();server.on('request', function(req, res) { var url = req.url; if (url === '/') { //備註:讀取index.html模板的內容,並將返回的data傳入模板函數_.template,傳入後可使用compiled()函數向該模板的數組和變量注入數據,最後將完整的HTML數據響應給客戶端瀏覽器!!! fs.readFile(path.join(__dirname, 'index.html'), 'utf8', function(err, data) { if (err) { return res.end(err.message); } // 如今的 data 就是 字符串 // 我把 html 字符串 總體的當成 模板字符串 var compiled = _.template(data); var htmlStr = compiled({ title: 'hello world', arr: [ { name: 'Jack' }, { name: 'rose' }, { name: 'mike' } ] }); res.end(htmlStr); }); }});server.listen(3000, function() { console.log('server is runnig at port 3000');});在此目錄下建立文件app.js,並添加服務器端代碼子主題最後,啓動服務器:node app.js客戶端打開瀏覽器查看第8章 綜合項目--個人音樂2019.2.24 16:00var http = require('http')var server = http.createServer(function (req,res) { res.end('<h1> hello world </h1>') })server.listen(3000)03_http.js04_瀏覽器的本質.js+++++++++++++++++var net = require('manychat')var server = net.createServer()server.on('connection',function (socket) { socket.on('data',function (data) { console.log(data.toString()) // 對於 客戶端來講, 這個時候,服務器發送給本身的消息,有沒有發送完畢,不肯定 // 因此 瀏覽器客戶端 保持了掛起的狀態,繼續等待 服務器給本身傳輸消息數據 socket.write('HTTP/1.1 200 成功了\n\nhello world') // 若是想告訴客戶端,本次的數據已經給你發送完畢了,不用等了 ,結束響應 // 結束響應就意味着,本次請求響應鏈接斷開,。 socket.end() })})server.listen(3000)項目簡介項目功能展現項目開發流程一、產品創意二、產品原型三、美工設計四、前端實現五、後端實現六、測試、試運行、上線需求分析一、數據模型分析二、路由設計三、功能開發展現歌曲信息編輯歌曲信息刪除歌曲添加歌曲項目結構一、render.js解析模板標記語法二、music.js封裝音樂文件相關的邏輯處理函數三、node_modules文件夾下的bootstrap響應式前端框架四、node_modules文件夾下的underscore:模板引擎用於注入後臺數據五、node_modules文件夾下的formidate:用於表單的數據處理,尤爲是表單的文件上傳處理六、uploads文件夾:用於存放MP3音頻文件和jpg圖片文件七、views文件夾:用於存放頁面八、app.js:項目的入口文件九、config.js配置端口十、package.json項目的說明文件十一、router.js:路由模塊,根據用戶的請求判斷路徑,而後將請求分發到具體的處理函數項目實現項目初始化項目說明文件package.json{ "name": "music-player", "version": "1.0.0", "description": "一個簡單的音樂播放器", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node app.js" }, "author": "iroc <mail@lipengzhou.com> (https://github.com/iroc)", "license": "MIT", "dependencies": { "bootstrap": "^3.3.6", "formidable": "^1.0.17", "underscore": "^1.8.3" }}"start": "nodemon app.js"當不知道入口文件時,會經過下面的命令自動找到start指令:npm start "dependencies": { "bootstrap": "^3.3.6", "formidable": "^1.0.17", "underscore": "^1.8.3" }須要選裝第三方依賴包npm install --save bootstrapnpm install --save formidable入口文件app.jsvar http = require('http')var config = require('./config')var router = require('./router')var render = require('./common/render')var server = http.createServer()server.on('request', function(req, res) { // 首先動態的給 Response 對象掛載了一個 render 方法,該方法用來讀取一個模板文件,注入數據,而後響應給當前請求 render(res) // 在進入 router 模塊以前 // 咱們就已經給 res 對象加了一個屬性方法叫作:render // 而後請求和響應被傳遞到一個路由的模塊中 // 該模塊就是專門用來對不一樣的請求路徑分發到具體的請求處理函數 router(req, res)})server.listen(config.port, config.host, function() { console.log('server is listening at port ' + config.port) console.log('pleast visit http://' + config.host + ':' + config.port)})配置文件config.jsvar path = require('path')module.exports = { port: 3000, host: '127.0.0.1', viewPath: path.join(__dirname, 'views'), uploadPath: path.join(__dirname, 'uploads')}路由文件router.js/** * 路由模塊:負責把具體的請求路徑分發的具體的請求處理函數 * 分發到具體的業務處理邏輯 * 用戶的每個請求都會對應後臺的一個具體的請求處理函數 */var fs = require('fs')var path = require('path')var _ = require('underscore')var handler = require('./handler')var musicController = require('./controllers/music')var userController = require('./controllers/user')var url = require('url')module.exports = function(req, res) { // 首頁 / 呈遞音樂列表頁面 // 添加音樂頁面 /add 呈遞添加音樂頁面 // 編輯音樂頁面 /edit 呈遞編輯音樂頁面 // 刪除音樂 /remove 處理刪除音樂請求 // 當使用 url核心模塊的 parse方法以後,該方法會自動幫你把路徑部分解析到 pathname 屬性中 // 同時他會把 查詢字符串部分 解析到 query 屬性 // 對於咱們的 url.parse 方法來講,它還有第二個參數,咱們能夠給它指定爲 true,那麼這個時候它會自動幫咱們把 query 屬性查詢字符串轉換爲一個對象 var urlObj = url.parse(req.url, true) req.query = urlObj.query console.log(urlObj.query) // 獲取當前請求路徑 // pathname 不包含查詢字符串 var pathname = urlObj.pathname var method = req.method console.log(method) if (method === 'GET' && pathname === '/') { musicController.showIndex(req, res) } else if (method === 'GET' && pathname === '/index.html') { musicController.showIndex(req, res) } else if (method === 'GET' && pathname.startsWith('/node_modules/')) { var staticPath = path.join(__dirname, pathname) fs.readFile(staticPath, 'utf8', function(err, data) { if (err) { return res.end(err.message) } res.end(data) }) } else if (method === 'GET' && pathname === '/add') { musicController.showAdd(req, res) } else if (method === 'GET' && pathname === '/edit') { musicController.showEdit(req, res) } else if (method === 'GET' && pathname === '/login') { userController.showLogin(req, res) } else if (method === 'GET' && pathname === '/register') { userController.showRegister(req, res) } else if (method === 'POST' && pathname === '/add') { musicController.doAdd(req, res) } else if (method === 'GET' && pathname ==='/remove') { musicController.doRemove(req, res) } else if (method === 'POST' && pathname === '/edit') { musicController.doEdit(req, res) }}// res.render('path',{})// render('path',{// title: 'Index'// },res)// function render(viewPath, obj, res) {// fs.readFile(viewPath, 'utf8', function(err, data) {// if (err) {// return res.end(err.message)// }// var compiled = _.template(data)// var htmlStr = compiled(obj)// res.end(htmlStr)// })// }解析模板標記語法render.jsvar fs = require('fs')var path = require('path')var _ = require('underscore')var config = require('../config')module.exports = function(res) { res.render = function(viewName, obj) { fs.readFile(path.join(config.viewPath, viewName) + '.html', 'utf8', function(err, data) { if (err) { return res.end(err.message) } var compiled = _.template(data) var htmlStr = compiled(obj || {}) res.end(htmlStr) }) }}封裝處理函數模塊music.jsvar qstring = require('querystring')var formidable = require('formidable')var config = require('../config')var path = require('path')var storage = [ { id: 1, title: '富士山下', singer: '陳奕迅', music: '陳奕迅 - 富士山下.mp3', poster: '陳奕迅.jpg' }, { id: 2, title: '石頭記', singer: '達明一派', music: '達明一派 - 石頭記.mp3', poster: '達明一派.jpg' }, { id: 3, title: '青城山下白素貞', singer: '好妹妹樂隊', music: '好妹妹樂隊 - 青城山下白素貞.mp3', poster: '好妹妹樂隊.jpg' }, { id: 4, title: '友情歲月', singer: '黃耀明', music: '黃耀明 - 友情歲月.mp3', poster: '黃耀明.jpg' }, { id: 5, title: '夢裏水鄉', singer: '江珊', music: '江珊 - 夢裏水鄉.mp3', poster: '江珊.jpg' }, { id: 6, title: 'Blowing In The Wind', singer: '南方二重唱', music: '南方二重唱 - Blowing In The Wind.mp3', poster: '南方二重唱.jpg' }, { id: 7, title: '女兒情', singer: '萬曉利', music: '萬曉利 - 女兒情.mp3', poster: '萬曉利.jpg' }, { id: 8, title: '王馨平', singer: '別問我是誰', music: '王馨平 - 別問我是誰.mp3', poster: '王馨平.jpg' }, { id: 9, title: '五環之歌', singer: '岳雲鵬', music: '岳雲鵬,MC Hotdog - 五環之歌.mp3', poster: '岳雲鵬.jpg' }]exports.showIndex = function(req, res) { res.render('index', { title: '首頁', musicList: storage })}exports.showAdd = function(req, res) { res.render('add', { title: '添加音樂' })}exports.doAdd = function(req, res) { // 表單post提交的時候,沒有enctype的狀況下,可使用下面這種方式來接收和處理post提交的數據 // var data = '' // req.on('data',function (chunk) { // data += chunk // }) // req.on('end',function () { // var postBody = qstring.parse(data) // console.log(postBody) // res.end(JSON.stringify(postBody)) // }) // 若是要處理有文件的表單,那麼可使用社區提供的一個包:formidable var form = new formidable.IncomingForm() form.uploadDir = config.uploadPath form.keepExtensions = true form.parse(req, function(err, fields, files) { if (err) { return res.end(err.message) } var title = fields.title var singer = fields.singer var music = path.basename(files.music.path) var poster = path.basename(files.poster.path) var id = 0 storage.forEach(function(item) { if (item.id > id) { id = item.id } }) storage.push({ id: id + 1, title: title, singer: singer, music: music, poster: poster }) res.writeHead(302, { 'Location': 'http://127.0.0.1:3000/' }) res.end() })}exports.showEdit = function(req, res) { var id = req.query.id var music = {} // 根據 id 查詢出該id在數組中對應的項 storage.forEach(function(item, index) { if (item.id == id) { music = item } }) res.render('edit', { title: '編輯音樂', music: music })}exports.doEdit = function(req, res) { console.log('doedit 被執行了') var id = req.query.id // 獲取用戶提交的數據 var data = '' req.on('data', function(chunk) { data += chunk }) req.on('end', function() { var postBody = qstring.parse(data) // 根據id找到數據中該項的索引 var music_index = 0 storage.forEach(function (item, index) { if (item.id == id) { music_index = index } }) storage[music_index].title = postBody.title storage[music_index].singer = postBody.singer res.writeHead(302, { 'Location': 'http://127.0.0.1:3000/' }) res.end() }) // 而後作修改操做}// 如何獲取和解析get請求提價的查詢字符串中參數 url模塊的parse方法的第二個參數// 如何從數組中刪除一個元素 spliceexports.doRemove = function(req, res) { // 獲取查詢字符串中的 id var id = req.query.id var music_index = 0 // 經過該 id 找到數組中的該項 storage.forEach(function(item, index) { if (item.id == id) { music_index = index } }) // 而後進行真正的刪除操做,根據索引下標進行刪除 storage.splice(music_index, 1) res.writeHead(302, { 'Location': 'http://127.0.0.1:3000/' }) res.end()}頁面顯示文件夾view下的index.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css"></head><body> <div class="container"> <div class="page-header"> <h1><a href="/">個人音樂</a> <small><%= title %></small></h1> </div> <a class="btn btn-success" href="/add">添加歌曲</a> <table class="table"> <thead> <tr> <th>編號</th> <th>標題</th> <th>歌手</th> <th>音樂名稱</th> <th>海報</th> <th>操做</th> </tr> </thead> <tbody> <% musicList.forEach(function(music){ %> <tr> <td><%= music.id %></td> <td><%= music.title %></td> <td><%= music.singer %></td> <td><%= music.music %></td> <td><%= music.poster %></td> <td> <a href="/edit?id=<%= music.id %>">修改</a> <a href="/remove?id=<%= music.id %>">刪除</a> </td> </tr> <% }) %> </tbody> </table> </div></body></html>添加歌曲頁面add.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css"></head><body> <div class="container"> <div class="page-header"> <h1><a href="/">個人音樂</a> <small><%= title %></small></h1> </div> <!-- 表單提交三要素: action 用來指定提交到那個請求地址 method 默認是get方式,當表單提交的時候,表單會把表單內的全部具備 name 屬性的 input 元素的值 以 name=value&name=value 的格式放到 url 地址中,而後發出請求 若是指定 method 爲 post 提交方式, 表單一樣的將表單內全部具備name屬性的input元素的值以 name=value&name=value 的格式 放到請求報問體 中,而後發出請求 若是要使用表單來上傳文件,那麼必須指定兩點: 1. 表單提交方法必須爲 post 2. 必定要 指定表單的 enctype 屬性 爲 multipart/form-data 上面兩點缺一不可,不然後臺收不到提交的文件數據 若是表單提交沒有file類型的input元素,那麼就不要手動的指定 enctype 若是有file類型的input元素,則必須指定enctype 屬性 爲 multipart/form-data --> <form action="/add" method="post" enctype="multipart/form-data"> <div class="form-group"> <label for="title">標題</label> <input type="text" class="form-control" id="title" name="title" placeholder="請輸入音樂標題"> </div> <div class="form-group"> <label for="artist">歌手</label> <input type="text" class="form-control" id="singer" name="singer" placeholder="請輸入歌手名稱"> </div> <div class="form-group"> <label for="music_file">音樂</label> <input type="file" id="music" name="music" accept="audio/*"> <p class="help-block">請選擇要上傳的音樂文件.</p> </div> <div class="form-group"> <label for="image_file">海報</label> <input type="file" id="poster" name="poster" accept="image/*"> <p class="help-block">請選擇要上傳的音樂海報.</p> </div> <button type="submit" class="btn btn-success">點擊添加</button> </form> </div></body></html>編輯歌曲頁面edit.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css"></head><body> <div class="container"> <div class="page-header"> <h1><a href="/">個人音樂</a> <small><%= title %></small></h1> </div> <form action="/edit?id=<%= music.id %>" method="post"> <div class="form-group"> <label for="title">標題</label> <input type="text" class="form-control" id="title" name="title" placeholder="請輸入音樂標題" value="<%= music.title %>"> </div> <div class="form-group"> <label for="artist">歌手</label> <input type="text" class="form-control" id="artist" name="singer" placeholder="請輸入歌手名稱" value="<%= music.singer %>"> </div> <!-- <div class="form-group"> <label for="music_file">音樂</label> <input type="file" id="music_file"> <p class="help-block">請選擇要上傳的音樂文件.</p> </div> <div class="form-group"> <label for="image_file">海報</label> <input type="file" id="image_file"> <p class="help-block">請選擇要上傳的音樂海報.</p> </div> --> <button type="submit" class="btn btn-success">肯定修改</button> </form> </div></body></html>