北京規(guī)劃建設(shè) 雜志 官方網(wǎng)站wordpress全站cdn教程
鶴壁市浩天電氣有限公司
2026/01/22 08:27:08
北京規(guī)劃建設(shè) 雜志 官方網(wǎng)站,wordpress全站cdn教程,wordpress免費(fèi)插件,四川網(wǎng)絡(luò)科技有限公司DockerNginxNode.js 全棧容器化部署
通過 Docker Compose 統(tǒng)一編排 Nginx、Node.js 服務(wù)#xff0c;實(shí)現(xiàn)前后端分離部署。前端使用 Unity WebGL 創(chuàng)建交互式界面#xff0c;后端基于 Node.js 提供 REST API 通信。重點(diǎn)講解網(wǎng)絡(luò)配置、反向代理策略、端口映射、跨域處理等實(shí)戰(zhàn)中…DockerNginxNode.js 全棧容器化部署通過 Docker Compose 統(tǒng)一編排 Nginx、Node.js 服務(wù)實(shí)現(xiàn)前后端分離部署。前端使用 Unity WebGL 創(chuàng)建交互式界面后端基于 Node.js 提供 REST API 通信。重點(diǎn)講解網(wǎng)絡(luò)配置、反向代理策略、端口映射、跨域處理等實(shí)戰(zhàn)中遇到的問題及解決方案為應(yīng)用部署提供完整參考。當(dāng)部署到部署到服務(wù)器的時(shí)候不知道服務(wù)的ip地址可以使用nginx代理服務(wù)然后使用請求的時(shí)候使用路徑請求然后ngixn返回服務(wù)器信息??梢栽趎ginx的docker-compose的環(huán)境配置中寫好ip和端口這樣就可以不修改conf文件了。docker配置項(xiàng)目路徑docker_net_work_test/ # 項(xiàng)目根目錄 ├── nginx/ # Nginx 服務(wù)目錄 │ ├── html/ # WebGL 前端文件 │ │ ├── Build/ # Unity WebGL 構(gòu)建輸出 │ │ ├── StreamingAssets/ # Unity 流媒體資源 │ │ └── index.html # 入口 HTML 文件 │ ├── Dockerfile # Nginx 鏡像構(gòu)建文件 │ └── nginx.conf # Nginx 配置文件 ├── node/ # Node.js 后端服務(wù)目錄 │ ├── node_modules/ # Node.js 依賴包 │ ├── .env # 環(huán)境變量配置 │ ├── app.js # Node.js 主應(yīng)用文件 │ ├── Dockerfile # Node.js 鏡像構(gòu)建文件 │ ├── package.json # Node.js 項(xiàng)目配置 │ └── package-lock.json # 依賴鎖文件 ├── docker-compose.nginx.yml # Nginx 服務(wù)編排配置 └── docker-compose.node.yml # Node.js 服務(wù)編排配置項(xiàng)目運(yùn)行先運(yùn)行node服務(wù)docker-compose -f docker-compose.node.yml up -d運(yùn)行nginx服務(wù)docker-compose -f docker-compose.nginx.yml up -d前端頁面http://主機(jī)IP:58231nodejs服務(wù)http://主機(jī)IP:13000/nodejs服務(wù)配置docker-compose.node.ymlservices:node:build:./node# 基于node目錄下的 Dockerfile 構(gòu)建鏡像container_name:node-app# 容器運(yùn)行時(shí)的名字ports:-13000:13000# 宿主機(jī):容器 端口映射networks:-backend# 定義屬于的網(wǎng)絡(luò)名稱 backend 網(wǎng)絡(luò)networks:backend:name:backend-net# 網(wǎng)絡(luò)名稱app.js/******************************************************************** * - HTTP 13000 (/ /api/health) * 依賴npm i morgan cors dotenv *******************************************************************/require(dotenv).config();consthttprequire(http);constexpressrequire(express);constcorsrequire(cors);constmorganrequire(morgan);/* ------------------ 配置 ------------------ */constHTTP_HOSTprocess.env.HTTP_HOST||0.0.0.0;constHTTP_PORTprocess.env.HTTP_PORT||13000;constNODE_ENVprocess.env.NODE_ENV||development;/* ------------------ Express ------------------ */constappexpress();// CORS 配置if([localhost,development,dev].includes(NODE_ENV)){app.use(cors());console.log((本地調(diào)試)/(開發(fā)),使用跨域);}elseif([production,pro].includes(NODE_ENV)){console.log(生產(chǎn)環(huán)境不使用跨域);}else{app.use(cors());console.log(未知環(huán)境使用跨域);}// 日志中間件morgan.token(date,()newDate().toLocaleString(zh-CN));app.use(morgan(:method :url code:status contentLength:res[content-length] - :response-time ms :date));app.use(express.json());/* ------------------ 路由 ------------------ */app.get(/api/config,(req,res){console.log(headers:,req.headers);// 1. 優(yōu)先用代理頭letportreq.get(X-Real-Port)||req.get(X-Forwarded-Port);// 2. 代理頭都沒有 → 從 Referer 提取if(!port){constrefererreq.get(Referer);constmrefererreferer.match(/:(d)//);portm?m[1]:(req.secure?443:80);}constprotoreq.get(X-Forwarded-Proto)||req.protocol;consthostreq.get(X-Forwarded-Host)||req.get(Host);consthostWithPorthost.includes(:)?host:${host}:${port};constbaseUrl${proto}://${hostWithPort};res.json({success:true,baseUrl,host:hostWithPort,proto,environment:NODE_ENV});});// 請求方式測試使用apiapp.get(/api/health,(req,res)res.json({success:true,message:服務(wù)健康,environment:NODE_ENV,timestamp:newDate().toISOString()}));app.get(/,(req,res)res.json({success:true,message:HTTP 服務(wù)正常,environment:NODE_ENV,paths:{http:/,config:/api/config,health:/api/health,post:/post,put:/put,delete:/delete}}));app.post(/post,(req,res)res.json({success:true,method:POST,message:POST 通道正常,timestamp:newDate().toISOString(),body:req.body}));app.put(/put,(req,res)res.json({success:true,method:PUT,message:PUT 通道正常,timestamp:newDate().toISOString(),body:req.body}));app.delete(/delete,(req,res)res.json({success:true,method:DELETE,message:DELETE 通道正常,timestamp:newDate().toISOString()}));app.use((req,res)res.status(404).json({success:false,message:路由不存在,path:req.path}));/* ------------------ 創(chuàng)建 HTTP 服務(wù)器 ------------------ */consthttpServerhttp.createServer(app);/* ------------------ 啟動(dòng) HTTP ------------------ */httpServer.listen(HTTP_PORT,HTTP_HOST,(){console.log( HTTP 服務(wù) http://${HTTP_HOST}:${HTTP_PORT});console.log(? 環(huán)境${NODE_ENV});console.log( 可用路由:);console.log(GET / - 服務(wù)狀態(tài));console.log(GET /api/config - 配置信息);console.log(GET /api/health - 健康檢查);console.log(POST /post - POST 測試);console.log(PUT /put - PUT 測試);console.log(DELETE /delete - DELETE 測試);});DockerfileFROM node:22-alpine WORKDIR /app COPY app.js . RUN npm i express morgan cors dotenv EXPOSE 13000 CMD [node,app.js]使用node:22-alpine鏡像拷貝node下的app.js到app文件夾下并安裝包暴露端口是13000與docker-compose中的端口暴露的一致。并啟動(dòng)服務(wù)。package.json{ name: unity-webgl-server, version: 1.0.0, main: app.js, scripts: { start: node app.js, dev: nodemon app.js }, dependencies: { aedes: ^0.51.3, cors: ^2.8.5, dotenv: ^17.2.3, express: ^4.18.2, morgan: ^1.10.1, websocket-stream: ^5.5.2, ws: ^8.13.0 }, devDependencies: { nodemon: ^3.0.1 } }.envHTTP_HOST0.0.0.0 HTTP_PORT13000 # 應(yīng)用配置localhost/development/production NODE_ENVlocalhostnginx配置配置ngixn并將html文件拷貝的到容器中使用外部的配置文件端口暴露58231對外8080使用docker內(nèi)部的端口網(wǎng)絡(luò)使用和node在同一個(gè)網(wǎng)絡(luò)下。docker-compose.nginx.ymlservices:nginx:build:./nginx# 構(gòu)建鏡像container_name:nginx-webports:-58231:8080# 宿主機(jī) 58231 映射到容器 8080networks:-backend# 跟 node 同一網(wǎng)絡(luò)networks:backend:name:backend-net#復(fù)用網(wǎng)絡(luò)external:true# 不新建網(wǎng)絡(luò)DockerfileFROM nginxinc/nginx-unprivileged:1.29.0-alpine3.22 COPY nginx.conf /etc/nginx/nginx.conf COPY html/ /usr/share/nginx/html/ USER root使用nginxinc/nginx-unprivileged:1.29.0-alpine3.22鏡像可以使用自己的鏡像??截恘ginx.conf拷貝html文件使用root權(quán)限。nginx.confworker_processes 1; events { worker_connections 1024; } http { # 引入MIME類型映射表 include mime.types; default_type application/octet-stream; # 開啟零拷貝發(fā)送文件 sendfile on; keepalive_timeout 65; # 虛擬主機(jī)配置 server { # 監(jiān)聽端口 listen 8080; # 服務(wù)綁定地址允許所有地址訪問 server_name 0.0.0.0; # Docker容器內(nèi)Node服務(wù)反向代理 location /docker_s/ { # 代理轉(zhuǎn)發(fā)地址指向Docker內(nèi)的node-app服務(wù)13000端口 proxy_pass http://node-app:13000/; # 傳遞客戶端真實(shí)訪問端口優(yōu)先取上層Nginx的X-Real-Port無則用當(dāng)前服務(wù)端口 proxy_set_header X-Forwarded-Port $http_x_real_port$server_port; proxy_set_header X-Real-Port $http_x_real_port$server_port; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 直接使用nginx提供API配置信息 location /nginx_s/api/config { set $api_proto http; set $api_port 58231; set $api_env production; set $api_host $host:$api_port; # 設(shè)置響應(yīng)頭為JSON格式 add_header Content-Type application/json; return 200 { success:true, aseUrl:$api_proto://$api_host, host:$api_host, proto:$api_proto, environment:$api_env }; } # 前端靜態(tài)資源入口 location / { # 靜態(tài)資源根目錄 root /usr/share/nginx/html; # 默認(rèn)索引文件 index index.html index.htm; # SPA路由兼容匹配不到文件時(shí)重定向到index.html try_files $uri $uri/ /index.html; } # # 5xx服務(wù)器錯(cuò)誤頁面配置 # error_page 500 502 503 504 /50x.html; # location /50x.html { # root html; # } } }location /docker_s/ {返回將請求反向代理到容器內(nèi)部的服務(wù)器。這里要使用docker的容器名稱 雖然在一個(gè)網(wǎng)絡(luò)里面使用localhost會(huì)訪問nginx的本機(jī)而不是訪問的實(shí)際的服務(wù)。location /nginx_s/api/config {在nginx中直接返回因?yàn)橛袝r(shí)候會(huì)獲取不到服務(wù)的ip地址。unity代碼unity 使用tmp使用unitybesthttpv3 進(jìn)行網(wǎng)絡(luò)通信。besthttpv3 不支持路由請求所以使用unitywebrequest請求獲取服務(wù)地址然后在使用unitybesthttpv3進(jìn)行請求。usingUnityEngine;usingUnityEngine.UI;usingUnityEngine.Networking;usingSystem.Collections;usingBest.HTTP;usingTMPro;namespaceAssets{internalclasshttp_test_unityrequest:MonoBehaviour{[SerializeField]publicTMP_InputFieldurlInputField_unity;[SerializeField]publicTMP_InputFieldurlInputField_besthttp;[SerializeField]publicButtonunityWebRequestButton;[SerializeField]publicButtonbestHttpRequestButton;[SerializeField]publicTMP_TextresponseText;privatevoidStart(){unityWebRequestButton.onClick.AddListener(()StartCoroutine(SendUnityWebRequest()));bestHttpRequestButton.onClick.AddListener(()StartCoroutine(SendBestHTTPRequest()));}privateIEnumeratorSendUnityWebRequest(){responseText.text$UnityWebRequest: 請求中...;using(UnityWebRequestrequestUnityWebRequest.Get(urlInputField_unity.text)){yieldreturnrequest.SendWebRequest();responseText.textrequest.result!UnityWebRequest.Result.Success?$UnityWebRequest錯(cuò)誤:
{request.error}
{request.downloadHandler?.text}:$UnityWebRequest成功:
{request.downloadHandler?.text};}}privateIEnumeratorSendBestHTTPRequest(){responseText.text$BestHTTP: 請求中...;boolrequestCompletedfalse;stringresult;newHTTPRequest(newSystem.Uri(urlInputField_besthttp.text),(req,res){resultresnull?請求失敗響應(yīng)為空:!res.IsSuccess?$BestHTTP錯(cuò)誤:
{res.Message}
{res.DataAsText}:$BestHTTP成功:
{res.DataAsText};requestCompletedtrue;}).Send();while(!requestCompleted)yieldreturnnull;responseText.textresult;}}}測試html代碼!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleGET請求發(fā)送器/title /head body h1GET請求發(fā)送器/h1 div label forurlInput請輸入請求路徑:/label br input typetext idurlInput placeholder/api/ size40 value/api/ /div br button onclicksendGetRequest()發(fā)送GET請求/button brbr div idresponseArea h3響應(yīng)結(jié)果:/h3 pre idresponseText等待請求.../pre /div script function sendGetRequest() { const urlInput document.getElementById(urlInput); const responseText document.getElementById(responseText); const path urlInput.value.trim(); if (!path) { responseText.textContent 錯(cuò)誤: 請輸入有效的路徑; return; } console.log(請求路徑:, path); responseText.textContent 正在請求: ${path}; // 直接獲取響應(yīng)不做任何狀態(tài)判斷 fetch(path) .then(response { console.log(響應(yīng)狀態(tài):, response.status, response.statusText); console.log(響應(yīng)URL:, response.url); // 無論什么狀態(tài)碼都返回響應(yīng)文本 return response.text().then(text { return { status: response.status, statusText: response.statusText, headers: Object.fromEntries([...response.headers.entries()]), url: response.url, body: text }; }); }) .then(result { // 顯示完整的響應(yīng)信息 responseText.textContent 響應(yīng)狀態(tài): ${result.status} ${result.statusText} 響應(yīng)URL: ${result.url} 響應(yīng)頭: ${JSON.stringify(result.headers, null, 2)} 響應(yīng)體: ${result.body}; }) .catch(error { responseText.textContent 請求異常: ${error.message}; }); } document.addEventListener(DOMContentLoaded, function() { document.getElementById(urlInput).value /api/; }); /script /body /html項(xiàng)目測試運(yùn)行項(xiàng)目后訪問http://localhost:58231/然后發(fā)送請求進(jìn)行測試可以轉(zhuǎn)到不同的location塊。轉(zhuǎn)發(fā)docker_s/api/health使用besthttpV3進(jìn)行請求網(wǎng)絡(luò)命令網(wǎng)絡(luò)與容器關(guān)系命令$netbackend-net; write-host 網(wǎng)絡(luò)名稱: $net; docker network inspect $net -f {{range .Containers}}{{.Name}}{{println}}{{end}} | Where-Object { $_ -ne } | ForEach-Object { $cid docker ps -q --no-trunc --filter name$_; if ($cid) { write-host 容器名稱: $_ 容器ID: $($cid.Substring(0,12)) } }可以查看網(wǎng)絡(luò)下有那些容器。輸出PS C:UsersGoodCooking $netbackend-net; write-host 網(wǎng)絡(luò)名稱: $net; docker network inspect $net -f {{ra nge .Containers}}{{.Name}}{{println}}{{end}} | Where-Object { $_ -ne } | ForEach-Object { $cid docker ps -q --no-trunc --filter name$_; if ($cid) { write-host 容器名稱: $_ 容器ID: $($cid.Substring(0,12)) } } 網(wǎng)絡(luò)名稱: backend-net 容器名稱: node-app 容器ID: 53a5b8a00a08 容器名稱: nginx-web 容器ID: e7ad62d8745b PS C:UsersGoodCooking