前后端分离实践(二)—— 使用Springboot2.0搭建REST风格的Java后端架构

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Alexshi5/article/details/90246219

前后端分离实践系列文章总目录

目录

一、统一的JSON数据返回格式

1、JSON响应结构预览

2、JSON响应结构与Java类的映射

3、添加Springboot-web模块的Maven依赖

4、新建一个Controller类编写JSON响应结构的测试方法

5、测试JSON响应结构

二、集成Swagger2进行在线接口文档维护

1、添加Swagger2的Maven依赖

2、在Springboot中启用Swagger2

3、在config包中添加Swagger2的配置类SwaggerConfig

4、测试Swagger的使用

三、使用AOP进行全局的异常处理

1、在config包中添加一个异常切面配置类ExceptionAspectConfig

2、在ApiController中添加测试异常处理的方法

3、进入swagger-ui页面进行/api/exception接口的测试

四、使用hibernate-validator进行请求参数验证

1、config包中添加一个验证配置类ValidatorConfig

2、dto包中添加一个用户请求参数类UserRequestDTO

3、在ApiController中添加一个测试方法

4、进入swagger-ui页面进行/api/addUser接口的测试

五、使用Token提供安全验证机制

1、新建一个Token异常处理类继承至RuntimeException

2、在全局异常配置类中添加TokenException的配置

3、在annotation包中新建一个Token验证注解UserTokenCheck

4、添加Springboot-AOP的Maven依赖

5、在config包中添加安全检查切面配置类验证用户Token信息

6、在ApiController中添加一个测试方法

7、进入swagger-ui页面进行/api/token接口的测试

六、使用CORS技术解决跨域问题

七、最终的项目结构预览

八、源码地址


一、统一的JSON数据返回格式

1、JSON响应结构预览

使用REST风格实现前后端分离架构,首先需要确定返回的JSON响应结构是统一的,也就是说每个REST请求返回的是相同结构的JSON数据。该JSON响应结构如下:

{

  "code":"000",

  "msg":"OK",

  "data":{}

}

2、JSON响应结构与Java类的映射

为了在架构中映射以上JSON响应结构,我们可以编写一个ApiResponse类与之对应,如下:

/**
 *  统一的JSON格式数据响应类
 */
public class ApiResponse<T> {

    private Integer code;

    private String msg;

    private T data;

    public ApiResponse(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    //getter setter ...
}

3、添加Springboot-web模块的Maven依赖

<!--spring web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

4、新建一个Controller类编写JSON响应结构的测试方法

在controller包中新建一个ApiController类,内容如下:

import com.mengfei.fbsepjava.pojo.ApiResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ApiController {

    @RequestMapping(value = "/hello",method = RequestMethod.GET)
    public ApiResponse<String> helloWorld(){
        return new ApiResponse<String>(9999,"OK","Hello World");
    }
}

 @RestController注解代表整个类中所有方法都会返回JSON格式的数据,相当于在每个方法上加上@ResponseBody注解

5、测试JSON响应结构

启动Springboot项目,直接在浏览器中访问http://localhost:8080/api/hello,访问结果如下图所示:

二、集成Swagger2进行在线接口文档维护

1、添加Swagger2的Maven依赖

<!--swagger-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.6.1</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.6.1</version>
</dependency>

2、在Springboot中启用Swagger2

在Springboot启动器的main方法中添加开启Swagger2的注解@EnableSwagger2

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication
@EnableSwagger2
public class FbsepJavaApplication {
    public static void main(String[] args) {
        SpringApplication.run(FbsepJavaApplication.class, args);
    }
}

3、在config包中添加Swagger2的配置类SwaggerConfig

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //扫描的swagger接口包路径
               .apis(RequestHandlerSelectors.basePackage("com.mengfei.fbsepjava.controller"))
                .paths(PathSelectors.any())
                .build()
                .globalOperationParameters(this.setParameter());//不需要添加全局参数时这一行可以删掉

    }


    //通过参数构造器为swagger添加对header参数的支持,如果不需要的话可以删掉
    private List<Parameter> setParameter(){
        ParameterBuilder ticketPar = new ParameterBuilder();
        List<Parameter> pars = new ArrayList<Parameter>();
        ticketPar.name("userToken").description("用户的Token信息")
                .modelRef(new ModelRef("string")).parameterType("header")
                .required(false) //header中的userToken参数现在设置的是非必填,传空也可以
                .build();
        pars.add(ticketPar.build());
        return pars;
    }


    private ApiInfo apiInfo() {

        return new ApiInfoBuilder()
                .title("Springboot利用swagger构建api文档")
                .description("简单优雅的restful风格 http://blog.csdn.net/")
                .termsOfServiceUrl("http://blog.csdn.net/")
                .version("1.0")
                .build();
    }

}

4、测试Swagger的使用

启动项目访问http://localhost:8080/swagger-ui.html,看到如下所示的页面:


点击Try it out!看到如下所示的返回内容:


更多关于Swagger的使用可以参考:Swagger的使用相关系列文档

三、使用AOP进行全局的异常处理

1、在config包中添加一个异常切面配置类ExceptionAspectConfig

import com.mengfei.fbsepjava.pojo.ApiResponse;

import org.jboss.logging.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.List;
import java.util.stream.Collectors;

/**
 * Title: 全局异常处理切面
 * Description: 利用 @ControllerAdvice + @ExceptionHandler 组合处理Controller层RuntimeException异常
 * 在运行时从上往下依次调用每个异常处理方法,匹配当前异常类型是否与@ExceptionHandler注解所定义的异常相匹配,
 * 若匹配,则执行该方法,同时忽略后续所有的异常处理方法,最终会返回经JSON序列化后的Response对象
 */
@ControllerAdvice
@ResponseBody
public class ExceptionAspectConfig {

    /** Log4j日志处理 */
    private static final Logger log = Logger.getLogger(ExceptionAspectConfig.class);

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ApiResponse handleHttpMessageNotReadableException(
            HttpMessageNotReadableException e) {
        log.error("could_not_read_json...", e);
        return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(),"could_not_read_json...",null);
    }

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse handleValidationException(MethodArgumentNotValidException e) {
        // 获取BindingResult对象,然后获取其中的错误信息
        // 如果开启了fail_fast,这里只会有一个信息
        // 如果没有,则可能会有多个验证提示信息
        List<String> errorInformation = e.getBindingResult().getAllErrors()
                .stream()
                .map(ObjectError::getDefaultMessage)
                .collect(Collectors.toList());
        log.error(errorInformation.toString(), e);
        return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(),errorInformation.toString(),null);
    }

    /**
     * 405 - Method Not Allowed。HttpRequestMethodNotSupportedException
     * 是ServletException的子类,需要Servlet API支持
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ApiResponse handleHttpRequestMethodNotSupportedException(
            HttpRequestMethodNotSupportedException e) {
        log.error("request_method_not_supported...", e);
        return new ApiResponse<>(HttpStatus.METHOD_NOT_ALLOWED.value(),"request_method_not_supported",null);
    }

    /**
     * 415 - Unsupported Media Type。HttpMediaTypeNotSupportedException
     * 是ServletException的子类,需要Servlet API支持
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public ApiResponse handleHttpMediaTypeNotSupportedException(Exception e) {
        log.error("content_type_not_supported...", e);
        return new ApiResponse<>(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(),"content_type_not_supported...",null);
    }

    /**
     * 500 - Internal Server Error
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public ApiResponse handleException(Exception e) {
        log.error("Internal Server Error...", e);
        return new ApiResponse<>(HttpStatus.INTERNAL_SERVER_ERROR.value(),"Internal Server Error...",null);
    }
}

2、在ApiController中添加测试异常处理的方法

//测试全局的异常处理
@RequestMapping(value = "/exception",method = RequestMethod.GET)
public ApiResponse testExceptionHandle(){
    int i = 10/0;
    return new ApiResponse<String>(9999,"OK",null);
}

3、进入swagger-ui页面进行/api/exception接口的测试

四、使用hibernate-validator进行请求参数验证

1、config包中添加一个验证配置类ValidatorConfig

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

@Configuration
public class ValidatorConfig {

    @Bean
    public Validator validator() {
        ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 将fail_fast设置为true即可,如果想验证全部字段,则设置为false或者取消配置即可
                .addProperty("hibernate.validator.fail_fast", "true")
                .buildValidatorFactory();
        return factory.getValidator();
    }
}

2、dto包中添加一个用户请求参数类UserRequestDTO

import org.hibernate.validator.constraints.Range;

import javax.validation.constraints.*;

public class UserRequestDTO {

    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotNull
    @Size(min = 5,max = 10,message = "密码必须在5-10位之间")
    private String pwd;

    @Range(min = 0,max = 120,message = "年龄不能为负数并且不能超过120岁")
    private Integer age;

    @Pattern(regexp = "^([0-9A-Za-z\\-_\\.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)$",message = "需要输入正确格式的邮箱")
    private String email;

    //getter setter toString() ...
}

3、在ApiController中添加一个测试方法

//测试hibernate-validator验证
@RequestMapping(value = "/addUser",method = RequestMethod.POST)
public ApiResponse addUser(@RequestBody @Valid UserRequestDTO requestDTO){
    System.out.println(requestDTO);
    return new ApiResponse<>(9999,"添加用户成功",requestDTO);
}

4、进入swagger-ui页面进行/api/addUser接口的测试

验证不通过时抛出的异常是MethodArgumentNotValidException,可在异常处理切面配置类中查看。


更多关于hibernate-validator验证的内容请参考:在SpringBoot中使用Hibernate Validate

五、使用Token提供安全验证机制

1、新建一个Token异常处理类继承至RuntimeException

/**
 * 自定义TokenException类
 */
public class TokenException extends RuntimeException {
    public TokenException(){super();}

    public TokenException(String message){super(message);}
}

2、在全局异常配置类中添加TokenException的配置

注意:不要添加到最后面去了,异常是从上到下依次匹配的,最后一个是Exception

/**
 * 1000 Token验证未通过
 */
@ExceptionHandler(TokenException.class)
public ApiResponse handleTokenException(TokenException e) {
    log.error("Token验证未通过", e);
    return new ApiResponse<>(1000,"Token验证未通过",null);
}

3、在annotation包中新建一个Token验证注解UserTokenCheck

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserTokenCheck {

}

4、添加Springboot-AOP的Maven依赖

<!--spring aop-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

5、在config包中添加安全检查切面配置类验证用户Token信息

import com.mengfei.fbsepjava.annotation.UserTokenCheck;
import com.mengfei.fbsepjava.exception.TokenException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Component
@Aspect
public class SecurityAspectConfig {
    //伪Token,暂存于内存,通常的解决方案是在用户登录时将Token信息存储在Redis中
    private static String USER_TOKEN = "123456789";

    @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public Object execute(ProceedingJoinPoint pjp) throws Throwable {
        //获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        //从切入点上获取目标方法
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();

        //若目标方法需要安全性检查,则进行Token验证,如果不进行此判断可以对所有HTTP请求进行安全性检查
        if (method.isAnnotationPresent(UserTokenCheck.class)) {
            // 从 request header 中获取当前 token
            String token = request.getHeader("userToken");
            // 检查 token 有效性
            if (!USER_TOKEN.equals(token)) {
                String message = String.format("用户Token验证不通过", token);
                throw new TokenException(message);
            }
        }
        // 调用目标方法
        return pjp.proceed();
    }
}

6、在ApiController中添加一个测试方法

//测试Token验证功能
@UserTokenCheck //在需要验证token的方法上添加此注解
@RequestMapping(value = "/token",method = RequestMethod.GET)
public ApiResponse<String> checkToken(){
    return new ApiResponse<String>(9999,"OK",null);
}

7、进入swagger-ui页面进行/api/token接口的测试

六、使用CORS技术解决跨域问题

Spring框架已经实现了CORS技术,我们只需要在Controller中添加对应的实现注解表明接口类允许跨域请求即可,也可添加在方法上,如下:

@CrossOrigin
@RestController
@RequestMapping("/api")
public class ApiController {
//……
}

更多关于@CrossOrigin注解的内容请参考:注解@CrossOrigin解决跨域问题

七、最终的项目结构预览

八、源码地址

https://github.com/Alexshi5/demo-fbsep/tree/master/fbsep-java

 

参考链接:

1、REST风格框架实战:从MVC到前后端分离(附完整Demo)

展开阅读全文

没有更多推荐了,返回首页