Node项目线上部署与发布

1. Linux命令

1.1 常用命令

  1. 显示文件或目录
    ls(选项)(参数)
    -l 表示long,长格式列出
    -a 表示all,包括隐藏文件
  2. 创建目录
    mkdir 目录
  3. 打印文件内容
    cat 文件
  4. 拷贝文件
    cp 原文件 新文件
  5. 移动或重命名
    mv 原文件 新文件
  6. 删除文件
    rm 文件
    注释:rm -rf 目录 强制递归删除目录。
  7. 查看当前路径
    pwd
  8. 删除空目录
    rmdir 目录
  9. 查找文件/目录
    find 文件/目录
  10. 创建空文件
    touch 文件

1.2 打包压缩

  1. 语法
    tar (选项) (参数)
    选项:-c 归档文件,-x 解压,-z gzip压缩文件,-j bzip2压缩文件,-v 显示压缩或解压缩过程 v(view)-f 使用档案名字(这个参数是最后一个参数,后面只能接档案名)
  2. 用法
    (1) 压缩
    tar -cvf /home/abc.tar /home/abc 只打包,不压缩
    tar -zcvf /home/abc.tar.gz /home/abc 打包,并用gzip压缩
    tar -jcvf /home/abc.tar.bz2 /home/abc 打包,并用bzip2压缩
    (2) 解压
    替换上面的命令tar -cvf / tar -zcvf / tar -jcvf 中的c换成x

1.3 vim使用

  1. vim有三种模式:命令模式、插入模式、编辑模式。
    使用ESCi:来切换模式。
  2. 命令模式下
    (1) vi 文件名 编辑或创建文件
    (2) dd 删除当前行
    (3) ndd 删除n
    (4) yy 复制当前行
    (5) nyy 复制n
    (6) p 粘贴
    (7) :q 退出
    (8) :q! 强制退出
    (9) :wq 保存并退出

1.4 用户及用户组

  1. 创建用户
    adduser 用户名
  2. 将用户添加到指定用户组
    gpasswd -a nmwei sudo

1.5 文件权限

  1. 三种基本权限
    (1)r 读 数值表示为4
    (2)w 写 数值表示为2
    (3)x 可执行 数值表示为1
  2. 权限查看
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/workspace$ ls -l
total 12
drwxrwxr-x 3 nmwei nmwei 4096 Jul 21 12:00 imoocmall
drwxrwxr-x 2 nmwei nmwei 4096 Jul 21 21:04 website
drwxrwxr-x 2 nmwei nmwei 4096 Jul 22 09:56 weixinapp
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/workspace$ 
  1. 权限分析
    drwxrwxr-x一共十个字符,分成四段。
    (1) 第1个字符d表示目录,-表示普通文件,l表示链接。
    (2) 第2-4个字符rwx表示当前所属用户的权限,用数值表示为4+2+1=7
    (3) 第5-7个字符rwx表示当前所属群组的权限,用数值表示为4+2+1=7
    (4) 第8-10个字符r-x表示其他用户的权限,用数值表示为4+1=5
    (5) 操作此文件的权限用数值表示为775
  2. 权限更改
    sudo chmod 765 filename

1.6 系统、软硬件及开发工具

  1. 以系统管理员(root)权限执行命令
    sudo + 命令
  2. 查看某端口对应进程PID
    lsof -i:3000
  3. 结束PID对应的某进程
    kill -9 PID
  4. 重启ssh
    sudo service ssh restart
  5. 启动、重新启动或关闭mongodb
    service mongod start/restart/stop
  6. 更新软件包列表
    apt-get update
  7. 更新已安装的软件包
    apt-get upgrade
    注释:执行apt-get upgrade命令之前应该先执行apt-get update
  8. 安装nginx
    apt-get install nginx
  9. 查看数据盘
    fdisk -l
  10. 查看磁盘使用情况
    df -h

2. 项目部署

部署之路

2.1 选购域名服务器及备案

  1. 域名提供厂商
    爱名网、GoDaddy阿里云
    注释:阿里云域名注册
  2. 服务器提供厂商
    (1) 国外:亚马逊AWSLinodeDigitOcean/HeroKu
    (2) 国内:阿里云ECS、青云/UCloud/百度云
  3. 阿里云服务器
    (1) 服务器ECS选购
    ① 操作系统选择:Ubuntu 14.04 64位
    ② 自定义密码(root):Aa*********
    ③ 安全组:类似防火墙功能,用于设置网络访问控制。
    注释:阿里云 - 云服务器ECS - 立即购买 - 一键购买 - 推荐配置 - 入门型 - 立即购买
    (2) 云服务器ECS查看
    注释:使用阿里云遇到问题时,可以通过提交工单进行提问。
  4. 域名备案
    导航栏 - 备案 - 备案服务号申请/备案服务号管理
    注释:阿里云ECS服务器可备案几个网站

2.2 远程登录服务器

2.2.1 root超级管理员权限登录

  1. 第一次root登录
    命令行输入:ssh root@47.96.***.***
    密码:Aa*********
    注释:①SSH是每一台Linux电脑的标准配置(SSH原理与运用)。 ② 退出ssh登录命令为control + d
    注意:① root是超级管理员用户,使用root权限操作服务器有一定的安全隐患。② 增强安全配置后的登录方式ssh -p 39999 nmwei@47.96.***.***
  2. 查看硬盘信息
    sudo fdisk -l
Disk /dev/vda: 42.9 GB, 42949672960 bytes
255 heads, 63 sectors/track, 5221 cylinders, total 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00040f1a

   Device Boot      Start         End      Blocks   Id  System
/dev/vda1   *        2048    83884031    41940992   83  Linux
  1. 查看硬盘使用信息
    df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            484M  4.0K  484M   1% /dev
tmpfs           100M  360K   99M   1% /run
/dev/vda1        40G  3.1G   35G   9% /
none            4.0K     0  4.0K   0% /sys/fs/cgroup
none            5.0M     0  5.0M   0% /run/lock
none            497M     0  497M   0% /run/shm
none            100M     0  100M   0% /run/user

2.2.2 创建用户及配置权限

  1. 创建新用户
    添加新用户: adduser nmwei
    输入密码: Nmw*********
  2. 给新用户添加权限
    (1) 将nmwei用户添加到sudo分组
    gpasswd -a nmwei sudo
    (2) 设置权限
    ① 打开配置文件
    sudo visudo
    ② 修改配置文件
    # User privilege specification
    root ALL=(ALL:ALL) ALL
    nmwei ALL=(ALL:ALL) ALL
    注意:配置文件中新添加内容(nmwei ALL=(ALL:ALL) ALL)表示nmwei用户可以以sudo + 命令的方式调用root权限执行所有命令。
    ③ 保存配置文件
    退出:control + X
    保存:shift + Y
    确认:return(回车)
    (3) 重启ssh
    sudo service ssh restart
  3. 使用新用户登录
    登录:ssh nmwei@47.96.***.***
    密码:Nmw*********
    注释:① 登录成功之后,默认所在路径为/home/nmwei。② ~对应路径即为/home/nmwei

2.2.3 本地无密码SSH登录

  1. SSH无密码登录介绍
    (1) 操作过程:本地生成私钥和公钥,公钥添加到服务器
    (2) 登录原理:本地私钥 → 本地添加到服务器的公钥 → 秘钥算法比对 → 登录成功
    注意:如果本地之前已经生成过秘钥,则不用重新生成。否则会覆盖当前秘钥,导致无法连接之前比对成功的远程服务器git仓库。
  2. 本地生成秘钥
    (1) 查看是否已有秘钥
➜  ~ pwd   #查看根路径
/Users/nimengwei
➜  ~ ls -a   #查看根目录下所有文件(夹)
➜  ~ cd .ssh   #进入.ssh文件夹 
➜  .ssh ls   #查看.ssh文件夹下所有文件(夹)
id_rsa      id_rsa.pub  known_hosts

注释:①.ssh目录位置为/Users/nimengwei/.ssh。 ②当前已经有.ssh目录以及id_rsa(私钥)、 id_rsa.pub(公钥)文件,则表示本地已经生成过秘钥。
(2) 生成秘钥
如果本地没有生成过秘钥,则需要生成秘钥。

➜  ~ ssh-keygen -t rsa -b 4096 -C "254060001@qq.com"  #连续回车(不需要输入密码),则创建成功

(3) 查看秘钥内容
查看私钥内容:cat id_rsa
查看公钥内容:cat id_rsa.pub
(4) 加入代理
运行ssh-agent代理:eval "$(ssh-agent -s)”
加入代理:ssh-add ~/.ssh/id_rsa

  1. 服务器生成秘钥
    登录服务器:ssh nmwei@47.96.***.***
    查看有没有.ssh文件:ls -a
    生成秘钥:ssh-keygen -t rsa -b 4096 -C "254060001@qq.com"
    运行ssh-agent代理:eval "$(ssh-agent -s)"
    加入代理:ssh-add ~/.ssh/id_rsa
  2. 添加本地公钥到服务器认证
    (1) 复制本地公钥
➜  ~ cd .ssh
➜  .ssh ls  
id_rsa      id_rsa.pub  known_hosts
➜  .ssh cat id_rsa.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQ... 254060001@qq.com
➜  .ssh 

(2) 添加本地公钥
① 创建/打开authorized_keys文件

nmwei@iZbp1ai95owp4twsjdkbv1Z:~/.ssh$ vi authorized_keys

② 切换到编辑模式

ESC

③ 粘贴本地公钥

ssh-rsa AAAAB3NzaC1yc2EAAAAD... 254060001@qq.com

④ 保存并退出

shift + :
wq!

(3) 授权并重启服务器

nmwei@iZbp1ai95owp4twsjdkbv1Z:~/.ssh$ chmod 600 authorized_keys
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/.ssh$ sudo service ssh restart
  1. ssh无密码登录
➜  ~ ssh nmwei@47.96.***.***
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ 
  1. ssh认证的其它使用场景
    GithubBitbucket以及Gitee等代码托管平台设置本地SSH公钥(id_rsa.pub),就可以使用SSH认证进行克隆上传代码,否则只能使用Https访问。
    例如GithubSettings - SSH and GPG keys - New SSH key

2.3 增强服务器安全等级

2.3.1 修改服务器默认登录端口

  1. Liunx服务器默认登录端口为22,使用默认登录端口有一定的安全隐患。可将服务器默认端口修改为其他端口。
  2. 打开配置文件
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ sudo vi /etc/ssh/sshd_config
  1. 编辑配置文件
# What ports, IPs and protocols we listen for
# Port 22  #禁用默认端口22
Port 39999  #添加一个39999登录端口
.....
AllowUsers root nmwei #服务器只允许root和nmwei用户登录

注意:① 修改sshd_config配置文件之前,应新开一个终端并登录到服务器,避免配置文件修改失败之后无法登录。② 1-1024之间的端口被系统占用,不要使用。最大端口号可设置为65536。③ 可以设置多个登录端口,即设置多行 Port ****

  1. 重启ssh
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ sudo service ssh restart
  1. 配置阿里云ECS安全组规则
    ECS安全组配置
  2. 验证ssh登录接口
➜  ~ ssh nmwei@47.96.***.***            
ssh: connect to host 47.96.***.*** port 22: Connection refused
➜  ~ ssh -p 39999 nmwei@47.96.***.***
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ 

注释:①22默认端口无法登录,只能通过39999登录端口。 ②此时服务器有ssh -p 39999 root@47.96.***.***ssh -p 39999 nmwei@47.96.***.***两种登录方式。

2.3.2 关闭服务器root登录权限

  1. 阿里云服务超级管理员用户名都为root,有一定的安全隐患。可将root超级管理员登录权限关闭。
  2. 打开配置文件
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ sudo vi /etc/ssh/sshd_config
  1. 编辑配置文件
PermitRootLogin no #不允许root登录
PasswordAuthentication yes #允许使用密码登录
AllowUsers nmwei #只允许nmwei用户登录
  1. 重启ssh
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ sudo service ssh restart
  1. 验证root登录
➜  ~ ssh -p 39999 root@47.96.***.***
root@47.96.***.***'s password: 
Permission denied, please try again.
➜  ~ ssh -p 39999 nmwei@47.96.***.***
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ 

注释:①root超级管理员无法登录,只有nmwei用户可以登录。②当前服务器只有ssh -p 39999 nmwei@47.96.***.***一种登录方式。

2.3.3 配置 iptables 和 Fail2Ban

2.4 搭建 Nodejs 生产环境

2.4.1 搭建Node环境

  1. 更新软件包列表
    sudo apt-get update
  2. 安装linux软件包文件
    sudo apt-get install vim openssl build-essential libssl-dev wget curl git
  3. 安装Nodejs
    ① 安装nvm
    wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
    注释:nvm被安装到了~/.nvm目录。
    ② 使用nvm安装Nodejs
    nvm install v8.11.3
    ③ 使用该Nodejs版本
    nvm use v8.11.3
    ④ 将该Nodejs版本设置为默认版本
    nvm alias default v8.11.3
    注释:使用nvm安装的node目录为~/.nvm/versions/node。例如,v8.11.3版本node安装目录为~/.nvm/versions/node/v8.11.3/bin,全局安装的node_modules目录为~/.nvm/versions/node/v8.11.3/lib
    参考:nvm
  4. 安装npm(指定国内镜像源)
    npm --registry=https://registry.npm.taobao.org install -g npm
    注释:① 临时使用淘宝镜像源安装npm --registry https://registry.npm.taobao.org install express。② 持久使用淘宝镜像源npm config set registry https://registry.npm.taobao.org
  5. 安装cnpm
    npm install -g cnpm
  6. 修改系统文件监督数目
    echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
  7. 安装npm包文件
    npm i pm2 webpack gulp grunt-cli -g

2.4.2 PM2使用介绍

  1. 开启一个服务
    pm2 start app.js
  2. 关闭一个服务
    pm2 stop app.js
  3. 删除一个服务
    pm2 delete app.js
  4. 查看服务列表
    pm2 list
  5. 展示一个服务信息
    pm2 show app
    注释:可以查看到服务对应的文件地址(script path)以及日志地址(log path)。
  6. 展示所有服务日志
    pm2 logs
  7. 清除所有日志
    pm2 flush

2.5 配置 Nginx 实现反向代理

  1. 安装Nginx
    (1) 更新包列表
    sudo apt-get update
    (2) 安装nginx
    sudo apt-get install nginx
    (3) 查看nginx版本
    nginx -v
  2. nginx配置代理转发
    (1) 创建/打开nginx代理配置文件
nmwei@iZbp1ai95owp4twsjdkbv1Z:/$ cd /etc/nginx/conf.d
nmwei@iZbp1ai95owp4twsjdkbv1Z:/etc/nginx/conf.d$ sudo vi  www-nimengwei-com-3000.conf

注释:nginx安装目录为/etc/nginx。② nginx配置文件目录为/etc/nginx/conf.d
(2) 编辑nginx代理配置文件

upstream website {
  server 127.0.0.1:3000;
}

server {
 listen 80;
 server_name 47.96.***.***;

 location / {
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
   proxy_set_header Host $http_host;
   proxy_set_header X-Nginx-Proxy true;
   proxy_pass http://website;
   proxy_redirect off;
  }
}

注释:该nginx代理配置文件的含义为当用户访问47.96.***.***时,转发到本机的http:127.0.0.1:3000
(3) nginx测试及重启
测试nginx配置文件是否正确:sudo nginx -t
nginx重启:sudo nginx -s reload
注释:nginx主页路径为/usr/share/nginx/html

  1. 返回头隐藏nginx版本信息
    (1) 通过nginx代理的服务会返回nginx版本信息
    响应头Response HeadersServer字段值为nginx/1.4.6 (Ubuntu)
    (2) 编辑nginx配置文件
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ cd /etc/nginx
nmwei@iZbp1ai95owp4twsjdkbv1Z:/etc/nginx$ ls
conf.d          mime.types           nginx.conf       sites-enabled
fastcgi_params  naxsi_core.rules     proxy_params     uwsgi_params
koi-utf         naxsi.rules          scgi_params      win-utf
koi-win         naxsi-ui.conf.1.4.1  sites-available
nmwei@iZbp1ai95owp4twsjdkbv1Z:/etc/nginx$ sudo vi nginx.conf

(3) 取消server_tokens字段注释

server_tokens off;

(4) nginx重启
sudo nginx -s reload
注释:此时响应头Response HeadersServer字段值为nginx

2.6 管理域名解析

2.6.1 域名DNS服务器

  1. 域名解析需要一个DNS服务器
    DNS服务器可以将域名匹配到对应的IP地址。
    一个域名只能够对应一个IP地址,但一个IP地址可以对应多个域名。
  2. 阿里云提供了云解析DNS服务
    当前分域名配的DNS服务器是:dns30.hichina.com, dns29.hichina.com
  3. 如何修改阿里云购买域名的DNS服务器
    登录阿里云 - 控制台 - 域名 - 管理 -修改DNS
    参考:DNSPod官网 - 常见问题 - 功能介绍及使用教程 - 各个注册商修改域名DNS地址的方法 - 万网注册商域名修改DNS地址

2.6.2 域名解析

  1. 主机记录
主机记录 含义
www 解析后的域名为 www.nimengwei.com
@ 直接解析主域名 nimengwei.com
* 泛解析,匹配其他所有域名 *.nimengwei.com
  1. 记录类型
记录类型 使用场景
A记录 地址记录,用来指定域名的IPv4地址(如:8.8.8.8),如果需要将域名指向一个IP地址,就需要添加A记录。
CNAME 如果需要将域名指向另一个域名,再由另一个域名提供ip地址,就需要添加CNAME记录。
TXT 在这里可以填写任何东西,长度限制255。绝大多数的TXT记录是用来做SPF记录(反垃圾邮件)。
NS 域名服务器记录,如果需要把子域名交给其他DNS服务商解析,就需要添加NS记录。
AAAA 用来指定主机名(或域名)对应的IPv6地址(例如:ff06:0:0:0:0:0:0:c3)记录 。
MX 如果需要设置邮箱,让邮箱能收到邮件,就需要添加MX记录。
显性URL 从一个地址301重定向到另一个地址的时候,就需要添加显性URL记录(注:DNSPod目前只支持301重定向)。
隐性URL 类似于显性URL,区别在于隐性URL不会改变地址栏中的域名。
SRV 记录了哪台计算机提供了哪个服务。格式为:服务的名字、点、协议的类型,例如:_xmpp-server._tcp
  1. 设置DNS解析实现域名访问七牛云资源

(1) 操作七牛云
进入七牛云 - 资源主页 - 对象存储 - 新建/选择存储空间 - 融合CDN加速域名 - 立即绑定一个域名 - 加速域名(weixin.cloud.nimengwei.com)
进入七牛云 - 资源主页 - 融合CDN -域名管理 - 复制CNAME记录值
(2) 操作阿里云 -
登录阿里云 - 控制台 - 域名 - 解析
添加记录 - 选择CNAME - 填写主机记录(weixin.cloud) - 粘贴记录值 - 添加

DNS解析七牛云资源

(3) 测试
进入七牛云 - 资源主页 - 对象存储 - 选择存储空间 - 内容管理 - 选择一张图片 - 复制外链 - 打开外链 - 将url中七牛默认外链域名pdrecbudd.bkt.clouddn.com替换为weixin.cloud.nimengwei.com - 成功访问

2.7 服务器配置安装 MongoDB

2.7.1 Ubuntu 14.04系统安装 MongoDB

  1. 必应搜索install mongodb on ubuntu
    安装文档:Install MongoDB Community Edition on Ubuntu
    注释:社区版本(Community)是免费的,企业版本(Enterprise)是收费的。
  2. 安装MongoDB

(1) Import the public key used by the package management system.

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4

(2) Create a list file for MongoDB.

echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list

(3) Reload local package database.

sudo apt-get update

(4) Install the MongoDB packages.

sudo apt-get install -y mongodb-org
  1. 操作MongoDB

(1) 启动MongoDB
sudo service mongod start
(2) 重新启动MongoDB
sudo service mongod restart
(3) 关闭MongoDB
sudo service mongod stop
(4) 修改MongoDB默认端口
编辑文件配置文件:sudo vi /etc/mongod.conf

net:
  port: 19999

(5) 连接MongoDB:
mongo --port 19999

2.7.2 MongoDB单表及数据库的导入导出

  1. 上传数据库
    (1) 本地数据库备份
    mongodump -h 127.0.0.1:27017 -d dumall(数据库名字) -o dumall-backup(生成备份文件夹名字)
    注释:授权启动的MongoDB导出数据库的命令为:mongodump -h 127.0.0.1:27017 -d dumall(数据库名字) -u dumall_manager(数据库用户名) -p dumall*********(数据库用户密码) -o dumall-backup(生成备份文件夹名字)。与未授权启动相比,新增用户名密码参数:-u 数据库用户名 -p 数据库用户密码
    (2) 上传数据库备份
    方法一:命令行上传
    本地打包压缩dumall-backup文件夹
    tar zcvf dumall.tar.gz(要生成的压缩包名字) dumall-backup(要打包的源文件夹)
    本地上传压缩包
    scp -P 39999 ./dumall.tar.gz nmwei@47.96.***.***:/home/nmwei/dbbackup/dumall-backup(上传到服务器的目录)
    服务器端解压缩包
    tar xvf dumall.tar.gz
    方法二:客户端上传
    使用FileZillia客户端将dumall-backup文件夹上传到服务器
    (3) 在服务器端导入dumall-backup数据库
    mongorestore -h 127.0.0.1:19999 -d dumall(生成的数据库名称) ./dbbackup/dumall-backup/dumall/(数据库表文件夹)
    注释:向授权启动的MongoDB导入数据库,则新增用户名密码参数:-u 数据库用户名 -p 数据库用户密码
  2. 上传数据库表
    (1) 本地数据库表导出
    方法一:MongoHub导出
    方法二:本地命令行导出
    mongoexport -d(指定数据库) demo -c(指定表名称) user -q(过滤条件,可以省略) '{"name": {$ne: null}}' -o(导出文件名) ./user.json
    注释:授权启动的MongoDB导出数据库表,则新增用户名密码参数:-u 数据库用户名 -p 数据库用户密码
    (2) 本地上传数据库表
    方法一:命令行上传
    scp -P 39999 ./user.json(本地文件) nmwei@47.96.***.***:/home/nmwei/dbbackup/dumall-backup/(上传到服务器的目录)
    注释:从服务器下载文件命令为:scp -P 39999 nmwei@47.96.***.***:/home/nmwei/dbbackup/dumall-backup/user.json(服务器文件) ./(本地文件目录)
    方法二:客户端上传
    使用FileZillia客户端将user.json上传到服务器。
    (3) 在服务器端导入user.json
    mongoimport -h 127.0.0.1:19999 -d(指定数据库) demo -c(指定表名称) user ./dbbackup/dumall-backup/user.json
    注释:向授权启动的MongoDB导入数据表,新增用户名密码参数:-u 数据库用户名 -p 数据库用户密码

2.7.3 线上MongoDB配置读写权限

  1. 创建用户
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ mongo --port 19999
> use admin
> db.createUser({user:'userAdmin', pwd:'userAdmin*********', roles:["userAdminAnyDatabase"]})
> db.createUser({user:"dbAdmin", pwd:"dbAdmin*********",roles:["readWriteAnyDatabase","dbAdminAnyDatabase","clusterAdmin"]})
> use dumall
> db.createUser({user:'dumall', pwd:'dumall*********', roles:[{role:'readWrite', db: 'dumall'}]})
> use admin
> db.system.users.find().count()
3

注释:① 创建dumall用户需要先使用use dumall命令进入dumall数据库。② userAdmin管理员(["userAdminAnyDatabase"]),可在admin中创建用户。dbAdmin管理员(["readWriteAnyDatabase","dbAdminAnyDatabase","clusterAdmin"]),拥有增删改查任意数据库、复制、集群操作的权限。

  1. 认证登录
    (1) 打开配置文件
    sudo vi /etc/mongod.conf
    (2) 编辑配置文件 
    security: #打开认证
     authorization: enabled
    注意:不但要添加authorization: enabled,还要取消security注释。
    (3) 重启数据库
    sudo service mongod restart

2.7.4 数据库定时备份

  1. 定时备份数据库到服务器
    (1) 创建备份文件夹、任务文件夹以及备份任务文件
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ ls
workspace
//创建备份文件夹
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ mkdir backup
//创建任务文件夹
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ mkdir tasks
//创建db备份文件夹
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ cd ~/backup
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/backup$ mkdir db
//创建dumall数据库备份文件夹
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/backup$ cd db
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/backup/db$ mkdir dumall
//创建dumall数据库备份任务脚本
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/backup/db$ cd ~/tasks
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/tasks$ vi dumalldb.backup.sh

注释:目录结构为~/backup/db/dumall~/tasks/dumalldb.backup.sh
(2) 编辑dumall数据库备份任务脚本

#!/bin/sh
backUpFolder=/home/nmwei/backup/db/dumall
date_now=`date +%Y%m%d%H%M`
backFileName=dumall_$date_now
cd $backUpFolder
mkdir -p $backFileName
mongodump -h 127.0.0.1:19999 -d dumall -u dumall -p dumall********* -o $backFileName
tar zcvf $backFileName.tar.gz $backFileName
rm -rf $backFileName

(3) 执行dumall数据库备份任务脚本

nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ sh ./tasks/dumalldb.backup.sh 

注释:生成~/backup/db/dumall/dumall_201808232214.tar.gz备份文件。
(4) 进入系统定时任务

nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ crontab -e

(5) 编辑系统定时任务

# m h  dom mon dow   command
30 22 23 * * sh /home/nmwei/tasks/dumalldb.backup.sh

(6) 退出系统定时任务
Control + x
Shift + y
Enter 回车
注释:m表示分钟,h表示小时,dom表示日期,mon表示月份,dow表示星期,command表示命令。

  1. 定时上传数据库备份到七牛云

(1) 创建上传七牛云js文件

nmwei@iZbp1ai95owp4twsjdkbv1Z:~/tasks$ vi dumalldb.upload.js

(2) 编辑上传七牛云js文件

var qiniu = require('qiniu');
var parts = process.env.NODE_ENV.split('@');
var file = parts[1] + '.tar.gz';
var filePath = parts[0] + '/' + file;
//上传凭证
var accessKey = 'ihsp5CZpo_-nPnTefUS_dXeWLd7tEOSEmOba9IlD';
var secretKey = 'w-VB9Q2wjeaLnq5rQ4sHEjiuqXNxrlR88azhKFos';
//创建鉴定对象mac
var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
//上传位置
var bucket = 'dbbackup'; //要上传的空间
var key = file; //要生成的文件名
var options = {
  scope: bucket + ':' + key
};
//生成uploadToken
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken=putPolicy.uploadToken(mac);
var config = new qiniu.conf.Config();
var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
// 文件上传
formUploader.putFile(uploadToken, key, filePath, putExtra, function(respErr,
  respBody, respInfo) {
  if (respErr) {
    throw respErr;
  }
  if (respInfo.statusCode == 200) {
    console.log(respBody);
  } else {
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

参考:七牛云 - 开发者中心 -SDK -官方SDK - 对象存储 - Node.js (服务端) - 文件上传
(3) 打开dumall数据库备份任务脚本

nmwei@iZbp1ai95owp4twsjdkbv1Z:~/tasks$ vi dumalldb.backup.sh

(4) 编辑dumall数据库备份任务脚本
结尾添加以下脚本:

NODE_ENV=$backUpFolder@$backFileName node /home/nmwei/tasks/dumalldb.upload.js

(5) tasks目录下安装qiniu模块

nmwei@iZbp1ai95owp4twsjdkbv1Z:~/tasks$ npm install qiniu

注释:npm install -g安装的npm包可以在命令行使用,而不是可以在任意项目中使用。
(6) 执行dumall数据库备份任务脚本

nmwei@iZbp1ai95owp4twsjdkbv1Z:~/tasks$ sh ./dumalldb.backup.sh 

问题:如果执行sudo sh ./dumalldb.backup.sh,则报错node: not found
注意:为提高安全性,每一个数据库应单独创建数据库备份、上传脚本以及定时任务。

此时,执行sh ~/tasks/dumalldb.backup.sh脚本,在~/backup/db/dumall目录下生成备份文件的同时,在七牛对象存储的dbbackup存储空间上传备份文件。

2.8 部署和发布上线 Nodejs 项目

2.8.1 本地代码上传到服务器

  1. 代码上传到服务器的两种方法
    方法一:本地通过FileZilla工具直接上传文件到服务器
    方法二: 本地 - 私有Git仓库 - 服务器
  2. 本地通过FileZilla工具直接上传文件到服务器
    (1) 使用FileZilla工具连接阿里云ECS
    文件 - 站点管理器 - 新站点
    (2) 配置站点
    FileZilla工具截图

    注释:端口不填,则表示默认22端口。密码为Nmw*********
    (3) 文件上传
  3. 本地 - 私有Git仓库 - 服务器
    (1) 码云配置本地公钥以及服务器公钥
➜  ~ cat .ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCp4QvDRnHi8EHcrftrv+I8BNXSuejAXkEWoJ6l/BCF7uuaCSEFBKVp6wfWAYEvQ/jCIIZFpXtRlnzpHVQmJTperdL83gs6OsSo......
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/.ssh$ cat id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC5mpJLl8XbH/5KorYLu3L0PPXwxVVpvxD1wKaGc6DmWT9yoK2xY5slnzZmaNeO2fxGzPiY6nhmoOn2uE4aQmdckEczbgDdQJdjuFWxBGtmxVYKL2a1Hl+zo93t59+h6/abGspFqANLVrde0vzvrtOtNgoSuhMMuhy6cpnVpUK8kdK/aM06++r5eF4RcRjDfnK3CdCH......

(2) 码云创建私有仓库并上传本地项目代码
注释:码云提供了免费的私有git仓库。
(3) 服务器克隆码云私有仓库代码

nmwei@iZbp1ai95owp4twsjdkbv1Z:~/workspace$ git clone git@gitee.com:nmwei/imoocmall.git
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/workspace$ cd imoocmall
nmwei@iZbp1ai95owp4twsjdkbv1Z:~/workspace/imoocmall$ npm install

2.8.2 配置PM2一键部署项目

  1. PM2不仅可以守护服务器Nodejs服务,实现平滑重启。也可以实现代码的自动更新,以及从本地到线上的部署。
    参考:PM2官网
  2. 本地项目中根目录创建ecosystem.json
{
  "apps" : [
    {
      "name" : "myweb",
      "script" : "app.js",
      "env": {
        "COMMON_VARIABLE": "true"
      },
      "env_production": {
        "NODE_ENV": "production"
      }
  }],
  "deploy" : {
    "production" : {
      "user" : "nmwei",
      "host" : ["47.96.***.***"],
      "port": "39999",
      "ref"  : "origin/master",
      "repo" : "git@gitee.com:nmwei/myweb.git",
      "path" : "/www/myweb/production",
      "ssh_options": "StrictHostKeyChecking=no",
      "env": {
        "NODE_ENV": "production"
      }
    }
  }
}

参考:PM2官网/Documentation/Deployment/SimpleDeploy
通过Github与PM2部署Node应用
注释:因为pm2的部署是通过ssh进行的,因此需要开通本地到远程服务器的无密码登录。

  1. 本地向git仓库中上传ecosystem.json文件
  2. 服务器创建项目文件夹
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ cd /www
nmwei@iZbp1ai95owp4twsjdkbv1Z:/www$ mkdir myweb
nmwei@iZbp1ai95owp4twsjdkbv1Z:/www$ ls
myweb  ssl

注释:创建myweb文件夹即可,无需创建子文件夹production

  1. 本地部署项目
➜  myweb git:(master) ✗ pm2 deploy ecosystem.json production setup

注释:如果本地部署命令失败,则需要在服务器修改文件夹权限:nmwei@iZbp1ai95owp4twsjdkbv1Z:/www$ sudo chmod 777 myweb
原理:本地利用PM2工具通知服务器向指定git仓库拉取代码。

  1. 部署项目目录介绍
nmwei@iZbp1ai95owp4twsjdkbv1Z:/www/myweb/production$ ls
current  shared  source
nmwei@iZbp1ai95owp4twsjdkbv1Z:/www/myweb/production$ cd current
nmwei@iZbp1ai95owp4twsjdkbv1Z:/www/myweb/production/current$ ls
app.js  README.md

(1) current: 运行文件。
(2) shared: 日志文件等。
(3) source: 源文件。

  1. 修改服务器.bashrc配置文件

(1) 编辑.bashrc文件

nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ vi .bashrc

(2) 修改.bashrc文件,注释以下代码

# If not running interactively, don't do anything
#case $- in
#    *i*) ;;
#      *) return;;
#esac

(3) 重新运行.bashrc文件

nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ source .bashrc
  1. 本地更新服务器代码并重启服务
➜  myweb git:(master) pm2 deploy ecosystem.json production 

注释:此时浏览器访问47.96.***.***:3000,则可以访问该服务。

  1. 添加域名解析
    (1) 进入阿里云域名解析页面
    登录阿里云 - 控制台 - 域名 - 解析
    (2) 添加一条域名解析记录


    主页域名解析记录
  2. 配置Nginx代理
    (1) 创建并编辑配置文件
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ cd /etc/nginx/conf.d
nmwei@iZbp1ai95owp4twsjdkbv1Z:/etc/nginx/conf.d$ sudo vi www-nimengwei-com-3000.conf

(2) 配置文件内容如下

upstream website {
  server 127.0.0.1:3000;
}

server {
 listen 80;
 server_name www.nimengwei.com 47.96.***.***;

 location / {
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
   proxy_set_header Host $http_host;
   proxy_set_header X-Nginx-Proxy true;
   proxy_pass http://website;
   proxy_redirect off;
  }
}

注释:该nginx代理配置文件的含义为当用户访问www.nimengwei.com47.96.***.***时,转发到本机的http:127.0.0.1:3000
(3) nginx测试及重启
测试nginx配置文件是否正确:sudo nginx -t
nginx重启:sudo nginx -s reload

  1. 配置阿里云ECS安全组规则
    允许3000端口被访问。
  2. 此时当用户通过www.nimengwei.com47.96.***.***路径,即可访问47.96.***.***:3000线上项目。
  3. 如果需要更新线上项目,则需要以下步骤:
    (1) 本地修改项目代码
    (2) 代码修改提交到码云git私有仓库
    (3) 本地通知服务器更新git仓库代码
➜  myweb git:(master) pm2 deploy ecosystem.json production 

2.8.3 PM2一键部署imoocmall项目

  1. 配置阿里云ECS安全组规则
    允许3001端口被访问。
  2. 服务器创建项目文件夹
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ cd /www
nmwei@iZbp1ai95owp4twsjdkbv1Z:/www$ mkdir imoocmall
nmwei@iZbp1ai95owp4twsjdkbv1Z:/www$ ls
imoocmall  myweb  ssl
  1. 本地项目根目录创建ecosystem.json并上传到git仓库
{
  "apps" : [
    {
      "name" : "imoocmall",
      "script" : "bin/www",
      "env": {
        "COMMON_VARIABLE": "true"
      },
      "env_production": {
        "NODE_ENV": "production"
      }
  }],
  "deploy" : {
    "production" : {
      "user" : "nmwei",
      "host" : ["47.96.237.151"],
      "port": "39999",
      "ref"  : "origin/master",
      "repo" : "git@gitee.com:nmwei/imoocmall.git",
      "path" : "/www/imoocmall/production",
      "ssh_options": "StrictHostKeyChecking=no",
      "post-deploy": "npm install && pm2 startOrRestart ecosystem.json --env production",
      "env": {
        "NODE_ENV": "production"
      }
    }
  }
}

注释:与myweb项目相比,添加了post-deploy部署后需要执行的命令。
注意:如果修改了ecosystem.json,不能只通过pm2 deploy ecosystem.json production命令更新,需要在服务器PM2上删除该项目(pm2 delete imoocmallc)后重新部署。

  1. 本地部署上传项目

(1) 项目部署

imoocmall git:(master) pm2 deploy ecosystem.json production setup

(2) 项目更新

imoocmall git:(master) pm2 deploy ecosystem.json production

注释:此时浏览器访问47.96.***.***:3001,则可以访问该服务。

  1. 服务器添加域名解析记录


    imooc二级域名解析记录
  2. 服务器配置nginx代理
    (1) 创建并编辑配置文件
nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ cd /etc/nginx/conf.d
nmwei@iZbp1ai95owp4twsjdkbv1Z:/etc/nginx/conf.d$ sudo vi imooc-nimengwei-com-3001.conf 

(2) 配置文件内容如下

upstream imooc {
  server 127.0.0.1:3001;
}

server {
 listen 80;
 server_name imooc.nimengwei.com;
 location / {
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
   proxy_set_header Host $http_host;
   proxy_set_header X-Nginx-Proxy true;
   proxy_pass http://imooc;
   proxy_redirect off;
 }
}

(3) nginx测试及重启
测试nginx配置文件是否正确:sudo nginx -t
nginx重启:sudo nginx -s reload

  1. 分析与总结
    (1) 分析
    由于imooc.nimengwei.com二级域名的解析记录值为47.96.***.***,所以用户访问imooc.nimengwei.com 地址时,相当于访问47.96.***.***IP地址。由于47.96.***.***对应的服务器配置了nginx代理,所以用户访问imooc.nimengwei.com时,代理转发到3001端口。
    (2) 总结
    此时当用户通过imooc.nimengwei.com路径,即可访问47.96.***.***:3001线上项目。

2.8.4 前端资源部署

  1. 如何将前端资源部署到后端项目,当访问后端'/'路由时,返回前端页面。
    方法一:利用 Express 托管静态文件
    方法二:利用Express路由返回静态文件
  2. 利用 Express 托管静态文件
//app.js
app.use(express.static(path.join(__dirname, 'views')));
  1. 利用Express路由返回静态文件
// app.js
var indexRouter = require('./routes/index');
...
app.set('views', path.join(__dirname, 'views'));
app.engine('.html', ejs.__express);
app.set('view engine', 'html');
...
`app.use('/', indexRouter);`
// routes/index.js
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});
module.exports = router;

注意:不要与全局拦截发生冲突。

2.9 配置 HTTPS 协议

2.9.1 SSL证书介绍

  1. Chrome浏览器会将http协议的网站标记为不安全,苹果也要求所有应用必须采用https的加密连接。在国内,微信小程序要求开发者必须使用https的接入域名。
  2. SSL证书分为如下种类
    (1) 扩展验证型(EVSSL证书
    (2) 组织验证型(OVSSL证书
    (3) 域名验证型(DVSSL证书
    三类证书安全等级依次降低。
  3. 申明SSL证书的平台
    又拍云、腾讯云、七牛、阿里云。
  4. 申请SSL证书的条件
    域名已经通过备案。

2.9.2 申请免费证书并配置Nginx

  1. 腾讯云申请SSL证书

(1) 进入证书申请页面
腾讯云 - 控制台 - 云产品 - 域名与网站 -SSL证书管理 - 申请证书
(2) 申请证书
填写证书信息 - 手动DNS验证 - 确认申请

填写证书信息

(3) 添加DNS解析记录
查看证书详情 - 添加DNS解析记录
DNS解析记录

(4) 下载证书并解压缩
(5) 将Nginx文件夹中的.key.crt文件上传到服务器

nmwei@iZbp1ai95owp4twsjdkbv1Z:~$ cd /www/ssl
nmwei@iZbp1ai95owp4twsjdkbv1Z:/www/ssl$ ls
1_blog.nimengwei.com_bundle.crt 
2_blog.nimengwei.com.key

(6) 配置nginx代理

nmwei@iZbp1ai95owp4twsjdkbv1Z:/etc/nginx/conf.d$ sudo vi blog-nimengwei-com-3003.conf 
upstream blog {
  server 127.0.0.1:3003;
}
server {
 listen 80;
 server_name blog.nimengwei.com;
 return 301 https://blog.nimengwei.com$request_uri;
}

server {
 listen 443;
 server_name blog.nimengwei.com; #填写绑定证书的域名
 ssl on;
 ssl_certificate /www/ssl/1_blog.nimengwei.com_bundle.crt;
 ssl_certificate_key /www/ssl/2_blog.nimengwei.com.key;
 ssl_session_timeout 5m;
 ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置
 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照这个套件配置
 ssl_prefer_server_ciphers on;

 if ($ssl_protocol = "") {
  rewrite ^(.*) https://$host$1 permanent;
 } 

 location / {
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
   proxy_set_header Host $http_host;
   proxy_set_header X-Nginx-Proxy true;
   proxy_pass http://blog;
   proxy_redirect off;
 }
}
nmwei@iZbp1ai95owp4twsjdkbv1Z:/etc/nginx/conf.d$ sudo nginx -t
nmwei@iZbp1ai95owp4twsjdkbv1Z:/etc/nginx/conf.d$ sudo nginx -s reload

参考:证书管理 - 详情 - 指引文档 - Nginx 证书部署
注释:亚洲诚信品牌免费型DVSSL证书仅支持绑定一个一级域名或者子域名,且同一主域最多只能申请20张证书(一级域名及其子域名均属于同一主域)。 若您的业务因此次调整受限,建议您购买泛域名型SSL证书。
注意:不同平台上申请的SSL证书的协议配置(ssl_protocols)和套件配置(ssl_ciphers)不同。

  1. 阿里云申请SSL证书
    阿里云 - 控制台 - 产品与服务 - 安全云盾 - SSL证书
    购买证书 - Symantec - 免费型DV SSL - 立即购买
    注释:其他步骤按照提示以及阿里云SSL配置进行操作。

参考资料

Node.js项目线上服务器部署与发布
阿里云ECS服务器文档
Linux命令大全
Linux中常用操作命令
MongDB的安装和基本操作(权限设置)

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

推荐阅读更多精彩内容

  • 2017.1.25 周四 雪 如果时间会有感情,我想和ta共一个故事。 如上次下雪时一般,后窗外的竹枝,尽裹冰霜...
    逆风有杨阅读 208评论 0 0
  • ln是一个很神奇的命令,它可以创建一个文件的影子,也可以通过一个通道进入另一个地方。:)其实,所有的这些把戏都是通...
    shuff1e阅读 212评论 0 0
  • 滴!加油卡
    渐行渐远_41cf阅读 156评论 0 2