將pdf轉(zhuǎn)換為jpg

近期遇到一個(gè)需求:
公司的電子稅票是以pdf文件格式保存的舆绎,這也是統(tǒng)一的標(biāo)準(zhǔn)格式哟沫。但是為了方便在手機(jī)App上查看,需要將稅票轉(zhuǎn)換為jpg格式。

解決:
拿到這個(gè)題目后压彭,我第一反應(yīng)就是去找一個(gè)Java的第三方庫(kù)來(lái)實(shí)現(xiàn)轉(zhuǎn)換。
嘗試了不少的第三方庫(kù)辱匿,比如pdfbox灶泵,ImageMagick等,但是效果都不理想补箍。要么是轉(zhuǎn)換出來(lái)的中文丟失改执,要么是表格數(shù)據(jù)排版混亂,有的甚至連圖片都丟失坑雅。網(wǎng)上說(shuō)jpedal效果不錯(cuò)辈挂,不過(guò)由于是商業(yè)版的無(wú)法嘗試。
Java的搜索了一圈后發(fā)現(xiàn)都不行裹粤。想想要不看看C#有沒(méi)有好的庫(kù)呢终蒂。然后就搜索到了今天的解決方案所用的Adobe Acorbat的Acrobat.dll 來(lái)進(jìn)行轉(zhuǎn)換的辦法。
Java無(wú)法直接使用dll遥诉,于是還用了jacob來(lái)調(diào)用dll 來(lái)實(shí)現(xiàn)拇泣。
不再羅嗦,直接上代碼突那。
jacob項(xiàng)目地址 https://sourceforge.net/projects/jacob-project/

以下代碼Pdf2Jpg2參考自 http://blog.csdn.net/love_5209/article/details/19162185

我主要做的修改是:將原來(lái)程序的每頁(yè)pdf轉(zhuǎn)換為一個(gè)jpg文件挫酿。修改為整個(gè)pdf所有頁(yè)面拼接為一個(gè)jpg文件。這樣方便存儲(chǔ)愕难。
主要思路是:先用一個(gè)循環(huán)獲取到pdf文件的寬度w和高度h早龟。然后設(shè)置一個(gè)寬度w,高度h的BufferedImage對(duì)象葱弟。然后在用一個(gè)循環(huán)讀取每頁(yè)的pdf內(nèi)容芝加,再粘貼到BufferedImage上藏杖,循環(huán)中適當(dāng)調(diào)整粘貼的坐標(biāo)。最后在循環(huán)外將BufferedImage對(duì)象輸出到j(luò)pg文件来吩,就完成了弟疆。

package com.invoicetopdf;
import java.awt.Graphics;  
import java.awt.Image;  
import java.awt.Toolkit;  
import java.awt.datatransfer.Clipboard;  
import java.awt.datatransfer.DataFlavor;  
import java.awt.datatransfer.Transferable;  
import java.awt.image.BufferedImage;  
import java.io.File;  
import java.io.FileOutputStream;  
import java.io.IOException;  
  
import java.util.Stack;

import javax.imageio.ImageIO;  
  

import com.jacob.activeX.ActiveXComponent;  
import com.jacob.com.ComThread;  
import com.jacob.com.Dispatch;  
import com.jacob.com.Variant;  
  
public class Pdf2Jpg2 {  
    /** 
     *  
     * @param filepath 
     *            pdf路徑 
     * @param savePath 
     *            img保存路徑 
     * @param times 
     *            縮放比例 如:1f為原圖比例 
     * @throws IOException  
     */  
    public static void savaPageAsJpgByAcrobat(String filepath, String savePath,  
            float times) throws IOException {  
        ComThread.InitSTA();//初始化com的線程   
        // 輸出  
        FileOutputStream out = null;  
        // PDF頁(yè)數(shù)  
        int pageNum = 0;  
        // PDF寬、高  
        int x, y = 0;  
        // PDF控制對(duì)象  
        Dispatch pdfObject = null;  
        // PDF坐標(biāo)對(duì)象  
        Dispatch pointxy = null;  
        // pdfActiveX PDDoc對(duì)象 主要建立PDF對(duì)象  
        ActiveXComponent app = new ActiveXComponent("AcroExch.PDDoc");  
        // pdfActiveX PDF的坐標(biāo)對(duì)象  
        ActiveXComponent point = new ActiveXComponent("AcroExch.Point");  
        try {  
            // 得到控制對(duì)象  
            pdfObject = app.getObject();  
            // 得到坐標(biāo)對(duì)象  
            pointxy = point.getObject();  
            // 打開(kāi)PDF文件恤溶,建立PDF操作的開(kāi)始  
            Dispatch.call(pdfObject, "Open", new Variant(filepath));  
            // 得到當(dāng)前打開(kāi)PDF文件的頁(yè)數(shù)  
            pageNum = Dispatch.call(pdfObject, "GetNumPages").toInt();  
            int allimgHeight = 0;
            int allimgWidth = 0;
            
            for (int i = 0; i < pageNum; i++) {  
                // 根據(jù)頁(yè)碼得到單頁(yè)P(yáng)DF  
                Dispatch page = Dispatch.call(pdfObject, "AcquirePage",  
                        new Variant(i)).toDispatch();  
                // 得到PDF單頁(yè)大小的Point對(duì)象  
                Dispatch pagePoint = Dispatch.call(page, "GetSize")  
                        .toDispatch(); 
                if (allimgWidth < (int) (Dispatch.get(pagePoint, "x").toInt() * 2 * times)){  
                    allimgWidth = (int) (Dispatch.get(pagePoint, "x").toInt() * 2 * times);
                }
                allimgHeight += (int) (Dispatch.get(pagePoint, "y").toInt() * 2 * times);
            }
            
            BufferedImage tag = new BufferedImage(allimgWidth, allimgHeight, 8);  
            Graphics graphics = tag.getGraphics();  
            
            Stack<Integer> pageHight = new Stack<Integer>();
            
            int tmpHight = 0;
            for (int i = 0; i < pageNum; i++) {  
                // 根據(jù)頁(yè)碼得到單頁(yè)P(yáng)DF  
                Dispatch page = Dispatch.call(pdfObject, "AcquirePage",  
                        new Variant(i)).toDispatch();  
                // 得到PDF單頁(yè)大小的Point對(duì)象  
                Dispatch pagePoint = Dispatch.call(page, "GetSize")  
                        .toDispatch();  
                // 創(chuàng)建PDF位置對(duì)象鸠天,為拷貝圖片到剪貼板做準(zhǔn)備  
                ActiveXComponent pdfRect = new ActiveXComponent("AcroExch.Rect");  
                // 得到單頁(yè)P(yáng)DF的寬  
                //int imgWidth = (int) (Dispatch.get(pagePoint, "x").toInt() * 2 * times);  
                //使用最寬的頁(yè)面作為統(tǒng)一寬度稠集。
                int imgWidth = allimgWidth; 
                // 得到單頁(yè)P(yáng)DF的高  
                int imgHeight = (int) (Dispatch.get(pagePoint, "y").toInt() * 2 * times);  
                
                // 控制PDF位置對(duì)象  
                Dispatch pdfRectDoc = pdfRect.getObject();  
                // 設(shè)置PDF位置對(duì)象的值  
                Dispatch.put(pdfRectDoc, "Left", new Integer(0));  
                Dispatch.put(pdfRectDoc, "Right", new Integer(imgWidth));  
                Dispatch.put(pdfRectDoc, "Top", new Integer(0));  
                Dispatch.put(pdfRectDoc, "Bottom", new Integer(imgHeight));  
                // 將設(shè)置好位置的PDF拷貝到Windows剪切板剥纷,參數(shù):位置對(duì)象,寬起點(diǎn)晦鞋,高起點(diǎn)线定,分辨率  
                Dispatch.call(page, "CopyToClipboard", new Object[] {  
                        pdfRectDoc, 0, 0, 200 * times });  
                Image image = getImageFromClipboard();  
                
                if (i==0){
                    graphics.drawImage(image, 0, 0, null); 
                }
                else {
                    graphics.drawImage(image, 0, tmpHight, null);
                }
                
                tmpHight += imgHeight;
                //graphics.dispose();  
                // 輸出圖片  
                //ImageIO.write(tag, "JPEG", new File(savePath + (i+1) + ".jpg"));  
            }  
            graphics.dispose(); 
            ImageIO.write(tag, "JPEG", new File(savePath +  "all.jpg"));  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            // 關(guān)閉PDF  
            app.invoke("Close", new Variant[] {});  
            ComThread.Release();//關(guān)閉com的線程   真正kill進(jìn)程  
        }  
  
    }  
  
    public static Image getImageFromClipboard() throws Exception {  
        Clipboard sysc = Toolkit.getDefaultToolkit().getSystemClipboard();  
        Transferable cc = sysc.getContents(null);  
        if (cc == null)  
            return null;  
        else if (cc.isDataFlavorSupported(DataFlavor.imageFlavor))  
            return (Image) cc.getTransferData(DataFlavor.imageFlavor);  
        return null;  
    }  
  
    public static void main(String[] args) throws IOException {
        //System.setProperty("java.library.path","d:/test/");
        savaPageAsJpgByAcrobat("d:/test/11.pdf", 
                "d:/test/", 1f); 
    }
}  

我在得到j(luò)pg文件后湾趾,還將jpg文件用二進(jìn)制的形式回寫(xiě)到了Oracle數(shù)據(jù)庫(kù)铛楣。具體代碼如下:

package com.invoicetopdf;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;

public class AutoInvoiceToPDF  {

    // base64位編碼轉(zhuǎn)成PDF
    public static void base64StringToPdf(String base64Content, String filePath)
            throws IOException {
        BASE64Decoder decoder = new BASE64Decoder();
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;

        try {
            byte[] bytes = decoder.decodeBuffer(base64Content);// base64編碼內(nèi)容轉(zhuǎn)換為字節(jié)數(shù)組
            ByteArrayInputStream byteInputStream = new ByteArrayInputStream(
                    bytes);
            bis = new BufferedInputStream(byteInputStream);
            File file = new File(filePath);
            File path = file.getParentFile();
            if (!path.exists()) {
                path.mkdirs();
            }
            fos = new FileOutputStream(file);
            bos = new BufferedOutputStream(fos);

            byte[] buffer = new byte[1024];
            int length = bis.read(buffer);
            while (length != -1) {
                bos.write(buffer, 0, length);
                length = bis.read(buffer);
            }
            bos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // closeStream(bis, fos, bos);
            bis.close();
            fos.close();
            bos.close();
            // System.out.println("生成成功");
        }
    }

    // pdf轉(zhuǎn)BASE64位編碼
    public static String PDFToBase64(File file) {
        BASE64Encoder encoder = new BASE64Encoder();
        FileInputStream fin = null;
        BufferedInputStream bin = null;
        ByteArrayOutputStream baos = null;
        BufferedOutputStream bout = null;
        try {
            fin = new FileInputStream(file);
            bin = new BufferedInputStream(fin);
            baos = new ByteArrayOutputStream();
            bout = new BufferedOutputStream(baos);
            byte[] buffer = new byte[1024];
            int len = bin.read(buffer);
            while (len != -1) {
                bout.write(buffer, 0, len);
                len = bin.read(buffer);
            }
            // 刷新此輸出流并強(qiáng)制寫(xiě)出所有緩沖的輸出字節(jié)
            bout.flush();
            byte[] bytes = baos.toByteArray();
            return encoder.encodeBuffer(bytes).trim();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fin.close();
                bin.close();
                bout.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    // GZIP壓縮
    public static String gzip(String primStr) {
        if (primStr == null || primStr.length() == 0) {
            return primStr;
        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        GZIPOutputStream gzip = null;
        try {
            gzip = new GZIPOutputStream(out);
            gzip.write(primStr.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (gzip != null) {
                try {
                    gzip.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return new sun.misc.BASE64Encoder().encode(out.toByteArray());
    }

    // GZIP解壓
    public static String gunzip(String compressedStr) {
        if (compressedStr == null) {
            return null;
        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayInputStream in = null;
        GZIPInputStream ginzip = null;
        byte[] compressed = null;
        String decompressed = null;
        try {
            compressed = new sun.misc.BASE64Decoder()
                    .decodeBuffer(compressedStr);
            in = new ByteArrayInputStream(compressed);
            ginzip = new GZIPInputStream(in);

            byte[] buffer = new byte[1024];
            int offset = -1;
            while ((offset = ginzip.read(buffer)) != -1) {
                out.write(buffer, 0, offset);
            }
            decompressed = out.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ginzip != null) {
                try {
                    ginzip.close();
                } catch (IOException e) {
                }
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                }
            }
        }

        return decompressed;
    }

    public static void ReadImgFromDB(Connection conn, int fphm) {
        // 從數(shù)據(jù)庫(kù)中讀取圖片。
        String sql = "select jpgbm from pdfinfo where fphm = '39410355'";
        Statement stmt;
        FileOutputStream fout;
        ResultSet rs;
        try {
            stmt = conn.createStatement();

            rs = stmt.executeQuery(sql);
            while (rs.next()) {
                Blob blob = rs.getBlob(1);
                byte barr[] = blob.getBytes(1, (int) blob.length());
                // System.out.println("blob length:" +blob.length());

                fout = new FileOutputStream("d:/dbjpg.jpg");
                fout.write(barr);
                fout.flush();
                fout.close();
            }
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }

    }

    public static void WirteImgToDB(Connection conn, int fphm) {
        // 把圖片更新回?cái)?shù)據(jù)庫(kù)
        PreparedStatement ps;
        try {
            ps = conn
                    .prepareStatement("update pdfinfo  set  jpgbm = ? where  fphm = ? ");
            FileInputStream fin;
            fin = new FileInputStream("d:\\test\\all.jpg");
            // System.out.println("file size:" + fin.available());
            ps.setBinaryStream(1, fin, fin.available());
            ps.setInt(2, fphm);
            int i = ps.executeUpdate();
            ps.close();
            // System.out.println(i + " records affected");
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void AllInOne(Connection conn, int fphm) {

        String pdfbm = "";
        try {
            Statement stmt = conn.createStatement();
            String sql = "select pdfbm from pdfinfo where fphm = '" + fphm
                    + "'";
            ResultSet rs = stmt.executeQuery(sql);
            while (rs.next()) {
                pdfbm = rs.getString("pdfbm");
            }
            rs.close();
            stmt.close();
            pdfbm = gunzip(pdfbm);
            // 生成pdf
            base64StringToPdf(pdfbm, "d:\\test\\11.pdf");

            Pdf2Jpg2.savaPageAsJpgByAcrobat("d:\\test\\11.pdf", "d:\\test\\",
                    0.9f);
            WirteImgToDB(conn, fphm);
            System.out.println("fphm:" + fphm + " done!");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        String url = "jdbc:oracle:thin:@172.xx.x.xx:1521:xxxx";
        String user = "xxxxx";
        String password = "xxxxx";
        Connection conn = null;
        try {
            Class.forName("oracle.jdbc.driver.OracleDriver");
            conn = DriverManager.getConnection(url, user, password);
        } catch (Exception e) {
            e.printStackTrace();
        }
        int fphm = 0;
        String sqlString = " select fphm from pdfinfo where jpgbm is null and rownum<5000 order by indate desc ";
        Statement stmt;
        try {
            stmt = conn.createStatement();

            ResultSet rs = stmt.executeQuery(sqlString);
            while (rs.next()) {
                fphm = rs.getInt("fphm");
                AllInOne(conn, fphm);
            }
            rs.close();
            stmt.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
    }

}

這個(gè)文件包含很多方法:
將數(shù)據(jù)庫(kù)中base64編碼的pdf編碼讀出保存為pdf文件 base64StringToPdf
將圖片寫(xiě)入Oracle數(shù)據(jù)庫(kù) WirteImgToDB
從Oracle讀出圖片并保存為文件 ReadImgFromDB
最后通過(guò)一個(gè)AllInOne將這些方法串聯(lián)起來(lái)。就可以達(dá)到讀取數(shù)據(jù)庫(kù)中的pdf base64編碼助琐,然后轉(zhuǎn)換為pdf祭埂,再轉(zhuǎn)換為jpg,最后將jpg的二進(jìn)制寫(xiě)入數(shù)據(jù)庫(kù)兵钮。
通過(guò)將這兩個(gè)源代碼打包為一個(gè)jar,再做一個(gè)定時(shí)任務(wù)泰演,就可以自動(dòng)去尋找沒(méi)有轉(zhuǎn)換生成jpg的稅票睦焕,自動(dòng)生成了。

需要注意的是:使用jacob.jar 需要找對(duì)版本袜炕,并將對(duì)應(yīng)的平臺(tái)(x86或者x64)的dll放到對(duì)應(yīng)的java.library.path中偎窘。一般復(fù)制到j(luò)ava運(yùn)行環(huán)境jre對(duì)應(yīng)的bin目錄下即可乌助。
推薦使用jacob 1.18版本。因?yàn)?.17版本在Eclispe下面運(yùn)行沒(méi)有問(wèn)題评架,但是在命令行里面運(yùn)行的時(shí)候總是提示 NoSuchFieldError: m_pDispatch 一切都是對(duì)的找不到原因眷茁。最后果斷換為1.18版本,就ok了纵诞。
jacob 1.18 需要Java7才能支持哦上祈!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市浙芙,隨后出現(xiàn)的幾起案子登刺,更是在濱河造成了極大的恐慌,老刑警劉巖嗡呼,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纸俭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡南窗,警方通過(guò)查閱死者的電腦和手機(jī)揍很,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)郎楼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人窒悔,你說(shuō)我怎么就攤上這事呜袁。” “怎么了简珠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵阶界,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我聋庵,道長(zhǎng)膘融,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任祭玉,我火速辦了婚禮氧映,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘攘宙。我一直安慰自己屯耸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布蹭劈。 她就那樣靜靜地躺著,像睡著了一般线召。 火紅的嫁衣襯著肌膚如雪铺韧。 梳的紋絲不亂的頭發(fā)上缓淹,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天哈打,我揣著相機(jī)與錄音,去河邊找鬼讯壶。 笑死料仗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伏蚊。 我是一名探鬼主播立轧,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼躏吊!你這毒婦竟也來(lái)了氛改?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤比伏,失蹤者是張志新(化名)和其女友劉穎胜卤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體赁项,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡葛躏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年澈段,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舰攒。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡败富,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出芒率,到底是詐尸還是另有隱情囤耳,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布偶芍,位于F島的核電站充择,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏匪蟀。R本人自食惡果不足惜椎麦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望材彪。 院中可真熱鬧观挎,春花似錦、人聲如沸段化。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)显熏。三九已至雄嚣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喘蟆,已是汗流浹背缓升。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蕴轨,地道東北人港谊。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像橙弱,于是被迫代替她去往敵國(guó)和親歧寺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容