最近在做项目,使用到了Mybatis-plus,页面上需要对某些字段进行排序,原来是固定好哪些字段需要排序,然后在Controller里接收参数,如下代码所示。
@LogOperation(name = "query urpOfi", objType = "Config")
@GetMapping
public ResponseEntity<DtoResponse<OfiDto>> queryOfi(
@RequestParam(name = "pageSize", defaultValue = "-1", required = false) Integer pageSize,
@RequestParam(name = "page", defaultValue = "-1", required = false) Integer page,
@RequestParam(value = "sort", defaultValue = "", required = false) String sort,
@RequestParam(value = "msFlag", defaultValue = "", required = false) String msFlag)
throws UnsupportedEncodingException {
sort = URLDecoder.decode(sort, StandardCharsets.UTF_8.toString());
msFlag = URLDecoder.decode(msFlag, StandardCharsets.UTF_8.toString());
余下省略......
其中@LogOperation使我们自定义的AOP日志注解,可以不用关注。这里存在的问题是参数没有校验,直接接收了,可能会被恶意攻击,同时sort为排序字段,前台会传一个固定好的字段值过来,无法实现动态排序(比如:前台随意传一个字段,只要在我的表数据Entity中存在该字段,就可以实现排序),通过以下方式实现动态排序。
@Data
public class BaseQueryDto<T> {
// 排序方式标识符号,”-“代表倒序
private static final String ORDER_BY_IDENTIFIER = "-";
private static final int MAX_PAGE_SIZE = 100;
private static final int MAX_PAGE = 10000;
private static final int MAX_SORT_LENGTH = 100;
/**
* 每页显示记录数
*/
@Range(min = 0, max = MAX_PAGE_SIZE)
protected Integer pageSize = 10;
/**
* 当前页
*/
@Range(min = 0, max = MAX_PAGE)
protected Integer page = 0;
/**
* 排序,包含2部分内容,排序方式及排序字段
*/
@Length(max = MAX_SORT_LENGTH)
protected String sort;
/**
* 获取排序方式,默认升序(asc)
*
* @return 排序方式
*/
public SqlOrderType getOrderType() {
if (StringUtils.isEmpty(sort)) {
return SqlOrderType.ASC;
}
if (sort.startsWith(ORDER_BY_IDENTIFIER)) {
return SqlOrderType.DESC;
}
return SqlOrderType.ASC;
}
private Optional<Class<?>> getEntityClass() {
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
try {
return Optional.of(Class.forName(actualTypeArguments[0].getTypeName()));
} catch (ClassNotFoundException e) {
return Optional.empty();
}
}
return Optional.empty();
}
private Optional<Field> getEntityField(String fieldName) {
Optional<Class<?>> optionalClass = getEntityClass();
return optionalClass.flatMap(it -> {
try {
return Optional.of(it.getDeclaredField(fieldName));
} catch (NoSuchFieldException e) {
return Optional.empty();
}
});
}
/**
* 获取数据排序的列
*
* @return 排序列
*/
public Optional<String> getOrderColumn() {
if (StringUtils.isEmpty(sort)) {
return Optional.empty();
}
return getEntityField(sort.replaceFirst(ORDER_BY_IDENTIFIER, "")).map(Field::getName);
}
/**
* 设置排序的QueryWrapper
*
* @param queryWrapper query wrapper
*/
public void setOrderQueryWrapper(QueryWrapper<T> queryWrapper) {
this.getOrderColumn().ifPresent(column -> {
queryWrapper.orderBy(true, this.getOrderType().equals(SqlOrderType.ASC), column);
});
}
}
BaseQueryDto是公共的查询Dto父类,泛型T为具体的表数据Entity。通过java beanvalidation控制入参大小及长度,前端传递的参数sort值一般为Entity中的字段名,例如“id”或者“-id”,带有“-”的代表是降序。会根据传入的sort值通过反射去判断在Entity中是否存在该字段,存在就正常排序,不存在就什么也不做。
如果只需要排序,那么Controller中用BaseQueryDto去接收参数即可,然后在Service中调用baseQueryDto.setOrderQueryWrapper(queryWrapper);
如果不仅仅排序,还需要对某些字段做模糊查询或者下拉框过滤查询,需要新建Dto继承BaseQueryDto。
@Data
public class OfiQueryDto extends BaseQueryDto<OfiEntity> {
/**
* 主从标识(过滤查询)
*/
@Length(max = 5)
private String msFlag = "";
/**
* 国内,国内备用,国际,国际备用网编码(模糊查询)
*/
@Length(max = 6)
private String networkCode1 = "";
@Length(max = 6)
private String networkCode2 = "";
@Length(max = 6)
private String networkCode3 = "";
@Length(max = 6)
private String networkCode4 = "";
}
Service中设置模糊查询,下拉框过滤和排序。
public ListPage<OfiEntity> queryOfiListPage(OfiQueryDto dto, Integer clusterId)
throws UnsupportedEncodingException {
QueryWrapper<OfiEntity> queryWrapper = Wrappers.query();
queryWrapper.lambda().eq(OfiEntity::getClusterId, clusterId);
setSelectQueryWrapper(dto, queryWrapper.lambda());
setLikeQueryWrapper(dto, queryWrapper.lambda());
dto.setOrderQueryWrapper(queryWrapper);
Page<OfiEntity> ofiEntityPage = new Page<>(dto.getPage(), dto.getPageSize());
ofiEntityPage = getBaseMapper().selectPage(ofiEntityPage, queryWrapper);
return ListPage.of(ofiEntityPage);
}