VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • Solr 入门实战(3)--Solr 配置文件

在 Solr 中有几个重要的配置文件,它们对 Solr 的运行起着重要作用;本文简要介绍下这些配置文件,文中使用到的软件版本:Solr 8.9.0。

1、solr.xml(全局)

solr.xml 文件为整个 Solr 服务器实例指定配置选项,用于设置一些全局配置;solr.xml 默认是在 server/solr 下。

复制代码
<solr>
  <int name="maxBooleanClauses">${solr.max.booleanClauses:1024}</int>
  <str name="sharedLib">${solr.sharedLib:}</str>
  <str name="allowPaths">${solr.allowPaths:}</str>

  <solrcloud>
    <str name="host">${host:}</str>
    <int name="hostPort">${solr.port.advertise:0}</int>
    <str name="hostContext">${hostContext:solr}</str>

    <bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool>

    <int name="zkClientTimeout">${zkClientTimeout:30000}</int>
    <int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:600000}</int>
    <int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
    <str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
    <str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
  </solrcloud>

  <shardHandlerFactory name="shardHandlerFactory"
    class="HttpShardHandlerFactory">
    <int name="socketTimeout">${socketTimeout:600000}</int>
    <int name="connTimeout">${connTimeout:60000}</int>
    <str name="shardsWhitelist">${solr.shardsWhitelist:}</str>
  </shardHandlerFactory>

  <metrics enabled="${metricsEnabled:true}"/>
</solr>
复制代码

a、<solrcloud> 元素定义了与 SolrCloud 相关的参数:

host:主机地址。
hostPort:主机端口,默认 8983。
hostContext:上下文路径,默认 solr。
leaderVoteWait:当 SolrCloud 启动时,假定有节点宕机了,每个 Solr 节点等待该节点时间。
leaderConflictResolveWait:当尝试为分片选择 Leader 时,该属性设置了副本等待解决冲突状态的最长时间,在执行重启时可能会发生状态信息中的临时冲突,尤其是在重启监视器的节点时。
genericCoreNodeNames:如果为 TRUE,则节点名称不是基于节点的地址,而是基于标识核心的通用名称。当一台不同的机器接管服务的时候,核心的东西就会更容易理解。
zkClientTimeout 连接 Zookeeper 的超时时间。
zkHost:Zookeeper 地址。
distribUpdateSoTimeout:集群内部更新的 socket 连接超时时间。
distribUpdateConnTimeout:集群内部更新的连接超时时间。
zkCredentialsProvider & zkACLProvider:用于设置 Zookeeper 访问控制。

b、<shardHandlerFactory> 元素用于定义分片处理程序,可以在这里定义自定义的分片处理程序。Solr 提供的默认和唯一的分片处理程序是 HttpShardHandlerFactory,它有如下参数:

socketTimeout:群集内查询和管理类请求的读取超时时间,默认与 <solrcloud> 元素中的 distribUpdateSoTimeout 相同。
connTimeout:群集内查询和管理类请求的连接超时时间,默认与 <solrcloud> 元素中的 distribUpdateConnTimeout 相同。
shardsWhitelist:在非集群模型下执行分布式搜索的 Solr 服务白名单列表。
urlScheme:用于分布式搜索的 URL 方案。
maxConnectionsPerHost:每台主机允许的最大连接数,默认为10000。
corePoolSize:服务请求线程池的初始大小,默认为 0。
maximumPoolSize:服务请求线程池的最大数量,默认无限制。
maxThreadIdleTime:在被kill之前,空闲线程在队列中持续存在的时间,以秒为单位,默认为5秒。
sizeOfQueue:如果线程池切换到后备队列,该队列的最大大小是多少,默认使用同步队列。
fairnessPolicy:用于配置线程池是否有利于吞吐量的公平性,默认值为 false 以支持吞吐量。
replicaRouting:配置访问副本的路由策略。

2、core.properties(每个 core)

core.properties 为每个 core 定义特定的属性,例如名称、核心所属的集合、模式的位置等。core.properties 可以在核心目录的任何位置,但是只会识别目录深度最浅的文件。

#Written by CorePropertiesLocator
#Mon Sep 06 06:45:09 UTC 2021
name=new_core

name:核心的名称,在使用 CoreAdminHandler 运行命令时,使用此名称来引用核心。
config:核心的配置文件名称,默认是 solrconfig.xml。
schema:核心的模式文件名,默认为 managed-schema。
dataDir:核心的数据目录(存储索引),绝对路径或相对于 instanceDir 的路径;默认是 data。
configSet:用于配核心的已定义配置集的名称。
properties:核心的属性文件的名称,该值可以是绝对路径名或相对于 instanceDir 的路径。
transient:如果为 true,则在 Solr 到达到 transientCacheSize 大小时,卸载核心;如果未指定(默认 false),那么会按照最近最少使用的顺序来卸载。在 SolrCloud 模式下不建议开启。
loadOnStartup:如果为 true,则在 Solr 启动时会加载核心,默认为 true,在 SolrCloud 模式下不建议设置为 false。
coreNodeName:仅在 SolrCloud 中使用,这是承载此副本的节点的唯一标识符。默认情况下 coreNodeName 会自动生成,但通过显式设置此属性允许您手动分配新的核心来替换现有的副本。例如,通过新计算机上进行备份恢复来更换发生硬件故障的计算机时,这可能很有用。
ulogDir:核心更新日志的绝对或相对目录。(SolrCloud模式下)
shard:核心所属的分片。(SolrCloud模式下)
collection:核心所属的集合。(SolrCloud模式下)
roles:SolrCloud 的未来参数或一种用户标记节点仅供自己使用的方法。

3、solrconfig.xml(每个 core)

solrconfig.xml 文件是影响 Solr 本身参数最多的配置文件;可以配置的重要功能有:

a、请求处理程序,处理对 Solr 的请求,例如向索引添加文档的请求或查询文档的请求。
b、监听器,“监听”特定查询相关事件的过程;监听器可用于触发特殊代码的执行,例如调用一些常见查询来预热缓存
c、Request Dispatcher,用于管理 HTTP 通信
d、Admin Web 界面
e、复制和副本相关的参数

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!--
 Licensed to the Apache Software Foundation (ASF) under one or more
 contributor license agreements.  See the NOTICE file distributed with
 this work for additional information regarding copyright ownership.
 The ASF licenses this file to You under the Apache License, Version 2.0
 (the "License"); you may not use this file except in compliance with
 the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
-->

<!--
     For more details about configurations options that may appear in
     this file, see http://wiki.apache.org/solr/SolrConfigXml.
-->
<config>
  <!-- In all configuration below, a prefix of "solr." for class names
       is an alias that causes solr to search appropriate packages,
       including org.apache.solr.(search|update|request|core|analysis)

       You may also specify a fully qualified Java classname if you
       have your own custom plugins.
    -->

  <!-- Controls what version of Lucene various components of Solr
       adhere to.  Generally, you want to use the latest version to
       get all bug fixes and improvements. It is highly recommended
       that you fully re-index after changing this setting as it can
       affect both how text is indexed and queried.
  -->
  <luceneMatchVersion>8.9.0</luceneMatchVersion>

  <!-- <lib/> directives can be used to instruct Solr to load any Jars
       identified and use them to resolve any "plugins" specified in
       your solrconfig.xml or schema.xml (ie: Analyzers, Request
       Handlers, etc...).

/schemaFactory
...skipping

       These require that the schema is both managed and mutable, by
       declaring schemaFactory as ManagedIndexSchemaFactory, with
       mutable specified as true.

       See http://wiki.apache.org/solr/GuessingFieldTypes
    -->
  <updateProcessor class="solr.UUIDUpdateProcessorFactory" name="uuid"/>
  <updateProcessor class="solr.RemoveBlankFieldUpdateProcessorFactory" name="remove-blank"/>
  <updateProcessor class="solr.FieldNameMutatingUpdateProcessorFactory" name="field-name-mutating">
    <str name="pattern">[^\w-\.]</str>
    <str name="replacement">_</str>
  </updateProcessor>
  <updateProcessor class="solr.ParseBooleanFieldUpdateProcessorFactory" name="parse-boolean"/>
  <updateProcessor class="solr.ParseLongFieldUpdateProcessorFactory" name="parse-long"/>
  <updateProcessor class="solr.ParseDoubleFieldUpdateProcessorFactory" name="parse-double"/>
  <updateProcessor class="solr.ParseDateFieldUpdateProcessorFactory" name="parse-date">
    <arr name="format">
      <str>yyyy-MM-dd['T'[HH:mm[:ss[.SSS]][z</str>
      <str>yyyy-MM-dd['T'[HH:mm[:ss[,SSS]][z</str>
      <str>yyyy-MM-dd HH:mm[:ss[.SSS]][z</str>
      <str>yyyy-MM-dd HH:mm[:ss[,SSS]][z</str>
      <str>[EEE, ]dd MMM yyyy HH:mm[:ss] z</str>
      <str>EEEE, dd-MMM-yy HH:mm:ss z</str>
      <str>EEE MMM ppd HH:mm:ss [z ]yyyy</str>
    </arr>
  </updateProcessor>
  <updateProcessor class="solr.AddSchemaFieldsUpdateProcessorFactory" name="add-schema-fields">
    <lst name="typeMapping">
      <str name="valueClass">java.lang.String</str>
      <str name="fieldType">text_general</str>
      <lst name="copyField">
        <str name="dest">*_str</str>
        <int name="maxChars">256</int>
      </lst>
      <!-- Use as default mapping instead of defaultFieldType -->
      <bool name="default">true</bool>
    </lst>
    <lst name="typeMapping">
      <str name="valueClass">java.lang.Boolean</str>
      <str name="fieldType">booleans</str>
    </lst>
    <lst name="typeMapping">
      <str name="valueClass">java.util.Date</str>
      <str name="fieldType">pdates</str>
    </lst>
    <lst name="typeMapping">
      <str name="valueClass">java.lang.Long</str>
      <str name="valueClass">java.lang.Integer</str>
      <str name="fieldType">plongs</str>
    </lst>
    <lst name="typeMapping">
      <str name="valueClass">java.lang.Number</str>
      <str name="fieldType">pdoubles</str>
    </lst>
  </updateProcessor>

  <!-- The update.autoCreateFields property can be turned to false to disable schemaless mode -->
  <updateRequestProcessorChain name="add-unknown-fields-to-the-schema" default="${update.autoCreateFields:true}"
           processor="uuid,remove-blank,field-name-mutating,parse-boolean,parse-long,parse-double,parse-date,add-schema-fields">
    <processor class="solr.LogUpdateProcessorFactory"/>
    <processor class="solr.DistributedUpdateProcessorFactory"/>
    <processor class="solr.RunUpdateProcessorFactory"/>
  </updateRequestProcessorChain>

  <!-- Deduplication

       An example dedup update processor that creates the "id" field
       on the fly based on the hash code of some other fields.  This
       example has overwriteDupes set to false since we are using the
       id field as the signatureField and Solr will maintain
       uniqueness based on that anyway.

    -->
  <!--
     <updateRequestProcessorChain name="dedupe">
       <processor class="solr.processor.SignatureUpdateProcessorFactory">
         <bool name="enabled">true</bool>
         <str name="signatureField">id</str>
         <str name="fields">name,features,cat</str>
         <str name="signatureClass">solr.processor.Lookup3Signature</str>
       </processor>
       <processor class="solr.LogUpdateProcessorFactory" />
       <processor class="solr.RunUpdateProcessorFactory" />
     </updateRequestProcessorChain>
    -->

  <!-- Response Writers

       http://wiki.apache.org/solr/QueryResponseWriter

       Request responses will be written using the writer specified by
       the 'wt' request parameter matching the name of a registered
       writer.

       The "default" writer is the default and will be used if 'wt' is
       not specified in the request.
    -->
  <!-- The following response writers are implicitly configured unless
       overridden...
    -->
  <!--
     <queryResponseWriter name="xml"
                          default="true"
                          class="solr.XMLResponseWriter" />
     <queryResponseWriter name="json" class="solr.JSONResponseWriter"/>
     <queryResponseWriter name="python" class="solr.PythonResponseWriter"/>
     <queryResponseWriter name="ruby" class="solr.RubyResponseWriter"/>
     <queryResponseWriter name="php" class="solr.PHPResponseWriter"/>
     <queryResponseWriter name="phps" class="solr.PHPSerializedResponseWriter"/>
     <queryResponseWriter name="csv" class="solr.CSVResponseWriter"/>
     <queryResponseWriter name="schema.xml" class="solr.SchemaXmlResponseWriter"/>
    -->

  <queryResponseWriter name="json" class="solr.JSONResponseWriter">
    <!-- For the purposes of the tutorial, JSON responses are written as
     plain text so that they are easy to read in *any* browser.
     If you expect a MIME type of "application/json" just remove this override.
    -->
    <str name="content-type">text/plain; charset=UTF-8</str>
  </queryResponseWriter>

  <!-- Query Parsers

       https://lucene.apache.org/solr/guide/query-syntax-and-parsing.html

       Multiple QParserPlugins can be registered by name, and then
       used in either the "defType" param for the QueryComponent (used
       by SearchHandler) or in LocalParams
    -->
  <!-- example of registering a query parser -->
  <!--
     <queryParser name="myparser" class="com.mycompany.MyQParserPlugin"/>
    -->

  <!-- Function Parsers

       http://wiki.apache.org/solr/FunctionQuery

       Multiple ValueSourceParsers can be registered by name, and then
       used as function names when using the "func" QParser.
    -->
  <!-- example of registering a custom function parser  -->
  <!--
     <valueSourceParser name="myfunc"
                        class="com.mycompany.MyValueSourceParser" />
    -->


  <!-- Document Transformers
       http://wiki.apache.org/solr/DocTransformers
    -->
  <!--
     Could be something like:
     <transformer name="db" class="com.mycompany.LoadFromDatabaseTransformer" >
       <int name="connection">jdbc://....</int>
     </transformer>

     To add a constant value to all docs, use:
     <transformer name="mytrans2" class="org.apache.solr.response.transform.ValueAugmenterFactory" >
       <int name="value">5</int>
     </transformer>

     If you want the user to still be able to change it with _value:something_ use this:
     <transformer name="mytrans3" class="org.apache.solr.response.transform.ValueAugmenterFactory" >
       <double name="defaultValue">5</double>
     </transformer>

      If you are using the QueryElevationComponent, you may wish to mark documents that get boosted.  The
      EditorialMarkerFactory will do exactly that:
     <transformer name="qecBooster" class="org.apache.solr.response.transform.EditorialMarkerFactory" />
    -->
</config>
复制代码

3.1、dataDir 和 directoryFactory

dataDir 用于指定索引数据的位置,默认情况下,Solr 将其索引数据存储在 ${core_home}/data 目录中,如果需要指定其他目录用于存储索引,可以在 core.properties 中配置 dataDir,或在 solrconfig.xml中配置 <dataDir> 参数。可以使用绝对路径或相对于 Solr 核心的 instanceDir 路径名来指定另一个目录。

directoryFactory 用于指定生成的目录类型。默认 solr.StandardDirectoryFactory 是基于文件系统的,并且试图为当前的 JVM 和平台选择最好的实现。可以通过指定 solr.MMapDirectoryFactory、solr.NIOFSDirectoryFactory 或 solr.SimpleFSDirectoryFactory 来强制使用特定的实现。solr.RAMDirectoryFactory 是基于内存的,不适用于复制。如果希望将索引存储在 HDFS 中,那么应该使用 solr.HdfsDirectoryFactory。

3.2、schemaFactory

托管模式

当 solrconfig.xml 中的 <schemaFactory> 文件没有显式声明时,Solr 隐式使用 ManagedIndexSchemaFactory,默认开启 “mutable”,且把 schema 的信息保存到 managed-schema 文件中。

<schemaFactory class="ManagedIndexSchemaFactory">
    <bool name="mutable">true</bool>
    <str name="managedSchemaResourceName">managed-schema</str>
</schemaFactory>

配置 ManagedIndexSchemaFactory,可以设置如下参数:

mutable 控制是否可以对模式数据进行更改。必须设置为 true,以允许使用 Schema API 进行编辑。
managedSchemaResourceName 可选参数,默认为“managed-schema”,为模式文件定义一个新的名称,该名称可以是除“schema.xml”以外的任何其他名称。

经典 schema.xml

另一种做法是显示配置 ClassicIndexSchemaFactory,ClassicIndexSchemaFactory 需要使用 schema.xml文件,且不允许在运行时对schema进行任何编程更改。schema.xml 必须手动编辑并且仅在加载 collection 时才会加载该文件

<schemaFactory class="ClassicIndexSchemaFactory"/>

两种模式的切换

1、从 schema.xml 到托管模式
修改 solrconfig.xml 指定使用 ManagedIndexSchemaFactory;一旦 Solr 重新启动,检测到 schema.xml 文件存在,但 managedSchemaResourceName 文件(即:“managed-schema”)不存在,现有的schema.xml 文件将被重命名为 schema.xml.bak,内容被重新写入 managed-schema 文件。
2、从托管模式到 schema.xml
a、重命名 managed-schema 文件为 schema.xml。
b、修改 solrconfig.xml,添加或编辑 <schemaFactory> 元素:<schemaFactory class="ClassicIndexSchemaFactory"/>
c、重新加载核心。

4、schema.xml/managed-schema(每个 core)

 该配置文件用于定义索引库的数据类型,同时指明某个类型的字段是不是要进行索引,是不是要进行保存到索引库里等等。类似传统数据库的 Schema。文件位于核心目录的 conf 文件夹中,该文件包含四类信息:uniqueKey、fieldType、field、dynamicField、copyField。

4.1、uniqueKey

用于定义唯一主键,必须且只能定义一个。在创建索引时必须指定唯一主键;唯一主键字段不可以是保留字段、复制字段,且不能分词。

<uniqueKey>id</uniqueKey>

4.2、fieldType

用于定义字段类型,可以包括以下四种类型的信息:

a、字段类型的名称(必须)。
b、一个实现类的名字(必须)。
c、如果一个字段的类型是 TextField,则需要有分析说明。
d、字段类型属性取决于实现类,一些属性可能是强制性的。

复制代码
<fieldType name="string" class="solr.StrField" sortMissingLast="true" docValues="true"/>
<fieldType name="strings" class="solr.StrField" sortMissingLast="true" docValues="true" multiValued="true"/>
<fieldType name="text_ar" class="solr.TextField" positionIncrementGap="100">
<analyzer>
  <tokenizer class="solr.StandardTokenizerFactory"/>
  <filter class="solr.LowerCaseFilterFactory"/>
  <filter class="solr.StopFilterFactory" words="lang/stopwords_ar.txt" ignoreCase="true"/>
  <filter class="solr.ArabicNormalizationFilterFactory"/>
  <filter class="solr.ArabicStemFilterFactory"/>
</analyzer>
</fieldType>
复制代码

字段类型的属性分为三个主要类别:

a、特定于字段类型类的属性。
b、常规属性,支持任何字段类型。
c、字段默认属性,指定默认属性将覆盖字段的默认行为。

常规属性:

name:fieldType 的名称。
class:该字段类型对应的类名。您可以用“solr”作为包名,如果是第三方类,需要使用完全限定的类名。solr.TextField 等效于 org.apache.solr.schema.TextField。
positionIncrementGap:对于多值字段,指定多个值之间的距离,这可以防止虚假词组匹配。
autoGeneratePhraseQueries:对于文本字段。如果为 true,Solr 将自动生成相邻词语的短语查询。如果为 false,则词语必须用双引号引起来以作为短语处理。
enableGraphQueries:是否启用图像查询。
docValuesFormat:定义字段类型的 docValuesFormat。
postingsFormat:定义字段类型的 postingsFormat。

默认属性:

默认属性可以在字段类型中指定,也可以在单个字段中指定,以覆盖默认值。

属性 描述 默认值
indexed 字段是否创建索引 true/false true
stored 字段是否存储 true/false true
docValues 字段的值是否放在面向列的 DocValues 结构中 true/false false

sortMissingFirst sortMissingLast

排序字段不存在时是否控制文档的位置 true/false false
multiValued 单个文档是否包含此字段类型的多个值 true/false false
uninvertible

如果为 true,则表示一个 indexed=“true” docValues="false" 字段在查询时可以用“un-inverted”构建大内存数据结构以代替 DocValues。 出于历史原因,默认为 true,但强烈建议用户将其设置 false 以保持稳定性,并据需要使用 docValues="true"。

true/false true
omitNorms 如果为 true,则省略与此字段关联的规范。对于所有原始字段类型,例如 int,float,data,bool 和 string,默认为 true。只有全文本字段需要规范。 true/false *
omitTermFreqAndPositions 如果为 true,则忽略此字段的词频率,位置和有效负载。对于不需要该信息的字段,这可以提高性能。对于非文本字段的所有字段类型,此属性默认为true true/false *
omitPositions 类似 omitTermFreqAndPositions 但保留词频率信息 true/false *
termVectors termPositions termOffsets termPayloads 这些选项指示 Solr 维护每个文档的完整词向量,可选地包括这些向量中每个词出现的位置,偏移和有效负载信息。这些可用于加速高亮显示和其他辅助功能,但在索引大小方面增加相当大的成本。它们对于 Solr 的典型用途不是必需的。 true/false false
required 是否必须。如果为 true,则 Solr 拒绝任何添加没有此字段的文档。 true/false false
useDocValuesAsStored 如果该字段已启用 docValues,设置此字段为 true,则允许在使用正则表达式的"*"时返回此字段显示,即使此字段的 stored=false true/false true
large 是否为 large 字段。large 字段始终是延迟加载的,如果实际值<512KB,则只占用文档高速缓存中的空间,此选项需要 stored=true 和 multiValued=false,它适用于可能具有非常大值的字段,以便它不会缓存在内存中。 true/false false

4.3、field

用于定义字段,可以包含以下属性:

name:字段的名称,必须。以下划线开头和结尾的名字为保留字段名,如 _version_。
type:字段对应字段类型,必须。该属性值对应 fieldType 标签中 name 的属性值。
default:默认值,非必须。

字段上也可以定义字段类型的默认属性以覆盖默认值;具体可以参考 4.2 的表格说明。

4.4、dynamicField

用于定义动态字段。如果模式中有很多字段的定义都是相同的,一个个定义就很麻烦,因此可以定一个规则,这些字段都以某个前缀开始或都以某个后缀结尾,按照这个规则配置一个字段就可以了,这就是动态字段。

<dynamicField name="*_txt_en" type="text_en" indexed="true" stored="true"/>
<dynamicField name="*_txt_ar" type="text_ar" indexed="true" stored="true"/>

动态字段只能用星号(*)通配符进行匹配,且只有前缀和后缀两种方式。

4.5、copyField

用于定义复制字段。复制字段允许将一个或多个字段的值填充到另外一个字段中,复制字段支持 field 和 dynamicField。它的用途主要有两种:

a、将多个字段内容填充到一个字段,来进行搜索。
b、对同一个字段进行不同的分词设置,创建一个新的搜索字段。

复制代码
<field name="name" type="text_ik" uninvertible="true" indexed="true" stored="true"/>
<field name="success" type="text_ik" uninvertible="true" indexed="true" stored="true"/>
<field name="content" type="text_ik" uninvertible="true" indexed="true" stored="false" multiValued="true"/>
  
<copyField source="name" dest="content"/>                                                                         
<copyField source="success" dest="content"/>
复制代码

上述例子中将 name 字段和 success 字段的内容复制到 content 字段中,根据关键字搜索 content 字段相当于搜索 name 字段和 success 字段。若目标字段(content)在文档中有自己的数据,则被复制的字段(name 和 success)的内容将添加到目标字段内容里。如果一个字段有多个值(不论这些值来自一个多值的数据源还是由多个复制字段复制而来),那么必须设置 multiValued="true"。

 

来源:

https://www.cnblogs.com/wuyongyin/p/15355652.html


相关教程