Koa+Mongodb 搭建商家店鋪服務(wù)端項(xiàng)目總結(jié)

背景

自從完成了客戶端管理后臺項(xiàng)目后,一個(gè)完整的web應(yīng)用前端方面的項(xiàng)目算是搭建完成了祷肯。最后還需要有一個(gè)提供API服務(wù)的后端項(xiàng)目服務(wù)前端應(yīng)用運(yùn)行筐摘,經(jīng)過一個(gè)月的開發(fā),現(xiàn)在基本上已經(jīng)全部完成侵俗,經(jīng)過部署后,完成了最后的上線運(yùn)行。

項(xiàng)目使用技術(shù)棧

本項(xiàng)目是基于nodejs主要使用koa+mongodb為核心開發(fā)的輕量級服務(wù)端應(yīng)用挠唆。接口是按照RESTful風(fēng)格進(jìn)行設(shè)計(jì)

使用主要中間件有
koa-compress,
koa-parameter,koa-connect-history-api-fallback,koa-static,koa-mount贿条。具體使用方式請?jiān)?code>koa官方倉庫查看

數(shù)據(jù)庫操作:mongoose

接口權(quán)限驗(yàn)證:jsonwebtoken

用戶密碼加密:bcryptjs

上傳資源存儲(chǔ):koa-multer

路由分發(fā):koa-router

接口參數(shù)解析: koa-bodyparser

接口使用文檔: https://konglingwen94.github.io/elm-seller-server

開發(fā)過程

數(shù)據(jù)庫設(shè)計(jì)

本項(xiàng)目選擇使用mongodb作為數(shù)據(jù)存儲(chǔ)的數(shù)據(jù)庫雹仿,因?yàn)槠鋵τ谇岸碎_發(fā)者有著天然的有好性,使用容易上手整以。
由于本人涉獵服務(wù)端領(lǐng)域尚處于初級階段胧辽,在數(shù)據(jù)庫設(shè)計(jì)方面經(jīng)驗(yàn)有限,在此分享出來僅供參考使用公黑。

mongodb使用bson類型作為數(shù)據(jù)存儲(chǔ)格式邑商。由于跟前端js的json類型可以互相轉(zhuǎn)換使用摄咆。所以這就減小了入門者設(shè)計(jì)數(shù)據(jù)庫表字段的難度,我們可以參照前端頁面需要展示的數(shù)據(jù)進(jìn)行設(shè)計(jì)人断。
然后通過使用mongoose這個(gè)庫作為快速操作數(shù)據(jù)庫的模型吭从,我們可以用代碼的形式設(shè)計(jì)mongodb表字段的模型,經(jīng)過mongoose的編譯可以存儲(chǔ)到真實(shí)的數(shù)據(jù)庫表中恶迈。

拿本項(xiàng)目中的商品表來說涩金,這是一個(gè)聲明好的mongooseSchema

 {
    name: String,
    price: Number,
    oldPrice: Number,
    description: String,
    sellCount: Number,
    rating: Number,
    info: String,
    menuID: ObjectId,
    image: String,
    online: { type: Boolean, default: true },
  },

通過mongoose模型的編譯方法后存儲(chǔ)到數(shù)據(jù)庫中的字段是這個(gè)樣子

數(shù)據(jù)庫存儲(chǔ)字段

Field Type Description
menuID ObjectId 商品分類 ID
name String 商品標(biāo)題
info String 商品信息
description String 商品簡介
image String 商品封面
online Boolean 是否發(fā)布
oldPrice Number 商品原價(jià)
price Number 商品售價(jià)
sellCount Number 售賣個(gè)數(shù)

查看完整的模型文件點(diǎn)這里

接口搭建

一個(gè)完整的API接口從接收請求到響應(yīng)數(shù)據(jù)完成,中間這個(gè)過程就是服務(wù)端處理各種代碼邏輯的蝉绷。這其中主要包括暴露接口地址,接口權(quán)限驗(yàn)證鸭廷,請求參數(shù)驗(yàn)證查詢數(shù)據(jù)庫熔吗,返回響應(yīng)信息這幾個(gè)階段辆床。為了符合服務(wù)端業(yè)務(wù)邏輯分層設(shè)計(jì)的模式,每一個(gè)處理階段都可以抽離到一個(gè)單獨(dú)的模塊桅狠,最后再把各種相關(guān)聯(lián)的模塊組裝起來打包成一個(gè)完整的項(xiàng)目讼载,這樣的模塊化設(shè)計(jì)可以很大的增強(qiáng)項(xiàng)目的維護(hù)性可讀性。用目錄結(jié)構(gòu)的方式展現(xiàn)就是這個(gè)樣子的

├── model  // 數(shù)據(jù)庫模型
│   ├── administrator.js
│   ├── seller.js
│   ├── rating.js
│   ├── category.js
│   └── food.js
├── helper
│   ├── validatorRules.json  // 參數(shù)驗(yàn)證規(guī)則
│   ├── mongoose.js  // mongoose連接腳本
│   ├── middleware.js // 項(xiàng)目中間件
│   └── util.js  // 工具函數(shù)
├── controller  // 控制器
│   ├── administrator.js
│   ├── seller.js
│   ├── rating.js
│   ├── category.js
│   └── food.js
├── config
│   └── config.default.json  // 項(xiàng)目配置文件
├── router
│   └── index.js  // 路由配置

model文件夾用來放數(shù)據(jù)庫表模型中跌,數(shù)據(jù)庫存儲(chǔ)了哪些字段在這個(gè)文件目錄查看一目了然咨堤。helper目錄存放了一些輔助的項(xiàng)目文件和一些腳本,其中middleware.js這個(gè)文件存放了整個(gè)項(xiàng)目的所有中間件漩符,按照模式分層的原則一喘,我把服務(wù)端接口的一些處理邏輯都抽離到了中間件里,其中包括接口權(quán)限驗(yàn)證嗜暴,請求參數(shù)驗(yàn)證這兩個(gè)主要的代碼處理邏輯凸克。controller目錄則是存放接口業(yè)務(wù)邏輯的地方,我們也把他叫做控制器闷沥,查詢數(shù)據(jù)庫返回響應(yīng)信息也是在這個(gè)模塊里面完成的萎战。最后就是統(tǒng)一分發(fā)路由接口,router目錄是項(xiàng)目所有接口分發(fā)的地方舆逃,在這里可以把不同的控制器分發(fā)到一個(gè)或多個(gè)路由接口地址上蚂维,這樣可以實(shí)現(xiàn)控制器文件的復(fù)用,不需要寫重復(fù)的業(yè)務(wù)代碼路狮。

權(quán)限驗(yàn)證和登錄(包含注冊功能)

面向多用戶服務(wù)的后端項(xiàng)目虫啥,權(quán)限驗(yàn)證是不可或缺的。本項(xiàng)目使用了authorization請求頭驗(yàn)證的方式判斷每一個(gè)請求的權(quán)限奄妨。為了方便處理涂籽,我把這一塊的代碼邏輯抽離到了一個(gè)中間件里。這樣對每一個(gè)接口是否驗(yàn)證權(quán)限也容易管理和閱讀展蒂。本項(xiàng)目的權(quán)限驗(yàn)證使用jsonwebtoken這個(gè)第三方插件作為生成秘鑰token的工具又活,用戶在登錄的時(shí)候服務(wù)端會(huì)生成一個(gè)token響應(yīng)到前端,前端根據(jù)運(yùn)行環(huán)境把它存儲(chǔ)下來锰悼,之后的每一個(gè)請求根據(jù)業(yè)務(wù)需要攜帶這個(gè)token傳遞到服務(wù)端柳骄,服務(wù)端根據(jù)設(shè)置好的驗(yàn)證規(guī)則返回不同的驗(yàn)證結(jié)果,這就是本項(xiàng)目接口權(quán)限驗(yàn)證整體運(yùn)行過程箕般。

通過控制器中的用戶登錄接口分析其中的業(yè)務(wù)邏輯時(shí)怎么處理的

// 這里僅展示業(yè)務(wù)邏輯代碼
  async login(ctx) {
    const { username, password } = ctx.request.body;

    let result = await AdministratorModel.findOne({ username });
    //如果沒有結(jié)果則 創(chuàng)建新用戶
    if (!result) {
      // 加密密碼
      const hashPass = await bcrypt.hash(password, 10);

      const newUser = await AdministratorModel.create({ password: hashPass, username });

      const token = jwt.sign({ username, role: newUser.role, level: newUser.level }, secretKey, {
        expiresIn,
      });

      return (ctx.body = { admin: omit(newUser.toObject(), ["password"]), token });
    }

    if (!bcrypt.compareSync(password, result.password)) {
      ctx.status = 400;

      return (ctx.body = { message: "密碼錯(cuò)誤" });
    }
    const user = result.toObject();
    const token = jwt.sign(user, secretKey, { expiresIn });
     
    ctx.body = { admin: omit(user, ["password"]), token };
  },

為了支持管理后天首次登陸即注冊的功能耐薯,本登錄代碼接口也包含了用戶注冊的業(yè)務(wù)邏輯。經(jīng)過參數(shù)解析和校驗(yàn)的過程后(代碼部分以中間件處理的方式在其他模塊)丝里,通過解構(gòu)即取到了前端傳遞的有效參數(shù)曲初。根據(jù)數(shù)據(jù)庫查詢的結(jié)果處理不同的業(yè)務(wù)邏輯,在取到創(chuàng)建后的用戶信息后需通過jsonwebtoken的簽名生成一個(gè)token杯聚,此token也是其他接口在驗(yàn)證用戶登錄狀態(tài)時(shí)唯一的驗(yàn)證信息臼婆。

通過登錄接口生成token后我們就可以對其他需要添加訪問權(quán)限的接口進(jìn)行鑒權(quán)驗(yàn)證了。下面是通過驗(yàn)證token是否有效判斷用戶登錄狀態(tài)的邏輯代碼中間件

// 統(tǒng)一抽離到一個(gè)中間件中幌绍,這里省略了引入其他模塊的過程
module.exports={
 adminRequired() {
    return async (ctx, next) => {
      let token = ctx.headers["authorization"];

      if (!token) {
        ctx.status = 400;
        return (ctx.body = { message: "沒有傳遞token" });
      }
      token = token.split(" ")[1];

      try {
        var decodeToken = jwt.verify(token, secretKey, { expiresIn });
      } catch (error) {
        ctx.status = 403;
        if (error.name === "TokenExpiredError") {
          return (ctx.body = { message: "過期的token" });
        }
        return (ctx.body = { message: "無效的token" });
      }

      ctx.state.adminInfo = decodeToken;
      await next();
    };
  },
 }

請求進(jìn)入到這里后颁褂,通過認(rèn)證請求頭先提取到token變量,當(dāng)token取到具體值后傀广,再使用jsonwebtoken內(nèi)部提供的驗(yàn)證函數(shù)校驗(yàn)颁独,根據(jù)不同的驗(yàn)證結(jié)果響應(yīng)不同的狀態(tài)碼和錯(cuò)誤信息。具體的驗(yàn)證結(jié)果錯(cuò)誤類型自行到插件倉庫查看伪冰,這里不做詳細(xì)介紹誓酒。當(dāng)驗(yàn)證通過后會(huì)解析出token的簽名內(nèi)容,如果鑒權(quán)接口其他地方的業(yè)務(wù)邏輯需要用到此信息的話贮聂,我們可以把它掛載到koa提供的特定命名空間字段上靠柑,這樣方便局部的邏輯代碼獲取。

備注:token使用的認(rèn)證類型需要根據(jù)前后端開發(fā)人員的約定使用寂汇,本項(xiàng)目使用Bearer ${token}的格式作為令牌訪問頭

為了符合koa中間件導(dǎo)出格式的設(shè)計(jì)原則病往,這個(gè)文件的中間件是以閉包的形式導(dǎo)出的,實(shí)際應(yīng)用到接口上的是這個(gè)閉包函數(shù)骄瓣,這樣設(shè)計(jì)的好處是我們在調(diào)用中間件函數(shù)的時(shí)可以傳遞參數(shù)進(jìn)去停巷,內(nèi)部實(shí)際生效的中間件可以根據(jù)外部傳遞的參數(shù)做邏輯上的處理。在路由配置表里面統(tǒng)一使用這個(gè)中間件的方式是這個(gè)樣子的

//部分代碼省略
const Router = require("koa-router");

const router = new Router({ prefix: "/api" });
const middleware = require("../helper/middleware");

router.post("/admin/foods", middleware.adminRequired(),FoodController.createOne);

數(shù)據(jù)庫分頁查詢功能

對于大多數(shù)前端項(xiàng)目榕栏,分頁顯示數(shù)據(jù)在一個(gè)非常常見的功能畔勤,對應(yīng)到服務(wù)端的代碼邏輯就是數(shù)據(jù)庫的過濾查詢。使用mongoose提供的過濾查詢操作API可以很容易完成這個(gè)需求扒磁,當(dāng)我們用到的地方比較多的時(shí)候庆揪,問題就出現(xiàn)了。對于前端請求的接口路徑一般是這個(gè)樣子的/api/foods?page=1&size=20妨托,我們需要對傳遞的querystirng做進(jìn)一步的判斷和解析才能應(yīng)用到數(shù)據(jù)庫參數(shù)的查詢上缸榛。問題是很多個(gè)接口都需要這個(gè)功能吝羞,使用起來比較繁瑣,那不如我們把這個(gè)解析查詢參數(shù)的過程抽離成一個(gè)模塊内颗,這樣更方便我們使用和維護(hù)【牛現(xiàn)在讓我們看一下封裝好的全部代碼吧!

module.exports = {
  resolvePagination(pagination = {}) {
    const defaults = { page: 1, size: 10 };

    pagination.page = parseInt(pagination.page, 10);
    pagination.size = parseInt(pagination.size, 10);

    if (Number.isNaN(pagination.page) || pagination.page <= 0) {
      pagination.page = defaults.page;
    }
    if (Number.isNaN(pagination.size) || pagination.size <= 0) {
      pagination.size = defaults.size;
    }

    const { page, size } = pagination;
    return {
      page,
      size,
    };
  },
  resolveFilterOptions(filter = {}) {
    let sort = {
      createdAt: -1,
    };
    sort = defaults({}, filter.sort, sort);

    const { page, pageSize } = resolvePagination({
      page: filter.page,
      size: filter.size,
    });
    return {
      limit: size,
      skip: (page - 1) * size,
      sort,
    };
  },
};

首先通過resolvePagination這個(gè)函數(shù)我們可以解析出有效的query參數(shù)均澳,在通過resolveFilterOptions這個(gè)函數(shù)解析出來符合mongoose數(shù)據(jù)篩選操作的查詢選項(xiàng)恨溜。通過模塊化引入的操作方式,應(yīng)用到實(shí)際的數(shù)據(jù)庫查詢過程中如下

// 代碼片段來自項(xiàng)目`controller`目錄
const {  resolveFilterOptions, resolvePagination } = require("../helper/utils");

module.exports={
 async queryListByOpts(ctx) {
    const { page, size } = resolvePagination({ page: ctx.query.page, size: ctx.query.size });

    const { skip, limit, sort } = resolveFilterOptions({ page, size });

    const total = await FoodModel.countDocuments();

    var results = await FoodModel.find().populate("category").sort(sort).skip(skip).limit(limit);

    ctx.body = {
      data: results,
      total,
      pagination: {
        page,
        size,
      },
    };
  },
}

從代碼中可以看到以獲取到前端傳遞的query類型參數(shù)為解析值找前,resolvePagination函數(shù)負(fù)責(zé)解析有效的數(shù)據(jù)分頁查詢選項(xiàng)糟袁,resolveFilterOptions函數(shù)解析出來了mongoose特定查詢語句格式的參數(shù),我們通過分離業(yè)務(wù)代碼和邏輯代碼的方式有效增強(qiáng)了代碼的模塊化結(jié)構(gòu)躺盛,也增加了代碼的復(fù)用性项戴,提高了項(xiàng)目的開發(fā)效率。

應(yīng)用的部署和運(yùn)行

本項(xiàng)目使用github-actions的持續(xù)集成功能自動(dòng)部署到云服務(wù)器槽惫,有了持續(xù)集成的服務(wù)肯尺,就省去了項(xiàng)目手動(dòng)構(gòu)建,測試躯枢,發(fā)布這一系列流程则吟,而且降低了手動(dòng)操作程序出錯(cuò)的風(fēng)險(xiǎn),具體的配置文件如下

name: Deploy files
on: [push]
jobs:

  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: copy file via ssh key
      uses: appleboy/scp-action@master
      with:
        host: ${{ secrets.SERVER_HOST }}
        username: ${{ secrets.SERVER_USERNAME }}
        key: ${{ secrets.SERVER_SSH_KEY }}
        port: ${{ secrets.SERVER_PORT }}
        source: "*"
        target: "/var/www/elm-seller-server"

    - name: executing remote ssh commands using ssh key
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.SERVER_HOST }}
        username: ${{ secrets.SERVER_USERNAME }}
        key: ${{ secrets.SERVER_SSH_KEY }}
        port: "22"
        script: |
          cd /var/www/elm-seller-server
          npm install
          
          npm start
          

從配置文件中看出锄蹂,服務(wù)器發(fā)布的環(huán)境變量都采用了加密的方式傳遞氓仲,比如${{secrets.SERVER_HOST}}這個(gè)環(huán)境變量,真實(shí)的存儲(chǔ)值需要我們在github倉庫的設(shè)置面板里的secret選項(xiàng)配置的得糜,當(dāng)本地使用git管理的倉庫推送到遠(yuǎn)程倉庫的時(shí)候就會(huì)觸發(fā)github-actions的自動(dòng)部署操作敬扛,同時(shí)我們還可以在workflows文件夾下面配置多個(gè)以.yml結(jié)尾的配置文件,一個(gè)配置文件對應(yīng)一個(gè)actions部署任務(wù)朝抖,本項(xiàng)目我就使用了兩個(gè)持續(xù)集成的任務(wù)啥箭,因?yàn)轫?xiàng)目對應(yīng)的說明文檔也需要及時(shí)的更新發(fā)布。至于部署文件模板怎么選擇需要根據(jù)個(gè)人的需求自己選擇設(shè)置治宣,github-actions官方市場提供了常用的集成任務(wù)模板供我們選擇

發(fā)布到云服務(wù)器的應(yīng)用我選擇使用PM2管理應(yīng)用急侥,應(yīng)用啟動(dòng)的配置文件點(diǎn)這里。pm2是一個(gè)面對node應(yīng)用的管理工具侮邀,我們可以方便的查看坏怪,重啟,刪除绊茧,停止铝宵,啟動(dòng)應(yīng)用

API文檔編寫

文檔的撰寫是一個(gè)后端項(xiàng)目不可或缺的一部分內(nèi)容,學(xué)會(huì)寫文檔可以回顧項(xiàng)目從設(shè)計(jì)到開發(fā)的過程华畏,發(fā)現(xiàn)有問題的地方可以第一時(shí)間發(fā)現(xiàn)鹏秋,及時(shí)的修復(fù)bug尊蚁。項(xiàng)目文檔是使用markdown語法編寫的REAEME文件,所有文件均在項(xiàng)目的docs目錄內(nèi)侣夷。文檔使用vuepress作為構(gòu)建工具預(yù)覽和發(fā)布枝誊。具體使用方式自行查看官方文檔,不做詳細(xì)介紹

文檔發(fā)布地址:https://konglingwen94.github.io/elm-seller-server

工具和環(huán)境

vscode mac node mongodb git github postman ssh

總結(jié)心得

從項(xiàng)目的需求規(guī)劃惜纸,到數(shù)據(jù)庫表設(shè)計(jì),api接口邏輯關(guān)注點(diǎn)的分離绝骚,最后成功的部署運(yùn)行以及文檔的撰寫完成耐版,自己初步掌握了服務(wù)端項(xiàng)目完整的開發(fā)流程,并積累了一些開發(fā)經(jīng)驗(yàn)可以在這分享压汪。

作為編程開發(fā)人員粪牲,在項(xiàng)目開發(fā)過程中遇到困難是很正常的,尤其是在調(diào)試代碼的時(shí)候各種各樣的錯(cuò)誤信息看的"眼花繚亂",尤其是服務(wù)端node的環(huán)境沒有瀏覽器客戶端調(diào)試方便止剖。遇到代碼出錯(cuò)不要怕腺阳,我們需要一步步排查出錯(cuò)的原因,如果錯(cuò)誤信息看起來不直觀我們可以借助第三方工具調(diào)試穿香,本項(xiàng)目我使用的是nodemon這個(gè)工具亭引,他可以熱加載應(yīng)用,也可以開啟debug的命令打開一個(gè)類似瀏覽器開發(fā)者工具的調(diào)試面板皮获,我們可以在控制臺面板查看程序拋出的錯(cuò)誤信息焙蚓,在source面板查看出錯(cuò)的代碼堆棧,借助這些工具的分析洒宝,只要有耐心购公,一點(diǎn)點(diǎn)思考出現(xiàn)錯(cuò)誤的問題,最終我們一定可以解決它雁歌。

支持

感謝所有點(diǎn)贊和關(guān)注的小伙伴們宏浩,對本項(xiàng)目有興趣的同學(xué)可以一塊和我交流,歡迎在下面留言靠瞎!

如果您對本項(xiàng)目由好的建議或者發(fā)現(xiàn)bug可以到項(xiàng)目倉庫提issues,也歡迎您的收藏和關(guān)注比庄,謝謝!

倉庫地址:https://github.com/konglingwen94/elm-seller-server

文檔地址:https://konglingwen94.github.io/elm-seller-server

w

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乏盐,一起剝皮案震驚了整個(gè)濱河市印蔗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丑勤,老刑警劉巖华嘹,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異法竞,居然都是意外死亡耙厚,警方通過查閱死者的電腦和手機(jī)强挫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薛躬,“玉大人俯渤,你說我怎么就攤上這事⌒捅Γ” “怎么了八匠?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長趴酣。 經(jīng)常有香客問我梨树,道長,這世上最難降的妖魔是什么岖寞? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任抡四,我火速辦了婚禮,結(jié)果婚禮上仗谆,老公的妹妹穿的比我還像新娘指巡。我一直安慰自己,他們只是感情好隶垮,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布藻雪。 她就那樣靜靜地躺著,像睡著了一般狸吞。 火紅的嫁衣襯著肌膚如雪阔涉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天捷绒,我揣著相機(jī)與錄音瑰排,去河邊找鬼。 笑死暖侨,一個(gè)胖子當(dāng)著我的面吹牛椭住,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播字逗,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼京郑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了葫掉?” 一聲冷哼從身側(cè)響起些举,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎俭厚,沒想到半個(gè)月后户魏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年叼丑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了关翎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鸠信,死狀恐怖纵寝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情星立,我是刑警寧澤爽茴,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站绰垂,受9級特大地震影響室奏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辕坝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荐健。 院中可真熱鬧酱畅,春花似錦、人聲如沸江场。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽址否。三九已至餐蔬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佑附,已是汗流浹背樊诺。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留音同,地道東北人词爬。 一個(gè)月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像权均,于是被迫代替她去往敵國和親顿膨。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

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