1. 程式人生 > >【SpringBoot】Http請求統一異常(返回資料)處理與單元測試

【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原始碼下載地址:

本文章由[諳憶]編寫, 所有權利保留。
歡迎轉載,分享是進步的源泉。