前后端分离之JSON数据交互格式抉择

场景:

用户在页面操作:添加一名学生信息(学生姓名为必填,其余属性均为选填)

package com.wisdom.qiqiao;

import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * 学生类
 */
@Data
public class Student implements Serializable {
    /**
     * 学生姓名(必填)
     */
    private String studentName;

    /**
     * 学生年龄(选填)
     */
    private Integer studentAge;

    /**
     * 学生住址(选填)
     */
    private String studentAddress;

    /**
     * 兴趣爱好(选填)
     */
    private List<Hobby> hobbyList;
}

/**
 * 兴趣爱好类
 */
@Data
class Hobby implements Serializable {
    /**
     * 兴趣爱好名称(必填)
     */
    private String hobbyName;

    /**
     * 兴趣爱好程度(1~10分)(非必填)
     */
    private Integer hobbyDegree;
}



假设用户将所有属性都进行填写,则前端传给后端的JSON格式如下:

{
    "studentName": "张三",
    "studentAge": 18,
    "studentAddress": "广东",
    "hobbyList": [
        {
            "hobbyName": "Java",
            "hobbyDegree": 8
        },
        {
            "hobbyName": "前端",
            "hobbyDegree": 7
        }
    ]
}

 

假设用户只填写了“必填项”(即 学生姓名),则前端传给后端JSON格式将存在异议,大致有四种:

格式一:

{
    "studentName": "张三"
}

格式二:

{
    "studentName": "张三",
    "studentAge": null,
    "studentAddress": null,
    "hobbyList": null
}

格式三:

{
    "studentName": "张三",
    "studentAge": null,
    "studentAddress": null,
    "hobbyList": []
}

格式四:

{
    "studentName": "张三",
    "studentAge": "",
    "studentAddress": "",
    "hobbyList": []
}

 

由于Spring Boot为我们集成了简便的校验注解,所以一般会采用优雅写法接收前端入参:

package com.wisdom.qiqiao;

import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.List;

/**
 * 学生 保存DTO
 *
 * @author Jiahai
 */
@Data
public class StudentForSaveDTO implements Serializable {
    /**
     * 学生姓名(必填)
     */
    @NotBlank(message = "学生姓名 不可为空")
    @Length(min = 2, max = 5, message = "学生名字 不合法")
    private String studentName;

    /**
     * 学生年龄(选填)
     */
    @Range(min = 18, max = 30, message = "学生年龄 不合法")
    private Integer studentAge;

    /**
     * 学生住址(选填)
     */
    @Length(min = 2, max = 5, message = "学生名字 不合法")
    private String studentAddress;

    /**
     * 兴趣爱好(选填)
     */
    @Valid
    @Size(min = 2, max = 5, message = "兴趣爱好 不合法")
    private List<HobbyForSaveDTO> hobbyList;
}

/**
 * 兴趣爱好类
 */
@Data
class HobbyForSaveDTO implements Serializable {
    /**
     * 兴趣爱好名称(必填)
     */
    @Length(min = 2, max = 6, message = "兴趣爱好名称 不合法")
    private String hobbyName;

    /**
     * 兴趣爱好程度(1~10分)(非必填)
     */
    @Range(min = 1, max = 10, message = "兴趣爱好程度 不合法")
    private Integer hobbyDegree;
}
package com.wisdom.qiqiao;

import com.wisdom.qiqiao.base.response.APIResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 学生 API
 *
 * @author Jiahai
 */
@RestController
@RequestMapping("/student")
public class StudentController {
    /**
     * 新增
     *
     * @param studentForSaveDTO 学生 新增入参DTO
     * @return
     */
    @PostMapping
    public APIResult save(@Validated @RequestBody StudentForSaveDTO studentForSaveDTO) {
        // TODO 保存学生信息...
        return APIResult.success();
    }
}

 

Spring Boot 为我们提供的校验注解(来自 spring boot starter validation),可以使得后端灵活处理以下场景:

  • 对于必填字段,可要求前端必填(例如 @NotBlank(message = "学生姓名 不可为空")),且对入参值进行指定规则校验(例如 @Length(min = 2, max = 5, message = "学生名字 不合法"))

  • 对于选填字段,可灵活处理 当前端传了值,则可进入指定校验;如果前端不传值,则跳过校验

 

个人看法:

  1. 后端只需要关注前端入参的必要字段(例如,如果后端能根据学生ID获取到学生的teacherId,则在新增学生信息的时候无需前端传teacherId);
  2. 抛开前后端数据接收格式来说,仅从网络带宽而言,前端传无意义的字段给后端,后端并不接收,但会占用更多的服务器带宽进行传输;
  3. 用户并未填写的字段,也以null或者空字符串""传给后端,则上述优雅接收入参方式不可行,后端则需要写很多if-else等做判断;
  4. 后端返回给前端数据也是入参,假设前端只需要studentId与studentName,而后端则返回学生表所有字段,尽管前端可以只接收需要的字段,但是后端从数据库查询消耗性能与带宽、从服务器返回到用户浏览器又消耗一次带宽,没有意义。

 

综上所述:

  1. 最优方案为 格式一;
  2. 前后端分离优势之一是为了将服务器的压力分摊到客户端,前端对数据进行适当处理(如入参校验,不用每次非法字符都给服务器校验),并不消耗服务器资源,多做一步处理并不是什么难事;
  3. 相反,后端也会根据前端所需字段而进行处理再返回,所以前端是否也应该对数据进行适当处理 再与后端交互呢?

 

基于格式一,前端 过滤参数 解决方案(可以加入Vue拦截器中):

// 需求:值为空的字段不用传, 所以过滤一下
const filterParams = params => Object.entries(params).reduce((obj, [key, val]) => ((obj[key] = val ? val : undefined), obj), {})

// 定义传给后端的参数
let params = {
	studentName: '张三',
	studentAge: '',
}

// 过滤之后 params = { studentName: '张三' }
post('http://www.url', filterParams(params))

基于格式三,前端 过滤参数 解决方案(可以加入Vue拦截器中):(格式二类似,自行调整)

// 需求, 值为空的字段需改为null, 所以过滤一下
const filterParams = params => Object.entries(params).reduce((obj, [key, val]) => ((obj[key] = val ? val : null), obj), {})

// 定义传给后端的参数
let params = {
	studentName: '张三',
	studentAge: '',
}

// 过滤之后 params = { studentName: '张三', studentAge: null }
post('http://www.url', filterParams(params))

 

 

本文仅为个人观点,欢迎大家发表意见,谢谢。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值