[部署03] gitlab-CI

导航

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数

[react] Hooks

[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend

[源码-vue06] Vue.nextTick 和 vm.$nextTick

前置知识

一些单词

CI - Continuous Integration 持续集成

integration:集成,结合
reference:参考
convert:转换,转变
consider:考虑

prior:事先,先前
familiarize:熟悉
( prior to getting started在开始之前 )
( You may want to familiarize yourself with these prior to getting started 您可能需要在开始之前熟悉这些内容 )

pipeline:管道
explain:解释,说明
brief:简要,短时间

indent:缩进
attention:注意
( you have to pay extra attention to indentation 您必须特别注意缩进 )

independently:独立的 ( indent:缩进 )
instantly:立刻
necessary:必要

Specific:具体的
fundamental:基本的
( Jobs are the most fundamental element of a .gitlab-ci.yml file. job是.gitlab-ci.yml文件的最基本元素。)

reserved:保留的
( Using reserved keywords 使用保留关键字 )

stuck:卡住
artifacts:工件

ssh key pair:密钥对
recursive:递归

Linux 一些常用命令


- which
    - which命令的作用是,在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。
    - 在找到第一个符合条件的程序文件时,就立刻停止搜索,省略其余未搜索目录。
    - 也就是说,使用which命令,就可以看到某个系统命令是否存在,以及执行的到底是哪一个位置的命令。 

- whereis name
    - 找出文件的路径 

- scp 
    - scp [可选参数] file_source file_target 
    - -r: 递归复制整个目录 ( recursive:递归的意思 )
    1. 从本地复制到远程
    scp local_file remote_ip:remote_folder
    scp local_file remote_username@remote_ip:remote_folder  指定了用户名
    
- ssh-keyscan
    - 浏览该系统上的 ssh 共有密钥

- ssh -T git@gitlab.com
    - 验证gitlab上是否正确的添加了 ssh key
    
    
uname -a -------------------------------- 查看服务器环境
// 比如:Linux VM_0_16_centos 3.10.0-862.el7.x86_64 #1 SMP Fri Apr 20 16:44:24 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux


mkdir -p -------------------------------- 递归创建目录文件夹
mkdir -p a/b 创建a/b这样关系的两个文件夹


su 用户名称 ----------------------------- 切换用户名称
su root 切换root用户
su gitlab-runner 切换到gitlab-root账户
su是 ( switch user ) 的缩写


whoami ---------------------------------- 查看当前登录用户名
su gitlab-runner
whoami // gitlab-runner


scp ------------------------------------- 用户复制文件和目录
scp 是 secure copy 的缩写 - 安全复制
scp [可选参数] file_source file_target 
-r: 递归复制整个目录 ( recursive:递归的意思 )
scp -r ${DIST_DIR} root@49.233.215.163:/root/d/

<font color=blue>Pipeline - 管道</font>

一次 Pipeline 其实相当于一次构建任务,里面可以包含多个流程,如安装依赖、运行测试、编译、部署测试服务器、部署生产服务器等流程。 任何提交或者 Merge Request 的合并都可以触发 Pipeline

  • <font color=red>一次pipeline相当于一次 ( 构建任务 ),包含多个 ( 流程 )</font>
  • <font color=red>任何 ( push提交 ) 或者 ( merge request的合并 ) 都可以 ( 触发pipeline )</font>
+------------------+           +----------------+
|                  |  trigger  |                |
|   Commit / MR    +---------->+    Pipeline    |
|                  |           |                |
+------------------+           +----------------+

<font color=blue>Stages - 构建阶段</font>

Stages 表示构建阶段,就是上面的流程。一次 Pipeline 中可以定义多个 Stages

  • 顺序:<font color=red>所有 Stages 会按照顺序运行,即当一个 Stage 完成后,下一个 Stage 才会开始</font>
  • 成功:<font color=red>只有当所有 Stages 完成后,该构建任务 (Pipeline) 才会成功</font>
  • 失败:<font color=red>如果任何一个 Stage 失败,那么后面的 Stages 不会执行,该构建任务 (Pipeline) 失败</font>
  • 总结:
    • 一个pipeline包含多个stage
    • 顺序执行,上一个stage执行完,才能执行下一个stage
    • 所有stage成功,pipeline才成功;只要有一个stage失败,整个pipeline就会失败
+--------------------------------------------------------+
|                                                        |
|  Pipeline                                              |
|                                                        |
|  +-----------+     +------------+      +------------+  |
|  |  Stage 1  |---->|   Stage 2  |----->|   Stage 3  |  |
|  +-----------+     +------------+      +------------+  |
|                                                        |
+--------------------------------------------------------+

<font color=blue>Jobs - 构建的工作</font>

Jobs 表示构建工作,表示某个 Stage 里面执行的工作。 我们可以在 Stages 里面定义多个 Jobs

  • 顺序:<font color=red>相同 Stage 中的 Jobs 会 ( 并行 ) 执行</font>
  • 成功:<font color=red>相同 Stage 中的 Jobs 都执行成功时,该 Stage 才会成功</font>
  • 失败:<font color=red>如果任何一个 Job 失败,那么该 Stage 失败,即该构建任务 (Pipeline) 失败</font>
  • 总结:
    • 一个 ( Pipeline ) 包含多个 ( Stage ),一个 ( Stage ) 包含多个 ( Job )
    • ( Stage ) 中的 ( Job ) 是 ( 并行 ) 执行的 --------------------- job并行
    • ( Job ) 都成功,则 ( Stage ) 成功
    • 一个 ( Job失 ) 败,则 ( Stage ) 失败,则 ( Pipeline ) 失败
+------------------------------------------+
|                                          |
|  Stage 1                                 |
|                                          |
|  +---------+  +---------+  +---------+   |
|  |  Job 1  |  |  Job 2  |  |  Job 3  |   |
|  +---------+  +---------+  +---------+   |
|                                          |
+------------------------------------------+

<font color=blue>YAML语言基础</font>

  • <font color=red>yaml 是专门用来写配置文件的语言,需要注意的就是缩进</font>
  • # 表示注释,从这个字符一直到行尾,都会被解析器忽略。
  • 数据结构有三种:对象数组纯量
(1) 纯量 - 单个的、不可再分的值
- 字符串 布尔值 整数 浮点数 Null 时间 日期
- null:用~表示
    - parent: ~ 
    - 转化为js ----------------------------- { parent: null }
- 字符串
    - 如果字符串之中包含 ( 空格 ) 或 ( 特殊字符 ),需要放在 ( 引号 ) 之中
    - str: '内容: 字符串'
    - 转化为js ----------------------------- { parent: null }
    - 单引号和双引号都可以使用,双引号不会对特殊字符转义
    - 多行字符串可以使用 ( | ) 保留换行符,也可以使用 ( > ) 折叠换行。
    - ( + ) 表示保留文字块末尾的换行,( - ) 表示删除字符串末尾的换行。
- 引用
    - ( & ) 锚点
    - ( * ) 别名,注意:* 是用来引用的 -------------------------------- 即*号表示&代表的内容
    - ( & ) 用来建立锚点(defaults),( << ) 表示合并到当前数据,( * ) 用来引用锚点。
defaults: &defaults --------------------------------------------------- &
  adapter:  postgres
  host:     localhost
development:
  database: myapp_development
  <<: *defaults ------------------------------------------------------- *
test:
  database: myapp_test
  <<: *defaults
等同于
defaults:
  adapter:  postgres
  host:     localhost
development:
  database: myapp_development
  adapter:  postgres ------------------------------------------------- * 表示 & 代替的内容
  host:     localhost
test:
  database: myapp_test
  adapter:  postgres
  host:     localhost
  
  
  
(2) 对象
animal: pets
转化为js ----------------------------------- { animal: 'pets' }



(3) 数组 - 一组连词线开头的行,构成一个数组
- Cat
- Dog
- Goldfish
转化为js ------------------------------------ [ 'Cat', 'Dog', 'Goldfish' ]
-
 - Cat
 - Dog
 - Goldfish
转化为js ----------------------------------- [ [ 'Cat', 'Dog', 'Goldfish' ] ]



(4) 复合结构
languages:
 - Ruby
 - Perl
 - Python 
websites:
 YAML: yaml.org 
 Ruby: ruby-lang.org 
 Python: python.org 
 Perl: use.perl.org 
转化为js
{ languages: [ 'Ruby', 'Perl', 'Python' ],
  websites: 
   { YAML: 'yaml.org',
     Ruby: 'ruby-lang.org',
     Python: 'python.org',
     Perl: 'use.perl.org' 
   } 
}

<font color=blue>RPM包</font>

  • .rpm 文件
  • <font color=red>命名规则:<name>-<version>-<release>.<arch>.rpm</font>
    • name:表示包的名称,包括主包名和分包名
    • version:表示包的版本信息
    • release:用于标识rpm包本身的发行号,可还包含适应的操作系统
    • <font color=red>arch</font>: 表示主机平台,noarch表示此包能安装到任何平台上,和架构无关

<font color=blue>sshpass</font>

  • sshpass是一个简单的,轻量级的命令行工具。
  • 安装:yum install -y sshpass
  • 安装:apt-get install -y sshpass
  • 常用操作如下
1. 连接本地主机
sshpass -p xxxx ssh xxx@xxx
sshpass -p {密码} ssh {用户名}@{主机ip}

2. 连接远程主机
sshpass -p {密码} ssh -p {端口号} {用户名}@{主机ip}

3. 读取文件中的密码连接远程主机
sshpass -f {密码文本文件} ssh {用户名}@{主机IP} 

4. 从远程主机拷贝取目录到本地 !!!
sshpass -p {密码} scp {用户名}@{主机}:{远程目录} {本地目录或者文件}

5. 从本地拷贝到远程 !!!
sshpass -p {密码} scp {本地目录} {用户名}@{主机}:{远程目录}

6. 连接远程主机并执行命令 !!!
sshpass -p {密码} ssh -o StrictHostKeyChecking=no {用户名}@{主机IP} 要执行的命令     
// -o StrictHostKeyChecking=no :忽略密码提示
// 如'rm -rf /tmp/test

<font color=blue>yum(centos) 和 apt-get(ubuntu) 的区别</font>

  • yum 和 apt-get 都是包管理工具,yum主要用于centos,apt-get主要用于ubuntu
Linux系统基本分为两大类:
1、Redhat系列:Redhat、Centos、Fedora等
2、Debian系列:Debian、Ubuntu等

 
Redhat系列
1、常见的安装包格式为:rpm包,安装rpm包的命令是:rpm-参数
2、包的管理工具:yum
3、支持tar包

 
Debian系列
1、常见的安装包格式为:deb包,安装deb包的命令是:dpkg-参数
2、包的管理工具:apt-get
3、支持tar包

建立ci持续集成只需要两个步骤

  • (一) 在项目更目录下新建:<font color=red>.gitlab-ci.yml</font> 文件
  • (二) 安装 <font color=red>gitlab runner</font> 在服务器上
  • 注意:gitlab runner的执行器 Executor 本文中使用的是 <font color=red>dokcer</font>

(一) .gitlab-ci.yml

  • 在代码被push到gitlab仓库后或者当merge-requset时,gitlab会去解析.gitlab-cli.yml文件,调用相应的runner来执行pipeline中stage中的job
  • 验证.gitlab-ci.yml是否有语法错误,使用 <font color=red>CI Lint</font>
  • .gitlab-ci.yml文件官网文档
  • 参数关键字如下表格
关键字 描述
script 可以用runner执行的Shell脚本,runner需要在服务器中安装gitlab runner
stages 定义pipeline中的stage,( 即定义构建阶段 )是一个数组 ------------------- 所有的stage
stage 一个job的流程,默认test -------------------------------------------------- 具体的stage
variables 定义一个变量
image 使用docker映像。也可用:image:name和image:entrypoint
tags 通过标签管理或匹配runner,即该job用哪个runner去执行
cache 在后续运行之间应缓存的文件列表。也可用:cache:paths,cache:key,cache:untracked和cache:policy。
only 指定当前job适用的git refs(分支、Tag)列表 ----------- 分支,变量,change等
except 除了git的哪些分支,其他都使用该job ----------------- 分支,变量,change等
<font color=red>artifacts</font> 将这个job生成的依赖传递给下一个job
用于在不同 stage 之间传递结果,通用的做法是将 build 阶段打包出来的文件定义为 artifacts,这样在 deploy 阶段就可以直接使用了
<font color=blue>
expire_in</font>: artifacets 的过期时间,因为这些数据都是保存在 Gitlab 机器上的,过于久远的资源就可以删除掉了
<font color=blue>paths</font>:路径是相对于项目目录($ CI_PROJECT_DIR)的,不能直接在其外部链接。 可以使用遵循通配符模式和filepath.Match的通配符。
# image: docker 镜像 这里选择node镜像
image: node

# 定义变量
variables: 
  CENTOS_IP: 'root@49.233.215.163'

# 缓存
cash:
  path:
    - node_modules/

# statges: 构建的所有阶段,一个数组 states
# state: 也可以在具体的每个job中使用 stage
stages:
  - echo
  - build
  - deploy

echo-statge:
  stage: echo
  tags:
    - dockernoderunner
  only:
    - master
  script:
    echo ${CI_COMMIT_SHA}
    echo ${CI_PROJECT_NAME}
    echo ${CI_PROJECT_NAMESPACE}
    echo ${CI_PROJECT_PATH}
    echo ${CI_PROJECT_URL}
    echo ${GITLAB_USER_NAME}
    echo ${GITLAB_USER_EMAIL}
    echo ${CI_PROJECT_DIR}
    echo ${CI_PIPELINE_ID}
    echo ${CI_COMMIT_REF_NAME}

# build-stage: 具体的每个job的名称,可以随便取,是一个对象
build-stage:
  # stage:需要和stages中的数组中的值对应
  stage: build
  # tags:要和注册runner时填写的tag对应,用来针对不同的job,执行不同的runner
  tags:
    - dockernoderunner
  script:
    - npm install --registry=https://registry.npm.taobao.org
    - npm run build --registry=https://registry.npm.taobao.org
  # only:这里表示只在master分支上 push或者merge等生效
  only:
    - master
  # artifacts:工件,表示将这个job传递给下一个job,这里传递dist文件夹
  artifacts:
    # expire_in:过期时间
    expire_in: 1 week
    paths:
      # 可以在 setting -> CI/CD -> Variables 中设置,提高安全性
      # - ${DIST_DIR} 这样写也是可以的
      - $DIST_DIR

deploy-stage:
    stage: deploy
    only:
      - master
    tags:
      - dockernoderunner
    before_script:
      # 在执行script 需要设置ssh免密登陆
      # 还要创建 SSH_PRIVATE_KEY 变量
      - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
      - eval $(ssh-agent -s)
      - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
      - mkdir -p ~/.ssh
      - chmod 700 ~/.ssh
      - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
      - chmod 644 ~/.ssh/known_hosts
      ##
      ## You can optionally disable host key checking. Be aware that by adding that
      ## you are susceptible to man-in-the-middle attacks.
      ## WARNING: Use this only with the Docker executor, if you use it with shell
      ## you will overwrite your user's SSH config.
      ##
      # - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
      # - ssh -p22  root@49.233.215.163
    script:
      # - ssh -p22  root@49.233.215.163
      # - sshpase -p ${PASSWORD} ssh -p 22 root@49.233.215.163
      # - yum -y install sshpass
      - apt-get update
      - apt-get install -y sshpass
      - sshpass -p ${PASSWORD} scp -r ${DIST_DIR} root@49.233.215.163:/root/d/
      # [ssh key](https://docs.gitlab.com/ee/ci/ssh_keys/README.html)
      # - 当您的CI / CD 的 jobs 在Docker容器中运行(意味着环境已包含在内)并且您想要在私有服务器中部署代码时,您需要一种访问它的方法。这是SSH key对派上用场的地方。 
      # [Generating a new SSH key pair](https://docs.gitlab.com/ee/ssh/README.html#generating-a-new-ssh-key-pair) 在设置ssh key之前要生成 ssh key pair 密钥对

      # 拷贝文件到服务器后,就可以用 nginx 容器做数据卷映射,从而启动最新代码的服务

(二) gitlab runner

(1) 在( CentOS Linux ) 上下载和安装 ( gitlab runner )

  1. 下载gitlab runner的 rpm 包 - 官网教程
  2. 安装 gitlab runner 安装教程
  3. 注册 gitlab runner 注册教程
  4. 注意:这里runner的执行器选择的是 docker - image是node
下载


1. 下载gitlab runner的 rmp 包

// curl -LJO https://gitlab-runner-downloads.s3.amazonaws.com/latest/rpm/gitlab-runner_<arch>.rpm

curl -LJO https://gitlab-runner-downloads.s3.amazonaws.com/latest/rpm/gitlab-runner_amd64.rpm

  // curl:命令是一个利用URL规则在命令行下工作的文件传输工具,它支持文件的上传和下载
  // -L/--location:跟踪重定向
  // -J:
  // -O/--remote-name:把输出写到该文件中,保留远程文件的文件名
  // <arch>:表示主机平台
  // 注意:如果下载很慢,window平台可以在本地下载好后使用xshell和xftp直接托到服务器上
  // 注意:我这里用的是腾讯云服务器

安装


2. 安装 gitlab runner的 rmp 包
rpm -i gitlab-runner_amd64.rpm


安装报错:
error: Failed dependencies: git is needed by gitlab-runner-12.8.0-1.x86_64


解决办法:加上 --nodeps --force
rpm -i gitlab-runner_amd64.rpm --nodeps --force
// 如果没有git,请安装
注册


注册Runner是将Runner与GitLab实例绑定的过程


3. 在注册gitlab runner前需要做两件事情
(1) 安装Doker,并启动
- 安装docker
- docker pull node
- docker run -id --name=node_c1 node
(2) 在gitlab项目中获取token
- gitlab项目 -> setting -> CI/CD -> Runners



4. 注册 Gitlab Runner
gitlab-runner register
这里可以使用交互式,也可以使用非交互式

1. Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
https://gitlab.com/
2. Please enter the gitlab-ci token for this runner
_GAFfSTQcz57k-hWzowr
3. Please enter the gitlab-ci description for this runner
runner-test
4. Please enter the gitlab-ci tags for this runner (comma separated):
tag-test
5. Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
docker
6. Please enter the Docker image (eg. ruby:2.6):
node
image
image
image
image
image
image
  • 当添加 deploy key 时 报错:
  • Fingerprint has already been taken, Deploy keys projects deploy key fingerprint has already been taken
    image
image
  • gitlab-runner is not in the sudoers file.This incident will be reported.的解决方法


    image

资料

名词解释 https://juejin.im/post/6844903637320368135
一个简单的 .gitlab-ci.yml文件配置 https://segmentfault.com/a/1190000016483568
部署一个简单的vue项目 https://www.jianshu.com/p/4bbef46a2aaa
.rpm文件扫盲 https://www.jianshu.com/p/27ebb82d25c7
gitlab runner安装教程 https://www.jianshu.com/p/1c1ecd3ce7c0
gitlab runner安装的具体步骤:https://juejin.im/post/6844903747747856391
curl命令:https://www.jianshu.com/p/07c4dddae43a
.gitlab-ci.yml关键字 https://blog.didiyun.com/index.php/2018/11/14/gitlab-cicd/
.gitlab-ci.yml关键字详解 https://cloud.tencent.com/developer/article/1376224

【可用的yml配置文】https://blog.csdn.net/BalaBalaYi/article/details/82497517
【可用的yml配置文件】https://juejin.im/post/6844903952543449102
【】https://juejin.im/post/6844903608731828232#heading-0
【可用的yml配置文件】https://juejin.im/post/6844903869739171848
【可用的yml配置文件】https://segmentfault.com/a/1190000021092024
【】https://www.jqhtml.com/50142.html

sshpass常用命令 http://linux.51yip.com/search/sshpass

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

推荐阅读更多精彩内容