
今天我们将对图书管理系统进行收尾工作,今天的开发任务有两个
- 实现统一数据返回格式
- 实现统一异常的处理
统一数据返回格式
在前面文章讲到,通过拦截器实现了登录验证功能,但是对于数据的返回还没有处理
public enum ResultStatus {
SUCCESS(200),
UNLOGIN(-1),
FAIL(-2);
private Integer code;
ResultStatus(int code) {
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
package com.example.demo.model;
import lombok.Data;
@Data
public class Result<T> {
private int status;//1. 200成功 2. -1未登录 3. -2 异常
private String errorMsg;//错误信息
private T data;
public static <T> Result success(T data) {
Result result = new Result();
result.setStatus(ResultStatus.SUCCESS.getCode());
result.setErrorMsg("");
result.setData(data);
return result;
}
public static Result unLogin() {
Result result = new Result();
result.setErrorMsg("未登录,请登录后访问");
result.setStatus(ResultStatus.UNLOGIN.getCode());
return result;
}
public static <T> Result fail(String msg) {
Result result = new Result();
result.setErrorMsg(msg);
result.setStatus(ResultStatus.FAIL.getCode());
result.setData("");
return result;
}
}
回顾之前内容,Result进行了封装
@RequestMapping("/getListByPage")
public Result<PageResult<BookInfo>> getListByPage(PageRequest pageRequest, HttpSession session) {
log.info("获取图书列表, pageRequest:{}", pageRequest);
UserInfo userInfo = (UserInfo) session.getAttribute("session_user_key");
if (userInfo==null || userInfo.getId()<0 ||
"".equals(userInfo.getUserName())){
return Result.unLogin();
}
//⽤⼾登录, 返回图书列表
PageResult<BookInfo> pageResult =
bookService.getBookListByPage(pageRequest);
return Result.success(pageResult);
}
getBookList();
function getBookList() {
$.ajax({
type: "get",
url: "/book/getListByPage" + location.search,
success: function (result) {
console.log(result);
if (result == null || result.data == null) {
location.href = "login.html";
return;
}
var finalHtml = "";
var data = result.data;
for (var book of data.records) {
finalHtml += '<tr>';
finalHtml += '<td><input type="checkbox" name="selectBook" value = "' + book.id + '" id = "selectBook" class="book-select" ></td > ';
finalHtml += '<td>' + book.id + '</td>';
finalHtml += '<td>' + book.bookName + '</td>';
finalHtml += '<td>' + book.author + '</td>';
finalHtml += '<td>' + book.count + '</td>';
finalHtml += '<td>' + book.price + '</td>';
finalHtml += '<td>' + book.publish + '</td>';
finalHtml += '<td>' + book.statusCN + '</td>';
finalHtml += '<td><div class="op">';
finalHtml += '<a href="book_update.html?bookId=' + book.id +
'">修改</a>';
finalHtml += '<a href="javascript:void(0)" onclick = "deleteBook(' + book.id + ')" > 删除</a > ';
finalHtml += '</div></td>';
finalHtml += "</tr>";
}
$("tbody").html(finalHtml);
$("#pageContainer").jqPaginator({
totalCounts: data.total, //总记录数
pageSize: 10, //每⻚的个数
visiblePages: 5, //可视⻚数
currentPage: data.pageRequest.currentPage, //当前⻚码
first: '<li class="page-item"><a class="page-link">⾸⻚</a></li> ', prev: '<li class="page-item"><a class="page-link" href = "javascript:void(0);" > 上⼀⻚<\/a><\/li>',
next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下⼀⻚<\/a><\/li>',
last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后⼀⻚<\/a><\/li>',
page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',
//⻚⾯初始化和⻚码点击时都会执⾏
onPageChange: function (page, type) {
if (type != 'init') {
location.href = "book_list.html?currentPage=" + page;
}
}
});
}
// 初始调用时无需传参(函数已设置默认值)
});
}
同时需要对前端代码进行修改
getBookList();
function getBookList() {
$.ajax({
type: "get",
url: "/book/getListByPage" + location.search,
success: function (result) {
console.log(result);
if (result.data != null || result.data.records != null) {
var finalHtml = "";
var data = result.data;
for (var book of data.records) {
finalHtml += '<tr>';
finalHtml += '<td><input type="checkbox" name="selectBook" value = "' + book.id + '" id = "selectBook" class="book-select" ></td > ';
finalHtml += '<td>' + book.id + '</td>';
finalHtml += '<td>' + book.bookName + '</td>';
finalHtml += '<td>' + book.author + '</td>';
finalHtml += '<td>' + book.count + '</td>';
finalHtml += '<td>' + book.price + '</td>';
finalHtml += '<td>' + book.publish + '</td>';
finalHtml += '<td>' + book.statusCN + '</td>';
finalHtml += '<td><div class="op">';
finalHtml += '<a href="book_update.html?bookId=' + book.id +
'">修改</a>';
finalHtml += '<a href="javascript:void(0)" onclick = "deleteBook(' + book.id + ')" > 删除</a > ';
finalHtml += '</div></td>';
finalHtml += "</tr>";
}
$("tbody").html(finalHtml);
$("#pageContainer").jqPaginator({
totalCounts: data.total, //总记录数
pageSize: 10, //每⻚的个数
visiblePages: 5, //可视⻚数
currentPage: data.pageRequest.currentPage, //当前⻚码
first: '<li class="page-item"><a class="page-link">⾸⻚</a></li> ', prev: '<li class="page-item"><a class="page-link" href = "javascript:void(0);" > 上⼀⻚<\/a><\/li>',
next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下⼀⻚<\/a><\/li>',
last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后⼀⻚<\/a><\/li>',
page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',
//⻚⾯初始化和⻚码点击时都会执⾏
onPageChange: function (page, type) {
if (type == "change") {
location.href = "book_list.html?currentPage=" + page;
}
}
});
}
else{
alert("没有图书");
location.href = "login.html";
}
}
// 初始调用时无需传参(函数已设置默认值)
});
}

但是如果每一个接口都这样写,岂不太麻烦了一点儿。
其实spring boot为我们提供了统一数据格式返回的功能
快速入门
统⼀的数据返回格式使⽤ @ControllerAdvice 和ResponseBodyAdvice 的⽅式实现@ControllerAdvice 表⽰控制器通知类添加类
@ControllerAdvice注解已经包含五大注解,会交给Spring进行管理
ResponseAdvice ,实现 ResponseBodyAdvice 接⼝,并在类上添加@ControllerAdvice 注解
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return Result.success(body);
}
}
- supports方法:判断是否要执行beforeBodyWrite方法.true为执行,false不执行.通过该⽅法可以选择哪些类或哪些方法的response要进行处理,其他的不进行处理
- beforeBodyWrite方法:对response方法进行具体操作处理
添加统⼀数据返回格式之前:

添加统⼀数据返回格式之后:

存在问题
问题现象:
我们继续测试登录的接⼝:

结果显⽰,发⽣内部错误
查看⽇志,⽇志报错

测试⼏种不同的返回结果,发现只有返回结果为String类型时才有这种错误发⽣,这里不在演示
解决方案:

统一格式返回的优点
- ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据
- 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接⼝都是这样返回的.
- 有利于项⽬统⼀数据的维护和修改.
- 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容
统一异常处理
当我们程序出现异常的时候,Spring会对我们进行异常相关的处理,当发生异常的时候,它会走到BasicErrorcontroller类中的 error方法。

我们可以自己来处理异常。
统⼀异常处理使⽤的是 @ControllerAdvice+@ExceptionHandler 来实现的
@ControllerAdvice 表⽰控制器通知类
@ExceptionHandler 是异常处理器
两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件
记得加: @ResponseBody
@Slf4j
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler
public Result<PageResult<BookInfo>> handler(Exception e){
log.info("发生异常e",e);
Result<PageResult<BookInfo>> result=new Result<>();
result.setErrorMsg(e.getMessage());
result.setData(null);
result.setStatus(ResultStatus.FAIL.getCode());
return result;
}
}
当然,只能爆Exception这么宽泛的异常就没有意义了
@ExceptionHandler
public Object handler(Exception e) {
return Result.fail(e.getMessage());
}
//空指针异常
@ExceptionHandler
public Object handler(NullPointerException e) {
return Result.fail(e.getMessage());
}
//算数异常
@ExceptionHandler
public Object handler(ArithmeticException e) {
return Result.fail(e.getMessage());
}
现在我们在测试类中添加两个异常观察一下:
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/t1")
public String t1() {
String s = "1";
char a = s.charAt(2);
return "t1";
}
@RequestMapping("/t2")
public Integer t2() {
int s = 10/0;
return 2;
}
@RequestMapping("/t3")
public Boolean t3() {
return true;
}
}

前端代码
。。。。。。。像上面那种把每个返回结果修改为Result后进行少量修改即可