ListForm 属于 DevKit 中的一个模块。
在中后台系统的开发中,我们经常会遇到需要展示列表数据的场景,这些列表数据需要支持排序、筛选、分页等功能。为了应对复杂的查询逻辑,开发人员通常需要编写大量重复的代码,涉及过滤条件、排序规则、分页控制、关联实体查询等。而通过设计一个灵活的 ListForm
,自动生成 buildQuery
方法的实现细节,可以极大地提高开发效率,减少冗余代码的编写。
什么是 ListForm?
ListForm 是一种用于处理数据查询请求的表单类。它主要用于接收查询参数,例如过滤条件、排序规则、分页信息等。通过 ListForm,开发人员可以清晰地定义查询的输入参数,而不必关心查询的实现细节。
在 ListForm
中,可以包含以下几个关键要素:
- 过滤条件:支持基于字段的等值查询、范围查询、集合查询等。
- 排序规则:允许对多个字段进行排序,支持升序和降序。
- 分页控制:通过指定页码和每页大小来控制查询的结果集。
- 关联实体查询:支持查询与主实体关联的子实体信息。
- 复杂查询封装:例如关键词搜索、特定条件匹配等。
ListForm
有以下几个特点:
- 自动构建查询:根据声明的表单参数和注解自动生成具体的
buildQuery
逻辑。 - 类型安全:生成的代码是类型安全的,避免了手动拼接 SQL 语句查询,减少了运行时错误。
- 条件受控:通过定义控制查询条件的可见性,避免了不合理的查询方式和组合。
一个示例
下面是一个简单的 ListForm
定义示例:
@Getter
@Setter
@ListForm
public class PlatformEmployeeListForm extends ListFormBase<PlatformEmployeeQuery> {
/* 只可根据 ID 排序,不可基于 ID 查询 */
@Sortable
Void id;
/* 下面 3 个默认 EQ 查询 */
String phoneNumber;
Boolean admin;
Boolean locked;
/* 下面 3 个显式 @Filter 指定过滤类型和对应字段 */
@Filter(value = FilterType.IN, field = "phoneNumber")
String[] phoneNumbers;
@Filter(value = FilterType.GTE, field = "createdAt")
LocalDateTime startCreatedAt;
@Filter(value = FilterType.LTE, field = "createdAt")
LocalDateTime endCreatedAt;
/* 查询出关联实体信息 */
@Wrap
Leader leader;
@Data
public static class Leader {
/* 只可根据 ID 排序,不可基于 ID 查询 */
@Sortable
Void id;
/* 下面 2 个默认 EQ 查询 */
Boolean admin;
String phoneNumber;
}
/* 需要单个参数控制查询多个字段时 */
@Filter(FilterType.NONE)
String keyword;
/* 员工或员工上级的手机号或姓名匹配关键词 */
PlatformEmployeeQuery.P matchKeyword() {
if (keyword == null) {
return null;
}
var $ = $platformEmployee;
return $.phoneNumber.eq(keyword)
.or($.name.contains(keyword))
.or($.$leader.phoneNumber.eq(keyword))
.or($.$leader.name.contains(keyword));
}
/* 固定匹配管理员 */
PlatformEmployeeQuery.P matchAdmin() {
return $platformEmployee.admin.eq(true);
}
/* 当没有指定排序参数时,默认按 ID 降序排列 */
PlatformEmployeeQuery.Sort[] defaultSort() {
return new PlatformEmployeeQuery.Sort[]{
$platformEmployee.id.desc()
};
}
}
在 Controller 中的使用示例:
@GetMapping("/list")
public PlatformEmployeeListResponse list(PlatformEmployeeListForm form) {
Page<PlatformEmployeeWrapper> page = platformEmployeeService.page(form.buildQuery());
return PlatformEmployeeListResponse.map(page);
}
在编译后,会为 ListForm
生成一个对应的子类,实现了关键的 buildQuery
方法。生成的内容如下:
public class PlatformEmployeeListForm$Generated extends PlatformEmployeeListForm {
@Override
public PlatformEmployeeQuery buildQuery() {
var $ = PlatformEmployeeQuery.$platformEmployee;
return PlatformEmployeeQuery.builder()
.wraps($.$leader)
.filter(buildFilters())
.sorts(buildSorts())
.build(Limit.page(getPage(), getSize()));
}
private PlatformEmployeeQuery.P[] buildFilters() {
var $ = PlatformEmployeeQuery.$platformEmployee;
List<PlatformEmployeeQuery.P> predicates = new ArrayList<>();
addPredicateIfPresent(phoneNumber, $.phoneNumber::eq, predicates);
addPredicateIfPresent(admin, $.admin::eq, predicates);
addPredicateIfPresent(locked, $.locked::eq, predicates);
addPredicateIfPresent(phoneNumbers, $.phoneNumber::in, predicates);
addPredicateIfPresent(startCreatedAt, $.createdAt::gte, predicates);
addPredicateIfPresent(endCreatedAt, $.createdAt::lte, predicates);
Optional.ofNullable(leader).ifPresent(leader -> {
addPredicateIfPresent(leader.admin, $.$leader.admin::eq, predicates);
addPredicateIfPresent(leader.phoneNumber, $.$leader.phoneNumber::eq, predicates);
});
Optional.ofNullable(matchKeyword()).ifPresent(predicates::add);
Optional.ofNullable(matchAdmin()).ifPresent(predicates::add);
return predicates.toArray(PlatformEmployeeQuery.P[]::new);
}
private PlatformEmployeeQuery.Sort[] buildSorts() {
var $ = PlatformEmployeeQuery.$platformEmployee;
var sorts = getSortCriteriaStream()
.map(sortCriteria -> {
boolean isDesc = sortCriteria.startsWith("-");
String field = isDesc ? sortCriteria.substring(1) : sortCriteria;
return switch (field) {
case "leader.id" -> isDesc ? $.$leader.id.desc() : $.$leader.id.asc();
case "id" -> isDesc ? $.id.desc() : $.id.asc();
default -> null;
};
})
.filter(Objects::nonNull)
.toArray(PlatformEmployeeQuery.Sort[]::new);
return sorts.length > 0 ? sorts : defaultSort();
}
}