spring boot和solr整合

上周簡單的學習了solr的數據導入以及IK分詞器的安裝吞歼,今天學習一下solr的一些基本操作银受,以及和spring boot的整合践盼。在開始項目之前簡單的學習一下solr的一些查詢的操作

一、admin頁面簡單查詢

先在本地啟動solr宾巍,然后進入solr admin管理頁面咕幻。在這之前我修改了數據庫的數據,所以需要重新導入新的數據顶霞。選擇自定義的core肄程,點擊"Query",入下圖所示:

圖-1.png

簡單介紹一下幾個常用查詢參數:
"q":the quert string选浑,即查詢條件蓝厌,":"表示查詢所有內容,支持多個查詢條件鲜侥。
"fq":fiter query褂始,即過濾查詢,使用Filter Query可以充分利用FilterQuery Cache描函,提高檢索性能崎苗。個人覺得可能更多時候用來做區(qū)間查詢。
"sort":sort field or function asc|desc舀寓,即根據指定field進行排序胆数。
"start,rows"主要用來做分頁,這個應該都比較熟悉互墓。
"fl":field list必尼,就是查詢結果顯示field,多個field用逗號分割篡撵。
"df" default search field判莉,默認查詢field。
Raw Query Parameters:就是所有的查詢條件育谬,只是查詢參數是在URL上而已券盅。
"wt": the wite type,即查詢結果的顯示形式膛檀,支持多種個數锰镀,json、xml咖刃、python泳炉、ruby、csv等嚎杨,最常用的可能還是json或者xml吧花鹅。
上面這個幾個都是比較差用的,查詢條件枫浙,其他的還有一些翠胰,就不再介紹了。
用一個簡單的需求來測試一下自脯,查詢條件:性別為male且地址為FR之景,年齡在30歲以下,結果按照年齡逆序排列膏潮,每頁只展示5條記錄,锻狗,輸出格式為json(默認):
圖-2.png

因為數據比較少,只有3條記錄焕参,所以分頁沒起到效果轻纪,注意一點的是:solr查詢的AND、OR叠纷、TO這些必須要大寫刻帚,自己用小寫發(fā)現沒起作用,查詢條件的格式一定邀注意涩嚣,不然就會出錯崇众,另外查詢條件也是支持通配符的("*"號)掂僵。這些常用的查詢還是比較簡單的,和sql也比較接近顷歌,這里就不過多介紹了锰蓬,建議自己動手練習一下。

二眯漩、spring boot整合solr

spring boot有關于solr的支持官方文檔芹扭, Spring Data JPA同樣也支持solr,自己有興趣可以嘗試一下赦抖,其實本質上是一樣的舱卡。我這次使用的是Spring Data Solr。
打開idea,創(chuàng)建一個spring boot項目,添加相關的依賴序仙,pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ypc</groupId>
    <artifactId>solrdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>solrdemo</name>
    <description>solr demo test</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-solr</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.2.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

接下來是項目的配置文件邦鲫,打開application.properties

server.port=9090
# 數據庫配置
spring.datasource.url=jdbc:postgresql://localhost:5432/springboot?useSSL=false&characterEncoding=utf8
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=123456

# mybatis 配置
mybatis.type-aliases-package=com.ypc.solrdemo.entity
mybatis.mapper-locations=classpath:mapper/*.xml

# solr配置
spring.data.solr.host=http://localhost:8983/solr/custom_core

spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

配置solr host的時候注意一定加上自己的core的名稱,不然會報500,錯誤,因為一個solr可以有多個core,服務器是沒辦法判斷你要使用那個core,所以必須顯示指定core的名稱蝴簇。
接下來是相關的controller service 以及mybatis的mapper和mapper.xml代碼,因為比較簡單這里就不再貼相關代碼了匆帚。
我這里主要是通過solr client熬词,簡單的做了一些CRUD操作:

1、單個查詢:

根據 id查詢吸重,先查詢solr互拾,如果solr沒用查詢到再查詢數據庫,并將結果添加到solr嚎幸;

2颜矿、查詢所有

也是先通過solr去查詢,如果查詢不到就去數據庫查詢嫉晶,然后將查詢結果添加到solr骑疆;

3、單個添加

先更新到數據庫替废,如果異常箍铭,則回滾,這里使用@Transactional注解椎镣,然后將添加到solr诈火,并提交,如果異常則回滾solr状答。

4冷守、根據id刪除

和單個添加一樣刀崖,如果數據庫事務失敗則回滾;成功教沾,則刪除solr里面的信息蒲跨,solr失敗同樣回滾译断。

5授翻、頁面搜索

通過一個搜索框,根據用戶搜索條件孙咪,查詢相關信息堪唐,并根據年齡排序,一個非常簡單的html頁面翎蹈。


一淮菠、根據Id查詢

根據id查詢方法有幾種,先說一種最簡單或者說最笨的方法荤堪,service代碼如下:

public User queryById(int id) {
        // 先通過solr查詢合陵,查詢不到查數據庫
        SolrQuery query = new SolrQuery();
        query.setQuery("id:" + id);
        User user = null;
        try {
            QueryResponse response = solrClient.query(query);
            SolrDocumentList documentList = response.getResults();
            if (!documentList.isEmpty()) {
                for (SolrDocument document:documentList) {
                    user = new User();
                    user.setId(id);
                    user.setAddress((String) document.get("address"));
                    user.setMobile((String) document.get("mobile"));
                    user.setUserName((String) document.get("userName"));
                    user.setAge((Integer) document.get("age"));
                    user.setDescription((String) document.get("description"));
                    LOGGER.info(">>>> query user from solr success <<<<");
                }
            } else {
                // 從數據庫查詢
                user = userMapper.queryById(id);
                if (user != null) {
                    solrClient.addBean(user,1000);
                }
                LOGGER.info(">>>> query user from database <<<<");
            }

        } catch (SolrServerException e) {
            LOGGER.error(e.getMessage(),e);
        } catch (IOException e) {
            LOGGER.error(e.getMessage(),e);
        }
        return user;
    }

這里query.setQuery("id:" + id);其實等同于query.set("q","id:" + id);也就是說使用solrClient查詢的時候設置的參數名稱和在admin管理頁面的查詢條件名稱是一樣的。當然也可以直接使用solrClient.getById(String.valueOf(id))這個方法澄阳,我為了更直觀的了解查詢時設置查詢參數拥知,因此使用了query.setQuery("id:" + id)方法。
查詢返回的結果是一個QueryResponse就是整個的查詢結果碎赢,包括header和結果兩部分低剔,如下圖:


圖-3.png

響應頭包括的內容有查詢狀態(tài)、查詢時間肮塞、參數襟齿;而結果里面是查詢到的數量,查詢起始數量枕赵,以及最終的查詢結果(這個結果是一個document list)猜欺。
先根據id查詢solr,獲取到SolrDocumentList拷窜,然后遍歷(雖然只有一個document)开皿,然后轉換成我們需要的實體對象。但是如果字段多的話在實體類User和SolrDocument之間轉換非常的不方便装黑,所以我們需要修改下項目的User類

@SolrDocument(solrCoreName = "custom_core")
public class User {

    @Id
    @Field
    private int id;
    @Field
    private String userName;
    @Field
    private String sex;
    @Field
    private String address;
    @Field
    private String description;
    @Field
    private int age;
    @Field
    private String mobile;
    @Field
    private String period;
    // 方法省略
}

可以在實體類上添加上@SolrDocument(solrCoreName = "custom_core")這個注解副瀑,表明這個實體類可以轉換成SolrDocument對象,此外一定不要忘了指定core的名稱恋谭。如果實體類屬性和solr的field對應不上糠睡,可以使用@Field(value="field名稱")注解,實現實體類和solr field之間的對應關系疚颊。
這樣在像solr添加User對象的時候就不需要手動將其轉換為SolrDocument了狈孔,這點在添加用戶時候我們再看⌒湃希現在的情況是我們查詢出來的是SolrDocument,而我們返回的User對象均抽,當然從功能上講直接返回SolrDocument好像也是可行的嫁赏。但是從邏輯來講,返回的應該就是User對象油挥。我自己看了一下好像不能直接轉換潦蝇,當然通過反射自己寫一個工具類方法也是可行的。我使用的是通過json深寥,其實solr查詢返回結果的形式攘乒,默認就是json,我只需要將SolrDocument顯示的轉成json惋鹅,然后在轉成User對象就可以了则酝,代碼如下:

@Override
    public User queryById(int id) {
        User user = null;
        try {
            SolrDocument solrDocument = solrClient.getById(String.valueOf(id));
            Gson gson = new Gson();
            // 方法1
            String solrString = gson.toJson(solrDocument);
            user = gson.fromJson(solrString,User.class);
            // 方法2
            Map<String,Object> map = solrDocument.getFieldValueMap();
            user = gson.fromJson(map.toString(),User.class);
            
            if (null == user) {
                user = userMapper.queryById(id);
                solrClient.addBean(user,1000);
            }

        } catch (SolrServerException e) {
            LOGGER.error(e.getMessage(),e);
        } catch (IOException e) {
            LOGGER.error(e.getMessage(),e);
        }
        return user;
    }

上面的方法中代碼明顯就簡單了不少,而且兩種方法都是可行的闰集,只是方法1先將SolrDocument轉成json串沽讹,然后再將json傳轉成對應的User對象;方法2值獲取SolrDocument的key vlaue值武鲁,然后轉成string后再轉成User對象爽雄。兩種方法都是可行的,但是我感覺方法2邀好一點洞坑,中間省去了對象轉json串這個步驟盲链,當然用其他方法也是可行的。

二迟杂、查詢所有

這個其實和單個查詢沒有上面區(qū)別刽沾,只是查詢條件設置成":"就可以了,代碼如下:

@Override
    public List<User> queryAll() {
        List<User> list = null;
        SolrQuery query = new SolrQuery();
        query.setQuery("*:*");
        query.setStart(0);
        query.setRows(20);
        try {
            QueryResponse response = solrClient.query(query);
            SolrDocumentList documentList = response.getResults();
            if (!documentList.isEmpty()) {
                Gson gson = new Gson();
                String listString = gson.toJson(documentList);
                list = gson.fromJson(listString, new TypeToken<List<User>>() {}.getType());
//              list = convertToModel(documentList);
                LOGGER.info(">>>> query user from solr success <<<<");
            } else {
                list = userMapper.queryAll();
                solrClient.addBeans(list);
                LOGGER.info(">>>> query user from database <<<<");
            }

        } catch (SolrServerException e) {
            LOGGER.error(e.getMessage(),e);
        } catch (IOException e) {
            LOGGER.error(e.getMessage(),e);
        }
        return list;
    }

查詢所有信息排拷,查出來結果是一個SolrDocumentList侧漓,可以遍歷出每個SolrDocument,并轉成User對象监氢,也可以直接轉成json串布蔗,然后直接轉成List<User>。另外是我設置的是值查詢前20個(實際沒有20個)浪腐,本來是想做一個分頁查詢的纵揍,但是前端沒處理好,所以就沒用分頁做查詢了议街。

三泽谨、單個添加

因為我沒用寫專門的form表單,所以就直接REST Client工具提交了用戶信息了,提交數據格式是json:controller和service如下:

    @ResponseBody
    @PostMapping("/insert")
    public Map<String, Object> insertUser(@RequestBody User user) {
        Map<String,Object> result = userService.insertAndUpdate(user);
        return result;
    }
    // service
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> insertAndUpdate(User user) {
        Map<String,Object> result = new HashMap<>();
        result.put("success",false);
        // 返回結果表示受影響的數據條數吧雹,而不是id值
        int insert = userMapper.insertUser(user);
        if (insert != 1) {
            throw new RuntimeException(" >>>> insert user to database failed,the return value should be 1,but result is:" + insert + " <<<<");
        }
        // 插入或者更新solr數據
        try {
            UpdateResponse response = solrClient.addBean(user,1000);
            int staus = response.getStatus();
            if (staus != 0) {
                LOGGER.error(">>>> update solr document failed <<<<");
                solrClient.rollback();
                result.put("message","insert user to solr failed");
                return result;
            }
        } catch (SolrServerException e) {
            LOGGER.error(e.getMessage(),e);
            result.put("message",e.getMessage());
            return result;
        } catch (IOException e) {
            LOGGER.error(e.getMessage(),e);
            result.put("message",e.getMessage());
            return result;
        }

        result.put("message","insert user to solr success");
        result.put("success",true);
        return result;
    }

solr的添加和更新是一個操作骨杂,添加則添加新數據,更新覆蓋原有數據雄卷,其他都是一樣的搓蚪,我這里使用了@Transactional注解,當我插入數據庫的數據記錄不等于1時丁鹉,自己拋出一個運行時異常(當然自己定義一個專門的統一異常更好些)妒潭,數據回滾。在更新solr的數據時鳄炉,solrClient.addBean(user,1000)杜耙;即在1秒鐘內自動提交事務搜骡,然后根據狀態(tài)值判斷是否成功拂盯,如果不成功則回滾,但是這一點自己有點疑問记靡,就是如果在1秒內提交事務失敗谈竿,是否需要顯式回滾?摸吠?這個還需要自己再查下相關的資料空凸。個人感覺應該是不需要的,但是為了保險起見寸痢,顯示的回滾了呀洲,如果有了解的朋友希望能指點一下。其實這里自己還遇到一個問題啼止,就是數據庫插入數據后道逗,插入的對象如何返回id的問題,因為數據庫設置的是id自增主鍵献烦,而返回結果是影響的行數滓窍,即插入的數據記錄數,那么如何獲取新插入的數據id呢巩那?開始自己想的是查詢最后一次插入的id值吏夯,即用:SELECT currval('user_id_seq');類似于mysql的SELECT LAST_INSERT_ID()。但是自己還想獲取受影響的記錄數以判斷插入是否成功即横,以便回滾噪生。后來網上找了些資料,修改mapper.xml文件如下:

<insert id="insertUser" parameterType="com.ypc.solrdemo.entity.User" useGeneratedKeys="true" keyProperty="id">
        insert into t_user (username,age,mobile,address,sex,description)
        values (#{userName},#{age},#{mobile},#{address},#{sex},#{description})
</insert>

這樣插入數據成功后东囚,對應的id值會直接映射到對象中跺嗽,mybatis官方文檔。useGeneratedKeys屬性只對插入和修改有效,這告訴MyBatis使用JDBC getGeneratedKeys方法來檢索數據庫內部生成的主鍵值(像MySQL或pgsql自動增量字段)抛蚁,默認值為false陈醒;keyProperty用來標識MyBatis將設置getGeneratedKeys返回的鍵值映射到哪個屬性上。

四瞧甩、根據id刪除

和根據id插入基本一樣钉跷,代碼如下:

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> deleteUserById(int id) {
        Map<String,Object> result = new HashMap<>();
        result.put("success",false);
        // 先刪除數據庫,再更新solr
        int delete = userMapper.deleteById(id);
        if (delete != 1) {
            throw new RuntimeException(">>>> delete user failed ,user id=" + id + " <<<<");
        }

        try {
            UpdateResponse response = solrClient.deleteById(String.valueOf(id),1000);
            int status = response.getStatus();
            if (status != 0) {
                LOGGER.error(">>>> delete user from solr failed ,user id=" + id + " <<<<");
                solrClient.rollback();
                result.put("message","delete user to solr failed");
                return result;
            }
        } catch (SolrServerException e) {
            LOGGER.error(e.getMessage(),e);
            result.put("message",e.getMessage());
            return result;
        } catch (IOException e) {
            LOGGER.error(e.getMessage(),e);
            result.put("message",e.getMessage());
            return result;
        }

        result.put("success",true);
        result.put("message","delete user success");
        return result;
    }
五肚逸、搜索框搜索

自己寫了一個簡單的html頁面爷辙,作為搜索框使用,用戶輸入條件朦促,后臺根據條件查詢膝晾,并且自己設置了一個簡單的根據年齡排序的功能,比較low务冕,但是功能上可以使用血当,點擊年齡會進行順序或者逆序排序。


圖-4.png

搜索功能禀忆,也就是根據多個字段進行OR查詢臊旭,這里其實可以設置查詢根據年齡順序或是逆序,但是為了后面排序的功能箩退,這里就省略了离熏,代碼如下:

    @Override
    public List<User> queryByCondition(String de) {
        List<User> list = null;
        // 關鍵字模糊查詢
        SolrQuery query = new SolrQuery();
        String nameLike = "userName:*" + de +  "*";
        String desLike = " OR description:*" + de+  "*";
        String sexLike = " OR sex:*" + de +  "*";
        String addLike = " OR address:*" + de +  "*";
        query.set("q",nameLike + desLike + sexLike + addLike);

        query.setStart(0);
        query.setRows(20);
        try {
            QueryResponse response = solrClient.query(query);
            SolrDocumentList documentList = response.getResults();
            if (!documentList.isEmpty()) {
                Gson gson = new Gson();
                String listString = gson.toJson(documentList);
                list = gson.fromJson(listString, new TypeToken<List<User>>() {}.getType());
            } else {
                LOGGER.info(">>>> no result returned by the filter query word: " + de + " <<<<");
            }

        } catch (SolrServerException e) {
            LOGGER.error(e.getMessage(),e);
        } catch (IOException e) {
            LOGGER.error(e.getMessage(),e);
        }
        return list;
    }

自己簡單測試一下,結果如下戴涝,可見年齡順序既不是順序也不是逆序:

圖-5.png

而根據年齡排序滋戳,只需要在上面代碼添加排序的規(guī)則就可以了,另外就是ajax傳遞參數時將是順序還是逆序這個標識位傳遞給后臺即可啥刻,這里就不再貼代碼了奸鸯。
好了,以上就是這次spring boot整合solr的一個很簡單的demo郑什,其實單機的solr操作并不復雜府喳,只需要掌握常用的一些操作即可。今天主要學習就是通過admin管理頁面熟悉常用的查詢操作和spring boot整合后solrClient的使用蘑拯,此外就是實體類和SolrDocument之間轉換的問題钝满。當然自己的demo很簡單,如果是復雜的場景就又不太一樣了申窘。
這次的代碼已經更新到我的github弯蚜,如果文章或代碼中有什么問題,歡迎指正L攴ā碎捺!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子收厨,更是在濱河造成了極大的恐慌晋柱,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诵叁,死亡現場離奇詭異雁竞,居然都是意外死亡,警方通過查閱死者的電腦和手機拧额,發(fā)現死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門碑诉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侥锦,你說我怎么就攤上這事进栽。” “怎么了恭垦?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵快毛,是天一觀的道長。 經常有香客問我署照,道長祸泪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任建芙,我火速辦了婚禮,結果婚禮上懂扼,老公的妹妹穿的比我還像新娘禁荸。我一直安慰自己,他們只是感情好阀湿,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布赶熟。 她就那樣靜靜地躺著,像睡著了一般陷嘴。 火紅的嫁衣襯著肌膚如雪映砖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天灾挨,我揣著相機與錄音邑退,去河邊找鬼。 笑死劳澄,一個胖子當著我的面吹牛地技,可吹牛的內容都是我干的。 我是一名探鬼主播秒拔,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼莫矗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起作谚,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤三娩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妹懒,有當地人在樹林里發(fā)現了一具尸體尽棕,經...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年彬伦,在試婚紗的時候發(fā)現自己被綠了滔悉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡单绑,死狀恐怖回官,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情搂橙,我是刑警寧澤歉提,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站区转,受9級特大地震影響苔巨,放射性物質發(fā)生泄漏。R本人自食惡果不足惜废离,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一侄泽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜻韭,春花似錦悼尾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至俯画,卻和暖如春析桥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背艰垂。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工泡仗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人材泄。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓沮焕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拉宗。 傳聞我的和親對象是個殘疾皇子峦树,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內容

  • 關于Mongodb的全面總結 MongoDB的內部構造《MongoDB The Definitive Guide》...
    中v中閱讀 31,934評論 2 89
  • 【陽光男孩 張文哲 9月30日 星期日 晴 堅持原創(chuàng)分享第334天】 它是一個圓形的辣辫,外面是一層粉色的巧克...
    張文哲閱讀 517評論 4 3
  • 風輕云淡,瞬逝經年魁巩,雁飛舞急灭,換了人間。 桃花易冷谷遂,三月揚州葬馋,琴繞夢,惜緣如弦肾扰。 清風聽雨畴嘶,一路相陪,蕭瑟處集晚,雅閣興...
    喜在心間閱讀 181評論 0 0
  • 第一次見渡渡鳥媽媽窗悯,如果要總結,我會用幸福和感動偷拔。 昨天晚上臨睡前我才跟娃提起:明天媽媽要帶你去見渡渡鳥媽...
    安娜_2018閱讀 1,098評論 3 0