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定义

继续跟踪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并完成实例的创建工作。

2.2.3 自动配置总结

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

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

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>

第二步:创建配置属性类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;}@Overridepublic 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;//通过构造方法注入配置属性对象HelloPropertiespublic HelloServiceAutoConfiguration(HelloProperties helloProperties) {this.helloProperties = helloProperties;}//实例化HelloService并载入Spring IoC容器@Bean@ConditionalOnMissingBeanpublic 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仓库供其他应用来使用。

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>

第二步:创建application.yml文件

server:port: 8080
hello:name: xiaomingaddress: 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中已经完成了自动配置,所以此处可以直接注入@Autowiredprivate 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

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>

第二步:自定义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仓库。

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中已经完成了自动配置,所以此处可以直接注入@Autowiredprivate 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

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>

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>

第二步:创建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文件内容

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防注入组件

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

应用 端口 说明 启动命令
pd-gateway 8760 网关服务 java -jar pd-gateway.jar &
pd-auth-server 8764 权限服务 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

注意: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。导入完成后如下:

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防注入组件

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

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>

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>

第二步:创建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 {@Beanpublic Docket createRestApi1() {Docket docket = new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).groupName("用户接口组").select()//为当前包路径.apis(RequestHandlerSelectors.basePackage("cn.itcast.controller.user")).build();return docket;}@Beanpublic 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


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>

第二步: 创建实体类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;}@Datapublic 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基础上需要排除的urlpublic String getGroup() {if (group == null || "".equals(group)) {return title;}return group;}}@Datapublic static class Contact {private String name = "pinda"; //联系人private String url = ""; //联系人urlprivate String email = ""; //联系人email}
}

第五步:创建application.yml文件

server:port: 7788
pinda:swagger:enabled: true #是否启用swaggerdocket:user:title: 用户模块base-package: cn.itcast.controller.usermenu: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 {@AutowiredSwaggerProperties swaggerProperties;private BeanFactory beanFactory;@Bean@ConditionalOnMissingBeanpublic 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();}@Overridepublic 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 #是否启用swaggertitle: test模块base-package: cn.itcast.controller

再次访问地址: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

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

pinda:swagger:enabled: true #是否启用swaggerdocket:user:title: 用户模块base-package: cn.itcast.controller.usermenu: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: truetitle: 在线接口文档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

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>

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

<dependency><groupId>com.github.dozermapper</groupId><artifactId>dozer-spring-boot-starter</artifactId><version>6.5.0</version>
</dependency>

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>

第二步:创建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-mappinghttp://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 {@Autowiredprivate Mapper mapper;@Testpublic 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);}@Testpublic 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);}@Testpublic 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);}
}

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

在配置类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 {@Autowiredprivate 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的映射文件位置

品达通用权限系统(Day 1~Day 2)相关推荐

  1. 品达通用权限系统(Day 5~Day 6)

    10. pd-tools-jwt pd-tools-jwt模块的定位是对于jwt令牌相关操作进行封装,为认证.鉴权提供支撑. 提供的功能:生成jwt token.解析jwt token 10.1 认证 ...

  2. 六、品达通用权限系统__pd-tools-log

    一.pd-tools-log pd-tools-log模块定位为日子模块,本质也是一个starter.提供的日志功能主要有两个方面: 通过logback框架可以在控制台或者日志文件记录日志信息 拦截用 ...

  3. 品达通用_9. pd-tools-log

    品达通用_9. pd-tools-log 文章目录 品达通用_9. pd-tools-log 9. pd-tools-log 9.1 logback 9.1.1 logback介绍 9.1.2 log ...

  4. 一步步教你如何用疯狂.NET架构中的通用权限系统 -- 如何控制用户显示的菜单权限...

    菜单权限是我们经常会遇到的权限,也是经常需要进行处理的权限,往往权限是通过控制菜单权限开始折腾起来的. 第一步:我的后台管理控制端,有一个叫模块配置的功能,这里集中配置,哪些模块可以用,哪些模块先锁定 ...

  5. Java通用权限系统视频(2021年高含金量版)

    来源: 来自网络,如侵权请告知博主删除????. 仅学习使用,请勿用于其他- 大家好,我是肉哥,最近有小伙伴联系我需要通用权限系统相关资源,分享给大家! 目录 01-课程介绍.mp4: U, ?9 r ...

  6. 职场不可怜弱者,求真务实、做深做透,坚持真理、鉴定信念,走火入魔.NET通用权限系统简易物理结构图解

    坚持真理.脚踏实地.做国人值得骄傲的软件产品,求真务实.做深做透,职场不可怜弱.无知者的打击就是对牛放屁,有一颗钢铁般坚强的心.10年如一日的对管理软件的深入研究激情,不做眼高手低的中国特色程序员,一 ...

  7. 一步步教你如何用疯狂.NET架构中的通用权限系统 -- 分级管理

       例如我们公司是有几百个员工的大型IT上市公司,在北京.宁波.杭州都有规模比较大的分公司,在这个规模的集团里总公司的管理员根本没精力去管里各个分公司的员工的详细权限等,说白了,他也不清楚分公司的具 ...

  8. 管理数据通用权限系统快速开发框架设计

    新手发帖,很多方面都是刚入门,有错误的地方请大家见谅,欢迎批评指正 系统在线演示地址: http://120.90.2.126:8051 登录账户:system,密码:system### DEMO下载 ...

  9. 通用权限系统-2023V1

    说明 本文主要对功能授权设计做详细说明,对于数据授权可以参考设计思路: 需求 原始需求 背景 假定一个系统,有自身的组织机构-部门,用户有各自的角色: 系统中存在多个应用套餐,一个应用套餐包含许多应用 ...

最新文章

  1. opencv投影变换
  2. pytorch 函数clamp
  3. GIT提交的时候出现 ! [rejected] master -> master (non-fast-forward)错误
  4. python 判断文件是否被占用_python 在删除文件的时候检测该文件是否被其他线程或者进程占用?...
  5. 个人永久性免费-Excel催化剂功能第75波-标签式报表转标准数据源
  6. @所有技术人,快来翻开属于你的2021定制日历!
  7. 鸿蒙电视应用市场,任正非:鸿蒙系统已上线,未来将被应用到手机、平板、电视系列产品上...
  8. 【转载】UMTS到LTE的系统架构演进
  9. php实现facebook登陆功能
  10. 论文: TextBoxes
  11. linux编译poco静态库,iOS——为Xcode编译POCO C++静态库
  12. !=EOF的含义和原理
  13. 类似qq新闻提示窗口样码(cpy)
  14. 量子物理 薛定谔的猫
  15. 通货膨胀 通货紧缩 贸易逆差
  16. Windows Live Essential 2011 Silent Install
  17. OpenStack硬件管理加速利器:Cyborg
  18. 小米MIX FOLD折叠屏上手体验:MIUI大更新 满血的掌上PC模式“有点狠”
  19. Java有哪些编程语言
  20. pandas用众数填充缺失值_数据处理之缺失值填充

热门文章

  1. 哈工大车万翔:自然语言处理范式正在变迁
  2. Atlas 200 HiLens Kit
  3. AcWing 1017. 怪盗基德的滑翔翼
  4. Alibaba内部首发“M8级”微服务架构手册,GitHub上杀疯了
  5. A-瑞神的序列 B- 消消乐大师-Q老师(M3)
  6. 在微信、支付宝、百度钱包实现点击返回按钮关闭当前页面和窗口
  7. 第六周项目2建立链栈算法库
  8. U盘删除附带的CD驱动器内的数据
  9. css解决图片缩小变模糊问题
  10. 怎么从pdf中提取图片