• 品达通用权限系统(Day 1~Day 2)


    1. 项目概述

    1.1 项目介绍

    对于企业中的项目绝大多数都需要进行用户权限管理、认证、鉴权、加密、解密、XSS防跨站攻击等。这些功能整体实现思路基本一致,但是大部分项目都需要实现一次,这无形中就形成了巨大的资源浪费。本项目就是针对这个问题,提供了一套通用的权限解决方案----品达通用权限系统。

    品达通用权限系统基于SpringCloud(Hoxton.SR1) +SpringBoot(2.2.2.RELEASE) 的微服务框架,具备通用的用户管理、资源权限管理、网关统一鉴权、XSS防跨站攻击等多个模块,支持多业务系统并行开发,支持多服务并行开发,可以作为后端服务的开发脚手架。核心技术采用SpringBoot、Zuul、Nacos、Fegin、Ribbon、Hystrix、JWT Token、Mybatis Plus等主要框架和中间件。

    本项目具有两个主要功能特性:

    • 用户权限管理

      具有用户、部门、岗位、角色、菜单管理,并通过网关进行统一的权限认证

    • 微服务开发框架

      本项目同时也是一个微服务开发框架,集成了基础的公共组件,包括数据库、缓存、日志、表单验证、对象转换、防注入和接口文档管理等工具。

    资料地址:https://download.csdn.net/download/qq_22075913/85737332

    1.2 业务架构

    在这里插入图片描述

    1.3 技术架构

    在这里插入图片描述

    1.4 环境要求

    • JDK : 1.8 +

    • Maven: 3.3 +

      http://maven.apache.org/download.cgi

    • Mysql: 5.6.0 +

      https://downloads.mysql.com/archives/community

    • Redis: 4.0 +

      https://redis.io/download

    • Nacos: 1.1.4

      https://github.com/alibaba/nacos/releases

    • Node: 11.3+(集成npm)

      https://nodejs.org/en/download

    2. Spring Boot starter

    我们知道Spring Boot大大简化了项目初始搭建以及开发过程,而这些都是通过Spring Boot提供的starter来完成的。品达通用权限系统就是基于Spring Boot进行开发,而且一些基础模块其本质就是starter,所以我们需要对Spring Boot的starter有一个全面深入的了解,这是我们开发品达通用权限系统的必备知识。

    2.1 starter介绍

    spring boot 在配置上相比spring要简单许多, 其核心在于spring-boot-starter, 在使用spring boot来搭建一个项目时, 只需要引入官方提供的starter, 就可以直接使用, 免去了各种配置。starter简单来讲就是引入了一些相关依赖和一些初始化的配置。

    Spring官方提供了很多starter,第三方也可以定义starter。为了加以区分,starter从名称上进行了如下规范:

    • Spring官方提供的starter名称为:spring-boot-starter-xxx

      例如Spring官方提供的spring-boot-starter-web

    • 第三方提供的starter名称为:xxx-spring-boot-starter

      例如由mybatis提供的mybatis-spring-boot-starter

    2.2 starter原理

    Spring Boot之所以能够帮我们简化项目的搭建和开发过程,主要是基于它提供的起步依赖和自动配置。

    2.2.1 起步依赖

    起步依赖,其实就是将具备某种功能的坐标打包到一起,可以简化依赖导入的过程。例如,我们导入spring-boot-starter-web这个starter,则和web开发相关的jar包都一起导入到项目中了。如下图所示:

    在这里插入图片描述

    2.2.2 自动配置

    自动配置,就是无须手动配置xml,自动配置并管理bean,可以简化开发过程。那么Spring Boot是如何完成自动配置的呢?

    自动配置涉及到如下几个关键步骤:

    • 基于Java代码的Bean配置
    • 自动配置条件依赖
    • Bean参数获取
    • Bean的发现
    • Bean的加载

    我们可以通过一个实际的例子mybatis-spring-boot-starter来说明自动配置的实现过程。

    2.2.2.1 基于Java代码的Bean配置

    当我们在项目中导入了mybatis-spring-boot-starter这个jar后,可以看到它包括了很多相关的jar包,如下图:
    在这里插入图片描述

    其中在mybatis-spring-boot-autoconfigure这个jar包中有如下一个MybatisAutoConfiguration自动配置类:

    在这里插入图片描述

    打开这个类,截取的关键代码如下:

    在这里插入图片描述
    在这里插入图片描述

    @Configuration@Bean这两个注解一起使用就可以创建一个基于java代码的配置类,可以用来替代传统的xml配置文件。

    @Configuration 注解的类可以看作是能生产让Spring IoC容器管理的Bean实例的工厂。

    @Bean 注解的方法返回的对象可以被注册到spring容器中。

    所以上面的MybatisAutoConfiguration这个类,自动帮我们生成了SqlSessionFactory和SqlSessionTemplate这些Mybatis的重要实例并交给spring容器管理,从而完成bean的自动注册。

    2.2.2.2 自动配置条件依赖

    MybatisAutoConfiguration这个类中使用的注解可以看出,要完成自动配置是有依赖条件的。

    在这里插入图片描述

    所以要完成Mybatis的自动配置,需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类,同时需要存在DataSource这个bean且这个bean完成自动注册。

    这些注解是spring boot特有的,常见的条件依赖注解有:

    注解功能说明
    @ConditionalOnBean仅在当前上下文中存在某个bean时,才会实例化这个Bean
    @ConditionalOnClass某个class位于类路径上,才会实例化这个Bean
    @ConditionalOnExpression当表达式为true的时候,才会实例化这个Bean
    @ConditionalOnMissingBean仅在当前上下文中不存在某个bean时,才会实例化这个Bean
    @ConditionalOnMissingClass某个class在类路径上不存在的时候,才会实例化这个Bean
    @ConditionalOnNotWebApplication不是web应用时才会实例化这个Bean
    @AutoConfigureAfter在某个bean完成自动配置后实例化这个bean
    @AutoConfigureBefore在某个bean完成自动配置前实例化这个bean
    2.2.2.3 Bean参数获取

    要完成mybatis的自动配置,需要我们在配置文件中提供数据源相关的配置参数,例如数据库驱动、连接url、数据库用户名、密码等。那么spring boot是如何读取yml或者properites配置文件的的属性来创建数据源对象的?

    在我们导入mybatis-spring-boot-starter这个jar包后会传递过来一个spring-boot-autoconfigure包,在这个包中有一个自动配置类DataSourceAutoConfiguration,如下所示:

    在这里插入图片描述
    我们可以看到这个类上加入了EnableConfigurationProperties这个注解,继续跟踪源码到DataSourceProperties这个类,如下:
    在这里插入图片描述
    可以看到这个类上加入了ConfigurationProperties注解,这个注解的作用就是把yml或者properties配置文件中的配置参数信息封装到ConfigurationProperties注解标注的bean(即DataSourceProperties)的相应属性上。

    @EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效。

    2.2.2.4 Bean的发现

    spring boot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包中的类,那么依赖包中的bean是如何被发现和加载的?

    我们需要从Spring Boot项目的启动类开始跟踪,在启动类上我们一般会加入SpringBootApplication注解,此注解的源码如下:

    在这里插入图片描述

    重点介绍如下三个注解:
    
    **SpringBootConfiguration**:作用就相当于**Configuration**注解,被注解的类将成为一个bean配置类
    
    **ComponentScan**:作用就是自动扫描并加载符合条件的组件,最终将这些bean加载到spring容器中
    
    **EnableAutoConfiguration** :这个注解很重要,借助@**Import**的支持,收集和注册依赖包中相关的bean定义
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    继续跟踪EnableAutoConfiguration注解源码:

    在这里插入图片描述

    @EnableAutoConfiguration注解引入了@Import这个注解。

    Import:导入需要自动配置的组件,此处为EnableAutoConfigurationImportSelector这个类

    EnableAutoConfigurationImportSelector类源码如下:

    在这里插入图片描述

    EnableAutoConfigurationImportSelector继承了AutoConfigurationImportSelector类,继续跟踪AutoConfigurationImportSelector类源码:

    在这里插入图片描述

    AutoConfigurationImportSelector类的getCandidateConfigurations方法中的调用了SpringFactoriesLoader类的loadFactoryNames方法,继续跟踪源码:

    在这里插入图片描述

    SpringFactoriesLoaderloadFactoryNames静态方法可以从所有的jar包中读取META-INF/spring.factories文件,而自动配置的类就在这个文件中进行配置:

    在这里插入图片描述

    spring.factories文件内容如下:

    在这里插入图片描述

    这样Spring Boot就可以加载到MybatisAutoConfiguration这个配置类了。

    2.2.2.5 Bean的加载
    Spring Boot应用中要让一个普通类交给Spring容器管理,通常有以下方法:
    
    1、使用 @Configuration@Bean 注解
    
    2、使用@Controller @Service @Repository @Component 注解标注该类并且启用@ComponentScan自动扫描
    
    3、使用@Import 方法
    
    其中Spring Boot实现自动配置使用的是@Import注解这种方式,
    AutoConfigurationImportSelector类的selectImports方法返回一组
    从META-INF/spring.factories文件中读取的bean的全类名,
    这样Spring Boot就可以加载到这些Bean并完成实例的创建工作。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.2.3 自动配置总结

    我们可以将自动配置的关键几步以及相应的注解总结如下:

    1@Configuration@Bean:基于Java代码的bean配置
    
    2@Conditional:设置自动配置条件依赖
    
    3@EnableConfigurationProperties@ConfigurationProperties:读取配置文件转换为bean
    
    4@EnableAutoConfiguration@Import:实现bean发现与加载
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.3 自定义starter

    本小节我们通过自定义两个starter来加强starter的理解和应用。

    2.3.1 案例一

    2.3.1.1 开发starter

    第一步:创建starter工程hello-spring-boot-starter并配置pom.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                                 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/>
        </parent>
        <groupId>cn.itcast</groupId>
        <artifactId>hello-spring-boot-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
            </dependency>
        </dependencies>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    第二步:创建配置属性类HelloProperties

    package cn.itcast.config;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    /*
     *读取配置文件转换为bean
     * */
    @ConfigurationProperties(prefix = "hello")
    public class HelloProperties {
        private String name;
        private String address;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        @Override
        public String toString() {
            return "HelloProperties{" +
                    "name='" + name + '\'' +
                    ", address='" + address + '\'' +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    第三步:创建服务类HelloService

    package cn.itcast.service;
    
    public class HelloService {
        private String name;
        private String address;
    
        public HelloService(String name, String address) {
            this.name = name;
            this.address = address;
        }
    
        public String sayHello(){
            return "你好!我的名字叫 " + name + ",我来自 " + address;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    第四步:创建自动配置类HelloServiceAutoConfiguration

    package cn.itcast.config;
    
    import cn.itcast.service.HelloService;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /*
    * 配置类,基于Java代码的bean配置
    * */
    
    @Configuration
    @EnableConfigurationProperties(HelloProperties.class)
    public class HelloServiceAutoConfiguration {
        private HelloProperties helloProperties;
    
        //通过构造方法注入配置属性对象HelloProperties
        public HelloServiceAutoConfiguration(HelloProperties helloProperties) {
            this.helloProperties = helloProperties;
        }
    
        //实例化HelloService并载入Spring IoC容器
        @Bean
        @ConditionalOnMissingBean
        public HelloService helloService(){
            return new HelloService(helloProperties.getName(),helloProperties.getAddress());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    第五步:在resources目录下创建META-INF/spring.factories

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    cn.itcast.config.HelloServiceAutoConfiguration
    
    • 1
    • 2

    至此starter已经开发完成了,可以将当前starter安装到本地maven仓库供其他应用来使用。

    2.3.1.2 使用starter

    第一步:创建maven工程myapp并配置pom.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/>
        </parent>
        <groupId>cn.itcast</groupId>
        <artifactId>myapp</artifactId>
        <version>1.0-SNAPSHOT</version>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--导入自定义starter-->
            <dependency>
                <groupId>cn.itcast</groupId>
                <artifactId>hello-spring-boot-starter</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    第二步:创建application.yml文件

    server:
      port: 8080
    hello:
      name: xiaoming
      address: beijing
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第三步:创建HelloController

    package cn.itcast.controller;
    
    import cn.itcast.service.HelloService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/hello")
    public class HelloController {
        //HelloService在我们自定义的starter中已经完成了自动配置,所以此处可以直接注入
        @Autowired
        private HelloService helloService;
    
        @GetMapping("/say")
        public String sayHello(){
            return helloService.sayHello();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    第四步:创建启动类HelloApplication

    package cn.itcast;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class HelloApplication {
        public static void main(String[] args) {
            SpringApplication.run(HelloApplication.class,args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    执行启动类main方法,访问地址http://localhost:8080/hello/say

    在这里插入图片描述

    2.3.2 案例二

    在前面的案例一中我们通过定义starter,自动配置了一个HelloService实例。本案例我们需要通过自动配置来创建一个拦截器对象,通过此拦截器对象来实现记录日志功能。

    我们可以在案例一的基础上继续开发案例二。

    2.3.2.1 开发starter

    第一步:在hello-spring-boot-starter的pom.xml文件中追加如下maven坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    第二步:自定义MyLog注解

    package cn.itcast.log;
    
    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 MyLog {
        /**
         * 方法描述
         */
        String desc() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    第三步:自定义日志拦截器MyLogInterceptor

    package cn.itcast.log;
    
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.lang.reflect.Method;
    
    /**
     * 日志拦截器
     */
    public class MyLogInterceptor extends HandlerInterceptorAdapter {
        private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
    
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                                 Object handler) throws Exception {
            HandlerMethod handlerMethod = (HandlerMethod)handler;
            Method method = handlerMethod.getMethod();//获得被拦截的方法对象
            MyLog myLog = method.getAnnotation(MyLog.class);//获得方法上的注解
            if(myLog != null){
                //方法上加了MyLog注解,需要进行日志记录
                long startTime = System.currentTimeMillis();
                startTimeThreadLocal.set(startTime);
            }
            return true;
        }
    
        public void postHandle(HttpServletRequest request, HttpServletResponse response, 
                               Object handler, ModelAndView modelAndView) throws Exception {
            HandlerMethod handlerMethod = (HandlerMethod)handler;
            Method method = handlerMethod.getMethod();//获得被拦截的方法对象
            MyLog myLog = method.getAnnotation(MyLog.class);//获得方法上的注解
            if(myLog != null){
                //方法上加了MyLog注解,需要进行日志记录
                long endTime = System.currentTimeMillis();
                Long startTime = startTimeThreadLocal.get();
                long optTime = endTime - startTime;
    
                String requestUri = request.getRequestURI();
                String methodName = method.getDeclaringClass().getName() + "." + 
                    				method.getName();
                String methodDesc = myLog.desc();
    
                System.out.println("请求uri:" + requestUri);
                System.out.println("请求方法名:" + methodName);
                System.out.println("方法描述:" + methodDesc);
                System.out.println("方法执行时间:" + optTime + "ms");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    第四步:创建自动配置类MyLogAutoConfiguration,用于自动配置拦截器、参数解析器等web组件

    package cn.itcast.config;
    
    import cn.itcast.log.MyLogInterceptor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * 配置类,用于自动配置拦截器、参数解析器等web组件
     */
    
    @Configuration
    public class MyLogAutoConfiguration implements WebMvcConfigurer{
        //注册自定义日志拦截器
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new MyLogInterceptor());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    第五步:在spring.factories中追加MyLogAutoConfiguration配置

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    cn.itcast.config.HelloServiceAutoConfiguration,\
    cn.itcast.config.MyLogAutoConfiguration
    
    • 1
    • 2
    • 3

    注意:我们在hello-spring-boot-starter中追加了新的内容,需要重新打包安装到maven仓库。

    2.3.2.2 使用starter

    在myapp工程的Controller方法上加入@MyLog注解

    package cn.itcast.controller;
    
    import cn.itcast.log.MyLog;
    import cn.itcast.service.HelloService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/hello")
    public class HelloController {
        //HelloService在我们自定义的starter中已经完成了自动配置,所以此处可以直接注入
        @Autowired
        private HelloService helloService;
    
        @MyLog(desc = "sayHello方法") //日志记录注解
        @GetMapping("/say")
        public String sayHello(){
            return helloService.sayHello();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    访问地址:http://localhost:8080/hello/say,查看控制台输出:

    请求uri:/hello/say
    请求方法名:cn.itcast.controller.HelloController.sayHello
    方法描述:sayHello方法
    方法执行时间:36ms
    
    • 1
    • 2
    • 3
    • 4

    3. lombok

    3.1 lombok介绍

    lombok是一个开源的代码生成库,能以简单的注解形式来简化Java类中的大量样板代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量。

    lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法,使代码看起来更简洁。

    lombok对应的maven坐标:

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.2 安装lombok插件

    要使用lombok需要在IDE中安装对应的lombok插件。本课程使用的开发工具为IntelliJ IDEA,安装插件过程如下:

    1、打开IntelliJ IDEA后点击菜单栏中的File–>Settings进入到设置页面

    2、点击设置页面中的Plugins进行插件的安装,在右侧选择Browse repositories…,然后在搜索页面输入lombok,可以查询到下方的Lombok Plugin,鼠标点击Lombok Plugin可在右侧看到Install按钮,点击该按钮便可安装

    3.3 lombok常用注解

    注解说明
    @Setter注解在类或属性,注解在类时为所有属性生成setter方法,注解在属性上时只为该属性生成setter方法
    @Getter使用方法同@Setter,区别在于生成的是getter方法
    @ToString注解在类,添加toString方法
    @EqualsAndHashCode注解在类,生成hashCode和equals方法
    @NoArgsConstructor注解在类,生成无参的构造方法
    @RequiredArgsConstructor注解在类,为类中需要特殊处理的属性生成构造方法,比如final和被@NonNull注解的属性
    @AllArgsConstructor注解在类,生成包含类中所有属性的构造方法
    @Data注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法
    @Slf4j注解在类,生成log变量,用于记录日志
    @Builder将类转变为建造者模式

    3.4 lombok入门案例

    第一步:创建maven工程lombok_demo并配置pom.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>cn.itcast</groupId>
        <artifactId>lombok_demo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <dependencies>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.10</version>
            </dependency>
        </dependencies>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    第二步:创建User类并加入lombok提供的注解

    package cn.itcast.entity;
    
    import lombok.*;
    
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private int id;
        private String name;
        private int age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    第三步:创建测试类TestLombok

    package cn.itcast;
    
    import cn.itcast.entity.User;
    
    public class TestLombok {
        public static void main(String[] args) {
            //无参构造方法
            User user1 = new User();
    
            /*
             setter方法
            */
            user1.setId(1);
            user1.setName("itcast");
            user1.setAge(18);
    
            /*
             getter方法
            */
            int id = user1.getId();
            String name = user1.getName();
            int age = user1.getAge();
    
            //带有所有参数的构造方法
            User user2 = new User(2,"itheima",20);
    
            //建造者模式
            User user3 = User.builder().id(3).name("boxuegu").age(22).build();
    
            /*
             toString方法
            */
            System.out.println(user1.toString());
            System.out.println(user2.toString());
            System.out.println(user3.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    注:可以使用反编译工具查看生成的class文件内容

    4. 项目搭建

    4.1 导入初始工程

    鉴于时间关系,我们不再手动创建工程,而是直接从课程资料中提供的初始工程pinda-authority导入即可。

    导入步骤:

    1、将初始工程pinda-authority复制到任意没有中文和空格的目录下

    2、打开IDEA,选择Open,选择pinda-authority工程目录即可

    导入的项目结构如下:

    在这里插入图片描述

    4.2 项目模块介绍

    品达通用权限系统项目整体工程结构和模块功能如下:

    pinda-authority              #聚合工程,用于聚合pd-parent、pd-apps、pd-tools等模块
    ├── pd-parent				 # 父工程,nacos配置及依赖包管理
    ├── pd-apps					 # 应用目录
    	├── pd-auth				 # 权限服务父工程
    		├── pd-auth-entity   # 权限实体
    		├── pa-auth-server   # 权限服务
    	├── pd-gateway			 # 网关服务
    └── pd-tools				 # 工具工程
    	├── pd-tools-common		 # 基础组件:基础配置类、函数、常量、统一异常处理、undertow服务器
    	├── pd-tools-core		 # 核心组件:基础实体、返回对象、上下文、异常处理、分布式锁、函数、树
    	├── pd-tools-databases	 # 数据源组件:数据源配置、数据权限、查询条件等
    	├── pd-tools-dozer		 # 对象转换:dozer配置、工具
    	├── pd-tools-j2cache	 # 缓存组件:j2cache、redis缓存
    	├── pd-tools-jwt         # JWT组件:配置、属性、工具
    	├── pd-tools-log	     # 日志组件:日志实体、事件、拦截器、工具
    	├── pd-tools-swagger2	 # 文档组件:knife4j文档
    	├── pd-tools-user        # 用户上下文:用户注解、模型和工具,当前登录用户信息注入模块
    	├── pd-tools-validator	 # 表单验证: 后台表单规则验证
    	├── pd-tools-xss		 # xss防注入组件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    项目服务有两个:网关服务和权限服务:

    应用端口说明启动命令
    pd-gateway8760网关服务java -jar pd-gateway.jar &
    pd-auth-server8764权限服务java -jar pd-auth-server.jar &

    由于本系统是基于当前非常流行的前后端分离开发方式开发,其中前端部分是由专门的前端开发人员负责,我们课程中直接使用即可。

    4.3 服务注册和配置中心

    本项目使用Nacos来作为服务的注册和配置中心。Nacos是阿里巴巴开源的一款支持服务注册与发现,配置管理以及微服务管理的组件。用来取代以前常用的注册中心(zookeeper , eureka等等),以及配置中心(spring cloud config等等)。Nacos是集成了注册中心和配置中心的功能,做到了二合一。

    安装和配置过程如下:

    第一步:下载Nacos安装包,地址https://github.com/alibaba/nacos/releases/download/1.1.4/nacos-server-1.1.4.zip

    第二步:将下载的zip压缩文件解压

    第三步:修改配置文件:NACOS_HOME/conf/application.properties

    spring.datasource.platform=mysql
    db.num=1
    db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
    db.user=root
    db.password=root
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意:Nacos在存储数据时既可以使用内置数据库存储,也可以通过第三方指定的数据库存储。我们上面指定了使用MySQL数据库来存储Nacos的相关数据,所以需要配置我们使用的MySQL数据库的数据源信息,这个可以根据自己的MySQL数据库进行相应调整,例如MySQL的地址、用户名、密码等。

    第四步:创建数据库

    CREATE DATABASE `nacos` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
    
    • 1

    第五步:执行NACOS_HOME/conf/nacos-mysql.sql数据库脚本文件,完成后可以看到创建了如下表

    在这里插入图片描述

    第六步:启动Nacos服务,双击NACOS_HOME/bin/startup.cmd

    第七步:访问Nacos控制台,地址http://localhost:8848/nacos,默认用户名/密码:nacos/nacos

    第八步:新建命名空间pinda-dev,

    在这里插入图片描述

    注意,命名空间的id需要替换到项目文件pd-parent/pom.xml中对应的nacos命名空间的id:

    在这里插入图片描述

    第九步:导入配置文件,选择nacos配置中心的命名空间,点击导入配置按钮,选择文件:docs/nacos/nacos_config_export_2020-03-23 17_31_42.zip。导入完成后如下:

    在这里插入图片描述

    4.4 Redis

    在项目开发阶段我们使用windows版的Redis,直接解压授课资料中redis-win32-win64.zip压缩文件,然后双击REDIS_HOME/64bit/redis-server.exe启动Redis服务即可使用。

    4.5 开发方式介绍

    通过前面导入的初始项目可以看到,pd-tools工程中的各个子模块已经完成了开发,这些子模块属于项目的基础组件,为我们后续开发网关服务和权限服务提供支持,而且有一些子模块在其他项目中也可以复用。由于这些子模块会涉及到一些新技术或者框架,所以本课程会先讲解这些新技术或者框架的使用方法,然后再带领大家通读pd-tools中的相关模块的代码实现,从而了解此模块的作用和开发过程。

    本课程会按照如下顺序介绍pd-tools中的各个模块:

    - [ ] pd-tools-swagger2	 # 文档组件:knife4j文档
    - [ ] pd-tools-common		 # 基础组件:基础配置类、函数、常量、统一异常处理、undertow服务器
    - [ ] pd-tools-core		 # 核心组件:基础实体、返回对象、上下文、异常处理、分布式锁、函数、树
    - [ ] pd-tools-databases	 # 数据源组件:数据源配置、数据权限、查询条件等
    - [ ] pd-tools-dozer		 # 对象转换:dozer配置、工具
    - [ ] pd-tools-j2cache	 # 缓存组件:j2cache、redis缓存
    - [ ] pd-tools-jwt         # JWT组件:配置、属性、工具
    - [ ] pd-tools-log	     # 日志组件:日志实体、事件、拦截器、工具
    - [ ] pd-tools-user        # 用户上下文:用户注解、模型和工具,当前登录用户信息注入模块
    - [ ] pd-tools-validator	 # 表单验证: 后台表单规则验证
    - [ ] pd-tools-xss		 # xss防注入组件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    学习完这些模块之后就可以开发后面的网关服务和权限服务了。

    5. pd-tools-swagger2

    pd-tools-swagger2模块定位为文档组件,前后端开发人员可以查看接口文档,为前后端开发人员的开发统一接口,方便后续的前后端联调对接工作。

    5.1 swagger介绍

    相信无论是前端还是后端开发,都或多或少地被接口文档折磨过。前端经常抱怨后端给的接口文档与实际情况不一致。后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新。其实无论是前端调用后端,还是后端调用后端,都期望有一个好的接口文档。但是这个接口文档对于程序员来说,就跟注释一样,经常会抱怨别人写的代码没有写注释,然而自己写起代码起来,最讨厌的,也是写注释。所以仅仅只通过强制来规范大家是不够的,随着时间推移,版本迭代,接口文档往往很容易就跟不上代码了。

    使用Swagger你只需要按照它的规范去定义接口及接口相关的信息。再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,生成多种语言的客户端和服务端的代码,以及在线接口调试页面等等。这样,如果按照新的开发模式,在开发新版本或者迭代版本的时候,只需要更新Swagger描述文件,就可以自动生成接口文档和客户端服务端代码,做到调用端代码、服务端代码以及接口文档的一致性。

    为了简化swagger的使用,Spring框架对swagger进行了整合,建立了Spring-swagger项目,后面改成了现在的Springfox。通过在项目中引入Springfox,可以扫描相关的代码,生成描述文件,进而生成与代码一致的接口文档和客户端代码。

    Springfox对应的maven坐标如下:

    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5.2 swagger常用注解

    注解说明
    @Api用在请求的类上,例如Controller,表示对类的说明
    @ApiModel用在类上,通常是实体类,表示一个返回响应数据的信息
    @ApiModelProperty用在属性上,描述响应类的属性
    @ApiOperation用在请求的方法上,说明方法的用途、作用
    @ApiImplicitParams用在请求的方法上,表示一组参数说明
    @ApiImplicitParam用在@ApiImplicitParams注解中,指定一个请求参数的各个方面

    5.3 swagger入门案例

    第一步:创建maven工程swagger_demo并配置pom.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.2.2.RELEASE</version>
    		<relativePath/>
    	</parent>
    	<groupId>cn.itcast</groupId>
    	<artifactId>swagger_demo</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>swagger_demo</name>
    	<description>Demo project for Spring Boot</description>
    	<properties>
    		<java.version>1.8</java.version>
    	</properties>
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>io.springfox</groupId>
    			<artifactId>springfox-swagger-ui</artifactId>
    			<version>2.9.2</version>
    		</dependency>
    		<dependency>
    			<groupId>io.springfox</groupId>
    			<artifactId>springfox-swagger2</artifactId>
    			<version>2.9.2</version>
    		</dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    	</dependencies>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    第二步:创建application.yml文件

    server:
      port: 9000
    
    • 1
    • 2

    第三步: 创建实体类User和Menu

    package cn.itcast.entity;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    @Data
    @ApiModel(description = "用户实体")
    public class User {
        @ApiModelProperty(value = "主键")
        private int id;
        @ApiModelProperty(value = "姓名")
        private String name;
        @ApiModelProperty(value = "年龄")
        private int age;
        @ApiModelProperty(value = "地址")
        private String address;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    package cn.itcast.entity;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    @Data
    @ApiModel(description = "菜单实体")
    public class Menu {
        @ApiModelProperty(value = "主键")
        private int id;
        @ApiModelProperty(value = "菜单名称")
        private String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    第四步:创建UserController和MenuController

    package cn.itcast.controller.user;
    
    import cn.itcast.entity.User;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiImplicitParams;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @RestController
    @RequestMapping("/user")
    @Api(tags = "用户控制器")
    public class UserController {
        @GetMapping("/getUsers")
        @ApiOperation(value = "查询所有用户", notes = "查询所有用户信息")
        public List<User> getAllUsers(){
            User user = new User();
            user.setId(100);
            user.setName("itcast");
            user.setAge(20);
            user.setAddress("bj");
            List<User> list = new ArrayList<>();
            list.add(user);
            return list;
        }
    
        @PostMapping("/save")
        @ApiOperation(value = "新增用户", notes = "新增用户信息")
        public String save(@RequestBody User user){
            return "OK";
        }
    
        @PutMapping("/update")
        @ApiOperation(value = "修改用户", notes = "修改用户信息")
        public String update(@RequestBody User user){
            return "OK";
        }
    
        @DeleteMapping("/delete")
        @ApiOperation(value = "删除用户", notes = "删除用户信息")
        public String delete(int id){
            return "OK";
        }
    
        @ApiImplicitParams({
       		@ApiImplicitParam(name = "pageNum", value = "页码", 
                           required = true, type = "Integer"),
        	@ApiImplicitParam(name = "pageSize", value = "每页条数", 
                           required = true, type = "Integer"),
        })
        @ApiOperation(value = "分页查询用户信息")
        @GetMapping(value = "page/{pageNum}/{pageSize}")
        public String findByPage(@PathVariable Integer pageNum,
                                 @PathVariable Integer pageSize) {
            return "OK";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    package cn.itcast.controller.menu;
    
    import cn.itcast.entity.Menu;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiImplicitParams;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @RestController
    @RequestMapping("/menu")
    @Api(tags = "菜单控制器")
    public class MenuController {
        @GetMapping("/getMenus")
        @ApiOperation(value = "查询所有菜单", notes = "查询所有菜单信息")
        public List<Menu> getMenus(){
            Menu menu = new Menu();
            menu.setId(100);
            menu.setName("itcast");
            List<Menu> list = new ArrayList<>();
            list.add(menu);
            return list;
        }
    
        @PostMapping("/save")
        @ApiOperation(value = "新增菜单", notes = "新增菜单信息")
        public String save(@RequestBody Menu menu){
            return "OK";
        }
    
        @PutMapping("/update")
        @ApiOperation(value = "修改菜单", notes = "修改菜单信息")
        public String update(@RequestBody Menu menu){
            return "OK";
        }
    
        @DeleteMapping("/delete")
        @ApiOperation(value = "删除菜单", notes = "删除菜单信息")
        public String delete(int id){
            return "OK";
        }
    
        @ApiImplicitParams({
        	@ApiImplicitParam(name = "pageNum", value = "页码", 
                             required = true, type = "Integer"),
        	@ApiImplicitParam(name = "pageSize", value = "每页条数", 
                             required = true, type = "Integer"),
        })
        @ApiOperation(value = "分页查询菜单信息")
        @GetMapping(value = "page/{pageNum}/{pageSize}")
        public String findByPage(@PathVariable Integer pageNum,
                                 @PathVariable Integer pageSize) {
            return "OK";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    第五步:创建配置类SwaggerAutoConfiguration

    package cn.itcast.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @Configuration
    @EnableSwagger2
    public class SwaggerAutoConfiguration {
        @Bean
        public Docket createRestApi1() {
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo()).groupName("用户接口组")
                    .select()
                    //为当前包路径
                    .apis(RequestHandlerSelectors.basePackage("cn.itcast.controller.user"))
                    .build();
            return docket;
        }
    
        @Bean
        public Docket createRestApi2() {
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo()).groupName("菜单接口组")
                    .select()
                    //为当前包路径
                    .apis(RequestHandlerSelectors.basePackage("cn.itcast.controller.menu"))
                    .build();
            return docket;
        }
        
        //构建 api文档的详细信息
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    //页面标题
                    .title("API接口文档")
                    //创建人
                    .contact(new Contact("黑马程序员", "http://www.itheima.com", ""))
                    //版本号
                    .version("1.0")
                    //描述
                    .description("API 描述")
                    .build();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    第六步:创建启动类SwaggerDemoApplication

    package cn.itcast;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class SwaggerDemoApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(SwaggerDemoApplication.class, args);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    执行启动类main方法启动项目,访问地址:http://localhost:9000/swagger-ui.html

    在这里插入图片描述
    在这里插入图片描述

    5.4 knife4j介绍

    knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名knife4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!其底层是对Springfox的封装,使用方式也和Springfox一致,只是对接口文档UI进行了优化。

    核心功能

    • 文档说明:根据Swagger的规范说明,详细列出接口文档的说明,包括接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等信息,对该接口的使用情况一目了然。

    • 在线调试:提供在线接口联调的强大功能,自动解析当前接口参数,同时包含表单验证,调用参数可返回接口响应内容、headers、响应时间、响应状态码等信息,帮助开发者在线调试。

    5.5 knife4j入门案例

    第一步:创建maven工程knife4j_demo并配置pom.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                                 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/>
        </parent>
        <groupId>cn.itcast</groupId>
        <artifactId>knife4j_demo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>knife4j-spring-boot-starter</artifactId>
                <version>2.0.1</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    第二步: 创建实体类User和Menu

    package cn.itcast.entity;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    @Data
    @ApiModel(description = "用户实体")
    public class User {
        @ApiModelProperty(value = "主键")
        private int id;
        @ApiModelProperty(value = "姓名")
        private String name;
        @ApiModelProperty(value = "年龄")
        private int age;
        @ApiModelProperty(value = "地址")
        private String address;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    package cn.itcast.entity;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    @Data
    @ApiModel(description = "菜单实体")
    public class Menu {
        @ApiModelProperty(value = "主键")
        private int id;
        @ApiModelProperty(value = "菜单名称")
        private String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    第三步:创建UserController和MenuController

    package cn.itcast.controller.user;
    
    import cn.itcast.entity.User;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiImplicitParams;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @RestController
    @RequestMapping("/user")
    @Api(tags = "用户控制器")
    public class UserController {
        @GetMapping("/getUsers")
        @ApiOperation(value = "查询所有用户", notes = "查询所有用户信息")
        public List<User> getAllUsers(){
            User user = new User();
            user.setId(100);
            user.setName("itcast");
            user.setAge(20);
            user.setAddress("bj");
            List<User> list = new ArrayList<>();
            list.add(user);
            return list;
        }
    
        @PostMapping("/save")
        @ApiOperation(value = "新增用户", notes = "新增用户信息")
        public String save(@RequestBody User user){
            return "OK";
        }
    
        @PutMapping("/update")
        @ApiOperation(value = "修改用户", notes = "修改用户信息")
        public String update(@RequestBody User user){
            return "OK";
        }
    
        @DeleteMapping("/delete")
        @ApiOperation(value = "删除用户", notes = "删除用户信息")
        public String delete(int id){
            return "OK";
        }
    
        @ApiImplicitParams({
       		@ApiImplicitParam(name = "pageNum", value = "页码", 
                           required = true, type = "Integer"),
        	@ApiImplicitParam(name = "pageSize", value = "每页条数", 
                           required = true, type = "Integer"),
        })
        @ApiOperation(value = "分页查询用户信息")
        @GetMapping(value = "page/{pageNum}/{pageSize}")
        public String findByPage(@PathVariable Integer pageNum,
                                 @PathVariable Integer pageSize) {
            return "OK";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    package cn.itcast.controller.menu;
    
    import cn.itcast.entity.Menu;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiImplicitParams;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @RestController
    @RequestMapping("/menu")
    @Api(tags = "菜单控制器")
    public class MenuController {
        @GetMapping("/getMenus")
        @ApiOperation(value = "查询所有菜单", notes = "查询所有菜单信息")
        public List<Menu> getMenus(){
            Menu menu = new Menu();
            menu.setId(100);
            menu.setName("itcast");
            List<Menu> list = new ArrayList<>();
            list.add(menu);
            return list;
        }
    
        @PostMapping("/save")
        @ApiOperation(value = "新增菜单", notes = "新增菜单信息")
        public String save(@RequestBody Menu menu){
            return "OK";
        }
    
        @PutMapping("/update")
        @ApiOperation(value = "修改菜单", notes = "修改菜单信息")
        public String update(@RequestBody Menu menu){
            return "OK";
        }
    
        @DeleteMapping("/delete")
        @ApiOperation(value = "删除菜单", notes = "删除菜单信息")
        public String delete(int id){
            return "OK";
        }
    
        @ApiImplicitParams({
        	@ApiImplicitParam(name = "pageNum", value = "页码", 
                             required = true, type = "Integer"),
        	@ApiImplicitParam(name = "pageSize", value = "每页条数", 
                             required = true, type = "Integer"),
        })
        @ApiOperation(value = "分页查询菜单信息")
        @GetMapping(value = "page/{pageNum}/{pageSize}")
        public String findByPage(@PathVariable Integer pageNum,
                                 @PathVariable Integer pageSize) {
            return "OK";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    第四步:创建配置属性类SwaggerProperties

    package cn.itcast.config;
    
    import lombok.*;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import java.util.ArrayList;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    
    /*
    *配置属性类,用于封装接口文档相关属性,从配置文件读取信息封装成当前对象
    */
    
    @Data
    @ConfigurationProperties(prefix = "pinda.swagger")
    public class SwaggerProperties {
        private String title = "在线文档"; //标题
        private String group = ""; //自定义组名
        private String description = "在线文档"; //描述
        private String version = "1.0"; //版本
        private Contact contact = new Contact(); //联系人
        private String basePackage = "com.itheima.pinda"; //swagger会解析的包路径
        private List<String> basePath = new ArrayList<>(); //swagger会解析的url规则
        private List<String> excludePath = new ArrayList<>();//在basePath基础上需要排除的url规则
        private Map<String, DocketInfo> docket = new LinkedHashMap<>(); //分组文档
    	public String getGroup() {
            if (group == null || "".equals(group)) {
                return title;
            }
            return group;
        }
        @Data
        public static class DocketInfo {
            private String title = "在线文档"; //标题
            private String group = ""; //自定义组名
            private String description = "在线文档"; //描述
            private String version = "1.0"; //版本
            private Contact contact = new Contact(); //联系人
            private String basePackage = ""; //swagger会解析的包路径
            private List<String> basePath = new ArrayList<>(); //swagger会解析的url规则
            private List<String> excludePath = new ArrayList<>();//在basePath基础上需要排除的url
            public String getGroup() {
                if (group == null || "".equals(group)) {
                    return title;
                }
                return group;
            }
        }
        @Data
        public static class Contact {
            private String name = "pinda"; //联系人
            private String url = ""; //联系人url
            private String email = ""; //联系人email
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    第五步:创建application.yml文件

    server:
      port: 7788
    pinda:
      swagger:
        enabled: true #是否启用swagger
        docket:
          user:
            title: 用户模块
            base-package: cn.itcast.controller.user
          menu:
            title: 菜单模块
            base-package: cn.itcast.controller.menu
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    第六步:创建配置类SwaggerAutoConfiguration

    package cn.itcast.config;
    
    import com.google.common.base.Predicate;
    import com.google.common.base.Predicates;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.BeanFactoryAware;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.config.ConfigurableBeanFactory;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.bind.annotation.RequestMethod;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.List;
    
    @Configuration
    @ConditionalOnProperty(name = "pinda.swagger.enabled", havingValue = "true", 
                           matchIfMissing = true)
    @EnableSwagger2
    @EnableConfigurationProperties(SwaggerProperties.class)
    public class SwaggerAutoConfiguration implements BeanFactoryAware {
        @Autowired
        SwaggerProperties swaggerProperties;
        private BeanFactory beanFactory;
        @Bean
        @ConditionalOnMissingBean
        public List<Docket> createRestApi(){
            ConfigurableBeanFactory configurableBeanFactory = 
                								(ConfigurableBeanFactory) beanFactory;
            List<Docket> docketList = new LinkedList<>();
            // 没有分组
            if (swaggerProperties.getDocket().isEmpty()) {
                Docket docket = createDocket(swaggerProperties);
                configurableBeanFactory.registerSingleton(swaggerProperties.getTitle(), 
                                                          docket);
                docketList.add(docket);
                return docketList;
            }
            // 分组创建
            for (String groupName : swaggerProperties.getDocket().keySet()){
                SwaggerProperties.DocketInfo docketInfo = 
                    swaggerProperties.getDocket().get(groupName);
                ApiInfo apiInfo = new ApiInfoBuilder()
                        //页面标题
                        .title(docketInfo.getTitle())
                        //创建人
                        .contact(new Contact(docketInfo.getContact().getName(),
                                docketInfo.getContact().getUrl(),
                                docketInfo.getContact().getEmail()))
                        //版本号
                        .version(docketInfo.getVersion())
                        //描述
                        .description(docketInfo.getDescription())
                        .build();
    
                // base-path处理
                // 当没有配置任何path的时候,解析/**
                if (docketInfo.getBasePath().isEmpty()) {
                    docketInfo.getBasePath().add("/**");
                }
                List<Predicate<String>> basePath = new ArrayList<>();
                for (String path : docketInfo.getBasePath()) {
                    basePath.add(PathSelectors.ant(path));
                }
    
                // exclude-path处理
                List<Predicate<String>> excludePath = new ArrayList<>();
                for (String path : docketInfo.getExcludePath()) {
                    excludePath.add(PathSelectors.ant(path));
                }
    
                Docket docket = new Docket(DocumentationType.SWAGGER_2)
                        .apiInfo(apiInfo)
                    	.groupName(docketInfo.getGroup())
                        .select()
                        //为当前包路径
                        .apis(RequestHandlerSelectors.basePackage(docketInfo.getBasePackage()))
                        .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)),Predicates.or(basePath)))
                        .build();
                configurableBeanFactory.registerSingleton(groupName, docket);
                docketList.add(docket);
            }
            return docketList;
        }
    
        //构建 api文档的详细信息
        private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
            return new ApiInfoBuilder()
                    //页面标题
                    .title(swaggerProperties.getTitle())
                    //创建人
                    .contact(new Contact(swaggerProperties.getContact().getName(),
                                            swaggerProperties.getContact().getUrl(),
                                            swaggerProperties.getContact().getEmail()))
                    //版本号
                    .version(swaggerProperties.getVersion())
                    //描述
                    .description(swaggerProperties.getDescription())
                    .build();
        }
    
        //创建接口文档对象
        private Docket createDocket(SwaggerProperties swaggerProperties) {
            //API 基础信息
            ApiInfo apiInfo = apiInfo(swaggerProperties);
    
            // base-path处理
            // 当没有配置任何path的时候,解析/**
            if (swaggerProperties.getBasePath().isEmpty()) {
                swaggerProperties.getBasePath().add("/**");
            }
            List<Predicate<String>> basePath = new ArrayList<>();
            for (String path : swaggerProperties.getBasePath()) {
                basePath.add(PathSelectors.ant(path));
            }
    
            // exclude-path处理
            List<Predicate<String>> excludePath = new ArrayList<>();
            for (String path : swaggerProperties.getExcludePath()) {
                excludePath.add(PathSelectors.ant(path));
            }
    
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo)
                    .groupName(swaggerProperties.getGroup())
                    .select()
                    .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
                    .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)),Predicates.or(basePath)))
                    .build();
        }
    
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148

    第七步:创建启动类SwaggerDemoApplication

    package cn.itcast;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class SwaggerDemoApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(SwaggerDemoApplication.class, args);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    执行启动类main方法启动项目,访问地址:http://localhost:7788/doc.html

    在这里插入图片描述

    在这里插入图片描述

    如果接口文档不分组,我们可以修改application.yml文件:

    server:
      port: 7788
    pinda:
      swagger:
        enabled: true #是否启用swagger
        title: test模块
        base-package: cn.itcast.controller
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    再次访问地址:http://localhost:7788/doc.html

    在这里插入图片描述

    可以看到所有的接口在一个分组中。

    5.6 pd-tools-swagger2使用

    通过上面的入门案例我们已经完成了接口文档的相关开发,而pd-tools-swagger2模块就是使用这种方式开发的,并且按照Spring boot starter的规范在/resources/META-INF中提供spring.factories文件,内容如下:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
        com.itheima.pinda.swagger2.SwaggerAutoConfiguration
    
    • 1
    • 2

    这样我们在其他模块中如果需要使用swagger接口文档功能,只需要引入这个starter并且在application.yml中进行swagger的相关配置即可,例如:

    pinda:
      swagger:
        enabled: true #是否启用swagger
        docket:
          user:
            title: 用户模块
            base-package: cn.itcast.controller.user
          menu:
            title: 菜单模块
            base-package: cn.itcast.controller.menu
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    具体使用过程:

    第一步:创建maven工程mySwaggerApp并配置pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/>
        </parent>
        <groupId>com.itheima</groupId>
        <artifactId>mySwaggerApp</artifactId>
        <version>1.0-SNAPSHOT</version>
        <dependencies>
            <!--引入我们自己定义的swagger基础模块-->
            <dependency>
                <groupId>com.itheima</groupId>
                <artifactId>pd-tools-swagger2</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    第二步:创建User实体类

    package com.itheima.entity;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    @Data
    @ApiModel(description = "用户实体")
    public class User {
        @ApiModelProperty(value = "主键")
        private int id;
        @ApiModelProperty(value = "姓名")
        private String name;
        @ApiModelProperty(value = "年龄")
        private int age;
        @ApiModelProperty(value = "地址")
        private String address;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    第三步:创建UserController

    package com.itheima.controller;
    
    import com.itheima.entity.User;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiImplicitParams;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @RestController
    @RequestMapping("/user")
    @Api(tags = "用户控制器")
    public class UserController {
        @GetMapping("/getUsers")
        @ApiOperation(value = "查询所有用户", notes = "查询所有用户信息")
        public List<User> getAllUsers(){
            User user = new User();
            user.setId(100);
            user.setName("itcast");
            user.setAge(20);
            user.setAddress("bj");
            List<User> list = new ArrayList<>();
            list.add(user);
            return list;
        }
    
        @PostMapping("/save")
        @ApiOperation(value = "新增用户", notes = "新增用户信息")
        public String save(@RequestBody User user){
            return "OK";
        }
    
        @PutMapping("/update")
        @ApiOperation(value = "修改用户", notes = "修改用户信息")
        public String update(@RequestBody User user){
            return "OK";
        }
    
        @DeleteMapping("/delete")
        @ApiOperation(value = "删除用户", notes = "删除用户信息")
        public String delete(int id){
            return "OK";
        }
    
        @ApiImplicitParams({
                @ApiImplicitParam(name = "pageNum", value = "页码",
                        required = true, type = "Integer"),
                @ApiImplicitParam(name = "pageSize", value = "每页条数",
                        required = true, type = "Integer"),
        })
        @ApiOperation(value = "分页查询用户信息")
        @GetMapping(value = "page/{pageNum}/{pageSize}")
        public String findByPage(@PathVariable Integer pageNum,
                                 @PathVariable Integer pageSize) {
            return "OK";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    第四步:创建application.yml

    server:
      port: 8080
    pinda:
      swagger:
        enabled: true
        title: 在线接口文档
        base-package: com.itheima.controller
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    第五步:创建启动类

    package com.itheima;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class MySwaggerApplication {
        public static void main(String[] args) {
            SpringApplication.run(MySwaggerApplication.class,args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    启动项目,访问地址:http://localhost:8080/doc.html

    在这里插入图片描述

    6. pd-tools-dozer

    pd-tools-dozer模块定位为对象转换,其本质就是一个Spring Boot starter,其他模块可以直接导入此模块就可以直接完成对象转换了。

    6.1 dozer介绍

    Dozer是Java Bean到Java Bean映射器,它以递归方式将数据从一个对象复制到另一个对象。 dozer是用来对两个对象之间属性转换的工具,有了这个工具之后,我们将一个对象的所有属性值转给另一个对象时,就不需要再去写重复的调用set和get方法了。dozer其实是对我们熟知的beanutils的封装。

    dozer的maven坐标:

    <dependency>
        <groupId>com.github.dozermapper</groupId>
        <artifactId>dozer-core</artifactId>
        <version>6.5.0</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    为了简化使用方式,dozer还提供了starter,其maven坐标为:

    <dependency>
        <groupId>com.github.dozermapper</groupId>
        <artifactId>dozer-spring-boot-starter</artifactId>
        <version>6.5.0</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.2 dozer入门案例

    第一步:创建maven工程dozer_demo并配置pom.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/>
        </parent>
        <groupId>cn.itcast</groupId>
        <artifactId>dozer_demo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <dependencies>
            <dependency>
                <groupId>com.github.dozermapper</groupId>
                <artifactId>dozer-spring-boot-starter</artifactId>
                <version>6.5.0</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    第二步:创建UserDTO和UserEntity

    package com.itheima.dto;
    import lombok.Data;
    @Data
    public class UserDTO {
        private String userId;
        private String userName;
        private int userAge;
        private String address;
        private String birthday;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    package com.itheima.entity;
    import lombok.Data;
    import java.util.Date;
    @Data
    public class UserEntity {
        private String id;
        private String name;
        private int age;
        private String address;
        private Date birthday;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    第三步:在resources/dozer/目录下创建dozer的全局配置文件global.dozer.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://dozermapper.github.io/schema/bean-mapping"
              xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping 
                                  http://dozermapper.github.io/schema/bean-mapping.xsd">
        <!--
        全局配置:
        <date-format>表示日期格式
         -->
        <configuration>
            <date-format>yyyy-MM-dd</date-format>
        </configuration>
    </mappings>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注:全局配置文件名称可以任意

    第四步:在resources/dozer/目录下创建dozer的映射文件biz.dozer.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://dozermapper.github.io/schema/bean-mapping"
              xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping
                                 http://dozermapper.github.io/schema/bean-mapping.xsd">
        <!--描述两个类中属性的对应关系,对于两个类中同名的属性可以不映射-->
        <mapping date-format="yyyy-MM-dd">
            <class-a>com.itheima.entity.UserEntity</class-a>
            <class-b>com.itheima.dto.UserDTO</class-b>
            <field>
                <a>id</a>
                <b>userId</b>
            </field>
            <field>
                <a>name</a>
                <b>userName</b>
            </field>
            <field>
                <a>age</a>
                <b>userAge</b>
            </field>
        </mapping>
    	<!--
    	可以使用map-id指定映射的标识,在程序中通过此标识来确定使用当前这个映射关系
    	-->
        <mapping date-format="yyyy-MM-dd" map-id="user">
            <class-a>com.itheima.entity.UserEntity</class-a>
            <class-b>com.itheima.dto.UserDTO</class-b>
            <field>
                <a>id</a>
                <b>userId</b>
            </field>
            <field>
                <a>name</a>
                <b>userName</b>
            </field>
            <field>
                <a>age</a>
                <b>userAge</b>
            </field>
        </mapping>
    </mappings>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    注:映射文件名称可以任意

    第五步:编写application.yml文件

    dozer:
      mappingFiles:
        - classpath:dozer/global.dozer.xml
        - classpath:dozer/biz.dozer.xml
    
    • 1
    • 2
    • 3
    • 4

    第六步:编写启动类DozerApp

    package com.itheima;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class DozerApp {
        public static void main(String[] args) {
            SpringApplication.run(DozerApp.class,args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    第七步:编写单元测试DozerTest

    package cn.itcast.test;
    
    import com.github.dozermapper.core.DozerBeanMapper;
    import com.github.dozermapper.core.DozerBeanMapperBuilder;
    import com.github.dozermapper.core.Mapper;
    import com.github.dozermapper.core.metadata.MappingMetadata;
    import com.itheima.DozerApp;
    import com.itheima.dto.UserDTO;
    import com.itheima.entity.UserEntity;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = DozerApp.class)
    public class DozerTest {
        @Autowired
        private Mapper mapper;
        @Test
        public void testDozer1(){
            UserDTO userDTO = new UserDTO();
            userDTO.setUserId("100");
            userDTO.setUserName("itcast");
            userDTO.setUserAge(20);
            userDTO.setAddress("bj");
            userDTO.setBirthday("2010-11-20");
    
            UserEntity user = mapper.map(userDTO, UserEntity.class);
            System.out.println(user);
        }
    
        @Test
        public void testDozer2(){
            UserDTO userDTO = new UserDTO();
            userDTO.setUserId("100");
            userDTO.setUserName("itcast");
            userDTO.setUserAge(20);
            userDTO.setAddress("bj");
            userDTO.setBirthday("2010-11-20");
    
            UserEntity user = new UserEntity();
            user.setId("200");
            System.out.println(user);
            mapper.map(userDTO,user);
            System.out.println(user);
        }
    
        @Test
        public void testDozer3(){
            UserDTO userDTO = new UserDTO();
            userDTO.setUserId("100");
            userDTO.setUserName("itcast");
            userDTO.setUserAge(20);
            userDTO.setAddress("bj");
    
            UserEntity user = new UserEntity();
            System.out.println(user);
            mapper.map(userDTO,user,"user");
            System.out.println(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    6.3 pd-tools-dozer使用

    在pd-tools-dozer模块中为了进一步简化操作,封装了一个工具类DozerUtils,其内部使用的就是Mapper对象进行的操作。并且按照Spring Boot starter的规范编写/resources/META-INF/spring.factories文件,内容如下:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
        com.itheima.pinda.dozer.DozerAutoConfiguration
    
    • 1
    • 2

    在配置类DozerAutoConfiguration中完成DozerUtils对象的创建,这样其他的程序如果需要使用dozer进行对象转换,只需要引入这个模块的maven坐标并且提供对应的映射文件就可以在程序中直接注入DozerUtils对象进行操作了。

    具体使用过程:

    第一步:创建maven工程myDozerApp并配置pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                                 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/>
        </parent>
        <groupId>com.itheima</groupId>
        <artifactId>myDozerApp</artifactId>
        <version>1.0-SNAPSHOT</version>
        <dependencies>
            <!--引入我们自己定义的dozer基础模块-->
            <dependency>
                <groupId>com.itheima</groupId>
                <artifactId>pd-tools-dozer</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    第二步:创建UserEntity和UserDTO

    package com.itheima.entity;
    
    import lombok.Data;
    
    @Data
    public class UserEntity {
        private Integer id;
        private String name;
        private int age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    package com.itheima.dto;
    
    import lombok.Data;
    
    @Data
    public class UserDTO {
        private Integer id;
        private String name;
        private int age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第三步:创建UserController

    package com.itheima.controller;
    
    import com.itheima.dto.UserDTO;
    import com.itheima.entity.UserEntity;
    import com.itheima.pinda.dozer.DozerUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
        @Autowired
        private DozerUtils dozerUtils; //在pd-tools-dozer中已经完成了自动配置,可以直接注入
    
        @GetMapping("/mapper")
        public UserEntity mapper(){
            UserDTO userDTO = new UserDTO();
            userDTO.setId(10);
            userDTO.setName("itcast");
            userDTO.setAge(20);
    
            UserEntity userEntity = dozerUtils.map(userDTO, UserEntity.class);
            return userEntity;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    第四步:创建application.yml

    server:
      port: 8080
    
    • 1
    • 2

    第五步:创建启动类

    package com.itheima;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class MyDozerApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyDozerApplication.class,args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    启动项目,访问地址:http://localhost:8080/user/mapper

    在这里插入图片描述

    注意:由于当前我们创建的UserEntity和UserDTO中的属性完全一致,所以并没有提供映射文件,如果这两个类中的属性存在不一致的情况,需要创建映射文件进行映射,并且还需要在application.yml中配置映射文件的位置,例如:

    dozer:
      mappingFiles:
        - classpath:dozer/biz.dozer.xml  #指定dozer的映射文件位置
    
    • 1
    • 2
    • 3
  • 相关阅读:
    Kotlin协程:Flow基础原理
    常见的安全设备详解
    VBA 输出到CMD控制台显示暨更新当前行显示
    C++基础——函数
    【性能测试】Jmeter性能压测-阶梯式/波浪式场景总结(详细)
    mask: rle, polygon
    static静态成员变量使用@Value注入方式
    找不到msvcp140.dll解决方法的5个解决方法以及msvcp140.dll丢失原因分析
    MySQL中表的增删查改(进阶),超详细!
    面向对象14:抽象类
  • 原文地址:https://blog.csdn.net/qq_22075913/article/details/125412653