<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                _神啊,求你賜給我平靜的心,去接受我無法改變的事;賜給我勇氣,去做我能改變的事;賜給我智慧,去分辨兩者的不同。 --平靜之禱_ ##1.30.1 論保持的力量 追到一個心儀的女生不難,難于如何保持和培養一份真摯的感情;獲得一時的財富也不難,難于如何長久保持收益;創業的公司很容易博得一時媒體的關注以及某次天使的投資,但難于如何排除各種障礙、充分利用各方資源發展成中企業及至上市公司。 同樣,提供一時的接口很容易,但當我們需要不斷為接口提供升級,以及當我們維護提供一整套接口時,面臨的困難和問題會越來越大。 所以,這是一場持久的戰役。需要我們用穩重的心態、專業的能力在背后持久支撐、推動。 值得慶幸的是,這些都是問題而不是限制,都是可以被解決的。 以下是結合 @郭了個浩浩 同學提供的apigee.web_api.pdf文檔,以及我們多年來的項目實際開發經驗為新手提供的一些建議,對老同學相信也會有所幫助。 每個建議通常會包括三部分: **現在主流的做法、PhalApi的做法以及項目的選取。 ** ##1.30.2 最佳實踐建議 為了大家查閱和翻看,這里先羅列本章的全部建議: + (1)接口風格和協議的選擇 - HTTP + (2)接口域名 - 使用api單獨域名 + (3)異常處理 - 200/400/500三大接口結果狀態碼 + (4)對外的命名規則 - 使用小寫加下劃線 + (5)對內的命名規則 - 使用駝峰法和遵循PEAR命名 + (6)安全與驗證 - 使用接口簽名和token登錄態雙重機制 + (7)返回結果格式 - JSON + (8)URL規則與路由映射 - 統一service接口服務,可一個文件一個接口 + (9)SDK包 - 給客戶端自由的調用空間和自由 + (10)接口文檔 - 使用markdown快速編寫 + (11)測試驅動開發 - 堅持單元測試 ##1.30.3 建議細說 ###(1)接口風格和協議的選擇 - HTTP 目前,后臺接口開發可以用RESTFull風格,也可以用Web Service;可以用SOAP協議、RPC協議,也可以用HTTP協議;可以用短鏈接,也可以使用長鏈接。如果我們希望繼續進行劃分,還可以分為同步或異步、單個或批量、是否有SDK包、內部接口還是開放接口平臺等。 ####主流的做法 現在看來,大部分大型的企業以及大多數的小公司使用的都是HTTP協議下的接口開發,部分使用RESTFull,但Web Service較少。如: + [優酷開放平臺](http://open.youku.com/docs/docs?id=44),[示例請點擊](https://openapi.youku.com/v2/videos/show_basic.json) + [微信公眾號-服務器接口協議](http://iot.weixin.qq.com/index.html),[示例請點擊](https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=) + [新浪微博開放平臺](open.weibo.com),使用REST風格,[示例請點擊](https://api.weibo.com/2/statuses/mentions/ids.json) + [Amazon](http://aws.amazon.com/cn/documentation/) ,采用Web Service、SOAP、REST等多種風格和協議提供服務,[示例請點擊](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETtagging.html) ####PhalApi的做法 我們選取了HTTP的協議,在于其無論是客戶端接入、開發調試,還是部署構建上都很容易實現,而且也符合主流,因為大家都比較熟悉。 這一點是非常重要的:因為簡單,后臺接口開發的同學才會更容易上手;因為容易,客戶端接入才會更加無壓力而不用擔心處處受挫。 ####項目的選取 根據項目不同的項目背景和需求,可以選擇你合適的風格或者協議。但是即使出于安全、性能或者其他技術或非技術的原因而不采用HTTP協議的情況下,你也可以在PhalApi原有的接口開發實現時,輕松擴展你需要的協議。如使用SOAP,PHPRpc或者swoole下的TCP協議。其中,部分協議已有擴展類庫提供支持。 ###(2)接口域名 - 使用api單獨域名 首先,有一點是可以肯定的。 接口系統應該有自己單獨的域名,而不應該附屬于網站或者管理后臺。 ####主流的做法 顯然,主流做法也是這樣做的。如: + 優酷開放平臺:https://openapi.youku.com + 微信公眾號: https://api.weixin.qq.com + 新浪微博: https://api.weibo.com ####項目的選取 如果可以,盡量讓接口系統使用獨立的域名,并且使用api作為一級域名。如: ``` //你的網站為: http://www.demo.com //則對應的接口為: http://api.demo.com ``` ###(3)異常處理 - 200/400/500三大接口結果狀態碼 對于接口的異常處理,在使用HTTP協議下,可以通過HTTP本身的響應狀態碼來進行區分。 在非HTTP協議并有SDK包的情況下,異常的處理手段則會更為多樣。 ####主流的做法 優酷接口采用了HTTP響應狀態碼加結果返回的形式,如: ```javascript Request URL:https://openapi.youku.com/v2/videos/show_basic.json Request Method:GET Status Code:400 Bad Request {"error":{"code":1004,"type":"SystemException","description":"Client id null"}} ``` 新浪微博也一樣: ```javascript Request URL:https://api.weibo.com/2/statuses/mentions/ids.json Request Method:GET Status Code:403 Forbidden {"error":"auth by Null spi!","error_code":21301,"request":"/2/statuses/mentions/ids.json"} ``` 微信接口則采用了統一200的形式,如: ```javascript Request URL:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid= Request Method:GET Status Code:200 OK {"errcode":41002,"errmsg":"appid missing"} ``` ###PhalApi的做法 為了與HTTP保持一致性,同時降低不必要的復雜性,我們采用了200/400/500三大接口結果狀態碼。 注意,這里所說的三大狀態碼,是指接口返回結果中的狀態碼,而不是HTTP的響應狀態。 也就是說接口全部的結果返回都應該是200,除非接口服務有內部未捕獲的異常,即: ```javascript Status Code:200 OK ``` 返回結果狀態碼剛是以下幾種: ```javascript //正常返回 { "ret": 200, "data": { //... }, "msg": "" } //客戶端非法請求 { "ret": 400, "data": [], "msg": "非法請求:接口服務Default.Test不存在" } //服務端內部錯誤 { "ret": 500, "data": [], "msg": "服務器運行錯誤: can not connect to database db_demo" } ####項目的選取 你可以根據你的需要,擴展400和500這兩系列的錯誤,如401表示登錄失敗等。 此外,在data里面,你也可以添加一個code來表示業務級的操作碼,以及客戶端根據不同的業務場景做出不同和反應、交互或引導提示。 ``` ###(4)對外的命名規則 - 使用小寫加下劃線 對外的命名,是指外部看得到的命名,如接口參數的名字,接口返回的結果節點名字,以及數據庫的表名、字段名。 ####主流的做法 新浪微博采用了小寫加下劃線的做法,如: ```javascript //URL https://c.api.weibo.com/2/friendships/followers/trend_count.json //請求參數 source access_token //返回結果 { "uid": 10438, "result": [ { "days": "2012-04-04", "follower_count_online":"15", //粉絲數 "active_follower":"14", //活躍粉絲數 "loyal_follower":"0" //互動粉絲數 }, .... ] } ``` Amazon采用了首字母大寫且無下劃線的做法,如: ```javascript //Responses HTTP/1.1 200 OK Date: Wed, 25 Nov 2009 12:00:00 GMT Connection: close Server: AmazonS3 <?xml version="1.0" encoding="UTF-8"?> <BucketLoggingStatus xmlns="http://doc.s3.amazonaws.com/2006-03-01"> <LoggingEnabled> <TargetBucket>mybucketlogs</TargetBucket> <TargetPrefix>mybucket-access_log-/</TargetPrefix> <TargetGrants> <Grant> <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="AmazonCustomerByEmail"> <EmailAddress>user@company.com</EmailAddress> </Grantee> <Permission>READ</Permission> </Grant> </TargetGrants> </LoggingEnabled> </BucketLoggingStatus> ``` ###PhalApi的做法 我們提倡使用全部小寫加下劃線的命名,因為這樣更符合客戶端的使用,如: 接口參數: ```javascript //正確的 &user_id=888 //錯誤的 &userId=888 ``` 返回字段: ```javascript //正確的 "device_type": "cube", //錯誤的 "deviceType": "cube", ``` 數據庫字段: ```javascript //正確的 `user_id` bigint(20) DEFAULT '0' COMMENT '創建者的用戶ID', //錯誤的 `userId` bigint(20) DEFAULT '0' COMMENT '創建者的用戶ID', ``` ###項目的選取 不管是使用全部小寫,還是全部大寫,項目都應該保持一致的命名風格,而不是混合凌亂的風格。 ###(5)對內的命名規則 - 使用駝峰法和遵循PEAR命名 與對外命名對應的則是對內的命名規則,這里又回歸到了老生常談的PHP代碼風格。 這里不作過多的說明,只是稍作提及。 ###PhalApi的做法 我們建議使用PEAR包的命名風格,和駝峰法,如下為一個接口示例: ```javascript $ vim ./Api/Default.php <?php /** * 默認接口服務類 * * @author: dogstar <chanzonghuang@gmail.com> 2014-10-04 */ class Api_Default extends PhalApi_Api { public function getRules() { return array( 'index' => array( 'username' => array('name' => 'username', 'default' => 'PHPer', ), ), ); } public function index() { return array( 'title' => 'Default Api', 'content' => T('Hello {name}, Welcome to use PhalApi!', array('name' => $this->username)), 'version' => PHALAPI_VERSION, 'time' => $_SERVER['REQUEST_TIME'], ); } } ``` ###項目的選取 你可以選擇你喜歡的風格,但團隊應該保持一致。 即便你不喜歡PhalApi約定的PEAR命名,你也可以自行實現內部的類加載機制。 當有多個項目或者多個模塊并存時,可以添加模塊名前綴來作區分,如: ```javascript $ tree . ├── Demo │?? └── Api │?? └── DUser.php ├── MyApp │?? └── Api │?? └── MUser.php └── Task └── Api └── TUser.php $ head */*/* ==> Demo/Api/DUser.php <== <?php class Api_DUser extends PhalApi_Api { } ==> MyApp/Api/MUser.php <== <?php class Api_MUser extends PhalApi_Api { } ==> Task/Api/TUser.php <== <?php class Api_TUser extends PhalApi_Api { } ``` 其它的Domain層和Model層等也類似,這樣可以避免類名沖突,或者IDE開發環境下的混淆。 ###(6)安全與驗證 - 使用接口簽名和token登錄態雙重機制 既然采用HTTP協議,那么安全方面就需要接口自身進行保證。 所幸,現在可用的加密手段有多種選擇。 對于接口簽名,我們可以使用非對稱的驗簽方式,如md5;也可以用對稱的方式,如RSA。 最后,為每一個接入的客戶端分配app_key和app_secrect即可。 當然,更好的安全是接口系統再提供登錄態的驗證,即通常所說的token。 這兩者的相合,會為接口增加更好的安全保障。 ####主流的做法 + 七牛云存儲,采用Access Key/Secret Key,并且在需要時添加相應的憑證 + 微信公眾號,采用由AppID(應用ID)和AppSecret(應用密鑰)生成的ACCESS_TOKEN + 優酷開放平臺,采用應用Key client_id ###PhalApi的做法 我們不提供具體的接口簽名方案,是因為把這種決策移交給項目應用本身進行定制。 而定制也是非常簡單的,只需要簡單的兩步即可: + 1、實現過濾器接口 **PhalApi_Filter::check()**; + 2、注冊過濾器服務 **DI()->filter**; 對于token,雖然框架沒有提供內置的實現,但可以從PhalApi的擴展類庫尋找這種支持,這一點已經User擴展類庫支持。 ###項目的選取 正如PhalApi提供的自由空間,項目可以自行實現接口簽名,和根據需要是否采用User擴展類庫,或者自行實現token的處理。 ###(7)返回結果格式 - JSON 在 [[1.14.1 統一返回的格式]](/wikis/%5B1.14%5D-%E7%BB%9F%E4%B8%80%E7%9A%84%E8%BF%94%E5%9B%9E%E6%A0%BC%E5%BC%8F%E5%92%8C%E7%BB%93%E6%9E%84%EF%BC%9Aret-data-msg.html)一節中,已經對JSON的返回格式作了說明,這里不再贅述,也只是稍作提及。 ####主流的做法 目前采用了JSON的格式返回的有: + 新浪微博 + 優酷開放平臺 + 騰訊開放平臺 + 微信接口 采用了XML格式返回的有: + Amazon ###PhalApi的做法 我們默認采了JSON的格式返回。 ###項目的選取 項目可以輕松擴展成其他格式的返回。 ###(8)URL規則與路由映射 - 統一service接口服務,可一個文件一個接口 先從項目內部的文件劃分說起,通常最為常見的情況是,很多開發人員都喜歡把很多很多很多接口都塞到一個接口文件里面。 這樣的文件,通常會有2K到3K左右。 我覺得這是一種極端,而且是一種不好的極端。因為文件過大的話,會帶來很多問題。 但與之對立的有另一種做法,即一個文件,一個接口。 這一點,在我之前就職的一家出名的游戲公司中得到了廣泛的認可和遵循。如: ```javascript //?service=UserInfo.Go <?php class Api_UserInfo extends PhalApi_Api { public function go() { //TODO } } //?service=GroupInfo.Go <?php class Api_GroupInfo extends PhalApi_Api { public function go() { //TODO } } ``` 雖然也是一種極端,但卻很好地做到了接口隔離,即不用擔心修改此接口的實現而影響到其他接口服務。 最后,我們再來聊URL規則,就更順暢了。如果我們采用一個文件對應一個接口,則我們可以省略Action(全部都為go()方法),簡寫成:?service=XXX。 再進一步,我們可以利用接口服務器(如Nginx)的規則Rewrite來提供更好的URL規則,同時盡量隱藏我們的接口內部實現細節,如: ```javascript //原始地 http://api.demo.com/?service=UserInfo.Go //簡化地 http://api.demo.com/?service=UserInfo //再進一步 http://api.demo.com/UserInfo //或者 http://api.demo.com/UserInfo.json ``` 還有一點需要關注的就是接口的版本,當有v1,v2,v3等不同的版本時,我們也需要在接口URL中體現這些版本的不同。 ####主流的做法 + 新浪微博的一個接口URL:https://c.api.weibo.com/2/friendships/followers/trend_count.json + 優酷平臺的一個接口URL:https://openapi.youku.com/v2/videos/show_basic.json + 微信的一個接口URL:https://api.weixin.qq.com/device/get_stat ###PhalApi的做法 目前而言,PhalApi在URL規則和路由這塊還比較欠缺,沒有像其他網站一樣提供強大的路由支持。 但我們在代碼實現的層面,可以提供不同的入口,以開放給不同的終端(內部的或者外部的), 以及不同的版本支持。如: ```javascript $ tree Public/ Public/ ├── v1 │?? └── index.php ├── v2 │?? └── index.php └── v3 └── index.php 3 directories, 3 files ``` 則對應的版本URL則可以為: ```javascript //v1版本 http://api.demo.com/v1/?service=Default.Index //v2版本 http://api.demo.com/v2/?service=Default.Index //v3版本 http://api.demo.com/v3/?service=Default.Index ``` ###項目的選取 項目可以結合不同的入口,以及接口服務器的URL規則Rewrite作一些自定的URL路由。 ###(9)SDK包 - 給客戶端自由的調用空間和自由 目前移動開發主要有iOS、Android、Windowns Phone、網站等不同的終端,各種終端又有不同的語言,如果我們需要提供SDK包,不僅僅需要考慮到縱向的版本升級,還需要維護橫向的多樣性。 而且,如果我們使用的是HTTP協議,則不必要擔心這些維護的成本,同時給客戶端提供一個自由的空間進行調用 -- 即客戶端可以自己編寫本身的接口客戶端。 ####主流的做法 很多國內的開放平臺接口都是不提供SDK包的,但有些安全度高的則會,如支持寶。 以下是一些提供了SDK的平臺 : + [支付寶SDK](https://openhome.alipay.com/doc/docIndex.htm?url=https://openhome.alipay.com/doc/viewKbDoc.htm?key=236698_261849&type=info) + [Yahoo! SDKs](https://developer.yahoo.com/social/sdk/) + [Amazon豐富的開發工具包](https://aws.amazon.com/cn/tools/) ###PhalApi的做法 我們暫時沒有提供SDK包,但對于PHP,有一個簡單的客戶端類,可見: [[1.13]-統一的接口請求方式](/wikis/%5B1.13%5D-%E7%BB%9F%E4%B8%80%E7%9A%84%E6%8E%A5%E5%8F%A3%E8%AF%B7%E6%B1%82%E6%96%B9%E5%BC%8F%EF%BC%9A_sevice=XXX.XXX.html) ###項目的選取 出于公司產品簇的項目考慮,項目可以內部提供SDK給同類的客戶端使用,如分為iOS版的客戶端SDK,以及Android版的客戶端SDK。 ####小故事:與SDK包的一個真實的痛苦經歷 有一點是非常重要的,千萬不要讓不懂PHP語言的人去開發提供PHP的SDK包,更不要使用所謂的工具自動轉換生成SDK包代碼。 在我曾經做過的一個項目中,因為需要接入一個接口系統,而這個接口是由專業的JAVA團隊維護的,但他們對PHP語言則是非常薄弱,以致他們使用了工具來生成PHP語言的SDK包。 這就導致了我在接入一個簡單的接口時,卻開發聯調耗費了兩天、測試聯調時耗費了在接口調用超時問題排查上。 而最后找到的原因卻是因為app_key不對而導致服務端異常,而在SDK包卻隱藏了這一異常錯誤信息,反而給出了time out超時的提示,嚴重誤導了排查的方向! 而當我嘗試深入去調試SDK時,得到卻又是既沒有code又沒有message的異常!最讓人難以忍受的是,他們提供的SDK包竟然和JAVA的企業系統一樣復雜的結構(正如他們是使用工具來生成轉換的)! 想象一下,PHP代碼下有\com\sina\webo\sdk\Constants.php這樣類似JAVA的文件結構,PHP的同學會作何感想?用JAVA的世界的方式來開發PHP,顯然是走不通的啊! 而執意要走的話,到最后就是各種接入的痛苦,稍微按奈不住的同學難免就會因為情緒問題而大開爭論了。而這一切,只是因為非PHP人員使用了自動生成工具。 我覺得,這是一種不負責任的做法,希望大家不要效仿。 ###(10)接口文檔 - 使用markdown快速編寫 (場外音:通過沐浴法理清了頭緒,繼續回來執筆編寫)。 就我個人經歷而言,markdown就是一個開始你會拒絕,接著你會越來越喜歡,到最后會愛不釋手的一個工具。 如果你或者你的團隊還在使用郵件或者work文檔來傳遞共享接口文檔,那就太不應該了;如果你正在使用某個WIKI系統進行文檔的維護但卻不喜歡它的編輯或者展示方式時,你可以嘗試使用一下markdown。 正如你現在正在查看的文檔也是通過markdown編寫的。 ####主流的做法 作為開放接口平臺,文檔肯定是以網站的形式提供。但很多時候,對于我們內部的接口或者小項目來說,顯然這樣的成本太大了。 接口,從簡單開始。 我們理應一直堅持這一點,所以文檔也是一樣,我們應該尋求一種在內部快速共享最新接口文檔的途徑。如: + 1、使用內部WIKI + 2、使用開源中國或者其他站點的WIKI(這時可以通過在線編輯或者GIT更新) ###項目的選取 你可以根據項目的需要,或者公司以往的做法,但至少不要再使用郵件或者word文檔。 ###(11)測試驅動開發 - 堅持單元測試 單元測試,在PhalApi里面不只一次提到了,這里再次進行說明,是希望能引起大家的關注,去嘗試體驗一下。 我們都知道,在開發一個新功能時、新接口時,修復一個BUG或者作一些大的調整或者重構工作,我們是毫無壓力的,而且這時的成本很低,僅在于開發人員本身的時間和精力的消耗。 當提測后進入測試階段,測試人員發現一個BUG后,有些團隊會以禪道或者Bugzilla或其他方式來紀錄和追蹤BUG。這時我們開發會覺得一個這么小的問題還需要去紀錄、去登記很不值得。然后,我們應當注意到這時修復一個BUG會涉及到測試人員資源的開銷。 當進入了回歸測試階段,特別是多系統交互、跨團隊合作時,一個BUG就會從一個人傳到另一個人,從這個團隊流到那個團隊,這時成本就會逐漸增大。 最后,上線后,當一個奇怪的問題出現后,我們需要定位原因就更加困難重重了。 我曾經就經歷這樣一番:有用戶發現游戲的道具減少了。我們一開始以為是某些運營配置、或者數據以及用戶的等級限制所引發的,但在排除了各種業務的問題后,到最后卻發現是PHP中array使用“+”運算而引發的血案! 在正常情況下,我們都知道array_merge()函數對于數值的下標則會追加并重新生成下標序列,即會合并;而數組+則會去掉相同下標的元素。 但實際情況下,線上BUG所產生的影響不在于排查和修復的時間成本,而在于在這段時間內所損失的金額、數據等成本。 當然,從測試的角度上看,測試并不能保證我們的系統沒有BUG,只能說暫時未發現BUG。 單元測試也一樣,作為開發人員,我們應當在最低成本的時期就及時發現我們直覺覺得可能會出現的問題并進行修復。 對我們親手所編寫的代碼負責,并且用客觀的方式來證明我們的代碼目前未發現問題,而不是主觀認為“我寫的代碼沒有問題”。更不應該一次又一次地犯下各種低級或者重復的錯誤,而讓團隊其他成員對我們喪失信任。 PhalApi一直很注重單元測試,也很注重自動化,為了減輕大家重復編寫單元測試骨架代碼的痛苦,我們提供了一個可以生成單元測試代碼的腳本。 假設我們有這么一個類: ```javascript <?php class Api_Default extends PhalApi_Api { public function index() { //TODO } } ``` 那么,我們可以這樣生成測試代碼: ```javascript $ cd .//Demo/Tests $ phalapi-buildtest ../Api/Default.php Api_Default ./test_env.php <?php /** * PhpUnderControl_ApiDefault_Test * * 針對 ../Api/Default.php Api_Default 類的PHPUnit單元測試 * * @author: dogstar 20150514 */ require_once dirname(__FILE__) . '/test_env.php'; if (!class_exists('Api_Default')) { require dirname(__FILE__) . '/../Api/Default.php'; } class PhpUnderControl_ApiDefault_Test extends PHPUnit_Framework_TestCase { public $apiDefault; protected function setUp() { parent::setUp(); $this->apiDefault = new Api_Default(); } protected function tearDown() { } /** * @group testGetRules */ public function testGetRules() { $rs = $this->apiDefault->getRules(); } /** * @group testIndex */ public function testIndex() { $rs = $this->apiDefault->index(); } } ``` > 溫馨提示: > 1. 可以先執行:ln -s /path/to/PhalApi/phalapi-buildtest /usr/bin/phalapi-buildtest > 2. test_env.php為測試環境初始化文件,可以在里面引用init.php文件,并作一些調整 > 3. 輸出的測試代碼可以重定向到./Demo/Tests/Api/Api_Default_Test.php,讓測試代碼與產品代碼對齊 最后,我們就可以這樣執行單元測試了: ```javascript $ phpunit ./Api_Default_Test.php ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看