• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

SpringBoot教程一RESTful API

武飞扬头像
buyue__
帮助1

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引入如下依赖

              <dependency>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                    <version>1.7.30</version>
                </dependency>

slf4j1.7.x会自动查找并绑定当前可用的日志实现库,而2.x版本则不再自动绑定需要连同实现库一起引入,如使用log4j则如下引入依赖

              <dependency>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                    <version>2.0.3</version>
                </dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>2.0.3</version>
  <scope>test</scope>
</dependency>

经测试使用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,内容如下

<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="true" scan="false" scanPeriod="60 seconds">
    <contextName>logback-config</contextName>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <!--  工程名,application配置中读取  -->
    <springProperty scope="context" name="springAppName" source="spring.application.name"/>
    <!--  日志目录,application配置中读取  -->
    <springProperty scope="context" name="loggerFolder" source="logger.folder"/>
    <!--  日志文件目录  -->
    <property name="FILE_PATH" value="${loggerFolder:-build}"/>
    <!--  日志文件名称  -->
    <property name="FILE_NAME" value="${FILE_PATH}/${springAppName}"/>
    <!--  彩色PATTERN  -->
    <property name="PATTERN" value="[%highlight(%-5level)][%green(%d{yyyy-MM-dd HH:mm:ss.SSS})][%boldMagenta(%class{39}.%M\\(%line\\))][%thread] -%msg%n"/>

    <!--  控制台输出  -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
        <encoder>
            <pattern>${PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!--  日志文件配置  -->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 写死文件名,会引起rollingPolicy设置无效  -->
        <!-- <file>${FILE_NAME}.log</file>  -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 循环滚动  -->
            <fileNamePattern>${FILE_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 保存最近10天 -->
            <maxHistory>10</maxHistory>
            <!-- 超过2G删除旧日志 -->
            <totalSizeCap>2GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>${PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- org.springframework包下的类只打印warn级别以上的日志 -->
    <logger name="org.springframework" level="warn"/>
    <!-- com.example.curd包下的类只打印info级别上日志 -->
    <!--<logger name="com.example.curd" level="info"/>-->

    <!-- spring配置文件值:spring.profiles.active=dev则启动dev的设置 -->
    <springProfile name="dev">
        <root level="error">
            <appender-ref ref="file"/>
        </root>
        <root level="warn">
            <appender-ref ref="file"/>
        </root>
        <root level="info">
            <appender-ref ref="console"/>
        </root>
    </springProfile>
    <springProfile name="test">
        <root level="warn">
            <appender-ref ref="file"/>
        </root>
        <root level="info">
            <appender-ref ref="console"/>
        </root>
    </springProfile>
    <springProfile name="prod">
        <root level="error">
            <appender-ref ref="file"/>
        </root>
    </springProfile>
</configuration>

学新通

需要注意的是上面<root level="warn">节点和<root level="info">的顺序不能搞反,级别高的先写

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> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        //存在多个filter时此处设置CorsFilter的执行顺序
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }

}

学新通

测试跨域

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>axios测试跨域访问</title>
    </head>
    <body>
        <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
        <script>
        const instance = axios.create({
            baseURL: 'http://localhost:8081/',
            timeout: 2000,
            headers: {'Access-Control-Allow-Origin': '*'}}
        );
        const res = instance.get('site/test');
        //const res = instance.get('ad/list');
        //const res = instance.post('ad/insert', { name: '' });
        console.log(res.data);
        </script>
    </body>
</html>
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbhhbi
系列文章
更多 icon
同类精品
更多 icon
继续加载