跳到主要内容
版本:2.24

与自定义模型交互

Halo 提供了两个类用于与自定义模型对象交互 ExtensionClientReactiveExtensionClient

它们提供了对自定义模型对象的增删改查操作,ExtensionClient 是阻塞式的用于后台任务如控制器中操作数据,而 ReactiveExtensionClient 返回值都是 Mono 或 Flux 是反应式非阻塞的,它们由 reactor 提供。

public interface ReactiveExtensionClient {

    // 已经过时,建议使用 listBy 或 listAll 代替
    <E extends Extension> Flux<E> list(Class<E> type, Predicate<E> predicate,
        Comparator<E> comparator);

    // 已经过时,建议使用 listBy 或 listAll 代替
    <E extends Extension> Mono<ListResult<E>> list(Class<E> type, Predicate<E> predicate,
        Comparator<E> comparator, int page, int size);

    <E extends Extension> Flux<E> listAll(Class<E> type, ListOptions options, Sort sort);

    <E extends Extension> Mono<ListResult<E>> listBy(Class<E> type, ListOptions options,
        PageRequest pageable);

    /**
     * Fetches Extension by its type and name.
     *
     * @param type is Extension type.
     * @param name is Extension name.
     * @param <E> is Extension type.
     * @return an optional Extension.
     */
    <E extends Extension> Mono<E> fetch(Class<E> type, String name);

    Mono<Unstructured> fetch(GroupVersionKind gvk, String name);

    <E extends Extension> Mono<E> get(Class<E> type, String name);

    /**
     * Creates an Extension.
     *
     * @param extension is fresh Extension to be created. Please make sure the Extension name does
     * not exist.
     * @param <E> is Extension type.
     */
    <E extends Extension> Mono<E> create(E extension);

    /**
     * Updates an Extension.
     *
     * @param extension is an Extension to be updated. Please make sure the resource version is
     * latest.
     * @param <E> is Extension type.
     */
    <E extends Extension> Mono<E> update(E extension);

    /**
     * Deletes an Extension.
     *
     * @param extension is an Extension to be deleted. Please make sure the resource version is
     * latest.
     * @param <E> is Extension type.
     */
    <E extends Extension> Mono<E> delete(E extension);
}

示例

如果你想在插件中根据 name 参数查询获取到 Person 自定义模型的数据,则可以这样写:

@Service
@RequiredArgsConstructor
public PersonService {
    private final ReactiveExtensionClient client;
    
    Mono<Person> getPerson(String name) {
        return client.fetch(Person.class, name);
    }
}

或者使用阻塞式 Client

@Service
@RequiredArgsConstructor
public PersonService {
    private final ExtensionClient client;
    
    Optional<Person> getPerson(String name) {
        return client.fetch(Person.class, name);
    }
}

注意:非阻塞线程中不能调用阻塞式方法。

我们建议你更多的使用响应式的 ReactiveExtensionClient 去替代 ExtensionClient

查询

ReactiveExtensionClient 提供了以下方法用于查询数据:

  • listBy:分页查询数据。
  • listNamesBy:分页查询对象名称。
  • listAll:查询所有数据。
  • listAllNames:查询所有对象名称。
  • listTopNames:查询指定数量的对象名称。
  • countBy:统计符合条件的数据数量。

这些方法都需要一个 ListOptions 参数,用于传递查询条件:

public class ListOptions {
    private LabelSelector labelSelector;
    private FieldSelector fieldSelector;
}

其中 LabelSelector 用于传递标签查询条件,FieldSelector 用于传递字段查询条件。

FieldSelector 支持比自动生成的 APIs 中更多的查询条件,可以通过 run.halo.app.extension.index.query.Queries 来构建。

import static run.halo.app.extension.index.query.Queries.and;
import static run.halo.app.extension.index.query.Queries.equal;

ListOptions.builder()
    .fieldQuery(and(
        equal("name", "test"),
        equal("age", 18)
    ))
    .build();

支持的查询条件如下:

方法说明示例
equal等于equal("name", "test")
notEqual不等于notEqual("name", "test")
greaterThan大于,可通过第三个参数控制是否包含边界greaterThan("age", 18)greaterThan("age", 18, true)
lessThan小于,可通过第三个参数控制是否包含边界lessThan("age", 18)lessThan("age", 18, true)
between在范围内,可分别控制上下边界是否包含between("age", 18, true, 20, false)
in在给定值范围内in("age", 18, 19, 20)
isNull值为空isNull("deletedAt")
all指定字段的所有值all("age")
startsWith以指定字符串开头startsWith("name", "test")
endsWith以指定字符串结尾endsWith("name", "test")
contains包含指定字符串contains("name", "test")
andand(equal("name", "test"), equal("age", 18))
oror(equal("name", "test"), equal("age", 18))
not取反not(equal("name", "test"))
labelExists标签存在labelExists("halo.run/hidden")
labelEqual标签等于labelEqual("env", "production")
labelIn标签值在给定范围内labelIn("env", Set.of("production", "staging"))

FieldSelector 中使用的所有字段都必须添加为索引,否则会抛出异常表示不支持该字段。关于如何使用索引请参考 自定义模型使用索引

备注

从 2.22.0 开始,QueryFactory 已过时,请使用 Queries 创建查询条件。取反查询可以通过 Queries.not(condition)condition.not() 构建。

可以通过 andor 方法组合和嵌套查询条件:

import run.halo.app.extension.index.query.Condition;
import static run.halo.app.extension.index.query.Queries.and;
import static run.halo.app.extension.index.query.Queries.equal;
import static run.halo.app.extension.index.query.Queries.or;

Condition query = and(
    or(equal("dept", "A"), equal("dept", "B")),
    or(equal("age", 19), equal("age", 18))
);
ListOptions.builder()
    .fieldQuery(query)
    .build();

构建 ListOptions

ListOptions 提供了 builder 方法用于构建查询条件,fieldQuery 方法用于传递字段查询条件,labelSelector 方法用于传递标签查询条件。

import static run.halo.app.extension.index.query.Queries.equal;

ListOptions.builder()
    .labelSelector()
    .eq("key-1", "value-1")
    .end()
    .fieldQuery(equal("key-2", "value-2"))
    .build();
  • labelSelector 之后使用 end 方法结束标签查询条件的构建。
  • andQueryorQuery 用于组合多个 FieldSelector 查询条件。

排序

listBylistNamesBylistAlllistAllNameslistTopNames 方法都支持传递 Sort 参数,用于传递排序条件。

import org.springframework.data.domain.Sort;

Sort.by(Sort.Order.asc("metadata.name")) 

通过 Sort.by 方法可以构建排序条件,Sort.Order 用于指定排序字段和排序方式,asc 表示升序,desc 表示降序。

排序中使用的字段必须是添加为索引的字段,否则会抛出异常表示不支持该字段。关于如何使用索引请参考 自定义模型使用索引

分页

listBy 方法支持传递 PageRequest 参数,用于传递分页条件。

import run.halo.app.extension.PageRequestImpl;

PageRequestImpl.of(1, 10);

PageRequestImpl.of(1, 10, Sort.by(Sort.Order.asc("metadata.name"));

PageRequestImpl.ofSize(10);

通过 PageRequestImpl.of 方法可以构建分页条件,具有两个参数的方法用于指定页码和每页数量,具有三个参数的方法用于指定页码、每页数量和排序条件。

ofSize 方法用于指定每页数量,页码默认为 1。