一行代碼颁褂,搞定瀏覽器數(shù)據(jù)庫 IndexedDB

前言

2021 年故响,如果你的前端應(yīng)用彩届,需要在瀏覽器上保存數(shù)據(jù)寨辩,有三個主流方案可以選擇:

  • Cookie:上古時代就已存在,但能應(yīng)用的業(yè)務(wù)場景非常有限
  • LocalStorage:使用簡單靈活腮恩,但是容量只有 10Mb,且不適合儲存結(jié)構(gòu)化數(shù)據(jù)
  • IndexedDB:算得上真正意義上的數(shù)據(jù)庫内颗,但坑異常多符衔,使用麻煩形帮,古老的 API 設(shè)計放在現(xiàn)代前端工程中總有種格格不入的感覺

我在大三的時候合冀,曾經(jīng)用 IndexedDB 寫過一個背單詞 App晰洒,當(dāng)時就有把 IndexedDB 封裝一遍的想法,但是由于學(xué)業(yè)緊張,后來就擱置了

最近铝宵,我終于有了空閑時間侣夷,于是撿起了當(dāng)年的想法衙传,開始嘗試用 TypeScriptIndexedDB 封裝一遍落君,把坑一個個填上纹冤,做成一個開發(fā)者友好的庫知残,并開源出來制恍,上傳至 npm

拍腦袋后,我決定把這個項目命名為 Godb.js

Godb.js

Godb.js 的出現(xiàn)型宝,讓你即使你不了解瀏覽器數(shù)據(jù)庫 IndexedDB,也能把它用的行云流水指巡,從而把關(guān)注點放到業(yè)務(wù)上面去

畢竟要用好 IndexedDB指煎,你需要翻無數(shù)遍 MDN,而 Godb 替你吃透了 MDN宅广,從而讓你把 IndexedDB 用的更好的同時,操作還更簡單了

本文發(fā)布時星立,項目處于 Alpha 階段(版本 0.3.x),意味著之后隨時可能會有 breaking changes南蹂,在正式版(1.0.0 及以后)發(fā)布之前秃嗜,建議不要把這個項目用到嚴(yán)肅的場景下

項目GitHub:
https://github.com/chenstarx/Godb.js

如果覺得不錯的話就點個 Star 吧~

項目完整文檔與官網(wǎng)正在緊張開發(fā)中,現(xiàn)階段可以通過下面的 demo 來嘗鮮

安裝

首先需要安裝泌类,這里默認(rèn)你使用了 webpack迅栅、gulp 等打包工具尔当,或在 vue、react 等項目中

npm install godb

在第一個正式版發(fā)布后疼蛾,還會提供 CDN 的引入方式,敬請期待~

簡單上手

操作非常簡單喳坠,增脊凰、刪、改豹储、查各只需要一行代碼:

import Godb from 'godb';

const testDB = new Godb('testDB');
const user = testDB.table('user');

const data = {
  name: 'luke',
  age: 22
};

user.add(data) // 增
  .then(id => user.get(id)) // 查,等價于 user.get({ id: id })
  .then(luke => user.put({ ...luke, age: 23 })) // 改
  .then(id => user.delete(id)); // 刪

這里注意增刪改查四個方法在 Promise.then 的返回值:

  • Table.get() 返回的是完整數(shù)據(jù)
  • Table.add()Table.put() 返回的是 id(也可以返回完整數(shù)據(jù)薄疚,評論區(qū)留言討論吧~)
  • Table.delete() 不返回數(shù)據(jù)(返回 undefined

第二點需要注意的就是,put(obj) 方法中的 obj 需要包含 id荧飞,否則就等價于 add(obj)

上面的 demo 中邢隧,get 得到的 luke 對象包含 id照激,因此是修改操作

之后會引入一個 update 方法來改進(jìn)這個問題

也可以一次性添加多條數(shù)據(jù)

const data = [
    {
        name: 'luke',
        age: 22
    },
    {
        name: 'elaine',
        age: 23
    }
];

user.addMany(data)
  .then(() => user.consoleTable());

addMany(data) 方法:

  • 嚴(yán)格按照 data 的順序添加
  • 返回 id 的數(shù)組,與 data 順序一致

之所以單獨寫個 addMany,而不在 add 里加一個判斷數(shù)組的邏輯,是因為用戶想要的可能就是添加一個數(shù)組到數(shù)據(jù)庫

注意:addManyadd 不要同步調(diào)用,如果在 addMany 正在執(zhí)行時調(diào)用 add,可能會導(dǎo)致數(shù)據(jù)庫里的順序不符合預(yù)期,請在 addMany 的回調(diào)完成后再調(diào)用 add

Table.consoleTable()

這里用了一個 Table.consoleTable() 的方法,它會在瀏覽器的控制臺打印出下面的內(nèi)容:

add-many.png

這里的 (index) 就是 id

雖然 chrome 開發(fā)者工具內(nèi)就能看到表內(nèi)所有數(shù)據(jù),但這個方法好處是可以在需要的時候打印出數(shù)據(jù)桑涎,方便 debug

注意:這個方法是異步的,因為需要在數(shù)據(jù)庫里把數(shù)據(jù)庫取出來禁谦;異步意味著緊接在它后面的代碼力喷,可能會在打印出結(jié)果之前執(zhí)行于样,如果不希望出現(xiàn)這種情況蚤蔓,使用 awaitPromise.then 即可

Table.find()

如果你想在數(shù)據(jù)庫中查找數(shù)據(jù),還可以使用 Table.find() 方法:

const data = [
    {
        name: 'luke',
        age: 22
    },
    {
        name: 'elaine',
        age: 23
    }
];

user.addMany(data)
  .then(() => {
    user.find((item) => {
        return item.age > 22;
    })
      .then((data) => console.log(data)) // { name: 'luke', age: 23 }
  });

Table.find(fn) 接受一個函數(shù) fn 作為參數(shù)糊余,這個函數(shù)的返回值為 truefalse

這個方法在內(nèi)部會從頭遍歷整個表(使用 IndexedDB 的 Cursor)秀又,然后把每一次的結(jié)果放進(jìn) fn 執(zhí)行,如果 fn 的返回值為 true(也可以是 1 這樣的等價于 true 的值)贬芥,就返回當(dāng)前的結(jié)果吐辙,停止遍歷

這個方法只會返回第一個滿足條件的值,如果需要返回所有滿足條件的值蘸劈,請使用 Table.findAll()昏苏,用法與 Table.find() 一致,但是會返回一個數(shù)組威沫,包含所有滿足條件的值

Schema

如果你希望數(shù)據(jù)庫的結(jié)構(gòu)更嚴(yán)格一點贤惯,也可以添加 schema

import Godb from 'godb';

// 定義數(shù)據(jù)庫結(jié)構(gòu)
const schema = {
    // user 表:
    user: {
        // user 表的字段:
        name: {
            type: String,
            unique: true // 指定 name 字段在表里唯一
        },
        age: Number
    }
}

const testDB = new Godb('testDB', schema);
const user = testDB.table('user');

const luke1 = {
    name: 'luke'
    age: 22
};

const luke2 = {
    name: 'luke'
    age: 19
};

user.add(luke1) // 沒問題
  .then(() => user.get({ name: 'luke' })) // 定義schema后,就可以用 id 以外的字段獲取到數(shù)據(jù)了
  .then(() => user.add(luke2)) // 報錯壹甥,name 重復(fù)了

如上面的例子

  • 定義了 schema救巷,因此 get() 可以傳入 id 以外的字段了,否則只能傳入 id
  • 指定了 user.name 這一項是唯一的句柠,因此無法添加重復(fù)的 name

get() vs find():

注意 get()find() 的區(qū)別浦译,如果 schema 中定義了字段棒假,get() 的查找效率會高于 find(),且數(shù)據(jù)量越大差距越大精盅,因為 find() 的實現(xiàn)方式是遍歷整個表帽哑,而 get() 是使用索引進(jìn)行查找

只有預(yù)先定義了 schemaGodb 才會給字段建立索引叹俏,因此建議在工程實踐中妻枕,盡量先定義好數(shù)據(jù)庫 schema

關(guān)于 schema:

部分同學(xué)或許會發(fā)現(xiàn),上面定義 schema 的方式有點眼熟粘驰,沒錯屡谐,正是參考了 mongoose

  • 定義數(shù)據(jù)庫的字段時,可以只指明數(shù)據(jù)類型蝌数,如上面的 age: Number
  • 也可以使用一個對象愕掏,里面除了定義數(shù)據(jù)類型 type,也指明這個字段是不是唯一的(unique: true)顶伞,之后會添加更多可選屬性饵撑,如用來指定字段默認(rèn)值的 default,和指向別的表的索引 ref

不定義 Schema 時唆貌,Godb 使用起來就像 MongoDB 一樣滑潘,可以靈活添加數(shù)據(jù);區(qū)別是 Mongodb 中锨咙,每條數(shù)據(jù)的唯一標(biāo)識符是 _id语卤,而 Godbid

雖然這樣做的問題是,IndexedDB 畢竟還是結(jié)構(gòu)化的酪刀,用戶使用不規(guī)范的話(如每次添加的數(shù)據(jù)結(jié)構(gòu)都不一樣)粱侣,久而久之可能會使得數(shù)據(jù)庫的字段特別多,且不同數(shù)據(jù)中沒用到的字段都是空的蓖宦,導(dǎo)致浪費齐婴,影響性能

定義 Schema 后,Godb 使用起來就像 MySQL 一樣稠茂,如果添加 Schema 沒有的字段柠偶,或者是字段類型不符合定義,會報錯(在寫文檔的時候還沒有實現(xiàn)這個功能睬关,即使 Schema 不符合也能加诱担,下個版本會安排上)

因此推薦在項目中,定義好 schema电爹,這樣不管是維護性上蔫仙,還是性能上,都要更勝一籌

另一個使用 await 的 CRUD demo:

import Godb from 'godb';

const schema = {
  user: {
    name: {
      type: String,
      unique: true
    },
    age: Number
  }
};

const db = new Godb('testDB', schema);
const user = db.table('user');

crud();

async function crud() {

  // 增:
  await user.addMany([
    {
      name: 'luke',
      age: 22
    },
    {
      name: 'elaine',
      age: 23
    }
  ]);

  console.log('add user: luke');
  // await 非必須丐箩,這里是為了防止打印順序不出錯
  await user.consoleTable();

  // 查:
  const luke = await user.get({ name: 'luke' });
  // const luke = await user.get(2); // 等價于:
  // const luke = await user.get({ id: 2 });

  // 改:
  luke.age = 23;
  await user.put(luke);

  console.log('update: set luke.age to 23');
  await user.consoleTable();

  // 刪:
  await user.delete({ name: 'luke' });

  console.log('delete user: luke');
  await user.consoleTable();

}

上面這段 demo摇邦,會在控制臺打印出下面的內(nèi)容:

crud-test.png

API 設(shè)計

因為「連接數(shù)據(jù)庫」和「連接表」這兩個操作是異步的恤煞,在設(shè)計之初,曾經(jīng)有兩個 API 方案施籍,區(qū)別在于:要不要把這兩個操作居扒,做為異步 API 提供給用戶

這里討論的不是「API 如何命名」這樣的細(xì)節(jié),而是「API 的使用方式」丑慎,因為這會直接影響到用戶使用 Godb 時的業(yè)務(wù)代碼編寫方式

以連接數(shù)據(jù)庫 -> 添加一條數(shù)據(jù)的過程為例

設(shè)計一:提供異步特性

GitHub 上大多數(shù)開源的 IndexedDB 封裝庫都是這么做的

import Godb from 'godb';

// 連接數(shù)據(jù)庫是異步的
Godb.open('testDB')
    .then(testDB => testDB.table('user')) // 連接表也需要異步
    .then(user => {
        user.add({
            name: 'luke',
            age: 22
        });
    });
});

這樣的優(yōu)點是喜喂,工作流程一目了然,畢竟對數(shù)據(jù)庫的操作竿裂,要放在連接數(shù)據(jù)庫之后

但是玉吁,這種設(shè)計不適合工程化的前端項目!

因為腻异,所有增刪改查等操作诈茧,都需要用戶,手動放到連接完成的異步回調(diào)之后捂掰,否則無法知道操作時有沒有連上數(shù)據(jù)庫和表

導(dǎo)致每次需要操作數(shù)據(jù)庫時,都要先打開數(shù)據(jù)庫一遍數(shù)據(jù)庫曾沈,才能繼續(xù)

即使你預(yù)先定義一個全局的連接这嚣,你在之后想要使用它時,如果不包一層 Promise塞俱,是無法確定數(shù)據(jù)庫和表姐帚,在使用時有沒有連接上的

以 Vue 為例,如果你在全局環(huán)境(比如 Vuex)定義了一個連接:

import Godb from 'godb';

new Vuex.Store({
  state: {
    godb: await Godb.open('testDB') // 不加 await 返回的就是 Promise 了
  }
});

這樣障涯,在 Vue 的任何一個組件中罐旗,我們都能訪問到 Godb 實例

問題來了,在你的組件中唯蝶,如果你想在組件初始化時九秀,比如 createdmounted 這樣的鉤子函數(shù)中(React 中就是 ComponentDidMount),去訪問數(shù)據(jù)庫:

new Vue({
   mounted() {
       const godb = this.$store.state.godb; // 從全局環(huán)境取出連接
       godb.table('user')
           .then(user => {
               user.add({
                   name: 'luke',
                   age: 22
               }); // user is undefined!
           });
   }
});

你會發(fā)現(xiàn)粘我,如果這個組件在 App 初始化時就被加載鼓蜒,在組件 mounted 函數(shù)觸發(fā)時,本地數(shù)據(jù)庫可能根本就沒有連接上U髯帧(連接數(shù)據(jù)庫這樣的操作都弹,最典型的執(zhí)行場景就是在組件加載時)

解決辦法是,在每一個需要操作數(shù)據(jù)庫的地方匙姜,都定義一個連接:

import Godb from 'godb';

new Vue({
    mounted() {
        Godb.open('testDB')
          .then(testDB => testDB.table('user'))
          .then(user => {
              user.add({
                  name: 'luke',
                  age: 22
              });
          });
    }
});

這樣不僅代碼又臭又長畅厢,性能低下(每次操作都需要先連接),在需要連接本地數(shù)據(jù)庫的組件多了后氮昧,維護起來更是一場噩夢

簡而言之框杜,就是這個方案浦楣,在工程化前端的不同組件中,需要在每次操作之前霸琴,都連一遍數(shù)據(jù)庫椒振,否則無法確保組件加載時,已經(jīng)連接上了 IndexedDB

設(shè)計二:隱藏連接的異步特性

我最終采用了這個方案梧乘,對開發(fā)者而言澎迎,甚至感覺不到「連接數(shù)據(jù)庫」和「連接表」這兩個操作是異步的

const testDB = new Godb('testDB');
const user = testDB.table('user');

user.add({
    name: 'luke',
    age: 22
}).then(id => console.log(id));

這樣使用上非常自然,開發(fā)者并不需要關(guān)心操作時有沒有連上數(shù)據(jù)庫和表选调,只需要在操作后的回調(diào)內(nèi)寫好自己的邏輯就可以

但是夹供,這個方案的缺點就是開發(fā)起來比較麻煩(嘿嘿,麻煩自己仁堪,方便用戶)

因為 new Codb('testDB') 內(nèi)部的連接數(shù)據(jù)庫的操作哮洽,實際上是異步的(因為 IndexedDB 的原生 API 就是異步的設(shè)計)

在連接數(shù)據(jù)庫的操作發(fā)出去后,即使還沒連接上弦聂,下面的 testDB.table('user')user.add() 也會先開始執(zhí)行

也就是說鸟辅,之后的「獲取 user 表」 和 「添加一條數(shù)據(jù)」實際上會先于「連上數(shù)據(jù)庫」這個過程執(zhí)行,如果實現(xiàn)該 API 設(shè)計時未處理這個問題莺葫,上面的示例代碼肯定會報錯

而要處理這個問題匪凉,我用到了下面兩個方法:

  • 在每次需要連上數(shù)據(jù)庫的操作中(比如 add()),先拿到數(shù)據(jù)庫的連接捺檬,再進(jìn)行操作
  • 使用隊列 Queue再层,在還未連接時,把需要連接數(shù)據(jù)庫的操作放進(jìn)隊列堡纬,等連接完成聂受,再執(zhí)行該隊列

具體而言,就是

  • Godb 的 class 中定義一個 getDB(callback)烤镐,用來獲取 IndexedDB 連接實例
  • 增刪改查中蛋济,都調(diào)用 getDB,在 callback 獲取到 IndexedDB 的連接實例后再進(jìn)行操作
  • getDB 中使用一個隊列炮叶,如果數(shù)據(jù)庫還沒連接上瘫俊,就把 callback 放進(jìn)隊列,在連接上后悴灵,執(zhí)行這個隊列中的函數(shù)
  • 連接完成時扛芽,直接把 IndexedDB 連接實例傳進(jìn) callback 執(zhí)行即可

在調(diào)用 getDB 時,可能有三種狀態(tài)(其實還有個數(shù)據(jù)庫已關(guān)閉的狀態(tài)积瞒,這里不討論):

  1. 剛初始化川尖,未發(fā)起和 IndexedDB 的連接
  2. 正在連接 IndexedDB,但還未連上
  3. 已經(jīng)連上茫孔,此時已經(jīng)有 IndexedDB 的連接實例

第一種狀態(tài)只在第一次執(zhí)行 getDB 時觸發(fā)叮喳,因為一旦嘗試建立連接就進(jìn)入下一個狀態(tài)了被芳;第一次執(zhí)行被我放到了 Godb 類的構(gòu)造函數(shù)中

第三種狀態(tài)時,也就是已經(jīng)連上數(shù)據(jù)庫后馍悟,直接把連接實例傳進(jìn) callback 執(zhí)行即可

關(guān)鍵是處理第二種狀態(tài)畔濒,此時正在連接數(shù)據(jù)庫,但還未連上锣咒,無法進(jìn)行增刪改查:

const testDB = new Godb('testDB');
const user = testDB.table('user');

user.add({ name: 'luke' }); // 此時數(shù)據(jù)庫正在連接侵状,還未連上
user.add({ name: 'elaine' }); // 此時數(shù)據(jù)庫正在連接,還未連上

testDB.onOpened = () => { // 數(shù)據(jù)庫連接成功的回調(diào)
    user.add({ name: 'lucas' }); // 此時已連接
}

上面的例子毅整,頭兩個 add 操作時其實數(shù)據(jù)庫并未連接上

那要如何操作趣兄,才能保證正常添加,并且 lukeelainelucas 進(jìn)入數(shù)據(jù)庫的順序和代碼一致呢悼嫉?

答案是使用隊列 Queue艇潭,把兩個 add 操作加進(jìn)隊列,在連接成功時戏蔑,按先進(jìn)先出的順序執(zhí)行

這樣蹋凝,用戶就不需要關(guān)心,操作時數(shù)據(jù)庫是否已經(jīng)連上了(注意增刪改查有異步回調(diào)总棵,在回調(diào)里可以知道是否操作成功)鳍寂,Godb 幫你在幕后做好了這一切

注意之所以使用 callback 而不是 Promise,是因為 JS 中的回調(diào)既可以是異步的彻舰,也可以是同步的

而連接成功,已經(jīng)有連接實例后候味,直接同步返回連接實例更好刃唤,沒必要再使用異步

還是以 Vue 為例,如果我們在 Vuex(全局變量)中添加連接實例:

import Godb from 'godb';

new Vuex.Store({
    state: {
        godb: new Godb('testDB')
    }
});

這樣白群,在所有組件中尚胞,我們都可以使用同一個連接實例:

new Vue({
    computed: {
        // 把全局實例變?yōu)榻M件屬性
        godb() {
            return this.$store.state.godb;
        }
    },
    mounted() {
        this.godb.table('user').add({
            name: 'luke',
            age: 22
        }).then(id => console.log(id));
    }
});

總結(jié)這個方案的優(yōu)點:

  • 性能更高(可以全局共享一個連接實例)
  • 代碼更簡潔
  • 最關(guān)鍵的,心智負(fù)擔(dān)低了很多帜慢!

缺點:Godb 開發(fā)更麻煩笼裳,不是簡單把 IndexedDB 包一層 Promise 就行

因此,我最終采用了這個方案粱玲,畢竟麻煩我一個躬柬,方便你我他,優(yōu)點遠(yuǎn)遠(yuǎn)蓋過了缺點

如果對實現(xiàn)好奇的話抽减,可以去閱讀源碼允青,當(dāng)前只是實現(xiàn)了基本的 CRUD,源碼暫時還不復(fù)雜

近期待辦

在把基本的 CRUD 完成后卵沉,我就寫下了這篇文章颠锉,讓大家來嘗嘗鮮

而接下來要做的事其實非常多法牲,近期我會完成下面的開發(fā):

  • Table.update():更好的更新數(shù)據(jù)的方案
  • 全局錯誤處理,目前代碼里 throw 的 Error 其實是沒被處理的
  • 如果定義了 Schema琼掠,那就在所有 Table 的方法執(zhí)行前都檢查 Schema
  • 如果定義了 Schema拒垃,保證數(shù)據(jù)庫的結(jié)構(gòu)和 Schema 一致

如果你有任何建議或意見,請在評論區(qū)留言瓷蛙,我會認(rèn)證讀每一個反饋

如果覺得這個項目有意思悼瓮,歡迎給文章點贊,歡迎來 GitHub 點個 star~

https://github.com/chenstarx/Godb.js

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末速挑,一起剝皮案震驚了整個濱河市谤牡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌姥宝,老刑警劉巖翅萤,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腊满,居然都是意外死亡套么,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門碳蛋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胚泌,“玉大人,你說我怎么就攤上這事肃弟$枋遥” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵笤受,是天一觀的道長穷缤。 經(jīng)常有香客問我,道長箩兽,這世上最難降的妖魔是什么津肛? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮汗贫,結(jié)果婚禮上喻犁,老公的妹妹穿的比我還像新娘柴信。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布辽聊。 她就那樣靜靜地躺著努潘,像睡著了一般授帕。 火紅的嫁衣襯著肌膚如雪鹏倘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音撮竿,去河邊找鬼吮便。 笑死,一個胖子當(dāng)著我的面吹牛幢踏,可吹牛的內(nèi)容都是我干的髓需。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼房蝉,長吁一口氣:“原來是場噩夢啊……” “哼僚匆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起搭幻,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤咧擂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后檀蹋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體松申,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年俯逾,在試婚紗的時候發(fā)現(xiàn)自己被綠了贸桶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡桌肴,死狀恐怖皇筛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坠七,我是刑警寧澤水醋,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站彪置,受9級特大地震影響拄踪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜悉稠,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一宫蛆、第九天 我趴在偏房一處隱蔽的房頂上張望艘包。 院中可真熱鬧的猛,春花似錦、人聲如沸想虎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舌厨。三九已至岂却,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躏哩。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工署浩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扫尺。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓筋栋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親正驻。 傳聞我的和親對象是個殘疾皇子弊攘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

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