koa2框架笔记

koa2框架笔记

Node.js是一一个异步的世界,官方API支持的都是callback 形式的异步编程模型,这会带来许多问题例如:

  • callback 嵌套问题
  • 异步函数中可能同步调用callback返回数据,带来不一致性。

为了解决以上问题Koa出现了

Koa--基于Node.js 平台的下一代web开发框架

koa是由Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的Web框架。使用 koa编写web应用,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。koa不在内核方法中绑定任何中间件,它 仅仅提供了一个轻量优雅的函数库,使得编写Web应用变得得心应手。开发思路和express差不多,最大的特点就是可以避免异步嵌套。

 koa2利用ES7的async/await特性,极大的解决了我们在做nodejs开发的时候异步给我们带来的烦恼。 

koa学习网站

Koa2.x框架的安装使用

  1. 安装Node.js 8.x以上的版本

    开发Koa2之前,Node.js 是确要求的,它要求Node.js版本高于V7.6。因为node.js 7.6版本开始完全支持async/await,所以才能完全你支持Koa2。

  2. 安装Koa:

    安装Koa框架和我们以前安装其他模块是一-样的。

    npm install koa --save

    -save参数,表示自动修改package.json文件,自动添加依赖项。

安装koa2

//  初始化package.json
npm init --yes

//  安装koa2
npm install koa

hello world代码

    //引入Koa
    const koa=require(' koa ');
    const app=new koa();
    //配置中间件 (可以先当做路由)
    app.use( async (ctx)=>{
    ctx.body= 'hello koa2'
    //监听端口
    app. listen(3000);    

Koa异步处理Async、Await 和Promise的使用(重点)

async是“异步”的简写,而await 可以认为是async wait的简写。所以应该很好理解async用于申明一个function 是异步的,而await 用于等待一个异步方法执行完成。

简单理解:

  • async是让方法变成异步。
  • await是等待异步方法执行完成。

详细说明:

async function testAsync(){
    return 'Hello async';
}
const result = testAsync();
console.log(result);
//输出结果
Promise { 'Hello async' }   

async是让方法变成异步,在终端里用node执行这段代码,你会发现输出了Promise {‘Helloasync’},这时候会发现它返回的是Promise。

await在等待async方法执行完毕执行

其实await等待的只是一个表达式,这个表达式在官方文档里说的是Promise对象,但是它也可以接受普通值。注意: await 必须在async方法中才可以使用因为await访问本身就会造成程序停止堵塞,所以必须在异步方法中才可以使用。

async/await同时使用

async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

await阻塞的功能 把异步改成一个同步

asyne function getData() {
    console.1og(2):
    return '这是一个数据’;
}
async function test(){
    console. log(1);
    var d=await getData();
    console. 1og(d);
    console.1og(3);
}
test();
同步                异步  1/d/3/2
//1 
//2
//这是一个数据
//3

async/await语法特点:

  1. 可以让异步逻辑用同步写法实现
  2. 最底层的await返回需要是Promise对象
  3. 可以通过多层async function的同步写法来代替传统的callback嵌套

koa路由、get 传值、动态路由

koa路由

路由(Routing)是由一个URI (或者叫路径)和一一个特定的HTTP方法(GET、POST等)组成的,涉及到应用如何响应客户端对某个网站节点的访问。通俗的讲: 路由就是根据不同的URL地址,加载不同的页面实现不同的功能。

Koa中的路由和Express有所不同,在Express中直接引入Express就可以配置路由,但是在Koa中我们需要安装对应的koa-router路由模块来实现。

npm install  koa-router --save
const Koa = require('koa');
const Router = require('koa-router'); 
//实例化
const app = new Koa();
const router=new Router();
//配置路由 
//ctx 上下文 context   req,res等信息都放在ctx里面
router.get('/', async (ctx)=> {
    ctx.body="首页";//返回数据  原生里面的res.send()
})
router.get('/news, async(ctx)=>{
    ctx.body= "这是一个新闻页面”
});
//启动路由
app.use(router.routes()); 
//可以配置也可以不配置   建议配置
app.use(router.allowedMethods()); 
//这是官方文档的推荐用法,我们可以看到router.allowedMethods()用在了路由匹配router.routes()之后,所以在当所有路由中间件最后调用。 此时根据ctx.status设置response响应头

app.listen(3000,()=>{
    console.1og('starting at port 3000' ) ;
});

koa路由get传值

在koa2中GET传值通过request接收,但是接收的方法有两种:query和querystring

  • query:返回的是格式化好的参数对象
  • querystring:返回的是请求字符串
router.get( '/news', (ctx)=>{
    let url=ctx.url;
//从request中获取GET请求
    let request =ctx.request;
    let req_query=request.query;
    let req_querystring=request.querystring;
//从ctx中直接获取
    let ctx_query = ctx.query;   //{id:123,name:123} 获取的对象 推荐
    let ctx_querystring = ctx.querystring;//id=123&name=123 获取的字符串
    ctx.body={
        url,
        req_ query,
        req_ querystring,
        ctx_ query,
        ctx_ querystring
    }
});

POST请求参数获取原理

对于POST请求的处理,koa2没有封装获取参数的方法,需要通过解析上下文context中的原生node.js请求对象req,将POST表单数据解析成query string(例如:a=1&b=2&c=3),再将query string 解析成JSON格式(例如:{"a":"1", "b":"2", "c":"3"})

注意:
ctx.request是context经过封装的请求对象,ctx.req是context提供的node.js原生HTTP请求对象,同理ctx.response是context经过封装的响应对象,ctx.res是context提供的node.js原生HTTP请求对象。

获取Post请求的步骤:

  1. 解析上下文ctx中的原生nodex.js对象req。
  2. 将POST表单数据解析成query string-字符串. (例如:user=jspang&age=18)
  3. 将字符串转换成JSON格式。
// 解析上下文里node原生请求的POST参数
function parsePostData( ctx ) {
  return new Promise((resolve, reject) => {
    try {
      let postdata = "";
      ctx.req.addListener('data', (data) => {
        postdata += data
      })
      ctx.req.addListener("end",function(){
        let parseData = parseQueryStr( postdata )
        resolve( parseData )
      })
    } catch ( err ) {
      reject(err)
    }
  })
}

// 将POST请求参数字符串解析成JSON
function parseQueryStr( queryStr ) {
  let queryData = {}
  let queryStrList = queryStr.split('&')
  console.log( queryStrList )
  for (  let [ index, queryStr ] of queryStrList.entries()  ) {
    let itemList = queryStr.split('=')
    queryData[ itemList[0] ] = decodeURIComponent(itemList[1])
  }
  return queryData
}

动态路由

router.get('/news/:id', async(ctx)=>{
    //获取动态路由的传值
    console.log(ctx.params)//{id:xxx} 
    ctx.body= "这是一个新闻页面”
});

中间件

通俗的讲: 中间件就是匹配路由之前或者匹配路由完成做的一系列的操作,我们就可以把它叫做中间件

在express中间件(Middleware) 是一个函数它可以访问请求对象(request object (req)),响应对象(response object(res))和web应用中处理请求-响应循环流程中的中间件,一般被命名为next的变量。在Koa中中间件和express有点类似

中间件的功能包括:

  • 执行任何代码
  • 修改请求和响应对象
  • 终结请求-响应循环
  • 调用堆栈中的下一一个中间件

如果get,post回调函数中,没有next参数,那么就匹配上第一个路由,就不会往下匹配了。如果想往下匹配的话,那么需要写next()

Koa应用可使用如下几种中间件:

  • 应用级中间件
  • 路由级中间件
  • 错误处理中间件
  • 第三方中间件

应用级中间件 匹配任何路由

app.use(async (ctx,next)=>{
    //匹配路由之前打印日期
    console.log(new Date());
    await next(); //当前路由匹配完成以后继续向下匹配
})

路由级中间件

//匹配到news路由以后继续向下匹配路由
router.get('/news', async(ctx, next)=>{
    console.log(1)
    await next()
})
router.get('/news', function (ctx) {
    ctx.body="Hello koa";
})

错误处理中间件

app.use(async (ctx,next)=> {
    await next();
    //如果页面找不到 
    if(ctx.status==404){
    ctx.status = 404;
    ctx.body="这是一个404页面"
}
}); 

Koa中间件的执行顺序

image
app.use(async (ctx,next)=> {
    console.log('1.这是第一个中间件01')
    await next();

    console.log('5.匹配路由完成以后又会返回来执行中间件')
}
}); 

app.use(async (ctx,next)=> {
    console.log('2.这是第一个中间件02')
    await next();

    console.log('4.匹配路由完成以后又会返回来执行中间件')
}
}); 

router.get('/news', async (ctx)=> {
    console.log('3.匹配到了这个路由')
    ctx.body="这是一个新闻";
})
结果
1、这是第一个中间件01
2、这是第二个中间件02
3、匹到了news这个路由
4、匹配路由完成以后又会返回来执行中间件
5、匹配路由完成以后又会返回来执行中间件

koa ejs模板引擎

ejs把nodejs后台的数据渲染到静态页面上

Koa中使用ejs 模板的使用

1.安装koa-views 和ejs

  1. 安装koa-views
    npm install --save koa-views
  2. 安装eis
    npm install ejs --save

2.引入koa-views 配置第三方中间件
//第一种方式配置 模板后缀名为ejs

const views = require('koa views');
app.use(views(‘views’,{extension:'ejs'}));//应用ejs模板引擎

//第二种方式配置 模板后缀名为html

const views = require('koa views');
app.use(views(‘views’,{
    map:{html:'ejs'}
}));

3.渲染对应的模板引擎

router.get('/add',async (ctx)=>{
    let title = 'hello koa2'
    await ctx.render(index',{
        title:title
    })  
})

4.Ejs引入模板 引入外部公共文件

<%- include public/header.ejs %>

5.Ejs绑定数据

<%=h%>

6.Ejs绑定html的数据

let content="<h2>这是一个h2</h2>”
await ctx.render ('/news',{
    content: content
})

<%-content%>

7.Ejs模板判断语句

<% if(true){ %>   
    true
<%} else{ %>
    false
<%} %>

8.Ejs模板中循环数据

<ul >
    <%for(var i=0;i<1ist. length;i++) {%>
         <1i><%=1ist[i]%></1i>
    <%}%>
</u1>

注意:我们需要在每一个路由的render里面都要這染一个公共的数据
公共的数据放在这个里面,这样的话在模板的任何地方都可以使用

//写一个中间件配置公共的信息
app. use (async (ctx,next)=> {
    ctx.state.userinfo='张三';
    await next();
})

koa 获取pos提交的数据

Koa中koa-bodyparser中间件的使用

1.安装koa-bodyparser

npm install --save koa-bodyparser

2.安装引入配置中间件

var Koa = require('koa'); 
var bodyParser = require('koa-bodyparser'); 
var app = new Koa();
app.use(bodyParser());
app.use(async ctx=> {
    ctx.body = ctx.request. body;
});

3.通过ctx.request.body 获取post提交的数据

//接收post提交的数据
router.post('/doAdd',async (ctx)=>{
   ctx.body=ctx.request.body;|
})

koa-static静态资源中间件

一、koa-static静态资源中间件的功能:

一个http请求访问web服务静态资源,一般响应结果有三种情况

  • 访问文本,例如js,css,png,jpg,gif
  • 访问静态目录
  • 找不到资源,抛出404错误

koa-static主要是用于访问静态资源

二、Koa 中koa-static中间件的使用

1、安装 koa-static

npm install --save koa-static

2、引入配置中间件

const static = require('koa-static'); 
//http://localhost:3000/css/basic.css 
//首先去static目录找,如果能找到返回对应的文件,找不到next()
//app.use(static(_dirname+'/static')); 
app.use(static('./static'));  


koa art-template模板引擎

一、Koa2中常见模板引擎的性能对比

适用于 koa 的模板引擎选择非常多,比如 jade、ejs、nunjucks、art-template等。

art-template 是一个简约、超快的模板引擎。

它采用作用域预声明的技术来优化模板渲染速度,从而获得接近 JavaScript 极限的运行性能,并且同时支持 NodeJS 和浏览器。

art-template支持ejs的语法,也可以用自己的类似angular数据绑定的语法

官网http://aui.github.io/art-template/

中文文档: http://aui.github.io/art-template/zh-cn/docs/

image

1.安装art-template 和 koa-art-template

npm install --save art-template
npm install --save koa-art-template

2.配置koa-art-template中间件:

const Koa = require('koa');
const render = require('koa-art-template');
const app = new Koa();
render(app, {
  root: path.join(__dirname, 'views'),//视图的位置
  extname: '.html',//后缀名
  debug: process.env.NODE_ENV !== 'production'//是否开启调试模式 true/false
});


router.get('/',async (ctx) {
  await ctx.render('index');
});

app.listen(8080);

三、art-template模板引擎语法

参考:http://aui.github.io/art-template/zh-cn/docs/syntax.html

绑定数据

{{list.name}}

绑定html数据

{{@list.h}}

条件

{{if num>20}}
    大于20
{{else}}
    小于20
{{/if}}        

循环数据

{{each list.data}}

{{$index}}---{{$value}}

{{/each}}

引入模板

{{include 'public/footer.html'}}

Cookie的使用

一、COOKIE 简介

  • cookie保存在浏览器客户端
  • 同一个浏览器访问同一个域共享数据
  1. 保存用户信息
  2. 浏览器历史记录
  3. 猜你喜欢的功能
  4. 10天免登陆
  5. 多个页面之间的数据传递
  6. cookie实现购物车功能

二、Koa2中 Cookie的使用

1、Koa中设置Cookie的值

ctx.cookies.set(name, value, [options])

通过 options 设置 cookie name 的 value :

options 名称 options 值
maxAge 一个数字表示从 Date.now() 得到的毫秒数
expires cookie 过期的 Date
path cookie 路径, 默认是'/'
domain cookie 域名
secure 安全 cookie 默认false,设置成true表示只有 https可以访问
httpOnly 是否只是服务器可访问 cookie, 默认是 true
overwrite 一个布尔值,表示是否覆盖以前设置的同名的 cookie (默认是 false). 如果是 true, 在同一个请求中设置相同名称的所有 Cookie(不管路径或域)是否在设置此Cookie 时从 Set-Cookie 标头中过滤掉。
path:’/news',   /*配置可以访问的页面*/
domain:'.baidu.com’  //正常情况不要设置默认就是当前域下面的所有页面都可以访问
//
a. baidu. com
b. baidu. com共享cookie的数据I

2、Koa中获取Cookie的值

ctx.cookies.get('name');

三、Koa中设置中文Cookie

console.log(new Buffer('hello, world!').toStr('base64');
// 转换成base64字符串:aGVsbG8sIHdvcmxkIQ==
console.log(new Buffer('aGVsbG8sIHdvcmxkIQ==', 'base64').toString());
// 还原base64字符串:hello, world!

Koa2中 Cookie的使用教程下载地址:https://pan.baidu.com/s/1KNaA97kGwNhavch5rP_G7w

上面地址失效请访问:https://www.itying.com/goods-800.html

Koa Session的使用

一、Koa-Session简单介绍

session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而session保存在服务器上。

二、Session的工作流程

当浏览器访问服务器并发送第一次请求时,服务器端会创建一个session对象,生成一个类似于key,value的键值对, 然后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带key(cookie),找到对应的session(value)。 客户的信息都保存在session中

三、koa-session的使用:

  1. 安装 koa-session
    npm install koa-session --save
  1. 引入express-session
    const session = require('koa-session');
  1. 设置官方文档提供的中间件
app.keys = ['some secret hurr'];//cookie的签名
const CONFIG = {
  key: 'koa:sess',   //cookie key (默认 is koa:sess)
  maxAge: 86400000,  // cookie的过期时间 默认一天
  overwrite: true,  //是否可以overwrite   没有效果 (默认default true)
  httpOnly: true, //cookie是否只有服务器端可以访问 (default true)
  signed: true,   //签名默认true
  rolling: false,  //在每次请求时强行设置session,这将重置session过期时间(默认:false)
  renew: true,  //当它快过期的时候重新设置  需要设置 true
};
app.use(session(CONFIG, app));
  1. 使用

    设置值 ctx.session.username = "张三";

    获取值 ctx.session.username

四、Koa中Cookie和Session区别

  1. cookie数据存放在客户的浏览器上,session数据放在服务器上。

  2. cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
    考虑到安全应当使用session。

  3. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
    考虑到减轻服务器性能方面,应当使用COOKIE。

  4. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

MongoDB Compass Community可视化工具 (官方提供)

MongoDB Compass是MongoDB官网提供的一个集创建数据库、管理集合和文档、
运行临时查询、评估和优化查询、性能图表、构建地理查询等功能为一体的MongoDB
可视化管理工具。

  1. 下载最新的mongodb安装完成后会自动安装mongodb可视化工具
    https://www.mongodb.com/download-center?imp=nav#community

  2. 单独下载mongodb可视化工具
    https://www.mongodb.com/products/compass
    https://www.mongodb.com/download-center?jimp=nav#compass

一、索引基础

索引是对数据库表中一列或多列的值进行排序的一一种结构,可以让我们查询数据库变得
更快。MongoDB 的索引几乎与传统的关系型数据库一模一样,这其中也包括- -些基本的查
询优化技巧。

下面是创建索引的命令 :

db.user.ensureIndex({"username":1})

获取当前集合的索引:

db.user.getIndexes()

删除索引的命令是:

db.user.dropIndex( {"username":1})

封装mongodb DB库之前的一些准备工作 es6 class类静态方法以及单例模式

es6 class类

//定义Person类
class Person {
    constructor (name, age) { /*类的构造函数,实例化的时候执行,new的时候执行*/
    this._name=name;
    this._age=age;
    }
    getName() {
        alert(this._ name);
    }
    setName (name) {
        this. name-name
    }
}

var  p=new Person('张三1',' 20' );//实例化

es6 里面的继承

class Web extends Person{ //extends关键字继承了Person
    constructor (name,age,sex) {
    super(name, age); // super 实例化子类的时候把子类的数据传给父类
    this.sex=sex;
    }
    print() {
    console.log(this.sex);
    }
}

es6 里面的静态方法

class Person {
    constructor (name) {
        this._ name=name; /*属 性*/
    }
    run(){ /*实例方法*/
        console.log(this._name);
    }
    static work(){ /*静态方法*/
        console.log('这是一个静态方法');
    }
}
var p=new Person('张三’); 
r.run();
Person.work(); //直接调用静态方法

es6 里面的单例

无论实例化多少次 构造函数只执行一次
优点:有利于提高性能

class Db {
    constructor:(){
        console.log("实例化会触发构造函数’);
        this.connect();
    }
    connect() {
        console. log('连接数据库’);
    }
    find({
        console. log('查询数据库' );
    }
    static getInstance(){    //单例   判断有没有实例,有直接返回
        if(!DB.instance){
            Db.instance=new new Db();
        }
        return Db.instance
    }
}

var db=new Db() 

var mydb1=Db.getInstance()  
var mydb2=Db.getInstance() 
var mydb2=Db.getInstance() 
//调用静态方法只实例化一次

封装Koa 操作Mongodb数据库的DB类库

目标

基于官方的node- mongodb-native驱动,封装一个更小、更快、更灵活的DB模块,让我们用nodejs操作Mongodb数据库更方便、更灵活。

  1. 安装mongodb

    cnpm install mongodb --save

  2. 引入mongodb下面的MongoClient

var MongoClient = require('mongodb').MongoClient;
  1. 定义数据库连接的地址 以及配置数据库
定义数据库连接的地址
var dburl = 'mongodb://localhost:27017/';
定义数据库的名称
var dbName = 'koa' 
  1. nodejs连接数据库

    MongoClient.connect(url,function(err,client){

    const db = client.db(dbName);  获取数据库db对象
    

    })

  2. 操作数据库

     MongoClient.connect(url,function(err,db){

         if(err){
             console. log(err);
             return;
         }
            //添加数据
            db.collection('user').insertOne({"name":"张三"},

            function(err,result){
                if(!err){
                    console. log(‘添加成功’);
                }
                db.close(); //关闭连接
            })

            //查询数据
            var resul t=db.collection('user').find({});
            result.toArray((err, result)=>{
                console.log(result) ;
            })
     })

Koa应用生成器以及Koa路由模块化

一、koa应用生成器

通过应用koa脚手架生成工具可以快速创建一个基于koa2的应用的骨架,实现路由模块化

  1. 全局安装

    npm install koa-generator -g

  2. 创建项目

    koa koa_ demo

  3. 安装依赖

    cd koa_ demo
    npm install

  4. 启动项目

    npm start

Koa2生成器的使用:

https://www.itying.com/koa/start-generator.html

目录结构如下图:


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