欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Node.js?搭建后端服務(wù)器內(nèi)置模塊(?http+url+querystring?的使用)

 更新時(shí)間:2022年09月08日 16:20:33   作者:海底燒烤店ai  
這篇文章主要介紹了Node.js搭建后端服務(wù)器內(nèi)置模塊(http+url+querystring的使用),文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容戒殺,具有一定的參考價(jià)值,需要的朋友可以參考一下

 前言

這一節(jié)我們?nèi)W(xué)習(xí)NodeJs的內(nèi)置模塊:httpurl、querystring ,并使用它們來(lái)搭建我們的node后端服務(wù)器,正式邁入后端開(kāi)發(fā)!

一、創(chuàng)建服務(wù)器

httpnode的內(nèi)置模塊,我們可以直接引入它進(jìn)行使用,http這個(gè)模塊內(nèi)有一個(gè)createServer方法,這個(gè)方法可以幫助我們快速創(chuàng)建一個(gè)服務(wù)器:

// 引入http模塊
const http = require("http");

// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
  // req:接受瀏覽器傳的參數(shù)
  // res:返回渲染的內(nèi)容
}).listen(3000, () => { // 服務(wù)器端口號(hào)為3000
    console.log("服務(wù)器啟動(dòng)啦!");
});

另一種寫(xiě)法(推薦寫(xiě)法):

const http = require("http");

// 創(chuàng)建服務(wù)器
const server = http.createServer();

server.on("request", (req, res) => {
    // req:接受瀏覽器傳的參數(shù)
    // res:返回渲染的內(nèi)容
});

server.listen(3000,() => { // 服務(wù)器端口號(hào)為3000
    console.log("服務(wù)器啟動(dòng)啦!");
});

createServer方法返回一個(gè)服務(wù)器對(duì)象,對(duì)象內(nèi)有一個(gè)listen方法,可以設(shè)置服務(wù)器啟動(dòng)的端口號(hào)和啟動(dòng)成功的回調(diào)內(nèi)容

通過(guò)nodemon運(yùn)行這個(gè)文件(nodemon可以在我們修改文件后幫助我們自動(dòng)重啟服務(wù)器),控制臺(tái)打印出了我們?cè)?code>listen中設(shè)置的回調(diào)內(nèi)容,說(shuō)明服務(wù)器啟動(dòng)成功了

我們直接在瀏覽器訪問(wèn)http://localhost:3000/時(shí)會(huì)發(fā)現(xiàn)瀏覽器一直在轉(zhuǎn)圈圈加載

注意: 直接在瀏覽器地址欄里訪問(wèn)服務(wù)器接口,相當(dāng)于是使用get請(qǐng)求訪問(wèn)服務(wù)器接口(并且沒(méi)有跨域限制)

這是因?yàn)槲覀儾](méi)有在我們創(chuàng)建的node服務(wù)器中返回任何內(nèi)容

二、返回響應(yīng)數(shù)據(jù)

我們可以通過(guò)我們定義的resresponse對(duì)象)參數(shù)向客戶端發(fā)送響應(yīng)內(nèi)容:

const http = require("http");

// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
    // req:接受瀏覽器傳的參數(shù)
    // res:返回渲染的內(nèi)容

    // 傳遞數(shù)據(jù)
    res.write("hello world");
    res.write("Ailjx");
    res.end();
}).listen(3000, () => {
    console.log("服務(wù)器啟動(dòng)啦!");
});

write方法傳遞內(nèi)容,可以調(diào)用多次write傳遞多條內(nèi)容,內(nèi)容必須是字符串格式最后必須調(diào)用end方法告訴請(qǐng)求的調(diào)用者我們響應(yīng)結(jié)束了也可以直接在end方法內(nèi)傳遞內(nèi)容,效果與使用write方法相同,但end方法只能調(diào)用一次

運(yùn)行上述代碼,我們直接在瀏覽器調(diào)用服務(wù)器接口:

如果服務(wù)器中不調(diào)用end方法,瀏覽器會(huì)收不到服務(wù)器傳遞的響應(yīng)結(jié)束的信號(hào),便會(huì)一直加載請(qǐng)求:

返回復(fù)雜對(duì)象數(shù)據(jù)

可以傳遞復(fù)雜對(duì)象數(shù)據(jù),但必須是字符串(JSON)的格式:

const http = require("http");

// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
    // end方法也可以傳遞內(nèi)容,效果與write相同
    res.end("{name:{me:'Ailjx'}}");
    // 或者res.end(JSON.stringify({ name: { me: "Ailjx" } }));
}).listen(3000, () => {
    console.log("服務(wù)器啟動(dòng)啦!");
});

上面瀏覽器顯示的數(shù)據(jù)被格式化了,是因?yàn)槲以跒g覽器中安裝了FeHelper(前端助手)插件,能夠自動(dòng)格式化JSON數(shù)據(jù)

返回html文檔數(shù)據(jù)

const http = require("http");

// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
    // 傳遞html內(nèi)容
    res.end(`
            <h1>我是Ailjx,你好!</h1>
    `);
}).listen(3000, () => {
    console.log("服務(wù)器啟動(dòng)啦!");
});

這時(shí)發(fā)現(xiàn)我們傳遞的中文亂碼了,我們可以在響應(yīng)頭的Content-Type中指定utf-8的字符集來(lái)解析中文,下面會(huì)講到

三、設(shè)置響應(yīng)頭和狀態(tài)碼

我們可以使用response對(duì)象的writeHead方法來(lái)同時(shí)設(shè)置狀態(tài)碼和響應(yīng)頭信息:

const http = require("http");

// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
    // 設(shè)置相應(yīng)頭,第一參數(shù)為狀態(tài)碼,第二個(gè)參數(shù)為響應(yīng)頭配置,第二個(gè)參數(shù)可不填
    res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
    // 傳遞html內(nèi)容
    res.end(`
            <h1>我是Ailjx,你好!</h1>
    `); // 直接在end中傳遞,效果與write方法相同
}).listen(3000, () => {
    console.log("服務(wù)器啟動(dòng)啦!");
});

我們也可以使用setHeader單獨(dú)設(shè)置響應(yīng)頭信息,statusCode單獨(dú)設(shè)置狀態(tài)碼:

const http = require("http");

// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
    // 設(shè)置相應(yīng)頭信息
    res.setHeader("Content-Type", "text/html;charset=utf-8");
    // 設(shè)置狀態(tài)碼
    res.statusCode = 200;
    // 傳遞html內(nèi)容
    res.end(`
            <h1>我是Ailjx,你好!</h1>
    `); // 直接在end中傳遞,效果與write方法相同
}).listen(3000, () => {
    console.log("服務(wù)器啟動(dòng)啦!");
});

四、實(shí)現(xiàn)路由接口

上面我們已經(jīng)成功創(chuàng)建了一個(gè)服務(wù)器,并能夠使用res參數(shù)中的writeend方法向調(diào)用者發(fā)送內(nèi)容,但發(fā)送的這些內(nèi)容是不會(huì)隨著我們請(qǐng)求的路徑而變化的:

const http = require("http");

// 創(chuàng)建服務(wù)器
const server = http.createServer();

server.on("request", (req, res) => {
    res.end("Ailjx");
});

server.listen(3000);

可以看到,我們請(qǐng)求(訪問(wèn))不同路徑,服務(wù)器返回的數(shù)據(jù)都是一樣的,而我們?cè)趯?shí)際開(kāi)發(fā)中往往是需要不同的路徑有不同的數(shù)據(jù)

這時(shí)我們就可以利用我們創(chuàng)建服務(wù)器時(shí)定義的req參數(shù)(request對(duì)象)來(lái)獲取用戶請(qǐng)求的路徑從而判斷需要返回哪些數(shù)據(jù):

const http = require("http");

// 創(chuàng)建服務(wù)器
const server = http.createServer();

server.on("request", (req, res) => {
    // req.url拿到用戶請(qǐng)求的路徑
    console.log(req.url);
    res.end();
});
server.listen(3000);

運(yùn)行代碼,之后在瀏覽器訪問(wèn)調(diào)用一下http://localhost:3000/list,控制臺(tái)會(huì)打印出:

可以看到我們?cè)L問(wèn)的/list路徑確實(shí)被打印出來(lái)了,但怎么還打印了一個(gè)/favicon.ico呢?

這其實(shí)是瀏覽器在訪問(wèn)一個(gè)域名時(shí)會(huì)自動(dòng)訪問(wèn)該域名下的/favicon.ico靜態(tài)文件,來(lái)作為網(wǎng)頁(yè)標(biāo)簽欄的小圖標(biāo),所以我們的服務(wù)器才會(huì)打印出/favicon.ico

如果是普通的ajax調(diào)用接口是不會(huì)出現(xiàn)這種情況的,這里我們是為了方便,直接使用瀏覽器訪問(wèn)接口來(lái)進(jìn)行演示,所以才出現(xiàn)這種請(qǐng)求,我們可以簡(jiǎn)單做一下處理:

const http = require("http");
// 創(chuàng)建服務(wù)器
const server = http.createServer();
server.on("request", (req, res) => {
    // req.url獲取用戶請(qǐng)求的路徑
    if (req.url === "/favicon.ico") {
    	// 讀取本地圖標(biāo)
        return;
    }
    console.log(req.url);
    res.end("Ailjx");
});
server.listen(3000);

這樣當(dāng)服務(wù)器收到/favicon.ico請(qǐng)求時(shí)就能直接跳過(guò)不對(duì)其進(jìn)行處理

創(chuàng)建簡(jiǎn)易路由應(yīng)用

現(xiàn)在,我們開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的路由應(yīng)用,我們先創(chuàng)建兩個(gè)模塊:

renderContent.js用來(lái)根據(jù)用戶請(qǐng)求路徑來(lái)返回對(duì)應(yīng)的內(nèi)容:

function renderContent(url) {
    switch (url) {
        case "/api/home":
            return `
                {
                    page:'首頁(yè)'
                }
            `;
        case "/api/about":
            return `
                {
                    page:'關(guān)于頁(yè)'
                }
                `;

        default:
            return "404";
    }
}
exports.renderContent = renderContent;

renderStatus.js用來(lái)根據(jù)用戶請(qǐng)求的路徑來(lái)返回對(duì)應(yīng)的響應(yīng)狀態(tài)碼:

function renderStatus(url) {
    const arr = ["/api/home", "/api/about"];
    return arr.includes(url) ? 200 : 404;
}

module.exports = {
    renderStatus,
};

之后在我們的服務(wù)器文件server.js中調(diào)用這兩個(gè)模塊:

const http = require("http");
const { renderContent } = require("./module/renderContent");
const { renderStatus } = require("./module/renderStatus");

// 創(chuàng)建服務(wù)器
const server = http.createServer();

server.on("request", (req, res) => {
    // req.url獲取用戶請(qǐng)求的路徑
    if (req.url === "/favicon.ico") {
        return;
    }
	// 響應(yīng)頭
    res.writeHead(renderStatus(req.url), {
    	 // 標(biāo)志返回JSON數(shù)據(jù) 
        "Content-Type": "application/json",
    });
	// 返回的內(nèi)容
    res.end(renderContent(req.url));
});

server.listen(3000);

之后啟動(dòng)服務(wù)器,在瀏覽器調(diào)用接口查看效果:

五、處理URL

在上面我們通過(guò)判斷req.url來(lái)實(shí)現(xiàn)了簡(jiǎn)易的路由接口應(yīng)用,但當(dāng)用戶調(diào)用帶有url參數(shù)的接口時(shí),這就會(huì)出現(xiàn)問(wèn)題:

這是因?yàn)檫@時(shí)req.url/api/about?name=ailj而并不是/api/about,我們可以手動(dòng)的對(duì)這個(gè)字符串進(jìn)行處理來(lái)獲得正確的路由路徑,也可以使用node.js的內(nèi)置模塊url來(lái)處理

URL格式轉(zhuǎn)換

修改上面的server.js文件:

const http = require("http");
const url = require("url");
const { renderContent } = require("./module/renderContent");
const { renderStatus } = require("./module/renderStatus");
// 創(chuàng)建服務(wù)器
const server = http.createServer();
server.on("request", (req, res) => {
    // req.url獲取用戶請(qǐng)求的路徑
    if (req.url === "/favicon.ico") {
        return;
    }
    // 新版使用全局的URL構(gòu)造函數(shù)
    // 傳兩個(gè)參數(shù)用法
    const myUrl = new URL(req.url, "http://127.0.0.1:3000").pathname;
    // 傳一個(gè)參數(shù)用法
    // const myUrl = new URL("http://127.0.0.1:3000" + req.url).pathname;
    console.log(new URL(req.url, "http://127.0.0.1:3000"));
    res.writeHead(renderStatus(myUrl), {
        "Content-Type": "application/json",
    });

    res.end(renderContent(myUrl));
});

server.listen(3000, () => {
    // 服務(wù)器端口號(hào)為3000
    console.log("服務(wù)器啟動(dòng)啦!");
});

全局的構(gòu)造函數(shù)UR可以將完整的 url地址轉(zhuǎn)換成url對(duì)象(WHATWG URL標(biāo)準(zhǔn)的對(duì)象)

我們可以對(duì)其傳遞兩個(gè)參數(shù),第一個(gè)是用戶請(qǐng)求的路徑(路由),第二個(gè)參數(shù)是地址的根域名(我們這里是本地啟動(dòng)的服務(wù)器,根域名為http://127.0.0.1:3000

也可以直接傳遞一個(gè)參數(shù),該參數(shù)是帶有域名的完整url地址

當(dāng)我們?cè)L問(wèn)http://localhost:3000/api/about?name=ailjx時(shí)server.js會(huì)打印出:

URL {
  href: 'http://127.0.0.1:3000/api/about?name=ailjx',
  origin: 'http://127.0.0.1:3000',
  protocol: 'http:',
  username: '',
  password: '',
  host: '127.0.0.1:3000',
  hostname: '127.0.0.1',
  port: '3000',
  pathname: '/api/about',
  search: '?name=ailjx',
  searchParams: URLSearchParams { 'name' => 'ailjx' },
  hash: ''
}

上面Url對(duì)象里 searchParamsurl的參數(shù)部分,是一個(gè)迭代器對(duì)象URLSearchParams對(duì)象

 // searchParams對(duì)象是一個(gè)迭代器對(duì)象
 const query = new URL(req.url, "http://127.0.0.1:3000").searchParams;
 // 使用get方法獲取指定的值
 console.log(query.get("name")); // ailjx

我們還可以從組成部分構(gòu)造 URL獲取構(gòu)造的字符串

const myURL = new URL("https://www.baidu.com");
myURL.port = "443";
myURL.pathname = "/ad/index.html";
myURL.search = "?id=8&name=mouse";
myURL.hash = "#tag=110";
// 獲取構(gòu)造的 URL 字符串,請(qǐng)使用href屬性訪問(wèn)器
console.log(myURL.href); // https://www.baidu.com/ad/index.html?id=8&name=mouse#tag=110

或者:

const pathname = '/a/b/c';
const search = '?d=e';
const hash = '#fgh';
const myURL = new URL(`https://example.org${pathname}${search}${hash}`);
console.log(myURL.href);

使用url.format方法可以自定義序列化url字符串,format方法接收兩個(gè)參數(shù):

  • new URL返回的一個(gè)WHATWG URL格式的對(duì)象
  • 配置對(duì)象:
    • fragment:序列化的網(wǎng)址字符串是否包含片段,默認(rèn)為true
    • auth:序列化的網(wǎng)址字符串是否包含用戶名和密碼,默認(rèn)為true
    • unicode:是否將出現(xiàn)在 URL 字符串的主機(jī)組件中的 Unicode 字符直接編碼而不是Punycode 編碼,默認(rèn)是false
    • search:序列化的網(wǎng)址字符串是否包含搜索查詢(參數(shù)),默認(rèn)為true
const myURL = new URL(
    "https://username:password@URL路徑序列化測(cè)試?name=ailjx#foo"
);

console.log(
    url.format(myURL, {
        fragment: false, // 不顯示片段(#foo)
        unicode: true, // 不轉(zhuǎn)化Unicode字符(中文字符)
        auth: false, // 不包含用戶名和密碼(username:password)
        search: false, // 不顯示參數(shù)(?name=ailjx)
    })
);
// 打印結(jié)果: 'https://url路徑序列化測(cè)試/'

舊版Node使用parse和format處理URL:

注意:舊版的parse方法官方表示已棄用,format方法在新版中使用方式有所更改

const http = require("http");
const url = require("url");
const { renderContent } = require("./module/renderContent");
const { renderStatus } = require("./module/renderStatus");
// 創(chuàng)建服務(wù)器
const server = http.createServer();
server.on("request", (req, res) => {
    // req.url獲取用戶請(qǐng)求的路徑
    if (req.url === "/favicon.ico") {
        return;
    }
    console.log(url.parse(req.url));
    
    const myUrl = url.parse(req.url).pathname;
    res.writeHead(renderStatus(myUrl), {
        "Content-Type": "application/json",
    });

    res.end(renderContent(myUrl));
});
server.listen(3000, () => {
    // 服務(wù)器端口號(hào)為3000
    console.log("服務(wù)器啟動(dòng)啦!");
});

url模塊的parse方法可以將完整的url地址轉(zhuǎn)換成url對(duì)象,如當(dāng)我們?cè)L問(wèn)http://localhost:3000/api/about?name=ailjx時(shí)server.js會(huì)打印出:

Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?name=ailjx',
  query: 'name=ailjx',
  pathname: '/api/about',
  path: '/api/about?name=ailjx',
  href: '/api/about?name=ailjx'
}

上面Url對(duì)象里 queryurl的參數(shù)部分,默認(rèn)是字符串的格式,可以給parse方法傳遞第二個(gè)參數(shù),將其轉(zhuǎn)換成對(duì)象格式:

url.parse(req.url,true)
Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?name=ailjx',
  query: [Object: null prototype] { name: 'ailjx' },
  pathname: '/api/about',
  path: '/api/about?name=ailjx',
  href: '/api/about?name=ailjx'
}

這時(shí)通過(guò)url.parse(req.url, true).query.name就可以拿到ailjx這個(gè)參數(shù)

parse方法相反的有一個(gè)format方法,它能將一個(gè) url對(duì)象轉(zhuǎn)換成url地址 :

const url = require("url");

const urlObject = {
    protocol: "https:",
    slashes: true,
    auth: null,
    host: "www.baidu.com:443",
    port: "443",
    hostname: "www.baidu.com",
    hash: "#tag=110",
    search: "?id=8&name=mouse",
    query: { id: "8", name: "mouse" },
    pathname: "/ad/index.html",
    path: "/ad/index.html?id=8&name=mouse",
};

const parsedObj = url.format(urlObject);

console.log(parsedObj); // https://www.baidu.com:443/ad/index.html?id=8&name=mouse#tag=110

URL路徑拼接

URL構(gòu)造函數(shù),傳遞兩個(gè)字符串路徑時(shí)能夠?qū)崿F(xiàn)路徑的拼接:

let myURL = new URL('http://Example.com/', 'https://example.org/');
// http://example.com/

myURL = new URL('https://Example.com/', 'https://example.org/');
// https://example.com/

myURL = new URL('foo://Example.com/', 'https://example.org/');
// foo://Example.com/

myURL = new URL('http:Example.com/', 'https://example.org/');
// http://example.com/

myURL = new URL('https:Example.com/', 'https://example.org/');
// https://example.org/Example.com/

myURL = new URL('foo:Example.com/', 'https://example.org/');
// foo:Example.com/

舊版Node使用resolve方法拼接路徑:

注意:舊版node的resolve方法官方表示已棄用

const url = require('url')

var a = url.resolve("/one/two/three", "four"); // /one/two/four
// var a = url.resolve("/one/two/three/", "four"); // /one/two/three/four
// var a = url.resolve("/one/two/three", "/four"); // /four
// var a = url.resolve("/one/two/three/", "/four"); // /four

var b = url.resolve("http://example.com/", "/one");
// var b = url.resolve("http://example.com/", "one");
// var b = url.resolve("http://example.com", "one");
// var b = url.resolve("http://example.com", "/one");
// 以上b的結(jié)果都是:http://example.com/one

var c = url.resolve("http://example.com/one", "two");
// var c = url.resolve("http://example.com/one", "/two");
// var c = url.resolve("http://example.com/one/", "/two");
// var c = url.resolve("http://example.com/one/a/b", "/two");
// 以上c的結(jié)果都是:http://example.com/two

var d = url.resolve("http://example.com/one/", "two"); // http://example.com/one/two

var e = url.resolve("http://example.com/one/aaa", "http://example.com/one/two");
// var e = url.resolve("/one/aaa", "http://example.com/one/two");
// 以上e的結(jié)果都是:http://example.com/one/two

resolve方法并不是簡(jiǎn)單的將兩個(gè)路徑直接拼接在一起,而是具有它自己的一些拼接規(guī)則:

  • 如果第二個(gè)路徑(接收的第二個(gè)參數(shù))開(kāi)頭前帶/,則將直接用第二個(gè)路徑替代第一個(gè)路徑的路由部分(不會(huì)替代第一個(gè)路徑的根域名,如上面的最后一個(gè)變量c所示:/two替代了/one/a/b
  • 如果第二個(gè)路徑開(kāi)頭前不帶/,第一個(gè)路徑結(jié)尾處不帶/,則第二個(gè)路徑將會(huì)替代第一個(gè)路徑的最后一個(gè)路由,如上邊的第一個(gè)變量a和第一個(gè)變量c
  • 第二個(gè)路徑開(kāi)頭前不帶/,第一個(gè)路徑結(jié)尾處帶/,則直接將第二個(gè)路徑拼接在第一個(gè)路徑后面,如上邊的第變量d和第二個(gè)變量a
  • 如果第二個(gè)路徑包含根域名(http://xxx),則直接以第二個(gè)路徑為主(第一個(gè)路徑失效) 處理URL路徑參數(shù)

注意:querystring方法官方表示已棄用

NodeJS有一個(gè)內(nèi)置模塊querystring,它里面的parsestringify方法可以幫助我們快速處理URL上的形如id=8&name=mouse的參數(shù):

const querystring = require("querystring");
var qs = "id=8&name=Ailjx";
// parse:路徑將參數(shù)轉(zhuǎn)化為對(duì)象
var parsed = querystring.parse(qs);
console.log(parsed.id, parsed.name); // 8 Ailjx

var qo = {
    x: 3,
    y: 4,
};
//stringify:將對(duì)象轉(zhuǎn)化為路徑參數(shù)
var parsed = querystring.stringify(qo);
console.log(parsed); // x=3&y=4

querystring中還有一對(duì)能夠轉(zhuǎn)義特殊字符的方法: escape/unescape

const querystring = require("querystring");
var str = 'ns"--';
// escape:將特殊字符轉(zhuǎn)義
var escaped = querystring.escape(str);
console.log(escaped); //ns%22--

// unescape:恢復(fù)轉(zhuǎn)義的特殊字符
console.log(querystring.unescape(escaped)); // ns"--

對(duì)于特殊字符的轉(zhuǎn)義在一些特殊情況下特別重要,例如我們通過(guò)用戶傳遞的參數(shù)來(lái)向mysql數(shù)據(jù)庫(kù)查詢數(shù)據(jù),我們?yōu)榱朔乐褂脩魝鬟f的參數(shù)與sql語(yǔ)句發(fā)送沖突,就可以對(duì)該參數(shù)進(jìn)行轉(zhuǎn)義,以防止sql注入

例如有一條含有用戶傳遞的param參數(shù)的sql語(yǔ)句:

let sql = `select * from users where name = "${param}" and del_status=1`

上面的sql語(yǔ)句在正常情況下是只能查詢到一條數(shù)據(jù),如:

// let param = 'Ailjx' ,對(duì)應(yīng)的sql語(yǔ)句如下:
select * from users where name = "Ailjx" and del_status=1

但當(dāng)paramsql語(yǔ)句沖突時(shí):

// let param = 'ns"--',對(duì)應(yīng)的sql語(yǔ)句如下:
select * from tb_nature where nature = "ns"-- " and del_status=1

可以看到del_status被參數(shù)中的--注釋掉了,失去了作用,這時(shí)這條sql能查詢到多條數(shù)據(jù),這就是sql注入的危害,也就是我們需要轉(zhuǎn)義特殊字符的原因

正確轉(zhuǎn)換文件路徑

url模塊針對(duì)轉(zhuǎn)換文件路徑提供了單獨(dú)的fileURLToPathpathToFileURL方法,它們不僅能正確進(jìn)行文件路徑的轉(zhuǎn)換而且能自動(dòng)適配不同的操作系統(tǒng)

fileURLToPath該方法能夠正確將文件網(wǎng)址url轉(zhuǎn)換成文件路徑:

const { fileURLToPath } = require("url");

console.log(new URL("file:///C:/path/").pathname); // 獲得錯(cuò)誤路徑:/C:/path/
console.log(fileURLToPath("file:///C:/path/")); // 獲得正確路徑:C:\path\ (Windows)

console.log(new URL("file://nas/foo.txt").pathname); //  獲得錯(cuò)誤路徑:/foo.txt
console.log(fileURLToPath("file://nas/foo.txt")); //  獲得正確路徑: \\nas\foo.txt (Windows)

console.log(new URL("file://c://你好.txt").pathname); //  獲得錯(cuò)誤路徑:/c://%E4%BD%A0%E5%A5%BD.txt
console.log(fileURLToPath("file://c://你好.txt")); //  獲得正確路徑: c:\\你好.txt (Windows)

pathToFileURL方法,能夠?qū)⑽募窂睫D(zhuǎn)換成文件網(wǎng)址url對(duì)象

const { pathToFileURL } = require("url");
console.log(new URL("/foo#1", "file:").href); // 錯(cuò)誤: file:///foo#1
console.log(pathToFileURL("/foo#1").href); // 正確:   file:///D:/foo%231 

console.log(new URL("/some/path%.c", "file:").href); // 錯(cuò)誤: file:///some/path%.c
console.log(pathToFileURL("/some/path%.c").href); // 正確:   file:///D:/some/path%25.c 

轉(zhuǎn)換為Options Url對(duì)象

urlToHttpOptions方法可以將new URL返回的WHATWG URL對(duì)象轉(zhuǎn)換成http.request()https.request()需要的Options Url對(duì)象

http.request()https.request()在下面會(huì)講到

const { urlToHttpOptions } = require("url");
const myURL = new URL('https://a:b@測(cè)試?abc#foo');
console.log(urlToHttpOptions(myURL));
/*
{
  protocol: 'https:',
  hostname: 'xn--g6w251d',
  hash: '#foo',
  search: '?abc',
  pathname: '/',
  path: '/?abc',
  href: 'https://a:b@xn--g6w251d/?abc#foo',
  auth: 'a:b'
}
*/

六、跨域處理

在不做處理的情況下,前后端交互會(huì)出現(xiàn)CORS跨域的問(wèn)題,如:

定義一個(gè)簡(jiǎn)單的服務(wù)器:

server.js

const http = require("http");
const url = require("url");
const server = http.createServer();
server.on("request", (req, res) => {
    const urlObj = url.parse(req.url, true);
    switch (urlObj.pathname) {
        case "/api/user":
            // 模擬數(shù)據(jù)
            const userinfo = {
                name: "Ailjx",
            };
            res.end(JSON.stringify(userinfo));
            break;
        default:
            res.end("404");
            break;
    }
});
server.listen(3000, () => {
    console.log("服務(wù)器啟動(dòng)啦!");
});

可以看到上面我們定義的服務(wù)器并沒(méi)有設(shè)置跨域,我們直接在html文件內(nèi)請(qǐng)求該服務(wù)器的接口:

index.html

<body>
    <div>jsonp接口測(cè)試</div>
    <script>
        fetch('http://localhost:3000/api/user')
        .then(res => res.json())
        .then(res => console.log(res))
    </script>
</body>

打開(kāi)該html文件,可以看到果然出現(xiàn)了CORS跨域的報(bào)錯(cuò)

這種問(wèn)題有多種解決方案:

  • 后端設(shè)置響應(yīng)頭來(lái)允許前端訪問(wèn)服務(wù)器(只需要后端進(jìn)行修改)
  • 前后端使用jsonp方式進(jìn)行交互(前后端都需要進(jìn)行相應(yīng)修改)
  • 前端配置代理(只需要前端進(jìn)行修改,這在像vuereact等這些框架中經(jīng)常使用)

后端設(shè)置跨域

后端可以直接在響應(yīng)頭里設(shè)置跨域,而前端不需要做額外的操作

我們下載一個(gè)vscodeLive Server插件,用于在線運(yùn)行html文件:

右鍵html文件選擇Open with Live Server

打開(kāi)后發(fā)現(xiàn)前端html的在線運(yùn)行地址為http://127.0.0.1:5500,我們?cè)诤蠖朔祷財(cái)?shù)據(jù)的響應(yīng)頭里允許該地址訪問(wèn)即可:

修改一下上邊的server.js

const http = require("http");
const url = require("url");
const server = http.createServer();
server.on("request", (req, res) => {
    const urlObj = url.parse(req.url, true);
    res.writeHead(200, {
        "content-type": "application/json;charset=utf-8",
         // 設(shè)置跨域允許http://127.0.0.1:5500訪問(wèn)
        "Access-Control-Allow-Origin": "http://127.0.0.1:5500",
         // "Access-Control-Allow-Origin": "*", 也可以使用'*',代表允許所有地址訪問(wèn)
    });
    switch (urlObj.pathname) {
        case "/api/user":
            // 模擬數(shù)據(jù)
            const userinfo = {
                name: "Ailjx",
            };
            res.end(JSON.stringify(userinfo));
            break;
        default:
            res.end("404");
            break;
    }
});

server.listen(3000, () => {
    console.log("服務(wù)器啟動(dòng)啦!");
});

這時(shí)前端就能正常調(diào)用該接口了:

jsonp接口

我們知道htmlscript標(biāo)簽可以引入js文件,并且重要的是使用script標(biāo)簽引入js沒(méi)有跨域的要求,所以我們可以在script標(biāo)簽的src屬性中調(diào)用我們的接口來(lái)獲取后端返回的數(shù)據(jù)

前端處理:

<body>
    <div>jsonp接口測(cè)試</div>
    <div>
        name: <b id="myName"></b>
    </div>
    <script>
        // 定義一個(gè)接收后端返回?cái)?shù)據(jù)的函數(shù)
        function getUser(params) {
            const myName = document.getElementById('myName')
            // 可以做一些操作
            myName.innerText = params.name
        }
        // 創(chuàng)建一個(gè)script標(biāo)簽
        const myScript = document.createElement('script')
        // script標(biāo)簽的src內(nèi)調(diào)用接口,需要將我們定義的接收數(shù)據(jù)的函數(shù)名傳遞給后端
        myScript.src = 'http://localhost:3000/api/user?cb=getUser'
        // 向文檔內(nèi)插入該script標(biāo)簽
        document.body.appendChild(myScript)
    </script>
</body>

或者直接用script調(diào)用后端接口:

<body>
    <div>jsonp接口測(cè)試</div>
    <div>
        name: <b id="myName"></b>
    </div>
    <script>
        // // 定義一個(gè)接收后端返回?cái)?shù)據(jù)的函數(shù)
        function getUser(params) {
            const myName = document.getElementById('myName')
            myName.innerText = params.name
        }
    </script>
    <!-- 調(diào)用后端接口 -->
    <script src="http://localhost:3000/api/user?cb=getUser"></script>
</body>

后端處理:

const http = require("http");
const url = require("url");
const server = http.createServer();

server.on("request", (req, res) => {
    const urlObj = url.parse(req.url, true);
    switch (urlObj.pathname) {
        case "/api/user":
            // 模擬數(shù)據(jù)
            const userinfo = {
                name: "Ailjx",
            };
            // urlObj.query.cb是前端傳遞的接收數(shù)據(jù)的函數(shù)名稱
            // 返回給前端一個(gè)函數(shù)調(diào)用
            res.end(`${urlObj.query.cb}(${JSON.stringify(userinfo)})`);
            break;
        default:
            res.end("404");
            break;
    }
});

server.listen(3000, () => {
    console.log("服務(wù)器啟動(dòng)啦!");
});

可以看到使用jsonp的原理就是在script標(biāo)簽中調(diào)用后端接口,因?yàn)?code>script標(biāo)簽內(nèi)的js代碼是立即執(zhí)行

所以我們需要提前定義一個(gè)接收后端參數(shù)的處理函數(shù),然后將該函數(shù)名傳遞給后端,后端根據(jù)這個(gè)函數(shù)名返回給前端一個(gè)該函數(shù)的調(diào)用并將需要給前端的數(shù)據(jù)作為該函數(shù)的參數(shù)

上述代碼的最終效果如下:

<script>
    // 定義一個(gè)接收后端返回?cái)?shù)據(jù)的函數(shù)
    function getUser(params) {
        const myName = document.getElementById('myName')
        myName.innerText = params.name
    }
</script>
  <!-- <script src="http://localhost:3000/api/user?cb=getUser"></script>的效果如下 -->
<script>
    getUser({
        name: "Ailjx",
    })
</script>

七、Node作為中間層使用

上面我們使用node搭建了后端服務(wù)器,使其作為服務(wù)端運(yùn)行,但其實(shí)node還能當(dāng)作客戶端反過(guò)來(lái)去調(diào)用其它服務(wù)端的接口,這使得node成為了一個(gè)中間層

因?yàn)榭缬蛑皇菫g覽器的限制,服務(wù)端之間的通信并不存在跨域的問(wèn)題,這樣我們就能借助node去調(diào)用第三方具有跨域限制的接口,這是將node作為中間層的一個(gè)非常實(shí)用的功能

模擬get請(qǐng)求(轉(zhuǎn)發(fā)跨域數(shù)據(jù))

在貓眼電影網(wǎng)上隨便找了一個(gè)帶有跨域的接口,我們直接調(diào)用時(shí)會(huì)報(bào)CORS跨域問(wèn)題:

<h1>使用node模擬get請(qǐng)求</h1>
<script>
    fetch('https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=%E8%A5%BF%E5%8D%8E&ci=936&channelId=4')
        .then(res => res.json())
        .then(res => {
            console.log(res);
        })
</script>

這時(shí)我們可以利用node幫我們?nèi)フ?qǐng)求這個(gè)接口的數(shù)據(jù):

const http = require("http");
const https = require("https");
// http和https的區(qū)別僅在于一個(gè)是http協(xié)議一個(gè)是https協(xié)議
const url = require("url");

const server = http.createServer();

server.on("request", (req, res) => {
    const urlObj = url.parse(req.url, true);

    res.writeHead(200, {
        "content-type": "application/json;charset=utf-8",
        "Access-Control-Allow-Origin": "http://127.0.0.1:5500",
    });

    switch (urlObj.pathname) {
        case "/api/maoyan":
            // 我們定義的httpget方法:使node充當(dāng)客戶端去貓眼的接口獲取數(shù)據(jù)
            httpget((data) => res.end(data));
            break;

        default:
            res.end("404");
            break;
    }
});

server.listen(3000, () => {
    console.log("服務(wù)器啟動(dòng)啦!");
});

function httpget(cb) {
    // 定義一個(gè)存放數(shù)據(jù)的變量
    let data = "";
    // 因?yàn)樨堁鄣慕涌谑莌ttps協(xié)議的,所以我們需要引入https
    // http和https都具有一個(gè)get方法能夠發(fā)起get請(qǐng)求,區(qū)別是一個(gè)是http協(xié)議,一個(gè)是https協(xié)議
    // http get方法第一個(gè)參數(shù)為接口地址,第二個(gè)參數(shù)為回調(diào)函數(shù)
    https.get(
        "https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=%E8%A5%BF%E5%8D%8E&ci=936&channelId=4",
        (res) => {
            // http get方法獲取的數(shù)據(jù)是一點(diǎn)點(diǎn)返回的,并不是直接返回全部
            // 監(jiān)聽(tīng)data,當(dāng)有數(shù)據(jù)返回時(shí)就會(huì)被調(diào)用
            res.on("data", (chunk) => {
                // 收集數(shù)據(jù)
                data += chunk;
            });
            // 監(jiān)聽(tīng)end,數(shù)據(jù)返回完畢后調(diào)用
            res.on("end", () => {
                cb(data);
            });
        }
    );
}

之后我們調(diào)用我們node的接口即可:

<h1>使用node模擬get請(qǐng)求</h1>
<script>
    fetch('http://localhost:3000/api/maoyan')
        .then(res => res.json())
        .then(res => {
            console.log(res);
        })
</script>

這里node即作為服務(wù)端給我們提供接口/api/maoyan,又充當(dāng)了一下客戶端去調(diào)用貓眼的接口,這樣我們就繞過(guò)了貓眼的跨域限制獲取了它的數(shù)據(jù)

模擬post請(qǐng)求(服務(wù)器提交)

使用node模擬post請(qǐng)求需要使用httphttpsrequest方法來(lái)進(jìn)行請(qǐng)求的配置,稍微有點(diǎn)麻煩:

httphttps模塊的區(qū)別僅在于一個(gè)是http協(xié)議一個(gè)是https協(xié)議

const http = require("http");
const https = require("https");
const url = require("url");
const server = http.createServer();
server.on("request", (req, res) => {
    const urlObj = url.parse(req.url, true);

    res.writeHead(200, {
        "content-type": "application/json;charset=utf-8",
        "Access-Control-Allow-Origin": "http://127.0.0.1:5500",
    });

    switch (urlObj.pathname) {
        case "/api/xiaomiyoumin":
            // httpPost方法:使node充當(dāng)客戶端去小米有品的post接口獲取數(shù)據(jù)
            httpPost((data) => res.end(data));
            break;

        default:
            res.end("404");
            break;
    }
});

server.listen(3000, () => {
    console.log("服務(wù)器啟動(dòng)啦!");
});

function httpPost(cb) {
    // 定義一個(gè)存放數(shù)據(jù)的變量
    let data = "";
    // 這是小米有品的一個(gè)post接口:"https://m.xiaomiyoupin.com/mtop/market/search/placeHolder"
    // 這個(gè)接口調(diào)用時(shí)需要傳“[{}, { baseParam: { ypClient: 1 } }]”這樣一個(gè)參數(shù)才能返回?cái)?shù)據(jù)

    // 配置Options Url請(qǐng)求對(duì)象
    const options = {
        // 域名
        hostname: "m.xiaomiyoupin.com",
        // 接口端口號(hào),443代表https,80代表http
        port: "443",
        // 路徑
        path: "/mtop/market/search/placeHolder",
        // 請(qǐng)求方式
        method: "POST",
        // 請(qǐng)求頭
        Headers: {
            // 表示接收json數(shù)據(jù)
            "Content-Type": "application/json",
        },
    };

    // http request方法第一個(gè)參數(shù)為請(qǐng)求對(duì)象,第二個(gè)參數(shù)為回調(diào)函數(shù),request方法返回一個(gè)值(),在該值內(nèi)通過(guò)調(diào)用write向post請(qǐng)求傳遞數(shù)據(jù)
    const req = https.request(options, (res) => {
        // 監(jiān)聽(tīng)data,當(dāng)有數(shù)據(jù)返回時(shí)就會(huì)被調(diào)用
        res.on("data", (chunk) => {
            // 收集數(shù)據(jù)
            data += chunk;
        });
        // 監(jiān)聽(tīng)end,數(shù)據(jù)返回完畢后調(diào)用
        res.on("end", () => {
            cb(data);
        });
    });
    // 發(fā)送post的參數(shù)
    // req.write(JSON.stringify([{}, { baseParam: { ypClient: 1 } }]));
    // 這里的使用與我們server服務(wù)器中的req參數(shù)使用方式差不多,不要忘記最后調(diào)用end方法,并且也可以直接在end方法內(nèi)傳遞數(shù)據(jù)
    req.end(JSON.stringify([{}, { baseParam: { ypClient: 1 } }]));
}
<body>
    <h1>使用node模擬post請(qǐng)求</h1>
    <script>
        fetch('http://localhost:3000/api/xiaomiyoumin')
            .then(res => res.json())
            .then(res => {
                console.log(res);
            })
    </script>
</body>

八、使用Node實(shí)現(xiàn)爬蟲(chóng)

我們使用node的一個(gè)cheerio包也可以實(shí)現(xiàn)爬蟲(chóng)功能,如我們爬取貓眼移動(dòng)端https://i.maoyan.com首頁(yè)的一些數(shù)據(jù):

我們?cè)谝粋€(gè)文件夾內(nèi)打開(kāi)終端,先生成package.json文件:

npm init

安裝cheerio

npm i cheerio

文件夾內(nèi)創(chuàng)建我們的服務(wù)器文件server.js

const https = require("https");
const http = require("http");
const cheerio = require("cheerio");

http.createServer((request, response) => {
    response.writeHead(200, {
        "content-type": "application/json;charset=utf-8",
    });
    const options = {
        hostname: "i.maoyan.com",
        port: 443,
        path: "/",
        method: "GET",
    };
    // 獲取頁(yè)面數(shù)據(jù)
    const req = https.request(options, (res) => {
        let data = "";
        res.on("data", (chunk) => {
            data += chunk;
        });

        res.on("end", () => {
            response.end(data);
        });
    });

    req.end();
}).listen(3000);

上面演示了使用 https.request方法配置get請(qǐng)求(接口為:https://i.maoyan.com)的寫(xiě)法,你也可以直接使用https.get進(jìn)行調(diào)用

通過(guò)瀏覽器調(diào)用我們的服務(wù)器:

可以看到我們成功獲取了貓眼移動(dòng)端首頁(yè)的html文檔,我們需要的數(shù)據(jù)都在文檔中了,之后我們只需將我們需要的數(shù)據(jù)提取出來(lái),這里就將用到cheerio

const https = require("https");
const http = require("http");
const cheerio = require("cheerio");
http.createServer((request, response) => {
    response.writeHead(200, {
        "content-type": "application/json;charset=utf-8",
    });

    const options = {
        hostname: "i.maoyan.com",
        port: 443,
        path: "/",
        method: "GET",
    };
    // 獲取頁(yè)面數(shù)據(jù)
    const req = https.request(options, (res) => {
        let data = "";
        res.on("data", (chunk) => {
            data += chunk;
        });

        res.on("end", () => {
            // 處理頁(yè)面數(shù)據(jù)
            filterData(data);
        });
    });
    function filterData(data) {
        let $ = cheerio.load(data);
        // 獲取class="column content"的元素
        let $movieList = $(".column.content");
        // console.log($movieList);
        let movies = [];
        $movieList.each((index, value) => {
            movies.push({
                // 獲取class為movie-title下的class為title的元素的文本值
                title: $(value).find(".movie-title .title").text(),
                detail: $(value).find(".detail .actor").text(),
            });
        });

        response.end(JSON.stringify(movies));
    }

    req.end();
}).listen(3000);

cheerio.load接收html文檔字符串,它返回一個(gè)對(duì)象,該對(duì)象與jQuery相似,我們可以對(duì)其使用jQuery的語(yǔ)法進(jìn)行操作

上面使用的class類名都是在貓眼首頁(yè)文檔的對(duì)應(yīng)類名,如:

重新訪問(wèn)一下我們的服務(wù)器:

可以看到我們已經(jīng)爬蟲(chóng)成功!

如果在請(qǐng)求https://i.maoyan.com接口時(shí)獲取不到數(shù)據(jù),可能是我們?yōu)g覽器上的貓眼網(wǎng)進(jìn)入了驗(yàn)證操作,我們?cè)谠L問(wèn)我們服務(wù)器的這個(gè)瀏覽器上打開(kāi)https://i.maoyan.com進(jìn)行驗(yàn)證一下,之后我們就能請(qǐng)求到https://i.maoyan.com的html文檔內(nèi)容了

到此這篇關(guān)于Node.js 搭建后端服務(wù)器內(nèi)置模塊( http+url+querystring 的使用)的文章就介紹到這了,更多相關(guān)Node.js 后端搭建內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 從零開(kāi)始在webstorm配置nodejs

    從零開(kāi)始在webstorm配置nodejs

    WebStorm是作為JS開(kāi)發(fā)IDE存在的,并且支持流行的Node.js以及JQuery等js框架,下面這篇文章主要給大家介紹了關(guān)于如何從零開(kāi)始在webstorm配置nodejs的相關(guān)資料,需要的朋友可以參考下
    2024-08-08
  • npm與nrm兩種方式查看源和切換鏡像詳解

    npm與nrm兩種方式查看源和切換鏡像詳解

    nrm(npm registry manager )是npm的鏡像源管理工具,它可以快速在讓你在本地源之間切換,下面這篇文章主要給大家介紹了關(guān)于npm與nrm兩種方式查看源和切換鏡像的相關(guān)資料,需要的朋友可以參考下
    2023-02-02
  • 使用nvm進(jìn)行多個(gè)nodejs版本的統(tǒng)一管理

    使用nvm進(jìn)行多個(gè)nodejs版本的統(tǒng)一管理

    隨著前端項(xiàng)目的越來(lái)越多,不同項(xiàng)目使用的nodejs版本可能不一樣,導(dǎo)致在切換不同項(xiàng)目時(shí)需要更換不同的nodejs版本,非常麻煩,本次推薦使用nvm進(jìn)行多個(gè)nodejs版本的統(tǒng)一管理,文中有詳細(xì)的圖文介紹,需要的朋友可以參考下
    2023-12-12
  • nodejs中使用HTTP分塊響應(yīng)和定時(shí)器示例代碼

    nodejs中使用HTTP分塊響應(yīng)和定時(shí)器示例代碼

    本文通過(guò)示例將要?jiǎng)?chuàng)建一個(gè)輸出純文本的HTTP服務(wù)器,輸出的純文本每隔一秒會(huì)新增100個(gè)用換行符分隔的時(shí)間戳。實(shí)例代碼非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下
    2017-03-03
  • Node.js 的模塊知識(shí)匯總

    Node.js 的模塊知識(shí)匯總

    node.js中是通過(guò)模塊來(lái)劃分為單位來(lái)劃分所有功能的。每個(gè)模塊為一個(gè)js文件。每個(gè)模塊中定義的全局變量或函數(shù)的作用范圍也被限制在這個(gè)模塊中,只能用exports對(duì)象將其傳遞到外部。
    2017-08-08
  • Node.js中的http請(qǐng)求客戶端示例(request client)

    Node.js中的http請(qǐng)求客戶端示例(request client)

    本篇文章主要介紹了Node.js中的http請(qǐng)求客戶端示例(request client),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • 學(xué)習(xí)Nodejs之fs模塊的使用詳解

    學(xué)習(xí)Nodejs之fs模塊的使用詳解

    這篇文章主要為大家詳細(xì)介紹了Nodejs之fs模塊的使用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-02-02
  • 一文帶你了解Node.js有哪些架構(gòu)模式

    一文帶你了解Node.js有哪些架構(gòu)模式

    Node.js 憑借其非阻塞、事件驅(qū)動(dòng)的架構(gòu),已成為構(gòu)建各種應(yīng)用程序的流行選擇,使用 Node.js 進(jìn)行開(kāi)發(fā)時(shí),選擇正確的架構(gòu)模式來(lái)滿足項(xiàng)目需求至關(guān)重要,在本文中,我們將探討幾種 Node.js 架構(gòu)模式并提供示例來(lái)說(shuō)明它們的用法,需要的朋友可以參考下
    2023-09-09
  • 編譯打包nodejs服務(wù)代碼如何部署到服務(wù)器

    編譯打包nodejs服務(wù)代碼如何部署到服務(wù)器

    這篇文章主要介紹了編譯打包nodejs服務(wù)代碼如何部署到服務(wù)器問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • node-sass一直安裝不上、安裝失敗的原因分析

    node-sass一直安裝不上、安裝失敗的原因分析

    一個(gè)項(xiàng)目一直以來(lái)運(yùn)行都是正常的,今天運(yùn)行就突然報(bào)錯(cuò)了,錯(cuò)誤如下:?錯(cuò)誤大致意思就是node-sass安裝失敗,下面這篇文章主要給大家介紹了關(guān)于node-sass一直安裝不上、安裝失敗的原因分析,需要的朋友可以參考下
    2023-02-02

最新評(píng)論