前言:需求
这是一个Spring + Angular前后台分离的项目,目前有一个查看作业列表的功能,并且已经设置了分页和几个查询参数,如图。
现在需要增加一个已评阅和未评阅的查询功能。
Work实体的属性如下:
id: number;
content: string;
createTime: Date;
item = new Item();
score: number;
student = new Student();
updateTime: Date;
reviewed: boolean;
attachments = new Array<Attachment>();
已评阅对应的属性是reviewed,是boolean类型。
目前前台服务层的代码:
// 已经写了分页参数、按学生姓名查找作业、按学号查找作业,共5个参数
getAll(params: {page: number, size: number, studentName?: string, studentSno?: string, itemId?: number }): Observable<Page<Work>> {
const _params = {
page: params.page.toLocaleString(),
size: params.size.toLocaleString(),
studentName: params.studentName ? params.studentName : null,
studentNo: params.studentSno ? params.studentSno : null,
itemId: params.itemId ? params.itemId.toLocaleString() : null
};
return this.httpClient.get<Page<Work>>(`${this.url}/getAll`, {params: _params});
}
由于前后台中间隔着一层Http,并且boolean具有特殊性,所以向后台传值比较困难,作为新手,我首先能想到几种方法:
- 后台不动,直接在前台过滤
- 直接把boolean的变量作为参数
- 把boolean转换为string类型,通过http传输,到后台转换为boolean
- 不增加参数,在后台建立三个方法,前台分别请求不同的方法,达到传输boolean的目的
尝试一、前台过滤(失败)
这个是最简单的,因为后台不用动,直接请求整页的数据,只在前台显示未批阅的作业即可。
/**
* 单选框被用户点击时
* @param $event 弹射值
* @param reviewed 评阅状态码1默认2已评阅3未评阅
*/
onCheckBoxChange($event: Event, reviewed: number) {
this.reviewed = reviewed;
this.load();
}
那么问题来了,由于请求的是一整页(10条)数据,如果这一页都是已批阅,那么这页直接就是白的...一条数据也没有。
虽然不符合操作逻辑,但这个又不算错误,系统不会报错...
前台过滤,失败。
尝试二:直接传boolean(失败)
直接在前台的M层加入参数:
reviewed: params.reviewed ? params.reviewed : null,
那么问题来了,httpClient只能传输String和对象(即使是传输对象,也是序列化变成字符串再传输的),不能传输number、boolean等类型。
对于number,需要转化成String类型来传输,比如:
itemId: params.itemId ? params.itemId.toLocaleString() : null
但对于boolean类型,并没有直接转化成String类型的方法。
因此,直接传boolean类型的参数的方式,失败。
尝试三、转化成String类型(成功)
除了Http请求,其他位置继续用Boolean传值,到发起请求的时候转化为String。
这样的好处是:只需要在前台的服务层和后台的C层增加转化代码,其他位置继续按照常规的逻辑,直接用Boolean。
在前台向后台的传输过程中:
true -> "true" -> true
false -> "false" -> false
undefined -> 前台不传值 -> null
这样就用三个值表示了“已评阅”“未评阅”“全部”三种状态。
缺点也比较明显,布尔值只有true和false,如果判断写成:
reviewed: params.reviewed ? 'true' : null,
那么undefined 和false都按false来处理,都不会传值。
所以就必须分开处理undefined和false,可以用一个嵌套判断来实现:
reviewed: params.reviewed === undefined ? null : params.reviewed ? '1' : '0',
同理,后台也需要判断,转化成Boolean:
Boolean _reviewed = null;
if (reviewed != null){
switch (reviewed) {
case "1": _reviewed = true;break;
case "0": _reviewed = false;break;
}
}
现在,实现了效果:
转化为字符串的方法,成功。
代码逻辑:
尝试四、通过不同方法间接传输参数(成功)
“尝试三”的写法,主要有两个问题:
- 分别处理undefined和false比较麻烦
- 代码可读性差
由于boolean具有特殊性,只有两个值,所以可以不传输这个参数,通过调用不同的方法,间接传输boolean。
现有的后台C层方法:
/**
* 获取所有作业
* @param pageable 分页信息
* @return 所有作业
*/
@GetMapping("getAll")
@JsonView(GetAllJsonView.class)
public Page<Work> findAll(
@RequestParam(required = false) Long itemId,
@RequestParam(required = false) String studentName,
@RequestParam(required = false) String studentNo,
Pageable pageable) {
return this.workService.getAll(
itemId,
studentName,
studentNo,
pageable);
}
目前有三个参数,如果不想增加参数,就再增加两个方法,而参数不变:
// 原C层的方法
public Page<Work> findAll(){}
// 获取已评阅的作业
public Page<Work> findAllReviewed(){}
// 获取未评阅的作业
public Page<Work> findAllUnReviewed(){}
写到这里大家应该明白了:在前台向后台的传输过程中
true -> 请求findAllReviewed() -> true
false -> 请求findAllUnreviewed() -> false
undefined -> 请求findAll() -> null
所以前台这么写:
getAll(params: {page: number, size: number, studentName?: string, studentSno?: string, itemId?: number, reviewed?: boolean}):
Observable<Page<Work>> {
// 准备参数
const _params = {
page: params.page.toLocaleString(),
size: params.size.toLocaleString(),
studentName: params.studentName ? params.studentName : null,
studentNo: params.studentSno ? params.studentSno : null,
itemId: params.itemId ? params.itemId.toLocaleString() : null
};
// 判断,向对应的方法发起请求
switch (params.reviewed) {
case true: return this.httpClient.get<Page<Work>>(`${this.url}/getAllReviewed`, {params: _params}); break;
case false: return this.httpClient.get<Page<Work>>(`${this.url}/getAllUnReviewed`, {params: _params}); break;
}
// 默认请求getAll()
return this.httpClient.get<Page<Work>>(`${this.url}/getAll`, {params: _params});
}
然后,后台的三个方法都调用同一个服务层,这时候就可以按照常规逻辑,传入Boolean类型了。
代码逻辑:
总结
通过Http传输boolean参数的方法:
- 前台转化成字符串进行传输,后台接收字符串转化回来
- 不在方法中传输参数,通过请求不同的请求,间接传输boolean参数
作为新手,我知道这一定不是最优解,如果有更好的方式,欢迎来打脸,哈哈哈。
扩展
在实际项目中,除了传值,还遇到了一个难处理的问题:数据库的条件查询(使用JPA)。
后台的仓库层WorkRepository功能:
default Page getAll(Item item, String studentName, String studentSno, @NotNull Pageable pageable) {
Assert.notNull(pageable, "传入的Pageable不能为null");
Specification<Work> specification = WorkSpecs.containingName(studentName)
.and(WorkSpecs.startWithNo(studentSno))
.and(WorkSpecs.belongToItem(item));
return this.findAll(specification, pageable);
}
其中的
Specification<Work> specification = WorkSpecs.containingName(studentName)
.and(WorkSpecs.startWithNo(studentSno))
.and(WorkSpecs.belongToItem(item));
就是查询条件,它分别调用了三个方法,分别按照姓名、学号、实验项目来查询。
查看WorkSpecs类,三个方法的代码如下:
public class WorkSpecs {
public static Specification<Work> belongToItem(Item item) {
if (null == item || null == item.getId()) {
return Specification.where(null);
}
return (Specification<Work>) (root, criteriaQuery, criteriaBuilder) ->
criteriaBuilder.equal(root.get("item").as(Item.class), item);
}
public static Specification<Work> containingName(String studentName) {
if (studentName != null) {
return (Specification<Work>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.like(root.get("student").get("name").as(String.class), String.format("%%%s%%", studentName));
} else {
return Specification.where(null);
}
}
public static Specification<Work> startWithNo(String studentNo) {
if (studentNo == null) {
return Specification.where(null);
}
return (Specification<Work>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.like(root.get("student").get("no").as(String.class), String.format("%s%%", studentNo));
}
}
现在增加一个reviewed参数,就要增加相应的查询条件。
所以第四个条件这样写:
public static Specification<Work> isReviewed(Boolean reviewed) {
if (reviewed == null) {
return Specification.where(null);
}
return (Specification<Work>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("reviewed").as(Boolean.class), reviewed);
}
在仓库层getAll()方法增加:
.and(WorkSpecs.isReviewed(reviewed));
就是实现JPA通过Boolean参数查询数据库了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。