摘要:說明這篇文章是我第一次認(rèn)真閱讀阿里巴巴開發(fā)手冊終極版的筆記。說明本手冊明確防止是調(diào)用者的責(zé)任。一年半載后,那么單元測試幾乎處于廢棄狀態(tài)。好的單元測試能夠最大限度地規(guī)避線上故障。
說明
這篇文章是我第一次(認(rèn)真)閱讀《阿里巴巴 Java 開發(fā)手冊(終極版)》的筆記。手冊本身對規(guī)范的講解已經(jīng)非常詳細(xì)了,如果你已經(jīng)有一定的開發(fā)經(jīng)驗(yàn)并且有良好的編碼習(xí)慣和意識,會發(fā)現(xiàn)大部分規(guī)范是符合常識的。所以本文不會再去做重復(fù)的說明,只是對其中一些可能沒留意到的或者說不在(我的)常識之內(nèi)的一些規(guī)范進(jìn)行整理記錄。當(dāng)然每家公司都有自己的一套規(guī)范標(biāo)準(zhǔn),所以大家也沒必要過分追究。
其中或許會有遺漏或者理解錯誤,希望各位擔(dān)待提點(diǎn)。
重點(diǎn)我會用黑體標(biāo)注。
引用部分為《阿里巴巴 Java 開發(fā)手冊(終極版)》原文
更新時間:2017-10-17
插件ide插件已發(fā)布:《阿里巴巴Java開發(fā)手冊》IDEA插件與Eclipse插件使用指南
第一節(jié) 編程規(guī)約 1 命名規(guī)范8.【強(qiáng)制】POJO 類中布爾類型的變量,都不要加 is,否則部分框架解析會引起序列化錯誤。
反例:定義為基本數(shù)據(jù)類型 Boolean isDeleted;的屬性,它的方法也是 isDeleted(),RPC框架在反向解析的時候,“以為”對應(yīng)的屬性名稱是 deleted,導(dǎo)致屬性獲取不到,進(jìn)而拋出異常。
2 常量定義16.【參考】各層命名規(guī)約:
A) Service/DAO 層方法命名規(guī)約
1) 獲取單個對象的方法用 get 做前綴。
2) 獲取多個對象的方法用 list 做前綴。(我習(xí)慣寫成 getXxxList)
3) 獲取統(tǒng)計(jì)值的方法用 count 做前綴。
4) 插入的方法用 save/insert 做前綴。
5) 刪除的方法用 remove/delete 做前綴。
6) 修改的方法用 update 做前綴。
1.【強(qiáng)制】不允許任何魔法值(即未經(jīng)定義的常量)直接出現(xiàn)在代碼中。
反例:
String key = "Id#taobao_" + tradeId;
cache.put(key, value);
魔法值:是指在代碼中直接出現(xiàn)的數(shù)值,而只有在這個數(shù)值記述的那部分代碼中才能明確了解其含義。
也就是我們常說的[硬編碼]或者[寫死],這類代碼需要定義常量來明確其含義。
5.【強(qiáng)制】采用 4 個空格縮進(jìn),禁止使用 tab 字符。
說明:如果使用 tab 縮進(jìn),必須設(shè)置 1 個 tab 為 4 個空格。IDEA 設(shè)置 tab 為 4 個空格時,請勿勾選 Use tab character;而在 eclipse 中,必須勾選 insert spaces for tabs。
有些同學(xué)可能會對這一條不以為然。如果是協(xié)調(diào)開發(fā),兩個工程師的格式化規(guī)則不一致很可能A同學(xué)無意把B同學(xué)的代碼重新格式化并提交,導(dǎo)致后邊查看svn變更記錄時傻逼了。
7.【強(qiáng)制】單行字符數(shù)限制不超過 120 個,超出需要換行,換行時遵循如下原則:
1) 第二行相對第一行縮進(jìn) 4 個空格,從第三行開始,不再繼續(xù)縮進(jìn),參考示例。
2) 運(yùn)算符與下文一起換行。
3) 方法調(diào)用的點(diǎn)符號與下文一起換行。
4) 方法調(diào)用時,多個參數(shù),需要換行時,在逗號后進(jìn)行。
5) 在括號前不要換行,見反例。
120這個長度限制很有意思,如圖:
這個長度大概是15寸筆記本1080分辨率字體14號左右的最佳可視長度。當(dāng)然應(yīng)該也不一定非要這么精準(zhǔn)吧。。
7.【強(qiáng)制】所有的相同類型的包裝類對象之間值的比較,全部使用 equals 方法比較。
說明:對于 Integer var = ? 在-128 至 127 范圍內(nèi)的賦值,Integer 對象是在IntegerCache.cache 產(chǎn)生,會復(fù)用已有對象,這個區(qū)間內(nèi)的 Integer 值可以直接使用==進(jìn)行判斷,但是這個區(qū)間之外的所有數(shù)據(jù),都會在堆上產(chǎn)生,并不會復(fù)用已有對象,這是一個大坑,推薦使用 equals 方法進(jìn)行判斷。
12.【強(qiáng)制】POJO 類必須寫 toString 方法。使用 IDE 的中工具:source> generate toString時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString。
說明:在方法執(zhí)行拋出異常時,可以直接調(diào)用 POJO 的 toString()方法打印其屬性值,便于排查問題。
吐槽:"使用 IDE 的中工具" 碼字錯誤哦!
13.【推薦】使用索引訪問用 String 的 split 方法得到的數(shù)組時,需做最后一個分隔符后有無內(nèi)容的檢查,否則會有拋 IndexOutOfBoundsException 的風(fēng)險。
說明:
String str = "a,b,c,,";
String[] ary = str.split(",");
// 預(yù)期大于 3,結(jié)果是 3
System.out.println(ary.length);
最好的做法是對集合類型的變量本身進(jìn)行判空校驗(yàn)或者大小判斷,不要想當(dāng)然。
5 集合處理2.【強(qiáng)制】ArrayList的subList結(jié)果不可強(qiáng)轉(zhuǎn)成ArrayList,否則會拋出ClassCastException異常,即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList.
說明:subList 返回的是 ArrayList 的內(nèi)部類 SubList,并不是 ArrayList ,而是ArrayList 的一個視圖,對于 SubList 子列表的所有操作最終會反映到原列表上。
5.【強(qiáng)制】使用工具類Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時,不能使用其修改集合相關(guān)的方法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。
說明:asList 的返回對象是一個 Arrays 內(nèi)部類,并沒有實(shí)現(xiàn)集合的修改方法。Arrays.asList體現(xiàn)的是適配器模式,只是轉(zhuǎn)換接口,后臺的數(shù)據(jù)仍是數(shù)組。
String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);
第一種情況:list.add("yangguanbao"); 運(yùn)行時異常。
第二種情況:str[0] = "gujin"; 那么 list.get(0)也會隨之修改。
10.【推薦】使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進(jìn)行遍歷。
說明:keySet 其實(shí)是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對象,另一次是從 hashMap 中取出key 所對應(yīng)的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。
正例:values()返回的是 V 值集合,是一個 list 集合對象;keySet()返回的是 K 值集合,是一個 Set 集合對象;entrySet()返回的是 K-V 值組合集合。
java8 是個好東西~
6 并發(fā)處理5.【強(qiáng)制】SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為static,必須加鎖,或者使用 DateUtils 工具類。
正例:注意線程安全,使用 DateUtils。亦推薦如下處理:
private static final ThreadLocaldf = new ThreadLocal () {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
說明:如果是 JDK8 的應(yīng)用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。
再說一遍,java8 是個好東西!LocalDateTime相關(guān)API
附贈一個java.util.Date和LocalDateTime互轉(zhuǎn)的例子:
private static Date localDateTimeToUDate(LocalDateTime localDateTime) { ZoneId zone = ZoneId.systemDefault(); Instant instant = localDateTime.atZone(zone).toInstant(); return Date.from(instant); } private static LocalDateTime uDateToLocalDate(Date date) { Instant instant = date.toInstant(); ZoneId zone = ZoneId.systemDefault(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); return localDateTime; }
7 控制語句14.【參考】 HashMap 在容量不夠進(jìn)行 resize 時由于高并發(fā)可能出現(xiàn)死鏈,導(dǎo)致 CPU 飆升,在開發(fā)過程中可以使用其它數(shù)據(jù)結(jié)構(gòu)或加鎖來規(guī)避此風(fēng)險。
3.【推薦】表達(dá)異常的分支時,少用 if-else 方式,這種方式可以改寫成:
if (condition) {
...
return obj;
}
// 接著寫 else 的業(yè)務(wù)邏輯代碼;
說明:如果非得使用 if()...else if()...else...方式表達(dá)邏輯,【強(qiáng)制】避免后續(xù)代碼維護(hù)困難,請勿超過 3 層。
正例:超過 3 層的 if-else 的邏輯判斷代碼可以使用衛(wèi)語句、策略模式、狀態(tài)模式等來實(shí)現(xiàn)...
我們公司codeReview時經(jīng)常看到有些同學(xué)的代碼是if(){}else if(){} else if(){}else{} 除了看上去low更主要的原因是過多的大括號層級不便于閱讀很容易搞混,尤其是跳出代碼塊的時候,連續(xù)幾個}}}基本就不知道跳到哪了徹底懵逼,還得折疊代碼或者滾上去重新回憶一下。
6.【推薦】接口入?yún)⒈Wo(hù),這種場景常見的是用于做批量操作的接口。
解釋一下,接口入?yún)⒈Wo(hù)就是對入?yún)⑦M(jìn)行校驗(yàn),包括允許的最大值或者其他范圍或邊界。防止請求大量數(shù)據(jù)導(dǎo)致接口“爆炸”。比如限制返回?cái)?shù)據(jù)最大條數(shù),超過限制直接return或者拋異常。
8 注釋規(guī)約感覺沒啥好說的。。
9 其它1.【強(qiáng)制】在使用正則表達(dá)式時,利用好其預(yù)編譯功能,可以有效加快正則匹配速度。
說明:不要在方法體內(nèi)定義:Pattern pattern = Pattern.compile(規(guī)則);
就是說定義成全局變量。
第二節(jié) 異常日志 1 異常處理3.【強(qiáng)制】對大段代碼進(jìn)行try-catch,這是不負(fù)責(zé)任的表現(xiàn)。catch時請分清穩(wěn)定代碼和非穩(wěn)定代碼,穩(wěn)定代碼指的是無論如何不會出錯的代碼。對于非穩(wěn)定代碼的catch盡可能進(jìn)行區(qū)分異常類型,再做對應(yīng)的異常處理。
9.【推薦】方法的返回值可以為null,不強(qiáng)制返回空集合,或者空對象等,必須添加注釋充分說明什么情況下會返回null值。調(diào)用方需要進(jìn)行null判斷防止NPE問題。說明:本手冊明確防止NPE是調(diào)用者的責(zé)任。即使被調(diào)用方法返回空集合或者空對象,對調(diào)用者來說,也并非高枕無憂,必須考慮到遠(yuǎn)程調(diào)用失敗、序列化失敗、運(yùn)行時異常等場景返回null的情況。
需要說明的是是否可以返回null是需要根據(jù)接口約定來判斷的,如果明確的返回對象的結(jié)構(gòu)類型,一定要返回這個對象,但他的屬性值可以是null,比如page對象:{data:null,pageNum:0,count:0}
2 日志規(guī)約第三節(jié) 單元測試4.【強(qiáng)制】對trace/debug/info級別的日志輸出,必須使用條件輸出形式或者使用占位符的方式。
說明:logger.debug("Processingtradewithid: " + id+ "andsymbol: " + symbol);如果日志級別是warn,上述日志不會打印,但是會執(zhí)行字符串拼接操作,如果symbol是對象,會執(zhí)行toString()方法,浪費(fèi)了系統(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);
4.【強(qiáng)制】單元測試是可以重復(fù)執(zhí)行的,不能受到外界環(huán)境的影響。
說明:單元測試通常會被放到持續(xù)集成中,每次有代碼check in時單元測試都會被執(zhí)行。如果單測對外部環(huán)境(網(wǎng)絡(luò)、服務(wù)、中間件等)有依賴,容易導(dǎo)致持續(xù)集成機(jī)制的不可用。
正例:為了不受外界環(huán)境影響,要求設(shè)計(jì)代碼時就把SUT的依賴改成注入,在測試時用spring這樣的DI框架注入一個本地(內(nèi)存)實(shí)現(xiàn)或者M(jìn)ock實(shí)現(xiàn)。
15.【參考】為了更方便地進(jìn)行單元測試,業(yè)務(wù)代碼應(yīng)避免以下情況:
構(gòu)造方法中做的事情過多。?存在過多的全局變量和靜態(tài)方法。
存在過多的外部依賴。
存在過多的條件語句。說明:多層條件語句建議使用衛(wèi)語句、策略模式、狀態(tài)模式等方式重構(gòu)。
和第一節(jié)if-else提到的一樣,避免多層代碼塊嵌套
16.【參考】不要對單元測試存在如下誤解:
那是測試同學(xué)干的事情。本文是開發(fā)手冊,凡是本文內(nèi)容都是與開發(fā)同學(xué)強(qiáng)相關(guān)的。
單元測試代碼是多余的。汽車的整體功能與各單元部件的測試正常與否是強(qiáng)相關(guān)的。
單元測試代碼不需要維護(hù)。一年半載后,那么單元測試幾乎處于廢棄狀態(tài)。
單元測試與線上故障沒有辯證關(guān)系。好的單元測試能夠最大限度地規(guī)避線上故障。
測試開發(fā)相親相愛是一家~
第四節(jié) 安全規(guī)約4.【強(qiáng)制】用戶請求傳入的任何參數(shù)必須做有效性驗(yàn)證。
說明:忽略參數(shù)校驗(yàn)可能導(dǎo)致:
pagesize過大導(dǎo)致內(nèi)存溢出
惡意orderby導(dǎo)致數(shù)據(jù)庫慢查詢
任意重定向?SQL注入
反序列化注入
正則輸入源串拒絕服務(wù)ReDoS
說明:Java代碼用正則來驗(yàn)證客戶端的輸入,有些正則寫法驗(yàn)證普通用戶輸入沒有問題,但是如果攻擊人員使用的是特殊構(gòu)造的字符串來驗(yàn)證,有可能導(dǎo)致死循環(huán)的結(jié)果。
老生常談的問題,但在工作中有時會忽略。
第五節(jié) MySQL數(shù)據(jù)庫 1. 建表規(guī)約8.【強(qiáng)制】varchar是可變長字符串,不預(yù)先分配存儲空間,長度不要超過5000,如果存儲長度大于此值,定義字段類型為text,獨(dú)立出來一張表,用主鍵來對應(yīng),避免影響其它字段索引效率。
大字段建外連表,避免影響索引效率。難點(diǎn)在于如何說服主工程師(滑稽)。
13.【推薦】字段允許適當(dāng)冗余,以提高查詢性能,但必須考慮數(shù)據(jù)一致。
冗余字段應(yīng)遵循:
1)不是頻繁修改的字段。
2)不是varchar超長字段,更不能是text字段。
正例:商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關(guān)聯(lián)的表中冗余存儲類目名稱,避免關(guān)聯(lián)查詢。
講一個笑話。公司老王出差去拉項(xiàng)目,對方博士生問“這個數(shù)據(jù)庫設(shè)計(jì)為什么不符合三范式?”
真事。
2. 索引規(guī)約14.【推薦】單表行數(shù)超過500萬行或者單表容量超過2GB,才推薦進(jìn)行分庫分表。
說明:如果預(yù)計(jì)三年后的數(shù)據(jù)量根本達(dá)不到這個級別,請不要在創(chuàng)建表時就分庫分表。
3.【強(qiáng)制】在varchar字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據(jù)實(shí)際文本區(qū)分度決定索引長度即可。
說明:索引的長度與區(qū)分度是一對矛盾體,一般對字符串類型數(shù)據(jù),長度為20的索引,區(qū)分度會高達(dá)90%以上,可以使用count(distinctleft(列名, 索引長度))/count(*)的區(qū)分度來確定。
5.【推薦】如果有orderby的場景,請注意利用索引的有序性。orderby最后的字段是組合索引的一部分,并且放在索引組合順序的最后,避免出現(xiàn)file_sort的情況,影響查詢性能。
正例:wherea=? andb=? orderbyc;索引:a_b_c
反例:索引中有范圍查找,那么索引有序性無法利用,如:WHEREa>10 ORDERBYb;索引a_b無法排序。
3. SQL語句6.【推薦】利用覆蓋索引來進(jìn)行查詢操作,避免回表。
說明:如果一本書需要知道第11章是什么標(biāo)題,會翻開第11章對應(yīng)的那一頁嗎?目錄瀏覽一下就好,這個目錄就是起到覆蓋索引的作用。
正例:能夠建立索引的種類:主鍵索引、唯一索引、普通索引,而覆蓋索引是一種查詢的一種效果,用explain的結(jié)果,extra列會出現(xiàn):usingindex。
1.【強(qiáng)制】不要使用count(列名)或count(常量)來替代count(*),count(*)是SQL92定義的標(biāo)準(zhǔn)統(tǒng)計(jì)行數(shù)的語法,跟數(shù)據(jù)庫無關(guān),跟NULL和非NULL無關(guān)。
說明:count(*)會統(tǒng)計(jì)值為NULL的行,而count(列名)不會統(tǒng)計(jì)此列為NULL值的行。
乖乖滾回count(*)
4. ORM映射【推薦】in操作能避免則避免,若實(shí)在避免不了,需要仔細(xì)評估in后邊的集合元素?cái)?shù)量,控制在1000個之內(nèi)。
3.【強(qiáng)制】不要用resultClass當(dāng)返回參數(shù),即使所有類屬性名與數(shù)據(jù)庫字段一一對應(yīng),也需要定義;反過來,每一個表也必然有一個與之對應(yīng)。
說明:配置映射關(guān)系,使字段與DO類解耦,方便維護(hù)。
編程一時爽,維護(hù)兩行淚~
5.【強(qiáng)制】iBATIS自帶的queryForList(StringstatementName,intstart,intsize)不推薦使用。
說明:其實(shí)現(xiàn)方式是在數(shù)據(jù)庫取到statementName對應(yīng)的SQL語句的所有記錄,再通過subList取start,size的子集合。
正例:
Mapmap = new HashMap ();
map.put("start", start);
map.put("size", size);
沒想到你是這樣的iBATIS!
第六節(jié) 工程結(jié)構(gòu) 1. 應(yīng)用分層2. 二方庫依賴2.【參考】(分層異常處理規(guī)約)在DAO層,產(chǎn)生的異常類型有很多,無法用細(xì)粒度的異常進(jìn)行catch,使用catch(Exceptione)方式,并thrownewDAOException(e),不需要打印日志,因?yàn)槿罩驹贛anager/Service層一定需要捕獲并打到日志文件中去,如果同臺服務(wù)器再打日志,浪費(fèi)性能和存儲。在Service層出現(xiàn)異常時,必須記錄出錯日志到磁盤,盡可能帶上參數(shù)信息,相當(dāng)于保護(hù)案發(fā)現(xiàn)場。如果Manager層與Service同機(jī)部署,日志方式與DAO層處理一致,如果是多帶帶部署,則采用與Service一致的處理方式。Web層絕不應(yīng)該繼續(xù)往上拋異常,因?yàn)橐呀?jīng)處于頂層,如果意識到這個異常將導(dǎo)致頁面無法正常渲染,那么就應(yīng)該直接跳轉(zhuǎn)到友好錯誤頁面,加上用戶容易理解的錯誤提示信息。開放接口層要將異常處理成錯誤碼和錯誤信息方式返回。
3. 服務(wù)器10.【參考】為避免應(yīng)用二方庫的依賴沖突問題,二方庫發(fā)布者應(yīng)當(dāng)遵循以下原則:
1)精簡可控原則。移除一切不必要的API和依賴,只包含ServiceAPI、必要的領(lǐng)域模型對象、Utils類、常量、枚舉等。如果依賴其它二方庫,盡量是provided引入,讓二方庫使用者去依賴具體版本號;無log具體實(shí)現(xiàn),只依賴日志框架。
2)穩(wěn)定可追溯原則。每個版本的變化應(yīng)該被記錄,二方庫由誰維護(hù),源碼在哪里,都需要能方便查到。除非用戶主動升級版本,否則公共二方庫的行為不應(yīng)該發(fā)生變化。
4.【推薦】在線上生產(chǎn)環(huán)境,JVM的Xms和Xmx設(shè)置一樣大小的內(nèi)存容量,避免在GC后調(diào)整堆大小帶來的壓力。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/67777.html
摘要:理解迭代對象迭代器生成器后端掘金本文源自作者的一篇博文,原文是,俺寫的這篇文章是按照自己的理解做的參考翻譯。比較的是兩個對象的內(nèi)容是后端掘金黑魔法之協(xié)程異步后端掘金本文為作者原創(chuàng),轉(zhuǎn)載請先與作者聯(lián)系。 完全理解關(guān)鍵字with與上下文管理器 - 掘金如果你有閱讀源碼的習(xí)慣,可能會看到一些優(yōu)秀的代碼經(jīng)常出現(xiàn)帶有 with 關(guān)鍵字的語句,它通常用在什么場景呢?今天就來說說 with 和 上下...
摘要:另一個用戶請求過來,負(fù)載均衡器指派這個請求到服務(wù)器。這樣就平攤了請求這種方式就叫做輪詢策略還有很多種,就看你想怎么實(shí)現(xiàn)了,反正這個邏輯的代碼放在負(fù)載均衡器上。 前言 只有光頭才能變強(qiáng)。文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y 這本書買了一段時間了,之前在杭州沒帶過去,現(xiàn)在讀完第三章,來做做筆記 showI...
摘要:強(qiáng)大的表單驗(yàn)證前端掘金支持非常強(qiáng)大的內(nèi)置表單驗(yàn)證,以及。面向?qū)ο蠛兔嫦蜻^程的區(qū)別的種設(shè)計(jì)模式全解析后端掘金一設(shè)計(jì)模式的分類總體來說設(shè)計(jì)模式分為三大類創(chuàng)建型模式,共五種工廠方法模式抽象工廠模式單例模式建造者模式原型模式。 強(qiáng)大的 Angular 表單驗(yàn)證 - 前端 - 掘金Angular 支持非常強(qiáng)大的內(nèi)置表單驗(yàn)證,maxlength、minlength、required 以及 patt...
摘要:能理解線程模型多線程優(yōu)缺點(diǎn)以及如何避免。多線程的出現(xiàn)主要是為了提高的利用率任務(wù)的執(zhí)行效率。所以要考慮清楚是否真的需要多線程。這一塊的內(nèi)容可以然我們知道寫大牛處理并發(fā)的思路,對我們自己編寫高質(zhì)量的多線程程序也有很多幫助。 showImg(https://segmentfault.com/img/remote/1460000015980196?w=2048&h=1363); 前言 已經(jīng)記不...
閱讀 1890·2021-11-24 09:39
閱讀 2535·2021-10-14 09:43
閱讀 3318·2021-10-08 10:10
閱讀 2266·2021-09-22 15:54
閱讀 2340·2019-08-29 17:20
閱讀 1573·2019-08-28 18:14
閱讀 2374·2019-08-26 13:28
閱讀 1111·2019-08-26 12:16