Node基础记录

Node回调函数

node是非堵塞,比如我们执行一个读取文件的操作,然后打印一句话,不需要等待文件读取完成,这样就可以在读取文件时同事执行接下来的代码,大大提高了程序的性能,因此,阻塞是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内。

Node事件驱动程序

image.png
//引入events模块
const events = require('events');

//创建eventEmitter对象
let eventEmitter = new events.EventEmitter();

//创建事件处理程序
var connectHandler = function connected() {
    console.log('连接成功');

    //触发 data事件
    eventEmitter.emit('data');
}

//绑定connection事件处理程序
eventEmitter.on('connection', connectHandler);

//使用匿名函数绑定data事件
eventEmitter.on('data', function () {
    console.log('数据接收成功');
})

//触发connection事件
eventEmitter.emit('connection');
console.log('程序执行完毕');

//结果是:
//连接成功。
//数据接收成功。
//程序执行完毕。

//on是绑定事件,emit是触发事件,先走的24行触发-->16行的绑定,然后调用函数connectHandler,函数
//打印连接成功,然后触发data,最后才是程序执行完毕

Node 应用程序是如何工作的?

在 Node 应用程序中,执行异步操作的函数将回调函数作为最后一个参数, 回调函数接收错误对象作为第一个参数。
个人理解:异步操作都有回调函数,回调函数的第一个参数是err,第二个是data

var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
   if (err){
      console.log(err.stack);
      return;
   }
   console.log(data.toString());
});
console.log("程序执行完毕");

EventEmitter

直达https://www.runoob.com/nodejs/nodejs-event.html

这个就是处理各种事件的,addListener可以监听事件的执行,on是注册监听器的,once是指监听一次,emit是触发那个on注册的事件的

Buffer

直达https://www.runoob.com/nodejs/nodejs-buffer.html
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。

但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。

理解:buffer就是一个二进制的内存空间,我们创建了之后能存很大的数据,比如文件什么的,菜鸟里面列举了例子,里面的方法有创建buffer,操作,比较,转码,读取等操作.

Stream(流)[重要]

直达https://www.runoob.com/nodejs/nodejs-stream.html

Stream翻译就是水流,我们可以把文件想象成碗里的水


image.png

从流中读取数据

创建 input.txt 文件,内容如下:

菜鸟教程官网地址:www.runoob.com
创建 main.js 文件, 代码如下:

var fs = require("fs");//引入文件操作模块
var data = '';

// 创建可读流
var readerStream = fs.createReadStream('input.txt');//通过文件操作模块创建阅读流

// 设置编码为 utf8。
readerStream.setEncoding('UTF8');

// 处理流事件 --> data, end, and error
readerStream.on('data', function(chunk) {//如果有这个文件并且有数据就触发这个
   data += chunk;
});

readerStream.on('end',function(){//数据读取完触发
   console.log(data);
});

readerStream.on('error', function(err){//接收和写入出错触发
   console.log(err.stack);
});

console.log("程序执行完毕");

以上代码执行结果如下:

程序执行完毕
菜鸟教程官网地址:www.runoob.com

写入流

var fs = require("fs");
var data = '菜鸟教程官网地址:www.runoob.com';

// 创建一个可以写入的流,写入到文件 output.txt 中
var writerStream = fs.createWriteStream('output.txt');

// 使用 utf8 编码写入数据
writerStream.write(data,'UTF8');

// 标记文件末尾
writerStream.end();

// 处理流事件 --> data, end, and error
writerStream.on('finish', function() {
    console.log("写入完成。");
});

writerStream.on('error', function(err){
   console.log(err.stack);
});

console.log("程序执行完毕");

管道流

image.png

管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。
(看图source翻译为水源头,dest翻译为目的地. 可以理解为:我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢的实现了大文件的复制过程。

var fs = require("fs");

// 创建一个可读流
var readerStream = fs.createReadStream('input.txt');

// 创建一个可写流
var writerStream = fs.createWriteStream('output.txt');

// 管道读写操作
// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中
readerStream.pipe(writerStream);

console.log("程序执行完毕");

链式流

链式是通过连接输出流到另外一个流并创建多个流操作链的机制。链式流一般用于管道操作。

接下来我们就是用管道和链式来压缩和解压文件。

var fs = require("fs");
var zlib = require('zlib');

// 压缩 input.txt 文件为 input.txt.gz
fs.createReadStream('input.txt')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('input.txt.gz'));
  
console.log("文件压缩完成。");

接下来,让我们来解压该文件

var fs = require("fs");
var zlib = require('zlib');

// 解压 input.txt.gz 文件为 input.txt
fs.createReadStream('input.txt.gz')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('input.txt'));
  
console.log("文件解压完成。");

(压缩和解压没什么区别,区别就在于阅读流和写入流里面的文件名不一样,压缩解压用的方法是一个)

模块系统

为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。

var hello = require('./hello');//这是引入根目录下的hello.js
hello.world();                        //执行hello的world方法

**Node.js 中自带了一个叫做 http 的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。

这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象。

Node.js 的 require 方法中的文件查找策略如下:

由于 Node.js 中存在 4 类模块(原生模块和3种文件模块),尽管 require 方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同。如下图所示:**


image.png

(意思是:虽然原生模块比第三方模块优先级高,但是都是加载缓存区中才会输出)

require方法接受以下几种参数的传递:

  • http、fs、path等,原生模块。
  • ./mod或../mod,相对路径的文件模块。
  • /pathtomodule/mod,绝对路径的文件模块。
  • mod,非原生模块的文件模块。

[exports 和 module.exports 的使用]
如果要对外暴露属性或方法,就用 exports 就行,要暴露对象(类似class,包含了很多属性和方法),就用 module.exports。

*注意:建议同时使用 exports 和 module.exports,,,如果先使用 exports 对外暴露属性或方法,再使用 module.exports 暴露对象,会使得 exports 上暴露的属性或者方法失效。

原因在于,exports 仅仅是 module.exports 的一个引用*

函数

匿名函数

直达https://www.runoob.com/nodejs/nodejs-function.html
在JavaScript中,一个函数可以作为另一个函数的参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。

function say(word) {
  console.log(word);
}

function execute(someFunction, value) {
  someFunction(value);
}

execute(say, "Hello");

我们把 say 函数作为execute函数的第一个变量进行了传递。这里传递的不是 say 的返回值,而是 say 本身!

这样一来, say 就变成了execute 中的本地变量 someFunction ,execute可以通过调用 someFunction() (带括号的形式)来使用 say 函数。

当然,因为 say 有一个变量, execute 在调用 someFunction 时可以传递这样一个变量。

function execute(someFunction, value) {
  someFunction(value);
}

execute(function(word){ console.log(word) }, "Hello");

我们在 execute 接受第一个参数的地方直接定义了我们准备传递给 execute 的函数。

用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做匿名函数 。
(意思就是execute调用的时候,里面第一个变量是直接把say函数拿过来了,变成execute的本地变量了)

http(常用)

Node有自有的本地库,可以直接引入,其他第三方的可以npm下载引入

var http = require('http');

//方法1
// http.createServer(function(request,response){
//     response.writeHead(200,{"Content-Type": "text/plain"})
//     response.write('hello world')
//     response.end();
// }).listen(8888)


//方法2,匿名函数用法
function server(request, response) {
    response.writeHead(200, { "Content-Type": "text/plain" })
    response.write('hello world')
    response.end();
}

http.createServer(server).listen(8888)

路由

直达https://www.runoob.com/nodejs/nodejs-router.html
菜鸟上演示了一个例子,server.js创建服务器,引入'url',

var pathname = url.parse(request.url).pathname;//这句可以获取到页面请求的路径(端口号后面的http://127.0.0.1:8888/abcd/ABCD)

server.js和router.js都试用exports把方法抛出,index.js用require引入,然后使用server.js的函数,参数为router的函数

全局对象

直达https://www.runoob.com/nodejs/nodejs-global-object.html
js的全部对象是window, node全部变量是global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。

在 Node.js 我们可以直接访问到 global 的属性,而不需要在应用中包含它。

// 输出全局变量 __filename 的值
console.log( __filename );
// 输出全局变量 __dirname 的值
console.log( __dirname );

process

process 是一个全局变量,即 global 对象的属性。

它用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口。
(通过跟菜鸟示例代码敲一遍理解:这个变量的方法就是为了获取到各种操作系统的信息等等)

属性挺多的,但是重要的好像没多少,回头再好好看下

util常用工具

直达https://www.runoob.com/nodejs/nodejs-util.html
util 是一个Node.js 核心模块,提供常用函数的集合,用于弥补核心 JavaScript 的功能 过于精简的不足。

const util = require('util');

util.callbackify
util.callbackify(original) 将 async 异步函数(或者一个返回值为 Promise 的函数)转换成遵循异常优先的回调风格的函数,例如将 (err, value) => ... 回调作为最后一个参数。 在回调函数中,第一个参数为拒绝的原因(如果 Promise 解决,则为 null),第二个参数则是解决的值。

util.inherits
util.inherits(constructor, superConstructor) 是一个实现对象间原型继承的函数。
菜鸟中的例子:Sub 仅仅继承了Base 在原型中定义的函数,而构造函数内部创造的 base 属 性和 sayHello 函数都没有被 Sub 继承。

util.inspect
util.inspect(object,[showHidden],[depth],[colors]) 是一个将任意对象转换 为字符串的方法,通常用于调试和错误输出。它至少接受一个参数 object,即要转换的对象。

showHidden 是一个可选参数,如果值为 true,将会输出更多隐藏信息。

depth 表示最大递归的层数,如果对象很复杂,你可以指定层数以控制输出信息的多 少。如果不指定depth,默认会递归 2 层,指定为 null 表示将不限递归层数完整遍历对象。 如果 colors 值为 true,输出格式将会以 ANSI 颜色编码,通常用于在终端显示更漂亮 的效果。

特别要指出的是,util.inspect 并不会简单地直接把对象转换为字符串,即使该对 象定义了 toString 方法也不会调用。

菜鸟中的例子:第二个参数为true后,将幻术的具体信息都输出了,比如函数的信息

util.isArray(object)
如果给定的参数 "object" 是一个数组返回 true,否则返回 false。

util.isRegExp(object)
如果给定的参数 "object" 是一个正则表达式返回true,否则返回false。

util.isDate(object)
如果给定的参数 "object" 是一个日期返回true,否则返回false。

文件系统

直达https://www.runoob.com/nodejs/nodejs-fs.html
文件系统很重要,方法也很多,没事多看看,东西比较多我就不记录了,直接点直达去看看

//首先引入文件操作模块
const fs = require("fs");

//异步读取文件全部内容
fs.readFile("test.txt", (err, data) => {
    //如果文件不存在就输出错误信息
    if (err) {
        console.log(err)
    } else {
        console.log(data.toString());
    }
})
console.log("readFile我出现就是异步")

//创建一个文件,如果文件存在则替换,第一个参数是文件名,第二个参数是数据,第三个是错误回调,这个可以用作复制操作
fs.writeFile("test.txt","文本内容", err => {
    if (err) {
        console.log(err)
    }
})
console.log("writeFile我出现就是异步");

//文件追加,如果文件不存在,则创建一个再追加内容
fs.appendFile("test.txt", "追加内容", err => {
    if (err) {
        //出错就输出info
        console.log(err)
    }
})
console.log("appendFile我出现就是异步")

fs.readFile("test.txt", (err, data) => {
    if (err) {
        console.log(err)
    } else {
        console.log(data.toString())
    }
})


//异步读取这个图片,没有的话打印错误,有的话把图片数据传给writeFile进行复制
fs.readFile("知识星球.png",(err,data)=>{
    if (err) {
        console.log(err)
    } else {
        console.log(data)
        fs.writeFile("test.png",data,err=>{
            if (err) {
                console.log("复制错误",err)
            }
        })
    }
})

GET/POST请求

直达https://www.runoob.com/nodejs/node-js-get-post.html

获取GET请求内容

由于GET请求直接被嵌入在路径中,URL是完整的请求路径,包括了?后面的部分,因此你可以手动解析后面的内容作为GET请求的参数。

var http = require('http');
var url = require('url');
var util = require('util');
 
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
    res.end(util.inspect(url.parse(req.url, true)));
}).listen(3000);
//浏览器中访问 http://localhost:3000/user?name=菜鸟教程&url=www.runoob.com 可查看到url的参数信息

获取 URL 的参数

我们可以使用 url.parse 方法来解析 URL 中的参数,代码如下:

var http = require('http');
var url = require('url');
var util = require('util');
 
http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain'});
 
    // 解析 url 参数
    var params = url.parse(req.url, true).query;  //这句是关键
    res.write("网站名:" + params.name);
    res.write("\n");
    res.write("网站 URL:" + params.url);
    res.end();
 
}).listen(3000);
//在浏览器中访问 http://localhost:3000/user?name=菜鸟教程&url=www.runoob.com 

获取 POST 请求内容

post比较特殊,好像是自己通过get改造成post
*POST 请求的内容全部的都在请求体中,http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作。

比如上传文件,而很多时候我们可能并不需要理会请求体的内容,恶意的POST请求会大大消耗服务器的资源,所以 node.js 默认是不会解析请求体的,当你需要的时候,需要手动来做*

这个代码非常有意思,首先效果实你刚进去是只有两个输入框,然后通过提交页面只显示内容,原因是req的on监听数据,当开始没有数据,所以走了最下面输出表单的逻辑,点击页面按钮提交数据请求后有数据了,就执行了chunk累加内容给body,就走了输出提交数据的逻辑

var http = require('http');
var querystring = require('querystring');
 
var postHTML = 
  '<html><head><meta charset="utf-8"><title>菜鸟教程 Node.js 实例</title></head>' +
  '<body>' +
  '<form method="post">' +
  '网站名: <input name="name"><br>' +
  '网站 URL: <input name="url"><br>' +
  '<input type="submit">' +
  '</form>' +
  '</body></html>';
 
http.createServer(function (req, res) {
  var body = "";
  req.on('data', function (chunk) {
    body += chunk;
  });
  req.on('end', function () {
    // 解析参数
    body = querystring.parse(body);
    // 设置响应头部信息及编码
    res.writeHead(200, {'Content-Type': 'text/html; charset=utf8'});
 
    if(body.name && body.url) { // 输出提交的数据
        res.write("网站名:" + body.name);
        res.write("<br>");
        res.write("网站 URL:" + body.url);
    } else {  // 输出表单
        res.write(postHTML);
    }
    res.end();
  });
}).listen(3000);

工具模块

OS模块

直达https://www.runoob.com/nodejs/nodejs-os-module.html

提供基本的系统操作函数。

var os = require("os")

path模块

Node.js path 模块提供了一些用于处理文件路径的小工具,
主要是对文件路径的一些操作,拼接,格式化,获取绝对路径,相对路径,后缀等等

var path = require("path")     //引入node的path模块

path.resolve('/foo/bar', './baz')   // returns '/foo/bar/baz'
path.resolve('/foo/bar', 'baz')   // returns '/foo/bar/baz'
path.resolve('/foo/bar', '/baz')   // returns '/baz'
path.resolve('/foo/bar', '../baz')   // returns '/foo/baz'
path.resolve('home','/foo/bar', '../baz')   // returns '/foo/baz'
path.resolve('home','./foo/bar', '../baz')   // returns '/home/foo/baz'
path.resolve('home','foo/bar', '../baz')   // returns '/home/foo/baz'

从后向前,若字符以 / 开头,不会拼接到前面的路径(因为拼接到此已经是一个绝对路径);若以 ../ 开头,拼接前面的路径,且不含最后一节路径;若以 ./ 开头 或者没有符号 则拼接前面路径;

需要注意的是:如果在处理完所有给定的 path 片段之后还未生成绝对路径,则再加上当前工作目录。

Net模块

Node.js Net 模块提供了一些用于底层的网络通信的小工具,包含了创建服务器/客户端的方法,我们可以通过以下方式引入该模块:

var net = require("net")

看了一下方法主要是创建tcp服务器.socket,IP等方法,回头有时间子再研究下

DNS模块

Node.js DNS 模块用于解析域名。引入 DNS 模块语法格式如下:

var dns = require("dns")

主要是对域名IP之类的操作,回头要研究下dns

Domain 模块

Node.js Domain(域) 简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的异常。引入 Domain 模块 语法格式如下:

var domain = require("domain")

有一些方法,基本都是对错误事件的操作,比try catch更好?

web模块

web服务器

var http = require('http');
var fs = require('fs');
var url = require('url');
 
 
// 创建服务器
http.createServer( function (request, response) {  
   // 解析请求,包括文件名,这句最重要
   var pathname = url.parse(request.url).pathname;
   
   // 输出请求的文件名
   console.log("Request for " + pathname + " received.");
   
   // 从文件系统中读取请求的文件内容
   fs.readFile(pathname.substr(1), function (err, data) {
      if (err) {
         console.log(err);
         // HTTP 状态码: 404 : NOT FOUND
         // Content Type: text/html
         response.writeHead(404, {'Content-Type': 'text/html'});
      }else{             
         // HTTP 状态码: 200 : OK
         // Content Type: text/html
         response.writeHead(200, {'Content-Type': 'text/html'});    
         
         // 响应文件内容
         response.write(data.toString());        
      }
      //  发送响应数据
      response.end();
   });   
}).listen(8080);
 
// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:8080/');

< 在浏览器输入:http://127.0.0.1:8080/index.html后,就会把index.html信息传输到页面上

Express

直达https://www.runoob.com/nodejs/nodejs-express-framework.html
(里面有很多方法,看下,就是简化了写法,能让你更快的写核心代码)
Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。

使用 Express 可以快速地搭建一个完整功能的网站。

Express 框架核心特性:

可以设置中间件来响应 HTTP 请求。

定义了路由表用于执行不同的 HTTP 请求动作。

可以通过向模板传递参数来动态渲染 HTML 页面。

RESTful API

点击直达https://www.runoob.com/nodejs/nodejs-restful-api.html
HTTP 方法
以下为 REST 基本架构的四个方法:
GET - 用于获取数据。

PUT - 用于更新或添加数据。

DELETE - 用于删除数据。

POST - 用于添加数据。

这个菜鸟上举的例子是对json用户的增删改查,

多进程

直达https://www.runoob.com/nodejs/nodejs-process.html
< 我们都知道 Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。

每个子进程总是带有三个流对象:child.stdin, child.stdout 和child.stderr。他们可能会共享父进程的 stdio 流,或者也可以是独立的被导流的流对象。
Node 提供了 child_process 模块来创建子进程,方法有:

  • exec - child_process.exec 使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。

  • spawn - child_process.spawn 使用指定的命令行参数创建新进程。

  • fork - child_process.fork 是 spawn()的特殊形式,用于在子进程中运行的模块,如 fork('./son.js') 相当于 spawn('node', ['./son.js']) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。

1.exec() 方法
2.spawn() 方法
3.fork 方法
感觉这三个都是创建子进程,然后执行,第一个参数是执行命令,没有很理解

JXcore 打包

直达https://www.runoob.com/nodejs/nodejs-jxcore-packaging.html
就是node文件比较多,需要用这个工具来打包
JXcore 是一个支持多线程的 Node.js 发行版本,基本不需要对你现有的代码做任何改动就可以直接线程安全地以多线程运行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容