語(yǔ)義化版本控制

語(yǔ)義化版本 2.0.0

摘要

版本格式:主版本號(hào).次版本號(hào).修訂號(hào),版本號(hào)遞增規(guī)則如下:

  • 1.主版本號(hào):當(dāng)你做了不兼容的 API 修改献烦,
  • 2.次版本號(hào):當(dāng)你做了向下兼容的功能性新增蚕冬,
  • 3.修訂號(hào):當(dāng)你做了向下兼容的問(wèn)題修正橘洞。

先行版本號(hào)及版本編譯信息可以加到“主版本號(hào).次版本號(hào).修訂號(hào)”的后面夹孔,作為延伸。

簡(jiǎn)介

在軟件管理的領(lǐng)域里存在著被稱作“依賴地獄”的死亡之谷琅轧,系統(tǒng)規(guī)模越大伍绳,加入的套件越多,你就越有可能在未來(lái)的某一天發(fā)現(xiàn)自己已深陷絕望之中乍桂。

在依賴高的系統(tǒng)中發(fā)布新版本套件可能很快會(huì)成為惡夢(mèng)冲杀。如果依賴關(guān)系過(guò)高,可能面臨版本控制被鎖死的風(fēng)險(xiǎn)(必須對(duì)每一個(gè)相依套件改版才能完成某次升級(jí))睹酌。而如果依賴關(guān)系過(guò)于松散权谁,又將無(wú)法避免版本的混亂(假設(shè)兼容于未來(lái)的多個(gè)版本已超出了合理數(shù)量)。當(dāng)你專案的進(jìn)展因?yàn)榘姹鞠嘁辣绘i死或版本混亂變得不夠簡(jiǎn)便和可靠忍疾,就意味著你正處于依賴地獄之中闯传。

作為這個(gè)問(wèn)題的解決方案之一,我提議用一組簡(jiǎn)單的規(guī)則及條件來(lái)約束版本號(hào)的配置和增長(zhǎng)卤妒。這些規(guī)則是根據(jù)(但不局限于)已經(jīng)被各種封閉甥绿、開放源碼軟件所廣泛使用的慣例所設(shè)計(jì)。為了讓這套理論運(yùn)作则披,你必須先有定義好的公共 API 共缕。這可以透過(guò)文件定義或代碼強(qiáng)制要求來(lái)實(shí)現(xiàn)。無(wú)論如何士复,這套 API 的清楚明了是十分重要的图谷。一旦你定義了公共 API翩活,你就可以透過(guò)修改相應(yīng)的版本號(hào)來(lái)向大家說(shuō)明你的修改”愎螅考慮使用這樣的版本號(hào)格式:XYZ (主版本號(hào).次版本號(hào).修訂號(hào))修復(fù)問(wèn)題但不影響API 時(shí)菠镇,遞增修訂號(hào);API 保持向下兼容的新增及修改時(shí)承璃,遞增次版本號(hào)利耍;進(jìn)行不向下兼容的修改時(shí),遞增主版本號(hào)盔粹。

我稱這套系統(tǒng)為“語(yǔ)義化的版本控制”隘梨,在這套約定下,版本號(hào)及其更新方式包含了相鄰版本間的底層代碼和修改內(nèi)容的信息舷嗡。

語(yǔ)義化版本控制規(guī)范(SemVer)

以下關(guān)鍵詞 MUST轴猎、MUST NOT、REQUIRED进萄、SHALL捻脖、SHALL NOT、SHOULD垮斯、SHOULD NOT郎仆、 RECOMMENDED、MAY兜蠕、OPTIONAL 依照 RFC 2119 的敘述解讀。(譯注:為了保持語(yǔ)句順暢抛寝, 以下文件遇到的關(guān)鍵詞將依照整句語(yǔ)義進(jìn)行翻譯熊杨,在此先不進(jìn)行個(gè)別翻譯。)

  • 1.使用語(yǔ)義化版本控制的軟件“必須 MUST ”定義公共 API盗舰。該 API 可以在代碼中被定義或出現(xiàn)于嚴(yán)謹(jǐn)?shù)奈募?nèi)晶府。無(wú)論何種形式都應(yīng)該力求精確且完整。

  • 2.標(biāo)準(zhǔn)的版本號(hào)“必須 MUST ”采用 XYZ 的格式钻趋,其中 X川陆、Y 和 Z 為非負(fù)的整數(shù),且“禁止 MUST NOT”在數(shù)字前方補(bǔ)零蛮位。X 是主版本號(hào)较沪、Y 是次版本號(hào)、而 Z 為修訂號(hào)失仁。每個(gè)元素“必須 MUST ”以數(shù)值來(lái)遞增尸曼。例如:1.9.1 -> 1.10.0 -> 1.11.0。

  • 3 .標(biāo)記版本號(hào)的軟件發(fā)行后萄焦,“禁止 MUST NOT ”改變?cè)摪姹拒浖膬?nèi)容控轿。任何修改都“必須 MUST ”以新版本發(fā)行。

  • 4.主版本號(hào)為零(0.y.z)的軟件處于開發(fā)初始階段,一切都可能隨時(shí)被改變茬射。這樣的公共 API 不應(yīng)該被視為穩(wěn)定版鹦蠕。

  • 5.'1.0.0 '的版本號(hào)用于界定公共 API 的形成。這一版本之后所有的版本號(hào)更新都基于公共 API 及其修改內(nèi)容在抛。

  • 6.修訂號(hào) Z(x.y.Z |
    x > 0)“必須 MUST ”在只做了向下兼容的修正時(shí)才遞增片部。這里的修正指的是針對(duì)不正確結(jié)果而進(jìn)行的內(nèi)部修改。

  • 7.次版本號(hào) Y(x.Y.z |
    x > 0)“必須 MUST ”在有向下兼容的新功能出現(xiàn)時(shí)遞增霜定。在任何公共 API 的功能被標(biāo)記為棄用時(shí)也“必須 MUST ”遞增档悠。也“可以 MAY ”在內(nèi)部程序有大量新功能或改進(jìn)被加入時(shí)遞增,其中“可以 MAY ”包括修訂級(jí)別的改變望浩。每當(dāng)次版本號(hào)遞增時(shí)辖所,修訂號(hào)“必須 MUST ”歸零。

  • 8.主版本號(hào) X(X.y.z |
    X > 0)“必須 MUST ”在有任何不兼容的修改被加入公共 API 時(shí)遞增磨德。其中“可以 MAY ”包括次版本號(hào)及修訂級(jí)別的改變缘回。每當(dāng)主版本號(hào)遞增時(shí),次版本號(hào)和修訂號(hào)“必須 MUST ”歸零典挑。

  • 9.先行版本號(hào)“可以 MAY ”被標(biāo)注在修訂版之后酥宴,先加上一個(gè)連接號(hào)再加上一連串以句點(diǎn)分隔的標(biāo)識(shí)符號(hào)來(lái)修飾。標(biāo)識(shí)符號(hào)“必須 MUST ”由 ASCII 碼的英數(shù)字和連接號(hào) [0-9A-Za-z-] 組成您觉,且“禁止 MUST NOT ”留白拙寡。數(shù)字型的標(biāo)識(shí)符號(hào)“禁止 MUST NOT ”在前方補(bǔ)零。先行版的優(yōu)先級(jí)低于相關(guān)聯(lián)的標(biāo)準(zhǔn)版本琳水。被標(biāo)上先行版本號(hào)則表示這個(gè)版本并非穩(wěn)定而且可能無(wú)法達(dá)到兼容的需求肆糕。范例:1.0.0-alpha、1.0.0-alpha.1在孝、1.0.0-0.3.7诚啃、1.0.0-x.7.z.92。

  • 10.版本編譯信息“可以 MAY ”被標(biāo)注在修訂版或先行版本號(hào)之后私沮,先加上一個(gè)加號(hào)再加上一連串以句點(diǎn)分隔的標(biāo)識(shí)符號(hào)來(lái)修飾始赎。標(biāo)識(shí)符號(hào)“必須 MUST ”由 ASCII 的英數(shù)字和連接號(hào) [0-9A-Za-z-] 組成,且“禁止 MUST NOT ”留白仔燕。當(dāng)判斷版本的優(yōu)先層級(jí)時(shí)造垛,版本編譯信息“可 SHOULD ”被忽略。因此當(dāng)兩個(gè)版本只有在版本編譯信息有差別時(shí)涨享,屬于相同的優(yōu)先層級(jí)筋搏。范例:1.0.0-alpha+001、1.0.0+20130313144700厕隧、1.0.0-beta+exp.sha.5114f85奔脐。

  • 11.版本的優(yōu)先層級(jí)指的是不同版本在排序時(shí)如何比較俄周。判斷優(yōu)先層級(jí)時(shí),“必須 MUST ”把版本依序拆分為主版本號(hào)髓迎、次版本號(hào)峦朗、修訂號(hào)及先行版本號(hào)后進(jìn)行比較(版本編譯信息不在這份比較的列表中)。由左到右依序比較每個(gè)標(biāo)識(shí)符號(hào)排龄,第一個(gè)差異值用來(lái)決定優(yōu)先層級(jí):主版本號(hào)波势、次版本號(hào)及修訂號(hào)以數(shù)值比較,例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1橄维。當(dāng)主版本號(hào)尺铣、次版本號(hào)及修訂號(hào)都相同時(shí),改以優(yōu)先層級(jí)比較低的先行版本號(hào)決定争舞。例如:1.0.0-alpha < 1.0.0凛忿。有相同主版本號(hào)、次版本號(hào)及修訂號(hào)的兩個(gè)先行版本號(hào)竞川,其優(yōu)先層級(jí)“必須 MUST ”透過(guò)由左到右的每個(gè)被句點(diǎn)分隔的標(biāo)識(shí)符號(hào)來(lái)比較店溢,直到找到一個(gè)差異值后決定:只有數(shù)字的標(biāo)識(shí)符號(hào)以數(shù)值高低比較蜻韭,有字母或連接號(hào)時(shí)則逐字以 ASCII 的排序來(lái)比較衫樊。數(shù)字的標(biāo)識(shí)符號(hào)比非數(shù)字的標(biāo)識(shí)符號(hào)優(yōu)先層級(jí)低咐蚯。若開頭的標(biāo)識(shí)符號(hào)都相同時(shí)吃环,欄位比較多的先行版本號(hào)優(yōu)先層級(jí)比較高。范例:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0- rc.1 < 1.0.0允扇。

為什么要使用語(yǔ)義化的版本控制耳高?

這并不是一個(gè)新的或者革命性的想法癞蚕。實(shí)際上革砸,你可能已經(jīng)在做一些近似的事情了除秀。問(wèn)題在于只是“近似”還不夠。如果沒(méi)有某個(gè)正式的規(guī)范可循算利,版本號(hào)對(duì)于依賴的管理并無(wú)實(shí)質(zhì)意義。將上述的想法命名并給予清楚的定義泳姐,讓你對(duì)軟件使用者傳達(dá)意向變得容易效拭。一旦這些意向變得清楚,彈性(但又不會(huì)太彈性)的依賴規(guī)范就能達(dá)成胖秒。

舉個(gè)簡(jiǎn)單的例子就可以展示語(yǔ)義化的版本控制如何讓依賴地獄成為過(guò)去缎患。假設(shè)有個(gè)名為“救火車”的函式庫(kù),它需要另一個(gè)名為“梯子”并已經(jīng)有使用語(yǔ)義化版本控制的套件阎肝。當(dāng)救火車創(chuàng)建時(shí)挤渔,梯子的版本號(hào)為 3.1.0。因?yàn)榫然疖囀褂昧艘恍┌姹?3.1.0 所新增的功能风题, 你可以放心地指定相依于梯子的版本號(hào)大等于 3.1.0 但小于 4.0.0判导。這樣嫉父,當(dāng)梯子版本 3.1.1 和 3.2.0 發(fā)布時(shí),你可以將直接它們納入你的套件管理系統(tǒng)眼刃,因?yàn)樗鼈兡芘c原有相依的軟件兼容绕辖。

作為一位負(fù)責(zé)任的開發(fā)者,你理當(dāng)確保每次套件升級(jí)的運(yùn)作與版本號(hào)的表述一致±藓欤現(xiàn)實(shí)世界是復(fù)雜的仪际,我們除了提高警覺外能做的不多。你所能做的就是讓語(yǔ)義化的版本控制為你提供一個(gè)健全的方式來(lái)發(fā)行以及升級(jí)套件昵骤,而無(wú)需推出新的相依套件树碱,節(jié)省你的時(shí)間及煩惱。

如果你對(duì)此認(rèn)同变秦,希望立即開始使用語(yǔ)義化版本控制成榜,你只需聲明你的函式庫(kù)正在使用它并遵循這些規(guī)則就可以了。請(qǐng)?jiān)谀愕?README 文件中保留此頁(yè)連結(jié)伴栓,讓別人也知道這些規(guī)則并從中受益伦连。

FAQ

在 0.y.z 初始開發(fā)階段,我該如何進(jìn)行版本控制钳垮?
最簡(jiǎn)單的做法是以 0.1.0 作為你的初始化開發(fā)版本惑淳,并在后續(xù)的每次發(fā)行時(shí)遞增次版本號(hào)。

如何判斷發(fā)布 1.0.0 版本的時(shí)機(jī)饺窿?
當(dāng)你的軟件被用于正式環(huán)境歧焦,它應(yīng)該已經(jīng)達(dá)到了 1.0.0 版。如果你已經(jīng)有個(gè)穩(wěn)定的 API 被使用者依賴肚医,也會(huì)是 1.0.0 版绢馍。如果你很擔(dān)心向下兼容的問(wèn)題,也應(yīng)該算是 1.0.0 版了肠套。

這不會(huì)阻礙快速開發(fā)和迭代嗎舰涌?
主版本號(hào)為零的時(shí)候就是為了做快速開發(fā)。如果你每天都在改變 API你稚,那么你應(yīng)該仍在主版本號(hào)為零的階段(0.y.z)瓷耙,或是正在下個(gè)主版本的獨(dú)立開發(fā)分支中。

對(duì)于公共 API刁赖,若即使是最小但不向下兼容的改變都需要產(chǎn)生新的主版本號(hào)搁痛,豈不是很快就達(dá)到 42.0.0 版?
這是開發(fā)的責(zé)任感和前瞻性的問(wèn)題宇弛。不兼容的改變不應(yīng)該輕易被加入到有許多依賴代碼的軟件中鸡典。升級(jí)所付出的代價(jià)可能是巨大的。要遞增主版本號(hào)來(lái)發(fā)行不兼容的改版枪芒,意味著你必須為這些改變所帶來(lái)的影響深思熟慮彻况,并且評(píng)估所涉及的成本及效益比谁尸。

為整個(gè)公共 API 寫文件太費(fèi)事了!
為供他人使用的軟件編寫適當(dāng)?shù)奈募贫猓悄阕鳛橐幻麑I(yè)開發(fā)者應(yīng)盡的職責(zé)症汹。保持專案高效一個(gè)非常重要的部份是掌控軟件的復(fù)雜度,如果沒(méi)有人知道如何使用你的軟件或不知道哪些函數(shù)的調(diào)用是可靠的贷腕,要掌控復(fù)雜度會(huì)是困難的背镇。長(zhǎng)遠(yuǎn)來(lái)看,使用語(yǔ)義化版本控制以及對(duì)于公共 API 有良好規(guī)范的堅(jiān)持泽裳,可以讓每個(gè)人及每件事都運(yùn)行順暢瞒斩。

萬(wàn)一不小心把一個(gè)不兼容的改版當(dāng)成了次版本號(hào)發(fā)行了該怎么辦?
一旦發(fā)現(xiàn)自己破壞了語(yǔ)義化版本控制的規(guī)范涮总,就要修正這個(gè)問(wèn)題胸囱,并發(fā)行一個(gè)新的次版本號(hào)來(lái)更正這個(gè)問(wèn)題并且恢復(fù)向下兼容。即使是這種情況瀑梗,也不能去修改已發(fā)行的版本烹笔。可以的話抛丽,將有問(wèn)題的版本號(hào)記錄到文件中谤职,告訴使用者問(wèn)題所在,讓他們能夠意識(shí)到這是有問(wèn)題的版本亿鲜。

如果我更新了自己的依賴但沒(méi)有改變公共 API 該怎么辦允蜈?
由于沒(méi)有影響到公共 API,這可以被認(rèn)定是兼容的蒿柳。若某個(gè)軟件和你的套件有共同依賴饶套,則它會(huì)有自己的依賴規(guī)范,作者也會(huì)告知可能的沖突垒探。要判斷改版是屬于修訂等級(jí)或是次版等級(jí)妓蛮,是依據(jù)你更新的依賴關(guān)系是為了修復(fù)問(wèn)題或是加入新功能。對(duì)于后者圾叼,我經(jīng)常會(huì)預(yù)期伴隨著更多的代碼仔引,這顯然會(huì)是一個(gè)次版本號(hào)級(jí)別的遞增。

如果我變更了公共 API 但無(wú)意中未遵循版本號(hào)的改動(dòng)怎么辦呢褐奥?(意即在修訂等級(jí)的發(fā)布中,誤將重大且不兼容的改變加到代碼之中)
自行做最佳的判斷翘簇。如果你有龐大的使用者群在依照公共 API 的意圖而變更行為后會(huì)大受影響撬码,那么最好做一次主版本的發(fā)布,即使嚴(yán)格來(lái)說(shuō)這個(gè)修復(fù)僅是修訂等級(jí)的發(fā)布版保。記住呜笑, 語(yǔ)義化的版本控制就是透過(guò)版本號(hào)的改變來(lái)傳達(dá)意義夫否。若這些改變對(duì)你的使用者是重要的,那就透過(guò)版本號(hào)來(lái)向他們說(shuō)明叫胁。

我該如何處理即將棄用的功能凰慈?
棄用現(xiàn)存的功能是軟件開發(fā)中的家常便飯,也通常是向前發(fā)展所必須的驼鹅。當(dāng)你棄用部份公共 API 時(shí)微谓,你應(yīng)該做兩件事:(1)更新你的文件讓使用者知道這個(gè)改變,(2)在適當(dāng)?shù)臅r(shí)機(jī)將棄用的功能透過(guò)新的次版本號(hào)發(fā)布输钩。在新的主版本完全移除棄用功能前豺型,至少要有一個(gè)次版本包含這個(gè)棄用信息,這樣使用者才能平順地轉(zhuǎn)移到新版 API买乃。

語(yǔ)義化版本對(duì)于版本的字串長(zhǎng)度是否有限制呢姻氨?
沒(méi)有,請(qǐng)自行做適當(dāng)?shù)呐袛嗉粞椤Ee例來(lái)說(shuō)肴焊,長(zhǎng)到 255 個(gè)字元的版本已過(guò)度夸張。再者功戚,特定的系統(tǒng)對(duì)于字串長(zhǎng)度可能會(huì)有他們自己的限制娶眷。

關(guān)于

語(yǔ)義化版本控制的規(guī)范是由 Gravatars 創(chuàng)辦者兼 GitHub 共同創(chuàng)辦者 Tom Preston-Werner 所建立。
如果您有任何建議疫铜,請(qǐng)到 GitHub 上提出您的問(wèn)題茂浮。
參考出處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市壳咕,隨后出現(xiàn)的幾起案子席揽,更是在濱河造成了極大的恐慌,老刑警劉巖谓厘,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幌羞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡竟稳,警方通過(guò)查閱死者的電腦和手機(jī)属桦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)他爸,“玉大人聂宾,你說(shuō)我怎么就攤上這事≌矬裕” “怎么了系谐?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我纪他,道長(zhǎng)鄙煤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任茶袒,我火速辦了婚禮梯刚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘薪寓。我一直安慰自己亡资,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布预愤。 她就那樣靜靜地躺著沟于,像睡著了一般。 火紅的嫁衣襯著肌膚如雪植康。 梳的紋絲不亂的頭發(fā)上旷太,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音销睁,去河邊找鬼供璧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛冻记,可吹牛的內(nèi)容都是我干的睡毒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼冗栗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼演顾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起隅居,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤钠至,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后胎源,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棉钧,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年涕蚤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宪卿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡万栅,死狀恐怖佑钾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情烦粒,我是刑警寧澤次绘,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響邮偎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜义黎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一禾进、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧廉涕,春花似錦泻云、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至层释,卻和暖如春婆瓜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贡羔。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工廉白, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乖寒。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓猴蹂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親楣嘁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子磅轻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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