Egg 框架簡述 (一):簡單的骨架認知

Egg 框架模型簡述 (一)

  1. 簡單的骨架認知

1-1. 簡述
1-2. 簡單層級關(guān)系
1-3. 路由(Router)
1-4. 內(nèi)置對象(Router)
1-5. 配置(Config)
1-6. 中間件(MiddleWare)

  1. 插件使用(Plugins)
  2. 持久層方案(egg-sequelize)
  3. Worker 和 高效負載均衡
  4. Agent 代理角色
  5. 定時任務(wù)

筆者的其他文章推薦: 《JS 函數(shù)式編程思維簡述》

1. 簡述

官方文檔:https://eggjs.org

? ? ? ?egg.js是基于koa為底層螟碎,由阿里nodejs團隊封裝的企業(yè)級Web應(yīng)用解決方案明肮,以約束和規(guī)范化團隊開發(fā)肖爵,幫助開發(fā)團隊和開發(fā)人員降低開發(fā)和維護成本為核心設(shè)計理念的優(yōu)秀解決方案。
? ? ? ?官方文檔對 egg.js 的闡述極致細致疫向,撰寫本文的目的僅僅是對 Egg 的整體結(jié)構(gòu)做一個簡述,以引導學習為主要目的样屠。
? ? ? ? P.S. 本文示例代碼部分使用 TypeScript 進行編寫碴里,因此所有源碼文件都以 .ts 作為擴展名。

2. 簡單層級關(guān)系

? ? ? ?MVC(Model View Controller)是一種軟件設(shè)計模式移斩,一種以“展示界面肚医、業(yè)務(wù)邏輯绢馍、數(shù)據(jù)模型”分離的方法組織代碼,將業(yè)務(wù)設(shè)計打散分離肠套,以便實現(xiàn)高可復用性舰涌,及可維護性。
? ? ? ?早些年的項目中你稚,Controller層級中需要處理的事情非常之多:接受用戶請求瓷耙、驗證請求有效性、計算或發(fā)送請求至Model抓取數(shù)據(jù)或修改刁赖、計算響應(yīng)數(shù)據(jù)搁痛、返回響應(yīng)數(shù)據(jù)等。


image

? ? ? ?隨著一些項目逐漸龐大宇弛,這樣的設(shè)計造成了同一文件(或函數(shù))的代碼劇增鸡典,可維護性降低。同時涯肩,有一些可公用的業(yè)務(wù)操作也急需單獨提取轿钠,因此形成了獨立的業(yè)務(wù)層,分化了Controller部分病苗。


image

至此疗垛,形成了常見的軟件設(shè)計層次結(jié)構(gòu)的主線路:

  • View:作為用戶的 視圖表現(xiàn) 部分,常見的展示形式如瀏覽器作為載體的網(wǎng)頁硫朦、原生APP應(yīng)用界面贷腕、桌面應(yīng)用界面等,用于提供用戶界面以便收集咬展、響應(yīng)用戶行為產(chǎn)生的數(shù)據(jù)泽裳;
  • Controller:作為 控制器層 部分,控制用戶界面(View)的數(shù)據(jù)流轉(zhuǎn)途徑破婆,主要行為包含接收用戶數(shù)據(jù)請求涮总、發(fā)送請求至業(yè)務(wù)層(Service)、獲取業(yè)務(wù)層(Service)數(shù)據(jù)響應(yīng)祷舀,將響應(yīng)數(shù)據(jù)發(fā)送至用戶界面(View)瀑梗,或生成相應(yīng)的模板界面發(fā)送至用戶;
  • Service:作為 業(yè)務(wù)處理層 部分裳扯,主要負責收集及對數(shù)據(jù)進行相應(yīng)的運算處理抛丽,主要行為包含收集控制器請求數(shù)據(jù)、數(shù)據(jù)有效性驗證饰豺、運算亿鲜、請求數(shù)據(jù)模型(Model)、接收數(shù)據(jù)模型(Model)響應(yīng)消息冤吨、響應(yīng)結(jié)果至控制器等蒿柳;
  • Model:作為 數(shù)據(jù)模型層 部分饶套,主要用于將數(shù)據(jù)持久化(OUT)、查詢持久化數(shù)據(jù)(IN)其馏,常見行為如對數(shù)據(jù)庫進行操作凤跑、緩存數(shù)據(jù)庫數(shù)據(jù)等;
// 這是一個 egg 項目的目錄結(jié)構(gòu)
├─ app
│  ├─ controller
│  │  └─ home.ts
│  ├─ service
│  │  └─ home.ts
│  └─ model
│     └─ user.ts

3. 路由(Router)

? ? ? ?路由主要用于對數(shù)據(jù)流向進行指引,并處理請求轉(zhuǎn)發(fā)叛复。生活中常見的就是家用的路由器:

image

? ? ? ?在Web應(yīng)用進行前后端交互的過程中仔引,路由亦起到了通過URL地址定位控制器函數(shù)的作用,當然褐奥,更準確的說法應(yīng)該是定位靜態(tài)資源(無論是接口數(shù)據(jù)咖耘、頁面、圖片等其他文件)撬码。如假設(shè) app/controller/home.ts 中存在函數(shù) a() 和函數(shù) b()儿倒,我們約定了跳轉(zhuǎn) http://luv-ui.com/a 則執(zhí)行函數(shù) a();跳轉(zhuǎn) http://luv-ui.com/b 則執(zhí)行函數(shù) b()。這是Web應(yīng)用中的控制器-路由的常見表現(xiàn)手段呜笑。
? ? ? ?在JAVA項目中夫否,常見的路由表現(xiàn)手段例如

  1. 在XML配置文件中對路由進行統(tǒng)一描述:
<package name="default" namespace="/" extends="struts-default">
    <action name="aa" class="com.pro.controller.HomeAction" method="aa">
        <result>/aa.jsp</result>
    </action>
    <action name="bb" class="com.pro.controller.HomeAction" method="bb">
        <result>/bb.jsp</result>
    </action>
  </package>
  1. 在JAVA控制器文件中以注解的形式進行單獨描述:
@RestController
@RequestMapping("/home")
public class HomeController {
    @RequestMapping(value = "/aa", method = RequestMethod.POST)
    public Message aa(){
        // do something
    }
    @RequestMapping(value = "/bb", method = RequestMethod.POST)
    public Message bb(){
        // do something
    }
}

在 Egg 中,約定了路由統(tǒng)一由 app/router.ts 進行定義叫胁,理由是:通過統(tǒng)一的配置凰慈,我們可以避免路由規(guī)則邏輯散落在多個地方,從而出現(xiàn)未知的沖突驼鹅,集中在一起我們可以更方便的來查看全局的路由規(guī)則微谓。
因此,我們的目錄結(jié)構(gòu)變化為:

// 這是一個 egg 項目的目錄結(jié)構(gòu)
├─ app
│  ├─ controller
│  │   └─ home.ts
│  ├─ service
│  │   └─ home.ts
│  ├─ model
│  │   └─ user.ts
│  └─ router.ts

router.ts 中的處理方式如:

import { Application } from 'egg';
export default (app: Application) => {
        const { controller, router } = app;
        router.get('/aa', controller.home.aa);
        router.get('/bb', controller.home.bb);
        router.post('/user/cc', controller.user.cc);
        // ...
}

其業(yè)務(wù)邏輯如下圖所示:


image

4. 內(nèi)置對象

? ? ? ?Egg 中包含兩種內(nèi)置對象:

  1. Koa 繼承的對象:Application输钩、Context豺型、RequestResponse
  2. 框架擴展的對象:Controller买乃、Service姻氨、HelperConfig剪验、Logger

其主要作用如下:

對象名 注釋
Application 全局應(yīng)用對象肴焊,在一個應(yīng)用中,只會實例化一個碉咆,我們可以為其掛載一些全局的方法和對象。在框架運行時蛀恩,會在 Application 實例上觸發(fā)一些事件疫铜。我們幾乎可以在編寫應(yīng)用時的任何一個地方獲取到 Application 對象用于操作。
Context 一個請求級別的對象双谆,在每一次收到用戶請求時壳咕,框架都會實例化一個 Context 對象席揽,這個對象封裝了這次用戶請求的信息,并提供了許多便捷的方法來獲取請求參數(shù)或者設(shè)置響應(yīng)信息谓厘。通常在 Middleware幌羞、ControllerService 中獲取操作竟稳。
Request 一個請求級別的對象属桦,封裝了 Node.js 原生的 HTTP Request 對象,提供了一系列輔助方法獲取 HTTP 請求常用參數(shù)他爸。通過 Context 對象的 ctx.request 來獲取其實例聂宾。
Response 一個請求級別的對象,封裝了 Node.js 原生的 HTTP Response 對象诊笤,提供了一系列輔助方法設(shè)置 HTTP 響應(yīng)系谐。通過 Context 對象的 ctx.response 來獲取其實例。
Controller Controller 控制器的基類讨跟,所有的 Controller 都應(yīng)該繼承于該基類纪他。它提供了如下常用屬性:
- ctx: 獲取當前請求中的Context對象;
- app: 應(yīng)用的 Application 實例;
- config:當前應(yīng)用的配置對象晾匠。
- service:包含應(yīng)用所有 Service 的對象茶袒。
- logger:為當前 Controller 封裝的 logger 日志對象。
Service Service 業(yè)務(wù)層的基類混聊,所有的 Service 都應(yīng)該繼承于該基類弹谁。其提供的屬性和基類調(diào)用的方式,都與 Controller 類似句喜。
Helper 用來提供一些實用的 utility 函數(shù)预愤。它的作用在于我們可以將一些常用的隸屬于工具對象的動作抽離在 helper.js 里面成為一個獨立的函數(shù),避免邏輯分散各處咳胃,同時可以更好的編寫測試用例植康。
Config Egg 推薦應(yīng)用開發(fā)遵循配置和代碼分離的原則,將一些需要硬編碼的業(yè)務(wù)配置都放到配置文件中展懈。在不同的運行環(huán)境可以應(yīng)用不同的配置改變框架運行方式销睁。(如開發(fā)環(huán)境和生產(chǎn)環(huán)境不同,對數(shù)據(jù)源存崖、日志冻记、插件等的應(yīng)用也可能有所不同)
Logger Egg 內(nèi)置了功能強大的日志功能,可以非常方便的打印各種級別的日志到對應(yīng)的日志文件中来惧,每一個 logger 對象都提供了 4 個級別的方法:
- logger.debug():用于調(diào)試階段日志記錄冗栗。
- logger.info():用于正常流程日志記錄。
- logger.warn():用于警告級別的日志記錄。
- logger.error():用于嚴重錯誤的日志記錄隅居。

4.1 應(yīng)用過程 - Controller

? ? ? ?結(jié)合數(shù)據(jù)流轉(zhuǎn)過程钠至,當數(shù)據(jù)傳遞至 Controller 時,我們需要進行相應(yīng)的處理胎源。Egg 約定了所有的 Controller 對象都放在 app/controller/ 位置棉钧。 Controller 部分大致長這個樣子:

import { Context, Controller } from 'egg';

export default class HomeController extends Controller {
    constructor(ctx: Context) {
        super(ctx);
        // do something
    }
    
    // 具體的請求函數(shù)
    public async foo() {
        const { ctx } = this; // this 代表當前 Controller 對象本身
        const { code } = ctx.query; // 獲取 Get 請求中的參數(shù) code
        ctx.body = await ctx.service.home.foo( code ); // 異步調(diào)用 Service 對象中的相應(yīng)業(yè)務(wù)處理,并將結(jié)果對調(diào)用者響應(yīng)
    }
}

在應(yīng)用的過程中,我們也可以創(chuàng)建自己的 BaseController 繼承自 Controller 基類涕蚤。再由具體的控制器類繼承自 BaseController 宪卿,以便于實現(xiàn)統(tǒng)一的代碼部分封裝。
? ? ? ?該示例中赞季,默認導出的類命名方式為 XxxController 愧捕,此時,在 router.ts 中申钩,便可以通過 app.controller.home.foo 來指定業(yè)務(wù)流轉(zhuǎn)至該函數(shù)次绘,來獲取相應(yīng)資源。
? ? ? ?同理撒遣,ctx 對象中包含的 service 對象, 囊括了所有 app/service/ 層級下的 Service 繼承類,因此可以簡單的使用 ctx.service.xxx.yyy 來定位業(yè)務(wù)函數(shù)邮偎。

4.2 應(yīng)用過程 - Service

? ? ? ?在業(yè)務(wù)處理的 Service 部分,Egg 約定了所有的 Service 對象都放在 app/service/ 位置义黎。大概長這個樣子:

import { Context, Service } from 'egg';

export default class HomeService extends Service {
    constructor(ctx: Context) {
        super(ctx);
        // do something
    }
    
    // 具體的業(yè)務(wù)處理函數(shù)
    public async foo( code: string ) {
        const { ctx } = this; // this 代表當前 Service 對象本身
        const where = { code };
        return await ctx.model.user.findAll({where}); // 通過 Model(數(shù)據(jù)模型) 部分獲取靜態(tài)資源
    }
}

至此禾进,我們所看到的業(yè)務(wù)流程就變成了這個樣子:


image

5. 配置(Config)

? ? ? ?Egg 使用代碼的方式配置當前應(yīng)用的運行方式,Egg 約定了所有的配置文件都放在 ./config/ 位置廉涕。目錄結(jié)構(gòu)如下:

// 這是一個 egg 項目的目錄結(jié)構(gòu)
├─ app
│  ├─ controller
│  │   └─ home.ts
│  ├─ service
│  │   └─ home.ts
│  ├─ model
│  │   └─ user.ts
│  └─ router.ts
├─ config
│  ├─ config.default.ts
│  ├─ config.prod.ts
│  └─ config.local.ts

配置文件返回的是一個 object 對象泻云,可以覆蓋框架的一些配置,應(yīng)用也可以將自己業(yè)務(wù)的配置放到這里方便管理狐蜕。配置文件大概長這個樣子:

// 配置文件的寫法 (config.default.ts)
import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';

export default (appInfo: EggAppInfo) => {
        const config = {} as PowerPartial<EggAppConfig>;
        // 其他的配置內(nèi)容...
        return {
          ...config
        };
}

我們常常在配置文件中定義 中間件宠纯、日志、其他插件 的運行方式层释,比如在整個應(yīng)用啟動的過程中婆瓜,運行哪些中間件;日志輸出的方式贡羔、其他一些插件在運行過程中的參數(shù)配置之類的廉白。這樣的配置,可能會區(qū)分為 開發(fā)環(huán)境乖寒、測試環(huán)境猴蹂、生產(chǎn)環(huán)境 等等,在每個環(huán)境中的配置方式都可能有所不同楣嘁。例如你的本地開發(fā)使用本地數(shù)據(jù)庫跑數(shù)據(jù)磅轻,連接本地庫的 IP覆获、用戶、密碼瓢省、端口等,與線上環(huán)境的肯定有所不同痊班。因此勤婚,針對不同環(huán)境應(yīng)用不同的配置非常有意義。

值得注意的是涤伐,config.default 在任何環(huán)境中都會被加載馒胆,但加載的過程中,若環(huán)境配置中有重復項凝果,則會覆蓋 default 中的內(nèi)容祝迂。


image

由于 config.{env}.ts 的優(yōu)先級更大 (它需要覆蓋默認配置,來彰顯自己的獨立性)器净,因此應(yīng)用啟動時配置文件的加載順序是:

  • config.default.ts
  • config.{env}.ts

如何變更當前運行環(huán)境中的啟動配置:

  1. config 目錄下新建文件 env型雳,在文件中鍵入當前環(huán)境關(guān)鍵字。如鍵入 prod山害,則在應(yīng)用啟動時加載文件 config/config.prod.ts;
  2. 配置環(huán)境變量 EGG_SERVER_ENV 指定運行環(huán)境纠俭,啟動應(yīng)用的過程中會讀取 process.env.EGG_SERVER_ENV 來判斷當前應(yīng)使用何種方式配置應(yīng)用。

注意浪慌,與其他語言開發(fā)項目不同的是冤荆,nodejs 作為服務(wù)器端環(huán)境,自提供了一個 webserver权纤,而無需使用其他容器作為應(yīng)用載體钓简。因此,應(yīng)用的啟動就代表著服務(wù)器的啟動汹想。

此時外邓,我們的項目結(jié)構(gòu)變成了這個樣子:


image

6. 中間件(MiddleWare)

? ? ? ?Egg 是基于 Koa 實現(xiàn)的,所以 Egg 的中間件形式和 Koa 的中間件形式是一樣的欧宜,都是基于洋蔥圈模型坐榆。每次我們編寫一個中間件,就相當于在洋蔥外面包了一層冗茸。類似于這個樣子:

image

? ? ? ?Egg 約定一個中間件是一個放置在 app/middleware/ 下的獨立文件席镀,并會 exports 一個函數(shù)。函數(shù)接收兩個參數(shù):
? ? ? ? - options: 中間件的配置項夏漱,框架會將 app.config[${middlewareName}] 傳遞進來豪诲。
? ? ? ? - app: 當前應(yīng)用 Application 的實例。
? ? ? ? 例如挂绰,我們寫了一個驗證請求中是否攜帶 token 的中間件:

// 一個中間件 ( app/middleware/xtoken.ts )
import { Context } from 'egg';

export default (options) => {
    return async (ctx: Context, next: Function) => {
        // 排除登錄路徑, 其他路徑需通過 token 校驗
        const { url } = ctx.request;
        if (!options.exclude[url]) {
            return await next();
        }
        // 檢查 token 有效性...
    };
}

? ? ? ?中間件編寫完成之后屎篱,我們需要在配置文件中服赎,配置該中間件,使其生效:

// 配置文件 (config.default.ts)
import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';

export default (appInfo: EggAppInfo) => {
        const config = {} as PowerPartial<EggAppConfig>;
        // 配置中間件
        config.middleware = ['xtoken', 'otherMiddleWare'];
        // 為中間件添加動態(tài)配置
        config.xtoken = {
            exclude: { '/access': true }
        };
        // 其他的配置內(nèi)容...
        return {
          ...config
        };
}

屆時交播,我們通過該中間件重虑,描述了所有的請求必須經(jīng)過 token 校驗,除了排除列表中的請求秦士。當然缺厉,這是應(yīng)用中使用中間件的方式,還可以在框架隧土、插件提针,乃至于在 router 中明確哪個請求才會由中間件進行處理。
此時的目錄結(jié)構(gòu)如下:

// 這是一個 egg 項目的目錄結(jié)構(gòu)
├─ app
│  ├─ controller
│  │   └─ home.ts
│  ├─ service
│  │   └─ home.ts
│  ├─ model
│  │   └─ user.ts
│  ├─ middleware
│  │   └─ xtoken.ts
│  └─ router.ts
├─ config
│  ├─ config.default.ts
│  ├─ config.prod.ts
│  └─ config.local.ts

多個中間件時

? ? ? ?當應(yīng)用中包含有多個中間件曹傀,則中間件的加載順序以 config 中聲明中間件的數(shù)組順序而定辐脖,假設(shè)我們在中間件定義中聲明:config.middleware = ['mw1', 'mw2', 'mw3']; ,則中間件的加載順序為:mw1 -> mw2 -> mw3皆愉,在請求攔截處理中的嵌套關(guān)系為:

image

由此可見嗜价,最后被加載的中間件,將置于請求過程中的最內(nèi)層進行攔截幕庐。

更簡單的攔截處理

? ? ? ?在上述示例中炭剪,我們在 config 配置文件中,在聲明中間件結(jié)束時翔脱,為 xtoken 設(shè)置了自定義屬性 exclude 作為攔截條件奴拦,在中間件的定義文件 app/middleware/xtoken.ts 中以參數(shù) options 獲取了攔截條件并執(zhí)行相應(yīng)的邏輯。而在實際開發(fā)應(yīng)用時届吁,中間件已配備了幾個通用參數(shù)错妖,用以更簡便的設(shè)置中間件的狀態(tài)

屬性名 類型 注釋
enable boolean 控制中間件是否開啟。
match string疚沐、stringp[]暂氯、RegEx、function 設(shè)置只有符合某些規(guī)則的請求前綴才會經(jīng)過這個中間件亮蛔。
ignore string痴施、stringp[]、RegEx究流、function 設(shè)置符合某些規(guī)則的請求前綴不經(jīng)過這個中間件辣吃。

因此,我們在 config 中的攔截規(guī)則便可以簡單的改造為:

// 配置文件 (config.default.ts)
import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';

export default (appInfo: EggAppInfo) => {
        const config = {} as PowerPartial<EggAppConfig>;
        // 配置中間件
        config.middleware = ['xtoken', 'otherMiddleWare'];
        // 為中間件添加動態(tài)配置
        config.xtoken = {
            // 配置所有的前綴為 /access 或 /morepath 的 url 不經(jīng)過該中間件
            ignore: [ '/access', '/morepath' ]
        };
        // 其他的配置內(nèi)容...
        return {
          ...config
        };
}

而在中間件文件中芬探,便可以省去了對于攔截條件的校驗 -

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末神得,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子偷仿,更是在濱河造成了極大的恐慌哩簿,老刑警劉巖宵蕉,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異节榜,居然都是意外死亡羡玛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門宗苍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缝左,“玉大人,你說我怎么就攤上這事浓若。” “怎么了蛇数?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵挪钓,是天一觀的道長。 經(jīng)常有香客問我耳舅,道長碌上,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任浦徊,我火速辦了婚禮馏予,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盔性。我一直安慰自己霞丧,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布冕香。 她就那樣靜靜地躺著蛹尝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悉尾。 梳的紋絲不亂的頭發(fā)上突那,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音构眯,去河邊找鬼愕难。 笑死,一個胖子當著我的面吹牛惫霸,可吹牛的內(nèi)容都是我干的猫缭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼壹店,長吁一口氣:“原來是場噩夢啊……” “哼饵骨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茫打,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤居触,失蹤者是張志新(化名)和其女友劉穎妖混,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轮洋,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡制市,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了弊予。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祥楣。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖汉柒,靈堂內(nèi)的尸體忽然破棺而出误褪,到底是詐尸還是另有隱情,我是刑警寧澤碾褂,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布兽间,位于F島的核電站,受9級特大地震影響正塌,放射性物質(zhì)發(fā)生泄漏嘀略。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一乓诽、第九天 我趴在偏房一處隱蔽的房頂上張望帜羊。 院中可真熱鬧,春花似錦鸠天、人聲如沸讼育。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窥淆。三九已至,卻和暖如春巍杈,著一層夾襖步出監(jiān)牢的瞬間忧饭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工筷畦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留词裤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓鳖宾,卻偏偏與公主長得像吼砂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鼎文,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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