Node後端知識彙總

挺秃然發表於2024-11-03
後端涉及的相關知識:https://www.jianshu.com/p/43e73134ec42【用到的外掛大全】

node單執行緒為什麼仍然適合高併發io:https://blog.csdn.net/weixin_39914938/article/details/114492053!!!!!!!!!!!!!
單執行緒的劣勢:CPU密集型任務佔用CPU時間長(可透過cluster方式解決)
無法利用CPU的多核(可透過cluster方式解決)
單執行緒丟擲異常使得程式停止(可透過try catch方式或自動重啟機制解決)



node樣本程式碼:angular中文站,node寫的,而且是開原始碼,自己去github找
???????????多執行緒【worker_threads模組】,多程序【cluster模組】,cpu核心【到底與執行緒數相關,還是核程序數相關】,pm2的cluster模式??????????????

程序和執行緒以及CPU多核的關係,這個是核心:https://www.cnblogs.com/valjeanshaw/p/11469514.html
一旦 JavaScript 操作阻塞了執行緒,事件迴圈也會被阻塞,因為是先進性主執行緒執行操作,執行完以後再把“事件迴圈”傳給libuv的執行緒池做併發處理,
程序是程式的一次執行過程,是一個動態概念,是程式在執行過程中分配和管理資源的基本單位
執行緒是CPU排程和分派的基本單位,它可與同屬一個程序的其他的執行緒共享程序所擁有的全部資源。
在作業系統中能同時執行多個程序(程式);而在同一個程序(程式)中有多個執行緒同時執行(透過CPU排程,在每個時間片中只有一個執行緒執行,也就是說如果是單核,雖然有多個執行緒,其實每個執行緒只在這個單核cpu上輪詢獲得一個時間片段,不是併發)
所以真正讓執行緒併發的,是多核cpu,每個cpu在同一個事件都有自己的執行緒在執行;系統在執行的時候會為每個程序分配不同的記憶體空間;而對執行緒而言,除了CPU外,系統不會為執行緒分配記憶體(執行緒所使用的資源來自其所屬程序的資源),執行緒組之間只能共享資源。如果執行緒數小於cpu核心數,那麼將有多餘的核心空閒,所以pm2這個程序管理器,給一個程序設定多個執行緒,而執行緒的數量就是cpu核心數,而不是程序數
???????????既然這樣,那為什麼cluster會按照核心數量來建立程序,然後worker_threads建立多執行緒???????????????

Pm2開啟的是一個node程序的多執行緒,為什麼說node的非同步IO比多執行緒更”有利於高併發“;Node透過事件驅動在單個執行緒上可以處理大併發的請求;啟動多個執行緒為了充分將CPU資源利用起來,而不是為了解決併發問題。
node使用的是事件驅動來處理併發,非同步來實現併發


!!!!!!!!!!!!!為什麼Node單執行緒可以實現高併發 start!!!!!!!!!!!!!!!!!
I/O密集型處理是node的強項,因為node的I/O請求都是非同步的(如:sql查詢請求、檔案流操作操作請求、http請求...)
非同步:發出操作指令,然後就可以去做別的事情了(主執行緒不需要等待),所有主執行緒操作完成後,再執行事件輪詢分配任務到libuv的執行緒池併發執行;nodejs底層的libuv是多執行緒的執行緒池用來並行io操作
雖然nodejs的I/O操作開啟了多執行緒,但是所有執行緒都是基於node服務程序開啟的,並不能充分利用cpu資源;pm2程序管理器可以解決這個問題;pm2 是一個帶有負載均衡功能的Node應用的程序管理器.

在過去單CPU時代,單任務在一個時間點只能執行單一程式。之後發展到多工階段,計算機能在同一時間點並行執行多工或多程序。雖然並不是真正意義上的“同一時間點”,而是多個任務或程序共享一個CPU,
並交由作業系統來完成多工間對CPU的執行切換,以使得每個任務都有機會獲得一定的時間片執行。而現在多核CPU的情況下,同一時間點可以執行多個任務,具體到這個任務在CPU哪個核上執行,這個就跟作業系統和CPU本身的設計相關了
js不適合CPU密集型計算,例如 for (let i = 0; i < 100000000; i++) {console.log(i); },這個會佔用cpu事件,導致阻塞後面的程序,所以多程序的意義就是,一個cpu被佔用了,其他cpu核心還可以正常,主要不是所有核心都被任務佔據,就不會阻塞
所以說,多核以及解決了cpu密集型計算的阻塞問題,node又是IO高併發,完美解決了node無法處理CPU密集型計算的劣勢

程序是“資源分配的最小單位”【這裡主要指記憶體】,執行緒是 CPU 排程的最小單位;執行緒分使用者執行緒和核心執行緒。核心執行緒再呼叫的時候可以去不同的核心去操作。所以多執行緒是可以利用到多核的。
!!!!!!!!!!!!!為什麼Node單執行緒可以實現高併發 start!!!!!!!!!!!!!!!!!


node服務端錯誤監控外掛: Fundebug 【貌似可以不用】

node的效能監控外掛: https://www.cnblogs.com/zqzjs/p/6210645.html
第三方的程序守護框架,pm2 和 forever ,它們都可以實現程序守護,底層也都是透過上面講的 child_process 模組和 cluster 模組 實現的,這裡就不再提它們的原理。
pm2:pm2是一個帶有負載均衡能力的node程序管理工具【包含了程序守護,程序管理器,管理的是多個程序,以及一個程序內的多個執行緒】:https://blog.csdn.net/syy2668/article/details/109840463 【直接讓pm2使用cluster模式啟動多程序,不需要修改任何node端程式碼】
可以用它來管理你的node程序,並檢視node程序的狀態,當然也支援效能監控,程序守護,負載均衡等功能;新增 --watch讓node出現錯誤的時候自動重啟,同時pm2的log命令可以檢視錯誤
pm2 log:檢視錯誤日誌
pm2 monitor:檢視node要用的程序狀態【記憶體佔用等】
啟動的時候用 --watch來讓程序出錯能自動重啟
超過使用記憶體上限後自動重啟,那麼可以加上--max-memory-restart引數
注意:pm2在docker上執行,命令是 pm2 start app.js --no-daemon // 設定啟動方式,以為pm2預設是在後臺執行,新增--no-daemon
pm2總體作用:錯誤日誌生成,錯誤自動重啟,記憶體溢位子哦對那個重啟,啟動後記憶體佔用和cpu佔用監控;pm2的fork模式也是多程序,但是還是node自帶的cluster模組實現多程序好
pm2 啟動模式 fork 和 cluster 的區別:
fork模式,單例項多程序,常用於多語言混編,比如php、python等,不支援埠複用,需要自己做應用的埠分配和負載均衡的子程序業務程式碼。
缺點就是單伺服器例項容易由於異常會導致伺服器例項崩潰。
cluster模式,多例項多程序,但是隻支援node,埠可以複用,不需要額外的埠配置,0程式碼實現負載均衡。
優點就是由於多例項機制,可以保證伺服器的容錯性,就算出現異常也不會使多個伺服器例項同時崩潰。
共同點,由於都是多程序,都需要訊息機制或資料持久化來實現資料共享。

資料監控: node_exporter+Prometheus+Grafana ;
1.Prometheus是最流行的效能監控外掛【只負責資料採集】;【適合監控docker容器】
2.node_exporter【對資料進行處理,匯出資料,比如cpu,記憶體,磁碟資料】;
3.Grafana【資料的視覺化監控平臺,基於】【資料來源:Graphite,InfluxDB,OpenTSDB,Prometheus,Elasticsearch,CloudWatch和KairosDB等;】
負載均衡: 一般的專案負載均衡,如果是純node後端,那麼用pm2就足夠了,如果是非常大型的專案,對負載均衡自由度較高,那就得用nginx來實現負載均衡了

KOA設計原理和大概使用
git命令
node的fs【檔案操作】和 crypto【hash加密演算法】 其他常用的模組!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
用python寫爬蟲,爬取網頁資料!!!!!!!!!!!!
node的多執行緒和多程序如何實現,如何進行壓力測試

序列:多個任務,一個一個執行
併發:多個執行緒在單個cpu核心執行,系統不停切換執行緒,看起來像在同時執行,實際上是不停切換 【如果電腦支援CPU超執行緒技術,那就可以進行併發】,
為什麼出現CPU超執行緒技術,因為給一個核心分配一個執行緒,沒有重複利用CPU的算力,因為CPU處理速度遠超執行緒,所以給一個核心設定多個執行緒【一般是2個執行緒】,
一個單執行緒核心每秒可以處理成千上萬個指令,但還是沒有重複利用到這一個CPU核心
並行:一個執行緒分配一個cpu核心,所有執行緒一起執行

多工:一個任務一個程序,電腦開音樂軟體,開啟電視劇播放器,開啟瀏覽器,系統防禦,輸入法等等,開了很多程序(一個軟體下面可能就有很多子程序,一個程序中包含多個執行緒),而CPU核心只有4個,這個時候最高並行也就4,要處理這麼多工,只能是高併發
多程序和多執行緒:一個程序內至少有1個執行緒,一般都是多個執行緒同時進行(一般一個程序最好設定的執行緒數是電腦CPI的超執行緒的數量,比如四核八執行緒,那麼開一個程序,裡面可以開4個執行緒並行,或者8個執行緒併發;再多的話意義不大)
而多程序就是作業系統頻繁的輪換程序,切換速度很快,所以我們看到電腦上多個軟體一起跑,感覺是並行,其實是CPU在輪換計算程序。
一個執行緒在同一時間內,只能屬於一個程序
系統的解決方案:
1.多程序,一個程序裡面1個執行緒
2.多執行緒,一個程序,一個程序裡面多個執行緒
3.多執行緒多程序,有多個程序,每個程序裡面有多個執行緒:(不太推薦,過於複雜,容易出錯)


Node如何解決CPU密集型計算的短板:
1.增加子程序(主要是解決CPU密集型計算,重複利用多核CPU;並不是為了高併發):Node.js的child_process模組可以建立子程序;也可以透過cluster模組建立程序。
增加子程序優缺點:
優點:獨立和資源隔離,充分利用多核CPU(一個程序使用一個CPU核心,多個程序就可以解決CPU使用率不足的問題);
穩定性高,因為主程序只負責排程不負責業務處理,子程序才負責資料的計算

//Node多程序架構分2種
代理模式(消耗2倍的檔案描述符,導致擴充套件能力不強):主程序負責監控80埠,接收和返回資料,並把這些使用者請求,一次分發給對應的子程序,每個子程序開一個埠監聽。這種模式下,系統擴充套件能力不強
控制代碼模式(這個比較好):原理沒看懂

缺點:各個程序之間的資料通訊較複雜,排程開銷大,記憶體消耗大

cluster和child_progress區別:
cluster(基於child_progress模組,比較高階的封裝):利用多核CPU資源,透過建立多個子程序(Worker程序)來處理併發請求,從而提高應用的處理能力和響應速度。Cluster模組透過主程序(Master程序)來監控和協調多個Worker程序的執行,
Worker程序之間透過程序間通訊(IPC)交換訊息,Cluster模組內建了一個負載均衡器,較複雜,
child_progress(最原始的):建立子程序,這樣計算CPU密集型計算的時候,就不會阻塞主程序了,就是一個主程序,多個子程序,child_process.fork(),父子程序之間透過IPC來交換資訊。
注意,不是一個請求就要建立一個子程序,想利用多核CPU,一般子程序數==CPU核心數
//以下建立的子程序都是預設非同步的,子程序的執行不會阻塞主程序。
spawn : 子程序中執行的是非node程式,提供一組引數後,執行的結果以流的形式返回。
execFile:子程序中執行的是非node程式,提供一組引數後,執行的結果以回撥的形式返回。
exec:子程序執行的是非node程式,傳入一串shell命令,執行後結果以回撥的形式返回,與execFile不同的是exec可以直接執行一串shell命令。
fork:子程序執行的是node程式,提供一組引數後,執行的結果以流的形式返回,與spawn不同,fork生成的子程序只能執行node應用。接下來的小節將具體的介紹這一些方法。

2.增加執行緒池【基本不用,因為Node中的libuv本身就是多執行緒,他這個多執行緒用來支援V8單執行緒的非同步IO高併發】(IO操作會阻塞執行緒,所以這個是用來解決IO操作的,對CPU密集型計算幫助不是很大):使用node-worker-threads-pool(基於Node預設模組worker_threads開發的一個更方便實用的執行緒池庫)、workerpool等執行緒池庫 【執行緒數量和CPU執行緒數量一致,比如四核八執行緒,那麼執行緒數量最大是8,再多也沒意義,一般執行緒數量是CPU核心的整數倍】;
其實,因為Node的事件驅動,可以用單個執行緒處理大量的併發請求,這裡涉及到libuv的event loop,上下文切換和執行緒池排程。
增加執行緒的優缺點:
優點:所有執行緒共享主程序記憶體空間,減少資料傳輸開銷;執行緒間控制和同步簡單,適合處理大量併發,執行緒之間切換成本低,適合高併發
缺點:執行緒共享地址空間,受2G的地址空間限制,不適合大量資料處理;執行緒之間同步和枷鎖較為複雜,容易觸發死鎖和競爭條件,執行緒崩潰可能影響整個程式的穩定性,因為他們共享了程序的記憶體池
多執行緒造成死鎖原因:執行緒之間相互等待對方資源釋放或者不當的順序,就會造成死鎖
多執行緒死鎖解決方案: 1.超時釋放資源來打破僵局,2.保證執行緒按照一定的順序來執行,邏輯簡單不容易造成死鎖。3.不要出現巢狀鎖。4. 檢測工具檢測死鎖
//注意,node的多執行緒死鎖,要和資料庫死鎖區分開來,那是另一個知識區
3.使用雲端計算服務:阿里雲函式計算、AWS Lambda等雲端計算服務,把CPU密集型計算交給雲端計算來做,彈性很足(其實雲端計算那邊用的估計是java或者其他後端,把資料計算好後返回給node伺服器)


多執行緒:worker_threads 模組或node-worker-threads-pool模組;【在js和v8引擎以及libuv中都有涉及】,其中node-worker-threads-pool是基於worker_threads模組的封裝

worker_threads相對於I/O密集型操作是沒有太大的幫助的,因為非同步的I/O操作比worker執行緒更有效率,但對於CPU密集型操作的效能會提升很大。

為什麼要多執行緒:https://www.cnblogs.com/liuyangofficial/p/7072595.html
其實就是兩點:【理解有問題,node是非同步的,比如磁碟IO阻塞會導致CPU阻塞麼,不會,是非同步的,分配給libuv的執行緒】
1.單執行緒的時候網路的IO,磁碟的IO,資料庫的計算,CUP的計算,都在一個執行緒裡面,任何一個計算量過大,都會阻塞執行緒,導致反應變慢,所以可以把計算量大的放到例外的子執行緒,
做到計算機的cpu,磁碟,網路,資料庫計算等並行;但是node因為網路和磁碟,資料庫等都是非同步IO,所以是放線上程池平行計算的,不會阻塞主執行緒;
2.正常的後端,多執行緒,是因為沒有非同步,所以磁碟的io和網路的io都不是事件輪詢,所以多執行緒可以充分利用到,一個執行緒如果在磁碟的IO時間太久,那麼它後續的cpu計算就還沒執行,cpu就會閒著;
那麼這個時候進行執行下一個worker,這個worker沒有大量的磁碟io計算只是cpu計算,那麼這個時候就是當前worker佔用cpu,之前的worker佔用磁碟io,磁碟和cpu同時各自執行,就沒有浪費
而node的網路io,磁碟io等都是非同步的都是進入libuv的執行緒池做併發,結果返回主執行緒,所以本身就是高併發,唯一的缺點就是主執行緒的cpu計算量太大導致阻塞,所以用cluster來開啟多程序;
開啟多程序,其實就是多執行緒,因為一個程序至少包含一個執行緒;這樣可以充分利用cpu核心數,也可以排除因為單個主執行緒的阻塞導致整個系統被阻塞。

總而言之:
1.除了磁碟io,cpu,資料庫等,還有很多很多資源的計算,用到計算機內不同的器件,多執行緒可以重複利用到,不會造成資源浪費;
2.多執行緒不會因為單個請求的阻塞而導致後面的請求也被阻塞;

多執行緒原理和如何實現多執行緒:
https://blog.csdn.net/springdou/article/details/107337686
https://blog.csdn.net/m0_38086372/article/details/106889530

parent執行緒建立子執行緒時的初始化過程:
1. 透過worker_threads建立一個worker。
2. 呼叫c++建立一個C++worker 物件,實際上是個空物件。
3. 給C++ worker物件建立執行緒ID。
4. C++ worker物件建立initialisation message channel,
5. 一個public message channel 的JS物件被建立,用來父執行緒和子執行緒透過postMessage函式互相通訊。
6. 父執行緒呼叫C++傳送workerData物件到c++ message channel,該物件用來傳輸給子執行緒初始化的時候呼叫。

子執行緒的執行過程:
1. 建立獨立的v8 isolate,並且assgin給worker, 這使worker有獨立的context。
2. libuv初始化,這讓worker有獨立的event loop。【libuv有執行緒池,池子裡全是子執行緒的任務,任務完成以後會把結果返回給主執行緒,主執行緒把workData傳入work,】
3. worker開始真正的執行,event loop開始執行。
4. worker呼叫c++,讀取主執行緒發過來的workerData
5. worker執行js 檔案或者code。作為獨立執行緒執行。

多程序:cluster 模組【可以共享一個公共埠】 ,cluster是經典的主從模型,開啟多程序不是為了解決高併發,主要是解決了單程序模式下 Node.js CPU 利用率不足的情況,充分利用多核 CPU 的效能;多核才有多程序
child_process模組也可以實現多程序,cluster模組是基於child_progress的封裝

NodeJs死鎖(暫時不談資料庫死鎖):程序A和B,都需要資源1和2,如果A程序獲取了資源1,B程序獲取了資源2,因為同一個資源只能被一個執行緒或者程序持有,所以A程序在等資源2被B釋放,程序B在等資源1被A釋放,相互等待,造成死鎖
解決方案:
1.設定最長等待時間,比如A程序獲取1資源後,等3秒還是拿不到資源2,就直接釋放資源1,
2.有鎖的情況下不進行阻塞操作
3.死鎖檢測
4.所有請求鎖按照相同的順序

NodeJs記憶體洩漏:
//原因:
1.自己程式碼寫的不好,資源被掛到了全域性物件上,造成記憶體洩漏
2.第三方外掛記憶體洩漏
//解決方案:
1.使用process.memoryUsage()定期監控記憶體使用情況,就是把process.memoryUsage()放在setInterval函式中,不能太過頻繁,因為影響效能 【這個一般不用,太難排查原因了】
2.用node-memwatch或node-heapdump來監控(獲取記憶體洩露資訊,然後修復)
3.用gc()進行強制回收:非常不推薦,因為會影響原來的
4.重啟應用清理記憶體(一般是緊急情況才使用,守護程序中就可以設定;同時如果發現記憶體洩漏,自己修復再重新發布即可)

Node啟動的時候,會建立V8的例項,這個例項包含了主執行緒,編譯執行緒,最佳化執行緒,分析器執行緒,垃圾回收執行緒等;其實Node預設是多執行緒的,只是js的執行是單執行緒的,這個執行緒就是主執行緒。
Node是否需要使用多執行緒: 多執行緒可以解決I/O高併發,CPU密集型計算,




編譯型語言:只須編譯一次就可以把原始碼編譯成機器語言,後面的執行無須重新編譯,直接使用之前的編譯結果就可以;因此其執行的效率比較高;C、C++
解釋性語言 :持動態型別,弱型別,在程式執行的時候才進行編譯,而編譯前需要確定變數的型別,效率比較低,對不同系統平臺有較大的相容性.:Python、JavaScript、Shell、Ruby、MATLAB

socket是一種網路變成介面(API),應用層可以透過這個socket介面對資料進行傳送和接收,它實際上就是對將要傳送的資料進行轉變,到了目的地後,接收方再對資料進行解析,而這個資料轉化和解析,是一套標準,被所有應用都認可。
socket給網路上的個體之間實現資料雙向通訊,那麼任何程式只要支援socket,其他所有的程式都可以透過套接字來向他傳輸資訊,不管是不同計算機之間,不同程序之間,不同協議之間,它就是一個資料轉化和解析的統一標準
socket是計算機網路通訊的基本基礎,是資料傳輸的基礎,廣泛用於各種網路應用中,socket分為“流式socket”(TCP中用到)和資料包socket(UDP中用到),套接字可以在各個層之間傳輸資料;程序之間也用可以用socket來傳輸資料
socket 被翻譯為“套接字”,它是計算機之間進行通訊的一種約定或一種方式。透過 socket 這種約定,一臺計算機可以接收其他計算機的資料,也可以向其他計算機傳送資料
socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆檔案”,都可以用“開啟open –> 讀寫write/read –> 關閉close”模式來操作。
一般情況下,socket是被用來各個層和層之間傳輸資料用的【比如傳輸層(tcp)的資料傳到應用層(http)】;
//OSI七層模型種,向上層傳輸叫upStream;反之就是Downstream;
Upstream:從傳輸層[tcp udp]的 Socket 讀取資料傳到上層【例如應用層】,即請求資料從下層向上層傳輸;
Downstream:反過來向 Socket 寫資料,就是從應用層向傳輸層傳送資料,方向由上往下,即向 Socket 寫

SSRF攻擊【服務端請求偽造】:https://blog.csdn.net/qq_43378996/article/details/124050308

!!!!!!!!!!!!!!!!!
0. node端各個版本的V8引擎的最佳化:
JIT編譯器:它是一個可以在執行時最佳化程式碼的動態編譯器

node端垃圾回收機制:https://blog.csdn.net/weixin_42317878/article/details/121880028
https://www.cnblogs.com/itstone/p/10477250.html
1.記憶體分2塊,新生代記憶體和老生代記憶體,它們有不同的處理機制
2.新生代記憶體用 Scavenge[ˈskævɪndʒ],新生代記憶體是臨時分配的記憶體,存活時間短;一分為二,from區放物件,另一半是空閒區,垃圾回收的時候,把from去的記憶體檢查一遍【標記清楚回收記憶體】,有用的全移動到空閒區;
原來的from區變空閒區,原來的空閒區變成了物件區;就是對調了;讓物件緊挨著,因為記憶體是連續分配的,零散的記憶體是記憶體碎片沒法利用,所以用這種移動端的方式,清理除了無用物件,也清理出了記憶體
Scavenge演算法是典型的浪費空間換時間的演算法。
3.老生代區是常用的變數,如果一個變數經過幾輪Scavenge演算法後還存在,那它就是常用變數,會被晉升,這個變數就會直接進入“老生代區”,;
老生代區的垃圾回收又不太一樣,是標記清楚【進來全打標記,再給使用的變數清除標記,那些有標記的就是沒用的變數,直接清除】;然後再標記整理【把存活的變數全部往一端靠,解決記憶體碎片的問題】

4.增量標記:js是單執行緒,垃圾回收的時候佔用時間過久就會卡頓【尤其是老生代區的回收,速度慢】,所以本來應該是一次性把所有變數的標記做完【標記清除】,現在是分批標記,就是中間穿插js
核心還是輪詢,標記一部分變數,然後輪循到js執行一段,然後繼續執行垃圾回收,等標記完成以後,在執行記憶體碎片整理【就是移動變數】
node端的單執行緒模仿多執行緒實現原理:事件輪詢,先進先出 (FIFO) ; 與這個相反的是佇列,佇列是先進先出
node端的監控
請求型別:get【查詢】,post【修改資料,增,刪,改,傾向於增加(一般ajax中也就用post代替了其他put,delete操作)】,put【類似於post,傾向於“改”】,delete[刪除操作],options[預檢操作]
cors跨域:簡單請求(simple request)和非簡單請求(not-so-simple request)詳見:https://www.ruanyifeng.com/blog/2016/04/cors.html

1.CORS支援所有型別的HTTP請求
2.IE8+都支援
3.原理:HTTP頭部允許瀏覽器和伺服器相互瞭解對方,從而決定請求或響應成功與否
4.後端相關設定header引數:
Access-Control-Allow-Origin:指定授權訪問的域
Access-Control-Allow-Methods:授權請求的方法(GET, POST, PUT, DELETE,OPTIONS等)
Access-Control-Expose-Headers:FooBar (服務端允許暴露給瀏覽器的自定義header)

Access-Control-Max-Age:設定在n秒不需要再傳送“預檢”請求【就是prelight請求】,這個是非簡請求中設定的,是服務端設定
Access-Control-Allow-Credentials :設定允許Cookie,設定“true”

核心:Access-Control-Allow-Headers用於前端向後端傳遞資料的場景,而Access-Control-Expose-Headers用於後端向前端傳遞資料的場景‌1。
Access-Control-Allow-Headers允許伺服器指定哪些HTTP頭部欄位可以被客戶端在跨域請求中包含。當前端向後端傳送請求時,如果請求中包含了不在簡單請求範圍內的頭部欄位(如refresh_token),伺服器需要在響應頭中透過Access-Control-Allow-Headers欄位明確允許這些欄位的傳輸‌1。
Access-Control-Expose-Headers則用於指示伺服器響應中的哪些頭部欄位可以被暴露給前端指令碼。在跨域請求中,即使伺服器響應中包含了某些頭部資訊,瀏覽器預設情況下也不會將這些資訊暴露給前端指令碼。透過設定Access-Control-Expose-Headers欄位,伺服器可以明確指示哪些頭部資訊是可以被前端指令碼訪問的‌ (Access-Control-Allow-Headers:X-Custom-Header, X-Another-Custom-Header )
簡單請求:
1.請求方式是head,get,post [基本ajax都滿足這個]
2.http的header不能超出下面的http頭:
Accept ;
Accept-Language ;
Content-Language;
Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
Last-Event-ID ;
除了簡單請求之外,所有的其他請求都是非簡單請求,簡單請求和非簡單請求的處理效果是不同的

簡單請求如何實現:
1.簡單請求只要在http的header種新增Origin欄位【一般的請求都是自動新增origin,不需要自己額外設定header】,例如Origin: http://api.bob.com 【協議域名埠(埠不寫就是預設80埠)】
後臺如果覺得這個域名可以允許跨域,就在返回的http資訊種設定“Access-Control-Allow-Origin”對應的域名,例如this.set("Access-Control-Allow-Origin", "http:localhost:8080/"),表示允許http:localhost:8080/跨域
接下來前端瀏覽器獲得返回的http請求資訊,先檢視Access-Control-Allow-Origin頭,如果沒有的話,就知道出錯了,瀏覽器丟擲一個錯誤,被XMLHttpRequest的onerror回撥函式捕獲;請求失敗;否則就請求成功
注意:如果Origin指定的域名在許可範圍內,伺服器返回的響應,會多出幾個頭資訊欄位;
Access-Control-Allow-Origin: http://api.bob.com :這個欄位是必須的
Access-Control-Allow-Credentials: true :該欄位可選。它的值是一個布林值,表示是否允許傳送Cookie。預設情況下,Cookie不包括在CORS請求之中。設為true [Credentials:資格,資質]
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面說到,CORS請求預設不傳送Cookie和HTTP認證資訊。如果要把Cookie發到伺服器,一方面要伺服器同意,指定Access-Control-Allow-Credentials欄位。
伺服器端要設定Access-Control-Allow-Credentials: true來統一ajax請求的cookie傳送
!!!!開發者必須在AJAX請求中開啟withCredentials屬性。 !!!var xhr = new XMLHttpRequest();;xhr.withCredentials = true;!!!!這樣瀏覽器才會允許把cookie跨域傳送出去,
不設定withCredentials為true的話,就算伺服器那邊統一接受跨域cookie,瀏覽器這邊也在跨域傳送cookie的時候也會拒絕。
如果要傳送Cookie,服務端Access-Control-Allow-Origin就不能設為星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用伺服器域名設定的Cookie才會上傳,其他域名的Cookie並不會上傳

非簡單請求:
1.除了簡單請求以外的請求,例如請求發是put delete;請求的http的header的content-type是“application/json”
2.非簡單請求會有prelight,預檢,就是傳送ajax請求之前先傳送一次http查詢請求【請求方法是OPTIONS】,cors非簡單請求,瀏覽器是自動傳送預檢請求的
var xhr = new XMLHttpRequest();
xhr.open('PUT', 'http://api.alice.com/cors', true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
上面這個請求是put,所以傳送這個請求之前,瀏覽器會自動傳送prelight請求,預檢請求的報文如下:
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive //注意,這個是tcp長連線,就是為了減少頻繁的三次握手,因為tcp通道里面接下來還會傳送其他http請求,沒必要斷開後在實施tcp三次握手
User-Agent: Mozilla/5.0...
prelight請求的核心是http請求頭中的Origin,Access-Control-Request-Method,Access-Control-Request-Headers這三個欄位【origin自動新增的,method是寫ajax的時候必填的引數,所以只要設定X-Custom-Header即可】
接下來,伺服器收到這個prelight請求,再響應這個請求,返回http給瀏覽器,瀏覽器收到響應以後,判斷http頭上是否有“相關的CORS相關的頭資訊欄位”,如果沒有,那就是prelight失敗,
觸發一個錯誤,被XMLHttpRequest物件的onerror回撥函式捕獲;如果響應的http頭上有 Access-Control-Allow-Origin;Access-Control-Allow-Methods;Access-Control-Allow-Headers:之類的欄位
那麼就是prelight請求成功,會自動繼續傳送真正的ajax請求;然後設定
xhr.open('PUT或其他', 'http://api.alice.com/cors', true);
xhr.setRequestHeader('X-Custom-Header', 'value');//用來設定Access-Control-Request-Headers為“X-Custom-Header”
xhr.setRequestHeader('Content-type', 'application/json');

content-type:
1.不常用 application/xml , text/xml ,aplication/octet-stream(這是應用程式檔案的預設值,很少用)
2.常用:application/x-www-form-url(get請求使用) , application/x-www-form-urlencoded (post請求簡單的使用)、multipart/form-data(post請求上傳資料使用)、text/plain【get和post都是用】,
application/json(post請求使用)





爬蟲指的是:向網站發起請求,獲取資源後分析並提取有用資料的程式,流程如下:模擬瀏覽器傳送請求(獲取網頁程式碼)->提取有用的資料->存放於資料庫或檔案中
如何反爬蟲:
1.最基礎的python爬站點介面或html,請求的時候是沒有ua的,所以最最基本的是透過ua來判斷最低階的爬蟲;
2.透過爬蟲的行為去判斷,比如訪問時間,訪問頻率;從而給每一個介面設定同ip+ua的訪問頻率;
3.蜜罐技術【誘捕非定向爬蟲,但是對定向爬蟲卻沒辦法】,設定一個對外快放,但是卻永遠不會對外展示的連線,就是沒有入口的連線【連線名字不能太簡單,萬一是真的使用者自己猜個人中心輸入網址,進了蜜罐,那就錯殺了】,
如果這個沒有外鏈的請求地址,被人請求了,那幾本就是爬蟲,直接封他的“IP+ua”

三種IO多路複用:poll select epoll 【epoll是最新最普遍的多路複用】
在linux 沒有實現epoll事件驅動機制之前,我們一般選擇用select或者poll等IO多路複用的方法來實現併發服務程式,epoll是實現高併發的關鍵,epoll原理如下:https://www.jianshu.com/p/548ef6a267ba
epoll核心:共享記憶體【mmap:程序和核心對映到同一個實體地址】,紅黑樹【紅黑樹是用來儲存這些描述符】,rdlist
epoll兩種模式:水平觸發【資料可讀時,epoll_wait()將會一直返回就緒事件】,邊緣觸發【對程式設計師要求較高:只能獲取一次就緒通知,如果沒有處理完全部資料,並且再次呼叫epoll_wait()的時候,它將會阻塞,因為就緒事件已經釋放出來了】

!!!!!!!!!!!!最準確的理解!!!!!!!!!!!!!
!!!!!!!!!!!!最準確的理解!!!!!!!!!!!!!https://zhuanlan.zhihu.com/p/74879045
Node在兩者之間給出了它的解決方案:利用單執行緒,遠離多執行緒死鎖、狀態同步等問題;利用非同步I/O,讓單執行緒遠離阻塞,以好使用CPU。node再應用層是單執行緒,在libuv是多執行緒【有執行緒池,處理非同步事件,例如建通的tcp連線,檔案非同步處理等】
node單執行緒模仿多執行緒的核心,最終的整體流程梳理:
0.node初始化,開啟主執行緒,node的初始化js執行,知道最終的listen監聽埠,如果有非同步的檔案操作或其他操作,也會新增到Event Queue(事件佇列),這些事件在客戶端請求的前面,會先輪循到,
但執不執行,取決於分配給整個事件的執行緒是否已經完成處理獲得事件的結果;
1.Client 請求到達 node api【一般就是http請求】,該請求被新增到Event Queue(事件佇列)。這是因為Node.js 無法同時處理多個請求【一個http請求就是一個執行緒,因為一個http請求會新增到事件佇列,而一個事件最終會在libuv中分配一個執行緒】。
2.Event Loop(事件迴圈) 始終檢查 Event Queue 中是否有待處理事件,如果有就從 Event Queue 中從前到後依次取出,然後提供服務。
3.Event Loop 是單執行緒非阻塞I/O,它會把請求傳送給 C++ Thread Pool(執行緒池)去處理,底層是基於C++ Libuv 非同步I/O模型結構可以支援高併發。
4.現在 C++ Thread Pool有大量的請求,如資料庫請求,檔案請求等。
5.任何執行緒完成任務時,Callback(回撥函式)就會被觸發,並將響應傳送給 Event Loop。
6.最終主執行緒上的 Event Loop(事件迴圈)在不斷的迴圈中,發現了某個事件已經可執行了,就會執行對應的程式碼,然後返回給對應的客戶端
事件迴圈:其實就是主執行緒上的一個無線的while迴圈輪詢事件列表,事件列表中的每一個事件都會有一個狀態,同時每一個事件關聯一個libuv中的執行緒,一旦執行緒處理完了,就會直接觸發回撥函式把資料傳給對應的事件,同時事件的狀態也變差可執行
主執行緒輪詢事件列表,發現某個事件已經時可執行狀態,就會拿裡面對應的資料進行同步執行【整個同步執行的js,一般就是後端最終生成http請求的response頭和內容的過程,主要是setheader,body按照template生成字串,以及一些公共的同步js處理】
核心:libuv的多執行緒併發因為在cpu中,cpu時輪著給整個執行緒執行的,所以不會因為單個執行緒計算太大導致其他執行緒被阻塞;但是js主執行緒不同,它js計算量過大,就會導致cpu被阻塞,而其他http請求的響應都是在node的事件輪詢中的;
一旦主執行緒計算量太大,cpu一直計算,導致主執行緒阻塞,後面的事件輪詢也就自然被阻塞了,worker thread模組就是來解決主執行緒cpu阻塞的問題的。
libuv有個執行緒池,出現非同步任務的時候,執行緒池裡面拿出一個執行緒來執行,執行完成以後,把這個執行緒歸還給執行緒池
應用層js是單執行緒和V8引擎是單執行緒,如果用到了libuv,在libuv上才是多執行緒【每個事件分配一個執行緒,併發執行,並且主執行緒輪詢事件列表,執行緒完成操作,就會觸發回撥函式給這個執行緒對應的事件,修改事件狀態和返回資料;然後事件輪詢的時候發現這個事件準備好了,就執行主執行緒的同步操作,把資訊返回給客戶端。】
NodeJs: 他是基於V8引擎和libuv構建的一個javascript平臺,其中libuv是自動整合的,所以不需要自己進行特殊化的多執行緒處理,V8引擎是單執行緒的
libuv:跨平臺的非同步I/O庫,由C語言編寫。它封裝了各個作業系統的API,提供網路、檔案和程序等功能。在Node.js中,libuv負責處理I/O操作,如檔案讀寫和網路請求等。它透過事件驅動的方式處理非同步操作,使得Node.js能夠在單個程序中處理大量的併發請求‌
V8引擎:在Node和chrome平臺上,用於編譯和執行JavaScript程式碼。它不僅實現了JavaScript的解析和執行,還支援自定義擴充,例如透過C++ API定義全域性變數等‌1。在Node.js中,V8負責JavaScript程式碼的編譯和執行,提供高效能的編譯最佳化和垃圾回收功能。
注意,V8引擎版本在不斷迭代,有些以前需要手動最佳化的東西,後來V8都自動最佳化了,寫程式碼的時候並不需要特別注意
最終結論: 因為Node本身包含libuv的多執行緒,所以只要新增child_progress或者cluster其中一個多程序模組,透過多程序有衝分利用多核CPU即可

作業系統的非同步IO有哪些:磁碟的讀寫【其實就是檔案的處理,fs模組的非同步方法】,DNS的查詢,資料庫的連線,網路請求的處理(例如客戶端的http請求);【IO裝置有cpu,磁碟等,執行緒就是佔用io裝置做計算處理,然後返回結果,是併發的】
事件迴圈就是主執行緒重複從訊息佇列中取訊息、執行的過程。
!!!!!!!!!!!!最準確的理解!!!!!!!!!!!!!
!!!!!!!!!!!!最準確的理解!!!!!!!!!!!!!
worker_threads模組處理多執行緒;child_process 模組和 cluster 模組處理多程序,cluster是child_progress的封裝
如果NodeJS本身就用到了libuv實現多執行緒,所以只需要再加一個child_progress或cluster的多程序即可,沒必要用worker_threads



node底層原理:詳見https://zhuanlan.zhihu.com/p/375276722:Node.js v10.5.0 的到來而改變,該版本增加了對多執行緒的支援
核心理解:一個cpu核心對應一個程序,一臺機器只有一個主程序,同時可以有多個子程序;一個程序裡面可以有多個執行緒,但是隻有一個主執行緒,有若干個子執行緒
node原本時單程序,單執行緒架構,單程序的原因不能使用cpu多核,單執行緒導致請求不能併發【用事件輪詢模仿多執行緒】
後來Cluster 模組的加入,支援node實現多程序;它是應用層的模組,直接透過require在js中引入,使用方法如下
var cluster=require("cluster");
var http=require("http");
var cpuNum=require("os").cups().length;//透過os模組獲取cpu核心數量
if(cluster.isMaster){//主程序
while(cupNum--){
cluser.fork();//建立子程序
}
}else{//子程序的話,直接監聽
http.createrServer(function(req,res){
...
}).listen(80)//呼叫listen函式的時候,子程序會給主程序傳送一個訊息;主程序就會建立一個socket,繫結地址,並置為監聽狀態;
當客戶端請求過來的時候,主程序負責建立tcp連線,然後透過檔案描述傳遞的方式傳遞給子程序處理,因為各個程序之間詳細是隔離的,這裡透過unix域的“件描述傳遞”來實現資訊互通
}
//因為是輪詢,所以第一遍值的是按照cpu核心數建立所有子程序,一個核心管理一個程序,



多程序模式有“主程序 accept【建立主程序,由主程序統一分配給子程序】”和“共享程序accept【子程序競爭方式獲取連線】”兩種模式。
node的多執行緒和js的單執行緒需要做區分,js執行是單執行緒的,但是在LIBUV上,是多執行緒處理,也就是js單執行緒的任務傳到LIBUV層,這個時候會進入它的一個主執行緒
主執行緒會給每個非同步任務【也不是件事件列表】分配各自的子執行緒
1.應用層[js實現]: 即 JavaScript 互動層,常見的就是“自己寫的js” + 第三方的Node.js的模組,比如 http,fs等第三方node外掛
2.V8引擎層[C++實現]: 負責解析和執行JS; V8 引擎來解析JavaScript 語法,進而和下層 API 互動【node,mongodb,chrome核心都用到了v8】
引擎優勢是,其他引擎都需要把js轉化成“位元組碼”或其他中間語言,他是直接省略這一步,直接轉“機器碼”
處理了很多負責的問題例如:
1.編譯最佳化:JIT
2.垃圾回收:後端的learning.md有介紹
3.記憶體管理:
注意:在V8引擎層,還會有其他用C++編寫的第三方庫,這個庫可以依賴下面的LIBUV層,也可以不依賴,因為下面的LIBUV層也有和它平級的C語言第三方引用程式

3.LIBUV層【C語言實現】: 是跨平臺的非同步IO庫【底層封裝】,它封裝了各個作業系統的一些 API, 提供網路還有檔案程序的這些功能;
在 JS 裡面是沒有網路檔案這些功能的,在前端時,是由瀏覽器提供的,而在 Node.js 裡,這些功能是由 Libuv 提供的。
實現了 事件迴圈、檔案操作等,是 Node.js 實現非同步的核心 。

LIBUV有個執行緒池,它負責非同步 I/O 操作,即與系統磁碟和網路的互動

對於資料的同步,有輪詢和觀察者模式這2種方法:
輪詢的思想是,監控一個變數,然後每個幾秒不斷輪詢,變數發生改變就觸發回撥函式來觸發響應;
觀察者模式就是觀察者先訂閱這個物件,物件一旦發生改變會自動呼叫之前函式來觸發響應


node的V8引擎和java的jmv直譯器差別:
1.JIT即時編譯,直接把js程式碼轉AST【抽象語法樹】,然後在直接轉“本地機器碼”【cpu可以識別進行計算】
而java在java程式碼轉AST後,還會轉“中間碼”,多了這個步驟,方便程式碼最佳化,但是多了非同步執行就慢了。
因為是直接把js透過抽象語法樹轉成“機器碼”,直接交給硬體就可以執行了。它不會產生“二進位制碼”或其他“中間碼”


//後端相關知識
0.任務排程:任務排程是分散式系統中非常重要的環節,它涉及到任務的安排、執行和資源管理。透過合理的任務排程,可以最佳化系統的效能,減少資源競爭和死鎖的風險,確保系統的穩定性和高效性‌1。
‌定時任務‌:如每天傳送報告、清理快取等。
‌週期性任務‌:如定時檢查系統狀態、更新資料等。
‌非同步任務‌:如處理後臺資料、傳送郵件等,以提高系統的響應能力和吞吐量‌
一般用node-schedule
0.後端快取:Map物件簡單快取(伺服器本地快取,類似於瀏覽器上的window快取),或用redis這類關聯式資料庫快取資料,
0.郵件服務:nodemailer外掛
0.log管理: 日誌管理用log4js
0.程序管理和監控:pm2,包含了程序守護【出問題自動重啟】
1.對應框架的選擇,比如node後端,選擇express大禮包還是koa或NextJs這種輕量級的框架,框架時對流程的規範和梳理
2.在框架的基礎上,對後端程式碼結構進行自己的梳理,比如自己寫controller,view,model,route四個資料夾,
透過route路由,然後在實現後端的mvc模式【model處理資料庫相關資料操作,view執行渲染,controller執行業務邏輯並且和view以及model連線通訊】
3.網站資料安全:xss【跨站指令碼攻擊】,csrf【跨站請求偽造】,ssrf【服務端請求偽造】,sql注入,重播攻擊,ddos,網路劫持,點選劫持,http安全頭等,資料加密解密加簽解籤,https證書[預防dns劫持和http劫持]
4.統一的資料轉化和許可權校驗(使用者登入態和登入驗證;運算元據時候的許可權校驗),快取機制,資料埋點和錯誤統計
等常用功能
5.資料庫:Node的主資料庫一般用mongodb,增刪改查【CRUD】基本操作;聚合,管道,mapreduce,事務;叢集,分片;
叢集:叢集是多個伺服器共同儲存資料,儲存的資料是相互同步共享的,有主從模式等叢集方式,防止一個伺服器掛了,另外的伺服器可以把資料庫同步到掛掉的伺服器上。
分片:把資料庫的各張表拆分到各個節點中去,也就是拆分到各個資料庫伺服器,這樣就很容易實現資料庫的擴充,因為單個伺服器容量是有限的,不能在單個伺服器上無限新增表,但是分片以後,這些表就可以新增到其他資料庫伺服器上。

6.高併發的各種問題:資料的髒讀,幻讀,不可重複讀等問題的產生以及如何解決【透過設定事務級別+加鎖來實現】
但加鎖的時候容易死鎖,同時加鎖的範圍和事務級別的高低又會影響高併發效能;

高併發的核心,就是選擇合適的事務隔離級別+加鎖,保證資料的“原子性、一致性、隔離性、永續性”的前提下,儘量給事務設定低的隔離級別,儘量縮小鎖的範圍;並且寫程式碼的時候避免產生死鎖【沒有事務也可能產生死鎖,因為系統會對增刪改自動加鎖】
原子性:事務要麼對資料修改全都成功,要麼全都失敗
一致性:事務完成以後得資料,要符合資料庫的約束條件,不能破話事物的完整性約束,比如A賬戶向B賬戶轉賬200,轉賬前後A+B賬戶總金額必須相等。
隔離性:各個事務的執行,不會被其他事務干擾(防止髒讀,不可重複讀,幻讀),所以就產生了對應的事務的隔離界別(未提交讀,已提交讀,可重複度,可序列化)
永續性:資料修改完成後,系統故障也不會丟失資料,是持久的,也就是已經更新到了資料庫中。
髒讀【修改時加排他鎖】:一個事務讀取了資料庫資料並修改,但還沒有提交到資料庫,這個時候另外一個事務也訪問了這個資料,就是髒讀
不可重複讀【行級鎖】:事務A第一次讀取資料a的值為100,此時事務A還沒提交;然後事務B在這個期間修改了資料a,改為了200;然後事務A再次讀取資料a的時候發現變成了200,就是同一個事務執行,裡面多次讀取資料,發現資料不一致,就是不可重複讀
幻讀【表級鎖】:類似不可重複度;不可重複讀和幻讀的區別是:前者是指讀到了已經提交的事務的更改資料(修改或刪除),後者是指讀到了其他已經提交事務的“新增資料”。

鎖的分類:
1.執行緒鎖(Node用不上,因為單執行緒):對於程式碼塊的鎖,有的時候執行緒A執行某個程式碼塊執行到一個非同步操作,跳轉到其他地方
整個時候執行緒B又執行整個程式碼塊,導致裡面的變數改變
一般都不會遇到執行緒鎖,因為基本每個任務都是單獨的例項,沒用到共享變數。
2.資料庫鎖:資料庫級別鎖【資料庫鎖】,表級別鎖【單集合鎖】,頁級別鎖【多文件鎖】,行級別鎖【單文件鎖】


如何防止死鎖:...

https://www.cnblogs.com/duanxz/p/12697030.html
mongo從4.0版本開始支援事務,需叢集部署,mongodb的事務隔離級別是怎樣的我還沒去了解;其他資料庫都是類似下面的
事務的隔離級別:【資料庫事務有一個預設的隔離級別,sqlServer的事務預設隔離級別是下面的2】
事務的隔離級別越高,併發能力也就越低;保證資料的“原子性、一致性、隔離性、永續性”的前提下,儘量選級別低的隔離級別

1.Read uncommitted 未提交讀
2.Read committed 已提交讀
3.Repeatable read 可重複讀
4.Serializable 可序列化 :這個是最高階別,能解決所有問題,但效能很低【讀取資料的時候直接鎖表,改表的其他所有操作都不能執行】

//不同隔離界下出現“髒讀,不可重複讀,幻讀”的可能性
隔離級別 髒讀 不可重複讀 幻讀
未提交讀 可能 可能 可能
已提交讀 不可能 可能 可能
可重複讀 不可能 不可能 可能
可序列化 不可能 不可能 不可能


資料加鎖,“死鎖”,鎖的讓渡:下面是mongodb的鎖的模式
R :共享鎖(S):資料可以有時候會自動加共享鎖,同時自己也可以手動加
即一個事務在讀取一個資料行的時候,其他事務也可以讀,但不能對該資料行進行增刪改(因為增刪改都是自動加排它鎖)。
W :排它鎖(X):資料可以有時候會自動加排他鎖,同時自己也可以手動加
即一個事務在讀取一個資料行的時候,其他事務不能對該資料行進行增刪改,不加鎖的查是可以的,加鎖的查是不可以的。
r :意向共享鎖(IS) :資料庫自動執行,不需要人工干預
w :意向排它鎖(IX):資料庫自動執行,不需要人工干預

樂觀鎖和悲觀鎖的理解【沒必要用】:mongodb 沒有樂觀鎖和悲觀鎖,這些需要自己在程式碼中實現

鎖的範圍:文件【鎖一個文件:行鎖】,集合【鎖整個集合:表鎖】,資料庫【鎖整個資料庫】

事務對某行或者某張表或者整個資料庫加鎖,就是整個事務對整個資源宣告主權,在我佔領這個資源的情況下,其他事務都無權對這個資源執行我不允許的操作;
同一個資源,可以接受多個事務的鎖,就是被多個事務佔領,但是有的鎖是不能重複加的
比如共享鎖,事務A對錶施加了共享鎖,其他事務也可以新增共享鎖,但不能新增排他鎖
如果事務A對錶施加了排他鎖,那其他任何事務都不能對錶施加共享鎖或排他鎖,不能增刪改,但可以查

mongodb的資料庫操作對應的加鎖機制:

操作 資料庫級別鎖 集合級別鎖
查詢(query) r (意向共享鎖(IS)) r (意向共享鎖(IS))
插入資料(insert) w (意向排它鎖(IX)) w (意向排它鎖(IX))
刪除資料(remove) w (意向排它鎖(IX)) w (意向排它鎖(IX))
更新資料(update) w (意向排它鎖(IX)) w (意向排它鎖(IX))
執行聚合操作(aggregation) r (意向共享鎖(IS)) r (意向共享鎖(IS))
建立索引(前臺建立Foreground) W (排它鎖(X))
建立索引(後臺建立Background) w (意向排它鎖(IX)) w (意向排它鎖(IX))
列出集合列表(List collections) r (意向共享鎖(IS))
版本4.0中修改.
Map-reduce操作 W (排它鎖(X) 和R 共享鎖(IS) w (意向排它鎖(IX)) and r (意向共享鎖(IS))


預設的增刪改查操作本身就是原子操作,資料庫自動會加鎖解鎖,不需要開發特地加鎖!!!
最麻煩的是“事務”,事務雖然執行結果具有原子性,但是執行的時候,會出現各種髒讀,不可重複度,幻讀等問題,需要設定事務的“隔離級別”,並且給事務加儘量小的鎖;















冪等 :在資料不變的情況下,一個操作,無論執行多少次,結果都是一樣的;冪等函式就是,任何時候呼叫,引數不變,結果不變

後臺處理資料庫業務邏輯:建立連結次數儘量少,多個請求儘量合併成一個請求,因為建立連線是比較費時的,損耗後臺伺服器,也損耗資料庫伺服器;
所以一個請求能完成的所有資料,就別分多次請求;使用者端一個行為,最好是連線一次資料庫

//需要一個本地資料庫,一個測試資料庫,分別用於本地開發和測試環境測試

資料庫:mongodb【和redis類似,都是記憶體型資料庫,先把資料放記憶體,然後再寫磁碟裡】【主要解決海量資料的訪問效率問題,但它比較佔資源,檔案比較大,磁碟和記憶體需要較大】
0.超級好用,易學,兩天就看得七七八八了
1.“nosql型別資料庫”中的“文件儲存”型別
2.database【資料庫】,collection【集合】,document【文件】,field【資料欄位/域】,index【索引】,primary key【主鍵】
3.基礎:增刪改查,
索引【提高查詢速度】:如何制定高效的索引,是提升效能的核心;https://www.cnblogs.com/yu-hailong/p/7631572.html
0.索引只要建立一遍,就會一直存在於資料庫伺服器的記憶體裡,資料庫更新,索引也會自動更新
1.把集合按照特定field欄位分組,其實就是把資料格式按照某些特定的key去排序,創造另一種便於查詢和操作的資料格式
例如使用者表的主鍵,文章表的主鍵也是id;評論表的主鍵,他們支架式有關聯的,比如某個使用者的主鍵設定為11111,那麼這個使用者的文章的主鍵設定成11111_文章id,然後評論主鍵設定為11111_文章id_評論id
有了上面這種主鍵和外來鍵的關聯,查詢某個使用者的所有文章,只要知道該使用者的id,就可以直接插文章這張表就可以了,不用級聯。查詢該人所有文章下的所有評論,也不用級聯;非常方便。

2.索引可以看成另一種資料庫中的資料,只是這個資料儲存在記憶體中,但是運算元據的時候,索引也必須更新,就當成資料庫的一部分
3.索引可制定權重,搜尋某個資料的時候,會按照關聯索引的權重去查詢,因為在記憶體,所以速度特別快,但是對記憶體要求高
4.如果不用索引,面對大批次的資料排序,資料庫伺服器會把用的所有資料全部塞到記憶體裡面計算,MongoDB 將會報錯 造成記憶體溢位,導致MongoDB報錯
5.其實每一個集合內的文件,文件的“主鍵_id”就是它的預設索引,但基本查詢不會用到這個欄位的查詢,索引需要自己建立索引

進階:mapReduce【大批次資料處理工作分解成一個個單元執行,然後再把結果合併】:這個是核心,必須掌握【用js編寫,並且基於js v8引擎解析】
mapReduce:https://www.cnblogs.com/boshen-hzb/p/10431295.html,說得非常好,其他人講得都是垃圾
使用場景:複雜大型的資料操作,並返回集合資料,如果只是統計綜合,平均值之類的,用聚合比較合適,因為較輕量級
1.filter篩選,
2.根據emit函式里的第一個引數進行分組,對應的值是第二個引數的陣列,分成若干組,完成map;
3.map產生的組的key就是emit函式里面的key,map組的value【陣列】就是emit(key,value)裡面的value,會把相同key的所有value集合在一起形成一個陣列;
最終reduce接收到的資料是[{key:[...values]},{key:[...values]},{key:[...values]}]
4.而reduce裡面會自動遍歷這個陣列的所有物件,例如上面的3個物件;
至於物件裡面的資料如何處理,就是看reduce函式里面的邏輯了,reduce會預設遍歷,然後
再執行reduce函式里面的邏輯,傳入的引數是每一個組物件,例如{key:[...values]}
最終產生一個聚合陣列,就是把每個組的返回值全push到陣列裡面

聚合+管道【一般使用者統計平均值,求和等遍歷大批次資料求值的操作】




4.高階:複本集(也就是叢集)【也是有主庫和從庫,主庫死了,從庫自動頂上】,分片【增加伺服器,提高資料儲存量和計算速度,把資料庫的一部分表分散到其他資料庫伺服器節點】
mongodb單個資料庫的資料結構:所以mongodb的資料庫集合,實際上就是一個JSON物件

{
db:{
//db下一個物件,就是一個“集合”
"collectionA":[{//一個物件就是一個文件
_id:ObjectId("456321456321546"),
name:"jeff",//一個key就是對應的field
age:24
},{//一個物件就是一個文件
_id:ObjectId("456321456321546"),
name:"jeff",//一個key就是對應的field
age:24
}],
"collectionB":[]
}
}





資料庫安全:
1.防止而已大批次查詢,資料庫記憶體溢位。
2.給可能的大批次查詢制定索引,提高效率的同時防止記憶體溢位【索引也不能太多,不然也會造成記憶體溢位】


















論壇的資料庫資料結構:資料庫結構設計,很容易關聯錯誤導致資料前後矛盾,畢業設計用到的一個軟體可以檢測這個問題【實體,主鍵,外來鍵設定】
最複雜的功能就是評論巢狀的設計:如何存檔資料方便查詢

{
ab:{
//users,articles,comments是三個集合,一般經常用到MapReduce命令來遍歷一個集合,比如用MapReduce遍歷users
users:[{
//下面是內容
uid,
userName,//帳號
passward,//密碼
phone,
name,//真實姓名
age,
sex,
favName,//網名|暱稱|花名【唯一,大家網名不能相互重複】
charts:[{type,data}]//data是圖示的資料,對資料格式的校驗前端完成,後臺和資料庫都不做處理

//下面是關聯key:關聯人,文章,說說|評論
fans:[uid]//粉絲
attention:[{uid,attentionLevel}]//關注哪些人
favComment:[uid]//點讚了哪些評論|說說
favArticle:[aid]//點讚了哪些文章
article:[aid,aid....]//文章
collect:[aid,aid...],//收藏
comment:[cid]//寫了哪些評論|說說
msgboard:[cid]//留言板,誰給這個人留言了
}],

//文章的查詢特別多,文章相對來說比“說說"少很多",且有些欄位也不一樣,沒必要放一個集合,邏輯上也說不通,效能上也不好
articles:[{//業務邏輯處理的時候,要特別注意是“文章”|“說說”|留言

//下面是內容
aid,//唯一id
time,//時間
title,//標題
content,//內容

//下面是關聯key
tag:["desc1","desc2"....]//標籤
comment:[aid,aid]//評論:只有針對文章的評論,其他都沒有
uid,//誰寫的
atuid,//作者@誰
faver:【uid】//點贊
relay:[uid,uid]//轉發
}]

//誰在什麼時候對誰說了什麼話,哪些人支援點贊
comments:[{//注意,這個不光是相互評論;還有一個是個人主頁留言板的第一層評論,

//下面是內容
cid,//唯一id
time,
content,

//下面是關聯key
uid,//誰寫的
target:{//針對誰
uid,//在某人留言板留言
aid,//在某個文章下留言
cid,//針對某個評論或說說留言
noid//不針對任何人,文章,評論|說說;也就是自己發獨立的說說
}
faver:【uid】//點贊
}]

}
}




關於前後端資料傳輸:


Form元素的EncType屬性表明提交資料的格式 用Enctype屬性指定將資料回發到伺服器時瀏覽器使用的編碼型別,預設的預設值是“application/x-www-form-urlencoded”。


下邊是說明:
application/x-www-form-urlencoded:窗體資料被編碼為名稱/值對。這是標準的編碼格式。


multipart/form-data:窗體資料被編碼為一條訊息,頁上的每個控制元件對應訊息中的一個部分。


text/plain:窗體資料以純文字形式進行編碼,其中不含任何控制元件或格式字元。

補充
form的enctype屬性為編碼方式,常用有兩種:


application/x-www-form-urlencoded和 multipart/form-data,


當action為get時候,瀏覽器用x-www-form-urlencoded的編碼方式把form 資料轉換成一個字串(name1=value1& amp;name2=value2...),然後把這個字串append到url後面,用?分割,載入這個新的url。


當action為post時候,瀏覽器把form資料封裝到http body中,然後傳送到server。如果沒有type=file的控制元件,用預設的application/x-www-form-urlencoded 就可以了。但是如果有type=file的話,就要用到multipart/form-data了。瀏覽器會把整個表單以控制元件為單位分割,併為每個部分加 上 Content-Disposition(form-data或者file),Content-Type(預設為text/plain),name(控制元件 name)等資訊。
其中Content-Type欄位的詳細是 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzAA0Ue6tbq3U3fAq ;注意這個 boundary:boundary 是分隔符,分隔多個檔案、表單項。如果不自己設定,預設由瀏覽器自動產生

“application/x-www-form-urlencoded”在向伺服器傳送大量的文字、包含非ASCII字元的文字或二進位制資料時這種編碼方式效率很低。



在檔案上載時,所使用的編碼型別應當是“multipart/form-data”,它既可以傳送文字資料,也支援二進位制資料上載。


Browser端<form>表單的ENCTYPE屬性值為multipart/form-data,它告訴我們傳輸的資料要用到多媒體傳輸 協議,由於多媒體傳輸的都是大量的資料,所以規定上傳檔案必須是post方法,<input>的type屬性必須是file。

上傳資料的型別【例如multipart/form-data; boundary=----WebKitFormBoundaryBjCL9yOqUJg6HYxg】,從this.req.headers["content-type"].split(";")[0]中獲取到型別【例如:multipart/form-data型別】
上傳的資料,是從this.req.on("data",function(data){ 處理data資料 })中獲取上傳資料,然後在this.req.on("end",function(data){ 資料完全接受完成後的業務處理 })





koa中一般最基本的外掛:
koa-router:路由
koa-static:靜態資源服務
koa-compress:壓縮
koa-helmet: http安全頭,例如X-Frame-Options為SAMEORIGIN防止網站被嵌入別的不同域網站的iframe裡面等
koa-bodyParser:把formData資料同步到this.request.body上
koa-jwt和jsonwebtoken外掛:登入態外掛,koa裡面jwt只設定了加密方式和流程;jsonwebtoken裡面
koa-conditional-get和koa-etag的快取

node-rsa:非對稱加密;公鑰傳給前端,前端框架jsencrypt用公鑰加密後傳回給後臺
crypto[ˈkrɪptəʊ] :hash加密【我選用的機密方式是sha256;常用的hash有md5和SHA256】,不可逆,用於轉譯敏感資料,比如密碼不能明文儲存,需要用crypto進行雜湊加密在儲存到資料庫
xss:防止跨站指令碼攻擊
base64url:Base64有三個字元+、/和=,在 URL 裡面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ 。這就是 Base64URL 演算法
ip,uuid/v4


formidable:圖片上傳外掛,
svg-captcha:驗證碼外掛,註冊時用到驗證碼



koa相關:
this.request是context經過封裝的請求物件,用起來更直觀和簡單;this.req是context提供的node.js原生HTTP請求物件

PM2:程序管理和程序守護
Docker_Compose或k8s:容器編排(如果使用K8s,就不需要PM2了)
Nginx:靜態資料伺服器+負載均衡(就算用了K8s,如果專案本身需要比較複雜的負載均衡,還是需要Nginx來實現)
總之,首先使用 Docker Compose 來啟動多個服務例項(多個容器),然後透過 Nginx 進行負載均衡,最後使用 PM2 來確保 Nginx 本身的高可用性和穩定性。或者直接Nginx+K8s完成容器編排+程序守護+負載均衡


轉化流程:
1.Blob轉其他:透過FileReader轉arrayBuffer,透過CreatUrlObj轉成對應檔案的URL新增到a連結下載各種檔案;
2.其他型別轉Blob:依賴Unit8Array,
3.Unit8Array依賴ArrayBuffer或原始的陣列; ArrayBuffer透過後端獲取;透過base64字串.split("")來獲得原始陣列array;注意所有檔案都可以是base64字串【後端可以傳檔案流過來】
4.前端這邊,js-base64是和後端的base64url處理方式類似,會有對+和/的相容處理,decode解碼的時候預設會把-和_轉成+和/;但是encode的時候,是否處理+和/,得看第二個引數,Base64.encode("fdsfds",true),表示會把+和/轉成-和_
前端這邊還有一個問題,如果base64不是用於http請求的傳輸資料,那麼就得用原始的base64,比如atob(base64編碼)或btoa(base64解碼)等,這個是非常嚴肅的問題,運氣好不出bug,如果傳輸的資料剛好出現先+和/特殊字元,


!!!!!大檔案下載【分塊】!!!!!!!!!

const express = require('express');
const fs = require('fs');
const app = express();
const port = 3000;
//node端
app.get('/file/:chunk', (req, res) => {
const { chunk } = req.params;
const filePath = 'path/to/your/large/file.ext';
const chunkSize = 1024 * 1024; // 1MB chunks
const fileStat = fs.statSync(filePath);
const fileStart = chunkSize * chunk;
const fileEnd = Math.min(fileStart + chunkSize, fileStat.size);
res.set({
'Content-Range': `bytes ${fileStart}-${fileEnd}/${fileStat.size}`,
'Accept-Ranges': 'bytes',
'Content-Length': (fileEnd - fileStart),
'Content-Type': 'application/octet-stream',
});
const stream = fs.createReadStream(filePath, { start: fileStart, end: fileEnd });
stream.pipe(res);
});

//前端
const chunkSize = 1024 * 1024; // 1MB chunks
let fileSize = null; // 檔案總大小
let receivedSize = 0; // 已接收大小
const chunkCount = Math.ceil(fileSize / chunkSize);
function fetchChunk(chunk) {
const xhr = new XMLHttpRequest();
xhr.open('GET', `http://localhost:3000/file/${chunk}`, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
if (xhr.status === 200) {
// 處理分塊資料
const data = new Uint8Array(xhr.response);
// 將資料寫入檔案或處理...
receivedSize += data.byteLength;
if (receivedSize < fileSize) {
fetchChunk(chunk + 1); // 繼續獲取下一個分塊
} else {
// 所有分塊已接收完畢
console.log('檔案下載完成');
}
}
};
xhr.send();
}
// 首先獲取檔案總大小
const xhr = new XMLHttpRequest();
xhr.open('HEAD', 'http://localhost:3000/file/0', true);
xhr.onload = function() {
if (xhr.status === 206) { // 狀態206表示部分內容響應
fileSize = Number(xhr.getResponseHeader('Content-Range').split('/')[1]);
fetchChunk(0); // 開始獲取第一個分塊
}
};
xhr.send();

!!!!!大檔案下載【分塊】!!!!!!!!!


!!!!!大檔案上傳【分塊】!!!!!!!!!
//核心file.slice(offset, offset + chunkSize + 1); //file是blob這個二進位制大物件,offset開始是0,就是上傳分塊的起始下標,chunkSize是每一塊的資料大小; 連續傳送ajax直到所有資料都發完

//客戶端
function uploadFile(file, url, chunkSize, callback) {
let fileSize = file.size;
let uploaded = 0;
function uploadChunk(file, chunkSize, offset) {
let chunk = file.slice(offset, offset + chunkSize + 1);
let formData = new FormData();
formData.append('file', chunk);
formData.append('filename', file.name);
formData.append('chunkSize', chunkSize);
formData.append('offset', offset);
$.ajax({
url: url,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(data) {
// 更新已上傳量
uploaded += chunk.size;
// 遞迴上傳剩餘塊或呼叫回撥
if (uploaded < fileSize) {
uploadChunk(file, chunkSize, offset + chunkSize);
} else {
callback();
}
},
error: function(error) {
console.error('Chunk upload failed:', error);
}
});
}
// 開始上傳第一塊
uploadChunk(file, chunkSize, 0);
}
// 使用示例
const file = document.getElementById('fileInput').files[0];
const url = '/upload'; // 上傳的URL
const chunkSize = 1024 * 1024; // 每個塊的大小,這裡是1MB
uploadFile(file, url, chunkSize, function() {
console.log('Upload completed');
});


//服務端
const http = require('http');
const fs = require('fs');
const filePath = 'path/to/your/file.ext'; // 要儲存檔案的路徑
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/upload') {
let fileSize = 0;
req.on('data', (chunk) => {
fileSize += chunk.length;
// 處理分塊資料
// 可以將分塊資料寫入檔案
fs.appendFileSync(filePath, chunk);
});
req.on('end', () => {
res.end('File received successfully');
});
} else {
res.end('Not found');
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});


!!!!!大檔案上傳【分塊】!!!!!!!!!

//
圖片轉base64: 用new FileReader().readAsDataURL ;或者canvas用drawImage劃到canvas上,然後用toDataURL方法把canvas轉為base64
base64圖片URL或者arrayBuffer如何轉為檔案的形式上傳到伺服器:
var arr = new Uint8Array([...imageBase64Url]|arrayBuffer);
var blobFileObj = new Blob(arr,{type:"image/png"});

//模仿表單上傳檔案
var fd = new FormData();
fd.append('file',localFile,"image/png");
fd.append('enctype',localFile,"multipart/form-data");
fd.append('key',value);//表單的key和value,比如姓名,年齡等常規表單的資料
$.ajax({
data:fd,
type:"post",
dataType:"json,
cache:false,//不設定快取
precessData:false,//data資料無需序列化,必須為false,因為上傳的是檔案流資訊
contentType:false//表示不設定contentType;前提是ajax得支援這個功能,有的ajax庫會有個預設的content-type而且還不能刪除這個值,只能覆蓋,那就完了
})

//模仿生成本地檔案並下載
var fileUrl = window.URL.createObjectURL(blobFileObj);//建立資原始檔的本地url
var link = document.createElement("a");
link.download = "dbfile.xls";//下載檔名
link.href = fileUrl; //下載檔案url
link.click();

服務端檔案,也可以透過arrayBuffer傳到前端,前端把他轉成Unit8Array陣列然後轉成Blob二進位制物件


前端資料轉化流程+後端如何實現前端的多個ajax分片資料接收:
1.Blob物件:是一個二進位制大物件【檔案,e.target.file就是這個型別,也可以透過new Blob([new Uint8Array(['a','d','s'...])],{type:'資料型別,例如image/png'}) 來刪除檔案物件的原始資料】;
2.ArrayBuffer ,物件用來表示通用的、固定長度的原始二進位制資料緩衝區,可以在FileReader物件的onload時間中透過this.result獲取;透過這個物件,可以獲取到建立Blob時所需的Uint8Array陣列,
3.Uint8Array,型別陣列,有非常多的型別陣列,這些資料型別是構造Blob的前提,所以要像操作檔案,必須要獲得對應的Uint8Array或其型別陣列物件
Uint8Array([1,3,4]|ArrayBuffer)都可以生成Uint8Array物件,也就是說裡面可以傳入原始陣列,也可以傳入ArrayBuffer資料
4.原始陣列[],這個就是二進位制流的最底層,上面第三步中的Uint8Array或其他陣列型別,都是接受原始陣列的傳入,接下來還可以用base64編碼和解碼;
5.base64字串,其實就是字串,例如圖片可以轉成base64字串【瀏覽器識別】;應用檔案【例如php exe字尾的檔案】也可以轉成base64字串,只是瀏覽器不識別而已,但這個資料流就是這個檔案。
其中 btoa (Binary to ASCII):base64編碼 這個函式是沒有對+和/符號做相容處理的,所以最好還是用相容的js-base包,否則的話,自己還得寫字串處理的相容問題
atob (ASCII to Binary):base64解碼 這個函式是沒有對+和/符號做相容處理的,所以最好還是用相容的js-base包,否則的話,自己還得寫字串處理的相容問題
如何從Blob【就是原始檔案ele.file】轉成base64字串,然後進行檔案的分割;步驟如下
var reader = new FileReader();//這是核心,讀取操作就是由它完成.
reader.readAsArrayBuffer(document.getElementById("idname").files[0]);//讀取檔案的二進位制內容【一般讀取應用檔案】;//reader.readAsDataURL是用來讀取圖片的base64字串
reader.onload = function () {//檔案閱讀器載入了好了這個檔案,就可以透過
//當讀取完成後回撥這個函式,然後此時檔案的內容儲存到了result中,直接操作即可this.result獲取arraybuffer物件
var binary = this.result;//arraybuffer物件(有3種表示方法)
var arr = new Uint8Array(binary);//提取二進位制位元組陣列,使用Uint8Array表示
var base64Str = Base64.encode([...arr].join(""),true);//最好別用btoa,用統一的js-base64這個包,能處理特殊符號的問題;
//把Unit8Array轉成原始陣列,然後獲得原始字串,然後在把字串進行base64轉碼,生成對應的base64字串,轉base64只是為了提高資料傳輸安全性
// ajax和form表單請求傳送,預設是不會給資料進行base64加密的,需要自己加密

// ...接下來的業務邏輯處理,如何分割這個“檔案流”
!!!特別注意的一點,如果base64字串傳給後端,需要把+和/轉成-和_,後端再轉回來【後端有base64url這個包可以正確處理+和/的問題,就是解碼的時候,會先把-和_轉成+和/然後再用base64解碼;編碼是先base64編碼,在把+和/轉成-和_】
前端這邊,js-base64是和後端的base64url處理方式類似,會有對+和/的相容處理,decode解碼的時候預設會把-和_轉成+和/;但是encode的時候,是否處理+和/,得看第二個引數,Base64.encode("fdsfds",true),表示會把+和/轉成-和_
前端這邊還有一個問題,如果base64不是用於http請求的傳輸資料,那麼就得用原始的base64,比如atob或btoa等,這個是非常嚴肅的問題,運氣好不出bug,如果傳輸的資料剛好出現先+和/特殊字元,
那麼首先得判斷你用這個base64,是處理本地的原生,還是url傳輸;如果全部用相容處理的base64,雖然可以解決url的問題;但是對於本地的業務處理的base64,就可能出錯,比如我要把圖片轉本地base64
如果按照url的base64轉,那麼就會出現圖片展示的問題,只能用原始的base64來處理;而且canvas產生的base64,都是原始的base64,這個得做特殊處理,否則就會有各種各樣的猜不透的bug
比如前端的jsencrpy中用到了base64,是原生的base64,在本地解密是可以的,但是這個資料透過url傳遞到後臺,就會有+和/的傳輸問題,所以前端在傳資料前,必須替換+和/,
後端那邊用base64url來解碼【把-和_轉成+和/然後再用base64解碼】,然後再node-rsa的私鑰去解密
}

如何從base64字串轉成對應的Blob檔案,然後透過FormData的形式傳送ajax請求,來上傳檔案
var originStr="kkefsfds";//原始字串
var base64=Base64.encode(originStr);//如果直接得到的字串就是base64字串,那就沒有必要btoa了,因為再執行依次,就編碼2次base64了【base64是可以編碼解碼的,而hash加密"比如md5加密或sha256加密"是不可逆的】
var originArray=[...base64];
var unitArr=new Uint8Array(originArray);//注意,這個originArray必須是base64字串轉化而來,否則Uint8Array會識別錯誤
var localFile=new Blob([unitArr],{type:"image/png"});//這裡是轉成png圖片格式的Blob物件,還可以是aplication/octet-stream(這是應用程式檔案的預設值,很少用)
var fd = new FormData();
fd.append('file',localFile, Date.now() + '-user-pic.'+match[1].split("/")[1]);//字尾名和mimetype必須對應
fd.append('enctype',"multipart/form-data");//表單資料的編碼方式,有下面三種【編碼格式==content-type】 “Content-Type是指http/https傳送資訊至伺服器時的內容編碼型別
//multipart/form-data才能用於檔案上傳【可以用文字和二進位制方式上傳】;
//application/x-www-form-urlencoded不是不能上傳檔案,是隻能上傳文字格式的檔案;在傳送前編碼所有字元(預設)
//text/plain:空格轉換為"+"號,但部隊特殊字元編碼
// ajax的預設編碼方式也是application/x-www-form-urlencoded;一般都設定成 application/json,也就是content-type設定為application/json
// content_type是mimetype的別名;提示Mime錯誤,就是content-type返回錯誤;例如abc.xls;而content-type是後端給這個檔案設定的編碼型別,告訴瀏覽器應該以什麼檔案的型別來開啟它;只要後端設定正確,就沒問題;
// 比如伺服器那邊傳送.avi檔案,它對應的mimetype是“video/x-msvideo”,也就是給這個response的content-type設定“video/x-msvideo”;如果設定錯了,設定成了 text/css;那麼瀏覽器按照css檔案格式去解析,就會提示Mime錯誤

fd.append('表單自定義key',"自定義value");//自定義資料,模仿表單中上傳的key和value
$.ajax({
data:fd
type:"post",
dataType: 'json',//如果不用dataType,預設傳的是form資料,資料只能傳一層,多層的資料會按照屬性遍歷分開傳
url:"",
cache:false,//不設定快取
precessData:false,//data資料無需序列化,必須為false,因為上傳的是檔案流資訊
contentType:false//表示不設定contentType;前提是ajax得支援這個功能,有的ajax庫會有個預設的content-type而且還不能刪除這個值,只能覆蓋,那就完了,只能修改ajax庫
// contentType:"" 這個contentType千萬別傳,如果框架的ajax中預設傳了,就得註釋掉,因為post方式上傳FormData型別的資料,他會預設設定成multipart/form-data;boundary.....格式,如果自己設定了,就會報錯
})

後端傳送“檔案流”到前端,如果把檔案流轉換成對應的“應用程式”或者“圖片”或excel【預設後端是傳base64格式的字串過來】
var unitArr=new Uint8Array([...atob(base64Str)]);//base64字串解碼後展開,因為後臺那邊是把檔案流透過base64編碼了,所以需要解碼
var file=new Blob([unitArr.buffer],{type:"application/vnd.ms-excel;charset=utf-8"});
var localUrl=window.URL.createObjectURL(file);//建立資原始檔的本地url
var link = document.createElement("a");
link.download = "dbfile.xls";//下載的檔名,注意這個字尾要和Blob中的type呼應
link.href = localUrl;//下載資原始檔的url,這裡是本地的url
link.click();//直接觸發click去下載資源
各個檔案的mime型別和字尾,詳見 http://blog.csdn.net/weixin_43299180/article/details/119818982 ;
只要再Blob的type中設定對應的型別+;charset=utf-8,然後a標籤的download屬性設定對應綴名的檔名即可

上傳進度監控:
...
//xhr.upload有:loadstart【開始上傳】 error【出錯】 abort【終止】 timeout【超時】 load【上傳成功】 loadend【上傳停止】等時間
xhr.upload.addEventListener('progress', e => {
if (e.lengthComputable) {
var percentage = Math.round((e.loaded * 100) / e.total);//上傳資源的百分比
}
})
xhr.open('post', 'http://localhost:5000/upload',true);
xhr.send(data);

如何實現大檔案分割上傳:多個ajax傳送給伺服器【可以序列傳送ajax,也可以併發ajax】,伺服器全部接收完成以後資源整合,然後做出反饋;
var mov = document.getElementById('mov').files[0];//Blob型別檔案,就是原始檔
var size=move.size;//檔案大小
var arr=[mov.slice(0,size*0.5),mov.slice(size*0.5)];//Blob這個檔案型別支援slice方法進行類似陣列的分割;
var fd1=new FormData();
fd1.append('file',arr[0]);//後端透過file欄位去拿對應的資料,這個名稱是可以自定義的
fd1.append('index',"0");
fd1.append('enctype',"multipart/form-data");
var fd2=new FormData();
fd2.append('file',arr[1]);
fd1.append('index',"1");
fd2.append('enctype',"multipart/form-data");
$.ajax({
...
data:fd1
})
$.ajax({
...
data:fd2
})

//上面是前端Blob型別的資料分塊,下面是後端的接收方法,如何實現“斷點續傳”【部分包傳送失敗,如何從接受】還沒寫
//思路是:
1.前端分批傳送包,後端接收到包就立馬把檔案存到靜態資源伺服器,而不是記憶體【佔用記憶體不好,而且不同的ajax請求之間,node端觸發的是不同的非同步事件,資料難以相互通訊,那麼大檔案放cookie或session明顯不對】
2.每個包都是放入靜態資原始檔夾後【存在相同字首就覆蓋,相同字首表示同一個分片,每個分片後跟隨時間戳,因為有的分片可能是之前的快取,要保證一次整個包的一輪上傳操作內每個包時間戳相同,第二次整體上傳各個分片用新的時間戳】,
當前分片上傳完成後,根據當前分片的時間戳,去靜態資源裡面尋找相同時間戳的其他分片,總體數量和前端的分片數相同,就表示上傳完成,直接把這些分片靜態資源整合到一個檔案,然後給前端返回上傳成功
3.如果想要斷點續傳,很簡單,哪個分片的ajax請求失敗了,就重新自動再傳送哪個分片的ajax請求,用相同的時間戳傳送,表示這些分片是同一批的;如果連續好幾次傳送失敗【一般不會】;那就重新上傳整個檔案,然後再重新分片傳送
這個時候各個分片就有新的時間戳,相當於重新傳送;【每次檔案上傳成功後,需要把所有的分片,以及上次的相同檔名的整個檔案都刪除】

this.req.on("data",function(data){ 處理data資料 })中獲取上傳資料,然後在this.req.on("end",function(data){ 資料完全接受完成後的業務處理 })


node的tcp連線數量有限,每個客戶端的http請求,都是非同步的事件,都會生成一個js執行環境,透過v8傳到libuv進入事件輪詢,從執行緒池中獲得一個執行緒來執行操作,多執行緒併發,
所以各個http請求是相互獨立的,tcp通道內有可能是一個http請求,如果是http2那就有多個http請求流的併發,tcp和http請求跟使用者是沒有對應關係的,一個使用者可以有多個http請求併發,也可以是多個tcp請求併發
伺服器是按照http作為最基本的單位,來執行事件的,它到了libuv,就能分配到一個執行緒,一個http可以看作是一個執行緒【java上出現一個http請求就開一個執行緒,node是在libuv層分配一個執行緒】
所以不同的http請求之間的資料傳遞,是比較難的,要麼放入使用者的服務端cookie,要麼放入session,要麼放入資料庫,這樣才能保證同一個使用者的資料的獨立性;遇到靜態資源,就放到該使用者id對應的靜態資原始檔夾下,也能保證資料的獨立性

url中,埠指的是伺服器的埠,伺服器預設是80埠對外訪問,客戶端瀏覽器可以是任何埠去訪問伺服器的80埠,而tcp四元組是客戶端ip,埠,伺服器ip 埠;伺服器ip和埠基本固定
所以伺服器可以允許的tcp連線==ip數*客戶端埠數,基本是無限的,但是linux本身的tcp連線是由上限的
"TCP/IP" 必須對外提供程式設計介面,這就是Socket, Socket跟"TCP/IP"並沒有必然的聯絡。Socket程式設計介面在設計的時候,就希望也能適應其他的網路協議
socket有幾個通用的介面才操作tcpip協議的資料 create,listen,accept,connect,read和write等等;socket是在“應用層和傳輸層【tcp】之間的一層,用於資料傳遞
同一個域名一般在一個tab瀏覽器上只允許6個tcp連線,因為客戶端ip相同,所以一般開啟6個埠,建立6個tcp連線去訪問;
////

相關文章