从壹开始前后端分离【再会】║最全的部署方案 & 最丰富的错误分析

缘起

哈喽大家好!今天是周一了,这几天趁着午休的时间又读了一本书《偷影子的人》,可以看看🙃。

今天也是入园的第九个月了,前后端分离系列到了今天正好是 43 篇,也写了 20 万字了,内心也是经历了各种曲折,是时候给《前后端分离》来一个句号了,为啥突然有了这个想法了呢?😮,周末两天在学习的时候,发现其他有的博主已经把自己的开源项目关闭更新了,刚开始还有点儿不理解,后来想了想,也确实无可厚非,吃完饭做了下思考:

1、我写的这个项目,当时不是为了写框架,只是想给大家提供下学习的参考,对比错误的,我很害怕会出现把注意力过多的放在框架上的,而忽略了基础知识学习的尴尬情况,这个在以后的新公司入职中,会比较被动,比如我前天面试遇到的,1: Vue运行时版本和完整版有啥区别和联系,2:Vue 异步更新DOM的机制有什么好处?如何实现的?

2、当然,有一些基础比较好的小伙伴,不仅能看懂我的代码,还能在我的项目基础上,进行封装,我还是很欢迎使用的,用在生产环境,如果我还一直进行大的迭代更新的话,可能对大家有一定的影响;

3、前两天群里有小伙伴问了一些部署的相关问题,我才发现,我竟忽略了这一部分,诚然,我也写了一些,但是没有统一起来,那就用这篇详细的部署文章来画个句号吧。

本篇文章会一直更新,内容十分丰富,包含了NetCore+vue 的各种部署方案,以及遇到的主要错误分析,目的就是为了让初学者可以有章可循。

一、部署

友1、WIN_独立部署

感谢群里(白云)小伙伴,博主 @小淋科技 提出的方案,我竟然忽略了,该打该打,官档都读不仔细,果然三人行必有我师!

微软官方文档 ——《独立部署 (SCD)》,使用这个方法很简单,只需要部署的时候,选择指定的平台,就可以不用安装其他,比如运行时、托管、SDK等,不过这样的话,可能下文的命令行操作不适用,不过还是很好很简单的方案,欢迎尝试。

image

1、WIN_IIS 部署,前后端不同站点

为了能有一个全新的环境,我还特地新买一台服务,要做好部署的准备,就必须要先进行初始化操作,也就是先准备好我们用到的原材料:

在服务器开启 IIS 就不多说了,相信每一个 Dotnet 开发者百分比必须要会的,其他的操作流程请看下边的步骤,具体的看动图:

先给自己的两个站点想好端口,比如我想的:core(1001端口)、Vue(1002端口)

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">// NetCore 部分

0、编译项目,保证代码没问题,并配置 CORS 跨域服务;//下边动图中没有配置CORS,大家自己要注意

①、发布项目;

②、拷贝到服务器指定文件夹;

③、IIS 添加站点;// 端口 1001
④、修改应用程序池为“无托管”;//这也是为啥要安装 windows hosting 的原因
⑤、安装 windows hosting(服务托管);//这是一个捆包,已经包含 Runtime
⑥、检查是否有指定的 Runtime(运行时);

⑦、重启项目,查看是否正常;http://localhost:1001/ // Vue 部分
①、Build 项目;

②、拷贝到服务器指定文件夹;

③、IIS 添加站点;// 端口 1002
④、运行项目,查看是否正常;http://</pre>

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">localhost</pre>

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">:1002/</pre>

动图1:

image

修改好 hash 模式后,就成功了,注意要在 Core 项目中配置 CORS 跨域:

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;"> c.AddPolicy("LimitRequests", policy => { // 支持多个域名端口,注意端口号后不要带/斜杆:比如localhost:8000/,是错的 // 注意,http://127.0.0.1:1818 和 http://localhost:1818 是不一样的,尽量写两个
policy
.WithOrigins("http://127.0.0.1:1002", "http://localhost:1002")
.AllowAnyHeader()
.AllowAnyMethod();
});</pre>

最终结果如下:

image

2、WIN_IIS 部署,前后端一个站点

从上边的过程中,可以看出来还是比较简单的,一气呵成,行云流水,不过可能自己操作的时候会出现这样那样的问题,毕竟我也是给很多小伙伴都部署过,没有上百,也有五十次了,如果你有错误,请看我文章下边第二章的 常见错误 ,找到你遇到的错误。

上边的操作中,我们是把两个项目部署到了 IIS 中的不同站点,当然,我们也可以部署到一个站点,这样的话,就是一个域名了,但是丝毫没有影响我们分开开发,我们前后端攻城狮还是和以前一样的开发,只是将发布的文件上传到指定服务器文件夹中就行了。

image

操作步骤和上边的其实是几乎一样的,只不过我们不需要配置 CORS 跨域了,因为是同一个站点,且是只需要一个端口号,我们定为 1003端口

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">// NetCore 部分,和上边的几乎一样,只不过不用进行 CORS 跨域设置
①、发布项目;

②、拷贝到服务器指定文件夹;

③、IIS 添加站点;// 端口 1003
④、修改应用程序池为“无托管”;//这也是为啥要安装 windows hosting 的原因
⑤、安装 windows hosting(服务托管);//这是一个捆包,已经包含 Runtime
⑥、检查是否有指定的 Runtime(运行时);

⑦、重启项目,查看是否正常;http://localhost:1003/ // Vue 部分
①、修改根路径,并且 Build 项目;// 还要修改接口api的路径,剩下的自己处理吧,不要项目后期用这个方法,路径修改麻烦

②、拷贝到服务器指定文件夹;//可以和 core 项目放到一起
③、修改 Core 的 IIS 站点,在它的站点内,嵌套一个vue站点;// 取名为 Vue
④、运行项目,查看是否正常;http://localhost:1003/vue/</pre>

动图2:

image

3、WIN_Git_Nginx_Kestrel 部署

上边的 IIS 部署咱们说完了,大家发现,存在一些问题:

1、过分的依赖 IIS,并没有完完全全的达到跨平台的目的;

2、操作步骤偏多,还需要拷贝文件,并且一个都不能少;

3、因为 IIS 会存在占资源的问题,每次如果我们更新 Blog.Core.dll 文件,都需要关闭站点,然后更新,再启动;

那接下来咱们就说说如何使用 Nginx+Kestrel 来简单的解决这些问题,步骤如下:

这里需要安装 Ngxin (http://nginx.org/en/download.html)、Git(https://git-scm.com/downloads)、SDK(http://go.microsoft.com/fwlink/?LinkID=287120),过程省略,大家自行安装,这里我们把端口定为,NetCore(1004端口)、Vue(1005端口)

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">// NetCore 部分 ①、执行 Blog.Core.Build.bat 文件;// 主要是 git pull ,dotnet build 和 dotnet run

②、Nginx 代理服务,将 8081 端口,代理到 1004;//具体的代码在下边
③、查看是否正常;// http://localhost:1004/ // Vue 部分
①、执行 BS 命令;// cnpm run BS,这里是我自己的命令,主要是git pull 和 build
②、Nginx 代理 Vue 服务,并配置跨域,代理到1005;//具体的代码在下边
③、查看是否正常;// http://localhost:1005/</pre>

大家可以看看,或者直接感受一下,这种部署方案,和 IIS 的方案,哪一个更好一些?三步走,也是很简单,而也很直观的。

// 红色是 NetCore 的 Nginx 相关配置;

// 绿色的是 Vue 的 Nginx 的相关配置;

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">#user nobody;
worker_processes 1; events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream; sendfile on;

#keepalive_timeout 0;
keepalive_timeout 600;
proxy_read_timeout 600; 
proxy_send_timeout 600; 

# dotnet core

upstream dotnetblogserver1 {
server 127.0.0.1:8081;
}
server {
listen 1004;
server_name localhost;
location /{
proxy_pass http://dotnetblogserver1;
index index.html index.htm;
}
location = /50x.html {
root html;
}
} # vue
server {
listen 1005;
server_name localhost;
location / {
root nuxt\Blog.Admin\dist;
index index.html index.htm;
}
location /api {
rewrite ^.+apb/?(.*)/1 break;
include uwsgi_params;
proxy_pass http://localhost:1004; #// 这是 netcore 端口
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
} } }</pre>

从上边的部署中,我们可以看到,过程中我们几乎都没有进行什么配置,或者说都是一次性的配置,比如 Git 安装,比如 Nginx 的学习,只要我们配置了一次,以后多个项目就可以很快的操作了,比 IIS 每一个都要 Publish 并且各种配置来的快些。

不仅如此,我们也换掉了 IIS ,使用了 netcore 自带的 Kestrel 应用服务器,并且不会出现任何不同步的问题,只要你本地调试好代码,并 push 到 git 上!

动图3:

image

4、WIN 部署到 Windows 服务

微软有提供 如何在windows服务托管asp.net core ,不过步骤比较麻烦,还需要改源码,网上找到一种方法 使用NSSM把.Net Core部署至windows服务

简单说一下步骤

1. 下载nssm:http://www.nssm.cc/download

2. 运行cmd,定位到nssm.exe文件路径,运行nssm install

3. 在弹出的窗口配置:

Path:dotnet所在的目录,一般默认是在C:\Program Files\dotnet\dotnet.exe;

Startup directory:程序所在的目录,就是最后程序dll所在的目录;

Arguments:程序dll的名称,一般是项目名加上.dll;

Service name:在此写上服务的名称即可。

最后点击install service 完成windows服务安装。

在windows服务找到对应服务名,启动,然后根据launchSettings.json配置的端口访问,即可调取接口。

image

5、Linux_Ubuntu_Nginx 部署

1、安装.NET Core

首先需要安装.NET Core Runtime: https://www.microsoft.com/net/download

image

点击之后,根据您的Linux发行版不同,选择相应的操作步骤:

image
image

最后执行dotnet --info验证安装是否成功:

image

2、安装Nginx

另外还需要安装Nginx,查看官网文档:https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-2.1&tabs=aspnetcore2x#install-nginx

安装好后,访问这个页面:http://yourIPAddress/index.nginx-debian.html,如果看到如下效果说明安装成功:

image

3、在服务器构建源码并发布

然后就是发布程序了,发布有两种办法:

  • 在开发机上执行 dotnet publish 然后把发布的文件远程到服务器上
  • 直接在服务器上使用源码构建并发布,我一般是这样做的。

如果直接在服务器上构建发布,所以需要安装.NET Core SDK:https://www.microsoft.com/net/learn/get-started-with-dotnet-tutorial

image

然后就可以使用发布命令了:dotnet publish --configuration Release

发布好的文件在 bin/Release/netcoreapp./publish **下面。

再把publish下的所有文件复制到我的目标文件夹即可:

image

在我的目标目录下,有这些文件:

image

如果执行 dotnet test.dll,这个程序就会在localhost:5000运行:

image

4、配置Nginx

然后我们再回来配置Nginx,进入 /etc/nginx/sites-available ,里面有一个Default文件,把它改个名,然后我们再建立一个新的Default文件:

image

保存后执行 sudo nginx -t 检验这个配置文件。

然后再执行 nginx -s reload 来重启nginx。

随后需要再把发布后的程序运行一下:dotnet test.dll:

image

在我使用网址访问80端口的时候,会自动跳转到 https//localhost:5001端口,导致连接失败:

可以在开发的时候,就直接去掉项目中使用 https 地址就行,在 launchSettions.json 文件中:

image

或者禁用 https 中间件:

image

打开浏览器访问试试:http://yourdomian.com/swagger/index.html

6、Linux_Docker 部署

1、docker安装

依次执行下列命令

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">sudo yum install -y yum-utils device-mapper-persistent-data lvm2

sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sudo yum makecache fast

sudo yum -y install docker-ce</pre>

检查docker是否安装成功:

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">docker version</pre>

若成功显示如下:

image

2、docker部署

2.1、上传文件到 linux 服务器

在 linux 下创建文件夹 BlogCoreAPI

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">mkdir BlogCoreAPI //创建文件夹</pre>

用 ftp 把发布的文件全部上传到 BlogCoreAPI

2.2、添加 Dockerfile 文件

进入目录 BlogCoreAPI

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">cd BlogCoreAPI</pre>

创建文件 Dockerfile

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">vim Dockerfile</pre>

内容如下,说明:镜像里创建 blogcore 文件夹,复制 linux 上的文件到镜像目录,对外暴露 8101 端口,运行程序

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">FROM microsoft/aspnetcore:2.2 RUN cd /usr/local/src

RUN mkdir blogcore

WORKDIR /usr/local/src/blogcore

COPY . ./ EXPOSE 8101 CMD ["dotnet", "Blog.Core.dll"]</pre>

保存

2.3、构建镜像

镜像名字叫 blogcore-image,禁用缓存,否则迭代时会出问题

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">docker build --no-cache -t cchr-image . //注意这个点 . 表示当前目录</pre>

2.4、运行容器

用镜像 blogcore-image 创建容器,指定容器名称是 blogcore-container,指定时区(默认是美国时间,相差8小时)

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">docker run --name=blogcore-container -dp 8101:8101--restart=always -e LC_ALL="en_US.UTF-8" -e TZ="Asia/Beijing" blogcore-image //修改默认时区到亚洲/北京,并且将容器里的 8101 端口映射到 linux 主机的 8101 端口,最后每次运行失败后自动重启</pre>

如果你想要查看刚刚创建的容器的时区是否成功,可以执行命令:

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">docker exec blogcore-container date //查询当前容器时区</pre>

打开浏览器访问试试:http://yourdomian.com/swagger/index.html

2.5、发布更新

上面说的是第一次发布,后期迭代,需要把容器删除,再构建镜像,运行容器。

2.5.1 删除容器

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">docker container rm -f cchr-container</pre>

2.5.2 构建镜像&运行容器

就和上边的一样了:

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">docker build --no-cache -t cchr-image .
docker run --name=blogcore-container -dp 8101:8101--restart=always -e LC_ALL="en_US.UTF-8" -e TZ="Asia/Beijing" blogcore-image </pre>

我还不知道有没有更快的方法,欢迎提建议。

7、PM2 做进程管理

我看到了很多教程,但是这里我尝试的时候依然不行,这里先留一个坑,提供有偿帮忙,如果有哪位会的话,可以联系我,或者下边留言 💴。

二、NetCore 错误

下边的都是我平时遇到的常见的错误,如果大家还有其他错误,可以留言下来,我补充到文章中,让更多的小伙伴都看到,避免麻烦。

1、错误码 —— 500.00

image

解析:发布后启动失败,进程内,网友提供,暂时我还没遇到

方案:

方法1:

应用池高级配置 - 启动32位应用程序 - 设置为True

方法2:

web.config

<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" startupTimeLimit="3600" requestTimeout="23:00:00" hostingModel="InProcess">

删除 hostingModel="InProcess"

2、错误码 —— 500.19

image
image

解析:未安装 windows 服务器托管,打不开当前项目的 模块 选项

方案

安装和项目匹配的 服务器托管( windows hosting ),这个是捆绑的可执行文件,除了托管,也把x86 和 x64的运行时都一起安装好了。

地址:https://dotnet.microsoft.com/download/thank-you/dotnet-runtime-2.2.4-windows-hosting-bundle-installer

image
image

3、错误码 —— 500.21

image

解析:没有安装指定的模块,如果你想问这个模块是在项目哪里写的,可以查看发布后的 web.config 文件

image

方案

1、如果你没有安装上边的 windows-hosting ,直接安装就可以解决这个问题;

2、如果已经安装了服务器托管,发现还没有这个 V2 模块,就是你项目的问题了,这个时候你可以从本地再发布 publish 一次,然后拷贝到服务器即可。

 但是,如果还没有的话,证明你本地开发的项目异常了,不过这个情况基本可以排除,只要是 SDK 2.2 开发的,本地

最终要确保 AspNetCoreModuleV2 模块被安装。

image

4、错误码 —— 500.30

image

解析:进程内失败,这个我没有遇到,有群里小伙伴说的。

方案:

image

5、错误码 —— 502.5

image

解析:没有安装指定的运行时,进程外启动失败。

方案

安装指定版本的 Runtime 运行时。

一般情况下,只要安装了服务器托管的话,会自动安装对应版本的运行时,但是有时候安装好了,还是会遇到这个错误,是因为我们后期项目迭代升级了:

举个栗子,本来我们的项目是 Netcore 2.1 版本,后来更新到了 2.2 ,但是服务器的运行时还是旧的 Runtime 2.1 运行时,我们把代码部署到服务器,发现没有指定运行时,就会出现这个问题。

下载地址:https://dotnet.microsoft.com/download/visual-studio-sdks?utm_source=getdotnetsdk&utm_medium=referral

image

6、错误码 —— 503

image

解析:IIS 项目应用程序池未开启

方案:开启程序池即可,

但是经过测试发现,不使用 “无托管代码” 也可以,这是为什么,很好奇?

image

7、An error occurred while starting the application

image

解析:项目已经可以启动,证明环境已经正常,只不过在项目启动的时候,出现了错误,也就是说是项目代码的问题。

方案:查看日志,看项目文件是否齐全。

如果你不知道如何打开日志,请看另一篇文章的这一节:https://www.cnblogs.com/laozhang-is-phi/p/9565227.html#autoid-5-5-0

下边这个错误,就是因为分离的原因,导致 Service.dll 和 Repository.dll 没有拷贝到发布文件夹下,从而导致项目没有启动起来。

image

8、总结

经过上边多个错误分析,如果我们第一次再服务器中部署,只需要安装 服务器托管 Windows-hosting 即可:

https://dotnet.microsoft.com/download/dotnet-core/2.2

image

三、Vue 错误

1、跨域失败

这都是很简单的错误,根据上边的动图,咱们也能看出来,关于跨域有以下几个方法:

1、IIS 部署,一定要在后端 NetCore 项目,进行 CORS 跨域;

2、除非你是在 IIS 中的同一个站点配置前后端两个项目;

3、如果是 nginx+kestrel 的话,记得在 nginx 中配置跨域代理;

4、其他方案,略;

2、页面刷新 404

为了这个错误,我上边的第一个动图,还特地的故意做了这个错误,就是 history 和 hash 的情况,当然也是有两个方案:

1、如果你是 IIS 部署,就得使用 hash 模式,不能用 history 模式;

2、不过如果用 nginx 的话,可以利用 404 页面的机制,将 index.html 页面 copy 一份,重命名成 404.html 即可;

四、结语

这篇文章会一直补充,尽量成为前后端分离的一个优质文章吧,希望大家能尽自己的力量,补充下。

前后端分离到这里就差不多了,江湖很大,各位有缘再会!

image

五、Github & Gitee

https://github.com/anjoy8/Blog.Core

https://gitee.com/laozhangIsPhi/Blog.Core


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

推荐阅读更多精彩内容