Java生成二維碼分享海報

這一篇文章我們就用 Java 來生成一下仿金山詞霸的海報。

As long as you can still grab a breath, you fight.
只要一息尚存,就不得不戰(zhàn)爹土。

有那么一段時間帮坚,我特別迷戀金山詞霸的每日一句分享海報。因為不僅海報上的圖片美稍浆,文字也特別美载碌,美得讓我感覺生活都有了詩意。就像文章開頭的那句中英文對照衅枫,中文和英文都妙極了嫁艇。

最近,又有很多人迷戀上了流利說的小程序分享海報(朋友圈比比皆是)弦撩。但不管是金山詞霸還是流利說步咪,分享的海報都不是自己的二維碼,這對于個人品牌的締造者來說孤钦,實在是一件出力不討好的事歧斟。

當然了,這種事難不倒作為程序員的我偏形。

01静袖、大致思路

  • 采集網(wǎng)絡(luò)圖片

  • 加載海報背景和個人品牌二維碼

  • 利用 Graphics2D 將網(wǎng)絡(luò)圖片繪制成海報封面

  • 利用 Graphics2D 在海報上打印中英文對照語

  • 利用 Graphics2D 在海報上繪制個人專屬二維碼

  • 使用 Swing 構(gòu)建圖形化界面

  • 將項目打成 jar 包發(fā)行

  • 運行 jar 包,填寫必要信息后生成海報

02俊扭、采集網(wǎng)絡(luò)圖片

第一步队橙,獲取網(wǎng)絡(luò)圖片的路徑。金山詞霸每日一句的圖片路徑地址形式如下所示萨惑【杩担可以根據(jù)當前日期獲取最新的圖片路徑。

// 金山詞霸的圖片路徑
String formatDate = DateFormatUtils.format(new Date(), "yyyyMMdd");
String picURL = "http://cdn.iciba.com/news/word/big_" + formatDate + "b.jpg";

第二步庸蔼,有了圖片路徑后解总,可以根據(jù)此路徑創(chuàng)建 HTTP get 請求。

// 根據(jù)路徑發(fā)起 HTTP get 請求
HttpGet httpget = new HttpGet(picURL);
// 使用 addHeader 方法添加請求頭部
httpget.addHeader("Content-Type", "text/html;charset=UTF-8");

// 配置請求的超時設(shè)置
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(500).setConnectTimeout(500)
        .setSocketTimeout(500).build();
httpget.setConfig(requestConfig);

第三步姐仅,創(chuàng)建 CloseableHttpClient 對象來執(zhí)行 HTTP get 請求花枫,并獲取響應信息 CloseableHttpResponseCloseableHttpClient 是一個抽象類掏膏,它是 HttpClient 的基本實現(xiàn)劳翰,也實現(xiàn)了 java.io.Closeable 接口。

CloseableHttpClient httpclient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpclient.execute(httpget);

第四步馒疹,從 CloseableHttpResponse 中獲取圖片的輸入流佳簸。

HttpEntity entity = response.getEntity();
InputStream picStream = entity.getContent();

第五步,從圖片輸入流中讀取信息颖变,并輸出到本地文件中生均。

File pic = Files.createTempFile(Paths.get("D:\\test"), "pic_", ".jpg");
FileOutputStream fos = new FileOutputStream(pic);
int read = 0;

// 1024Byte(字節(jié))=1KB 1024KB=1MB
byte[] bytes = new byte[1024 * 100];
while ((read = inputStream.read(bytes)) != -1) {
    fos.write(bytes, 0, read);
}

fos.flush();
fos.close();

在指定的臨時目錄下可以查看采集到的圖片听想,如下所示。

03疯特、加載海報背景和個人品牌二維碼

海報背景的大小為 678 * 1013 像素哗魂,個人品牌二維碼的大小為 128 * 128 像素。兩張圖片都是事先準備好的漓雅,放在 src 目錄下录别。整個項目的目錄結(jié)構(gòu)圖如下所示。

接下來邻吞,我們把這兩張圖片分別讀取到臨時文件當中组题,供后續(xù)動作使用。

第一步抱冷,創(chuàng)建 ClassLoader 對象崔列,從 classpath 的根路徑下查找資源。

ClassLoader classLoader = ReadBgAndQrcode.class.getClassLoader();

第二步旺遮,通過 classLoader.getResourceAsStream() 讀取海報背景和個人品牌二維碼赵讯,復制到臨時文件中。

File bgFile = Files.createTempFile(DIRECTORY, "bg_", ".jpg").toFile();
InputStream inputStream = classLoader.getResourceAsStream("default_bgimg.jpg");
FileUtils.copyInputStreamToFile(inputStream, bgFile);
logger.debug("背景:" + bgFile.getAbsolutePath());


File qrcodeFile = Files.createTempFile(DIRECTORY, "qrcode_", ".jpg").toFile();
InputStream qrcodeInputStream = classLoader.getResourceAsStream("default_qrcodeimg.jpg");
FileUtils.copyInputStreamToFile(qrcodeInputStream, qrcodeFile);
logger.debug("二維碼:" + qrcodeFile.getAbsolutePath());

在指定的臨時目錄下可以查看海報背景和個人品牌二維碼耿眉,如下所示边翼。

05、利用 Graphics2D 將網(wǎng)絡(luò)圖片繪制成海報封面

Graphics2D 類擴展了 Graphics 類鸣剪,提供了對幾何形狀组底、坐標轉(zhuǎn)換、顏色管理和文本布局更為復雜的控制筐骇,是用于呈現(xiàn)二維形狀债鸡、文本和圖像的基礎(chǔ)類。

BufferedImage 使用可訪問的圖像數(shù)據(jù)緩沖區(qū)描述圖像铛纬,由顏色模型和圖像數(shù)據(jù)柵格組成厌均,所有 BufferedImage 對象的左上角坐標為(0,0)告唆。

可以利用 BufferedImage 類的 createGraphics() 方法獲取 Graphics2D 對象莫秆。

第一步,將海報背景和海報封面讀入到 BufferedImage 對象中悔详。注意,deleteOnExit() 方法請求在虛擬機終止時刪除此抽象路徑名所表示的文件或目錄惹挟。

// 背景
File bgFile = FileUtil.read("bg_", ".jpg", "default_bgimg.jpg");
bgFile.deleteOnExit();
BufferedImage bgImage = ImageIO.read(bgFile);

// 封面圖
File picFile = CapturePic.capture();
picFile.deleteOnExit();
BufferedImage picImage = ImageIO.read(picFile);

第二步茄螃,計算封面圖的起始坐標,以及高度和寬度连锯。

// 封面圖的起始坐標
int pic_x = MARGIN, pic_y = MARGIN;
// 封面圖的寬度
int pic_width = bgImage.getWidth() - MARGIN * 2;
// 封面圖的高度
int pic_height = picImage.getHeight() * pic_width / picImage.getWidth();

第三步归苍,在海報背景上繪制封面圖用狱。

Graphics2D graphics2d = bgImage.createGraphics();
// 在背景上繪制封面圖
graphics2d.drawImage(picImage, pic_x, pic_y, pic_width, pic_height, null);
// 釋放圖形上下文,以及它正在使用的任何系統(tǒng)資源拼弃。
graphics2d.dispose();

第四步夏伊,將繪制好的圖像輸出到文件中。

File posterFile = Files.createTempFile(FileUtil.DIRECTORY, "poster_", ".jpg").toFile();
ImageIO.write(bgImage, "jpg", posterFile);

在指定的臨時目錄下可以查看海報吻氧,如下所示溺忧。

06、利用 Graphics2D 在海報上打印中文

Font 類表示字體盯孙,用于以可見的方式呈現(xiàn)文本鲁森。字體提供了將字符序列映射到象形文字序列以及在圖形和組件對象上呈現(xiàn)象形文字序列所需的信息。

第一步振惰,通過 GraphicsEnvironment 類的 getAvailableFontFamilyNames() 查看計算機上允許使用的字體歌溉。

String[] fontNames = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();

for (String fontName : fontNames) {
    System.out.println(fontName);
}

大致的中文字體有這么一些(還有更多,未列出):

宋體
幼圓
微軟雅黑
微軟雅黑 Light
新宋體
方正姚體
方正舒體
楷體
隸書
黑體

第二步骑晶,設(shè)置字體和顏色痛垛。

// Font 的構(gòu)造參數(shù)依次是字體名字,字體式樣桶蛔,字體大小
Font font = new Font("微軟雅黑", Font.PLAIN, 28);
g.setFont(font);
// RGB
g.setColor(new Color(71, 71, 71));

第三步匙头,根據(jù)當前字體下每個中文字符的寬度,以及海報可容納的最大文本寬度羽圃,對文本進行換行乾胶。

計算每個字體的寬度時,需要用到 sun.font.FontDesignMetrics朽寞,它擴展了 java.awt.FontMetrics识窿。FentMetrics 類定義了一個字體度量對象,該對象封裝了有關(guān)在特定屏幕上呈現(xiàn)特定字體的信息脑融。FontDesignMetrics 提供了更多指標的 Font 信息喻频。

FontDesignMetrics 有幾個重要的值需要說明一下:

  • 基準點是 baseline
  • ascent 是 baseline 之上至字符最高處的距離
  • descent 是 baseline 之下至字符最低處的距離
  • leading 文檔說的很含糊,其實是上一行字符的 descent 到下一行的 ascent 之間的距離
  • top 指的是指的是最高字符到 baseline 的值肘迎,即 ascent 的最大值
  • bottom 指的是最下字符到 baseline 的值甥温,即 descent 的最大值

FontDesignMetricscharWidth() 方法可以計算字符的寬度。

public static String makeLineFeed(String zh, FontDesignMetrics metrics, int max_width) {
    StringBuilder sb = new StringBuilder();
    int line_width = 0;
    for (int i = 0; i < zh.length(); i++) {
        char c = zh.charAt(i);
        sb.append(c);

        // 如果主動換行則跳過
        if (sb.toString().endsWith("\n")) {
            line_width = 0;
            continue;
        }

        // FontDesignMetrics 的 charWidth() 方法可以計算字符的寬度
        int char_width = metrics.charWidth(c);
        line_width += char_width;

        // 如果當前字符的寬度加上之前字符串的已有寬度超出了海報的最大寬度妓布,則換行
        if (line_width >= max_width - char_width) {
            line_width = 0;
            sb.append("\n");
        }
    }
    return sb.toString();
}

假如文本是“沉默王二姻蚓,《Web 全棧開發(fā)進階之路》作者;一個不止寫代碼的程序員匣沼,還寫有趣有益的文字狰挡,給不喜歡嚴肅的你。”我們來通過 makeLineFeed() 方法試驗一下加叁。

Font font = new Font("微軟雅黑", Font.PLAIN, 28);
FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);

String zh = "沉默王二倦沧,《Web 全棧開發(fā)進階之路》作者;一個不止寫代碼的程序員它匕,還寫有趣有益的文字展融,給不喜歡嚴肅的你。";

String[] rows = makeLineFeed(zh, metrics, 600).split("\n");
for (int i = 0; i < rows.length; i++) {
    System.out.println(rows[i]);
}

其結(jié)果如下所示豫柬。

沉默王二告希,《Web 全棧開發(fā)進階之路》作者;
一個不止寫代碼的程序員轮傍,還寫有趣有益的文字
暂雹,給不喜歡嚴肅的你。

第四步创夜,將自動換行后的文本在海報背景上打印杭跪。

這里需要用到 FontDesignMetricsgetHeight() 方法獲取每行文本的高度。對照下面的示意圖驰吓,理解 height 的具體高度涧尿。

// 自動換行后的文本
String zhWrap = FontUtil.makeLineFeed(graphics2dPoster.getZh(), metrics, graphics2dPoster.getSuitableWidth());

// 拆分行
String[] zhWraps = zhWrap.split("\n");

// 將每一行在海報背景上打印
for (int i = 0; i < zhWraps.length; i++) {
    graphics2dPoster.addCurrentY(metrics.getHeight());
    graphics2d.drawString(zhWraps[i], MARGIN, graphics2dPoster.getCurrentY());
}

此時的海報效果如下圖所示。

可以看得出檬贰,文字帶有很強的鋸齒感姑廉,怎么消除呢?

graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

如果英語不好的話翁涤,看起來這段代碼會很吃力桥言。ANTIALIASING 單詞的意思就是“消除混疊現(xiàn)象,消除走樣葵礼,圖形保真”号阿。

07、利用 Graphics2D 在海報上打印英文

英文和中文最大的不同在于鸳粉,換行的單位不再是單個字符扔涧,而是整個單詞。

第一步届谈,根據(jù)當前字體下每個英文單詞的寬度枯夜,以及海報可容納的最大文本寬度,對文本進行換行艰山。

public static String makeEnLineFeed(String en, FontDesignMetrics metrics, int max_width) {
    // 每個單詞后追加空格
    char space = ' ';
    int spaceWidth = metrics.charWidth(space);

    // 按照空格對英文文本進行拆分
    String[] words = en.split(String.valueOf(space));
    // 利用 StringBuilder 對字符串進行修改
    StringBuilder sb = new StringBuilder();
    // 每行文本的寬度
    int len = 0;

    for (int i = 0; i < words.length; i++) {
        String word = words[i];

        int wordWidth = metrics.stringWidth(word);
        // 疊加當前單詞的寬度
        len += wordWidth;

        // 超出最大寬度湖雹,進行換行
        if (len > max_width) {
            sb.append("\n");
            sb.append(word);
            sb.append(space);

            // 下一行的起始寬度
            len = wordWidth + spaceWidth;
        } else {
            sb.append(word);
            sb.append(space);

            // 多了一個空格
            len += spaceWidth;
        }
    }
    return sb.toString();
}

假如文本是“Fear can hold you prisoner. Hope can set you free. It takes a strong man to save himself, and a great man to save another.”我們來通過 makeEnLineFeed() 方法試驗一下。

Font font = new Font("微軟雅黑", Font.PLAIN, 28);
FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);

String en = "Fear can hold you prisoner. Hope can set you free. It takes a strong man to save himself, and a great man to save another.";
String[] rows = makeEnLineFeed(en, metrics, 600).split("\n");
for (int i = 0; i < rows.length; i++) {
    System.out.println(rows[i]);
}

其結(jié)果如下所示曙搬。

Fear can hold you prisoner. Hope can set
you free. It takes a strong man to save
himself, and a great man to save another.

第三步摔吏,將自動換行后的文本在海報背景上打印汤踏。

// 設(shè)置封面圖和下方中文之間的距離
graphics2dPoster.addCurrentY(20);

Graphics2D graphics2d = graphics2dPoster.getGraphics2d();
graphics2d.setColor(new Color(157, 157, 157));

FontDesignMetrics metrics = FontDesignMetrics.getMetrics(graphics2d.getFont());
String enWrap = FontUtil.makeEnLineFeed(graphics2dPoster.getEn(), metrics, graphics2dPoster.getSuitableWidth());
String[] enWraps = enWrap.split("\n");
for (int i = 0; i < enWraps.length; i++) {
    graphics2dPoster.addCurrentY(metrics.getHeight());
    graphics2d.drawString(enWraps[i], MARGIN, graphics2dPoster.getCurrentY());
}

此時的海報效果如下圖所示。

07舔腾、利用 Graphics2D 在海報上繪制個人專屬二維碼

有了前面繪制海報封面的經(jīng)驗,繪制二維碼就變得輕而易舉了搂擦。

// 二維碼
File qrcodeFile = FileUtil.read("qrcode_", ".jpg", "default_qrcodeimg.jpg");
qrcodeFile.deleteOnExit();

BufferedImage qrcodeImage = ImageIO.read(qrcodeFile);
// 二維碼起始坐標
int qrcode_x = bgImage.getWidth() - qrcodeImage.getWidth() - MARGIN;
int qrcode_y = bgImage.getHeight() - qrcodeImage.getHeight() - MARGIN;
graphics2dPoster.getGraphics2d().drawImage(qrcodeImage, qrcode_x, qrcode_y, qrcodeImage.getWidth(),
        qrcodeImage.getHeight(), null);

此時的海報效果如下圖所示稳诚。

是不是感覺海報的左下角比較空白,整體的對稱性不夠自然瀑踢,那就在左下角追加一些二維碼的描述文本吧扳还。

graphics2d.setColor(new Color(71, 71, 71));
Font font = new Font(USE_FONT_NAME, Font.PLAIN, 22);
graphics2d.setFont(font);
FontDesignMetrics metrics = FontDesignMetrics.getMetrics(graphics2d.getFont());

graphics2d.drawString("沉默王二", MARGIN, bgImage.getHeight() - MARGIN - metrics.getHeight() * 2);
graphics2d.drawString("一個幽默的程序員", MARGIN, bgImage.getHeight() - MARGIN - metrics.getDescent());

此時的海報效果如下圖所示。

08橱夭、使用 Swing 構(gòu)建圖形化界面

Swing 是一個用于 Java GUI 編程(圖形界面設(shè)計)的工具包(類庫)氨距;換句話說,Java 之所以可以用來開發(fā)帶界面的 PC 軟件棘劣,就是因為 Swing 的存在俏让。

Swing 使用純粹的 Java 代碼來模擬各種控件,沒有使用本地操作系統(tǒng)的內(nèi)在方法茬暇,所以 Swing 是跨平臺的首昔。也正是因為 Swing 的這種特性,人們通常把 Swing 控件稱為輕量級控件糙俗。

Eclipse 默認是不支持可視化的 Swing 編程的勒奇,但 Eclipse 的插件市場上有這樣一個好插件——WindowBuilder,使用它可以大幅度地降低開發(fā)難度巧骚,迅速地提升開發(fā)效率赊颠。

下載地址:https://marketplace.eclipse.org/content/windowbuilder

可直接拖拽到 Eclipse 進行安裝,如下圖劈彪。

注意竣蹦,Eclipse 的版本要求為:

2018-09 (4.9), Photon (4.8), Oxygen (4.7), Neon (4.6), 2018-12 (4.10), 2019-03 (4.11)

拖拽到 Eclipse 后的效果如下:

安裝完成后,會提醒你重啟 Eclipse粉臊。

溫馨提示:安裝的過程大約持續(xù) 3 分鐘的時間草添,中間可能會失敗,重試幾次即可扼仲。不用擔心远寸,Eclipse 會智能地保存失敗前的進度。

安裝成功后屠凶,就可以使用可視化工具設(shè)計界面了驰后,如下圖所示:

09、將項目打成 jar 包發(fā)行

在將應用程序進行打包時矗愧,使用者都希望開發(fā)者只提供一個單獨的文件灶芝,而不是包含大量源碼的文件夾郑原。jar 包存在的目的正源于此。

將項目打成 jar 包也很簡單夜涕,在 Eclipse 中犯犁,可依次右鍵項目→Export→Runnable JAR file。你將會看到以下界面女器。

選擇 main 方法所在類酸役,指定導出目標,選擇 Copy required libraries 選項驾胆,點擊「Finish」即可涣澡。在指定的目錄下可找到生成的 jar 包文件。

10丧诺、運行 jar 包入桂,填寫必要信息后生成海報

如果電腦上安裝了 Java 的運行環(huán)境,雙擊該 jar 包文件就可以運行驳阎。運行后的界面抗愁,如下圖所示「阋可以填寫中文驹愚、英文、海報封面路徑劣纲,然后點擊按鈕生成海報逢捺。

PS:為了便于大家的學習,我已經(jīng)將源碼放在了 GitHub 上癞季,地址如下劫瞳。

https://github.com/qinggee/poster/tree/jinshanciba

趕快去 star 吧!

上一篇:Java :接口和抽象類绷柒,傻傻分不清

下一篇:Java 生成二維碼分享海報

謝謝大家的閱讀志于,原創(chuàng)不易,喜歡就隨手點個贊??废睦,這將是我最強的寫作動力伺绽。如果覺得文章對你有點幫助,還挺有趣嗜湃,就關(guān)注一下我的公眾號「沉默王二」奈应。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市购披,隨后出現(xiàn)的幾起案子杖挣,更是在濱河造成了極大的恐慌,老刑警劉巖刚陡,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惩妇,死亡現(xiàn)場離奇詭異株汉,居然都是意外死亡,警方通過查閱死者的電腦和手機歌殃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進店門乔妈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人氓皱,你說我怎么就攤上這事褒翰。” “怎么了匀泊?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朵你。 經(jīng)常有香客問我各聘,道長,這世上最難降的妖魔是什么抡医? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任躲因,我火速辦了婚禮,結(jié)果婚禮上忌傻,老公的妹妹穿的比我還像新娘大脉。我一直安慰自己,他們只是感情好水孩,可當我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布镰矿。 她就那樣靜靜地躺著,像睡著了一般俘种。 火紅的嫁衣襯著肌膚如雪秤标。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天宙刘,我揣著相機與錄音苍姜,去河邊找鬼。 笑死悬包,一個胖子當著我的面吹牛衙猪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播布近,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼垫释,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吊输?” 一聲冷哼從身側(cè)響起饶号,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎季蚂,沒想到半個月后茫船,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琅束,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年算谈,在試婚紗的時候發(fā)現(xiàn)自己被綠了涩禀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡然眼,死狀恐怖艾船,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情高每,我是刑警寧澤屿岂,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站鲸匿,受9級特大地震影響爷怀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜带欢,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一运授、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乔煞,春花似錦吁朦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至空骚,卻和暖如春锦溪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背府怯。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工刻诊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牺丙。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓则涯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親冲簿。 傳聞我的和親對象是個殘疾皇子粟判,可洞房花燭夜當晚...
    茶點故事閱讀 43,566評論 2 349