SpringBoot官方文档
现在项目普遍实行前后端分离,为了前后端人员更好地联调需要制定一套统一的接口规范
实现返回固定的JSON格式,如:
{
code:0,
message:'操作成功',
data:null
}
{
code:10001,
message:'参数错误',
data:null
}
1、Http的常用请求方法Method
GET 一搬用于获取数据
POST 用于提交数据
PUT 用于修改数据
DELETE 用于删除数据
2、Restful api 常用的几个注解
@RestController 一般用于Controoler类上
@ResponseBody 用了这个 RestController就没有必要在加ResponseBody了
@GetMapping 方法上
@PostMapping 方法上
@PutMapping 方法上
@DeleteMapping 方法上
@PostMapping和@PutMapping作用接近,都是用来向服务器提交信息。如果是新增(insert)信息,倾向于用@PostMapping,如果是更新(update)信息,倾向于用@PutMapping。
创建名为curd
的Spring工程
创建src/main/java/com.example.curd/controller/SiteController.java
文件,内容如下
package com.example.curd.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/site")
public class SiteController {
@GetMapping("/test")
public String test(){
return "site/test";
}
@GetMapping("/detail/{id}")
public Object detail(@PathVariable Integer id)
{
return "查看详情" + id;
}
@PostMapping("/add")
public Object add(@RequestParam String name){
return "新增:" + name;
}
@PutMapping("/update")
public Object update(@RequestParam Integer id,@RequestParam String name){
return "更新ID为" + id + "的名称为" + name;
}
@DeleteMapping("/delete/{id}")
public Object delete(@PathVariable Integer id)
{
return "删除ID为" + id + "的记录";
}
}
VO、DTO、BO、DO、PO、POJO、Entity的概念、区别和应用
SpringBoot工程目录结构参考
https://www.jianshu.com/p/e2dcde64b681
阿里巴巴Java开发手册中的DO、DTO、BO、AO、VO、POJO定义
返回码ResultCode类
创建src/main/java/com.example.curd/enums/ResultCode.java
文件,内容如下
package com.example.curd.enums;
public enum ResultCode {
SUCCESS(0,"成功"),
PARAMS_ERROR(10000,"参数错误"),//大类
PARAMS_IS_INVALID(10001,"参数无效"),
PARAMS_NOT_COMPLETE(10002,"参数不全"),
DATA_ERROR(20000,"数据错误"),//大类
DATA_NOT_FOUND(20001,"数据没找到"),
DATA_ALREADY_EXISTED(20002,"数据已存在"),
UNKNOWN_ERROR(99999,"未知错误");
private Integer code;
private String desc;
private ResultCode(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public Integer getCode(){
return code;
}
public String getDesc(){
return desc;
}
}
泛型相关
Result 类
创建src/main/java/com.example.curd/vo/Result.java
文件,内容如下
package com.example.curd.vo;
import java.io.Serializable;
public class Result implements Serializable {
private Integer code;
private String message;
private Object data;
public Result() {
}
public Result(Integer code, String message,Object data) {
this.setCode(code);
this.setMessage(message);
this.setData(data);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
创建src/main/java/com.example.curd/util/ResultUtil.java
文件,内容如下
package com.example.curd.util;
import com.example.curd.enums.ResultCode;
import com.example.curd.vo.*;
public class ResultUtil {
//基本用法1
public static Result create(Integer code, String message, Object data) {
return new Result(code,message,data);
}
//基本用法2
public static Result create(ResultCode resultCode,Object data) {
return create(resultCode.getCode(),resultCode.getDesc(),data);
}
//基本用法3
public static Result create(ResultCode resultCode) {
return create(resultCode,null);
}
//成功
public static Result success() {
return create(ResultCode.SUCCESS);
}
//成功
public static Result success(Object data) {
return create(ResultCode.SUCCESS,data);
}
//失败
public static Result fail(ResultCode resultCode) {
return create(resultCode);
}
//失败
public static Result fail(ResultCode resultCode,Object data) {
return create(resultCode,data);
}
}
用法
SiteController.java
文件增加如下内容
@GetMapping("/test")
public Object test()
{
//return ResultUtil.success();
return ResultUtil.fail(ResultCode.DATA_ALREADY_EXISTED);
}
创建自定义异常类CustomException
,位置src/main/java/com.example.curd/exception/CustomException.java
package com.example.curd.exception;
import com.example.curd.vo.ResultCode;
public class CustomException extends RuntimeException {
public CustomException(Integer code, String message) {
this.code = code;
this.message = message;
}
public CustomException(ResultCode resultCode) {
this.code = resultCode.getCode();
this.message = resultCode.getDesc();
}
private Integer code;
private String message;
public Integer getCode() {
return code;
}
public CustomException setCode(Integer code) {
this.code = code;
return this;
}
public String getMessage() {
return message;
}
public CustomException setMessage(String message) {
this.message = message;
return this;
}
}
创建异常处理增强类
RestControllerAdvice作用
创建src/main/java/com.example.curd/advice/ExceptionHandlerAdvice.java
文件,内容如下
package com.example.curd.advice;
import com.example.curd.enums.ResultCode;
import com.example.curd.exception.CustomException;
import com.example.curd.util.ResultUtil;
import com.example.curd.vo.Result;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
@RestControllerAdvice
public class ExceptionHandlerAdvice {
/*
//应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
@InitBinder
public void initBinder(WebDataBinder binder) {}
//把值绑定到Model中,使全局@RequestMapping可以获取到该值
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("author", "vastshine");
}
*/
//自定义异常(业务异常)捕捉处理
@ExceptionHandler(value = CustomException.class)
public Result customExceptionHandler(CustomException ex) {
return ResultUtil.create(ex.getCode(),ex.getMessage(),null);
}
//参数验证异常
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) {
return ResultUtil.create(ResultCode.PARAMS_ERROR,ex.getAllErrors());
}
//其他异常(全局异常)捕捉处理
@ExceptionHandler(value = Exception.class)
public Result globalExceptionHandler(Exception ex) {
return ResultUtil.create(ResultCode.UNKNOWN_ERROR,ex);
}
}
用法
@GetMapping("/test")
public Object test()
{
throw new CustomException(ResultCode.DATA_ALREADY_EXISTED);
}
使用AOP记录日志
日志组件使用slf4j,slf4j简介
slf4j本身不是一个日志实现库,而是一个日志库的抽象层,它必须依赖底层的日志库,SLF4J必须和其他的具体日志实现库配合才能正常运行
pom.xml
文件增加依赖
slf4j1.7.x之前的版本和slf4j2.x的版本改动较大,slf4j2.x需要java9以上
slf4j1.7.x引入如下依赖
org.slf4j
slf4j-api
1.7.30
slf4j1.7.x会自动查找并绑定当前可用的日志实现库,而2.x版本则不再自动绑定需要连同实现库一起引入,如使用log4j
则如下引入依赖
org.slf4j
slf4j-api
2.0.3
org.slf4j
slf4j-log4j12
2.0.3
test
经测试使用slf4j2.x版本存在其他问题,所以这里继续使用1.x版本
SpringBoot默认日志实现库为logback
默认情况下,INFO级别将日志输出到控制台,不会写到日志文件。
用户可以自定义修改日志等级和输出方式。
logback日志级别从低到高分为ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF,定义于ch.qos.logback.classic.Level
类中。
默认情况下高级别会继承低级别的能力,
可以通过addtivity
参数改为false
关闭继承。
如果级别为WARN,则高于WARN的级别都会输出。
添加配置文件src/main/java/resources/logback-spring.xml
,内容如下
logback-config
DEBUG
${PATTERN}
utf8
${FILE_NAME}.%d{yyyy-MM-dd}.log
10
2GB
${PATTERN}
utf8
需要注意的是上面
节点和
的顺序不能搞反,级别高的先写
application.properties
内容如下
spring.application.name = cur
mybatis.configuration.map-underscore-to-camel-case = true
spring.jackson.date-format = yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone = GMT+8
server.address = localhost
server.port = 8081
logger.folder = logs
spring.profiles.active = dev
application.dev.properties
内容如下
logger.folder = logs_dev
server.address = localhost
server.port = 8081
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://192.168.1.1:3306/curd
spring.datasource.username = root
spring.datasource.password = 123456
application.prod.properties
内容如下
logger.folder = logs_prod
创建切面类src/main/java/com.example.curd/aspect/LogAspect.java
处理日志
package com.example.curd.aspect;
import com.example.curd.exception.CustomException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.*;
import org.slf4j.*;
import java.lang.reflect.Method;
@Aspect
@Component
public class LogAspect {
//全部拦截使用@Around(value = "execution(* com.example.curd..*.* (..))")
//注意:一般不使用全部拦截,尽可能减少拦截范围,因为可能会出现多个拦截器冲突的情况(如控制器又嵌套了拦截器)
//这里只拦截controller
private final String POINT_CUT = "execution(* com.example.curd.controller.*.* (..))";
//[@Aspect各种通知的执行顺序](https://blog.csdn.net/qq_41936224/article/details/107100175)
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
private static final ObjectMapper mapper = new ObjectMapper();
//前置通知
@Before(value = POINT_CUT)
public void before(JoinPoint joinPoint) {
System.out.println("处理@Before");
}
//环绕通知
@Around(value = POINT_CUT)
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("处理@Around");
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String name = method.getName();
Object[] args = joinPoint.getArgs();
Object object = joinPoint.getTarget();
Object result = joinPoint.proceed();
//只会输出到控制台
log.info("class :{} , method :{}, args :{} , result :{}", object.getClass().getName(),name,mapper.writeValueAsString(args),result);
return result;
}
//正常返回通知(无异常发生才会执行该通知)
@AfterReturning(value = POINT_CUT)
public void afterReturning(JoinPoint joinPoint) {
System.out.println("处理@AfterReturning");
}
//异常通知
@AfterThrowing(value = POINT_CUT,throwing="e")
public void afterThrowing(Throwable e) {
System.out.println("处理@AfterThrowing");
/*
if(e instanceof CustomException){
log.error("发生了自定义异常:",e);
}else{
log.error("发生了系统异常了:",e);
}
*/
//只记录全局异常(没作异常处理的异常)
if(e instanceof Exception){
log.error("发生了异常了:",e);
}
}
//后置通知
@After(value = POINT_CUT)
public void after(JoinPoint joinPoint) {
System.out.println("处理@After");
}
}
@Aspect各种通知的执行顺序
统一Response的返回格式
创建src/main/java/com.example.curd/advice/MyResponseBodyAdvice.java
,内容如下
package com.example.curd.advice;
import com.example.curd.util.ResultUtil;
import com.example.curd.vo.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
//return true;//一律通过;当controller返回String时会报错
return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//异常发生时已包装过数据无需再次包装数据
if(body instanceof Result){
return body;
}
//包装数据统一格式
return ResultUtil.success(body);
}
}
测试
@GetMapping("/test")
public String logTest(){
System.out.println("测试LogAspect");
//return "ok";
throw new CustomException(ResultCode.DATA_ALREADY_EXISTED);
}
跨域处理
增加全局跨域处理类GlobalCorsConfiguration
package com.example.curd.configuration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.*;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class GlobalCorsConfiguration {
@Bean
public CorsConfiguration buildConfig() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern(CorsConfiguration.ALL);
config.setAllowCredentials(true);
config.addAllowedMethod(CorsConfiguration.ALL);
config.addAllowedHeader(CorsConfiguration.ALL);
config.addExposedHeader(CorsConfiguration.ALL);
return config;
}
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
//存在多个filter时此处设置CorsFilter的执行顺序
FilterRegistrationBean bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
测试跨域
axios测试跨域访问