完善:完善用户登录 API,优化部分包结构(引入 MyBatis Plus、多数据源、P6Spy、Liquibase 等依赖,详情可见 README 介绍)

This commit is contained in:
Charles7c 2022-12-25 12:35:35 +08:00
parent 00e2b44d0e
commit 78e84e8941
28 changed files with 954 additions and 106 deletions

112
README.md
View File

@ -7,7 +7,7 @@
### 简介
ContiNew-Admin (incubating) 中后台管理框架Continue New Admin持续以最新流行技术栈构建。当前阶段采用的技术栈Spring Boot、Undertow、Sa-Token、JWT、Redis、Redisson、Hutool 等。
ContiNew-Admin (incubating) 中后台管理框架Continue New Admin持续以最新流行技术栈构建。当前阶段采用的技术栈Spring Boot、Undertow、Sa-Token、JWT、MariaDB、MyBatis Plus、Redis、Redisson、Hutool 等。
### 开始
@ -18,7 +18,7 @@ git clone https://github.com/Charles7c/continew-admin.git
# 2.在 IDEIntelliJ IDEA/Eclipse中打开本项目
# 3.修改配置文件中的 Redis 配置信息
# [3.也可以在 IntelliJ IDEA 中直接配置程序启动环境变量REDIS_HOST、REDIS_PORT、REDIS_PWD、REDIS_DB]
# [3.也可以在 IntelliJ IDEA 中直接配置程序启动环境变量(DB_HOST、DB_PORT、DB_USER、DB_PWD、DB_NAMEREDIS_HOST、REDIS_PORT、REDIS_PWD、REDIS_DB]
# 4.启动程序
# 4.1 启动成功:访问 http://localhost:8000/页面输出ContiNew-Admin backend service started successfully.
@ -29,7 +29,7 @@ git clone https://github.com/Charles7c/continew-admin.git
# 5.1.1 服务器安装好 docker 及 docker-compose参考https://blog.charles7c.top/categories/fragments/2022/10/31/CentOS%E5%AE%89%E8%A3%85Docker
# 5.1.2 执行 mvn package -P prod 进行项目打包,将 target 目录下的 continew-admin.jar 放到 /docker/continew-admin/server 目录下
# 5.1.3 将 docker 目录上传到服务器 / 目录下并授权chmod -R 777 /docker
# 5.1.4 修改 docker-compose.yml 中的 Redis 配置、continew-admin-server 配置、Nginx 配置
# 5.1.4 修改 docker-compose.yml 中的 MariaDB 配置、Redis 配置、continew-admin-server 配置、Nginx 配置
# 5.1.5 执行 docker-compose up -d 创建并后台运行所有容器
# 5.2 其他方式部署
```
@ -41,6 +41,13 @@ git clone https://github.com/Charles7c/continew-admin.git
| [Spring Boot](https://spring.io/projects/spring-boot) | 2.7.6 | 简化新 Spring 应用的初始搭建以及开发过程。 |
| [Undertow](https://undertow.io/) | 2.2.20.Final | 采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。 |
| [Sa-Token + JWT](https://sa-token.dev33.cn/) | 1.33.0 | 轻量级 Java 权限认证框架,让鉴权变得简单、优雅。 |
| [MariaDB](https://mariadb.org/) | 10.10.2 | MySQL 的一个分支,主要由开源社区在维护,完全兼容 MySQL包括 API 和命令行,能轻松成为 MySQL 的代替品。 |
| [MyBatis Plus](https://baomidou.com/) | 3.5.2 | MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率。 |
| [dynamic-datasource-spring-boot-starter](https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611) | 3.6.1 | 基于 Spring Boot 的快速集成多数据源的启动器。 |
| Hikari | 4.0.3 | JDBC 连接池,号称 “史上最快连接池”SpringBoot 在 2.0 之后,采用的默认数据库连接池就是 Hikari。 |
| [mysql-connector-j](https://dev.mysql.com/doc/connector-j/8.0/en/) | 8.0.31 | MySQL Java 驱动。 |
| [P6Spy](https://github.com/p6spy/p6spy) | 3.9.1 | SQL 性能分析组件。 |
| [Liquibase](https://github.com/liquibase/liquibase) | 4.9.1 | 用于管理数据库版本,跟踪、管理和应用数据库变化。 |
| [Redis](https://redis.io/) | 6.2.7 | 高性能的 key-value 数据库。 |
| [Redisson](https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D) | 3.19.0 | 不仅仅是一个 Redis Java 客户端,同其他 Redis Java 客户端有着很大的区别,相比之下其他客户端提供的功能还仅仅停留在作为数据库驱动层面上,比如仅针对 Redis 提供连接方式,发送命令和处理返回结果等。而 Redisson 充分的利用了 Redis 键值数据库提供的一系列优势,基于 Java 实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。 |
| Easy Captcha | 1.6.2 | Java 图形验证码,支持 gif、中文、算术等类型可用于 Java Web、JavaSE 等项目。 |
@ -58,52 +65,63 @@ git clone https://github.com/Charles7c/continew-admin.git
```bash
continew-admin # 全局通用项目配置及依赖版本管理
├─ continew-admin-webapi # API 模块(存放 Controller 层代码,打包部署的模块)
│ ├─ src
│ │ ├─ main
│ │ │ ├─ java # 工程源文件代码目录
│ │ │ │ └─ top
│ │ │ │ └─ charles7c
│ │ │ │ ├─ cnadmin
│ │ │ │ │ └─ webapi
│ │ │ │ │ └─ controller
│ │ │ │ │ └─ auth # 认证相关 API
│ │ │ │ └─ ContinewAdminApplication.java # 启动入口
│ │ │ ├─ resources # 工程配置目录
│ └─ src
│ └─ main
│ ├─ java # 工程源文件代码目录
│ │ └─ top
│ │ └─ charles7c
│ │ └─ cnadmin
│ │ ├─ webapi
│ │ │ └─ controller
│ │ │ └─ auth # 认证相关 API
│ │ └─ ContinewAdminApplication.java # 启动入口
│ └─ resources # 工程配置目录
│ └─ db.changelog.v0.0.1 # 数据库脚本文件
├─ continew-admin-system # 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等)
│ ├─ src
│ │ ├─ main
│ │ │ ├─ java # 工程源文件代码目录
│ │ │ │ └─ top
│ │ │ │ └─ charles7c
│ │ │ │ └─ cnadmin
│ │ │ │ └─ auth # 认证相关业务及配置
│ │ │ │ ├─ config # 认证相关配置
│ │ │ │ │ ├─ satoken # Sa-Token 配置
│ │ │ │ │ └─ properties # 认证相关配置属性
│ │ │ │ ├─ model # 认证相关模型
│ │ │ │ │ ├─ entity # 认证相关实体对象
│ │ │ │ │ ├─ request # 认证相关请求对象
│ │ │ │ │ └─ vo # 认证相关 VOView Object
│ │ │ │ └─ service # 认证相关业务
│ │ │ │ └─ impl # 认证相关业务实现
│ └─ src
│ └─ main
│ ├─ java # 工程源文件代码目录
│ │ └─ top
│ │ └─ charles7c
│ │ └─ cnadmin
│ │ ├─ auth # 认证相关业务及配置
│ │ │ ├─ config # 认证相关配置
│ │ │ │ ├─ satoken # Sa-Token 配置
│ │ │ │ └─ properties # 认证相关配置属性
│ │ │ ├─ model # 认证相关模型
│ │ │ │ ├─ request # 认证相关请求对象
│ │ │ │ └─ vo # 认证相关 VOView Object
│ │ │ └─ service # 认证相关业务接口及实现类
│ │ │ └─ impl # 认证相关业务实现类
│ │ └─ system # 系统管理相关业务及配置
│ │ ├─ mapper # 系统管理相关 Mapper
│ │ ├─ model # 系统管理相关模型
│ │ │ └─ entity # 系统管理相关实体对象
│ │ └─ service # 系统管理相关业务接口及实现类
│ │ └─ impl # 系统管理相关业务实现类
│ └─ resources # 工程配置目录
│ └─ mapper # MyBatis Mapper XML 文件目录
├─ continew-admin-common # 公共模块(存放公共工具类,公共配置等)
│ ├─ src
│ │ ├─ main
│ │ │ ├─ java # 工程源文件代码目录
│ │ │ │ └─ top
│ │ │ │ └─ charles7c
│ │ │ │ └─ cnadmin
│ │ │ │ └─ common
│ │ │ │ ├─ config # 公共配置
│ │ │ │ │ ├─ jackson # Jackson 配置
│ │ │ │ │ └─ properties # 公共配置属性
│ │ │ │ ├─ consts # 公共常量
│ │ │ │ ├─ exception # 公共异常
│ │ │ │ ├─ handler # 公共处理器
│ │ │ │ ├─ model # 公共模型
│ │ │ │ │ ├─ entity # 公共实体对象
│ │ │ │ │ └─ vo # 公共 VOView Object
│ │ │ │ └─ util # 公共工具类
│ └─ src
│ └─ main
│ └─ java # 工程源文件代码目录
│ └─ top
│ └─ charles7c
│ └─ cnadmin
│ └─ common
│ ├─ config # 公共配置
│ │ ├─ jackson # Jackson 配置
│ │ ├─ mybatis # MyBatis Plus 配置
│ │ └─ properties # 公共配置属性
│ ├─ consts # 公共常量
│ ├─ exception # 公共异常
│ ├─ handler # 公共处理器
│ ├─ model # 公共模型
│ │ ├─ dto # 公共 DTOData Transfer Object
│ │ ├─ entity # 公共实体对象
│ │ └─ vo # 公共 VOView Object
│ └─ util # 公共工具类
│ └─ helper # 公共 Helper助手
```
### License

View File

@ -84,6 +84,31 @@ limitations under the License.
<artifactId>sa-token-dao-redis-jackson</artifactId>
</dependency>
<!-- ################ 持久层相关 ################ -->
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- Dynamic Datasource基于 Spring Boot 的快速集成多数据源的启动器) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
</dependency>
<!-- MySQL Java 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- P6SpySQL 性能分析组件) -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
</dependency>
<!-- ################ 工具库相关 ################ -->
<!-- Knife4j前身是 swagger-bootstrap-ui集 Swagger2 和 OpenAPI3 为一体的增强解决方案) -->
<dependency>

View File

@ -0,0 +1,130 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.config.mybatis;
import java.util.Date;
import org.apache.ibatis.reflection.MetaObject;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import cn.hutool.core.util.ObjectUtil;
import top.charles7c.cnadmin.common.exception.ServiceException;
import top.charles7c.cnadmin.common.model.entity.BaseEntity;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
/**
* MyBatis Plus 元对象处理器配置插入或修改时自动填充
*
* @author Charles7c
* @since 2022/12/22 19:52
*/
public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
/** 创建人 */
private static final String CREATE_USER = "createUser";
/** 创建时间 */
private static final String CREATE_TIME = "createTime";
/** 修改人 */
private static final String UPDATE_USER = "updateUser";
/** 修改时间 */
private static final String UPDATE_TIME = "updateTime";
/**
* 插入数据时填充
*
* @param metaObject
* 元对象
*/
@Override
public void insertFill(MetaObject metaObject) {
try {
if (ObjectUtil.isNull(metaObject)) {
return;
}
Long createUser = LoginHelper.getUserId();
Date createTime = new Date();
if (metaObject.getOriginalObject() instanceof BaseEntity) {
// 继承了 BaseEntity 的类填充创建信息
BaseEntity baseEntity = (BaseEntity)metaObject.getOriginalObject();
baseEntity.setCreateUser(baseEntity.getCreateUser() != null ? baseEntity.getCreateUser() : createUser);
baseEntity.setCreateTime(baseEntity.getCreateTime() != null ? baseEntity.getCreateTime() : createTime);
baseEntity.setUpdateUser(baseEntity.getUpdateUser() != null ? baseEntity.getUpdateUser() : createUser);
baseEntity.setUpdateTime(baseEntity.getUpdateTime() != null ? baseEntity.getUpdateTime() : createTime);
} else {
// 未继承 BaseEntity 的类根据类中拥有的创建信息进行填充不存在创建信息不进行填充
this.fillFieldValue(metaObject, CREATE_USER, createUser, false);
this.fillFieldValue(metaObject, CREATE_TIME, createTime, false);
this.fillFieldValue(metaObject, UPDATE_USER, createUser, false);
this.fillFieldValue(metaObject, UPDATE_TIME, createTime, false);
}
} catch (Exception e) {
throw new ServiceException("插入数据时自动填充异常:" + e.getMessage());
}
}
/**
* 修改数据时填充
*
* @param metaObject
* 元对象
*/
@Override
public void updateFill(MetaObject metaObject) {
try {
if (ObjectUtil.isNull(metaObject)) {
return;
}
Long updateUser = LoginHelper.getUserId();
Date updateTime = new Date();
if (metaObject.getOriginalObject() instanceof BaseEntity) {
// 继承了 BaseEntity 的类填充修改信息
BaseEntity baseEntity = (BaseEntity)metaObject.getOriginalObject();
baseEntity.setUpdateUser(updateUser);
baseEntity.setUpdateTime(updateTime);
} else {
// 未继承 BaseEntity 的类根据类中拥有的修改信息进行填充不存在修改信息不进行填充
this.fillFieldValue(metaObject, UPDATE_USER, updateUser, true);
this.fillFieldValue(metaObject, UPDATE_TIME, updateTime, true);
}
} catch (Exception e) {
throw new ServiceException("修改数据时自动填充异常:" + e.getMessage());
}
}
/**
* 填充属性值
*
* @param metaObject
* 元数据对象
* @param fieldName
* 要填充的属性名
* @param fillFieldValue
* 要填充的属性值
* @param isOverride
* 如果属性值不为空是否覆盖true 覆盖false 不覆盖
*/
private void fillFieldValue(MetaObject metaObject, String fieldName, Object fillFieldValue, boolean isOverride) {
if (metaObject.hasSetter(fieldName)) {
Object fieldValue = metaObject.getValue(fieldName);
setFieldValByName(fieldName, fieldValue != null && !isOverride ? fieldValue : fillFieldValue, metaObject);
}
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.config.mybatis;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import cn.hutool.core.net.NetUtil;
/**
* MyBatis Plus 配置
*
* @author Charles7c
* @since 2022/12/22 19:51
*/
@Configuration
@MapperScan("${mybatis-plus.mapper-package}")
public class MybatisPlusConfiguration {
/**
* 插件配置
*
* @return /
*/
@Bean
MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor());
return interceptor;
}
/**
* 分页插件配置<a href="https://baomidou.com/pages/97710a/#paginationinnerinterceptor">...</a>
*/
private PaginationInnerInterceptor paginationInnerInterceptor() {
// 对于单一数据库类型来说都建议配置该值避免每次分页都去抓取数据库类型
// PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 溢出总页数后是否进行处理
paginationInnerInterceptor.setOverflow(false);
// 单页分页条数限制
paginationInnerInterceptor.setMaxLimit(-1L);
return paginationInnerInterceptor;
}
/**
* 元对象处理器配置插入或修改时自动填充
*/
@Bean
MetaObjectHandler metaObjectHandler() {
return new MyBatisPlusMetaObjectHandler();
}
/**
* ID 生成器配置仅在主键类型idType配置为 ASSIGN_ID ASSIGN_UUID 时有效使用网卡信息绑定雪花生成器防止集群雪花 ID 重复
*/
@Bean
IdentifierGenerator idGenerator() {
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.exception;
import lombok.NoArgsConstructor;
/**
* 业务异常
*
* @author Charles7c
* @since 2022/12/23 22:55
*/
@NoArgsConstructor
public class ServiceException extends RuntimeException {
public ServiceException(String message) {
super(message);
}
}

View File

@ -19,10 +19,12 @@ package top.charles7c.cnadmin.common.handler;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@ -37,6 +39,8 @@ import cn.hutool.core.util.StrUtil;
import top.charles7c.cnadmin.common.exception.BadRequestException;
import top.charles7c.cnadmin.common.model.vo.R;
import top.charles7c.cnadmin.common.util.ExceptionUtils;
import top.charles7c.cnadmin.common.util.StreamUtils;
/**
* 全局异常处理器
@ -85,7 +89,8 @@ public class GlobalExceptionHandler {
@ExceptionHandler(BindException.class)
public R handleBindException(BindException e, HttpServletRequest request) {
log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, "");
return R.fail(HttpStatus.BAD_REQUEST.value(), message);
}
/**
@ -95,7 +100,8 @@ public class GlobalExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
public R constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, "");
return R.fail(HttpStatus.BAD_REQUEST.value(), message);
}
/**
@ -105,7 +111,8 @@ public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
return R.fail(HttpStatus.BAD_REQUEST.value(), ExceptionUtils
.exToNull(() -> Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage()));
}
/**

View File

@ -14,20 +14,20 @@
* limitations under the License.
*/
package top.charles7c.cnadmin.auth.model.entity;
package top.charles7c.cnadmin.common.model.dto;
import java.io.Serializable;
import lombok.Data;
import top.charles7c.cnadmin.common.model.entity.BaseEntity;
/**
* 用户实体
* 登录用户信息
*
* @author Charles7c
* @since 2022/12/21 20:42
* @since 2022/12/24 13:01
*/
@Data
public class SysUser extends BaseEntity {
public class LoginUser implements Serializable {
private static final long serialVersionUID = 1L;
@ -41,11 +41,6 @@ public class SysUser extends BaseEntity {
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 昵称
*/
@ -57,10 +52,25 @@ public class SysUser extends BaseEntity {
private Integer gender;
/**
* 头像
* 手机号码
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 头像地址
*/
private String avatar;
/**
* 备注
*/
private String notes;
/**
* 状态1启用 2禁用
*/

View File

@ -21,6 +21,9 @@ import java.util.Date;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
/**
* 实体类基类
*
@ -35,20 +38,24 @@ public class BaseEntity implements Serializable {
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
private Long createUser;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 修改人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.util;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import cn.hutool.core.collection.CollUtil;
/**
* Stream 工具类
*
* @author Charles7c
* @since 2022/12/22 19:51
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StreamUtils {
/**
* 将集合中的指定字段使用分隔符拼接成字符串
*
* @param collection
* 集合
* @param function
* 字段方法
* @param delimiter
* 分隔符
* @param <E>
* /
* @return 拼接结果
*/
public static <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) {
if (CollUtil.isEmpty(collection)) {
return StringUtils.EMPTY;
}
return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.common.util.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.stp.StpUtil;
import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.common.util.ExceptionUtils;
/**
* 登录助手
*
* @author Charles7c
* @since 2022/12/24 12:58
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class LoginHelper {
private static final String LOGIN_USER_KEY = "LOGIN_USER";
/**
* 用户登录并缓存用户信息
*
* @param loginUser
* 登录用户信息
*/
public static void login(LoginUser loginUser) {
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
StpUtil.login(loginUser.getUserId());
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
}
/**
* 获取登录用户信息
*
* @return /
*/
public static LoginUser getLoginUser() {
LoginUser loginUser = (LoginUser)SaHolder.getStorage().get(LOGIN_USER_KEY);
if (loginUser != null) {
return loginUser;
}
try {
loginUser = (LoginUser)StpUtil.getTokenSession().get(LOGIN_USER_KEY);
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
} catch (Exception ignored) {
}
return loginUser;
}
/**
* 获取登录用户 ID
*
* @return /
*/
public static Long getUserId() {
return ExceptionUtils.exToNull(() -> getLoginUser().getUserId());
}
/**
* 获取登录用户名
*
* @return /
*/
public static String getUsername() {
return ExceptionUtils.exToNull(() -> getLoginUser().getUsername());
}
/**
* 获取登录用户昵称
*
* @return /
*/
public static String getNickname() {
return ExceptionUtils.exToNull(() -> getLoginUser().getNickname());
}
}

View File

@ -21,13 +21,16 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import top.charles7c.cnadmin.auth.model.entity.SysUser;
import top.charles7c.cnadmin.auth.service.LoginService;
import top.charles7c.cnadmin.auth.service.UserService;
import top.charles7c.cnadmin.common.consts.CommonConstants;
import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.common.util.CheckUtils;
import top.charles7c.cnadmin.common.util.SecureUtils;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
import top.charles7c.cnadmin.system.model.entity.SysUser;
import top.charles7c.cnadmin.system.service.UserService;
/**
* 登录业务实现类
@ -53,7 +56,8 @@ public class LoginServiceImpl implements LoginService {
CheckUtils.exIfEqual(CommonConstants.STATUS_DISABLE, sysUser.getStatus(), "此账号已被禁用,如有疑问,请联系管理员");
// 登录
StpUtil.login(userId);
LoginUser loginUser = BeanUtil.copyProperties(sysUser, LoginUser.class);
LoginHelper.login(loginUser);
// 返回令牌
return StpUtil.getTokenValue();

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import top.charles7c.cnadmin.system.model.entity.SysUser;
/**
* 用户 Mapper
*
* @author Charles7c
* @since 2022/12/22 21:47
*/
public interface UserMapper extends BaseMapper<SysUser> {}

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.system.model.entity;
import java.util.Date;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import top.charles7c.cnadmin.common.model.entity.BaseEntity;
/**
* 用户实体
*
* @author Charles7c
* @since 2022/12/21 20:42
*/
@Data
@TableName("sys_user")
public class SysUser extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 用户 ID
*/
@TableId
private Long userId;
/**
* 用户名
*/
private String username;
/**
* 昵称
*/
private String nickname;
/**
* 密码
*/
private String password;
/**
* 性别0未知 1男 2女
*/
private Integer gender;
/**
* 手机号码
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 头像地址
*/
private String avatar;
/**
* 备注
*/
private String notes;
/**
* 状态1启用 2禁用
*/
private Integer status;
/**
* 最后一次修改密码的时间
*/
private Date pwdResetTime;
}

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
package top.charles7c.cnadmin.auth.service;
package top.charles7c.cnadmin.system.service;
import top.charles7c.cnadmin.auth.model.entity.SysUser;
import top.charles7c.cnadmin.system.model.entity.SysUser;
/**
* 用户业务接口

View File

@ -14,15 +14,17 @@
* limitations under the License.
*/
package top.charles7c.cnadmin.auth.service.impl;
package top.charles7c.cnadmin.system.service.impl;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.charles7c.cnadmin.auth.model.entity.SysUser;
import top.charles7c.cnadmin.auth.service.UserService;
import top.charles7c.cnadmin.common.util.SecureUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import top.charles7c.cnadmin.system.mapper.UserMapper;
import top.charles7c.cnadmin.system.model.entity.SysUser;
import top.charles7c.cnadmin.system.service.UserService;
/**
* 用户业务实现类
@ -31,24 +33,13 @@ import top.charles7c.cnadmin.common.util.SecureUtils;
* @since 2022/12/21 21:49
*/
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
@Override
public SysUser getByUsername(String username) {
if (!"admin".equals(username)) {
return null;
}
SysUser sysUser = new SysUser();
sysUser.setUserId(1L);
sysUser.setUsername("admin");
sysUser.setPassword(SecureUtils.md5Salt("123456", sysUser.getUserId().toString()));
sysUser.setNickname("超级管理员");
sysUser.setGender(1);
sysUser.setStatus(1);
sysUser.setCreateUser(1L);
sysUser.setCreateTime(new Date());
sysUser.setUpdateUser(1L);
sysUser.setUpdateTime(new Date());
return sysUser;
return userMapper.selectOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));
}
}

View File

@ -0,0 +1,4 @@
<?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="top.charles7c.cnadmin.system.mapper.UserMapper">
</mapper>

View File

@ -32,6 +32,12 @@ limitations under the License.
<description>API 模块(存放 Controller 层代码,打包部署的模块)</description>
<dependencies>
<!-- Liquibase用于管理数据库版本跟踪、管理和应用数据库变化 -->
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@ -77,7 +77,7 @@ public class LoginController {
@SaIgnore
@Operation(summary = "用户退出", description = "注销用户的当前登录")
@Parameter(name = "Authorization", description = "令牌", required = true, example = "Bearer xxxxxxxxx",
@Parameter(name = "Authorization", description = "令牌", required = true, example = "Bearer xxxx-xxxx-xxxx-xxxx",
in = ParameterIn.HEADER)
@PostMapping("/logout")
public R logout() {

View File

@ -3,6 +3,56 @@ server:
# HTTP 端口(默认 8080
port: 8000
--- ### 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
## 动态数据源配置可配多主多从m1、s1...、纯粹多库mysql、oracle...、混合配置m1、s1、oracle...
dynamic:
# 是否启用 P6SpySQL 性能分析组件,默认 false该插件有性能损耗不建议生产环境使用
p6spy: true
# 设置默认的数据源或者数据源组(默认 master
primary: master
# 严格匹配数据源true 未匹配到指定数据源时抛异常false 使用默认数据源;默认 false
strict: false
datasource:
# 主库配置(可配多个,构成多主)
master:
url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: ${DB_USER:root}
password: ${DB_PWD:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库配置(可配多个,构成多从)
slave_1:
url: jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username:
password:
lazy: true
driver-class-name: com.mysql.cj.jdbc.Driver
type: ${spring.datasource.type}
hikari:
# 最大连接池数量
max-pool-size: 20
# 最小空闲线程数量
min-idle: 10
# 获取连接超时时间
connection-timeout: 10000
# 校验超时时间
validation-timeout: 5000
# 空闲连接最大存活时间(默认 10 分钟)
idle-timeout: 60000
# 此属性控制池中连接的最长生命周期0 表示无限生命周期(默认 30 分钟)
max-lifetime: 900000
# 连接测试 query配置检测连接是否有效
connection-test-query: SELECT 1
--- ### Liquibase 配置
spring.liquibase:
# 是否启用
enabled: true
# 配置文件路径
change-log: classpath:/db/changelog/db.changelog-master.yaml
--- ### Redis 单机配置
spring:
redis:
@ -23,12 +73,6 @@ spring:
security:
# 排除路径配置
excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
- /webSocket/**
# 接口文档相关资源
- /favicon.ico
- /doc.html

View File

@ -3,6 +3,56 @@ server:
# HTTP 端口(默认 8080
port: 18000
--- ### 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
## 动态数据源配置可配多主多从m1、s1...、纯粹多库mysql、oracle...、混合配置m1、s1、oracle...
dynamic:
# 是否启用 P6SpySQL 性能分析组件,默认 false该插件有性能损耗不建议生产环境使用
p6spy: false
# 设置默认的数据源或者数据源组(默认 master
primary: master
# 严格匹配数据源true 未匹配到指定数据源时抛异常false 使用默认数据源;默认 false
strict: false
datasource:
# 主库配置(可配多个,构成多主)
master:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: ${DB_USER:root}
password: ${DB_PWD:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库配置(可配多个,构成多从)
slave_1:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username:
password:
lazy: true
driver-class-name: com.mysql.cj.jdbc.Driver
type: ${spring.datasource.type}
hikari:
# 最大连接池数量
max-pool-size: 20
# 最小空闲线程数量
min-idle: 10
# 获取连接超时时间
connection-timeout: 10000
# 校验超时时间
validation-timeout: 5000
# 空闲连接最大存活时间(默认 10 分钟)
idle-timeout: 60000
# 此属性控制池中连接的最长生命周期0 表示无限生命周期(默认 30 分钟)
max-lifetime: 900000
# 连接测试 query配置检测连接是否有效
connection-test-query: SELECT 1
--- ### Liquibase 配置
spring.liquibase:
# 是否启用
enabled: true
# 配置文件路径
change-log: classpath:/db/changelog/db.changelog-master.yaml
--- ### Redis 单机配置
spring:
redis:
@ -19,17 +69,6 @@ spring:
# 是否开启 SSL
ssl: false
--- ### 安全配置
security:
# 排除路径配置
excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
- /webSocket/**
--- ### 非对称加密配置(例如:密码加密传输,前端公钥加密,后端私钥解密;在线生成 RSA 密钥对http://web.chacuo.net/netrsakeypair
rsa:
# 私钥

View File

@ -79,6 +79,54 @@ sa-token:
# JWT秘钥
jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
--- ### 安全配置
security:
# 排除路径配置
excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
- /webSocket/**
--- ### MyBatis Plus 配置
mybatis-plus:
# Mapper 接口扫描包配置(该配置为自定义配置,非 MP 配置,不支持多包,如有需要可通过注解配置或提升扫描包层级)
# 该配置目前的唯一使用场景为:@MapperScan("${mybatis-plus.mapper-package}")
mapper-package: top.charles7c.**.mapper
# Mapper XML 文件目录配置
mapper-locations: classpath*:/mapper/**/*Mapper.xml
# 类型别名扫描包配置
type-aliases-package: top.charles7c.**.model
check-config-location: true
configuration:
# 自动驼峰命名规则camel case映射
map-underscore-to-camel-case: true
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
auto-mapping-behavior: PARTIAL
# MyBatis 自动映射时未知列或未知属性处理策
# NONE不做处理 WARNING打印相关警告 FAILING抛出异常和详细信息
auto-mapping-unknown-column-behavior: NONE
# 日志配置
# 默认org.apache.ibatis.logging.slf4j.Slf4jImpl
# 更详细会有性能损耗org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭(可单纯使用 p6spy 分析org.apache.ibatis.logging.nologging.NoLoggingImpl
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
global-config:
banner: true
db-config:
# 主键类型(默认 assign_id 表示自行赋值)
# auto 代表使用数据库自增策略(需要在表中设置好自增约束)
id-type: AUTO
# 逻辑删除字段
logic-delete-field: isDeleted
# 逻辑删除全局值(默认 1表示已删除
logic-delete-value: 1
# 逻辑未删除全局值(默认 0表示未删除
logic-not-delete-value: 0
--- ### 服务器配置
server:
servlet:

View File

@ -0,0 +1,7 @@
databaseChangeLog:
- include:
file: db/changelog/v0.0.1/continew-admin_table.sql
- include:
file: db/changelog/v0.0.1/continew-admin_column.sql
- include:
file: db/changelog/v0.0.1/continew-admin_data.sql

View File

@ -0,0 +1,2 @@
-- liquibase formatted sql

View File

@ -0,0 +1,6 @@
-- liquibase formatted sql
-- changeset Charles7c:1
-- 初始化默认用户admin/123456test/123456
INSERT IGNORE INTO `sys_user` VALUES (1, 'admin', '超级管理员', 'f0df7414507bcb57e07e18555821228a', 1, NULL, 'charles7c@126.com', NULL, NULL, 1, NULL, 1, NOW(), 1, NOW());
INSERT IGNORE INTO `sys_user` VALUES (2, 'test', '测试员', '8e114197e1b33783a00542ad67e80516', 0, NULL, NULL, NULL, NULL, 2, NULL, 1, NOW(), 1, NOW());

View File

@ -0,0 +1,25 @@
-- liquibase formatted sql
-- changeset Charles7c:1
CREATE TABLE IF NOT EXISTS `sys_user` (
`user_id` bigint(20) unsigned AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(255) NOT NULL COMMENT '用户名',
`nickname` varchar(255) DEFAULT NULL COMMENT '昵称',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`gender` tinyint(1) unsigned DEFAULT 0 COMMENT '性别0未知 1男 2女',
`phone` varchar(255) DEFAULT NULL COMMENT '手机号码',
`email` varchar(255) DEFAULT NULL COMMENT '邮箱',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像地址',
`notes` varchar(512) DEFAULT NULL COMMENT '备注',
`status` tinyint(1) unsigned DEFAULT 1 COMMENT '状态1启用 2禁用',
`pwd_reset_time` datetime DEFAULT NULL COMMENT '最后一次修改密码的时间',
`create_user` bigint(20) unsigned NOT NULL COMMENT '创建人',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_user` bigint(20) unsigned NOT NULL COMMENT '修改人',
`update_time` datetime NOT NULL COMMENT '修改时间',
PRIMARY KEY (`user_id`) USING BTREE,
UNIQUE INDEX `uk_username`(`username`) USING BTREE,
UNIQUE INDEX `uk_email`(`email`) USING BTREE,
INDEX `idx_createUser`(`create_user`) USING BTREE,
INDEX `idx_updateUser`(`update_user`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

View File

@ -0,0 +1,30 @@
############################################################################
# P6Spy 配置SQL 性能分析组件) #
############################################################################
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 SQL
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 P6Spy Driver 代理
deregisterdrivers=true
# 取消 JDBC URL 前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# SQL语句打印时间格式
databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否启用慢 SQL 记录
outagedetection=true
# 慢 SQL 记录标准 2 秒
outagedetectioninterval=2
# 是否过滤 Log
filter=true
# 过滤 Log 时所排除的 SQL 关键字,以逗号分隔
exclude=SELECT 1

View File

@ -1,5 +1,21 @@
version: '3'
services:
mariadb:
container_name: mariadb
image: mariadb
restart: always
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 你的root用户密码
# 初始化数据库(后续的初始化 SQL 会在这个库执行)
MYSQL_DATABASE: continew_admin
#MYSQL_USER: 你的数据库用户名
#MYSQL_PASSWORD: 你的数据库密码
ports:
- '3306:3306'
volumes:
- /docker/mysql/:/var/lib/mysql
privileged: true
redis:
container_name: redis
image: redis:6.2.7
@ -20,6 +36,11 @@ services:
restart: always
environment:
TZ: Asia/Shanghai
DB_HOST: 172.17.0.1
DB_PORT: 3306
DB_USER: 你的数据库用户名
DB_PWD: 你的数据库密码
DB_NAME: continew_admin
REDIS_HOST: 172.17.0.1
REDIS_PORT: 6379
REDIS_PWD: 你的 Redis 密码
@ -30,6 +51,7 @@ services:
- /docker/continew-admin/server/logs:/logs
depends_on:
- redis
- mariadb
privileged: true
nginx:
container_name: nginx

29
pom.xml
View File

@ -44,6 +44,11 @@ limitations under the License.
<properties>
<sa-token.version>1.33.0</sa-token.version>
<!-- ### 持久层相关 ### -->
<mybatis-plus.version>3.5.2</mybatis-plus.version>
<dynamic-ds.version>3.6.1</dynamic-ds.version>
<p6spy.version>3.9.1</p6spy.version>
<!-- ### 工具库相关 ### -->
<knife4j.version>4.0.0</knife4j.version>
<redisson.version>3.19.0</redisson.version>
@ -78,7 +83,7 @@ limitations under the License.
<exclusions>
<exclusion>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<artifactId>hutool-jwt</artifactId>
</exclusion>
</exclusions>
</dependency>
@ -90,6 +95,28 @@ limitations under the License.
<version>${sa-token.version}</version>
</dependency>
<!-- ################ 持久层相关 ################ -->
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Dynamic Datasource基于 Spring Boot 的快速集成多数据源的启动器) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${dynamic-ds.version}</version>
</dependency>
<!-- P6SpySQL 性能分析组件) -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>${p6spy.version}</version>
</dependency>
<!-- ################ 工具库相关 ################ -->
<!-- Knife4j前身是 swagger-bootstrap-ui集 Swagger2 和 OpenAPI3 为一体的增强解决方案) -->
<dependency>