摘要:的官方描述是是一個獨立于中間件和路由的實例,你可以將看作是只能執(zhí)行執(zhí)行中間件和路由的小心應(yīng)用。最大的不同在于只能已模塊形式存在并不能獨立運行。另外,加密的公鑰也被稱為證書??蛻舳嗽谀玫焦€證書后會向這樣的證書頒發(fā)機構(gòu)進行驗證。
作為 Express 中的最大特點之一,路由讓你可以將不同的請求映射到不同的中間件中。這一章我們將會深入學(xué)習(xí)這部分的內(nèi)容,另外還包括如何在 Express 使用 HTTPS 以及部分 Express 4 中的新特性等等。當(dāng)然,學(xué)習(xí)過程還是通過示例應(yīng)用和代碼的形式進行展現(xiàn)的。
什么是路由?假設(shè),現(xiàn)在你嘗試通過 example.com/someone 訪問某人的推特或者微博主頁,你會發(fā)現(xiàn)該請求的 HTTP 內(nèi)容大致如下:
GET /someone http/1.1
其中包含了 HTTP 請求使用的方法(GET),URI 信息(/someone) 以及 HTTP 協(xié)議版本 (1.1)。Express 中的路由就是負(fù)責(zé)將其中的 HTTP 方法和 URI 這對組合映射到對應(yīng)的中間件。簡單說就是, /about_me 的GET 請求會執(zhí)行某個中間件而對于 /new_user 的 POST 請求則執(zhí)行另一個中間件。
下面我們通過一個簡單示例來看看到底路由時如何工作的。
路由的一個簡單示例下面我們就對 example.com/someone 請求進行一個簡單的實現(xiàn),代碼如下:
var express = require("express"); var app = express(); app.get("/someone", function(request, response) { response.send(" Welcome to someone"s homepage! "); }); app.use(function(request, response) { response.status(404).send("Page not found!"); }); app.listen(3000);
上面代碼中真正有價值的是第三行:當(dāng)你通過 HTTP 的 GET 方法對 /someone 發(fā)起請求時,程序會執(zhí)行該中間件中的代碼,其他請求則會被忽略并跳轉(zhuǎn)到下一個中間件。
路由的特性從工作原理來說:路由就是通過對 HTTP 方法和的 URI 的組合進行映射來實現(xiàn)對不同請求的分別處理。當(dāng)然,除了上面那種最簡單的使用方式之外,Express 的路由還有更多實用的使用技巧和方法。
含參的通配路由注意:在其它一些框架中(例如,Ruby on Rails )會有一個專門的文件進行路由管理,但是 Express 中并沒有這樣的規(guī)定,你可以將路由按模塊分開管理。
在上面的使用方式中使用的是全等判斷來進行路由匹配的。雖然對于 /someone 這類非常管用,但是對于形如 /users/1、/users/2 這類 RESTful 路由就明顯不那么友好了。因為如果將后者路由一一列出的話,不管是從工作量還是后期維護來說都是非常差開發(fā)體驗。針對這種情況,我們可以使用 Express 中含參的通配路由來解決。
該方法的工作原理就是,在路由中使用參數(shù)進行通配表示。而該參數(shù)所表示的具體數(shù)值會在變量 params 中獲取到,下面是簡單的代碼示例:
app.get("/users/:userid", function(req, res) { // 將userId轉(zhuǎn)換為整型 var userId = parseInt(req.params.userid, 10); // ... });
這樣 RESTful 風(fēng)格的動態(tài)路由就完全可以通過這種含參的通配路由進行處理。那么無論是 /users/123 還是 /users/8 都會被映射到同一中間件。需要注意的是:雖然 /users/ 或者 /users/123/posts 不會被匹配,但是 /users/cake 和 /users/horse_ebooks 確會被匹配到。所以,如果實現(xiàn)更精準(zhǔn)的路由匹配的話就需要使用其他方式了。
使用正則表達式匹配路由針對上面的問題,我們可以使用正則來對路由進行更精準(zhǔn)的匹配。
注意:如果你對正則表達式部分的內(nèi)容不熟悉的話,那么我建議你去查看該文檔。
假設(shè)現(xiàn)在我們只需要匹配 /users/123 和 /users/456 這種通配參數(shù)為數(shù)字的動態(tài)路由的同時忽略其他路由格式,那么可以將代碼改為:
app.get(/^/users/(d+)$/, function(req, res) { var userId = parseInt(req.params[0], 10); // ... });
通過正則表達式代碼對通配參數(shù)作為了嚴(yán)格限定:該參數(shù)必須是數(shù)字類型。
正則表達式可能閱讀起來并不是很友好,但是它卻可以實現(xiàn)對復(fù)雜路由匹配規(guī)則的準(zhǔn)確定義。例如,你想匹配路由 /users/100-500 這類表示某個用戶范圍的列表頁面,那么該正則如下:
app.get(/^/users/(d+)-(d+)$/, function(req, res) { var startId = parseInt(req.params[0], 10); var endId = parseInt(req.params[1], 10); // … });
甚至你還可以作出更復(fù)雜的正則匹配路由定義,例如:匹配某個包含特定 UUID 的路由。UUID 是一長串 16 進制的字符串,大致如下:
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
如果,其中的 x 表示任何 16 進制數(shù)字,而 y 只能是 8,9,A 或者 B 。那么該路由的正則匹配就是:
var horribleRegexp = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/i; app.get(horribleRegexp, function(req, res) { var uuid = req.params[0]; // ... });
還有更多的使用示例就不一一列舉了。這里只需記住一點:正則表達式可以讓你的路由匹配定義更上一層樓。
捕獲查詢參數(shù)另一種常用的動態(tài)傳入 URL 參數(shù)的方法就是通過查詢字符串(query string)。例如,當(dāng)你使用谷歌搜索 javascript-themed burrito 時,你可以會發(fā)現(xiàn)對應(yīng)的 URL 可能是 https://www.google.com/search... 。
如果 Google 是用 Express 進行實現(xiàn)的話(實際上不是),那么可以這樣來獲取用戶傳入的信息:
app.get("/search", function(req, res) { // req.query.q == "javasript-themed burrito" // ... });
需要注意的是:查詢參數(shù)中存在其實存在著類型安全問題。例如:如果你訪問 ?arg=something 那么 req.query.arg 就是一個字符串類型,但是如果訪問的是 ?arg=something&arg=somethingelse 的話 req.query.arg 就變?yōu)榱艘粋€數(shù)組類型。簡單來說:不要輕易的斷定查詢參數(shù)的類型。
使用 Router 劃分你的 app伴隨著應(yīng)用的擴張,程序中產(chǎn)生的路由也會越來越多。而對這些龐大的路由進行管理并不是一件輕松的事,不過好在 Express 4 新增了 Router (可以理解為路由器)特性。Router 的官方描述是:
Router 是一個獨立于中間件和路由的實例,你可以將 Router 看作是只能執(zhí)行執(zhí)行中間件和路由的小心應(yīng)用。而 Express 程序本身就內(nèi)置了一個 Router 實例。
Router 的行為與中間件類型,它可以通過 .use() 來調(diào)用其他的 Router 實例。
換句話就是,可以使用 Router 將應(yīng)用劃分為幾個小的模塊。雖然對于一些小型應(yīng)用來說這樣做可能是過度設(shè)計,但是一旦 app.js 中的路由擴張?zhí)斓脑捘憔涂梢钥紤]使用 Router 進行模塊拆分了。
注意:程序越大 Router 發(fā)揮的作用就越明顯。雖然這里我不會編寫一個大型應(yīng)用程序,但是你可以在你的腦海中對下面的示例功能進行無限擴張。
var express = require("express"); var path = require("path"); // 引入 API Router var apiRouter = require("./routes/api_router"); var app = express(); var staticPath = path.resolve(__dirname, "static"); app.use(express.static(staticPath)); // API Router 文件的調(diào)用 app.use("/api", apiRouter); app.listen(3000);
如上所示,Router 的使用方式和之前的中間件非常類似。其實 Router 本質(zhì)上就是中間件。在代碼中我們將所有 /api 開頭的 URL 全部轉(zhuǎn)發(fā)到了 apiRouter 中了, 這意味著 /api/users 和 /api/message 的處理都會在 apiRouter 中進行。
下面就是 api_router.js 文件的一個簡單代碼示例:
var express = require("express"); var ALLOWED_IPS = [ "127.0.0.1", "123.456.7.89" ]; var api = express.Router(); api.use(function(req, res, next) { var userIsAllowed = ALLOWED_IPS.indexOf(req.ip) !== -1; if(!userIsAllowed) { res.status(401).send("Not authorized!"); } else { next(); } }); api.get("/users", function(req, res) { /* ... */ }); api.post("/users", function(req, res) { /* ... */ }); api.get("/messages", function(req, res) { /* ... */ }); api.post("/messages", function(req, res) { /* ... */ }); module.exports = api;
其實 Router 與 app.js 在功能上沒有任何區(qū)別,都是處理中間件和路由。最大的不同在于:Router 只能已模塊形式存在并不能獨立運行。
參照示例,你可以在自己的應(yīng)用中按模塊劃分出更多的 Router 。
靜態(tài)文件除非應(yīng)用是純 API 服務(wù),否則總可能需要發(fā)送靜態(tài)文件。這些文件可能是靜態(tài)圖片 CSS 樣式文件或者是靜態(tài) HTML 文件。在前面文章的基礎(chǔ)之上,這部分將介紹更深入的部分內(nèi)容。
靜態(tài)文件中間件因為前面章節(jié)對靜態(tài)文件中間件實現(xiàn)進行過詳細(xì)介紹,所以這里直接查看代碼:
var express = require("express"); var path = require("path"); var http = require("http"); var app = express(): // 設(shè)置你的靜態(tài)文件路徑 var publicPath = pathresolve(dirname, "public"); // 從靜態(tài)文件夾中發(fā)送靜態(tài)文件 app.use(express.static(publicPath)); app.use(function(request, response) { response.writeHead(200, { "Content-Type": "text/plain"}); reponse.end("Looks like you didn"t find a static file."); }); http.createServer(app).listen(3000);修改靜態(tài)文件的 URL
通常情況下,我們會把站點的靜態(tài)文件 URL 路徑直接掛在域名后面,例如:http://jokes.edu 站點中的 jokes.txt 文件 URL 樣式應(yīng)該是 http://jokes.edu/jokes.txt 。
當(dāng)然,你可可以按照自己的習(xí)慣給這些靜態(tài)文件提供 URL 。例如,將一些無序但有趣的圖片存放在文件夾 offensive 中并將其中圖片的 URL 設(shè)置為 http://jokes.edu/offensive/ph... 這種形式。那么該樣式 URL 如何實現(xiàn)呢?
在 Express 中,我們可以使用指定前綴的中間件來對靜態(tài)文件 URL 進行自定義。所以上面問題的代碼實現(xiàn)如下:
// ... var photoPath = path.resolve(__dirname, "offensive-photos-folder"); app.use("/offensive", express.static(photoPath)); // ...
這樣你所有靜態(tài)文件的 URL 都可以實現(xiàn)自定義了,而不是粗暴的直接掛在域名后面了。其實除了靜態(tài)中間件和前面 Router 外,其它中間件同樣可以指定 URL 前綴。
多個靜態(tài)文件夾的路由實際上砸真實項目中可能戶存在多個靜態(tài)文件夾,例如:一個存放 CSS 等公用文件的 public 文件夾,一個存放用戶上傳文件的 user_uploads 文件夾。那么對于這種情況又該如何處理呢?
首先 epxress.static 本身作為中間件是可以在代碼中多次調(diào)用的:
// ... var publiscPath = path.resolve(__dirname, "public"); var userUploadPath = path.resove(__dirname, "user_uploads"); app.use(express.static(publicPath)); app.use(express.static(userUploadsPath)); // ...
接下來,我們通過四個模擬場景看看上面代碼是如何工作的:
用戶請求的資源兩個文件夾里都沒有則上面兩個中間件都會被跳過執(zhí)行。
用戶請求的資源只在 public 里面則第一個中間件響應(yīng)執(zhí)行并返回。
用戶請求的資源只在 user_uploads 里面則第一個中間件被跳過而第二個得道執(zhí)行。
用戶請求的資源在兩個文件夾中都存在則第一個中間件響應(yīng)執(zhí)行并返回,第二個不會得到執(zhí)行。
對于第四章情況,如果該資源是相同的還好說,但是一旦只是資源同名就存在明顯錯誤了。為此,我們依舊可以使用 URL 前綴來應(yīng)對:
// ... app.use("/public", express.static(publicPath)); app.use("/uploads", express.static(userUploadsPath)); // ...
這樣對于同名文件 image.jpg Express 會將其分別映射到 /public/image.jpg 和 /uploads/image.jpg 。
路由到靜態(tài)文件映射在程序中有可能還存在對動態(tài)路由請求響應(yīng)靜態(tài)文件情形,例如,當(dāng)用戶訪問 /users/123/profile_photo 路徑時程序需要發(fā)送該用戶的圖片。靜態(tài)中間件本身時無法處理該需求,不過好在 Express 可以使用與靜態(tài)中間件類似的機制來處理這種情況。
假設(shè)當(dāng)有人發(fā)起 /users/:userid/profile_photo 請求時,我們都需要響應(yīng)對應(yīng) userid 用戶的圖片。另外,假設(shè)程序中存在一個名為 getProfilePhotoPath 的函數(shù),該函數(shù)可以根據(jù) userid 獲取圖片的存儲路徑。那么該功能的實現(xiàn)代碼如下:
app.get("/users/:userid/profile_photo", function(req, res) { res.sendFile(getProfilePhotoPath(req.params.userid)); });
僅僅只需指定路由然后通過 sendFile 函數(shù),我們就可以完成該路由對應(yīng)文件的發(fā)送任務(wù)。
在 Express 使用 HTTPSHTTPS 是在 HTTP 基礎(chǔ)上添加了一個安全層,通常情況下該安全層被稱為 TLS 或者 SSL 。雖然兩個名字可以互換,但是 TSL 在技術(shù)上涵蓋了 SSL。
這里并不會介紹 HTTPS 復(fù)雜的 RSA 加密數(shù)學(xué)原理(歐拉函數(shù))。簡單來說 HTTPS 的加密過程就是:所有的客戶端都使用服務(wù)端公開的公鑰加密請求信息,然后服務(wù)端使用私鑰對加密后內(nèi)容進行解密。這樣就能在某種程度上防止信息被竊聽。另外,加密的公鑰也被稱為證書??蛻舳嗽谀玫焦€證書后會向 Google 這樣的證書頒發(fā)機構(gòu)進行驗證。
注意:類似 Heroku 這樣的虛擬主機商已經(jīng)提供了 HTPPS 服務(wù),所以這部分內(nèi)容只在你需要自己實現(xiàn) HTTPS 時才派得上用場。
首先,我們通過 OpenSSL 生成自簽名的公鑰和私鑰。Windows 系統(tǒng)可以使用去官網(wǎng)獲取 OpenSSL 安裝文件,Linux 可以使用保管理器進行安裝,而 macOS 系統(tǒng)已經(jīng)預(yù)裝過了。通過 openssl version 驗證系統(tǒng)是否成功安裝了 OpenSSL, 確保安裝后輸入下面兩個命令:
openssl genrsa -out privatekey.pem 1024
openssl req -new -key privatekey.pem -out request.pem
第一個命令會生成名為 privatekey.pem 的私鑰。第二個命令會讓你輸入一些信息,然后使用 privatekey.pem 生成簽名的證書請求文件 request.pem 。然后你就可以去證書請求機構(gòu)申請一個加密的公鑰證書。雖然大部分證書都是收費的,但是你還是可以去 letsencrypt 申請免費版本證書。
一旦獲取了 SSL 證書文件,你就可以使用 Node 內(nèi)置的 HTTPS 模塊了,代碼如下:
var express = require("express"); var https = require("https"); var fs = require("fs"); var app = express(); // ... 定義你的app ... // 定義一個對象來保存證書和私鑰 var httpsOptions = { key: fs.fs.readFileSync("path/to/private/key.pem"); cert: fs.fs.readFileSync("path/to/certificate.pem"); } https.createServer(httpsOptions, app).listen(3000);
除了配置私鑰和公鑰證書參數(shù)之外,其他部分與之前 HTTP 模塊的使用時一致的。當(dāng)然,如果你想同時支持 HTTP 和 HTTPS 協(xié)議的話也是可以的:
var express = require("express"); var http = require("http"); var https = require("https"); var fs = require("fs"); var app = express(); // ... 定義你的app ... var httpsOptions = { key: fs.readFileSync("path/to/private/key.pem"), cret: fs.readFileSync("path/to/certificate.pem") }; http.createServer(app).listen(80); https.createServer(httpsOptions, app).listen(443);
需要注意的是 HTTP 和 HTTPS 協(xié)議 同時開啟時需要使用不同的端口號。
路由的應(yīng)用示例接下來,我們搭建一個簡單的 web 程序鞏固一下這章所學(xué)的路由內(nèi)容。該應(yīng)用的主要功能是通過美國的 ZIP 郵政編碼返回該地區(qū)的溫度。
示例使用的是美式郵政編碼,所以該示例只能在作者所在的美國正常使用。當(dāng)然,你完全可以使用 H5 的 Geolocation API 對其進行改造。
示例主要包含兩個部分:
一個靜態(tài)頁,用于詢問用戶的 ZPI 編碼。用戶輸入編碼后會通過 AJAX 發(fā)送異步請求獲取天氣。
解析獲得 JSON 格式數(shù)據(jù),并將結(jié)果映射 ZIP 編碼對應(yīng)的動態(tài)路由上。
準(zhǔn)備工作在示例中需要使用的 Node 類庫有:Express、ForecastIO (用于獲取天氣數(shù)據(jù))、Zippity-do-dah ( 將ZIP編碼轉(zhuǎn)為緯度/經(jīng)度 )、EJS 模版引擎。
新建應(yīng)用文件夾,并復(fù)制下面內(nèi)容到 package.json 文件中:
{ "name": "temperature-by-zip", "private": true, "scripts": { "start": "node app.js" }, "dependencies": { "ejs": "^2.3.1", "express": "^5.0.0", "forecastio": "^0.2.0", "zippity-do-dah": "0.0.x" } }
使用 npm install 命令完成依賴項的安裝,并新建兩個文件夾:public 和 views。另外,示例程序還會用到 jQuery 和名為 Pure 的 CSS 框架。最后,你需要去 Forecast.io 官網(wǎng) 注冊開發(fā)賬號獲取 API 接口密鑰。
主入口代碼準(zhǔn)備工作完成后,接下來就是編寫代碼了。這里我們從程序的主入口開始編寫 JavaScript 代碼,新建 app.js 文件并拷貝代碼:
var path = require("path"); var express = require("express"); var zipdb = require("zippity-do-dah"); var ForecastIo = require("forecastio"); var app = express(); var weather = new ForecastIo("你的FORECAST.IO的API密鑰"); app.use(express.static(path.resolve(__dirname, "public"))); app.set("views", path.resolve(__dirname, "views")); app.set("view engine", "ejs"); app.get("/", function(req, res) { res.render("index"); }); app.get(/^/(d{5})$/, function(req, res, next) { var zipcode = req.params[0]; var location = zipdb.zipcode(zipcode); if (!location.zipcode) { next(); return; } var latitude = location.latitude; var longitude = location.longitude; weather.forecast(latitude, longitude, function(err, data) { if (err) { next(); return; } res.json({ zipcode: zipcode, temperature: data.currently.temperature }); }); }); app.use(function(req, res) { res.status(404).render("404"); }); app.listen(3000);
接下來就是使用 EJS 引擎編寫視圖文件了。
兩個視圖示例應(yīng)用中會有兩個視圖:404 頁面和主頁。為了盡可能保持頁面風(fēng)格的統(tǒng)一,這里將會使用到模版技術(shù)。首先動手實現(xiàn)通用的 header 和 footer 模版。
其中 views/header.ejs 文件中的代碼如下:
Temperature by ZIP code
緊接著就是 views/footer.ejs :
完成上面通用模版之后,下面就可以實現(xiàn) 404 頁面 views/404.ejs 了:
<% include header %>404 error! File not found.
<% include footer %>
同樣的,主頁 views/index.ejs 代碼如下:
<% include header %><% include footer %>What"s your ZIP code?
上面頁面代碼中使用了一些 Pure 框架里的樣式來優(yōu)化界面 UI 。
除此之外,我們還需要在 public/main.css 指定頁面布局:
html { display: table; width: 100%; height: 100%; } body { display: table-cell; vertical-align: middle; text-align: center; }
在該樣式文件中,我們將頁面內(nèi)容同時設(shè)置為了水平和垂直居中。
最后拷貝下面的代碼,把缺失的 public/main.js 補充完整。
$(function() { var $h1 = $("h1"); var $zip = $("input[name="zip"]"); $("form").on("submit", function(event) { // 禁止表單的默認(rèn)提交 event.preventDefault(); var zipCode = $.trim($zip.val()); $h1.text("Loading..."); var request = $.ajax({ url: "/" + zipCode, dataType: "json" }); request.done(function(data) { var temperature = data.temperature; $h1.html("It is " + temperature + "° in " + zipCode + "."); }); request.fail(function() { $h1.text("Error!"); }); }); });運行示例程序
結(jié)束所有編碼任務(wù)后,下面我們通過 npm start 運行示例程序。當(dāng)你訪問 http://localhost:3000 并輸入 ZIP 編碼后界面如下:
在這個簡單的示例中,我們使用了 Express 中的路由特性,另外還使用了 EJS 模版引擎來編寫視圖文件。你可以在此基礎(chǔ)上繼續(xù)發(fā)揮想象力完善該示例。
總結(jié)在本章中,我們學(xué)到了:
從概念上知道了什么是路由:進行 URL 和代碼的映射的工具。
簡單的路由以及常用映射處理。
獲取路由中的參數(shù)。
Express 4 路由模塊的新特性。
將路由應(yīng)用到中間件處理。
如何在 Express 中使用 HTTPS。
原文地址
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84850.html
摘要:前言要做一個全沾的工程師,對于后端和數(shù)據(jù)庫來說,即使不認(rèn)識也要見個面的?;玖私獾母拍罹秃?,主要是安裝上數(shù)據(jù)庫,并進行簡單的增刪操作。 前言:要做一個全沾的工程師,對于后端和數(shù)據(jù)庫來說,即使不認(rèn)識也要見個面的。本文給的例子很簡單,也貼出來源碼,只要一步步下來,就可以跑起來啦~~~ 思考一個需求:做一個登錄頁面,自己搭建服務(wù)和數(shù)據(jù)庫,將用戶輸入的登錄信息保存到數(shù)據(jù)庫如何完成呢:首先選擇...
摘要:文件配置默認(rèn)共用中創(chuàng)建標(biāo)簽不對標(biāo)簽中內(nèi)容做轉(zhuǎn)義處理拓展配置添加配置頭部標(biāo)簽管理通過實現(xiàn)頭部標(biāo)簽管理,在中的配置。 一、為什么選擇Nuxt.js 多數(shù)是基于webpack構(gòu)建的項目,編譯出來的html文件,資源文件都被打包到j(luò)s中,以下圖404頁面代碼為例。從代碼中可以看出,這樣的頁面是不利于 搜索引擎優(yōu)化(SEO, Search Engine Optimization) ,并且 內(nèi)容到...
摘要:多一個技能多一條出路,祝你在自學(xué)道路上越走越好,掌握自己的核心技能,不只是優(yōu)秀,還要成為不可替代的人 NodeJs+Express+Mysql + Vuejs 項目實戰(zhàn) 最近準(zhǔn)備寫一系列文章,全面講述如何基于NodeJs + Express + Mysql + Vuejs 從零開發(fā)前后端完全分離項目; 文筆及技術(shù)可能在某些方面欠佳,請您指正,共同學(xué)習(xí)進步 前端:Vuejs全家桶 后端:...
摘要:上面代碼的關(guān)鍵是模塊的方法,表示生成一個服務(wù)器實例。該方法接受一個回調(diào)函數(shù),該回調(diào)函數(shù)的參數(shù),分別為代表請求和回應(yīng)的對象和對象。循環(huán)請求過來時放入數(shù)組的對象,當(dāng)請求方法和路徑與對象中的一致時,執(zhí)行回調(diào)方法。 目錄 概述 hello-world 實例 運行原理 多路由多回調(diào)以及中間件 概述 Express是一個基于 Node.js 平臺,快速、開放、極簡的 web 開發(fā)框架。主要有 ...
閱讀 1637·2021-10-14 09:43
閱讀 5566·2021-09-07 10:21
閱讀 1290·2019-08-30 15:56
閱讀 2135·2019-08-30 15:53
閱讀 1244·2019-08-30 15:44
閱讀 2021·2019-08-30 15:44
閱讀 1332·2019-08-29 17:24
閱讀 762·2019-08-29 15:19