一,技术介绍
技术选型 | 功能说明 |
---|---|
springboot | 是一种基于 Spring 框架的快速开发应用程序的框架,它的主要作用是简化 Spring 应用程序的配置和开发,同时提供一系列开箱即用的功能和组件,如内置服务器、数据访问、安全、监控等,使开发者可以更加高效地构建和部署应用程序 |
Maven | 快速的引入jar包进行开发,自动构建部署 |
tomcat | web服务器,快速部署发布web 服务。Tomcat是一个开源的Java Servlet容器,它可以运行Java Servlet和JavaServer Pages(JSP)应用程序。作为一个Web服务器,它可以处理HTTP请求和响应,并将它们传递给Java Servlet和JSP应用程序进行处理。 |
Thymeleaf | Thymeleaf是一种Java模板引擎,它可以将HTML、XML、JavaScript等文件转换为可执行的模板。在开发Web应用程序时,通常会使用Tomcat作为Web服务器,而Thymeleaf可以作为模板引擎来生成动态的Web页面。因此,Thymeleaf和Tomcat可以一起使用来构建动态Web应用程序。 |
junit | 单元测试框架 |
mybatis | 将Java对象与关系数据库进行映射,实现数据的持久化操作。mybatis的mapper文件是储存sql语句的一个xml文件,他替代了JDBC在类中写多条语句的问题,简化了步骤。 |
redis | 用作缓存。它的读写速度非常快,每秒可以处理超过10万次读写操作。高并发访问数据时直接走内存,和直接查询数据库相比,redis的高效性、快速性优势明显 |
mysql | 关系型数据库 |
Spring、Spring Boot和Spring Cloud的关系
Spring是一个开源的Java框架,它提供了IoC(控制反转)和AOP(面向切面编程)两个核心特性:
- IoC(控制反转)是指将对象的创建、依赖关系的管理和对象的生命周期交给Spring容器来管理,而不是由程序员手动管理。Spring容器会根据配置文件或注解来创建对象,并将对象之间的依赖关系注入到对象中。这样可以降低代码的耦合度,提高代码的可维护性和可测试性。
- AOP(面向切面编程)是一种编程思想,它将程序中的横切关注点(如日志、事务、安全等)从业务逻辑中分离出来,形成一个独立的模块,称为切面。Spring AOP通过动态代理技术,在运行时将切面织入到目标对象的方法中,从而实现对目标对象的增强。这样可以提高代码的复用性和可维护性,同时也可以降低代码的耦合度。
我们可以这样理解:正是由于 IoC (控制反转,把创建好的对象给Spring进行管理)和 AOP(面向切面编程,不修改源代码的情况下进行功能增加) 这两个强大的功能才有了强大的轻量级开源JavaEE框架 Spring;Spring 生态不断地发展才有了 Spring Boot;Spring Boot 开发、部署的简化,使得 Spring Cloud 微服务治理方案彻底落地。
Spring Boot 在 Spring Cloud 中起到了承上启下的作用:
- Springboot 将原有的 xml 配置,简化为 java 注解
- 使用 IDE 可以很方便的搭建一个 springboot 项目,选择对应的 maven 依赖,简化Spring应用的初始搭建以及开发过程
- springboot 有内置的 tomcat 服务器,可以 jar 形式启动一个服务,可以快速部署发布 web 服务
- springboot 使用 starter 依赖自动完成 bean 配置,解决 bean 之间的冲突,并引入相关的 jar 包
Mybatis和Redis缓存的区别
Mybatis和Redis缓存的区别在于:
- Mybatis缓存是基于内存的,而Redis缓存是基于磁盘的。即Mybatis缓存是在应用程序内部实现的,而Redis缓存是在外部服务器上实现的,这意味着Redis缓存可以在多个应用程序之间共享,而Mybatis缓存只能在单个应用程序实例中使用。
- Mybatis缓存是局部缓存,只能缓存查询结果,而Redis缓存可以缓存任何类型的数据,包括对象、列表、哈希表等。
- Mybatis缓存是默认开启的,但需要手动配置,而Redis缓存需要安装和配置Redis服务器。
- Mybatis缓存是基于时间和空间的限制,而Redis缓存可以设置过期时间和最大内存使用量。
二,项目介绍
本项目是一个基于SpringBoot的用户权限管理系统,主要实现用户的注册、登录、角色管理、权限管理等功能。
数据库设计
表设计 | 作用 |
---|---|
用户表 | 用户表(user)用于存储系统中的用户信息,包括用户ID、用户名、密码、邮箱、手机号等字段。 |
角色表 | 角色表(role)用于存储系统中的角色信息,包括角色ID、角色名称、角色描述等字段 |
权限表 | 权限表(permission)用于存储系统中的权限信息,包括权限ID、权限名称、权限描述等字段。 |
用户角色关联表 | 用户角色关联表(user_role)用于存储系统中用户与角色之间的关系,包括用户ID、角色ID等字段。 |
角色权限关联表 | 角色权限关联表(role_permission)用于存储系统中角色与权限之间的关系,包括角色ID、权限ID等字段。 |
功能模块
用户注册:提供用户注册功能。
用户登录:提供用户登录功能。
角色管理:提供角色的增删改查功能。
权限管理:提供权限的增删改查功能。
用户角色管理:提供用户与角色的关联管理功能。
角色权限管理:提供角色与权限的关联管理功能。
代码结构
SpringBootRedis 工程项目结构如下:
bean - 实体层
controller - Controller 层:http响应接口
dao - 数据操作层 (Data Access Object):访问数据库的接口
service - 业务逻辑层:服务接口及实现
job - 任务管理
task - 具体任务处理
Application - 启动类
resources 资源文件夹
application.properties - 应用配置文件,应用启动会自动读取配置
mabatis文件夹
Mapper.xml - mybatis 关系映射 xml 文件
templates文件夹
xxx.html 页面Thymeleaf模板
三,项目实现
1,配置依赖
pom.xml依赖
demospringboot\pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demospringboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demospringboot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<!-- 添加mybatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- 添加redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 添加mysql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
<scope>runtime</scope>
<!-- MySQL5.x时,请使用5.x的连接器(cmd执行mysql -V确定)
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
-->
</dependency>
<!-- 添加thymeleaf依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 添加swagger依赖 -->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.0.RELEASE</version>
</dependency>
<!-- 添加junit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
application.properties配置
demospringboot\src\main\resources\application.properties
:
# mysql 指定使用的数据库
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mysql5: spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 执行初始化sql
spring.datasource.initialize=true
spring.datasource.schema=classpath:schema.sql
# redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.shutdown-timeout=100ms
# 默认线程池
spring.task.execution.pool.core-size=2
spring.task.execution.pool.max-size=5
spring.task.execution.pool.queue-capacity=10
spring.task.execution.pool.keep-alive=60
spring.task.execution.pool.allow-core-thread-timeout=true
spring.task.execution.shutdown.await-termination=false
spring.task.execution.shutdown.await-termination-period=
spring.task.execution.thread-name-prefix=task-
# mybatis 指定mapper xml映射文件
mybatis.mapper-locations=classpath:mybatis/*.xml
# 打印mybatis的执行sql
# mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# swagger
swagger.title=spring-boot-starter-swagger
swagger.description=Starter for swagger 2.x
swagger.version=1.4.0.RELEASE
swagger.license=Apache License, Version 2.0
swagger.licenseUrl=https://www.apache.org/licenses/LICENSE-2.0.html
swagger.termsOfServiceUrl=https://github.com/xxx/spring-boot-starter-swagger
swagger.contact.name=didi
swagger.contact.url=http://blog.xxx.com
swagger.contact.email=xxx.com
swagger.base-package=com.example.demospringboot
swagger.base-path=/**
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
2,数据库初始化
在前面的application.properties的sql配置中,我们指定了会自动创建mydatabase,并且指定了初始化sql文件:
spring.datasource.initialize=true
spring.datasource.schema=classpath:schema.sql
对应的demospringboot\src\main\resources\schema.sql
主要用来初始化设计的5张表,sql语句如下:
-- 测试表初始化
-- 测试表初始化
drop database if exists mydatabase;
create database if not exists mydatabase character set utf8;
use mydatabase;
drop table if exists user;
drop table if exists role;
drop table if exists permission;
drop table if exists user_role;
drop table if exists role_permission;
drop table if exists jobs;
-- 用户表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(32) NOT NULL COMMENT '用户名',
`password` varchar(64) NOT NULL COMMENT '密码',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
-- 角色表
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`name` varchar(32) NOT NULL COMMENT '角色名称',
`description` varchar(128) DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
-- 权限表
CREATE TABLE `permission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`name` varchar(32) NOT NULL COMMENT '权限名称',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
-- 用户角色关联表
CREATE TABLE `user_role` (
`user_id` int(11) NOT NULL COMMENT '用户ID',
`role_id` int(11) NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`,`role_id`),
KEY `role_id` (`role_id`),
CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
-- 角色权限关联表
CREATE TABLE `role_permission` (
`role_id` int(11) NOT NULL COMMENT '角色ID',
`permission_id` int(11) NOT NULL COMMENT '权限ID',
PRIMARY KEY (`role_id`,`permission_id`),
KEY `permission_id` (`permission_id`),
CONSTRAINT `role_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `role_permission_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';
-- 初始化测试数据
INSERT INTO user VALUES(1,'Jacky','123456');
INSERT INTO user VALUES(2,'Mike','123456');
INSERT INTO user VALUES(3,'Tom','123456');
INSERT INTO user VALUES(4,'Lily','123456');
INSERT INTO role VALUES(1,'admin', 'system administrator');
INSERT INTO role VALUES(2,'user', 'ordinary users');
-- 一个管理员,其他普通用户
INSERT INTO user_role VALUES(1,1);
INSERT INTO user_role VALUES(2,2);
INSERT INTO user_role VALUES(3,2);
INSERT INTO user_role VALUES(4,2);
INSERT INTO permission VALUES(1, 'create');
INSERT INTO permission VALUES(2, 'read');
INSERT INTO permission VALUES(3, 'update');
INSERT INTO permission VALUES(4, 'delete');
-- 管理员有增删改查权限,普通用户只有读权限
INSERT INTO role_permission VALUES(1,1);
INSERT INTO role_permission VALUES(1,2);
INSERT INTO role_permission VALUES(1,3);
INSERT INTO role_permission VALUES(1,4);
INSERT INTO role_permission VALUES(2,2);
--任务表
CREATE TABLE `jobs` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '任务ID',
`sendMail` int(11) NOT NULL COMMENT '发送邮件任务',
`analysisLog` int(11) NOT NULL COMMENT '分析日志任务',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
INSERT INTO jobs VALUES(1,0,1);
这里我们额外创建了一张任务表,sendMail为1表示需要创建任务向用户发邮件, analysisLog为1表示每晚定时分析日志。
然后主类实现CommandLineRunner run接口,执行initDatabase进行初始化:
进行如下调用:
package com.example.demospringboot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.cache.annotation.EnableCaching;
import com.example.demospringboot.workmanager.WorkManager;
import com.example.demospringboot.workmanager.AsyncWorkManager;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import com.spring4all.swagger.EnableSwagger2Doc;
@EnableCaching
@EnableAsync
@EnableSwagger2Doc
@SpringBootApplication
// 需要指定扫描的类,并在配置文件指定mybatis.mapper-locations为对应的xml路径
@MapperScan(value = {"com.example.demospringboot.dao"})
public class DemospringbootApplication implements CommandLineRunner {
@Autowired
private DataSource dataSource;
public static void main(String[] args) {
SpringApplication.run(DemospringbootApplication.class, args);
}
@Override
public void run(String... strings) throws SQLException {
initDatabase();
}
private void initDatabase() throws SQLException {
System.out.println("======== 自动初始化数据库开始 ========");
Resource initData = new ClassPathResource("schema.sql");
Connection connection = null;
try {
connection = dataSource.getConnection();
ScriptUtils.executeSqlScript(connection, initData);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (connection != null) {
connection.close();
}
}
System.out.println("======== 自动初始化数据库结束 ========");
}
}
如上,我们通过DataSource.getConnection()总是从datasource或连接池返回一个新的连接,并通过ScriptUtils.executeSqlScript执行了我们的sql脚本。需要注意如果开发者没有手工释放这连接(显式调用 Connection.close() 方法),则这个连接将永久被占用(处于 active 状态),造成连接泄漏!