node中的緩存機(jī)制

node中的緩存機(jī)制

緩存是node開(kāi)發(fā)中一個(gè)很重要的概念缭乘,它應(yīng)用在很多地方,例如瀏覽器有緩存琉用、DNS有緩存堕绩、包括服務(wù)器也有緩存策幼。

一、緩存作用

那緩存是為了做什么呢奴紧?

1.為了提高速度特姐,提高效率。
2.減少數(shù)據(jù)傳輸黍氮,節(jié)省網(wǎng)費(fèi)唐含。
3.減少服務(wù)器的負(fù)擔(dān),提高網(wǎng)站性能沫浆。
4.加快客戶端加載網(wǎng)頁(yè)的速度捷枯。

二、緩存分類

那緩存有幾種策略呢专执?

強(qiáng)制緩存:

1淮捆、概念:

客戶端訪問(wèn)服務(wù)器請(qǐng)求資源,請(qǐng)求成功之后客戶端會(huì)緩存到本地本股,緩存到本地之后争剿,如果以后客戶端再請(qǐng)求該資源此時(shí)不需要請(qǐng)求服務(wù)器了,直接訪問(wèn)本地的就可以痊末。

2、特點(diǎn):

強(qiáng)制緩存不需要與服務(wù)器發(fā)生交互

3哩掺、客戶端訪問(wèn)強(qiáng)制緩存的流程圖解
image

1)緩存命中
客戶端請(qǐng)求數(shù)據(jù)凿叠,現(xiàn)在本地的緩存數(shù)據(jù)庫(kù)中查找,如果本地緩存數(shù)據(jù)庫(kù)中有該數(shù)據(jù)嚼吞,且該數(shù)據(jù)沒(méi)有失效盒件。則取緩存數(shù)據(jù)庫(kù)中的該數(shù)據(jù)返回給客戶端。

2)緩存未命中
客戶端請(qǐng)求數(shù)據(jù)舱禽,現(xiàn)在本地的緩存數(shù)據(jù)庫(kù)中查找炒刁,如果本地緩存數(shù)據(jù)庫(kù)中有該數(shù)據(jù),且該數(shù)據(jù)失效誊稚。則向服務(wù)器請(qǐng)求該數(shù)據(jù)翔始,此時(shí)服務(wù)器返回該數(shù)據(jù)和該數(shù)據(jù)的緩存規(guī)則返回給客戶端,客戶端收到該數(shù)據(jù)和緩存規(guī)則后里伯,一起放到本地的緩存數(shù)據(jù)庫(kù)中留存城瞎。以備下次使用。

4疾瓮、如何實(shí)現(xiàn)強(qiáng)制緩存脖镀?

1、瀏覽器會(huì)將文件緩存到Cache目錄狼电,第二次請(qǐng)求時(shí)瀏覽器會(huì)先檢查Cache目錄下是否含有該文件蜒灰,如果有弦蹂,并且還沒(méi)到Expires設(shè)置的時(shí)間,即文件還沒(méi)有過(guò)期强窖,那么此時(shí)瀏覽器將直接從Cache目錄中讀取文件凸椿,而不再發(fā)送請(qǐng)求
2、Expires是服務(wù)器響應(yīng)消息頭字段毕骡,在響應(yīng)http請(qǐng)求時(shí)告訴瀏覽器在過(guò)期時(shí)間前瀏覽器可以直接從瀏覽器緩存取數(shù)據(jù)削饵,而無(wú)需再次請(qǐng)求,這是HTTP1.0的內(nèi)容,現(xiàn)在瀏覽器均默認(rèn)使用HTTP1.1,所以基本可以忽略
3未巫、Cache-Control與Expires的作用一致窿撬,都是指明當(dāng)前資源的有效期,控制瀏覽器是否直接從瀏覽器緩存取數(shù)據(jù)還是重新發(fā)請(qǐng)求到服務(wù)器取數(shù)據(jù),如果同時(shí)設(shè)置的話叙凡,其優(yōu)先級(jí)高于Expires

把資源緩存在客戶端劈伴,如果客戶端再次需要此資源的時(shí)候,先獲取到緩存中的數(shù)據(jù)握爷,看是否過(guò)期跛璧,如果過(guò)期了。再請(qǐng)求服務(wù)器
如果沒(méi)過(guò)期新啼,則根本不需要向服務(wù)器確認(rèn)追城,直接使用本地緩存即可

Cache-Control
private 客戶端可以緩存
public 客戶端和代理服務(wù)器都可以緩存
max-age=60 緩存內(nèi)容將在60秒后失效
no-cache 需要使用對(duì)比緩存驗(yàn)證數(shù)據(jù),強(qiáng)制向源服務(wù)器再次驗(yàn)證. 禁用強(qiáng)制緩存
no-store 所有內(nèi)容都不會(huì)緩存,強(qiáng)制緩存和對(duì)比緩存都不會(huì)觸發(fā)燥撞。兼用強(qiáng)制緩存和對(duì)比緩存?

/**
* 1. 第一次訪問(wèn)服務(wù)器的時(shí)候座柱,服務(wù)器返回資源和緩存的標(biāo)識(shí),客戶端則會(huì)把此資源緩存在本地的緩存數(shù)據(jù)庫(kù)中物舒。
* 2. 第二次客戶端需要此數(shù)據(jù)的時(shí)候色洞,要取得緩存的標(biāo)識(shí),然后去問(wèn)一下服務(wù)器我的資源是否是最新的冠胯。
* 如果是最新的則直接使用緩存數(shù)據(jù)火诸,如果不是最新的則服務(wù)器返回新的資源和緩存規(guī)則,客戶端根據(jù)緩存規(guī)則緩存新的數(shù)據(jù)荠察。
*/
let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
let crypto = require('crypto');
/**
* 強(qiáng)制緩存
* 把資源緩存在客戶端置蜀,如果客戶端再次需要此資源的時(shí)候,先獲取到緩存中的數(shù)據(jù)悉盆,看是否過(guò)期盾碗,如果過(guò)期了。再請(qǐng)求服務(wù)器
* 如果沒(méi)過(guò)期舀瓢,則根本不需要向服務(wù)器確認(rèn)廷雅,直接使用本地緩存即可
*/
http.createServer(function (req, res) {
   let { pathname } = url.parse(req.url, true);
   let filepath = path.join(__dirname, pathname);
   console.log(filepath);
   fs.stat(filepath, (err, stat) => {
       if (err) {
           return sendError(req, res);
       } else {
           send(req, res, filepath);
}
});
}).listen(8080);
function sendError(req, res) {
   res.end('Not Found');
}
function send(req, res, filepath) {
   res.setHeader('Content-Type', mime.getType(filepath));
   //expires指定了此緩存的過(guò)期時(shí)間,此響應(yīng)頭是1.0定義的,在1.1里面已經(jīng)不再使用了
   res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString());
   res.setHeader('Cache-Control', 'max-age=30');
   fs.createReadStream(filepath).pipe(res);
}

對(duì)比緩存:

1航缀、概念:

瀏覽器第一次請(qǐng)求數(shù)據(jù)時(shí)商架,服務(wù)器會(huì)將緩存標(biāo)識(shí)與數(shù)據(jù)一起返回給客戶端,客戶端將二者備份至緩存數(shù)據(jù)庫(kù)中芥玉。
再次請(qǐng)求數(shù)據(jù)時(shí)蛇摸,客戶端將備份的緩存標(biāo)識(shí)發(fā)送給服務(wù)器,服務(wù)器根據(jù)緩存標(biāo)識(shí)進(jìn)行判斷灿巧,判斷成功后赶袄,返回304狀態(tài)碼,通知客戶端比較成功抠藕,可以使用緩存數(shù)據(jù)饿肺。

2、特點(diǎn):需要進(jìn)行比較判斷是否可以使用緩存
3盾似、對(duì)比緩存流程圖解

1)客戶端第一次發(fā)請(qǐng)求


image

客戶端第一次請(qǐng)求數(shù)據(jù)敬辣,發(fā)現(xiàn)本地緩存中沒(méi)有,就向服務(wù)器發(fā)起請(qǐng)求零院,然后服務(wù)器把請(qǐng)求的數(shù)據(jù)返回給客戶端溉跃,并和客戶端商量你要保存到本地緩存中的規(guī)則,即是否緩存 緩存時(shí)間 有沒(méi)有標(biāo)示 最后修改時(shí)間等信息告抄。
2)客戶端第二次發(fā)請(qǐng)求


image

客戶端發(fā)起請(qǐng)請(qǐng)求
--->查看本地的緩存數(shù)據(jù)庫(kù)中是否有緩存---> 沒(méi)有---> 向服務(wù)器發(fā)起請(qǐng)求--->服務(wù)器返回200和響應(yīng)內(nèi)容--->顯示

--->查看本地的緩存數(shù)據(jù)庫(kù)中是否有緩存---> 有 ---> 緩存沒(méi)有過(guò)期(本地)---> 緩存中讀取--->顯示

--->查看本地的緩存數(shù)據(jù)庫(kù)中是否有緩存---> 有 ---> 緩存已過(guò)期(本地)---> 本地的緩存中有沒(méi)有Etag和Last-Modified --->有--->發(fā)給服務(wù)器對(duì)應(yīng)的字段 if-none-match 和if-modified-since ---> 服務(wù)器策略撰茎。如果這兩個(gè)字段和服務(wù)器上的這兩個(gè)字段相同 ---> 說(shuō)明數(shù)據(jù)沒(méi)有更新--->返回304--->服務(wù)器從它的緩存庫(kù)中獲取到數(shù)據(jù)給客戶端--->顯示

--->查看本地的緩存數(shù)據(jù)庫(kù)中是否有緩存---> 有 ---> 緩存已過(guò)期(本地)---> 本地的緩存中有沒(méi)有Etag和Last-Modified --->有--->發(fā)給服務(wù)器對(duì)應(yīng)的字段 if-none-match 和if-modified-since ---> 服務(wù)器策略。如果這兩個(gè)字段和服務(wù)器上的這兩個(gè)字段相同 --->說(shuō)明數(shù)據(jù)有更新--->返回200--->重新獲取--->顯示

4打洼、如何實(shí)現(xiàn)對(duì)比緩存乾吻?

/**

    1. 第一次訪問(wèn)服務(wù)器的時(shí)候,服務(wù)器返回資源和緩存的標(biāo)識(shí)拟蜻,客戶端則會(huì)把此資源緩存在本地的緩存數(shù)據(jù)庫(kù)中。
    1. 第二次客戶端需要此數(shù)據(jù)的時(shí)候枯饿,要取得緩存的標(biāo)識(shí)酝锅,然后去問(wèn)一下服務(wù)器我的資源是否是最新的。
  • 如果是最新的則直接使用緩存數(shù)據(jù)奢方,如果不是最新的則服務(wù)器返回新的資源和緩存規(guī)則搔扁,客戶端根據(jù)緩存規(guī)則緩存新的數(shù)據(jù)。
    */
    我們通過(guò)標(biāo)示字段來(lái)判斷緩存中的數(shù)據(jù)是否有效
    這個(gè)標(biāo)示有兩種形式:
第一種是最后修改時(shí)間蟋字,Last-Modified

1稿蹲、Last-Modified:響應(yīng)時(shí)告訴客戶端此資源的最后修改時(shí)間
2、If-Modified-Since:當(dāng)資源過(guò)期時(shí)(使用Cache-Control標(biāo)識(shí)的max-age)鹊奖,發(fā)現(xiàn)資源具有Last-Modified聲明苛聘,則再次向服務(wù)器請(qǐng)求時(shí)帶上頭If-Modified-Since。
3、服務(wù)器收到請(qǐng)求后發(fā)現(xiàn)有頭If-Modified-Since則與被請(qǐng)求資源的最后修改時(shí)間進(jìn)行比對(duì)设哗。若最后修改時(shí)間較新唱捣,說(shuō)明資源又被改動(dòng)過(guò),則響應(yīng)最新的資源內(nèi)容并返回200狀態(tài)碼网梢;
4震缭、若最后修改時(shí)間和If-Modified-Since一樣,說(shuō)明資源沒(méi)有修改战虏,則響應(yīng)304表示未更新拣宰,告知瀏覽器繼續(xù)使用所保存的緩存文件。

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
// http://localhost:8080/index.html
http.createServer(function (req, res) {
    let {pathname} = url.parse(req.url);
    let filepath = path.join(__dirname,pathname);
    console.log(filepath);
    fs.stat(filepath,function (err, stat) {
          if (err) {
              return sendError(req,res)
          } else {
              // 再次請(qǐng)求的時(shí)候會(huì)問(wèn)服務(wù)器自從上次修改之后有沒(méi)有改過(guò)
              let ifModifiedSince = req.headers['if-modified-since'];
              console.log(req.headers);
              let LastModified = stat.ctime.toGMTString();
              console.log(LastModified);
              if (ifModifiedSince == LastModified) {
                  res.writeHead('304');
                  res.end('')
              } else {
                  return send(req,res,filepath,stat)
              }
          }
    })

}).listen(8080)

function send(req,res,filepath,stat) {
    res.setHeader('Content-Type', mime.getType(filepath));
    // 發(fā)給客戶端之后烦感,客戶端會(huì)把此時(shí)間保存下來(lái)巡社,下次再獲取此資源的時(shí)候會(huì)把這個(gè)時(shí)間再發(fā)給服務(wù)器
    res.setHeader('Last-Modified', stat.ctime.toGMTString());
    fs.createReadStream(filepath).pipe(res)
}

function sendError(req,res) {
    res.end('Not Found')
}
最后修改時(shí)間存在問(wèn)題

1、某些服務(wù)器不能精確得到文件的最后修改時(shí)間啸盏, 這樣就無(wú)法通過(guò)最后修改時(shí)間來(lái)判斷文件是否更新了重贺。
2、某些文件的修改非常頻繁回懦,在秒以下的時(shí)間內(nèi)進(jìn)行修改. Last-Modified只能精確到秒气笙。
3、一些文件的最后修改時(shí)間改變了怯晕,但是內(nèi)容并未改變潜圃。 我們不希望客戶端認(rèn)為這個(gè)文件修改了。
4舟茶、如果同樣的一個(gè)文件位于多個(gè)CDN服務(wù)器上的時(shí)候內(nèi)容雖然一樣谭期,修改時(shí)間不一樣。

第二種是Etag

ETag是實(shí)體標(biāo)簽的縮寫(xiě)吧凉,根據(jù)實(shí)體內(nèi)容生成的一段hash字符串,可以標(biāo)識(shí)資源的狀態(tài)隧出。當(dāng)資源發(fā)生改變時(shí),ETag也隨之發(fā)生變化阀捅。 ETag是Web服務(wù)端產(chǎn)生的胀瞪,然后發(fā)給瀏覽器客戶端。

1饲鄙、客戶端想判斷緩存是否可用可以先獲取緩存中文檔的ETag凄诞,然后通過(guò)If-None-Match發(fā)送請(qǐng)求給Web服務(wù)器詢問(wèn)此緩存是否可用。
2忍级、服務(wù)器收到請(qǐng)求帆谍,將服務(wù)器的中此文件的ETag,跟請(qǐng)求頭中的If-None-Match相比較,如果值是一樣的,說(shuō)明緩存還是最新的,Web服務(wù)器將發(fā)送304 Not Modified響應(yīng)碼給客戶端表示緩存未修改過(guò),可以使用轴咱。
3汛蝙、如果不一樣則Web服務(wù)器將發(fā)送該文檔的最新版本給瀏覽器客戶端

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
let crypto = require('let crypto = require(\'mime\');\n');
// http://localhost:8080/index.html
http.createServer(function (req, res) {
    let {pathname} = url.parse(req.url);
    let filepath = path.join(__dirname,pathname);
    console.log(filepath);
    fs.stat(filepath,function (err, stat) {
        if (err) {
            return sendError(req,res)
        } else {

            let ifNoneMatch = req.headers['if-none-match'];
            // 一烈涮、顯然當(dāng)我們的文件非常大的時(shí)候通過(guò)下面的方法就行不通來(lái),這時(shí)候我們可以用流來(lái)解決,可以節(jié)約內(nèi)存
            let out = fs.createReadStream(filepath);
            let md5 = crypto.createHash('md5');
            out.on('data',function (data) {
                md5.update(data)
            });
            out.on('end',function () {
                let etag = md5.update(content).digest('hex');
                // md5算法的特點(diǎn) 1. 相同的輸入相同的輸出 2.不同的輸入不通的輸出 3.不能根據(jù)輸出反推輸入 4.任意的輸入長(zhǎng)度輸出長(zhǎng)度是相同的
                if (ifNoneMatch == etag) {
                    res.writeHead('304');
                    res.end('')
                } else {
                    return send(req,res,filepath,stat, etag)
                }
            });
            
            // 二患雇、再次請(qǐng)求的時(shí)候會(huì)問(wèn)服務(wù)器自從上次修改之后有沒(méi)有改過(guò)
            // fs.readFile(filepath,function (err, content) {
            //     let etag = crypto.createHash('md5').update(content).digest('hex');
            //     // md5算法的特點(diǎn) 1. 相同的輸入相同的輸出 2.不同的輸入不通的輸出 3.不能根據(jù)輸出反推輸入 4.任意的輸入長(zhǎng)度輸出長(zhǎng)度是相同的
            //     if (ifNoneMatch == etag) {
            //         res.writeHead('304');
            //         res.end('')
            //     } else {
            //         return send(req,res,filepath,stat, etag)
            //     }
            // };
            // 但是上面的一方案也不是太好跃脊,讀一點(diǎn)緩存一點(diǎn),文件非常大的話需要好長(zhǎng)時(shí)間苛吱,而且我們的node不適合cup密集型酪术,即不適合來(lái)做大量的運(yùn)算,所以說(shuō)還有好多其他的算法
            // 三翠储、通過(guò)文件的修改時(shí)間減去文件的大小
            // let etag = `${stat.ctime}-${stat.size}`; // 這個(gè)也不是太好
            // if (ifNoneMatch == etag) {
            //     res.writeHead('304');
            //     res.end('')
            // } else {
            //     return send(req,res,filepath,stat, etag)
            // }
        }
    })

}).listen(8080)

function send(req,res,filepath,stat, etag) {
    res.setHeader('Content-Type', mime.getType(filepath));
    // 第一次服務(wù)器返回的時(shí)候绘雁,會(huì)把文件的內(nèi)容算出來(lái)一個(gè)標(biāo)示發(fā)送給客戶端
    //客戶端看到etag之后,也會(huì)把此標(biāo)識(shí)符保存在客戶端援所,下次再訪問(wèn)服務(wù)器的時(shí)候庐舟,發(fā)給服務(wù)器
    res.setHeader('Etag', etag);
    fs.createReadStream(filepath).pipe(res)
}

function sendError(req,res) {
    res.end('Not Found')
}
存在問(wèn)題

都需要向服務(wù)器端發(fā)請(qǐng)求與服務(wù)器端發(fā)生交互

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市住拭,隨后出現(xiàn)的幾起案子挪略,更是在濱河造成了極大的恐慌,老刑警劉巖滔岳,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杠娱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谱煤,警方通過(guò)查閱死者的電腦和手機(jī)摊求,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)刘离,“玉大人室叉,你說(shuō)我怎么就攤上這事×蛱瑁” “怎么了茧痕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)恼除。 經(jīng)常有香客問(wèn)我踪旷,道長(zhǎng),這世上最難降的妖魔是什么缚柳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮搪锣,結(jié)果婚禮上秋忙,老公的妹妹穿的比我還像新娘。我一直安慰自己构舟,他們只是感情好灰追,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般弹澎。 火紅的嫁衣襯著肌膚如雪朴下。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天苦蒿,我揣著相機(jī)與錄音殴胧,去河邊找鬼。 笑死佩迟,一個(gè)胖子當(dāng)著我的面吹牛团滥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播报强,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼灸姊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了秉溉?” 一聲冷哼從身側(cè)響起力惯,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎召嘶,沒(méi)想到半個(gè)月后父晶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苍蔬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年诱建,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碟绑。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俺猿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出格仲,到底是詐尸還是另有隱情押袍,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布凯肋,位于F島的核電站谊惭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侮东。R本人自食惡果不足惜圈盔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望悄雅。 院中可真熱鬧驱敲,春花似錦、人聲如沸宽闲。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至娩梨,卻和暖如春沿腰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狈定。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工颂龙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掸冤。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓厘托,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親稿湿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铅匹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容