FastDFS服務(wù)器集群部署和集成客戶端到SpringBoot

FastDFS是一個(gè)開源的輕量級分布式文件系統(tǒng),它對文件進(jìn)行管理,功能包括:文件存儲、文件同步、文件訪問(文件上傳、文件下載)等,解決了大容量存儲和負(fù)載均衡的問題,同時(shí)也能做到在集群環(huán)境下一臺機(jī)子上傳文件,同時(shí)該組下的其他節(jié)點(diǎn)下也備份了上傳的文件。做分布式系統(tǒng)開發(fā)時(shí),其中要解決的一個(gè)問題就是圖片、音視頻、文件共享的問題和數(shù)據(jù)備份,分布式文件系統(tǒng)正好可以解決這個(gè)需求。FastDFS的服務(wù)主要有兩個(gè)角色Tracker和Storage,Tracker服務(wù)用于負(fù)責(zé)調(diào)度storage節(jié)點(diǎn)與client通信,在訪問上起負(fù)載均衡的作用,和記錄storage節(jié)點(diǎn)的運(yùn)行狀態(tài),是連接client和storage節(jié)點(diǎn)的樞紐,Storage用于保存文件

  • FastDFS集群部署

    • 整體部署模塊圖


      FastDFS部署示意圖.png
    • 環(huán)境準(zhǔn)備

      • 名稱 描述
        centos系統(tǒng)版本 6.9
        libfatscommon FastDFS分離出的一些公用函數(shù)包
        FastDFS FastDFS主程序
        fastdfs-nginx-module FastDFS和nginx的關(guān)聯(lián)模塊
        nginx nginx1.15.5
      • 安裝編譯環(huán)境

      yum install git gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl-devel wget vim -y
      
      • 磁盤安裝路徑說明

        說明 位置
        FastDFS所以安裝包安裝位置 /usr/local/src
        tracker數(shù)據(jù) /data/fdfs/tracker
        Storage數(shù)據(jù) /data/fdfs/Storage
        配置文件路徑 /etc/fdfs
    • 安裝libfatscommon

      • 下載libfatscommon

      • 解壓、安裝

        unzip libfastcommon-master.zip
        cd libfastcommon-master
        ./make.sh && ./make.sh install #編譯安裝
        
    • 安裝FastDFS

      • 下載FastDFS

      • 解壓、安裝

        unzip fastdfs-master.zip
        cd fastdfs-master
        ./make.sh && ./make.sh install #編譯安裝
        cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf
        cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
        cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf #客戶端文件,測試用
        cp /usr/local/src/fastdfs/conf/http.conf /etc/fdfs/ #供nginx訪問使用
        cp /usr/local/src/fastdfs/conf/mime.types /etc/fdfs/ #供nginx訪問使用
        
      etc目錄下fdfs目錄.png
  • 安裝fastdfs-nginx-module

    • 下載fastdfs-nginx-module

    • 解壓、安裝

      unzip fastdfs-nginx-module-master.zip
      cp /usr/local/src/fastdfs-nginx-module-master/src/mod_fastdfs.conf /etc/fdfs #復(fù)制配置文件到fdfs目錄
      
  • 安裝nginx

    • 下載nginx

    • 解壓、安裝

      tar -zxvf nginx-1.15.5.tar.gz
      cd nginx-1.15.5
      #添加fastdfs-nginx-module模塊
      ./configure --add-module=/usr/local/src/fastdfs-nginx-module-master/src/ 
      make && make install #編譯安裝
      
  • FastDFS集群部署配置

    • tracker配置
    #服務(wù)器ip為 xxx.xxx.78.12, xxx.xxx.78.13
    vim /etc/fdfs/tracker.conf
    #需要修改的內(nèi)容如下
    port=22122  # tracker服務(wù)器端口(默認(rèn)22122,一般不修改)
    base_path=/data/fdfs/tracker #存儲日志和數(shù)據(jù)的根目錄
    
    tracker配置.png
  • Storage配置

 ```
 vim /etc/fdfs/storage.conf
 #需要修改的內(nèi)容如下
 port=23000  # storage服務(wù)端口(默認(rèn)23000,一般不修改)
 base_path=/data/fdfs/storage  # 數(shù)據(jù)和日志文件存儲根目錄
 store_path0=/data/fdfs/storage  # 第一個(gè)存儲目錄
 tracker_server=xxx.xxx.78.12:22122  # 服務(wù)器1
 tracker_server=xxx.xxx.78.13:22122  # 服務(wù)器2
 http.server_port=8888  # http訪問文件的端口(默認(rèn)8888,看情況修改,和nginx中保持一致)
 ```
- client配置

 ```
 vim /etc/fdfs/client.conf
 #需要修改的內(nèi)容如下
 base_path=/home/moe/dfs
 tracker_server=xxx.xxx.78.12:22122  # 服務(wù)器1
 tracker_server=xxx.xxx.78.13:22122  # 服務(wù)器2
 ```
- 配置nginx訪問
 ```
 vim /etc/fdfs/mod_fastdfs.conf
 #需要修改的內(nèi)容如下
 tracker_server=xxx.xxx.78.12:22122  # 服務(wù)器1
 tracker_server=xxx.xxx.78.13:22122  # 服務(wù)器2
 url_have_group_name=true
 store_path0=/data/fdfs/storage
 
 #配置nginx.config
 vim /usr/local/nginx/conf/nginx.conf
 #添加如下配置
 server {
 listen       8888;    ## 該端口為storage.conf中的http.server_port相同
 server_name  localhost;
 location ~/group[0-9]/ {
    ngx_fastdfs_module;
 }
 ......
 ......
 error_page   500 502 503 504  /50x.html;
 location = /50x.html {
 root   html;
 }
 }
 ```
  • 啟動(dòng)服務(wù)、測試

    啟動(dòng)之前我們還需要在防火墻開通端口
    vim  /etc/sysconfig/iptables
    -A INPUT -m state --state NEW -m tcp -p tcp --dport 22122 -j ACCEPT
    -A INPUT -m state --state NEW -m tcp -p tcp --dport 23000 -j ACCEPT
    -A INPUT -m state --state NEW -m tcp -p tcp --dport 8888 -j ACCEPT
    
    service iptables restart #重啟防火墻
    
    防火墻端口.png
  • 每個(gè)服務(wù)的啟動(dòng)、關(guān)閉和重啟操作

    #tracker
    /etc/init.d/fdfs_trackerd start #啟動(dòng)tracker服務(wù)
    /etc/init.d/fdfs_trackerd restart #重啟動(dòng)tracker服務(wù)
    /etc/init.d/fdfs_trackerd stop #停止tracker服務(wù)
    chkconfig fdfs_trackerd on #自啟動(dòng)tracker服務(wù)
    
    #storage
    /etc/init.d/fdfs_storaged start #啟動(dòng)storage服務(wù)
    /etc/init.d/fdfs_storaged restart #重動(dòng)storage服務(wù)
    /etc/init.d/fdfs_storaged stop #停止動(dòng)storage服務(wù)
    chkconfig fdfs_storaged on #自啟動(dòng)storage服務(wù)
    
    #nginx
    /usr/local/nginx/sbin/nginx #啟動(dòng)nginx
    /usr/local/nginx/sbin/nginx -s reload #重啟nginx
    /usr/local/nginx/sbin/nginx -s stop #停止nginx
    ```![檢測集群1.png](https://upload-images.jianshu.io/upload_images/2174557-566537008c57d1e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    ![查看運(yùn)行的服務(wù).png](https://upload-images.jianshu.io/upload_images/2174557-e12afac6ca295a2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
  • 檢測集群

    # 會顯示會有幾臺storage服務(wù)器,有2臺就會顯示 Storage 1-Storage 2的詳細(xì)信息
    /usr/bin/fdfs_monitor /etc/fdfs/storage.conf
    
    檢測集群1.png

    檢測集群2.png
  • 圖片上傳測試

    #上傳成功返回 文件訪問 ID
    # fdfs_upload_file 客戶端配置文件      上傳文件路徑
    fdfs_upload_file /etc/fdfs/client.conf /data/test.png
    
    上傳文件成功.png
  • 測試文件訪問
    http://xxx.xxx.78.12/group1/M00/00/00/rB9ODFvXuSiAWBYBAALSAkm_6RQ360.png
    http://xxx.xxx.78.13/group1/M00/00/00/rB9ODFvXuSiAWBYBAALSAkm_6RQ360.png
    
    測試nginx默認(rèn)端口80 訪問剛剛上傳的文件,兩個(gè)地址都能訪問通一個(gè)文件,達(dá)到數(shù)據(jù)備份目的。

至此,F(xiàn)astDFS服務(wù)器部署完成

  • FastDFS客戶端集成到SpringBoot

    • 首先根據(jù)官方源碼提示,我們先下載源碼使用maven編譯成jar包放到公司maven私服(Nexus),或者你本地的maven私服(也有其他ant等方式,具體請查看github)
      FastDFS-java-client-SDK源碼下載地址

      #編譯jar包(解壓下載的FastDFS-java-client-SDK源碼,使用mvn命令需要先有maven環(huán)境)
      mvn clean install
      
      
      編譯打包FastDFS-java-client.png

      fastdfs-client-java打包成功.png
    • maven項(xiàng)目pom.xml中添加依賴

      <dependency>
        <groupId>org.csource</groupId>
        <artifactId>fastdfs-client-java</artifactId>
        <version>1.27-SNAPSHOT</version>
      </dependency>
      
    • 接下來我們在項(xiàng)目resources目錄下添加fdfs_client.conf文件

      connect_timeout = 30
      network_timeout = 30
      charset = UTF-8
      http.tracker_http_port = 80
      http.anti_steal_token = no
      http.secret_key = 123456
      #前面配置的集群tracker服務(wù)器地址
      tracker_server = xxx.xxx.78.12:22122
      tracker_server = xxx.xxx.78.13:22122
      
    • 寫一個(gè)上傳文件對象類

      /**
       * @Author: maoqitian
       * @Date: 2018/10/26 0026 17:57
       * @Description: FastDFS 文件類
       */
       public class FastDFSFileEntity {
       //文件名稱
       private String name;
       //內(nèi)容
       private byte[] content;
       //文件類型
       private String ext;
       //md5值
       private String md5;
       //作者
       private String author;
       public FastDFSFileEntity(String name, byte[] content, String ext, String height,
                        String width, String author) {
         super();
         this.name = name;
         this.content = content;
         this.ext = ext;
         this.author = author;
        }
      
        public FastDFSFileEntity(String name, byte[] content, String ext) {
         super();
         this.name = name;
         this.content = content;
         this.ext = ext;
         }
      
         public String getName() {
         return name;
         }
      
         public void setName(String name) {
         this.name = name;
         }
      
         public byte[] getContent() {
         return content;
         }
      
         public void setContent(byte[] content) {
         this.content = content;
         }
      
         public String getExt() {
         return ext;
         }
      
         public void setExt(String ext) {
         this.ext = ext;
         }
      
         public String getMd5() {
         return md5;
         }
      
         public void setMd5(String md5) {
         this.md5 = md5;
         }
      
         public String getAuthor() {
         return author;
         }
      
         public void setAuthor(String author) {
         this.author = author;
         }
        }
      
    • 編寫FastDFS操作類,主要是加載初始化配置Tracker服務(wù)器,文件上傳,下載,刪除等操作工具類

       /**
       * @Author: maoqitian
       * @Date: 2018/10/29 0029 9:30
       * @Description: FastDFS 操作類
       */
       public class FastDFSClient {
      
        private static org.slf4j.Logger logger = LoggerFactory.getLogger(FastDFSClient.class);
        //雙重守護(hù)單例
        private static volatile FastDFSClient mInstance;
      
        /**
         * 加載配置信息
         **/
        static {
         try {
             String filePath=new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
             ClientGlobal.init(filePath);
         }catch (Exception e){
             logger.error("FastDFS Client Init Fail!",e);
         }
        }
      
        private FastDFSClient(){
      
        }
      
         public static FastDFSClient getInstance(){
         if(mInstance == null){
            synchronized (FastDFSClient.class){
                if(mInstance == null){
                    mInstance=new FastDFSClient();
                }
            }
         }
         return mInstance;
        }
      
        /**
         * @Author maoqitian
         * @Description 上傳文件
         * @Date 2018/10/29 0029 9:42
         * @Param [fastDFSFileEntity]
         * @return java.lang.String[]
         **/
         public  String[] upload(FastDFSFileEntity file){
         logger.info("File Name: " + file.getName() + "File Length:" + file.getContent().length);
      
         NameValuePair[] metalist=new NameValuePair[1];
      
         metalist[0]=new NameValuePair("author",file.getAuthor());
      
         long startTime = System.currentTimeMillis();
         String[] uploadResults= null;
         StorageClient storageClient=null;
         try {
      
             storageClient=getTrackerClient();
             uploadResults = storageClient.upload_file(file.getContent(),file.getExt(),metalist);
         }catch (IOException e){
             logger.error("IO Exception when uploadind the file:"+file.getName(),e);
         }
         catch (Exception e){
             logger.error("Non IO Exception when uploadind the file:"+file.getName(),e);
         }
         logger.info("upload_file time used:" + (System.currentTimeMillis() - startTime) + " ms");
         if(uploadResults==null && storageClient!=null){
             logger.error("upload file fail, error code:" + storageClient.getErrorCode());
         }
         String groupName = uploadResults[0];
         String remoteFileName = uploadResults[1];
      
         logger.info("upload file successfully!!!" + "group_name:" + groupName + ", remoteFileName:" + " " + remoteFileName);
         return uploadResults;
         }
      
      
         public  FileInfo getFile(String groupName, String remoteFileName) {
         try {
             StorageClient storageClient = getTrackerClient();
             return storageClient.get_file_info(groupName, remoteFileName);
         } catch (IOException e) {
             logger.error("IO Exception: Get File from Fast DFS failed", e);
         } catch (Exception e) {
             logger.error("Non IO Exception: Get File from Fast DFS failed", e);
         }
         return null;
         }
      
         public  InputStream downFile(String groupName, String remoteFileName) {
         try {
             StorageClient storageClient = getTrackerClient();
             byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
             InputStream ins = new ByteArrayInputStream(fileByte);
             return ins;
         } catch (IOException e) {
             logger.error("IO Exception: Get File from Fast DFS failed", e);
         } catch (Exception e) {
             logger.error("Non IO Exception: Get File from Fast DFS failed", e);
         }
         return null;
         }
         /**
          * @Author maoqitian
          * @Description
          * @Date 2018/10/31 0031 11:19
          * @Param [remoteFileName]
          * @return int -1 失敗 0成功
          **/
         public int deleteFile(String remoteFileName)
             throws Exception {
         StorageClient storageClient = getTrackerClient();
         int i = storageClient.delete_file("group1", remoteFileName);
         logger.info("delete file successfully!!!" + i);
         return i;
         }
      
         public StorageServer[] getStoreStorages(String groupName)
             throws IOException {
         TrackerClient trackerClient = new TrackerClient();
         TrackerServer trackerServer = trackerClient.getConnection();
         return trackerClient.getStoreStorages(trackerServer, groupName);
         }
      
          public ServerInfo[] getFetchStorages(String groupName,
                                                 String remoteFileName) throws IOException {
         TrackerClient trackerClient = new TrackerClient();
         TrackerServer trackerServer = trackerClient.getConnection();
         return trackerClient.getFetchStorages(trackerServer, groupName, remoteFileName);
         }
      
         public  String getTrackerUrl() throws IOException {
         return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":"+ClientGlobal.getG_tracker_http_port()+"/";
         }
      
         /**
          * @Author maoqitian
          * @Description 獲取 StorageClient
          * @Date 2018/10/29 0029 10:33
          * @Param []
          * @return org.csource.fastdfs.StorageClient
          **/
         private StorageClient getTrackerClient() throws IOException{
         TrackerServer trackerServer=getTrackerServer();
         StorageClient storageClient=new StorageClient(trackerServer,null);
         return storageClient;
         }
         /**
          * @Author maoqitian
          * @Description 獲取 TrackerServer
          * @Date 2018/10/29 0029 10:34
          * @Param []
          * @return org.csource.fastdfs.TrackerServer
          **/
         private  TrackerServer getTrackerServer() throws IOException {
         TrackerClient trackerClient=new TrackerClient();
         TrackerServer trackerServer = trackerClient.getConnection();
         return trackerServer;
       }
      
      
    • Controller編寫,接收請求并上傳文件返回文件訪問路徑(這里寫一個(gè)文件上傳的例子,其他文件下載,刪除等功能可根據(jù)自己需求進(jìn)行編寫)

         /**
      * @Author maoqitian
      * @Description  上傳文件
      * @Date 2018/10/30 0030 15:07
      * @Param [file]
      * @return com.gxxmt.common.utils.ResultApi
      **/
       @RequestMapping("/upload")
       public ResultApi upload(@RequestParam("file") MultipartFile file) throws Exception {
         if (file.isEmpty()) {
             throw new RRException("上傳文件不能為空");
         }
         String url;
         //此處域名獲取可以根據(jù)自需求編寫
         String domainUrl = OSSFactory.build().getDomainPath();
         logger.info("配置的域名為"+domainUrl);
         if (StringUtils.isNotBlank(domainUrl)){
             url = uploadFile(file,domainUrl);
             return ResultApi.success.put("url",url);
         }else {
             return ResultApi.error("域名配置為空,請先配置對象存儲域名");
         }
       }
       
      /**
      * @Author maoqitian
      * @Description 上傳文件到 FastDFS
      * @Date 2018/10/29 0029 11:11
      * @Param [file]
      * @Param [domainName] 域名
      * @return path 文件訪問路徑
      **/
      public String uploadFile(MultipartFile file,String domainName) throws IOException {
      
         String[] fileAbsolutePath={};
         String fileName=file.getOriginalFilename();
         String ext=fileName.substring(fileName.lastIndexOf(".")+1);
         byte[] file_buff=null;
         InputStream inputStream = file.getInputStream();
         if(inputStream!=null){
             int available = inputStream.available();
             file_buff=new byte[available];
             inputStream.read(file_buff);
         }
         inputStream.close();
         FastDFSFileEntity fastDFSFileEntity=new FastDFSFileEntity(fileName,file_buff,ext);
         try {
             fileAbsolutePath=FastDFSClient.getInstance().upload(fastDFSFileEntity);
             logger.info(fileAbsolutePath.toString());
         }catch (Exception e){
             logger.error("upload file Exception!",e);
             throw new RRException("文件上傳出錯(cuò)"+e);
         }
         if(fileAbsolutePath == null){
             logger.error("upload file failed,please upload again!");
             throw new RRException("文件上傳失敗,請重新上傳");
         }
         String path=domainName+fileAbsolutePath[0]+ "/"+fileAbsolutePath[1];
         return path;
      }
      
  • 測試一下該方法,上傳一個(gè)圖片

    • 由日志打印我們可以看出圖片已經(jīng)上傳成功


      FastDFS-java-client 上傳圖片成功.png
    • 測試訪問上傳的圖片


      測試上傳的圖片是否可以進(jìn)行訪問.png

到此,F(xiàn)astDFS服務(wù)器集群部署和集成客戶端到SpringBoot中已經(jīng)完成,以后我們就可以愉快的使用FastDFS服務(wù)保存我們的圖片等并備份。如果文章中有寫得不對的地方,請給我留言指出,大家一起學(xué)習(xí)進(jìn)步。如果覺得我的文章給予你幫助,也請給我一個(gè)喜歡和關(guān)注。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,677評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,643評論 2 380

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