Python API 類型系統(tǒng)的設(shè)計(jì)與演變

API 與類型系統(tǒng)

由于眾所周知的原因,至今仍有大量生產(chǎn)環(huán)境的代碼跑在 Python 2.7 之上祷蝌,在 Python 2 的世界里,并沒有一個官方的類型系統(tǒng)實(shí)現(xiàn)。那么生產(chǎn)環(huán)境的類型系統(tǒng)是如何實(shí)現(xiàn)的呢春宣,為什么一定要在在線服務(wù)上實(shí)現(xiàn)類型系統(tǒng)?下文將針對這兩個問題進(jìn)行深入討論嫉你。

什么是 API 的類型系統(tǒng)

人們常說一門編程語言的類型系統(tǒng)月帝,通常指一門編程語言在表達(dá)式的類型意義上所具有的表達(dá)能力。而對于 API 來說幽污,對于其輸入(參數(shù))和輸出(響應(yīng))都能夠有完善的類型表達(dá)嚷辅,那么就可以認(rèn)為它具有了基本的類型系統(tǒng)。

一個包含了方法(Method/HTTP verb)和路徑(Path)的 API距误,常常稱之為一個訪問點(diǎn)(endpoint)或 API簸搞,每一個 API 具有一個描述性質(zhì)的聲明,稱之為 Schema准潭,Schema 可以有多種定義方式趁俊,但至少會包含參數(shù)(請求字段及其類型定義)和響應(yīng)(狀態(tài)碼,響應(yīng)字段及類型定義)刑然。比較典型的是?OpenAPI?規(guī)范的定義寺擂,該規(guī)范將在下文詳細(xì)介紹。

那么在線服務(wù)上實(shí)現(xiàn)類型系統(tǒng)有何意義?如果一個 API Framework 或者 RPC Remote Call 沒有類型系統(tǒng)沽讹,會出現(xiàn)什么樣的問題呢般卑?

為什么要在在線服務(wù)上實(shí)現(xiàn)類型系統(tǒng)

本文認(rèn)為在線服務(wù)上的類型系統(tǒng)至少有以下幾種直接的作用:

驗(yàn)證參數(shù)的可靠性爽雄,由于在服務(wù)開發(fā)時蝠检,不能信任用戶的輸入,應(yīng)做好最壞的假設(shè)焰檩,就如同墨菲在靜靜地看著你峦萎。

自動生成文檔和超文本鏈接忆首,一個完善的 Schema 系統(tǒng)妒潭,可以為?HATEOAS(Hypertext As The Engine Of Application State) 提供支持。

自動生成 Definition 文件(比如?thrift揣钦,protobuf?等 RPC 定義)雳灾,用于在服務(wù)端提供兼容多種協(xié)議的網(wǎng)關(guān),在客戶端為終端用戶提供本地驗(yàn)證機(jī)制冯凹。

和異常系統(tǒng)結(jié)合谎亩,可以為異常診斷和 Traceback 提供支持炒嘲,使用更有針對性的診斷方式。

可以和接口測試相結(jié)合匈庭,推斷返回值的類型(但 Python 2 的庫實(shí)現(xiàn)比較龐雜夫凸,很難實(shí)現(xiàn)這一點(diǎn))。

安全性和可解釋性

API 類型系統(tǒng)的作用阱持,最終可以總結(jié)為在 「 安全性 」和「 可解釋性」 上的提升夭拌。

如果沒有一個一致的類型系統(tǒng),往往要使用大量冗余代碼(自定義函數(shù))來進(jìn)行參數(shù)校驗(yàn)衷咽,而非通過自定義類型來驗(yàn)證鸽扁。并且耗費(fèi)大量的精力人工編寫接口文檔,在接口變更后還要人工修改和校對镶骗。

在類型系統(tǒng)中桶现,安全性和可解釋性是互相依存的關(guān)系,僅從安全性考慮鼎姊,如果代碼結(jié)構(gòu)合理骡和,使用自定義函數(shù)進(jìn)行參數(shù)校驗(yàn)也是可以接受的,但函數(shù)在可解釋性上是弱于類型系統(tǒng)的相寇,對于接口附加的元信息(比如參數(shù)類型慰于,參數(shù)是否可選,參數(shù)描述)難以自然地表述裆赵。

類型系統(tǒng)在提升了安全性的同時,還兼顧了系統(tǒng)的可解釋性跺嗽,這是在服務(wù)治理上非常需要的一點(diǎn)战授。

類型系統(tǒng)實(shí)踐

下面以 Python 2.7 為例,詳細(xì)介紹下如何在一個在線服務(wù)上實(shí)現(xiàn)類型系統(tǒng)桨嫁,以及類型系統(tǒng)可以幫助研發(fā)人員做哪些有意義的事情植兰。

marshmallow

Python 2 中沒有一個官方的類型系統(tǒng)實(shí)現(xiàn),所以在 API 參數(shù)的驗(yàn)證中璃吧,往往是通過外掛第三方 Schema 實(shí)現(xiàn)的楣导。

marshmallow 是本文選用的一個對類型系統(tǒng)進(jìn)行建模的 Python 庫,它有著極高的流行程度畜挨,提供了基本的類型定義筒繁、參數(shù)驗(yàn)證功能和序列化 / 反序列化機(jī)制。

現(xiàn)在假設(shè)研發(fā)團(tuán)隊(duì)要開發(fā)一個用戶相關(guān)的接口巴元,首先要對用戶這個服務(wù)資源進(jìn)行抽象定義毡咏,一個基本的 Schema 定義如下:

清單 1. 一個用戶接口參數(shù)模式定義

1

2

3

4

5

6

7

8

9

10

11

12

13

# -*- coding: utf-8 -*-


import re

from marshmallow import Schema, fields, validate

from myapp import fields as myfields



class UserSchema(Schema):

????user_id = myfields.UserId(required=True, help=u'用戶的唯一 ID')

????nickname = fields.Str(required=True,

??????????????????????????validate=validate.Length(min=2, max=20),

??????????????????????????help=u'用戶的昵稱')

????email = fields.Email(required=True, u'用戶的郵箱,不可重復(fù)')

marshmallow 自帶了許多內(nèi)建類型逮刨,比如 Email呕缭,URL,UUID 等,研發(fā)人員也可以根據(jù)業(yè)務(wù)來定制自定義類型恢总,比如上文的 UserId 可以像這樣定義:

清單 2. 自定義類型示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

# -*- coding: utf-8 -*-


import re



class UserId(fields.Field):

????""" 長度為 10 - 17 的迎罗,由字母、數(shù)字片仿、下劃線組成的 ID """

????pattern = re.compile(r'^[a-zA-Z0-9\_]{10-17}$')


????# 必選的

????default_error_messages = {

????????'invalid': u'不是一個有效的用戶 ID',

????????'format': u'{value} 無法被格式化為 ID 字符串',

????}


????def _serialize(self, value, attr, obj):

????????return value


????def _deserialize(self, value, attr, data):

????????# 可以使用任何驗(yàn)證方式纹安,而不僅僅是正則表達(dá)式

????????if not self.pattern.match(value):

????????????self.fail('invalid', value=value)

????????return value

服務(wù)開發(fā)人員也可以自己寫裝飾器或使用開源的庫,比如?webargs?來根據(jù)這個 Schema 做參數(shù)驗(yàn)證(以 Flask 為例):

清單 3. Web 框架集成示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

# -*- coding: utf-8 -*-


from flask import Flask, jsonify

from webargs.flaskparser import use_args


from myapp.schema import UserSchema


app = Flask(__name__)



@app.route('/', methods=('GET',))

@use_args(UserSchema)

def echo_user(args):

????return jsonify(**args)


if __name__ == '__main__':

????app.run()

在生產(chǎn)環(huán)境的服務(wù)中,通常會選擇重載 API 注冊用的裝飾器(比如 @app.route 和 @use_args)來收集 API 的定義存儲到一個全局的對象里(可能是遠(yuǎn)程對象)滋戳,來實(shí)現(xiàn)框架級的 API 反射機(jī)制钻蔑,以允許服務(wù)實(shí)例在運(yùn)行時拿到所有已注冊的 API 的聲明,以給第三方工具 / RPC 客戶端提供最新的 Schema奸鸯。

在上面的代碼定義里咪笑,大家可以發(fā)現(xiàn) API 類型系統(tǒng)中幾個重要的功能都已經(jīng)存在了:

Schema 允許以接口為粒度定義類型聲明

fields 允許自定義類型(包括類型的校驗(yàn)規(guī)則,描述和錯誤信息)

validate 允許自定義校驗(yàn)規(guī)則

webargs 幫助類型系統(tǒng)與框架進(jìn)行集成

但僅僅有這些就夠了嗎娄涩?

validator 和枚舉

在繁忙的業(yè)務(wù)系統(tǒng)開發(fā)過程中窗怒,通常需要一定程度的抽象來增強(qiáng)代碼的可重用性,比如正則表達(dá)式和枚舉等蓄拣。

枚舉是一種特殊的類型扬虚,在線服務(wù)對它的可描述性有著更多的訴求。在閱讀一個 API 的定義時球恤,人們看到枚舉字段辜昵,不僅僅想看到這個字段期望什么樣的枚舉值,更想看到每一個枚舉值所代表的涵義咽斧,這就要求類型系統(tǒng)擴(kuò)展(或許是約束)枚舉值的定義堪置。

Python 內(nèi)置的枚舉類型有它的優(yōu)勢,但枚舉值使用了包裝類型张惹,取值時需要通過 .value 函數(shù)來獲取舀锨,而本文所描述的服務(wù)已經(jīng)在線上運(yùn)行許久了,改造工程浩大宛逗,于是采用了類似于 Flask Config Object 的定義風(fēng)格坎匿。

清單 4. 一種可選的枚舉聲明定義

1

2

3

4

5

6

7

8

class UserStateEnum(object):

????OK = 0

????PENDING = 1


????__desc__ = {

????????OK: u'有效用戶',

????????PENDING: u'封禁用戶'

????}

通過定義一個類,約定類屬性名大寫為枚舉屬性雷激,描述信息放在特殊的字段里替蔬,以此來表示枚舉類型。

這是一個關(guān)鍵的思維模式:在線服務(wù)在擴(kuò)展時必須要考慮?API?的可解釋性屎暇。

異常和 RFC 4918

在線服務(wù)對于異常系統(tǒng)的訴求是將異常按照危重等級進(jìn)行分離进栽,保證高危異常的可追溯性,以及低危異常的可解釋性恭垦。

在理想的情況下快毛,可以把異常簡單分為三類:

系統(tǒng)異常格嗅,由于系統(tǒng)故障或程序 Bug 導(dǎo)致的,應(yīng)及時發(fā)送到 Issue Tracking 的系統(tǒng)中并發(fā)送警報唠帝。

業(yè)務(wù)異常屯掖,由于用戶的輸入不符合業(yè)務(wù)邏輯導(dǎo)致的異常,比如用戶不存在襟衰√可以從日志中審計(jì),可能會需要進(jìn)行 Issue Tracking瀑晒,無需報警绍坝。

參數(shù)錯誤,用戶的輸入不符合文檔約定(契約)苔悦,比如期望參數(shù)是一個 URL轩褐,但傳來一個普通字符串。同樣可以從日志中審計(jì)玖详,但無需進(jìn)行 Issue Tracking把介,無需報警。

在責(zé)權(quán)劃分上蟋座,類型系統(tǒng)應(yīng)該只包含了第三類異常拗踢,不涉及業(yè)務(wù)邏輯和系統(tǒng)異常的處理。

由于本文所描述的 Web 層遵循 REST 語義來進(jìn)行服務(wù)開發(fā)向臀,最早的 HTTP Status 使用了 500巢墅,隨著類型系統(tǒng)的完善,響應(yīng)狀態(tài)碼也逐漸細(xì)分券膀,上面三類異常分別對應(yīng) 500君纫、400、422 三種 Status Code三娩。

關(guān)于 422 狀態(tài)碼的選取庵芭,可以參考?RFC 4918?和參考文獻(xiàn)中一些有益的討論妹懒。

OpenAPI 與可解釋性

對于在線服務(wù)的描述和定義雀监,本文比較傾向于參考 OpenAPI 規(guī)范,原因是它對機(jī)器更加友好眨唬,有著嚴(yán)謹(jǐn)?shù)?Spec 定義会前,有利于生成和分析,同時背后有谷歌匾竿、微軟等商業(yè)公司和強(qiáng)大的社區(qū)支持瓦宜。

相對于API BluePrintRAML?等規(guī)范所強(qiáng)調(diào)的人類可讀性(Human Readable)岭妖,Swagger?更加注重定義的規(guī)范化和通用性临庇,鼓勵社區(qū)共同推進(jìn)規(guī)范的演進(jìn)反璃,在本文寫作時,OpenAPI Specification(OAS) 3.0?已經(jīng)發(fā)布假夺,一個欣欣向榮的社區(qū)也是影響本文選型的關(guān)鍵因素淮蜈。

類型系統(tǒng)在這里的作用是,對在線服務(wù)的接口定義進(jìn)行描述已卷,并生成一個符合 OpenAPI 規(guī)范定義的 JSON 文檔梧田,以支持文檔生成工具(比如?Swagger)、前端 Mock 工具(比如國內(nèi)的?Easy-Mock)侧蘸、接口測試工具(比如下文提到的基于?py.test?的實(shí)現(xiàn))和前端驗(yàn)證庫的需要裁眯。

在 OpenAPI 規(guī)范中,與類型系統(tǒng)相關(guān)的部分主要集中在 paths讳癌、schema穿稳、data types 三個章節(jié),本文主要實(shí)現(xiàn) data types 章節(jié)中所描述的類型與?marshmallow?類型之間的映射析桥,這里舉幾個特殊的例子司草。

表 1 OAS Data Type 與 Marshmallow Type 的映射

OAS TypeOAS FormatMarshmallow描述

stringemailEmail電子郵件

stringuuidUUIDUUID

integerenumEnum(Int)上文中定義的枚舉類型

stringList(Str)字符串列表

在?OpenAPI?的定義里,每一個類型(type)都有一個可選的格式(format)可以定義泡仗,通常是根據(jù)業(yè)務(wù)所需來定制埋虹,這里取 fields 類的類名(小寫)作為 format 值。

這里有一個特例娩怎,對于容器類型搔课,比如 Enum 和 List,它們的類型取決于它所包裝的類型截亦,對于在線服務(wù)爬泥,常常需要類型系統(tǒng)具有確定性,是不允許 Union 類型存在的崩瓤,這樣設(shè)計(jì)主要是為了減少序列化 / 反序列化的成本袍啡,同時簡化代碼的分支邏輯。

這里舉例說明容器類型的類型定義是如何翻譯成?OpenAPI?的類型定義的:

清單 5. List(Int) 翻譯為 OpenAPI/OAS 示例

1

2

3

4

5

6

{

??"type": "array",

??"items": {

????"type": "integer"

??}

}

清單 6. Enum(Int) 翻譯為 OpenAPI/OAS 示例

1

2

3

4

5

6

7

8

9

{

??"schema": {

????"type": "integer",

????"enum": [

??????400

??????404

????]

??}

}

接口測試與文檔生成

在完成了上述基礎(chǔ)的工作之后却桶,就要與測試框架進(jìn)行集成了境输。

類型系統(tǒng)與測試框架集成的意義是什么呢?可以分兩個類別來看待:

第一個類別是需要嚴(yán)格限定接口響應(yīng)字段的類型颖系,這個時候開發(fā)人員會在代碼中對接口的響應(yīng)做類型聲明嗅剖,那么在測試用例中,類型系統(tǒng)的作用自然就是對響應(yīng)字段類型的校驗(yàn)了嘁扼,本文稱之為嚴(yán)格模式信粮。

第二個類別是接口響應(yīng)無類型聲明,那么接口的響應(yīng)定義就不再具備可解釋性趁啸,而可解釋性對自動化的文檔生成是最重要的因素强缘。本文所描述的在線業(yè)務(wù)處于這樣一個階段督惰,所以在類型系統(tǒng)實(shí)現(xiàn)中主要解決的就是這個問題。

如果沒有響應(yīng)參數(shù)的類型定義旅掂,就需要推導(dǎo)響應(yīng)的類型姑丑,類型推導(dǎo)的方式有兩種,靜態(tài)的和動態(tài)的(運(yùn)行時)辞友。

靜態(tài)分析在 Python 2 中的實(shí)現(xiàn)難度比較高栅哀,因?yàn)榇罅康牡谌綆於紱]有明確的類型信息,同時許多要經(jīng)過網(wǎng)絡(luò)的上下游服務(wù)也都沒有提供嚴(yán)格的定義称龙,難以在這樣復(fù)雜的環(huán)境中通過靜態(tài)分析拿到接口響應(yīng)類型信息留拾。

由于團(tuán)隊(duì)有寫接口測試的習(xí)慣,最終選擇了在運(yùn)行接口測試的時候鲫尊,和 Python 的測試框架 py.test 集成痴柔,通過收集接口測試的返回值來做運(yùn)行時的類型推導(dǎo)。

下面盡可能簡單地描述一下一個真實(shí)的實(shí)現(xiàn)疫向,本文使用?yaml?來做用例的定義咳蔚,比如:

清單 7. 使用 Yaml 描述的測試用例示例

1

2

3

4

5

6

7

8

- uri: /echo

??method: GET

??desc: 測試 ECHO 服務(wù)

??status: 200

??params:

????ping: "pong"

??responses:

????ping: "pong"

Pytest 提供了參數(shù)化的功能可以用來生成用例,apis 是用例定義的列表:

清單 8. 描述文件與 pytest 集成的示例

1

2

3

4

5

@pytest.mark.parametrize("case", apis)

def test_api(case, case_manager, mocker):

????case_obj = case_manager.add(case)

????case_obj.run(mocker)

????print(case_obj.real_response)

用例執(zhí)行后搔驼,用例的響應(yīng)被保存下來谈火,再嘗試對每一個響應(yīng)字段的值做一個簡單的類型推導(dǎo)。

清單 9. 一種響應(yīng)值類型推導(dǎo)的實(shí)現(xiàn)示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

pattern_inferer_map = {

????date_pattern: {'type': 'string', 'format': 'date'},

????datetime_pattern: {'type': 'string', 'format': 'date-time'},

????ip_pattern: {'type': 'string', 'format': 'ip'},

????uuid_pattern: {'type': 'string', 'format': 'uuid'},

????base64_pattern: {'type': 'string', 'format': 'byte'},

????// ...

}


def infer_value(value):

????if isinstance(value, string_types):

????????for pattern, type_info in pattern_inferer_map.items():

????????????if pattern.match(value):

????????????????return type_info

????????return {'type': 'string'}

????elif isinstance(value, int):

????????return {'type': 'number', 'format': 'int64'}

????elif isinstance(value, float):

????????return {'type': 'number', 'format': 'double'}

????elif isinstance(value, bool):

????????return {'type': 'boolean'}



def inferer_response(response):

????return {k: infer_value(v) for k, v in response.items()}

暴力地對 Python 類型和 OAS 的類型做一個映射舌涨,這樣就用最簡單的辦法完成了一個接口響應(yīng)的類型推斷糯耍。

很容易看出,這樣的類型推斷會存在許多問題囊嘉,比如 int 和 float 類型的精度無法表達(dá)温技,字符串類型的 format 可能會有誤判,尤其依賴完備的測試用例等等扭粱。

但本文為什么仍然愿意推薦這種方法舵鳞,因?yàn)樗梢允褂米钚〉某杀荆畲笙薅鹊貪M足研發(fā)人員的基本訴求——拿到接口相應(yīng)的基本類型信息琢蛤,提升可解釋性蜓堕,這是類型系統(tǒng)中非常重要的一部分。

小結(jié)

就這樣虐块,本文通過重載服務(wù)框架的路由裝飾器來收集 API 的參數(shù)類型信息俩滥,通過接口測試來收集 API 的響應(yīng)類型信息嘉蕾,通過注冊自定義的枚舉類型和業(yè)務(wù)類型贺奠,再配合框架本身的屬性,就可以生成定制化的错忱、符合 OpenAPI 規(guī)范的文檔了儡率。

擁有類型系統(tǒng)的在線服務(wù)挂据,在接口校驗(yàn)、異常處理儿普、測試和文檔生成等方面都有全方位的提升崎逃,滿足了工程師們對一個服務(wù)在安全性和可解釋性上的基本訴求,這是非常值得投入的一件事眉孩。

新世界的戰(zhàn)鼓

上文介紹了過去兩年間个绍,我在 Python 2 在線服務(wù)類型系統(tǒng)中的一些思考與實(shí)踐。與此同時 Python 也在迅速發(fā)展浪汪,包括 Instgram 在內(nèi)的諸多公司巴柿,已將 Python 3 應(yīng)用于生產(chǎn)環(huán)境了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末死遭,一起剝皮案震驚了整個濱河市广恢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呀潭,老刑警劉巖钉迷,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钠署,居然都是意外死亡糠聪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門谐鼎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枷颊,“玉大人,你說我怎么就攤上這事该面∝裁纾” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵隔缀,是天一觀的道長题造。 經(jīng)常有香客問我,道長猾瘸,這世上最難降的妖魔是什么界赔? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮牵触,結(jié)果婚禮上淮悼,老公的妹妹穿的比我還像新娘。我一直安慰自己揽思,他們只是感情好袜腥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钉汗,像睡著了一般羹令。 火紅的嫁衣襯著肌膚如雪鲤屡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天福侈,我揣著相機(jī)與錄音酒来,去河邊找鬼。 笑死肪凛,一個胖子當(dāng)著我的面吹牛堰汉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伟墙,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼衡奥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了远荠?” 一聲冷哼從身側(cè)響起矮固,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎譬淳,沒想到半個月后档址,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡邻梆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年守伸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浦妄。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡尼摹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出剂娄,到底是詐尸還是另有隱情蠢涝,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布阅懦,位于F島的核電站和二,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏耳胎。R本人自食惡果不足惜惯吕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怕午。 院中可真熱鬧废登,春花似錦、人聲如沸郁惜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吏颖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恨樟,已是汗流浹背半醉。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留劝术,地道東北人缩多。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像养晋,于是被迫代替她去往敵國和親衬吆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

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