阿里規(guī)約異常和日志

異常日志

(一) 異常處理

異常接口及類結(jié)構(gòu)
  1. 【強制】Java 類庫中定義的一類 RuntimeException 可以通過預(yù)先檢查進(jìn)行規(guī)避床未,而不應(yīng)該通過 catch 來處理塑娇,比如:IndexOutOfBoundsException垢啼,NullPointerException 等等业稼。
  正例:if (obj != null) {...} 
  反例:try { obj.method() } catch (NullPointerException e) {...} 
  理解:判空是一個永恒的話題,只要你不確定變量是否為空氢惋,都應(yīng)該判空洞翩,否則后患無窮。另外焰望,catch語句塊中不要寫業(yè)務(wù)邏輯骚亿,就打印或拋出異常日志就可以了,當(dāng)然這可能只對開發(fā)階段有用熊赖,最好采用錯誤代碼結(jié)合輸出日志進(jìn)行處理来屠。
  1. 【強制】異常不要用來做流程控制,條件控制震鹉,因為異常的處理效率比條件分支低俱笛。
理解:禁止使用異常來封裝業(yè)務(wù)邏輯,業(yè)務(wù)異常應(yīng)該用錯誤碼來表示传趾,系統(tǒng)異常則使用Java原生異常迎膜。
      異常處理是通過異常表查詢來實現(xiàn)的,肯定沒有跳轉(zhuǎn)語句性能高墨缘。
  1. 【強制】對大段代碼進(jìn)行 try-catch星虹,這是不負(fù)責(zé)任的表現(xiàn)零抬。catch 時請分清穩(wěn)定代碼和非穩(wěn) 定代碼镊讼,穩(wěn)定代碼指的是無論如何不會出錯的代碼宽涌。對于非穩(wěn)定代碼的 catch 盡可能進(jìn)行區(qū)分異常類型,再做對應(yīng)的異常處理蝶棋。
理解:不能簡單的catch Throwable卸亮,然后打印日志,這是不負(fù)責(zé)任的表現(xiàn)玩裙,應(yīng)該有針對的抓住和處理異常兼贸。
  1. 【強制】捕獲異常是為了處理它,不要捕獲了卻什么都不處理而拋棄之吃溅,如果不想處理它溶诞,請將該異常拋給它的調(diào)用者。最外層的業(yè)務(wù)使用者决侈,必須處理異常螺垢,將其轉(zhuǎn)化為用戶可以理解的內(nèi)容。
理解:我們不能讓客戶看到的是Java原生異常碼赖歌。
  1. 【強制】有 try 塊放到了事務(wù)代碼中枉圃,catch 異常后,如果需要回滾事務(wù)庐冯,一定要注意手動回滾事務(wù)孽亲。
理解:框架我們一般基本采用聲明式事務(wù),出現(xiàn)異常需要回滾的情況展父,建議繼續(xù)拋出異常讓聲明式事務(wù)自動回滾返劲,不建議代碼中手工控制事務(wù)。
  1. 【強制】finally 塊必須對資源對象栖茉、流對象進(jìn)行關(guān)閉旭等,有異常也要做 try-catch。
說明:如果 JDK7 及以上衡载,可以使用 try-with-resources 方式搔耕。 
理解:資源對象如數(shù)據(jù)庫連接,流對象如文件流痰娱,關(guān)于try catch finally的使用弃榨。
     1、不管有沒有異常梨睁,finally中的代碼都會執(zhí)行
     2鲸睛、當(dāng)try、catch中有return時坡贺,finally中的代碼依然會繼續(xù)執(zhí)行
     3官辈、finally是在return后面的表達(dá)式運算之后執(zhí)行的箱舞,此時并沒有返回運算之后的值,而是把值保存起來拳亿,不管finally對該值做任何的改變晴股,返回的值都不會改變,依然返回保存起來的值肺魁。也就是說方法的返回值是在finally運算之前就確定了的电湘。
     4、finally代碼中最好不要包含return鹅经,程序會提前退出寂呛,也就是說返回的值不是try或catch中的值
舉例:     ?
     情況1:try{}catch{}finally{} return;
      程序正常進(jìn)行
     情況2:try{return;}catch{}finally{} return;
      無異常:a、先執(zhí)行try中的語句瘾晃,b贷痪、然后執(zhí)行finally中的語句,c蹦误、最后執(zhí)行try中的return
      有異常:返回值是finally后面的return語句
     情況3:try{} catch{return;} finally{} return;
      無異常:執(zhí)行finally中的代碼塊劫拢,然后執(zhí)行return語句
      有異常:a、先執(zhí)行catch中的代碼塊 b胖缤、然后執(zhí)行finally中的語句尚镰,c、最后執(zhí)行catch中的return哪廓,finally后面的return不會被執(zhí)行
     情況4:try{return;} catch{} finally{return;}
      無異常:a狗唉、先執(zhí)行try中的語句,包括return后面的表達(dá)式涡真,b分俯、然后執(zhí)行finally中的語句,c哆料、最后執(zhí)行finally中的return 
      有異常:返回的值是finally中return后面的表達(dá)式的值缸剪,因為finally中有return語句,所以會提前退出东亦。
     情況5:try{} catch{return} finally{return};
      先執(zhí)行try中的代碼塊
      無異常:執(zhí)行finally中的代碼塊杏节,然后執(zhí)行finally中的return
      有異常:a、先執(zhí)行catch中的語句典阵,包括return后面的表達(dá)式奋渔,b、然后執(zhí)行finally中的語句壮啊,c嫉鲸、最后執(zhí)行finally中的return,因為finally中有return語句歹啼,所以會提前退出
     情況6:try{return;} catch{return;} finally{return;}
      先執(zhí)行try中的代碼塊玄渗,包括return后面的表達(dá)式
      無異常:執(zhí)行finally中的代碼塊座菠,然后執(zhí)行finally中的return
      有異常:a、先執(zhí)行catch中的語句藤树,包括return后面的表達(dá)式浴滴,b、然后執(zhí)行finally中的語句也榄,c巡莹、最后執(zhí)行finally中的return司志,因為finally中有return語句甜紫,所以會提前退出。 ?
     JDK7之前骂远,通常的try  catch finally用法 
     public static void main(String[] args) {
      FileInputStream inputStream = null;
      try {
      inputStream = new FileInputStream(new File("test"));
      System.out.println(inputStream.read());
      } catch (IOException e) {
      throw new RuntimeException(e.getMessage(), e);
      } finally {
      if (inputStream != null) {
      try {
      inputStream.close();
      } catch (IOException e) {
      throw new RuntimeException(e.getMessage(), e);
      }
      }
      }
     }
     ?
     JDk7 try-with-resources
     public static void main(String[] args) {
      try (FileInputStream inputStream = new FileInputStream(new File("test"))) {
      System.out.println(inputStream.read());
      } catch (IOException e) {
      throw new RuntimeException(e.getMessage(), e);
      }
     }
  1. 【強制】不能在 finally 塊中使用 return囚霸,finally 塊中的 return 返回后方法結(jié)束執(zhí)行,不會再執(zhí)行 try 塊中的 return 語句激才。
    理解:見6的詳細(xì)分析拓型。
  2. 【強制】捕獲異常與拋異常,必須是完全匹配瘸恼,或者捕獲異常是拋異常的父類劣挫。
說明:如果預(yù)期對方拋的是繡球,實際接到的是鉛球东帅,就會產(chǎn)生意外情況压固。
理解:異常處理后,讓異常變得更小靠闭,而不是變大帐我,大而化小,小而化了愧膀。
  1. 【推薦】方法的返回值可以為 null拦键,不強制返回空集合,或者空對象等檩淋,必須添加注釋充分說明什么情況下會返回 null 值芬为。調(diào)用方需要進(jìn)行 null 判斷防止 NPE 問題。
說明:本手冊明確防止 NPE 是調(diào)用者的責(zé)任蟀悦。即使被調(diào)用方法返回空集合或者空對象媚朦,對調(diào)用者來說,也并非高枕無憂熬芜,必須考慮到遠(yuǎn)程調(diào)用失敗莲镣、序列化失敗、運行時異常等場景返回 null 的情況涎拉。
理解:只要不確定的變量瑞侮,一定要判空的圆,別自找麻煩。
  1. 【推薦】防止 NPE半火,是程序員的基本修養(yǎng)越妈,注意 NPE 產(chǎn)生的場景:
1)返回類型為基本數(shù)據(jù)類型,return 包裝數(shù)據(jù)類型的對象時钮糖,自動拆箱有可能產(chǎn)生 NPE梅掠。 
     反例:public int f() { return Integer 對象}, 如果為 null店归,自動解箱拋 NPE
    2) 數(shù)據(jù)庫的查詢結(jié)果可能為 null阎抒。  
    3) 集合里的元素即使 isNotEmpty,取出的數(shù)據(jù)元素也可能為 null消痛。  
    4) 遠(yuǎn)程調(diào)用返回對象時且叁,一律要求進(jìn)行空指針判斷,防止 NPE秩伞。  
    5) 對于 Session 中獲取的數(shù)據(jù)逞带,建議 NPE 檢查,避免空指針纱新。  
    6) 級聯(lián)調(diào)用 obj.getA().getB().getC()展氓;一連串調(diào)用,易產(chǎn)生 NPE脸爱。 
    正例:使用 JDK8 的 Optional 類來防止 NPE 問題遇汞。
    理解:自J2SE 5.0開始提供的基本數(shù)據(jù)類型的自動裝箱(autoboxing)、拆箱(unboxing)功能阅羹。
    何為自動裝箱:
    當(dāng)我們創(chuàng)建一個Integer對象時勺疼,卻可以這樣:
     Integer i = 100; (注意:不是 int i = 100; )
    實際上,執(zhí)行上面那句代碼的時候捏鱼,系統(tǒng)為我們執(zhí)行了:Integer i = new Integer(100); 此即基本數(shù)據(jù)類型的自動裝箱功能执庐。
    ?
    何為自動拆箱
    自動拆箱(unboxing),也就是將對象中的基本數(shù)據(jù)從對象中自動取出导梆。如下可實現(xiàn)自動拆箱:
    Integer integer=100;
    int flag=integer;  //該語句即實現(xiàn)了自動拆箱轨淌。
    ?
    JDK8 的 Optional 類來防止 NPE詳見如下鏈接。

==Java8 Optional 的正確使用姿勢==

  1. 【推薦】定義時區(qū)分 unchecked / checked 異常看尼,避免直接拋出 new RuntimeException()递鹉, 更不允許拋出 Exception 或者 Throwable,應(yīng)使用有業(yè)務(wù)含義的自定義異常藏斩。推薦業(yè)界已定義過的自定義異常躏结,如:DAOException / ServiceException 等。
理解:將派生于Error或者RuntimeException的異常稱為unchecked異常狰域,所有其他的異常稱為checked異常媳拴。
     盡量不要在拋出異常時throw new RuntimeException("xxxx"); 應(yīng)該使用具備業(yè)務(wù)含義的自定義異常類黄橘,這樣做可以在捕獲異常時提供方便
     絕對不要在拋出異常時throw new Exception("xxx")或throw new Throwable("xxx"),這樣做不僅僅是屏蔽了異常本身的業(yè)務(wù)含義屈溉,同時也屏蔽了異常的分類(checked/unchecked)塞关,甚至連Exception和Error的區(qū)別也屏蔽了
     異常處理機(jī)制見如下鏈接。

==Java異匙咏恚控制機(jī)制和異常處理原則==

  1. 【參考】在代碼中使用“拋異撤”還是“返回錯誤碼”,對于公司外的 http/api 開放接口必須 使用“錯誤碼”线梗;而應(yīng)用內(nèi)部推薦異常拋出椰于;跨應(yīng)用間 RPC 調(diào)用優(yōu)先考慮使用 Result 方式,封 裝 isSuccess()方法缠导、“錯誤碼”廉羔、“錯誤簡短信息”溉痢。
說明:關(guān)于 RPC 方法返回方式使用 Result 方式的理由:  
     1)使用拋異常返回方式僻造,調(diào)用方如果沒有捕獲到就會產(chǎn)生運行時錯誤。  
     2)如果不加棧信息孩饼,只是 new 自定義異常髓削,加入自己的理解的 error message,對于調(diào)用端解決問題的幫助不會太多镀娶。如果加了棧信息立膛,在頻繁調(diào)用出錯的情況下,數(shù)據(jù)序列化和傳輸?shù)男阅軗p耗也是問題梯码。 
理解:業(yè)務(wù)異常使用Result模式宝泵,系統(tǒng)異常用Java原生異常。
     RPC建議使用Result模式轩娶,不想讓一個異常在系統(tǒng)間跳來跳去的儿奶,異常是包含調(diào)用棧的。
  1. 【參考】避免出現(xiàn)重復(fù)的代碼(Don’t Repeat Yourself)鳄抒,即 DRY 原則闯捎。
說明:隨意復(fù)制和粘貼代碼,必然會導(dǎo)致代碼的重復(fù)许溅,在以后需要修改時瓤鼻,需要修改所有的副本,容易遺漏贤重。必要時抽取共性方法茬祷,或者抽象公共類,甚至是組件化并蝗。 
正例:一個類中有多個 public 方法祭犯,都需要進(jìn)行數(shù)行相同的參數(shù)校驗操作耐朴,這個時候請抽取: private boolean checkParam(DTO dto) {...}  
理解:DRY原則對于優(yōu)化和維護(hù)程序都很有用處盹憎。這一條放這里有點莫名其妙筛峭。

(二) 日志規(guī)約

  1. 【強制】應(yīng)用中不可直接使用日志系統(tǒng)(Log4j、Logback)中的 API陪每,而應(yīng)依賴使用日志框架 SLF4J 中的 API影晓,使用門面模式的日志框架,有利于維護(hù)和各個類的日志處理方式統(tǒng)一檩禾。
     import org.slf4j.Logger;  
     import org.slf4j.LoggerFactory;  
     private static final Logger logger = LoggerFactory.getLogger(Abc.class); 
     理解:設(shè)計模式分為三大類:
     創(chuàng)建型模式挂签,共五種:工廠方法模式、抽象工廠模式盼产、單例模式饵婆、建造者模式、原型模式戏售。
     結(jié)構(gòu)型模式侨核,共七種:適配器模式、裝飾器模式灌灾、代理模式搓译、外觀模式、橋接模式锋喜、組合模式些己、享元模式。
     行為型模式嘿般,共十一種:策略模式段标、模板方法模式、觀察者模式炉奴、迭代子模式逼庞、責(zé)任鏈模式、命令模式盆佣、備忘錄模式往堡、狀態(tài)模式、訪問者模式共耍、中介者模式虑灰、解釋器模式。
     所謂門面模式(外觀模式)是對象的結(jié)構(gòu)模式痹兜,外部與一個子系統(tǒng)的通信必須通過一個統(tǒng)一的門面對象進(jìn)行穆咐。門面模式提供一個高層次的接口,使得子系統(tǒng)更易于使用。門面模式實例如下:

門面模式

  1. 【強制】日志文件推薦至少保存 15 天对湃,因為有些異常具備以“周”為頻次發(fā)生的特點崖叫。
理解:日志文件也不要保存太久,日志的增長很快拍柒,如果不及時處理(最好能自動化處理)心傀,就會導(dǎo)致系統(tǒng)不可以用,譬如nginx做負(fù)載均衡拆讯,日志如果達(dá)到70%脂男,會嚴(yán)重影響訪問性能。處理方法參見:

nginx訪問日志文件過大導(dǎo)致服務(wù)器性能降低解決方法

  1. 【強制】應(yīng)用中的擴(kuò)展日志(如打點(數(shù)據(jù)統(tǒng)計的日志稱為打點日志)种呐、臨時監(jiān)控宰翅、訪問日志等)命名方式:
 appName_logType_logName.log。
     logType:日志類型爽室,推薦分類有 stats/desc/monitor/visit 等汁讼;
     logName:日志描述。
     這種命名的好處:通過文件名就可知道日志文件屬于什么應(yīng)用阔墩,什么類型嘿架,什么目的,也有利于歸類查找戈擒。
正例:mppserver 應(yīng)用中單獨監(jiān)控時區(qū)轉(zhuǎn)換異常眶明,如:mppserver_monitor_timeZoneConvert.log 
說明:推薦對日志進(jìn)行分類,如將錯誤日志和業(yè)務(wù)日志分開存放筐高,便于開發(fā)人員查看,也便于通過日志對系統(tǒng)進(jìn)行及時監(jiān)控丑瞧。
  1. 【強制】對 trace/debug/info 級別的日志輸出柑土,必須使用條件輸出形式或者使用占位符的方 式。
說明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 
     如果日志級別是 warn绊汹,上述日志不會打印稽屏,但是會執(zhí)行字符串拼接操作,如果 symbol 是對象西乖, 會執(zhí)行 toString()方法狐榔,浪費了系統(tǒng)資源,執(zhí)行了上述操作获雕,最終日志卻沒有打印薄腻。 
正例:(條件) if (logger.isDebugEnabled()) {    
      logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 
      } 
正例:(占位符) logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
理解:Log4j建議只使用四個級別,優(yōu)先級從高到低分別是 ERROR届案、WARN庵楷、INFO、DEBUG。通過在這里定義的級別尽纽,您可以控制到應(yīng)用程序中相應(yīng)級別的日志信息的開關(guān)咐蚯。比如在這里定義了INFO級別,則應(yīng)用程序中所有DEBUG級別的日志信息將不被打印出來弄贿,也是說大于等于的級別的日志才輸出春锋。
  1. 【強制】避免重復(fù)打印日志,浪費磁盤空間差凹,務(wù)必在 log4j.xml 中設(shè)置 additivity=false看疙。
正例:<logger name="com.taobao.dubbo.config" additivity="false">  
理解:log4j配置詳細(xì)說明參見:

log4j基本配置及日志級別配置詳解

  1. 【強制】異常信息應(yīng)該包括兩類信息:案發(fā)現(xiàn)場信息和異常堆棧信息。如果不處理直奋,那么通過 關(guān)鍵字 throws 往上拋出能庆。
正例:logger.error(各類參數(shù)或者對象 toString + "_" + e.getMessage(), e); 
理解:打印日志一定要包含環(huán)境
  1. 【推薦】謹(jǐn)慎地記錄日志。生產(chǎn)環(huán)境禁止輸出 debug 日志脚线;有選擇地輸出 info 日志搁胆;如果使用 warn 來記錄剛上線時的業(yè)務(wù)行為信息,一定要注意日志輸出量的問題邮绿,避免把服務(wù)器磁盤撐爆渠旁,并記得及時刪除這些觀察日志。
說明:大量地輸出無效日志船逮,不利于系統(tǒng)性能提升顾腊,也不利于快速定位錯誤點。記錄日志時請思考:這些日志真的有人看嗎挖胃?看到這條日志你能做什么杂靶?能不能給問題排查帶來好處? 
理解:打印日志只需要打印核心內(nèi)容酱鸭,不要隨便就把對象json序列化打印出來吗垮,如果是列表會很大。
  1. 【參考】可以使用 warn 日志級別來記錄用戶輸入?yún)?shù)錯誤的情況凹髓,避免用戶投訴時烁登,無所適從。注意日志輸出的級別蔚舀,error 級別只記錄系統(tǒng)邏輯出錯饵沧、異常等重要的錯誤信息。如非必 要赌躺,請不要在此場景打出 error 級別狼牺。
理解:合理利用warn級別日志,error級別日志最重要寿谴,理想情況下生產(chǎn)上產(chǎn)生的error和warn日志開發(fā)要定期的梳理锁右。
  1. 【推薦】盡量用英文來描述日志錯誤信息,如果日志中的錯誤信息用英文描述不清楚的話使用 中文描述即可,否則容易產(chǎn)生歧義咏瑟。國際化團(tuán)隊或海外部署的服務(wù)器由于字符集問題拂到,【強制】 使用全英文來注釋和描述日志錯誤信息。

討論

異常處理和日志處理在combiz上可能需要改進(jìn)一下码泞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兄旬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子余寥,更是在濱河造成了極大的恐慌领铐,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宋舷,死亡現(xiàn)場離奇詭異绪撵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)祝蝠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門音诈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绎狭,你說我怎么就攤上這事细溅。” “怎么了儡嘶?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵喇聊,是天一觀的道長。 經(jīng)常有香客問我蹦狂,道長誓篱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任鸥咖,我火速辦了婚禮燕鸽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啼辣。我一直安慰自己,他們只是感情好御滩,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布鸥拧。 她就那樣靜靜地躺著,像睡著了一般削解。 火紅的嫁衣襯著肌膚如雪富弦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天氛驮,我揣著相機(jī)與錄音腕柜,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛盏缤,可吹牛的內(nèi)容都是我干的砰蠢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼唉铜,長吁一口氣:“原來是場噩夢啊……” “哼台舱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起潭流,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤竞惋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后灰嫉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拆宛,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年讼撒,在試婚紗的時候發(fā)現(xiàn)自己被綠了浑厚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡椿肩,死狀恐怖瞻颂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情郑象,我是刑警寧澤贡这,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站厂榛,受9級特大地震影響盖矫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜击奶,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一辈双、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧柜砾,春花似錦湃望、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至担映,卻和暖如春废士,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蝇完。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工官硝, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留矗蕊,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓氢架,卻偏偏與公主長得像傻咖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子达箍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

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