jenkins+ant+jmeter自动化环境搭建(一)

写在最前面:

jmeter:测试接口的工具,支持java语言;

ant:Apache Ant是一个Java库和命令行工具,其任务是将构建文件中描述的进程作为相互依赖的目标和扩展点。只要使用过Linux系统的读者,应该知道 make这个命令。当编译Linux内核及一些软件源程序时,经常要用这个命令。Make命令其实就 是一个项目管理工具,而Ant所实现功能与此类似,像make,gnumake和nmake这些编译工具都有 一定的缺陷,但是Ant却克服了这些工具的缺陷。

jenkins:持续集成工具

一、安装前准备

1.JDK:jdk-8u121-windows-x64

2.jmeter工具:apache-jmeter-3.1

3.ANT工具:apache-ant-1.9.10-bin

4.jenkins工具:jenkins2.109的jenkins.war

5.jmeter插件:JMeterPlugins-Extras-1.4.0、JMeterPlugins-Standard-1.4.0

以上安装包工具及版本下载地址:https://pan.baidu.com/s/4kWiEEXL

二、软件安装

1.JDK的安装

双击JDK安装包,选择安装路径(查看jdk安装路径:cmd,java -verbose,我自己安装在C:\Program Files\Java\jdk1.8.0_121);


图片.png

1)系统变量→新建 JAVA_HOME 变量 。
变量值填写jdk的安装目录(本人是 C:\Program Files\Java\jdk1.8.0_121);
2)系统变量→寻找 Path 变量→编辑
在变量值最后输入 %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
(注意原来Path的变量值末尾有没有;号,如果没有,先输入;号再输入上面的代码)
3)系统变量→新建 CLASSPATH 变量
变量值填写 .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar (注意最前面有一点)
系统变量配置完毕
4)检验是否配置成功 运行cmd 输入 java -version (java 和 -version 之间有空格)
若如图所示 显示版本信息 则说明安装和配置成功。


图片.png

2.jmeter软件安装
解压apache-jmeter-3.1.zip到C:\apache-jmeter-3.1\apache-jmeter-3.1目录下。
图片.png

Jmeter插件的安装。解压JMeterPlugins-Extras-1.3.0.zip、JMeterPlugins-Standard-1.3.0.zip,将对应ext目录下的文件拷贝到C:\apache-jmeter-3.1\apache-jmeter-3.1\lib\ext目录下;


图片.png

配置jmeter环境变量:
  1. 设置环境变量,新建系统变量在变量名中输入:JMETER_HOME,变量值中输入:C:\apache-jmeter-3.1\apache-jmeter-3.1,点击确定即可。
  2. 在环境变量CLASSPATH中添加%JMETER_HOME%\lib\ext\ApacheJMeter_core.jar;%JMETER_HOME%\lib\jorphan.jar;%JMETER_HOME%\lib\logkit-1.2.jar;
    在环境变量Path中添加%JMETER_HOME%\bin
  3. 检验是否配置成功 运行cmd 输入 jmeter -v (jmeter和 -v 之间有空格)
    若如图所示 显示版本信息 则说明安装和配置成功。


    图片.png

    三、安装ANT

解压apache-ant-1.9.10-bin.zip到C:\Program Files (x86)\Java\apache-ant-1.9.10目录下;


图片.png

配置ANT环境变量
1)新建以下环境变量:
ANT_HOME:C:\Program Files (x86)\Java\apache-ant-1.9.10,此为你的ANT安装路径;
设置完成后,点击"确认"来保存。
2)在环境变量PATH的最后面添加一下内容:
;%ANT_HOME%\bin 注意前面的分号,如果PATH中最后没有分号";"的话要加上,有的话就可去除。
进入cmd命令窗口,在CMD窗口中输入ant -version,敲击回城键,会显示出你安装的ANT版本,如果成功显示,说明你的配置应该是成功了.


图片.png

四、ANT+Jmeter整合
1、将Jmeter所在目录下extras子目录里的ant-jmeter-1.1.1.jar复制到ant所在目录lib子目录之下,ant运行时才能找到

"org.programmerplanet.ant.taskdefs.jmeter.JMeterTask"这个类,之后可以触发Jmeter脚本
2、修改Jmeter的bin目录下jmeter.properties文件的配置:jmeter.save.saveservice.output_format=xml
3、建了一个目录C:\apache-jmeter-3.1\apache-jmeter-3.1\demo\resultLog,目录中分别建立了html和jtl两个文件夹,之后Ant运行之后将jtl中的文件转换成html中
(为什么要创建jtl和html,可以直接通过jenkins查看生成的折线图以及html报表,这两个文件夹存放相应格式的报告)


图片.png

html:存放生成的html报告
jtl:存放生成的jtl报告
4、在C:\apache-jmeter-3.1\apache-jmeter-3.1\scripts文件中建立一个build.xml文件,build.xml内容如下(注意:jmeter下也有一个build.xml文件,这个新建的文件跟jmeter下的不一样,这个build.xml要在网上找,具体如下配置):
图片.png

build.xml代码,注意要修改红色框框的内容
图片.png

5.将build.xml中的第42行
style="${jmeter.home}/extras/jmeter-results-detail-report_21.xsl">
拷贝以下内容将这个路径下的jmeter-results-detail-report_21.xsl文件覆盖,这个文件说明了xsl文件装换为html文件的一些规则
 <?xml version="1.0"?><xsl:stylesheet xmlns:xsl="[http://www.w3.org/1999/XSL/Transform"](http://www.w3.org/1999/XSL/Transform%22); version="1.0">

<!--

Licensed to the Apache Software Foundation (ASF) under one or more

contributor license agreements. See the NOTICE file distributed with

this work for additional information regarding copyright ownership.

The ASF licenses this file to You under the Apache License, Version 2.0

(the "License"); you may not use this file except in compliance with

the License. You may obtain a copy of the License at

[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)

Unless required by applicable law or agreed to in writing, software

distributed under the License is distributed on an "AS IS" BASIS,

WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the License for the specific language governing permissions and

limitations under the License.

-->

<!--

Stylesheet for processing 2.1 output format test result files

To uses this directly in a browser, add the following to the JTL file as line 2:

<?xml-stylesheet type="text/xsl" href="../extras/jmeter-results-detail-report_21.xsl"?>

and you can then view the JTL in a browser

-->

<xsl:output method="html" indent="yes" encoding="UTF-8" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" />

<!-- Defined parameters (overrideable) --><xsl:param name="showData" select="'n'"/><xsl:param name="titleReport" select="'Load Test Results'"/><xsl:param name="dateReport" select="'date not defined'"/>

<xsl:template match="testResults">

<html>

<head>

<title><xsl:value-of select="$titleReport" /></title>

<style type="text/css">

body {

font:normal 68% verdana,arial,helvetica;

color:#000000;

}

table tr td, table tr th {

font-size: 68%;

}

table.details tr th{

color: #ffffff;

font-weight: bold;

text-align:center;

background:#2674a6;

white-space: nowrap;

}

table.details tr td{

background:#eeeee0;

white-space: nowrap;

}

h1 {

margin: 0px 0px 5px; font: 165% verdana,arial,helvetica

}

h2 {

margin-top: 1em; margin-bottom: 0.5em; font: bold 125% verdana,arial,helvetica

}

h3 {

margin-bottom: 0.5em; font: bold 115% verdana,arial,helvetica

}

.Failure {

font-weight:bold; color:red;

}

img

{

border-width: 0px;

}

.expand_link

{

position=absolute;

right: 0px;

width: 27px;

top: 1px;

height: 27px;

}

.page_details

{

display: none;

}

.page_details_expanded

{

display: block;

display/* hide this definition from IE5/6 */: table-row;

}

</style>

<script language="JavaScript"><![CDATA[

function expand(details_id)

{

document.getElementById(details_id).className = "page_details_expanded";

}

function collapse(details_id)

{

document.getElementById(details_id).className = "page_details";

}

function change(details_id)

{

if(document.getElementById(details_id+"_image").src.match("expand"))

{

document.getElementById(details_id+"_image").src = "collapse.png";

expand(details_id);

}

else

{

document.getElementById(details_id+"_image").src = "expand.png";

collapse(details_id);

}

}

]]></script>

</head>

<body>

<xsl:call-template name="pageHeader" />

<xsl:call-template name="summary" />

<hr size="1" width="95%" align="center" />

<xsl:call-template name="pagelist" />

<hr size="1" width="95%" align="center" />

<xsl:call-template name="detail" />

</body>

</html></xsl:template>

<xsl:template name="pageHeader">

<h1><xsl:value-of select="$titleReport" /></h1>

<table width="100%">

<tr>

<td align="left">Date report: <xsl:value-of select="$dateReport" /></td>

<td align="right">Designed for use with <a href="[http://jmeter.apache.org/"](http://jmeter.apache.org/%22);>JMeter</a> and <a href="[http://ant.apache.org"](http://ant.apache.org%22/);>Ant</a>.</td>

</tr>

</table>

<hr size="1" /></xsl:template>

<xsl:template name="summary">

<h2>Summary</h2>

<table align="center" class="details" border="0" cellpadding="5" cellspacing="2" width="95%">

<tr valign="top">

<th># Samples</th>

<th>Failures</th>

<th>Success Rate</th>

<th>Average Time</th>

<th>Min Time</th>

<th>Max Time</th>

</tr>

<tr valign="top">

<xsl:variable name="allCount" select="count(/testResults/*)" />

<xsl:variable name="allFailureCount" select="count(/testResults/*[attribute::s='false'])" />

<xsl:variable name="allSuccessCount" select="count(/testResults/*[attribute::s='true'])" />

<xsl:variable name="allSuccessPercent" select="$allSuccessCount div $allCount" />

<xsl:variable name="allTotalTime" select="sum(/testResults/*/@t)" />

<xsl:variable name="allAverageTime" select="$allTotalTime div $allCount" />

<xsl:variable name="allMinTime">

<xsl:call-template name="min">

<xsl:with-param name="nodes" select="/testResults/*/@t" />

</xsl:call-template>

</xsl:variable>

<xsl:variable name="allMaxTime">

<xsl:call-template name="max">

<xsl:with-param name="nodes" select="/testResults/*/@t" />

</xsl:call-template>

</xsl:variable>

<xsl:attribute name="class">

<xsl:choose>

<xsl:when test="$allFailureCount &gt; 0">Failure</xsl:when>

</xsl:choose>

</xsl:attribute>

<td align="center">

<xsl:value-of select="$allCount" />

</td>

<td align="center">

<xsl:value-of select="$allFailureCount" />

</td>

<td align="center">

<xsl:call-template name="display-percent">

<xsl:with-param name="value" select="$allSuccessPercent" />

</xsl:call-template>

</td>

<td align="center">

<xsl:call-template name="display-time">

<xsl:with-param name="value" select="$allAverageTime" />

</xsl:call-template>

</td>

<td align="center">

<xsl:call-template name="display-time">

<xsl:with-param name="value" select="$allMinTime" />

</xsl:call-template>

</td>

<td align="center">

<xsl:call-template name="display-time">

<xsl:with-param name="value" select="$allMaxTime" />

</xsl:call-template>

</td>

</tr>

</table></xsl:template>

<xsl:template name="pagelist">

<h2>Pages</h2>

<table align="center" class="details" border="0" cellpadding="5" cellspacing="2" width="95%">

<tr valign="top">

<th>URL</th>

<th># Samples</th>

<th>Failures</th>

<th>Success Rate</th>

<th>Average Time</th>

<th>Min Time</th>

<th>Max Time</th>

<th></th>

</tr>

<xsl:for-each select="/testResults/*[not(@lb = preceding::*/@lb)]">

<xsl:variable name="label" select="@lb" />

<xsl:variable name="count" select="count(../*[@lb = current()/@lb])" />

<xsl:variable name="failureCount" select="count(../*[@lb = current()/@lb][attribute::s='false'])" />

<xsl:variable name="successCount" select="count(../*[@lb = current()/@lb][attribute::s='true'])" />

<xsl:variable name="successPercent" select="$successCount div $count" />

<xsl:variable name="totalTime" select="sum(../*[@lb = current()/@lb]/@t)" />

<xsl:variable name="averageTime" select="$totalTime div $count" />

<xsl:variable name="minTime">

<xsl:call-template name="min">

<xsl:with-param name="nodes" select="../*[@lb = current()/@lb]/@t" />

</xsl:call-template>

</xsl:variable>

<xsl:variable name="maxTime">

<xsl:call-template name="max">

<xsl:with-param name="nodes" select="../*[@lb = current()/@lb]/@t" />

</xsl:call-template>

</xsl:variable>

<tr valign="top">

<xsl:attribute name="class">

<xsl:choose>

<xsl:when test="$failureCount &gt; 0">Failure</xsl:when>

</xsl:choose>

</xsl:attribute>

<td>

<xsl:if test="$failureCount > 0">

<a><xsl:attribute name="href">#<xsl:value-of select="$label" /></xsl:attribute>

<xsl:value-of select="$label" />

</a>

</xsl:if>

<xsl:if test="0 >= $failureCount">

<xsl:value-of select="$label" />

</xsl:if>

</td>

<td align="center">

<xsl:value-of select="$count" />

</td>

<td align="center">

<xsl:value-of select="$failureCount" />

</td>

<td align="right">

<xsl:call-template name="display-percent">

<xsl:with-param name="value" select="$successPercent" />

</xsl:call-template>

</td>

<td align="right">

<xsl:call-template name="display-time">

<xsl:with-param name="value" select="$averageTime" />

</xsl:call-template>

</td>

<td align="right">

<xsl:call-template name="display-time">

<xsl:with-param name="value" select="$minTime" />

</xsl:call-template>

</td>

<td align="right">

<xsl:call-template name="display-time">

<xsl:with-param name="value" select="$maxTime" />

</xsl:call-template>

</td>

<td align="center">

<a href="">

<xsl:attribute name="href"><xsl:text/>javascript:change('page_details_<xsl:value-of select="position()" />')</xsl:attribute>

<img src="expand.png" alt="expand/collapse"><xsl:attribute name="id"><xsl:text/>page_details_<xsl:value-of select="position()" />_image</xsl:attribute></img>

</a>

</td>

</tr>

<tr class="page_details">

<xsl:attribute name="id"><xsl:text/>page_details_<xsl:value-of select="position()" /></xsl:attribute>

<td colspan="8" bgcolor="#FF0000">

<div align="center">

<b>Details for Page "<xsl:value-of select="$label" />"</b>

<table bordercolor="#000000" bgcolor="#2674A6" border="0" cellpadding="1" cellspacing="1" width="95%">

<tr>

<th>Thread</th>

<th>Iteration</th>

<th>Time (milliseconds)</th>

<th>Bytes</th>

<th>Success</th>

</tr>

<xsl:for-each select="../*[@lb = $label and @tn != $label]">

<tr>

<td><xsl:value-of select="@tn" /></td>

<td align="center"><xsl:value-of select="position()" /></td>

<td align="right"><xsl:value-of select="@t" /></td>

<!-- TODO allow for missing bytes field -->

<td align="right"><xsl:value-of select="@by" /></td>

<td align="center"><xsl:value-of select="@s" /></td>

</tr>

</xsl:for-each>

</table>

</div>

</td>

</tr>

</xsl:for-each>

</table></xsl:template>

<xsl:template name="detail">

<xsl:variable name="allFailureCount" select="count(/testResults/*[attribute::s='false'])" />

<xsl:if test="$allFailureCount > 0">

<h2>Failure Detail</h2>

<xsl:for-each select="/testResults/*[not(@lb = preceding::*/@lb)]">

<xsl:variable name="failureCount" select="count(../*[@lb = current()/@lb][attribute::s='false'])" />

<xsl:if test="$failureCount > 0">

<h3><xsl:value-of select="@lb" /><a><xsl:attribute name="name"><xsl:value-of select="@lb" /></xsl:attribute></a></h3>

<table align="center" class="details" border="0" cellpadding="5" cellspacing="2" width="95%">

<tr valign="top">

<th>Response</th>

<th>Failure Message</th>

<xsl:if test="$showData = 'y'">

<th>Response Data</th>

</xsl:if>

</tr>

<xsl:for-each select="/testResults/*[@lb = current()/@lb][attribute::s='false']">

<tr>
<td><xsl:value-of select="@rc | @rs" /> - <xsl:value-of select="@rm" /></td>
<td><xsl:value-of select="assertionResult/failureMessage" /></td>
<xsl:if test="$showData = 'y'">
<td><xsl:value-of select="./binary" /></td>
</xsl:if>
</tr>
</xsl:for-each>
</table>
</xsl:if>

</xsl:for-each>
</xsl:if></xsl:template>

<xsl:template name="min">
<xsl:param name="nodes" select="/.." />
<xsl:choose>
<xsl:when test="not($nodes)">NaN</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$nodes">
<xsl:sort data-type="number" />
<xsl:if test="position() = 1">
<xsl:value-of select="number(.)" />
</xsl:if>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose></xsl:template>

<xsl:template name="max">
<xsl:param name="nodes" select="/.." />
<xsl:choose>
<xsl:when test="not($nodes)">NaN</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$nodes">
<xsl:sort data-type="number" order="descending" />
<xsl:if test="position() = 1">
<xsl:value-of select="number(.)" />
</xsl:if>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose></xsl:template>

<xsl:template name="display-percent">
<xsl:param name="value" />
<xsl:value-of select="format-number($value,'0.00%')" /></xsl:template>

<xsl:template name="display-time">
<xsl:param name="value" />
<xsl:value-of select="format-number($value,'0 ms')" /></xsl:template>
</xsl:stylesheet>

6、进入Dos窗口,切换到build.xml路径(我的是C:\apache-jmeter-3.1\apache-jmeter-3.1\extras),输入命令ant敲回车,运行结束会有success信息提示

图片.png

7、查看报告:
1.在\demo\resultLog\html路径下查看html报告如下
图片.png

五、Jenkins的安装
1,接下来安装jenkins,下载war包
放到tomcat下的webapps下:
图片.png

2,查看端口命令:
(1)netstat -ano:显示机器的端口占用情况
(2)netstat -ano|findstr “提示的端口”:显示某个端口的占用情况
(3)tasklist|findstr “pid”:根据pid找到进程名称
图片.png

疑惑:查找到的端口具体每列代表什么意思?后面linux学习总结
3,修改tomcat默认的端口号port(tomcat下的conf中的server.xml):
图片.png

4,重启tomcat,打开地址:http://localhost:8081/jenkins

5,打开的页面要求粘贴密码,具体路径在:C:\Users\dell.jenkins\secrets\initialAdminPassword

6,进入jenkins后选择推荐的插件安装。

7,创建管理员用户:


图片.png

用户名:root

密码:C:\Users\dell.jenkins\secrets\initialAdminPassword中复制的内容

全名:默认跟用户名同名

电子邮件:404423741@qq.com

保存并完成,显示:


图片.png

六、配置
1、jenkins系统设置->管理插件->可选插件,搜索Performance plugin(Jmeter报告需要),安装此插件


图片.png

2、因为需要用到ANT和JDK,所以需要在jenkins中添加插件,选择点击“系统管理”,之后在全局工具配置的界面看到ant和jdk选项,然后将自己本地的ANT和JDK地址填写上
图片.png

图片.png

图片.png

3、配置、测试邮件1

在系统设置中找到Jenkins Locaction项填入Jenkins URL和系统管理员邮件地址,系统管理员邮件地址一定要配置,否则发不了邮件通知。因为邮件通知都是由系统管理员的邮箱发出来的


图片.png

按照如图所示设置即可,本处设置的是QQ企业邮箱的服务,其他邮箱按照类似设置即可


图片.png

首先在度娘中找到SMTP服务器地址和端口号及设置QQ邮箱的POP3/SMTP服务


图片.png

测试邮件发送成功之后,Extended E-mail Notification参照上面的设置即可。(注意:必须配置,否则后期项目构建发送邮件将失败)
4、创建job:


图片.png

构建步骤:
图片.png

点击保存,立即构建,成功:


图片.png

具体成功或者失败原因可看控制台输出:
图片.png

图片.png

html报告:
图片.png

疑问:
1,配置的发送邮件这次没有用到,不知道什么时候开启,发送,具体后面琢磨。
2,html报告里面pages中有三个请求:Sample1,Sample2,HTTP请求,其实HTTP请求才是我要测试的脚本,但是显示3个,上面Sample1,Sample2显示的什么意思?相关的配置在哪里?

3,如果只对于jmeter+ant集成的话,怎么看html的报告,在这个文章中jmeter+ant集成和jmeter+ant+jenkins集成的html报告成一样的了。

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

推荐阅读更多精彩内容