5

前言:需求

这是一个Spring + Angular前后台分离的项目,目前有一个查看作业列表的功能,并且已经设置了分页和几个查询参数,如图。

图片.png

现在需要增加一个已评阅未评阅的查询功能。
图片.png

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条)数据,如果这一页都是已批阅,那么这页直接就是白的...一条数据也没有。

图片.png

虽然不符合操作逻辑,但这个又不算错误,系统不会报错...

前台过滤,失败。

尝试二:直接传boolean(失败)

直接在前台的M层加入参数:

reviewed: params.reviewed ? params.reviewed : null,

那么问题来了,httpClient只能传输String和对象(即使是传输对象,也是序列化变成字符串再传输的),不能传输number、boolean等类型。

图片.png

对于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;
    }
}

现在,实现了效果:
图片.png

转化为字符串的方法,成功。

代码逻辑:

图片.png

尝试四、通过不同方法间接传输参数(成功)

“尝试三”的写法,主要有两个问题:

  • 分别处理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类型了。

代码逻辑:

图片.png

总结

通过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参数查询数据库了。


LYX6666
1.6k 声望75 粉丝

一个正在茁壮成长的零基础小白