持续集成工具: Jenkins学习

持续集成工具: Jenkins学习

-- 部分内容收集自网络,如有侵权,请联系作者删除

一. 概念

  • 在过去的开发整体流程中,是所有人写好代码之后统一进行合并(svn,git),然后进行测试,确保准发布的版本无误后再进行版本的正式发布。在这种流程下,往常会把风险堆到软件发布前的最后阶段,在整体测试的环节下出现许多不可预知的问题。那持续的概念就是,做一部分就马上递交给下一个流程,这样一个持续化的过程能够尽早地发现并解决问题,避免把问题都暴露在一个环节上。
  1. 持续部署


    1530005183252.png
    • 装修厨房
      • 全部装好之后发现灯不亮,电路有问题,冷热水装反了,管路有问题。这些问题要解决就必须把地砖、墙砖拆掉,一个环节有问题,其他环节跟着返工。
      • 解决方案:
        • 任何安装完成后及时进行测试,确保其可以正常工作。
    • 项目开发
      • 开发过程中,本地进行测试能够通过,但是部署到服务器上运行出现问题
      • 解决方案:
        • 仅仅单元测试还不够,各个模块都必须能够在服务器上运行
    • 关注点
      • 持续部署的关注点在于项目功能部署至服务器后可以运行,为下一步测试环节或最终用户正式使用做好准备
  2. 持续集成


    1530005162855.png
    • 装修厨房
      • 装修厨房时我们需要铺地砖,如果把所有地砖都切好再拿去铺就会发现:每一块地砖单独看都是好的,但是实际铺的时候,把所有地砖整合起来,发现和厨房 地面总体尺寸不匹配,边边角角的地砖需要重新切,时间和物料成本陡然升高。
      • 解决方案:
        • 切一块铺一块,根据需要的尺寸来切,尽早发现尺寸变化,避免返工。
    • 项目开发
      • 各个小组分别负责各个具体模块开发,本模块独立测试虽然能够通过,但是上线前夕将所有模块整合到一起集成测试却发现很多问题,想要解决就需要把很多代码返工重写而且仍然有可能有问题,但现在时间很可能不够了。
      • 解决方案:
        • 经常性、频繁的把所有模块集成在一起进行测试,有问题尽早发现,这就是持 续集成。
    • 关注点
      • 持续集成的关注点在于尽早发现项目整体运行问题,尽早解决。
  3. 持续交付


    1530005211213.png
    • 装修厨房
      • 全部装修好之后房屋主人来验收,各项功能都正常,但是水龙头的样式主人不 喜欢,灶台的位置主人不满意,要求返工。
      • 解决方案:
        • 房屋主人随时查看装修进度,施工团队及时调整。
    • 项目开发
      • 项目的各个升级版本之间间隔时间太长,对用户反馈感知迟钝,无法精确改善 用户体验,用户流失严重。
      • 解决方案:
        • 用小版本不断进行快速迭代,不断收集用户反馈信息,用最快的速度改进优化。
    • 关注点
      • 持续交付的关注点在于研发团队的最新代码能够尽快让最终用户体验到
  • 优缺点
    • 降低风险
      • 一天中进行多次的集成,并做了相应的测试,这样有利于检查缺陷,了解软件的健康状况,减少假定。
    • 减少重复过程
      • 产生重复过程有两个方面的原因,一个是编译、测试、打包、部署等等固定操作都必须要做,无法省略任何一个环节
      • 一个缺陷如果没有及时发现,有可能导致后续代码的开发方向是错误的,要修复问题需要重新编写受影响的所有代码。而使用 Jenkins 等持续集成工具既可以把构建环节从手动完成转换为自动化完成,又可以通过增加集成频次尽早发现缺陷避免方向性错误。
    • 任何时间、任何地点生成可部署的软件
      • 持续集成可以让您在任何时间发布可以部署的软件。从外界来看,这是持续集成最明显的好处,我们可以对改进软件品质和减少风险说起来滔滔不绝,但对于客户来说,可以部署的软件产品是最实际的资产。利用持续集成,您可以经常对源代码进行一些小改动,并将这些改动和其他的代码进行集成。如果出现问题,项目成员马上就会被通知到,问题会第一时间被修复。不采用持续集成的情况下,这些问题有可能到交付前的集成测试的时候才发现,有可能会导致延迟发布产品,而在急于修复这些缺陷的时候又有可能引入新的缺陷,最终可能导致项目失败。
    • 增强项目的可见性
      • 持续集成让我们能够注意到趋势并进行有效的决策。如果没有真实或最新的数据提供支持,项目就会遇到麻烦,每个人都会提出他最好的猜测。通常,项目成员通过手工收集这些信息,增加了负担,也很耗时。
      • 持续集成可以带来两点积极效果:
        • 有效决策:持续集成系统为项目构建状态和品质指标提供了及时的信息,有些持续集成系统可以报告功能完成度和缺陷率。
        • 注意到趋势:由于经常集成,我们可以看到一些趋势,如构建成功或失败、总体品质以及其它的项目信息。
    • 建立团队对开发产品的信心
      • 持续集成可以建立开发团队对开发产品的信心,因为他们清楚的知道每一次构建的结果,他们知道他们对软件的改动造成了哪些影响,结果怎么样。

二. 持续集成工具

1.1 持续集成工具

  • Jenkins 和 Hudson

    • 目前最流行的一款持续集成及自动化部署工具。

      • Jenkins 和 Hundson 之间的关系:2009 年,甲骨文收购了 Sun 并继承了 Hudson 代码库。在 2011 年年初,甲骨文和开源社区之间的关系破裂,该项目被分成两个独立的项目:
      • Jenkins:由大部分原始开发人员组成
      • Hudson:由甲骨文公司继续管理

      所以 Jenkins 和 Hudson 是两款非常相似的产品。

1.2 技术组合

  • Jenkins 其本身上没有整合太多的功能,只是提供了一个持续集成的平台,它是通过大量的插件,实现了一系列的持续化集成的工作
    • 整合 GitHub 或 Subversion实现版本的控制
    • 通过构建工具(ANT,MAVEN,GRADLE)等进行项目的部署
    • 通过与sonarqube的结合,实现自动化测试
    • 通过与shell脚本的结合,实现了部署,增量包等一系列的功能

三. 部署方式的对比

  • 传统的部署方式
1530020348129.png
  • 自动化的部署方式


    1530020387978.png
  • 搭建上述持续集成环境可以把整个构建、部署过程,自动化测试等过程进行集成,实现自动化操作。在很大程度上减轻了开发人员或者维护人员的工作量,对于程序员的日常开发来说不会造成任何额外负担

四. 环境搭建

  • 使用的相关软件版本
    • centos 7
    • JDK1.8
    • Tomcat8
    • Jenkins 2.107.2
    • maven 3
    • gradle 3.5
  • Jenkins的架设对于一个JAVAEE程序员来说,与在linux上部署项目并无太大的差异,主要分为以下几个环节

4.1 JDK的安装

  1. 上传JDK安装包到服务器登录用户的home目录下
1530023524258.png
  1. 解压JDK到/usr/local/java

    1530023759049.png

    执行命令

    #查看本机上安装的java
    rpm -qa | grep java
        tzdata-java-2015g-1.el7.noarch
        javapackages-tools-3.4.1-11.el7.noarch
        java-1.8.0-openjdk-headless-1.8.0.65-3.b17.el7.x86_64
        java-1.8.0-openjdk-1.8.0.65-3.b17.el7.x86_64
        python-javapackages-3.4.1-11.el7.noarch
    #卸载所有相关的java软件
    rpm -e --nodeps java-1.8.0-openjdk-headless-1.8.0.65-3.b17.el7.x86_64
    rpm -e --nodeps java-1.8.0-openjdk-1.8.0.65-3.b17.el7.x86_64
    #创建java目录
    mkdir /usr/local/java
    #解压jdk压缩包文件到java目录当中
    tar -zvxf jdk-8u161-linux-x64.tar.gz -C /usr/local/java
    
  2. 配置环境变量

    vim /etc/profile
    
        #set java environment
        JAVA_HOME=/usr/local/java/jdk1.8.0_161
        CLASSPATH=.:$JAVA_HOME/lib.tools.jar
        PATH=$JAVA_HOME/bin:$PATH
        export JAVA_HOME CLASSPATH PATH
    
    #重新加载配置文件:
    source /etc/profile
    
  3. 测试安装是否成功

    java -version
    
1530024218219.png

* 显示以上信息表示安装JDK成功

4.2 Tomcat的安装

  1. 上传tomcat的安装文件
1530024604426.png
  1. 创建tomcat的安装路径并解压

    #创建tomcat目录,由于一台机器上往后可能需要多个服务器,所以分开存放tomcat,避免冲突
    mkdir -p /usr/local/tomcat/jenkins
    #解压tomcat压缩包文件到/usr/local/tomcat/jenkins目录当中
    tar -zvxf apache-tomcat-8.0.51.tar.gz -C /usr/local/tomcat/jenkins
    
  2. 配置tomcat

    • 通过查看tomca的catalina.sh文件中我们可以看到,tomca建议我们对tomcat的个性化配置通过在bin目录下新建一个setenv.sh进行参数上的配置

      1530026334596.png
    vim setenv.sh
    #########setenv.sh文件信息###############
        #!/bin/sh
        CATALINA_HOME=/usr/local/tomcat/jenkins/apache-tomcat-8.0.51
        JAVA_HOME=/usr/local/java/jdk1.8.0_161
    ########################################
    chmod 744 setenv.sh
    
    • 配置/usr/local/tomcat/jenkins/apache-tomcat-8.0.51/conf目录下的server.xml配置文件

      • 一共修改以下三处的端口
      <Server port="1313" shutdown="SHUTDOWN">
          
       <Connector port="1314" protocol="HTTP/1.1"
                     connectionTimeout="20000"
                     redirectPort="8443" />
          
      <Connector port="1315" protocol="AJP/1.3" redirectPort="8443" />
      
  3. 设置防火墙

    #配置防火墙,开放1314的端口
    /sbin/iptables -I INPUT -p tcp --dport 1314 -j ACCEPT
    
  4. 启动tomcat

    #进入到
    cd /usr/local/tomcat/jenkins/apache-tomcat-8.0.51/bin
    #执行启动脚本
    ./startup.sh
    
    1530027265046.png
    • 访问地址成功表示安装完成

4.3 maven的安装

  1. 上传安装包到服务器

    1530030405670.png
  2. 创建安装目录并且解压到指定的目录下

    mkdir /usr/local/maven
    tar -zvxf apache-maven-3.5.4-bin.tar.gz -C /usr/local/maven/
    
  3. 配置环境变量

    vim /etc/profile
        MAVEN_HOME=/usr/local/maven/apache-maven-3.5.4
        PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH
    source /etc/profile
    
  4. 测试安装结果

    mvn -v
    
    1530067631723.png

4.4 gradle的安装

  1. 上传安装包到服务端


    1530067656417.png
  2. 创建目录并且解压到指定的目录下

    mkdir /usr/local/gradle
    unzip gradle-3.5-bin.zip -d /usr/local/gradle/
    
  3. 配置环境变量

    vim /etc/profile
        GRADLE_HOME=/usr/local/gradle/gradle-3.5
        PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$GRADLE_HOME/bin:$PATH
    source /etc/profile
    
  4. 测试安装结果

    gradle -v
    
1530067903139.png

4.5 Jenkins的部署

  1. 下载Jenkins

    • jenkins的官网下载jenkins的安装包

      1530027702182.png
    1530027722669.png
  2. 上传到服务端

    • 将jenkins上传到tomcat的webapps目录下
    1530027828027.png
  1. 配置Jenkins目录

    • Jenkins默认的工作目录位于用户目录下的.jenkins中,在刚刚的启动脚本上配置jenkins的目录,如果需要修改自己指定的工作目录,有以下三种方式

      • 设置JENKINS_HOME环境参数

        #编辑profile文件
        vi /etc/profile
        #在最后加入
        export JENKINS_HOME=xxxx
        #保存,退出后执行.让配置生效
        source  /etc/profile
        
    • 使用Web容器的管理工具设置JENKINS_HOME环境参数.

      #打开tomcat的bin目录,编辑catalina.sh文件。
      # OS specific support.  $var _must_ be set to either true or false.上面添加
      export JENKINS_HOME=xxx
      
    • 更改Jenkins.war(或者在展开的Web容器)内的web.xml配置文件

      <!-- if specified, this value is used as the Hudson home directory -->
      <env-entry>
          <env-entry-name>HUDSON_HOME</env-entry-name>
          <env-entry-type>java.lang.String</env-entry-type>
          <env-entry-value></env-entry-value>
      </env-entry>
      <!-- 在<env-entry-value>节点中填入路径,windows系统建议使用/分隔路径 -->
      
  2. Jenkins初始化

    • 在配置结束后,重启tomcat服务器,查看tomcat的运行日志

      tail -fn 1000 /usr/local/tomcat/jenkins/apache-tomcat-8.0.51/logs/catalina.out
      
      1530028444311.png
    • 访问地址

      http://服务器的ip:1314/jenkins/
      
      1530028998395.png
      • 其中为了确保安全,我们需要到jenkins地址提示的路径下获取密钥填写到网页中

        cat /usr/local/tomcat/jenkins/JENKINS_HOME/secrets/initialAdminPassword
        
      1530029073523.png
      • 将获取到的密码填写入输入框中点击继续
    • 安装插件

    1530029252641.png

    * 在这里,如果没有特殊的需求的话,直接选择安装推荐插件即可,如果有特殊的需要,可以自己自定义选择插件来安装
    * 静等插件安装结束
    1530029548682.png

    * 打×的插件是由于网络传输导致的安装失败,后面再重新安装即可。这个步骤中如果选择了安装插件则 Linux 必须能够联网

    • 创建登录账户,默认为admin

      1530029762950.png
    • 到此,就可以开始使用Jenkins了

      1530029778300.png

五 Jenkins系统初始化配置

  • 在jenkins中么,常用配置一般分为以下三个地方

    1530068142093.png
    • 全局安全配置
    1530068268618.png

    * 刚开始入门阶段,我们一般不需要做太多的权限设置,如果需要进行权限上的管理,可以开启安全矩阵功能进行对用户权限的控制


    1530068295764.png
    • 全局工具配置

      • 在此环节配置当中,没有太多需要操作的地方,主要是把在服务器上设置的一系列的工具目录配置到jenkins当中即可,

      • 需要注意的是,在配置过程中都必须要使用绝对路径,并且在非必须的情况下,把自动勾选去掉,使用我们自己安装的工具

        1530068632576.png
        1530068651605.png
    • 管理插件

      1530068760238.png
      • 在这里,以deploy to container这个插件为例,该插件主要功能让jenkins自动的把构建好的项目部署到服务器中间件当中,但是这也不是必须的插件,因为一般我们可以使用shell脚本来完成这些操作

六. Jenkins+svn+maven的项目构建

6.1 创建工程

  1. 点击新建任务

    1530069586404.png
  1. 填写任务的相关信息
1530069757985.png
  1. 通用配置

    1530070153500.png
1530070492304.png
  1. 源码管理

    1530070690662.png
1530070821297.png
1530070890052.png
1530071357137.png
  • check-out Strategy的各选项说明
Check-out Strategy 第一次build 第n次build(除第一次)
Use 'svn update' as much as possible 将workspace下的所有文件清空,然后从svn上check out一份完整的项目到workspace下 update前不会revert
Always check out a fresh copy 将workspace下的所有文件清空,然后从svn上check out一份完整的项目到workspace下 删除workspace下的所有文件,然后重新check out一份完整的项目到workspace下。
Emulate clean checkout by first deleting unversioned/ignored files, then 'svn update' 将workspace下的所有文件清空,然后从svn上check out一份完整的项目到workspace下 update前先删除unversioned/ignored文件
Use 'svn update' as much as possible, with 'svn revert' before update 将workspace下的所有文件清空,然后从svn上check out一份完整的项目到workspace下 update前先revert
  1. 构建触发器

    1530086317897.png
    • 主要用于配置构建的触发条件
      • 触发远程构建 (例如,使用脚本)

        • 可以通过使用以下URL远程触发构建,远程触发的基本原理是外部给 Jenkins 项目特定的 URL 地址发送请求, 但必须以请求参数的形式携带一个特定值,这个特定值就是这里的“身份验证令牌

          • JENKINS_URL/job/testPorject/build?token=TOKEN_NAME 或者 /buildWithParameters?token=TOKEN_NAME

          • 可在后面叠加&cause=Cause+Text提供构建参数的原因等信息

            1530087936045.png
      • 其他工程构建后触发 :

        • 在其他项目触发的时候触发,里面有分为三种情况,也就是其他项目构建成功、失败、或者不稳定的时候触发项目
      • 轮询 SCM

        • 定时检查源码变更(根据SCM软件的版本号),如果有更新就checkout最新code下来,然后执行构建动作
          • */5 * * * * (每5分钟检查一次源码变化)
      • 定时构建

[图片上传中...(1530086939631.png-4c2281-1538705797773-0)]
* 周期进行项目构建(它不关心源码是否发生变化),例如:

            *   在 Schedule 中填写 0 * * * *

                *   第一个参数代表的是分钟 minute,取值 0~59
                *   第二个参数代表的是小时 hour,取值 0~23
                *   第三个参数代表的是天 day,取值 1~31
                *   第四个参数代表的是月 month,取值 1~12
                *   最后一个参数代表的是星期 week,取值 0~7,0 和 7 都是表示星期天

                所以 0 * * * * 表示的就是每个小时的第 0 分钟执行构建
  1. 构建环境

    1530086939631.png
  • 一般这个不需要配置,用于选择当前构建的环境
  1. 构建

    1530087083665.png
    • pre steps

      • 用于构建前的操作,其中可以选择多个先前操作,一般在这里用于执行关闭中间件,以及对当前项目进行库的备份与项目备份等操作
      1530087132088.png
    • Build

      • 在这个步骤中主要是用于项目的构建,通过执行maven命令将项目构建出来
    • post steps

      • 这个步骤与pre steps可选方式一样,都是通过一系列的配置,完成项目构建之后的工作,比如将构建完成的项目部署到服务器中间件,启动项目等操作
      • 服务端相关的自动部署脚本
      #!/bin/bash
      TOMCAT_HOME=/usr/local/tomcat/test/apache-tomcat-8.0.51
      WAR_NAME=employeemanager-0.0.1-SNAPSHOT
      JENKINS_HOME=/usr/local/tomcat/jenkins/JENKINS_HOME/workspace/testPorject/target
      TARGET_WAR_NAME=employeemanager
      BACK_HOME=/back_war
      time=`date +%Y%m%d%s`
      
      #################公用方法#####################
      function kill_tomcat(){
       if [ `ps auxwwww|grep $1|grep -v grep|wc -l` -gt 0 ]  
       then  
           for pid in `ps auxwww|grep $1|grep -v grep|tr -s ' '|cut -d ' ' -f2`  
           do  
               kill -9 $pid 2>&1 > /dev/null  
           done  
           echo "终止tomcat进程:$pid"
       else
           echo "tomcat无进程启动,不需要终止"
       fi 
      }
      function clean(){
       cd $1/webapps
       rm -rf $2
      }
      function back(){
       if [ -d $2 ];then
           mkdir $2
       fi
       [ -f ./$1.war ] && mv $1.war $2/$1.war_$time
      }
      function deploy_tomcat(){
       cd $1
       [ -f ./$2.war ] && cp ./$2.war $3/webapps/$4.war
      }
      function start_tomcat(){
       cd $1/bin
       sudo ./startup.sh
      }
      #################公用方法#####################
      
      echo "1.终止tomcat进程"
      kill_tomcat $TOMCAT_HOME
      echo "2.清理webapps目录"
      clean $TOMCAT_HOME $TARGET_WAR_NAME
      echo "2.备份webapps目录"
      back $TARGET_WAR_NAME $BACK_HOME
      echo "3.部署项目到webapps下"
      deploy_tomcat $JENKINS_HOME $WAR_NAME $TOMCAT_HOME $TARGET_WAR_NAME
      echo "3.启动tomcat"
      start_tomcat $TOMCAT_HOME
      
  1. 构建后的操作
1530087524750.png
  • 构建设置

    • 这里集成了一个邮件通知的插件,如果在外网的话可以在构建结束后发送构建相关的信息到指定的邮箱当中
  • 构建够的操作

    1530087590363.png
    • 这里主要是用于完成构建后的后续善后工作,在这里可以使用之前提到的deploy to container插件,将项目部署到相关的容器当中

6.2 手动构建

  • Jenkins 使用天气状况来表示构建成功率

    1530087885861.png
  • 开始构建

    1530088718060.png
    • 点击参数化构建后,填写完参数,点开始构建,那么项目就开始进入构建环节,jenkins会帮我们从svn开始下载源码,然后通过maven进行项目的编译打包,通过执行构建脚本,对项目进行自动部署等操作
    1530088349814.png
    • 在第一次构建当中,maven需要进行初始化,会下载一系列的依赖,需要一个漫长等待的过程
    1530088623933.png
    • 自此项目构建完成,显示构建成功

      1530118124996.png
    • 浏览测试成功

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

推荐阅读更多精彩内容