SpringBoot教程(一)RESTful API

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工程

SpringBoot教程(一)RESTful API_第1张图片
SpringBoot教程(一)RESTful API_第2张图片

创建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测试跨域访问
    
    
        
        
    

你可能感兴趣的:(Spring,设计模式,spring,boot,restful,java)