场景:
用户在页面操作:添加一名学生信息(学生姓名为必填,其余属性均为选填)
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 = "学生名字 不合法"))
-
对于选填字段,可灵活处理 当前端传了值,则可进入指定校验;如果前端不传值,则跳过校验
个人看法:
- 后端只需要关注前端入参的必要字段(例如,如果后端能根据学生ID获取到学生的teacherId,则在新增学生信息的时候无需前端传teacherId);
- 抛开前后端数据接收格式来说,仅从网络带宽而言,前端传无意义的字段给后端,后端并不接收,但会占用更多的服务器带宽进行传输;
- 用户并未填写的字段,也以null或者空字符串""传给后端,则上述优雅接收入参方式不可行,后端则需要写很多if-else等做判断;
- 后端返回给前端数据也是入参,假设前端只需要studentId与studentName,而后端则返回学生表所有字段,尽管前端可以只接收需要的字段,但是后端从数据库查询消耗性能与带宽、从服务器返回到用户浏览器又消耗一次带宽,没有意义。
综上所述:
- 最优方案为 格式一;
- 前后端分离优势之一是为了将服务器的压力分摊到客户端,前端对数据进行适当处理(如入参校验,不用每次非法字符都给服务器校验),并不消耗服务器资源,多做一步处理并不是什么难事;
- 相反,后端也会根据前端所需字段而进行处理再返回,所以前端是否也应该对数据进行适当处理 再与后端交互呢?
基于格式一,前端 过滤参数 解决方案(可以加入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))
本文仅为个人观点,欢迎大家发表意见,谢谢。