Node.js 學(xué)習(xí)筆記

一坝橡、模塊
1. 在Node環(huán)境中,一個(gè).js文件就稱(chēng)之為一個(gè)模塊(module)涂滴。
2. 模塊化的優(yōu)點(diǎn):提高代碼的可維護(hù)性友酱、可重用性高、可以避免函數(shù)名和變量名沖突柔纵。
3. 模塊化示例

// hello.js
'use strict';

var str = "Hello ";

function greet(name) {
    console.log(str + name + '!');
}

module.exports = greet;
// main.js
'use strict';

var greet = require('./hello'); // 引入hello模塊

var str = 'Andy';

greet(str); // Hello Andy!

① 這種模塊加載機(jī)制被稱(chēng)為CommonJS規(guī)范缔杉。
② 每個(gè).js文件都是一個(gè)模塊。
③ 內(nèi)部使用的變量名和函數(shù)名不沖突搁料,比如hello.js和main.js 中的 str變量或详,互不影響。
④ 模塊中對(duì)外暴露輸出變量(可以為任意對(duì)象郭计,函數(shù)霸琴,數(shù)組等) module.exports = variable; 一個(gè)模塊引用其他模塊暴露的變量 var ref = require('module_name');

4. 模塊化實(shí)現(xiàn)的原理
瀏覽器中,大量使用全局變量可不好昭伸。如果你在a.js中使用了全局變量s梧乘,那么,在b.js中也使用全局變量s庐杨,將造成沖突选调,b.js中對(duì)s賦值會(huì)改變a.js的運(yùn)行邏輯。
Node.js 如何實(shí)現(xiàn)此模塊化的呢灵份?

// Node.js 加載hello.js之后仁堪,包裝一下,變成如下進(jìn)行執(zhí)行
(function() {
    var str = "Hello ";

    function greet(name) {
        console.log(str + name + '!');
    }

    module.exports = greet;
})();

/*
 * 實(shí)現(xiàn) module.exports
 */

// 準(zhǔn)備module對(duì)象
var module = {
    id: 'a',
    exports: {}
};
var load = function(module) {
    // 讀取 hello.js代碼
    function greet(name) {
        console.log('Hello ' + name + '!');
    }

    module.exports = greet;
    // hello.js 代碼結(jié)束
    return module.exports;
};
var exported = load(module);
// 保存module
save(module, exported);

① 全局變量str經(jīng)過(guò)包裝之后變成匿名函數(shù)內(nèi)部的局部變量填渠。如若Node.js繼續(xù)加載其他模塊弦聂,模塊中的變量str也互不干擾。
② Node.js在加載js文件之前先準(zhǔn)備一個(gè)變量module氛什,并將其傳入加載load函數(shù)莺葫,最終返回module.exports。

5. module.exports vs exports

/*
 *  module.exports
 */
// a.js
function hello() {
    console.log('Hello world!');
}

function greet(name) {
    console.log('Hello ' + name);
}

module.exports = {
    hello: hello,
    greet: greet
};
// b.js
var foo = require('./a');

foo.hello(); // Hello world!
foo.greet('Andy'); // Hello Andy
/*
 * exports
 */
// a.js
function hello() {
    console.log('Hello world!');
}

function greet(name) {
    console.log('Hello ' + name);
}

exports.hello = hello;
exports.greet = greet;

// b.js
var foo = require('./a');

foo.hello(); // Hello world!
foo.greet('Andy'); // Hello Andy
/* 
 * Node 加載機(jī)制:
*/
//Node 把待加載的js文件放入一個(gè)包裝函數(shù)load中執(zhí)行枪眉。在執(zhí)行這個(gè)load()函數(shù)之前捺檬,Node準(zhǔn)備了module變量:

// module變量
var module = {
    id: 'hello',
    exports: {}
}
// load函數(shù)最終返回module.exports
var load = function(exports, module) {
    // hello.js 的文件內(nèi)容
    ...
    // load函數(shù)返回
    return module.exports;
}
var exported = load(module.exports, module);

默認(rèn)情況下,Node準(zhǔn)備的exports變量和module.exports變量實(shí)際上是同一個(gè)變量瑰谜,并且初始化為空對(duì)象{}

exports.foo = function() { return 'foo';};
exports.bar = function() {return 'bar';};

也可寫(xiě)成

module.exports.foo = function() { return 'foo';};
module.exports.bar = function() {return 'bar';};

如若輸出函數(shù)或者數(shù)組

module.exports = function() { return 'foo';};
exports = module.exports = {};

① 如若輸出鍵值對(duì)象{}欺冀,可以利用exports這個(gè)已存在的空對(duì)象{},并繼續(xù)添加新的鍵值萨脑;
② 如若輸出一個(gè)函數(shù)或數(shù)組隐轩,必須直接對(duì)module.exports對(duì)象賦值;
③所以建議使用module.exports = xxx 方式輸出模塊變量渤早。
④ exports 是指向的 module.exports 的引用职车。
⑤ require() 返回的是 module.exports 而不是 exports。

二鹊杖、基本模塊
1. fs - 文件系統(tǒng)模塊悴灵,負(fù)責(zé)讀寫(xiě)文件
① 異步讀文件

// 讀取文本文件
'use strict';

var fs = require('fs');

fs.readFile('demo.txt', 'utf-8', function(err, data) { 
   if (err) { // err 正常為null,異常時(shí)為錯(cuò)誤對(duì)象
       console.log(err);
   } else { // data 讀取到的String骂蓖,異常時(shí)為undefined
       console.log(data);
   }
});
// 讀取圖片文件
'use strict';

var fs = require('fs');

fs.readFile('demo.png', function(err, data) {
    if (err) {
        console.log(err);
    } else {
        console.log(data); // 一個(gè)Buffer對(duì)象(一個(gè)包含零個(gè)或任意個(gè)字節(jié)的數(shù)組)
        console.log(data.length + ' bytes');
    }
});

Buffer對(duì)象可以和String相互轉(zhuǎn)換

// Buffer -> String
var text = data.toString('utf-8');
// String -> Buffer
var buf = Buffer.from(text, 'utf-8');

② 同步讀文件

'use strict';

var fs = require('fs');
// 同步讀取文件不接受回掉函數(shù)
var data = fs.readFileSync('demo.txt', 'utf-8');
console.log(data); 

如若同步讀取文件發(fā)生錯(cuò)誤积瞒,用try...catch捕獲錯(cuò)誤

'use strict';

try {
    var data = fs.readFileSync('demo.txt', 'utf-8');
    console.log(data);
} catch (err) {
    console.log(err);
}

③ 異步寫(xiě)文件

'use strict';

var fs = require('fs');

var data = 'Hello, world!';
fs.writeFile('output.txt', data, function(err) {
    if (err) {
        console.log(err);
    } else {
        console.log('ok');
    }
});
  • 如若文件不存在,則會(huì)自動(dòng)創(chuàng)建一個(gè)
  • 如若傳入的數(shù)據(jù)為String登下,默認(rèn)按UTF-8寫(xiě)入文件茫孔,如若是Buffer對(duì)象,則寫(xiě)入的為二進(jìn)制文件被芳。
  • 回調(diào)函數(shù)只關(guān)心是否寫(xiě)入成功缰贝,所以只需一個(gè)err參數(shù)即可。

④ 同步寫(xiě)文件

'use strict';

var fs = require('fs');

var data = 'Hello world!';
fs.writeFileSync('output.txt', data);

⑤ stat -- 返回文件或目錄的詳細(xì)信息畔濒,比如大小剩晴、創(chuàng)建時(shí)間等


'use strict';

var fs = require('fs');

fs.stat('demo.txt', function(err, stat) {
    if (err) {
        console.log(err);
    } else {
        // 是否是文件
        console.log('isFile: ' + stat.isFile());
        // 是否是 目錄
        console.log('isDirectory: ' + stat.isDirectory());
        if (stat.isFile()) {
            console.log('size: ' + stat.size); // 文件大小
            console.log('birth time: ' + stat.birthtime); // 創(chuàng)建時(shí)間
            console.log('modified time: ' + stat.mtime); // 修改時(shí)間
        }

    }
});

⑥ statSync

'use strict';

var fs = require('fs');

var stat = fs.statSync('demo.txt');
console.log('isFile: ' + stat.isFile());
// 是否是 目錄
console.log('isDirectory: ' + stat.isDirectory());
if (stat.isFile()) {
    console.log('size: ' + stat.size); // 文件大小
    console.log('birth time: ' + stat.birthtime); // 創(chuàng)建時(shí)間
    console.log('modified time: ' + stat.mtime); // 修改時(shí)間
}

2. stream - 僅在服務(wù)區(qū)端可用的模塊,目的支持“流”這種數(shù)據(jù)結(jié)構(gòu)
① 從文件流讀取文本內(nèi)容

/*
 * 從文本流讀取文本內(nèi)容
 */
'use strict';

var fs = require('fs');

var rs = fs.createReadStream('demo.txt', 'utf-8');
// data 事件流表示流數(shù)據(jù)可以讀取
rs.on('data', function(chunk) {
    console.log('data event');
    console.log(chunk);
});
// end 事件表示這個(gè)流已到末尾侵状,無(wú)數(shù)據(jù)可讀
rs.on('end', function() {
    console.log('end event');
});
// error 事件表示出錯(cuò)
rs.on('error', function(err) {
    console.log('Error: ' + err);
});

② 以流形式寫(xiě)入文件

/*
 * 以流的形式寫(xiě)入文件
 */

'use strict';

var fs = require('fs');

// 寫(xiě)入文本內(nèi)容
var ws1 = fs.createWriteStream('output1.txt', 'utf-8');
ws1.write('使用Stream寫(xiě)入文本數(shù)據(jù)\n');
ws1.write('END');
ws1.end();

// 寫(xiě)入二進(jìn)制數(shù)據(jù)
var ws2 = fs.createWriteStream('output2.txt');
ws2.write(new Buffer('使用Stream寫(xiě)入二進(jìn)制數(shù)據(jù)\n', 'utf-8'));
ws2.write(new Buffer('END'));
ws2.end();

注:若寫(xiě)入的文件不存在赞弥,則會(huì)創(chuàng)建一個(gè)新的文件。

③ pipe

'use strict';

var fs = require('fs');

var rs = fs.createReadStream('demo.txt');
var ws = fs.createWriteStream('copied.txt');

rs.pipe(ws);

pipe()把一個(gè)文件流和另一個(gè)文件流串起來(lái)壹将,源文件的所有數(shù)據(jù)就自動(dòng)寫(xiě)入到目標(biāo)文件中嗤攻。實(shí)際為一個(gè)復(fù)制文件的過(guò)程
默認(rèn)情況,Readable數(shù)據(jù)流數(shù)據(jù)讀取完畢之后诽俯,end事件觸發(fā)后妇菱,將自動(dòng)關(guān)閉Writable數(shù)據(jù)流。

readable.pipe(writable, { end: false }); // 禁止自動(dòng)關(guān)閉Writable流

3. http
① HTTP服務(wù)器

/*
 * 創(chuàng)建http服務(wù)
 */
'use strict';

var http = require('http');
// 創(chuàng)建http server
var server = http.createServer(function(request,response) {
    // 回調(diào)函數(shù)接收request和response對(duì)象
    // 獲得HTTP請(qǐng)求的method和url
    console.log(request.method + ':' + request.url);
    // 將HTTP響應(yīng)200寫(xiě)入response暴区,同時(shí)設(shè)置Content-Type:text/html;
    response.writeHead(200, {'content-Type': 'text/html'});
    // 將HTTP響應(yīng)的HTML內(nèi)容寫(xiě)入response
    response.end('<h1>Hello world!</h1>');
});

// 監(jiān)聽(tīng)8090端口
server.listen(8090);

console.log('Server is running at htpp://127.0.0.1:8090');

啟動(dòng)服務(wù)之后闯团,在瀏覽器中輸入http://127.0.0.1:8090之后

輸出.png

② 文件服務(wù)器

/*
 * 文件服務(wù)器
 */

'use strict';

var
    fs = require('fs'),
    url = require('url'),
    path = require('path'),
    http = require('http');

// 從命令行參數(shù)獲取root目錄,默認(rèn)是當(dāng)前目錄
var root = path.resolve(process.argv[2] || '.');
console.log('Static root dir: ' + root);

// 創(chuàng)建服務(wù)器
var server = http.createServer(function(request, response) {
    // 獲得URL的path
    var pathname = url.parse(request.url).pathname;
    // 獲得對(duì)應(yīng)的本地文件路徑
    var filepath = path.join(root, pathname);
    // 獲取文件狀態(tài)
    fs.stat(filepath, function(err, stats) {
        if(!err && stats.isFile()) {
            // 沒(méi)有出錯(cuò)并且文件存在
            console.log('200' + request.url);
            // 發(fā)送200響應(yīng)
            response.writeHead(200);
            // 將文件流導(dǎo)向response;
            fs.createReadStream(filepath).pipe(response);
        } else {
            // 出錯(cuò)了或者文件不存在
            console.log('404' + request.url);
            // 發(fā)送404響應(yīng)
            response.writeHead(404);
            response.end('404 Not Found');
        }
    });
});

server.listen(8090);

console.log('Server is running at http://127.0.0.1:8090');

在命令行運(yùn)行 node server.js /path/dir; /path/dir 為本地一個(gè)有效目錄仙粱,然后在瀏覽器中輸入 http://127.0.0.1:8090/index.html房交,本地目錄有此文件index.html,服務(wù)器就可以把文件內(nèi)容發(fā)送給瀏覽器伐割。

輸出

4. crypto - 提供通用的加密和哈希算法
①Hash

將長(zhǎng)度不固定的消息作為輸入候味,通過(guò)運(yùn)行hash函數(shù)刃唤,生成固定長(zhǎng)度的輸出,這段輸出就叫做摘要白群。過(guò)程不可逆尚胞,即輸入固定的情況產(chǎn)生固定的輸出,但知道輸出的情況無(wú)法反推輸入帜慢。

常見(jiàn)摘要算法:

  • MD5: 128 位
  • SHA1: 160位
  • SHA256: 256位
  • SHA512: 512位
/*
 * MD5:128位
 */
const crypto = require('crypto');
const hash = crypto.createHash('md5');

// 可以多次調(diào)用update(); hash.update()方法就是將字符串相加
hash.update('Hello, world!');
hash.update('Hello, nodejs!');

console.log(hash.digest('hex')); // hash.digest()將字符串加密返回
/*
 * SHA-1 :160 位
 */

const crypto = require('crypto');
const hash = crypto.createHash('sha1');

// 可以多次調(diào)用update(); hash.update()方法就是將字符串相加
hash.update('Hello, world!');
hash.update('Hello, nodejs!');

console.log(hash.digest('hex')); // hash.digest()將字符串加密返回

同理?yè)Q成 把參數(shù)換成 sha256笼裳、sha512即可。

② Hmac

/*
 * Hmac
 */

const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', 'secret-key'); // 需要一個(gè)密鑰

hmac.update('Hello, world!');
hmac.update('Hello, nodejs!');

console.log(hmac.digest('hex'));
  • 可以理解為帶密鑰的hash函數(shù)粱玲。
  • 只要密鑰發(fā)生了變化躬柬,即使同樣的輸入,輸出也不同抽减。

③ 對(duì)稱(chēng)加密

加密和解密都用同一個(gè)密鑰允青,常見(jiàn)的對(duì)稱(chēng)加密算法有:DES、3DES胯甩、AES昧廷、Blowfish、RC5偎箫、IDEA


/*
 * AES
 */

const crypto = require('crypto');

// 加密
function aesEncrypt(data, key) {
    const cipher = crypto.createCipher('aes192', key);
    var crypted = cipher.update(data, 'utf-8', 'hex');
    console.log("cc" + crypted);
    crypted += cipher.final('hex');
    return crypted;
}
// 解密
function aesDecrypt(encrypted, key) {
    const decipher = crypto.createDecipher('aes192', key);
    var decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8'); // decipher.final() 返回加密的內(nèi)容
    return decrypted;
}

var data = 'Hello, this is a secret message!';
var key = 'Password!';
var encrypted = aesEncrypt(data, key);
var decrypted = aesDecrypt(encrypted, key);

console.log('Plain text: ' + data);
console.log('Encrypted text: ' + encrypted);
console.log('Decrypted text:' + decrypted);

三木柬、Web開(kāi)發(fā)
1. koa
① 入門(mén)示例

/*
 * Koa2
 */

// 導(dǎo)入的為一個(gè)class
const Koa = require('koa');

const app = new Koa();

app.use(async(ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '<h1>Hello, koa2!</h1>';
});

app.listen(3000);
console.log('app started at port 3000');
  • 參數(shù)ctx是由koa傳入的封裝了request和response的變量;
  • next是koa傳入的將要處理的下一個(gè)異步函數(shù)淹办;
  • 關(guān)鍵字async和await眉枕,可以把一個(gè)function變?yōu)楫惒侥J剑?/li>
  • async標(biāo)記的函數(shù)稱(chēng)為異步函數(shù),在異步函數(shù)中怜森,可以用await調(diào)用另一個(gè)異步函數(shù)
  • ctx.url相當(dāng)于ctx.request.url速挑,ctx.type相當(dāng)于ctx.response.type;
/*
 * koa middleware
 */

const Koa = require('koa');

const app = new Koa();

app.use(async(ctx, next) => {
    console.log(`Step1: + ${ctx.request.method} ${ctx.request.url}`); // 打印URL
    await next(); // 調(diào)用下一個(gè)middleware
});

app.use(async(ctx, next) => {
    const start = new Date().getTime();
    await next(); // 調(diào)用下一個(gè)middleware
    const ms = new Date().getTime() - start; // 耗費(fèi)時(shí)間
    console.log(`Step2: Time: ${ms}ms`); // 打印消耗時(shí)間
});

app.use(async(ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '<h1>Hello, koa2!</h1>';
    console.log("Step3");
});

app.listen(3000);
console.log('app started at port 3000');
  • 當(dāng)用瀏覽器訪問(wèn)http://localhost:3000時(shí),命令行的輸出為:
    Step1: +GET /
    Step3
    Step2: Time: 2ms
  • 每收到一個(gè)http請(qǐng)求副硅,koa就會(huì)調(diào)用app.use()注冊(cè)的async函數(shù)姥宝,并傳入ctx和next參數(shù)。app.use()的順序決定了middleware的順序恐疲。

② 處理URL

/*
 * koa-router GET
 */

const Koa = require('koa');

const router = require('koa-router')();

const app = new Koa();

// log request url
app.use(async(ctx, next) => {
    console.log(`Process ${ctx.request.method} ${ctx.request.url}`);
    await next();
});

// add url-route
router.get('/hello/:name', async(ctx, next) => {
    var name = ctx.params.name;
    ctx.response.body = `<h1>Hello, ${name}!`;
});

router.get('/', async(ctx, next) => {
    ctx.response.body = `<h1>Index</h1>`;
});

// add router middleware;
app.use(router.routes());

app.listen(3000);
console.log('app started at port 3000');

在瀏覽器輸入:http:/localhost:3000腊满,返回的頁(yè)面顯示 Index;
在瀏覽器輸入:http://localhost:3000/hello/andy培己,返回的頁(yè)面顯示 Hello, andy! ;
router.get('/path', async fn)來(lái)注冊(cè)一個(gè)GET請(qǐng)求碳蛋。可以在請(qǐng)求路徑中使用帶變量的/hello/:name省咨,變量可以通過(guò)ctx.params.name訪問(wèn)

輸出
輸出
/*
 * koa-router POST
 */

const Koa = require('koa');

const router = require('koa-router')();

const bodyParser = require('koa-bodyparser');

var app = new Koa();
app.use(async(ctx, next) => {
    console.log(`Process ${ctx.request.method} ${ctx.request.url}`);
    await next();
});

app.use(bodyParser()); // 用來(lái)解析原始request請(qǐng)求肃弟,把解析后的參數(shù)綁定到ctx.request.body中

router.get('/', async(ctx, next) => {
    ctx.response.body = `<h1>Index</h1>
        <form action="/signin" method="post">
            <p>Name: <input name="name" value="koa"></p>
            <p>Password: <input name="password" type="password"></p>
            <p><input type="submit" value="Submit"></p>
        </form>`;
});

router.post('/signin', async(ctx, next) => {
    var
        name = ctx.request.body.name || '',
        password = ctx.request.body.password || '';
    console.log(`signin with name: ${name}, password:${password}`);
    if (name === 'koa' && password === '12345') {
        ctx.response.body = `<h1>Welcome, ${name}!`;
    } else {
        ctx.response.body = `<h1>Login failed!</h1>
            <p><a href="/">Try again</a></p>`;
    }
});

app.use(router.routes());
app.listen(3000);
console.log('app started at port 3000');

koa-bodyparser必須在router之前被注冊(cè)到app對(duì)象上;
類(lèi)似的put、delete、head請(qǐng)求也可用router處理

優(yōu)化目錄

controllers 
    -- hello.js
    -- index.js
controller.js
app.js
// hello.js
var fn_hello = async(ctx, next) => {
    var name = ctx.params.name;
    ctx.response.body = `<h1>Hello, ${name}!</h1>`;
};

module.exports = {
    'GET /hello/:name': fn_hello
};
// index.js
// 主頁(yè)
var fn_index = async(ctx, next) => {
    ctx.response.body = `<h1>Index</h1>
        <form action="/signin" method="post">
            <p>Name: <input name="name" value="koa"></p>
            <p>Password: <input name="password" type="password"></p>
            <p><input type="submit" value="Submit"></p>
        </form>`;
};

// 登錄
var fn_signin = async(ctx, next) => {
    var
        name = ctx.request.body.name || '',
        password = ctx.request.body.password || '';
    console.log(`signin with name: ${name}, password:${password}`);
    if (name === 'koa' && password === '12345') {
        ctx.response.body = `<h1>Welcome, ${name}!`;
    } else {
        ctx.response.body = `<h1>Login failed!</h1>
            <p><a href="/">Try again</a></p>`;
    }
};

module.exports = {
    'GET /': fn_index,
    'POST /signin': fn_signin
};
// controller.js
const fs = require('fs');

function addMapping(router, mapping) {
    for (var url in mapping) {
        if (url.startsWith('GET')) {
            var path = url.substring(4);
            router.get(path, mapping[url]);
            console.log(`register URL mapping: GET ${path}`);
        } else if (url.startsWith('POST')) {
            var path = url.substring(5);
            router.post(path, mapping[url]);
            console.log(`register URL mapping: POST ${path}`);
        } else {
            console.log(`invalid URL: ${url}`);
        }
    }
}

function addControllers(router) {
    var files = fs.readdirSync(__dirname + '/controllers');
    var js_files = files.filter((f)=>{
        return f.endsWith(".js");
    });

    for (var f of js_files) {
        console.log(`process controller: ${f}...`);
        let mapping = require(__dirname + '/controllers/' +f);
        addMapping(router, mapping);
    }
}

module.exports = function(dir) {
    let
        controllers_dir = dir || 'controllers';
        router = require('koa-router')();
    addControllers(router, controllers_dir);
    return router.routes();
}
// app.js
const controller = require('./controller');
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
var app = new Koa();
app.use(bodyParser());
app.use(controller());

app.listen(3000);
console.log('app started at port 3000');

③ Nunjuncks
目錄

views
   -- hello.html
demo.js

// demo.js
const nunjucks =require('nunjucks');

function createEnv(path, opts) {
    // 參數(shù) opts
    var autoescape = opts.autoescape === undefined ? true: opts.autoescape,
    noCache = opts.noCache || false,
    watch = opts.watch || false,
    throwOnUndefined = opts.throwOnUndefined || false,
    // nunjucks
    env = new nunjucks.Environment (
        new nunjucks.FileSystemLoader('views', {
            noCache: noCache,
            watch: watch
        }), {
            autoescape: autoescape,
            throwOnUndefined: throwOnUndefined
        }
    );
    if (opts.filters) {
        for (var f in opts.filters) {
            env.addFilter(f, opts.filters[f]);
        }
    }
    return env;
}

var env = createEnv('views', {
    watch: true,
    filters: {
        hex: function(n) {
            return '0x' + n.toString(16);
        }
    }
});

var s1 = env.render('hello.html', {name: '小明'});
console.log(s1); // <h1>Hello 小明</h1>
var s2 = env.render('hello.html', {name: '<script>alert("小明")</script>'});
console.log(s2); // <h1>Hello &lt;script&gt;alert(&quot;小明&quot;)&lt;/script&lt;/script&gt;</h1>

參考資料
廖雪峰JavaScritp教程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末笤受,一起剝皮案震驚了整個(gè)濱河市穷缤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌箩兽,老刑警劉巖绅项,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異比肄,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)囊陡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)芳绩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人撞反,你說(shuō)我怎么就攤上這事妥色。” “怎么了遏片?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵嘹害,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我吮便,道長(zhǎng)笔呀,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任髓需,我火速辦了婚禮许师,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘僚匆。我一直安慰自己微渠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布咧擂。 她就那樣靜靜地躺著逞盆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪松申。 梳的紋絲不亂的頭發(fā)上云芦,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音攻臀,去河邊找鬼焕数。 笑死,一個(gè)胖子當(dāng)著我的面吹牛刨啸,可吹牛的內(nèi)容都是我干的堡赔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼设联,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼善已!你這毒婦竟也來(lái)了灼捂?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤换团,失蹤者是張志新(化名)和其女友劉穎悉稠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體艘包,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡的猛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了想虎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卦尊。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖舌厨,靈堂內(nèi)的尸體忽然破棺而出岂却,到底是詐尸還是另有隱情,我是刑警寧澤裙椭,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布躏哩,位于F島的核電站,受9級(jí)特大地震影響揉燃,放射性物質(zhì)發(fā)生泄漏扫尺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一炊汤、第九天 我趴在偏房一處隱蔽的房頂上張望器联。 院中可真熱鬧,春花似錦婿崭、人聲如沸拨拓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)渣磷。三九已至,卻和暖如春授瘦,著一層夾襖步出監(jiān)牢的瞬間醋界,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工提完, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留形纺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓徒欣,卻偏偏與公主長(zhǎng)得像逐样,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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