java对文档的相关操作

在实际项目的开发过程中,有一些需求是涉及到对各种文档文件的操作,由于在近期的工作中使用到一些 word 和 *pdf *的操作,因此在这篇文章中,我会简单介绍下 *word *和 pdf 的相关操作方法。

1、pdf文档预览

word、excel等文档的预览,如果是免费的,绝大多数都是基于openoffice去实现的,给大家提供一个参考:文档预览开源服务
(如果考虑付费产品的话,可以看看:永中office、office365等)。

这里介绍使用pdf.js来预览pdf,pdf.js下载链接,提取码:ew60,需要将这个pdfjs文件夹放到项目中(我这边是放在webapp/resources/plugin里面,如下图)。

调用方法:(假设pdfjs访问的路径是$prefix/pdfjs)

需要给预览页面的iframe的src属性设置如下:

    "$prefix/pdfjs/web/viewer.html?file=" + encodeURIComponent(url)

上面的url指的是文件的访问地址(如:http://localhost:8080/file/abc.txt

ps:使用pdfjs预览时,插件中默认会有下载和打印按钮,如果项目中需要对这两个按钮设置操作权限的话,则需要在js中去处理。(如果不需要这两个按钮,则只需要在pdfjs/web/viewer.html中找到id="download"、id="print"、 id="secondaryPrint" 和 id="secondaryDownload",分别加上style="display: none;")

js动态隐藏下载和打印按钮,如下图:

2、文档解析-word文档占位符替换

word占位符替换一般有两种情况
①替换word段落的占位符(标题、段落等除表格以外的元素)
②替换word表格的占位符
相关方法如下:

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 替换文档中的段落和表格占位符
 *
 * @author zhuLong
 * @since 2020/6/5 9:54
 */
public class WordReplaceUtil {

    /**
     * 替换段落中的占位符
     *
     * @param doc    需要替换的文档
     * @param params 替换的参数,key=占位符,value=实际值
     */
    public static void replaceInPara(XWPFDocument doc, Map<String, Object> params) {
        Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
        XWPFParagraph para;
        while (iterator.hasNext()) {
            para = iterator.next();
            if (!StringUtils.isEmpty(para.getParagraphText())) {
                replaceInPara(para, params);
            }
        }
    }

    /**
     * 替换段落中的占位符
     *
     * @param para
     */
    public static void replaceInPara(XWPFParagraph para, Map<String, Object> params) {
        // 获取当前段落的文本
        String sourceText = para.getParagraphText();
        // 控制变量
        boolean replace = false;
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String key = entry.getKey();
            if (sourceText.indexOf(key) != -1) {
                Object value = entry.getValue();
                if (value instanceof String) {
                    // 替换文本占位符
                    sourceText = sourceText.replace(key, value.toString());
                    replace = true;
                }
            }
        }
        if (replace) {
            Integer fontSize = null;
            boolean isBold = false;
            // 获取段落中的行数
            List<XWPFRun> runList = para.getRuns();
            for (int i = runList.size() - 1; i >= 0; i--) {
                if (runList.get(i).getFontSize() > 0) {
                    fontSize = runList.get(i).getFontSize();
                }
                if (runList.get(i).isBold()) {
                    isBold = runList.get(i).isBold();
                }

                // 删除之前的行
                para.removeRun(i);
            }
            // 创建一个新的文本并设置为替换后的值 这样操作之后之前文本的样式就没有了,待改进
            XWPFRun run = para.createRun();
            run.setBold(isBold);
            run.setText(sourceText);
            if (fontSize != null) {
                run.setFontSize(fontSize);
            }
        }
    }

    /**
     * 替换表格中的占位符
     *
     * @param doc
     * @param params
     */
    public static void replaceTable(XWPFDocument doc, Map<String, Object> params) {
        // 获取文档中所有的表格
        Iterator<XWPFTable> iterator = doc.getTablesIterator();
        XWPFTable table;
        List<XWPFTableRow> rows;
        List<XWPFTableCell> cells;
        List<XWPFParagraph> paras;
        while (iterator.hasNext()) {
            table = iterator.next();
            if (table.getRows().size() > 1) {
                //判断表格是需要替换还是需要插入,判断逻辑有${为替换,
                if (matcher(table.getText()).find()) {
                    rows = table.getRows();
                    for (XWPFTableRow row : rows) {
                        cells = row.getTableCells();
                        for (XWPFTableCell cell : cells) {
                            paras = cell.getParagraphs();
                            for (XWPFParagraph para : paras) {
                                replaceInPara(para, params);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 正则匹配字符串
     *
     * @param str
     * @return
     */
    private static Matcher matcher(String str) {
        Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return matcher;
    }

    /**
     * 需要替换的内容
     */
    private static Map<String, Object> createParamsMap() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("${name}", "abc");
        map.put("${sex}", "男");
        return map;
    }

    public static void main(String[] args) throws Exception {
        File mainFile = new File("C:\\Users\\zhulong\\Desktop\\abc.docx");
        InputStream in = new FileInputStream(mainFile);
        OPCPackage srcPackage = OPCPackage.open(in);
        XWPFDocument doc = new XWPFDocument(srcPackage);
        WordReplaceUtil.replaceTable(doc, createParamsMap());
    }
}

3、文档解析-word文档添加行数据

import cn.hutool.core.util.ArrayUtil;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.List;

/**
 * word 文档操作
 *
 * @author zhuLong
 * @since 2020/6/29 9:16
 */
public class WordUtils {
    /**
     * insertRow 在word表格中指定位置插入一行,复制指定行样式
     *
     * @param copyrowIndex 需要复制的行位置
     * @param newrowIndex  需要新增一行的位置
     */
    public static void insertRow(XWPFTable table, int copyrowIndex, int newrowIndex, String[] datas) {
        // 在表格中指定的位置新增一行
        XWPFTableRow targetRow = table.insertNewTableRow(newrowIndex);
        // 获取需要复制行对象
        XWPFTableRow copyRow = table.getRow(copyrowIndex);
        //复制行对象
        targetRow.getCtRow().setTrPr(copyRow.getCtRow().getTrPr());
        //或许需要复制的行的列
        List<XWPFTableCell> copyCells = copyRow.getTableCells();
        //复制列对象
        XWPFTableCell targetCell = null;
        for (int i = 0; i < copyCells.size(); i++) {
            XWPFTableCell copyCell = copyCells.get(i);
            String a = copyCell.getText();
            targetCell = targetRow.addNewTableCell();
            targetCell.getCTTc().setTcPr(copyCell.getCTTc().getTcPr());
            if (copyCell.getParagraphs() != null && copyCell.getParagraphs().size() > 0) {
                targetCell.getParagraphs().get(0).getCTP().setPPr(copyCell.getParagraphs().get(0).getCTP().getPPr());
                if (copyCell.getParagraphs().get(0).getRuns() != null
                        && copyCell.getParagraphs().get(0).getRuns().size() > 0) {
                    XWPFRun cellR = targetCell.getParagraphs().get(0).createRun();
                    cellR.setBold(copyCell.getParagraphs().get(0).getRuns().get(0).isBold());
                    if (ArrayUtil.isNotEmpty(datas)) {
                        cellR.setText(datas[i]);
                        cellR.setFontSize(10);
                    }
                }
            }
        }

    }

    public static void main(String[] args) throws Exception{
        File mainFile = new File("C:\\Users\\zhulong\\Desktop\\abc.docx");
        InputStream in = new FileInputStream(mainFile);
        OPCPackage srcPackage = OPCPackage.open(in);
        XWPFDocument doc = new XWPFDocument(srcPackage);
        // 动态插入一行
        List<XWPFTable> tables = doc.getTables();//获取word中所有的表格
        XWPFTable table = tables.get(0);//获取第一个表格
        String[] datas = new String[5];
        datas[0] = "a";
        datas[1] = "b";
        datas[2] = "c";
        datas[3] = "d";
        datas[4] = "e";
        WordUtils.insertRow(table, 1, 2, datas);
    }
}

4、文档解析-pdf相关操作

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.*;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.List;
import java.util.Map;

/**
 * pdf相关操作工具类
 *
 * @author zhuLong
 * @since 2020/6/18 17:03
 */
public class PdfUtil {

    private static final Logger logger = LoggerFactory.getLogger(PdfUtil.class);

    /*
     * 合并pdf文件
     * @param files 要合并文件数组(绝对路径如{ "e:\\1.pdf", "e:\\2.pdf" ,
     * "e:\\3.pdf"}),合并的顺序按照数组中的先后顺序,如2.pdf合并在1.pdf后。
     * @param newfile 合并后新产生的文件绝对路径,如 e:\\temp\\tempNew.pdf,
     * @return boolean 合并成功返回true;否则,返回false
     *
     */
    public static boolean mergePdfFiles(String[] files, String newfile) {
        boolean retValue = false;
        Document document = null;
        try {
            document = new Document(new PdfReader(files[0]).getPageSize(1));
            PdfCopy copy = new PdfCopy(document, new FileOutputStream(newfile));
            document.open();
            for (int i = 0; i < files.length; i++) {
                PdfReader reader = new PdfReader(files[i]);
                int n = reader.getNumberOfPages();
                for (int j = 1; j <= n; j++) {
                    document.newPage();
                    PdfImportedPage page = copy.getImportedPage(reader, j);
                    copy.addPage(page);
                }
            }
            retValue = true;
        } catch (Exception e) {
            System.out.println(e);
        } finally {
            System.out.println("执行结束");
            if (document != null) {
                document.close();
            }
        }
        return retValue;
    }

    /**
     * pdf转png
     *
     * @param inputStream pdf文件输入流
     * @author zhuLong
     * @since 2020/6/19 14:17
     */
    public static File pdf2png(InputStream inputStream) {
        try {
            PDDocument doc = PDDocument.load(inputStream);
            PDFRenderer renderer = new PDFRenderer(doc);
            int pageCount = doc.getNumberOfPages();
            for (int i = 0; i < pageCount; i++) {
                BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
                // 创建临时文件
                File temp = File.createTempFile("myTempFile", ".png");
                ImageIO.write(image, "png", temp);
                return temp;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * pdf转png
     *
     * @param filePath pdf文件路径
     * @author zhuLong
     * @since 2020/6/18 23:00
     */
    public static File pdf2png(String filePath) throws FileNotFoundException {
        // 将pdf装图片 并且自定义图片得格式大小
        File file = new File(filePath);
        InputStream inputStream = new FileInputStream(file);
        return pdf2png(inputStream);
    }

    /**
     * pdf转png
     *
     * @param file pdf文件
     * @author zhuLong
     * @since 2020/6/18 23:00
     */
    public static File pdf2png(File file) throws FileNotFoundException {
        // 将pdf装图片 并且自定义图片得格式大小
        InputStream inputStream = new FileInputStream(file);
        return pdf2png(inputStream);
    }

    /**
     * @param imgPath 图片路径
     * @param pdf     生成的pdf
     * @author zhuLong
     * @since 2020/6/18 23:00
     */
    public static void image2pdf(String imgPath, File pdf) throws DocumentException, IOException {
        Document document = new Document();
        OutputStream os = new FileOutputStream(pdf);
        PdfWriter.getInstance(document, os);
        document.open();
        createPdf(document, imgPath);
        document.close();
    }

    private static void createPdf(Document document, String imgPath) {
        try {
            Image image = Image.getInstance(imgPath);
            float documentWidth = document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin();
            System.out.println(documentWidth + "");
            float documentHeight = documentWidth / 580 * 850;//重新设置宽高
            System.out.println(documentHeight + "");
            image.scaleAbsolute(documentWidth, documentHeight);//重新设置宽高
            document.add(image);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (document != null) {
                document.close();
            }
        }
    }

    /**
     * 给pdf上添加水印文本
     *
     * @param contentList x、y坐标及文本的集合
     * @param file        源pdf文件
     * @param fontSize    字体大小
     * @author zhuLong
     * @since 2020/6/19 9:40
     */
    public static File setWatermark(List<Map<String, Object>> contentList, File file, float fontSize) throws FileNotFoundException {
        InputStream in = new FileInputStream(file);
        return setWatermark(contentList, in, fontSize);
    }

    /**
     * 给pdf上添加水印文本
     *
     * @param contentList x、y坐标及文本的集合
     * @param inputStream 源pdf文件输入流
     * @param fontSize    字体大小
     * @author zhuLong
     * @since 2020/6/19 9:40
     */
    public static File setWatermark(List<Map<String, Object>> contentList, InputStream inputStream, float fontSize) {
        PdfReader reader = null;
        PdfStamper stamper = null;
        try {
            reader = new PdfReader(inputStream);

            // 创建临时文件
            File dest = File.createTempFile("pdf", ".pdf");
            stamper = new PdfStamper(reader, new FileOutputStream(dest));
            //不可遮挡文字,只操作第一页
            PdfContentByte content = stamper.getOverContent(1);
            content.saveState();
            content.fill();
            content.restoreState();
            BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            //开始写入文本
            content.beginText();
            //字体大小
            content.setFontAndSize(base, fontSize);
            if (!contentList.isEmpty()) {
                for (Map<String, Object> map : contentList) {
                    //设置字体的输出位置
                    int x = Integer.parseInt(map.get("x").toString());
                    int y = Integer.parseInt(map.get("y").toString());
                    content.setTextMatrix(x, y);
                    content.showText(map.get("contentText").toString());
                }
            }
            content.endText();
            return dest;
        } catch (Exception e) {
            logger.error("操作pdf文件异常", e);
        } finally {
            try {
                if (stamper != null) {
                    stamper.close();
                }
                if (reader != null) {
                    reader.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static void main(String[] args) throws IOException {
        File file = new File("C:\\Users\\zhulong\\Desktop\\1.pdf");
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        Map<String, Object> map1 = new HashMap<String, Object>();
        map1.put("x", 480);
        map1.put("y", 690);
        map1.put("contentText", "10001");
        list.add(map1);
        Map<String, Object> map2 = new HashMap<String, Object>();
        map2.put("x", 425);
        map2.put("y", 690);
        map2.put("contentText", LocalDateTime.now().getYear());
        list.add(map2);
        File file1 = setWatermark(list, file, 14);
        System.out.println(file1.getAbsolutePath());
    }

}

5、图片裁剪拼接

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;

/**
 * 图片相关工具类
 *
 * @author zhuLong
 * @since 2020/6/18 23:15
 */
public class ImageUtil {

    /**
     * 裁剪图片
     *
     * @param imgIn  待裁剪的图片
     * @param imgOut 裁剪后的图片
     */
    public static void cutImg(File imgIn, File imgOut, int x, int y, int width, int height) {
        try {
            BufferedImage bufferedImage = ImageIO.read(imgIn);
            BufferedImage back = bufferedImage.getSubimage(x, y, width, height);
            ImageIO.write(back, "png", imgOut);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Java拼接多张图片
     *
     * @param pics    图片路径数组
     * @param type    图片类型
     * @param dstFile 目标文件
     */
    public static void merge(File[] pics, String type, File dstFile) {

        int len = pics.length;
        if (len < 1) {
            System.out.println("pics len < 1");
        }
        BufferedImage[] images = new BufferedImage[len];
        int[][] ImageArrays = new int[len][];
        for (int i = 0; i < len; i++) {
            try {
                images[i] = ImageIO.read(pics[i]);
            } catch (Exception e) {
                e.printStackTrace();
            }
            int width = images[i].getWidth();
            int height = images[i].getHeight();
            ImageArrays[i] = new int[width * height];// 从图片中读取RGB
            ImageArrays[i] = images[i].getRGB(0, 0, width, height,
                    ImageArrays[i], 0, width);
        }

        int dst_height = 0;
        int dst_width = images[0].getWidth();
        for (int i = 0; i < images.length; i++) {
            dst_width = dst_width > images[i].getWidth() ? dst_width
                    : images[i].getWidth();

            dst_height += images[i].getHeight();
        }
        if (dst_height < 1) {
            System.out.println("dst_height < 1");
        }

        // 生成新图片
        try {
            BufferedImage ImageNew = new BufferedImage(dst_width, dst_height,
                    BufferedImage.TYPE_INT_RGB);
            int height_i = 0;
            for (int i = 0; i < images.length; i++) {
                ImageNew.setRGB(0, height_i, dst_width, images[i].getHeight(),
                        ImageArrays[i], 0, dst_width);
                height_i += images[i].getHeight();
            }
            // 写图片
            ImageIO.write(ImageNew, type, dstFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6、word文档转换

Free Spire.Doc for Java 是一款免费、专业的Java Word组件,开发人员使用它可以轻松地将Word文档创建、读取、编辑、转换和打印等功能集成到自己的Java应用程序中。作为一款完全独立的组件,Free Spire.Doc for Java的运行环境无需安装Microsoft Office。

Free Spire.Doc for Java能执行多种Word文档处理任务,包括生成、读取、转换和打印Word文档,插入图片,添加页眉和页脚,创建表格,添加表单域和邮件合并域,添加书签,添加文本和图片水印,设置背景颜色和背景图片,添加脚注和尾注,添加超链接,加密和解密Word文档,添加批注,添加形状等。

友情提示:免费版有篇幅限制。在加载或保存Word 文档时,要求 Word 文档不超过 500 个段落,25 个表格。同时将 Word 文档转换为 PDF 和 XPS 等格式时,仅支持转换前三页。

在这里我们只介绍利用Free Spire.Doc for Java 将word转换为pdf。
1> pom文件引入依赖

<repositories>
    <repository>
        <id>com.e-iceblue</id>
        <url>http://repo.e-iceblue.cn/repository/maven-public/</url>
    </repository>
</repositories>

<dependencies>
        ......
        <!-- spire操作word -->
        <dependency>
            <groupId>e-iceblue</groupId>
            <artifactId>spire.doc.free</artifactId>
            <version>2.7.3</version>
        </dependency>
</dependencies>

2>方法调用

public static void main(String[] args) {
        //加载word示例文档
        Document document = new Document();
        document.loadFromFile("C:\\Users\\zhulong\\Desktop\\test.docx");
        //保存结果文件
        document.saveToFile("C:\\Users\\zhulong\\Desktop\\test.pdf", FileFormat.PDF); 
}

当然,excel转pdf也是支持的,可以使用Free Spire.XLS for Java,免费版有一定的限制,官网地址:https://www.e-iceblue.cn/Introduce/Free-Spire-XLS-JAVA.html

另外,也可以考虑使用openoffice组件进行文档的转换。

7、文档转换后字体乱码问题

一般文档转换经常会出现一种现象:本地(windows)上测试没问题,但是到了服务器(linux)上测试就出现中文乱码等问题,这种现象基本都是因为linux服务器上没有相关字体导致的。

解决方法:

  • 第一步:在linux服务器上安装中文字体库
    安装参考链接:linux安装中文字体库
    安装成功后,记得把你的应用服务重启再试一下,如果还是不行,说明你的源文档中的相关字体在linux服务器上找不到,进行第二步。
  • 第二步:定位你的文档中乱码那块的字体名称,然后到 C:\Windows\Fonts 这个目录下(windows系统)找到对应字体文件,复制到linux服务器上的/usr/share/fonts文件夹里面,然后依次执行如下命令:
mkfontscale //字体扩展
mkfontdir   //新增字体目录
fc-cache    //刷新缓存

注意:执行完之后,依然需要重启应用服务。

一般由于字体问题导致的乱码通过这种方法基本都可以得到解决。

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