【SpringBoot】Http請求統一異常(返回資料)處理與單元測試
對返回資料格式的統一
首先規定一下錯誤的輸出格式:
{
"code": 1,
"msg": "提示",
"data": null
}
data是一個物件
首先定義一個http請求返回的類
package cn.chenhaoxiang.common.entity;
/**
* Created with IntelliJ IDEA.
* User: 陳浩翔.
* Date: 2018/1/7.
* Time: 下午 2:17.
* Explain: http請求返回的最外層物件
*/
public class Result<T> {
/**
* 錯誤碼
*/
private Integer code;
/**
* 提示資訊
*/
private String msg;
/**
* 返回的具體內容
*/
private T data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg () {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
然後可以定義一個工具類:
package cn.chenhaoxiang.utils;
import cn.chenhaoxiang.common.entity.Result;
/**
* Created with IntelliJ IDEA.
* User: 陳浩翔.
* Date: 2018/1/7.
* Time: 下午 2:45.
* Explain:
*/
public class ResultUtil {
public static Result success(Object object){
Result result = new Result();
result.setCode(1);
result.setMsg("成功");
result.setData(object);
return result;
}
public static Result successNoData(){
return success(null);
}
public static Result error(Integer code,String msg){
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
這個ResultUtil中的方法,其實寫在BaseController中也挺不錯的
People實體類中:
@NotBlank(message = "名字不能為空")//String 不是 null 且去除兩端空白字元後的長度(trimmed length)大於 0
private String name;
@NotNull(message = "分數必傳")//CharSequence, Collection, Map 和 Array 物件不能是 null, 但可以是空集(size = 0)
private Double score;
private String address;
@Min(value = 18,message = "年齡必須大於18")//message為提示 20180103
private Integer age;
屬性上的註解,都是限制條件,註釋上有說明
在Controller中使用的時候:
/**
* 新增一個物件
* 增加一個功能,如果年齡大於18,就不讓新增進資料庫 20180103
* @param people
* @return
*/
@PostMapping(value = "/add")
public Result<People> peopleAdd(@Valid People people, BindingResult bindingResult){//@Valid 註解表示使用資料校驗 People類中對年齡進行了限制 ,驗證返回結果會在bindingResult物件中 20180103
//@RequestParam(value = "people") 直接傳類的時候,建議不要使用RequestParam註解
//當然,你可以選擇每一個引數都寫上,但沒必要,更多的時候是直接傳類物件,注意url的引數名和類中屬性名對上
if(bindingResult.hasErrors()){//驗證出現錯誤
return ResultUtil.error(0,bindingResult.getFieldError().getDefaultMessage());
}
return ResultUtil.success( peopleDao.save(people));
}
訪問,看結果:
失敗的只演示這個,再演示一個新增成功的
這樣完成了對返回資料格式的統一
對不同結果統一返回處理
獲取分數判斷
如果分數小於60,返回”不及格”
分數大於60且小於80,返回”良好”
下面來看程式碼吧
在Service層的實現類中
/**
* 往往業務有點複雜,不能直接返回String,比如在這裡
* @param id
*/
@Override
public void getScore(Integer id) throws Exception {
People people = peopleDao.findOne(id);
Double score = people.getScore();
if(score<60){
//返回 "不及格"
throw new PeopleException(ResultEnum.FLUNK);
}else if(score>=60 && score<80 ){
//返回 "良好"
throw new PeopleException(ResultEnum.WELL);
}
//前面的只是作為校驗,也就是條件,條件滿足後才有後面的操作
//如果分數大於80,則給他進行另外的操作,這個時候就不好返回字串了
//有的可能用數字來標誌,返回1,2,3等等,然後在Controller再判斷,這樣是可以達到效果,但是程式碼寫起來很彆扭,在service中判斷一次,controller還需要再判斷一次
// 而且返回1,2,3都是自己標記的,假如這個標誌多了呢,是不是很麻煩
//這個時候,統一異常處理就派上用處了
}
自定義列舉ResultEnum
package cn.chenhaoxiang.enums;
/**
* Created with IntelliJ IDEA.
* User: 陳浩翔.
* Date: 2018/1/7.
* Time: 下午 3:31.
* Explain:
*/
public enum ResultEnum {
UNKONW_ERROR(-1,"未知錯誤"),
ERROR(0,"失敗"),
SUCCESS(1,"成功"),
FLUNK(100,"不及格"),
WELL(101,"良好")
;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
private Integer code;
private String msg;
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
自定義異常類PeopleException
package cn.chenhaoxiang.exception;
import cn.chenhaoxiang.enums.ResultEnum;
/**
* Created with IntelliJ IDEA.
* User: 陳浩翔.
* Date: 2018/1/7.
* Time: 下午 3:18.
* Explain:
*/
public class PeopleException extends RuntimeException {//不要繼承Exception ,Spring只對你的異常是RuntimeException的才會進行事務回滾
private Integer code;
public PeopleException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
}
public PeopleException(Integer code,String message) {
super(message);//父類本身就有message
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
異常捕獲 統一異常返回格式
package cn.chenhaoxiang.handle;
import cn.chenhaoxiang.common.entity.Result;
import cn.chenhaoxiang.enums.ResultEnum;
import cn.chenhaoxiang.exception.PeopleException;
import cn.chenhaoxiang.utils.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Created with IntelliJ IDEA.
* User: 陳浩翔.
* Date: 2018/1/7.
* Time: 下午 3:13.
* Explain:異常捕獲 統一異常返回格式
*/
@ControllerAdvice
public class ExceptionHandle {
private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
@ExceptionHandler(value = Exception.class)//宣告需要捕獲的異常類 - 寫成PeopleException,就是隻會捕獲PeopleException異常了
@ResponseBody //由於返回瀏覽器那邊是json格式,就需要這個
public Result handle(Exception e){
if(e instanceof PeopleException){
PeopleException peopleException = (PeopleException) e;
return ResultUtil.error(peopleException.getCode(),peopleException.getMessage());
}else {
logger.error("[系統異常]-{}",e);
return ResultUtil.error(ResultEnum.UNKONW_ERROR);
}
}
}
固定返回格式,避免邏輯在一個地方處理,另一個地方重複處理,我們用異常來處理
將code和message固定成列舉,來統一管理
單元測試
測試對任何專案來說是必不可少的
測試Service中的findOne方法
第一種方式,可以自己去test目錄下寫測試類
package cn.chenhaoxiang;
import cn.chenhaoxiang.entity.People;
import cn.chenhaoxiang.service.PeopleService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Created with IntelliJ IDEA.
* User: 陳浩翔.
* Date: 2018/1/7.
* Time: 下午 3:52.
* Explain:
*/
@RunWith(SpringRunner.class)//表示在測試環境中跑
@SpringBootTest//表示將啟動整個spring 的工程
public class PeopleServiceTest {
@Autowired
private PeopleService peopleService;
@Test
public void findOneTest(){
People people = peopleService.findOne(7);
//使用斷言
Assert.assertEquals(new Integer(20),people.getAge());
}
}
第二種方式,如果你是使用的IDEA這個工具,可以直接這樣
PeopleService介面的findOne方法上右鍵,出現如下的
選擇go to,然後點選test
因為我已經用方式一建立了一個測試方法,沒事,可以再建立一個演示一下
選擇需要測試的方法
也就是勾上你需要測試的方法
點選ok,會給你在test目錄下建立如下的類
package cn.chenhaoxiang.service;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created with IntelliJ IDEA.
* User: 陳浩翔.
* Date: 2018/1/7.
* Time: 下午 4:04.
* Explain:
*/
public class PeopleServiceTest {
@Test
public void findOne() throws Exception {
}
}
然後你進行新增類註解
@RunWith(SpringRunner.class)//表示在測試環境中跑
@SpringBootTest//表示將啟動整個spring 的工程
和注入介面
@Autowired
private PeopleService peopleService;
其他的就類似方式一了,只是相對於方式一,少寫了一點程式碼,對應的包,類,方法名都給你建好了。
對Controller測試
我們對controller的獲取所有人的方法進行測試,也就是測試
/**
* 獲取所有的人的資料
* @return
*/
@GetMapping(value = "/peoples")
public List<People> getPeople(){
logger.info("getPeople");
return peopleDao.findAll();//一句SQL都沒寫,很方便
}
我們在IDEA中使用方式二,右鍵go to的方式進行
首先我們相對與之前的service測試需要多加一個@AutoConfigureMockMvc註解
package cn.chenhaoxiang.controller;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import static org.junit.Assert.*;
/**
* Created with IntelliJ IDEA.
* User: 陳浩翔.
* Date: 2018/1/7.
* Time: 下午 4:09.
* Explain:
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc //Controller測試的,需要是用這個註解
public class IndexControllerTest {
// @Autowired
// private IndexController indexController;
// @Test
// public void getPeople1() throws Exception {
// indexController.getPeople();//這樣只是對方法進行了測試
// //我們想用url進行測試,而且可以進行post或者get方法
// }
@Autowired
private MockMvc mvc;
@Test
public void getPeople() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/peoples"))//如果是post,就是呼叫post方法
.andExpect(MockMvcResultMatchers.status().isOk());//對返回的狀態碼進行判斷
// .andExpect(MockMvcResultMatchers.content().string("a"))//對返回值進行判斷,這裡是200
}
//當進行打包的時候,會執行所有的單元測試方法,如果有失敗,就會出現打包失敗
//如果打包的時候希望跳過單元測試,則打包命令為
// mvn clean package -Damven.test.skip=true
}
可以在測試輸出中看到結果的
然後測試一下post請求,並帶引數的
/**
* post測試,並帶引數
* @throws Exception
*/
@Test
public void peopleEdit() throws Exception {
//傳送請求
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.post("/edit").param("id","6")
.param("name","測試Controller")
.param("score","20.00")
.param("age","29"))//如果是post,就是呼叫post方法
.andExpect(MockMvcResultMatchers.status().isOk());//對返回的狀態碼進行判斷
MvcResult mvcResult = resultActions.andReturn();
String result = mvcResult.getResponse().getContentAsString();
System.out.println("客戶端獲得反饋資料:" + result);
}
傳遞的是People引數,在這裡我們傳參不要直接傳People物件或者該物件的json,應該對每個屬性都用param賦值傳
完整的Controller測試類
package cn.chenhaoxiang.controller;
import cn.chenhaoxiang.entity.People;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import static org.junit.Assert.*;
/**
* Created with IntelliJ IDEA.
* User: 陳浩翔.
* Date: 2018/1/7.
* Time: 下午 4:09.
* Explain:
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc //Controller測試的,需要是用這個註解
public class IndexControllerTest {
// @Autowired
// private IndexController indexController;
// @Test
// public void getPeople1() throws Exception {
// indexController.getPeople();//這樣只是對方法進行了測試
// //我們想用url進行測試,而且可以進行post或者get方法
// }
@Autowired
private MockMvc mvc;
@Test
public void getPeople() throws Exception {
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get("/peoples"))//如果是post,就是呼叫post方法
.andExpect(MockMvcResultMatchers.status().isOk());//對返回的狀態碼進行判斷,這個isOK是200
// .andExpect(MockMvcResultMatchers.content().string("a"))//對返回值進行判斷
MvcResult mvcResult = resultActions.andReturn();
String result = mvcResult.getResponse().getContentAsString();
System.out.println("客戶端獲得反饋資料:" + result);
}
//當進行打包的時候,會執行所有的單元測試方法,如果有失敗,就會出現打包失敗
//如果打包的時候希望跳過單元測試,則打包命令為
// mvn clean package -Damven.test.skip=true
/**
* post測試,並帶引數
* @throws Exception
*/
@Test
public void peopleEdit() throws Exception {
//傳送請求
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.post("/edit").param("id","6")
.param("name","測試Controller")
.param("score","20.00")
.param("age","29"))//如果是post,就是呼叫post方法
.andExpect(MockMvcResultMatchers.status().isOk());//對返回的狀態碼進行判斷
MvcResult mvcResult = resultActions.andReturn();
String result = mvcResult.getResponse().getContentAsString();
System.out.println("客戶端獲得反饋資料:" + result);
}
}
原始碼下載地址:
GITHUB原始碼下載地址:
本文章由[諳憶]編寫, 所有權利保留。
歡迎轉載,分享是進步的源泉。