接上篇毕设利器,教你从零搭建一个有规范的spring boot项目【二】
初步调通了数据库,能够对数据库做基本的增上改查操作,那么接下来,就要注意一些规范和统一的问题了。
拿返回结果的处理来举例子。
上一篇博客中,我们的返回结果格式是这样的:
前端朋友拿到这个数据了,可能要拿到name这个字段去展示是吧,但要是后台程序出错了怎么办?
出错了,就不会返回name这个字段,前端也就跟着不知所措了。
因此我们还要返回一个字段,告诉前端他是否请求成功。
而这种字段通常是有一定规范的,举个例子,打开bilibili官网,打开右键检查,然后刷新,可以看到B站首页的请求数据的返回:
(吐槽一下这张图,为什么癌症越来越多了,哈哈哈哈哈)
另外你还可以看到code、count、message这三个字段。
一般code为200或者0,就是请求成功并且返回数据正常。
这些都是可以自定义的,所以根据业务需求不同,你可以随意定义,不过大家一般都会有以下三个字段:
上面这么说可能比较抽象,直接跟着做会清晰点。
首先引入fastjson的依赖:
依赖在下面,可以直接复制,粘完记得点一下上图的右上角刷新一下依赖。
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.46version>
dependency>
接着新建core.support.http包,注意与controller平级:
然后把下面的工具类粘在这个包里,其实这工具类爱放哪放哪,放在这个包只是出于个人习惯的归档分类而已,你有自己的习惯的话,完全可以放在你喜欢的地方:
package com.TandK.core.support.http;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.util.HashMap;
import java.util.Map;
public class HttpResponseSupport {
private HttpResponseSupport() {
}
private static JSONObject responseJson;
private static JSONObject responseErrorJson;
static {
responseJson = new JSONObject();
responseErrorJson = new JSONObject();
}
public synchronized static ResponseEntity<Object> success(String msg, String reason) {
responseErrorJson.clear();
responseJson.put("code", 0);
responseJson.put("message", msg);
responseJson.put("reason", reason);
responseJson.put("data", msg);
return obtainResponseEntity(HttpStatus.OK, responseJson);
}
public synchronized static ResponseEntity<Object> success(Object obj) {
responseJson = new JSONObject();
responseJson.put("code", 0);
responseJson.put("data", obj);
return obtainResponseEntity(HttpStatus.OK, responseJson);
}
public synchronized static ResponseEntity<Object> success(IPage obj) {
Map<String, Object> map = new HashMap<String, Object>(2) {{
put("total",obj.getTotal());
put("data",obj.getRecords());
}};
responseJson = new JSONObject();
responseJson.put("code", 0);
responseJson.put("data", map);
return obtainResponseEntity(HttpStatus.OK, responseJson);
}
public synchronized static ResponseEntity<Object> error(HttpStatus httpStatus, String msg, Object obj) {
JSONObject tempJSON = new JSONObject();
responseErrorJson.clear();
responseErrorJson.put("type", httpStatus.getReasonPhrase());
responseErrorJson.put("message", msg);
responseErrorJson.put("reason", obj);
responseErrorJson.put("code", "9999");
return obtainResponseEntity(httpStatus, responseErrorJson);
}
private synchronized static ResponseEntity<Object> obtainResponseEntity(HttpStatus httpStatus, Object response) {
return new ResponseEntity<>(response, httpStatus);
}
}
再在core包里新建一个constant包,这个包用来存放各种各样的常量,结构如图:
建一个常量类,存放各种返回信息的常量:
package com.TandK.core.constant;
/**
* @author TandK
*/
public final class HttpMessageConstant {
private HttpMessageConstant() {
}
public static final String HTTP_MESSAGE_FAIL = "非法请求";
public static final String HTTP_MESSAGE_BAG_REQUEST = "请求体不完整";
public static final String HTTP_MESSAGE_NOT_FOUND = "资源不存在";
public static final String HTTP_MESSAGE_FORBIDDEN = "请勿重复请求";
public static final String HTTP_MESSAGE_CONFLICT = "请求资源冲突";
public static final String HTTP_MESSAGE_PRECONDITION_FAILED = "请求头有误";
public static final String HTTP_MESSAGE_UNAUTHORIZED = "鉴权失败";
public static final String HTTP_MESSAGE_INTERNAL_SERVER_ERROR = "服务器内部错误";
public static final String HTTP_MESSAGE_BAG_REQUEST_EXCEL = "excel error";
public static final String HTTP_DATE_BAG_REQUEST = "时间格式异常";
}
上面这两个类只是工具类而已,完全可以不仔细看,当然你有阅读别人代码的习惯,那也可以看看。
接下来可以回到我们上次查询用户列表的方法:
在最后返回的时候,稍做一下修改:
当然service层和controller层方法声明的返回类型都要改改:
然后启动一下项目,再次访问用户列表接口,可以看到返回格式改变了:
这种数据格式是方法正常执行的格式,那么方法要是执行异常怎么办?
别着急,我们先看一下统一异常处理吧。
统一异常处理有两个目的,还是举一点实际情况的例子来说明吧。
假设我们项目中有发送短信的需求,现在发送短信都是借助第三方服务,比如阿里云。
那么调用阿里云的接口,就可能有异常的情况出现,我们可以try-catch捕获,但是通过我们自定义异常,再通过统一异常处理,可以帮助我们快速找到出问题的原因。
这是其一。
另外一个情况,假设在查询用户列表的时候,加了点查询条件,这个时候找不到符合查询条件的数据,那我们怎么办?
返回下面这样的格式给前端同学吗?
{"code":0,"data":[]}
那前端同学给用户展示的就是个空白列表了啊。
假设你们的项目需求就是这样,那也可以。
但如果你们的项目需求是要告诉用户,找不到符合条件的用户,那可能需要给前端同学一点提示了。
前端同学固然可以自己判断data的长度,但我们返回的话可以更友好些。
这个时候我们就可以自定义一个业务异常,通过统一异常处理来告诉前端同学,找不到符合条件的用户。
而处理好统一异常处理后,我们直接抛异常就可以了。可以大大减少我们的工作量。
这是其二。
具体怎么做,跟着往下做就是了。
首先在core包里新建一个exception包,层级结构如图:
建一个GlobalExceptionHandler类,通过这个类来捕获整个项目的异常:
package com.TandK.core.exception;
import com.TandK.core.constant.HttpMessageConstant;
import com.TandK.core.support.http.HttpResponseSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* @author TandK
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 系统异常,返回服务器内部错误
* @param e
* @param request
* @return
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ResponseEntity<Object> systemException(Throwable e, HttpServletRequest request) {
logger.error("出现了 全局Exception异常, 地址是:{},异常是:{}", request.getRequestURI(), e);
return HttpResponseSupport.error(HttpStatus.INTERNAL_SERVER_ERROR, HttpMessageConstant.HTTP_MESSAGE_INTERNAL_SERVER_ERROR, e.getMessage());
}
}
说明如下图:
这样子所有异常——java所有异常都继承于Throwable类,都会进入这个方法——都会进入这个方法。
我们大可以试一试,抛出一个异常:
这说明异常被抛出之后,确实地执行了这个方法:
这样的话,前端同学也能根据返回信息做出相应提示,比如服务器繁忙之类的。
除了Throwable类,我们也可以捕获其他异常。
比如自定义一个业务异常,当上面的用户列表,查询不到的时候的,可以直接抛出一个异常,然后统一进入这个异常处理的方法,告诉前端没有符合条件的查询。
通常来说,我们还可以自定义一个异常枚举,来规范我们的项目。
以业务异常为例,那么上面的思路转化成代码便是如下步骤。
自定义异常枚举:
package com.TandK.core.exception;
import lombok.Getter;
/**
* @author TandK
* @since 2021/8/14 17:13
*/
@Getter
public enum BusinessExceptionEumn {
/**
* 之后每多一个异常状态,在这里加就行
*/
THIRD_NET_ERROR(10001, "第三方服务网络异常", "第三方服务网络异常"),
UPDATE_ERROR(10002, "更新异常", "更新异常"),
ADD_ERROR(10003, "插入异常", "插入异常"),
RESULT_NOT_FOUND(10004, "查询结果不存在", "查询结果不存在"),
SENSITIVE_MESSAGE_CHECK_NOT_PASS(10005, "存在敏感词", "存在敏感词");
/**
* code 异常状态码
*/
private final int code;
/**
* msg 异常信息
*/
private final String msg;
/**
* reason 异常原因
*/
private final String reason;
BusinessExceptionEumn(int code, String msg, String reason) {
this.code = code;
this.msg = msg;
this.reason = reason;
}
}
要说明的话都写在注释里了,可以仔细看一下注释。
再自定义一个【自定义异常】接口,以后我们的自定义异常都可以实现这个接口,这么做的好处将在后面体现出来:
package com.TandK.core.exception;
/**
* @author TandK
* @since 2021/8/14 21:41
*/
public interface CustomException {
String getMessage();
String getReason();
int getCode();
}
然后新建一个业务异常,实现CustomException接口,继承RuntimeException:
package com.TandK.core.exception;
public class BusinessException extends RuntimeException implements CustomException{
/**
*
*/
private static final long serialVersionUID = 1L;
private static final int DEFCODE = 8600;
/**
* exception message
*/
private String message;
/**
* exception reason
*/
private String reason;
/**
* error code
* default: 500
*/
private int code = 8600;
public BusinessException(BusinessExceptionEumn bizCodeEnum) {
super(bizCodeEnum.getMsg());
this.message = bizCodeEnum.getMsg();
this.reason = bizCodeEnum.getReason();
this.code = bizCodeEnum.getCode();
}
public BusinessException(String message) {
super(message);
this.code = DEFCODE;
this.message = message;
this.reason = message;
}
public BusinessException(int code, String message, Throwable cause) {
super(message, cause);
this.code = code;
this.message = message;
this.reason = message;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
this.code = DEFCODE;
this.message = message;
this.reason = message;
}
@Override
public String getMessage() {
return message;
}
@Override
public String getReason() {
return reason;
}
@Override
public int getCode() {
return code;
}
}
重点看这个方法,有了业务异常枚举,我们以后抛出异常都是用这个方法抛出:
上面的代码都是在exception包下新建的,其实你看代码都能知道这一点。
接下来回去修改HttpResponseSupport类,加入一个方法:
public synchronized static ResponseEntity<Object> error(HttpStatus httpStatus, CustomException e) {
JSONObject tempJSON = new JSONObject();
responseErrorJson.clear();
responseErrorJson.put("type", httpStatus.getReasonPhrase());
responseErrorJson.put("message", e.getMessage());
responseErrorJson.put("reason", e.getReason());
responseErrorJson.put("code", e.getCode());
return obtainResponseEntity(httpStatus, responseErrorJson);
}
这个方法能接受我们的自定义异常,并用前面的规范格式返回。
在这里,前面自定义异常接口的好处也体现出来了,之后我们再需要自定义异常,比如支付异常什么的,都可以实现自定义异常接口,然后用这个方法统一处理返回格式。
然后上面这个方法用在下面这个地方:
插一下具体代码,有需要可以直接复制:
/**
* 业务异常
* @param e
* @param request
* @return
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Object> businessException(BusinessException e, HttpServletRequest request) {
logger.error("BusinessException, 地址是:{},异常是:{}", request.getRequestURI(), e);
return HttpResponseSupport.error(HttpStatus.OK, e);
}
这样,我们在项目中遇到一个问题,比如查询不到符合条件的用户列表,就可以直接抛用户异常了。
抛出来了,会被GlobalExceptionHandler拦截下来,进入对应的异常处理方法。
进入方法后,我们有需要呢,就打印一下,方便我们快速定位问题,还可以封装成统一格式返回给前端。
这就是上面代码的思路和目的,这些准备工作完了之后,接下来我们只需要在有需要的时候,像下面这样抛异常就可以了,甚至不用自己捕获处理。
做个测试,运行项目,把数据库里的用户删掉:
再调用接口,看看结果:
上面的很多代码,很多都是准备工作,做好这些准备工作之后,我们在接下来的编写接口的过程中,对于返回结果和异常的处理其实就是下面两步:
这样用起来就很简单了。
这里只是举了业务异常的例子,有些时候一些异常是不需要打印日志的,比如查不到用户这点屁事,他确实不是真正的异常,那么你可以再弄一个异常,专门处理这些屁事。
我就比较懒了,都塞业务异常了,毕竟还是要看具体项目来,项目不大的话,弄那么自定义异常,花里胡哨的,有这些时间还不如写写业务代码呢,你说是不是?
但要是项目大的话,规范还是很重要的,毕竟几个人分工开发,还要考虑后续维护的问题,有个统一规范还是很重要的。
有什么问题可以留言,看到了都会尽量帮忙解决。
2021-09-26更新:
下篇在这里:《毕设利器,教你从零搭建一个有规范的spring boot项目【四】——参数校验》