亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

通過demo學習OpenStack開發(fā)所需的基礎(chǔ)知識 -- API服務(4)

meislzhua / 1715人閱讀

摘要:到這里,我們的服務的框架已經(jīng)搭建完成,并且測試服務器也跑起來了。上面的代碼也就可以修改為再次運行我們的測試服務器,就可以返現(xiàn)返回值為格式了。我們先來完成利用來檢查返回值的代碼方法的第一個參數(shù)表示返回值的類型這樣就完成了的返回值檢查了。

上一篇文章說到,我們將以實例的形式來繼續(xù)講述這個API服務的開發(fā)知識,這里會使用Pecan和WSME兩個庫。

設(shè)計REST API

要開發(fā)REST API服務,我們首先需要設(shè)計一下這個服務。設(shè)計包括要實現(xiàn)的功能,以及接口的具體規(guī)范。我們這里要實現(xiàn)的是一個簡單的用戶管理接口,包括增刪改查等功能。如果讀者對REST API不熟悉,可以先從Wiki頁面了解一下。

另外,為了方便大家閱讀和理解,本系列的代碼會放在github上,diabloneo/webdemo。

Version of REST API

在OpenStack的項目中,都是在URL中表明這個API的版本號的,比如Keystone的API會有/v2.0/v3的前綴,表明兩個不同版本的API;Magnum項目目前的API則為v1版本。因為我們的webdemo項目才剛剛開始,所以我們也把我們的API版本設(shè)置為v1,下文會說明怎么實現(xiàn)這個version號的設(shè)置。

REST API of Users

我們將要設(shè)計一個管理用戶的API,這個和Keystone的用戶管理的API差不多,這里先列出每個API的形式,以及簡要的內(nèi)容說明。這里我們會把上面提到的version號也加入到URL path中,讓讀者能更容易聯(lián)系起來。

GET /v1/users 獲取所有用戶的列表。

POST /v1/users 創(chuàng)建一個用戶

GET /v1/users/ 獲取一個特定用戶的詳細信息。

PUT /v1/users/ 修改一個用戶的詳細信息。

DELETE /v1/users/ 刪除一個用戶。

這些就是我們要實現(xiàn)的用戶管理的API了。其中,表示使用一個UUID字符串,這個是OpenStack中最經(jīng)常被用來作為各種資源ID的形式,如下所示:

In [5]: import uuid
In [6]: print uuid.uuid4()
adb92482-baab-4832-84bc-f842f3eabd66
In [7]: print uuid.uuid4().hex
29520c88de6b4c76ae8deb48db0a71e7

因為是個demo,所以我們設(shè)置一個用戶包含的信息會比較簡單,只包含name和age。

使用Pecan搭建API服務的框架

接下來就要開始編碼工作了。首先要把整個服務的框架搭建起來。我們會在軟件包管理這篇文件中的代碼基礎(chǔ)上繼續(xù)我們的demo(所有這些代碼在github的倉庫里都能看到)。

代碼目錄結(jié)構(gòu)

一般來說,OpenStack項目中,使用Pecan來開發(fā)API服務時,都會在代碼目錄下有一個專門的API目錄,用來保存API相關(guān)的代碼。比如Magnum項目的magnum/api,或者Ceilometer項目的ceilometer/api等。我們的代碼也遵守這個規(guī)范,讓我們直接來看下我們的代碼目錄結(jié)構(gòu)(#后面的表示注釋):

? ~/programming/python/webdemo/webdemo/api git:(master) ? $ tree .
.
├── app.py           # 這個文件存放WSGI application的入口
├── config.py        # 這個文件存放Pecan的配置
├── controllers/     # 這個目錄用來存放Pecan控制器的代碼
├── hooks.py         # 這個文件存放Pecan的hooks代碼(本文中用不到)
└── __init__.py

這個在API服務(3)這篇文章中已經(jīng)說明過了。

先讓我們的服務跑起來

為了后面更好的開發(fā),我們需要先讓我們的服務在本地跑起來,這樣可以方便自己做測試,看到代碼的效果。不過要做到這點,還是有些復雜的。

必要的代碼

首先,先創(chuàng)建config.py文件的內(nèi)容:

app = {
    "root": "webdemo.api.controllers.root.RootController",
    "modules": ["webdemo.api"],
    "debug": False,
}

就是包含了Pecan的最基本配置,其中指定了root controller的位置。然后看下app.py文件的內(nèi)容,主要就是讀取config.py中的配置,然后創(chuàng)建一個WSGI application:

import pecan

from webdemo.api import config as api_config


def get_pecan_config():
    filename = api_config.__file__.replace(".pyc", ".py")
    return pecan.configuration.conf_from_file(filename)


def setup_app():
    config = get_pecan_config()

    app_conf = dict(config.app)
    app = pecan.make_app(
        app_conf.pop("root"),
        logging=getattr(config, "logging", {}),
        **app_conf
    )

    return app

然后,我們至少還需要實現(xiàn)一下root controller,也就是webdemo/api/controllers/root.py這個文件中的RootController類:

from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan


class RootController(rest.RestController):

    @wsme_pecan.wsexpose(wtypes.text)
    def get(self):
        return "webdemo"
本地測試服務器

為了繼續(xù)開放的方便,我們要先創(chuàng)建一個Python腳本,可以啟動一個單進程的API服務。這個腳本會放在webdemo/cmd/目錄下,名稱是api.py(這目錄和腳本名稱也是慣例),來看看我們的api.py吧:

from wsgiref import simple_server

from webdemo.api import app


def main():
    host = "0.0.0.0"
    port = 8080

    application = app.setup_app()
    srv = simple_server.make_server(host, port, application)

    srv.serve_forever()


if __name__ == "__main__":
    main()
運行測試服務器的環(huán)境

要運行這個測試服務器,首先需要安裝必要的包,并且設(shè)置正確的路徑。在后面的文章中,我們將會知道,這個可以通過tox這個工具來實現(xiàn)?,F(xiàn)在,我們先做個簡單版本的,就是手動創(chuàng)建這個運行環(huán)境。

首先,完善一下requirements.txt這個文件,包含我們需要的包:

pbr<2.0,>=0.11
pecan
WSME

然后,我們手動創(chuàng)建一個virtualenv環(huán)境,并且安裝requirements.txt中要求的包:

? ~/programming/python/webdemo git:(master) ? $ virtualenv .venv
New python executable in .venv/bin/python
Installing setuptools, pip, wheel...done.
? ~/programming/python/webdemo git:(master) ? $ source .venv/bin/activate
(.venv)? ~/programming/python/webdemo git:(master) ? $ pip install -r requirement.txt
...
Successfully installed Mako-1.0.3 MarkupSafe-0.23 WSME-0.8.0 WebOb-1.5.1 WebTest-2.0.20 beautifulsoup4-4.4.1 logutils-0.3.3 netaddr-0.7.18 pbr-1.8.1 pecan-1.0.3 pytz-2015.7 simplegeneric-0.8.1 singledispatch-3.4.0.3 six-1.10.0 waitress-0.8.10
啟動我們的服務

啟動服務需要技巧,因為我們的webdemo還沒有安裝到系統(tǒng)的Python路徑中,也不在上面創(chuàng)建virtualenv環(huán)境中,所以我們需要通過指定PYTHONPATH這個環(huán)境變量來為Python程序增加庫的查找路徑:

(.venv)? ~/programming/python/webdemo git:(master) ? $ PYTHONPATH=. python webdemo/cmd/api.py

現(xiàn)在測試服務器已經(jīng)起來了,可以通過瀏覽器訪問http://localhost:8080/ 這個地址來查看結(jié)果。(你可能會發(fā)現(xiàn),返回的是XML格式的結(jié)果,而我們想要的是JSON格式的。這個是WSME的問題,我們后面再來處理)。

到這里,我們的REST API服務的框架已經(jīng)搭建完成,并且測試服務器也跑起來了。

用戶管理API的實現(xiàn)

現(xiàn)在我們來實現(xiàn)我們在第一章設(shè)計的API。這里先說明一下:我們會直接使用Pecan的RestController來實現(xiàn)REST API,這樣可以不用為每個接口指定接受的method。

讓API返回JSON格式的數(shù)據(jù)

現(xiàn)在,所有的OpenStack項目的REST API的返回格式都是使用JSON標準,所以我們也要這么做。那么有什么辦法能夠讓WSME框架返回JSON數(shù)據(jù)呢?可以通過設(shè)置wsmeext.pecan.wsexpose()rest_content_types參數(shù)來是先。這里,我們借鑒一段Magnum項目中的代碼,把這段代碼存放在文件webdemo/api/expose.py中:

import wsmeext.pecan as wsme_pecan


def expose(*args, **kwargs):
    """Ensure that only JSON, and not XML, is supported."""
    if "rest_content_types" not in kwargs:
        kwargs["rest_content_types"] = ("json",)

    return wsme_pecan.wsexpose(*args, **kwargs)

這樣我們就封裝了自己的expose裝飾器,每次都會設(shè)置響應的content-type為JSON。上面的root controller代碼也就可以修改為:

from pecan import rest
from wsme import types as wtypes

from webdemo.api import expose


class RootController(rest.RestController):

    @expose.expose(wtypes.text)
    def get(self):
        return "webdemo"

再次運行我們的測試服務器,就可以返現(xiàn)返回值為JSON格式了。

實現(xiàn) GET /v1

這個其實就是實現(xiàn)v1這個版本的API的路徑前綴。在Pecan的幫助下,我們很容易實現(xiàn)這個,只要按照如下兩步做即可:

先實現(xiàn)v1這個controller

把v1 controller加入到root controller中

按照OpenStack項目的規(guī)范,我們會先建立一個webdemo/api/controllers/v1/目錄,然后將v1 controller放在這個目錄下的一個文件中,假設(shè)我們就放在v1/controller.py文件中,效果如下:

from pecan import rest
from wsme import types as wtypes

from webdemo.api import expose


class V1Controller(rest.RestController):

    @expose.expose(wtypes.text)
    def get(self):
        return "webdemo v1controller"

然后把這個controller加入到root controller中:

...
from webdemo.api.controllers.v1 import controller as v1_controller
from webdemo.api import expose


class RootController(rest.RestController):
    v1 = v1_controller.V1Controller()

    @expose.expose(wtypes.text)
    def get(self):
        return "webdemo"

此時,你訪問http://localhost:8080/v1就可以看到結(jié)果了。

實現(xiàn) GET /v1/users 添加users controller

這個API就是返回所有的用戶信息,功能很簡單。首先要添加users controller到上面的v1 controller中。為了不影響閱讀體驗,這里就不貼代碼了,請看github上的示例代碼。

使用WSME來規(guī)范API的響應值

上篇文章中,我們已經(jīng)提到了WSME可以用來規(guī)范API的請求和響應的值,這里我們就要用上它。首先,我們要參考OpenStack的慣例來設(shè)計這個API的返回值:

{
  "users": [
    {
      "name": "Alice",
      "age": 30
    },
    {
      "name": "Bob",
      "age": 40
    }
  ]
}

其中users是一個列表,列表中的每個元素都是一個user。那么,我們要如何使用WSME來規(guī)范我們的響應值呢?答案就是使用WSME的自定義類型。我們可以利用WSME的類型功能定義出一個user類型,然后再定義一個user的列表類型。最后,我們就可以使用上面的expose方法來規(guī)定這個API返回的是一個user的列表類型。

定義user類型和user列表類型

這里我們需要用到WSME的Complex types的功能,請先看一下文檔Types。簡單說,就是我們可以把WSME的基本類型組合成一個復雜的類型。我們的類型需要繼承自wsme.types.Base這個類。因為我們在本文只會實現(xiàn)一個user相關(guān)的API,所以這里我們把所有的代碼都放在webdemo/api/controllers/v1/users.py文件中。來看下和user類型定義相關(guān)的部分:

from wsme import types as wtypes


class User(wtypes.Base):
    name = wtypes.text
    age = int


class Users(wtypes.Base):
    users = [User]

這里我們定義了class User,表示一個用戶信息,包含兩個字段,name是一個文本,age是一個整型。class Users表示一組用戶信息,包含一個字段users,是一個列表,列表的元素是上面定義的class User。完成這些定義后,我們就使用WSME來檢查我們的API是否返回了合格的值;另一方面,只要我們的API返回了這些類型,那么就能通過WSME的檢查。我們先來完成利用WSME來檢查API返回值的代碼:

class UsersController(rest.RestController):

    # expose方法的第一個參數(shù)表示返回值的類型
    @expose.expose(Users)
    def get(self):
        pass

這樣就完成了API的返回值檢查了。

實現(xiàn)API邏輯

我們現(xiàn)在來完成API的邏輯部分。不過為了方便大家理解,我們直接返回一個寫好的數(shù)據(jù),就是上面貼出來的那個。

class UsersController(rest.RestController):

    @expose.expose(Users)
    def get(self):
        user_info_list = [
            {
                "name": "Alice",
                "age": 30,
            },
            {
                "name": "Bob",
                "age": 40,
            }
        ]
        users_list = [User(**user_info) for user_info in user_info_list]
        return Users(users=users_list)

代碼中,會先根據(jù)user信息生成User實例的列表users_list,然后再生成Users實例。此時,重啟測試服務器后,你就可以從瀏覽器訪問http://localhost:8080/v1/users,就能看到結(jié)果了。

實現(xiàn) POST /v1/users

這個API會接收用戶上傳的一個JSON格式的數(shù)據(jù),然后打印出來(實際中一般是存到數(shù)據(jù)庫之類的),要求用戶上傳的數(shù)據(jù)符合User類型的規(guī)范,并且返回的狀態(tài)碼為201。代碼如下:

class UsersController(rest.RestController):

    @expose.expose(None, body=User, status_code=201)
    def post(self, user):
        print user

可以使用curl程序來測試:

 ~/programming/python/webdemo git:(master) ? $ curl -X POST http://localhost:8080/v1/users -H "Content-Type: application/json" -d "{"name": "Cook", "age": 50}" -v
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /v1/users HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 27
>
* upload completely sent off: 27 out of 27 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 201 Created
< Date: Mon, 16 Nov 2015 15:18:24 GMT
< Server: WSGIServer/0.1 Python/2.7.10
< Content-Length: 0
<
* Closing connection 0

同時,服務器上也會打印出:

127.0.0.1 - - [16/Nov/2015 23:16:28] "POST /v1/users HTTP/1.1" 201 0

我們用3行代碼就實現(xiàn)了這個POST的邏輯?,F(xiàn)在來說明一下這里的秘密。expose裝飾器的第一個參數(shù)表示這個方法沒有返回值;第三個參數(shù)表示這個API的響應狀態(tài)碼是201,如果不加這個參數(shù),在沒有返回值的情況下,默認會返回204。第二個參數(shù)要說明一下,這里用的是body=User,你也可以直接寫User。使用body=User這種形式,你可以直接發(fā)送符合User規(guī)范的JSON字符串;如果是用expose(None, User, status_code=201)那么你需要發(fā)送下面這樣的數(shù)據(jù):

{ "user": {"name": "Cook", "age": 50} }

你可以自己測試一下區(qū)別。要更多的了解本節(jié)提到的expose參數(shù),請參考WSM文檔Functions。

最后,你接收到一個創(chuàng)建用戶請求時,一般會為這個用戶分配一個id。本文前面已經(jīng)提到了OpenStack項目中一般使用UUID。你可以修改一下上面的邏輯,為每個用戶分配一個UUID。

實現(xiàn) GET /v1/users/

要實現(xiàn)這個API,需要兩個步驟:

在UsersController中解析出的部分,然后把請求傳遞給這個一個新的UserController。從命名可以看出,UsersController是針對多個用戶的,UserController是針對一個用戶的。

在UserController中實現(xiàn)get()方法。

使用_lookup()方法

Pecan的_lookup()方法是controller中的一個特殊方法,Pecan會在特定的時候調(diào)用這個方法來實現(xiàn)更靈活的URL路由。Pecan還支持用戶實現(xiàn)_default()_route()方法。這些方法的具體說明,請閱讀Pecan的文檔:routing。

我們這里只用到_lookup()方法,這個方法會在controller中沒有其他方法可以執(zhí)行且沒有_default()方法的時候執(zhí)行。比如上面的UsersController中,沒有定義/v1/users/如何處理,它只能返回404;如果你定義了_lookup()方法,那么它就會調(diào)用該方法。

_lookup()方法需要返回一個元組,元組的第一個元素是下一個controller的實例,第二個元素是URL path中剩余的部分。

在這里,我們就需要在_lookup()方法中解析出UUID的部分并傳遞給新的controller作為新的參數(shù),并且返回剩余的URL path。來看下代碼:

class UserController(rest.RestController):

    def __init__(self, user_id):
        self.user_id = user_id


class UsersController(rest.RestController):

    @pecan.expose()
    def _lookup(self, user_id, *remainder):
        return UserController(user_id), remainder

_lookup()方法的形式為_lookup(self, user_id, *remainder),意思就是會把/v1/users/中的部分作為user_id這個參數(shù),剩余的按照"/"分割為一個數(shù)組參數(shù)(這里remainder為空)。然后,_lookup()方法里會初始化一個UserController實例,使用user_id作為初始化參數(shù)。這么做之后,這個初始化的控制器就能知道是要查找哪個用戶了。然后這個控制器會被返回,作為下一個控制被調(diào)用。請求的處理流程就這么轉(zhuǎn)移到UserController中了。

實現(xiàn)API邏輯

實現(xiàn)前,我們要先修改一下我們返回的數(shù)據(jù),里面需要增加一個id字段。對應的User定義如下:

class User(wtypes.Base):
    id = wtypes.text
    name = wtypes.text
    age = int

現(xiàn)在,完整的UserController代碼如下:

class UserController(rest.RestController):

    def __init__(self, user_id):
        self.user_id = user_id

    @expose.expose(User)
    def get(self):
        user_info = {
            "id": self.user_id,
            "name": "Alice",
            "age": 30,
        }
        return User(**user_info)

使用curl來檢查一下效果:

? ~/programming/python/webdemo git:(master) ? $ curl http://localhost:8080/v1/users/29520c88de6b4c76ae8deb48db0a71e7
{"age": 30, "id": "29520c88de6b4c76ae8deb48db0a71e7", "name": "Alice"}
定義WSME類型的技巧

你可能會有疑問:這里我們修改了User類型,增加了一個id字段,那么前面實現(xiàn)的POST /v1/users會不會失效呢?你可以自己測試一下。(答案是不會,因為這個類型里的字段都是可選的)。這里順便講兩個技巧。

如何設(shè)置一個字段為強制字段

像下面這樣做就可以了(你可以測試一下,改成這樣后,不傳遞id的POST /v1/users會失?。?/p>

class User(wtypes.Base):
    id = wtypes.wsattr(wtypes.text, mandatory=True)
    name = wtypes.text
    age = int

如何檢查一個可選字段的值是否存在

檢查這個值是否為None是肯定不行的,需要檢查這個值是否為wsme.Unset。

實現(xiàn) PUT /v1/users/

這個和上一個API一樣,不過_lookup()方法已經(jīng)實現(xiàn)過了,直接添加方法到UserController中即可:

class UserController(rest.RestController):

    @expose.expose(User, body=User)
    def put(self, user):
        user_info = {
            "id": self.user_id,
            "name": user.name,
            "age": user.age + 1,
        }
        return User(**user_info)

通過curl來測試:

? ~/programming/python/webdemo git:(master) ? $ curl -X PUT http://localhost:8080/v1/users/29520c88de6b4c76ae8deb48db0a71e7 -H "Content-Type: application/json" -d "{"name": "Cook", "age": 50}"
{"age": 51, "id": "29520c88de6b4c76ae8deb48db0a71e7", "name": "Cook"}% 
實現(xiàn) DELETE /v1/users/

同上,沒有什么新的內(nèi)容:

class UserController(rest.RestController):

    @expose.expose()
    def delete(self):
        print "Delete user_id: %s" % self.user_id
總結(jié)

到此為止,我們已經(jīng)完成了我們的API服務了,雖然沒有實際的邏輯,但是本文搭建起來的框架也是OpenStack中API服務的一個常用框架,很多大項目的API服務代碼都和我們的webdemo長得差不多。最后再說一下,本文的代碼在github上托管著:diabloneo/webdemo。

現(xiàn)在我們已經(jīng)了解了包管理和API服務了,那么接下來就要開始數(shù)據(jù)庫相關(guān)的操作了。大部分OpenStack的項目都是使用非常著名的sqlalchemy庫來實現(xiàn)數(shù)據(jù)庫操作的,本系列接下來的文章就是要來說明數(shù)據(jù)庫的相關(guān)知識和應用。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/44182.html

相關(guān)文章

  • 通過demo學習OpenStack開發(fā)需的基礎(chǔ)知識 -- API服務(1)

    摘要:通過,也就是通過各個項目提供的來使用各個服務的功能。通過使用的方式是由各個服務自己實現(xiàn)的,比如負責計算的項目實現(xiàn)了計算相關(guān)的,負責認證的項目實現(xiàn)了認證和授權(quán)相關(guān)的。的服務都是使用的方式來部署的。 使用OpenStack服務的方式 OpenStack項目作為一個IaaS平臺,提供了三種使用方式: 通過Web界面,也就是通過Dashboard(面板)來使用平臺上的功能。 通過命令行,也就...

    Jason_Geng 評論0 收藏0
  • 通過demo學習OpenStack開發(fā)需的基礎(chǔ)知識 -- 數(shù)據(jù)庫(2)

    摘要:在實際項目中,這么做肯定是不行的實際項目中不會使用內(nèi)存數(shù)據(jù)庫,這種數(shù)據(jù)庫一般只是在單元測試中使用。接下來,我們將會了解中單元測試的相關(guān)知識。 在上一篇文章,我們介紹了SQLAlchemy的基本概念,也介紹了基本的使用流程。本文我們結(jié)合webdemo這個項目來介紹如何在項目中使用SQLAlchemy。另外,我們還會介紹數(shù)據(jù)庫版本管理的概念和實踐,這也是OpenStack每個項目都需要做的...

    mingzhong 評論0 收藏0
  • 通過demo學習OpenStack開發(fā)需的基礎(chǔ)知識 -- 單元測試

    摘要:本文將進入單元測試的部分,這也是基礎(chǔ)知識中最后一個大塊。本文將重點講述和中的單元測試的生態(tài)環(huán)境。另外,在中指定要運行的單元測試用例的完整語法是。中使用模塊管理單元測試用例。每個項目的單元測試代碼結(jié)構(gòu)可 本文將進入單元測試的部分,這也是基礎(chǔ)知識中最后一個大塊。本文將重點講述Python和OpenStack中的單元測試的生態(tài)環(huán)境。 單元測試的重要性 github上有個人畫了一些不同語言的學...

    douzifly 評論0 收藏0
  • 通過demo學習OpenStack開發(fā)需的基礎(chǔ)知識 -- 軟件包管理

    摘要:不幸的是,在軟件包管理十分混亂,至少歷史上十分混亂。的最大改進是將函數(shù)的參數(shù)單獨放到一個的文件中這些成為包的元數(shù)據(jù)。基于的版本號管理。的版本推導這里重點說明一下基于的版本號管理這個功能。開發(fā)版本號的形式如下。 為什么寫這個系列 OpenStack是目前我所知的最大最復雜的基于Python項目。整個OpenStack項目包含了數(shù)十個主要的子項目,每個子項目所用到的庫也不盡相同。因此,對于...

    blastz 評論0 收藏0
  • 通過demo學習OpenStack開發(fā)需的基礎(chǔ)知識 -- API服務(3)

    摘要:從上面的例子可以看出,決定響應類型的主要是傳遞給函數(shù)的參數(shù),我們看下函數(shù)的完整聲明參數(shù)用來指定返回值的模板,如果是就會返回內(nèi)容,這里可以指定一個文件,或者指定一個模板。用來做什么上面兩節(jié)已經(jīng)說明了可以比較好的處理請求中的參數(shù)以及控制返回值。 上一篇文章我們了解了一個巨啰嗦的框架:Paste + PasteDeploy + Routes + WebOb。后來OpenStack社區(qū)的人受不...

    ybak 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<