赞
踩
对于企业中的项目绝大多数都需要进行用户权限管理、认证、鉴权、加密、解密、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


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
我们知道Spring Boot大大简化了项目初始搭建以及开发过程,而这些都是通过Spring Boot提供的starter来完成的。品达通用权限系统就是基于Spring Boot进行开发,而且一些基础模块其本质就是starter,所以我们需要对Spring Boot的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
Spring Boot之所以能够帮我们简化项目的搭建和开发过程,主要是基于它提供的起步依赖和自动配置。
起步依赖,其实就是将具备某种功能的坐标打包到一起,可以简化依赖导入的过程。例如,我们导入spring-boot-starter-web这个starter,则和web开发相关的jar包都一起导入到项目中了。如下图所示:

自动配置,就是无须手动配置xml,自动配置并管理bean,可以简化开发过程。那么Spring Boot是如何完成自动配置的呢?
自动配置涉及到如下几个关键步骤:
我们可以通过一个实际的例子mybatis-spring-boot-starter来说明自动配置的实现过程。
当我们在项目中导入了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的自动注册。
从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 |
要完成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注解生效。
spring boot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包中的类,那么依赖包中的bean是如何被发现和加载的?
我们需要从Spring Boot项目的启动类开始跟踪,在启动类上我们一般会加入SpringBootApplication注解,此注解的源码如下:

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

@EnableAutoConfiguration注解引入了@Import这个注解。
Import:导入需要自动配置的组件,此处为EnableAutoConfigurationImportSelector这个类
EnableAutoConfigurationImportSelector类源码如下:

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

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

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

spring.factories文件内容如下:

这样Spring Boot就可以加载到MybatisAutoConfiguration这个配置类了。
在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、@Configuration与@Bean:基于Java代码的bean配置
2、@Conditional:设置自动配置条件依赖
3、@EnableConfigurationProperties与@ConfigurationProperties:读取配置文件转换为bean
4、@EnableAutoConfiguration与@Import:实现bean发现与加载
本小节我们通过自定义两个starter来加强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>
第二步:创建配置属性类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 + '\'' + '}'; } }
第三步:创建服务类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;
}
}
第四步:创建自动配置类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()); } }
第五步:在resources目录下创建META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.itcast.config.HelloServiceAutoConfiguration
至此starter已经开发完成了,可以将当前starter安装到本地maven仓库供其他应用来使用。
第一步:创建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>
第二步:创建application.yml文件
server:
port: 8080
hello:
name: xiaoming
address: beijing
第三步:创建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(); } }
第四步:创建启动类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);
}
}
执行启动类main方法,访问地址http://localhost:8080/hello/say

在前面的案例一中我们通过定义starter,自动配置了一个HelloService实例。本案例我们需要通过自动配置来创建一个拦截器对象,通过此拦截器对象来实现记录日志功能。
我们可以在案例一的基础上继续开发案例二。
第一步:在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>
第二步:自定义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 "";
}
第三步:自定义日志拦截器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"); } } }
第四步:创建自动配置类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()); } }
第五步:在spring.factories中追加MyLogAutoConfiguration配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.itcast.config.HelloServiceAutoConfiguration,\
cn.itcast.config.MyLogAutoConfiguration
注意:我们在hello-spring-boot-starter中追加了新的内容,需要重新打包安装到maven仓库。
在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(); } }
访问地址:http://localhost:8080/hello/say,查看控制台输出:
请求uri:/hello/say
请求方法名:cn.itcast.controller.HelloController.sayHello
方法描述:sayHello方法
方法执行时间:36ms
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>
要使用lombok需要在IDE中安装对应的lombok插件。本课程使用的开发工具为IntelliJ IDEA,安装插件过程如下:
1、打开IntelliJ IDEA后点击菜单栏中的File–>Settings进入到设置页面
2、点击设置页面中的Plugins进行插件的安装,在右侧选择Browse repositories…,然后在搜索页面输入lombok,可以查询到下方的Lombok Plugin,鼠标点击Lombok Plugin可在右侧看到Install按钮,点击该按钮便可安装
| 注解 | 说明 |
|---|---|
| @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 | 将类转变为建造者模式 |
第一步:创建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>
第二步:创建User类并加入lombok提供的注解
package cn.itcast.entity;
import lombok.*;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private int age;
}
第三步:创建测试类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()); } }
注:可以使用反编译工具查看生成的class文件内容
鉴于时间关系,我们不再手动创建工程,而是直接从课程资料中提供的初始工程pinda-authority导入即可。
导入步骤:
1、将初始工程pinda-authority复制到任意没有中文和空格的目录下
2、打开IDEA,选择Open,选择pinda-authority工程目录即可
导入的项目结构如下:

品达通用权限系统项目整体工程结构和模块功能如下:
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防注入组件
项目服务有两个:网关服务和权限服务:
| 应用 | 端口 | 说明 | 启动命令 |
|---|---|---|---|
| pd-gateway | 8760 | 网关服务 | java -jar pd-gateway.jar & |
| pd-auth-server | 8764 | 权限服务 | java -jar pd-auth-server.jar & |
由于本系统是基于当前非常流行的前后端分离开发方式开发,其中前端部分是由专门的前端开发人员负责,我们课程中直接使用即可。
本项目使用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
注意:Nacos在存储数据时既可以使用内置数据库存储,也可以通过第三方指定的数据库存储。我们上面指定了使用MySQL数据库来存储Nacos的相关数据,所以需要配置我们使用的MySQL数据库的数据源信息,这个可以根据自己的MySQL数据库进行相应调整,例如MySQL的地址、用户名、密码等。
第四步:创建数据库
CREATE DATABASE `nacos` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
第五步:执行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。导入完成后如下:

在项目开发阶段我们使用windows版的Redis,直接解压授课资料中redis-win32-win64.zip压缩文件,然后双击REDIS_HOME/64bit/redis-server.exe启动Redis服务即可使用。
通过前面导入的初始项目可以看到,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防注入组件
学习完这些模块之后就可以开发后面的网关服务和权限服务了。
pd-tools-swagger2模块定位为文档组件,前后端开发人员可以查看接口文档,为前后端开发人员的开发统一接口,方便后续的前后端联调对接工作。
相信无论是前端还是后端开发,都或多或少地被接口文档折磨过。前端经常抱怨后端给的接口文档与实际情况不一致。后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新。其实无论是前端调用后端,还是后端调用后端,都期望有一个好的接口文档。但是这个接口文档对于程序员来说,就跟注释一样,经常会抱怨别人写的代码没有写注释,然而自己写起代码起来,最讨厌的,也是写注释。所以仅仅只通过强制来规范大家是不够的,随着时间推移,版本迭代,接口文档往往很容易就跟不上代码了。
使用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>
| 注解 | 说明 |
|---|---|
| @Api | 用在请求的类上,例如Controller,表示对类的说明 |
| @ApiModel | 用在类上,通常是实体类,表示一个返回响应数据的信息 |
| @ApiModelProperty | 用在属性上,描述响应类的属性 |
| @ApiOperation | 用在请求的方法上,说明方法的用途、作用 |
| @ApiImplicitParams | 用在请求的方法上,表示一组参数说明 |
| @ApiImplicitParam | 用在@ApiImplicitParams注解中,指定一个请求参数的各个方面 |
第一步:创建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>
第二步:创建application.yml文件
server:
port: 9000
第三步: 创建实体类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; }
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;
}
第四步:创建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"; } }
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"; } }
第五步:创建配置类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(); } }
第六步:创建启动类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);
}
}
执行启动类main方法启动项目,访问地址:http://localhost:9000/swagger-ui.html


knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名knife4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!其底层是对Springfox的封装,使用方式也和Springfox一致,只是对接口文档UI进行了优化。
核心功能:
文档说明:根据Swagger的规范说明,详细列出接口文档的说明,包括接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等信息,对该接口的使用情况一目了然。
在线调试:提供在线接口联调的强大功能,自动解析当前接口参数,同时包含表单验证,调用参数可返回接口响应内容、headers、响应时间、响应状态码等信息,帮助开发者在线调试。
第一步:创建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>
第二步: 创建实体类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; }
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;
}
第三步:创建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"; } }
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"; } }
第四步:创建配置属性类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 } }
第五步:创建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
第六步:创建配置类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; } }
第七步:创建启动类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);
}
}
执行启动类main方法启动项目,访问地址:http://localhost:7788/doc.html


如果接口文档不分组,我们可以修改application.yml文件:
server:
port: 7788
pinda:
swagger:
enabled: true #是否启用swagger
title: test模块
base-package: cn.itcast.controller
再次访问地址:http://localhost:7788/doc.html

可以看到所有的接口在一个分组中。
通过上面的入门案例我们已经完成了接口文档的相关开发,而pd-tools-swagger2模块就是使用这种方式开发的,并且按照Spring boot starter的规范在/resources/META-INF中提供spring.factories文件,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itheima.pinda.swagger2.SwaggerAutoConfiguration
这样我们在其他模块中如果需要使用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
具体使用过程:
第一步:创建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>
第二步:创建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; }
第三步:创建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"; } }
第四步:创建application.yml
server:
port: 8080
pinda:
swagger:
enabled: true
title: 在线接口文档
base-package: com.itheima.controller
第五步:创建启动类
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);
}
}
启动项目,访问地址:http://localhost:8080/doc.html

pd-tools-dozer模块定位为对象转换,其本质就是一个Spring Boot starter,其他模块可以直接导入此模块就可以直接完成对象转换了。
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>
为了简化使用方式,dozer还提供了starter,其maven坐标为:
<dependency>
<groupId>com.github.dozermapper</groupId>
<artifactId>dozer-spring-boot-starter</artifactId>
<version>6.5.0</version>
</dependency>
第一步:创建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>
第二步:创建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;
}
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;
}
第三步:在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>
注:全局配置文件名称可以任意
第四步:在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>
注:映射文件名称可以任意
第五步:编写application.yml文件
dozer:
mappingFiles:
- classpath:dozer/global.dozer.xml
- classpath:dozer/biz.dozer.xml
第六步:编写启动类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);
}
}
第七步:编写单元测试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); } }
在pd-tools-dozer模块中为了进一步简化操作,封装了一个工具类DozerUtils,其内部使用的就是Mapper对象进行的操作。并且按照Spring Boot starter的规范编写/resources/META-INF/spring.factories文件,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itheima.pinda.dozer.DozerAutoConfiguration
在配置类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>
第二步:创建UserEntity和UserDTO
package com.itheima.entity;
import lombok.Data;
@Data
public class UserEntity {
private Integer id;
private String name;
private int age;
}
package com.itheima.dto;
import lombok.Data;
@Data
public class UserDTO {
private Integer id;
private String name;
private int age;
}
第三步:创建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; } }
第四步:创建application.yml
server:
port: 8080
第五步:创建启动类
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);
}
}
启动项目,访问地址:http://localhost:8080/user/mapper

注意:由于当前我们创建的UserEntity和UserDTO中的属性完全一致,所以并没有提供映射文件,如果这两个类中的属性存在不一致的情况,需要创建映射文件进行映射,并且还需要在application.yml中配置映射文件的位置,例如:
dozer:
mappingFiles:
- classpath:dozer/biz.dozer.xml #指定dozer的映射文件位置
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。