介紹
寫后臺管理程序,與之類似php .net java
目標
數據服務,文件服務,web服務
優勢
性能高,方便、入門難度低、大公司都在用(BAT)
劣勢
- 服務器提供的相對較少
- 相對其他語言,能用的上的學習資料少
- 對程序員的要求高了
環境安裝
測試環境: win+r->命令行(運行->cmd)->node -v
版本
Vx(主).x(子).x(修正)
主版本: 變化了,1/3的API發生巨變 , 使用方式變化了
子版本: API沒有刪減,使用方式沒變化,內部實現發生了變化
修正版:什么都沒變,處理一下bug
V6.8.0 穩定
V6.9.1 非穩定版
beta 測試
rc 、alpha測試穩定
node命令行
node 回車
運行
window
a. 找到目標目錄-》地址欄輸入cmd-》node 文件名.js | node 文件名
b. 當前目錄->右鍵->git bash-> node 文件名
蘋果
終端->cd 目錄-> node 文件名.js | node 文件名
vscode
新建終端->cd 目錄->node 文件名.js | node 文件名
調試->運行
webstrom
terminal| run
開發注意
nodejs
使用的是ECMA
語法,不可使用DOM
,BOM
web服務器
構成
- 機器: 電腦
- 數據庫:mysql | sqlserver | mongoDB | oracle
- 數據庫存的是: 數字|字符
- 磁盤(硬盤) 文件本身(圖,視頻,PDF) 文件服務器
- 管理程序:nodejs(管理前后端工程文件)
前后端交互流程
大后端
? 用戶 - > 地址欄(http[s]請求) -> web服務器(收到) - > nodejs處理請求(返回靜態、動態)->請求數據庫服務(返回結果)->nodejs(接收)->node渲染頁面->瀏覽器(接收頁面,完成最終渲染)
大前端
? 用戶 - > http[s]請求 -> web服務器(收到) - > nodejs處理請求(返回靜態、動態)->請求數據庫服務(返回結果)->nodejs(接收)->返回給前端(渲染)->瀏覽器(接收頁面,完成最終渲染)
實現
引入http模塊
let http = require('http')
創建web服務 返回http對象
let app = http.createServer((req,res)=>{
req 請求體 瀏覽器->服務器
req.url 地址 提取地址欄數據
req.on('data',()=>{}) 提取非地址欄數據 所有的http[s]都會觸發end事件 這個可能一次抓不完,寫一個變量累加,抓完后,要寫end在end里面進行后續操作
req.on('end',()=>{})
res 響應 服務器->瀏覽器
res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'});響應頭設置
res.write(字符/數據<string><buffer>) 返回數據
res.end() 結束響應 必須
})
監聽服務器
app.listen(端口,[地址],[回調])
監聽成功,回調一次
端口: 1-65535 1024以下系統占用
虛擬地址localhost 真實域名xx.duapp.com
更新后,需要每次服務器自動重啟
推薦命令行工具:
supervisor
nodemon
安裝方式:
npm install supervisor -g
fs模塊
磁盤操作,文件操作
讀取
fs.readFile('文件路徑',[編碼方式],(err,data)=>{})
[^err ]: err 錯誤 ,null沒有錯誤 //讀取失敗
編碼方式可以設置成utf-8 如果不設置會自動成為buffer流
變量 = fs.readFileSync('文件路徑')
處理錯誤
try{要排錯的代碼}catch(e){}
try{
//要測試代碼錯誤代碼
let data = fs.readFileSync('./html/index123.html');
console.log('data',data)
}catch(e){//錯誤事件對象 error / ev / e
//處理錯誤,包裝后續代碼正常
console.log('e',e)
}
更名
fs.renameSync('改前','改后',回調可以捕獲錯誤);
fs.rename('改前','改后',(err)=>{})
刪除
fs.unlinkSync('文件路徑')
靜態資源托管
什么是靜態資源
xx.css
xx.html
xx.js
xx.圖片
xx.json
xx.字體
...
前端資源請求
<a href=".."></a>
<img src="..."/>
location.href="..."
body{
background:url(....)
}
后端資源讀取
fs.readFile(文件名,[編碼方式],回調(err,data));
接口實現
前端
表單:get/post/put/delete/...
js: ajax/jsonp
后端
處理方式:http[s]
? address: req.url
抓取 get請求的數據 切字符 | url模塊
? !address: req.on('data',(chunk)=>{CHUNK==每次收到的數據buffer})
? req.on('end',()=>{ 接收完畢 切字符 querystring })
postman 一個不用寫前端,就可以發出各種請求的軟件
url模塊
作用
處理 url型的字符串
用法
url.parse(str,true) 返回 對象 true將query處理為對象
str -> obj 返回 對象 true
protocol: 'http:', 協議
slashes: true, 雙斜杠
auth: null, 作者
host: 'localhost:8002', 主機
port: '8002', 端口
hostname: 'localhost', baidu
hash: '#title', 哈希(錨)
search: '?username=sdfsdf&content=234234', 查詢字符串
query: 'username=sdfsdf&content=234234', 數據
pathname: '/aaa', 文件路徑
path: '/aaa?username=sdfsdf&content=234234', 文件路徑
href: 'http://localhost:8002/aaa?username=sdfsdf&content=234234#title'
url.format(obj) 返回字符
obj -> str 返回str
querystring 模塊
作用
處理查詢字符串 如:?key=value&key2=value2
用法
querystring.parse(str) 返回對象
querystring.stringify(obj) 返回字符串
模塊化 commonJS
介紹
是主要為了JS在后端的表現制定,commonJS 是個規范 nodejs / webpack 是一個實現
ECMA 是個規范 js / as 實現了他
其他模塊化規范:seajs.js / require.js CMD/AMD/UMD es5
作用
是變量具有文件作用域,不污染全局變量
系統模塊
http
fs
querystring
url
輸入
require('模塊名')
require('模塊名').xx 按需引用
不指定路徑:先找系統模塊-> 再從項目環境找node_modules|bower_components (依賴模塊)->not found
指定路徑 : 找指定路徑 -> not found
支持任何類型
輸出
exports.自定義屬性 = 值 | any
批量輸出 都是屬性
可輸出多次
module.exports = 值 | any
只能輸出一次
注意
commonJS 是 nodejs 默認模塊管理方式,不支持es6的模塊化管理方式,但支持所有es6+語法
使用: commonjs 引入 let http = require(“http”)
? 導出 exports.a = a 單個導出,但是可以輸出多次
? module.exports = a 讓這個a可以作為對象數組什么的,輸出全部,但是只能輸出一次
? es6模塊管理方式:引入 import d from './mod/d'
? 導出 export default d
? 舉例 export 可以導出的是一個對象中包含的多個 屬性,方法。
? export default 只能導出 一個 可以不具名的 對象。
? import {fn} from ‘./xxx/xxx’ ( export 導出方式的 引用方式 )
? import fn from ‘./xxx/xxx1’ ( export default 導出方式的 引用方式
NPM
作用
幫助你安裝模塊(包),自動安裝依賴,管理包(增,刪,更新,項目所有包)
類似: bower yarn
安裝到全局環境
- 安裝到電腦系統環境下
- 使用時在任何位置都可以使用
- 被全局安裝的通常是:命令行工具,腳手架
npm i 包名 -g 安裝
npm uninstall 包名 -g 卸載
安裝到項目環境
只能在當前目錄使用,需要使用npm代運行
初始化項目環境
npm init
初始化npm管理文件package.json
package-lock.json 文件用來固化依賴
{
"name": "npm", //項目名稱
"version": "0.0.1", //版本
"description": "test and play", //描述
"main": "index.js", //入口文件
"dependencies": { //項目依賴 上線也要用
"jquery": "^3.2.1"
},
"devDependencies": { //開發依賴 上線就不用
"animate.css": "^3.5.2"
},
"scripts": { //命令行
"test": "命令行",
},
"repository": { //倉庫信息
"type": "git",
"url": "git+https://github.com/alexwa9.github.io/2017-8-28.git"
},
"keywords": [ //關鍵詞
"test",'xx','oo'
],
"author": "wan9",
"license": "ISC", //認證
"bugs": {
"url": "https://github.com/alexwa9.github.io/2017-8-28/issues"http://問題提交
},
"homepage": "https://github.com/alexwa9.github.io/2017-8-28#readme"http://首頁
}
項目依賴
只能在當前項目下使用,上線了,也需要這個依賴 --save
//安裝
npm i 包名 --save
npm install 包名 -S
npm install 包名@x.x.x -S
//卸載
npm uninstall 包名 --save
npm uninstall 包名 -S
開發依賴
只能在當前項目下使用 ,上線了,依賴不需要了 --save-dev
npm install 包名 --save-dev
npm install 包名 -D
查看包
npm list 列出所有已裝包
npm outdated 版本對比(安裝過得包)
npm info 包名 查看當前包概要信息
npm view 包名 versions 查看包歷史版本列表
安裝所有依賴
npm install
安裝package.json里面指定的所有包
版本約束
^x.x.x 約束主版本,后續找最新
~x.x.x 保持前兩位不變,后續找最新
* 裝最新
x.x.x 定死了一個版本
選擇源
npm install nrm -g 安裝選擇源的工具包
nrm ls 查看所有源
nrm test 測試所有源
nrm use 切換源名
安裝卡頓時
ctrl + c -> npm uninstall 包名 -> npm cache 清除緩存 -> 換4g網絡 -> npm install 包名
npm run XXX 會運行package下scripts下的命令
發布包
- 官網 注冊
- 登錄
-
npm login
登錄 - 輸入 user/password/email
-
- 創建包
npm init -y
- 創建入口index.js
- 編寫,輸出
- 發布
npm publish
- 迭代
- 先修改package里面版本號
npm publish
- 刪除
npm unpublish
包的發布、迭代、刪除,需要在包目錄下進行
刪除包,有時需要發送郵件
擴展
peerDependencies 發布依賴
optionalDependencies 可選依賴
bundledDependencies 捆綁依賴
contributors 為你的包裝做出貢獻的人。貢獻者是一群人。
files 項目中包含的文件。您可以指定單個文件,整個目錄或使用通配符來包含符合特定條件的文件
YARN
安裝
注意:為省事,不要用npm i yarn -g,去安裝yarn,而是去下載壓縮包,保證注冊表和環境變量的硬寫入,后期通過yarn安裝全局包時方便
使用
初始化一個新項目
yarn init
添加依賴包
yarn add [package]
yarn add [package]@[version]
yarn add [package]@[tag]
將依賴項添加到不同依賴項類別中
分別添加到 dependencies
,devDependencies
、peerDependencies
和 optionalDependencies
類別中:
yarn add [package] --save | -S
yarn add [package] --dev | -D
yarn add [package] --peer
yarn add [package] --optional
升級依賴包
yarn upgrade [package]
yarn upgrade [package]@[version]
yarn upgrade [package]@[tag]
移除依賴包
yarn remove [package]
安裝項目的全部依賴
yarn
或者
yarn install
安裝到全局
yarn global add [package] //global的位置測試不能變
yarn global remove [package]
BOWER
安裝bower
npm install -g bower
安裝包到全局環境
bower i 包名 -g 安裝
bower uninstall 包名 -g 卸載
安裝包到項目環境
初始化項目環境
bower init
bower.json 第三方包管理配置文件
項目依賴
只能在當前項目下使用,上線了,也需要這個依賴 --save
//安裝
同npm
bower install 包名#x.x.x -S 指定版本使用#
//卸載
同npm
開發依賴
只能在當前項目下使用 ,上線了,依賴不需要了 --save-dev
同npm
EXPRESS
nodejs庫,不用基礎做起,工作簡單化,點擊進入官網,類似的還有 koa
特點
二次封裝,非侵入式,增強形
搭建web服務
let express=require('express')
let server=express()
let server.listen(端口,地址,回調)
靜態資源托管
server.use(express.static('./www'));
多資源托管
app.use(express.static(path.join(__dirname, 'public','template')));
app.use('/admin',express.static(path.join(__dirname, 'public','admin'))); //加別名。訪問admin時會訪問admin下的靜態資源
app.use(express.static(path.join(__dirname, 'public'))); //使靜態資源托管返回擴大到public下
接口響應
支持各種請求姿勢:get、post、put、delete...
app.請求姿勢API(接口名稱,處理函數)
app.get(url,(req,res,next)=>{})
app.post(url,(req,res,next)=>{})
...
req 請求體
request 對象表示 HTTP 請求,包含了請求查詢字符串,參數,內容,HTTP 頭部等屬性
req.query //獲取地址欄的數據
req.body //獲取非地址欄的數據 依賴中間件 body-parser
中間件使用:body-parser
1. npm install body-parser
2. let bodyParser = require('body-parser')
3. app.use(bodyParser ())
req.params //獲取動態接口名 返回一個對象{id:3}
eg:app.get('/api/goods/:id',(req,res)=>{
// console.log('詳情',req.params)
})
req.method //獲取前端提交方式
req.body依賴中間件
中間件使用:body-parser
- npm install body-parser
- let bodyParser = require('body-parser')
- app.use(bodyParser ())
res 響應體
response 對象表示 HTTP 響應,即在接收到請求時向客戶端發送的 HTTP 響應數據
res.send(any) //對等 res.write + res.end
res.end(string|buffer)
res.json(json) //返回json
res.status(404).send({error:1,msg:"Sorry can't find that!"}) //返回狀態碼返回一個404
res.jsonp(響應數據) //調用請求時的回調函數并傳遞響應數據
res.sendFile(path.resolve('public/error.html'))//渲染純 HTML 文件
jsonp響應
server.set('jsonp callback name','cb')//默認callback
server.get('/jsonp接口',(req,res,next)=>res.jsonp(數據))
處理一部分接口
共有業務邏輯,在一起給處理了
server.all('/admin/*',(req,res,next)=>{}))
all匹配全路徑 處理所有HTTP
需要next 延續后續
use
安裝中間件、路由、接受一個函數,
server.use([地址],中間件|路由|函數體)
中間件
middleware, 處理自定義業務,只處理請求到結束響應的中間部分
舉例
npm i body-parser -S //安裝包
let bodyParser=require('body-parser')//引入中間件
server.use(bodyParser())//安裝中間件
body-parser 使用方式,實時查詢 npm,可獲得最新
后端跳轉
res.redirect(url) 指向一個接口
用新接口代替老接口,在老接口res.redirect(url) 轉到新接口
use可以結果根請求 server.use("/"或者不寫或者寫“/*",(req,res,next)=>{
? 需要next(),才能往下傳
})
擴展
req
- req.app:當callback為外部文件時,用req.app訪問express的實例
- req.baseUrl:獲取路由當前安裝的URL路徑
- req.cookies:Cookies
- req.fresh / req.stale:判斷請求是否還「新鮮」
- req.hostname / req.ip:獲取主機名和IP地址
- req.originalUrl:獲取原始請求URL
- req.path:獲取請求路徑
- req.protocol:獲取協議類型
- req.route:獲取當前匹配的路由
- req.subdomains:獲取子域名
- req.accepts():檢查可接受的請求的文檔類型
- req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages:返回指定字符集的第一個可接受字符編碼
- req.get():獲取指定的HTTP請求頭
- req.is():判斷請求頭Content-Type的MIME類型
res
- res.app:同req.app一樣
- res.append():追加指定HTTP頭
- res.set()在res.append()后將重置之前設置的頭
- res.cookie(name,value [,option]):設置Cookie
- opition: domain / expires / httpOnly / maxAge / path / secure / signed
- res.clearCookie():清除Cookie
- res.download():傳送指定路徑的文件
- res.get():返回指定的HTTP頭
- res.location():只設置響應的Location HTTP頭,不設置狀態碼或者close response
- res.render(view,[locals],callback):渲染一個view,同時向callback傳遞渲染后的字符串,如果在渲染過程中有錯誤發生next(err)將會被自動調用。callback將會被傳入一個可能發生的錯誤以及渲染后的頁面,這樣就不會自動輸出了。
- res.sendFile(path [,options] [,fn]):傳送指定路徑的文件 -會自動根據文件extension設定Content-Type
- res.set():設置HTTP頭,傳入object可以一次設置多個頭
- res.status():設置HTTP狀態碼
- res.type():設置Content-Type的MIME類型
作業
把jquery項目利用express搭建的node服務器來管理,保證資源托管到位,部分接口實現
身份驗證
HTTP 是一種沒有狀態的協議,也就是它并不知道是誰訪問。客戶端用戶名密碼通過了身份驗證,不過下回這個客戶端再發送請求時候,還得再驗證
session
思想
1、客戶端用戶名跟密碼請求登錄
2、服務端收到請求,去庫驗證用戶名與密碼
3、驗證成功后,服務端種一個cookie或發一個字符到客戶端,同時服務器保留一份session
4、客戶端收到 響應 以后可以把收到的字符存到cookie
5、客戶端每次向服務端請求資源的cookie會自動攜帶
6、服務端收到請求,然后去驗證cookie和session,如果驗證成功,就向客戶端返回請求的庫數據
Session存儲位置: 服務器內存,磁盤,或者數據庫里
Session存儲內容: id,存儲時間,用戶名等說明一下登錄的用戶是誰
客戶端攜帶 : cookie自動帶,localStorage手動帶
如何保存信息給瀏覽器
前端種:
cookie/localstorage
后端種:
服務器給瀏覽器種cookie: cookie-parser
服務器給瀏覽器種cookie的同時在服務器上生成seesion: cookie-session
cookie-session
安裝引入
let cookieSession = require('cookie-session')
配置中間件
app.use(cookieSession({
name:'保存到服務器的session的名字',
keys:[必傳參數,代表加密層級], //示例keys:['aa','bb','cc','dd'],
maxAge:1000 //保留cookie的時間
}))
種cookie,備份session
req.session.key=value //示例req.session['nz_1909']='userid';
讀cookie對比session
req.session.key 取cookie對比服務器的session,會返回userid,可以從返回的這個值是否存在來進行自動登錄
刪除cokkie、session
delete req.session.key
req.session.key = undefined
token
思想
在服務端不需要存儲用戶的登錄記錄,全部發給客戶端有客戶端自己存(cookie,local)
1、客戶端使用用戶名跟密碼請求登錄
2、服務端收到請求,去驗證用戶名與密碼
3、驗證成功后,服務端會簽發一個 Token(加了密的字符串),再把這個 Token 發送給客戶端
4、客戶端收到 Token 以后可以把它存儲起來,比如放在 Cookie 里或者 Local Storage 里
5、客戶端每次向服務端請求資源的時候需要帶著服務端簽發的 Token
6、服務端收到請求,然后去驗證客戶端請求里面帶著的 Token,如果驗證成功,就向客戶端返回請求的數據
實現
jsonwebtoken的安裝引入
let jwt = require('jsonwebtoken')
生成簽名
let token = jwt.sign(payload, secretOrPrivateKey, [options, callback])
校驗token
jwt.verify(token, secretOrPublicKey, [options, callback])
token刪除
有客戶端,負責刪除
session vs token
session | token | |
---|---|---|
服務端保存用戶信息 | √ | × |
避免CSRF攻擊 | × | √ |
安裝性 | 一般 | 高 |
多服務器粘性問題 | 存在 | 不存在 |
多服務器粘性問題
當在應用中進行 session的讀,寫或者刪除操作時,會有一個文件操作發生在操作系統的temp 文件夾下,至少在第一次時。假設有多臺服務器并且 session 在第一臺服務上創建。當你再次發送請求并且這個請求落在另一臺服務器上,session 信息并不存在并且會獲得一個“未認證”的響應。我知道,你可以通過一個粘性 session 解決這個問題。然而,在基于 token 的認證中,這個問題很自然就被解決了。沒有粘性 session 的問題,因為在每個發送到服務器的請求中這個請求的 token 都會被攔截
文件上傳
思想
前端表單->后端接收到文件本身->保存到服務器上->給數據庫記錄文件一些信息->庫返回給nodejs相關信息->nodejs返回給前端
前端: <input type=file enctype="multipart/form-data" name="fieldname"
實現
multer->文件名會隨機->fs模塊改名->path系統模塊解析磁盤路徑
后端:multer 接受 form-data編碼數據
path系統模塊
操作系統磁盤路徑
編碼
windows: c:\\user\\admin\\a.jpg
mac: ~/desktop/1901
UI呈現
windows: c:\user\admin
mac: ~/desktop/1901
API
磁盤路徑解析 parse
path.parse('c:\\wamp\\xx.png') // string -> object
//返回
{
root: 'c:\\', 盤符
dir: 'c:\\wamp', 目錄
base: 'xx.png', 文件名
ext: '.png', 擴展名
name: 'xx' 文件,不含擴展名
}
片段合并join
path.join('磁盤路徑1','磁盤路徑2','磁盤路徑n')
__dirname 魔術變量 返回當前文件所在的磁盤路徑
片段合并 resolve
path.resolve('磁盤路徑1','磁盤路徑n')
合并磁盤片段,右到左找根,左到右拼接,沒有找到根,以當前文件路徑為根,
對比join來說,不需要__dirname 這個魔術變量
multer中間件
multer 接受 form-data編碼數據,所有要求前端攜帶時注意一下,如:
<input type=file enctype="multipart/form-data" name="fieldname"
,
使用
//1 引入
let multer = require('multer');
//2 實例化
let objMulter = multer({ dest: './upload' }); //dest: 指定 保存位置(存到服務器)
//安裝中間件,
app.use(objMulter.any()); //允許上傳什么類型文件,any 代表任何類型
中間件擴展了req請求體 req.files
app.get('/reg',(req,res)=>{
req.files
})
? fieldname: 表單name名
? originalname: 上傳的文件名
? encoding: 編碼方式
? mimetype: 文件類型
? buffer: 文件本身
? size:尺寸
? destination: 保存路徑
? filename: 保存后的文件名 不含后綴
? path: 保存磁盤路徑+保存后的文件名 不含后綴
后端渲染
通常根據后端返回的json數據,然后來生成html被稱為前端渲染,而后端渲染是后端把json與html結合渲染好后返回到瀏覽器,沒前端什么事了
模板引擎
無論前后誰來渲染頁面,都會用到模板引擎,前端渲染頁面實際上是操作dom,后端渲染頁面是把數據和html字符拼接后丟給瀏覽器
引擎 | 前端 | 后端 |
---|---|---|
angularJs | √ | × |
vue/mustach | √ | √ |
react | √ | √ |
angularTs/mustach | √ | √ |
jade/pug | × | √ |
ejs | × | √ |
jquery + art-template | √ | × |
handlerbars | √ | × |
jade
原理:fs抓取前端靜態頁面 + jade + 數據 -> 返回send(data) -> 瀏覽器
特點:侵入式,強依賴
使用
let jade = require('jade')
let html = jade.renderFile('jade模板文件',{數據},{pretty:true}); //返回字符
jade模板文件語法
父子要縮進
屬性: 標簽(key=value,key2=value)
內容: 標簽 內容
ejs
原理:fs抓取前端靜態頁面 + ejs + 數據 -> 返回send(data) -> 瀏覽器
特點:非侵入式,溫和,弱依賴
使用
let ejs = require('ejs')
ejs.renderFile('ejs模板文件',{要合并到html數據},回調(err,data))
err:錯誤,null代表沒有錯誤
data: 渲染后的字符|流
ejs模板 : 后綴名為ejs的html文件
ejs模板文件語法
- ejs 結構就是html
- 輸出: <%= 數據名|屬性名|變量名 + 表達式 %>
- 語句: <% 語句 %> 需要被<% %> 包裹
- 非轉義輸出: <%- 數據名|變量名 + 表達式 %> 就是標簽不會被轉成符號gt等
- 載入公共:<%- include('./hd.ejs',{數據}) %> //注意有個-
eg:條件渲染
?
<% if(bl){ %>
<nav>菜單1</nav>
<% }else{ %>
<nav>菜單2</nav>
<% } %>
列表渲染
<ul>
<% for(var i=0;i<list.length;i++){ %>
<li>
<a href="<%= list[i].href %>">
<%= list[i].des %>
</a>
</li>
<% } %>
</ul>
多引擎管理
把多個模板引擎用在一個后端應用中,統一他們的用法,綁定到res、req身上
安裝+配置
npm i consolidate ejs jade -S
注意: ejs jade 等多個引擎需要安裝,但無需引入
app.js
//中間件配置
app.set('view.engine','html'); //模板最終 輸出類型設置
app.set('views','./views'); //引擎模板目錄設置
app.engine('html',consolidate.ejs); //輸出與引擎匹配
app.engine('css',consolidate.jade); //輸出與引擎匹配
//渲染
app.get('xxx',(req,res)=>{
res.render('模板文件名要加后綴',{要渲染的數據}) //整合頁面和數據,完成渲染,發往瀏覽器,并結束響應
})
路由
告訴你去哪,對于前端,主要是導向告訴瀏覽器應該去哪,對于后端,可以理解為一個子服務,一個路由就是一個小的服務(server/app),處理一個接口
配置和使用
/routes/xx.js
// 1. 創建路由
let router = express.Router();
//2 路由處理響應
router.響應API(地址, 處理函數)
//3. 導出路由
module.exports = router;
/app.js主服務
//安裝路由
app.use('地址',router);
當需要嵌套使用時 /routes/xx.js
//字路由里安裝路由 嵌套
router.use('地址',子router)
//截獲當前路由下的部分公共業務
router.all('*',當前router路由下以及所有子路由的驗證工作) //需要next 延續
all出現 的位置很重要,all的上方的路由截獲不到,只能截獲all一下的路由
主路由的地址對應子路由的根
如:app.js :
/api/user
~~ user.js:/
如: app.js:
/api/user/add
~~ user.js:/add
作業
實現jquery項目當中,自動登錄,注冊時頭像的上傳,接口邏輯利用路由實現
數據庫
mysql
關系數據庫,二維表,不存在子表
sql語句
建庫
CREATE DATABASE `2017-12-6` DEFAULT CHARACTER SET armscii8 COLLATE armscii8_general_ci;
建表
CREATE TABLE `2020-12-6`.`user` (
`name` VARCHAR( 32 ) NOT NULL ,
`age` INT( 3 ) NOT NULL ,
`address` VARCHAR( 128 ) NOT NULL
) ENGINE = INNODB
增
INSERT INTO 表 (字段列表) VALUES(值列表)
INSERT INTO user (name,age,address) VALUES('蘇菲',38,'外灘18號')
刪
DELETE FROM 表 WHERE 字段名=值
DELETE FROM user WHERE name='alex'
改
UPDATE 表 SET 字段名=值 WHERE 字段名=值
UPDATE user set name='sufei' WHERE name='蘇菲'
查
SELECT ? FROM 表
SELECT * FROM user 查所有
node + mysql客戶端
安裝+引入
npm install mysql -S
var mysql = require('mysql');
創建庫鏈接
var connection = mysql.createConnection({
host : 'localhost',//主機名
user : 'me',
password : 'secret',
database : 'my_db'//庫名
});
connection.connect();//連接數據庫
表操作
connection.query('SQL語句', function (error//錯誤, results//結果, fields//成功后的描述) {
if (error) throw error;
console.log('The solution is: ', results== 查詢array|| 增刪改object);
});
關閉庫
connection.end();
mongodb
非關系型數據庫,又叫nosql,緩存型,使用場景多是解決大規模數據集合多重數據種類
找到安裝目錄C:\Program Files\MongoDB\Server\4.0\bin -> cmd回車-> mongod --dbpath c:\data\db
data和db目錄要手動創建
- 服務端啟動: 可選
找到安裝目錄C:\Program Files\MongoDB\Server\4.0\bin -> cmd回車-> mongod 回車
一般開啟會默認啟動
- 客戶端啟動:
找到安裝目錄C:\Program Files\MongoDB\Server\4.0\bin -> cmd回車-> mongo 回車
- 環境變量 可選
為了在任意盤符下去都可以啟動 mongod服務端|mongo客戶端,把安裝目錄添加到環境變量
mysql vs mongodb
mysql | mongoDb | |
---|---|---|
database(庫) | database(庫) | |
table(表) | collection(集合) | |
row(一條數據) | document(文檔) | |
column(字段) | field(區域) | |
二維表,每次存到磁盤 | json,存在緩存,關閉時存到磁盤 | 存儲方式 |
mongodb命令行操作 聲明式 | obj.api()
庫操作
查: show dbs
db 查看當前庫
建: use 庫名 沒有建,有就切換
集合(表)操作
建:db.createCollection('表名',{配置})
//配置:{size:文件大小,capped:true,max:條數|文檔數} capped定量
//db.表(集合).isCapped() 返回 true/false 是否是定量
查:show collections / db.getCollectionNames()
刪:db.表|集合.drop()
文檔(row)操作
增
db.集合.save({}) //添加一條
db.集合.insert({}) //添加一條
db.集合.insertOne({}) //添加一條
db.集合.save([{},{}]) //多條
db.集合.insert([{},{}]) //多條
//insert 不會替換相同ID,會操作失敗
//save會對相同id的數據進行替換
刪
db.集合.deleteOne({要刪數據條件描述}) //一條
db.集合.remove({},true) //一條
db.集合.remove({要刪數據條件描述}) //多條
db.集合.remove({}) //清空表
改
db.集合.update({查詢條件},{替換條件},[插入false],[全替換false])
查詢條件
{age:22} age == 22
{age:{lt:22}} age < 22
{age:{lte:22}} age<=22
{age:{gte:22}} age<=122 && age>=22
{$or:[{age:22},{age:122}]} 22 or 122
{key:value,key2:value2} value && value2
{name:/正則/}替換條件
{
inc:{age:-1}}
$inc 年齡遞減1
查
所有:db.集合.find(條件)
條數: db.集合.find().count()
db.集合.find({條件},{指定要顯示列區域})
指定要顯示列區域
username:1 顯示這個區域,其他不顯示
username:0 不顯示這個區域,其他顯示
_id 是默認顯示
排
db.集合.find().sort({key:1,key2:-1}) //升
db.集合.find().sort({key:-1}) //降
限定
db.集合.find().limit(number) //限定
db.集合.find().skip(number) //跳過
db.集合.findOne()//找第一個
db.集合.find().limit(1) //查詢第一條
node + mongodb客戶端
安裝+引入
npm install mongodb -S
var mysql = require('mongodb');
實例化并連接
let mongoCt = mongodb.MongoClient;
mongoCt.connect('協議://地址:端口',{ useUnifiedTopology: true },回調(err,client))
//err 錯誤 client鏈接后的客戶端
//沒改的話,默認:mongodb://127.0.0.1:27017
鏈接庫和集合
let db = client.db('庫名')
let user = db.collection('集合名');
集合操作
//user.API() 集合操作 返回 對象
//增
insertOne(對象數據,(err,res)=>{}) //res = 對象 成功后返回的數據
insertMany(arr數據,(err,res)=>{}) //res = 對象 成功后返回的數據
//res.result.n 結果 ok 狀態
//res.ops內容 數組
//result.insertedId 插入后的id
//刪:
deleteOne({條件},(err,result)=>{})
//改:
updateOne({條件},{更新后},(err,res)=>{})
updateMany({條件},{更新后},(err,res)=>{})
updateMany({條件},{更新后},{配置},(err,res)=>{})
//配置: upsert:true 未找到的時候是否插入 projection:true 是否進行全局替換
//更新后:替換條件{$set:{數據},$inc:{age:-1}} 比如說
eg: user.updateMany({
age:{$gt:16}
},{
$set:{address:'人民公園',fans:111}
},{
upsert:false,//沒找到,是否把address插入成新數據
projection:true//讓什么顯示,true全部顯示,projection:{key:1}讓key顯示,0為不顯示
},(err,result)=>{})
//查:
user.find({條件},{skip:1,limit:1,projection:{key:1}},(err,result)=>{result=對象})
user.find({條件},{projection:{key:0}}).toArray((err,result)=>{reulst==arr})
user.countDocuments((err,num)=>{num返回數量})
skip 跳過第一個 projection:{key:1} 使key這個鍵顯示,當設置為0的時候讓他不顯示
//排
user.find(..).sort({key:-1}).toArray..
user.find({},{projection:{},sort:{key:-1}}).toArray..
關閉庫
client.close()
node + mongoose
可視化客戶端
Express生成器
應用程序生成器、腳手架 、命令行工具、自動搭建項目環境的,無需手動
安裝
npm install express-generator -g
驗證
express -h
生成環境
express -e 目錄 | .
// . 當前目錄創建
//-e 需要ejs模板引擎
//express -f 強制在非空目錄下創建
cd 目錄
npm install //安裝依賴包
npm start
node ./bin/www
項目
定義數據字典
也就是數據庫設計,有了數據結構,后端才知道如何存儲,前端才知道如何渲染,有條不成文的規定,數據結構有前端說了算,請求姿勢(api分格)后端說了算,最終老大說了算
數據結構
q關鍵字篩選
banner: [
{
"_id" : xx,
"title" : "1",
"sub_title" : "1",
"banner" : "xxxx",
"time":234234,
"detail" : {
"auth" : "",
"content" : "<p>xxx<p>",
"icon" : "/upload/banner/9d4083b4f1d28a6c0fb4c463526790eb.jpg"
},
}
]
product:
{
"_id" : xx,
"title" : "1_",
"des" : "2",
"time":234234,
"detail" : {
"auth" : "4",
"content" :"<p>3</p>",
"auth_icon" : "/upload/user/xxx.jpg"
}
}
user:
{
"_id" : xx,
"username" : "alex",
"password" : "alex123",
"follow" : "100",
"fans" : "200",
"nikename" : "九叔_",
"icon" : "/upload/968a3b7218ee744931276a64c9b7ea01.png",
"time" : 1551620448550
}
super:
{
"_id" : xx,
"username" : "admin",
"password" : "admin123",
"icon" : "/img/avatar-5.jpg"
}
請求方式 RESTful API
增 POST /user body中包含數據
刪 DELETE /user/1 | user?id=1 根據ID刪除用戶信息
改 PUT|PATCH /user body中包含數據 PUT覆蓋修改 PATCH局部修改
查 GET /user/1 | user?id=1 GET 根據用戶id查詢用戶數據 沒有id查詢所有 /1 返對象 id=1 返回數組>對象
分頁 _page 第幾頁, _limit一頁多少條
GET /user?_page=7 不傳遞默認0條
GET /user?_page=7&_limit=20 不傳遞默認10條
排序 _sort設定排序的字段 _order設定排序的方式(默認升序)
GET /user?_sort=views&_order=asc
GET /user/1/comments?_sort=votes&_order=asc
GET /user?_sort=title,views&_order=desc,asc 多個字段排序
任意切片數據 _start 開始不包含 _end 結束包含
GET /users?_start=20&_end=30
GET /user/1/comments?_start=20&_end=30
GET /user/1/comments?_start=20&_limit=10
全文檢索 GET /user?q=九哥
搭建開發環境
引入各種包、各種中間件、做好目錄規劃
bin |-
www 啟動文件
utils|- 全局公共
|- douban|mgd|mysql
config 全局配置
|- global (_page,_limit,q,upload...)
|- server (local,http,https)
public 資源托管
|-admin 管理端
|-template 用戶端
|-upload
|- banner|product|user
|- product
|- home|follow|column
routes 子服務,路由
admin 管理端
|- feedback
|- success|error
|- product
|- add|del|check
|- banner
|- add|del|check
|- user
|- add|del|check
|- home| product|banner|user
|- islogin | login | reg | logout
api 用戶端
|- product (home/follow/column)
|- banner
|- user
|- login
|- reg
|- logout
proxy 代理
|- douban
|- ....
views 管理端模板 ejs
|- feedback
|- success|error|app_error
|- ... 結構同 admin 管理端
|- common
|- header|footer|slider|crumb|toolbar|paging
用戶端API
用戶密碼入庫時加密
let bcrypt = require('bcrypt')
加密: var hash = bcrypt.hashSync(用戶傳過來的明文密碼, 加鹽數);
校驗: bcrypt.compareSync(用戶傳過來的明文密碼, hash); // true|false
短信驗證
-
開通短信服務
- 登錄阿里云賬號->進入
-
設置簽名管理
- 添加 簽名、模板 進入
- 沖1塊錢,簽名是收費的
-
申成短信服務器接口的node代碼
PhoneNumbers: 電話 SignName: 簽名 TemplateCode: 模板id accessKeyId: 阿里云賬號->accessKey管理 accessKeySecret: 阿里云賬號->accessKey管理
管理端API
登錄注銷
登錄接口:/admin/login/submit
實現
var express = require('express');
var router = express.Router();
var mgdb = require('../../common/mgdb')
router.get('/', function(req, res, next) {
res.render('login',{});
});
router.post('/submit', function(req, res, next) {
let {username,password} = req.body;
mgdb(
{collection:'admin'},
({collection,client})=>{
collection.find(
{username,password},
{
projection:{_id:0}
}
).toArray((err,result)=>{
if(!err && result.length>0){
//種cookie , 留session
req.session['username']=result[0].username;
req.session['icon']=result[0].icon;
res.redirect('/admin/home');
}else{
// res.redirect(跳轉地址==string)
res.redirect('/admin/error?msg=登錄失敗,用戶或者密碼有誤')
}
})
}
)
});
module.exports = router;
//======================================
注銷接口:/admin/reg
實現
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
// req.session=null;
// req.session.username=undefined
delete req.session.username;//刪除session 混淆 cookie
delete req.session.icon;
res.redirect('/admin/login');
});
module.exports = router;
===============================
自動登錄
app.all('/admin/*',require('./routes/admin/islogin'))
islogin
module.exports=(req,res,next)=>{
if(!req.session['username']){
res.redirect('/admin/login')
}else{
//處理公共參數
let start = req.query.start ? req.query.start - 1 : require('../../config/global').page_start - 1;
let count = req.query.count ? req.query.count - 0 : require('../../config/global').page_num - 0;
let q = req.query.q ? req.query.q : require('../../config/global').q;
let rule = req.query.rule ? req.query.rule : require('../../config/global').rule;
let _id = req.query._id;
let dataName = req.query.dataName;
let page_header = dataName;
let active = dataName;
res.params = {start,count,q,rule,dataName,page_header,active,_id}
res.user_session={username:req.session.username,icon:req.session.icon}
next();//交給app.use后續響應處理
}
};
添加
接口: /admin/product/add?dataName=xx
實現
var express = require('express');
var router = express.Router();
var pathLib = require('path')
var uploadUrl = require('../../../config/global').upload.product
var fs = require('fs');
var mgdb = require('../../../common/mgdb')
router.get('/', function(req, res, next) {
//1.必傳參數
let dataName = req.query.dataName;
if(!dataName){
res.redirect('/admin/error?msg=dataName為必傳參數')
return;
}
//公共數據 start=1|q=''|rule=''|page_header|dataName|user_session
let common_data={
...res.user_session,
...res.params,
page_header:dataName+'添加',
}
res.render('product/add',common_data);
});
router.post('/submit', function(req, res, next) {
//1.必傳參數
let dataName = req.body.dataName;
if(!dataName){
console.log(1)
res.send('/admin/error?msg=dataName為必傳參數')
return;
}
//2.整理公共數據|庫數據
let {title,content,des,auth} = req.body;
let time = Date.now();//添加時間
//multer拆出上傳圖片,需要解決沒有上傳頭像
let auth_icon = req.files.length ? uploadUrl + req.files[0].filename + pathLib.parse(req.files[0].originalname).ext : '';
if(auth_icon){
fs.renameSync(
req.files[0].path,
req.files[0].path+pathLib.parse(req.files[0].originalname).ext
)
}else{
auth_icon = '/upload/noimage.png';
}
//3.寫庫 + 跳轉
mgdb({
collection:dataName
},({collection,client})=>{
collection.insertOne({
title,des,time,detail:{auth,content,auth_icon}
},(err,result)=>{
if(!err && result.result.n){
let io=require('../../../bin/www');
io.emit('update_product', {data:result.ops[0]})
res.send('/admin/product?dataName='+dataName+'&start=1')
}else{
res.send('/admin/error?msg=集合操作錯誤')
}
client.close();
})
})
});
module.exports = router;
富文本框使用
注意jq庫沖突: 要使用富文本框提供的jquery-3.2.1.slim.min
問題: slim.min沒有$.ajax
解決: 使用jquery,禁用slim.min圖片上傳FormData混合提交 流文件與普通表單混合
form_data = new FormData() | new FormData(表單本身)
form_data.append(key,value) 通過req.body獲取
value 可以是file:
<input type="file" name="file2" id="file2" />
formData.append("file2", $('#file2')[0].files[0]);
通過multer的req.files獲取$.ajax({ contentType: false,//不設置編碼類型,在進行文件流與普通字符串混合上傳的時候,需要設置為false processData: false,//不進行數據處理 })
后端需要處理未傳圖(req.files空)
js抓取ejs變量
form_data.append('dataName',"<%=dataName%>");
ajax提交后,nodejs需返回跳轉地址,由前端跳轉
子節點排序
.sort({'detail.time':-1,xx:oo})
刪除
接口: /admin/product/del?dataName=xx&_id=xx&start=2&count=2&q=b&rule=_id
實現
var express = require('express');
var router = express.Router();
var mgdb = require('../../../common/mgdb');
router.get('/', function(req, res, next) {
//1.必傳參數
let {dataName,_id,start,count,q,rule} = res.params;
if(!dataName || !_id){
res.redirect('/admin/error?msg=dataName和_id為必傳參數')
return;
}
//3. 寫庫
mgdb({
collection:dataName
},({collection,client,ObjectID})=>{
collection.deleteOne({
_id:ObjectID(_id)
},(err,result)=>{
//4. 渲染頁面|跳轉頁面
if(!err && result.result.n){
res.redirect('/admin/product?dataName='+dataName+'&start='+(start+1)+'&count='+count+'&q='+q+'&rule='+rule)
}else{
res.redirect('/admin/error?msg='+dataName+'操作錯誤')
}
client.close();
})
})
});
module.exports = router;
ID操作注意
var ObjectId = require('mongodb').ObjectId;
id = ObjectId(req.query.id); 此時的id才是ajax傳過來的id,才能與數據庫對照
修改
接口: /admin/product/check?dataName=xx&_id=xx&start=2&count=2&q=b&rule=_id
實現
var express = require('express');
var router = express.Router();
var mgdb = require('../../../common/mgdb')
let pathLib = require('path');
let fs = require('fs');
let uploadUrl = require('../../../config/global').upload.product;
router.get('/', function (req, res, next) {
//1.必傳參數
let {dataName,_id,start} = res.params;
if (!dataName || !_id) {
res.redirect('/admin/error?msg=dataName和_id為必傳參數')
return;
}
//公共數據
let common_data = {
...res.user_session,
...res.params,
page_header: dataName + '修改',
start:start+1
}
//找到這條數據
mgdb({
collection: dataName
}, ({ collection, client, ObjectID }) => {
collection.find({
_id: ObjectID(_id)
}).toArray((err, result) => {
if (!err && result.length>0) {
let data = {
...common_data,
page_data: result[0]
}
console.log(data)
res.render('product/check', data);
} else {
res.redirect('/admin/error?msg=' + dataName + '操作錯誤')
}
client.close();
})
})
});
router.post('/submit', function (req, res, next) {
//1.必傳參數
let dataName = req.body.dataName;
let _id = req.body._id;
if (!dataName || !_id) {
res.redirect('/admin/error?msg=dataName和_id為必傳參數')
return;
}
//可選參數
let start = req.body.start ? req.body.start - 0 : require('../../../config/global').page_start
let count = req.body.count ? req.body.count - 0 : require('../../../config/global').page_num
let q = req.body.q ? req.body.q : require('../../../config/global').q;
let rule = req.body.rule ? req.body.rule : require('../../../config/global').rule;
//2.整理公共數據|庫數據
let {title,content,des,auth,old_auth_icon} = req.body;
//old_auth_icon 添加是保存的圖
//multer拆出上傳圖片,需要解決沒有上傳頭像
let auth_icon = req.files.length ? uploadUrl + req.files[0].filename + pathLib.parse(req.files[0].originalname).ext : '';
if(auth_icon){
fs.renameSync(
req.files[0].path,
req.files[0].path+pathLib.parse(req.files[0].originalname).ext
)
}else{
auth_icon = old_auth_icon;
}
//3.寫庫 + 跳轉
mgdb({
collection:dataName
},({collection,client,ObjectID})=>{
collection.updateOne({
_id:ObjectID(_id)
},{
$set:{title,des,detail:{auth,content,auth_icon}}
},(err,result)=>{
if(!err && result.result.n){
res.send('/admin/product?dataName='+dataName+'&start='+start+'&count='+count+'&q='+q+'&rule='+rule)
}else{
res.send('/admin/error?msg=集合操作錯誤')
}
client.close();
})
})
});
module.exports = router;
后端需要處理未傳圖(req.files空)
前端修改時抓取庫圖地址(渲染用),提交時傳遞接收到的庫圖和本地圖,服務器優先抓取本地圖
修改時刪除之前的圖片fs.unlink
ajax提交后,nodejs需返回跳轉地址,由前端跳轉
檢索|排序
接口: /admin/product?dataName=home&start=2&count=2&q=b&rule=detail.time
實現
var express = require('express');
var router = express.Router();
var mgdb = require('../../common/mgdb')
router.get('/', function (req, res, next) {
let {dataName,q,rule,start,count} = res.params;
if (!dataName) {
res.redirect('/admin/error?msg=dataName為必傳參數')
return;
}
let common_data = {
...res.user_session,
...res.params,
page_header: dataName + '列表',
start: start + 1,
api_name:'product'
}
mgdb({
collection: dataName
}, ({ collection, client }) => {
collection.find(
q ? { title: eval('/' + q + '/g') } : {},
{
projection: {
_id: 1, title: 1
},
// sort: rule? {[rule]:-1} : {'detail.auth':-1}
sort: rule ? { [rule]: -1 } : { 'time': -1 } //排序條件默認按時間排序
}
).toArray((err, result) => {
let checkResult = result.slice(start * count, start * count + count)//提取要分頁的數據
let data = {
...common_data,
page_data: checkResult,
page_count: Math.ceil(result.length / count)//計算總頁數
}
res.render('product', data);
client.close();
})
})
});
router.use('/add', require('./product/add'));
router.use('/del', require('./product/del'));
router.use('/check', require('./product/check'));
module.exports = router;
查詢 eval('/'+ q +'/g')
排序 sort:rule ? {[rule]:-1} : {'detail.time':-1}
排序關鍵字: (標題title|時間:detail.title)
分頁: 取所有,挑出對應頁數,返回給瀏覽器
接口文檔編寫
為了以后前端使用方便,把api的使用方式,或者說請求方式做成文檔,文檔可以是,word,pdf
擴展
代理
前端通過ajax訪問第三方接口時,會出現瀏覽器的跨域行為,可以通過后端代理繞過,前端只需要訪問我方后端接口,我方后端去請求第三方接口,瀏覽器和我方后端同域,這樣繞開瀏覽器的跨域限定
豆瓣的請求姿勢
hostname:'douban.uieee.com',//主機名
port: 443,//端口
path:'/v2/movie/top250?start=3&count=1',
method:'get'
聚合的請求參數
hostname:'v.juhe.cn',
// port:80,
path:'/toutiao/index?type=&key=55f8053eba54dab5a301a00f45523164',
method:'GET'
我方后端請求
let http[s]=require('http[s]')
options={
hostname:'api.douban.com',
port:443,
path:'/v2/movie/top250?count='+req.query.count,
method:'GET'
};
//發送http[s]請求
let reqHttp = http[s].request(配置項,回調(響應對象resHttp)){ //返回請求對象reqHttp
resHttp 響應對象
resHttp.statusCode 狀態碼 200 OK
resHttp.headers 獲取響應頭信息
resHttp.setEncoding('utf-8') 設置編碼方式
resHttp.on('data/end',fn) ->send給前端
});
reqHttp //請求對象
reqHttp.on('error',(err)=>{console.log(err)}); //監聽請求失敗信息
reqHttp.end();//請求結束
正向代理 VS 反向代理
有一臺服務器出現在客戶端和真實服務器之間,這臺服務器叫代理服務器,他可能兩端都有可能出現
正向代理(客戶端代理) | 反向代理(服務端代理) | |
---|---|---|
目標 | 幫客戶端訪問其無法訪問的服務器 | 幫服務器做負載均衡,安全防護等 |
位置 | 客戶端架設 | 服務器架設 |
知曉 | 真實服務器不知道客戶端是誰 | 客戶端不知道真實服務器是誰 |
場景 | vue、react的開發環境中架設,瀏覽器端安裝代理軟件 | 機器集群中部署一個反向代理服務器ngnix |
socket.io
介紹
Web領域的實時推送技術,也被稱作Realtime技術。這種技術要達到的目的是讓用戶不需要刷新瀏覽器就可以獲得實時更新。它有著廣泛的應用場景,比如在線聊天室、在線客服系統、評論系統、WebIM等。
原理
雙向通信,前端H5api (WebSocket) + 后端net模塊
服務端配置
修改www
const SOCKETIO = require('socket.io');//創建socket實例
const io = SOCKETIO.listen(server);//監聽http實例,未來3000端口下的http請求,會觸發socket
module.export = io;//寫在最后
注意: www 不熱重啟,不檢查
客戶端配置
html注入客戶端庫
<script src="/socket.io/socket.io.js"></script>
<script>
連接服務器:socket = io('http://localhost:3000');
</script>
服務端推送
//完成一次添加工作后↓
let io = require('../../../bin/www'); //require要在需要時再引入
io.emit('mess_type',{data:'服務端的推送數據')//推送
客戶端接收
<script src="/socket.io/socket.io.js"></script>
<script>
//連接服務器
var socket = io('http://localhost:3000');
socket.on('new_movie',(data)=>{
console.log('首頁_客戶端收到',data)
})
</script>
客戶端推送到客戶端
客戶端(未指定消息|指定的消息)->服務器(廣播|私信給指定)->客戶端
聊天室思想
客戶端(未指定消息|指定的消息)->服務器(廣播|私信給指定)->客戶端
服務端API
檢測客戶端連接:io.on('connection', (socket) =>{}) 回調函數接收客戶端socket
接受:socket.on('消息名稱',(data)=>{}) data=接受到的消息
廣播: io.emit('消息名稱', {數據});
檢測客戶端下線: socket.on('disconnect',(data)=>{})
接受私信:
socket.on('消息名稱',(toUserName,data,callback)=>{})
toUserName==目標用戶 callback==給發送端的回調
發私信: 接受消息的socket.emit('消息名稱',{數據})
發私信 -> socket == onlineUsers[toUserName]
注意,data數據 里面不可以包含socket對象,發往客戶端,量太大
客戶端API
發送未指定消息: socket.emit('消息名稱',{到服務器的數據})
發送私息: socket.emit('消息名稱',toUserName,{到服務器的數據},(由服務器返回的數據)=>{})
接受消息: socket.on('消息名稱',(data)=>{})
跨域
有時,前端和后端的工程文件不在同一個域,也會出現跨域,一下是解決方案
后端解決
部分接口允許
要允許的接口內部
res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
所有接口允許
let core = require('core');
app.use(cors({
//允許所有前端域名
"origin": ["http://localhost:8001","http://localhost:5000","http://localhost:8080"],
"credentials":true,//允許攜帶憑證
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE", //被允許的提交方式
"allowedHeaders":['Content-Type','Authorization']//被允許的post方式的請求頭
}));
前端解決
jsonp
瀏覽器裝插件
環境做代理(webpack)
異步流程控制
有時前端的請求在后端處理時,后端要多次請求庫時,用的上
安裝:npm i async -S
串行無關聯
多個異步依次請求,請求之間不依賴
async.series([fn1(callback),fn2(callback)],處理函數(err,result))
//callback(err,數據)->callback(null,'one')
//花費時間是:fn1+fn2
async.series({xx:fn(callback),xx:fn(callback)},處理函數(err,result))
//花費時間是:fn1+fn2
并行無關聯
多個異步同時請求,請求之間不依賴
async.parallel(數組|對象,回調(err,result)) √
async.parallel([fn1(callback),fn2(callback)],處理函數(err,result))
callback(err,數據)->callback(null,'one')
async.parallel({xx:fn(callback),xx:fn(callback)},處理函數(err,result))
//花費時間是:用時最多的那個fn
串行有關聯
多個異步依次請求,請求之間依賴
async.waterfall(數組|對象,回調(err,result)) √
async.waterfall(
[fn1(callback){callback(null,data)},fn2(data,callback)],
處理函數(err,result)
)
//result 接受最后一個函數傳遞過來的一個參數