springboot服务器没响应,SpringBoot统一异常和Http响应

1.2. Http响应内容统一封装

我们在开发前端和后端进行交互服务过程中,受制于前后端的工作职责明确,在交互协议的定义上理解也较为不同,造成一个项目服务中重复定义交互内容以及编码上重复编写,不利于项目维护。所以基于此,将后端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端,进行再次封装,供前端以及外部调用。

通常情况下我们后端返回给前端都会采用 JSON 的定义,具体如下:

{

// 返回状态码

code:integer,

// 返回信息描述

message:string,

// 返回值

data:object

}

code状态码

在状态码的定义上,在满足业务需求的基础上,避免凌乱,一般业界同行做法就是参考HTTP请求返回的状态码。

具体如 百度 - HTTP状态码。

这里我贴出我将我项目中的常用的罗列出来,供大家参考。

package xyz.wongs.drunkard.base.message.enums;

/**

* @ClassName

* @Description

* 1000~1999 区间表示参数错误

* 2000~2999 区间表示用户错误

* 3000~3999 区间表示接口异常

* @author WCNGS@QQ.COM

* @Github https://github.com/rothschil

* @date 2020/8/2 13:31

* @Version 1.0.0

*/

public enum ResultCode {

/** 成功 **/

SUCCESS(0,"成功"),

/** 失败 **/

FAILURE(-1,"失败"),

EXCEPTION(201, "未知异常"),

RUNTIME_EXCEPTION(202, "运行时异常"),

NULL_POINTER_EXCEPTION(203, "空指针异常"),

CLASS_CAST_EXCEPTION(204, "类型转换异常"),

IO_EXCEPTION(205, "IO异常"),

SYSTEM_EXCEPTION(210, "系统异常"),

NOT_FOUND(404, "Not Found"),

/**

* 1000~1999 区间表示参数错误

*/

PARAMS_IS_INVALID(1001,"参数无效"),

PARAMS_IS_BANK(1002,"参数为空"),

PARAMS_TYPE_BIND_ERROR(1003,"参数类型错误"),

PARAMS_NOT_COMPLETE(1004,"参数缺失"),

/**

* 2000~2999 区间表示用户错误

*/

USER_NOT_LOGGED_IN(2001,"用户未登录,访问路径需要验证"),

USER_NOT_LOGIN_ERROR(2002,"用户不存在或密码错误"),

USER_ACCOUNT_FORBIDDEN(2003,"用户被禁用"),

USER_NOT_EXIST(2004,"用户不存在"),

USER_HAS_EXISTED(2005,"用户已存在"),

USER_IS_EXPIRED(2006,"用户账号已过期"),

USER_FIRST_LANDING(2007, "首次登录"),

USER_TOKEN_EXPIRED(2008,"Token过期"),

USER_TOKEN_GENERTATION_FAIL(2009,"生成Token失败"),

USER_SIGN_VERIFI_NOT_COMPLIANT(2010,"签名校验不合规"),

USER_PASSWORD_RESET_FAILED(2011, "重置密码失败"),

USER_UNKONWN_INDENTITY(2012, "未知身份"),

MANY_USER_LOGINS(2111,"多用户在线"),

TOO_MANY_PASSWD_ENTER(2112, "密码输入次数过多"),

VERIFICATION_CODE_INCORECT(2202,"图形验证码不正确"),

VERIFICATION_CODE_FAIL(2203,"图形验证码生产失败"),

/**

* 3000~3999 区间表示接口异常

*/

API_EXCEPTION(3000, "接口异常"),

API_NOT_FOUND_EXCEPTION(3002, "接口不存在"),

API_REQ_MORE_THAN_SET(3003, "接口访问过于频繁,请稍后再试"),

API_IDEMPOTENT_EXCEPTION(3004, "接口不可以重复提交,请稍后再试"),

API_PARAM_EXCEPTION(3005, "参数异常"),

API_PARAM_MISSING_EXCEPTION(3006, "缺少参数"),

API_METHOD_NOT_SUPPORTED_EXCEPTION(3007, "不支持的Method类型"),

API_METHOD_PARAM_TYPE_EXCEPTIION(3008, "参数类型不匹配"),

ARRAY_EXCEPTION(11001, "数组异常"),

ARRAY_OUT_OF_BOUNDS_EXCEPTION(11002, "数组越界异常"),

JSON_SERIALIZE_EXCEPTION(30000, "序列化数据异常"),

JSON_DESERIALIZE_EXCEPTION(30001, "反序列化数据异常"),

READ_RESOURSE_EXCEPTION(31002, "读取资源异常"),

READ_RESOURSE_NOT_FOUND_EXCEPTION(31003, "资源不存在异常"),

DATA_EXCEPTION(32004, "数据异常"),

DATA_NOT_FOUND_EXCEPTION(32005, "未找到符合条件的数据异常"),

DATA_CALCULATION_EXCEPTION(32006, "数据计算异常"),

DATA_COMPRESS_EXCEPTION(32007, "数据压缩异常"),

DATA_DE_COMPRESS_EXCEPTION(32008, "数据解压缩异常"),

DATA_PARSE_EXCEPTION(32009, "数据转换异常"),

ENCODING_EXCEPTION(33006, "编码异常"),

ENCODING_UNSUPPORTED_EXCEPTION(33006, "编码不支持异常"),

DATE_PARSE_EXCEPTION(34001, "日期转换异常"),

MAILE_SEND_EXCEPTION(35001, "邮件发送异常");

/**

*

*/

private Integer code;

/**

*

*/

private String msg;

ResultCode(Integer code, String msg) {

this.code = code;

this.msg = msg;

}

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;

}

}

message内容

这个不解释啦, 就是编码的文字的意义,说清楚就行,没必要太较真,自行脑补。

data数据

这就是业务具体数据啦,根据具体业务,内容也不同,这一章节也没必要说。

这里小结下,我们除了要有需要定义的内容有两块:返回的JSON消息体(这里区分正常响应返回、异常响应返回),还需要一套状态码详细定义;再有我们这里做的是WEB,既然做通用,怎能少拦截器。

摆脱了繁琐的文字,下面开始张罗着贴实现代码啦。

1.2.1. 消息体

结合我们定义的状态码,我们返回的消息体主要实现一个 Serializable,不要问我为什么。

1.2.1.1. 正常响应

package xyz.wongs.drunkard.base.message.response;

import lombok.Data;

import xyz.wongs.drunkard.base.message.enums.ResultCode;

import java.io.Serializable;

/**

* @ClassName

* @Description

* @author WCNGS@QQ.COM

* @Github https://github.com/rothschil

* @date 2020/8/2 13:48

* @Version 1.0.0

*/

@Data

public class Result implements Serializable {

private static final long serialVersionUID = -4505655308965878999L;

private Integer code;

private String message;

private Object data;

private Result() {

}

public Result(ResultCode resultCode, Object data) {

this.code = resultCode.getCode();

this.message = resultCode.getMsg();

this.data = data;

}

private void setResultCode(ResultCode resultCode) {

this.code = resultCode.getCode();

this.message = resultCode.getMsg();

}

/** 返回成功

* @Description

* @param

* @return xyz.wongs.drunkard.base.message.response.R

* @throws

* @date 20/11/13 17:15

*/

public static Result success() {

Result result = new Result();

result.setResultCode(ResultCode.SUCCESS);

return result;

}

/** 返回成功

* @Description

* @param

* @return xyz.wongs.drunkard.base.message.response.R

* @throws

* @date 20/11/13 17:15

*/

public static Result success(Object data) {

Result result = new Result();

result.setResultCode(ResultCode.SUCCESS);

result.setData(data);

return result;

}

/** 返回失败

* @Description

* @param

* @return xyz.wongs.drunkard.base.message.response.R

* @throws

* @date 20/11/13 17:15

*/

public static Result fail(Integer code, String message) {

Result result = new Result();

result.setCode(code);

result.setMessage(message);

return result;

}

/** 返回失败

* @Description

* @param

* @return xyz.wongs.drunkard.base.message.response.R

* @throws

* @date 20/11/13 17:15

*/

public static Result fail(ResultCode resultCode) {

Result result = new Result();

result.setResultCode(resultCode);

return result;

}

}

1.2.1.2. 异常响应

package xyz.wongs.drunkard.base.message.response;

import lombok.Data;

import xyz.wongs.drunkard.base.message.enums.ResultCode;

import java.io.Serializable;

/**

* @author WCNGS@QQ.COM

* @ClassName ErrorResult

* @Description 异常错误的返回信息实体

* @Github https://github.com/rothschil

* @date 20/11/18 10:42

* @Version 1.0.0

*/

@Data

public class ErrorResult implements Serializable {

private static final long serialVersionUID = -4505655308965878999L;

/**

* 错误编码

**/

private Integer code;

/**

* 消息描述

**/

private String msg;

/**

* 错误

**/

private String exception;

public static ErrorResult fail(ResultCode resultCode, Throwable e, String message) {

ErrorResult errorResult = ErrorResult.fail(resultCode, e);

errorResult.setMsg(message);

return errorResult;

}

public static ErrorResult fail(ResultCode resultCode, Throwable e) {

ErrorResult errorResult = new ErrorResult();

errorResult.setCode(resultCode.getCode());

errorResult.setMsg(resultCode.getMsg());

errorResult.setException(e.getClass().getName());

return errorResult;

}

public static ErrorResult fail(Integer code, String message) {

ErrorResult errorResult = new ErrorResult();

errorResult.setCode(code);

errorResult.setMsg(message);

return errorResult;

}

}

这样两个消息体就写完啦。

1.2.2. 拦截器

我们这里需要做的就是利用拦截器拦截请求,检查判断是否此请求返回的值需要包装。核心就是判断一个注解annoation是否存在方法或类中。

为了演示的完整,我将代码贴完整。

1.2.2.1. Annoation注解

/**

* @ClassName ResponseResult

* @Description

* @author WCNGS@QQ.COM

* @Github https://github.com/rothschil

* @date 20/10/30 21:57

* @Version 1.0.0

*/

@Target({ElementType.TYPE,ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface ResponseResult {

}

1.2.2.2. 拦截器

package xyz.wongs.drunkard.base.interceptor;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Component;

import org.springframework.web.method.HandlerMethod;

import org.springframework.web.servlet.HandlerInterceptor;

import xyz.wongs.drunkard.base.message.annoation.ResponseResult;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.lang.reflect.Method;

/**

* @author WCNGS@QQ.COM

* @ClassName ResponseResultInterceptor

* @Description 请求的拦截器

* @Github https://github.com/rothschil

* @date 20/10/30 22:08

* @Version 1.0.0

*/

@Slf4j

@Component

public class ResponseResultInterceptor implements HandlerInterceptor {

private static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

if (handler instanceof HandlerMethod) {

final HandlerMethod handlerMethod = (HandlerMethod) handler;

final Class> clazz = handlerMethod.getBeanType();

final Method method = handlerMethod.getMethod();

if (clazz.isAnnotationPresent(ResponseResult.class)) {

request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));

} else if (method.isAnnotationPresent(ResponseResult.class)) {

request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class));

}

}

return true;

}

}

着十几行代码的核心处理逻辑,就是获取此请求Annoation注解,是否需要返回值包装,并设置一个属性标记,交由下一处理ResponseResultHandler来具体封装返回值。

细心的人会发现这里只处置正常成功的内容返回,对于异常的内容并未处置。关于异常处置我理解统一放在一起来编写,这样代码结构性会更好。由此引出下一章节,全局异常。

package xyz.wongs.drunkard.base.handler;

import lombok.extern.slf4j.Slf4j;

import org.springframework.core.MethodParameter;

import org.springframework.http.MediaType;

import org.springframework.http.converter.HttpMessageConverter;

import org.springframework.http.server.ServerHttpRequest;

import org.springframework.http.server.ServerHttpResponse;

import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import xyz.wongs.drunkard.base.message.annoation.ResponseResult;

import xyz.wongs.drunkard.base.message.response.Result;

import javax.servlet.http.HttpServletRequest;

/**

* @ClassName ResponseResultHandler

* @Description 消息返回体

* @author WCNGS@QQ.COM

* @Github https://github.com/rothschil

* @date 20/11/10 09:28

* @Version 1.0.0

*/

@Slf4j

@ControllerAdvice(basePackages = "xyz.wongs.drunkard")

public class ResponseResultHandler implements ResponseBodyAdvice {

private static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

/**

* @Description 判断是否要执行 beforeBodyWrite 方法,true为执行,false不执行,有注解标记的时候处理返回值

* @param returnType

* @param converterType

* @return boolean

* @throws

* @date 20/11/13 10:50

*/

@Override

public boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) {

ServletRequestAttributes sra =(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();

HttpServletRequest request = sra.getRequest();

ResponseResult responseResult = (ResponseResult)request.getAttribute(RESPONSE_RESULT_ANN);

return responseResult==null?false:true;

}

@Override

public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectContentType, Class extends HttpMessageConverter>> selectConverterType, ServerHttpRequest request, ServerHttpResponse response) {

log.error(" ENTER MSG .... Excu");

if(body instanceof Result){

return (Result) body;

}

return Result.success(body);

}

}

1.2.2.3. 全局异常

这里所有的异常都使用到 ErrorResult 类。

package xyz.wongs.drunkard.base.message.exception;

import lombok.extern.slf4j.Slf4j;

import org.springframework.http.HttpStatus;

import org.springframework.validation.BindException;

import org.springframework.web.bind.MethodArgumentNotValidException;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.ResponseStatus;

import org.springframework.web.bind.annotation.RestControllerAdvice;

import xyz.wongs.drunkard.base.message.enums.ResultCode;

import xyz.wongs.drunkard.base.message.response.ErrorResult;

import javax.servlet.http.HttpServletRequest;

import javax.validation.ConstraintViolationException;

/**

* @author WCNGS@QQ.COM

* @ClassName GlobalExceptionHandler

* @Description 全局异常处理Handler

* @Github https://github.com/rothschil

* @date 2019/9/23 15:03

* @Version 1.0.0

*/

@RestControllerAdvice

@Slf4j

public class GlobalExceptionHandler {

/**

* 参数校验不通过

*

* @param ex

* @return xyz.wongs.drunkard.base.message.response.ErrorResult

* @throws

* @author WCNGS@QQ.COM

* @See

* @date 2019/9/23 17:53

* @since

*/

@ExceptionHandler(value = ConstraintViolationException.class)

@ResponseBody

public ErrorResult handleConstraintViolationException(ConstraintViolationException ex) {

log.error("ConstraintViolationException msg:{}", ex.getMessage());

return ErrorResult.fail(ResultCode.PARAMS_IS_INVALID, ex);

}

/**

* 自定义异常

*

* @param request

* @param ex

* @return xyz.wongs.drunkard.base.message.response.ErrorResult

* @throws

* @author WCNGS@QQ.COM

* @See

* @date 2019/9/23 17:53

* @since

*/

@org.springframework.web.bind.annotation.ExceptionHandler(DrunkardException.class)

@ResponseBody

public ErrorResult handleWeathertopException(HttpServletRequest request, DrunkardException ex) {

log.error("WeathertopRuntimeException code:{},msg:{}", ex.getCode(), ex.getMessage());

return ErrorResult.fail(ex.getCode(), ex.getMessage());

}

/**

* @param e

* @param request

* @return xyz.wongs.drunkard.base.message.response.ErrorResult

* @throws

* @Description 拦截抛出的异常,@ResponseStatus:用来改变响应状态码

* @date 20/11/13 11:14

*/

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)

@ExceptionHandler(Throwable.class)

public ErrorResult handlerThrowable(Throwable e, HttpServletRequest request) {

log.error("发生未知异常!原因是: ", e);

ErrorResult error = ErrorResult.fail(ResultCode.RUNTIME_EXCEPTION, e);

return error;

}

/**

* @param e

* @param request

* @return xyz.wongs.drunkard.base.message.response.ErrorResult

* @throws

* @Description 参数校验异常

* @date 20/11/13 11:14

*/

@ExceptionHandler(BindException.class)

public ErrorResult handleBindExcpetion(BindException e, HttpServletRequest request) {

log.error("发生参数校验异常!原因是:", e);

ErrorResult error = ErrorResult.fail(ResultCode.API_PARAM_EXCEPTION, e, e.getAllErrors().get(0).getDefaultMessage());

return error;

}

@ExceptionHandler(MethodArgumentNotValidException.class)

public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {

log.error("发生参数校验异常!原因是:", e);

ErrorResult error = ErrorResult.fail(ResultCode.API_PARAM_EXCEPTION, e, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());

return error;

}

}

1.2.3. 例子

以上虽然将所有代码贴出,这列为凑完整,顺道将写个例子来,写个 Controller

package xyz.wongs.drunkard.war3.web.controller;

import com.github.hiwepy.ip2region.spring.boot.IP2regionTemplate;

import com.github.hiwepy.ip2region.spring.boot.ext.RegionAddress;

import lombok.extern.slf4j.Slf4j;

import org.nutz.plugins.ip2region.DataBlock;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;

import xyz.wongs.drunkard.base.aop.annotion.ApplicationLog;

import xyz.wongs.drunkard.base.message.annoation.ResponseResult;

import xyz.wongs.drunkard.base.message.exception.DrunkardException;

import xyz.wongs.drunkard.war3.limit.RequestLimit;

import java.io.IOException;

import java.util.HashMap;

import java.util.Map;

/**

* @ClassName IndexController

* @Description

* @author WCNGS@QQ.COM

* @Github https://github.com/rothschil

* @date 20/11/18 11:00

* @Version 1.0.0

*/

@Slf4j

@RestController

@ResponseResult

public class IndexController {

@RequestLimit(maxCount=3,second=20)

@ApplicationLog

@GetMapping("/test")

public Map test() {

HashMap data = new HashMap<>(3);

data.put("info", "测试成功");

return data;

}

@ApplicationLog

@GetMapping("/fail")

public Integer error() {

// 查询结果数

int res = 0;

if( res == 0 ) {

throw new DrunkardException("没有数据");

}

return res;

}

@Autowired

IP2regionTemplate template;

/** 根据输入IP地址,返回解析后的地址

* @Description

* @param ip

* @return xyz.wongs.drunkard.base.message.response.ResponseResult

* @throws

* @date 2020/8/17 18:26

*/

@GetMapping(value = "/convert/{ip}")

public DataBlock convertDataBlock(@PathVariable String ip){

DataBlock dataBlock = null;

try {

dataBlock = template.binarySearch(ip);

} catch (IOException e) {

e.printStackTrace();

}

return dataBlock;

}

/** 根据输入IP地址,返回解析后的地址

* @Description

* @param ip

* @return xyz.wongs.drunkard.base.message.response.ResponseResult

* @throws

* @date 2020/8/17 18:26

*/

@RequestLimit(maxCount=3)

@GetMapping(value = "/region/{ip}")

public RegionAddress convert(@PathVariable String ip){

RegionAddress regionAddress = null;

try {

regionAddress = template.getRegionAddress(ip);

} catch (IOException e) {

e.printStackTrace();

}

return regionAddress;

}

@GetMapping(value = "/region/ip={ip}")

public RegionAddress caseInsensitive(@PathVariable String ip){

RegionAddress regionAddress = null;

try {

regionAddress = template.getRegionAddress(ip);

} catch (IOException e) {

e.printStackTrace();

}

return regionAddress;

}

}

访问 http://localhost:9090/region/ip=109.27.45.12 这是我之前一个例子,用来解析IP地址,获取地域信息的。

06a46a181664

正常响应

06a46a181664

异常响应

1.2.4. 源码地址,如果觉得对你有帮助,请Star

06a46a181664

觉得对你有帮助,请Star

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值