目录
响应结果的类型
当成功的添加相册时,服务器将响应:
添加成功!
如果相册的名称已经被占用,服务器将响应:
添加相册失败,相册名称已经被占用!
如果提交的请求参数不符合服务器端的检查规则,例如没有提交相册名称时,服务器将响应:
添加相册失败,必须提交相册名称!
以上响应的结果其实是无法正常使用的!当客户端向服务器发请求后,如果得到的是以上结果,则需要:
axios.post("/album/add-new", v.album).then(function (response) {
if (response.data == '添加成功!'){
// ...
} else if (response.data == '添加相册失败,相册名称已经被占用!'){
// ...
} else if (response.data == '添加相册失败,必须提交相册名称!') {
// ...
} else if (... 其它错误) {
// ...
}
})
以上代码中,使用字符串来判断操作结果是错误的做法!因为字符串是可能调整的,并且,不适用于国际化!
通常,应该使用特定的代码(或代号)来表示不同的状态!例如,使用1
表示成功,使用2
表示某种失败,使用3
表示另一种失败等等!
另外,服务器端仍应该提供出错时的描述文本,而客户端只需要获取到这些文本并直接显示在软件的界面中即可(客户端不会自行组织语言来设计错误时的描述文本)。
所以,服务器端需要向客户端响应操作结果对应的代码(或代码),同时还要响应出错时的描述文本!
则可以创建JsonResult
类,在类中声明需要响应的数据属性,例如:
@Data
public class JsonResult implements Serializable {
private Integer state;
private String message;
}
然后,处理请求的方法、处理异常的方法,都应该使用以上类型作为方法的返回值类型,例如:
@PostMapping("/add-new")
public JsonResult addNew(@Valid AlbumAddNewParam albumAddNewParam) {
log.debug("开始处理【添加相册】的请求,参数:{}", albumAddNewParam);
albumService.addNew(albumAddNewParam);
JsonResult jsonResult = new JsonResult();
jsonResult.setState(1);
jsonResult.setMessage("添加成功!");
return jsonResult;
}
@ExceptionHandler
public JsonResult handleServiceException(ServiceException e) {
log.warn("程序运行过程中出现了ServiceException,将统一处理!");
log.warn("异常信息:{}", e.getMessage());
JsonResult jsonResult = new JsonResult();
jsonResult.setState(2);
jsonResult.setMessage(e.getMessage());
return jsonResult;
}
经过以上调整以后,在执行过程中,服务器端的Spring MVC会将返回的对象转换为JSON格式再进行响应,例如:
{
"state": 1,
"message": "添加成功!"
}
{
"state": 2,
"message": "添加相册失败,相册名称已经被占用!"
}
{
"state": 3,
"message": "添加相册失败,必须提交相册名称!"
}
后续,客户端的代码就可以调整为:
axios.post("/album/add-new", v.album).then(function (response) {
if (response.data.state == 1){
// 成功
} else if (response.data.state == 2){
// 显示response.data.message
} else if (response.data.state == 3) {
// 显示response.data.message
}
})
对响应结果类型在进行优化
在项目中,已经开始使用自定义的JsonResult
类作为响应结果类型:
@Data
public class JsonResult implements Serializable {
private Integer state;
private String message;
}
然后,在处理请求或处理异常时,都将响应JsonResult
类型的结果,例如:
@PostMapping("/add-new")
public JsonResult addNew(@Valid AlbumAddNewParam albumAddNewParam) {
log.debug("开始处理【添加相册】的请求,参数:{}", albumAddNewParam);
albumService.addNew(albumAddNewParam);
JsonResult jsonResult = new JsonResult(); // <<< 本次关注的代码
jsonResult.setState(1); // <<< 本次关注的代码
jsonResult.setMessage("添加成功!"); // <<< 本次关注的代码
return jsonResult; // <<< 本次关注的代码
}
以上使用了4行代码来创建JsonResult
对象、为属性赋值并返回,是不合适的,应该简化这些代码!
解决方案-1:构造方法
在JsonResult
类中添加带参数的构造方法:
@Data
public class JsonResult implements Serializable {
private Integer state;
private String message;
public JsonResult(Integer state, String message) {
this.state = state;
this.message = message;
}
}
则控制器的代码可以调整为:
@PostMapping("/add-new")
public JsonResult addNew(@Valid AlbumAddNewParam albumAddNewParam) {
log.debug("开始处理【添加相册】的请求,参数:{}", albumAddNewParam);
albumService.addNew(albumAddNewParam);
return new JsonResult(1, "添加成功!"); // <<< 新的代码
}
以上做法可以成功的精简构建JsonResult
对象的代码,但是,存在问题:
-
传入的参数的可读性可能较差,当构造方法有多个参数时,可能不便于了解传入的各值是哪些属性的值
-
构造方法的名称必须与类名完全相同,则不便于表达某些含义
解决方案-2:链式方法
将JsonResult
类中的Setter方法全部调整为返回当前对象的方法:
// @Data
public class JsonResult implements Serializable {
private Integer state;
private String message;
public JsonResult setState(Integer state) {
this.state = state;
return this;
}
public JsonResult setMessage(String message) {
this.message = message;
return this;
}
// 其它方法
}
则控制器中的代码可以调整为:
@PostMapping("/add-new")
public JsonResult addNew(@Valid AlbumAddNewParam albumAddNewParam) {
log.debug("开始处理【添加相册】的请求,参数:{}", albumAddNewParam);
albumService.addNew(albumAddNewParam);
// return new JsonResult(1, "添加成功");
return new JsonResult().setState(1).setMessage("添加成功");
}
如果你的项目是基于Lombok的,其实,你并不需要自行调整类中的Setter方法,只需要在类上添加@Accessors(chain = true)
注解配置,则Lombok生成的Setter方法也都是返回当前对象的,所以,也可以使用链式写法!
@Data
@Accessors(chain = true)
public class JsonResult implements Serializable {
private Integer state;
private String message;
}
以上做法可以成功的解决构造方法多参数时各参数值意义不明确的问题,但是,这种做法也存在问题:
-
当属性较多时,需要调用多个Setter方法,则代码篇幅可能较长
-
无解
-
不一定真的的算是“问题”
-
解决方法-3:静态方法
在JsonResult
类中添加新的方法:
public static JsonResult ok() {
JsonResult jsonResult = new JsonResult();
jsonResult.setState(1);
return jsonResult;
}
则处理请求的方法可以调整为:
@PostMapping("/add-new")
public JsonResult addNew(@Valid AlbumAddNewParam albumAddNewParam) {
// ...
return JsonResult.ok();
}
这种做法的优点在于:方法名称是自定义的,可以使用更加贴切的方法名,以表示设计意图!
使用枚举
由于“操作失败”的原因可能有多种(例如相册名称已经被占用、提交的请求参数基本格式有误等),所以,表示失败的方法需要参数,使得“操作失败”时可以传入不同的值,最终可以向客户端响应不同的结果,例如:
public static JsonResult fail(Integer state, String message) {
JsonResult jsonResult = new JsonResult();
jsonResult.setState(state);
jsonResult.setMessage(message);
return jsonResult;
}
则处理异常时可以是:
@ExceptionHandler
public JsonResult handleServiceException(ServiceException e) {
return JsonResult.fail(2, e.getMessage());
}
为了保证传入的参数是有效的,避免随意传值(毕竟以上fail
方法的第1个参数是Integer
类型的,有40多亿种可能的值),可以使用枚举进行限制!
首先,定义枚举类型:
public enum ServiceCode {
OK(20000),
ERR_BAD_REQUEST(40000),
ERR_CONFLICT(40900),
ERR_UNKNOWN(99999)
;
private Integer value;
public Integer getValue() {
return value;
}
ServiceCode(Integer value) {
this.value = value;
}
}
然后,将fail()
方法的第1个参数类型改为枚举,并在方法体中获取枚举参数值对应的数值:
public static JsonResult fail(ServiceCode serviceCode, String message) {
JsonResult jsonResult = new JsonResult();
jsonResult.setState(serviceCode.getValue());
jsonResult.setMessage(message);
return jsonResult;
}
则在处理异常时需要调整为:
@ExceptionHandler
public JsonResult handleServiceException(ServiceException e) {
return JsonResult.fail(ServiceCode.ERR_CONFLICT, e.getMessage());
}
关于枚举