Itextpdf生成电子签章

概述


印章是我国特有的历史文化产物,古代主要用作身份凭证和行驶职权的工具。它的起源是由于社会生活的实际需要。早在商周时代,印章就已经产生。如今的印章已成为一种独特的,融实用性和艺术性为一体的艺术瑰宝。传统的印章容易被坏人、小人私刻;从而新闻鲜有报道某某私刻公章,侵吞国家财产。随着计算机技术、加密技术及图像处理技术的发展,出现了电子签章。电子签章是电子签名的一种表现形式,利用图像处理技术、数字加密技术将电子签名操作转化为与纸质文件盖章操作相同的可视效果,同时利用电子签名技术保障电子信息的真实性和完整性以及签名人的不可否认性。

电子签章与数字证书一样是身份验证的一种手段,泛指所有以电子形式存在,依附在电子文件并与其逻辑关联,可用以辨识电子文件签署者身份,保证文件的完整性,并表示签署者同意电子文件所陈述事实的内容。一般来说对电子签章的认定都是从技术角度而言的。主要是指通过特定的技术方案来鉴别当事人的身份及确保电子资料内容不被篡改的安全保障措施。电子签章常于发送安全电子邮件、访问安全站点、网上招标投标、网上签约、安全网上公文传送、公司合同、电子处方笺等。

技术选型


目前主流处理PDF文件两个jar包分别是:

  1. 开源组织Apache的PDFBox,官网https://pdfbox.apache.org/
  2. 大名鼎鼎adobe公司的iText,官网https://itextpdf.com/tags/adobe,其中iText又分为iText5和iText7

如何在PDFBox、iText5和iText7选出合适自己项目的技术呢?

对比PDFBox、iText5和iText7这三者:

  1. PDFBox的功能相对较弱,iText5和iText7的功能非常强悍;

  2. iText5的资料网上相对较多,如果出现问题容易找到解决方案;PDFBox和iText7的网上资料相对较少,如果出现问题不易找到相关解决方案;

  3. 通过阅读PDFBox代码目前PDFBox还没提供自定义签章的相关接口;iText5和iText7提供了处理自定义签章的相关实现

  4. PDFBox只能实现把签章图片加签到PDF文件;iText5和iText7除了可以把签章图片加签到PDF文件,还可以实现直接对签章进行绘制,把文件绘制到签章上。

  5. PDFBox和iText5/iText7使用的协议不一样。PDFBox使用的是APACHE LICENSE VERSION 2.0(https://www.apache.org/licenses/);iText5/iText7使用的是AGPL(https://itextpdf.com/agpl)。PDFBox免费使用,AGPL商用收费

本分享JAVA对PDF文件进行电子签章需要实现的功能:

  1. 生成证书。与PDFBox、iText5和iText7技术无关
  2. 按模板输出PDF文件:PDFBox、iText5和iText7都可以完成,但是PDFBox会遇到中文乱码比较棘手的问题
  3. 在PDF文件中实现把签章图片加签到PDF文件:PDFBox、iText5和iText7都可以实现,没有很多的区别
  4. 在PDF文件中绘制签章:iText5和iText7都可以实现,PDFBox目前不支持
  5. 在PDF文件中生成高清签章:iText5和iText7都可以实现,PDFBox目前不支持
  6. 在PDF文件中进行多次签名::PDFBox、iText5和iText7都可以完成,没有区别

通过相关技术分析和要实现的功能分析,采用iText5进行开发,唯一遗憾的是iText商用收费;但是这不是做技术需要关心的!!选用iText5的理由:

  1. 使用iText5能实现全部的功能
  2. 如何在开发中遇到相关问题,容易找到相应解决方案

准备相关文件:


1.背景色为空的印章图片
2.扩展名为.p12的证书(参考资料:https://blog.csdn.net/devil_bye/article/details/82759140)
3.freemarker模版

引入相关maven依赖

    <!--Freemarker wls-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.28</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.itextpdf.tool/xmlworker -->
        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.5.13</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf -->
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf</artifactId>
            <version>9.1.16</version>
        </dependency>

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.56</version>
        </dependency>

编写相关代码


1.工具类,第一个函数功能是将freemarker模转换成pdf,第二个函数是给pdf添加电子签章。

package ect.inv.util;

import com.hand.hap.core.IRequest;
import com.hand.hap.fnd.dto.Company;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.security.*;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.Map;

/**
 * @ClassName: ItextPDFUtil
 * @Description: itext导出pdf通用类
 * @create: 2019-01-22 11:14
 * @Verison:1,0
 **/
public class ItextPDFUtil {
    private static Logger logger = LoggerFactory.getLogger(ItextPDFUtil.class);

    private static String CHINA_TEX_INTER="CHINA_TEX_INTER";  //中纺棉国际
    private static String ME_SPIN_COTTON="ME_SPIN_COTTON";   //中纺棉花

    /**
     * 生成pdf
     * @param request
     * @param root
     * @param pdfName
     * @param pngName
     * @param docurx
     * @param docury
     * @return
     * @throws TemplateException
     * @throws IOException
     * @throws Exception
     */
    public static ByteArrayOutputStream processPdf(HttpServletRequest request, Map root, String pdfName, String pngName
            , Float docurx, Float docury) throws TemplateException, IOException, Exception {
        String basePath = request.getSession().getServletContext().getRealPath("/");
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
        //设置加载模板的目录
        String ftlUrl = basePath + "/WEB-INF/view/ftl";
        logger.info("pdf模板路径:" + ftlUrl);
        cfg.setDirectoryForTemplateLoading(new File(ftlUrl));
        // 设置编码
        cfg.setDefaultEncoding("UTF-8");
        logger.info("从指定的模板目录中加载对应的模板文件");
        // 从指定的模板目录中加载对应的模板文件
        Template temp = cfg.getTemplate("" + pdfName + ".ftl");
        root.put("basePath", basePath);
        String fileName = basePath + "/WEB-INF/view/" + pdfName + System.currentTimeMillis() + ".html";
        logger.info("生成HTML文件名:" + fileName);
        File file = new File(fileName);
        if (!file.exists()) {
            file.createNewFile();
        }
        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
        temp.process(root, out);
        String outputFileName = basePath + "/resources/template" + System.currentTimeMillis() + ".pdf";
        String outputSignFileName = basePath + "/resources/template" + System.currentTimeMillis() + "sign.pdf";
        logger.info("生成PDF文件名:" + outputFileName);
        Document document = null;
        if (docurx == null || docury == null) {
            //默认设置
            document = new Document(PageSize.A4); // 横向打印
        } else {
            document = new Document(new RectangleReadOnly(docurx, docury));
        }
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(outputFileName));
        document.open();
        XMLWorkerHelper.getInstance().parseXHtml(writer, document, new FileInputStream(fileName), Charset.forName("UTF-8"));
        document.close();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        String DEST = outputFileName;
        if (!StringUtils.isEmpty(pngName)) {
            /*=============================电子签章 Start===========================================*/
            String KEYSTORE = basePath + "/lib/pdf/zrong2.p12";
            char[] PASSWORD = "chinatex".toCharArray();//keystory密码
            String SRC = outputFileName;//原始pdf
            DEST = outputSignFileName;//签名完成的pdf
            String chapterPath = basePath + "/lib/pdf/" + pngName + ".png";//签章图片
            String reason = "理由";
            String location = "位置";
            sign(new FileInputStream(SRC), new FileOutputStream(DEST),
                    new FileInputStream(KEYSTORE), PASSWORD,
                    reason, location, chapterPath);
            /*=============================电子签章 Start==========================================*/
        }
        InputStream is = new FileInputStream(DEST);
        int buf;
        while ((buf = is.read()) != -1) {
            baos.write(buf);
        }
        baos.flush();
        is.close();
        out.close();
        writer.close();
        file = new File(fileName);
        file.delete();
        file = new File(outputFileName);
        file.delete();
        file = new File(DEST);
        file.delete();
        return baos;
    }


    /**
     * 在已经生成的pdf上添加电子签章,生成新的pdf并将其输出出来
     * @param src
     * @param dest
     * @param p12Stream
     * @param password
     * @param reason
     * @param location
     * @param chapterPath
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws DocumentException
     */
    public static void sign(InputStream src  //需要签章的pdf文件路径
            , OutputStream dest  // 签完章的pdf文件路径
            , InputStream p12Stream, //p12 路径
                            char[] password
            , String reason  //签名的原因,显示在pdf签名属性中,随便填
            , String location, String chapterPath) //签名的地点,显示在pdf签名属性中,随便填
            throws GeneralSecurityException, IOException, DocumentException {
        //读取keystore ,获得私钥和证书链
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(p12Stream, password);
        String alias = (String) ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
        Certificate[] chain = ks.getCertificateChain(alias);

        //下边的步骤都是固定的,照着写就行了,没啥要解释的
        // Creating the reader and the stamper,开始pdfreader
        PdfReader reader = new PdfReader(src);
        //目标文件输出流
        //创建签章工具PdfStamper ,最后一个boolean参数
        //false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
        //true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
        PdfStamper stamper = PdfStamper.createSignature(reader, dest, '\0', null, false);
        // 获取数字签章属性对象,设定数字签章的属性
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setReason(reason);
        appearance.setLocation(location);
        //设置签名的位置,页码,签名域名称,多次追加签名的时候,签名预名称不能一样
        //签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角
        //四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y
        appearance.setVisibleSignature(new Rectangle(300, 600, 630, 500), 1, "sig1");
        //读取图章图片,这个image是itext包的image
        Image image = Image.getInstance(chapterPath);
        appearance.setSignatureGraphic(image);
        appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
        //设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
        appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

        // 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现
        // 摘要算法
        ExternalDigest digest = new BouncyCastleDigest();
        // 签名算法
        ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, null);
        // 调用itext签名方法完成pdf签章CryptoStandard.CMS 签名方式,建议采用这种
        MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
    }

2.freemarker模版

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Title</title>
    <style mce_bogus="1" type="text/css">
        .template {
            font-family: "SimSun";
            color: black;
            padding: 10px 40px 10px 40px;
        }
        .template div {
            line-height: 1.5;
        }
        .header1 {
            font-size: 20px;
            font-weight: 800;
            text-align: center
        }
        .header2 {
            font-family: "SimSun";
            font-size: 15px;
            font-weight: 800;
            text-align: center
        }

        .table1 table{
            line-height: 1;
            margin-top: 5px;
            width: 100%;
            border : 0.5px solid black;
            border: 0.5px solid black;
            table-layout:fixed;
            border-collapse: collapse;
            overflow:hidden;
        }
        .table1 td{
            text-align:center;
            border: 0.5px solid black;
            border: 0.5px solid black;
            word-break:break-all;
            border-collapse: collapse;
            font-size: 15px;
        }
    </style>
</head>
<body>
    <div id="templateNumFiv" class="template">
        <p class="header1">${(modelNumTre.companyFullName)?default("")}</p>
        <p class="header2">国产棉提货(出库)单</p><br/><br/><br/>
        <table width="100%">
            <tr>
                <td width="70%" style="text-align: left;font-size: 15px;">${(modelNumTre.subinvName)?default("")}:</td>
                <td width="30%" style="text-align: left;font-size: 15px;">编号:${(modelNumTre.outNum)?default("")}</td>
            </tr>
        </table>
        <div style="font-size: 15px;">&nbsp;&nbsp;请将我司存放在贵仓库的${modelNumTre.wareOutbatches?size}批棉花(重量共计${wareOutWeight?default("")?string("#.######")}吨)的货权转移至${(modelNumTre.exeCustName)?default("")} 名下,请贵仓库给予${(modelNumTre.exeCustName)?default("")}办理提货手续,具体批次如下:</div><br/>
        <table class="table1" style="width: 100%;table-layout:fixed;word-break:break-all;padding-bottom: 0px;margin-bottom: 0px;border-bottom: 0px;border-collapse:collapse;" >
            <tr>
                <td style="width: 10%;text-align: center;" >买方</td>
                <td style="width: 45%;text-align: center;" >${(modelNumTre.exeCustName)?default("")}</td>
                <td style="width: 15%;text-align: center;">合同号</td>
                <td style="width: 30%;text-align: center;" >${(modelNumTre.conNum)?default("")}</td>
            </tr>
        </table>
        <table class="table1" style="width: 100%;table-layout:fixed;word-break:break-all;padding-bottom: 0px;margin-bottom: 0px;border-bottom: 0px;border-collapse:collapse;" >
            <tr>
                <td style="width: 10%;text-align: center;">序号</td>
                <td style="width: 10%;text-align: center;">产地</td>
                <td style="width: 25%;text-align: center;">批次</td>
                <td style="width: 10%;text-align: center;">件数</td>
                <td style="width: 15%;text-align: center;">重量</td>
                <td style="width: 20%;text-align: center;">重量标准</td>
                <td style="width: 10%;text-align: center;">货位</td>
            </tr>
            <#if modelNumTre.wareOutbatches?? && (modelNumTre.wareOutbatches?size > 0) >
                <#list modelNumTre.wareOutbatches as aim>
                    <tr>
                        <td style="width: 10%;text-align: center;">${aim_index+1}</td>
                        <td style="width: 10%;text-align: center;">${(aim.origin)?default("")}</td>
                        <td style="width: 25%;text-align: center;">${(aim.batchNum)?default("")}</td>
                        <td style="width: 10%;text-align: center;">${(aim.batchQty)?default("")}</td>
                        <td style="width: 15%;text-align: center;">${aim.batchWeight?default("")?string("#.######")}</td>
                        <td style="width: 20%;text-align: center;">${(modelNumTre.outQualityStand)?default("")}</td>
                        <td style="width: 10%;text-align: center;">${(aim.loctNum)?default("")}</td>
                    </tr>
                </#list>
                <tr>
                    <td style="width: 10%;text-align: center;">汇总</td>
                    <td style="width: 10%;text-align: center;"></td>
                    <td style="width: 25%;text-align: center;"></td>
                    <td style="width: 10%;text-align: center;">${(wareOutConut)?default("")}</td>
                    <td style="width: 15%;text-align: center;">${wareOutWeight?default("")?string("#.######")}</td>
                    <td style="width: 20%;text-align: center;"></td>
                    <td style="width: 10%;text-align: center;"></td>
                </tr>
            </#if>
        </table>
        <br/>
        <div style="font-size: 15px">费用承担:<label style="display: inline;" id="fivPay">${(fee)?default("")}</label><br/>
            本提货(出库)单传真件、扫描件与原件具有同等法律效力。
        </div>
        <br/>
        <table width="100%" style="">

            <tr>
                <td style="width:60%;font-size: 15px;"></td>
                <td style="width:40%;text-align: center;font-size: 15px;">${(modelNumTre.companyFullName)?default("")}</td>
            </tr>
            <tr>
                <td style="width:60%;font-size: 15px;"></td>
                <td style="width:40%;text-align: center;font-size: 15px;">${(orderDate005)?default("")}</td>
            </tr>
        </table><br/><br/><br/><br/><br/><br/>

        <table width="100%">
            <tr>
                <td style="width:33%;text-align: left;font-size: 15px;">部门经理:</td>
                <td style="width:33%;text-align: left;font-size: 15px;">经办人:${(modelNumTre.peopleName)?default("")}</td>
                <td style="width:33%;text-align: left;font-size: 15px;"><#if '${isWf?default("")}'=='Y'>审核人:${employeeName?default("")}<#else >审核人:${(actName)?default("")}</#if></td>
            </tr>
        </table>
        <br/>
        <table width="100%">
            <tr>
                <td style="width:100%;text-align: left;font-size: 15px;">收款信息:${(modelNumTre.remark)?default("")}</td>
            </tr>
        </table>
    </div>
</body>
</html>

3.调用电子签章功具类和freemarker模版生成具有电子签章的pdf

 @RequestMapping(value = "/ect/inv/ware/outbound/ftlToOnePDF")
    @ResponseBody
    public void ftlToOnePDF(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,String cottonType,String outId)
            throws Exception{
        String pdfName="template_inv_ware_outbound_GB_Sign";
        String basePath = this.getViewPath();
        logger.info("项目路径:"+basePath);
        IRequest iRequest = createRequestContext(httpServletRequest);
        WareOutbound wareOutbound=new WareOutbound();
        wareOutbound.setOutId(Long.parseLong(outId));
        wareOutbound = service.self().selectByPrimaryKey(iRequest, wareOutbound);
        Company company=new Company();
        company.setCompanyId(wareOutbound.getComId());
        company=companyMapper.selectByPrimaryKey(company);

        ModelAndView data=service.getModelAndViewNumThree(iRequest,wareOutbound,new ModelAndView(),null);
        String pngName= ItextPDFUtil.companyToSign(iRequest,company);
        ByteArrayOutputStream out= ItextPDFUtil.processPdf(httpServletRequest,data.getModelMap(),pdfName,pngName,595.0F,842.0F);//调用了PDF打印工具类
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/pdf");
        OutputStream sOut = httpServletResponse.getOutputStream();
        sOut.flush();
        sOut.write(out.toByteArray());
        sOut.close();
    }

4.展示效果

屏幕快照 2019-02-27 下午10.46.53.png

至此,电子签章的代码整理完毕。

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

推荐阅读更多精彩内容