新建工程和确定工程目录

项目分的模块为:

  • lab-admin项目核心模块
  • lab-common项目通用工具模块

1. 新建工程及多Module子工程

一、新建一个Maven工程

image-20220823210533909

二、填写项目信息

image-20220823210653240

三、删除一些新建Maven工程的不需要的文件(src文件夹

四、在该工程下新建子Module

image-20220823210942582

五、填写子Module信息(也是一个Maven工程)

image-20220823211018102

成果:

(对应pom.xml会自动写好子父对应关系)

image-20220823211104847

六、重复第五步,完成其他子Module创建

除了Module名不同,其他操作都一样。

2. 编写pom.xml文件

父级pom文件

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
<?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>org.opengms</groupId>
    <artifactId>opengms</artifactId>
    <version>1.0.0</version>

    <description>OpenGMS 实验室</description>

    <packaging>pom</packaging>

    <modules>
        <module>lab-admin</module>
        <module>lab-common</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <lab.version>1.0.0</lab.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
        <druid.version>1.2.11</druid.version>
        <bitwalker.version>1.21</bitwalker.version>
        <swagger.version>3.0.0</swagger.version>
        <kaptcha.version>2.3.2</kaptcha.version>
        <mybatis-spring-boot.version>2.2.2</mybatis-spring-boot.version>
        <pagehelper.boot.version>1.4.3</pagehelper.boot.version>
        <fastjson.version>2.0.9</fastjson.version>
        <oshi.version>6.2.1</oshi.version>
        <commons.io.version>2.11.0</commons.io.version>
        <commons.fileupload.version>1.4</commons.fileupload.version>
        <commons.collections.version>3.2.2</commons.collections.version>
        <poi.version>4.1.2</poi.version>
        <velocity.version>2.3</velocity.version>
        <jwt.version>0.9.1</jwt.version>
    </properties>

    <!-- 依赖声明 -->
    <dependencyManagement>
        <dependencies>
            <!-- SpringBoot的依赖配置-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.5.14</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- SpringBoot集成mybatis框架 -->
            <!-- pagehelper依赖包含了mybatis的依赖 -->
            <!--<dependency>-->
            <!--    <groupId>org.mybatis.spring.boot</groupId>-->
            <!--    <artifactId>mybatis-spring-boot-starter</artifactId>-->
            <!--    <version>${mybatis-spring-boot.version}</version>-->
            <!--</dependency>-->

            <!-- pagehelper 分页插件 -->
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>${pagehelper.boot.version}</version>
            </dependency>


            <!-- 阿里JSON解析器 -->
            <dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>${fastjson.version}</version>
            </dependency>

            <!-- io常用工具类 -->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>${commons.io.version}</version>
            </dependency>

            <!-- 文件上传工具类 -->
            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>${commons.fileupload.version}</version>
            </dependency>

            <!-- excel工具 -->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>${poi.version}</version>
            </dependency>

            <!-- Token生成与解析-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>

            <!-- 解析客户端操作系统、浏览器等 -->
            <dependency>
                <groupId>eu.bitwalker</groupId>
                <artifactId>UserAgentUtils</artifactId>
                <version>${bitwalker.version}</version>
            </dependency>

            <!-- 阿里数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>

            <!-- 验证码 -->
            <dependency>
                <groupId>com.github.penggle</groupId>
                <artifactId>kaptcha</artifactId>
                <version>${kaptcha.version}</version>
            </dependency>

            <!-- 获取系统信息 -->
            <dependency>
                <groupId>com.github.oshi</groupId>
                <artifactId>oshi-core</artifactId>
                <version>${oshi.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <!--编译及打包项目配置-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            
            <!-- 不执行单元测试,但会编译测试类并在target/test-classes目录下生成相应的class -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <!--依赖下载镜像源-->
    <repositories>
        <repository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>

    <!--插件镜像源-->
    <pluginRepositories>
        <pluginRepository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

注意点:

**一、**Maven中的<dependencyManagement>元素提供了一种管理依赖版本号的方式。在<dependencyManagement>元素中声明所依赖的jar包的版本号等信息,那么所有子项目再次引入此依赖jar包时则无需显式的列出版本号。Maven会沿着父子层级向上寻找拥有<dependencyManagement>元素的项目,然后使用它指定的版本号。

**二、**关于

1
2
<type>pom</type>
<scope>import</scope>

解释:

这样<dependencies>里面的依赖如果没有版本信息,就可以参考引入的pom文件的<dependencyManagement>里面的版本信息。就像maven继承方法似的,在父pom的< dependencyManagement>里,放入版本信息,在若干子pom里都省去版本信息了。子 pom只需到父pom的<dependencyManagement>里,找到相应的artifactId和groupId的版本信息即可。

**三、**关于

1
<packaging>pom</packaging>

解释:

配置<packaging>pom</packaging>的意思是使用maven分模块管理,都会有一个父级项目,pom文件一个重要的属性就是packaging(打包类型),一般来说所有的父级项目的packaging都为pom,packaging默认类型jar类型,如果不做配置,maven会将该项目打成jar包。

其他可填值:

pom ———> 父类型都为pom类型

jar ———> 内部调用或者是作服务使用

war ———> 需要部署的项目

**四、**Maven中optional和scope元素的使用

解释:https://developer.aliyun.com/article/844335

lab-admin的pom文件

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<?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">
    <parent>
        <artifactId>opengms</artifactId>
        <groupId>org.opengms</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-admin</artifactId>

    <description>
        管理模块
    </description>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <dependencies>

        <!-- spring-boot-devtools -->
        <!--<dependency>-->
        <!--    <groupId>org.springframework.boot</groupId>-->
        <!--    <artifactId>spring-boot-devtools</artifactId>-->
        <!--    <optional>true</optional> &lt;!&ndash; 表示依赖不会传递 &ndash;&gt;-->
        <!--</dependency>-->

        <!-- SpringBoot Web容器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--以下是排除的组件-->
            <exclusions>
                <!--排除默认自带的log组件-->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


        <!--log4j2日志相关 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <!-- SpringBoot yml配置文件编写智能提示 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- pagehelper 分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
        </dependency>

        <!--mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--自动生成实体类get/set方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>


        <!--项目common通用工具类 -->
        <dependency>
            <groupId>org.opengms</groupId>
            <artifactId>lab-common</artifactId>
            <version>1.0.0</version>
        </dependency>


        <!-- SpringBoot 拦截器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- 阿里数据库连接池 -->
        <!--<dependency>-->
        <!--    <groupId>com.alibaba</groupId>-->
        <!--    <artifactId>druid-spring-boot-starter</artifactId>-->
        <!--</dependency>-->

        <!-- 验证码 -->
        <!--<dependency>-->
        <!--    <groupId>com.github.penggle</groupId>-->
        <!--    <artifactId>kaptcha</artifactId>-->
        <!--    <exclusions>-->
        <!--        <exclusion>-->
        <!--            <artifactId>javax.servlet-api</artifactId>-->
        <!--            <groupId>javax.servlet</groupId>-->
        <!--        </exclusion>-->
        <!--    </exclusions>-->
        <!--</dependency>-->

        <!-- 获取系统信息 -->
        <!--<dependency>-->
        <!--    <groupId>com.github.oshi</groupId>-->
        <!--    <artifactId>oshi-core</artifactId>-->
        <!--</dependency>-->


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.1.1.RELEASE</version>
                <configuration>
                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
                </configuration>
                <executions>
                    <execution>
                        <!--打成可执行的运行包(.jar .war)-->
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <warName>${project.artifactId}</warName>
                </configuration>
            </plugin>
        </plugins>
        <finalName>${project.artifactId}</finalName>
    </build>

</project>

lab-common的pom文件

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<?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">
    <parent>
        <artifactId>opengms</artifactId>
        <groupId>org.opengms</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-common</artifactId>

    <description>
        common通用工具
    </description>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>

        <!-- Spring框架基本的核心工具 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>

        <!-- SpringWeb模块 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>

        <!-- spring security 安全认证 -->
        <!--<dependency>-->
        <!--    <groupId>org.springframework.boot</groupId>-->
        <!--    <artifactId>spring-boot-starter-security</artifactId>-->
        <!--</dependency>-->

        <!-- 自定义验证注解 -->
        <!--<dependency>-->
        <!--    <groupId>org.springframework.boot</groupId>-->
        <!--    <artifactId>spring-boot-starter-validation</artifactId>-->
        <!--</dependency>-->

        <!--常用工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <!-- JSON工具类 -->
        <!--<dependency>-->
        <!--    <groupId>com.fasterxml.jackson.core</groupId>-->
        <!--    <artifactId>jackson-databind</artifactId>-->
        <!--</dependency>-->

        <!-- 阿里JSON解析器 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>

        <!-- io常用工具类 -->
        <!--<dependency>-->
        <!--    <groupId>commons-io</groupId>-->
        <!--    <artifactId>commons-io</artifactId>-->
        <!--</dependency>-->

        <!-- 文件上传工具类 -->
        <!--<dependency>-->
        <!--    <groupId>commons-fileupload</groupId>-->
        <!--    <artifactId>commons-fileupload</artifactId>-->
        <!--</dependency>-->

        <!-- excel工具 -->
        <!--<dependency>-->
        <!--    <groupId>org.apache.poi</groupId>-->
        <!--    <artifactId>poi-ooxml</artifactId>-->
        <!--</dependency>-->

        <!-- yml解析器 -->
        <!--<dependency>-->
        <!--    <groupId>org.yaml</groupId>-->
        <!--    <artifactId>snakeyaml</artifactId>-->
        <!--</dependency>-->

         <!--Token生成与解析-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

        <!-- Jaxb -->
        <!--<dependency>-->
        <!--    <groupId>javax.xml.bind</groupId>-->
        <!--    <artifactId>jaxb-api</artifactId>-->
        <!--</dependency>-->

        <!-- redis 缓存操作 -->
        <!--<dependency>-->
        <!--    <groupId>org.springframework.boot</groupId>-->
        <!--    <artifactId>spring-boot-starter-data-redis</artifactId>-->
        <!--</dependency>-->

        <!-- pool 对象池 -->
        <!--<dependency>-->
        <!--    <groupId>org.apache.commons</groupId>-->
        <!--    <artifactId>commons-pool2</artifactId>-->
        <!--</dependency>-->

        <!-- 解析客户端操作系统、浏览器等 -->
        <!--<dependency>-->
        <!--    <groupId>eu.bitwalker</groupId>-->
        <!--    <artifactId>UserAgentUtils</artifactId>-->
        <!--</dependency>-->

        <!-- servlet包 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>


    </dependencies>


</project>

初始化admin模块

1. 添加依赖

上一节已添加

**父项目:**引入pagehelper-spring-boot-starter(后面分页实现使用的分页插件),并规定版本号。

pagehelper-spring-boot-starter内含依赖:(主要是有整合mybatis的驱动mybatis-spring-boot-starter,引入它就可以不用再声明引入mybatis驱动),如下图:

1
2
3
4
5
6
<!-- pagehelper 分页插件 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>${pagehelper.boot.version}</version>
</dependency>

lab-admin

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!-- pagehelper 分页插件 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

<!--mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

2. 准备数据库表

建库:

image-20220823214621095

建表:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- ----------------------------
-- 用户信息表
-- ----------------------------
drop table if exists sys_user;
create table sys_user (
  user_id           bigint(20)      not null auto_increment    comment '用户ID',
  user_name         varchar(30)     not null                   comment '用户名',
  user_type         varchar(2)      default '00'               comment '用户类型(00系统用户)',
  email             varchar(50)     default ''                 comment '用户邮箱',
  phonenumber       varchar(11)     default ''                 comment '手机号码',
  sex               char(1)         default '0'                comment '用户性别(0男 1女 2未知)',
  avatar            varchar(100)    default ''                 comment '头像地址',
  password          varchar(100)    default ''                 comment '密码',
  login_ip          varchar(128)    default ''                 comment '最后登录IP',
  login_date        datetime                                   comment '最后登录时间',
  create_by         varchar(64)     default ''                 comment '创建者',
  create_time       datetime                                   comment '创建时间',
  update_by         varchar(64)     default ''                 comment '更新者',
  update_time       datetime                                   comment '更新时间',
  remark            varchar(500)    default null               comment '备注',
  primary key (user_id)
) engine=innodb auto_increment=100 comment = '用户信息表';

insert into sys_user 
(user_id, user_name, email, phonenumber)
values(1, '阿彬', '7b@163.com', '15888888888');

3. 整合mybatis等初始化配置

application.yml配置文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 项目相关配置
lab:
  # 名称
  name: OpenGMS-Lab
  # 版本
  version: 1.0.0
  # 版权年份
  copyrightYear: 2022
  # 文件路径 示例( Windows配置E:/opengms-lab/uploadPath,Linux配置 /home/opengms-lab/uploadPath)
#  profile: E:/opengms-lab/uploadPath
  # 验证码类型 math 数组计算 char 字符验证
  captchaType: math

# 开发环境配置
server:
  # 服务器的HTTP端口,默认为8080
  port: 8888

spring:
  profiles:
    active: dev
  datasource:
    name: lab_datasource
    url: jdbc:mysql://127.0.0.1:3306/opengms_lab?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456


# MyBatis配置
mybatis:
  # 搜索指定包别名
  typeAliasesPackage: org.opengms.**.entity.po
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  # 加载全局的配置文件
  configLocation: classpath:mybatis/mybatis-config.xml

application-dev.yml配置文件:

1
2
lab:
  profile: E:/opengms-lab/uploadPath

mybatils配置文件

image-20220823214347217
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 全局参数 -->
    <settings>
        <!-- 使全局的映射器启用或禁用缓存 -->
        <setting name="cacheEnabled"             value="true"   />
        <!-- 允许JDBC 支持自动生成主键 -->
        <setting name="useGeneratedKeys"         value="true"   />
        <!-- 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新 -->
        <setting name="defaultExecutorType"      value="SIMPLE" />
		<!-- 指定 MyBatis 所用日志的具体实现 -->
        <setting name="logImpl"                  value="SLF4J"  />
        <!-- 使用驼峰命名法转换字段 -->
		 <setting name="mapUnderscoreToCamelCase" value="true"/>
	</settings>
	
</configuration>

1.default-executor-type详解:https://blog.csdn.net/fengshuiyue/article/details/89222654

2.use-generated-keys详解:https://blog.csdn.net/u012060033/article/details/79948353

就是

3.cache-enabled详解:https://blog.csdn.net/canot/article/details/51491732

缓存暂时没用到,后面整合redis后用redis做缓存。

4. 创建各层测试

image-20220823215724350

一、User实体类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Data
public class User {

    private static final long serialVersionUID = 1L;

    /** 用户ID */
    private Long userId;

    /** 用户名 */
    private String userName;

    /** 用户邮箱 */
    private String email;

    /** 手机号码 */
    private String phonenumber;

}

二、controller层

UserController

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    IUserService userService;

    @GetMapping(value = "/{userId}" )
    public User selectUserById(@PathVariable(value = "userId") Long userId){
        // Long userId = 101L;
        return userService.selectUserById(userId);
    }

}

三、service层

IUserService

1
2
3
public interface IUserService {
    User selectUserById(Long userId);
}

UserServiceImpl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User selectUserById(Long userId) {
        return userMapper.selectUserById(userId);
    }
}

四、dao层

UserMapper

1
2
3
4
5
6
@Mapper
public interface UserMapper {

    User selectUserById(Long userId);

}

UserMapper.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.opengms.admin.mapper.UserMapper">

    <resultMap type="User" id="UserResult">
        <result property="userId"       column="user_id"      />
        <result property="userName"     column="user_name"    />
        <result property="email"        column="email"        />
        <result property="phonenumber"  column="phonenumber"  />
    </resultMap>

    <select id="selectUserById" parameterType="Long" resultMap="UserResult">
        select user_id, user_name, email, phonenumber from sys_user as u
        where u.user_id = #{userId}
    </select>

</mapper>

5. 启动日志

banner.txt

https://www.bootschool.net/ascii

image-20221116223804408
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Application Version: ${lab.version}
Spring Boot Version: ${spring-boot.version}
////////////////////////////////////////////////////////////////////
//                          _ooOoo_                               //
//                         o8888888o                              //
//                         88" . "88                              //
//                         (| ^_^ |)                              //
//                         O\  =  /O                              //
//                      ____/`---'\____                           //
//                    .'  \\|     |//  `.                         //
//                   /  \\|||  :  |||//  \                        //
//                  /  _||||| -:- |||||-  \                       //
//                  |   | \\\  -  /// |   |                       //
//                  | \_|  ''\---/''  |   |                       //
//                  \  .-\__  `-`  ___/-. /                       //
//                ___`. .'  /--.--\  `. . ___                     //
//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
//      ========`-.____`-.___\_____/___.-`____.-'========         //
//                           `=---='                              //
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
//             佛祖保佑       永不宕机      永无BUG               //
////////////////////////////////////////////////////////////////////

   ____                    _____ __  __  _____   _           _
  / __ \                  / ____|  \/  |/ ____| | |         | |
 | |  | |_ __   ___ _ __ | |  __| \  / | (___   | |     __ _| |__
 | |  | | '_ \ / _ \ '_ \| | |_ | |\/| |\___ \  | |    / _` | '_ \
 | |__| | |_) |  __/ | | | |__| | |  | |____) | | |___| (_| | |_) |
  \____/| .__/ \___|_| |_|\_____|_|  |_|_____/  |______\__,_|_.__/
        | |
        |_|

6. 读取项目相关配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Component
@ConfigurationProperties(prefix = "lab")
public class LabConfig
{
    /** 项目名称 */
    private String name;

    /** 版本 */
    private String version;

    /** 版权年份 */
    private String copyrightYear;

    /** 上传路径 */
    private static String profile;

    /** 验证码类型 */
    private static String captchaType;

}

7. 直接拷贝lab-common整个模块并在admin中引入

image-20220823220442050

lab-admin的pom文件:

1
2
3
4
5
6
<!--项目common通用工具类 -->
<dependency>
    <groupId>org.opengms</groupId>
    <artifactId>lab-common</artifactId>
    <version>1.0.0</version>
</dependency>

线程配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
@EnableScheduling  //同步
@EnableAsync  //异步
public class ScheduleConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数 核心线程数线程数定义了最小可以同时运行的线程数量
        executor.setCorePoolSize(3);
        // 设置最大线程数 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数
        executor.setMaxPoolSize(5);
        // 设置队列容量 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中
        executor.setQueueCapacity(200);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("AsyncThread-");
        // 设置拒绝策略 当最大池被填满时,此策略为我们提供可伸缩队列
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }

}

跨域、登录验证、全局异常配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
public class WebConfig implements WebMvcConfigurer{

    //解决跨域问题
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**"); //允许远端访问的域名
            // .allowedOrigins("http://localhost:8099")
            //允许请求的方法("POST", "GET", "PUT", "OPTIONS", "DELETE")
            // .allowedMethods("*")
            //允许请求头
            // .allowedHeaders("*");
    }

    //登录请求问题
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /**
         * kn4j 在想文档相关的资源  需要放开
         */
        String[]  swaggerPatterns =  {
                "/swagger-resources/**",
                "/webjars/**",
                "/v2/**",
                "/swagger-ui.html/**",
                "/doc.html/**" };
        registry.addInterceptor(authenticationInterceptor())
                .excludePathPatterns(swaggerPatterns)
                .addPathPatterns("/**");    // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}

整合日志实现

整体思路:SLF4j+log4j2+Aop

1. 添加依赖

lab-admin

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- SpringBoot Web容器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!--以下是排除的组件-->
    <exclusions>
        <!--排除默认自带的log组件-->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>


<!--log4j2日志相关 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

<!-- SpringBoot 拦截器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 创建数据库表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- ----------------------------
-- 操作日志记录
-- ----------------------------
drop table if exists sys_oper_log;
create table sys_oper_log (
  oper_id           bigint(20)      not null auto_increment    comment '日志主键',
  title             varchar(50)     default ''                 comment '模块标题',
  business_type     int(2)          default 0                  comment '业务类型(0其它 1新增 2修改 3删除)',
  method            varchar(100)    default ''                 comment '方法名称',
  request_method    varchar(10)     default ''                 comment '请求方式',
  operator_type     int(1)          default 0                  comment '操作类别(0其它 1后台用户 2手机端用户)',
  oper_name         varchar(50)     default ''                 comment '操作人员',
  oper_url          varchar(255)    default ''                 comment '请求URL',
  oper_ip           varchar(128)    default ''                 comment '主机地址',
  oper_location     varchar(255)    default ''                 comment '操作地点',
  oper_param        varchar(2000)   default ''                 comment '请求参数',
  json_result       varchar(2000)   default ''                 comment '返回参数',
  status            int(1)          default 0                  comment '操作状态(0正常 1异常)',
  error_msg         varchar(2000)   default ''                 comment '错误消息',
  oper_time         datetime                                   comment '操作时间',
  primary key (oper_id)
) engine=innodb auto_increment=100 comment = '操作日志记录';

3. 自定义注解@Log及相关枚举

一、Log

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
    /**
     * 模块 
     */
    String title() default "";

    /**
     * 功能
     */
    BusinessType businessType() default BusinessType.OTHER;

    /**
     * 操作人类别
     */
    OperatorType operatorType() default OperatorType.MANAGE;

    /**
     * 是否保存请求的参数
     */
    boolean isSaveRequestData() default true;

    /**
     * 是否保存响应的参数
     */
    boolean isSaveResponseData() default true;
}

二、BusinessType

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public enum BusinessType
{
    /**
     * 其它
     */
    OTHER,

    /**
     * 新增
     */
    INSERT,

    /**
     * 修改
     */
    UPDATE,

    /**
     * 删除
     */
    DELETE,

    /**
     * 授权
     */
    GRANT,

    /**
     * 导出
     */
    EXPORT,

    /**
     * 导入
     */
    IMPORT,

    /**
     * 强退
     */
    FORCE,

    /**
     * 生成代码
     */
    GENCODE,
    
    /**
     * 清空数据
     */
    CLEAN,
}

三、OperatorType

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public enum OperatorType
{
    /**
     * 其它
     */
    OTHER,

    /**
     * 后台用户
     */
    MANAGE,

    /**
     * 手机端用户
     */
    MOBILE
}

4. AOP切面处理请求操作日志

核心的LogAspect切面处理类中的保存Log具体处理方法handleLog主要逻辑:

  1. 获得注解
  2. 从缓存获取当前的用户信息(暂缺,先模拟一个)
  3. 获取请求各项需要记录的基本信息
  4. 判断请求是否有异常
  5. 保存到数据库
  6. 打印到控制台

代码片段如下,详细代码见LogAspect

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * 处理完请求后执行
 *
 * @param joinPoint 切点
 */
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
{
    handleLog(joinPoint, controllerLog, null, jsonResult);
}

5. aop代理配置

1
2
3
4
5
6
@Configuration
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
public class ApplicationConfig {

}

参考链接:

注解@EnableAspectJAutoProxy(exposeProxy = true) exposeProxy 的用法

5. log4j2的配置文件

日志的配置主要用在生产环境中

参考链接:

SpringBoot 配置控制台彩色日志输出

配置文件位置:

image-20220823221600695

application.yml

1
2
3
4
5
#日志配置
logging:
  level:
    org.opengms: debug
    org.springframework: warn

application-prod.yml

1
2
3
4
5
6
7
8
lab:
  profile: /home/opengms-lab/uploadPath

#日志配置
logging:
  config: classpath:log4j2-config.xml
  file:
    path: /home/opengms-lab/logs

参考链接:

log4j2 配置文件读取 application.yml 的日志路径变量

log4j2-config.xml

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<?xml version="1.0" encoding="UTF-8"?>
<!--
    6个优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。
    如果设置优先级为WARN,那么OFF、FATAL、ERROR、WARN 4个级别的log能正常输出
    设置为OFF 表示不记录log4j2本身的日志,
 -->
<!-- status:用来指定log4j本身的打印日志级别,monitorInterval:指定log4j自动重新配置的监测间隔时间 -->
<Configuration status="fatal">

    <Properties>
        <!-- 日志输出格式 -->
        <!--<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:-} [%15.15t] %-40.40logger{39} : %m%n" />-->
        <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%5level} %style{%pid}{yellow} --- [%15.15t] %style{%-40.40logger{39}}{blue} : %msg%n%style{%throwable}{red}" />

        <!-- ${sys:LOG_PATH} 读取的就是 application.yml 中的 logging.file.path 的值 -->
        <property name="baseDir" value="${sys:LOG_PATH}" />

    </Properties>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
            <ThresholdFilter level="debug" onMatch="ACCEPT"
                             onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false" noConsoleNoAnsi="false"/>
            <!--<PatternLayout-->
            <!--        pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%5level} %style{%pid}{yellow} -&#45;&#45; [%15.15t] %style{%-40.40logger{39}}{blue} : %msg%n%style{%throwable}{red}"-->
            <!--        disableAnsi="false" noConsoleNoAnsi="false"/>-->
        </Console>

        <!--debug级别日志文件输出-->
        <!--<RollingFile name="debug_appender" fileName="${baseDir}/debug.log"-->
        <!--             filePattern="${baseDir}/debug_%i.log.%d{yyyy-MM-dd}">-->
        <!--    &lt;!&ndash; 过滤器 &ndash;&gt;-->
        <!--    <Filters>-->
        <!--        &lt;!&ndash; 限制日志级别在debug及以上在info以下 &ndash;&gt;-->
        <!--        <ThresholdFilter level="debug"/>-->
        <!--        <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>-->
        <!--    </Filters>-->
        <!--    &lt;!&ndash; 日志格式 &ndash;&gt;-->
        <!--    <PatternLayout pattern="${LOG_PATTERN}"/>-->
        <!--    &lt;!&ndash; 策略 &ndash;&gt;-->
        <!--    <Policies>-->
        <!--        &lt;!&ndash; 每隔一天转存 &ndash;&gt;-->
        <!--        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>-->
        <!--        &lt;!&ndash; 文件大小 &ndash;&gt;-->
        <!--        <SizeBasedTriggeringPolicy size="100 MB"/>-->
        <!--    </Policies>-->
        <!--    &lt;!&ndash; DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖&ndash;&gt;-->
        <!--    <DefaultRolloverStrategy max="15"/>-->
        <!--</RollingFile>-->

        <!-- info级别日志文件输出 -->
        <RollingFile name="info_appender" fileName="${baseDir}/info.log"
                     filePattern="${baseDir}/info_%i.log.%d{yyyy-MM-dd}">
            <!-- 过滤器 -->
            <Filters>
                <!-- 限制日志级别在info及以上在error以下 -->
                <ThresholdFilter level="info"/>
                <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <!-- 日志格式 -->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!-- 策略 -->
            <Policies>
                <!-- 每隔一天转存 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <!-- 文件大小 -->
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- error级别日志文件输出 -->
        <RollingFile name="error_appender" fileName="${baseDir}/error.log"
                     filePattern="${baseDir}/error_%i.log.%d{yyyy-MM-dd}">
            <!-- 过滤器 -->
            <Filters>
                <!-- 限制日志级别在error及以上 -->
                <ThresholdFilter level="error"/>
            </Filters>
            <!-- 日志格式 -->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!-- 每隔一天转存 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <!-- 文件大小 -->
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>
    </Appenders>
    <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
    <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
    <!--监控系统信息-->
    <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
    <Loggers>
        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <logger name="org.mybatis" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </logger>

        <Logger name="org.springframework" level="WARN" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>

        <!--pagehelper日志输出限制-->
        <!--<Logger name="com.github.pagehelper" level="info" additivity="false">-->
        <!--    <AppenderRef ref="Console"/>-->
        <!--</Logger>-->

        <!-- 设置com.zaxxer.hikari包下的日志只打印WARN及以上级别的日志 -->
        <logger name="com.zaxxer.hikari" level="WARN" additivity="false">
            <appender-ref ref="Console"/>
        </logger>

        <!-- 设置org.hibernate.validator包下的日志只打印WARN及以上级别的日志 -->
        <logger name="org.hibernate.validator" level="WARN" additivity="false">
            <appender-ref ref="Console"/>
        </logger>

        <!-- 设置org.apache包下的日志只打印WARN及以上级别的日志 -->
        <!--    <logger name="org.apache" level="WARN" additivity="false">-->
        <!--      <appender-ref ref="Console"/>-->
        <!--    </logger>-->

        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="debug_appender"/>
            <AppenderRef ref="info_appender"/>
            <AppenderRef ref="error_appender"/>
        </Root>

    </Loggers>
</Configuration>

参考链接:

log4j2.xml 的标签 loggers 中 root 的属性 level 指的是什么

整合Redis缓存

1. 添加依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!--redis 缓存操作 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--pool 对象池 -->
<!-- spring2.0集成redis所需common-pool2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2. Redis数据库连接信息配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
spring:
  redis:
    # Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
    database: 0
    host: localhost
    port: 6379
    # 连接密码(默认为空)
    password:
    # 连接超时时间(毫秒)
    timeout: 50ms
    lettuce:
      pool:
        # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-wait: -1
        # 连接池中的最大空闲连接 默认 8
        max-idle: 8
        # 连接池中的最小空闲连接 默认 0
        min-idle: 0

3. 在RedisConfig中自定义RedisTemplate解决序列化问题

这里使用jackon。fastjson出事太多了,实际业务开发中尽量少用为妙

fastjson到底做错了什么?为什么会被频繁爆出漏洞?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> template(RedisConnectionFactory factory) {
        // 创建RedisTemplate<String, Object>对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);
        // redis key 序列化方式使用stringSerial
        template.setKeySerializer(new StringRedisSerializer());
        // redis value 序列化方式自定义
        // template.setValueSerializer(new GenericFastJsonRedisSerializer());
        template.setValueSerializer(valueSerializer());
        // redis hash key 序列化方式使用stringSerial
        template.setHashKeySerializer(new StringRedisSerializer());
        // redis hash value 序列化方式自定义
        // template.setHashValueSerializer(new GenericFastJsonRedisSerializer());
        template.setHashValueSerializer(valueSerializer());
        return template;
    }

    private RedisSerializer<Object> valueSerializer() {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
            new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        // 此项必须配置,否则如果序列化的对象里边还有对象,会报如下错误:
        //     java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
        objectMapper.activateDefaultTyping(
            objectMapper.getPolymorphicTypeValidator(),
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY);
        // 旧版写法:
        // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        return jackson2JsonRedisSerializer;
    }

}

使用jackson在进行序列化反序列化时 redis报错: Could not read JSON: Unrecognized field “enabled“

参考链接:https://blog.csdn.net/qq_41706331/article/details/119242055

原因:序列化时多了几个字段,这是因为为了整合springsecurity在User类中实现了UserDetails接口,重写了这些方法

解决:使用@JsonIgnoreProperties注解,可以在User对象在序列化时忽略这些字段。在User类前添加:

1
@JsonIgnoreProperties({"enabled","accountNonExpired", "accountNonLocked", "credentialsNonExpired", "authorities"})

4. 封装RedisUtils – Redis常用命令工具类

5.Redis测试使用

在lab-admin中扫描common模块的bean(因为springboot的@SpringBootApplication注解默认扫描范围为自己的启动类所在的包及其子包)

参考链接:

SpringBoot多模块项目中无法注入其他模块中的spring bean

1
2
3
4
5
6
7
8
@Configuration
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
// 扫描common模块的bean
@ComponentScan(value = {"org.opengms.common"})
public class ApplicationConfig {

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Autowired
private RedisUtils redisUtils;

@GetMapping("/helloRedis")
public String helloRedis(){
    // 测试写入一个string类型键值,过期时间20S
    redisUtils.set("key1", "Gangbb", 20);
    System.out.println("获取key1值"+ redisUtils.set("key1", "测试redis", 20));
    return "测试redis";
}

@Cacheable("cache1")
@GetMapping("/helloRedis2")
public String helloRedis2(){
    return "xxredis";
}

@Cacheable("cache2")
@GetMapping("/helloRedis3")
public JSONObject helloRedis3(){
    return getTestDetail();
}


public JSONObject getTestDetail() {
    JSONObject retJson = new JSONObject();
    String retCode = "1";
    String retMsg = "操作失败!";
    JSONObject bizDataJson = new JSONObject();
    try {
        User user = new User();
        user.setUserId(22L);
        user.setUserName("bin");
        user.setEmail("78280@qq.com");

        String key = "user::"+user.getUserId();
        //向Redis中缓存数据,-1为设置永久时效
        // redisUtils.set(key, user,-1);

        bizDataJson = JSONObject.parseObject(JSON.toJSONString(user));
        retCode = "0";
        retMsg = "操作成功!";
    } catch (Exception e) {
        log.error(String.valueOf(e));
    }
    retJson.put("retCode", retCode);
    retJson.put("retMsg", retMsg);
    retJson.put("bizData", bizDataJson);
    return retJson;
}

参考链接:

spring boot 缓存@EnableCaching

使用@Cacheable时存到redis中的数据是Hex格式的

解决:@EnableCaching与@Cacheable的使用方法,结合redis进行说明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Configuration
@EnableCaching
public class RedisConfig {

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory){
  //Duration.ofSeconds(120)设置缓存默认过期时间120秒
  RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(120));
  //解决使用@Cacheable,redis数据库value乱码
  config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
  RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();
  return cacheManager;
}

}

6. Mybatis使用Redis做二级缓存

还未实现

参考链接:

从零搭建若依(Ruoyi-Vue)管理系统(6)–整合Redis缓存

国际化消息处理

1. 设置项目文件编码

需要将properties的文件编码设置成utf-8,否则会出现乱码????

image-20220824213206910

2. 新建一个Resourse Bundle

  1. lab-adminresources目录下新建i8n文件夹储存所有国际化文本属性。
  2. 新建messages Resourse Bundle
image-20220824212732662 image-20220824212744713

参考链接:

idea 2021. 2.3 中使用springboot 国际化 Resource Bundle不显示问题

2. 国际化工具类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class MessageUtils
{
    /**
     * 根据消息键和参数 获取消息 委托给spring messageSource
     *
     * @param code 消息键
     * @param args 参数
     * @return 获取国际化翻译值
     */
    public static String message(String code, Object... args)
    {
        MessageSource messageSource = SpringUtils.getBean(MessageSource.class);

        try {
            return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
        } catch (Exception e) {
            return code;
        }
    }
}

4. 测试使用

前端请求接口时在请求头Accept-Language附带语言信息(中文:zh-cn;英文:en-us;不设置默认中文)注意中间是一个横杠不是下划线,与properties文件有区别

image-20220824212936716
1
2
3
4
@GetMapping("/message")
public String test() {
    return MessageUtils.message("user.login.success");
}

统一对象返回和异常处理

1. 定义统一返回对象

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
public class ApiResponse extends HashMap<String, Object> {

    private static final long serialVersionUID = 1L;

    /** 状态码 */
    public static final String CODE_TAG = "code";

    /** 返回内容 */
    public static final String MSG_TAG = "msg";

    /** 数据对象 */
    public static final String DATA_TAG = "data";

    /**
     * 初始化一个新创建的 ApiResponse 对象,使其表示一个空消息。
     */
    public ApiResponse()
    {
    }

    /**
     * 初始化一个新创建的 ApiResponse 对象
     *
     * @param code 状态码
     * @param msg 返回内容
     */
    public ApiResponse(int code, String msg)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 ApiResponse 对象
     *
     * @param code 状态码
     * @param msg 返回内容
     * @param data 数据对象
     */
    public ApiResponse(int code, String msg, Object data)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data))
        {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static ApiResponse success()
    {
        return ApiResponse.success("操作成功");
    }

    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static ApiResponse success(Object data)
    {
        return ApiResponse.success("操作成功", data);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static ApiResponse success(String msg)
    {
        return ApiResponse.success(msg, null);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static ApiResponse success(String msg, Object data)
    {
        return new ApiResponse(HttpStatus.SUCCESS, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @return
     */
    public static ApiResponse error()
    {
        return ApiResponse.error("操作失败");
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static ApiResponse error(String msg)
    {
        return ApiResponse.error(msg, null);
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static ApiResponse error(String msg, Object data)
    {
        return new ApiResponse(HttpStatus.ERROR, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @param code 状态码
     * @param msg 返回内容
     * @return 警告消息
     */
    public static ApiResponse error(int code, String msg)
    {
        return new ApiResponse(code, msg, null);
    }

    /**
     * 方便链式调用
     *
     * @param key 键
     * @param value 值
     * @return 数据对象
     */
    @Override
    public ApiResponse put(String key, Object value)
    {
        super.put(key, value);
        return this;
    }
    
    
}

2. 自定义异常类及错误码规范

定义一个自定义异常类,抛出时传入不同的状态码 ,通过不同状态码以及messages.properties中对应的错误信息区分不同异常。只要对状态码有一个统一定义规范就能有效区分不同异常进行管理

自定义异常类ServiceException

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public final class ServiceException extends RuntimeException
{
    private static final long serialVersionUID = 1L;

    /**
     * 错误码
     */
    private Integer code;

    /**
     * 错误提示
     */
    private String message;

}

message.properties

1
2
3
4
5
6
7
user.email.not.valid=邮箱格式错误
user.mobile.phone.number.not.valid=手机号格式错误
user.login.success=登录成功
user.register.success=注册成功
user.notfound=请重新登录
user.forcelogout=管理员强制退出,请重新登录
user.unknown.error=未知错误,请重新登录

image-20220824214635329

3. 全局异常处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 业务异常
     */
    @ExceptionHandler(ServiceException.class)
    public ApiResponse handleServiceException(ServiceException e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址[ {} ],发生业务异常.", requestURI, e);
        Integer code = e.getCode();
        return StringUtils.isNotNull(code) ? ApiResponse.error(code, e.getMessage()) : ApiResponse.error(e.getMessage());
    }

    /**
     * 拦截未知的运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public ApiResponse handleRuntimeException(RuntimeException e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址[ {} ],发生未知异常.", requestURI, e);
        return ApiResponse.error(e.getMessage());
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public ApiResponse handleException(Exception e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址[ {} ],发生系统异常.", requestURI, e);
        return ApiResponse.error(e.getMessage());
    }
    
}

4. 异常测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@GetMapping("/exception")
public ApiResponse exception() {

    int a = 1;
    int b = 0;

    if (b == 0){
        throw new ServiceException(MessageUtils.message("service.division.byZero"));
    }

    return ApiResponse.success(a / 0.5);
}

Mybatias分页支持

1. 引入依赖

1
2
3
4
5
6
7
<dependencies>
	<!-- pagehelper 分页插件 -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

2. 配置yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# PageHelper分页插件
pagehelper:
  # 指定分页插件使用哪种数据库方言
  helperDialect: mysql
  # 当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。
  # reasonable: true
  # 支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,
  # 自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。
  supportMethodsArguments: true
  # 增加了该参数来配置参数映射,用于从对象中根据属性名取值
  params: count=countSql

所有配置项:

  1. helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。 你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值: oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby **特别注意:**使用 SqlServer2012 数据库时,需要手动指定为 sqlserver2012,否则会使用 SqlServer2005 的方式进行分页。 你也可以实现 AbstractHelperDialect,然后配置该属性为实现类的全限定名称即可使用自定义的实现方法。
  2. offsetAsPageNum:默认值为 false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时,会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页。
  3. rowBoundsWithCount:默认值为false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为true时,使用 RowBounds 分页会进行 count 查询。
  4. pageSizeZero:默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是 Page 类型)。
  5. reasonable:分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
  6. params:为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
  7. supportMethodsArguments:支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。 使用方法可以参考测试代码中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTestArgumentsObjTest
  8. autoRuntimeDialect:默认值为 false。设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页 (不支持自动选择sqlserver2012,只能使用sqlserver),用法和注意事项参考下面的场景五
  9. closeConn:默认值为 true。当使用运行时动态数据源或没有设置 helperDialect 属性自动获取数据库类型时,会自动获取一个数据库连接, 通过该属性来设置是否关闭获取的这个连接,默认true关闭,设置为 false 后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定。
  10. aggregateFunctions(5.1.5+):默认为所有常见数据库的聚合函数,允许手动添加聚合函数(影响行数),所有以聚合函数开头的函数,在进行 count 转换时,会套一层。其他函数和列会被替换为 count(0),其中count列可以自己配置。

更多信息可以可以看官方文档:

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

3. 封装分页相关工具

3.1 BaseController

这是一个web层通用处理类。核心方法startPage()请求分页数据方法,使继承BaseController的Controller可以快速使用分页。

参考链接:

springMVC之@InitBinder的用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@Slf4j
public class BaseController
{
    // protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 将前台传递过来的日期格式的字符串,自动转化为Date类型
     */
    @InitBinder
    public void initBinder(WebDataBinder binder)
    {
        // Date 类型转换
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
        {
            @Override
            public void setAsText(String text)
            {
                setValue(DateUtils.parseDate(text));
            }
        });
    }

    /**
     * 设置请求分页数据
     */
    protected void startPage()
    {
        PageUtils.startPage();
    }

    /**
     * 设置请求排序数据
     */
    protected void startOrderBy()
    {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        if (StringUtils.isNotEmpty(pageDomain.getOrderBy()))
        {
            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
            PageHelper.orderBy(orderBy);
        }
    }

    /**
     * 清理分页的线程变量
     */
    protected void clearPage()
    {
        PageUtils.clearPage();
    }

    /**
     * 响应请求分页数据
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected TableDataInfo getDataTable(List<?> list)
    {
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setMsg("查询成功");
        rspData.setRows(list);
        rspData.setTotal(new PageInfo(list).getTotal());
        return rspData;
    }

}

3.2 TableDataInfo

自定义分页结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class TableDataInfo implements Serializable
{
    private static final long serialVersionUID = 1L;

    /** 总记录数 */
    private long total;

    /** 列表数据 */
    private List<?> rows;

    /** 消息状态码 */
    private int code;

    /** 消息内容 */
    private String msg;

}

3.3 数据传输类PageDTO

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class PageDTO
{
    /** 当前记录起始索引 */
    private Integer pageNum;

    /** 每页显示记录数 */
    private Integer pageSize;

    /** 排序列 */
    private String orderByColumn;

    /** 排序的方向desc或者asc */
    private String isAsc = "asc";

    /** 分页参数合理化 */
    private Boolean reasonable = true
}

4. 测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    /**
     * 查询列表,分页。 返回 TableDataInfo
     * @return
     */
    @GetMapping("/page")
    public TableDataInfo testPageHelper(){

        startPage();
        List<SysOperLog> sysOperationLogs = sysOperLogService.selectAll();

        return getDataTable(sysOperationLogs);
    }	
1
2
3
<select id="selectAll" resultType="SysOperLog">
    select * from sys_oper_log
</select>
image-20220825143144983

Swagger集成

1.pom依赖

父级pom

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<properties>
    <swagger.version>3.0.0</swagger.version>
</properties>

<!-- Swagger3依赖 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>${swagger.version}</version>
    <exclusions>
        <exclusion>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
        </exclusion>
    </exclusions>
</dependency>

admin pom

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!-- swagger3-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
</dependency>
<!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-models</artifactId>
    <version>1.6.2</version>
</dependency>

2.swagger config

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
@Configuration
public class SwaggerConfig implements WebMvcConfigurer
{
    /** 系统基础配置 */
    @Autowired
    private LabConfig labConfig;

    /** 是否开启swagger */
    @Value("${swagger.enabled}")
    private boolean enabled;


    /**
     * 创建API
     */
    @Bean
    public Docket createRestApi()
    {
        return new Docket(DocumentationType.OAS_30)
                // 是否启用Swagger
                .enable(enabled)
                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
                .apiInfo(apiInfo())
                // 设置哪些接口暴露给Swagger展示
                .select()
                // 扫描所有有注解的api,用这种方式更灵活
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                // 扫描指定包中的swagger注解
                // .apis(RequestHandlerSelectors.basePackage("com.opengms.project.tool.swagger"))
                // 扫描所有 .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                /* 设置安全模式,swagger可以设置访问token */
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts());
    }

    /**
     * 安全模式,这里指定token通过Authorization头请求头传递
     */
    private List<SecurityScheme> securitySchemes()
    {
        List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
        apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
        return apiKeyList;
    }

    /**
     * 安全上下文
     */
    private List<SecurityContext> securityContexts()
    {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
                        .build());
        return securityContexts;
    }

    /**
     * 默认的安全上引用
     */
    private List<SecurityReference> defaultAuth()
    {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }

    /**
     * 添加摘要信息
     */
    private ApiInfo apiInfo()
    {
        // 用ApiInfoBuilder进行定制
        return new ApiInfoBuilder()
                // 设置标题
                .title("OPENGMS LAB接口文档")
                // 描述
                // .description("接口文档 description")
                // 作者信息
                .contact(new Contact(labConfig.getName(), null, null))
                // 版本
                .version("版本号:" + labConfig.getVersion())
                .build();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        /** swagger配置 */
        registry.addResourceHandler("/swagger-ui/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");

        // registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        //
        // // 解决swagger无法访问
        // registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        //
        // // 解决swagger的js文件无法访问
        // registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");

    }
}

3.使用

swagger2的3.0版本的访问地址/swagger-ui/index.html 2.x之前访问地址/swagger-ui.html

访问http://localhost:8888/swagger-ui/index.html

使用Swagger Tools插件自动生成接口文档注解:Alt + Ins

image-20220902153650357

4.集成Knife4j

添加pom

1
2
3
4
5
6
<!-- knife4j -->
<dependency>
	<groupId>com.github.xiaoymin</groupId>
	<artifactId>knife4j-spring-boot-starter</artifactId>
	<version>3.0.3</version>
</dependency>

访问地址

http://localhost:8888/doc.html

参数配置

1.创建数据库表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- ----------------------------
-- 参数配置表
-- ----------------------------
drop table if exists sys_config;
create table sys_config (
  config_id         int(5)          not null auto_increment    comment '参数主键',
  config_name       varchar(100)    default ''                 comment '参数名称',
  config_key        varchar(100)    default ''                 comment '参数键名',
  config_value      varchar(500)    default ''                 comment '参数键值',
  config_type       char(1)         default 'N'                comment '系统内置(Y是 N否)',
  create_by         varchar(64)     default ''                 comment '创建者',
  create_time       datetime                                   comment '创建时间',
  update_by         varchar(64)     default ''                 comment '更新者',
  update_time       datetime                                   comment '更新时间',
  remark            varchar(500)    default null               comment '备注',
  primary key (config_id)
) engine=innodb auto_increment=100 comment = '参数配置表';

insert into sys_config values(1, '主框架页-默认皮肤样式名称',     'sys.index.skinName',            'skin-blue',     'Y', 'admin', sysdate(), '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow' );
insert into sys_config values(2, '用户管理-账号初始密码',         'sys.user.initPassword',         '123456',        'Y', 'admin', sysdate(), '', null, '初始化密码 123456' );
insert into sys_config values(3, '主框架页-侧边栏主题',           'sys.index.sideTheme',           'theme-dark',    'Y', 'admin', sysdate(), '', null, '深色主题theme-dark,浅色主题theme-light' );
insert into sys_config values(4, '账号自助-验证码开关',           'sys.account.captchaEnabled',    'true',          'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(true开启,false关闭)');
insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser',      'false',         'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)');

2.初始化参数

SysConfigServiceImpl

1
2
3
4
5
6
7
8
/**
 * 项目启动时,初始化参数到缓存
 */
@PostConstruct
public void init()
{
    loadingConfigCache();
}

登录和授权

登录实现

1.引入 Spring Security 模块

1
2
3
4
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

引入依赖后我们在尝试去访问之前的接口就会自动跳转到一个SpringSecurity的默认登陆页面,默认用户名是user,密码会输出在控制台。 必须登陆之后才能对接口进行访问。

2.编写 Spring Security 配置类

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;
    
    /**
     * 认证失败处理类
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;
    
    /**
     * 跨域过滤器
     */
    @Autowired
    private CorsFilter corsFilter;

    /**
     * 允许匿名访问的地址
     */
    @Autowired
    private PermitAllUrlProperties permitAllUrl;

    /**
     * 解决 无法直接注入 AuthenticationManager
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }

    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        // 注解标记允许匿名访问的url
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
        permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());

        httpSecurity
                // CSRF禁用,因为不使用session
                // 关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                .antMatchers("/login", "/register", "/captchaImage").anonymous()
                // 静态资源,可匿名访问
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        // 添加Logout filter
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);

        // 开启自动配置的登录功能
        // http.formLogin()
        //     .loginPage("/toLogin")  //自定义登录页
        //     .loginProcessingUrl("/loginRequest")  // 登陆表单提交的请求
        //     .passwordParameter("password");  // 表单中password需与该处对应。默认是password
        // http.formLogin();

        // 开启自动配置的注销的功能
        // http.logout().logoutSuccessUrl("/"); // 注销成功来到首页

        // 记住我
        // http.rememberMe();
        // http.rememberMe().
        //     rememberMeParameter("remember"); // 定制记住我的参数
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }

    /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        // 数据库验证
        // Spring Security 提供了BCryptPasswordEncoder类,
        // 实现Spring的PasswordEncoder接口使用BCrypt强哈希方法来加密密码
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

3.配置类相关模块

UserDetailsService 自定义用户认证逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Autowired
    private ISysUserService userService;
    
    @Autowired
    private SysPasswordService passwordService;

    @Autowired
    private SysPermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        SysUser user = userService.selectUserByUserName(username);
        if (StringUtils.isNull(user))
        {
            log.info("登录用户:{} 不存在.", username);
            throw new ServiceException("登录用户:" + username + " 不存在");
        }
        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
        {
            log.info("登录用户:{} 已被删除.", username);
            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
        }
        else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
        {
            log.info("登录用户:{} 已被停用.", username);
            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
        }

        passwordService.validate(user);

        return createLoginUser(user);
    }

    public UserDetails createLoginUser(SysUser user)
    {
        return new LoginUser(user.getUserId(), user);
    }
}

AuthenticationEntryPointImpl 认证失败处理类 返回未授权

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{
    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
            throws IOException
    {
        int code = HttpStatus.UNAUTHORIZED;
        String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
        ServletUtils.renderString(response, JSON.toJSONString(ApiResponse.error(code, msg)));
    }
}

LogoutSuccessHandlerImpl 自定义退出处理类 返回成功

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
    @Autowired
    private TokenService tokenService;


    @Autowired
    private IAsyncService asyncService;


    /**
     * 退出处理
     * 
     * @return
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser))
        {
            String userName = loginUser.getUsername();
            // 删除用户缓存记录
            tokenService.delLoginUser(loginUser.getToken());
            // 记录用户退出日志
            asyncService.recordLogininfor(userName, Constants.LOGOUT, "退出成功");
        }
        ServletUtils.renderString(response, JSON.toJSONString(ApiResponse.error(HttpStatus.SUCCESS, "退出成功")));
    }
}

JwtAuthenticationTokenFiltertoken token过滤器 验证token有效性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
    @Autowired
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
        {
            tokenService.verifyToken(loginUser);
            // 获取权限信息封装到Authentication中
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            // 存入SecurityContextHolder
            SecurityContextHolder.getContext().setAuthentication(authenticationToken)
        }
        chain.doFilter(request, response);
    }
}

ResourcesConfig 跨域过滤器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
public class ResourcesConfig implements WebMvcConfigurer
{
    /**
     * 跨域配置
     */
    @Bean
    public CorsFilter corsFilter()
    {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        // 设置访问源地址
        config.addAllowedOriginPattern("*");
        // 设置访问源请求头
        config.addAllowedHeader("*");
        // 设置访问源请求方法
        config.addAllowedMethod("*");
        // 有效期 1800秒
        config.setMaxAge(1800L);
        // 添加映射路径,拦截一切请求
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        // 返回新的CorsFilter
        return new CorsFilter(source);
    }
}

PermitAllUrlProperties 设置Anonymous注解允许匿名访问的url

参考链接:

ApplicationContextAware接口的作用

这个接口其实就是获取Spring容器的Bean,在我们写一些框架代码时,或者是看一些框架源码时经常会看到这个接口ApplicationContextAware 的使用,Spring容器会检测容器中的所有Bean,如果发现某个Bean实现了ApplicationContextAware接口,Spring容器会在创建该Bean之后,自动调用该Bean的setApplicationContextAware()方法,调用该方法时,会将容器本身作为参数传给该方法——该方法中的实现部分将Spring传入的参数(容器本身)赋给该类对象的applicationContext实例变量,因此接下来可以通过该applicationContext实例变量来访问容器本身

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Configuration 
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{
    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");

    private ApplicationContext applicationContext;

    private List<String> urls = new ArrayList<>();

    public String ASTERISK = "*";

    @Override
    public void afterPropertiesSet()
    {
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();

        map.keySet().forEach(info -> {
            HandlerMethod handlerMethod = map.get(info);

            // 获取方法上边的注解 替代path variable 为 *
            Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
            Optional.ofNullable(method).ifPresent(anonymous -> info.getPatternsCondition().getPatterns()
                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));

            // 获取类上边的注解, 替代path variable 为 *
            Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
            Optional.ofNullable(controller).ifPresent(anonymous -> info.getPatternsCondition().getPatterns()
                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException
    {
        this.applicationContext = context;
    }

    public List<String> getUrls()
    {
        return urls;
    }

    public void setUrls(List<String> urls)
    {
        this.urls = urls;
    }
}

4.登录模块

authenticationManager.authenticate 会调用 UserDetailsServiceImpl.loadUserByUsername 进行身份认证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public String login(String username, String password, String code, String uuid) {

        // boolean captchaEnabled = configService.selectCaptchaEnabled();
        // 验证码开关
        if (false)
        {
            // validateCaptcha(username, code, uuid);
        }
        // 用户验证
        Authentication authentication = null;
        try
        {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                String message = MessageUtils.message("user.password.not.match");
                asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, message);
                throw new ServiceException(message);
            }
            else
            {
                asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage());
                throw new ServiceException(e.getMessage());
            }
        }
        asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);

    }

5.登出模块

SecurityConfig中添加登出过滤链

1
2
// 添加Logout filter
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);

LogoutSuccessHandlerImpl自定义退出处理类 返回成功

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
    @Autowired
    private TokenService tokenService;


    @Autowired
    private IAsyncService asyncService;


    /**
     * 退出处理
     * 
     * @return
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser))
        {
            String userName = loginUser.getUsername();
            // 删除用户缓存记录
            tokenService.delLoginUser(loginUser.getToken());
            // 记录用户退出日志
            asyncService.recordLogininfor(userName, Constants.LOGOUT, "退出成功");
        }
        ServletUtils.renderString(response, JSON.toJSONString(ApiResponse.error(HttpStatus.SUCCESS, "退出成功")));
    }
}

6.注册模块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public String register(RegisterDTO registerBody) {
    String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();

    boolean captchaEnabled = configService.selectCaptchaEnabled();
    // 验证码开关
    if (captchaEnabled)
    {
        // validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
    }

    if (StringUtils.isEmpty(username))
    {
        msg = "用户名不能为空";
    }
    else if (StringUtils.isEmpty(password))
    {
        msg = "用户密码不能为空";
    }
    else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
        || username.length() > UserConstants.USERNAME_MAX_LENGTH)
    {
        msg = "账户长度必须在2到20个字符之间";
    }
    else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
        || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
    {
        msg = "密码长度必须在5到20个字符之间";
    }
    else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username)))
    {
        msg = "保存用户'" + username + "'失败,注册账号已存在";
    }
    else
    {
        SysUser sysUser = new SysUser();
        sysUser.setUserName(username);
        sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword()));
        boolean regFlag = userService.registerUser(sysUser);
        if (!regFlag)
        {
            msg = "注册失败,请联系系统管理人员";
        }
        else
        {
            asyncService.recordLogininfor(username, Constants.REGISTER,
                MessageUtils.message("user.register.success"));
        }
    }
    return msg;


}

授权实现

1.创建数据库表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
-- ----------------------------
-- 角色信息表
-- ----------------------------
drop table if exists sys_role;
create table sys_role (
  role_id              bigint(20)      not null auto_increment    comment '角色ID',
  role_name            varchar(30)     not null                   comment '角色名称',
  role_key             varchar(100)    not null                   comment '角色权限字符串',
  role_sort            int(4)          not null                   comment '显示顺序',
  data_scope           char(1)         default '1'                comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)',
  menu_check_strictly  tinyint(1)      default 1                  comment '菜单树选择项是否关联显示',
  dept_check_strictly  tinyint(1)      default 1                  comment '部门树选择项是否关联显示',
  status               char(1)         not null                   comment '角色状态(0正常 1停用)',
  del_flag             char(1)         default '0'                comment '删除标志(0代表存在 2代表删除)',
  create_by            varchar(64)     default ''                 comment '创建者',
  create_time          datetime                                   comment '创建时间',
  update_by            varchar(64)     default ''                 comment '更新者',
  update_time          datetime                                   comment '更新时间',
  remark               varchar(500)    default null               comment '备注',
  primary key (role_id)
) engine=innodb auto_increment=100 comment = '角色信息表';

-- ----------------------------
-- 菜单权限表
-- ----------------------------
drop table if exists sys_menu;
create table sys_menu (
  menu_id           bigint(20)      not null auto_increment    comment '菜单ID',
  menu_name         varchar(50)     not null                   comment '菜单名称',
  parent_id         bigint(20)      default 0                  comment '父菜单ID',
  order_num         int(4)          default 0                  comment '显示顺序',
  path              varchar(200)    default ''                 comment '路由地址',
  component         varchar(255)    default null               comment '组件路径',
  query             varchar(255)    default null               comment '路由参数',
  is_frame          int(1)          default 1                  comment '是否为外链(0是 1否)',
  is_cache          int(1)          default 0                  comment '是否缓存(0缓存 1不缓存)',
  menu_type         char(1)         default ''                 comment '菜单类型(M目录 C菜单 F按钮)',
  visible           char(1)         default 0                  comment '菜单状态(0显示 1隐藏)',
  status            char(1)         default 0                  comment '菜单状态(0正常 1停用)',
  perms             varchar(100)    default null               comment '权限标识',
  icon              varchar(100)    default '#'                comment '菜单图标',
  create_by         varchar(64)     default ''                 comment '创建者',
  create_time       datetime                                   comment '创建时间',
  update_by         varchar(64)     default ''                 comment '更新者',
  update_time       datetime                                   comment '更新时间',
  remark            varchar(500)    default ''                 comment '备注',
  primary key (menu_id)
) engine=innodb auto_increment=2000 comment = '菜单权限表';

-- ----------------------------
-- 角色和菜单关联表  角色1-N菜单
-- ----------------------------
drop table if exists sys_role_menu;
create table sys_role_menu (
  role_id   bigint(20) not null comment '角色ID',
  menu_id   bigint(20) not null comment '菜单ID',
  primary key(role_id, menu_id)
) engine=innodb comment = '角色和菜单关联表';


-- ----------------------------
-- 用户和角色关联表  用户N-1角色
-- ----------------------------
drop table if exists sys_user_role;
create table sys_user_role (
  user_id   bigint(20) not null comment '用户ID',
  role_id   bigint(20) not null comment '角色ID',
  primary key(user_id, role_id)
) engine=innodb comment = '用户和角色关联表';

2.权限过滤器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
    @Autowired
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
        {
            tokenService.verifyToken(loginUser);
            // 获取权限信息封装到Authentication中
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            // 存入SecurityContextHolder
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}

3.登录时权限赋值

UserDetailsServiceImplloadUserByUsername 中为user赋上权限

image-20220901230449453

4.权限查询Mapper

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<select id="selectMenuListByUserId" parameterType="SysMenu" resultMap="SysMenuResult">
   select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
   from sys_menu m
   left join sys_role_menu rm on m.menu_id = rm.menu_id
   left join sys_user_role ur on rm.role_id = ur.role_id
   left join sys_role ro on ur.role_id = ro.role_id
   where ur.user_id = #{params.userId}
   <if test="menuName != null and menuName != ''">
           AND m.menu_name like concat('%', #{menuName}, '%')
   </if>
   <if test="visible != null and visible != ''">
           AND m.visible = #{visible}
   </if>
   <if test="status != null and status != ''">
           AND m.status = #{status}
   </if>
   order by m.parent_id, m.order_num
</select>

5.自定义权限方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Service("ss")
public class PermissionService
{
    /** 所有权限标识 */
    private static final String ALL_PERMISSION = "*:*:*";

    /** 管理员角色权限标识 */
    private static final String SUPER_ADMIN = "admin";

    private static final String ROLE_DELIMETER = ",";

    private static final String PERMISSION_DELIMETER = ",";

    /**
     * 验证用户是否具备某权限
     * 
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    public boolean hasPermi(String permission)
    {
        if (StringUtils.isEmpty(permission))
        {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
        {
            return false;
        }
        return hasPermissions(loginUser.getPermissions(), permission);
    }
}

6.接口实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * 获取菜单列表
 */
@PreAuthorize("@ss.hasPermi('system:menu:list')")
@GetMapping("/list")
public AjaxResult list(SysMenu menu)
{
    List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
    return AjaxResult.success(menus);
}

参数校验

参考链接:

https://blog.csdn.net/qq_37132495/article/details/118804544

参数检验之——–BindingResult总结

参数注解说明

注解名称 功能
@Xss 检查该字段是否存在跨站脚本工具
@Null 检查该字段为空
@NotNull 不能为null
@NotBlank 不能为空,常用于检查空字符串
@NotEmpty 不能为空,多用于检测list是否size是0
@Max 该字段的值只能小于或等于该值
@Min 该字段的值只能大于或等于该值
@Past 检查该字段的日期是在过去
@Future 检查该字段的日期是否是属于将来的日期
@Email 检查是否是一个有效的email地址
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
@Size(min=, max=) 检查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等
@Length(min=,max=) 检查所属的字段的长度是否在min和max之间,只能用于字符串
@AssertTrue 用于boolean字段,该字段只能为true
@AssertFalse 该字段的值只能为false

1.pom.xml

1
2
3
4
5
<!-- 自定义验证注解 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2.校验注解

1.在controller上声明@Validated需要对数据进行校验

1
2
3
4
@GetMapping("/args")
public String argsTest(@Validated @RequestBody SysUser sysUser){
    return "success";
}

2.在对应字段Get方法加上参数校验注解,如果不符合验证要求,则会以message的信息为准,返回给前端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SysUser extends BaseEntity {
	...
    
    // @Xss(message = "用户账号不能包含脚本字符")
    @NotBlank(message = "用户账号不能为空")
    @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Email(message = "邮箱格式不正确")
    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
    public String getPhonenumber() {
        return phonenumber;
    }
    
    ...
}

也可以直接放在字段上面声明。

1
2
@Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
private String nickName;

3.异常捕获

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
 * 自定义验证异常
 */
@ExceptionHandler(BindException.class)
public ApiResponse handleBindException(BindException e, HttpServletRequest request)
{
    // log.error(e.getMessage(), e);
    // String message = e.getAllErrors().get(0).getDefaultMessage();
    // return ApiResponse.error(message);
    String requestURI = request.getRequestURI();
    ApiResponse apiResponse = handleBindingResult(e.getBindingResult());
    log.error("请求地址[ {} ], 发生参数校验异常 'BindException' : {}", requestURI , apiResponse.get("msg"));
    return apiResponse;
}

/**
 * 自定义验证异常 参数校验异常处理
 */
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request)
{
    // log.error(e.getMessage(), e);
    // String message = e.getBindingResult().getFieldError().getDefaultMessage();
    // return ApiResponse.error(message);
    String requestURI = request.getRequestURI();
    ApiResponse apiResponse = handleBindingResult(e.getBindingResult());
    log.error("请求地址[ {} ], 发生参数校验异常 'MethodArgumentNotValidException' : {}", requestURI , apiResponse.get("msg"));
    return apiResponse;
}

/**
 * 处理参数校验异常信息
 * @param result
 * @return
 */
private ApiResponse handleBindingResult(BindingResult result) {
    List<String> errorList = new ArrayList<>();
    if (result.hasErrors()) {
        List<FieldError> list = result.getFieldErrors();
        for (FieldError error : list) {
            String message = "<"+ error.getField() + ">校验不通过:" + error.getDefaultMessage();
            errorList.add(message);
        }
    }
    if (errorList.size() == 0) {
        return ApiResponse.error();
    }
    // A0002-参数校验异常
    return ApiResponse.error(result.getObjectName()+ ":" + errorList.toString());
}

自定义注解校验

1、新增Xss注解,设置自定义校验器XssValidator.class

其中的validatedBy属性指定了需要进行校验的策略类集合,这是一个数组。XssValidator.class是自定义的校验器,具体的逻辑由这个校验器来完成。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**
 * 自定义xss校验注解
 * 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER })
@Constraint(validatedBy = { XssValidator.class })
public @interface Xss
{
    String message()

    default "不允许任何脚本运行";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

2、自定义Xss校验器,实现ConstraintValidator接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * 自定义xss校验注解实现
 * 
 */
public class XssValidator implements ConstraintValidator<Xss, String>
{
    private final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />";

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext)
    {
        return !containsHtml(value);
    }

    public boolean containsHtml(String value)
    {
        Pattern pattern = Pattern.compile(HTML_PATTERN);
        Matcher matcher = pattern.matcher(value);
        return matcher.matches();
    }
}

3、实体类使用自定义的@Xss注解

1
2
3
4
5
6
7
@Xss(message = "登录账号不能包含脚本字符")
@NotBlank(message = "登录账号不能为空")
@Size(min = 0, max = 30, message = "登录账号长度不能超过30个字符")
public String getLoginName()
{
	return loginName;
}

此时在去保存会进行验证,如果不符合规则的字符(例如<script>alert(1);</script>)会提示登录账号不能包含脚本字符,代表限制成功。

4、xss过滤器 ==TODO==

防重复提交

@RepeatSubmit

菜单控制

数据库

微服务

参考连接

springcloud教程 – 1.快速搭建入门级demo,看这一篇就够了

创建一个新的工程lab-drive

1.添加依赖

父级pom文件

1
2
3
4
5
<modules>
    <module>lab-admin</module>
    <module>lab-common</module>
    <module>lab-drive</module>    +++++
</modules>

子级pom文件

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
        <scope>test</scope>
    </dependency>

    <!-- SpringBoot Web容器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!--以下是排除的组件-->
        <exclusions>
            <!--排除默认自带的log组件-->
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>


    <!--log4j2日志相关 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>

    <!-- SpringBoot yml配置文件编写智能提示 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- pagehelper 分页插件 -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
    </dependency>

    <!--mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!-- swagger3-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-boot-starter</artifactId>
    </dependency>
    <!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
    <dependency>
        <groupId>io.swagger</groupId>
        <artifactId>swagger-models</artifactId>
        <version>1.6.2</version>
    </dependency>
    <!-- knife4j -->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>

    <!--项目common通用工具类 -->
    <dependency>
        <groupId>org.opengms</groupId>
        <artifactId>lab-common</artifactId>
        <version>1.0.0</version>
    </dependency>

    <!--nacos注册中心-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
        <!--<version>3.1.0</version>-->
    </dependency>


</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.1.1.RELEASE</version>
            <configuration>
                <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
            </configuration>
            <executions>
                <execution>
                    <!--打成可执行的运行包(.jar .war)-->
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
                <failOnMissingWebXml>false</failOnMissingWebXml>
                <warName>${project.artifactId}</warName>
            </configuration>
        </plugin>
    </plugins>
    <finalName>${project.artifactId}</finalName>
</build>

yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 项目相关配置
container:
  # 名称
  name: OpenGMS-Lab-Container
  # 版本
  version: 1.0.0
  # 版权年份
  copyrightYear: 2022
  # 文件路径 示例( Windows配置E:/opengms-lab/uploadPath,Linux配置 /home/opengms-lab/uploadPath)
#  profile: E:/opengms-lab/uploadPath

# 开发环境配置
server:
  # 服务器的HTTP端口,默认为8080(8888是jupyter默认端口)
  port: 8810
  servlet:
    context-path: /container

spring:
  profiles:
    active: dev
  datasource:
    name: lab_datasource
    url: jdbc:mysql://127.0.0.1:3306/opengms_container?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        enabled: false
  application:
    name: lab-container
  # Springboot2.6以后将SpringMVC 默认路径匹配策略从AntPathMatcher 更改为PathPatternParser,导致出错
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

# MyBatis配置
mybatis:
  # 搜索指定包别名
  typeAliasesPackage: org.opengms.**.entity.po
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  # 加载全局的配置文件
  configLocation: classpath:mybatis/mybatis-config.xml


# Swagger配置
swagger:
  # 是否开启swagger
  enabled: true
  # 请求前缀
  pathMapping: /container

#socket配置
socket:
  host: 127.0.0.1
  port: 6001

2.项目配置

拷贝项目基础框架文件及文件夹

entity文件夹中的ApiResponse

constant文件夹

config文件夹中的AppConfig

LabApplication

lombok.config

线程、跨域、全局异常配置

线程配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
@EnableScheduling  //同步
@EnableAsync  //异步
public class ScheduleConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数 核心线程数线程数定义了最小可以同时运行的线程数量
        executor.setCorePoolSize(3);
        // 设置最大线程数 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数
        executor.setMaxPoolSize(5);
        // 设置队列容量 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中
        executor.setQueueCapacity(200);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("AsyncThread-");
        // 设置拒绝策略 当最大池被填满时,此策略为我们提供可伸缩队列
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }

}

跨域配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
public class WebConfig implements WebMvcConfigurer{

    //解决跨域问题
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**"); //允许远端访问的域名
            // .allowedOrigins("http://localhost:8099")
            //允许请求的方法("POST", "GET", "PUT", "OPTIONS", "DELETE")
            // .allowedMethods("*")
            //允许请求头
            // .allowedHeaders("*");
    }

    //登录请求问题
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /**
         * kn4j 在想文档相关的资源  需要放开
         */
        String[]  swaggerPatterns =  {
                "/swagger-resources/**",
                "/webjars/**",
                "/v2/**",
                "/swagger-ui.html/**",
                "/doc.html/**" };
        registry.addInterceptor(authenticationInterceptor())
                .excludePathPatterns(swaggerPatterns)
                .addPathPatterns("/**");    // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}

全局异常配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 业务异常
     */
    @ExceptionHandler(ServiceException.class)
    public ApiResponse handleServiceException(ServiceException e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址[ {} ],发生业务异常.", requestURI, e);
        Integer code = e.getCode();
        return StringUtils.isNotNull(code) ? ApiResponse.error(code, e.getMessage()) : ApiResponse.error(e.getMessage());
    }

    /**
     * 拦截未知的运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public ApiResponse handleRuntimeException(RuntimeException e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址[ {} ],发生未知异常.", requestURI, e);
        return ApiResponse.error(e.getMessage());
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public ApiResponse handleException(Exception e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址[ {} ],发生系统异常.", requestURI, e);
        return ApiResponse.error(e.getMessage());
    }
    
}

日志配置

[跳转](#5. log4j2的配置文件)

整合mybatis

[跳转](# 3. 整合mybatis等初始化配置)

Swagger集成

[Swagger](# Swagger集成)

Knife4j

3.使用nacos

使用springcloud的时候版本要和springboot版本兼容

https://spring.io/projects/spring-cloud#overview

https://blog.csdn.net/qq_38637558/article/details/114448690

springboot2.5.x 高版本整合nacos报错

2022-11-18 18:50:20.449 ERROR 37996 — [ main] o.s.b.SpringApplication : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘configurationPropertiesBeans’ defined in class path resource [org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.class]: Post-processing of merged bean definition failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]

参考链接

https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

安装nacos并启动

image-20221118184945756

1
startup.cmd -m standalone

项目配置nacos

在父工程的pom文件中的<dependencyManagement>中引入SpringCloudAlibaba的依赖:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<properties>
    <spring-boot.version>2.6.7</spring-boot.version>
    <spring-cloud.version>2021.0.4</spring-cloud.version>
    <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
</properties>

<!-- 依赖声明 -->
<dependencyManagement>
    <dependencies>
        <!-- SpringBoot的依赖配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <!--<version>2.5.14</version>-->
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- springCloud -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--nacos注册中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

然后在各微服务的pom文件中引入nacos-discovery依赖:

1
2
3
4
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

各微服务yml配置

1
2
3
4
5
6
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
  application:
    name: lab-drive

注:如果不想使用nacos作为服务注册和发现的话,设置 spring.cloud.nacos.discovery.enabled 为false

4.使用feign

添加pom依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!--feign远程调用-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--httpClient的依赖 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
<!--防止出现找不到hostname的异常-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<!--使用高版本的speingcloud之后,spring想要用loadBalancer替换掉ribbon,启动的时候会有警告-->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.8.8</version>
</dependency>

https://blog.csdn.net/JGMa_TiMo/article/details/121971430

配置yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#feign配置
feign:
  client:
    config:
      default: # default全局的配置
        loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数

添加@EnableFeignClients注解

1
2
3
4
@Configuration
@EnableFeignClients(basePackages = "org.opengms.admin.clients")
public class ApplicationConfig {
}

编写client

1
2
3
4
5
6
7
8
9
@FeignClient("lab-container")
public interface ContainerClient {

    String CONTEXT_PATH = "/container";

    @GetMapping(CONTEXT_PATH + "/container/list/image")
    ApiResponse listImages();

}

不通过注册中心使用feign

设置yml

1
2
3
4
spring:
  cloud:
      discovery:
        enabled: false

client

1
2
3
4
5
6
7
8
9
@FeignClient(name = "lab-container", url = "localhost:8810/container")
public interface ContainerClient {

    String CONTEXT_PATH = "";

    @GetMapping(CONTEXT_PATH + "/container/list/image")
    ApiResponse listImages();

}

name:服务名称 (注:若使用注册中心,此处应该写生产者服务名称) url:生产者的url地址