前后台分离的项目中,如何优雅的传输boolean类型的参数

前言:需求

这是一个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参数查询数据库了。

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

1.3k 声望
51 粉丝
0 条评论
推荐阅读
【DEBUG】记录weixin-java-mp启动时出现“找不到 xxxx”的问题
districtServiceImpl -&gt; webUserService -&gt; weChatMpService -&gt; WxMessageInMemoryDuplicateCheckerSingleton失败

LYX66663阅读 281

angular 中ActivatedRoute 和 Router,以及记录后台遇到的问题
ActivatedRoute 和 Router的区别前台angular使用这两个来进行路由的操作,但是好像一直不大清楚区别。这里简单记录一下。区别 {代码...} ActivatedRoute是当前组件的路由对象,包含了当前的路由信息。router是全...

weiweiyi6阅读 1.3k

使用springboot+angular实现web端微信扫码登陆
现在微信的使用用户越来越多,如果网站添加上微信登录,就能节省很多用户注册时间,极大缩小了注册流程。会让用户觉得特别方便。接下来我们就说一下怎么来实现Web端微信扫码登录。

郝泽龙_HZ6阅读 968

记录一个 SpringSecurity 和 x-auth-token 一直登录失败的排查过程
第一步:初始化项目,粘贴基本功能,如简单的页面、实体、必要的服务和控制器等,如果出现依赖,则视情况粘贴依赖或删掉代码第二步:粘贴登录功能,当前这个项目用的和之前学的已经不一样了,使用的是 SpringSecu...

LYX66664阅读 556

解决angular 报错 url unsafe
遇到报错使用的img 标签出现了报错 {代码...} 报错这个url 为 unsafeXSS首先先了解为什么会出现unsafe为了系统性的防范 XSS 问题,Angular 默认把所有值都当做不可信任的。即 unsafe跨站脚本(XSS)允许攻击者将恶...

weiweiyi3阅读 423

实现第三方登陆:微信扫码登录 (spring boot)
前言各种官方网站通常都会有app、微信公众号等。比如央视网,银行等。当我们关注公众号或者app后,这些应用就可以在移动端方便地将信息推送给用户。统一各产品线的账号体系,实现一个账号处处使用的目标是非常有...

weiweiyi4阅读 643

Async Pipe 以及Promise
前言之前在写项目的时候引用某个管道的时候 &lt;td&gt;{{ house | housePlace }}发现效果不是想要的, 而是如下图的效果,并没有显示出正确的地址!参考项目中的代码发现需要加上async管道 &lt;td&gt;{{ house | h...

weiweiyi2阅读 956

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

1.3k 声望
51 粉丝
宣传栏