概述
通过开发数据存储插件,可以为 Fess 添加从新数据源获取内容的功能。数据存储会从数据库、 API、文件等外部系统中获取记录,并按照在管理界面中设置的映射脚本,将其转换为索引字段后, 注册到 Fess 的索引中。
CSV、JSON、数据库、Git、各类云服务等公开的连接器(fess-ds-*)都是基于这一机制实现 的。作为实现模板,官方公开了 fess-ds-example ,创建新连接器时, 复制该模板作为起点会比较简单。
基本结构
数据存储插件由以下三个要素构成:
创建继承
AbstractDataStore的类实现
getName()和storeData()两个方法在
fess_ds++.xml中注册为组件
最小实现
Note
getName() 和 storeData() 都是 protected 的抽象方法。请注意,在 Fess 15.x 中,DataConfig 所在的包是 org.codelibs.fess.opensearch.config.exentity (此前的 org.codelibs.fess.es.config.exentity 已被废弃)。
组件注册
为了让 Fess 识别所创建的数据存储,需要在 src/main/resources/fess_ds++.xml 中 注册组件。
通过 <postConstruct name="register">,组件生成后会自动调用 AbstractDataStore 所具有的 register() 方法,将自身注册到 DataStoreFactory 中。此时注册的名称即为 getName() 的返回值(上例中为 ExampleDataStore),也就是在管理界面的数据存储设置 中选择的”处理器名”。
AbstractDataStore
主要方法
| 方法 | 类别 | 说明 |
|---|---|---|
getName() | 实现(必需) | 返回数据存储处理器名的抽象方法。按照惯例返回 getClass().getSimpleName() |
storeData() | 实现(必需) | 执行数据获取、转换及索引注册的抽象方法 |
register() | 继承(通常无需修改) | 由 fess_ds++.xml 的 postConstruct 自动调用,注册到 DataStoreFactory |
store() | 继承(框架调用) | 框架调用的入口。准备好 defaultDataMap 等后调用 storeData() |
convertValue() | 继承(辅助方法) | 通过脚本引擎对 scriptMap 的值(模板)进行求值 |
getScriptType() | 继承(辅助方法) | 获取 script_type 参数(默认为 Groovy) |
getReadInterval() | 继承(辅助方法) | 获取 readInterval 参数(毫秒) |
sleep() | 继承(辅助方法) | 休眠指定的毫秒数(用于记录间的等待) |
storeData 的参数
传递给 storeData() 方法的参数:
| 参数 | 类型 | 说明 |
|---|---|---|
dataConfig | DataConfig | 数据存储设置(ID、处理器名、参数、脚本等) |
callback | IndexUpdateCallback | 将生成的文档注册到索引的回调 |
paramMap | DataStoreParams | 管理界面”参数”栏中设置的值。可通过 getAsString(key) / getAsString(key, default) / get(key) / asMap() / containsKey(key) 进行访问 |
scriptMap | Map<String, String> | 管理界面”脚本”栏中的设置。键为索引字段名,值为待求值的脚本模板 |
defaultDataMap | Map<String, Object> | 各文档的默认字段值(配置ID、boost、role、mimetype、虚拟主机等)。由框架准备 |
Warning
paramMap 的类型不是 Map<String, String>,而是 DataStoreParams。由于 DataStoreParams 并未实现 Map 接口,获取值时请使用返回字符串的 getAsString(),而不是 get()。
数据处理流程
storeData() 的实现按以下流程处理数据:
从外部系统获取源记录。
将源记录的字段合并到
paramMap.asMap()中,构建resultMap(脚本将针对该resultMap进行求值)。使用
convertValue(scriptType, template, resultMap)对scriptMap中的每个 条目求值,并将结果存入dataMap。需要注意的是,映射关系并非硬编码在代码中,而是由 管理员在”脚本”栏中定义。调用
callback.store(paramMap, dataMap),将其作为文档注册到索引中。
实现示例
简单的数据存储
以下是从外部 API 获取记录并注册到索引的示例。
fetchRecords() 是从外部系统获取记录列表的自定义方法。获取到的每条记录 (Map<String, Object>)的字段名,即为可在 scriptMap 的脚本中引用的名称。 DataStoreException 是 org.codelibs.fess.exception 包中的类。
分页支持
在处理大量数据时,需要按页获取并处理数据。将每条记录的处理逻辑(构建 resultMap、 对 scriptMap 求值、调用 callback.store())拆分到类似 processRecord() 的 方法中,可以将其与获取逻辑分离。
认证实现
外部系统的认证由连接器一侧负责实现。以下是使用常见 HTTP 客户端库的实现示例,并非 Fess 提供的 API。请将所使用的库作为插件的依赖项一并打包。
OAuth 2.0
API密钥认证
错误处理
对于应中断处理的致命错误,请抛出 DataStoreException。
Note
在以 fess-ds-example 为代表的实际连接器中,为了避免单条记录的错误导致整个爬取 中断,会以记录为单位捕获 CrawlingAccessException,并将出错的 URL 记录到 FailureUrlService 中。此外,还会利用 DataStoreCrawlingException 的中断 标志,来控制是否中断整个爬取过程。如需实现健壮的连接器,请参考 ExampleDataStore 的实现。
测试
单元测试
Fess 的插件使用 UTFlute 的 LastaDiTestCase 进行测试。测试使用 JUnit 5 (Jupiter)编写。通过将 IndexUpdateCallback 替换为收集已注册 dataMap 的实现, 无需使用模拟(mock)库即可验证映射结果。
Note
由于基类中的 setUp 已附加了 @BeforeEach,因此重写时无需在其上重新附加生命 周期注解。请为每个测试方法附加 @Test(org.junit.jupiter.api.Test)。
构建与安装
pom.xml
插件以 fess-parent 作为父 POM,构建为 jar。对 fess 和 opensearch 的依赖, 由于在运行时由 Fess 本体提供,因此设置为 provided。
测试使用 JUnit 5 和 org.dbflute.utflute:utflute-lastaflute。
构建
会在 target/ 目录下生成 fess-ds-example-15.8.0.jar。
安装
将生成的 JAR 安装到 Fess 中,并重启 Fess。安装步骤的详细信息请参考 插件。安装后,从管理界面的”爬虫 > 数据存储”创建新设置,并在 “处理器名”中指定 getName() 返回的名称(本例中为 ExampleDataStore)。
配置示例
管理界面中的配置示例:
参数
“参数”栏中填写连接器从 paramMap 读取的键和值。
脚本
“脚本”栏中以 左边=右边 的形式描述映射关系。左边为索引字段名,右边为引用源记录字段 的脚本(默认使用 Groovy)。以下是源记录具有 url / title / content / updated_at / content_type 字段时的示例。
Note
右边可引用的字段名,取决于连接器存入 resultMap 中的值(paramMap 的值以及 源记录的字段)。在 CSV、JSON 等现有连接器中,可能会带有类似 data.* 的专属前缀, 请参考各连接器的文档。
参考信息
插件架构 - 插件架构
插件 - 插件安装
数据存储连接器概述 - 数据存储连接器概述
fess-ds-example - 数据存储插件的实现模板
GitHub: fess-ds-* - 公开连接器示例