Solr之查询页面,索引,SolrJ

1 Solr查询

1.1 查询页面

1.1.1 基本查询

参数 意义
q 查询的关键字,此参数最为重要,例如,q=id:1,默认为q=:,
fl 指定返回哪些字段,用逗号或空格分隔,注意:字段区分大小写,例如,fl= id,title,sort
start 返回结果的第几条记录开始,一般分页用,默认0开始
rows 指定返回结果最多有多少条记录,默认值为 10,配合start实现分页
sort 排序方式,例如id desc 表示按照 “id” 降序
wt (writer type) 指定输出格式,有 xml, json, php等
fq(filter query) 过虑查询,提供一个可选的筛选器查询。返回在q查询符合结果中同时符合的fq条件的查询结果,例如:q=id:1&fq=sort:[1 TO 5],找关键字id为1 的,并且sort是1到5之间的
df 默认的查询字段,一般默认指定。
qt (query type) 指定那个类型来处理查询请求,一般不用指定,默认是standard。
indent 返回的结果是否缩进,默认关闭,用 indent=true
version 查询语法的版本,建议不使用它,由服务器指定默认值。

1.1.2 Solr检索运算符

符号 意义
: 指定字段查指定值,如返回所有值:
? 表示单个任意字符的通配
* 表示多个任意字符的通配(不能在检索的项开始使用*或者?符号)
~ 表示模糊检索,如检索拼写类似于”roam”的项这样写:roam~将找到形如foam和roams的单词;roam~0.8,检索返回相似度在0.8以上的记录。
+ 存在操作符,要求符号”+”后的项必须在文档相应的域中存在
() 用于构成子查询
[] 包含范围检索,如检索某时间段记录,包含头尾,date:[201507 TO 201510]
{} 不包含范围检索,如检索某时间段记录,不包含头尾date:{201507 TO 201510}

1.1.3 高亮

符号 意义
h1 是否高亮,hl=true,表示采用高亮
hl.fl 设定高亮显示的字段,用空格或逗号隔开的字段列表。要启用某个字段的highlight功能,就得保证该字段在schema中是stored。如果该参数未被给出,那么就会高亮默认字段 standard handler会用df参数,dismax字段用qf参数。你可以使用星号去方便的高亮所有字段。如果你使用了通配符,那么要考虑启用hl.requiredFieldMatch选项。
hl.requireFieldMatch 如果置为true,除非用hl.fl指定了该字段,查询结果才会被高亮。它的默认值是false。
hl.usePhraseHighlighter 如果一个查询中含有短语(引号框起来的)那么会保证一定要完全匹配短语的才会被高亮。
hl.highlightMultiTerm 如果使用通配符和模糊搜索,那么会确保与通配符匹配的term会高亮。默认为false,同时hl.usePhraseHighlighter要为true。
hl.fragsize 返回的最大字符数。默认是100.如果为0,那么该字段不会被fragmented且整个字段的值会被返回。

1.1.4 分组

1.1.4.1 分组(Field Facet)

facet参数字段必须被索引,facet=onfacet=true

符号 意义
facet.field 分组的字段
facet.prefix 表示Facet字段前缀
facet.limit Facet字段返回条数
facet.offict 开始条数,偏移量,它与facet.limit配合使用可以达到分页的效果
facet.mincount Facet字段最小count,默认为0
facet.missing 如果为on或true,那么将统计那些Facet字段值为null的记录
facet.sort 表示 Facet 字段值以哪种顺序返回 .格式为true(count)false(index,lex)true(count)表示按照 count 值从大到小排列,false(index,lex) 表示按照字段值的自然顺序 (字母 , 数字的顺序 ) 排列 . 默认情况下为 true(count)

1.1.4.2 分组(Date Facet)

对日期类型的字段进行 Facet,Solr 为日期字段提供了更为方便的查询统计方式 .注意 ,Date Facet的字段类型必须是 DateField( 或其子类型 ). 需要注意的是 , 使用 Date Facet时 , 字段名 , 起始时间 , 结束时间 , 时间间隔这 4 个参数都必须提供 。

符号 意义
facet.date 该参数表示需要进行 Date Facet 的字段名 , 与 facet.field 一样 , 该参数可以被设置多次 , 表示对多个字段进行 Date Facet.
facet.date.start 起始时间 , 时间的一般格式为 ” 2015-12-31T23:59:59Z”, 另外可以使用 ”NOW”,”YEAR”,”MONTH” 等等 ,
facet.date.end 结束时间
facet.date.gap 时间间隔,如果 start 为 2015-1-1,end 为 2016-1-1,gap 设置为 ”+1MONTH” 表示间隔1 个月 , 那么将会把这段时间划分为 12 个间隔段 .
facet.date.hardend 表示 gap 迭代到 end 时,还剩余的一部分时间段,是否继续去下一个间隔. 取值可以为 true

1.2 创建索引文件

1.2.1 使用Post上传文件

1.2.1.1 Linux下使用

Solr 包含一个简单的命令行工具,即 Post 工具(bin/post 工具),用于将各种类型的内容发布到 Solr 服务器。
bin/post 工具是一个 Unix shell 脚本;对于 Windows使用情况不支持

1.2.1.1.1 索引XML

将文件扩展名为 .xml 的所有文档添加到命名为 gettingstarted 的集合或核心中。

bin/post -c gettingstarted *.xml

将所有带有文件扩展名为 .xml 的文档添加到在端口 8984 上运行的 Solr 上的 gettingstarted 集合/内核。

bin/post -c gettingstarted -p 8984 *.xml

发送 XML 参数以从 gettingstarted 中删除文档。

bin/post -c gettingstarted -d '<delete><id>42</id></delete>'
1.2.1.1.2 索引 CSV

将所有 CSV 文件索引到 gettingstarted

bin/post -c gettingstarted *.csv

将制表符分隔的文件索引到 gettingstarted:

bin/post -c signals -params "separator=%09" -type text/csv data.tsv

内容类型(-type)参数是需要将文件视为正确的类型,否则将被忽略,并记录一个警告,因为它不知道 .tsv 文件是什么类型的内容。该 CSV 处理器支持 separator 参数,并通过使用 -params 设置传递。

1.2.1.1.3 索引 JSON

将所有 JSON 文件编入索引 gettingstarted。

bin/post -c gettingstarted *.json
1.2.1.1.4 索引丰富的文档(PDF、Word、HTML等)

PDF 文件索引到 gettingstarted。

bin/post -c gettingstarted a.pdf

自动检测文件夹中的内容类型,并对其进行递归扫描,以便为编入 gettingstarted 的文档进行索引。

bin/post -c gettingstarted afolder/

自动检测文件夹中的内容类型,但将其限制为 PPT 和 HTML 文件并将其索引到 gettingstarted。

bin/post -c gettingstarted -filetypes ppt,html afolder/
1.2.1.1.5 索引到受密码保护的 Solr(基本身份验证)

索引一个 PDF 作为用户 solr 使用密码 SolrRocks:

bin/post -u solr:SolrRocks -c gettingstarted a.pdf

1.2.1.2 Windows下使用

由于bin/post 目前仅作为 Unix shell 脚本存在,但是它将其工作委派给了一个具有跨平台能力的Java 程序。该 SimplePostTool 可以直接在支持的环境,包括 Windows上运行。

1.2.1.2.1 SimplePostTool

bin/post 脚本目前委托给一个名为 SimplePostTool 的独立 Java 程序。

捆绑到可执行 JAR 中的这个工具可以直接运行 java -jar example/exampledocs/post.jar。可以直接发送命令到 Solr 服务器。

java -jar example/exampledocs/post.jar -h
SimplePostTool version 5.0.0
Usage: java [SystemProperties] -jar post.jar [-h|-] [<file|folder|url|arg> [<file|folder|url|arg>...]]

索引文件示例:
上传csv文件到指定核心实例 : java -Dc=test_core -Dtype=text/csv -jar example/exampledocs/post.jar example/exampledocs/books.csv
[图片上传失败...(image-b5a110-1681978125139)]
如果是上传xml文件,则xml示例如下所示

<add>
<doc>
  <field name="id">USD</field>
  <field name="name">One Dollar</field>
  <field name="manu">Bank of America</field>
  <field name="manu_id_s">boa</field>
  <field name="cat">currency</field>
  <field name="features">Coins and notes</field>
  <field name="price_c">1,USD</field>
  <field name="inStock">true</field>
</doc>
<doc> ... </doc>
</add>

1.2.2 Dataimport页面报错

当选中一个核心实例时,Dataimport页面报错

The solrconfig.xml file for this index does not have an operational DataImportHandler defined!

1.2.2.1 修改solrconfig.xml

修改方法在当前核心实例的下操作,比如选中test_core,在路径server\solr\test_core\conf下打开文件solrconfig.xml,在里边加入如下内容,放置的位置你可以放到其他requestHandler 旁边:

<requestHandler name="/dataimport" class="solr.DataImportHandler"> 
      <lst name="defaults"> 
        <str name="config">data-config.xml</str> 
      </lst> 
    </requestHandler>

1.2.2.2 data-config.xml

data-config.xml文件配置在和solrconfig.xml同样位置即可

<dataConfig>
    <dataSource name="jdbcDataSource" type="JdbcDataSource" 
    driver="oracle.jdbc.driver.OracleDriver"
    url="jdbc:oracle:thin:@localhost:1521:orcl" 
    user="test" password="test"/>
    <document>
        <entity dataSource="jdbcDataSource" name="country"  
        query="select * from test" >
            <field column="ID" name="id"></field>
            <field column="SORT" name="sort"></field>
        </entity>
    </document>
  </dataConfig>

dataconfig的结构不是一成不变的,entityfield元素中的属性是随意的,这主要取决于processortransformer
以下是entity的默认属性:

  • name(必需的):name是唯一的,用以标识entity
  • processor:只有当datasource不是RDBMS时才是必需的。默认值是SqlEntityProcessor
  • transformer:转换器将会被应用到这个entity上
  • pkentity的主键,它是可选的,但使用“增量导入”的时候是必需。它跟schema.xml中定义的uniqueKey没有必然的联系,但它们可以相同。
  • rootEntity:默认情况下,document元素下就是根实体了,如果没有根实体的话,直接在实体下面的实体将会被看做跟实体。对于根实体对应的数据库中返回的数据的每一行,solr都将生成一个document

SqlEntityProcessor的属性

  • query (required) :sql语句
  • deltaQuery : 只在“增量导入”中使用
  • parentDeltaQuery : 只在“增量导入”中使用
  • deletedPkQuery : 只在“增量导入”中使用
  • deltaImportQuery : (只在“增量导入”中使用) . 如果这个存在,那么它将会在“增量导入”中导入phase时代替query产生作用。

数据源也可以配置在solrconfig.xml

<dataSource type="JdbcDataSource" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost/dbname" user="db_username" password="db_password"/>
  • driver(必需的):jdbc驱动名称
  • url(必需的):jdbc链接
  • user:用户名
  • password:密码
  • type 指定了实现的类型。它是可选的。默认的实现是JdbcDataSource
  • namedatasources的名字,当有多个datasources时,可以使用name属性加以区分
    其他的属性都是随意的,根据你使用的DataSource实现而定。
  • 多数据源
    一个配置文件可以配置多个数据源。增加一个dataSource元素就可以增加一个数据源了。name属性可以区分不同的数据源。如果配置了多于一个的数据源,那么要注意将name配置成唯一的
<dataSource type="JdbcDataSource" name="ds-1" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://db1-host/dbname" user="db_username" password="db_password"/>

<dataSource type="JdbcDataSource" name="ds-2" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://db2-host/dbname" user="db_username" password="db_password"/>

然后这样使用 ..

<entity name="one" dataSource="ds-1" ...>
   ..
</entity>
<entity name="two" dataSource="ds-2" ...>
   ..
</entity>

1.2.2.3 迁移jar包

在解压后的solr压缩包dist文件夹内的包(如下所示)迁移到解压后的server\solr-webapp\webapp\WEB-INF\lib文件夹内

image.png

2 第三方工具 SolrJ

使用一个第三方工具 SolrJ,使用 Java 语言来把数据加入到索引里

2.1 pom.xml


<dependency>
    <groupId>org.apache.solr</groupId>
    <artifactId>solr-solrj</artifactId>
    <version>9.0.0</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>
<dependency>
  <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.8.0</version>
</dependency>

2.2 模板数据和实体

10001,房屋卫士自流平美缝剂瓷砖地砖专用双组份真瓷胶防水填缝剂镏金色,品质建材,398.00,上海,540785126782
10002,艾瑞泽手工大号小号调温热熔胶枪玻璃胶枪硅胶条热溶胶棒20W-100W,品质建材,21.80,山东青岛,24727352473
10003,HIGOLD/悍高 水槽双槽 厨房洗菜盆304不锈钢加厚拉丝手工水槽套餐,品质建材,2198.00,广东佛山,40972207020

实体类,每个字段上都有 @Field 注解,用来告诉 Solr 这些和 core里的字段对应

import lombok.Data;
import org.apache.solr.client.solrj.beans.Field;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
    @Field
    int id;
    @Field
    String name;
    @Field
    String category;
    @Field
    float price;
    @Field
    String place;
    @Field
    String code;
}

2.3 实体工具类

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class ProductUtil {

    public static void main(String[] args) throws IOException{
        String fileName = "D:\\Users\\admin\\Desktop\\140k_products.txt";

        List<Product> products = file2List(fileName);

        System.out.println(products.size());
    }

    public static List<Product> file2List(String fileName) throws IOException{
        File file = new File(fileName);
        List<String> strings = FileUtils.readLines(file, "UTF-8");
        List<Product> productList = new ArrayList<>();
        for(String str:strings){
            String[] split = str.split(",");
            Product product = new Product(Integer.parseInt(split[0]),split[1],split[2],Float.parseFloat(split[3]),split[4],split[5]);
            productList.add(product);
        }
        return productList;
    }
}

2.4 读取工具类

import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.beans.DocumentObjectBinder;
import org.apache.solr.client.solrj.impl.Http2SolrClient;
import org.apache.solr.common.SolrInputDocument;

import java.util.List;

public class SolrUtil {

    public static SolrClient client;
    private static String url;
    static {
        url="http://localhost:8983/solr/test001";
        client=new Http2SolrClient.Builder(url).build();
    }

    public static <T> boolean batchSaveOrUpdate(List<T> entitites) throws Exception{
        DocumentObjectBinder binder = new DocumentObjectBinder();
        int total = entitites.size();
        int count=0;
        for(T t:entitites){
            SolrInputDocument doc = binder.toSolrInputDocument(t);
            client.add(doc);
            System.out.printf("添加数据到索引中,总共要添加 %d 条记录,当前添加 第 %d %n",total,++count);
        }
        client.commit();
       // client.close();
        return true;
    }
}

main方法调用

public static void main(String[] args) throws Exception {
    String fileName = "D:\\Users\\admin\\Desktop\\140k_products.txt";
    List<Product> products = ProductUtil.file2List(fileName);
    SolrUtil.batchSaveOrUpdate(products);
}

2.5 查询分析

2.5.1 分页查询

public static QueryResponse query(String keywords,int startOfPage,int numberOfPage) throws Exception{
   SolrQuery query = new SolrQuery();
   query.setStart(startOfPage);
   query.setRows(numberOfPage);

   query.setQuery(keywords);
   QueryResponse rsp = client.query(query);
  // client.close();
   return rsp;
}

main方法调用

import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;

import java.util.Collection;

public class PageQuery {
    public static void main(String[] args) throws Exception{
        QueryResponse query = SolrUtil.query("name:手机", 0, 10);
        SolrDocumentList results = query.getResults();
        System.out.println("累计找到的条数:"+results.getNumFound());
        if(!results.isEmpty()){
        //  先 打印 求出列名
            Collection<String> fieldNameList = results.get(0).getFieldNames();
            for(String filedName:fieldNameList){
                System.out.print(filedName+"\t");
            }
            System.out.println();
        }
        for (SolrDocument result:results){
            Collection<String> fieldNames = result.getFieldNames();
            // 根据字段名 求 值
            for(String field:fieldNames){
                System.out.print(result.get(field)+"\t");
            }
            System.out.println();
        }
    }
}

2.5.2 高亮查询

public static void queryHighLight(String keywords) throws Exception{
        SolrQuery query = new SolrQuery();
        query.setStart(0);//开始页数
        query.setRows(10);//每页显示条数
        query.setQuery(keywords);// 设置查询关键字
        query.setHighlight(true);// 开启高亮
        query.addHighlightField("name"); // 高亮字段
        query.setHighlightSimplePre("<span style='color:red'>");// 高亮单词的前缀
        query.setHighlightSimplePost("</span>"); // 高亮单词的后缀

        query.setHighlightFragsize(100);//摘要最长100个字符
        QueryResponse resp = client.query(query); //查询
        client.close();
        NamedList<Object> response = resp.getResponse();//获取高亮字段name相应结果
        NamedList<?> highlighting = (NamedList<?>) response.get("highlighting");
        for (int i = 0; i < highlighting.size(); i++) {
            System.out.println(highlighting.getName(i) + ":" + highlighting.getVal(i));
        }

        //获取查询结果
        SolrDocumentList results = resp.getResults();
        for (SolrDocument result : results) {
            System.out.println(result.toString());
        }

    }

main调用

public static void main(String[] args) throws Exception{
    SolrUtil.queryHighLight("name:手机");
}

2.6 修改更新删除索引文件

//增加或者更新索引
public static <T> boolean saveOrUpdate(T entity) throws Exception{
    DocumentObjectBinder binder = new DocumentObjectBinder();
    SolrInputDocument doc = binder.toSolrInputDocument(entity);
    client.add(doc);
    client.commit();  
    return true;
}
//删除索引
public static <T> boolean deleteById(String id) throws Exception{
    try {
        client.deleteById(id);
        client.commit();
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
    return true;
}

main方法调用

import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;

import java.util.Collection;

public class TestSolr4j {

    public static void main(String[] args) throws Exception{
        String keyword = "name:手机";
        System.out.println("修改之前");
        query(keyword);

        Product product = new Product();
        product.setId(51173);
        product.setName("修改后的手机");
        SolrUtil.<Product>saveOrUpdate(product);
        System.out.println("修改之后");
        query(keyword);

        SolrUtil.deleteById("51173");
        System.out.println("删除之后");
        query(keyword);
    }


    public static void query(String keyword) throws Exception{
        QueryResponse response = SolrUtil.query(keyword, 0, 10);
        SolrDocumentList resultList = response.getResults();
        System.out.println("累计找到的条数:"+resultList.getNumFound());
        if(!resultList.isEmpty()){
            Collection<String> fieldNameList = resultList.get(0).getFieldNames();
            for(String filedName:fieldNameList){
                System.out.print(filedName+"\t");
            }
            System.out.println();
        }
        for (SolrDocument result:resultList){
            Collection<String> fieldNames = result.getFieldNames();
            for(String field:fieldNames){
                System.out.print(result.get(field)+"\t");
            }
            System.out.println();
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 1、Schema介绍 schema 是什么? Schema:模式,是集合/内核中字段的定义,让solr知道集合/内...
    WinnXUDONG阅读 3,554评论 0 0
  • 今天总结Solr 的查询参数,还是那句话,只有先明白了solr的基础内容和查询语法,后续学习solr 的操作,都是...
    凌烟阁主5221阅读 505评论 0 0
  • 1. Solr 官网 搜索引擎是指一个庞大的互联网资源数据库,如网页,新闻组,程序,图像等。它有助于在万维网上定位...
    _凌浩雨阅读 2,182评论 0 4
  • 一、Solr介绍 1.简介:Solr是基于Lucene的面向企业搜索的web应用,Sole是一个独立的企业级搜...
    故人望曲江阅读 829评论 0 1
  • Apache Solr中的主要配置文件如下 Solr.xml - 它是包含Solr Cloud相关信息,此文件是在...
    n_xy阅读 895评论 0 0